David van Moolenbroek 00b67f09dd Import NetBSD named(8)
Also known as ISC bind.  This import adds utilities such as host(1),
dig(1), and nslookup(1), as well as many other tools and libraries.

Change-Id: I035ca46e64f1965d57019e773f4ff0ef035e4aa3
2017-03-21 22:00:06 +00:00

891 lines
21 KiB
C

/* $NetBSD: fs.c,v 1.3 2014/12/10 04:38:03 christos Exp $ */
/*
* Automated Testing Framework (atf)
*
* Copyright (c) 2007 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/defs.h"
#include "atf-c/error.h"
#include "fs.h"
#include "sanity.h"
#include "text.h"
#include "user.h"
/* ---------------------------------------------------------------------
* Prototypes for auxiliary functions.
* --------------------------------------------------------------------- */
static bool check_umask(const mode_t, const mode_t);
static atf_error_t copy_contents(const atf_fs_path_t *, char **);
static mode_t current_umask(void);
static atf_error_t do_mkdtemp(char *);
static atf_error_t normalize(atf_dynstr_t *, char *);
static atf_error_t normalize_ap(atf_dynstr_t *, const char *, va_list);
static void replace_contents(atf_fs_path_t *, const char *);
static const char *stat_type_to_string(const int);
/* ---------------------------------------------------------------------
* The "invalid_umask" error type.
* --------------------------------------------------------------------- */
struct invalid_umask_error_data {
/* One of atf_fs_stat_*_type. */
int m_type;
/* The original path causing the error. */
/* XXX: Ideally this would be an atf_fs_path_t, but if we create it
* from the error constructor, we cannot delete the path later on.
* Can't remember why atf_error_new does not take a hook for
* deletion. */
char m_path[1024];
/* The umask that caused the error. */
mode_t m_umask;
};
typedef struct invalid_umask_error_data invalid_umask_error_data_t;
static
void
invalid_umask_format(const atf_error_t err, char *buf, size_t buflen)
{
const invalid_umask_error_data_t *data;
PRE(atf_error_is(err, "invalid_umask"));
data = atf_error_data(err);
snprintf(buf, buflen, "Could not create the temporary %s %s because "
"it will not have enough access rights due to the current "
"umask %05o", stat_type_to_string(data->m_type),
data->m_path, (unsigned int)data->m_umask);
}
static
atf_error_t
invalid_umask_error(const atf_fs_path_t *path, const int type,
const mode_t failing_mask)
{
atf_error_t err;
invalid_umask_error_data_t data;
data.m_type = type;
strncpy(data.m_path, atf_fs_path_cstring(path), sizeof(data.m_path));
data.m_path[sizeof(data.m_path) - 1] = '\0';
data.m_umask = failing_mask;
err = atf_error_new("invalid_umask", &data, sizeof(data),
invalid_umask_format);
return err;
}
/* ---------------------------------------------------------------------
* 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
bool
check_umask(const mode_t exp_mode, const mode_t min_mode)
{
const mode_t actual_mode = (~current_umask() & exp_mode);
return (actual_mode & min_mode) == min_mode;
}
static
atf_error_t
copy_contents(const atf_fs_path_t *p, char **buf)
{
atf_error_t err;
char *str;
str = (char *)malloc(atf_dynstr_length(&p->m_data) + 1);
if (str == NULL)
err = atf_no_memory_error();
else {
strcpy(str, atf_dynstr_cstring(&p->m_data));
*buf = str;
err = atf_no_error();
}
return err;
}
static
mode_t
current_umask(void)
{
const mode_t current = umask(0);
(void)umask(current);
return current;
}
static
atf_error_t
do_mkdtemp(char *tmpl)
{
atf_error_t err;
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;
}
static
atf_error_t
do_mkstemp(char *tmpl, int *fdout)
{
atf_error_t err;
PRE(strstr(tmpl, "XXXXXX") != NULL);
*fdout = mkstemp(tmpl);
if (*fdout == -1)
err = atf_libc_error(errno, "Cannot create temporary file "
"with template '%s'", tmpl);
else
err = atf_no_error();
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;
}
static
void
replace_contents(atf_fs_path_t *p, const char *buf)
{
atf_error_t err;
PRE(atf_dynstr_length(&p->m_data) == strlen(buf));
atf_dynstr_clear(&p->m_data);
err = atf_dynstr_append_fmt(&p->m_data, "%s", buf);
INV(!atf_is_error(err));
}
static
const char *
stat_type_to_string(const int type)
{
const char *str;
if (type == atf_fs_stat_blk_type)
str = "block device";
else if (type == atf_fs_stat_chr_type)
str = "character device";
else if (type == atf_fs_stat_dir_type)
str = "directory";
else if (type == atf_fs_stat_fifo_type)
str = "named pipe";
else if (type == atf_fs_stat_lnk_type)
str = "symbolic link";
else if (type == atf_fs_stat_reg_type)
str = "regular file";
else if (type == atf_fs_stat_sock_type)
str = "socket";
else if (type == atf_fs_stat_wht_type)
str = "whiteout";
else {
UNREACHABLE;
str = NULL;
}
return str;
}
/* ---------------------------------------------------------------------
* 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;
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)
{
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);
}
/*
* Getters.
*/
atf_error_t
atf_fs_path_branch_path(const atf_fs_path_t *p, atf_fs_path_t *bp)
{
const size_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
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)
{
size_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);
}
}
return err;
}
void
atf_fs_stat_copy(atf_fs_stat_t *dest, const atf_fs_stat_t *src)
{
dest->m_type = src->m_type;
dest->m_sb = src->m_sb;
}
void
atf_fs_stat_fini(atf_fs_stat_t *st ATF_DEFS_ATTRIBUTE_UNUSED)
{
}
/*
* 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;
}
mode_t
atf_fs_stat_get_mode(const atf_fs_stat_t *st)
{
return st->m_sb.st_mode & ~S_IFMT;
}
off_t
atf_fs_stat_get_size(const atf_fs_stat_t *st)
{
return st->m_sb.st_size;
}
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;
/*
* 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 *buf;
if (!check_umask(S_IRWXU, S_IRWXU)) {
err = invalid_umask_error(p, atf_fs_stat_dir_type, current_umask());
goto out;
}
err = copy_contents(p, &buf);
if (atf_is_error(err))
goto out;
err = do_mkdtemp(buf);
if (atf_is_error(err))
goto out_buf;
replace_contents(p, buf);
INV(!atf_is_error(err));
out_buf:
free(buf);
out:
return err;
}
atf_error_t
atf_fs_mkstemp(atf_fs_path_t *p, int *fdout)
{
atf_error_t err;
char *buf;
int fd;
if (!check_umask(S_IRWXU, S_IRWXU)) {
err = invalid_umask_error(p, atf_fs_stat_reg_type, current_umask());
goto out;
}
err = copy_contents(p, &buf);
if (atf_is_error(err))
goto out;
err = do_mkstemp(buf, &fd);
if (atf_is_error(err))
goto out_buf;
replace_contents(p, buf);
*fdout = fd;
INV(!atf_is_error(err));
out_buf:
free(buf);
out:
return err;
}
atf_error_t
atf_fs_rmdir(const atf_fs_path_t *p)
{
atf_error_t err;
if (rmdir(atf_fs_path_cstring(p))) {
if (errno == EEXIST) {
/* Some operating systems (e.g. OpenSolaris 200906) return
* EEXIST instead of ENOTEMPTY for non-empty directories.
* Homogenize the return value so that callers don't need
* to bother about differences in operating systems. */
errno = ENOTEMPTY;
}
err = atf_libc_error(errno, "Cannot remove directory");
} else
err = atf_no_error();
return err;
}
atf_error_t
atf_fs_unlink(const atf_fs_path_t *p)
{
atf_error_t err;
const char *path;
path = atf_fs_path_cstring(p);
if (unlink(path) != 0)
err = atf_libc_error(errno, "Cannot unlink file: '%s'", path);
else
err = atf_no_error();
return err;
}