diff options
| author | Marcel Moolenaar <marcel@FreeBSD.org> | 2012-09-04 23:07:32 +0000 | 
|---|---|---|
| committer | Marcel Moolenaar <marcel@FreeBSD.org> | 2012-09-04 23:07:32 +0000 | 
| commit | 679bf1899d7d81eaa5b2e95cba72d5db6f7491a3 (patch) | |
| tree | 748bcc46e1493df6fa88441f5e3783a5e2266e13 /atf-run/fs.cpp | |
Diffstat (limited to 'atf-run/fs.cpp')
| -rw-r--r-- | atf-run/fs.cpp | 264 | 
1 files changed, 264 insertions, 0 deletions
| diff --git a/atf-run/fs.cpp b/atf-run/fs.cpp new file mode 100644 index 000000000000..b8931e588730 --- /dev/null +++ b/atf-run/fs.cpp @@ -0,0 +1,264 @@ +// +// 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 + +extern "C" { +#include <sys/types.h> +#include <sys/param.h> +#include <sys/mount.h> +#include <sys/stat.h> + +#include <unistd.h> +} + +#include <cerrno> +#include <cstdlib> +#include <cstring> + +#include "atf-c++/detail/process.hpp" +#include "atf-c++/detail/sanity.hpp" + +#include "fs.hpp" +#include "user.hpp" + +namespace impl = atf::atf_run; +#define IMPL_NAME "atf::atf_run" + +// ------------------------------------------------------------------------ +// Auxiliary functions. +// ------------------------------------------------------------------------ + +static void cleanup_aux(const atf::fs::path&, dev_t, bool); +static void cleanup_aux_dir(const atf::fs::path&, const atf::fs::file_info&, +                            bool); +static void do_unmount(const atf::fs::path&); + +// 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 atf::fs::path& p, dev_t parent_device, bool erase) +{ +    try { +        atf::fs::file_info fi(p); + +        if (fi.get_type() == atf::fs::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() == atf::fs::file_info::dir_type) +                atf::fs::rmdir(p); +            else +                atf::fs::remove(p); +        } +    } catch (const atf::system_error& e) { +        if (e.code() != ENOENT && e.code() != ENOTDIR) +            throw e; +    } +} + +static +void +cleanup_aux_dir(const atf::fs::path& p, const atf::fs::file_info& fi, +                bool erase) +{ +    if (erase && ((fi.get_mode() & S_IRWXU) != S_IRWXU)) { +        int retries = max_retries; +retry_chmod: +        if (chmod(p.c_str(), fi.get_mode() | S_IRWXU) == -1) { +            if (retries > 0) { +                retries--; +                ::sleep(retry_delay_in_seconds); +                goto retry_chmod; +            } else { +                throw atf::system_error(IMPL_NAME "::cleanup(" + +                                        p.str() + ")", "chmod(2) failed", +                                        errno); +            } +        } +    } + +    std::set< std::string > subdirs; +    { +        bool ok = false; +        int retries = max_retries; +        while (!ok) { +            INV(retries > 0); +            try { +                const atf::fs::directory d(p); +                subdirs = d.names(); +                ok = true; +            } catch (const atf::system_error& e) { +                retries--; +                if (retries == 0) +                    throw e; +                ::sleep(retry_delay_in_seconds); +            } +        } +        INV(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 atf::fs::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 atf::fs::path& abs_path = in_path.is_absolute() ? +        in_path : in_path.to_absolute(); + +#if defined(HAVE_UNMOUNT) +    int retries = max_retries; +retry_unmount: +    if (unmount(abs_path.c_str(), 0) == -1) { +        if (errno == EBUSY && retries > 0) { +            retries--; +            ::sleep(retry_delay_in_seconds); +            goto retry_unmount; +        } else { +            throw atf::system_error(IMPL_NAME "::cleanup(" + in_path.str() + +                                    ")", "unmount(2) failed", errno); +        } +    } +#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 +    // satf::fser to just leave the system-specific umount(8) tool deal +    // with it, at least for now. + +    const atf::fs::path prog("umount"); +    atf::process::argv_array argv("umount", abs_path.c_str(), NULL); + +    atf::process::status s = atf::process::exec(prog, argv, +        atf::process::stream_inherit(), atf::process::stream_inherit()); +    if (!s.exited() || s.exitstatus() != EXIT_SUCCESS) +        throw std::runtime_error("Call to unmount failed"); +#endif +} + +// ------------------------------------------------------------------------ +// The "temp_dir" class. +// ------------------------------------------------------------------------ + +impl::temp_dir::temp_dir(const atf::fs::path& p) +{ +    atf::utils::auto_array< char > buf(new char[p.str().length() + 1]); +    std::strcpy(buf.get(), p.c_str()); +    if (::mkdtemp(buf.get()) == NULL) +        throw system_error(IMPL_NAME "::temp_dir::temp_dir(" + +                           p.str() + ")", "mkdtemp(3) failed", +                           errno); + +    m_path.reset(new atf::fs::path(buf.get())); +} + +impl::temp_dir::~temp_dir(void) +{ +    cleanup(*m_path); +} + +const atf::fs::path& +impl::temp_dir::get_path(void) +    const +{ +    return *m_path; +} + +// ------------------------------------------------------------------------ +// Free functions. +// ------------------------------------------------------------------------ + +atf::fs::path +impl::change_directory(const atf::fs::path& dir) +{ +    atf::fs::path olddir = get_current_dir(); + +    if (olddir != dir) { +        if (::chdir(dir.c_str()) == -1) +            throw system_error(IMPL_NAME "::chdir(" + dir.str() + ")", +                               "chdir(2) failed", errno); +    } + +    return olddir; +} + +void +impl::cleanup(const atf::fs::path& p) +{ +    atf::fs::file_info fi(p); +    cleanup_aux(p, fi.get_device(), true); +} + +atf::fs::path +impl::get_current_dir(void) +{ +    std::auto_ptr< char > cwd; +#if defined(HAVE_GETCWD_DYN) +    cwd.reset(getcwd(NULL, 0)); +#else +    cwd.reset(getcwd(NULL, MAXPATHLEN)); +#endif +    if (cwd.get() == NULL) +        throw atf::system_error(IMPL_NAME "::get_current_dir()", +                                "getcwd() failed", errno); + +    return atf::fs::path(cwd.get()); +} | 
