diff options
Diffstat (limited to 'src/core/umount.c')
-rw-r--r-- | src/core/umount.c | 198 |
1 files changed, 110 insertions, 88 deletions
diff --git a/src/core/umount.c b/src/core/umount.c index de826edd4d..ab2d4ffa62 100644 --- a/src/core/umount.c +++ b/src/core/umount.c @@ -48,8 +48,9 @@ typedef struct MountPoint { char *path; - char *options; - char *type; + char *remount_options; + unsigned long remount_flags; + bool try_remount_ro; dev_t devnum; LIST_FIELDS(struct MountPoint, mount_point); } MountPoint; @@ -61,8 +62,7 @@ static void mount_point_free(MountPoint **head, MountPoint *m) { LIST_REMOVE(mount_point, *head, m); free(m->path); - free(m->options); - free(m->type); + free(m->remount_options); free(m); } @@ -85,8 +85,7 @@ static int mount_points_list_get(MountPoint **head) { return -errno; for (i = 1;; i++) { - _cleanup_free_ char *path = NULL, *options = NULL, *type = NULL; - char *p = NULL; + _cleanup_free_ char *path = NULL, *options = NULL, *flags = NULL, *type = NULL, *p = NULL; MountPoint *m; int k; @@ -96,15 +95,15 @@ static int mount_points_list_get(MountPoint **head) { "%*s " /* (3) major:minor */ "%*s " /* (4) root */ "%ms " /* (5) mount point */ - "%*s" /* (6) mount flags */ + "%ms" /* (6) mount flags */ "%*[^-]" /* (7) optional fields */ "- " /* (8) separator */ "%ms " /* (9) file system type */ "%*s" /* (10) mount source */ "%ms" /* (11) mount options */ "%*[^\n]", /* some rubbish at the end */ - &path, &type, &options); - if (k != 3) { + &path, &flags, &type, &options); + if (k != 4) { if (k == EOF) break; @@ -127,22 +126,53 @@ static int mount_points_list_get(MountPoint **head) { mount_point_ignore(p) || path_startswith(p, "/dev") || path_startswith(p, "/sys") || - path_startswith(p, "/proc")) { - free(p); + path_startswith(p, "/proc")) continue; - } m = new0(MountPoint, 1); - if (!m) { - free(p); + if (!m) return -ENOMEM; - } - m->path = p; - m->options = options; - options = NULL; - m->type = type; - type = NULL; + free_and_replace(m->path, p); + + /* 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. */ + m->try_remount_ro = detect_container() <= 0 && + !fstype_is_network(type) && + !fstype_is_api_vfs(type) && + !fstype_is_ro(type) && + !fstab_test_yes_no_option(options, "ro\0rw\0"); + + if (m->try_remount_ro) { + _cleanup_free_ char *unknown_flags = NULL; + + /* 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 = mount_option_mangle(flags, 0, &m->remount_flags, &unknown_flags); + if (r < 0) + return r; + if (!isempty(unknown_flags)) + log_warning("Ignoring unknown mount flags '%s'.", unknown_flags); + + r = mount_option_mangle(options, m->remount_flags, &m->remount_flags, &m->remount_options); + if (r < 0) + return r; + + /* MS_BIND is special. If it is provided it will only make the mount-point + * read-only. If left out, the super block itself is remounted, which we want. */ + m->remount_flags = (m->remount_flags|MS_REMOUNT|MS_RDONLY) & ~MS_BIND; + } LIST_PREPEND(mount_point, *head, m); } @@ -333,6 +363,8 @@ static int delete_loopback(const char *device) { _cleanup_close_ int fd = -1; int r; + assert(device); + fd = open(device, O_RDONLY|O_CLOEXEC); if (fd < 0) return errno == ENOENT ? 0 : -errno; @@ -382,12 +414,14 @@ static bool nonunmountable_path(const char *path) { || path_startswith(path, "/run/initramfs"); } -static int remount_with_timeout(MountPoint *m, char *options, int *n_failed) { +static int remount_with_timeout(MountPoint *m, int umount_log_level) { pid_t pid; int r; BLOCK_SIGNALS(SIGCHLD); + assert(m); + /* 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 @@ -396,12 +430,12 @@ static int remount_with_timeout(MountPoint *m, char *options, int *n_failed) { if (r < 0) return r; if (r == 0) { - log_info("Remounting '%s' read-only in with options '%s'.", m->path, options); + log_info("Remounting '%s' read-only in with options '%s'.", m->path, m->remount_options); /* Start the mount operation here in the child */ - r = mount(NULL, m->path, NULL, MS_REMOUNT|MS_RDONLY, options); + r = mount(NULL, m->path, NULL, m->remount_flags, m->remount_options); if (r < 0) - log_error_errno(errno, "Failed to remount '%s' read-only: %m", m->path); + log_full_errno(umount_log_level, errno, "Failed to remount '%s' read-only: %m", m->path); _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS); } @@ -411,19 +445,21 @@ static int remount_with_timeout(MountPoint *m, char *options, int *n_failed) { log_error_errno(r, "Remounting '%s' timed out, issuing SIGKILL to PID " PID_FMT ".", m->path, pid); (void) kill(pid, SIGKILL); } else if (r == -EPROTO) - log_error_errno(r, "Remounting '%s' failed abnormally, child process " PID_FMT " aborted or exited non-zero.", m->path, pid); + log_debug_errno(r, "Remounting '%s' failed abnormally, child process " PID_FMT " aborted or exited non-zero.", m->path, pid); else if (r < 0) log_error_errno(r, "Remounting '%s' failed unexpectedly, couldn't wait for child process " PID_FMT ": %m", m->path, pid); return r; } -static int umount_with_timeout(MountPoint *m, bool *changed) { +static int umount_with_timeout(MountPoint *m, int umount_log_level) { pid_t pid; int r; BLOCK_SIGNALS(SIGCHLD); + assert(m); + /* 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 @@ -443,7 +479,7 @@ static int umount_with_timeout(MountPoint *m, bool *changed) { * then return EBUSY).*/ r = umount2(m->path, MNT_FORCE); if (r < 0) - log_error_errno(errno, "Failed to unmount %s: %m", m->path); + log_full_errno(umount_log_level, errno, "Failed to unmount %s: %m", m->path); _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS); } @@ -453,7 +489,7 @@ static int umount_with_timeout(MountPoint *m, bool *changed) { log_error_errno(r, "Unmounting '%s' timed out, issuing SIGKILL to PID " PID_FMT ".", m->path, pid); (void) kill(pid, SIGKILL); } else if (r == -EPROTO) - log_error_errno(r, "Unmounting '%s' failed abnormally, child process " PID_FMT " aborted or exited non-zero.", m->path, pid); + log_debug_errno(r, "Unmounting '%s' failed abnormally, child process " PID_FMT " aborted or exited non-zero.", m->path, pid); else if (r < 0) log_error_errno(r, "Unmounting '%s' failed unexpectedly, couldn't wait for child process " PID_FMT ": %m", m->path, pid); @@ -462,38 +498,15 @@ static int umount_with_timeout(MountPoint *m, bool *changed) { /* 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) { +static int mount_points_list_umount(MountPoint **head, bool *changed, int umount_log_level) { MountPoint *m; int n_failed = 0; assert(head); + assert(changed); 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 - 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 (detect_container() <= 0 && - !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 - * except for the desired changes. Since we want - * to remount read-only, we should filter out - * rw (and ro too, because it confuses the kernel) */ - (void) fstab_filter_options(m->options, "rw\0ro\0", NULL, NULL, &options); - + if (m->try_remount_ro) { /* We always try to remount directories * read-only first, before we go on and umount * them. @@ -508,16 +521,19 @@ static int mount_points_list_umount(MountPoint **head, bool *changed) { * somehwere else via a bind mount. If we * explicitly remount the super block of that * alias read-only we hence should be - * relatively safe regarding keeping dirty an fs + * relatively safe regarding keeping a dirty fs * 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)) + * and skip the subsequent umount if it fails. */ + if (remount_with_timeout(m, umount_log_level) < 0) { + /* Remount failed, but try unmounting anyway, + * unless this is a mount point we want to skip. */ + if (nonunmountable_path(m->path)) { n_failed++; - continue; + continue; + } } } @@ -528,12 +544,10 @@ static int mount_points_list_umount(MountPoint **head, bool *changed) { continue; /* Trying to umount */ - if (umount_with_timeout(m, changed) < 0) + if (umount_with_timeout(m, umount_log_level) < 0) n_failed++; - else { - if (changed) - *changed = true; - } + else + *changed = true; } return n_failed; @@ -544,13 +558,12 @@ static int swap_points_list_off(MountPoint **head, bool *changed) { int n_failed = 0; assert(head); + assert(changed); LIST_FOREACH_SAFE(mount_point, m, n, *head) { log_info("Deactivating swap %s.", m->path); if (swapoff(m->path) == 0) { - if (changed) - *changed = true; - + *changed = true; mount_point_free(head, m); } else { log_warning_errno(errno, "Could not deactivate swap %s: %m", m->path); @@ -561,12 +574,13 @@ static int swap_points_list_off(MountPoint **head, bool *changed) { return n_failed; } -static int loopback_points_list_detach(MountPoint **head, bool *changed) { +static int loopback_points_list_detach(MountPoint **head, bool *changed, int umount_log_level) { MountPoint *m, *n; int n_failed = 0, k; struct stat root_st; assert(head); + assert(changed); k = lstat("/", &root_st); @@ -585,12 +599,12 @@ static int loopback_points_list_detach(MountPoint **head, bool *changed) { log_info("Detaching loopback %s.", m->path); r = delete_loopback(m->path); if (r >= 0) { - if (r > 0 && changed) + if (r > 0) *changed = true; mount_point_free(head, m); } else { - log_warning_errno(errno, "Could not detach loopback %s: %m", m->path); + log_full_errno(umount_log_level, errno, "Could not detach loopback %s: %m", m->path); n_failed++; } } @@ -598,12 +612,13 @@ static int loopback_points_list_detach(MountPoint **head, bool *changed) { return n_failed; } -static int dm_points_list_detach(MountPoint **head, bool *changed) { +static int dm_points_list_detach(MountPoint **head, bool *changed, int umount_log_level) { MountPoint *m, *n; int n_failed = 0, r; dev_t rootdev; assert(head); + assert(changed); r = get_block_device("/", &rootdev); if (r <= 0) @@ -611,21 +626,18 @@ static int dm_points_list_detach(MountPoint **head, bool *changed) { LIST_FOREACH_SAFE(mount_point, m, n, *head) { - if (major(rootdev) != 0) - if (rootdev == m->devnum) { - n_failed ++; - continue; - } + if (major(rootdev) != 0 && rootdev == m->devnum) { + n_failed ++; + continue; + } log_info("Detaching DM %u:%u.", major(m->devnum), minor(m->devnum)); r = delete_dm(m->devnum); if (r >= 0) { - if (changed) - *changed = true; - + *changed = true; mount_point_free(head, m); } else { - log_warning_errno(errno, "Could not detach DM %s: %m", m->path); + log_full_errno(umount_log_level, errno, "Could not detach DM %s: %m", m->path); n_failed++; } } @@ -633,16 +645,18 @@ static int dm_points_list_detach(MountPoint **head, bool *changed) { return n_failed; } -static int umount_all_once(bool *changed) { +static int umount_all_once(bool *changed, int umount_log_level) { int r; LIST_HEAD(MountPoint, mp_list_head); + assert(changed); + LIST_HEAD_INIT(mp_list_head); r = mount_points_list_get(&mp_list_head); if (r < 0) goto end; - r = mount_points_list_umount(&mp_list_head, changed); + r = mount_points_list_umount(&mp_list_head, changed, umount_log_level); end: mount_points_list_free(&mp_list_head); @@ -650,17 +664,19 @@ static int umount_all_once(bool *changed) { return r; } -int umount_all(bool *changed) { +int umount_all(bool *changed, int umount_log_level) { bool umount_changed; int r; + 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. */ do { umount_changed = false; - r = umount_all_once(&umount_changed); + r = umount_all_once(&umount_changed, umount_log_level); if (umount_changed) *changed = true; } while (umount_changed); @@ -672,6 +688,8 @@ int swapoff_all(bool *changed) { int r; LIST_HEAD(MountPoint, swap_list_head); + assert(changed); + LIST_HEAD_INIT(swap_list_head); r = swap_list_get(&swap_list_head); @@ -686,17 +704,19 @@ int swapoff_all(bool *changed) { return r; } -int loopback_detach_all(bool *changed) { +int loopback_detach_all(bool *changed, int umount_log_level) { int r; LIST_HEAD(MountPoint, loopback_list_head); + assert(changed); + LIST_HEAD_INIT(loopback_list_head); r = loopback_list_get(&loopback_list_head); if (r < 0) goto end; - r = loopback_points_list_detach(&loopback_list_head, changed); + r = loopback_points_list_detach(&loopback_list_head, changed, umount_log_level); end: mount_points_list_free(&loopback_list_head); @@ -704,17 +724,19 @@ int loopback_detach_all(bool *changed) { return r; } -int dm_detach_all(bool *changed) { +int dm_detach_all(bool *changed, int umount_log_level) { int r; LIST_HEAD(MountPoint, dm_list_head); + assert(changed); + LIST_HEAD_INIT(dm_list_head); r = dm_list_get(&dm_list_head); if (r < 0) goto end; - r = dm_points_list_detach(&dm_list_head, changed); + r = dm_points_list_detach(&dm_list_head, changed, umount_log_level); end: mount_points_list_free(&dm_list_head); |