1 // Copyright 2010 The Kyua Authors. 2 // All rights reserved. 3 // 4 // Redistribution and use in source and binary forms, with or without 5 // modification, are permitted provided that the following conditions are 6 // met: 7 // 8 // * Redistributions of source code must retain the above copyright 9 // notice, this list of conditions and the following disclaimer. 10 // * Redistributions in binary form must reproduce the above copyright 11 // notice, this list of conditions and the following disclaimer in the 12 // documentation and/or other materials provided with the distribution. 13 // * Neither the name of Google Inc. nor the names of its contributors 14 // may be used to endorse or promote products derived from this software 15 // without specific prior written permission. 16 // 17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 29 #include "utils/fs/operations.hpp" 30 31 #if defined(HAVE_CONFIG_H) 32 # include "config.h" 33 #endif 34 35 extern "C" { 36 #include <sys/param.h> 37 #if defined(HAVE_SYS_MOUNT_H) 38 # include <sys/mount.h> 39 #endif 40 #include <sys/stat.h> 41 #if defined(HAVE_SYS_STATVFS_H) && defined(HAVE_STATVFS) 42 # include <sys/statvfs.h> 43 #endif 44 #if defined(HAVE_SYS_VFS_H) 45 # include <sys/vfs.h> 46 #endif 47 #include <sys/wait.h> 48 49 #include <unistd.h> 50 } 51 52 #include <cerrno> 53 #include <cstdlib> 54 #include <cstring> 55 #include <fstream> 56 #include <iostream> 57 #include <sstream> 58 #include <string> 59 60 #include "utils/auto_array.ipp" 61 #include "utils/defs.hpp" 62 #include "utils/env.hpp" 63 #include "utils/format/macros.hpp" 64 #include "utils/fs/directory.hpp" 65 #include "utils/fs/exceptions.hpp" 66 #include "utils/fs/path.hpp" 67 #include "utils/logging/macros.hpp" 68 #include "utils/optional.ipp" 69 #include "utils/sanity.hpp" 70 #include "utils/units.hpp" 71 72 namespace fs = utils::fs; 73 namespace units = utils::units; 74 75 using utils::optional; 76 77 78 namespace { 79 80 81 /// Operating systems recognized by the code below. 82 enum os_type { 83 os_unsupported = 0, 84 os_freebsd, 85 os_linux, 86 os_netbsd, 87 os_sunos, 88 }; 89 90 91 /// The current operating system. 92 static enum os_type current_os = 93 #if defined(__FreeBSD__) 94 os_freebsd 95 #elif defined(__linux__) 96 os_linux 97 #elif defined(__NetBSD__) 98 os_netbsd 99 #elif defined(__SunOS__) 100 os_sunos 101 #else 102 os_unsupported 103 #endif 104 ; 105 106 107 /// Specifies if a real unmount(2) is available. 108 /// 109 /// We use this as a constant instead of a macro so that we can compile both 110 /// versions of the unmount code unconditionally. This is a way to prevent 111 /// compilation bugs going unnoticed for long. 112 static const bool have_unmount2 = 113 #if defined(HAVE_UNMOUNT) 114 true; 115 #else 116 false; 117 #endif 118 119 120 #if !defined(UMOUNT) 121 /// Fake replacement value to the path to umount(8). 122 # define UMOUNT "do-not-use-this-value" 123 #else 124 # if defined(HAVE_UNMOUNT) 125 # error "umount(8) detected when unmount(2) is also available" 126 # endif 127 #endif 128 129 130 #if !defined(HAVE_UNMOUNT) 131 /// Fake unmount(2) function for systems without it. 132 /// 133 /// This is only provided to allow our code to compile in all platforms 134 /// regardless of whether they actually have an unmount(2) or not. 135 /// 136 /// \return -1 to indicate error, although this should never happen. 137 static int 138 unmount(const char* /* path */, 139 const int /* flags */) 140 { 141 PRE(false); 142 return -1; 143 } 144 #endif 145 146 147 /// Error code returned by subprocess to indicate a controlled failure. 148 const int exit_known_error = 123; 149 150 151 static void run_mount_tmpfs(const fs::path&, const uint64_t) UTILS_NORETURN; 152 153 154 /// Executes 'mount -t tmpfs' (or a similar variant). 155 /// 156 /// This function must be called from a subprocess as it never returns. 157 /// 158 /// \param mount_point Location on which to mount a tmpfs. 159 /// \param size The size of the tmpfs to mount. If 0, use unlimited. 160 static void 161 run_mount_tmpfs(const fs::path& mount_point, const uint64_t size) 162 { 163 const char* mount_args[16]; 164 std::string size_arg; 165 166 std::size_t last = 0; 167 switch (current_os) { 168 case os_freebsd: 169 mount_args[last++] = "mount"; 170 mount_args[last++] = "-ttmpfs"; 171 if (size > 0) { 172 size_arg = F("-osize=%s") % size; 173 mount_args[last++] = size_arg.c_str(); 174 } 175 mount_args[last++] = "tmpfs"; 176 mount_args[last++] = mount_point.c_str(); 177 break; 178 179 case os_linux: 180 mount_args[last++] = "mount"; 181 mount_args[last++] = "-ttmpfs"; 182 if (size > 0) { 183 size_arg = F("-osize=%s") % size; 184 mount_args[last++] = size_arg.c_str(); 185 } 186 mount_args[last++] = "tmpfs"; 187 mount_args[last++] = mount_point.c_str(); 188 break; 189 190 case os_netbsd: 191 mount_args[last++] = "mount"; 192 mount_args[last++] = "-ttmpfs"; 193 if (size > 0) { 194 size_arg = F("-o-s%s") % size; 195 mount_args[last++] = size_arg.c_str(); 196 } 197 mount_args[last++] = "tmpfs"; 198 mount_args[last++] = mount_point.c_str(); 199 break; 200 201 case os_sunos: 202 mount_args[last++] = "mount"; 203 mount_args[last++] = "-Ftmpfs"; 204 if (size > 0) { 205 size_arg = F("-o-s%s") % size; 206 mount_args[last++] = size_arg.c_str(); 207 } 208 mount_args[last++] = "tmpfs"; 209 mount_args[last++] = mount_point.c_str(); 210 break; 211 212 default: 213 std::cerr << "Don't know how to mount a temporary file system in this " 214 "host operating system\n"; 215 std::exit(exit_known_error); 216 } 217 mount_args[last] = NULL; 218 219 const char** arg; 220 std::cout << "Mounting tmpfs onto " << mount_point << " with:"; 221 for (arg = &mount_args[0]; *arg != NULL; arg++) 222 std::cout << " " << *arg; 223 std::cout << "\n"; 224 225 const int ret = ::execvp(mount_args[0], 226 UTILS_UNCONST(char* const, mount_args)); 227 INV(ret == -1); 228 std::cerr << "Failed to exec " << mount_args[0] << "\n"; 229 std::exit(EXIT_FAILURE); 230 } 231 232 233 /// Unmounts a file system using unmount(2). 234 /// 235 /// \pre unmount(2) must be available; i.e. have_unmount2 must be true. 236 /// 237 /// \param mount_point The file system to unmount. 238 /// 239 /// \throw fs::system_error If the call to unmount(2) fails. 240 static void 241 unmount_with_unmount2(const fs::path& mount_point) 242 { 243 PRE(have_unmount2); 244 245 if (::unmount(mount_point.c_str(), 0) == -1) { 246 const int original_errno = errno; 247 throw fs::system_error(F("unmount(%s) failed") % mount_point, 248 original_errno); 249 } 250 } 251 252 253 /// Unmounts a file system using umount(8). 254 /// 255 /// \pre umount(2) must not be available; i.e. have_unmount2 must be false. 256 /// 257 /// \param mount_point The file system to unmount. 258 /// 259 /// \throw fs::error If the execution of umount(8) fails. 260 static void 261 unmount_with_umount8(const fs::path& mount_point) 262 { 263 PRE(!have_unmount2); 264 265 const pid_t pid = ::fork(); 266 if (pid == -1) { 267 const int original_errno = errno; 268 throw fs::system_error("Cannot fork to execute unmount tool", 269 original_errno); 270 } else if (pid == 0) { 271 const int ret = ::execlp(UMOUNT, "umount", mount_point.c_str(), NULL); 272 INV(ret == -1); 273 std::cerr << "Failed to exec " UMOUNT "\n"; 274 std::exit(EXIT_FAILURE); 275 } 276 277 int status; 278 retry: 279 if (::waitpid(pid, &status, 0) == -1) { 280 const int original_errno = errno; 281 if (errno == EINTR) 282 goto retry; 283 throw fs::system_error("Failed to wait for unmount subprocess", 284 original_errno); 285 } 286 287 if (WIFEXITED(status)) { 288 if (WEXITSTATUS(status) == EXIT_SUCCESS) 289 return; 290 else 291 throw fs::error(F("Failed to unmount %s; returned exit code %s") 292 % mount_point % WEXITSTATUS(status)); 293 } else 294 throw fs::error(F("Failed to unmount %s; unmount tool received signal") 295 % mount_point); 296 } 297 298 299 /// Stats a file, without following links. 300 /// 301 /// \param path The file to stat. 302 /// 303 /// \return The stat structure on success. 304 /// 305 /// \throw system_error An error on failure. 306 static struct ::stat 307 safe_stat(const fs::path& path) 308 { 309 struct ::stat sb; 310 if (::lstat(path.c_str(), &sb) == -1) { 311 const int original_errno = errno; 312 throw fs::system_error(F("Cannot get information about %s") % path, 313 original_errno); 314 } 315 return sb; 316 } 317 318 319 } // anonymous namespace 320 321 322 /// Copies a file. 323 /// 324 /// \param source The file to copy. 325 /// \param target The destination of the new copy; must be a file name, not a 326 /// directory. 327 /// 328 /// \throw error If there is a problem copying the file. 329 void 330 fs::copy(const fs::path& source, const fs::path& target) 331 { 332 std::ifstream input(source.c_str()); 333 if (!input) 334 throw error(F("Cannot open copy source %s") % source); 335 336 std::ofstream output(target.c_str()); 337 if (!output) 338 throw error(F("Cannot create copy target %s") % target); 339 340 char buffer[1024]; 341 while (input.good()) { 342 input.read(buffer, sizeof(buffer)); 343 if (input.good() || input.eof()) 344 output.write(buffer, input.gcount()); 345 } 346 if (!input.good() && !input.eof()) 347 throw error(F("Error while reading input file %s") % source); 348 } 349 350 351 /// Queries the path to the current directory. 352 /// 353 /// \return The path to the current directory. 354 /// 355 /// \throw fs::error If there is a problem querying the current directory. 356 fs::path 357 fs::current_path(void) 358 { 359 char* cwd; 360 #if defined(HAVE_GETCWD_DYN) 361 cwd = ::getcwd(NULL, 0); 362 #else 363 cwd = ::getcwd(NULL, MAXPATHLEN); 364 #endif 365 if (cwd == NULL) { 366 const int original_errno = errno; 367 throw fs::system_error(F("Failed to get current working directory"), 368 original_errno); 369 } 370 371 try { 372 const fs::path result(cwd); 373 std::free(cwd); 374 return result; 375 } catch (...) { 376 std::free(cwd); 377 throw; 378 } 379 } 380 381 382 /// Checks if a file exists. 383 /// 384 /// Be aware that this is racy in the same way as access(2) is. 385 /// 386 /// \param path The file to check the existance of. 387 /// 388 /// \return True if the file exists; false otherwise. 389 bool 390 fs::exists(const fs::path& path) 391 { 392 return ::access(path.c_str(), F_OK) == 0; 393 } 394 395 396 /// Locates a file in the PATH. 397 /// 398 /// \param name The file to locate. 399 /// 400 /// \return The path to the located file or none if it was not found. The 401 /// returned path is always absolute. 402 optional< fs::path > 403 fs::find_in_path(const char* name) 404 { 405 const optional< std::string > current_path = utils::getenv("PATH"); 406 if (!current_path || current_path.get().empty()) 407 return none; 408 409 std::istringstream path_input(current_path.get() + ":"); 410 std::string path_component; 411 while (std::getline(path_input, path_component, ':').good()) { 412 const fs::path candidate = path_component.empty() ? 413 fs::path(name) : (fs::path(path_component) / name); 414 if (exists(candidate)) { 415 if (candidate.is_absolute()) 416 return utils::make_optional(candidate); 417 else 418 return utils::make_optional(candidate.to_absolute()); 419 } 420 } 421 return none; 422 } 423 424 425 /// Calculates the free space in a given file system. 426 /// 427 /// \param path Path to a file in the file system for which to check the free 428 /// disk space. 429 /// 430 /// \return The amount of free space usable by a non-root user. 431 /// 432 /// \throw system_error If the call to statfs(2) fails. 433 utils::units::bytes 434 fs::free_disk_space(const fs::path& path) 435 { 436 #if defined(HAVE_STATVFS) 437 struct ::statvfs buf; 438 if (::statvfs(path.c_str(), &buf) == -1) { 439 const int original_errno = errno; 440 throw fs::system_error(F("Failed to stat file system for %s") % path, 441 original_errno); 442 } 443 return units::bytes(uint64_t(buf.f_bsize) * buf.f_bavail); 444 #elif defined(HAVE_STATFS) 445 struct ::statfs buf; 446 if (::statfs(path.c_str(), &buf) == -1) { 447 const int original_errno = errno; 448 throw fs::system_error(F("Failed to stat file system for %s") % path, 449 original_errno); 450 } 451 return units::bytes(uint64_t(buf.f_bsize) * buf.f_bavail); 452 #else 453 # error "Don't know how to query free disk space" 454 #endif 455 } 456 457 458 /// Checks if the given path is a directory or not. 459 /// 460 /// \return True if the path is a directory; false otherwise. 461 bool 462 fs::is_directory(const fs::path& path) 463 { 464 const struct ::stat sb = safe_stat(path); 465 return S_ISDIR(sb.st_mode); 466 } 467 468 469 /// Creates a directory. 470 /// 471 /// \param dir The path to the directory to create. 472 /// \param mode The permissions for the new directory. 473 /// 474 /// \throw system_error If the call to mkdir(2) fails. 475 void 476 fs::mkdir(const fs::path& dir, const int mode) 477 { 478 if (::mkdir(dir.c_str(), static_cast< mode_t >(mode)) == -1) { 479 const int original_errno = errno; 480 throw fs::system_error(F("Failed to create directory %s") % dir, 481 original_errno); 482 } 483 } 484 485 486 /// Creates a directory and any missing parents. 487 /// 488 /// This is separate from the fs::mkdir function to clearly differentiate the 489 /// libc wrapper from the more complex algorithm implemented here. 490 /// 491 /// \param dir The path to the directory to create. 492 /// \param mode The permissions for the new directories. 493 /// 494 /// \throw system_error If any call to mkdir(2) fails. 495 void 496 fs::mkdir_p(const fs::path& dir, const int mode) 497 { 498 try { 499 fs::mkdir(dir, mode); 500 } catch (const fs::system_error& e) { 501 if (e.original_errno() == ENOENT) { 502 fs::mkdir_p(dir.branch_path(), mode); 503 fs::mkdir(dir, mode); 504 } else if (e.original_errno() != EEXIST) 505 throw e; 506 } 507 } 508 509 510 /// Creates a temporary directory that is world readable/accessible. 511 /// 512 /// The temporary directory is created using mkdtemp(3) using the provided 513 /// template. This should be most likely used in conjunction with 514 /// fs::auto_directory. 515 /// 516 /// The temporary directory is given read and execute permissions to everyone 517 /// and thus should not be used to protect data that may be subject to snooping. 518 /// This goes together with the assumption that this function is used to create 519 /// temporary directories for test cases, and that those test cases may 520 /// sometimes be executed as an unprivileged user. In those cases, we need to 521 /// support two different things: 522 /// 523 /// - Allow the unprivileged code to write to files in the work directory by 524 /// name (e.g. to write the results file, whose name is provided by the 525 /// monitor code running as root). This requires us to grant search 526 /// permissions. 527 /// 528 /// - Allow the test cases themselves to call getcwd(3) at any point. At least 529 /// on NetBSD 7.x, getcwd(3) requires both read and search permissions on all 530 /// path components leading to the current directory. This requires us to 531 /// grant both read and search permissions. 532 /// 533 /// TODO(jmmv): A cleaner way to support this would be for the test executor to 534 /// create two work directory hierarchies directly rooted under TMPDIR: one for 535 /// root and one for the unprivileged user. However, that requires more 536 /// bookkeeping for no real gain, because we are not really trying to protect 537 /// the data within our temporary directories against attacks. 538 /// 539 /// \param path_template The template for the temporary path, which is a 540 /// basename that is created within the TMPDIR. Must contain the XXXXXX 541 /// pattern, which is atomically replaced by a random unique string. 542 /// 543 /// \return The generated path for the temporary directory. 544 /// 545 /// \throw fs::system_error If the call to mkdtemp(3) fails. 546 fs::path 547 fs::mkdtemp_public(const std::string& path_template) 548 { 549 PRE(path_template.find("XXXXXX") != std::string::npos); 550 551 const fs::path tmpdir(utils::getenv_with_default("TMPDIR", "/tmp")); 552 const fs::path full_template = tmpdir / path_template; 553 554 utils::auto_array< char > buf(new char[full_template.str().length() + 1]); 555 std::strcpy(buf.get(), full_template.c_str()); 556 if (::mkdtemp(buf.get()) == NULL) { 557 const int original_errno = errno; 558 throw fs::system_error(F("Cannot create temporary directory using " 559 "template %s") % full_template, 560 original_errno); 561 } 562 const fs::path path(buf.get()); 563 564 if (::chmod(path.c_str(), 0755) == -1) { 565 const int original_errno = errno; 566 567 try { 568 rmdir(path); 569 } catch (const fs::system_error& e) { 570 // This really should not fail. We just created the directory and 571 // have not written anything to it so there is no reason for this to 572 // fail. But better handle the failure just in case. 573 LW(F("Failed to delete just-created temporary directory %s") 574 % path); 575 } 576 577 throw fs::system_error(F("Failed to grant search permissions on " 578 "temporary directory %s") % path, 579 original_errno); 580 } 581 582 return path; 583 } 584 585 586 /// Creates a temporary file. 587 /// 588 /// The temporary file is created using mkstemp(3) using the provided template. 589 /// This should be most likely used in conjunction with fs::auto_file. 590 /// 591 /// \param path_template The template for the temporary path, which is a 592 /// basename that is created within the TMPDIR. Must contain the XXXXXX 593 /// pattern, which is atomically replaced by a random unique string. 594 /// 595 /// \return The generated path for the temporary directory. 596 /// 597 /// \throw fs::system_error If the call to mkstemp(3) fails. 598 fs::path 599 fs::mkstemp(const std::string& path_template) 600 { 601 PRE(path_template.find("XXXXXX") != std::string::npos); 602 603 const fs::path tmpdir(utils::getenv_with_default("TMPDIR", "/tmp")); 604 const fs::path full_template = tmpdir / path_template; 605 606 utils::auto_array< char > buf(new char[full_template.str().length() + 1]); 607 std::strcpy(buf.get(), full_template.c_str()); 608 if (::mkstemp(buf.get()) == -1) { 609 const int original_errno = errno; 610 throw fs::system_error(F("Cannot create temporary file using template " 611 "%s") % full_template, original_errno); 612 } 613 return fs::path(buf.get()); 614 } 615 616 617 /// Mounts a temporary file system with unlimited size. 618 /// 619 /// \param in_mount_point The path on which the file system will be mounted. 620 /// 621 /// \throw fs::system_error If the attempt to mount process fails. 622 /// \throw fs::unsupported_operation_error If the code does not know how to 623 /// mount a temporary file system in the current operating system. 624 void 625 fs::mount_tmpfs(const fs::path& in_mount_point) 626 { 627 mount_tmpfs(in_mount_point, units::bytes()); 628 } 629 630 631 /// Mounts a temporary file system. 632 /// 633 /// \param in_mount_point The path on which the file system will be mounted. 634 /// \param size The size of the tmpfs to mount. If 0, use unlimited. 635 /// 636 /// \throw fs::system_error If the attempt to mount process fails. 637 /// \throw fs::unsupported_operation_error If the code does not know how to 638 /// mount a temporary file system in the current operating system. 639 void 640 fs::mount_tmpfs(const fs::path& in_mount_point, const units::bytes& size) 641 { 642 // SunOS's mount(8) requires paths to be absolute. To err on the side of 643 // caution, let's make the mount point absolute in all cases. 644 const fs::path mount_point = in_mount_point.is_absolute() ? 645 in_mount_point : in_mount_point.to_absolute(); 646 647 const pid_t pid = ::fork(); 648 if (pid == -1) { 649 const int original_errno = errno; 650 throw fs::system_error("Cannot fork to execute mount tool", 651 original_errno); 652 } 653 if (pid == 0) 654 run_mount_tmpfs(mount_point, size); 655 656 int status; 657 retry: 658 if (::waitpid(pid, &status, 0) == -1) { 659 const int original_errno = errno; 660 if (errno == EINTR) 661 goto retry; 662 throw fs::system_error("Failed to wait for mount subprocess", 663 original_errno); 664 } 665 666 if (WIFEXITED(status)) { 667 if (WEXITSTATUS(status) == exit_known_error) 668 throw fs::unsupported_operation_error( 669 "Don't know how to mount a tmpfs on this operating system"); 670 else if (WEXITSTATUS(status) == EXIT_SUCCESS) 671 return; 672 else 673 throw fs::error(F("Failed to mount tmpfs on %s; returned exit " 674 "code %s") % mount_point % WEXITSTATUS(status)); 675 } else { 676 throw fs::error(F("Failed to mount tmpfs on %s; mount tool " 677 "received signal") % mount_point); 678 } 679 } 680 681 682 /// Recursively removes a directory. 683 /// 684 /// This operation simulates a "rm -r". No effort is made to forcibly delete 685 /// files and no attention is paid to mount points. 686 /// 687 /// \param directory The directory to remove. 688 /// 689 /// \throw fs::error If there is a problem removing any directory or file. 690 void 691 fs::rm_r(const fs::path& directory) 692 { 693 const fs::directory dir(directory); 694 695 for (fs::directory::const_iterator iter = dir.begin(); iter != dir.end(); 696 ++iter) { 697 if (iter->name == "." || iter->name == "..") 698 continue; 699 700 const fs::path entry = directory / iter->name; 701 702 if (fs::is_directory(entry)) { 703 LD(F("Descending into %s") % entry); 704 fs::rm_r(entry); 705 } else { 706 LD(F("Removing file %s") % entry); 707 fs::unlink(entry); 708 } 709 } 710 711 LD(F("Removing empty directory %s") % directory); 712 fs::rmdir(directory); 713 } 714 715 716 /// Removes an empty directory. 717 /// 718 /// \param file The directory to remove. 719 /// 720 /// \throw fs::system_error If the call to rmdir(2) fails. 721 void 722 fs::rmdir(const path& file) 723 { 724 if (::rmdir(file.c_str()) == -1) { 725 const int original_errno = errno; 726 throw fs::system_error(F("Removal of %s failed") % file, 727 original_errno); 728 } 729 } 730 731 732 /// Obtains all the entries in a directory. 733 /// 734 /// \param path The directory to scan. 735 /// 736 /// \return The set of all directory entries in the given directory. 737 /// 738 /// \throw fs::system_error If reading the directory fails for any reason. 739 std::set< fs::directory_entry > 740 fs::scan_directory(const fs::path& path) 741 { 742 std::set< fs::directory_entry > contents; 743 744 fs::directory dir(path); 745 for (fs::directory::const_iterator iter = dir.begin(); iter != dir.end(); 746 ++iter) { 747 contents.insert(*iter); 748 } 749 750 return contents; 751 } 752 753 754 /// Removes a file. 755 /// 756 /// \param file The file to remove. 757 /// 758 /// \throw fs::system_error If the call to unlink(2) fails. 759 void 760 fs::unlink(const path& file) 761 { 762 if (::unlink(file.c_str()) == -1) { 763 const int original_errno = errno; 764 throw fs::system_error(F("Removal of %s failed") % file, 765 original_errno); 766 } 767 } 768 769 770 /// Unmounts a file system. 771 /// 772 /// \param in_mount_point The file system to unmount. 773 /// 774 /// \throw fs::error If the unmount fails. 775 void 776 fs::unmount(const fs::path& in_mount_point) 777 { 778 // FreeBSD's unmount(2) requires paths to be absolute. To err on the side 779 // of caution, let's make it absolute in all cases. 780 const fs::path mount_point = in_mount_point.is_absolute() ? 781 in_mount_point : in_mount_point.to_absolute(); 782 783 static const int unmount_retries = 3; 784 static const int unmount_retry_delay_seconds = 1; 785 786 int retries = unmount_retries; 787 retry: 788 try { 789 if (have_unmount2) { 790 unmount_with_unmount2(mount_point); 791 } else { 792 unmount_with_umount8(mount_point); 793 } 794 } catch (const fs::system_error& error) { 795 if (error.original_errno() == EBUSY && retries > 0) { 796 LW(F("%s busy; unmount retries left %s") % mount_point % retries); 797 retries--; 798 ::sleep(unmount_retry_delay_seconds); 799 goto retry; 800 } 801 throw; 802 } 803 } 804