From ee72df1c7b449232b513cf11fc377d724331632e Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 28 Jul 2020 18:16:19 +0200 Subject: [PATCH 01/20] firstboot: move --image= logic into common code That way we can reuse it in tmpfiles/sysusers/journalctl and so on. --- src/firstboot/firstboot.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index d56de0bb25..e4c7a2d374 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -23,7 +23,6 @@ #include "memory-util.h" #include "mkdir.h" #include "mount-util.h" -#include "namespace-util.h" #include "os-util.h" #include "parse-util.h" #include "path-util.h" From 140788f75f3e2812fc40e01fb996b972fb9d6266 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 28 Jul 2020 23:39:09 +0200 Subject: [PATCH 02/20] dissect: support --discard=list --- src/dissect/dissect.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index 318cd37c6f..d0ae2a61d3 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -117,7 +117,13 @@ static int parse_argv(int argc, char *argv[]) { flags = DISSECT_IMAGE_DISCARD_ON_LOOP | DISSECT_IMAGE_DISCARD; else if (streq(optarg, "crypt")) flags = DISSECT_IMAGE_DISCARD_ANY; - else + else if (streq(optarg, "list")) { + puts("disabled\n" + "all\n" + "crypt\n" + "loop"); + return 0; + } else return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown --discard= parameter: %s", optarg); From 1ffd93683bcc31465ed9c9c90bdfaa5d7cb8d8f5 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 28 Jul 2020 18:49:55 +0200 Subject: [PATCH 03/20] mkdir: handle mkdir_p() of simple filename gracefully --- src/basic/mkdir.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/basic/mkdir.c b/src/basic/mkdir.c index 6ebc2b95fd..d591e65e41 100644 --- a/src/basic/mkdir.c +++ b/src/basic/mkdir.c @@ -106,7 +106,7 @@ int mkdir_parents_internal(const char *prefix, const char *path, mode_t mode, ui /* return immediately if directory exists */ e = strrchr(path, '/'); if (!e) - return -EINVAL; + return 0; if (e == path) return 0; From 5c05f062646169cf8df838310a1577431ea43a73 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 28 Jul 2020 18:50:17 +0200 Subject: [PATCH 04/20] dissect: optionally mkdir directory to overmount --- src/dissect/dissect.c | 55 +++++++++++++++++++++++++++++--------- src/shared/dissect-image.c | 10 +++++++ src/shared/dissect-image.h | 1 + 3 files changed, 53 insertions(+), 13 deletions(-) diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index d0ae2a61d3..0384c2c3f0 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -13,8 +13,10 @@ #include "main-func.h" #include "parse-util.h" #include "path-util.h" +#include "pretty-print.h" #include "string-util.h" #include "strv.h" +#include "terminal-util.h" #include "user-util.h" #include "util.h" @@ -37,15 +39,21 @@ STATIC_DESTRUCTOR_REGISTER(arg_verity_data, freep); STATIC_DESTRUCTOR_REGISTER(arg_root_hash_sig_path, freep); STATIC_DESTRUCTOR_REGISTER(arg_root_hash_sig, freep); -static void help(void) { - printf("%s [OPTIONS...] IMAGE\n" - "%s [OPTIONS...] --mount IMAGE PATH\n" - "Dissect a file system OS image.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -m --mount Mount the image to the specified directory\n" +static int help(void) { + _cleanup_free_ char *link = NULL; + int r; + + r = terminal_urlify_man("systemd-dissect", "1", &link); + if (r < 0) + return log_oom(); + + printf("%1$s [OPTIONS...] IMAGE\n" + "%1$s [OPTIONS...] --mount IMAGE PATH\n\n" + "%5$sDissect a file system OS image.%6$s\n\n" + "%3$sOptions:%4$s\n" " -r --read-only Mount read-only\n" " --fsck=BOOL Run fsck before mounting\n" + " --mkdir Make mount directory before mounting, if missing\n" " --discard=MODE Choose 'discard' mode (disabled, loop, all, crypto)\n" " --root-hash=HASH Specify root hash for verity\n" " --root-hash-sig=SIG Specify pkcs7 signature of root hash for verity\n" @@ -53,9 +61,19 @@ static void help(void) { " or as an ASCII base64 encoded string prefixed by\n" " 'base64:'\n" " --verity-data=PATH Specify data file with hash tree for verity if it is\n" - " not embedded in IMAGE\n", - program_invocation_short_name, - program_invocation_short_name); + " not embedded in IMAGE\n" + "\n%3$sCommands:%4$s\n" + " -h --help Show this help\n" + " --version Show package version\n" + " -m --mount Mount the image to the specified directory\n" + " -M Shortcut for --mount --mkdir\n" + "\nSee the %2$s for details.\n" + , program_invocation_short_name + , link + , ansi_underline(), ansi_normal() + , ansi_highlight(), ansi_normal()); + + return 0; } static int parse_argv(int argc, char *argv[]) { @@ -67,6 +85,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_FSCK, ARG_VERITY_DATA, ARG_ROOT_HASH_SIG, + ARG_MKDIR, }; static const struct option options[] = { @@ -79,6 +98,7 @@ static int parse_argv(int argc, char *argv[]) { { "fsck", required_argument, NULL, ARG_FSCK }, { "verity-data", required_argument, NULL, ARG_VERITY_DATA }, { "root-hash-sig", required_argument, NULL, ARG_ROOT_HASH_SIG }, + { "mkdir", no_argument, NULL, ARG_MKDIR }, {} }; @@ -87,13 +107,12 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hmr", options, NULL)) >= 0) { + while ((c = getopt_long(argc, argv, "hmrM", options, NULL)) >= 0) { switch (c) { case 'h': - help(); - return 0; + return help(); case ARG_VERSION: return version(); @@ -102,6 +121,16 @@ static int parse_argv(int argc, char *argv[]) { arg_action = ACTION_MOUNT; break; + case ARG_MKDIR: + arg_flags |= DISSECT_IMAGE_MKDIR; + break; + + case 'M': + /* Shortcut combination of the above two */ + arg_action = ACTION_MOUNT; + arg_flags |= DISSECT_IMAGE_MKDIR; + break; + case 'r': arg_flags |= DISSECT_IMAGE_READ_ONLY; break; diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index d3f183b50c..2b3d6c08f0 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -1047,6 +1047,12 @@ static int mount_partition( if (!strextend_with_separator(&options, ",", m->mount_options, NULL)) return -ENOMEM; + if (FLAGS_SET(flags, DISSECT_IMAGE_MKDIR)) { + r = mkdir_p(p, 0755); + if (r < 0) + return r; + } + r = mount_verbose(LOG_DEBUG, node, p, fstype, MS_NODEV|(rw ? 0 : MS_RDONLY), options); if (r < 0) return r; @@ -1080,6 +1086,10 @@ int dissected_image_mount(DissectedImage *m, const char *where, uid_t uid_shift, if (flags & DISSECT_IMAGE_MOUNT_ROOT_ONLY) return 0; + /* Mask DISSECT_IMAGE_MKDIR for all subdirs: the idea is that only the top-level mount point is + * created if needed, but the image itself not modified. */ + flags &= ~DISSECT_IMAGE_MKDIR; + r = mount_partition(m->partitions + PARTITION_HOME, where, "/home", uid_shift, flags); if (r < 0) return r; diff --git a/src/shared/dissect-image.h b/src/shared/dissect-image.h index 7f67c8745e..3d0a191d71 100644 --- a/src/shared/dissect-image.h +++ b/src/shared/dissect-image.h @@ -69,6 +69,7 @@ typedef enum DissectImageFlags { DISSECT_IMAGE_FSCK = 1 << 11, /* File system check the partition before mounting (no effect when combined with DISSECT_IMAGE_READ_ONLY) */ DISSECT_IMAGE_NO_PARTITION_TABLE = 1 << 12, /* Only recognize single file system images */ DISSECT_IMAGE_VERITY_SHARE = 1 << 13, /* When activating a verity device, reuse existing one if already open */ + DISSECT_IMAGE_MKDIR = 1 << 14, /* Make directory to mount right before mounting, if missing */ } DissectImageFlags; struct DissectedImage { From bacf21e9e900f66da72b3f47b0de0e8fcb3c4e16 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 28 Jul 2020 23:38:23 +0200 Subject: [PATCH 05/20] copy: add copy_access() helper for copying access mode --- src/basic/copy.c | 15 +++++++++++++++ src/basic/copy.h | 1 + 2 files changed, 16 insertions(+) diff --git a/src/basic/copy.c b/src/basic/copy.c index b384010ae3..54f7235b16 100644 --- a/src/basic/copy.c +++ b/src/basic/copy.c @@ -969,6 +969,21 @@ int copy_times(int fdf, int fdt, CopyFlags flags) { return 0; } +int copy_access(int fdf, int fdt) { + struct stat st; + + assert(fdf >= 0); + assert(fdt >= 0); + + if (fstat(fdf, &st) < 0) + return -errno; + + if (fchmod(fdt, st.st_mode & 07777) < 0) + return -errno; + + return 0; +} + int copy_xattr(int fdf, int fdt) { _cleanup_free_ char *names = NULL; int ret = 0, r; diff --git a/src/basic/copy.h b/src/basic/copy.h index af8e88af04..ab9031038e 100644 --- a/src/basic/copy.h +++ b/src/basic/copy.h @@ -61,4 +61,5 @@ static inline int copy_bytes(int fdf, int fdt, uint64_t max_bytes, CopyFlags cop } int copy_times(int fdf, int fdt, CopyFlags flags); +int copy_access(int fdf, int fdt); int copy_xattr(int fdf, int fdt); From 33973b841d686959e099dd3ea1e35e3814d48e3d Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 28 Jul 2020 19:47:43 +0200 Subject: [PATCH 06/20] dissect: add support for copying files in/out of image --- src/dissect/dissect.c | 236 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 231 insertions(+), 5 deletions(-) diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index 0384c2c3f0..0ccb603b5d 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -6,26 +6,38 @@ #include #include "architecture.h" +#include "copy.h" #include "dissect-image.h" +#include "fd-util.h" +#include "fs-util.h" #include "hexdecoct.h" #include "log.h" #include "loop-util.h" #include "main-func.h" +#include "mkdir.h" +#include "mount-util.h" +#include "namespace-util.h" #include "parse-util.h" #include "path-util.h" #include "pretty-print.h" +#include "stat-util.h" #include "string-util.h" #include "strv.h" #include "terminal-util.h" +#include "tmpfile-util.h" #include "user-util.h" #include "util.h" static enum { ACTION_DISSECT, ACTION_MOUNT, + ACTION_COPY_FROM, + ACTION_COPY_TO, } arg_action = ACTION_DISSECT; static const char *arg_image = NULL; static const char *arg_path = NULL; +static const char *arg_source = NULL; +static const char *arg_target = NULL; static DissectImageFlags arg_flags = DISSECT_IMAGE_REQUIRE_ROOT|DISSECT_IMAGE_DISCARD_ON_LOOP|DISSECT_IMAGE_RELAX_VAR_CHECK|DISSECT_IMAGE_FSCK; static void *arg_root_hash = NULL; static char *arg_verity_data = NULL; @@ -48,7 +60,9 @@ static int help(void) { return log_oom(); printf("%1$s [OPTIONS...] IMAGE\n" - "%1$s [OPTIONS...] --mount IMAGE PATH\n\n" + "%1$s [OPTIONS...] --mount IMAGE PATH\n" + "%1$s [OPTIONS...] --copy-from IMAGE PATH [TARGET]\n" + "%1$s [OPTIONS...] --copy-to IMAGE [SOURCE] PATH\n\n" "%5$sDissect a file system OS image.%6$s\n\n" "%3$sOptions:%4$s\n" " -r --read-only Mount read-only\n" @@ -67,6 +81,8 @@ static int help(void) { " --version Show package version\n" " -m --mount Mount the image to the specified directory\n" " -M Shortcut for --mount --mkdir\n" + " -x --copy-from Copy files from image to host\n" + " -a --copy-to Copy files from host to image\n" "\nSee the %2$s for details.\n" , program_invocation_short_name , link @@ -99,6 +115,8 @@ static int parse_argv(int argc, char *argv[]) { { "verity-data", required_argument, NULL, ARG_VERITY_DATA }, { "root-hash-sig", required_argument, NULL, ARG_ROOT_HASH_SIG }, { "mkdir", no_argument, NULL, ARG_MKDIR }, + { "copy-from", no_argument, NULL, 'x' }, + { "copy-to", no_argument, NULL, 'a' }, {} }; @@ -107,7 +125,7 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hmrM", options, NULL)) >= 0) { + while ((c = getopt_long(argc, argv, "hmrMxa", options, NULL)) >= 0) { switch (c) { @@ -131,6 +149,15 @@ static int parse_argv(int argc, char *argv[]) { arg_flags |= DISSECT_IMAGE_MKDIR; break; + case 'x': + arg_action = ACTION_COPY_FROM; + arg_flags |= DISSECT_IMAGE_READ_ONLY; + break; + + case 'a': + arg_action = ACTION_COPY_TO; + break; + case 'r': arg_flags |= DISSECT_IMAGE_READ_ONLY; break; @@ -233,7 +260,7 @@ static int parse_argv(int argc, char *argv[]) { case ACTION_DISSECT: if (optind + 1 != argc) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Expected a file path as only argument."); + "Expected an image file path as only argument."); arg_image = argv[optind]; arg_flags |= DISSECT_IMAGE_READ_ONLY; @@ -242,12 +269,41 @@ static int parse_argv(int argc, char *argv[]) { case ACTION_MOUNT: if (optind + 2 != argc) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Expected a file path and mount point path as only arguments."); + "Expected an image file path and mount point path as only arguments."); arg_image = argv[optind]; arg_path = argv[optind + 1]; break; + case ACTION_COPY_FROM: + if (argc < optind + 2 || argc > optind + 3) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Expected an image file path, a source path and an optional destination path as only arguments."); + + arg_image = argv[optind]; + arg_source = argv[optind + 1]; + arg_target = argc > optind + 2 ? argv[optind + 2] : "-" /* this means stdout */ ; + + arg_flags |= DISSECT_IMAGE_READ_ONLY; + break; + + case ACTION_COPY_TO: + if (argc < optind + 2 || argc > optind + 3) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Expected an image file path, an optional source path and a destination path as only arguments."); + + arg_image = argv[optind]; + + if (argc > optind + 2) { + arg_source = argv[optind + 1]; + arg_target = argv[optind + 2]; + } else { + arg_source = "-"; /* this means stdin */ + arg_target = argv[optind + 1]; + } + + break; + default: assert_not_reached("Unknown action."); } @@ -351,7 +407,13 @@ static int run(int argc, char *argv[]) { } case ACTION_MOUNT: - r = dissected_image_decrypt_interactively(m, NULL, arg_root_hash, arg_root_hash_size, arg_verity_data, arg_root_hash_sig_path, arg_root_hash_sig, arg_root_hash_sig_size, arg_flags, &di); + r = dissected_image_decrypt_interactively( + m, NULL, + arg_root_hash, arg_root_hash_size, + arg_verity_data, + arg_root_hash_sig_path, arg_root_hash_sig, arg_root_hash_sig_size, + arg_flags, + &di); if (r < 0) return r; @@ -370,6 +432,170 @@ static int run(int argc, char *argv[]) { loop_device_relinquish(d); break; + case ACTION_COPY_FROM: + case ACTION_COPY_TO: { + _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL; + _cleanup_(rmdir_and_freep) char *created_dir = NULL; + _cleanup_free_ char *temp = NULL; + + r = dissected_image_decrypt_interactively( + m, NULL, + arg_root_hash, arg_root_hash_size, + arg_verity_data, + arg_root_hash_sig_path, arg_root_hash_sig, arg_root_hash_sig_size, + arg_flags, + &di); + if (r < 0) + return r; + + r = detach_mount_namespace(); + if (r < 0) + return log_error_errno(r, "Failed to detach mount namespace: %m"); + + r = tempfn_random_child(NULL, program_invocation_short_name, &temp); + if (r < 0) + return log_error_errno(r, "Failed to generate temporary mount directory: %m"); + + r = mkdir_p(temp, 0700); + if (r < 0) + return log_error_errno(r, "Failed to create mount point: %m"); + + created_dir = TAKE_PTR(temp); + + r = dissected_image_mount(m, created_dir, UID_INVALID, arg_flags); + if (r == -EUCLEAN) + return log_error_errno(r, "File system check on image failed: %m"); + if (r < 0) + return log_error_errno(r, "Failed to mount image: %m"); + + mounted_dir = TAKE_PTR(created_dir); + + if (di) { + r = decrypted_image_relinquish(di); + if (r < 0) + return log_error_errno(r, "Failed to relinquish DM devices: %m"); + } + + loop_device_relinquish(d); + + if (arg_action == ACTION_COPY_FROM) { + _cleanup_close_ int source_fd = -1, target_fd = -1; + + source_fd = chase_symlinks_and_open(arg_source, mounted_dir, CHASE_PREFIX_ROOT|CHASE_WARN, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL); + if (source_fd < 0) + return log_error_errno(source_fd, "Failed to open source path '%s' in image '%s': %m", arg_source, arg_image); + + /* Copying to stdout? */ + if (streq(arg_target, "-")) { + r = copy_bytes(source_fd, STDOUT_FILENO, (uint64_t) -1, COPY_REFLINK); + if (r < 0) + return log_error_errno(r, "Failed to copy bytes from %s in mage '%s' to stdout: %m", arg_source, arg_image); + + /* When we copy to stdou we don't copy any attributes (i.e. no access mode, no ownership, no xattr, no times) */ + break; + } + + /* Try to copy as directory? */ + r = copy_directory_fd(source_fd, arg_target, COPY_REFLINK|COPY_MERGE_EMPTY|COPY_SIGINT); + if (r >= 0) + break; + if (r != -ENOTDIR) + return log_error_errno(r, "Failed to copy %s in image '%s' to '%s': %m", arg_source, arg_image, arg_target); + + r = fd_verify_regular(source_fd); + if (r == -EISDIR) + return log_error_errno(r, "Target '%s' exists already and is not a directory.", arg_target); + if (r < 0) + return log_error_errno(r, "Source path %s in image '%s' is neither regular file nor directory, refusing: %m", arg_source, arg_image); + + /* Nah, it's a plain file! */ + target_fd = open(arg_target, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0600); + if (target_fd < 0) + return log_error_errno(errno, "Failed to create regular file at target path '%s': %m", arg_target); + + r = copy_bytes(source_fd, target_fd, (uint64_t) -1, COPY_REFLINK); + if (r < 0) + return log_error_errno(r, "Failed to copy bytes from %s in mage '%s' to '%s': %m", arg_source, arg_image, arg_target); + + (void) copy_xattr(source_fd, target_fd); + (void) copy_access(source_fd, target_fd); + (void) copy_times(source_fd, target_fd, 0); + + /* When this is a regular file we don't copy ownership! */ + + } else { + _cleanup_close_ int source_fd = -1, target_fd = -1; + _cleanup_close_ int dfd = -1; + _cleanup_free_ char *dn = NULL; + + assert(arg_action == ACTION_COPY_TO); + + dn = dirname_malloc(arg_target); + if (!dn) + return log_oom(); + + r = chase_symlinks(dn, mounted_dir, CHASE_PREFIX_ROOT|CHASE_WARN, NULL, &dfd); + if (r < 0) + return log_error_errno(r, "Failed to open '%s': %m", dn); + + /* Are we reading from stdin? */ + if (streq(arg_source, "-")) { + target_fd = openat(dfd, basename(arg_target), O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_EXCL, 0644); + if (target_fd < 0) + return log_error_errno(errno, "Failed to open target file '%s': %m", arg_target); + + r = copy_bytes(STDIN_FILENO, target_fd, (uint64_t) -1, COPY_REFLINK); + if (r < 0) + return log_error_errno(r, "Failed to copy bytes from stdin to '%s' in image '%s': %m", arg_target, arg_image); + + /* When we copy from stdin we don't copy any attributes (i.e. no access mode, no ownership, no xattr, no times) */ + break; + } + + source_fd = open(arg_source, O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (source_fd < 0) + return log_error_errno(source_fd, "Failed to open source path '%s': %m", arg_source); + + r = fd_verify_regular(source_fd); + if (r < 0) { + if (r != -EISDIR) + return log_error_errno(r, "Source '%s' is neither regular file nor directory: %m", arg_source); + + /* We are looking at a directory. */ + + target_fd = openat(dfd, basename(arg_target), O_RDONLY|O_DIRECTORY|O_CLOEXEC); + if (target_fd < 0) { + if (errno != ENOENT) + return log_error_errno(errno, "Failed to open destination '%s': %m", arg_target); + + r = copy_tree_at(source_fd, ".", dfd, basename(arg_target), UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE|COPY_SIGINT); + } else + r = copy_tree_at(source_fd, ".", target_fd, ".", UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE|COPY_SIGINT); + if (r < 0) + return log_error_errno(r, "Failed to copy '%s' to '%s' in image '%s': %m", arg_source, arg_target, arg_image); + + break; + } + + /* We area looking at a regular file */ + target_fd = openat(dfd, basename(arg_target), O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_EXCL, 0600); + if (target_fd < 0) + return log_error_errno(errno, "Failed to open target file '%s': %m", arg_target); + + r = copy_bytes(source_fd, target_fd, (uint64_t) -1, COPY_REFLINK); + if (r < 0) + return log_error_errno(r, "Failed to copy bytes from '%s' to '%s' in image '%s': %m", arg_source, arg_target, arg_image); + + (void) copy_xattr(source_fd, target_fd); + (void) copy_access(source_fd, target_fd); + (void) copy_times(source_fd, target_fd, 0); + + /* When this is a regular file we don't copy ownership! */ + } + + break; + } + default: assert_not_reached("Unknown action."); } From 16b7459280e193e39ade76696a1194a298ab575d Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 28 Jul 2020 23:49:35 +0200 Subject: [PATCH 07/20] dissect: show more information in output Let's show size and image filename. --- src/dissect/dissect.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index 0ccb603b5d..f1f323b131 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -4,11 +4,14 @@ #include #include #include +#include +#include #include "architecture.h" #include "copy.h" #include "dissect-image.h" #include "fd-util.h" +#include "format-util.h" #include "fs-util.h" #include "hexdecoct.h" #include "log.h" @@ -342,6 +345,7 @@ static int run(int argc, char *argv[]) { switch (arg_action) { case ACTION_DISSECT: { + uint64_t size; unsigned i; for (i = 0; i < _PARTITION_DESIGNATOR_MAX; i++) { @@ -375,6 +379,15 @@ static int run(int argc, char *argv[]) { putchar('\n'); } + printf(" Name: %s\n", basename(arg_image)); + + if (ioctl(d->fd, BLKGETSIZE64, &size) < 0) + log_debug_errno(errno, "Failed to query size of loopback device: %m"); + else { + char t[FORMAT_BYTES_MAX]; + printf(" Size: %s\n", format_bytes(t, sizeof(t), size)); + } + r = dissected_image_acquire_metadata(m); if (r < 0) return log_error_errno(r, "Failed to acquire image metadata: %m"); From e3659eb2366ad30a89e7a3511d40f55a2a69e49a Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 29 Jul 2020 15:13:20 +0200 Subject: [PATCH 08/20] dissect: load verity metadata earlier That way we can turn off kernel partition scanning if verity data is available (as we don't support verity for full GPT images, only for simple file system images). --- src/dissect/dissect.c | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index f1f323b131..f44c8b3783 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -327,18 +327,37 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; - r = loop_device_make_by_path(arg_image, (arg_flags & DISSECT_IMAGE_READ_ONLY) ? O_RDONLY : O_RDWR, LO_FLAGS_PARTSCAN, &d); + r = verity_metadata_load( + arg_image, NULL, + arg_root_hash ? NULL : &arg_root_hash, + &arg_root_hash_size, + arg_verity_data ? NULL : &arg_verity_data, + arg_root_hash_sig_path || arg_root_hash_sig ? NULL : &arg_root_hash_sig_path); + if (r < 0) + return log_error_errno(r, "Failed to read verity artifacts for %s: %m", arg_image); + + r = loop_device_make_by_path( + arg_image, + (arg_flags & DISSECT_IMAGE_READ_ONLY) ? O_RDONLY : O_RDWR, + arg_verity_data ? 0 : LO_FLAGS_PARTSCAN, + &d); if (r < 0) return log_error_errno(r, "Failed to set up loopback device: %m"); - r = verity_metadata_load(arg_image, NULL, arg_root_hash ? NULL : &arg_root_hash, &arg_root_hash_size, - arg_verity_data ? NULL : &arg_verity_data, - arg_root_hash_sig_path || arg_root_hash_sig ? NULL : &arg_root_hash_sig_path); - if (r < 0) - return log_error_errno(r, "Failed to read verity artefacts for %s: %m", arg_image); - arg_flags |= arg_verity_data ? DISSECT_IMAGE_NO_PARTITION_TABLE : 0; - - r = dissect_image_and_warn(d->fd, arg_image, arg_root_hash, arg_root_hash_size, arg_verity_data, NULL, arg_flags, &m); + if (arg_verity_data) + arg_flags |= DISSECT_IMAGE_NO_PARTITION_TABLE; /* We only support Verity per file system, + * hence if there's external Verity data + * available we turn off partition table + * support */ + r = dissect_image_and_warn( + d->fd, + arg_image, + arg_root_hash, + arg_root_hash_size, + arg_verity_data, + NULL, + arg_flags, + &m); if (r < 0) return r; From 89d00f2e3fcf088bd9374d67ebeee59ecca577b5 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 29 Jul 2020 15:15:07 +0200 Subject: [PATCH 09/20] dissect: beef up dissection output Let's use a proper table for outputting partition information. Let's also put the general information about the image first, and the table after that. Moreover, dissect the image before showing any output, so that we can early on return an error if the image is not valid. --- src/dissect/dissect.c | 115 ++++++++++++++++++++++++++++-------------- 1 file changed, 78 insertions(+), 37 deletions(-) diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index f44c8b3783..c9f16d7a64 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -11,6 +11,7 @@ #include "copy.h" #include "dissect-image.h" #include "fd-util.h" +#include "format-table.h" #include "format-util.h" #include "fs-util.h" #include "hexdecoct.h" @@ -364,53 +365,26 @@ static int run(int argc, char *argv[]) { switch (arg_action) { case ACTION_DISSECT: { + _cleanup_(table_unrefp) Table *t = NULL; uint64_t size; - unsigned i; - for (i = 0; i < _PARTITION_DESIGNATOR_MAX; i++) { - DissectedPartition *p = m->partitions + i; - - if (!p->found) - continue; - - printf("Found %s '%s' partition", - p->rw ? "writable" : "read-only", - partition_designator_to_string(i)); - - if (!sd_id128_is_null(p->uuid)) - printf(" (UUID " SD_ID128_FORMAT_STR ")", SD_ID128_FORMAT_VAL(p->uuid)); - - if (p->fstype) - printf(" of type %s", p->fstype); - - if (p->architecture != _ARCHITECTURE_INVALID) - printf(" for %s", architecture_to_string(p->architecture)); - - if (dissected_image_can_do_verity(m, i)) - printf(" %s verity", dissected_image_has_verity(m, i) ? "with" : "without"); - - if (p->partno >= 0) - printf(" on partition #%i", p->partno); - - if (p->node) - printf(" (%s)", p->node); - - putchar('\n'); - } + r = dissected_image_acquire_metadata(m); + if (r == -EMEDIUMTYPE) + return log_error_errno(r, "Not a valid OS image, no os-release file included."); + if (r == -ENXIO) + return log_error_errno(r, "No root partition discovered."); + if (r < 0) + return log_error_errno(r, "Failed to acquire image metadata: %m"); printf(" Name: %s\n", basename(arg_image)); if (ioctl(d->fd, BLKGETSIZE64, &size) < 0) log_debug_errno(errno, "Failed to query size of loopback device: %m"); else { - char t[FORMAT_BYTES_MAX]; - printf(" Size: %s\n", format_bytes(t, sizeof(t), size)); + char s[FORMAT_BYTES_MAX]; + printf(" Size: %s\n", format_bytes(s, sizeof(s), size)); } - r = dissected_image_acquire_metadata(m); - if (r < 0) - return log_error_errno(r, "Failed to acquire image metadata: %m"); - if (m->hostname) printf(" Hostname: %s\n", m->hostname); @@ -435,6 +409,73 @@ static int run(int argc, char *argv[]) { *p, *q); } + putc('\n', stdout); + + t = table_new("rw", "designator", "partition uuid", "fstype", "architecture", "verity", "node", "partno"); + if (!t) + return log_oom(); + + (void) table_set_empty_string(t, "-"); + (void) table_set_align_percent(t, table_get_cell(t, 0, 7), 100); + + for (unsigned i = 0; i < _PARTITION_DESIGNATOR_MAX; i++) { + DissectedPartition *p = m->partitions + i; + + if (!p->found) + continue; + + r = table_add_many( + t, + TABLE_STRING, p->rw ? "rw" : "ro", + TABLE_STRING, partition_designator_to_string(i)); + if (r < 0) + return table_log_add_error(r); + + if (sd_id128_is_null(p->uuid)) + r = table_add_cell(t, NULL, TABLE_EMPTY, NULL); + else + r = table_add_cell(t, NULL, TABLE_UUID, &p->uuid); + if (r < 0) + return table_log_add_error(r); + + r = table_add_many( + t, + TABLE_STRING, p->fstype, + TABLE_STRING, architecture_to_string(p->architecture)); + if (r < 0) + return table_log_add_error(r); + + if (arg_verity_data) + r = table_add_cell(t, NULL, TABLE_STRING, "external"); + else if (dissected_image_can_do_verity(m, i)) + r = table_add_cell(t, NULL, TABLE_STRING, yes_no(dissected_image_has_verity(m, i))); + else + r = table_add_cell(t, NULL, TABLE_EMPTY, NULL); + if (r < 0) + return table_log_add_error(r); + + + if (p->partno < 0) /* no partition table, naked file system */ { + r = table_add_cell(t, NULL, TABLE_STRING, arg_image); + if (r < 0) + return table_log_add_error(r); + + r = table_add_cell(t, NULL, TABLE_EMPTY, NULL); + } else { + r = table_add_cell(t, NULL, TABLE_STRING, p->node); + if (r < 0) + return table_log_add_error(r); + + r = table_add_cell(t, NULL, TABLE_INT, &p->partno); + } + if (r < 0) + return table_log_add_error(r); + } + + r = table_print(t, stdout); + if (r < 0) + return log_error_errno(r, "Failed to dump table: %m"); + break; } From f5ea63a5e17e18f1c22c901e0e48601e091ec859 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 29 Jul 2020 15:16:27 +0200 Subject: [PATCH 10/20] dissect: properly propagate some relevant dissection errors Let's send some specific error codes from helper process to parent via the return value, and convert them back there. --- src/shared/dissect-image.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 2b3d6c08f0..c42c4569b2 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -9,6 +9,7 @@ #include #include #include +#include #include "sd-device.h" #include "sd-id128.h" @@ -1596,7 +1597,14 @@ int decrypted_image_relinquish(DecryptedImage *d) { return 0; } -int verity_metadata_load(const char *image, const char *root_hash_path, void **ret_roothash, size_t *ret_roothash_size, char **ret_verity_data, char **ret_roothashsig) { +int verity_metadata_load( + const char *image, + const char *root_hash_path, + void **ret_roothash, + size_t *ret_roothash_size, + char **ret_verity_data, + char **ret_roothashsig) { + _cleanup_free_ char *verity_filename = NULL, *roothashsig_filename = NULL; _cleanup_free_ void *roothash_decoded = NULL; size_t roothash_decoded_size = 0; @@ -1758,6 +1766,10 @@ int dissected_image_acquire_metadata(DissectedImage *m) { goto finish; if (r == 0) { r = dissected_image_mount(m, t, UID_INVALID, DISSECT_IMAGE_READ_ONLY|DISSECT_IMAGE_MOUNT_ROOT_ONLY|DISSECT_IMAGE_VALIDATE_OS); + if (r == -EMEDIUMTYPE) /* No /etc/os-release */ + _exit(EX_OSFILE); + if (r == -ENXIO) /* No root partition */ + _exit(EX_DATAERR); if (r < 0) { log_debug_errno(r, "Failed to mount dissected image: %m"); _exit(EXIT_FAILURE); @@ -1847,6 +1859,10 @@ int dissected_image_acquire_metadata(DissectedImage *m) { child = 0; if (r < 0) goto finish; + if (r == EX_OSFILE) + return -EMEDIUMTYPE; /* No os-release file */ + if (r == EX_DATAERR) + return -ENXIO; /* No root partition */ if (r != EXIT_SUCCESS) return -EPROTO; From 37e44c3f95088ba0bd175cae9ae575f130d85948 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 29 Jul 2020 15:17:22 +0200 Subject: [PATCH 11/20] dissect: immediately close pipes when we determined we have no data for them This effectively makes little difference because we exit soon later anyway, which will close the fds, too. However, it's still useful since it means the parent will get EOF events on them in the order we process things and isn't delayed to process the data from the pipes until the child dies. --- src/dissect/dissect.c | 626 +++++++++++++++++++------------------ src/shared/dissect-image.c | 3 +- 2 files changed, 328 insertions(+), 301 deletions(-) diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index c9f16d7a64..b2ef84c0e9 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -315,10 +315,328 @@ static int parse_argv(int argc, char *argv[]) { return 1; } -static int run(int argc, char *argv[]) { - _cleanup_(loop_device_unrefp) LoopDevice *d = NULL; +static int action_dissect(DissectedImage *m, LoopDevice *d) { + _cleanup_(table_unrefp) Table *t = NULL; + uint64_t size; + int r; + + assert(m); + assert(d); + + r = dissected_image_acquire_metadata(m); + if (r == -EMEDIUMTYPE) + return log_error_errno(r, "Not a valid OS image, no os-release file included."); + if (r == -ENXIO) + return log_error_errno(r, "No root partition discovered."); + if (r < 0) + return log_error_errno(r, "Failed to acquire image metadata: %m"); + + printf(" Name: %s\n", basename(arg_image)); + + if (ioctl(d->fd, BLKGETSIZE64, &size) < 0) + log_debug_errno(errno, "Failed to query size of loopback device: %m"); + else { + char s[FORMAT_BYTES_MAX]; + printf(" Size: %s\n", format_bytes(s, sizeof(s), size)); + } + + if (m->hostname) + printf(" Hostname: %s\n", m->hostname); + + if (!sd_id128_is_null(m->machine_id)) + printf("Machine ID: " SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(m->machine_id)); + + if (!strv_isempty(m->machine_info)) { + char **p, **q; + + STRV_FOREACH_PAIR(p, q, m->machine_info) + printf("%s %s=%s\n", + p == m->machine_info ? "Mach. Info:" : " ", + *p, *q); + } + + if (!strv_isempty(m->os_release)) { + char **p, **q; + + STRV_FOREACH_PAIR(p, q, m->os_release) + printf("%s %s=%s\n", + p == m->os_release ? "OS Release:" : " ", + *p, *q); + } + + putc('\n', stdout); + + t = table_new("rw", "designator", "partition uuid", "fstype", "architecture", "verity", "node", "partno"); + if (!t) + return log_oom(); + + (void) table_set_empty_string(t, "-"); + (void) table_set_align_percent(t, table_get_cell(t, 0, 7), 100); + + for (unsigned i = 0; i < _PARTITION_DESIGNATOR_MAX; i++) { + DissectedPartition *p = m->partitions + i; + + if (!p->found) + continue; + + r = table_add_many( + t, + TABLE_STRING, p->rw ? "rw" : "ro", + TABLE_STRING, partition_designator_to_string(i)); + if (r < 0) + return table_log_add_error(r); + + if (sd_id128_is_null(p->uuid)) + r = table_add_cell(t, NULL, TABLE_EMPTY, NULL); + else + r = table_add_cell(t, NULL, TABLE_UUID, &p->uuid); + if (r < 0) + return table_log_add_error(r); + + r = table_add_many( + t, + TABLE_STRING, p->fstype, + TABLE_STRING, architecture_to_string(p->architecture)); + if (r < 0) + return table_log_add_error(r); + + if (arg_verity_data) + r = table_add_cell(t, NULL, TABLE_STRING, "external"); + else if (dissected_image_can_do_verity(m, i)) + r = table_add_cell(t, NULL, TABLE_STRING, yes_no(dissected_image_has_verity(m, i))); + else + r = table_add_cell(t, NULL, TABLE_EMPTY, NULL); + if (r < 0) + return table_log_add_error(r); + + if (p->partno < 0) /* no partition table, naked file system */ { + r = table_add_cell(t, NULL, TABLE_STRING, arg_image); + if (r < 0) + return table_log_add_error(r); + + r = table_add_cell(t, NULL, TABLE_EMPTY, NULL); + } else { + r = table_add_cell(t, NULL, TABLE_STRING, p->node); + if (r < 0) + return table_log_add_error(r); + + r = table_add_cell(t, NULL, TABLE_INT, &p->partno); + } + if (r < 0) + return table_log_add_error(r); + } + + r = table_print(t, stdout); + if (r < 0) + return log_error_errno(r, "Failed to dump table: %m"); + + return 0; +} + +static int action_mount(DissectedImage *m, LoopDevice *d) { _cleanup_(decrypted_image_unrefp) DecryptedImage *di = NULL; + int r; + + assert(m); + assert(d); + + r = dissected_image_decrypt_interactively( + m, NULL, + arg_root_hash, arg_root_hash_size, + arg_verity_data, + arg_root_hash_sig_path, arg_root_hash_sig, arg_root_hash_sig_size, + arg_flags, + &di); + if (r < 0) + return r; + + r = dissected_image_mount(m, arg_path, UID_INVALID, arg_flags); + if (r == -EUCLEAN) + return log_error_errno(r, "File system check on image failed: %m"); + if (r < 0) + return log_error_errno(r, "Failed to mount image: %m"); + + if (di) { + r = decrypted_image_relinquish(di); + if (r < 0) + return log_error_errno(r, "Failed to relinquish DM devices: %m"); + } + + loop_device_relinquish(d); + return 0; +} + +static int action_copy(DissectedImage *m, LoopDevice *d) { + _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL; + _cleanup_(decrypted_image_unrefp) DecryptedImage *di = NULL; + _cleanup_(rmdir_and_freep) char *created_dir = NULL; + _cleanup_free_ char *temp = NULL; + int r; + + assert(m); + assert(d); + + r = dissected_image_decrypt_interactively( + m, NULL, + arg_root_hash, arg_root_hash_size, + arg_verity_data, + arg_root_hash_sig_path, arg_root_hash_sig, arg_root_hash_sig_size, + arg_flags, + &di); + if (r < 0) + return r; + + r = detach_mount_namespace(); + if (r < 0) + return log_error_errno(r, "Failed to detach mount namespace: %m"); + + r = tempfn_random_child(NULL, program_invocation_short_name, &temp); + if (r < 0) + return log_error_errno(r, "Failed to generate temporary mount directory: %m"); + + r = mkdir_p(temp, 0700); + if (r < 0) + return log_error_errno(r, "Failed to create mount point: %m"); + + created_dir = TAKE_PTR(temp); + + r = dissected_image_mount(m, created_dir, UID_INVALID, arg_flags); + if (r == -EUCLEAN) + return log_error_errno(r, "File system check on image failed: %m"); + if (r < 0) + return log_error_errno(r, "Failed to mount image: %m"); + + mounted_dir = TAKE_PTR(created_dir); + + if (di) { + r = decrypted_image_relinquish(di); + if (r < 0) + return log_error_errno(r, "Failed to relinquish DM devices: %m"); + } + + loop_device_relinquish(d); + + if (arg_action == ACTION_COPY_FROM) { + _cleanup_close_ int source_fd = -1, target_fd = -1; + + source_fd = chase_symlinks_and_open(arg_source, mounted_dir, CHASE_PREFIX_ROOT|CHASE_WARN, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL); + if (source_fd < 0) + return log_error_errno(source_fd, "Failed to open source path '%s' in image '%s': %m", arg_source, arg_image); + + /* Copying to stdout? */ + if (streq(arg_target, "-")) { + r = copy_bytes(source_fd, STDOUT_FILENO, (uint64_t) -1, COPY_REFLINK); + if (r < 0) + return log_error_errno(r, "Failed to copy bytes from %s in mage '%s' to stdout: %m", arg_source, arg_image); + + /* When we copy to stdou we don't copy any attributes (i.e. no access mode, no ownership, no xattr, no times) */ + return 0; + } + + /* Try to copy as directory? */ + r = copy_directory_fd(source_fd, arg_target, COPY_REFLINK|COPY_MERGE_EMPTY|COPY_SIGINT); + if (r >= 0) + return 0; + if (r != -ENOTDIR) + return log_error_errno(r, "Failed to copy %s in image '%s' to '%s': %m", arg_source, arg_image, arg_target); + + r = fd_verify_regular(source_fd); + if (r == -EISDIR) + return log_error_errno(r, "Target '%s' exists already and is not a directory.", arg_target); + if (r < 0) + return log_error_errno(r, "Source path %s in image '%s' is neither regular file nor directory, refusing: %m", arg_source, arg_image); + + /* Nah, it's a plain file! */ + target_fd = open(arg_target, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0600); + if (target_fd < 0) + return log_error_errno(errno, "Failed to create regular file at target path '%s': %m", arg_target); + + r = copy_bytes(source_fd, target_fd, (uint64_t) -1, COPY_REFLINK); + if (r < 0) + return log_error_errno(r, "Failed to copy bytes from %s in mage '%s' to '%s': %m", arg_source, arg_image, arg_target); + + (void) copy_xattr(source_fd, target_fd); + (void) copy_access(source_fd, target_fd); + (void) copy_times(source_fd, target_fd, 0); + + /* When this is a regular file we don't copy ownership! */ + + } else { + _cleanup_close_ int source_fd = -1, target_fd = -1; + _cleanup_close_ int dfd = -1; + _cleanup_free_ char *dn = NULL; + + assert(arg_action == ACTION_COPY_TO); + + dn = dirname_malloc(arg_target); + if (!dn) + return log_oom(); + + r = chase_symlinks(dn, mounted_dir, CHASE_PREFIX_ROOT|CHASE_WARN, NULL, &dfd); + if (r < 0) + return log_error_errno(r, "Failed to open '%s': %m", dn); + + /* Are we reading from stdin? */ + if (streq(arg_source, "-")) { + target_fd = openat(dfd, basename(arg_target), O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_EXCL, 0644); + if (target_fd < 0) + return log_error_errno(errno, "Failed to open target file '%s': %m", arg_target); + + r = copy_bytes(STDIN_FILENO, target_fd, (uint64_t) -1, COPY_REFLINK); + if (r < 0) + return log_error_errno(r, "Failed to copy bytes from stdin to '%s' in image '%s': %m", arg_target, arg_image); + + /* When we copy from stdin we don't copy any attributes (i.e. no access mode, no ownership, no xattr, no times) */ + return 0; + } + + source_fd = open(arg_source, O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (source_fd < 0) + return log_error_errno(source_fd, "Failed to open source path '%s': %m", arg_source); + + r = fd_verify_regular(source_fd); + if (r < 0) { + if (r != -EISDIR) + return log_error_errno(r, "Source '%s' is neither regular file nor directory: %m", arg_source); + + /* We are looking at a directory. */ + + target_fd = openat(dfd, basename(arg_target), O_RDONLY|O_DIRECTORY|O_CLOEXEC); + if (target_fd < 0) { + if (errno != ENOENT) + return log_error_errno(errno, "Failed to open destination '%s': %m", arg_target); + + r = copy_tree_at(source_fd, ".", dfd, basename(arg_target), UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE|COPY_SIGINT); + } else + r = copy_tree_at(source_fd, ".", target_fd, ".", UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE|COPY_SIGINT); + if (r < 0) + return log_error_errno(r, "Failed to copy '%s' to '%s' in image '%s': %m", arg_source, arg_target, arg_image); + + return 0; + } + + /* We area looking at a regular file */ + target_fd = openat(dfd, basename(arg_target), O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_EXCL, 0600); + if (target_fd < 0) + return log_error_errno(errno, "Failed to open target file '%s': %m", arg_target); + + r = copy_bytes(source_fd, target_fd, (uint64_t) -1, COPY_REFLINK); + if (r < 0) + return log_error_errno(r, "Failed to copy bytes from '%s' to '%s' in image '%s': %m", arg_source, arg_target, arg_image); + + (void) copy_xattr(source_fd, target_fd); + (void) copy_access(source_fd, target_fd); + (void) copy_times(source_fd, target_fd, 0); + + /* When this is a regular file we don't copy ownership! */ + } + + return 0; +} + +static int run(int argc, char *argv[]) { _cleanup_(dissected_image_unrefp) DissectedImage *m = NULL; + _cleanup_(loop_device_unrefp) LoopDevice *d = NULL; int r; log_parse_environment(); @@ -364,316 +682,24 @@ static int run(int argc, char *argv[]) { switch (arg_action) { - case ACTION_DISSECT: { - _cleanup_(table_unrefp) Table *t = NULL; - uint64_t size; - - r = dissected_image_acquire_metadata(m); - if (r == -EMEDIUMTYPE) - return log_error_errno(r, "Not a valid OS image, no os-release file included."); - if (r == -ENXIO) - return log_error_errno(r, "No root partition discovered."); - if (r < 0) - return log_error_errno(r, "Failed to acquire image metadata: %m"); - - printf(" Name: %s\n", basename(arg_image)); - - if (ioctl(d->fd, BLKGETSIZE64, &size) < 0) - log_debug_errno(errno, "Failed to query size of loopback device: %m"); - else { - char s[FORMAT_BYTES_MAX]; - printf(" Size: %s\n", format_bytes(s, sizeof(s), size)); - } - - if (m->hostname) - printf(" Hostname: %s\n", m->hostname); - - if (!sd_id128_is_null(m->machine_id)) - printf("Machine ID: " SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(m->machine_id)); - - if (!strv_isempty(m->machine_info)) { - char **p, **q; - - STRV_FOREACH_PAIR(p, q, m->machine_info) - printf("%s %s=%s\n", - p == m->machine_info ? "Mach. Info:" : " ", - *p, *q); - } - - if (!strv_isempty(m->os_release)) { - char **p, **q; - - STRV_FOREACH_PAIR(p, q, m->os_release) - printf("%s %s=%s\n", - p == m->os_release ? "OS Release:" : " ", - *p, *q); - } - - putc('\n', stdout); - - t = table_new("rw", "designator", "partition uuid", "fstype", "architecture", "verity", "node", "partno"); - if (!t) - return log_oom(); - - (void) table_set_empty_string(t, "-"); - (void) table_set_align_percent(t, table_get_cell(t, 0, 7), 100); - - for (unsigned i = 0; i < _PARTITION_DESIGNATOR_MAX; i++) { - DissectedPartition *p = m->partitions + i; - - if (!p->found) - continue; - - r = table_add_many( - t, - TABLE_STRING, p->rw ? "rw" : "ro", - TABLE_STRING, partition_designator_to_string(i)); - if (r < 0) - return table_log_add_error(r); - - if (sd_id128_is_null(p->uuid)) - r = table_add_cell(t, NULL, TABLE_EMPTY, NULL); - else - r = table_add_cell(t, NULL, TABLE_UUID, &p->uuid); - if (r < 0) - return table_log_add_error(r); - - r = table_add_many( - t, - TABLE_STRING, p->fstype, - TABLE_STRING, architecture_to_string(p->architecture)); - if (r < 0) - return table_log_add_error(r); - - if (arg_verity_data) - r = table_add_cell(t, NULL, TABLE_STRING, "external"); - else if (dissected_image_can_do_verity(m, i)) - r = table_add_cell(t, NULL, TABLE_STRING, yes_no(dissected_image_has_verity(m, i))); - else - r = table_add_cell(t, NULL, TABLE_EMPTY, NULL); - if (r < 0) - return table_log_add_error(r); - - - if (p->partno < 0) /* no partition table, naked file system */ { - r = table_add_cell(t, NULL, TABLE_STRING, arg_image); - if (r < 0) - return table_log_add_error(r); - - r = table_add_cell(t, NULL, TABLE_EMPTY, NULL); - } else { - r = table_add_cell(t, NULL, TABLE_STRING, p->node); - if (r < 0) - return table_log_add_error(r); - - r = table_add_cell(t, NULL, TABLE_INT, &p->partno); - } - if (r < 0) - return table_log_add_error(r); - } - - r = table_print(t, stdout); - if (r < 0) - return log_error_errno(r, "Failed to dump table: %m"); - + case ACTION_DISSECT: + r = action_dissect(m, d); break; - } case ACTION_MOUNT: - r = dissected_image_decrypt_interactively( - m, NULL, - arg_root_hash, arg_root_hash_size, - arg_verity_data, - arg_root_hash_sig_path, arg_root_hash_sig, arg_root_hash_sig_size, - arg_flags, - &di); - if (r < 0) - return r; - - r = dissected_image_mount(m, arg_path, UID_INVALID, arg_flags); - if (r == -EUCLEAN) - return log_error_errno(r, "File system check on image failed: %m"); - if (r < 0) - return log_error_errno(r, "Failed to mount image: %m"); - - if (di) { - r = decrypted_image_relinquish(di); - if (r < 0) - return log_error_errno(r, "Failed to relinquish DM devices: %m"); - } - - loop_device_relinquish(d); + r = action_mount(m, d); break; case ACTION_COPY_FROM: - case ACTION_COPY_TO: { - _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL; - _cleanup_(rmdir_and_freep) char *created_dir = NULL; - _cleanup_free_ char *temp = NULL; - - r = dissected_image_decrypt_interactively( - m, NULL, - arg_root_hash, arg_root_hash_size, - arg_verity_data, - arg_root_hash_sig_path, arg_root_hash_sig, arg_root_hash_sig_size, - arg_flags, - &di); - if (r < 0) - return r; - - r = detach_mount_namespace(); - if (r < 0) - return log_error_errno(r, "Failed to detach mount namespace: %m"); - - r = tempfn_random_child(NULL, program_invocation_short_name, &temp); - if (r < 0) - return log_error_errno(r, "Failed to generate temporary mount directory: %m"); - - r = mkdir_p(temp, 0700); - if (r < 0) - return log_error_errno(r, "Failed to create mount point: %m"); - - created_dir = TAKE_PTR(temp); - - r = dissected_image_mount(m, created_dir, UID_INVALID, arg_flags); - if (r == -EUCLEAN) - return log_error_errno(r, "File system check on image failed: %m"); - if (r < 0) - return log_error_errno(r, "Failed to mount image: %m"); - - mounted_dir = TAKE_PTR(created_dir); - - if (di) { - r = decrypted_image_relinquish(di); - if (r < 0) - return log_error_errno(r, "Failed to relinquish DM devices: %m"); - } - - loop_device_relinquish(d); - - if (arg_action == ACTION_COPY_FROM) { - _cleanup_close_ int source_fd = -1, target_fd = -1; - - source_fd = chase_symlinks_and_open(arg_source, mounted_dir, CHASE_PREFIX_ROOT|CHASE_WARN, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL); - if (source_fd < 0) - return log_error_errno(source_fd, "Failed to open source path '%s' in image '%s': %m", arg_source, arg_image); - - /* Copying to stdout? */ - if (streq(arg_target, "-")) { - r = copy_bytes(source_fd, STDOUT_FILENO, (uint64_t) -1, COPY_REFLINK); - if (r < 0) - return log_error_errno(r, "Failed to copy bytes from %s in mage '%s' to stdout: %m", arg_source, arg_image); - - /* When we copy to stdou we don't copy any attributes (i.e. no access mode, no ownership, no xattr, no times) */ - break; - } - - /* Try to copy as directory? */ - r = copy_directory_fd(source_fd, arg_target, COPY_REFLINK|COPY_MERGE_EMPTY|COPY_SIGINT); - if (r >= 0) - break; - if (r != -ENOTDIR) - return log_error_errno(r, "Failed to copy %s in image '%s' to '%s': %m", arg_source, arg_image, arg_target); - - r = fd_verify_regular(source_fd); - if (r == -EISDIR) - return log_error_errno(r, "Target '%s' exists already and is not a directory.", arg_target); - if (r < 0) - return log_error_errno(r, "Source path %s in image '%s' is neither regular file nor directory, refusing: %m", arg_source, arg_image); - - /* Nah, it's a plain file! */ - target_fd = open(arg_target, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0600); - if (target_fd < 0) - return log_error_errno(errno, "Failed to create regular file at target path '%s': %m", arg_target); - - r = copy_bytes(source_fd, target_fd, (uint64_t) -1, COPY_REFLINK); - if (r < 0) - return log_error_errno(r, "Failed to copy bytes from %s in mage '%s' to '%s': %m", arg_source, arg_image, arg_target); - - (void) copy_xattr(source_fd, target_fd); - (void) copy_access(source_fd, target_fd); - (void) copy_times(source_fd, target_fd, 0); - - /* When this is a regular file we don't copy ownership! */ - - } else { - _cleanup_close_ int source_fd = -1, target_fd = -1; - _cleanup_close_ int dfd = -1; - _cleanup_free_ char *dn = NULL; - - assert(arg_action == ACTION_COPY_TO); - - dn = dirname_malloc(arg_target); - if (!dn) - return log_oom(); - - r = chase_symlinks(dn, mounted_dir, CHASE_PREFIX_ROOT|CHASE_WARN, NULL, &dfd); - if (r < 0) - return log_error_errno(r, "Failed to open '%s': %m", dn); - - /* Are we reading from stdin? */ - if (streq(arg_source, "-")) { - target_fd = openat(dfd, basename(arg_target), O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_EXCL, 0644); - if (target_fd < 0) - return log_error_errno(errno, "Failed to open target file '%s': %m", arg_target); - - r = copy_bytes(STDIN_FILENO, target_fd, (uint64_t) -1, COPY_REFLINK); - if (r < 0) - return log_error_errno(r, "Failed to copy bytes from stdin to '%s' in image '%s': %m", arg_target, arg_image); - - /* When we copy from stdin we don't copy any attributes (i.e. no access mode, no ownership, no xattr, no times) */ - break; - } - - source_fd = open(arg_source, O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (source_fd < 0) - return log_error_errno(source_fd, "Failed to open source path '%s': %m", arg_source); - - r = fd_verify_regular(source_fd); - if (r < 0) { - if (r != -EISDIR) - return log_error_errno(r, "Source '%s' is neither regular file nor directory: %m", arg_source); - - /* We are looking at a directory. */ - - target_fd = openat(dfd, basename(arg_target), O_RDONLY|O_DIRECTORY|O_CLOEXEC); - if (target_fd < 0) { - if (errno != ENOENT) - return log_error_errno(errno, "Failed to open destination '%s': %m", arg_target); - - r = copy_tree_at(source_fd, ".", dfd, basename(arg_target), UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE|COPY_SIGINT); - } else - r = copy_tree_at(source_fd, ".", target_fd, ".", UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE|COPY_SIGINT); - if (r < 0) - return log_error_errno(r, "Failed to copy '%s' to '%s' in image '%s': %m", arg_source, arg_target, arg_image); - - break; - } - - /* We area looking at a regular file */ - target_fd = openat(dfd, basename(arg_target), O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_EXCL, 0600); - if (target_fd < 0) - return log_error_errno(errno, "Failed to open target file '%s': %m", arg_target); - - r = copy_bytes(source_fd, target_fd, (uint64_t) -1, COPY_REFLINK); - if (r < 0) - return log_error_errno(r, "Failed to copy bytes from '%s' to '%s' in image '%s': %m", arg_source, arg_target, arg_image); - - (void) copy_xattr(source_fd, target_fd); - (void) copy_access(source_fd, target_fd); - (void) copy_times(source_fd, target_fd, 0); - - /* When this is a regular file we don't copy ownership! */ - } - + case ACTION_COPY_TO: + r = action_copy(m, d); break; - } default: assert_not_reached("Unknown action."); } - return 0; + return r; } DEFINE_MAIN_FUNCTION(run); diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index c42c4569b2..953598f2ff 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -1776,7 +1776,7 @@ int dissected_image_acquire_metadata(DissectedImage *m) { } for (k = 0; k < _META_MAX; k++) { - _cleanup_close_ int fd = -1; + _cleanup_close_ int fd = -ENOENT; const char *p; fds[2*k] = safe_close(fds[2*k]); @@ -1788,6 +1788,7 @@ int dissected_image_acquire_metadata(DissectedImage *m) { } if (fd < 0) { log_debug_errno(fd, "Failed to read %s file of image, ignoring: %m", paths[k]); + fds[2*k+1] = safe_close(fds[2*k+1]); continue; } From fa45d12c1c601a91bd85533f79158007b49971c1 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 11 Aug 2020 15:54:16 +0200 Subject: [PATCH 12/20] dissect: use recognizable error if we are supposed to mount an encrypted fs Also, document EBUSY --- src/shared/dissect-image.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 953598f2ff..9a5a463b20 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -1004,9 +1004,9 @@ static int mount_partition( if (!m->found || !node || !fstype) return 0; - /* Stacked encryption? Yuck */ + /* We are looking at an encrypted partition? This either means stacked encryption, or the caller didn't call dissected_image_decrypt() beforehand. Let's return a recognizable error for this case. */ if (streq_ptr(fstype, "crypto_LUKS")) - return -ELOOP; + return -EUNATCH; rw = m->rw && !(flags & DISSECT_IMAGE_READ_ONLY); @@ -1067,6 +1067,15 @@ int dissected_image_mount(DissectedImage *m, const char *where, uid_t uid_shift, assert(m); assert(where); + /* Returns: + * + * -ENXIO → No root partition found + * -EMEDIUMTYPE → DISSECT_IMAGE_VALIDATE_OS set but no os-release file found + * -EUNATCH → Encrypted partition found for which no dm-crypt was set up yet + * -EUCLEAN → fsck for file system failed + * -EBUSY → File system already mounted/used elsewhere (kernel) + */ + if (!m->partitions[PARTITION_ROOT].found) return -ENXIO; From af187ab237827788c8361170e851ef02d9a3b1bd Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 11 Aug 2020 15:56:12 +0200 Subject: [PATCH 13/20] dissect: introduce new helper dissected_image_mount_and_warn() and use it everywhere --- src/dissect/dissect.c | 12 ++++-------- src/nspawn/nspawn.c | 13 ++++++------- src/shared/dissect-image.c | 29 +++++++++++++++++++++++++---- src/shared/dissect-image.h | 1 + 4 files changed, 36 insertions(+), 19 deletions(-) diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index b2ef84c0e9..f575e1b28b 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -450,11 +450,9 @@ static int action_mount(DissectedImage *m, LoopDevice *d) { if (r < 0) return r; - r = dissected_image_mount(m, arg_path, UID_INVALID, arg_flags); - if (r == -EUCLEAN) - return log_error_errno(r, "File system check on image failed: %m"); + r = dissected_image_mount_and_warn(m, arg_path, UID_INVALID, arg_flags); if (r < 0) - return log_error_errno(r, "Failed to mount image: %m"); + return r; if (di) { r = decrypted_image_relinquish(di); @@ -500,11 +498,9 @@ static int action_copy(DissectedImage *m, LoopDevice *d) { created_dir = TAKE_PTR(temp); - r = dissected_image_mount(m, created_dir, UID_INVALID, arg_flags); - if (r == -EUCLEAN) - return log_error_errno(r, "File system check on image failed: %m"); + r = dissected_image_mount_and_warn(m, created_dir, UID_INVALID, arg_flags); if (r < 0) - return log_error_errno(r, "Failed to mount image: %m"); + return r; mounted_dir = TAKE_PTR(created_dir); diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 6d6fe87ed1..1b83f5ad58 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -3369,14 +3369,13 @@ static int outer_child( * uid shift known. That way we can mount VFAT file systems shifted to the right place right away. This * makes sure ESP partitions and userns are compatible. */ - r = dissected_image_mount(dissected_image, directory, arg_uid_shift, - DISSECT_IMAGE_MOUNT_ROOT_ONLY|DISSECT_IMAGE_DISCARD_ON_LOOP| - (arg_read_only ? DISSECT_IMAGE_READ_ONLY : DISSECT_IMAGE_FSCK)| - (arg_start_mode == START_BOOT ? DISSECT_IMAGE_VALIDATE_OS : 0)); - if (r == -EUCLEAN) - return log_error_errno(r, "File system check for image failed: %m"); + r = dissected_image_mount_and_warn( + dissected_image, directory, arg_uid_shift, + DISSECT_IMAGE_MOUNT_ROOT_ONLY|DISSECT_IMAGE_DISCARD_ON_LOOP| + (arg_read_only ? DISSECT_IMAGE_READ_ONLY : DISSECT_IMAGE_FSCK)| + (arg_start_mode == START_BOOT ? DISSECT_IMAGE_VALIDATE_OS : 0)); if (r < 0) - return log_error_errno(r, "Failed to mount image root file system: %m"); + return r; } r = determine_uid_shift(directory); diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 9a5a463b20..bab587ba13 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -1145,6 +1145,29 @@ int dissected_image_mount(DissectedImage *m, const char *where, uid_t uid_shift, return 0; } +int dissected_image_mount_and_warn(DissectedImage *m, const char *where, uid_t uid_shift, DissectImageFlags flags) { + int r; + + assert(m); + assert(where); + + r = dissected_image_mount(m, where, uid_shift, flags); + if (r == -ENXIO) + return log_error_errno(r, "Not root file system found in image."); + if (r == -EMEDIUMTYPE) + return log_error_errno(r, "No suitable os-release file in image found."); + if (r == -EUNATCH) + return log_error_errno(r, "Encrypted file system discovered, but decryption not requested."); + if (r == -EUCLEAN) + return log_error_errno(r, "File system check on image failed."); + if (r == -EBUSY) + return log_error_errno(r, "File system already mounted elsewhere."); + if (r < 0) + return log_error_errno(r, "Failed to mount image: %m"); + + return r; +} + #if HAVE_LIBCRYPTSETUP typedef struct DecryptedPartition { struct crypt_device *device; @@ -2031,11 +2054,9 @@ int mount_image_privately_interactively( created_dir = TAKE_PTR(temp); - r = dissected_image_mount(dissected_image, created_dir, UID_INVALID, flags); - if (r == -EUCLEAN) - return log_error_errno(r, "File system check on image failed: %m"); + r = dissected_image_mount_and_warn(dissected_image, created_dir, UID_INVALID, flags); if (r < 0) - return log_error_errno(r, "Failed to mount image: %m"); + return r; if (decrypted_image) { r = decrypted_image_relinquish(decrypted_image); diff --git a/src/shared/dissect-image.h b/src/shared/dissect-image.h index 3d0a191d71..4d21789e18 100644 --- a/src/shared/dissect-image.h +++ b/src/shared/dissect-image.h @@ -106,6 +106,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(DissectedImage*, dissected_image_unref); int dissected_image_decrypt(DissectedImage *m, const char *passphrase, const void *root_hash, size_t root_hash_size, const char *verity_data, const char *root_hash_sig_path, const void *root_hash_sig, size_t root_hash_sig_size, DissectImageFlags flags, DecryptedImage **ret); int dissected_image_decrypt_interactively(DissectedImage *m, const char *passphrase, const void *root_hash, size_t root_hash_size, const char *verity_data, const char *root_hash_sig_path, const void *root_hash_sig, size_t root_hash_sig_size, DissectImageFlags flags, DecryptedImage **ret); int dissected_image_mount(DissectedImage *m, const char *dest, uid_t uid_shift, DissectImageFlags flags); +int dissected_image_mount_and_warn(DissectedImage *m, const char *where, uid_t uid_shift, DissectImageFlags flags); int dissected_image_acquire_metadata(DissectedImage *m); From af8219d5620e53e867a371641dd07185e90f2e18 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 11 Aug 2020 15:59:44 +0200 Subject: [PATCH 14/20] dissect: show proper error strings for more errors Also, make inability to decrypt and EBUSY a non-fatal issue, since we still are able to display the mount table then. --- src/dissect/dissect.c | 59 ++++++++++++++++++++++---------------- src/shared/dissect-image.c | 39 ++++++++++++++++++------- 2 files changed, 62 insertions(+), 36 deletions(-) diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index f575e1b28b..d6b6303ee1 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -323,14 +323,6 @@ static int action_dissect(DissectedImage *m, LoopDevice *d) { assert(m); assert(d); - r = dissected_image_acquire_metadata(m); - if (r == -EMEDIUMTYPE) - return log_error_errno(r, "Not a valid OS image, no os-release file included."); - if (r == -ENXIO) - return log_error_errno(r, "No root partition discovered."); - if (r < 0) - return log_error_errno(r, "Failed to acquire image metadata: %m"); - printf(" Name: %s\n", basename(arg_image)); if (ioctl(d->fd, BLKGETSIZE64, &size) < 0) @@ -340,28 +332,45 @@ static int action_dissect(DissectedImage *m, LoopDevice *d) { printf(" Size: %s\n", format_bytes(s, sizeof(s), size)); } - if (m->hostname) - printf(" Hostname: %s\n", m->hostname); + putc('\n', stdout); - if (!sd_id128_is_null(m->machine_id)) - printf("Machine ID: " SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(m->machine_id)); + r = dissected_image_acquire_metadata(m); + if (r == -ENXIO) + return log_error_errno(r, "No root partition discovered."); + if (r == -EMEDIUMTYPE) + return log_error_errno(r, "Not a valid OS image, no os-release file included."); + if (r == -EUCLEAN) + return log_error_errno(r, "File system check of image failed."); + if (r == -EUNATCH) + log_warning_errno(r, "OS image is encrypted, proceeding without showing OS image metadata."); + else if (r == -EBUSY) + log_warning_errno(r, "OS image is currently in use, proceeding without showing OS image metadata."); + else if (r < 0) + return log_error_errno(r, "Failed to acquire image metadata: %m"); + else { + if (m->hostname) + printf(" Hostname: %s\n", m->hostname); - if (!strv_isempty(m->machine_info)) { - char **p, **q; + if (!sd_id128_is_null(m->machine_id)) + printf("Machine ID: " SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(m->machine_id)); - STRV_FOREACH_PAIR(p, q, m->machine_info) - printf("%s %s=%s\n", - p == m->machine_info ? "Mach. Info:" : " ", - *p, *q); - } + if (!strv_isempty(m->machine_info)) { + char **p, **q; - if (!strv_isempty(m->os_release)) { - char **p, **q; + STRV_FOREACH_PAIR(p, q, m->machine_info) + printf("%s %s=%s\n", + p == m->machine_info ? "Mach. Info:" : " ", + *p, *q); + } - STRV_FOREACH_PAIR(p, q, m->os_release) - printf("%s %s=%s\n", - p == m->os_release ? "OS Release:" : " ", - *p, *q); + if (!strv_isempty(m->os_release)) { + char **p, **q; + + STRV_FOREACH_PAIR(p, q, m->os_release) + printf("%s %s=%s\n", + p == m->os_release ? "OS Release:" : " ", + *p, *q); + } } putc('\n', stdout); diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index bab587ba13..8cbf4828b8 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -1772,12 +1772,14 @@ int dissected_image_acquire_metadata(DissectedImage *m) { }; _cleanup_strv_free_ char **machine_info = NULL, **os_release = NULL; + _cleanup_close_pair_ int error_pipe[2] = { -1, -1 }; _cleanup_(rmdir_and_freep) char *t = NULL; _cleanup_(sigkill_waitp) pid_t child = 0; sd_id128_t machine_id = SD_ID128_NULL; _cleanup_free_ char *hostname = NULL; unsigned n_meta_initialized = 0, k; - int fds[2 * _META_MAX], r; + int fds[2 * _META_MAX], r, v; + ssize_t n; BLOCK_SIGNALS(SIGCHLD); @@ -1793,16 +1795,22 @@ int dissected_image_acquire_metadata(DissectedImage *m) { if (r < 0) goto finish; + if (pipe2(error_pipe, O_CLOEXEC) < 0) { + r = -errno; + goto finish; + } + r = safe_fork("(sd-dissect)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE, &child); if (r < 0) goto finish; if (r == 0) { + error_pipe[0] = safe_close(error_pipe[0]); + r = dissected_image_mount(m, t, UID_INVALID, DISSECT_IMAGE_READ_ONLY|DISSECT_IMAGE_MOUNT_ROOT_ONLY|DISSECT_IMAGE_VALIDATE_OS); - if (r == -EMEDIUMTYPE) /* No /etc/os-release */ - _exit(EX_OSFILE); - if (r == -ENXIO) /* No root partition */ - _exit(EX_DATAERR); if (r < 0) { + /* Let parent know the error */ + (void) write(error_pipe[1], &r, sizeof(r)); + log_debug_errno(r, "Failed to mount dissected image: %m"); _exit(EXIT_FAILURE); } @@ -1825,8 +1833,10 @@ int dissected_image_acquire_metadata(DissectedImage *m) { } r = copy_bytes(fd, fds[2*k+1], (uint64_t) -1, 0); - if (r < 0) + if (r < 0) { + (void) write(error_pipe[1], &r, sizeof(r)); _exit(EXIT_FAILURE); + } fds[2*k+1] = safe_close(fds[2*k+1]); } @@ -1834,6 +1844,8 @@ int dissected_image_acquire_metadata(DissectedImage *m) { _exit(EXIT_SUCCESS); } + error_pipe[1] = safe_close(error_pipe[1]); + for (k = 0; k < _META_MAX; k++) { _cleanup_fclose_ FILE *f = NULL; @@ -1891,11 +1903,16 @@ int dissected_image_acquire_metadata(DissectedImage *m) { r = wait_for_terminate_and_check("(sd-dissect)", child, 0); child = 0; if (r < 0) - goto finish; - if (r == EX_OSFILE) - return -EMEDIUMTYPE; /* No os-release file */ - if (r == EX_DATAERR) - return -ENXIO; /* No root partition */ + return r; + + n = read(error_pipe[0], &v, sizeof(v)); + if (n < 0) + return -errno; + if (n == sizeof(v)) + return v; /* propagate error sent to us from child */ + if (n != 0) + return -EIO; + if (r != EXIT_SUCCESS) return -EPROTO; From 5a151082d77524ef490edbb836dbb49819118120 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 29 Jul 2020 15:39:33 +0200 Subject: [PATCH 15/20] meson: move systemd-dissect to /usr/bin --- meson.build | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/meson.build b/meson.build index 134e95d475..227fe097a8 100644 --- a/meson.build +++ b/meson.build @@ -1898,8 +1898,7 @@ if conf.get('HAVE_BLKID') == 1 include_directories : includes, link_with : [libshared], install_rpath : rootlibexecdir, - install : true, - install_dir : rootlibexecdir) + install : true) endif if conf.get('ENABLE_RESOLVE') == 1 From 61f403a14f571c3b99c545118b65e5b528ad98b6 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 29 Jul 2020 15:43:43 +0200 Subject: [PATCH 16/20] man: document systemd-dissect --- man/rules/meson.build | 1 + man/systemd-dissect.xml | 244 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 245 insertions(+) create mode 100644 man/systemd-dissect.xml diff --git a/man/rules/meson.build b/man/rules/meson.build index 3fb454faa9..d545f032a2 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -818,6 +818,7 @@ manpages = [ ['systemd-debug-generator', '8', [], ''], ['systemd-delta', '1', [], ''], ['systemd-detect-virt', '1', [], ''], + ['systemd-dissect', '1', [], ''], ['systemd-environment-d-generator', '8', ['30-systemd-environment-d-generator'], diff --git a/man/systemd-dissect.xml b/man/systemd-dissect.xml new file mode 100644 index 0000000000..181b6568d0 --- /dev/null +++ b/man/systemd-dissect.xml @@ -0,0 +1,244 @@ + + + + + + + + systemd-dissect + systemd + + + + systemd-dissect + 1 + + + + systemd-dissect + Dissect file system OS images + + + + + systemd-dissect OPTIONS IMAGE + + + systemd-dissect OPTIONS IMAGE PATH + + + systemd-dissect OPTIONS IMAGE PATH TARGET + + + systemd-dissect OPTIONS IMAGE SOURCE PATH + + + + + Description + + systemd-dissect is a tool for introspecting and interacting with file system OS + disk images. It supports four different operations: + + + Show general OS image information, including the image's + os-release5 data, + machine ID, partition information and more. + + Mount an OS image to a local directory. In this mode it will dissect the OS image and + mount the included partitions according to their designation onto a directory and possibly + sub-directories. + + Copy files and directories in and out of an OS image. + + + The tool may operate on three types of OS images: + + + OS disk images containing a GPT partition table envelope, with partitions marked + according to the Discoverable Partitions + Specification. + + OS disk images containing just a plain file-system without an enveloping partition + table. (This file system is assumed to be the root file system of the OS.) + + OS disk images containing a GPT or MBR partition table, with a single + partition only. (This partition is assumed to contain the root file system of the OS.) + + + OS images may use any kind of Linux-supported file systems. In addition they may make use of LUKS + disk encryption, and contain Verity integrity information. Note that qualifying OS images may be booted + with system-nspawn1's + switch, and be used as root file system for system service using the + RootImage= unit file setting, see + system.exec5. + + + + Commands + + If neither of the command switches listed below are passed the specified disk image is opened and + general information about the image and the contained partitions and their use is shown. + + + + + + + Mount the specified OS image to the specified directory. This will dissect the image, + determine the OS root file system — as well as possibly other partitions — and mount them to the + specified directory. If the OS image contains multiple partitions marked with the Discoverable Partitions Specification + multiple nested mounts are established. This command expects two arguments: a path to an image file + and a path to a directory where to mount the image. + + To unmount an OS image mounted like this use umount8's + switch (for recursive operation), so that the OS image and all nested partition + mounts are unmounted. + + When the OS image contains LUKS encrypted or Verity integrity protected file systems + appropriate volumes are automatically set up and marked for automatic disassembly when the image is + unmounted. + + The OS image may either be specified as path to an OS image stored in a regular file or may + refer to block device node (in the latter case the block device must be the "whole" device, i.e. not + a partition device). (The other supported commands described here support this, too.) + + All mounted file systems are checked with the appropriate fsck8 + implementation in automatic fixing mode, unless explicitly turned off () or + read-only operation is requested (). + + + + + + This is a shortcut for . + + + + + + + Copies a file or directory from the specified OS image into the specified location on + the host file system. Expects three arguments: a path to an image file, a source path (relative to + the image's root directory) and a destination path (relative to the current working directory, or an + absolute path, both outside of the image). If the destination path is omitted or specified as dash + (-), the specified file is written to standard output. If the source path in the + image file system refers to a regular file it is copied to the destination path. In this case access + mode, extended attributes and timestamps are copied as well, but file ownership is not. If the source + path in the image refers to a directory, it is copied to the destination path, recursively with all + containing files and directories. In this case the file ownership is copied too. + + + + + + + Copies a file or directory from the specified location in the host file system into + the specified OS image. Expects three arguments: a path to an image file, a source path (relative to + the current working directory, or an absolute path, both outside of the image) and a destination path + (relative to the image's root directory). If the source path is omitted or specified as dash + (-), the data to write is read from standard input. If the source path in the host + file system refers to a regular file, it is copied to the destination path. In this case access mode, + extended attributes and timestamps are copied as well, but file ownership is not. If the source path + in the host file system refers to a directory it is copied to the destination path, recursively with + all containing files and directories. In this case the file ownership is copied + too. + + As with file system checks are implicitly run before the copy + operation begins. + + + + + + + + + + Options + + The following options are understood: + + + + + + + Operate in read-only mode. By default will establish + writable mount points. If this option is specified they are established in read-only mode + instead. + + + + + + Turn off automatic file system checking. By default when an image is accessed for + writing (by or ) the file systems contained in the OS + image are automatically checked using the appropriate fsck8 + command, in automatic fixing mode. This behavior may be switched off using + . + + + + + + If combined with the directory to mount the OS image to is + created if it is missing. Note that the directory is not automatically removed when the disk image is + unmounted again. + + + + + + Takes one of disabled, loop, + all, crypto. If disabled the image is + accessed with empty block discarding turned off. if loop discarding is enabled if + operating on a regular file. If crypt discarding is enabled even on encrypted file + systems. If all discarding is unconditionally enabled. + + + + + + + + Configure various aspects of Verity data integrity for the OS + image. expects a hex-encoding top-level Verity hash to use for setting + up the Verity integrity protection. expects the path to a file + containing a PKCS#7 signature file for the hash. This signature is passed to the kernel during + activation, which will match it against signature keys available in the kernel + keyring. expects the path to a file with the Verity data to use for + the OS image, in case it is stored in a detached file. It is recommended to embed the Verity data + directly in the image, using the Verity mechanisms in the Discoverable Partitions Specification. + + + + + + + + Exit status + + On success, 0 is returned, a non-zero failure code + otherwise. + + + + See Also + + systemd1, + system-nspawn1, + system.exec5, + Discoverable Partitions Specification, + umount8 + + + + From 0b9481cf2ec328015b535af79b80f61f14255dfc Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 11 Aug 2020 23:16:28 +0200 Subject: [PATCH 17/20] json: add helpers for dealing with id128 + strv --- src/shared/json.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ src/shared/json.h | 6 ++++++ 2 files changed, 51 insertions(+) diff --git a/src/shared/json.c b/src/shared/json.c index 27a3a518fe..2e151578ff 100644 --- a/src/shared/json.c +++ b/src/shared/json.c @@ -430,6 +430,12 @@ int json_variant_new_base64(JsonVariant **ret, const void *p, size_t n) { return json_variant_new_stringn(ret, s, k); } +int json_variant_new_id128(JsonVariant **ret, sd_id128_t id) { + char s[SD_ID128_STRING_MAX]; + + return json_variant_new_string(ret, sd_id128_to_string(id, s)); +} + static void json_variant_set(JsonVariant *a, JsonVariant *b) { assert(a); @@ -1964,6 +1970,17 @@ int json_variant_set_field_boolean(JsonVariant **v, const char *field, bool b) { return json_variant_set_field(v, field, m); } +int json_variant_set_field_strv(JsonVariant **v, const char *field, char **l) { + _cleanup_(json_variant_unrefp) JsonVariant *m = NULL; + int r; + + r = json_variant_new_array_strv(&m, l); + if (r < 0) + return r; + + return json_variant_set_field(v, field, m); +} + int json_variant_merge(JsonVariant **v, JsonVariant *m) { _cleanup_(json_variant_unrefp) JsonVariant *w = NULL; _cleanup_free_ JsonVariant **array = NULL; @@ -3579,6 +3596,34 @@ int json_buildv(JsonVariant **ret, va_list ap) { break; } + case _JSON_BUILD_ID128: { + sd_id128_t id; + + if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) { + r = -EINVAL; + goto finish; + } + + id = va_arg(ap, sd_id128_t); + + if (current->n_suppress == 0) { + r = json_variant_new_id128(&add, id); + if (r < 0) + goto finish; + } + + n_subtract = 1; + + if (current->expect == EXPECT_TOPLEVEL) + current->expect = EXPECT_END; + else if (current->expect == EXPECT_OBJECT_VALUE) + current->expect = EXPECT_OBJECT_KEY; + else + assert(current->expect == EXPECT_ARRAY_ELEMENT); + + break; + } + case _JSON_BUILD_OBJECT_BEGIN: if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) { diff --git a/src/shared/json.h b/src/shared/json.h index ceb01a2028..ae71593d7b 100644 --- a/src/shared/json.h +++ b/src/shared/json.h @@ -7,6 +7,8 @@ #include #include +#include "sd-id128.h" + #include "macro.h" #include "string-util.h" #include "log.h" @@ -65,6 +67,7 @@ int json_variant_new_array_bytes(JsonVariant **ret, const void *p, size_t n); int json_variant_new_array_strv(JsonVariant **ret, char **l); int json_variant_new_object(JsonVariant **ret, JsonVariant **array, size_t n); int json_variant_new_null(JsonVariant **ret); +int json_variant_new_id128(JsonVariant **ret, sd_id128_t id); static inline int json_variant_new_string(JsonVariant **ret, const char *s) { return json_variant_new_stringn(ret, s, (size_t) -1); @@ -183,6 +186,7 @@ int json_variant_set_field_string(JsonVariant **v, const char *field, const char int json_variant_set_field_integer(JsonVariant **v, const char *field, intmax_t value); int json_variant_set_field_unsigned(JsonVariant **v, const char *field, uintmax_t value); int json_variant_set_field_boolean(JsonVariant **v, const char *field, bool b); +int json_variant_set_field_strv(JsonVariant **v, const char *field, char **l); int json_variant_append_array(JsonVariant **v, JsonVariant *element); @@ -223,6 +227,7 @@ enum { _JSON_BUILD_LITERAL, _JSON_BUILD_STRV, _JSON_BUILD_BASE64, + _JSON_BUILD_ID128, _JSON_BUILD_MAX, }; @@ -243,6 +248,7 @@ enum { #define JSON_BUILD_LITERAL(l) _JSON_BUILD_LITERAL, ({ const char *_x = l; _x; }) #define JSON_BUILD_STRV(l) _JSON_BUILD_STRV, ({ char **_x = l; _x; }) #define JSON_BUILD_BASE64(p, n) _JSON_BUILD_BASE64, ({ const void *_x = p; _x; }), ({ size_t _y = n; _y; }) +#define JSON_BUILD_ID128(id) _JSON_BUILD_ID128, ({ sd_id128_t _x = id; _x; }) int json_build(JsonVariant **ret, ...); int json_buildv(JsonVariant **ret, va_list ap); From de8231b0072807d98351e5ef2d633e470a43b0f2 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 11 Aug 2020 23:16:44 +0200 Subject: [PATCH 18/20] dissect: add support for outputting JSON --- man/systemd-dissect.xml | 9 ++++ src/dissect/dissect.c | 106 ++++++++++++++++++++++++++++++++++++---- src/partition/repart.c | 2 +- 3 files changed, 107 insertions(+), 10 deletions(-) diff --git a/man/systemd-dissect.xml b/man/systemd-dissect.xml index 181b6568d0..fd70e1bfc1 100644 --- a/man/systemd-dissect.xml +++ b/man/systemd-dissect.xml @@ -153,6 +153,15 @@ operation begins. + + MODE + + Shows output formatted as JSON. Expects one of short (for the + shortest possible output without any redundant whitespace or line breaks), pretty + (for a pretty version of the same, with indentation and line breaks) or off (to turn + off json output). + + diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index d6b6303ee1..375c5739b2 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -49,6 +49,8 @@ static size_t arg_root_hash_size = 0; static char *arg_root_hash_sig_path = NULL; static void *arg_root_hash_sig = NULL; static size_t arg_root_hash_sig_size = 0; +static bool arg_json = false; +static JsonFormatFlags arg_json_format_flags = 0; STATIC_DESTRUCTOR_REGISTER(arg_root_hash, freep); STATIC_DESTRUCTOR_REGISTER(arg_verity_data, freep); @@ -80,6 +82,8 @@ static int help(void) { " 'base64:'\n" " --verity-data=PATH Specify data file with hash tree for verity if it is\n" " not embedded in IMAGE\n" + " --json=pretty|short|off\n" + " Generate JSON output\n" "\n%3$sCommands:%4$s\n" " -h --help Show this help\n" " --version Show package version\n" @@ -106,6 +110,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_VERITY_DATA, ARG_ROOT_HASH_SIG, ARG_MKDIR, + ARG_JSON, }; static const struct option options[] = { @@ -121,6 +126,7 @@ static int parse_argv(int argc, char *argv[]) { { "mkdir", no_argument, NULL, ARG_MKDIR }, { "copy-from", no_argument, NULL, 'x' }, { "copy-to", no_argument, NULL, 'a' }, + { "json", required_argument, NULL, ARG_JSON }, {} }; @@ -250,6 +256,26 @@ static int parse_argv(int argc, char *argv[]) { SET_FLAG(arg_flags, DISSECT_IMAGE_FSCK, r); break; + case ARG_JSON: + if (streq(optarg, "pretty")) { + arg_json = true; + arg_json_format_flags = JSON_FORMAT_PRETTY|JSON_FORMAT_COLOR_AUTO; + } else if (streq(optarg, "short")) { + arg_json = true; + arg_json_format_flags = JSON_FORMAT_NEWLINE; + } else if (streq(optarg, "off")) { + arg_json = false; + arg_json_format_flags = 0; + } else if (streq(optarg, "help")) { + puts("pretty\n" + "short\n" + "off"); + return 0; + } else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown argument to --json=: %s", optarg); + + break; + case '?': return -EINVAL; @@ -315,24 +341,45 @@ static int parse_argv(int argc, char *argv[]) { return 1; } +static int strv_pair_to_json(char **l, JsonVariant **ret) { + _cleanup_strv_free_ char **jl = NULL; + char **a, **b; + + STRV_FOREACH_PAIR(a, b, l) { + char *j; + + j = strjoin(*a, "=", *b); + if (!j) + return log_oom(); + + if (strv_consume(&jl, j) < 0) + return log_oom(); + } + + return json_variant_new_array_strv(ret, jl); +} + static int action_dissect(DissectedImage *m, LoopDevice *d) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; _cleanup_(table_unrefp) Table *t = NULL; - uint64_t size; + uint64_t size = UINT64_MAX; int r; assert(m); assert(d); - printf(" Name: %s\n", basename(arg_image)); + if (!arg_json) + printf(" Name: %s\n", basename(arg_image)); if (ioctl(d->fd, BLKGETSIZE64, &size) < 0) log_debug_errno(errno, "Failed to query size of loopback device: %m"); - else { + else if (!arg_json) { char s[FORMAT_BYTES_MAX]; printf(" Size: %s\n", format_bytes(s, sizeof(s), size)); } - putc('\n', stdout); + if (!arg_json) + putc('\n', stdout); r = dissected_image_acquire_metadata(m); if (r == -ENXIO) @@ -347,7 +394,7 @@ static int action_dissect(DissectedImage *m, LoopDevice *d) { log_warning_errno(r, "OS image is currently in use, proceeding without showing OS image metadata."); else if (r < 0) return log_error_errno(r, "Failed to acquire image metadata: %m"); - else { + else if (!arg_json) { if (m->hostname) printf(" Hostname: %s\n", m->hostname); @@ -373,7 +420,34 @@ static int action_dissect(DissectedImage *m, LoopDevice *d) { } } - putc('\n', stdout); + if (arg_json) { + _cleanup_(json_variant_unrefp) JsonVariant *mi = NULL, *osr = NULL; + + if (!strv_isempty(m->machine_info)) { + r = strv_pair_to_json(m->machine_info, &mi); + if (r < 0) + return log_oom(); + } + + if (!strv_isempty(m->os_release)) { + r = strv_pair_to_json(m->os_release, &osr); + if (r < 0) + return log_oom(); + } + + r = json_build(&v, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("name", JSON_BUILD_STRING(basename(arg_image))), + JSON_BUILD_PAIR("size", JSON_BUILD_INTEGER(size)), + JSON_BUILD_PAIR_CONDITION(m->hostname, "hostname", JSON_BUILD_STRING(m->hostname)), + JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(m->machine_id), "machineId", JSON_BUILD_ID128(m->machine_id)), + JSON_BUILD_PAIR_CONDITION(mi, "machineInfo", JSON_BUILD_VARIANT(mi)), + JSON_BUILD_PAIR_CONDITION(osr, "osRelease", JSON_BUILD_VARIANT(osr)))); + if (r < 0) + return log_oom(); + } + + if (!arg_json) + putc('\n', stdout); t = table_new("rw", "designator", "partition uuid", "fstype", "architecture", "verity", "node", "partno"); if (!t) @@ -435,9 +509,23 @@ static int action_dissect(DissectedImage *m, LoopDevice *d) { return table_log_add_error(r); } - r = table_print(t, stdout); - if (r < 0) - return log_error_errno(r, "Failed to dump table: %m"); + if (arg_json) { + _cleanup_(json_variant_unrefp) JsonVariant *jt = NULL; + + r = table_to_json(t, &jt); + if (r < 0) + return log_error_errno(r, "Failed to convert table to JSON: %m"); + + r = json_variant_set_field(&v, "mounts", jt); + if (r < 0) + return log_oom(); + + json_variant_dump(v, arg_json_format_flags, stdout, NULL); + } else { + r = table_print(t, stdout); + if (r < 0) + return log_error_errno(r, "Failed to dump table: %m"); + } return 0; } diff --git a/src/partition/repart.c b/src/partition/repart.c index 9a9fd6dff3..572db3bdc4 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -2826,7 +2826,7 @@ static int help(void) { " --seed=UUID 128bit seed UUID to derive all UUIDs from\n" " --size=BYTES Grow loopback file to specified size\n" " --json=pretty|short|off\n" - " Generate json output\n" + " Generate JSON output\n" "\nSee the %s for details.\n" , program_invocation_short_name , ansi_highlight(), ansi_normal() From 35afe47abe1b9d4c034d5ad6e01e49addb6b976c Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 11 Aug 2020 23:32:19 +0200 Subject: [PATCH 19/20] test: update tests to use new JSON output instead of human readable output --- test/units/testsuite-50.sh | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/test/units/testsuite-50.sh b/test/units/testsuite-50.sh index 587184e854..bb3b20da3e 100755 --- a/test/units/testsuite-50.sh +++ b/test/units/testsuite-50.sh @@ -28,25 +28,25 @@ cp /usr/share/minimal.* "${image_dir}/" image="${image_dir}/minimal" roothash="$(cat ${image}.roothash)" -/usr/lib/systemd/systemd-dissect ${image}.raw | grep -q -F "Found read-only 'root' partition of type squashfs with verity" -/usr/lib/systemd/systemd-dissect ${image}.raw | grep -q -F "MARKER=1" -/usr/lib/systemd/systemd-dissect ${image}.raw | grep -q -F -f /usr/lib/os-release +systemd-dissect --json=short ${image}.raw | grep -q -F '{"rw":"ro","designator":"root","partition_uuid":null,"fstype":"squashfs","architecture":null,"verity":"external"' +systemd-dissect ${image}.raw | grep -q -F "MARKER=1" +systemd-dissect ${image}.raw | grep -q -F -f /usr/lib/os-release mv ${image}.verity ${image}.fooverity mv ${image}.roothash ${image}.foohash -/usr/lib/systemd/systemd-dissect ${image}.raw --root-hash=${roothash} --verity-data=${image}.fooverity | grep -q -F "Found read-only 'root' partition of type squashfs with verity" -/usr/lib/systemd/systemd-dissect ${image}.raw --root-hash=${roothash} --verity-data=${image}.fooverity | grep -q -F "MARKER=1" -/usr/lib/systemd/systemd-dissect ${image}.raw --root-hash=${roothash} --verity-data=${image}.fooverity | grep -q -F -f /usr/lib/os-release +systemd-dissect --json=short ${image}.raw --root-hash=${roothash} --verity-data=${image}.fooverity | grep -q -F '{"rw":"ro","designator":"root","partition_uuid":null,"fstype":"squashfs","architecture":null,"verity":"external"' +systemd-dissect ${image}.raw --root-hash=${roothash} --verity-data=${image}.fooverity | grep -q -F "MARKER=1" +systemd-dissect ${image}.raw --root-hash=${roothash} --verity-data=${image}.fooverity | grep -q -F -f /usr/lib/os-release mv ${image}.fooverity ${image}.verity mv ${image}.foohash ${image}.roothash mkdir -p ${image_dir}/mount ${image_dir}/mount2 -/usr/lib/systemd/systemd-dissect --mount ${image}.raw ${image_dir}/mount +systemd-dissect --mount ${image}.raw ${image_dir}/mount cat ${image_dir}/mount/usr/lib/os-release | grep -q -F -f /usr/lib/os-release cat ${image_dir}/mount/etc/os-release | grep -q -F -f /usr/lib/os-release cat ${image_dir}/mount/usr/lib/os-release | grep -q -F "MARKER=1" # Verity volume should be shared (opened only once) -/usr/lib/systemd/systemd-dissect --mount ${image}.raw ${image_dir}/mount2 +systemd-dissect --mount ${image}.raw ${image_dir}/mount2 verity_count=$(ls -1 /dev/mapper/ | grep -c verity) # In theory we should check that count is exactly one. In practice, libdevmapper # randomly and unpredictably fails with an unhelpful EINVAL when a device is open @@ -111,12 +111,16 @@ dd if=${image}.raw of=${loop}p1 dd if=${image}.verity of=${loop}p2 losetup -d ${loop} -/usr/lib/systemd/systemd-dissect --root-hash ${roothash} ${image}.gpt | grep -q "Found read-only 'root' partition (UUID $(head -c 32 ${image}.roothash)) of type squashfs for .* with verity on partition #1" -/usr/lib/systemd/systemd-dissect --root-hash ${roothash} ${image}.gpt | grep -q "Found read-only 'root-verity' partition (UUID $(tail -c 32 ${image}.roothash)) of type DM_verity_hash for .* on partition #2" -/usr/lib/systemd/systemd-dissect --root-hash ${roothash} ${image}.gpt | grep -q -F "MARKER=1" -/usr/lib/systemd/systemd-dissect --root-hash ${roothash} ${image}.gpt | grep -q -F -f /usr/lib/os-release +# Derive partition UUIDs from root hash, in UUID syntax +ROOT_UUID=$(systemd-id128 -u show $(head -c 32 ${image}.roothash) -u | tail -n 1 | cut -b 6-) +VERITY_UUID=$(systemd-id128 -u show $(tail -c 32 ${image}.roothash) -u | tail -n 1 | cut -b 6-) -/usr/lib/systemd/systemd-dissect --root-hash ${roothash} --mount ${image}.gpt ${image_dir}/mount +systemd-dissect --json=short --root-hash ${roothash} ${image}.gpt | grep -q '{"rw":"ro","designator":"root","partition_uuid":"'$ROOT_UUID'","fstype":"squashfs","architecture":"x86-64","verity":"yes","node":' +systemd-dissect --json=short --root-hash ${roothash} ${image}.gpt | grep -q '{"rw":"ro","designator":"root-verity","partition_uuid":"'$VERITY_UUID'","fstype":"DM_verity_hash","architecture":"x86-64","verity":null,"node":' +systemd-dissect --root-hash ${roothash} ${image}.gpt | grep -q -F "MARKER=1" +systemd-dissect --root-hash ${roothash} ${image}.gpt | grep -q -F -f /usr/lib/os-release + +systemd-dissect --root-hash ${roothash} --mount ${image}.gpt ${image_dir}/mount cat ${image_dir}/mount/usr/lib/os-release | grep -q -F -f /usr/lib/os-release cat ${image_dir}/mount/etc/os-release | grep -q -F -f /usr/lib/os-release cat ${image_dir}/mount/usr/lib/os-release | grep -q -F "MARKER=1" From 1af83e7c37bc408678b43add88555e92102d9031 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 28 Jul 2020 23:43:26 +0200 Subject: [PATCH 20/20] update TODO --- TODO | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/TODO b/TODO index 07f21fc06e..a8d0ca290f 100644 --- a/TODO +++ b/TODO @@ -42,9 +42,6 @@ Features: mounting a subdir of the root fs as actual root. This can be used as fstype-agnostic version of btrfs' rootflags=subvol=foobar. -* add --copy-from and --copy-to command to systemd-dissect which copies stuff - in and out of a disk image - * Support ProtectProc= or so, using: https://patchwork.kernel.org/cover/11310197/ * if /usr/bin/swapoff fails due to OOM, log a friendly explanatory message about it @@ -112,10 +109,6 @@ Features: * systemd-path: add ESP and XBOOTLDR path. Add "private" runtime/state/cache dir enum, mapping to $RUNTIME_DIRECTORY, $STATE_DIRECTORY and such -* make "systemd-dissect" an official supported tool, i.e. move to /usr/bin/ and - provide man page. Given that we now have a tool that can generate images like - this, it's useful to have one that can dump contents of them, too. - * All tools that support --root= should also learn --image= so that they can operate on disk images directly. Specifically: bootctl, systemctl, coredumpctl. (Already done: systemd-nspawn, systemd-firstboot, @@ -1153,10 +1146,6 @@ Features: - optionally automatically add FORWARD rules to iptables whenever nspawn is running, remove them when shut down. -* dissect - - refuse mounting over a mount point - - automatically discover .roothash files in dissect, similarly to nspawn - * machined: - add an API so that libvirt-lxc can inform us about network interfaces being removed or added to an existing machine