//
// 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.
//
static const int access_f = 1 << 0;
static const int access_r = 1 << 1;
static const int access_w = 1 << 2;
static const int 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.
//!
static
void
eaccess(const tools::fs::path& p, int mode)
{
assert(mode & access_f || mode & access_r ||
mode & access_w || mode & access_x);
struct stat st;
if (lstat(p.c_str(), &st) == -1)
throw tools::system_error(IMPL_NAME "::eaccess",
"Cannot get information from file " +
p.str(), errno);
/* Early return if we are only checking for existence and the file
* exists (stat call returned). */
if (mode & access_f)
return;
bool ok = false;
if (tools::user::is_root()) {
if (!ok && !(mode & 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 && (tools::user::euid() == st.st_uid)) {
ok = ((mode & access_r) && (st.st_mode & S_IRUSR)) ||
((mode & access_w) && (st.st_mode & S_IWUSR)) ||
((mode & access_x) && (st.st_mode & S_IXUSR));
}
if (!ok && tools::user::is_member_of_group(st.st_gid)) {
ok = ((mode & access_r) && (st.st_mode & S_IRGRP)) ||
((mode & access_w) && (st.st_mode & S_IWGRP)) ||
((mode & access_x) && (st.st_mode & S_IXGRP));
}
if (!ok && ((tools::user::euid() != st.st_uid) &&
!tools::user::is_member_of_group(st.st_gid))) {
ok = ((mode & access_r) && (st.st_mode & S_IROTH)) ||
((mode & access_w) && (st.st_mode & S_IWOTH)) ||
((mode & access_x) && (st.st_mode & S_IXOTH));
}
}
if (!ok)
throw tools::system_error(IMPL_NAME "::eaccess", "Access check failed",
EACCES);
}
//!
//! \brief A controlled version of access(2).
//!
//! This function reimplements the standard access(2) system call to
//! safely control its exit status and raise an exception in case of
//! failure.
//!
static
bool
safe_access(const impl::path& p, int mode, int experr)
{
try {
eaccess(p, mode);
return true;
} catch (const tools::system_error& e) {
if (e.code() == experr)
return false;
else
throw e;
}
}
// The cleanup routines below are tricky: they are executed immediately after
// a test case's death, and after we have forcibly killed any stale processes.
// However, even if the processes are dead, this does not mean that the file
// system we are scanning is stable. In particular, if the test case has
// mounted file systems through fuse/puffs, the fact that the processes died
// does not mean that the file system is truly unmounted.
//
// The code below attempts to cope with this by catching errors and either
// ignoring them or retrying the actions on the same file/directory a few times
// before giving up.
static const int max_retries = 5;
static const int retry_delay_in_seconds = 1;
// 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
void
cleanup_aux(const impl::path& p, dev_t parent_device, bool erase)
{
try {
impl::file_info fi(p);
if (fi.get_type() == impl::file_info::dir_type)
cleanup_aux_dir(p, fi, fi.get_device() == parent_device);
if (fi.get_device() != parent_device)
do_unmount(p);
if (erase) {
if (fi.get_type() == impl::file_info::dir_type)
impl::rmdir(p);
else
impl::remove(p);
}
} catch (const tools::system_error& e) {
if (e.code() != ENOENT && e.code() != ENOTDIR)
throw e;
}
}
std::set< std::string > subdirs;
{
bool ok = false;
int retries = max_retries;
while (!ok) {
assert(retries > 0);
try {
const impl::directory d(p);
subdirs = d.names();
ok = true;
} catch (const tools::system_error& e) {
retries--;
if (retries == 0)
throw e;
::sleep(retry_delay_in_seconds);
}
}
assert(ok);
}
for (std::set< std::string >::const_iterator iter = subdirs.begin();
iter != subdirs.end(); iter++) {
const std::string& name = *iter;
if (name != "." && name != "..")
cleanup_aux(p / name, fi.get_device(), erase);
}
}
static
void
do_unmount(const impl::path& in_path)
{
// 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.
const impl::path& abs_path = in_path.is_absolute() ?
in_path : in_path.to_absolute();
// ------------------------------------------------------------------------
// The "file_info" class.
// ------------------------------------------------------------------------
const int impl::file_info::blk_type = 1;
const int impl::file_info::chr_type = 2;
const int impl::file_info::dir_type = 3;
const int impl::file_info::fifo_type = 4;
const int impl::file_info::lnk_type = 5;
const int impl::file_info::reg_type = 6;
const int impl::file_info::sock_type = 7;
const int impl::file_info::wht_type = 8;
impl::file_info::file_info(const path& p)
{
if (lstat(p.c_str(), &m_sb) == -1)
throw system_error(IMPL_NAME "::file_info",
"Cannot get information of " + p.str() + "; " +
"lstat(2) failed", errno);
int type = m_sb.st_mode & S_IFMT;
switch (type) {
case S_IFBLK: m_type = blk_type; break;
case S_IFCHR: m_type = chr_type; break;
case S_IFDIR: m_type = dir_type; break;
case S_IFIFO: m_type = fifo_type; break;
case S_IFLNK: m_type = lnk_type; break;
case S_IFREG: m_type = reg_type; break;
case S_IFSOCK: m_type = sock_type; break;
case S_IFWHT: m_type = wht_type; break;
default:
throw system_error(IMPL_NAME "::file_info", "Unknown file type "
"error", EINVAL);
}
}
// Do not bother to provide a default value for PATH. If it is not
// there something is broken in the user's environment.
if (!tools::env::has("PATH"))
throw std::runtime_error("PATH not defined in the environment");
std::vector< std::string > dirs =
tools::text::split(tools::env::get("PATH"), ":");
bool found = false;
for (std::vector< std::string >::const_iterator iter = dirs.begin();
!found && iter != dirs.end(); iter++) {
const path& dir = path(*iter);
if (is_executable(dir / prog))
found = true;
}
return found;
}
void
impl::rmdir(const path& p)
{
if (::rmdir(p.c_str())) {
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;
}
throw system_error(IMPL_NAME "::rmdir", "Cannot remove directory",
errno);
}
}