From 85737dd378f4f93432ad6298209333a9baea53f5 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 6 Mar 2023 11:52:33 +0100 Subject: [PATCH 01/11] udev: add new udev_builtin_add_propertyf() helper --- src/udev/udev-builtin.c | 18 ++++++++++++++++++ src/udev/udev-builtin.h | 3 +++ 2 files changed, 21 insertions(+) diff --git a/src/udev/udev-builtin.c b/src/udev/udev-builtin.c index c98c6fa714..566641e400 100644 --- a/src/udev/udev-builtin.c +++ b/src/udev/udev-builtin.c @@ -134,3 +134,21 @@ int udev_builtin_add_property(sd_device *dev, bool test, const char *key, const return 0; } + +int udev_builtin_add_propertyf(sd_device *dev, bool test, const char *key, const char *valf, ...) { + _cleanup_free_ char *val = NULL; + va_list ap; + int r; + + assert(dev); + assert(key); + assert(valf); + + va_start(ap, valf); + r = vasprintf(&val, valf, ap); + va_end(ap); + if (r < 0) + return log_oom_debug(); + + return udev_builtin_add_property(dev, test, key, val); +} diff --git a/src/udev/udev-builtin.h b/src/udev/udev-builtin.h index bcfec03aae..490b2dbe96 100644 --- a/src/udev/udev-builtin.h +++ b/src/udev/udev-builtin.h @@ -6,6 +6,8 @@ #include "sd-device.h" #include "sd-netlink.h" +#include "macro.h" + typedef enum UdevBuiltinCommand { #if HAVE_BLKID UDEV_BUILTIN_BLKID, @@ -78,5 +80,6 @@ int udev_builtin_run(sd_device *dev, sd_netlink **rtnl, UdevBuiltinCommand cmd, void udev_builtin_list(void); bool udev_builtin_should_reload(void); int udev_builtin_add_property(sd_device *dev, bool test, const char *key, const char *val); +int udev_builtin_add_propertyf(sd_device *dev, bool test, const char *key, const char *valf, ...) _printf_(4, 5); int udev_builtin_hwdb_lookup(sd_device *dev, const char *prefix, const char *modalias, const char *filter, bool test); From be86e98350528b458dd5ec17bad84bbe2aea3976 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 6 Mar 2023 11:53:26 +0100 Subject: [PATCH 02/11] udev-builtin-blkid: pick up info of backing file This adds support for retrieving info about the inode backing a loopback file to udev-builtin-blkid. It will pick up the inode number and device of the backing inode, as well as the lo_file_name[] array that the loopback device maintains. A later patch uses this information to create block device symlinks in /dev/ that allow refering block devices by their backing inodes. This is useful when separate tools set up a loopback device from those which ultimately shall mount them, and there shall be a stable reference be passed along. For example, we can add a new kernel option setuploop= or so which allows setting up a block device via a generator, and still have a way to safely reference later. And yes, this doesn't directly have anything to do with the probing libblkid does, but it's close enough, and we have the device open anyway here, so the additional ioctl() here should not hurt. --- src/udev/udev-builtin-blkid.c | 108 ++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/src/udev/udev-builtin-blkid.c b/src/udev/udev-builtin-blkid.c index d2de03d5f9..154cf7000f 100644 --- a/src/udev/udev-builtin-blkid.c +++ b/src/udev/udev-builtin-blkid.c @@ -5,11 +5,17 @@ * Copyright © 2011 Karel Zak */ +#if HAVE_VALGRIND_MEMCHECK_H +#include +#endif + #include #include #include +#include #include #include +#include #include #include "sd-id128.h" @@ -17,6 +23,7 @@ #include "alloc-util.h" #include "blkid-util.h" #include "device-util.h" +#include "devnum-util.h" #include "efi-loader.h" #include "errno-util.h" #include "fd-util.h" @@ -234,11 +241,86 @@ static int probe_superblocks(blkid_probe pr) { return blkid_do_safeprobe(pr); } +static int read_loopback_backing_inode( + sd_device *dev, + int fd, + dev_t *ret_devno, + ino_t *ret_inode, + char **ret_fname) { + + _cleanup_free_ char *fn = NULL; + struct loop_info64 info; + const char *name; + int r; + + assert(dev); + assert(fd >= 0); + assert(ret_devno); + assert(ret_inode); + assert(ret_fname); + + /* Retrieves various fields of the current loopback device backing file, so that we can ultimately + * use it to create stable symlinks to loopback block devices, based on what they are backed by. We + * pick up inode/device as well as file name field. Note that we pick up the "lo_file_name" field + * here, which is an arbitrary free-form string provided by userspace. We do not return the sysfs + * attribute loop/backing_file here, because that is directly accessible from udev rules anyway. And + * sometimes, depending on context, it's a good thing to return the string userspace can freely pick + * over the string automatically generated by the kernel. */ + + r = sd_device_get_sysname(dev, &name); + if (r < 0) + return r; + + if (!startswith(name, "loop")) + goto notloop; + + if (ioctl(fd, LOOP_GET_STATUS64, &info) < 0) { + if (ERRNO_IS_NOT_SUPPORTED(errno)) + goto notloop; + + return -errno; + } + +#if HAVE_VALGRIND_MEMCHECK_H + VALGRIND_MAKE_MEM_DEFINED(&info, sizeof(info)); +#endif + + if (isempty((char*) info.lo_file_name) || + strnlen((char*) info.lo_file_name, sizeof(info.lo_file_name)-1) == sizeof(info.lo_file_name)-1) + /* Don't pick up file name if it is unset or possibly truncated. (Note: we can't really know + * the file file name is truncated if it uses sizeof(info.lo_file_name)-1 as length; it could + * also just mean the string is just that long and wasn't truncated — but the fact is simply + * that we cannot know in that case if it was truncated or not, hence we assume the worst and + * suppress — at least for now. For shorter strings we know for sure it wasn't truncated, + * hence that's always safe.) */ + fn = NULL; + else { + fn = memdup_suffix0(info.lo_file_name, sizeof(info.lo_file_name)); + if (!fn) + return -ENOMEM; + } + + *ret_inode = info.lo_inode; + *ret_devno = info.lo_device; + *ret_fname = TAKE_PTR(fn); + return 1; + + +notloop: + *ret_devno = 0; + *ret_inode = 0; + *ret_fname = NULL; + return 0; +} + static int builtin_blkid(sd_device *dev, sd_netlink **rtnl, int argc, char *argv[], bool test) { const char *devnode, *root_partition = NULL, *data, *name; _cleanup_(blkid_free_probep) blkid_probe pr = NULL; + _cleanup_free_ char *backing_fname = NULL; bool noraid = false, is_gpt = false; _cleanup_close_ int fd = -EBADF; + ino_t backing_inode = 0; + dev_t backing_devno = 0; int64_t offset = 0; int r; @@ -352,6 +434,32 @@ static int builtin_blkid(sd_device *dev, sd_netlink **rtnl, int argc, char *argv if (is_gpt) find_gpt_root(dev, pr, test); + r = read_loopback_backing_inode( + dev, + fd, + &backing_devno, + &backing_inode, + &backing_fname); + if (r < 0) + log_device_debug_errno(dev, r, "Failed to read loopback backing inode, ignoring: %m"); + else if (r > 0) { + udev_builtin_add_propertyf(dev, test, "ID_LOOP_BACKING_DEVICE", DEVNUM_FORMAT_STR, DEVNUM_FORMAT_VAL(backing_devno)); + udev_builtin_add_propertyf(dev, test, "ID_LOOP_BACKING_INODE", "%" PRIu64, (uint64_t) backing_inode); + + if (backing_fname) { + /* In the worst case blkid_encode_string() will blow up to 4x the string + * length. Hence size the buffer to 4x of the longest string + * read_loopback_backing_inode() might return */ + char encoded[sizeof_field(struct loop_info64, lo_file_name) * 4 + 1]; + + assert(strlen(backing_fname) < ELEMENTSOF(encoded) / 4); + blkid_encode_string(backing_fname, encoded, ELEMENTSOF(encoded)); + + udev_builtin_add_property(dev, test, "ID_LOOP_BACKING_FILENAME", backing_fname); + udev_builtin_add_property(dev, test, "ID_LOOP_BACKING_FILENAME_ENC", encoded); + } + } + return 0; } From 4d2a9e3ea6f1dbfb702abfd5312b1c8c719f4ecf Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 6 Mar 2023 12:06:21 +0100 Subject: [PATCH 03/11] loop-util: keep track of inode/devnum of backing file --- src/shared/loop-util.c | 10 +++++++++- src/shared/loop-util.h | 6 ++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/shared/loop-util.c b/src/shared/loop-util.c index 6e187efe94..96a42b4125 100644 --- a/src/shared/loop-util.c +++ b/src/shared/loop-util.c @@ -595,6 +595,8 @@ static int loop_device_make_internal( } d->backing_file = TAKE_PTR(backing_file); + d->backing_inode = st.st_ino; + d->backing_devno = st.st_dev; log_debug("Successfully acquired %s, devno=%u:%u, nr=%i, diskseq=%" PRIu64, d->node, @@ -841,11 +843,12 @@ int loop_device_open( _cleanup_close_ int fd = -EBADF, lock_fd = -EBADF; _cleanup_free_ char *node = NULL, *backing_file = NULL; + dev_t devnum, backing_devno = 0; struct loop_info64 info; + ino_t backing_inode = 0; uint64_t diskseq = 0; LoopDevice *d; const char *s; - dev_t devnum; int r, nr = -1; assert(dev); @@ -878,6 +881,9 @@ int loop_device_open( if (!backing_file) return -ENOMEM; } + + backing_devno = info.lo_device; + backing_inode = info.lo_inode; } r = fd_get_diskseq(fd, &diskseq); @@ -913,6 +919,8 @@ int loop_device_open( .node = TAKE_PTR(node), .dev = sd_device_ref(dev), .backing_file = TAKE_PTR(backing_file), + .backing_inode = backing_inode, + .backing_devno = backing_devno, .relinquished = true, /* It's not ours, don't try to destroy it when this object is freed */ .devno = devnum, .diskseq = diskseq, diff --git a/src/shared/loop-util.h b/src/shared/loop-util.h index 5c79ed7cce..ea461434e9 100644 --- a/src/shared/loop-util.h +++ b/src/shared/loop-util.h @@ -14,12 +14,14 @@ struct LoopDevice { unsigned n_ref; int fd; int lock_fd; - int nr; /* The loopback device index (i.e. 4 for /dev/loop4); if this object encapsulates a non-loopback block device, set to -1 */ - dev_t devno; + int nr; /* The loopback device index (i.e. 4 for /dev/loop4); if this object encapsulates a non-loopback block device, set to -1 */ + dev_t devno; /* The loopback device's own dev_t */ char *node; sd_device *dev; char *backing_file; bool relinquished; + dev_t backing_devno; /* The backing file's dev_t */ + ino_t backing_inode; /* The backing file's ino_t */ uint64_t diskseq; /* Block device sequence number, monothonically incremented by the kernel on create/attach, or 0 if we don't know */ uint64_t uevent_seqnum_not_before; /* uevent sequm right before we attached the loopback device, or UINT64_MAX if we don't know */ usec_t timestamp_not_before; /* CLOCK_MONOTONIC timestamp taken immediately before attaching the loopback device, or USEC_INFINITY if we don't know */ From d2430d50970deda19ac851669e685a1cc72a8200 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 6 Mar 2023 12:07:18 +0100 Subject: [PATCH 04/11] loop-util: add call for setting the autoclear flag at arbitrary times --- src/shared/loop-util.c | 19 +++++++++++++++++++ src/shared/loop-util.h | 2 ++ 2 files changed, 21 insertions(+) diff --git a/src/shared/loop-util.c b/src/shared/loop-util.c index 96a42b4125..27a58952bb 100644 --- a/src/shared/loop-util.c +++ b/src/shared/loop-util.c @@ -1111,3 +1111,22 @@ int loop_device_sync(LoopDevice *d) { return RET_NERRNO(fsync(d->fd)); } + +int loop_device_set_autoclear(LoopDevice *d, bool autoclear) { + struct loop_info64 info; + + assert(d); + + if (ioctl(d->fd, LOOP_GET_STATUS64, &info) < 0) + return -errno; + + if (autoclear == FLAGS_SET(info.lo_flags, LO_FLAGS_AUTOCLEAR)) + return 0; + + SET_FLAG(info.lo_flags, LO_FLAGS_AUTOCLEAR, autoclear); + + if (ioctl(d->fd, LOOP_SET_STATUS64, &info) < 0) + return -errno; + + return 1; +} diff --git a/src/shared/loop-util.h b/src/shared/loop-util.h index ea461434e9..c98b69ceee 100644 --- a/src/shared/loop-util.h +++ b/src/shared/loop-util.h @@ -49,3 +49,5 @@ int loop_device_refresh_size(LoopDevice *d, uint64_t offset, uint64_t size); int loop_device_flock(LoopDevice *d, int operation); int loop_device_sync(LoopDevice *d); + +int loop_device_set_autoclear(LoopDevice *d, bool autoclear); From 999ac3e2b0b729cba0db27cdcace28ff509e0fe4 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 6 Mar 2023 12:07:57 +0100 Subject: [PATCH 05/11] loop-util: add API for selecting "lo_file_name" field for a loopback device --- src/shared/loop-util.c | 35 +++++++++++++++++++++++++++++++++++ src/shared/loop-util.h | 1 + 2 files changed, 36 insertions(+) diff --git a/src/shared/loop-util.c b/src/shared/loop-util.c index 27a58952bb..7d92a8ccce 100644 --- a/src/shared/loop-util.c +++ b/src/shared/loop-util.c @@ -1130,3 +1130,38 @@ int loop_device_set_autoclear(LoopDevice *d, bool autoclear) { return 1; } + +int loop_device_set_filename(LoopDevice *d, const char *name) { + struct loop_info64 info; + + assert(d); + + /* Sets the .lo_file_name of the loopback device. This is supposed to contain the path to the file + * backing the block device, but is actually just a free-form string you can pass to the kernel. Most + * tools that actually care for the backing file path use the sysfs attribute file loop/backing_file + * which is a kernel generated string, subject to file system namespaces and such. + * + * .lo_file_name is useful since userspace can select it freely when creating a loopback block + * device, and we can use it for /dev/loop/by-ref/ symlinks, and similar, so that apps can recognize + * their own loopback files. */ + + if (name && strlen(name) >= sizeof(info.lo_file_name)) + return -ENOBUFS; + + if (ioctl(d->fd, LOOP_GET_STATUS64, &info) < 0) + return -errno; + + if (strneq((char*) info.lo_file_name, strempty(name), sizeof(info.lo_file_name))) + return 0; + + if (name) { + strncpy((char*) info.lo_file_name, name, sizeof(info.lo_file_name)-1); + info.lo_file_name[sizeof(info.lo_file_name)-1] = 0; + } else + memzero(info.lo_file_name, sizeof(info.lo_file_name)); + + if (ioctl(d->fd, LOOP_SET_STATUS64, &info) < 0) + return -errno; + + return 1; +} diff --git a/src/shared/loop-util.h b/src/shared/loop-util.h index c98b69ceee..dda14ec4f0 100644 --- a/src/shared/loop-util.h +++ b/src/shared/loop-util.h @@ -51,3 +51,4 @@ int loop_device_flock(LoopDevice *d, int operation); int loop_device_sync(LoopDevice *d); int loop_device_set_autoclear(LoopDevice *d, bool autoclear); +int loop_device_set_filename(LoopDevice *d, const char *name); From 07d6072e0ea25e6047007bca53b329b03c76db43 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 6 Mar 2023 11:59:16 +0100 Subject: [PATCH 06/11] 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 --- man/systemd-dissect.xml | 25 +++++++ src/dissect/dissect.c | 146 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 171 insertions(+) diff --git a/man/systemd-dissect.xml b/man/systemd-dissect.xml index 7dfad29c35..1d0532713d 100644 --- a/man/systemd-dissect.xml +++ b/man/systemd-dissect.xml @@ -32,6 +32,12 @@ systemd-dissect OPTIONS PATH + + systemd-dissect OPTIONS IMAGE + + + systemd-dissect OPTIONS PATH + systemd-dissect OPTIONS IMAGE @@ -173,6 +179,25 @@ This is a shortcut for . + + + + 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 losetup --find --show, 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. + + + + + + Detach the specified disk image from a loopback block device. This undoes the effect + of 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. + + diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index db26645fd9..6f7b44f0aa 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -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, From 236d1fa210839eafe03250214f708667c20ddb7f Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 6 Mar 2023 12:00:45 +0100 Subject: [PATCH 07/11] dissect: allow setting "lo_file_name" field of loopback block devices When attaching a loopback file this allows us to set an explicit name for it. This is useful since it allows a caller to pre-select a string that is directly attached to the loopback file. Via udev rules we'l later make the device accessible through this name. Note that "lo_file_name" is supposed to carry a file name of the backing file, but the kernel actually does not care or enforce any of that, it just stores the filename and returns it later. This makes it so useful, as userspace has total control of that field. "lo_file_name" should not be confused with the sysattr "loop/backing_file" which is actually maintained by the kernel itself, and always shows the file to the backing inode without userspace having direct control over the returned string. Because the sysattr is generated by the kernel it is subject to file system namespacing and everything, while "lo_file_name" is not, it's really just a string passed through the kernel. --- man/systemd-dissect.xml | 19 +++++++++++++++++++ src/dissect/dissect.c | 25 +++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/man/systemd-dissect.xml b/man/systemd-dissect.xml index 1d0532713d..06c57a22ec 100644 --- a/man/systemd-dissect.xml +++ b/man/systemd-dissect.xml @@ -386,6 +386,25 @@ + + + + Configures the "reference" string the kernel shall report as backing file for the + loopback block device. While this is supposed to be a path or filename referencing the backing file, + this is not enforced and the kernel accepts arbitrary free-form strings, chosen by the user. Accepts + arbitrary strings up to a length of 63 characters. This sets the kernel's + .lo_file_name field for the block device. Note this is distinct from the + /sys/class/block/loopX/loop/backing_file attribute file that always reports a + path referring to the actual backing file. The latter is subject to mount namespace translation, the + former is not. + + This setting is particularly useful in combination with the command, + as it allows later referencing the allocated loop device via /dev/loop/by-ref/… + symlinks. Example: first, set up the loopback device via systemd-dissect attach + --loop-ref=quux foo.raw, and then reference it in a command via the specified filename: + cfdisk /dev/loop/by-ref/quux. + + diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index 6f7b44f0aa..a688e6f32c 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -81,9 +81,11 @@ static bool arg_legend = true; static bool arg_rmdir = false; static bool arg_in_memory = false; static char **arg_argv = NULL; +static char *arg_loop_ref = NULL; STATIC_DESTRUCTOR_REGISTER(arg_verity_settings, verity_settings_done); STATIC_DESTRUCTOR_REGISTER(arg_argv, strv_freep); +STATIC_DESTRUCTOR_REGISTER(arg_loop_ref, freep); static int help(void) { _cleanup_free_ char *link = NULL; @@ -123,6 +125,7 @@ static int help(void) { " not embedded in IMAGE\n" " --json=pretty|short|off\n" " Generate JSON output\n" + " --loop-ref=NAME Set reference string for loopback device\n" "\n%3$sCommands:%4$s\n" " -h --help Show this help\n" " --version Show package version\n" @@ -214,6 +217,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_DISCOVER, ARG_ATTACH, ARG_DETACH, + ARG_LOOP_REF, }; static const struct option options[] = { @@ -242,6 +246,7 @@ static int parse_argv(int argc, char *argv[]) { { "copy-to", no_argument, NULL, 'a' }, { "json", required_argument, NULL, ARG_JSON }, { "discover", no_argument, NULL, ARG_DISCOVER }, + { "loop-ref", required_argument, NULL, ARG_LOOP_REF }, {} }; @@ -435,6 +440,20 @@ static int parse_argv(int argc, char *argv[]) { arg_action = ACTION_DISCOVER; break; + case ARG_LOOP_REF: + if (isempty(optarg)) { + arg_loop_ref = mfree(arg_loop_ref); + break; + } + + if (strlen(optarg) >= sizeof_field(struct loop_info64, lo_file_name)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Loop device ref string '%s' is too long.", optarg); + + r = free_and_strdup_warn(&arg_loop_ref, optarg); + if (r < 0) + return r; + break; + case '?': return -EINVAL; @@ -1671,6 +1690,12 @@ static int run(int argc, char *argv[]) { if (r < 0) return log_error_errno(r, "Failed to set up loopback device for %s: %m", arg_image); + if (arg_loop_ref) { + r = loop_device_set_filename(d, arg_loop_ref); + if (r < 0) + log_warning_errno(r, "Failed to set loop reference string to '%s', ignoring: %m", arg_loop_ref); + } + r = dissect_loop_device_and_warn( d, &arg_verity_settings, From 6f5ef9e4c7b240d67cdefb1232c9e85e43d31586 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 6 Mar 2023 12:04:44 +0100 Subject: [PATCH 08/11] dissect: shorten code a bit --- src/dissect/dissect.c | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index a688e6f32c..8b1f1bf20c 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -1718,29 +1718,23 @@ static int run(int argc, char *argv[]) { switch (arg_action) { case ACTION_DISSECT: - r = action_dissect(m, d); - break; + return action_dissect(m, d); case ACTION_MOUNT: - r = action_mount(m, d); - break; + return action_mount(m, d); case ACTION_LIST: case ACTION_MTREE: case ACTION_COPY_FROM: case ACTION_COPY_TO: - r = action_list_or_mtree_or_copy(m, d); - break; + return action_list_or_mtree_or_copy(m, d); case ACTION_WITH: - r = action_with(m, d); - break; + return action_with(m, d); default: assert_not_reached(); } - - return r; } DEFINE_MAIN_FUNCTION(run); From 5ac52d1f7b7cd11cad8b5c2e9812d7ee7560a517 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 6 Mar 2023 12:55:59 +0100 Subject: [PATCH 09/11] udev: add /dev/loop/ symlinks This adds symlinks that allow accessing loopback block devices via stable names that reference their backing block devices, make the unpredictable naming of loopback devices less of an issue. Example: 1. Create a loopback block device for a file $F losetup --find $F 2. Reference the backing block device via its inode: L="$(stat -c '/dev/loop/by-inode/%Hd:%Ld-%i' $F)" fdisk $L In the above the loop device name (which might be /dev/loop47 or any other name) is not used at all. --- rules.d/60-persistent-storage.rules.in | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/rules.d/60-persistent-storage.rules.in b/rules.d/60-persistent-storage.rules.in index 498b24c22d..43c2060d59 100644 --- a/rules.d/60-persistent-storage.rules.in +++ b/rules.d/60-persistent-storage.rules.in @@ -141,4 +141,15 @@ ENV{ID_PART_ENTRY_SCHEME}=="gpt", ENV{ID_PART_ENTRY_NAME}=="?*", SYMLINK+="disk/ ENV{DISKSEQ}=="?*", ENV{DEVTYPE}!="partition", ENV{ID_IGNORE_DISKSEQ}!="1", SYMLINK+="disk/by-diskseq/$env{DISKSEQ}" ENV{DISKSEQ}=="?*", ENV{DEVTYPE}=="partition", ENV{ID_IGNORE_DISKSEQ}!="1", SYMLINK+="disk/by-diskseq/$env{DISKSEQ}-part%n" +# Create symlinks that allow referencing loopback devices by their backing file's inode number +ENV{DEVTYPE}!="partition", ENV{ID_LOOP_BACKING_DEVICE}!="", ENV{ID_LOOP_BACKING_INODE}!="", SYMLINK+="loop/by-inode/$env{ID_LOOP_BACKING_DEVICE}-$env{ID_LOOP_BACKING_INODE}" +ENV{DEVTYPE}=="partition", ENV{ID_LOOP_BACKING_DEVICE}!="", ENV{ID_LOOP_BACKING_INODE}!="", SYMLINK+="loop/by-inode/$env{ID_LOOP_BACKING_DEVICE}-$env{ID_LOOP_BACKING_INODE}-part%n" + +# Similar, but uses the .lo_file_name field of the loopback device (note that +# this is basically just a free-form string passed from userspace to the kernel +# when the device is created, it is not necessarily a file system path like the +# "loop/backing_file" sysfs attribute, which is always an absolute path) +ENV{DEVTYPE}!="partition", ENV{ID_LOOP_BACKING_FILENAME_ENC}!="", SYMLINK+="loop/by-ref/$env{ID_LOOP_BACKING_FILENAME_ENC}" +ENV{DEVTYPE}=="partition", ENV{ID_LOOP_BACKING_FILENAME_ENC}!="", SYMLINK+="loop/by-ref/$env{ID_LOOP_BACKING_FILENAME_ENC}-part%n" + LABEL="persistent_storage_end" From f5e46b9e09d4ff3f1e6ee6e3a90adc704780a661 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 6 Mar 2023 13:23:48 +0100 Subject: [PATCH 10/11] test: test new systemd-dissect --attach/--detach/--loop-ref= and /dev/loop/* symlinks Let's test that everything we just added works in combination. --- test/units/testsuite-50.sh | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test/units/testsuite-50.sh b/test/units/testsuite-50.sh index cb99b811c9..741167a215 100755 --- a/test/units/testsuite-50.sh +++ b/test/units/testsuite-50.sh @@ -430,6 +430,28 @@ mount -t ddi "${image}.gpt" "$T" -o ro,X-mount.mkdir,discard umount -R "$T" rmdir "$T" +LOOP="$(systemd-dissect --attach --loop-ref=waldo "${image}.raw")" + +# Wait until the symlinks we want to test are established +udevadm trigger -w "$LOOP" + +# Check if the /dev/loop/* symlinks really reference the right device +test /dev/loop/by-ref/waldo -ef "$LOOP" + +if [ "$(stat -c '%Hd:%Ld' "${image}.raw")" != '?d:?d' ] ; then + # Old stat didn't know the %Hd and %Ld specifiers and turned them into ?d + # instead. Let's simply skip the test on such old systems. + test "$(stat -c '/dev/loop/by-inode/%Hd:%Ld-%i' "${image}.raw")" -ef "$LOOP" +fi + +# Detach by loopback device +systemd-dissect --detach "$LOOP" + +# Detach by backing inode +systemd-dissect --attach --loop-ref=waldo "${image}.raw" +systemd-dissect --detach "${image}.raw" +(! systemd-dissect --detach "${image}.raw") + echo OK >/testok exit 0 From aa03f49917f88f8e4ef0175010c6a41cd4dd20cf Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 6 Mar 2023 12:13:57 +0100 Subject: [PATCH 11/11] update TODO --- TODO | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/TODO b/TODO index 541c02cb12..42334537e5 100644 --- a/TODO +++ b/TODO @@ -132,6 +132,12 @@ Features: * set MS_NOSYMFOLLOW for ESP and XBOOTLDR mounts both in gpt-generator and in dissect.c +* rework loopback support in fstab: when "loop" option is used, then + instantiate a new systemd-loop@.service for the source path, set the + lo_file_name field for it to something recognizable derived from the fstab + line, and then generate a mount unit for it using a udev generated symlink + based on lo_file_name. + * remove tomoyo support, it's obsolete and unmaintained apparently * journald: add varlink service that allows subscribing to certain log events, @@ -277,9 +283,6 @@ Features: * systemd-sysext: for sysext DDIs picked up via EFI stub, set much stricter image policy by default -* systemd-dissect: maybe add "--attach" and "--detach" verbs which - synchronously attach a DDI to a loopback device but not actually mount them. - * pam_systemd_home: add module parameter to control whether to only accept only password or only pcks11/fido2 auth, and then use this to hook nicely into two of the three PAM stacks gdm provides.