dissect: add commands for attaching/detaching loopback devices

Sometimes it is useful attaching DDIs without mounting them. We could
use "losetup" for that, but doing this in systemd-dissect has various
benefits:

1. we superficially validate the DDI first
2. we set the sector size depending on what we determine
3. we synchronously create the per-partition block devices
This commit is contained in:
Lennart Poettering
2023-03-06 11:59:16 +01:00
parent 999ac3e2b0
commit 07d6072e0e
2 changed files with 171 additions and 0 deletions

View File

@@ -32,6 +32,12 @@
<cmdsynopsis>
<command>systemd-dissect <arg choice="opt" rep="repeat">OPTIONS</arg> <option>--umount</option> <arg choice="plain"><replaceable>PATH</replaceable></arg></command>
</cmdsynopsis>
<cmdsynopsis>
<command>systemd-dissect <arg choice="opt" rep="repeat">OPTIONS</arg> <option>--attach</option> <arg choice="plain"><replaceable>IMAGE</replaceable></arg></command>
</cmdsynopsis>
<cmdsynopsis>
<command>systemd-dissect <arg choice="opt" rep="repeat">OPTIONS</arg> <option>--detach</option> <arg choice="plain"><replaceable>PATH</replaceable></arg></command>
</cmdsynopsis>
<cmdsynopsis>
<command>systemd-dissect <arg choice="opt" rep="repeat">OPTIONS</arg> <option>--list</option> <arg choice="plain"><replaceable>IMAGE</replaceable></arg></command>
</cmdsynopsis>
@@ -173,6 +179,25 @@
<listitem><para>This is a shortcut for <option>--umount --rmdir</option>.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--attach</option></term>
<listitem><para>Attach the specified disk image to an automatically allocated loopback block device,
and print the path to the loopback block device to standard output. This is similar to an invocation
of <command>losetup --find --show</command>, but will validate the image as DDI before attaching, and
derive the correct sector size to use automatically. Moreover, it ensures the per-partition block
devices are created before returning. Takes a path to a disk image file.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--detach</option></term>
<listitem><para>Detach the specified disk image from a loopback block device. This undoes the effect
of <option>--attach</option> above. This expects either a path to a loopback block device as an
argument, or the path to the backing image file. In the latter case it will automatically determine
the right device to detach.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--list</option></term>
<term><option>-l</option></term>

View File

@@ -52,6 +52,8 @@ static enum {
ACTION_DISSECT,
ACTION_MOUNT,
ACTION_UMOUNT,
ACTION_ATTACH,
ACTION_DETACH,
ACTION_LIST,
ACTION_MTREE,
ACTION_WITH,
@@ -94,6 +96,8 @@ static int help(void) {
printf("%1$s [OPTIONS...] IMAGE\n"
"%1$s [OPTIONS...] --mount IMAGE PATH\n"
"%1$s [OPTIONS...] --umount PATH\n"
"%1$s [OPTIONS...] --attach IMAGE\n"
"%1$s [OPTIONS...] --detach PATH\n"
"%1$s [OPTIONS...] --list IMAGE\n"
"%1$s [OPTIONS...] --mtree IMAGE\n"
"%1$s [OPTIONS...] --with IMAGE [COMMAND…]\n"
@@ -126,6 +130,8 @@ static int help(void) {
" -M Shortcut for --mount --mkdir\n"
" -u --umount Unmount the image from the specified directory\n"
" -U Shortcut for --umount --rmdir\n"
" --attach Attach the disk image to a loopback block device\n"
" --detach Detach a loopback block device gain\n"
" -l --list List all the files and directories of the specified\n"
" OS image\n"
" --mtree Show BSD mtree manifest of OS image\n"
@@ -206,6 +212,8 @@ static int parse_argv(int argc, char *argv[]) {
ARG_JSON,
ARG_MTREE,
ARG_DISCOVER,
ARG_ATTACH,
ARG_DETACH,
};
static const struct option options[] = {
@@ -215,6 +223,8 @@ static int parse_argv(int argc, char *argv[]) {
{ "no-legend", no_argument, NULL, ARG_NO_LEGEND },
{ "mount", no_argument, NULL, 'm' },
{ "umount", no_argument, NULL, 'u' },
{ "attach", no_argument, NULL, ARG_ATTACH },
{ "detach", no_argument, NULL, ARG_DETACH },
{ "with", no_argument, NULL, ARG_WITH },
{ "read-only", no_argument, NULL, 'r' },
{ "discard", required_argument, NULL, ARG_DISCARD },
@@ -291,6 +301,14 @@ static int parse_argv(int argc, char *argv[]) {
arg_rmdir = true;
break;
case ARG_ATTACH:
arg_action = ACTION_ATTACH;
break;
case ARG_DETACH:
arg_action = ACTION_DETACH;
break;
case 'l':
arg_action = ACTION_LIST;
arg_flags |= DISSECT_IMAGE_READ_ONLY;
@@ -454,6 +472,22 @@ static int parse_argv(int argc, char *argv[]) {
arg_path = argv[optind];
break;
case ACTION_ATTACH:
if (optind + 1 != argc)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Expected an image file path as only argument.");
arg_image = argv[optind];
break;
case ACTION_DETACH:
if (optind + 1 != argc)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Expected an image file path or loopback device as only argument.");
arg_image = argv[optind];
break;
case ACTION_LIST:
case ACTION_MTREE:
if (optind + 1 != argc)
@@ -1486,6 +1520,113 @@ static int action_discover(void) {
return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
}
static int action_attach(DissectedImage *m, LoopDevice *d) {
int r;
assert(m);
assert(d);
r = loop_device_set_autoclear(d, false);
if (r < 0)
return log_error_errno(r, "Failed to disable auto-clear logic on loopback device: %m");
r = dissected_image_relinquish(m);
if (r < 0)
return log_error_errno(r, "Failed to relinquish DM and loopback block devices: %m");
puts(d->node);
return 0;
}
static int action_detach(const char *path) {
_cleanup_(loop_device_unrefp) LoopDevice *loop = NULL;
_cleanup_close_ int fd = -EBADF;
struct stat st;
int r;
fd = open(path, O_PATH|O_CLOEXEC);
if (fd < 0)
return log_error_errno(errno, "Failed to open '%s': %m", path);
if (fstat(fd, &st) < 0)
return log_error_errno(errno, "Failed to stat '%s': %m", path);
if (S_ISBLK(st.st_mode)) {
r = loop_device_open_from_fd(fd, O_RDONLY, LOCK_EX, &loop);
if (r < 0)
return log_error_errno(r, "Failed to open '%s' as loopback block device: %m", path);
} else if (S_ISREG(st.st_mode)) {
_cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
sd_device *d;
/* If a regular file is specified, search for a loopback block device that is backed by it */
r = sd_device_enumerator_new(&e);
if (r < 0)
return log_error_errno(r, "Failed to allocate enumerator: %m");
r = sd_device_enumerator_add_match_subsystem(e, "block", true);
if (r < 0)
return log_error_errno(r, "Failed to match block devices: %m");
r = sd_device_enumerator_add_match_sysname(e, "loop*");
if (r < 0)
return log_error_errno(r, "Failed to match loopback block devices: %m");
(void) sd_device_enumerator_allow_uninitialized(e);
FOREACH_DEVICE(e, d) {
_cleanup_(loop_device_unrefp) LoopDevice *entry_loop = NULL;
const char *name, *devtype;
r = sd_device_get_sysname(d, &name);
if (r < 0) {
log_warning_errno(r, "Failed to get enumerated device's sysname, skipping: %m");
continue;
}
r = sd_device_get_devtype(d, &devtype);
if (r < 0) {
log_warning_errno(r, "Failed to get devtype of '%s', skipping: %m", name);
continue;
}
if (!streq(devtype, "disk")) /* Filter out partition block devices */
continue;
r = loop_device_open(d, O_RDONLY, LOCK_SH, &entry_loop);
if (r < 0) {
log_warning_errno(r, "Failed to open loopback block device '%s', skipping: %m", name);
continue;
}
if (entry_loop->backing_devno == st.st_dev && entry_loop->backing_inode == st.st_ino) {
/* Found it! The kernel allows attaching a single file to multiple loopback
* devices. Let's destruct them in reverse order, i.e. find the last matching
* loopback device here, rather than the first. */
loop_device_unref(loop);
loop = TAKE_PTR(entry_loop);
}
}
if (!loop)
return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "No loopback block device backed by '%s' found.", path);
r = loop_device_flock(loop, LOCK_EX);
if (r < 0)
return log_error_errno(r, "Failed to upgrade device lock: %m");
}
r = loop_device_set_autoclear(loop, true);
if (r < 0)
log_warning_errno(r, "Failed to enable autoclear logic on '%s', ignoring: %m", loop->node);
loop_device_unrelinquish(loop);
return 0;
}
static int run(int argc, char *argv[]) {
_cleanup_(dissected_image_unrefp) DissectedImage *m = NULL;
_cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
@@ -1503,6 +1644,8 @@ static int run(int argc, char *argv[]) {
if (arg_action == ACTION_UMOUNT)
return action_umount(arg_path);
if (arg_action == ACTION_DETACH)
return action_detach(arg_image);
if (arg_action == ACTION_DISCOVER)
return action_discover();
@@ -1537,6 +1680,9 @@ static int run(int argc, char *argv[]) {
if (r < 0)
return r;
if (arg_action == ACTION_ATTACH)
return action_attach(m, d);
r = dissected_image_load_verity_sig_partition(
m,
d->fd,