tools/llvm: Do not build with symbols
[minix3.git] / external / bsd / kyua-testers / dist / fs.c
blobc670c263c403abc145ab2c0163792dc8caea5b42
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.
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.
29 #include "fs.h"
31 #if defined(HAVE_CONFIG_H)
32 # include "config.h"
33 #endif
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>
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>
53 #include "defs.h"
54 #include "error.h"
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
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
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))
94 assert(false);
95 return -1;
97 #endif
100 /// Scans a directory and executes a callback on each entry.
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.
106 /// \return True if the directory scan and the calls to the callback function
107 /// are all successful; false otherwise.
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)
115 bool ok = true;
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;
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);
140 closedir(dirp);
143 return ok;
147 /// Stats a file, without following links.
149 /// \param path The file to stat.
150 /// \param [out] sb Pointer to the stat structure in which to place the result.
152 /// \return The stat structure on success; none on failure.
154 /// \note Errors are logged to stderr.
155 static bool
156 try_stat(const char* path, struct stat* sb)
158 if (lstat(path, sb) == -1) {
159 warn("lstat(%s) failed", path);
160 return false;
161 } else
162 return true;
166 /// Removes a directory.
168 /// \param path The directory to remove.
170 /// \return True on success; false otherwise.
172 /// \note Errors are logged to stderr.
173 static bool
174 try_rmdir(const char* path)
176 if (rmdir(path) == -1) {
177 warn("rmdir(%s) failed", path);
178 return false;
179 } else
180 return true;
184 /// Removes a file.
186 /// \param path The file to remove.
188 /// \return True on success; false otherwise.
190 /// \note Errors are logged to stderr.
191 static bool
192 try_unlink(const char* path)
194 if (unlink(path) == -1) {
195 warn("unlink(%s) failed", path);
196 return false;
197 } else
198 return true;
202 /// Unmounts a mount point.
204 /// \param path The location to unmount.
206 /// \return True on success; false otherwise.
208 /// \note Errors are logged to stderr.
209 static bool
210 try_unmount(const char* path)
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;
222 /// Attempts to weaken the permissions of a file.
224 /// \param path The file to unprotect.
226 /// \return True on success; false otherwise.
228 /// \note Errors are logged to stderr.
229 static bool
230 try_unprotect(const char* path)
232 static const mode_t new_mode = 0700;
234 if (chmod(path, new_mode) == -1) {
235 warnx("chmod(%s, %04o) failed", path, new_mode);
236 return false;
237 } else
238 return true;
242 /// Attempts to weaken the permissions of a symbolic link.
244 /// \param path The symbolic link to unprotect.
246 /// \return True on success; false otherwise.
248 /// \note Errors are logged to stderr.
249 static bool
250 try_unprotect_symlink(const char* path)
252 static const mode_t new_mode = 0700;
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
268 /// Traverses a hierarchy unmounting any mount points in it.
270 /// \param current_path The file or directory to traverse.
271 /// \param raw_parent_sb The stat structure of the enclosing directory.
273 /// \return True on success; false otherwise.
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)
279 const struct stat* parent_sb = raw_parent_sb;
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);
290 if (current_sb.st_dev != parent_sb->st_dev)
291 ok &= try_unmount(current_path);
294 return ok;
298 /// Traverses a hierarchy and removes all of its contents.
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.
304 /// \param current_path The file or directory to traverse.
305 /// \param raw_parent_sb The stat structure of the enclosing directory.
307 /// \return True on success; false otherwise.
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)
313 const struct stat* parent_sb = raw_parent_sb;
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().
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);
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);
346 return ok;
350 /// Unmounts a file system using unmount(2).
352 /// \pre unmount(2) must be available; i.e. have_unmount2 must be true.
354 /// \param mount_point The file system to unmount.
356 /// \return An error object.
357 static kyua_error_t
358 unmount_with_unmount2(const char* mount_point)
360 assert(have_unmount2);
362 if (unmount(mount_point, 0) == -1) {
363 return kyua_libc_error_new(errno, "unmount(%s) failed",
364 mount_point);
367 return kyua_error_ok();
371 /// Unmounts a file system using umount(8).
373 /// \pre umount(2) must not be available; i.e. have_unmount2 must be false.
375 /// \param mount_point The file system to unmount.
377 /// \return An error object.
378 static kyua_error_t
379 unmount_with_umount8(const char* mount_point)
381 assert(!have_unmount2);
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);
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);
407 } else
408 error = kyua_libc_error_new(EFAULT, "umount(8) crashed");
410 return error;
414 /// Recursively removes a directory.
416 /// \param root The directory or file to remove. Cannot be a mount point.
418 /// \return An error object.
419 kyua_error_t
420 kyua_fs_cleanup(const char* root)
422 struct stat current_sb;
423 bool ok = try_stat(root, &current_sb);
424 if (ok)
425 ok &= recursive_cleanup(root, &current_sb);
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();
435 /// Concatenates a set of strings to form a path.
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.
442 /// \return An error if there is not enough memory to fulfill the request; OK
443 /// otherwise.
444 kyua_error_t
445 kyua_fs_concat(char** const output, const char* first, ...)
447 va_list ap;
448 const char* component;
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);
455 va_end(ap);
457 *output = (char*)malloc(length);
458 if (output == NULL)
459 return kyua_oom_error_new();
460 char* iterator = *output;
462 int added_size;
463 added_size = snprintf(iterator, length, "%s", first);
464 iterator += added_size; length -= added_size;
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;
471 va_end(ap);
473 return kyua_error_ok();
477 /// Queries the path to the current directory.
479 /// \param [out] out_cwd Dynamically-allocated pointer to a string holding the
480 /// current path. The caller must use free() to release it.
482 /// \return An error object.
483 kyua_error_t
484 kyua_fs_current_path(char** out_cwd)
486 char* cwd;
487 #if defined(HAVE_GETCWD_DYN)
488 cwd = getcwd(NULL, 0);
489 #else
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;
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();
506 /// Converts a path to absolute.
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.
512 /// \return An error if there is not enough memory to fulfill the request; OK
513 /// otherwise.
514 kyua_error_t
515 kyua_fs_make_absolute(const char* original, char** const output)
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;
527 error = kyua_fs_current_path(&current_path);
528 if (kyua_error_is_set(error))
529 return error;
531 error = kyua_fs_concat(output, current_path, original, NULL);
532 free(current_path);
533 return error;
538 /// Unmounts a file system.
540 /// \param mount_point The file system to unmount.
542 /// \return An error object.
543 kyua_error_t
544 kyua_fs_unmount(const char* mount_point)
546 kyua_error_t error;
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;
555 static const int unmount_retries = 3;
556 static const int unmount_retry_delay_seconds = 1;
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);
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;
577 out:
578 free(abs_mount_point);
579 return error;