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.
This commit is contained in:
Daan De Meyer 2023-07-06 10:58:44 +02:00
parent a130b09513
commit 2292fa1e31
3 changed files with 151 additions and 106 deletions

View file

@ -202,8 +202,8 @@
<term><option>--list</option></term>
<term><option>-l</option></term>
<listitem><para>Prints the paths of all the files and directories in the specified OS image to
standard output.</para></listitem>
<listitem><para>Prints the paths of all the files and directories in the specified OS image or
directory to standard output.</para></listitem>
</varlistentry>
<varlistentry>
@ -212,7 +212,7 @@
<listitem><para>Generates a BSD
<citerefentry project='die-net'><refentrytitle>mtree</refentrytitle><manvolnum>8</manvolnum></citerefentry>
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 @@
<term><option>--copy-from</option></term>
<term><option>-x</option></term>
<listitem><para>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
(<literal>-</literal>), 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.</para></listitem>
<listitem><para>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 (<literal>-</literal>), 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.</para></listitem>
</varlistentry>
<varlistentry>
@ -258,15 +259,15 @@
<term><option>-a</option></term>
<listitem><para>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
(<literal>-</literal>), 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.</para>
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 (<literal>-</literal>), 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.</para>
<para>As with <option>--mount</option> file system checks are implicitly run before the copy
operation begins.</para></listitem>

View file

@ -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) {

View file

@ -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