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