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 #if defined(__minix) && !defined(NDEBUG)
388 const int ret =
389 #endif /* defined(__minix) && !defined(NDEBUG) */
390 execlp(UMOUNT, "umount", mount_point, NULL);
391 assert(ret == -1);
392 err(EXIT_FAILURE, "Failed to execute " UMOUNT);
393 }
394
395 kyua_error_t error = kyua_error_ok();
396 int status;
397 if (waitpid(pid, &status, 0) == -1) {
398 error = kyua_libc_error_new(errno, "waitpid(%d) failed", pid);
399 } else {
400 if (WIFEXITED(status)) {
401 if (WEXITSTATUS(status) == EXIT_SUCCESS)
402 assert(!kyua_error_is_set(error));
403 else {
404 error = kyua_libc_error_new(EBUSY, "unmount(%s) failed",
405 mount_point);
406 }
407 } else
408 error = kyua_libc_error_new(EFAULT, "umount(8) crashed");
409 }
410 return error;
411 }
412
413
414 /// Recursively removes a directory.
415 ///
416 /// \param root The directory or file to remove. Cannot be a mount point.
417 ///
418 /// \return An error object.
419 kyua_error_t
kyua_fs_cleanup(const char * root)420 kyua_fs_cleanup(const char* root)
421 {
422 struct stat current_sb;
423 bool ok = try_stat(root, ¤t_sb);
424 if (ok)
425 ok &= recursive_cleanup(root, ¤t_sb);
426
427 if (!ok) {
428 warnx("Cleanup of '%s' failed", root);
429 return kyua_libc_error_new(EPERM, "Cleanup of %s failed", root);
430 } else
431 return kyua_error_ok();
432 }
433
434
435 /// Concatenates a set of strings to form a path.
436 ///
437 /// \param [out] output Pointer to a dynamically-allocated string that will hold
438 /// the resulting path, if all goes well.
439 /// \param first First component of the path to concatenate.
440 /// \param ... All other components to concatenate.
441 ///
442 /// \return An error if there is not enough memory to fulfill the request; OK
443 /// otherwise.
444 kyua_error_t
kyua_fs_concat(char ** const output,const char * first,...)445 kyua_fs_concat(char** const output, const char* first, ...)
446 {
447 va_list ap;
448 const char* component;
449
450 va_start(ap, first);
451 size_t length = strlen(first) + 1;
452 while ((component = va_arg(ap, const char*)) != NULL) {
453 length += 1 + strlen(component);
454 }
455 va_end(ap);
456
457 *output = (char*)malloc(length);
458 if (output == NULL)
459 return kyua_oom_error_new();
460 char* iterator = *output;
461
462 int added_size;
463 added_size = snprintf(iterator, length, "%s", first);
464 iterator += added_size; length -= added_size;
465
466 va_start(ap, first);
467 while ((component = va_arg(ap, const char*)) != NULL) {
468 added_size = snprintf(iterator, length, "/%s", component);
469 iterator += added_size; length -= added_size;
470 }
471 va_end(ap);
472
473 return kyua_error_ok();
474 }
475
476
477 /// Queries the path to the current directory.
478 ///
479 /// \param [out] out_cwd Dynamically-allocated pointer to a string holding the
480 /// current path. The caller must use free() to release it.
481 ///
482 /// \return An error object.
483 kyua_error_t
kyua_fs_current_path(char ** out_cwd)484 kyua_fs_current_path(char** out_cwd)
485 {
486 char* cwd;
487 #if defined(HAVE_GETCWD_DYN)
488 cwd = getcwd(NULL, 0);
489 #else
490 {
491 const char* static_cwd = ::getcwd(NULL, MAXPATHLEN);
492 const kyua_error_t error = kyua_fs_concat(&cwd, static_cwd, NULL);
493 if (kyua_error_is_set(error))
494 return error;
495 }
496 #endif
497 if (cwd == NULL) {
498 return kyua_libc_error_new(errno, "getcwd() failed");
499 } else {
500 *out_cwd = cwd;
501 return kyua_error_ok();
502 }
503 }
504
505
506 /// Converts a path to absolute.
507 ///
508 /// \param original The path to convert; may already be absolute.
509 /// \param [out] output Pointer to a dynamically-allocated string that will hold
510 /// the absolute path, if all goes well.
511 ///
512 /// \return An error if there is not enough memory to fulfill the request; OK
513 /// otherwise.
514 kyua_error_t
kyua_fs_make_absolute(const char * original,char ** const output)515 kyua_fs_make_absolute(const char* original, char** const output)
516 {
517 if (original[0] == '/') {
518 *output = (char*)malloc(strlen(original) + 1);
519 if (output == NULL)
520 return kyua_oom_error_new();
521 strcpy(*output, original);
522 return kyua_error_ok();
523 } else {
524 char* current_path= NULL; /* LSC: needed when compiling in -O3 */
525 kyua_error_t error;
526
527 error = kyua_fs_current_path(¤t_path);
528 if (kyua_error_is_set(error))
529 return error;
530
531 error = kyua_fs_concat(output, current_path, original, NULL);
532 free(current_path);
533 return error;
534 }
535 }
536
537
538 /// Unmounts a file system.
539 ///
540 /// \param mount_point The file system to unmount.
541 ///
542 /// \return An error object.
543 kyua_error_t
kyua_fs_unmount(const char * mount_point)544 kyua_fs_unmount(const char* mount_point)
545 {
546 kyua_error_t error;
547
548 // FreeBSD's unmount(2) requires paths to be absolute. To err on the side
549 // of caution, let's make it absolute in all cases.
550 char* abs_mount_point;
551 error = kyua_fs_make_absolute(mount_point, &abs_mount_point);
552 if (kyua_error_is_set(error))
553 goto out;
554
555 static const int unmount_retries = 3;
556 static const int unmount_retry_delay_seconds = 1;
557
558 int retries = unmount_retries;
559 retry:
560 if (have_unmount2) {
561 error = unmount_with_unmount2(abs_mount_point);
562 } else {
563 error = unmount_with_umount8(abs_mount_point);
564 }
565 if (kyua_error_is_set(error)) {
566 assert(kyua_error_is_type(error, "libc"));
567 if (kyua_libc_error_errno(error) == EBUSY && retries > 0) {
568 kyua_error_warn(error, "%s busy; unmount retries left %d",
569 abs_mount_point, retries);
570 kyua_error_free(error);
571 retries--;
572 sleep(unmount_retry_delay_seconds);
573 goto retry;
574 }
575 }
576
577 out:
578 free(abs_mount_point);
579 return error;
580 }
581