xref: /minix/external/bsd/kyua-testers/dist/fs.c (revision 84d9c625)
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
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
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
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
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
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
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
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
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
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, &current_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                                         &current_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
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, &current_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                                             &current_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
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
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
417 kyua_fs_cleanup(const char* root)
418 {
419     struct stat current_sb;
420     bool ok = try_stat(root, &current_sb);
421     if (ok)
422         ok &= recursive_cleanup(root, &current_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
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
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
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= NULL; /* LSC: needed when compiling in -O3 */
522         kyua_error_t error;
523 
524         error = kyua_fs_current_path(&current_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
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