systemd-mount: support discovery of loop backing file
authorYu Watanabe <watanabe.yu+github@gmail.com>
Wed, 5 Jul 2017 12:54:40 +0000 (21:54 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Wed, 5 Jul 2017 12:54:40 +0000 (21:54 +0900)
```
$ suro systemd-mount /path/to/disk.img
Started unit run-media-system-disk.img.mount for mount point: /run/media/system/disk.img
```

Closes #6226.

man/systemd-mount.xml
src/mount/mount-tool.c

index f3910ca..18f14b3 100644 (file)
@@ -83,7 +83,8 @@
     checker to execute a priori), and may make use of the auto-mounting logic.</para>
 
     <para>The command takes either one or two arguments. If only one argument is specified it should refer to a block
-    device containing a file system (e.g. <literal>/dev/sdb1</literal>), which is then probed for a label and other
+    device or regular file containing a file system (e.g. <literal>/dev/sdb1</literal> or
+    <literal>/path/to/disk.img</literal>). If it is a block device, which is then probed for a label and other
     metadata, and is mounted to a directory whose name is generated from the label. In this mode the block device must
     exist at the time of invocation of the command, so that it may be probed. If the device is found to be a removable
     block device (e.g. a USB stick) an automount point instead of a regular mount point is created (i.e. the
index 65d57d8..52b8ef5 100644 (file)
@@ -636,6 +636,125 @@ static int start_transient_automount(
         return 0;
 }
 
+static int find_mount_points(const char *what, char ***list) {
+        _cleanup_fclose_ FILE *proc_self_mountinfo = NULL;
+        _cleanup_strv_free_ char **l = NULL;
+        size_t bufsize = 0, n = 0;
+
+        assert(what);
+        assert(list);
+
+        /* Returns all mount points obtained from /proc/self/mountinfo in *list,
+         * and the number of mount points as return value. */
+
+        proc_self_mountinfo = fopen("/proc/self/mountinfo", "re");
+        if (!proc_self_mountinfo)
+                return log_error_errno(errno, "Can't open /proc/self/mountinfo: %m");
+
+        for (;;) {
+                _cleanup_free_ char *path = NULL, *where = NULL, *dev = NULL;
+                int r;
+
+                r = fscanf(proc_self_mountinfo,
+                           "%*s "       /* (1) mount id */
+                           "%*s "       /* (2) parent id */
+                           "%*s "       /* (3) major:minor */
+                           "%*s "       /* (4) root */
+                           "%ms "       /* (5) mount point */
+                           "%*s"        /* (6) mount options */
+                           "%*[^-]"     /* (7) optional fields */
+                           "- "         /* (8) separator */
+                           "%*s "       /* (9) file system type */
+                           "%ms"        /* (10) mount source */
+                           "%*s"        /* (11) mount options 2 */
+                           "%*[^\n]",   /* some rubbish at the end */
+                           &path, &dev);
+                if (r != 2) {
+                        if (r == EOF)
+                                break;
+
+                        continue;
+                }
+
+                if (!streq(what, dev))
+                        continue;
+
+                r = cunescape(path, UNESCAPE_RELAX, &where);
+                if (r < 0)
+                        continue;
+
+                /* one extra slot is needed for the terminating NULL */
+                if (!GREEDY_REALLOC(l, bufsize, n + 2))
+                        return log_oom();
+
+                l[n] = strdup(where);
+                if (!l[n])
+                        return log_oom();
+
+                n++;
+        }
+
+        l[n] = NULL;
+        *list = l;
+        l = NULL; /* avoid freeing */
+
+        return n;
+}
+
+static int find_loop_device(const char *backing_file, char **loop_dev) {
+        _cleanup_closedir_ DIR *d = NULL;
+        struct dirent *de;
+        _cleanup_free_ char *l = NULL;
+
+        assert(backing_file);
+        assert(loop_dev);
+
+        d = opendir("/sys/devices/virtual/block");
+        if (!d) {
+                if (errno == ENOENT)
+                        return -ENOENT;
+                return log_error_errno(errno, "Can't open directory /sys/devices/virtual/block: %m");
+        }
+
+        FOREACH_DIRENT(de, d, return -errno) {
+                _cleanup_free_ char *sys = NULL, *fname = NULL;
+                int r;
+
+                dirent_ensure_type(d, de);
+
+                if (de->d_type != DT_DIR)
+                        continue;
+
+                if (!startswith(de->d_name, "loop"))
+                        continue;
+
+                sys = strjoin("/sys/devices/virtual/block/", de->d_name, "/loop/backing_file");
+                if (!sys)
+                        return log_oom();
+
+                r = read_one_line_file(sys, &fname);
+                if (r < 0)
+                        continue;
+
+                if (files_same(fname, backing_file, 0) <= 0)
+                        continue;
+
+                l = strjoin("/dev/", de->d_name);
+                if (!l)
+                        return log_oom();
+
+                break;
+        }
+
+        if (!l)
+                return -ENOENT;
+
+        *loop_dev = l;
+        l = NULL; /* avoid freeing */
+
+        return 0;
+}
+
 static int stop_mount(
                 sd_bus *bus,
                 const char *where,
@@ -729,64 +848,13 @@ static int stop_mounts(
         return 0;
 }
 
-static int umount_by_mountinfo(sd_bus *bus, const char *what) {
-        _cleanup_fclose_ FILE *proc_self_mountinfo = NULL;
-        int r, r2 = 0;
-
-        proc_self_mountinfo = fopen("/proc/self/mountinfo", "re");
-        if (!proc_self_mountinfo)
-                return -errno;
-
-        for (;;) {
-                _cleanup_free_ char *path = NULL, *where = NULL, *dev = NULL;
-                int k;
-
-                k = fscanf(proc_self_mountinfo,
-                           "%*s "       /* (1) mount id */
-                           "%*s "       /* (2) parent id */
-                           "%*s "       /* (3) major:minor */
-                           "%*s "       /* (4) root */
-                           "%ms "       /* (5) mount point */
-                           "%*s"        /* (6) mount options */
-                           "%*[^-]"     /* (7) optional fields */
-                           "- "         /* (8) separator */
-                           "%*s "       /* (9) file system type */
-                           "%ms"        /* (10) mount source */
-                           "%*s"        /* (11) mount options 2 */
-                           "%*[^\n]",   /* some rubbish at the end */
-                           &path, &dev);
-                if (k != 2) {
-                        if (k == EOF)
-                                break;
-
-                        continue;
-                }
-
-                if (!streq(what, dev))
-                        continue;
-
-                r = cunescape(path, UNESCAPE_RELAX, &where);
-                if (r < 0) {
-                        r2 = r;
-                        continue;
-                }
-
-                r = stop_mounts(bus, where);
-                if (r < 0) {
-                        r2 = r;
-                        continue;
-                }
-        }
-
-        return r2;
-}
-
 static int umount_by_device(sd_bus *bus, const char *what) {
         _cleanup_udev_device_unref_ struct udev_device *d = NULL;
         _cleanup_udev_unref_ struct udev *udev = NULL;
-        _cleanup_free_ char *where = NULL;
+        _cleanup_strv_free_ char **list = NULL;
         struct stat st;
         const char *v;
+        char **l;
         int r, r2 = 0;
 
         assert(what);
@@ -814,78 +882,33 @@ static int umount_by_device(sd_bus *bus, const char *what) {
         }
 
         v = udev_device_get_property_value(d, "SYSTEMD_MOUNT_WHERE");
-        if (!isempty(v)) {
-                where = strdup(v);
-                if (!where)
-                        return log_oom();
-                r2 = stop_mounts(bus, where);
-        }
+        if (!isempty(v))
+                r2 = stop_mounts(bus, v);
 
-        v = udev_device_get_devnode(d);
-        if (!isempty(v)) {
-                r = umount_by_mountinfo(bus, v);
+        r = find_mount_points(what, &list);
+        if (r < 0)
+                return r;
+
+        for (l = list; *l; l++) {
+                r = stop_mounts(bus, *l);
                 if (r < 0)
-                        return r;
+                        r2 = r;
         }
 
         return r2;
 }
 
 static int umount_loop(sd_bus *bus, const char *backing_file) {
-        _cleanup_closedir_ DIR *d = NULL;
-        struct dirent *de;
-        int r, r2 = 0;
-        bool found = false;
+        _cleanup_free_ char *loop_dev = NULL;
+        int r;
 
         assert(backing_file);
 
-        d = opendir("/sys/devices/virtual/block");
-        if (!d) {
-                if (errno == ENOENT)
-                        return log_error_errno(errno, "File %s is not mounted.", backing_file);
-                return log_error_errno(errno, "Can't open directory /sys/devices/virtual/block: %m");
-        }
-
-        FOREACH_DIRENT(de, d, return -errno) {
-                _cleanup_free_ char *sys = NULL, *what = NULL, *fname = NULL;
-
-                dirent_ensure_type(d, de);
-
-                if (de->d_type != DT_DIR)
-                        continue;
-
-                if (!startswith(de->d_name, "loop"))
-                        continue;
-
-                sys = strjoin("/sys/devices/virtual/block/", de->d_name, "/loop/backing_file");
-                if (!sys)
-                        return log_oom();
-
-                r = read_one_line_file(sys, &fname);
-                if (r < 0) {
-                        if (r != -ENOENT)
-                                r2 = log_error_errno(r, "Can't read %s: %m", sys);
-                        continue;
-                }
-
-                if (!files_same(fname, backing_file, 0))
-                        continue;
-
-                found = true;
-
-                what = strjoin("/dev/", de->d_name);
-                if (!what)
-                        return log_oom();
-
-                r = umount_by_device(bus, what);
-                if (r < 0)
-                        r2 = r;
-        }
-
-        if (!found)
-                return log_error_errno(ENOENT, "File %s is not mounted.", backing_file);
+        r = find_loop_device(backing_file, &loop_dev);
+        if (r < 0)
+                return log_error_errno(r, r == -ENOENT ? "File %s is not mounted." : "Can't get loop device for %s: %m", backing_file);
 
-        return r2;
+        return umount_by_device(bus, loop_dev);
 }
 
 static int action_umount(
@@ -1019,6 +1042,8 @@ static int acquire_mount_where(struct udev_device *d) {
                 }
 
                 escaped = xescape(name, "\\");
+                if (!escaped)
+                        return log_oom();
                 if (!filename_is_valid(escaped))
                         return 0;
 
@@ -1033,6 +1058,32 @@ static int acquire_mount_where(struct udev_device *d) {
         return 1;
 }
 
+static int acquire_mount_where_for_loop_dev(const char *loop_dev) {
+        _cleanup_strv_free_ char **list = NULL;
+        int r;
+
+        if (arg_mount_where)
+                return 0;
+
+        r = find_mount_points(loop_dev, &list);
+        if (r < 0)
+                return r;
+        else if (r == 0) {
+                log_error("Can't find mount point of %s. It is expected that %s is already mounted on a place.", loop_dev, loop_dev);
+                return -EINVAL;
+        } else if (r >= 2) {
+                log_error("%s is mounted on %d places. It is expected that %s is mounted on a place.", loop_dev, r, loop_dev);
+                return -EINVAL;
+        }
+
+        arg_mount_where = strdup(list[0]);
+        if (!arg_mount_where)
+                return log_oom();
+
+        log_debug("Discovered where=%s", arg_mount_where);
+        return 1;
+}
+
 static int acquire_description(struct udev_device *d) {
         const char *model, *label;
 
@@ -1104,27 +1155,95 @@ static int acquire_removable(struct udev_device *d) {
         return 1;
 }
 
-static int discover_device(void) {
+static int discover_loop_backing_file(void) {
         _cleanup_udev_device_unref_ struct udev_device *d = NULL;
         _cleanup_udev_unref_ struct udev *udev = NULL;
+        _cleanup_free_ char *loop_dev = NULL;
         struct stat st;
         const char *v;
         int r;
 
-        if (!arg_discover)
+        r = find_loop_device(arg_mount_what, &loop_dev);
+        if (r < 0 && r != -ENOENT)
+                return log_error_errno(errno, "Can't get loop device for %s: %m", arg_mount_what);
+
+        if (r == -ENOENT) {
+                _cleanup_free_ char *escaped = NULL;
+
+                if (arg_mount_where)
+                        return 0;
+
+                escaped = xescape(basename(arg_mount_what), "\\");
+                if (!escaped)
+                        return log_oom();
+                if (!filename_is_valid(escaped))
+                        return -EINVAL;
+
+                arg_mount_where = strjoin("/run/media/system/", escaped);
+                if (!arg_mount_where)
+                        return log_oom();
+
+                log_debug("Discovered where=%s", arg_mount_where);
                 return 0;
+        }
 
-        if (!is_device_path(arg_mount_what)) {
-                log_error("Discovery only supported for block devices, don't know what to do.");
+        if (stat(loop_dev, &st) < 0)
+                return log_error_errno(errno, "Can't stat %s: %m", loop_dev);
+
+        if (!S_ISBLK(st.st_mode)) {
+                log_error("Invalid file type: %s", loop_dev);
                 return -EINVAL;
         }
 
+        udev = udev_new();
+        if (!udev)
+                return log_oom();
+
+        d = udev_device_new_from_devnum(udev, 'b', st.st_rdev);
+        if (!d)
+                return log_oom();
+
+        v = udev_device_get_property_value(d, "ID_FS_USAGE");
+        if (!streq_ptr(v, "filesystem")) {
+                log_error("%s does not contain a known file system.", arg_mount_what);
+                return -EINVAL;
+        }
+
+        r = acquire_mount_type(d);
+        if (r < 0)
+                return r;
+
+        r = acquire_mount_options(d);
+        if (r < 0)
+                return r;
+
+        r = acquire_mount_where_for_loop_dev(loop_dev);
+        if (r < 0)
+                return r;
+
+        r = acquire_description(d);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
+static int discover_device(void) {
+        _cleanup_udev_device_unref_ struct udev_device *d = NULL;
+        _cleanup_udev_unref_ struct udev *udev = NULL;
+        struct stat st;
+        const char *v;
+        int r;
+
         if (stat(arg_mount_what, &st) < 0)
                 return log_error_errno(errno, "Can't stat %s: %m", arg_mount_what);
 
+        if (S_ISREG(st.st_mode))
+                return discover_loop_backing_file();
+
         if (!S_ISBLK(st.st_mode)) {
-                log_error("Path %s is not a block device, don't know what to do.", arg_mount_what);
-                return -ENOTBLK;
+                log_error("Invalid file type: %s", arg_mount_what);
+                return -EINVAL;
         }
 
         udev = udev_new();
@@ -1371,9 +1490,20 @@ int main(int argc, char* argv[]) {
                 goto finish;
         }
 
-        r = discover_device();
-        if (r < 0)
+        path_kill_slashes(arg_mount_what);
+
+        if (!path_is_safe(arg_mount_what)) {
+                log_error("Path contains unsafe components: %s", arg_mount_what);
+                r = -EINVAL;
                 goto finish;
+        }
+
+        if (arg_discover) {
+                r = discover_device();
+                if (r < 0)
+                        goto finish;
+        }
+
         if (!arg_mount_where) {
                 log_error("Can't figure out where to mount %s.", arg_mount_what);
                 r = -EINVAL;
@@ -1389,7 +1519,7 @@ int main(int argc, char* argv[]) {
         }
 
         if (!path_is_safe(arg_mount_where)) {
-                log_error("Path contains unsafe components.");
+                log_error("Path contains unsafe components: %s", arg_mount_where);
                 r = -EINVAL;
                 goto finish;
         }