1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
5 #include <linux/loop.h>
10 #include "architecture.h"
12 #include "dissect-image.h"
15 #include "format-table.h"
16 #include "format-util.h"
18 #include "hexdecoct.h"
20 #include "loop-util.h"
21 #include "main-func.h"
23 #include "mount-util.h"
24 #include "namespace-util.h"
25 #include "parse-argument.h"
26 #include "parse-util.h"
27 #include "path-util.h"
28 #include "pretty-print.h"
29 #include "stat-util.h"
30 #include "string-util.h"
32 #include "terminal-util.h"
33 #include "tmpfile-util.h"
34 #include "user-util.h"
42 } arg_action
= ACTION_DISSECT
;
43 static const char *arg_image
= NULL
;
44 static const char *arg_path
= NULL
;
45 static const char *arg_source
= NULL
;
46 static const char *arg_target
= NULL
;
47 static DissectImageFlags arg_flags
= DISSECT_IMAGE_REQUIRE_ROOT
|DISSECT_IMAGE_DISCARD_ON_LOOP
|DISSECT_IMAGE_RELAX_VAR_CHECK
|DISSECT_IMAGE_FSCK
;
48 static VeritySettings arg_verity_settings
= VERITY_SETTINGS_DEFAULT
;
49 static JsonFormatFlags arg_json_format_flags
= JSON_FORMAT_OFF
;
50 static PagerFlags arg_pager_flags
= 0;
51 static bool arg_legend
= true;
53 STATIC_DESTRUCTOR_REGISTER(arg_verity_settings
, verity_settings_done
);
55 static int help(void) {
56 _cleanup_free_
char *link
= NULL
;
59 r
= terminal_urlify_man("systemd-dissect", "1", &link
);
63 printf("%1$s [OPTIONS...] IMAGE\n"
64 "%1$s [OPTIONS...] --mount IMAGE PATH\n"
65 "%1$s [OPTIONS...] --copy-from IMAGE PATH [TARGET]\n"
66 "%1$s [OPTIONS...] --copy-to IMAGE [SOURCE] PATH\n\n"
67 "%5$sDissect a file system OS image.%6$s\n\n"
69 " --no-pager Do not pipe output into a pager\n"
70 " --no-legend Do not show the headers and footers\n"
71 " -r --read-only Mount read-only\n"
72 " --fsck=BOOL Run fsck before mounting\n"
73 " --mkdir Make mount directory before mounting, if missing\n"
74 " --discard=MODE Choose 'discard' mode (disabled, loop, all, crypto)\n"
75 " --root-hash=HASH Specify root hash for verity\n"
76 " --root-hash-sig=SIG Specify pkcs7 signature of root hash for verity\n"
77 " as a DER encoded PKCS7, either as a path to a file\n"
78 " or as an ASCII base64 encoded string prefixed by\n"
80 " --verity-data=PATH Specify data file with hash tree for verity if it is\n"
81 " not embedded in IMAGE\n"
82 " --json=pretty|short|off\n"
83 " Generate JSON output\n"
84 "\n%3$sCommands:%4$s\n"
85 " -h --help Show this help\n"
86 " --version Show package version\n"
87 " -m --mount Mount the image to the specified directory\n"
88 " -M Shortcut for --mount --mkdir\n"
89 " -x --copy-from Copy files from image to host\n"
90 " -a --copy-to Copy files from host to image\n"
91 "\nSee the %2$s for details.\n",
92 program_invocation_short_name
,
102 static int parse_argv(int argc
, char *argv
[]) {
117 static const struct option options
[] = {
118 { "help", no_argument
, NULL
, 'h' },
119 { "version", no_argument
, NULL
, ARG_VERSION
},
120 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
121 { "no-legend", no_argument
, NULL
, ARG_NO_LEGEND
},
122 { "mount", no_argument
, NULL
, 'm' },
123 { "read-only", no_argument
, NULL
, 'r' },
124 { "discard", required_argument
, NULL
, ARG_DISCARD
},
125 { "fsck", required_argument
, NULL
, ARG_FSCK
},
126 { "root-hash", required_argument
, NULL
, ARG_ROOT_HASH
},
127 { "root-hash-sig", required_argument
, NULL
, ARG_ROOT_HASH_SIG
},
128 { "verity-data", required_argument
, NULL
, ARG_VERITY_DATA
},
129 { "mkdir", no_argument
, NULL
, ARG_MKDIR
},
130 { "copy-from", no_argument
, NULL
, 'x' },
131 { "copy-to", no_argument
, NULL
, 'a' },
132 { "json", required_argument
, NULL
, ARG_JSON
},
141 while ((c
= getopt_long(argc
, argv
, "hmrMxa", options
, NULL
)) >= 0) {
152 arg_pager_flags
|= PAGER_DISABLE
;
160 arg_action
= ACTION_MOUNT
;
164 arg_flags
|= DISSECT_IMAGE_MKDIR
;
168 /* Shortcut combination of the above two */
169 arg_action
= ACTION_MOUNT
;
170 arg_flags
|= DISSECT_IMAGE_MKDIR
;
174 arg_action
= ACTION_COPY_FROM
;
175 arg_flags
|= DISSECT_IMAGE_READ_ONLY
;
179 arg_action
= ACTION_COPY_TO
;
183 arg_flags
|= DISSECT_IMAGE_READ_ONLY
;
187 DissectImageFlags flags
;
189 if (streq(optarg
, "disabled"))
191 else if (streq(optarg
, "loop"))
192 flags
= DISSECT_IMAGE_DISCARD_ON_LOOP
;
193 else if (streq(optarg
, "all"))
194 flags
= DISSECT_IMAGE_DISCARD_ON_LOOP
| DISSECT_IMAGE_DISCARD
;
195 else if (streq(optarg
, "crypt"))
196 flags
= DISSECT_IMAGE_DISCARD_ANY
;
197 else if (streq(optarg
, "list")) {
204 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
205 "Unknown --discard= parameter: %s",
207 arg_flags
= (arg_flags
& ~DISSECT_IMAGE_DISCARD_ANY
) | flags
;
212 case ARG_ROOT_HASH
: {
213 _cleanup_free_
void *p
= NULL
;
216 r
= unhexmem(optarg
, strlen(optarg
), &p
, &l
);
218 return log_error_errno(r
, "Failed to parse root hash '%s': %m", optarg
);
219 if (l
< sizeof(sd_id128_t
))
220 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
221 "Root hash must be at least 128bit long: %s", optarg
);
223 free_and_replace(arg_verity_settings
.root_hash
, p
);
224 arg_verity_settings
.root_hash_size
= l
;
228 case ARG_ROOT_HASH_SIG
: {
233 if ((value
= startswith(optarg
, "base64:"))) {
234 r
= unbase64mem(value
, strlen(value
), &p
, &l
);
236 return log_error_errno(r
, "Failed to parse root hash signature '%s': %m", optarg
);
238 r
= read_full_file(optarg
, (char**) &p
, &l
);
240 return log_error_errno(r
, "Failed to read root hash signature file '%s': %m", optarg
);
243 free_and_replace(arg_verity_settings
.root_hash_sig
, p
);
244 arg_verity_settings
.root_hash_sig_size
= l
;
248 case ARG_VERITY_DATA
:
249 r
= parse_path_argument(optarg
, false, &arg_verity_settings
.data_path
);
255 r
= parse_boolean(optarg
);
257 return log_error_errno(r
, "Failed to parse --fsck= parameter: %s", optarg
);
259 SET_FLAG(arg_flags
, DISSECT_IMAGE_FSCK
, r
);
263 r
= parse_json_argument(optarg
, &arg_json_format_flags
);
273 assert_not_reached("Unhandled option");
278 switch (arg_action
) {
281 if (optind
+ 1 != argc
)
282 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
283 "Expected an image file path as only argument.");
285 arg_image
= argv
[optind
];
286 arg_flags
|= DISSECT_IMAGE_READ_ONLY
;
290 if (optind
+ 2 != argc
)
291 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
292 "Expected an image file path and mount point path as only arguments.");
294 arg_image
= argv
[optind
];
295 arg_path
= argv
[optind
+ 1];
298 case ACTION_COPY_FROM
:
299 if (argc
< optind
+ 2 || argc
> optind
+ 3)
300 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
301 "Expected an image file path, a source path and an optional destination path as only arguments.");
303 arg_image
= argv
[optind
];
304 arg_source
= argv
[optind
+ 1];
305 arg_target
= argc
> optind
+ 2 ? argv
[optind
+ 2] : "-" /* this means stdout */ ;
307 arg_flags
|= DISSECT_IMAGE_READ_ONLY
;
311 if (argc
< optind
+ 2 || argc
> optind
+ 3)
312 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
313 "Expected an image file path, an optional source path and a destination path as only arguments.");
315 arg_image
= argv
[optind
];
317 if (argc
> optind
+ 2) {
318 arg_source
= argv
[optind
+ 1];
319 arg_target
= argv
[optind
+ 2];
321 arg_source
= "-"; /* this means stdin */
322 arg_target
= argv
[optind
+ 1];
328 assert_not_reached("Unknown action.");
334 static int strv_pair_to_json(char **l
, JsonVariant
**ret
) {
335 _cleanup_strv_free_
char **jl
= NULL
;
338 STRV_FOREACH_PAIR(a
, b
, l
) {
341 j
= strjoin(*a
, "=", *b
);
345 if (strv_consume(&jl
, j
) < 0)
349 return json_variant_new_array_strv(ret
, jl
);
352 static int action_dissect(DissectedImage
*m
, LoopDevice
*d
) {
353 _cleanup_(json_variant_unrefp
) JsonVariant
*v
= NULL
;
354 _cleanup_(table_unrefp
) Table
*t
= NULL
;
355 uint64_t size
= UINT64_MAX
;
361 if (arg_json_format_flags
& (JSON_FORMAT_OFF
|JSON_FORMAT_PRETTY
|JSON_FORMAT_PRETTY_AUTO
))
362 (void) pager_open(arg_pager_flags
);
364 if (arg_json_format_flags
& JSON_FORMAT_OFF
)
365 printf(" Name: %s\n", basename(arg_image
));
367 if (ioctl(d
->fd
, BLKGETSIZE64
, &size
) < 0)
368 log_debug_errno(errno
, "Failed to query size of loopback device: %m");
369 else if (arg_json_format_flags
& JSON_FORMAT_OFF
) {
370 char s
[FORMAT_BYTES_MAX
];
371 printf(" Size: %s\n", format_bytes(s
, sizeof(s
), size
));
374 if (arg_json_format_flags
& JSON_FORMAT_OFF
)
377 r
= dissected_image_acquire_metadata(m
);
379 return log_error_errno(r
, "No root partition discovered.");
381 return log_error_errno(r
, "File system check of image failed.");
382 if (r
== -EMEDIUMTYPE
)
383 log_warning_errno(r
, "Not a valid OS image, no os-release file included. Proceeding anyway.");
384 else if (r
== -EUNATCH
)
385 log_warning_errno(r
, "OS image is encrypted, proceeding without showing OS image metadata.");
386 else if (r
== -EBUSY
)
387 log_warning_errno(r
, "OS image is currently in use, proceeding without showing OS image metadata.");
389 return log_error_errno(r
, "Failed to acquire image metadata: %m");
390 else if (arg_json_format_flags
& JSON_FORMAT_OFF
) {
392 printf(" Hostname: %s\n", m
->hostname
);
394 if (!sd_id128_is_null(m
->machine_id
))
395 printf("Machine ID: " SD_ID128_FORMAT_STR
"\n", SD_ID128_FORMAT_VAL(m
->machine_id
));
397 if (!strv_isempty(m
->machine_info
)) {
400 STRV_FOREACH_PAIR(p
, q
, m
->machine_info
)
402 p
== m
->machine_info
? "Mach. Info:" : " ",
406 if (!strv_isempty(m
->os_release
)) {
409 STRV_FOREACH_PAIR(p
, q
, m
->os_release
)
411 p
== m
->os_release
? "OS Release:" : " ",
415 if (!strv_isempty(m
->extension_release
)) {
418 STRV_FOREACH_PAIR(p
, q
, m
->extension_release
)
420 p
== m
->extension_release
? "Extension Release:" : " ",
425 !sd_id128_is_null(m
->machine_id
) ||
426 !strv_isempty(m
->machine_info
) ||
427 !strv_isempty(m
->extension_release
) ||
428 !strv_isempty(m
->os_release
))
431 _cleanup_(json_variant_unrefp
) JsonVariant
*mi
= NULL
, *osr
= NULL
, *exr
= NULL
;
433 if (!strv_isempty(m
->machine_info
)) {
434 r
= strv_pair_to_json(m
->machine_info
, &mi
);
439 if (!strv_isempty(m
->os_release
)) {
440 r
= strv_pair_to_json(m
->os_release
, &osr
);
445 if (!strv_isempty(m
->extension_release
)) {
446 r
= strv_pair_to_json(m
->extension_release
, &exr
);
451 r
= json_build(&v
, JSON_BUILD_OBJECT(
452 JSON_BUILD_PAIR("name", JSON_BUILD_STRING(basename(arg_image
))),
453 JSON_BUILD_PAIR("size", JSON_BUILD_INTEGER(size
)),
454 JSON_BUILD_PAIR_CONDITION(m
->hostname
, "hostname", JSON_BUILD_STRING(m
->hostname
)),
455 JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(m
->machine_id
), "machineId", JSON_BUILD_ID128(m
->machine_id
)),
456 JSON_BUILD_PAIR_CONDITION(mi
, "machineInfo", JSON_BUILD_VARIANT(mi
)),
457 JSON_BUILD_PAIR_CONDITION(osr
, "osRelease", JSON_BUILD_VARIANT(osr
)),
458 JSON_BUILD_PAIR_CONDITION(exr
, "extensionRelease", JSON_BUILD_VARIANT(exr
))));
463 t
= table_new("rw", "designator", "partition uuid", "fstype", "architecture", "verity", "node", "partno");
467 (void) table_set_empty_string(t
, "-");
468 (void) table_set_align_percent(t
, table_get_cell(t
, 0, 7), 100);
470 for (PartitionDesignator i
= 0; i
< _PARTITION_DESIGNATOR_MAX
; i
++) {
471 DissectedPartition
*p
= m
->partitions
+ i
;
478 TABLE_STRING
, p
->rw
? "rw" : "ro",
479 TABLE_STRING
, partition_designator_to_string(i
));
481 return table_log_add_error(r
);
483 if (sd_id128_is_null(p
->uuid
))
484 r
= table_add_cell(t
, NULL
, TABLE_EMPTY
, NULL
);
486 r
= table_add_cell(t
, NULL
, TABLE_UUID
, &p
->uuid
);
488 return table_log_add_error(r
);
492 TABLE_STRING
, p
->fstype
,
493 TABLE_STRING
, architecture_to_string(p
->architecture
));
495 return table_log_add_error(r
);
497 if (arg_verity_settings
.data_path
)
498 r
= table_add_cell(t
, NULL
, TABLE_STRING
, "external");
499 else if (dissected_image_can_do_verity(m
, i
))
500 r
= table_add_cell(t
, NULL
, TABLE_STRING
, yes_no(dissected_image_has_verity(m
, i
)));
502 r
= table_add_cell(t
, NULL
, TABLE_EMPTY
, NULL
);
504 return table_log_add_error(r
);
506 if (p
->partno
< 0) /* no partition table, naked file system */ {
507 r
= table_add_cell(t
, NULL
, TABLE_STRING
, arg_image
);
509 return table_log_add_error(r
);
511 r
= table_add_cell(t
, NULL
, TABLE_EMPTY
, NULL
);
513 r
= table_add_cell(t
, NULL
, TABLE_STRING
, p
->node
);
515 return table_log_add_error(r
);
517 r
= table_add_cell(t
, NULL
, TABLE_INT
, &p
->partno
);
520 return table_log_add_error(r
);
523 if (arg_json_format_flags
& JSON_FORMAT_OFF
) {
524 (void) table_set_header(t
, arg_legend
);
526 r
= table_print(t
, NULL
);
528 return table_log_print_error(r
);
530 _cleanup_(json_variant_unrefp
) JsonVariant
*jt
= NULL
;
532 r
= table_to_json(t
, &jt
);
534 return log_error_errno(r
, "Failed to convert table to JSON: %m");
536 r
= json_variant_set_field(&v
, "mounts", jt
);
540 json_variant_dump(v
, arg_json_format_flags
, stdout
, NULL
);
546 static int action_mount(DissectedImage
*m
, LoopDevice
*d
) {
547 _cleanup_(decrypted_image_unrefp
) DecryptedImage
*di
= NULL
;
553 r
= dissected_image_decrypt_interactively(
555 &arg_verity_settings
,
561 r
= dissected_image_mount_and_warn(m
, arg_path
, UID_INVALID
, arg_flags
);
566 r
= decrypted_image_relinquish(di
);
568 return log_error_errno(r
, "Failed to relinquish DM devices: %m");
571 loop_device_relinquish(d
);
575 static int action_copy(DissectedImage
*m
, LoopDevice
*d
) {
576 _cleanup_(umount_and_rmdir_and_freep
) char *mounted_dir
= NULL
;
577 _cleanup_(decrypted_image_unrefp
) DecryptedImage
*di
= NULL
;
578 _cleanup_(rmdir_and_freep
) char *created_dir
= NULL
;
579 _cleanup_free_
char *temp
= NULL
;
585 r
= dissected_image_decrypt_interactively(
587 &arg_verity_settings
,
593 r
= detach_mount_namespace();
595 return log_error_errno(r
, "Failed to detach mount namespace: %m");
597 r
= tempfn_random_child(NULL
, program_invocation_short_name
, &temp
);
599 return log_error_errno(r
, "Failed to generate temporary mount directory: %m");
601 r
= mkdir_p(temp
, 0700);
603 return log_error_errno(r
, "Failed to create mount point: %m");
605 created_dir
= TAKE_PTR(temp
);
607 r
= dissected_image_mount_and_warn(m
, created_dir
, UID_INVALID
, arg_flags
);
611 mounted_dir
= TAKE_PTR(created_dir
);
614 r
= decrypted_image_relinquish(di
);
616 return log_error_errno(r
, "Failed to relinquish DM devices: %m");
619 loop_device_relinquish(d
);
621 if (arg_action
== ACTION_COPY_FROM
) {
622 _cleanup_close_
int source_fd
= -1, target_fd
= -1;
624 source_fd
= chase_symlinks_and_open(arg_source
, mounted_dir
, CHASE_PREFIX_ROOT
|CHASE_WARN
, O_RDONLY
|O_CLOEXEC
|O_NOCTTY
, NULL
);
626 return log_error_errno(source_fd
, "Failed to open source path '%s' in image '%s': %m", arg_source
, arg_image
);
628 /* Copying to stdout? */
629 if (streq(arg_target
, "-")) {
630 r
= copy_bytes(source_fd
, STDOUT_FILENO
, UINT64_MAX
, COPY_REFLINK
);
632 return log_error_errno(r
, "Failed to copy bytes from %s in mage '%s' to stdout: %m", arg_source
, arg_image
);
634 /* When we copy to stdout we don't copy any attributes (i.e. no access mode, no ownership, no xattr, no times) */
638 /* Try to copy as directory? */
639 r
= copy_directory_fd(source_fd
, arg_target
, COPY_REFLINK
|COPY_MERGE_EMPTY
|COPY_SIGINT
|COPY_HARDLINKS
);
643 return log_error_errno(r
, "Failed to copy %s in image '%s' to '%s': %m", arg_source
, arg_image
, arg_target
);
645 r
= fd_verify_regular(source_fd
);
647 return log_error_errno(r
, "Target '%s' exists already and is not a directory.", arg_target
);
649 return log_error_errno(r
, "Source path %s in image '%s' is neither regular file nor directory, refusing: %m", arg_source
, arg_image
);
651 /* Nah, it's a plain file! */
652 target_fd
= open(arg_target
, O_WRONLY
|O_CREAT
|O_EXCL
|O_CLOEXEC
|O_NOCTTY
|O_NOFOLLOW
, 0600);
654 return log_error_errno(errno
, "Failed to create regular file at target path '%s': %m", arg_target
);
656 r
= copy_bytes(source_fd
, target_fd
, UINT64_MAX
, COPY_REFLINK
);
658 return log_error_errno(r
, "Failed to copy bytes from %s in mage '%s' to '%s': %m", arg_source
, arg_image
, arg_target
);
660 (void) copy_xattr(source_fd
, target_fd
);
661 (void) copy_access(source_fd
, target_fd
);
662 (void) copy_times(source_fd
, target_fd
, 0);
664 /* When this is a regular file we don't copy ownership! */
667 _cleanup_close_
int source_fd
= -1, target_fd
= -1;
668 _cleanup_close_
int dfd
= -1;
669 _cleanup_free_
char *dn
= NULL
;
671 assert(arg_action
== ACTION_COPY_TO
);
673 dn
= dirname_malloc(arg_target
);
677 r
= chase_symlinks(dn
, mounted_dir
, CHASE_PREFIX_ROOT
|CHASE_WARN
, NULL
, &dfd
);
679 return log_error_errno(r
, "Failed to open '%s': %m", dn
);
681 /* Are we reading from stdin? */
682 if (streq(arg_source
, "-")) {
683 target_fd
= openat(dfd
, basename(arg_target
), O_WRONLY
|O_CREAT
|O_CLOEXEC
|O_NOCTTY
|O_EXCL
, 0644);
685 return log_error_errno(errno
, "Failed to open target file '%s': %m", arg_target
);
687 r
= copy_bytes(STDIN_FILENO
, target_fd
, UINT64_MAX
, COPY_REFLINK
);
689 return log_error_errno(r
, "Failed to copy bytes from stdin to '%s' in image '%s': %m", arg_target
, arg_image
);
691 /* When we copy from stdin we don't copy any attributes (i.e. no access mode, no ownership, no xattr, no times) */
695 source_fd
= open(arg_source
, O_RDONLY
|O_CLOEXEC
|O_NOCTTY
);
697 return log_error_errno(source_fd
, "Failed to open source path '%s': %m", arg_source
);
699 r
= fd_verify_regular(source_fd
);
702 return log_error_errno(r
, "Source '%s' is neither regular file nor directory: %m", arg_source
);
704 /* We are looking at a directory. */
706 target_fd
= openat(dfd
, basename(arg_target
), O_RDONLY
|O_DIRECTORY
|O_CLOEXEC
);
709 return log_error_errno(errno
, "Failed to open destination '%s': %m", arg_target
);
711 r
= copy_tree_at(source_fd
, ".", dfd
, basename(arg_target
), UID_INVALID
, GID_INVALID
, COPY_REFLINK
|COPY_REPLACE
|COPY_SIGINT
|COPY_HARDLINKS
);
713 r
= copy_tree_at(source_fd
, ".", target_fd
, ".", UID_INVALID
, GID_INVALID
, COPY_REFLINK
|COPY_REPLACE
|COPY_SIGINT
|COPY_HARDLINKS
);
715 return log_error_errno(r
, "Failed to copy '%s' to '%s' in image '%s': %m", arg_source
, arg_target
, arg_image
);
720 /* We area looking at a regular file */
721 target_fd
= openat(dfd
, basename(arg_target
), O_WRONLY
|O_CREAT
|O_CLOEXEC
|O_NOCTTY
|O_EXCL
, 0600);
723 return log_error_errno(errno
, "Failed to open target file '%s': %m", arg_target
);
725 r
= copy_bytes(source_fd
, target_fd
, UINT64_MAX
, COPY_REFLINK
);
727 return log_error_errno(r
, "Failed to copy bytes from '%s' to '%s' in image '%s': %m", arg_source
, arg_target
, arg_image
);
729 (void) copy_xattr(source_fd
, target_fd
);
730 (void) copy_access(source_fd
, target_fd
);
731 (void) copy_times(source_fd
, target_fd
, 0);
733 /* When this is a regular file we don't copy ownership! */
739 static int run(int argc
, char *argv
[]) {
740 _cleanup_(dissected_image_unrefp
) DissectedImage
*m
= NULL
;
741 _cleanup_(loop_device_unrefp
) LoopDevice
*d
= NULL
;
744 log_parse_environment();
747 r
= parse_argv(argc
, argv
);
751 r
= verity_settings_load(
752 &arg_verity_settings
,
753 arg_image
, NULL
, NULL
);
755 return log_error_errno(r
, "Failed to read verity artifacts for %s: %m", arg_image
);
757 if (arg_verity_settings
.data_path
)
758 arg_flags
|= DISSECT_IMAGE_NO_PARTITION_TABLE
; /* We only support Verity per file system,
759 * hence if there's external Verity data
760 * available we turn off partition table
763 r
= loop_device_make_by_path(
765 FLAGS_SET(arg_flags
, DISSECT_IMAGE_READ_ONLY
) ? O_RDONLY
: O_RDWR
,
766 FLAGS_SET(arg_flags
, DISSECT_IMAGE_NO_PARTITION_TABLE
) ? 0 : LO_FLAGS_PARTSCAN
,
769 return log_error_errno(r
, "Failed to set up loopback device: %m");
771 r
= dissect_image_and_warn(
774 &arg_verity_settings
,
781 switch (arg_action
) {
784 r
= action_dissect(m
, d
);
788 r
= action_mount(m
, d
);
791 case ACTION_COPY_FROM
:
793 r
= action_copy(m
, d
);
797 assert_not_reached("Unknown action.");
803 DEFINE_MAIN_FUNCTION(run
);