Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/contrib/kyua/utils/fs/operations.cpp
48081 views
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
::chmod(directory.c_str(), 0700);
696
for (fs::directory::const_iterator iter = dir.begin(); iter != dir.end();
697
++iter) {
698
if (iter->name == "." || iter->name == "..")
699
continue;
700
701
const fs::path entry = directory / iter->name;
702
703
if (fs::is_directory(entry)) {
704
LD(F("Descending into %s") % entry);
705
::chmod(entry.c_str(), 0700);
706
fs::rm_r(entry);
707
} else {
708
LD(F("Removing file %s") % entry);
709
fs::unlink(entry);
710
}
711
}
712
713
LD(F("Removing empty directory %s") % directory);
714
fs::rmdir(directory);
715
}
716
717
718
/// Removes an empty directory.
719
///
720
/// \param file The directory to remove.
721
///
722
/// \throw fs::system_error If the call to rmdir(2) fails.
723
void
724
fs::rmdir(const path& file)
725
{
726
if (::rmdir(file.c_str()) == -1) {
727
const int original_errno = errno;
728
throw fs::system_error(F("Removal of %s failed") % file,
729
original_errno);
730
}
731
}
732
733
734
/// Obtains all the entries in a directory.
735
///
736
/// \param path The directory to scan.
737
///
738
/// \return The set of all directory entries in the given directory.
739
///
740
/// \throw fs::system_error If reading the directory fails for any reason.
741
std::set< fs::directory_entry >
742
fs::scan_directory(const fs::path& path)
743
{
744
std::set< fs::directory_entry > contents;
745
746
fs::directory dir(path);
747
for (fs::directory::const_iterator iter = dir.begin(); iter != dir.end();
748
++iter) {
749
contents.insert(*iter);
750
}
751
752
return contents;
753
}
754
755
756
/// Removes a file.
757
///
758
/// \param file The file to remove.
759
///
760
/// \throw fs::system_error If the call to unlink(2) fails.
761
void
762
fs::unlink(const path& file)
763
{
764
if (::unlink(file.c_str()) == -1) {
765
const int original_errno = errno;
766
throw fs::system_error(F("Removal of %s failed") % file,
767
original_errno);
768
}
769
}
770
771
772
/// Unmounts a file system.
773
///
774
/// \param in_mount_point The file system to unmount.
775
///
776
/// \throw fs::error If the unmount fails.
777
void
778
fs::unmount(const fs::path& in_mount_point)
779
{
780
// FreeBSD's unmount(2) requires paths to be absolute. To err on the side
781
// of caution, let's make it absolute in all cases.
782
const fs::path mount_point = in_mount_point.is_absolute() ?
783
in_mount_point : in_mount_point.to_absolute();
784
785
static const int unmount_retries = 3;
786
static const int unmount_retry_delay_seconds = 1;
787
788
int retries = unmount_retries;
789
retry:
790
try {
791
if (have_unmount2) {
792
unmount_with_unmount2(mount_point);
793
} else {
794
unmount_with_umount8(mount_point);
795
}
796
} catch (const fs::system_error& error) {
797
if (error.original_errno() == EBUSY && retries > 0) {
798
LW(F("%s busy; unmount retries left %s") % mount_point % retries);
799
retries--;
800
::sleep(unmount_retry_delay_seconds);
801
goto retry;
802
}
803
throw;
804
}
805
}
806
807