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-util.h"
26 #include "path-util.h"
27 #include "pretty-print.h"
28 #include "stat-util.h"
29 #include "string-util.h"
31 #include "terminal-util.h"
32 #include "tmpfile-util.h"
33 #include "user-util.h"
41 } arg_action
= ACTION_DISSECT
;
42 static const char *arg_image
= NULL
;
43 static const char *arg_path
= NULL
;
44 static const char *arg_source
= NULL
;
45 static const char *arg_target
= NULL
;
46 static DissectImageFlags arg_flags
= DISSECT_IMAGE_REQUIRE_ROOT
|DISSECT_IMAGE_DISCARD_ON_LOOP
|DISSECT_IMAGE_RELAX_VAR_CHECK
|DISSECT_IMAGE_FSCK
;
47 static VeritySettings arg_verity_settings
= VERITY_SETTINGS_DEFAULT
;
48 static JsonFormatFlags arg_json_format_flags
= JSON_FORMAT_OFF
;
49 static PagerFlags arg_pager_flags
= 0;
50 static bool arg_legend
= true;
52 STATIC_DESTRUCTOR_REGISTER(arg_verity_settings
, verity_settings_done
);
54 static int help(void) {
55 _cleanup_free_
char *link
= NULL
;
58 r
= terminal_urlify_man("systemd-dissect", "1", &link
);
62 printf("%1$s [OPTIONS...] IMAGE\n"
63 "%1$s [OPTIONS...] --mount IMAGE PATH\n"
64 "%1$s [OPTIONS...] --copy-from IMAGE PATH [TARGET]\n"
65 "%1$s [OPTIONS...] --copy-to IMAGE [SOURCE] PATH\n\n"
66 "%5$sDissect a file system OS image.%6$s\n\n"
68 " --no-pager Do not pipe output into a pager\n"
69 " --no-legend Do not show the headers and footers\n"
70 " -r --read-only Mount read-only\n"
71 " --fsck=BOOL Run fsck before mounting\n"
72 " --mkdir Make mount directory before mounting, if missing\n"
73 " --discard=MODE Choose 'discard' mode (disabled, loop, all, crypto)\n"
74 " --root-hash=HASH Specify root hash for verity\n"
75 " --root-hash-sig=SIG Specify pkcs7 signature of root hash for verity\n"
76 " as a DER encoded PKCS7, either as a path to a file\n"
77 " or as an ASCII base64 encoded string prefixed by\n"
79 " --verity-data=PATH Specify data file with hash tree for verity if it is\n"
80 " not embedded in IMAGE\n"
81 " --json=pretty|short|off\n"
82 " Generate JSON output\n"
83 "\n%3$sCommands:%4$s\n"
84 " -h --help Show this help\n"
85 " --version Show package version\n"
86 " -m --mount Mount the image to the specified directory\n"
87 " -M Shortcut for --mount --mkdir\n"
88 " -x --copy-from Copy files from image to host\n"
89 " -a --copy-to Copy files from host to image\n"
90 "\nSee the %2$s for details.\n"
91 , program_invocation_short_name
93 , ansi_underline(), ansi_normal()
94 , ansi_highlight(), ansi_normal());
99 static int parse_argv(int argc
, char *argv
[]) {
114 static const struct option options
[] = {
115 { "help", no_argument
, NULL
, 'h' },
116 { "version", no_argument
, NULL
, ARG_VERSION
},
117 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
118 { "no-legend", no_argument
, NULL
, ARG_NO_LEGEND
},
119 { "mount", no_argument
, NULL
, 'm' },
120 { "read-only", no_argument
, NULL
, 'r' },
121 { "discard", required_argument
, NULL
, ARG_DISCARD
},
122 { "fsck", required_argument
, NULL
, ARG_FSCK
},
123 { "root-hash", required_argument
, NULL
, ARG_ROOT_HASH
},
124 { "root-hash-sig", required_argument
, NULL
, ARG_ROOT_HASH_SIG
},
125 { "verity-data", required_argument
, NULL
, ARG_VERITY_DATA
},
126 { "mkdir", no_argument
, NULL
, ARG_MKDIR
},
127 { "copy-from", no_argument
, NULL
, 'x' },
128 { "copy-to", no_argument
, NULL
, 'a' },
129 { "json", required_argument
, NULL
, ARG_JSON
},
138 while ((c
= getopt_long(argc
, argv
, "hmrMxa", options
, NULL
)) >= 0) {
149 arg_pager_flags
|= PAGER_DISABLE
;
157 arg_action
= ACTION_MOUNT
;
161 arg_flags
|= DISSECT_IMAGE_MKDIR
;
165 /* Shortcut combination of the above two */
166 arg_action
= ACTION_MOUNT
;
167 arg_flags
|= DISSECT_IMAGE_MKDIR
;
171 arg_action
= ACTION_COPY_FROM
;
172 arg_flags
|= DISSECT_IMAGE_READ_ONLY
;
176 arg_action
= ACTION_COPY_TO
;
180 arg_flags
|= DISSECT_IMAGE_READ_ONLY
;
184 DissectImageFlags flags
;
186 if (streq(optarg
, "disabled"))
188 else if (streq(optarg
, "loop"))
189 flags
= DISSECT_IMAGE_DISCARD_ON_LOOP
;
190 else if (streq(optarg
, "all"))
191 flags
= DISSECT_IMAGE_DISCARD_ON_LOOP
| DISSECT_IMAGE_DISCARD
;
192 else if (streq(optarg
, "crypt"))
193 flags
= DISSECT_IMAGE_DISCARD_ANY
;
194 else if (streq(optarg
, "list")) {
201 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
202 "Unknown --discard= parameter: %s",
204 arg_flags
= (arg_flags
& ~DISSECT_IMAGE_DISCARD_ANY
) | flags
;
209 case ARG_ROOT_HASH
: {
210 _cleanup_free_
void *p
= NULL
;
213 r
= unhexmem(optarg
, strlen(optarg
), &p
, &l
);
215 return log_error_errno(r
, "Failed to parse root hash '%s': %m", optarg
);
216 if (l
< sizeof(sd_id128_t
))
217 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
218 "Root hash must be at least 128bit long: %s", optarg
);
220 free_and_replace(arg_verity_settings
.root_hash
, p
);
221 arg_verity_settings
.root_hash_size
= l
;
225 case ARG_ROOT_HASH_SIG
: {
230 if ((value
= startswith(optarg
, "base64:"))) {
231 r
= unbase64mem(value
, strlen(value
), &p
, &l
);
233 return log_error_errno(r
, "Failed to parse root hash signature '%s': %m", optarg
);
235 r
= read_full_file(optarg
, (char**) &p
, &l
);
237 return log_error_errno(r
, "Failed to read root hash signature file '%s': %m", optarg
);
240 free_and_replace(arg_verity_settings
.root_hash_sig
, p
);
241 arg_verity_settings
.root_hash_sig_size
= l
;
245 case ARG_VERITY_DATA
:
246 r
= parse_path_argument_and_warn(optarg
, false, &arg_verity_settings
.data_path
);
252 r
= parse_boolean(optarg
);
254 return log_error_errno(r
, "Failed to parse --fsck= parameter: %s", optarg
);
256 SET_FLAG(arg_flags
, DISSECT_IMAGE_FSCK
, r
);
260 r
= json_parse_cmdline_parameter_and_warn(optarg
, &arg_json_format_flags
);
270 assert_not_reached("Unhandled option");
275 switch (arg_action
) {
278 if (optind
+ 1 != argc
)
279 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
280 "Expected an image file path as only argument.");
282 arg_image
= argv
[optind
];
283 arg_flags
|= DISSECT_IMAGE_READ_ONLY
;
287 if (optind
+ 2 != argc
)
288 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
289 "Expected an image file path and mount point path as only arguments.");
291 arg_image
= argv
[optind
];
292 arg_path
= argv
[optind
+ 1];
295 case ACTION_COPY_FROM
:
296 if (argc
< optind
+ 2 || argc
> optind
+ 3)
297 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
298 "Expected an image file path, a source path and an optional destination path as only arguments.");
300 arg_image
= argv
[optind
];
301 arg_source
= argv
[optind
+ 1];
302 arg_target
= argc
> optind
+ 2 ? argv
[optind
+ 2] : "-" /* this means stdout */ ;
304 arg_flags
|= DISSECT_IMAGE_READ_ONLY
;
308 if (argc
< optind
+ 2 || argc
> optind
+ 3)
309 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
310 "Expected an image file path, an optional source path and a destination path as only arguments.");
312 arg_image
= argv
[optind
];
314 if (argc
> optind
+ 2) {
315 arg_source
= argv
[optind
+ 1];
316 arg_target
= argv
[optind
+ 2];
318 arg_source
= "-"; /* this means stdin */
319 arg_target
= argv
[optind
+ 1];
325 assert_not_reached("Unknown action.");
331 static int strv_pair_to_json(char **l
, JsonVariant
**ret
) {
332 _cleanup_strv_free_
char **jl
= NULL
;
335 STRV_FOREACH_PAIR(a
, b
, l
) {
338 j
= strjoin(*a
, "=", *b
);
342 if (strv_consume(&jl
, j
) < 0)
346 return json_variant_new_array_strv(ret
, jl
);
349 static int action_dissect(DissectedImage
*m
, LoopDevice
*d
) {
350 _cleanup_(json_variant_unrefp
) JsonVariant
*v
= NULL
;
351 _cleanup_(table_unrefp
) Table
*t
= NULL
;
352 uint64_t size
= UINT64_MAX
;
358 if (arg_json_format_flags
& (JSON_FORMAT_OFF
|JSON_FORMAT_PRETTY
|JSON_FORMAT_PRETTY_AUTO
))
359 (void) pager_open(arg_pager_flags
);
361 if (arg_json_format_flags
& JSON_FORMAT_OFF
)
362 printf(" Name: %s\n", basename(arg_image
));
364 if (ioctl(d
->fd
, BLKGETSIZE64
, &size
) < 0)
365 log_debug_errno(errno
, "Failed to query size of loopback device: %m");
366 else if (arg_json_format_flags
& JSON_FORMAT_OFF
) {
367 char s
[FORMAT_BYTES_MAX
];
368 printf(" Size: %s\n", format_bytes(s
, sizeof(s
), size
));
371 if (arg_json_format_flags
& JSON_FORMAT_OFF
)
374 r
= dissected_image_acquire_metadata(m
);
376 return log_error_errno(r
, "No root partition discovered.");
378 return log_error_errno(r
, "File system check of image failed.");
379 if (r
== -EMEDIUMTYPE
)
380 log_warning_errno(r
, "Not a valid OS image, no os-release file included. Proceeding anyway.");
381 else if (r
== -EUNATCH
)
382 log_warning_errno(r
, "OS image is encrypted, proceeding without showing OS image metadata.");
383 else if (r
== -EBUSY
)
384 log_warning_errno(r
, "OS image is currently in use, proceeding without showing OS image metadata.");
386 return log_error_errno(r
, "Failed to acquire image metadata: %m");
387 else if (arg_json_format_flags
& JSON_FORMAT_OFF
) {
389 printf(" Hostname: %s\n", m
->hostname
);
391 if (!sd_id128_is_null(m
->machine_id
))
392 printf("Machine ID: " SD_ID128_FORMAT_STR
"\n", SD_ID128_FORMAT_VAL(m
->machine_id
));
394 if (!strv_isempty(m
->machine_info
)) {
397 STRV_FOREACH_PAIR(p
, q
, m
->machine_info
)
399 p
== m
->machine_info
? "Mach. Info:" : " ",
403 if (!strv_isempty(m
->os_release
)) {
406 STRV_FOREACH_PAIR(p
, q
, m
->os_release
)
408 p
== m
->os_release
? "OS Release:" : " ",
413 !sd_id128_is_null(m
->machine_id
) ||
414 !strv_isempty(m
->machine_info
) ||
415 !strv_isempty(m
->os_release
))
418 _cleanup_(json_variant_unrefp
) JsonVariant
*mi
= NULL
, *osr
= NULL
;
420 if (!strv_isempty(m
->machine_info
)) {
421 r
= strv_pair_to_json(m
->machine_info
, &mi
);
426 if (!strv_isempty(m
->os_release
)) {
427 r
= strv_pair_to_json(m
->os_release
, &osr
);
432 r
= json_build(&v
, JSON_BUILD_OBJECT(
433 JSON_BUILD_PAIR("name", JSON_BUILD_STRING(basename(arg_image
))),
434 JSON_BUILD_PAIR("size", JSON_BUILD_INTEGER(size
)),
435 JSON_BUILD_PAIR_CONDITION(m
->hostname
, "hostname", JSON_BUILD_STRING(m
->hostname
)),
436 JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(m
->machine_id
), "machineId", JSON_BUILD_ID128(m
->machine_id
)),
437 JSON_BUILD_PAIR_CONDITION(mi
, "machineInfo", JSON_BUILD_VARIANT(mi
)),
438 JSON_BUILD_PAIR_CONDITION(osr
, "osRelease", JSON_BUILD_VARIANT(osr
))));
443 t
= table_new("rw", "designator", "partition uuid", "fstype", "architecture", "verity", "node", "partno");
447 (void) table_set_empty_string(t
, "-");
448 (void) table_set_align_percent(t
, table_get_cell(t
, 0, 7), 100);
450 for (PartitionDesignator i
= 0; i
< _PARTITION_DESIGNATOR_MAX
; i
++) {
451 DissectedPartition
*p
= m
->partitions
+ i
;
458 TABLE_STRING
, p
->rw
? "rw" : "ro",
459 TABLE_STRING
, partition_designator_to_string(i
));
461 return table_log_add_error(r
);
463 if (sd_id128_is_null(p
->uuid
))
464 r
= table_add_cell(t
, NULL
, TABLE_EMPTY
, NULL
);
466 r
= table_add_cell(t
, NULL
, TABLE_UUID
, &p
->uuid
);
468 return table_log_add_error(r
);
472 TABLE_STRING
, p
->fstype
,
473 TABLE_STRING
, architecture_to_string(p
->architecture
));
475 return table_log_add_error(r
);
477 if (arg_verity_settings
.data_path
)
478 r
= table_add_cell(t
, NULL
, TABLE_STRING
, "external");
479 else if (dissected_image_can_do_verity(m
, i
))
480 r
= table_add_cell(t
, NULL
, TABLE_STRING
, yes_no(dissected_image_has_verity(m
, i
)));
482 r
= table_add_cell(t
, NULL
, TABLE_EMPTY
, NULL
);
484 return table_log_add_error(r
);
486 if (p
->partno
< 0) /* no partition table, naked file system */ {
487 r
= table_add_cell(t
, NULL
, TABLE_STRING
, arg_image
);
489 return table_log_add_error(r
);
491 r
= table_add_cell(t
, NULL
, TABLE_EMPTY
, NULL
);
493 r
= table_add_cell(t
, NULL
, TABLE_STRING
, p
->node
);
495 return table_log_add_error(r
);
497 r
= table_add_cell(t
, NULL
, TABLE_INT
, &p
->partno
);
500 return table_log_add_error(r
);
503 if (arg_json_format_flags
& JSON_FORMAT_OFF
) {
504 (void) table_set_header(t
, arg_legend
);
506 r
= table_print(t
, NULL
);
508 return table_log_print_error(r
);
510 _cleanup_(json_variant_unrefp
) JsonVariant
*jt
= NULL
;
512 r
= table_to_json(t
, &jt
);
514 return log_error_errno(r
, "Failed to convert table to JSON: %m");
516 r
= json_variant_set_field(&v
, "mounts", jt
);
520 json_variant_dump(v
, arg_json_format_flags
, stdout
, NULL
);
526 static int action_mount(DissectedImage
*m
, LoopDevice
*d
) {
527 _cleanup_(decrypted_image_unrefp
) DecryptedImage
*di
= NULL
;
533 r
= dissected_image_decrypt_interactively(
535 &arg_verity_settings
,
541 r
= dissected_image_mount_and_warn(m
, arg_path
, UID_INVALID
, arg_flags
);
546 r
= decrypted_image_relinquish(di
);
548 return log_error_errno(r
, "Failed to relinquish DM devices: %m");
551 loop_device_relinquish(d
);
555 static int action_copy(DissectedImage
*m
, LoopDevice
*d
) {
556 _cleanup_(umount_and_rmdir_and_freep
) char *mounted_dir
= NULL
;
557 _cleanup_(decrypted_image_unrefp
) DecryptedImage
*di
= NULL
;
558 _cleanup_(rmdir_and_freep
) char *created_dir
= NULL
;
559 _cleanup_free_
char *temp
= NULL
;
565 r
= dissected_image_decrypt_interactively(
567 &arg_verity_settings
,
573 r
= detach_mount_namespace();
575 return log_error_errno(r
, "Failed to detach mount namespace: %m");
577 r
= tempfn_random_child(NULL
, program_invocation_short_name
, &temp
);
579 return log_error_errno(r
, "Failed to generate temporary mount directory: %m");
581 r
= mkdir_p(temp
, 0700);
583 return log_error_errno(r
, "Failed to create mount point: %m");
585 created_dir
= TAKE_PTR(temp
);
587 r
= dissected_image_mount_and_warn(m
, created_dir
, UID_INVALID
, arg_flags
);
591 mounted_dir
= TAKE_PTR(created_dir
);
594 r
= decrypted_image_relinquish(di
);
596 return log_error_errno(r
, "Failed to relinquish DM devices: %m");
599 loop_device_relinquish(d
);
601 if (arg_action
== ACTION_COPY_FROM
) {
602 _cleanup_close_
int source_fd
= -1, target_fd
= -1;
604 source_fd
= chase_symlinks_and_open(arg_source
, mounted_dir
, CHASE_PREFIX_ROOT
|CHASE_WARN
, O_RDONLY
|O_CLOEXEC
|O_NOCTTY
, NULL
);
606 return log_error_errno(source_fd
, "Failed to open source path '%s' in image '%s': %m", arg_source
, arg_image
);
608 /* Copying to stdout? */
609 if (streq(arg_target
, "-")) {
610 r
= copy_bytes(source_fd
, STDOUT_FILENO
, (uint64_t) -1, COPY_REFLINK
);
612 return log_error_errno(r
, "Failed to copy bytes from %s in mage '%s' to stdout: %m", arg_source
, arg_image
);
614 /* When we copy to stdou we don't copy any attributes (i.e. no access mode, no ownership, no xattr, no times) */
618 /* Try to copy as directory? */
619 r
= copy_directory_fd(source_fd
, arg_target
, COPY_REFLINK
|COPY_MERGE_EMPTY
|COPY_SIGINT
|COPY_HARDLINKS
);
623 return log_error_errno(r
, "Failed to copy %s in image '%s' to '%s': %m", arg_source
, arg_image
, arg_target
);
625 r
= fd_verify_regular(source_fd
);
627 return log_error_errno(r
, "Target '%s' exists already and is not a directory.", arg_target
);
629 return log_error_errno(r
, "Source path %s in image '%s' is neither regular file nor directory, refusing: %m", arg_source
, arg_image
);
631 /* Nah, it's a plain file! */
632 target_fd
= open(arg_target
, O_WRONLY
|O_CREAT
|O_EXCL
|O_CLOEXEC
|O_NOCTTY
|O_NOFOLLOW
, 0600);
634 return log_error_errno(errno
, "Failed to create regular file at target path '%s': %m", arg_target
);
636 r
= copy_bytes(source_fd
, target_fd
, (uint64_t) -1, COPY_REFLINK
);
638 return log_error_errno(r
, "Failed to copy bytes from %s in mage '%s' to '%s': %m", arg_source
, arg_image
, arg_target
);
640 (void) copy_xattr(source_fd
, target_fd
);
641 (void) copy_access(source_fd
, target_fd
);
642 (void) copy_times(source_fd
, target_fd
, 0);
644 /* When this is a regular file we don't copy ownership! */
647 _cleanup_close_
int source_fd
= -1, target_fd
= -1;
648 _cleanup_close_
int dfd
= -1;
649 _cleanup_free_
char *dn
= NULL
;
651 assert(arg_action
== ACTION_COPY_TO
);
653 dn
= dirname_malloc(arg_target
);
657 r
= chase_symlinks(dn
, mounted_dir
, CHASE_PREFIX_ROOT
|CHASE_WARN
, NULL
, &dfd
);
659 return log_error_errno(r
, "Failed to open '%s': %m", dn
);
661 /* Are we reading from stdin? */
662 if (streq(arg_source
, "-")) {
663 target_fd
= openat(dfd
, basename(arg_target
), O_WRONLY
|O_CREAT
|O_CLOEXEC
|O_NOCTTY
|O_EXCL
, 0644);
665 return log_error_errno(errno
, "Failed to open target file '%s': %m", arg_target
);
667 r
= copy_bytes(STDIN_FILENO
, target_fd
, (uint64_t) -1, COPY_REFLINK
);
669 return log_error_errno(r
, "Failed to copy bytes from stdin to '%s' in image '%s': %m", arg_target
, arg_image
);
671 /* When we copy from stdin we don't copy any attributes (i.e. no access mode, no ownership, no xattr, no times) */
675 source_fd
= open(arg_source
, O_RDONLY
|O_CLOEXEC
|O_NOCTTY
);
677 return log_error_errno(source_fd
, "Failed to open source path '%s': %m", arg_source
);
679 r
= fd_verify_regular(source_fd
);
682 return log_error_errno(r
, "Source '%s' is neither regular file nor directory: %m", arg_source
);
684 /* We are looking at a directory. */
686 target_fd
= openat(dfd
, basename(arg_target
), O_RDONLY
|O_DIRECTORY
|O_CLOEXEC
);
689 return log_error_errno(errno
, "Failed to open destination '%s': %m", arg_target
);
691 r
= copy_tree_at(source_fd
, ".", dfd
, basename(arg_target
), UID_INVALID
, GID_INVALID
, COPY_REFLINK
|COPY_REPLACE
|COPY_SIGINT
|COPY_HARDLINKS
);
693 r
= copy_tree_at(source_fd
, ".", target_fd
, ".", UID_INVALID
, GID_INVALID
, COPY_REFLINK
|COPY_REPLACE
|COPY_SIGINT
|COPY_HARDLINKS
);
695 return log_error_errno(r
, "Failed to copy '%s' to '%s' in image '%s': %m", arg_source
, arg_target
, arg_image
);
700 /* We area looking at a regular file */
701 target_fd
= openat(dfd
, basename(arg_target
), O_WRONLY
|O_CREAT
|O_CLOEXEC
|O_NOCTTY
|O_EXCL
, 0600);
703 return log_error_errno(errno
, "Failed to open target file '%s': %m", arg_target
);
705 r
= copy_bytes(source_fd
, target_fd
, (uint64_t) -1, COPY_REFLINK
);
707 return log_error_errno(r
, "Failed to copy bytes from '%s' to '%s' in image '%s': %m", arg_source
, arg_target
, arg_image
);
709 (void) copy_xattr(source_fd
, target_fd
);
710 (void) copy_access(source_fd
, target_fd
);
711 (void) copy_times(source_fd
, target_fd
, 0);
713 /* When this is a regular file we don't copy ownership! */
719 static int run(int argc
, char *argv
[]) {
720 _cleanup_(dissected_image_unrefp
) DissectedImage
*m
= NULL
;
721 _cleanup_(loop_device_unrefp
) LoopDevice
*d
= NULL
;
724 log_parse_environment();
727 r
= parse_argv(argc
, argv
);
731 r
= verity_settings_load(
732 &arg_verity_settings
,
733 arg_image
, NULL
, NULL
);
735 return log_error_errno(r
, "Failed to read verity artifacts for %s: %m", arg_image
);
737 if (arg_verity_settings
.data_path
)
738 arg_flags
|= DISSECT_IMAGE_NO_PARTITION_TABLE
; /* We only support Verity per file system,
739 * hence if there's external Verity data
740 * available we turn off partition table
743 r
= loop_device_make_by_path(
745 FLAGS_SET(arg_flags
, DISSECT_IMAGE_READ_ONLY
) ? O_RDONLY
: O_RDWR
,
746 FLAGS_SET(arg_flags
, DISSECT_IMAGE_NO_PARTITION_TABLE
) ? 0 : LO_FLAGS_PARTSCAN
,
749 return log_error_errno(r
, "Failed to set up loopback device: %m");
751 r
= dissect_image_and_warn(
754 &arg_verity_settings
,
761 switch (arg_action
) {
764 r
= action_dissect(m
, d
);
768 r
= action_mount(m
, d
);
771 case ACTION_COPY_FROM
:
773 r
= action_copy(m
, d
);
777 assert_not_reached("Unknown action.");
783 DEFINE_MAIN_FUNCTION(run
);