From 2292fa1e311d4c81d11d9de88cf9a9a6bef0befd Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 6 Jul 2023 10:58:44 +0200 Subject: [PATCH] dissect: Allow a few verbs to operate on directories as well as image files --copy-to, --copy-from, --list and --mtree are useful for image directories as well as image files, so for those verbs, let's check if we were passed a directory and skip all the image file setup if that's the case. --- man/systemd-dissect.xml | 43 ++++---- src/dissect/dissect.c | 204 +++++++++++++++++++++---------------- test/units/testsuite-50.sh | 10 ++ 3 files changed, 151 insertions(+), 106 deletions(-) diff --git a/man/systemd-dissect.xml b/man/systemd-dissect.xml index 0f7928a6c1e..ce41faaf1a2 100644 --- a/man/systemd-dissect.xml +++ b/man/systemd-dissect.xml @@ -202,8 +202,8 @@ - Prints the paths of all the files and directories in the specified OS image to - standard output. + Prints the paths of all the files and directories in the specified OS image or + directory to standard output. @@ -212,7 +212,7 @@ Generates a BSD mtree8 - compatible file manifest of the specified disk image. This is useful for comparing disk image + compatible file manifest of the specified disk image or directory. This is useful for comparing image contents in detail, including inode information and other metadata. While the generated manifest will contain detailed inode information, it currently excludes extended attributes, file system capabilities, MAC labels, @@ -242,15 +242,16 @@ - 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 OS image or directory into the + specified location on the host file system. Expects three arguments: a path to an image file or + directory, 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. @@ -258,15 +259,15 @@ 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. + the specified OS image or directory. Expects three arguments: a path to an image file or directory, 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. diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index d2a95eaf681..9db8f862e8e 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -64,6 +64,7 @@ static enum { ACTION_VALIDATE, } arg_action = ACTION_DISSECT; static char *arg_image = NULL; +static char *arg_root = NULL; static char *arg_path = NULL; static const char *arg_source = NULL; static const char *arg_target = NULL; @@ -87,6 +88,7 @@ static char *arg_loop_ref = NULL; static ImagePolicy* arg_image_policy = NULL; STATIC_DESTRUCTOR_REGISTER(arg_image, freep); +STATIC_DESTRUCTOR_REGISTER(arg_root, freep); STATIC_DESTRUCTOR_REGISTER(arg_path, freep); STATIC_DESTRUCTOR_REGISTER(arg_verity_settings, verity_settings_done); STATIC_DESTRUCTOR_REGISTER(arg_argv, strv_freep); @@ -206,6 +208,31 @@ static int patch_argv(int *argc, char ***argv, char ***buf) { return 1; } +static int parse_image_path_argument(const char *path, char **ret_root, char **ret_image) { + _cleanup_free_ char *p = NULL; + struct stat st; + int r; + + assert(ret_image); + + r = parse_path_argument(path, /* suppress_root= */ false, &p); + if (r < 0) + return r; + + if (stat(p, &st) < 0) + return log_error_errno(errno, "Failed to stat %s: %m", p); + + if (S_ISDIR(st.st_mode)) { + if (!ret_root) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "%s is not an image file.", p); + + *ret_root = TAKE_PTR(p); + } else + *ret_image = TAKE_PTR(p); + + return 0; +} + static int parse_argv(int argc, char *argv[]) { enum { @@ -493,7 +520,7 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected an image file path as only argument."); - r = parse_path_argument(argv[optind], /* suppress_root= */ false, &arg_image); + r = parse_image_path_argument(argv[optind], NULL, &arg_image); if (r < 0) return r; @@ -506,7 +533,7 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected an image file path and mount point path as only arguments."); - r = parse_path_argument(argv[optind], /* suppress_root= */ false, &arg_image); + r = parse_image_path_argument(argv[optind], NULL, &arg_image); if (r < 0) return r; @@ -532,7 +559,7 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected an image file path as only argument."); - r = parse_path_argument(argv[optind], /* suppress_root= */ false, &arg_image); + r = parse_image_path_argument(argv[optind], NULL, &arg_image); if (r < 0) return r; break; @@ -542,7 +569,7 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected an image file path or loopback device as only argument."); - r = parse_path_argument(argv[optind], /* suppress_root= */ false, &arg_image); + r = parse_image_path_argument(argv[optind], NULL, &arg_image); if (r < 0) return r; break; @@ -551,9 +578,9 @@ static int parse_argv(int argc, char *argv[]) { case ACTION_MTREE: if (optind + 1 != argc) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Expected an image file path as only argument."); + "Expected an image file or directory path as only argument."); - r = parse_path_argument(argv[optind], /* suppress_root= */ false, &arg_image); + r = parse_image_path_argument(argv[optind], &arg_root, &arg_image); if (r < 0) return r; @@ -563,9 +590,9 @@ static int parse_argv(int argc, char *argv[]) { 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."); + "Expected an image file or directory path, a source path and an optional destination path as only arguments."); - r = parse_path_argument(argv[optind], /* suppress_root= */ false, &arg_image); + r = parse_image_path_argument(argv[optind], &arg_root, &arg_image); if (r < 0) return r; arg_source = argv[optind + 1]; @@ -577,9 +604,9 @@ static int parse_argv(int argc, char *argv[]) { 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."); + "Expected an image file or directory path, an optional source path and a destination path as only arguments."); - r = parse_path_argument(argv[optind], /* suppress_root= */ false, &arg_image); + r = parse_image_path_argument(argv[optind], &arg_root, &arg_image); if (r < 0) return r; @@ -599,7 +626,7 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected an image file path and an optional command line."); - r = parse_path_argument(argv[optind], /* suppress_root= */ false, &arg_image); + r = parse_image_path_argument(argv[optind], NULL, &arg_image); if (r < 0) return r; @@ -622,7 +649,7 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected an image file path as only argument."); - r = parse_path_argument(argv[optind], /* suppress_root= */ false, &arg_image); + r = parse_image_path_argument(argv[optind], NULL, &arg_image); if (r < 0) return r; @@ -1232,46 +1259,52 @@ static int action_list_or_mtree_or_copy(DissectedImage *m, LoopDevice *d) { _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL; _cleanup_(rmdir_and_freep) char *created_dir = NULL; _cleanup_free_ char *temp = NULL; + const char *root; int r; - assert(m); - assert(d); assert(IN_SET(arg_action, ACTION_LIST, ACTION_MTREE, ACTION_COPY_FROM, ACTION_COPY_TO)); - r = detach_mount_namespace(); - if (r < 0) - return log_error_errno(r, "Failed to detach mount namespace: %m"); + if (arg_image) { + assert(m); + assert(d); - 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 = detach_mount_namespace(); + if (r < 0) + return log_error_errno(r, "Failed to detach mount namespace: %m"); - r = mkdir_p(temp, 0700); - if (r < 0) - return log_error_errno(r, "Failed to create mount point: %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"); - created_dir = TAKE_PTR(temp); + r = mkdir_p(temp, 0700); + if (r < 0) + return log_error_errno(r, "Failed to create mount point: %m"); - r = dissected_image_mount_and_warn(m, created_dir, UID_INVALID, UID_INVALID, arg_flags); - if (r < 0) - return r; + created_dir = TAKE_PTR(temp); - mounted_dir = TAKE_PTR(created_dir); + r = dissected_image_mount_and_warn(m, created_dir, UID_INVALID, UID_INVALID, arg_flags); + if (r < 0) + return r; - r = loop_device_flock(d, LOCK_UN); - if (r < 0) - return log_error_errno(r, "Failed to unlock loopback block device: %m"); + mounted_dir = TAKE_PTR(created_dir); - r = dissected_image_relinquish(m); - if (r < 0) - return log_error_errno(r, "Failed to relinquish DM and loopback block devices: %m"); + r = loop_device_flock(d, LOCK_UN); + if (r < 0) + return log_error_errno(r, "Failed to unlock loopback block device: %m"); + + r = dissected_image_relinquish(m); + if (r < 0) + return log_error_errno(r, "Failed to relinquish DM and loopback block devices: %m"); + } + + root = mounted_dir ?: arg_root; switch (arg_action) { case ACTION_COPY_FROM: { _cleanup_close_ int source_fd = -EBADF, target_fd = -EBADF; - source_fd = chase_and_open(arg_source, mounted_dir, CHASE_PREFIX_ROOT|CHASE_WARN, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL); + source_fd = chase_and_open(arg_source, root, 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); @@ -1328,7 +1361,7 @@ static int action_list_or_mtree_or_copy(DissectedImage *m, LoopDevice *d) { return log_error_errno(r, "Failed to extract filename from target path '%s': %m", arg_target); is_dir = r == O_DIRECTORY; - r = chase(dn, mounted_dir, CHASE_PREFIX_ROOT|CHASE_WARN, NULL, &dfd); + r = chase(dn, root, CHASE_PREFIX_ROOT|CHASE_WARN, NULL, &dfd); if (r < 0) return log_error_errno(r, "Failed to open '%s': %m", dn); @@ -1398,7 +1431,7 @@ static int action_list_or_mtree_or_copy(DissectedImage *m, LoopDevice *d) { case ACTION_MTREE: { _cleanup_close_ int dfd = -EBADF; - dfd = open(mounted_dir, O_DIRECTORY|O_CLOEXEC|O_RDONLY); + dfd = open(root, O_DIRECTORY|O_CLOEXEC|O_RDONLY); if (dfd < 0) return log_error_errno(errno, "Failed to open mount directory: %m"); @@ -1778,64 +1811,65 @@ static int run(int argc, char *argv[]) { break; } - r = verity_settings_load( + if (arg_image) { + r = verity_settings_load( &arg_verity_settings, arg_image, NULL, NULL); - if (r < 0) - return log_error_errno(r, "Failed to read verity artifacts for %s: %m", arg_image); - - if (arg_verity_settings.data_path) - 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 */ - - if (arg_action == ACTION_VALIDATE) - return action_validate(); - - 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, /* sector_size= */ UINT32_MAX, loop_flags, LOCK_SH, &d); - else - r = loop_device_make_by_path(arg_image, open_flags, /* sector_size= */ UINT32_MAX, loop_flags, LOCK_SH, &d); - if (r < 0) - return log_error_errno(r, "Failed to set up loopback device for %s: %m", arg_image); - - if (arg_loop_ref) { - r = loop_device_set_filename(d, arg_loop_ref); if (r < 0) - log_warning_errno(r, "Failed to set loop reference string to '%s', ignoring: %m", arg_loop_ref); - } + return log_error_errno(r, "Failed to read verity artifacts for %s: %m", arg_image); - r = dissect_loop_device_and_warn( - d, - &arg_verity_settings, - /* mount_options= */ NULL, - arg_image_policy, - arg_flags, - &m); - if (r < 0) - return r; + if (arg_verity_settings.data_path) + 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 */ - if (arg_action == ACTION_ATTACH) - return action_attach(m, d); + if (arg_action == ACTION_VALIDATE) + return action_validate(); - r = dissected_image_load_verity_sig_partition( - m, - d->fd, - &arg_verity_settings); - if (r < 0) - return log_error_errno(r, "Failed to load verity signature partition: %m"); + 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, /* sector_size= */ UINT32_MAX, loop_flags, LOCK_SH, &d); + else + r = loop_device_make_by_path(arg_image, open_flags, /* sector_size= */ UINT32_MAX, loop_flags, LOCK_SH, &d); + if (r < 0) + return log_error_errno(r, "Failed to set up loopback device for %s: %m", arg_image); - if (arg_action != ACTION_DISSECT) { - r = dissected_image_decrypt_interactively( - m, NULL, + if (arg_loop_ref) { + r = loop_device_set_filename(d, arg_loop_ref); + if (r < 0) + log_warning_errno(r, "Failed to set loop reference string to '%s', ignoring: %m", arg_loop_ref); + } + + r = dissect_loop_device_and_warn( + d, &arg_verity_settings, - arg_flags); + /* mount_options= */ NULL, + arg_image_policy, + arg_flags, + &m); if (r < 0) return r; + + if (arg_action == ACTION_ATTACH) + return action_attach(m, d); + + r = dissected_image_load_verity_sig_partition( + m, + d->fd, + &arg_verity_settings); + if (r < 0) + return log_error_errno(r, "Failed to load verity signature partition: %m"); + + if (arg_action != ACTION_DISSECT) { + r = dissected_image_decrypt_interactively( + m, NULL, + &arg_verity_settings, + arg_flags); + if (r < 0) + return r; + } } switch (arg_action) { diff --git a/test/units/testsuite-50.sh b/test/units/testsuite-50.sh index 9222fc70108..ab2c2959c05 100755 --- a/test/units/testsuite-50.sh +++ b/test/units/testsuite-50.sh @@ -540,6 +540,16 @@ systemctl stop test-root-ephemeral timeout 10 bash -c 'while ! test -z "$(ls -A /var/lib/systemd/ephemeral-trees)"; do sleep .5; done' test ! -f /tmp/img/abc +systemd-dissect --mtree /tmp/img +systemd-dissect --list /tmp/img + +read -r SHA256SUM1 _ < <(systemd-dissect --copy-from /tmp/img etc/os-release | sha256sum) +test "$SHA256SUM1" != "" + +echo abc > abc +systemd-dissect --copy-to /tmp/img abc /abc +test -f /tmp/img/abc + echo OK >/testok exit 0