1 // Copyright 2012 Google Inc.
2 // All rights reserved.
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
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.
31 #if defined(HAVE_CONFIG_H)
35 #if defined(HAVE_UNMOUNT)
36 # include <sys/param.h>
37 # include <sys/mount.h>
57 /// Specifies if a real unmount(2) is available.
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)
71 /// Fake replacement value to the path to umount(8).
72 # define UMOUNT "do-not-use-this-value"
74 # if defined(HAVE_UNMOUNT)
75 # error "umount(8) detected when unmount(2) is also available"
80 #if !defined(HAVE_UNMOUNT)
81 /// Fake unmount(2) function for systems without it.
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.
86 /// \param unused_path The mount point to be unmounted.
87 /// \param unused_flags The flags to the unmount(2) call.
89 /// \return -1 to indicate error, although this should never happen.
91 unmount(const char* KYUA_DEFS_UNUSED_PARAM(path
),
92 const int KYUA_DEFS_UNUSED_PARAM(flags
))
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.
111 try_iterate_directory(const char* directory
,
112 bool (*callback
)(const char*, const void*),
113 const void* argument
)
117 DIR* dirp
= opendir(directory
);
119 warn("opendir(%s) failed", directory
);
123 while ((dp
= readdir(dirp
)) != NULL
) {
124 const char* name
= dp
->d_name
;
125 if (strcmp(name
, ".") == 0 || strcmp(name
, "..") == 0)
129 const kyua_error_t error
= kyua_fs_concat(&subdir
, directory
, name
,
131 if (kyua_error_is_set(error
)) {
132 kyua_error_free(error
);
133 warn("path concatenation failed");
136 ok
&= callback(subdir
, argument
);
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.
156 try_stat(const char* path
, struct stat
* sb
)
158 if (lstat(path
, sb
) == -1) {
159 warn("lstat(%s) failed", path
);
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.
174 try_rmdir(const char* path
)
176 if (rmdir(path
) == -1) {
177 warn("rmdir(%s) failed", path
);
186 /// \param path The file to remove.
188 /// \return True on success; false otherwise.
190 /// \note Errors are logged to stderr.
192 try_unlink(const char* path
)
194 if (unlink(path
) == -1) {
195 warn("unlink(%s) failed", path
);
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.
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
);
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.
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
);
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.
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
);
261 warnx("lchmod(%s, %04o) failed; system call not implemented", path
,
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.
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
, ¤t_sb
);
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
,
290 if (current_sb
.st_dev
!= parent_sb
->st_dev
)
291 ok
&= try_unmount(current_path
);
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
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.
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
, ¤t_sb
);
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
);
328 try_unprotect(current_path
);
330 if (current_sb
.st_dev
!= parent_sb
->st_dev
) {
331 ok
&= recursive_unmount(current_path
, parent_sb
);
333 ok
&= recursive_cleanup(current_path
, parent_sb
);
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
,
339 ok
&= try_rmdir(current_path
);
341 ok
&= try_unlink(current_path
);
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.
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",
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.
379 unmount_with_umount8(const char* mount_point
)
381 assert(!have_unmount2
);
383 const pid_t pid
= fork();
385 return kyua_libc_error_new(errno
, "fork() failed");
386 } else if (pid
== 0) {
387 #if defined(__minix) && !defined(NDEBUG)
389 #endif /* defined(__minix) && !defined(NDEBUG) */
390 execlp(UMOUNT
, "umount", mount_point
, NULL
);
392 err(EXIT_FAILURE
, "Failed to execute " UMOUNT
);
395 kyua_error_t error
= kyua_error_ok();
397 if (waitpid(pid
, &status
, 0) == -1) {
398 error
= kyua_libc_error_new(errno
, "waitpid(%d) failed", pid
);
400 if (WIFEXITED(status
)) {
401 if (WEXITSTATUS(status
) == EXIT_SUCCESS
)
402 assert(!kyua_error_is_set(error
));
404 error
= kyua_libc_error_new(EBUSY
, "unmount(%s) failed",
408 error
= kyua_libc_error_new(EFAULT
, "umount(8) crashed");
414 /// Recursively removes a directory.
416 /// \param root The directory or file to remove. Cannot be a mount point.
418 /// \return An error object.
420 kyua_fs_cleanup(const char* root
)
422 struct stat current_sb
;
423 bool ok
= try_stat(root
, ¤t_sb
);
425 ok
&= recursive_cleanup(root
, ¤t_sb
);
428 warnx("Cleanup of '%s' failed", root
);
429 return kyua_libc_error_new(EPERM
, "Cleanup of %s failed", root
);
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
445 kyua_fs_concat(char** const output
, const char* first
, ...)
448 const char* component
;
451 size_t length
= strlen(first
) + 1;
452 while ((component
= va_arg(ap
, const char*)) != NULL
) {
453 length
+= 1 + strlen(component
);
457 *output
= (char*)malloc(length
);
459 return kyua_oom_error_new();
460 char* iterator
= *output
;
463 added_size
= snprintf(iterator
, length
, "%s", first
);
464 iterator
+= added_size
; length
-= added_size
;
467 while ((component
= va_arg(ap
, const char*)) != NULL
) {
468 added_size
= snprintf(iterator
, length
, "/%s", component
);
469 iterator
+= added_size
; length
-= added_size
;
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.
484 kyua_fs_current_path(char** out_cwd
)
487 #if defined(HAVE_GETCWD_DYN)
488 cwd
= getcwd(NULL
, 0);
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
))
498 return kyua_libc_error_new(errno
, "getcwd() failed");
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
515 kyua_fs_make_absolute(const char* original
, char** const output
)
517 if (original
[0] == '/') {
518 *output
= (char*)malloc(strlen(original
) + 1);
520 return kyua_oom_error_new();
521 strcpy(*output
, original
);
522 return kyua_error_ok();
524 char* current_path
= NULL
; /* LSC: needed when compiling in -O3 */
527 error
= kyua_fs_current_path(¤t_path
);
528 if (kyua_error_is_set(error
))
531 error
= kyua_fs_concat(output
, current_path
, original
, NULL
);
538 /// Unmounts a file system.
540 /// \param mount_point The file system to unmount.
542 /// \return An error object.
544 kyua_fs_unmount(const char* mount_point
)
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
))
555 static const int unmount_retries
= 3;
556 static const int unmount_retry_delay_seconds
= 1;
558 int retries
= unmount_retries
;
561 error
= unmount_with_unmount2(abs_mount_point
);
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
);
572 sleep(unmount_retry_delay_seconds
);
578 free(abs_mount_point
);