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 extern "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 54 namespace impl = tools::fs; 55 #define IMPL_NAME "tools::fs" 56 57 // ------------------------------------------------------------------------ 58 // Auxiliary functions. 59 // ------------------------------------------------------------------------ 60 61 static void cleanup_aux(const impl::path&, dev_t, bool); 62 static void cleanup_aux_dir(const impl::path&, const impl::file_info&, 63 bool); 64 static void do_unmount(const impl::path&); 65 static bool safe_access(const impl::path&, int, int); 66 67 static const int access_f = 1 << 0; 68 static const int access_r = 1 << 1; 69 static const int access_w = 1 << 2; 70 static 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 //! 77 static 78 void 79 eaccess(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 //! 138 static 139 bool 140 safe_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. 163 static const int max_retries = 5; 164 static 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. 171 static 172 void 173 cleanup_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 196 static 197 void 198 cleanup_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; 203 retry_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 245 static 246 void 247 do_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; 256 retry_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 269 static 270 std::string 271 normalize(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 302 impl::path::path(const std::string& s) : 303 m_data(normalize(s)) 304 { 305 } 306 307 impl::path::~path(void) 308 { 309 } 310 311 const char* 312 impl::path::c_str(void) 313 const 314 { 315 return m_data.c_str(); 316 } 317 318 std::string 319 impl::path::str(void) 320 const 321 { 322 return m_data; 323 } 324 325 bool 326 impl::path::is_absolute(void) 327 const 328 { 329 return !m_data.empty() && m_data[0] == '/'; 330 } 331 332 bool 333 impl::path::is_root(void) 334 const 335 { 336 return m_data == "/"; 337 } 338 339 impl::path 340 impl::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 352 std::string 353 impl::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 365 impl::path 366 impl::path::to_absolute(void) 367 const 368 { 369 assert(!is_absolute()); 370 return get_current_dir() / m_data; 371 } 372 373 bool 374 impl::path::operator==(const path& p) 375 const 376 { 377 return m_data == p.m_data; 378 } 379 380 bool 381 impl::path::operator!=(const path& p) 382 const 383 { 384 return m_data != p.m_data; 385 } 386 387 impl::path 388 impl::path::operator/(const std::string& p) 389 const 390 { 391 return path(m_data + "/" + normalize(p)); 392 } 393 394 impl::path 395 impl::path::operator/(const path& p) 396 const 397 { 398 return path(m_data) / p.m_data; 399 } 400 401 bool 402 impl::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 412 const int impl::file_info::blk_type = 1; 413 const int impl::file_info::chr_type = 2; 414 const int impl::file_info::dir_type = 3; 415 const int impl::file_info::fifo_type = 4; 416 const int impl::file_info::lnk_type = 5; 417 const int impl::file_info::reg_type = 6; 418 const int impl::file_info::sock_type = 7; 419 const int impl::file_info::wht_type = 8; 420 421 impl::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 444 impl::file_info::~file_info(void) 445 { 446 } 447 448 dev_t 449 impl::file_info::get_device(void) 450 const 451 { 452 return m_sb.st_dev; 453 } 454 455 ino_t 456 impl::file_info::get_inode(void) 457 const 458 { 459 return m_sb.st_ino; 460 } 461 462 mode_t 463 impl::file_info::get_mode(void) 464 const 465 { 466 return m_sb.st_mode & ~S_IFMT; 467 } 468 469 off_t 470 impl::file_info::get_size(void) 471 const 472 { 473 return m_sb.st_size; 474 } 475 476 int 477 impl::file_info::get_type(void) 478 const 479 { 480 return m_type; 481 } 482 483 bool 484 impl::file_info::is_owner_readable(void) 485 const 486 { 487 return m_sb.st_mode & S_IRUSR; 488 } 489 490 bool 491 impl::file_info::is_owner_writable(void) 492 const 493 { 494 return m_sb.st_mode & S_IWUSR; 495 } 496 497 bool 498 impl::file_info::is_owner_executable(void) 499 const 500 { 501 return m_sb.st_mode & S_IXUSR; 502 } 503 504 bool 505 impl::file_info::is_group_readable(void) 506 const 507 { 508 return m_sb.st_mode & S_IRGRP; 509 } 510 511 bool 512 impl::file_info::is_group_writable(void) 513 const 514 { 515 return m_sb.st_mode & S_IWGRP; 516 } 517 518 bool 519 impl::file_info::is_group_executable(void) 520 const 521 { 522 return m_sb.st_mode & S_IXGRP; 523 } 524 525 bool 526 impl::file_info::is_other_readable(void) 527 const 528 { 529 return m_sb.st_mode & S_IROTH; 530 } 531 532 bool 533 impl::file_info::is_other_writable(void) 534 const 535 { 536 return m_sb.st_mode & S_IWOTH; 537 } 538 539 bool 540 impl::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 550 impl::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 568 std::set< std::string > 569 impl::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 584 impl::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 596 impl::temp_dir::~temp_dir(void) 597 { 598 cleanup(*m_path); 599 } 600 601 const impl::path& 602 impl::temp_dir::get_path(void) 603 const 604 { 605 return *m_path; 606 } 607 608 // ------------------------------------------------------------------------ 609 // Free functions. 610 // ------------------------------------------------------------------------ 611 612 bool 613 impl::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 626 bool 627 impl::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 649 bool 650 impl::is_executable(const path& p) 651 { 652 if (!exists(p)) 653 return false; 654 return safe_access(p, access_x, EACCES); 655 } 656 657 void 658 impl::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 670 void 671 impl::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 686 void 687 impl::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 697 impl::path 698 impl::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 711 void 712 impl::cleanup(const path& p) 713 { 714 impl::file_info fi(p); 715 cleanup_aux(p, fi.get_device(), true); 716 } 717 718 impl::path 719 impl::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