]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
udev-builtin-blkid: pick up info of backing file
authorLennart Poettering <lennart@poettering.net>
Mon, 6 Mar 2023 10:53:26 +0000 (11:53 +0100)
committerLennart Poettering <lennart@poettering.net>
Thu, 9 Mar 2023 15:40:55 +0000 (16:40 +0100)
This adds support for retrieving info about the inode backing a loopback
file to udev-builtin-blkid. It will pick up the inode number and device
of the backing inode, as well as the lo_file_name[] array that the
loopback device maintains.

A later patch uses this information to create block device symlinks in
/dev/ that allow refering block devices by their backing inodes. This is
useful when separate tools set up a loopback device from those which
ultimately shall mount them, and there shall be a stable reference be
passed along. For example, we can add a new kernel option setuploop= or
so which allows setting up a block device via a generator, and still
have a way to safely reference later.

And yes, this doesn't directly have anything to do with the probing
libblkid does, but it's close enough, and we have the device open anyway
here, so the additional ioctl() here should not hurt.

src/udev/udev-builtin-blkid.c

index d2de03d5f9b23e21e507e0e6efd5b32fb0c71fd5..154cf7000f9ea2aff8a31e0cb1855885745a30ae 100644 (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;
 }