]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
shutdown: Move busy mounts to not block parent mounts
authorValentin David <valentin.david@canonical.com>
Tue, 8 Nov 2022 12:53:44 +0000 (13:53 +0100)
committerValentin David <valentin.david@canonical.com>
Wed, 11 Jan 2023 14:30:42 +0000 (15:30 +0100)
There is a case that confuses systemd-shutdown: a filesystem has been moved to
a mount point which is part of another filesystem from an image from that
former filesystem. systemd-shutdown cannot unmount any of those two
filesystems. It needs first to move the filesystem containing the image of the
other out of the tree of that image.

Here we move leaf mount points when they are busy so that they do not block
parent mounts. We can only move leafs at each iteration since moving mount
points also move sub mount points which would invalidate we read from
`/proc/self/mountinfo`.

src/shared/libmount-util.c
src/shared/libmount-util.h
src/shutdown/umount.c
src/shutdown/umount.h

index bcb21b5abadccbace0e95e19d60fbf100848c1ce..381890473326fa89038d14f48e71718282458f44 100644 (file)
@@ -38,3 +38,22 @@ int libmount_parse(
         *ret_iter = TAKE_PTR(iter);
         return 0;
 }
+
+int libmount_is_leaf(
+                struct libmnt_table *table,
+                struct libmnt_fs *fs) {
+        int r;
+
+        _cleanup_(mnt_free_iterp) struct libmnt_iter *iter_children = NULL;
+        iter_children = mnt_new_iter(MNT_ITER_FORWARD);
+        if (!iter_children)
+                return log_oom();
+
+        /* We care only whether it exists, it is unused */
+        _unused_ struct libmnt_fs *child;
+        r = mnt_table_next_child_fs(table, iter_children, fs, &child);
+        if (r < 0)
+                return r;
+
+        return r == 1;
+}
index 6f6f36ad527a7a95e236cdada6ff44247bf05ab4..2f789e7426f53e0d2d2a9c5712f10441d645c67a 100644 (file)
@@ -14,3 +14,7 @@ int libmount_parse(
                 FILE *source,
                 struct libmnt_table **ret_table,
                 struct libmnt_iter **ret_iter);
+
+int libmount_is_leaf(
+                struct libmnt_table *table,
+                struct libmnt_fs *fs);
index 1326b32f6af4681a9eed272528115d508e910535..61bd9d26012ae2d0a895fb3e3a6f40c4683295b9 100644 (file)
@@ -23,6 +23,7 @@
 
 #include "alloc-util.h"
 #include "blockdev-util.h"
+#include "chase-symlinks.h"
 #include "constants.h"
 #include "device-util.h"
 #include "dirent-util.h"
 #include "fs-util.h"
 #include "fstab-util.h"
 #include "libmount-util.h"
+#include "mkdir.h"
 #include "mount-setup.h"
 #include "mount-util.h"
 #include "mountpoint-util.h"
 #include "parse-util.h"
 #include "path-util.h"
 #include "process-util.h"
+#include "random-util.h"
 #include "signal-util.h"
+#include "stat-util.h"
 #include "string-util.h"
 #include "strv.h"
 #include "sync-util.h"
@@ -155,6 +159,11 @@ int mount_points_list_get(const char *mountinfo, MountPoint **head) {
                 if (!m)
                         return log_oom();
 
+                r = libmount_is_leaf(table, fs);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to get children mounts for %s from %s: %m", path, mountinfo ?: "/proc/self/mountinfo");
+                bool leaf = r;
+
                 *m = (MountPoint) {
                         .remount_options = remount_options,
                         .remount_flags = remount_flags,
@@ -163,6 +172,7 @@ int mount_points_list_get(const char *mountinfo, MountPoint **head) {
                         /* Unmount sysfs/procfs/… lazily, since syncing doesn't matter there, and it's OK if
                          * something keeps an fd open to it. */
                         .umount_lazily = is_api_vfs,
+                        .leaf = leaf,
                 };
 
                 m->path = strdup(path);
@@ -709,7 +719,8 @@ static int umount_with_timeout(MountPoint *m, bool last_try) {
 /* 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, bool last_try) {
-        int n_failed = 0;
+        int n_failed = 0, r;
+        _cleanup_free_ char *resolved_mounts_path = NULL;
 
         assert(head);
         assert(changed);
@@ -744,10 +755,63 @@ static int mount_points_list_umount(MountPoint **head, bool *changed, bool last_
                         continue;
 
                 /* Trying to umount */
-                if (umount_with_timeout(m, last_try) < 0)
+                r = umount_with_timeout(m, last_try);
+                if (r < 0)
                         n_failed++;
                 else
                         *changed = true;
+
+                /* If a mount is busy, we move it to not keep parent mount points busy.
+                 * If a mount point is not a leaf, moving it would invalidate our mount table.
+                 * More moving will occur in next iteration with a fresh mount table.
+                 */
+                if (r != -EBUSY || !m->leaf)
+                        continue;
+
+                _cleanup_free_ char *dirname = NULL;
+
+                r = path_extract_directory(m->path, &dirname);
+                if (r < 0) {
+                        n_failed++;
+                        log_full_errno(last_try ? LOG_ERR : LOG_INFO, r, "Cannot find directory for %s: %m", m->path);
+                        continue;
+                }
+
+                /* We need to canonicalize /run/shutdown/mounts. We cannot compare inodes, since /run
+                 * might be bind mounted somewhere we want to unmount. And we need to move all mounts in
+                 * /run/shutdown/mounts from there.
+                 */
+                if (!resolved_mounts_path)
+                        (void) chase_symlinks("/run/shutdown/mounts", NULL, 0, &resolved_mounts_path, NULL);
+                if (!path_equal(dirname, resolved_mounts_path)) {
+                        char newpath[STRLEN("/run/shutdown/mounts/") + 16 + 1];
+
+                        xsprintf(newpath, "/run/shutdown/mounts/%016" PRIx64, random_u64());
+
+                        /* on error of is_dir, assume directory */
+                        if (is_dir(m->path, true) != 0) {
+                                r = mkdir_p(newpath, 0000);
+                                if (r < 0) {
+                                        log_full_errno(last_try ? LOG_ERR : LOG_INFO, r, "Could not create directory %s: %m", newpath);
+                                        continue;
+                                }
+                        } else {
+                                r = touch_file(newpath, /* parents= */ true, USEC_INFINITY, UID_INVALID, GID_INVALID, 0700);
+                                if (r < 0) {
+                                        log_full_errno(last_try ? LOG_ERR : LOG_INFO, r, "Could not create file %s: %m", newpath);
+                                        continue;
+                                }
+                        }
+
+                        log_info("Moving mount %s to %s.", m->path, newpath);
+
+                        r = RET_NERRNO(mount(m->path, newpath, NULL, MS_MOVE, NULL));
+                        if (r < 0) {
+                                n_failed++;
+                                log_full_errno(last_try ? LOG_ERR : LOG_INFO, r, "Could not move %s to %s: %m", m->path, newpath);
+                        } else
+                                *changed = true;
+                }
         }
 
         return n_failed;
index 6261e719b0c67f7ba7a91f2e2275809822a4a0a5..a742ac0af55548009b4a8d6eaff044111b18d6ff 100644 (file)
@@ -20,6 +20,7 @@ typedef struct MountPoint {
         unsigned long remount_flags;
         bool try_remount_ro;
         bool umount_lazily;
+        bool leaf;
         dev_t devnum;
         LIST_FIELDS(struct MountPoint, mount_point);
 } MountPoint;