summaryrefslogtreecommitdiff
path: root/src/core/umount.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/umount.c')
-rw-r--r--src/core/umount.c167
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;
}