1 // Copyright 2012 Google Inc.
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 "fs.h"
30
31 #if defined(HAVE_CONFIG_H)
32 # include "config.h"
33 #endif
34
35 #if defined(HAVE_UNMOUNT)
36 # include <sys/param.h>
37 # include <sys/mount.h>
38 #endif
39 #include <sys/stat.h>
40 #include <sys/wait.h>
41
42 #include <assert.h>
43 #include <dirent.h>
44 #include <err.h>
45 #include <errno.h>
46 #include <stdarg.h>
47 #include <stdbool.h>
48 #include <stdio.h>
49 #include <stdlib.h>
50 #include <string.h>
51 #include <unistd.h>
52
53 #include "defs.h"
54 #include "error.h"
55
56
57 /// Specifies if a real unmount(2) is available.
58 ///
59 /// We use this as a constant instead of a macro so that we can compile both
60 /// versions of the unmount code unconditionally. This is a way to prevent
61 /// compilation bugs going unnoticed for long.
62 static const bool have_unmount2 =
63 #if defined(HAVE_UNMOUNT)
64 true;
65 #else
66 false;
67 #endif
68
69
70 #if !defined(UMOUNT)
71 /// Fake replacement value to the path to umount(8).
72 # define UMOUNT "do-not-use-this-value"
73 #else
74 # if defined(HAVE_UNMOUNT)
75 # error "umount(8) detected when unmount(2) is also available"
76 # endif
77 #endif
78
79
80 #if !defined(HAVE_UNMOUNT)
81 /// Fake unmount(2) function for systems without it.
82 ///
83 /// This is only provided to allow our code to compile in all platforms
84 /// regardless of whether they actually have an unmount(2) or not.
85 ///
86 /// \param unused_path The mount point to be unmounted.
87 /// \param unused_flags The flags to the unmount(2) call.
88 ///
89 /// \return -1 to indicate error, although this should never happen.
90 static int
unmount(const char * KYUA_DEFS_UNUSED_PARAM (path),const int KYUA_DEFS_UNUSED_PARAM (flags))91 unmount(const char* KYUA_DEFS_UNUSED_PARAM(path),
92 const int KYUA_DEFS_UNUSED_PARAM(flags))
93 {
94 assert(false);
95 return -1;
96 }
97 #endif
98
99
100 /// Scans a directory and executes a callback on each entry.
101 ///
102 /// \param directory The directory to scan.
103 /// \param callback The function to execute on each entry.
104 /// \param argument A cookie to pass to the callback function.
105 ///
106 /// \return True if the directory scan and the calls to the callback function
107 /// are all successful; false otherwise.
108 ///
109 /// \note Errors are logged to stderr and do not stop the algorithm.
110 static bool
try_iterate_directory(const char * directory,bool (* callback)(const char *,const void *),const void * argument)111 try_iterate_directory(const char* directory,
112 bool (*callback)(const char*, const void*),
113 const void* argument)
114 {
115 bool ok = true;
116
117 DIR* dirp = opendir(directory);
118 if (dirp == NULL) {
119 warn("opendir(%s) failed", directory);
120 ok &= false;
121 } else {
122 struct dirent* dp;
123 while ((dp = readdir(dirp)) != NULL) {
124 const char* name = dp->d_name;
125 if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0)
126 continue;
127
128 char* subdir;
129 const kyua_error_t error = kyua_fs_concat(&subdir, directory, name,
130 NULL);
131 if (kyua_error_is_set(error)) {
132 kyua_error_free(error);
133 warn("path concatenation failed");
134 ok &= false;
135 } else {
136 ok &= callback(subdir, argument);
137 free(subdir);
138 }
139 }
140 closedir(dirp);
141 }
142
143 return ok;
144 }
145
146
147 /// Stats a file, without following links.
148 ///
149 /// \param path The file to stat.
150 /// \param [out] sb Pointer to the stat structure in which to place the result.
151 ///
152 /// \return The stat structure on success; none on failure.
153 ///
154 /// \note Errors are logged to stderr.
155 static bool
try_stat(const char * path,struct stat * sb)156 try_stat(const char* path, struct stat* sb)
157 {
158 if (lstat(path, sb) == -1) {
159 warn("lstat(%s) failed", path);
160 return false;
161 } else
162 return true;
163 }
164
165
166 /// Removes a directory.
167 ///
168 /// \param path The directory to remove.
169 ///
170 /// \return True on success; false otherwise.
171 ///
172 /// \note Errors are logged to stderr.
173 static bool
try_rmdir(const char * path)174 try_rmdir(const char* path)
175 {
176 if (rmdir(path) == -1) {
177 warn("rmdir(%s) failed", path);
178 return false;
179 } else
180 return true;
181 }
182
183
184 /// Removes a file.
185 ///
186 /// \param path The file to remove.
187 ///
188 /// \return True on success; false otherwise.
189 ///
190 /// \note Errors are logged to stderr.
191 static bool
try_unlink(const char * path)192 try_unlink(const char* path)
193 {
194 if (unlink(path) == -1) {
195 warn("unlink(%s) failed", path);
196 return false;
197 } else
198 return true;
199 }
200
201
202 /// Unmounts a mount point.
203 ///
204 /// \param path The location to unmount.
205 ///
206 /// \return True on success; false otherwise.
207 ///
208 /// \note Errors are logged to stderr.
209 static bool
try_unmount(const char * path)210 try_unmount(const char* path)
211 {
212 const kyua_error_t error = kyua_fs_unmount(path);
213 if (kyua_error_is_set(error)) {
214 kyua_error_warn(error, "Cannot unmount %s", path);
215 kyua_error_free(error);
216 return false;
217 } else
218 return true;
219 }
220
221
222 /// Attempts to weaken the permissions of a file.
223 ///
224 /// \param path The file to unprotect.
225 ///
226 /// \return True on success; false otherwise.
227 ///
228 /// \note Errors are logged to stderr.
229 static bool
try_unprotect(const char * path)230 try_unprotect(const char* path)
231 {
232 static const mode_t new_mode = 0700;
233
234 if (chmod(path, new_mode) == -1) {
235 warnx("chmod(%s, %04o) failed", path, new_mode);
236 return false;
237 } else
238 return true;
239 }
240
241
242 /// Attempts to weaken the permissions of a symbolic link.
243 ///
244 /// \param path The symbolic link to unprotect.
245 ///
246 /// \return True on success; false otherwise.
247 ///
248 /// \note Errors are logged to stderr.
249 static bool
try_unprotect_symlink(const char * path)250 try_unprotect_symlink(const char* path)
251 {
252 static const mode_t new_mode = 0700;
253
254 #if HAVE_WORKING_LCHMOD
255 if (lchmod(path, new_mode) == -1) {
256 warnx("lchmod(%s, %04o) failed", path, new_mode);
257 return false;
258 } else
259 return true;
260 #else
261 warnx("lchmod(%s, %04o) failed; system call not implemented", path,
262 new_mode);
263 return false;
264 #endif
265 }
266
267
268 /// Traverses a hierarchy unmounting any mount points in it.
269 ///
270 /// \param current_path The file or directory to traverse.
271 /// \param raw_parent_sb The stat structure of the enclosing directory.
272 ///
273 /// \return True on success; false otherwise.
274 ///
275 /// \note Errors are logged to stderr and do not stop the algorithm.
276 static bool
recursive_unmount(const char * current_path,const void * raw_parent_sb)277 recursive_unmount(const char* current_path, const void* raw_parent_sb)
278 {
279 const struct stat* parent_sb = raw_parent_sb;
280
281 struct stat current_sb;
282 bool ok = try_stat(current_path, ¤t_sb);
283 if (ok) {
284 if (S_ISDIR(current_sb.st_mode)) {
285 assert(!S_ISLNK(current_sb.st_mode));
286 ok &= try_iterate_directory(current_path, recursive_unmount,
287 ¤t_sb);
288 }
289
290 if (current_sb.st_dev != parent_sb->st_dev)
291 ok &= try_unmount(current_path);
292 }
293
294 return ok;
295 }
296
297
298 /// Traverses a hierarchy and removes all of its contents.
299 ///
300 /// This honors mount points: when a mount point is encountered, it is traversed
301 /// in search for other mount points, but no files within any of these are
302 /// removed.
303 ///
304 /// \param current_path The file or directory to traverse.
305 /// \param raw_parent_sb The stat structure of the enclosing directory.
306 ///
307 /// \return True on success; false otherwise.
308 ///
309 /// \note Errors are logged to stderr and do not stop the algorithm.
310 static bool
recursive_cleanup(const char * current_path,const void * raw_parent_sb)311 recursive_cleanup(const char* current_path, const void* raw_parent_sb)
312 {
313 const struct stat* parent_sb = raw_parent_sb;
314
315 struct stat current_sb;
316 bool ok = try_stat(current_path, ¤t_sb);
317 if (ok) {
318 // Weakening the protections of a file is just a best-effort operation.
319 // If this fails, we may still be able to do the file/directory removal
320 // later on, so ignore any failures from try_unprotect().
321 //
322 // One particular case in which this fails is if try_unprotect() is run
323 // on a symbolic link that points to a file for which the unprotect is
324 // not possible, and lchmod(3) is not available.
325 if (S_ISLNK(current_sb.st_mode))
326 try_unprotect_symlink(current_path);
327 else
328 try_unprotect(current_path);
329
330 if (current_sb.st_dev != parent_sb->st_dev) {
331 ok &= recursive_unmount(current_path, parent_sb);
332 if (ok)
333 ok &= recursive_cleanup(current_path, parent_sb);
334 } else {
335 if (S_ISDIR(current_sb.st_mode)) {
336 assert(!S_ISLNK(current_sb.st_mode));
337 ok &= try_iterate_directory(current_path, recursive_cleanup,
338 ¤t_sb);
339 ok &= try_rmdir(current_path);
340 } else {
341 ok &= try_unlink(current_path);
342 }
343 }
344 }
345
346 return ok;
347 }
348
349
350 /// Unmounts a file system using unmount(2).
351 ///
352 /// \pre unmount(2) must be available; i.e. have_unmount2 must be true.
353 ///
354 /// \param mount_point The file system to unmount.
355 ///
356 /// \return An error object.
357 static kyua_error_t
unmount_with_unmount2(const char * mount_point)358 unmount_with_unmount2(const char* mount_point)
359 {
360 assert(have_unmount2);
361
362 if (unmount(mount_point, 0) == -1) {
363 return kyua_libc_error_new(errno, "unmount(%s) failed",
364 mount_point);
365 }
366
367 return kyua_error_ok();
368 }
369
370
371 /// Unmounts a file system using umount(8).
372 ///
373 /// \pre umount(2) must not be available; i.e. have_unmount2 must be false.
374 ///
375 /// \param mount_point The file system to unmount.
376 ///
377 /// \return An error object.
378 static kyua_error_t
unmount_with_umount8(const char * mount_point)379 unmount_with_umount8(const char* mount_point)
380 {
381 assert(!have_unmount2);
382
383 const pid_t pid = fork();
384 if (pid == -1) {
385 return kyua_libc_error_new(errno, "fork() failed");
386 } else if (pid == 0) {
387 const int ret = execlp(UMOUNT, "umount", mount_point, NULL);
388 assert(ret == -1);
389 err(EXIT_FAILURE, "Failed to execute " UMOUNT);
390 }
391
392 kyua_error_t error = kyua_error_ok();
393 int status;
394 if (waitpid(pid, &status, 0) == -1) {
395 error = kyua_libc_error_new(errno, "waitpid(%d) failed", pid);
396 } else {
397 if (WIFEXITED(status)) {
398 if (WEXITSTATUS(status) == EXIT_SUCCESS)
399 assert(!kyua_error_is_set(error));
400 else {
401 error = kyua_libc_error_new(EBUSY, "unmount(%s) failed",
402 mount_point);
403 }
404 } else
405 error = kyua_libc_error_new(EFAULT, "umount(8) crashed");
406 }
407 return error;
408 }
409
410
411 /// Recursively removes a directory.
412 ///
413 /// \param root The directory or file to remove. Cannot be a mount point.
414 ///
415 /// \return An error object.
416 kyua_error_t
kyua_fs_cleanup(const char * root)417 kyua_fs_cleanup(const char* root)
418 {
419 struct stat current_sb;
420 bool ok = try_stat(root, ¤t_sb);
421 if (ok)
422 ok &= recursive_cleanup(root, ¤t_sb);
423
424 if (!ok) {
425 warnx("Cleanup of '%s' failed", root);
426 return kyua_libc_error_new(EPERM, "Cleanup of %s failed", root);
427 } else
428 return kyua_error_ok();
429 }
430
431
432 /// Concatenates a set of strings to form a path.
433 ///
434 /// \param [out] output Pointer to a dynamically-allocated string that will hold
435 /// the resulting path, if all goes well.
436 /// \param first First component of the path to concatenate.
437 /// \param ... All other components to concatenate.
438 ///
439 /// \return An error if there is not enough memory to fulfill the request; OK
440 /// otherwise.
441 kyua_error_t
kyua_fs_concat(char ** const output,const char * first,...)442 kyua_fs_concat(char** const output, const char* first, ...)
443 {
444 va_list ap;
445 const char* component;
446
447 va_start(ap, first);
448 size_t length = strlen(first) + 1;
449 while ((component = va_arg(ap, const char*)) != NULL) {
450 length += 1 + strlen(component);
451 }
452 va_end(ap);
453
454 *output = (char*)malloc(length);
455 if (output == NULL)
456 return kyua_oom_error_new();
457 char* iterator = *output;
458
459 int added_size;
460 added_size = snprintf(iterator, length, "%s", first);
461 iterator += added_size; length -= added_size;
462
463 va_start(ap, first);
464 while ((component = va_arg(ap, const char*)) != NULL) {
465 added_size = snprintf(iterator, length, "/%s", component);
466 iterator += added_size; length -= added_size;
467 }
468 va_end(ap);
469
470 return kyua_error_ok();
471 }
472
473
474 /// Queries the path to the current directory.
475 ///
476 /// \param [out] out_cwd Dynamically-allocated pointer to a string holding the
477 /// current path. The caller must use free() to release it.
478 ///
479 /// \return An error object.
480 kyua_error_t
kyua_fs_current_path(char ** out_cwd)481 kyua_fs_current_path(char** out_cwd)
482 {
483 char* cwd;
484 #if defined(HAVE_GETCWD_DYN)
485 cwd = getcwd(NULL, 0);
486 #else
487 {
488 const char* static_cwd = ::getcwd(NULL, MAXPATHLEN);
489 const kyua_error_t error = kyua_fs_concat(&cwd, static_cwd, NULL);
490 if (kyua_error_is_set(error))
491 return error;
492 }
493 #endif
494 if (cwd == NULL) {
495 return kyua_libc_error_new(errno, "getcwd() failed");
496 } else {
497 *out_cwd = cwd;
498 return kyua_error_ok();
499 }
500 }
501
502
503 /// Converts a path to absolute.
504 ///
505 /// \param original The path to convert; may already be absolute.
506 /// \param [out] output Pointer to a dynamically-allocated string that will hold
507 /// the absolute path, if all goes well.
508 ///
509 /// \return An error if there is not enough memory to fulfill the request; OK
510 /// otherwise.
511 kyua_error_t
kyua_fs_make_absolute(const char * original,char ** const output)512 kyua_fs_make_absolute(const char* original, char** const output)
513 {
514 if (original[0] == '/') {
515 *output = (char*)malloc(strlen(original) + 1);
516 if (output == NULL)
517 return kyua_oom_error_new();
518 strcpy(*output, original);
519 return kyua_error_ok();
520 } else {
521 char* current_path;
522 kyua_error_t error;
523
524 error = kyua_fs_current_path(¤t_path);
525 if (kyua_error_is_set(error))
526 return error;
527
528 error = kyua_fs_concat(output, current_path, original, NULL);
529 free(current_path);
530 return error;
531 }
532 }
533
534
535 /// Unmounts a file system.
536 ///
537 /// \param mount_point The file system to unmount.
538 ///
539 /// \return An error object.
540 kyua_error_t
kyua_fs_unmount(const char * mount_point)541 kyua_fs_unmount(const char* mount_point)
542 {
543 kyua_error_t error;
544
545 // FreeBSD's unmount(2) requires paths to be absolute. To err on the side
546 // of caution, let's make it absolute in all cases.
547 char* abs_mount_point;
548 error = kyua_fs_make_absolute(mount_point, &abs_mount_point);
549 if (kyua_error_is_set(error))
550 goto out;
551
552 static const int unmount_retries = 3;
553 static const int unmount_retry_delay_seconds = 1;
554
555 int retries = unmount_retries;
556 retry:
557 if (have_unmount2) {
558 error = unmount_with_unmount2(abs_mount_point);
559 } else {
560 error = unmount_with_umount8(abs_mount_point);
561 }
562 if (kyua_error_is_set(error)) {
563 assert(kyua_error_is_type(error, "libc"));
564 if (kyua_libc_error_errno(error) == EBUSY && retries > 0) {
565 kyua_error_warn(error, "%s busy; unmount retries left %d",
566 abs_mount_point, retries);
567 kyua_error_free(error);
568 retries--;
569 sleep(unmount_retry_delay_seconds);
570 goto retry;
571 }
572 }
573
574 out:
575 free(abs_mount_point);
576 return error;
577 }
578