/* * Automated Testing Framework (atf) * * Copyright (c) 2007, 2008 The NetBSD Foundation, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #if defined(HAVE_CONFIG_H) #include "bconfig.h" #endif #include <sys/types.h> #include <sys/param.h> #include <sys/mount.h> #include <sys/stat.h> #include <sys/wait.h> #include <dirent.h> #include <errno.h> #include <libgen.h> #include <stdarg.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include "atf-c/fs.h" #include "atf-c/sanity.h" #include "atf-c/text.h" #include "atf-c/user.h" /* --------------------------------------------------------------------- * The "child" error type. * --------------------------------------------------------------------- */ /* XXX The need for this conditional here is extremely ugly. This * exception should belong to another module with more process-related * utilities. */ #if !defined(HAVE_UNMOUNT) struct child_error_data { char m_cmd[4096]; int m_state; }; typedef struct child_error_data child_error_data_t; static void child_format(const atf_error_t err, char *buf, size_t buflen) { const child_error_data_t *data; PRE(atf_error_is(err, "child")); data = atf_error_data(err); snprintf(buf, buflen, "Unknown error while executing \"%s\"; " "exit state was %d", data->m_cmd, data->m_state); } static atf_error_t child_error(const char *cmd, int state) { atf_error_t err; child_error_data_t data; snprintf(data.m_cmd, sizeof(data.m_cmd), "%s", cmd); data.m_state = state; err = atf_error_new("child", &data, sizeof(data), child_format); return err; } #endif /* !defined(HAVE_UNMOUNT) */ /* --------------------------------------------------------------------- * The "unknown_file_type" error type. * --------------------------------------------------------------------- */ struct unknown_type_error_data { const char *m_path; int m_type; }; typedef struct unknown_type_error_data unknown_type_error_data_t; static void unknown_type_format(const atf_error_t err, char *buf, size_t buflen) { const unknown_type_error_data_t *data; PRE(atf_error_is(err, "unknown_type")); data = atf_error_data(err); snprintf(buf, buflen, "Unknown file type %d of %s", data->m_type, data->m_path); } static atf_error_t unknown_type_error(const char *path, int type) { atf_error_t err; unknown_type_error_data_t data; data.m_path = path; data.m_type = type; err = atf_error_new("unknown_type", &data, sizeof(data), unknown_type_format); return err; } /* --------------------------------------------------------------------- * Auxiliary functions. * --------------------------------------------------------------------- */ static atf_error_t cleanup_aux(const atf_fs_path_t *, dev_t, bool); static atf_error_t cleanup_aux_dir(const char *, dev_t, bool); static atf_error_t do_unmount(const atf_fs_path_t *); static atf_error_t normalize(atf_dynstr_t *, char *); static atf_error_t normalize_ap(atf_dynstr_t *, const char *, va_list); /* The erase parameter in this routine is to control nested mount points. * We want to descend into a mount point to unmount anything that is * mounted under it, but we do not want to delete any files while doing * this traversal. In other words, we erase files until we cross the * first mount point, and after that point we only scan and unmount. */ static atf_error_t cleanup_aux(const atf_fs_path_t *p, dev_t parent_device, bool erase) { const char *pstr = atf_fs_path_cstring(p); atf_error_t err; atf_fs_stat_t st; err = atf_fs_stat_init(&st, p); if (atf_is_error(err)) goto out; if (atf_fs_stat_get_type(&st) == atf_fs_stat_dir_type) { err = cleanup_aux_dir(pstr, atf_fs_stat_get_device(&st), atf_fs_stat_get_device(&st) == parent_device); if (atf_is_error(err)) goto out_st; } if (atf_fs_stat_get_device(&st) != parent_device) { err = do_unmount(p); if (atf_is_error(err)) goto out_st; } if (erase) { if (atf_fs_stat_get_type(&st) == atf_fs_stat_dir_type) { if (rmdir(pstr) == -1) err = atf_libc_error(errno, "Cannot remove directory " "%s", pstr); else INV(!atf_is_error(err)); } else { if (unlink(pstr) == -1) err = atf_libc_error(errno, "Cannot remove file %s", pstr); else INV(!atf_is_error(err)); } } out_st: atf_fs_stat_fini(&st); out: return err; } static atf_error_t cleanup_aux_dir(const char *pstr, dev_t this_device, bool erase) { DIR *d; atf_error_t err; struct dirent *de; d = opendir(pstr); if (d == NULL) { err = atf_libc_error(errno, "Cannot open directory %s", pstr); goto out; } err = atf_no_error(); while (!atf_is_error(err) && (de = readdir(d)) != NULL) { atf_fs_path_t p; err = atf_fs_path_init_fmt(&p, "%s/%s", pstr, de->d_name); if (!atf_is_error(err)) { if (strcmp(de->d_name, ".") != 0 && strcmp(de->d_name, "..") != 0) err = cleanup_aux(&p, this_device, erase); atf_fs_path_fini(&p); } } closedir(d); out: return err; } static atf_error_t do_unmount(const atf_fs_path_t *p) { atf_error_t err; atf_fs_path_t pa; const char *pastr; /* At least, FreeBSD's unmount(2) requires the path to be absolute. * Let's make it absolute in all cases just to be safe that this does * not affect other systems. */ if (!atf_fs_path_is_absolute(p)) err = atf_fs_path_to_absolute(p, &pa); else err = atf_fs_path_copy(&pa, p); if (atf_is_error(err)) goto out; pastr = atf_fs_path_cstring(&pa); #if defined(HAVE_UNMOUNT) if (unmount(pastr, 0) == -1) err = atf_libc_error(errno, "Cannot unmount %s", pastr); else err = atf_no_error(); #else { /* We could use umount(2) instead if it was available... but * trying to do so under, e.g. Linux, is a nightmare because we * also have to update /etc/mtab to match what we did. It is * simpler to just leave the system-specific umount(8) tool deal * with it, at least for now. */ char *cmd; err = atf_text_format(&cmd, "umount '%s'", pastr); if (!atf_is_error(err)) { int state = system(cmd); if (state == -1) err = atf_libc_error(errno, "Failed to run \"%s\"", cmd); else if (!WIFEXITED(state) || WEXITSTATUS(state) != EXIT_SUCCESS) err = child_error(cmd, state); free(cmd); } } #endif atf_fs_path_fini(&pa); out: return err; } static atf_error_t normalize(atf_dynstr_t *d, char *p) { const char *ptr; char *last; atf_error_t err; bool first; PRE(strlen(p) > 0); PRE(atf_dynstr_length(d) == 0); if (p[0] == '/') err = atf_dynstr_append_fmt(d, "/"); else err = atf_no_error(); first = true; last = NULL; /* Silence GCC warning. */ ptr = strtok_r(p, "/", &last); while (!atf_is_error(err) && ptr != NULL) { if (strlen(ptr) > 0) { err = atf_dynstr_append_fmt(d, "%s%s", first ? "" : "/", ptr); first = false; } ptr = strtok_r(NULL, "/", &last); } return err; } static atf_error_t normalize_ap(atf_dynstr_t *d, const char *p, va_list ap) { char *str; atf_error_t err; va_list ap2; err = atf_dynstr_init(d); if (atf_is_error(err)) goto out; va_copy(ap2, ap); err = atf_text_format_ap(&str, p, ap2); va_end(ap2); if (atf_is_error(err)) atf_dynstr_fini(d); else { err = normalize(d, str); free(str); } out: return err; } /* --------------------------------------------------------------------- * The "atf_fs_path" type. * --------------------------------------------------------------------- */ /* * Constructors/destructors. */ atf_error_t atf_fs_path_init_ap(atf_fs_path_t *p, const char *fmt, va_list ap) { atf_error_t err; va_list ap2; atf_object_init(&p->m_object); va_copy(ap2, ap); err = normalize_ap(&p->m_data, fmt, ap2); va_end(ap2); return err; } atf_error_t atf_fs_path_init_fmt(atf_fs_path_t *p, const char *fmt, ...) { va_list ap; atf_error_t err; va_start(ap, fmt); err = atf_fs_path_init_ap(p, fmt, ap); va_end(ap); return err; } atf_error_t atf_fs_path_copy(atf_fs_path_t *dest, const atf_fs_path_t *src) { atf_object_copy(&dest->m_object, &src->m_object); return atf_dynstr_copy(&dest->m_data, &src->m_data); } void atf_fs_path_fini(atf_fs_path_t *p) { atf_dynstr_fini(&p->m_data); atf_object_fini(&p->m_object); } /* * Getters. */ atf_error_t atf_fs_path_branch_path(const atf_fs_path_t *p, atf_fs_path_t *bp) { const ssize_t endpos = atf_dynstr_rfind_ch(&p->m_data, '/'); atf_error_t err; if (endpos == atf_dynstr_npos) err = atf_fs_path_init_fmt(bp, "."); else if (endpos == 0) err = atf_fs_path_init_fmt(bp, "/"); else { atf_object_init(&bp->m_object); err = atf_dynstr_init_substr(&bp->m_data, &p->m_data, 0, endpos); } #if defined(HAVE_CONST_DIRNAME) INV(atf_equal_dynstr_cstring(&bp->m_data, dirname(atf_dynstr_cstring(&p->m_data)))); #endif /* defined(HAVE_CONST_DIRNAME) */ return err; } const char * atf_fs_path_cstring(const atf_fs_path_t *p) { return atf_dynstr_cstring(&p->m_data); } atf_error_t atf_fs_path_leaf_name(const atf_fs_path_t *p, atf_dynstr_t *ln) { ssize_t begpos = atf_dynstr_rfind_ch(&p->m_data, '/'); atf_error_t err; if (begpos == atf_dynstr_npos) begpos = 0; else begpos++; err = atf_dynstr_init_substr(ln, &p->m_data, begpos, atf_dynstr_npos); #if defined(HAVE_CONST_BASENAME) INV(atf_equal_dynstr_cstring(ln, basename(atf_dynstr_cstring(&p->m_data)))); #endif /* defined(HAVE_CONST_BASENAME) */ return err; } bool atf_fs_path_is_absolute(const atf_fs_path_t *p) { return atf_dynstr_cstring(&p->m_data)[0] == '/'; } bool atf_fs_path_is_root(const atf_fs_path_t *p) { return atf_equal_dynstr_cstring(&p->m_data, "/"); } /* * Modifiers. */ atf_error_t atf_fs_path_append_ap(atf_fs_path_t *p, const char *fmt, va_list ap) { atf_dynstr_t aux; atf_error_t err; va_list ap2; va_copy(ap2, ap); err = normalize_ap(&aux, fmt, ap2); va_end(ap2); if (!atf_is_error(err)) { const char *auxstr = atf_dynstr_cstring(&aux); const bool needslash = auxstr[0] != '/'; err = atf_dynstr_append_fmt(&p->m_data, "%s%s", needslash ? "/" : "", auxstr); atf_dynstr_fini(&aux); } return err; } atf_error_t atf_fs_path_append_fmt(atf_fs_path_t *p, const char *fmt, ...) { va_list ap; atf_error_t err; va_start(ap, fmt); err = atf_fs_path_append_ap(p, fmt, ap); va_end(ap); return err; } atf_error_t atf_fs_path_append_path(atf_fs_path_t *p, const atf_fs_path_t *p2) { return atf_fs_path_append_fmt(p, "%s", atf_dynstr_cstring(&p2->m_data)); } atf_error_t atf_fs_path_to_absolute(const atf_fs_path_t *p, atf_fs_path_t *pa) { atf_error_t err; PRE(!atf_fs_path_is_absolute(p)); err = atf_fs_getcwd(pa); if (atf_is_error(err)) goto out; err = atf_fs_path_append_path(pa, p); if (atf_is_error(err)) atf_fs_path_fini(pa); out: return err; } /* * Operators. */ bool atf_equal_fs_path_fs_path(const atf_fs_path_t *p1, const atf_fs_path_t *p2) { return atf_equal_dynstr_dynstr(&p1->m_data, &p2->m_data); } /* --------------------------------------------------------------------- * The "atf_fs_path" type. * --------------------------------------------------------------------- */ /* * Constants. */ const int atf_fs_stat_blk_type = 1; const int atf_fs_stat_chr_type = 2; const int atf_fs_stat_dir_type = 3; const int atf_fs_stat_fifo_type = 4; const int atf_fs_stat_lnk_type = 5; const int atf_fs_stat_reg_type = 6; const int atf_fs_stat_sock_type = 7; const int atf_fs_stat_wht_type = 8; /* * Constructors/destructors. */ atf_error_t atf_fs_stat_init(atf_fs_stat_t *st, const atf_fs_path_t *p) { atf_error_t err; const char *pstr = atf_fs_path_cstring(p); if (lstat(pstr, &st->m_sb) == -1) { err = atf_libc_error(errno, "Cannot get information of %s; " "lstat(2) failed", pstr); } else { int type = st->m_sb.st_mode & S_IFMT; err = atf_no_error(); switch (type) { case S_IFBLK: st->m_type = atf_fs_stat_blk_type; break; case S_IFCHR: st->m_type = atf_fs_stat_chr_type; break; case S_IFDIR: st->m_type = atf_fs_stat_dir_type; break; case S_IFIFO: st->m_type = atf_fs_stat_fifo_type; break; case S_IFLNK: st->m_type = atf_fs_stat_lnk_type; break; case S_IFREG: st->m_type = atf_fs_stat_reg_type; break; case S_IFSOCK: st->m_type = atf_fs_stat_sock_type; break; #if defined(S_IFWHT) case S_IFWHT: st->m_type = atf_fs_stat_wht_type; break; #endif default: err = unknown_type_error(pstr, type); } } if (!atf_is_error(err)) atf_object_init(&st->m_object); return err; } void atf_fs_stat_copy(atf_fs_stat_t *dest, const atf_fs_stat_t *src) { atf_object_copy(&dest->m_object, &src->m_object); dest->m_type = src->m_type; dest->m_sb = src->m_sb; } void atf_fs_stat_fini(atf_fs_stat_t *st) { atf_object_fini(&st->m_object); } /* * Getters. */ dev_t atf_fs_stat_get_device(const atf_fs_stat_t *st) { return st->m_sb.st_dev; } ino_t atf_fs_stat_get_inode(const atf_fs_stat_t *st) { return st->m_sb.st_ino; } int atf_fs_stat_get_type(const atf_fs_stat_t *st) { return st->m_type; } bool atf_fs_stat_is_owner_readable(const atf_fs_stat_t *st) { return st->m_sb.st_mode & S_IRUSR; } bool atf_fs_stat_is_owner_writable(const atf_fs_stat_t *st) { return st->m_sb.st_mode & S_IWUSR; } bool atf_fs_stat_is_owner_executable(const atf_fs_stat_t *st) { return st->m_sb.st_mode & S_IXUSR; } bool atf_fs_stat_is_group_readable(const atf_fs_stat_t *st) { return st->m_sb.st_mode & S_IRGRP; } bool atf_fs_stat_is_group_writable(const atf_fs_stat_t *st) { return st->m_sb.st_mode & S_IWGRP; } bool atf_fs_stat_is_group_executable(const atf_fs_stat_t *st) { return st->m_sb.st_mode & S_IXGRP; } bool atf_fs_stat_is_other_readable(const atf_fs_stat_t *st) { return st->m_sb.st_mode & S_IROTH; } bool atf_fs_stat_is_other_writable(const atf_fs_stat_t *st) { return st->m_sb.st_mode & S_IWOTH; } bool atf_fs_stat_is_other_executable(const atf_fs_stat_t *st) { return st->m_sb.st_mode & S_IXOTH; } /* --------------------------------------------------------------------- * Free functions. * --------------------------------------------------------------------- */ const int atf_fs_access_f = 1 << 0; const int atf_fs_access_r = 1 << 1; const int atf_fs_access_w = 1 << 2; const int atf_fs_access_x = 1 << 3; atf_error_t atf_fs_cleanup(const atf_fs_path_t *p) { atf_error_t err; atf_fs_stat_t info; err = atf_fs_stat_init(&info, p); if (atf_is_error(err)) return err; err = cleanup_aux(p, atf_fs_stat_get_device(&info), true); atf_fs_stat_fini(&info); return err; } /* * An implementation of access(2) but using the effective user value * instead of the real one. Also avoids false positives for root when * asking for execute permissions, which appear in SunOS. */ atf_error_t atf_fs_eaccess(const atf_fs_path_t *p, int mode) { atf_error_t err; struct stat st; bool ok; PRE(mode & atf_fs_access_f || mode & atf_fs_access_r || mode & atf_fs_access_w || mode & atf_fs_access_x); if (lstat(atf_fs_path_cstring(p), &st) == -1) { err = atf_libc_error(errno, "Cannot get information from file %s", atf_fs_path_cstring(p)); goto out; } err = atf_no_error(); /* Early return if we are only checking for existence and the file * exists (stat call returned). */ if (mode & atf_fs_access_f) goto out; ok = false; if (atf_user_is_root()) { if (!ok && !(mode & atf_fs_access_x)) { /* Allow root to read/write any file. */ ok = true; } if (!ok && (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) { /* Allow root to execute the file if any of its execution bits * are set. */ ok = true; } } else { if (!ok && (atf_user_euid() == st.st_uid)) { ok = ((mode & atf_fs_access_r) && (st.st_mode & S_IRUSR)) || ((mode & atf_fs_access_w) && (st.st_mode & S_IWUSR)) || ((mode & atf_fs_access_x) && (st.st_mode & S_IXUSR)); } if (!ok && atf_user_is_member_of_group(st.st_gid)) { ok = ((mode & atf_fs_access_r) && (st.st_mode & S_IRGRP)) || ((mode & atf_fs_access_w) && (st.st_mode & S_IWGRP)) || ((mode & atf_fs_access_x) && (st.st_mode & S_IXGRP)); } if (!ok && ((atf_user_euid() != st.st_uid) && !atf_user_is_member_of_group(st.st_gid))) { ok = ((mode & atf_fs_access_r) && (st.st_mode & S_IROTH)) || ((mode & atf_fs_access_w) && (st.st_mode & S_IWOTH)) || ((mode & atf_fs_access_x) && (st.st_mode & S_IXOTH)); } } if (!ok) err = atf_libc_error(EACCES, "Access check failed"); out: return err; } atf_error_t atf_fs_exists(const atf_fs_path_t *p, bool *b) { atf_error_t err; err = atf_fs_eaccess(p, atf_fs_access_f); if (atf_is_error(err)) { if (atf_error_is(err, "libc") && atf_libc_error_code(err) == ENOENT) { atf_error_free(err); err = atf_no_error(); *b = false; } } else *b = true; return err; } atf_error_t atf_fs_getcwd(atf_fs_path_t *p) { atf_error_t err; char *cwd; #if defined(HAVE_GETCWD_DYN) cwd = getcwd(NULL, 0); #else cwd = getcwd(NULL, MAXPATHLEN); #endif if (cwd == NULL) { err = atf_libc_error(errno, "Cannot determine current directory"); goto out; } err = atf_fs_path_init_fmt(p, "%s", cwd); free(cwd); out: return err; } atf_error_t atf_fs_mkdtemp(atf_fs_path_t *p) { atf_error_t err; char *tmpl; tmpl = p->m_data.m_data; /* XXX: Ugly */ PRE(strstr(tmpl, "XXXXXX") != NULL); if (mkdtemp(tmpl) == NULL) err = atf_libc_error(errno, "Cannot create temporary directory " "with template '%s'", tmpl); else err = atf_no_error(); return err; }