Merge pull request #28274 from DaanDeMeyer/dissect-root

dissect: Allow a few verbs to operate on directories as well as image files
This commit is contained in:
Luca Boccassi
2023-07-06 14:41:48 +01:00
committed by GitHub
3 changed files with 172 additions and 108 deletions
+130 -86
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;
@@ -85,8 +86,10 @@ static bool arg_in_memory = false;
static char **arg_argv = NULL;
static char *arg_loop_ref = NULL;
static ImagePolicy* arg_image_policy = NULL;
static bool arg_mtree_hash = true;
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);
@@ -135,6 +138,7 @@ static int help(void) {
" --json=pretty|short|off\n"
" Generate JSON output\n"
" --loop-ref=NAME Set reference string for loopback device\n"
" --mtree-hash=BOOL Whether to include SHA256 hash in the mtree output\n"
"\n%3$sCommands:%4$s\n"
" -h --help Show this help\n"
" --version Show package version\n"
@@ -206,6 +210,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 {
@@ -230,6 +259,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_LOOP_REF,
ARG_IMAGE_POLICY,
ARG_VALIDATE,
ARG_MTREE_HASH,
};
static const struct option options[] = {
@@ -261,6 +291,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "loop-ref", required_argument, NULL, ARG_LOOP_REF },
{ "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
{ "validate", no_argument, NULL, ARG_VALIDATE },
{ "mtree-hash", required_argument, NULL, ARG_MTREE_HASH },
{}
};
@@ -478,6 +509,12 @@ static int parse_argv(int argc, char *argv[]) {
arg_action = ACTION_VALIDATE;
break;
case ARG_MTREE_HASH:
r = parse_boolean_argument("--mtree-hash=", optarg, &arg_mtree_hash);
if (r < 0)
return r;
break;
case '?':
return -EINVAL;
@@ -493,7 +530,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 +543,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 +569,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 +579,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 +588,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 +600,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 +614,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 +636,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 +659,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;
@@ -1175,7 +1212,7 @@ static int mtree_print_item(
ansi_normal(),
(uint64_t) sx->stx_size);
if (inode_fd >= 0 && sx->stx_size > 0) {
if (arg_mtree_hash && inode_fd >= 0 && sx->stx_size > 0) {
uint8_t hash[SHA256_DIGEST_SIZE];
r = get_file_sha256(inode_fd, hash);
@@ -1232,46 +1269,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 +1371,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 +1441,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 +1821,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) {