diff options
Diffstat (limited to 'src/core/umount.c')
-rw-r--r-- | src/core/umount.c | 167 |
1 files changed, 125 insertions, 42 deletions
diff --git a/src/core/umount.c b/src/core/umount.c index 813d257139..7f8ddb99ee 100644 --- a/src/core/umount.c +++ b/src/core/umount.c @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ /*** This file is part of systemd. @@ -27,6 +28,7 @@ #include "libudev.h" #include "alloc-util.h" +#include "def.h" #include "escape.h" #include "fd-util.h" #include "fstab-util.h" @@ -34,6 +36,7 @@ #include "list.h" #include "mount-setup.h" #include "path-util.h" +#include "signal-util.h" #include "string-util.h" #include "udev-util.h" #include "umount.h" @@ -158,7 +161,7 @@ static int swap_list_get(MountPoint **head) { for (i = 2;; i++) { MountPoint *swap; - char *dev = NULL, *d; + _cleanup_free_ char *dev = NULL, *d = NULL; int k; k = fscanf(proc_swaps, @@ -174,27 +177,21 @@ static int swap_list_get(MountPoint **head) { break; log_warning("Failed to parse /proc/swaps:%u.", i); - free(dev); continue; } - if (endswith(dev, " (deleted)")) { - free(dev); + if (endswith(dev, " (deleted)")) continue; - } r = cunescape(dev, UNESCAPE_RELAX, &d); - free(dev); if (r < 0) return r; swap = new0(MountPoint, 1); - if (!swap) { - free(d); + if (!swap) return -ENOMEM; - } - swap->path = d; + free_and_replace(swap->path, d); LIST_PREPEND(mount_point, *head, swap); } @@ -381,13 +378,95 @@ static bool nonunmountable_path(const char *path) { || path_startswith(path, "/run/initramfs"); } -static int mount_points_list_umount(MountPoint **head, bool *changed, bool log_error) { - MountPoint *m, *n; +static int remount_with_timeout(MountPoint *m, char *options, int *n_failed) { + pid_t pid; + int r; + + BLOCK_SIGNALS(SIGCHLD); + + /* Due to the possiblity of a remount operation hanging, we + * fork a child process and set a timeout. If the timeout + * lapses, the assumption is that that particular remount + * failed. */ + pid = fork(); + if (pid < 0) + return log_error_errno(errno, "Failed to fork: %m"); + + if (pid == 0) { + log_info("Remounting '%s' read-only in with options '%s'.", m->path, options); + + /* Start the mount operation here in the child */ + r = mount(NULL, m->path, NULL, MS_REMOUNT|MS_RDONLY, options); + if (r < 0) + log_error_errno(errno, "Failed to remount '%s' read-only: %m", m->path); + + _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS); + } + + r = wait_for_terminate_with_timeout(pid, DEFAULT_TIMEOUT_USEC); + if (r == -ETIMEDOUT) { + log_error_errno(errno, "Remounting '%s' - timed out, issuing SIGKILL to PID "PID_FMT".", m->path, pid); + (void) kill(pid, SIGKILL); + } else if (r < 0) + log_error_errno(r, "Failed to wait for process: %m"); + + return r; +} + +static int umount_with_timeout(MountPoint *m, bool *changed) { + pid_t pid; + int r; + + BLOCK_SIGNALS(SIGCHLD); + + /* Due to the possiblity of a umount operation hanging, we + * fork a child process and set a timeout. If the timeout + * lapses, the assumption is that that particular umount + * failed. */ + pid = fork(); + if (pid < 0) + return log_error_errno(errno, "Failed to fork: %m"); + + if (pid == 0) { + log_info("Unmounting '%s'.", m->path); + + /* Start the mount operation here in the child Using MNT_FORCE + * causes some filesystems (e.g. FUSE and NFS and other network + * filesystems) to abort any pending requests and return -EIO + * rather than blocking indefinitely. If the filesysten is + * "busy", this may allow processes to die, thus making the + * filesystem less busy so the unmount might succeed (rather + * then return EBUSY).*/ + r = umount2(m->path, MNT_FORCE); + if (r < 0) + log_error_errno(errno, "Failed to unmount %s: %m", m->path); + + _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS); + } + + r = wait_for_terminate_with_timeout(pid, DEFAULT_TIMEOUT_USEC); + if (r == -ETIMEDOUT) { + log_error_errno(errno, "Unmounting '%s' - timed out, issuing SIGKILL to PID "PID_FMT".", m->path, pid); + (void) kill(pid, SIGKILL); + } else if (r < 0) + log_error_errno(r, "Failed to wait for process: %m"); + + return r; +} + +/* This includes remounting readonly, which changes the kernel mount options. + * Therefore the list passed to this function is invalidated, and should not be reused. */ + +static int mount_points_list_umount(MountPoint **head, bool *changed) { + MountPoint *m; int n_failed = 0; assert(head); - LIST_FOREACH_SAFE(mount_point, m, n, *head) { + LIST_FOREACH(mount_point, m, *head) { + bool mount_is_readonly; + + mount_is_readonly = fstab_test_yes_no_option(m->options, "ro\0rw\0"); /* If we are in a container, don't attempt to read-only mount anything as that brings no real @@ -395,9 +474,12 @@ static int mount_points_list_umount(MountPoint **head, bool *changed, bool log_e the superblock here, not the bind mount. If the filesystem is a network fs, also skip the remount. It brings no value (we cannot leave - a "dirty fs") and could hang if the network is down. */ + a "dirty fs") and could hang if the network is down. + Note that umount2() is more careful and will not + hang because of the network being down. */ if (detect_container() <= 0 && - !fstype_is_network(m->type)) { + !fstype_is_network(m->type) && + !mount_is_readonly) { _cleanup_free_ char *options = NULL; /* MS_REMOUNT requires that the data parameter * should be the same from the original mount @@ -421,13 +503,15 @@ static int mount_points_list_umount(MountPoint **head, bool *changed, bool log_e * explicitly remount the super block of that * alias read-only we hence should be * relatively safe regarding keeping dirty an fs - * we cannot otherwise see. */ - log_info("Remounting '%s' read-only with options '%s'.", m->path, options); - if (mount(NULL, m->path, NULL, MS_REMOUNT|MS_RDONLY, options) < 0) { - if (log_error) - log_notice_errno(errno, "Failed to remount '%s' read-only: %m", m->path); + * we cannot otherwise see. + * + * Since the remount can hang in the instance of + * remote filesystems, we remount asynchronously + * and skip the subsequent umount if it fails */ + if (remount_with_timeout(m, options, &n_failed) < 0) { if (nonunmountable_path(m->path)) n_failed++; + continue; } } @@ -437,19 +521,12 @@ static int mount_points_list_umount(MountPoint **head, bool *changed, bool log_e if (nonunmountable_path(m->path)) continue; - /* Trying to umount. We don't force here since we rely - * on busy NFS and FUSE file systems to return EBUSY - * until we closed everything on top of them. */ - log_info("Unmounting %s.", m->path); - if (umount2(m->path, 0) == 0) { + /* Trying to umount */ + if (umount_with_timeout(m, changed) < 0) + n_failed++; + else { if (changed) *changed = true; - - mount_point_free(head, m); - } else { - if (log_error) - log_warning_errno(errno, "Could not unmount %s: %m", m->path); - n_failed++; } } @@ -550,9 +627,8 @@ static int dm_points_list_detach(MountPoint **head, bool *changed) { return n_failed; } -int umount_all(bool *changed) { +static int umount_all_once(bool *changed) { int r; - bool umount_changed; LIST_HEAD(MountPoint, mp_list_head); LIST_HEAD_INIT(mp_list_head); @@ -560,22 +636,29 @@ int umount_all(bool *changed) { if (r < 0) goto end; - /* retry umount, until nothing can be umounted anymore */ + r = mount_points_list_umount(&mp_list_head, changed); + + end: + mount_points_list_free(&mp_list_head); + + return r; +} + +int umount_all(bool *changed) { + bool umount_changed; + int r; + + /* Retry umount, until nothing can be umounted anymore. Mounts are + * processed in order, newest first. The retries are needed when + * an old mount has been moved, to a path inside a newer mount. */ do { umount_changed = false; - mount_points_list_umount(&mp_list_head, &umount_changed, false); + r = umount_all_once(&umount_changed); if (umount_changed) *changed = true; - } while (umount_changed); - /* umount one more time with logging enabled */ - r = mount_points_list_umount(&mp_list_head, &umount_changed, true); - - end: - mount_points_list_free(&mp_list_head); - return r; } |