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