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
10 changed files with 457 additions and 16 deletions
+175 -10
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);
+63 -1
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;
}
+7 -2
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);
+108
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;
}
+18
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);
}
+3
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);