Home | History | Annotate | Line # | Download | only in dist
      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;
    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