Merge pull request #26693 from poettering/udev-loop-links

udev: add /dev/loop/by-inode/… + /dev/loop/by-ref/… loopback block device symlinks
This commit is contained in:
Lennart Poettering 2023-03-10 09:34:31 +01:00 committed by GitHub
commit 862481ece0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 457 additions and 16 deletions

9
TODO
View file

@ -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.

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>
@ -361,6 +386,25 @@
</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--loop-ref=</option></term>
<listitem><para>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
<literal>.lo_file_name</literal> field for the block device. Note this is distinct from the
<filename>/sys/class/block/loopX/loop/backing_file</filename> 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.</para>
<para>This setting is particularly useful in combination with the <option>--attach</option> command,
as it allows later referencing the allocated loop device via <filename>/dev/loop/by-ref/…</filename>
symlinks. Example: first, set up the loopback device via <command>systemd-dissect attach
--loop-ref=quux foo.raw</command>, and then reference it in a command via the specified filename:
<command>cfdisk /dev/loop/by-ref/quux</command>.</para></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="no-pager" />
<xi:include href="standard-options.xml" xpointer="no-legend" />
<xi:include href="standard-options.xml" xpointer="json" />

View file

@ -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"

View file

@ -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);

View file

@ -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;
}

View file

@ -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);

View file

@ -5,11 +5,17 @@
* Copyright © 2011 Karel Zak <kzak@redhat.com>
*/
#if HAVE_VALGRIND_MEMCHECK_H
#include <valgrind/memcheck.h>
#endif
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <linux/loop.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#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;
}

View file

@ -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);
}

View file

@ -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);

View file

@ -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