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:" : " ",
416 !sd_id128_is_null(m
->machine_id
) ||
417 !strv_isempty(m
->machine_info
) ||
418 !strv_isempty(m
->os_release
))
421 _cleanup_(json_variant_unrefp
) JsonVariant
*mi
= NULL
, *osr
= NULL
;
423 if (!strv_isempty(m
->machine_info
)) {
424 r
= strv_pair_to_json(m
->machine_info
, &mi
);
429 if (!strv_isempty(m
->os_release
)) {
430 r
= strv_pair_to_json(m
->os_release
, &osr
);
435 r
= json_build(&v
, JSON_BUILD_OBJECT(
436 JSON_BUILD_PAIR("name", JSON_BUILD_STRING(basename(arg_image
))),
437 JSON_BUILD_PAIR("size", JSON_BUILD_INTEGER(size
)),
438 JSON_BUILD_PAIR_CONDITION(m
->hostname
, "hostname", JSON_BUILD_STRING(m
->hostname
)),
439 JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(m
->machine_id
), "machineId", JSON_BUILD_ID128(m
->machine_id
)),
440 JSON_BUILD_PAIR_CONDITION(mi
, "machineInfo", JSON_BUILD_VARIANT(mi
)),
441 JSON_BUILD_PAIR_CONDITION(osr
, "osRelease", JSON_BUILD_VARIANT(osr
))));
446 t
= table_new("rw", "designator", "partition uuid", "fstype", "architecture", "verity", "node", "partno");
450 (void) table_set_empty_string(t
, "-");
451 (void) table_set_align_percent(t
, table_get_cell(t
, 0, 7), 100);
453 for (PartitionDesignator i
= 0; i
< _PARTITION_DESIGNATOR_MAX
; i
++) {
454 DissectedPartition
*p
= m
->partitions
+ i
;
461 TABLE_STRING
, p
->rw
? "rw" : "ro",
462 TABLE_STRING
, partition_designator_to_string(i
));
464 return table_log_add_error(r
);
466 if (sd_id128_is_null(p
->uuid
))
467 r
= table_add_cell(t
, NULL
, TABLE_EMPTY
, NULL
);
469 r
= table_add_cell(t
, NULL
, TABLE_UUID
, &p
->uuid
);
471 return table_log_add_error(r
);
475 TABLE_STRING
, p
->fstype
,
476 TABLE_STRING
, architecture_to_string(p
->architecture
));
478 return table_log_add_error(r
);
480 if (arg_verity_settings
.data_path
)
481 r
= table_add_cell(t
, NULL
, TABLE_STRING
, "external");
482 else if (dissected_image_can_do_verity(m
, i
))
483 r
= table_add_cell(t
, NULL
, TABLE_STRING
, yes_no(dissected_image_has_verity(m
, i
)));
485 r
= table_add_cell(t
, NULL
, TABLE_EMPTY
, NULL
);
487 return table_log_add_error(r
);
489 if (p
->partno
< 0) /* no partition table, naked file system */ {
490 r
= table_add_cell(t
, NULL
, TABLE_STRING
, arg_image
);
492 return table_log_add_error(r
);
494 r
= table_add_cell(t
, NULL
, TABLE_EMPTY
, NULL
);
496 r
= table_add_cell(t
, NULL
, TABLE_STRING
, p
->node
);
498 return table_log_add_error(r
);
500 r
= table_add_cell(t
, NULL
, TABLE_INT
, &p
->partno
);
503 return table_log_add_error(r
);
506 if (arg_json_format_flags
& JSON_FORMAT_OFF
) {
507 (void) table_set_header(t
, arg_legend
);
509 r
= table_print(t
, NULL
);
511 return table_log_print_error(r
);
513 _cleanup_(json_variant_unrefp
) JsonVariant
*jt
= NULL
;
515 r
= table_to_json(t
, &jt
);
517 return log_error_errno(r
, "Failed to convert table to JSON: %m");
519 r
= json_variant_set_field(&v
, "mounts", jt
);
523 json_variant_dump(v
, arg_json_format_flags
, stdout
, NULL
);
529 static int action_mount(DissectedImage
*m
, LoopDevice
*d
) {
530 _cleanup_(decrypted_image_unrefp
) DecryptedImage
*di
= NULL
;
536 r
= dissected_image_decrypt_interactively(
538 &arg_verity_settings
,
544 r
= dissected_image_mount_and_warn(m
, arg_path
, UID_INVALID
, arg_flags
);
549 r
= decrypted_image_relinquish(di
);
551 return log_error_errno(r
, "Failed to relinquish DM devices: %m");
554 loop_device_relinquish(d
);
558 static int action_copy(DissectedImage
*m
, LoopDevice
*d
) {
559 _cleanup_(umount_and_rmdir_and_freep
) char *mounted_dir
= NULL
;
560 _cleanup_(decrypted_image_unrefp
) DecryptedImage
*di
= NULL
;
561 _cleanup_(rmdir_and_freep
) char *created_dir
= NULL
;
562 _cleanup_free_
char *temp
= NULL
;
568 r
= dissected_image_decrypt_interactively(
570 &arg_verity_settings
,
576 r
= detach_mount_namespace();
578 return log_error_errno(r
, "Failed to detach mount namespace: %m");
580 r
= tempfn_random_child(NULL
, program_invocation_short_name
, &temp
);
582 return log_error_errno(r
, "Failed to generate temporary mount directory: %m");
584 r
= mkdir_p(temp
, 0700);
586 return log_error_errno(r
, "Failed to create mount point: %m");
588 created_dir
= TAKE_PTR(temp
);
590 r
= dissected_image_mount_and_warn(m
, created_dir
, UID_INVALID
, arg_flags
);
594 mounted_dir
= TAKE_PTR(created_dir
);
597 r
= decrypted_image_relinquish(di
);
599 return log_error_errno(r
, "Failed to relinquish DM devices: %m");
602 loop_device_relinquish(d
);
604 if (arg_action
== ACTION_COPY_FROM
) {
605 _cleanup_close_
int source_fd
= -1, target_fd
= -1;
607 source_fd
= chase_symlinks_and_open(arg_source
, mounted_dir
, CHASE_PREFIX_ROOT
|CHASE_WARN
, O_RDONLY
|O_CLOEXEC
|O_NOCTTY
, NULL
);
609 return log_error_errno(source_fd
, "Failed to open source path '%s' in image '%s': %m", arg_source
, arg_image
);
611 /* Copying to stdout? */
612 if (streq(arg_target
, "-")) {
613 r
= copy_bytes(source_fd
, STDOUT_FILENO
, (uint64_t) -1, COPY_REFLINK
);
615 return log_error_errno(r
, "Failed to copy bytes from %s in mage '%s' to stdout: %m", arg_source
, arg_image
);
617 /* When we copy to stdou we don't copy any attributes (i.e. no access mode, no ownership, no xattr, no times) */
621 /* Try to copy as directory? */
622 r
= copy_directory_fd(source_fd
, arg_target
, COPY_REFLINK
|COPY_MERGE_EMPTY
|COPY_SIGINT
|COPY_HARDLINKS
);
626 return log_error_errno(r
, "Failed to copy %s in image '%s' to '%s': %m", arg_source
, arg_image
, arg_target
);
628 r
= fd_verify_regular(source_fd
);
630 return log_error_errno(r
, "Target '%s' exists already and is not a directory.", arg_target
);
632 return log_error_errno(r
, "Source path %s in image '%s' is neither regular file nor directory, refusing: %m", arg_source
, arg_image
);
634 /* Nah, it's a plain file! */
635 target_fd
= open(arg_target
, O_WRONLY
|O_CREAT
|O_EXCL
|O_CLOEXEC
|O_NOCTTY
|O_NOFOLLOW
, 0600);
637 return log_error_errno(errno
, "Failed to create regular file at target path '%s': %m", arg_target
);
639 r
= copy_bytes(source_fd
, target_fd
, (uint64_t) -1, COPY_REFLINK
);
641 return log_error_errno(r
, "Failed to copy bytes from %s in mage '%s' to '%s': %m", arg_source
, arg_image
, arg_target
);
643 (void) copy_xattr(source_fd
, target_fd
);
644 (void) copy_access(source_fd
, target_fd
);
645 (void) copy_times(source_fd
, target_fd
, 0);
647 /* When this is a regular file we don't copy ownership! */
650 _cleanup_close_
int source_fd
= -1, target_fd
= -1;
651 _cleanup_close_
int dfd
= -1;
652 _cleanup_free_
char *dn
= NULL
;
654 assert(arg_action
== ACTION_COPY_TO
);
656 dn
= dirname_malloc(arg_target
);
660 r
= chase_symlinks(dn
, mounted_dir
, CHASE_PREFIX_ROOT
|CHASE_WARN
, NULL
, &dfd
);
662 return log_error_errno(r
, "Failed to open '%s': %m", dn
);
664 /* Are we reading from stdin? */
665 if (streq(arg_source
, "-")) {
666 target_fd
= openat(dfd
, basename(arg_target
), O_WRONLY
|O_CREAT
|O_CLOEXEC
|O_NOCTTY
|O_EXCL
, 0644);
668 return log_error_errno(errno
, "Failed to open target file '%s': %m", arg_target
);
670 r
= copy_bytes(STDIN_FILENO
, target_fd
, (uint64_t) -1, COPY_REFLINK
);
672 return log_error_errno(r
, "Failed to copy bytes from stdin to '%s' in image '%s': %m", arg_target
, arg_image
);
674 /* When we copy from stdin we don't copy any attributes (i.e. no access mode, no ownership, no xattr, no times) */
678 source_fd
= open(arg_source
, O_RDONLY
|O_CLOEXEC
|O_NOCTTY
);
680 return log_error_errno(source_fd
, "Failed to open source path '%s': %m", arg_source
);
682 r
= fd_verify_regular(source_fd
);
685 return log_error_errno(r
, "Source '%s' is neither regular file nor directory: %m", arg_source
);
687 /* We are looking at a directory. */
689 target_fd
= openat(dfd
, basename(arg_target
), O_RDONLY
|O_DIRECTORY
|O_CLOEXEC
);
692 return log_error_errno(errno
, "Failed to open destination '%s': %m", arg_target
);
694 r
= copy_tree_at(source_fd
, ".", dfd
, basename(arg_target
), UID_INVALID
, GID_INVALID
, COPY_REFLINK
|COPY_REPLACE
|COPY_SIGINT
|COPY_HARDLINKS
);
696 r
= copy_tree_at(source_fd
, ".", target_fd
, ".", UID_INVALID
, GID_INVALID
, COPY_REFLINK
|COPY_REPLACE
|COPY_SIGINT
|COPY_HARDLINKS
);
698 return log_error_errno(r
, "Failed to copy '%s' to '%s' in image '%s': %m", arg_source
, arg_target
, arg_image
);
703 /* We area looking at a regular file */
704 target_fd
= openat(dfd
, basename(arg_target
), O_WRONLY
|O_CREAT
|O_CLOEXEC
|O_NOCTTY
|O_EXCL
, 0600);
706 return log_error_errno(errno
, "Failed to open target file '%s': %m", arg_target
);
708 r
= copy_bytes(source_fd
, target_fd
, (uint64_t) -1, COPY_REFLINK
);
710 return log_error_errno(r
, "Failed to copy bytes from '%s' to '%s' in image '%s': %m", arg_source
, arg_target
, arg_image
);
712 (void) copy_xattr(source_fd
, target_fd
);
713 (void) copy_access(source_fd
, target_fd
);
714 (void) copy_times(source_fd
, target_fd
, 0);
716 /* When this is a regular file we don't copy ownership! */
722 static int run(int argc
, char *argv
[]) {
723 _cleanup_(dissected_image_unrefp
) DissectedImage
*m
= NULL
;
724 _cleanup_(loop_device_unrefp
) LoopDevice
*d
= NULL
;
727 log_parse_environment();
730 r
= parse_argv(argc
, argv
);
734 r
= verity_settings_load(
735 &arg_verity_settings
,
736 arg_image
, NULL
, NULL
);
738 return log_error_errno(r
, "Failed to read verity artifacts for %s: %m", arg_image
);
740 if (arg_verity_settings
.data_path
)
741 arg_flags
|= DISSECT_IMAGE_NO_PARTITION_TABLE
; /* We only support Verity per file system,
742 * hence if there's external Verity data
743 * available we turn off partition table
746 r
= loop_device_make_by_path(
748 FLAGS_SET(arg_flags
, DISSECT_IMAGE_READ_ONLY
) ? O_RDONLY
: O_RDWR
,
749 FLAGS_SET(arg_flags
, DISSECT_IMAGE_NO_PARTITION_TABLE
) ? 0 : LO_FLAGS_PARTSCAN
,
752 return log_error_errno(r
, "Failed to set up loopback device: %m");
754 r
= dissect_image_and_warn(
757 &arg_verity_settings
,
764 switch (arg_action
) {
767 r
= action_dissect(m
, d
);
771 r
= action_mount(m
, d
);
774 case ACTION_COPY_FROM
:
776 r
= action_copy(m
, d
);
780 assert_not_reached("Unknown action.");
786 DEFINE_MAIN_FUNCTION(run
);