1// 2// Automated Testing Framework (atf) 3// 4// Copyright (c) 2007 The NetBSD Foundation, Inc. 5// All rights reserved. 6// 7// Redistribution and use in source and binary forms, with or without 8// modification, are permitted provided that the following conditions 9// are met: 10// 1. Redistributions of source code must retain the above copyright 11// notice, this list of conditions and the following disclaimer. 12// 2. Redistributions in binary form must reproduce the above copyright 13// notice, this list of conditions and the following disclaimer in the 14// documentation and/or other materials provided with the distribution. 15// 16// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND 17// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 18// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 19// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY 21// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 23// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 25// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 26// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 27// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28// 29 30#if defined(HAVE_CONFIG_H) 31#include "bconfig.h" 32#endif 33 34extern "C" { 35#include <sys/types.h> 36#include <sys/param.h> 37#include <sys/mount.h> 38#include <sys/stat.h> 39 40#include <unistd.h> 41} 42 43#include <cerrno> 44#include <cstdlib> 45#include <cstring> 46 47#include "atf-c++/detail/process.hpp" 48#include "atf-c++/detail/sanity.hpp" 49 50#include "fs.hpp" 51#include "user.hpp" 52 53namespace impl = atf::atf_run; 54#define IMPL_NAME "atf::atf_run" 55 56// ------------------------------------------------------------------------ 57// Auxiliary functions. 58// ------------------------------------------------------------------------ 59 60static void cleanup_aux(const atf::fs::path&, dev_t, bool); 61static void cleanup_aux_dir(const atf::fs::path&, const atf::fs::file_info&, 62 bool); 63static void do_unmount(const atf::fs::path&); 64 65// The cleanup routines below are tricky: they are executed immediately after 66// a test case's death, and after we have forcibly killed any stale processes. 67// However, even if the processes are dead, this does not mean that the file 68// system we are scanning is stable. In particular, if the test case has 69// mounted file systems through fuse/puffs, the fact that the processes died 70// does not mean that the file system is truly unmounted. 71// 72// The code below attempts to cope with this by catching errors and either 73// ignoring them or retrying the actions on the same file/directory a few times 74// before giving up. 75static const int max_retries = 5; 76static const int retry_delay_in_seconds = 1; 77 78// The erase parameter in this routine is to control nested mount points. 79// We want to descend into a mount point to unmount anything that is 80// mounted under it, but we do not want to delete any files while doing 81// this traversal. In other words, we erase files until we cross the 82// first mount point, and after that point we only scan and unmount. 83static 84void 85cleanup_aux(const atf::fs::path& p, dev_t parent_device, bool erase) 86{ 87 try { 88 atf::fs::file_info fi(p); 89 90 if (fi.get_type() == atf::fs::file_info::dir_type) 91 cleanup_aux_dir(p, fi, fi.get_device() == parent_device); 92 93 if (fi.get_device() != parent_device) 94 do_unmount(p); 95 96 if (erase) { 97 if (fi.get_type() == atf::fs::file_info::dir_type) 98 atf::fs::rmdir(p); 99 else 100 atf::fs::remove(p); 101 } 102 } catch (const atf::system_error& e) { 103 if (e.code() != ENOENT && e.code() != ENOTDIR) 104 throw e; 105 } 106} 107 108static 109void 110cleanup_aux_dir(const atf::fs::path& p, const atf::fs::file_info& fi, 111 bool erase) 112{ 113 if (erase && ((fi.get_mode() & S_IRWXU) != S_IRWXU)) { 114 int retries = max_retries; 115retry_chmod: 116 if (chmod(p.c_str(), fi.get_mode() | S_IRWXU) == -1) { 117 if (retries > 0) { 118 retries--; 119 ::sleep(retry_delay_in_seconds); 120 goto retry_chmod; 121 } else { 122 throw atf::system_error(IMPL_NAME "::cleanup(" + 123 p.str() + ")", "chmod(2) failed", 124 errno); 125 } 126 } 127 } 128 129 std::set< std::string > subdirs; 130 { 131 bool ok = false; 132 int retries = max_retries; 133 while (!ok) { 134 INV(retries > 0); 135 try { 136 const atf::fs::directory d(p); 137 subdirs = d.names(); 138 ok = true; 139 } catch (const atf::system_error& e) { 140 retries--; 141 if (retries == 0) 142 throw e; 143 ::sleep(retry_delay_in_seconds); 144 } 145 } 146 INV(ok); 147 } 148 149 for (std::set< std::string >::const_iterator iter = subdirs.begin(); 150 iter != subdirs.end(); iter++) { 151 const std::string& name = *iter; 152 if (name != "." && name != "..") 153 cleanup_aux(p / name, fi.get_device(), erase); 154 } 155} 156 157static 158void 159do_unmount(const atf::fs::path& in_path) 160{ 161 // At least, FreeBSD's unmount(2) requires the path to be absolute. 162 // Let's make it absolute in all cases just to be safe that this does 163 // not affect other systems. 164 const atf::fs::path& abs_path = in_path.is_absolute() ? 165 in_path : in_path.to_absolute(); 166 167#if defined(HAVE_UNMOUNT) 168 int retries = max_retries; 169retry_unmount: 170 if (unmount(abs_path.c_str(), 0) == -1) { 171 if (errno == EBUSY && retries > 0) { 172 retries--; 173 ::sleep(retry_delay_in_seconds); 174 goto retry_unmount; 175 } else { 176 throw atf::system_error(IMPL_NAME "::cleanup(" + in_path.str() + 177 ")", "unmount(2) failed", errno); 178 } 179 } 180#else 181 // We could use umount(2) instead if it was available... but 182 // trying to do so under, e.g. Linux, is a nightmare because we 183 // also have to update /etc/mtab to match what we did. It is 184 // satf::fser to just leave the system-specific umount(8) tool deal 185 // with it, at least for now. 186 187 const atf::fs::path prog("umount"); 188 atf::process::argv_array argv("umount", abs_path.c_str(), NULL); 189 190 atf::process::status s = atf::process::exec(prog, argv, 191 atf::process::stream_inherit(), atf::process::stream_inherit()); 192 if (!s.exited() || s.exitstatus() != EXIT_SUCCESS) 193 throw std::runtime_error("Call to unmount failed"); 194#endif 195} 196 197// ------------------------------------------------------------------------ 198// The "temp_dir" class. 199// ------------------------------------------------------------------------ 200 201impl::temp_dir::temp_dir(const atf::fs::path& p) 202{ 203 atf::utils::auto_array< char > buf(new char[p.str().length() + 1]); 204 std::strcpy(buf.get(), p.c_str()); 205 if (::mkdtemp(buf.get()) == NULL) 206 throw system_error(IMPL_NAME "::temp_dir::temp_dir(" + 207 p.str() + ")", "mkdtemp(3) failed", 208 errno); 209 210 m_path.reset(new atf::fs::path(buf.get())); 211} 212 213impl::temp_dir::~temp_dir(void) 214{ 215 cleanup(*m_path); 216} 217 218const atf::fs::path& 219impl::temp_dir::get_path(void) 220 const 221{ 222 return *m_path; 223} 224 225// ------------------------------------------------------------------------ 226// Free functions. 227// ------------------------------------------------------------------------ 228 229atf::fs::path 230impl::change_directory(const atf::fs::path& dir) 231{ 232 atf::fs::path olddir = get_current_dir(); 233 234 if (olddir != dir) { 235 if (::chdir(dir.c_str()) == -1) 236 throw system_error(IMPL_NAME "::chdir(" + dir.str() + ")", 237 "chdir(2) failed", errno); 238 } 239 240 return olddir; 241} 242 243void 244impl::cleanup(const atf::fs::path& p) 245{ 246 atf::fs::file_info fi(p); 247 cleanup_aux(p, fi.get_device(), true); 248} 249 250atf::fs::path 251impl::get_current_dir(void) 252{ 253 std::auto_ptr< char > cwd; 254#if defined(HAVE_GETCWD_DYN) 255 cwd.reset(getcwd(NULL, 0)); 256#else 257 cwd.reset(getcwd(NULL, MAXPATHLEN)); 258#endif 259 if (cwd.get() == NULL) 260 throw atf::system_error(IMPL_NAME "::get_current_dir()", 261 "getcwd() failed", errno); 262 263 return atf::fs::path(cwd.get()); 264} 265