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 30extern "C" { 31#include <sys/param.h> 32#include <sys/types.h> 33#include <sys/mount.h> 34#include <sys/stat.h> 35#include <sys/wait.h> 36 37#include <dirent.h> 38#include <libgen.h> 39#include <unistd.h> 40} 41 42#include <cassert> 43#include <cerrno> 44#include <cstdlib> 45#include <cstring> 46 47#include "auto_array.hpp" 48#include "env.hpp" 49#include "exceptions.hpp" 50#include "fs.hpp" 51#include "text.hpp" 52#include "user.hpp" 53 54namespace impl = tools::fs; 55#define IMPL_NAME "tools::fs" 56 57// ------------------------------------------------------------------------ 58// Auxiliary functions. 59// ------------------------------------------------------------------------ 60 61static void cleanup_aux(const impl::path&, dev_t, bool); 62static void cleanup_aux_dir(const impl::path&, const impl::file_info&, 63 bool); 64static void do_unmount(const impl::path&); 65static bool safe_access(const impl::path&, int, int); 66 67static const int access_f = 1 << 0; 68static const int access_r = 1 << 1; 69static const int access_w = 1 << 2; 70static const int access_x = 1 << 3; 71 72//! 73//! An implementation of access(2) but using the effective user value 74//! instead of the real one. Also avoids false positives for root when 75//! asking for execute permissions, which appear in SunOS. 76//! 77static 78void 79eaccess(const tools::fs::path& p, int mode) 80{ 81 assert(mode & access_f || mode & access_r || 82 mode & access_w || mode & access_x); 83 84 struct stat st; 85 if (lstat(p.c_str(), &st) == -1) 86 throw tools::system_error(IMPL_NAME "::eaccess", 87 "Cannot get information from file " + 88 p.str(), errno); 89 90 /* Early return if we are only checking for existence and the file 91 * exists (stat call returned). */ 92 if (mode & access_f) 93 return; 94 95 bool ok = false; 96 if (tools::user::is_root()) { 97 if (!ok && !(mode & access_x)) { 98 /* Allow root to read/write any file. */ 99 ok = true; 100 } 101 102 if (!ok && (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) { 103 /* Allow root to execute the file if any of its execution bits 104 * are set. */ 105 ok = true; 106 } 107 } else { 108 if (!ok && (tools::user::euid() == st.st_uid)) { 109 ok = ((mode & access_r) && (st.st_mode & S_IRUSR)) || 110 ((mode & access_w) && (st.st_mode & S_IWUSR)) || 111 ((mode & access_x) && (st.st_mode & S_IXUSR)); 112 } 113 if (!ok && tools::user::is_member_of_group(st.st_gid)) { 114 ok = ((mode & access_r) && (st.st_mode & S_IRGRP)) || 115 ((mode & access_w) && (st.st_mode & S_IWGRP)) || 116 ((mode & access_x) && (st.st_mode & S_IXGRP)); 117 } 118 if (!ok && ((tools::user::euid() != st.st_uid) && 119 !tools::user::is_member_of_group(st.st_gid))) { 120 ok = ((mode & access_r) && (st.st_mode & S_IROTH)) || 121 ((mode & access_w) && (st.st_mode & S_IWOTH)) || 122 ((mode & access_x) && (st.st_mode & S_IXOTH)); 123 } 124 } 125 126 if (!ok) 127 throw tools::system_error(IMPL_NAME "::eaccess", "Access check failed", 128 EACCES); 129} 130 131//! 132//! \brief A controlled version of access(2). 133//! 134//! This function reimplements the standard access(2) system call to 135//! safely control its exit status and raise an exception in case of 136//! failure. 137//! 138static 139bool 140safe_access(const impl::path& p, int mode, int experr) 141{ 142 try { 143 eaccess(p, mode); 144 return true; 145 } catch (const tools::system_error& e) { 146 if (e.code() == experr) 147 return false; 148 else 149 throw e; 150 } 151} 152 153// The cleanup routines below are tricky: they are executed immediately after 154// a test case's death, and after we have forcibly killed any stale processes. 155// However, even if the processes are dead, this does not mean that the file 156// system we are scanning is stable. In particular, if the test case has 157// mounted file systems through fuse/puffs, the fact that the processes died 158// does not mean that the file system is truly unmounted. 159// 160// The code below attempts to cope with this by catching errors and either 161// ignoring them or retrying the actions on the same file/directory a few times 162// before giving up. 163static const int max_retries = 5; 164static const int retry_delay_in_seconds = 1; 165 166// The erase parameter in this routine is to control nested mount points. 167// We want to descend into a mount point to unmount anything that is 168// mounted under it, but we do not want to delete any files while doing 169// this traversal. In other words, we erase files until we cross the 170// first mount point, and after that point we only scan and unmount. 171static 172void 173cleanup_aux(const impl::path& p, dev_t parent_device, bool erase) 174{ 175 try { 176 impl::file_info fi(p); 177 178 if (fi.get_type() == impl::file_info::dir_type) 179 cleanup_aux_dir(p, fi, fi.get_device() == parent_device); 180 181 if (fi.get_device() != parent_device) 182 do_unmount(p); 183 184 if (erase) { 185 if (fi.get_type() == impl::file_info::dir_type) 186 impl::rmdir(p); 187 else 188 impl::remove(p); 189 } 190 } catch (const tools::system_error& e) { 191 if (e.code() != ENOENT && e.code() != ENOTDIR) 192 throw e; 193 } 194} 195 196static 197void 198cleanup_aux_dir(const impl::path& p, const impl::file_info& fi, 199 bool erase) 200{ 201 if (erase && ((fi.get_mode() & S_IRWXU) != S_IRWXU)) { 202 int retries = max_retries; 203retry_chmod: 204 if (chmod(p.c_str(), fi.get_mode() | S_IRWXU) == -1) { 205 if (retries > 0) { 206 retries--; 207 ::sleep(retry_delay_in_seconds); 208 goto retry_chmod; 209 } else { 210 throw tools::system_error(IMPL_NAME "::cleanup(" + 211 p.str() + ")", "chmod(2) failed", 212 errno); 213 } 214 } 215 } 216 217 std::set< std::string > subdirs; 218 { 219 bool ok = false; 220 int retries = max_retries; 221 while (!ok) { 222 assert(retries > 0); 223 try { 224 const impl::directory d(p); 225 subdirs = d.names(); 226 ok = true; 227 } catch (const tools::system_error& e) { 228 retries--; 229 if (retries == 0) 230 throw e; 231 ::sleep(retry_delay_in_seconds); 232 } 233 } 234 assert(ok); 235 } 236 237 for (std::set< std::string >::const_iterator iter = subdirs.begin(); 238 iter != subdirs.end(); iter++) { 239 const std::string& name = *iter; 240 if (name != "." && name != "..") 241 cleanup_aux(p / name, fi.get_device(), erase); 242 } 243} 244 245static 246void 247do_unmount(const impl::path& in_path) 248{ 249 // At least, FreeBSD's unmount(2) requires the path to be absolute. 250 // Let's make it absolute in all cases just to be safe that this does 251 // not affect other systems. 252 const impl::path& abs_path = in_path.is_absolute() ? 253 in_path : in_path.to_absolute(); 254 255 int retries = max_retries; 256retry_unmount: 257 if (unmount(abs_path.c_str(), 0) == -1) { 258 if (errno == EBUSY && retries > 0) { 259 retries--; 260 ::sleep(retry_delay_in_seconds); 261 goto retry_unmount; 262 } else { 263 throw tools::system_error(IMPL_NAME "::cleanup(" + in_path.str() + 264 ")", "unmount(2) failed", errno); 265 } 266 } 267} 268 269static 270std::string 271normalize(const std::string& in) 272{ 273 assert(!in.empty()); 274 275 std::string out; 276 277 std::string::size_type pos = 0; 278 do { 279 const std::string::size_type next_pos = in.find('/', pos); 280 281 const std::string component = in.substr(pos, next_pos - pos); 282 if (!component.empty()) { 283 if (pos == 0) 284 out += component; 285 else if (component != ".") 286 out += "/" + component; 287 } 288 289 if (next_pos == std::string::npos) 290 pos = next_pos; 291 else 292 pos = next_pos + 1; 293 } while (pos != std::string::npos); 294 295 return out.empty() ? "/" : out; 296} 297 298// ------------------------------------------------------------------------ 299// The "path" class. 300// ------------------------------------------------------------------------ 301 302impl::path::path(const std::string& s) : 303 m_data(normalize(s)) 304{ 305} 306 307impl::path::~path(void) 308{ 309} 310 311const char* 312impl::path::c_str(void) 313 const 314{ 315 return m_data.c_str(); 316} 317 318std::string 319impl::path::str(void) 320 const 321{ 322 return m_data; 323} 324 325bool 326impl::path::is_absolute(void) 327 const 328{ 329 return !m_data.empty() && m_data[0] == '/'; 330} 331 332bool 333impl::path::is_root(void) 334 const 335{ 336 return m_data == "/"; 337} 338 339impl::path 340impl::path::branch_path(void) 341 const 342{ 343 const std::string::size_type endpos = m_data.rfind('/'); 344 if (endpos == std::string::npos) 345 return path("."); 346 else if (endpos == 0) 347 return path("/"); 348 else 349 return path(m_data.substr(0, endpos)); 350} 351 352std::string 353impl::path::leaf_name(void) 354 const 355{ 356 std::string::size_type begpos = m_data.rfind('/'); 357 if (begpos == std::string::npos) 358 begpos = 0; 359 else 360 begpos++; 361 362 return m_data.substr(begpos); 363} 364 365impl::path 366impl::path::to_absolute(void) 367 const 368{ 369 assert(!is_absolute()); 370 return get_current_dir() / m_data; 371} 372 373bool 374impl::path::operator==(const path& p) 375 const 376{ 377 return m_data == p.m_data; 378} 379 380bool 381impl::path::operator!=(const path& p) 382 const 383{ 384 return m_data != p.m_data; 385} 386 387impl::path 388impl::path::operator/(const std::string& p) 389 const 390{ 391 return path(m_data + "/" + normalize(p)); 392} 393 394impl::path 395impl::path::operator/(const path& p) 396 const 397{ 398 return path(m_data) / p.m_data; 399} 400 401bool 402impl::path::operator<(const path& p) 403 const 404{ 405 return std::strcmp(m_data.c_str(), p.m_data.c_str()) < 0; 406} 407 408// ------------------------------------------------------------------------ 409// The "file_info" class. 410// ------------------------------------------------------------------------ 411 412const int impl::file_info::blk_type = 1; 413const int impl::file_info::chr_type = 2; 414const int impl::file_info::dir_type = 3; 415const int impl::file_info::fifo_type = 4; 416const int impl::file_info::lnk_type = 5; 417const int impl::file_info::reg_type = 6; 418const int impl::file_info::sock_type = 7; 419const int impl::file_info::wht_type = 8; 420 421impl::file_info::file_info(const path& p) 422{ 423 if (lstat(p.c_str(), &m_sb) == -1) 424 throw system_error(IMPL_NAME "::file_info", 425 "Cannot get information of " + p.str() + "; " + 426 "lstat(2) failed", errno); 427 428 int type = m_sb.st_mode & S_IFMT; 429 switch (type) { 430 case S_IFBLK: m_type = blk_type; break; 431 case S_IFCHR: m_type = chr_type; break; 432 case S_IFDIR: m_type = dir_type; break; 433 case S_IFIFO: m_type = fifo_type; break; 434 case S_IFLNK: m_type = lnk_type; break; 435 case S_IFREG: m_type = reg_type; break; 436 case S_IFSOCK: m_type = sock_type; break; 437 case S_IFWHT: m_type = wht_type; break; 438 default: 439 throw system_error(IMPL_NAME "::file_info", "Unknown file type " 440 "error", EINVAL); 441 } 442} 443 444impl::file_info::~file_info(void) 445{ 446} 447 448dev_t 449impl::file_info::get_device(void) 450 const 451{ 452 return m_sb.st_dev; 453} 454 455ino_t 456impl::file_info::get_inode(void) 457 const 458{ 459 return m_sb.st_ino; 460} 461 462mode_t 463impl::file_info::get_mode(void) 464 const 465{ 466 return m_sb.st_mode & ~S_IFMT; 467} 468 469off_t 470impl::file_info::get_size(void) 471 const 472{ 473 return m_sb.st_size; 474} 475 476int 477impl::file_info::get_type(void) 478 const 479{ 480 return m_type; 481} 482 483bool 484impl::file_info::is_owner_readable(void) 485 const 486{ 487 return m_sb.st_mode & S_IRUSR; 488} 489 490bool 491impl::file_info::is_owner_writable(void) 492 const 493{ 494 return m_sb.st_mode & S_IWUSR; 495} 496 497bool 498impl::file_info::is_owner_executable(void) 499 const 500{ 501 return m_sb.st_mode & S_IXUSR; 502} 503 504bool 505impl::file_info::is_group_readable(void) 506 const 507{ 508 return m_sb.st_mode & S_IRGRP; 509} 510 511bool 512impl::file_info::is_group_writable(void) 513 const 514{ 515 return m_sb.st_mode & S_IWGRP; 516} 517 518bool 519impl::file_info::is_group_executable(void) 520 const 521{ 522 return m_sb.st_mode & S_IXGRP; 523} 524 525bool 526impl::file_info::is_other_readable(void) 527 const 528{ 529 return m_sb.st_mode & S_IROTH; 530} 531 532bool 533impl::file_info::is_other_writable(void) 534 const 535{ 536 return m_sb.st_mode & S_IWOTH; 537} 538 539bool 540impl::file_info::is_other_executable(void) 541 const 542{ 543 return m_sb.st_mode & S_IXOTH; 544} 545 546// ------------------------------------------------------------------------ 547// The "directory" class. 548// ------------------------------------------------------------------------ 549 550impl::directory::directory(const path& p) 551{ 552 DIR* dp = ::opendir(p.c_str()); 553 if (dp == NULL) 554 throw system_error(IMPL_NAME "::directory::directory(" + 555 p.str() + ")", "opendir(3) failed", errno); 556 557 struct dirent* dep; 558 while ((dep = ::readdir(dp)) != NULL) { 559 path entryp = p / dep->d_name; 560 insert(value_type(dep->d_name, file_info(entryp))); 561 } 562 563 if (::closedir(dp) == -1) 564 throw system_error(IMPL_NAME "::directory::directory(" + 565 p.str() + ")", "closedir(3) failed", errno); 566} 567 568std::set< std::string > 569impl::directory::names(void) 570 const 571{ 572 std::set< std::string > ns; 573 574 for (const_iterator iter = begin(); iter != end(); iter++) 575 ns.insert((*iter).first); 576 577 return ns; 578} 579 580// ------------------------------------------------------------------------ 581// The "temp_dir" class. 582// ------------------------------------------------------------------------ 583 584impl::temp_dir::temp_dir(const path& p) 585{ 586 tools::auto_array< char > buf(new char[p.str().length() + 1]); 587 std::strcpy(buf.get(), p.c_str()); 588 if (::mkdtemp(buf.get()) == NULL) 589 throw tools::system_error(IMPL_NAME "::temp_dir::temp_dir(" + 590 p.str() + ")", "mkdtemp(3) failed", 591 errno); 592 593 m_path.reset(new path(buf.get())); 594} 595 596impl::temp_dir::~temp_dir(void) 597{ 598 cleanup(*m_path); 599} 600 601const impl::path& 602impl::temp_dir::get_path(void) 603 const 604{ 605 return *m_path; 606} 607 608// ------------------------------------------------------------------------ 609// Free functions. 610// ------------------------------------------------------------------------ 611 612bool 613impl::exists(const path& p) 614{ 615 try { 616 eaccess(p, access_f); 617 return true; 618 } catch (const system_error& e) { 619 if (e.code() == ENOENT) 620 return false; 621 else 622 throw; 623 } 624} 625 626bool 627impl::have_prog_in_path(const std::string& prog) 628{ 629 assert(prog.find('/') == std::string::npos); 630 631 // Do not bother to provide a default value for PATH. If it is not 632 // there something is broken in the user's environment. 633 if (!tools::env::has("PATH")) 634 throw std::runtime_error("PATH not defined in the environment"); 635 std::vector< std::string > dirs = 636 tools::text::split(tools::env::get("PATH"), ":"); 637 638 bool found = false; 639 for (std::vector< std::string >::const_iterator iter = dirs.begin(); 640 !found && iter != dirs.end(); iter++) { 641 const path& dir = path(*iter); 642 643 if (is_executable(dir / prog)) 644 found = true; 645 } 646 return found; 647} 648 649bool 650impl::is_executable(const path& p) 651{ 652 if (!exists(p)) 653 return false; 654 return safe_access(p, access_x, EACCES); 655} 656 657void 658impl::remove(const path& p) 659{ 660 if (file_info(p).get_type() == file_info::dir_type) 661 throw tools::system_error(IMPL_NAME "::remove(" + p.str() + ")", 662 "Is a directory", 663 EPERM); 664 if (::unlink(p.c_str()) == -1) 665 throw tools::system_error(IMPL_NAME "::remove(" + p.str() + ")", 666 "unlink(" + p.str() + ") failed", 667 errno); 668} 669 670void 671impl::rmdir(const path& p) 672{ 673 if (::rmdir(p.c_str())) { 674 if (errno == EEXIST) { 675 /* Some operating systems (e.g. OpenSolaris 200906) return 676 * EEXIST instead of ENOTEMPTY for non-empty directories. 677 * Homogenize the return value so that callers don't need 678 * to bother about differences in operating systems. */ 679 errno = ENOTEMPTY; 680 } 681 throw system_error(IMPL_NAME "::rmdir", "Cannot remove directory", 682 errno); 683 } 684} 685 686void 687impl::change_ownership(const path& p, const std::pair < int, int >& user) 688{ 689 if (::chown(p.c_str(), user.first, user.second) == -1) { 690 std::stringstream ss; 691 ss << IMPL_NAME "::chown(" << p.str() << ", " << user.first << ", " 692 << user.second << ")"; 693 throw tools::system_error(ss.str(), "chown(2) failed", errno); 694 } 695} 696 697impl::path 698impl::change_directory(const path& dir) 699{ 700 path olddir = get_current_dir(); 701 702 if (olddir != dir) { 703 if (::chdir(dir.c_str()) == -1) 704 throw tools::system_error(IMPL_NAME "::chdir(" + dir.str() + ")", 705 "chdir(2) failed", errno); 706 } 707 708 return olddir; 709} 710 711void 712impl::cleanup(const path& p) 713{ 714 impl::file_info fi(p); 715 cleanup_aux(p, fi.get_device(), true); 716} 717 718impl::path 719impl::get_current_dir(void) 720{ 721 char *cwd = getcwd(NULL, 0); 722 if (cwd == NULL) 723 throw tools::system_error(IMPL_NAME "::get_current_dir()", 724 "getcwd() failed", errno); 725 726 try { 727 impl::path p(cwd); 728 free(cwd); 729 return p; 730 } catch(...) { 731 free(cwd); 732 throw; 733 } 734} 735