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. diff --git a/man/systemd-dissect.xml b/man/systemd-dissect.xml index 7dfad29c35..06c57a22ec 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. + + @@ -361,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/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" diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index db26645fd9..8b1f1bf20c 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, @@ -79,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; @@ -94,6 +98,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" @@ -119,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" @@ -126,6 +133,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 +215,9 @@ static int parse_argv(int argc, char *argv[]) { ARG_JSON, ARG_MTREE, ARG_DISCOVER, + ARG_ATTACH, + ARG_DETACH, + ARG_LOOP_REF, }; static const struct option options[] = { @@ -215,6 +227,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 }, @@ -232,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 }, {} }; @@ -291,6 +306,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; @@ -417,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; @@ -454,6 +491,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 +1539,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 +1663,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(); @@ -1528,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, @@ -1537,6 +1705,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, @@ -1547,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); diff --git a/src/shared/loop-util.c b/src/shared/loop-util.c index 6e187efe94..7d92a8ccce 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, @@ -1103,3 +1111,57 @@ 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; +} + +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 5c79ed7cce..dda14ec4f0 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 */ @@ -47,3 +49,6 @@ 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); +int loop_device_set_filename(LoopDevice *d, const char *name); 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; } 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); 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