From 6bea3d8e0f076b27323daba89f46993cd1925f0d Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 8 Dec 2022 12:45:26 +0100 Subject: [PATCH 1/5] data-fd-util: add new memfd_clone_fd() helper This adds a new helper for cloning any file's contents (or block device contents) into a new memfd. --- src/shared/data-fd-util.c | 52 +++++++++++++++++++++++++++++++++++++++ src/shared/data-fd-util.h | 1 + 2 files changed, 53 insertions(+) diff --git a/src/shared/data-fd-util.c b/src/shared/data-fd-util.c index b6eb3d9f41..58ee1845af 100644 --- a/src/shared/data-fd-util.c +++ b/src/shared/data-fd-util.c @@ -4,6 +4,9 @@ #include #include #include +#if HAVE_LINUX_MEMFD_H +#include +#endif #include "alloc-util.h" #include "copy.h" @@ -12,6 +15,8 @@ #include "fs-util.h" #include "io-util.h" #include "memfd-util.h" +#include "missing_mman.h" +#include "missing_syscall.h" #include "tmpfile-util.h" /* When the data is smaller or equal to 64K, try to place the copy in a memfd/pipe */ @@ -343,3 +348,50 @@ finish: return fd_reopen(tmp_fd, O_RDONLY|O_CLOEXEC); } + +int memfd_clone_fd(int fd, const char *name, int mode) { + _cleanup_close_ int mfd = -EBADF; + bool ro; + int r; + + /* Creates a clone of a regular file in a memfd. Unlike copy_data_fd() this returns strictly a memfd + * (and if it can't it will fail). Thus the resulting fd is seekable, and definitely reports as + * S_ISREG. */ + + assert(fd >= 0); + assert(name); + assert(IN_SET(mode & O_ACCMODE, O_RDONLY, O_RDWR)); + assert((mode & ~(O_RDONLY|O_RDWR|O_CLOEXEC)) == 0); + + ro = (mode & O_ACCMODE) == O_RDONLY; + + mfd = memfd_create(name, + ((FLAGS_SET(mode, O_CLOEXEC) || ro) ? MFD_CLOEXEC : 0) | + (ro ? MFD_ALLOW_SEALING : 0)); + if (mfd < 0) + return -errno; + + r = copy_bytes(fd, mfd, UINT64_MAX, COPY_REFLINK); + if (r < 0) + return r; + + if (ro) { + _cleanup_close_ int rfd = -1; + + r = memfd_set_sealed(mfd); + if (r < 0) + return r; + + rfd = fd_reopen(mfd, mode); + if (rfd < 0) + return rfd; + + return TAKE_FD(rfd); + } + + off_t f = lseek(mfd, 0, SEEK_SET); + if (f < 0) + return -errno; + + return TAKE_FD(mfd); +} diff --git a/src/shared/data-fd-util.h b/src/shared/data-fd-util.h index 827e149662..f0e7923556 100644 --- a/src/shared/data-fd-util.h +++ b/src/shared/data-fd-util.h @@ -5,3 +5,4 @@ int acquire_data_fd(const void *data, size_t size, unsigned flags); int copy_data_fd(int fd); +int memfd_clone_fd(int fd, const char *name, int mode); From fcd8a19da8a4f6d24ad46fe4a11f8be50424e317 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 8 Dec 2022 12:45:48 +0100 Subject: [PATCH 2/5] loop-util: add new loop_device_make_by_path_memory() helper This uses the new memfd_clone_fd() call to make an in-memory copy of some file before setting up a loopback block device on it. --- src/shared/loop-util.c | 42 ++++++++++++++++++++++++++++++++++++++++++ src/shared/loop-util.h | 1 + 2 files changed, 43 insertions(+) diff --git a/src/shared/loop-util.c b/src/shared/loop-util.c index fb7e80b1b5..ed31454e31 100644 --- a/src/shared/loop-util.c +++ b/src/shared/loop-util.c @@ -17,6 +17,7 @@ #include "alloc-util.h" #include "blockdev-util.h" +#include "data-fd-util.h" #include "device-util.h" #include "devnum-util.h" #include "env-util.h" @@ -640,6 +641,47 @@ int loop_device_make_by_path( return loop_device_make_internal(path, fd, open_flags, 0, 0, 0, loop_flags, lock_op, ret); } +int loop_device_make_by_path_memory( + const char *path, + int open_flags, + uint32_t loop_flags, + int lock_op, + LoopDevice **ret) { + + _cleanup_close_ int fd = -EBADF, mfd = -EBADF; + _cleanup_free_ char *fn = NULL; + struct stat st; + int r; + + assert(path); + assert(IN_SET(open_flags, O_RDWR, O_RDONLY)); + assert(ret); + + loop_flags &= ~LO_FLAGS_DIRECT_IO; /* memfds don't support O_DIRECT, hence LO_FLAGS_DIRECT_IO can't be used either */ + + fd = open(path, O_CLOEXEC|O_NONBLOCK|O_NOCTTY|O_RDONLY); + if (fd < 0) + return -errno; + + if (fstat(fd, &st) < 0) + return -errno; + + if (!S_ISREG(st.st_mode) && !S_ISBLK(st.st_mode)) + return -EBADF; + + r = path_extract_filename(path, &fn); + if (r < 0) + return r; + + mfd = memfd_clone_fd(fd, fn, open_flags|O_CLOEXEC); + if (mfd < 0) + return mfd; + + fd = safe_close(fd); /* Let's close the original early */ + + return loop_device_make_internal(NULL, mfd, open_flags, 0, 0, 0, loop_flags, lock_op, ret); +} + static LoopDevice* loop_device_free(LoopDevice *d) { _cleanup_close_ int control = -1; int r; diff --git a/src/shared/loop-util.h b/src/shared/loop-util.h index e466a5abbd..512b5ab4a2 100644 --- a/src/shared/loop-util.h +++ b/src/shared/loop-util.h @@ -30,6 +30,7 @@ struct LoopDevice { int loop_device_make(int fd, int open_flags, uint64_t offset, uint64_t size, uint32_t block_size, uint32_t loop_flags, int lock_op, LoopDevice **ret); int loop_device_make_by_path(const char *path, int open_flags, uint32_t loop_flags, int lock_op, LoopDevice **ret); +int loop_device_make_by_path_memory(const char *path, int open_flags, uint32_t loop_flags, int lock_op, LoopDevice **ret); int loop_device_open(sd_device *dev, int open_flags, int lock_op, LoopDevice **ret); int loop_device_open_from_fd(int fd, int open_flags, int lock_op, LoopDevice **ret); int loop_device_open_from_path(const char *path, int open_flags, int lock_op, LoopDevice **ret); From 6c07d570751b1886d8fe0e4ccab6cefe3d8cdb28 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 8 Dec 2022 12:46:09 +0100 Subject: [PATCH 3/5] dissect: add a mode for operating on an in-memory copy of a DDI, instead of directly on it This is useful for operating in ephemeral, writable mode on any image, including read-only ones. It also has the benefit of not keeping the image file's filesystem busy. Inspired by the discussions in #25648 --- src/dissect/dissect.c | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index e7ea582300..b7bcf731f7 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -77,6 +77,7 @@ static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF; static PagerFlags arg_pager_flags = 0; static bool arg_legend = true; static bool arg_rmdir = false; +static bool arg_in_memory = false; static char **arg_argv = NULL; STATIC_DESTRUCTOR_REGISTER(arg_verity_settings, verity_settings_done); @@ -108,6 +109,7 @@ static int help(void) { " --mkdir Make mount directory before mounting, if missing\n" " --rmdir Remove mount directory after unmounting\n" " --discard=MODE Choose 'discard' mode (disabled, loop, all, crypto)\n" + " --in-memory Copy image into memory\n" " --root-hash=HASH Specify root hash for verity\n" " --root-hash-sig=SIG Specify pkcs7 signature of root hash for verity\n" " as a DER encoded PKCS7, either as a path to a file\n" @@ -200,6 +202,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_VERITY_DATA, ARG_MKDIR, ARG_RMDIR, + ARG_IN_MEMORY, ARG_JSON, ARG_MTREE, ARG_DISCOVER, @@ -222,6 +225,7 @@ static int parse_argv(int argc, char *argv[]) { { "verity-data", required_argument, NULL, ARG_VERITY_DATA }, { "mkdir", no_argument, NULL, ARG_MKDIR }, { "rmdir", no_argument, NULL, ARG_RMDIR }, + { "in-memory", no_argument, NULL, ARG_IN_MEMORY }, { "list", no_argument, NULL, 'l' }, { "mtree", no_argument, NULL, ARG_MTREE }, { "copy-from", no_argument, NULL, 'x' }, @@ -340,6 +344,10 @@ static int parse_argv(int argc, char *argv[]) { break; } + case ARG_IN_MEMORY: + arg_in_memory = true; + break; + case ARG_ROOT_HASH: { _cleanup_free_ void *p = NULL; size_t l; @@ -1392,7 +1400,8 @@ static int action_discover(void) { static int run(int argc, char *argv[]) { _cleanup_(dissected_image_unrefp) DissectedImage *m = NULL; _cleanup_(loop_device_unrefp) LoopDevice *d = NULL; - int r; + uint32_t loop_flags; + int open_flags, r; log_setup(); @@ -1417,12 +1426,13 @@ static int run(int argc, char *argv[]) { * available we turn off partition table * support */ - r = loop_device_make_by_path( - arg_image, - FLAGS_SET(arg_flags, DISSECT_IMAGE_DEVICE_READ_ONLY) ? O_RDONLY : O_RDWR, - FLAGS_SET(arg_flags, DISSECT_IMAGE_NO_PARTITION_TABLE) ? 0 : LO_FLAGS_PARTSCAN, - LOCK_SH, - &d); + open_flags = FLAGS_SET(arg_flags, DISSECT_IMAGE_DEVICE_READ_ONLY) ? O_RDONLY : O_RDWR; + loop_flags = FLAGS_SET(arg_flags, DISSECT_IMAGE_NO_PARTITION_TABLE) ? 0 : LO_FLAGS_PARTSCAN; + + if (arg_in_memory) + r = loop_device_make_by_path_memory(arg_image, open_flags, loop_flags, LOCK_SH, &d); + else + r = loop_device_make_by_path(arg_image, open_flags, loop_flags, LOCK_SH, &d); if (r < 0) return log_error_errno(r, "Failed to set up loopback device for %s: %m", arg_image); From e7e2fbdd60e9f3d452b80f48e3d0029f5c1dc827 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 8 Dec 2022 12:46:24 +0100 Subject: [PATCH 4/5] man: add doc for new --in-memory switch of systemd-dissect --- man/systemd-dissect.xml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/man/systemd-dissect.xml b/man/systemd-dissect.xml index 8c6211b601..1461d98fea 100644 --- a/man/systemd-dissect.xml +++ b/man/systemd-dissect.xml @@ -320,6 +320,15 @@ systems. If all discarding is unconditionally enabled. + + + + If specified an in-memory copy of the specified disk image is used. This may be used + to operate with write-access on a (possibly read-only) image, without actually modifying the original + file. This may also be used in order to operate on a disk image without keeping the originating file + system busy, in order to allow it to be unmounted. + + From 971d83a8d3bc991788348ce81134826edb31ce7c Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 8 Dec 2022 12:46:41 +0100 Subject: [PATCH 5/5] test: add simple test for new --in-memory switch for systemd-dissect --- test/units/testsuite-50.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/units/testsuite-50.sh b/test/units/testsuite-50.sh index 031803f5b8..088e567727 100755 --- a/test/units/testsuite-50.sh +++ b/test/units/testsuite-50.sh @@ -237,6 +237,12 @@ grep -q -F -f "$os_release" "${image_dir}/mount/etc/os-release" grep -q -F "MARKER=1" "${image_dir}/mount/usr/lib/os-release" systemd-dissect --umount "${image_dir}/mount" +systemd-dissect --root-hash "${roothash}" --mount "${image}.gpt" --in-memory "${image_dir}/mount" +grep -q -F -f "$os_release" "${image_dir}/mount/usr/lib/os-release" +grep -q -F -f "$os_release" "${image_dir}/mount/etc/os-release" +grep -q -F "MARKER=1" "${image_dir}/mount/usr/lib/os-release" +systemd-dissect --umount "${image_dir}/mount" + # add explicit -p MountAPIVFS=yes once to test the parser systemd-run -P -p RootImage="${image}.gpt" -p RootHash="${roothash}" -p MountAPIVFS=yes cat /usr/lib/os-release | grep -q -F "MARKER=1"