diff options
author | Lennart Poettering <lennart@poettering.net> | 2022-09-06 16:40:32 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-09-06 16:40:32 +0200 |
commit | 4acbe1f6cc7fbab22f7a016db58a035c80e6dd7a (patch) | |
tree | 46b8c80a08f5fa199f9505a8c20edda91377c45d | |
parent | 802e7f72341a8c7a2e4c9a50d3853d5c64c7ecec (diff) | |
parent | 05768ae36b4af0c18b4837fa6041fd66a05f5e36 (diff) | |
download | systemd-4acbe1f6cc7fbab22f7a016db58a035c80e6dd7a.tar.gz |
Merge pull request #24425 from poettering/shutdown-lazily
shutdown: lazily umount all API vfs
-rw-r--r-- | src/shutdown/umount.c | 170 | ||||
-rw-r--r-- | src/shutdown/umount.h | 3 |
2 files changed, 88 insertions, 85 deletions
diff --git a/src/shutdown/umount.c b/src/shutdown/umount.c index 1e691379a4..e650b82170 100644 --- a/src/shutdown/umount.c +++ b/src/shutdown/umount.c @@ -76,16 +76,15 @@ int mount_points_list_get(const char *mountinfo, MountPoint **head) { return log_error_errno(r, "Failed to parse %s: %m", mountinfo ?: "/proc/self/mountinfo"); for (;;) { + _cleanup_free_ char *options = NULL, *remount_options = NULL; struct libmnt_fs *fs; const char *path, *fstype; - _cleanup_free_ char *options = NULL; unsigned long remount_flags = 0u; - _cleanup_free_ char *remount_options = NULL; - bool try_remount_ro; + bool try_remount_ro, is_api_vfs; _cleanup_free_ MountPoint *m = NULL; r = mnt_table_next_fs(table, iter, &fs); - if (r == 1) + if (r == 1) /* EOF */ break; if (r < 0) return log_error_errno(r, "Failed to get next entry from %s: %m", mountinfo ?: "/proc/self/mountinfo"); @@ -96,52 +95,45 @@ int mount_points_list_get(const char *mountinfo, MountPoint **head) { fstype = mnt_fs_get_fstype(fs); - /* Combine the generic VFS options with the FS-specific - * options. Duplicates are not a problem here, because the only - * options that should come up twice are typically ro/rw, which - * are turned into MS_RDONLY or the inversion of it. + /* Combine the generic VFS options with the FS-specific options. Duplicates are not a problem + * here, because the only options that should come up twice are typically ro/rw, which are + * turned into MS_RDONLY or the inversion of it. * - * Even if there are duplicates later in mount_option_mangle() - * they shouldn't hurt anyways as they override each other. - */ + * Even if there are duplicates later in mount_option_mangle() they shouldn't hurt anyways as + * they override each other. */ if (!strextend_with_separator(&options, ",", mnt_fs_get_vfs_options(fs))) return log_oom(); if (!strextend_with_separator(&options, ",", mnt_fs_get_fs_options(fs))) return log_oom(); - /* Ignore mount points we can't unmount because they - * are API or because we are keeping them open (like - * /dev/console). Also, ignore all mounts below API - * file systems, since they are likely virtual too, - * and hence not worth spending time on. Also, in - * unprivileged containers we might lack the rights to - * unmount these things, hence don't bother. */ + /* Ignore mount points we can't unmount because they are API or because we are keeping them + * open (like /dev/console). Also, ignore all mounts below API file systems, since they are + * likely virtual too, and hence not worth spending time on. Also, in unprivileged containers + * we might lack the rights to unmount these things, hence don't bother. */ if (mount_point_is_api(path) || mount_point_ignore(path) || PATH_STARTSWITH_SET(path, "/dev", "/sys", "/proc")) continue; - /* If we are in a container, don't attempt to - * read-only mount anything as that brings no real - * benefits, but might confuse the host, as we remount - * the superblock here, not the bind mount. + is_api_vfs = fstype_is_api_vfs(fstype); + + /* If we are in a container, don't attempt to read-only mount anything as that brings no real + * benefits, but might confuse the host, as we remount 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. - * Note that umount2() is more careful and will not - * hang because of the network being down. */ + * 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. Note that umount2() is more + * careful and will not hang because of the network being down. */ try_remount_ro = detect_container() <= 0 && !fstype_is_network(fstype) && - !fstype_is_api_vfs(fstype) && + !is_api_vfs && !fstype_is_ro(fstype) && !fstab_test_yes_no_option(options, "ro\0rw\0"); if (try_remount_ro) { - /* mount(2) states that mount flags and options need to be exactly the same - * as they were when the filesystem was mounted, except for the desired - * changes. So we reconstruct both here and adjust them for the later - * remount call too. */ + /* mount(2) states that mount flags and options need to be exactly the same as they + * were when the filesystem was mounted, except for the desired changes. So we + * reconstruct both here and adjust them for the later remount call too. */ r = mnt_fs_get_propagation(fs, &remount_flags); if (r < 0) { @@ -160,17 +152,25 @@ int mount_points_list_get(const char *mountinfo, MountPoint **head) { remount_flags = (remount_flags|MS_REMOUNT|MS_RDONLY) & ~MS_BIND; } - m = new0(MountPoint, 1); + m = new(MountPoint, 1); if (!m) return log_oom(); + *m = (MountPoint) { + .remount_options = remount_options, + .remount_flags = remount_flags, + .try_remount_ro = try_remount_ro, + + /* 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, + }; + m->path = strdup(path); if (!m->path) return log_oom(); - m->remount_options = TAKE_PTR(remount_options); - m->remount_flags = remount_flags; - m->try_remount_ro = try_remount_ro; + TAKE_PTR(remount_options); LIST_PREPEND(mount_point, *head, TAKE_PTR(m)); } @@ -202,7 +202,7 @@ int swap_list_get(const char *swaps, MountPoint **head) { const char *source; r = mnt_table_next_fs(t, i, &fs); - if (r == 1) + if (r == 1) /* EOF */ break; if (r < 0) return log_error_errno(r, "Failed to get next entry from %s: %m", swaps ?: "/proc/swaps"); @@ -377,8 +377,8 @@ static int md_list_get(MountPoint **head) { continue; } - /* MD "containers" are a special type of MD devices, used for external metadata. - * Since it doesn't provide RAID functionality in itself we don't need to stop it. */ + /* MD "containers" are a special type of MD devices, used for external metadata. Since it + * doesn't provide RAID functionality in itself we don't need to stop it. */ if (streq(md_level, "container")) continue; @@ -529,11 +529,12 @@ static bool nonunmountable_path(const char *path) { } static void log_umount_blockers(const char *mnt) { + _cleanup_free_ char *blockers = NULL; + int r; + _cleanup_closedir_ DIR *dir = opendir("/proc"); if (!dir) - return (void) log_warning_errno(errno, "opendir(/proc) failed: %m"); - - _cleanup_free_ char *blockers = NULL; + return (void) log_warning_errno(errno, "Failed to open /proc/: %m"); FOREACH_DIRENT_ALL(de, dir, break) { if (!IN_SET(de->d_type, DT_DIR, DT_UNKNOWN)) @@ -543,37 +544,50 @@ static void log_umount_blockers(const char *mnt) { if (parse_pid(de->d_name, &pid) < 0) continue; - _cleanup_closedir_ DIR *pid_dir = xopendirat(dirfd(dir), de->d_name, 0); - if (!pid_dir) - continue; + _cleanup_free_ char *fdp = path_join(de->d_name, "fd"); + if (!fdp) + return (void) log_oom(); - _cleanup_closedir_ DIR *fd_dir = xopendirat(dirfd(pid_dir), "fd", 0); - if (!fd_dir) + _cleanup_closedir_ DIR *fd_dir = xopendirat(dirfd(dir), fdp, 0); + if (!fd_dir) { + if (errno != ENOENT) /* process gone by now? */ + log_debug_errno(errno, "Failed to open /proc/%s/, ignoring: %m",fdp); continue; + } + bool culprit = false; FOREACH_DIRENT(fd_de, fd_dir, break) { - _cleanup_free_ char *open_file = NULL, *comm = NULL; - - if (readlinkat_malloc(dirfd(fd_dir), fd_de->d_name, &open_file) < 0) - continue; + _cleanup_free_ char *open_file = NULL; - if (!path_startswith(open_file, mnt)) + r = readlinkat_malloc(dirfd(fd_dir), fd_de->d_name, &open_file); + if (r < 0) { + if (r != -ENOENT) /* fd closed by now */ + log_debug_errno(r, "Failed to read link /proc/%s/%s, ignoring: %m", fdp, fd_de->d_name); continue; + } - if (PATH_STARTSWITH_SET(open_file, "/dev", "/sys", "/proc")) - continue; + if (path_startswith(open_file, mnt)) { + culprit = true; + break; + } + } - if (get_process_comm(pid, &comm) < 0) - continue; + if (!culprit) + continue; - if (!strextend_with_separator(&blockers, ", ", comm)) - return (void) log_oom(); + _cleanup_free_ char *comm = NULL; + r = get_process_comm(pid, &comm); + if (r < 0) { + if (r != -ESRCH) /* process gone by now */ + log_debug_errno(r, "Failed to read process name of PID " PID_FMT ": %m", pid); + continue; + } - if (!strextend(&blockers, "(", de->d_name, ")")) - return (void) log_oom(); + if (!strextend_with_separator(&blockers, ", ", comm)) + return (void) log_oom(); - break; - } + if (!strextend(&blockers, "(", de->d_name, ")")) + return (void) log_oom(); } if (blockers) @@ -635,30 +649,19 @@ static int umount_with_timeout(MountPoint *m, bool last_try) { if (r == 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 - * than return EBUSY). */ - r = RET_NERRNO(umount2(m->path, MNT_FORCE)); + /* 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 than return EBUSY). */ + r = RET_NERRNO(umount2(m->path, + UMOUNT_NOFOLLOW | /* Don't follow symlinks: this should never happen unless our mount list was wrong */ + (m->umount_lazily ? MNT_DETACH : MNT_FORCE))); if (r < 0) { log_full_errno(last_try ? LOG_ERR : LOG_INFO, r, "Failed to unmount %s: %m", m->path); if (r == -EBUSY && last_try) log_umount_blockers(m->path); - - /* If API filesystems under /oldroot cannot be unmounted we can still lazily unmount - * them to unblock /oldroot. They serve no function to us anymore and should be - * memory-only and hence safe to unmount like this. */ - if (in_initrd() && - PATH_STARTSWITH_SET(m->path, "/oldroot/dev", "/oldroot/proc", "/oldroot/sys")) { - log_info("Lazily unmounting '%s' instead.", m->path); - r = umount2(m->path, MNT_FORCE | MNT_DETACH); - if (r < 0) - log_error_errno(errno, "Failed to lazily unmount %s: %m", m->path); - } } _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS); @@ -855,9 +858,8 @@ int umount_all(bool *changed, bool last_try) { assert(changed); - /* 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. */ + /* 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; diff --git a/src/shutdown/umount.h b/src/shutdown/umount.h index 618b754011..a4154c9099 100644 --- a/src/shutdown/umount.h +++ b/src/shutdown/umount.h @@ -18,7 +18,8 @@ typedef struct MountPoint { char *path; char *remount_options; unsigned long remount_flags; - bool try_remount_ro; + bool try_remount_ro:1; + bool umount_lazily:1; dev_t devnum; LIST_FIELDS(struct MountPoint, mount_point); } MountPoint; |