xref: /freebsd/contrib/kyua/utils/fs/operations.cpp (revision ed549cb0c53f8438c52593ce811f6fcc812248e9)
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