]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/core/umount.c
Add SPDX license identifiers to source files under the LGPL
[thirdparty/systemd.git] / src / core / umount.c
index 1e5459ed80c83ba532d7d6bd301d4d335d71b40e..cd195e12af6fd14d419837ea5e1a5f08d37230c5 100644 (file)
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
@@ -19,7 +20,6 @@
 
 #include <errno.h>
 #include <fcntl.h>
-#include <linux/dm-ioctl.h>
 #include <linux/loop.h>
 #include <string.h>
 #include <sys/mount.h>
 #include "escape.h"
 #include "fd-util.h"
 #include "fstab-util.h"
+#include "linux-3.13/dm-ioctl.h"
 #include "list.h"
 #include "mount-setup.h"
 #include "path-util.h"
 #include "string-util.h"
 #include "udev-util.h"
 #include "umount.h"
+#include "mount-util.h"
 #include "util.h"
 #include "virt.h"
 
 typedef struct MountPoint {
         char *path;
         char *options;
+        char *type;
         dev_t devnum;
         LIST_FIELDS(struct MountPoint, mount_point);
 } MountPoint;
@@ -76,7 +79,7 @@ static int mount_points_list_get(MountPoint **head) {
                 return -errno;
 
         for (i = 1;; i++) {
-                _cleanup_free_ char *path = NULL, *options = NULL;
+                _cleanup_free_ char *path = NULL, *options = NULL, *type = NULL;
                 char *p = NULL;
                 MountPoint *m;
                 int k;
@@ -90,12 +93,12 @@ static int mount_points_list_get(MountPoint **head) {
                            "%*s"        /* (6) mount flags */
                            "%*[^-]"     /* (7) optional fields */
                            "- "         /* (8) separator */
-                           "%*s "       /* (9) file system type */
+                           "%ms "       /* (9) file system type */
                            "%*s"        /* (10) mount source */
                            "%ms"        /* (11) mount options */
                            "%*[^\n]",   /* some rubbish at the end */
-                           &path, &options);
-                if (k != 2) {
+                           &path, &type, &options);
+                if (k != 3) {
                         if (k == EOF)
                                 break;
 
@@ -132,6 +135,8 @@ static int mount_points_list_get(MountPoint **head) {
                 m->path = p;
                 m->options = options;
                 options = NULL;
+                m->type = type;
+                type = NULL;
 
                 LIST_PREPEND(mount_point, *head, m);
         }
@@ -344,39 +349,65 @@ static int delete_loopback(const char *device) {
 }
 
 static int delete_dm(dev_t devnum) {
-        _cleanup_close_ int fd = -1;
-        int r;
+
         struct dm_ioctl dm = {
-                .version = {DM_VERSION_MAJOR,
-                            DM_VERSION_MINOR,
-                            DM_VERSION_PATCHLEVEL},
+                .version = {
+                        DM_VERSION_MAJOR,
+                        DM_VERSION_MINOR,
+                        DM_VERSION_PATCHLEVEL
+                },
                 .data_size = sizeof(dm),
                 .dev = devnum,
         };
 
+        _cleanup_close_ int fd = -1;
+
         assert(major(devnum) != 0);
 
         fd = open("/dev/mapper/control", O_RDWR|O_CLOEXEC);
         if (fd < 0)
                 return -errno;
 
-        r = ioctl(fd, DM_DEV_REMOVE, &dm);
-        return r >= 0 ? 0 : -errno;
+        if (ioctl(fd, DM_DEV_REMOVE, &dm) < 0)
+                return -errno;
+
+        return 0;
+}
+
+static bool nonunmountable_path(const char *path) {
+        return path_equal(path, "/")
+#if ! HAVE_SPLIT_USR
+                || path_equal(path, "/usr")
+#endif
+                || path_startswith(path, "/run/initramfs");
 }
 
+/* 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 log_error) {
-        MountPoint *m, *n;
+        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
                    benefits, but might confuse the host, as we remount
-                   the superblock here, not the bind mount. */
-                if (detect_container() <= 0)  {
+                   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
@@ -399,34 +430,37 @@ static int mount_points_list_umount(MountPoint **head, bool *changed, bool log_e
                          * 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 the fs we
-                         * can otherwise not see dirty. */
+                         * relatively safe regarding keeping dirty an fs
+                         * we cannot otherwise see. */
                         log_info("Remounting '%s' read-only with options '%s'.", m->path, options);
-                        (void) mount(NULL, m->path, NULL, MS_REMOUNT|MS_RDONLY, 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);
+                                if (nonunmountable_path(m->path))
+                                        n_failed++;
+                        }
                 }
 
                 /* Skip / and /usr since we cannot unmount that
                  * anyway, since we are running from it. They have
                  * already been remounted ro. */
-                if (path_equal(m->path, "/")
-#ifndef HAVE_SPLIT_USR
-                    || path_equal(m->path, "/usr")
-#endif
-                    || path_startswith(m->path, "/run/initramfs")
-                )
+                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. */
+                /* Trying to umount. 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).*/
                 log_info("Unmounting %s.", m->path);
-                if (umount2(m->path, 0) == 0) {
+                if (umount2(m->path, MNT_FORCE) == 0) {
                         if (changed)
                                 *changed = true;
-
-                        mount_point_free(head, m);
-                } else if (log_error) {
-                        log_warning_errno(errno, "Could not unmount %s: %m", m->path);
+                } else {
+                        if (log_error)
+                                log_warning_errno(errno, "Could not unmount %s: %m", m->path);
                         n_failed++;
                 }
         }
@@ -495,22 +529,22 @@ static int loopback_points_list_detach(MountPoint **head, bool *changed) {
 
 static int dm_points_list_detach(MountPoint **head, bool *changed) {
         MountPoint *m, *n;
-        int n_failed = 0, k;
-        struct stat root_st;
+        int n_failed = 0, r;
+        dev_t rootdev;
 
         assert(head);
 
-        k = lstat("/", &root_st);
+        r = get_block_device("/", &rootdev);
+        if (r <= 0)
+                rootdev = 0;
 
         LIST_FOREACH_SAFE(mount_point, m, n, *head) {
-                int r;
 
-                if (k >= 0 &&
-                    major(root_st.st_dev) != 0 &&
-                    root_st.st_dev == m->devnum) {
-                        n_failed++;
-                        continue;
-                }
+                if (major(rootdev) != 0)
+                        if (rootdev == m->devnum) {
+                                n_failed ++;
+                                continue;
+                        }
 
                 log_info("Detaching DM %u:%u.", major(m->devnum), minor(m->devnum));
                 r = delete_dm(m->devnum);
@@ -528,9 +562,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, bool log_error) {
         int r;
-        bool umount_changed;
         LIST_HEAD(MountPoint, mp_list_head);
 
         LIST_HEAD_INIT(mp_list_head);
@@ -538,23 +571,31 @@ int umount_all(bool *changed) {
         if (r < 0)
                 goto end;
 
+        r = mount_points_list_umount(&mp_list_head, changed, log_error);
+
+  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 */
         do {
                 umount_changed = false;
 
-                mount_points_list_umount(&mp_list_head, &umount_changed, false);
+                umount_all_once(&umount_changed, false);
                 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);
-        if (r <= 0)
-                goto end;
-
-  end:
-        mount_points_list_free(&mp_list_head);
+        r = umount_all_once(&umount_changed, true);
+        if (umount_changed)
+                *changed = true;
 
         return r;
 }