+/* SPDX-License-Identifier: LGPL-2.1+ */
/***
This file is part of systemd.
#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;
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;
"%*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;
m->path = p;
m->options = options;
options = NULL;
+ m->type = type;
+ type = NULL;
LIST_PREPEND(mount_point, *head, m);
}
}
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 mound. */
- 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
* 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
- )
+ 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++;
}
}
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);
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);
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;
}