systemd-mount: support discovery of loop backing file

```
$ 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.
This commit is contained in:
Yu Watanabe 2017-07-05 21:54:40 +09:00
parent 601b08420d
commit f0aac575fc
2 changed files with 258 additions and 127 deletions

View 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

View 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");
}
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);
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);
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,6 +1155,79 @@ static int acquire_removable(struct udev_device *d) {
return 1;
}
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;
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 (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;
@ -1111,20 +1235,15 @@ static int discover_device(void) {
const char *v;
int r;
if (!arg_discover)
return 0;
if (!is_device_path(arg_mount_what)) {
log_error("Discovery only supported for block devices, don't know what to do.");
return -EINVAL;
}
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;
}