]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/dissect/dissect.c
dissect: add --make-archive option to convert DDI to tarball
[thirdparty/systemd.git] / src / dissect / dissect.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
a2ea3b2f
LP
2
3#include <fcntl.h>
a2ea3b2f 4#include <getopt.h>
e08f94ac
LP
5#include <linux/loop.h>
6#include <stdio.h>
19df770f 7#include <sys/file.h>
16b74592
LP
8#include <sys/ioctl.h>
9#include <sys/mount.h>
a2ea3b2f 10
ac1f1adf
DDM
11#include "sd-device.h"
12
a2ea3b2f 13#include "architecture.h"
ac1f1adf 14#include "blockdev-util.h"
d6b4d1c7 15#include "build.h"
f461a28d 16#include "chase.h"
33973b84 17#include "copy.h"
ca822829
YW
18#include "device-util.h"
19#include "devnum-util.h"
0305cf6e 20#include "discover-image.h"
a2ea3b2f 21#include "dissect-image.h"
994c9c70 22#include "env-util.h"
db02190e 23#include "escape.h"
33973b84 24#include "fd-util.h"
89e62e0b 25#include "fileio.h"
89d00f2e 26#include "format-table.h"
16b74592 27#include "format-util.h"
33973b84 28#include "fs-util.h"
4623e8e6 29#include "hexdecoct.h"
b68f4cad 30#include "libarchive-util.h"
a2ea3b2f
LP
31#include "log.h"
32#include "loop-util.h"
149afb45 33#include "main-func.h"
33973b84
LP
34#include "mkdir.h"
35#include "mount-util.h"
ac1f1adf 36#include "mountpoint-util.h"
33973b84 37#include "namespace-util.h"
614b022c 38#include "parse-argument.h"
e475f729 39#include "parse-util.h"
e7cbe5cb 40#include "path-util.h"
5c05f062 41#include "pretty-print.h"
06186c4c 42#include "process-util.h"
0cf16924 43#include "recurse-dir.h"
db02190e 44#include "sha256.h"
33973b84 45#include "stat-util.h"
a2ea3b2f 46#include "string-util.h"
a1edd22e 47#include "strv.h"
5c05f062 48#include "terminal-util.h"
33973b84 49#include "tmpfile-util.h"
8e1ac16b 50#include "uid-classification.h"
2d3a5a73 51#include "user-util.h"
d7688568 52#include "vpick.h"
a2ea3b2f
LP
53
54static enum {
55 ACTION_DISSECT,
56 ACTION_MOUNT,
ac1f1adf 57 ACTION_UMOUNT,
07d6072e
LP
58 ACTION_ATTACH,
59 ACTION_DETACH,
0cf16924 60 ACTION_LIST,
db02190e 61 ACTION_MTREE,
06186c4c 62 ACTION_WITH,
33973b84
LP
63 ACTION_COPY_FROM,
64 ACTION_COPY_TO,
0305cf6e 65 ACTION_DISCOVER,
dee4a623 66 ACTION_VALIDATE,
b68f4cad 67 ACTION_MAKE_ARCHIVE,
a2ea3b2f 68} arg_action = ACTION_DISSECT;
a3c3386e 69static char *arg_image = NULL;
2292fa1e 70static char *arg_root = NULL;
a3c3386e 71static char *arg_path = NULL;
33973b84
LP
72static const char *arg_source = NULL;
73static const char *arg_target = NULL;
4b5de5dd
LP
74static DissectImageFlags arg_flags =
75 DISSECT_IMAGE_GENERIC_ROOT |
4b5de5dd
LP
76 DISSECT_IMAGE_DISCARD_ON_LOOP |
77 DISSECT_IMAGE_RELAX_VAR_CHECK |
78 DISSECT_IMAGE_FSCK |
74a54bae 79 DISSECT_IMAGE_USR_NO_ROOT |
73d88b80
LP
80 DISSECT_IMAGE_GROWFS |
81 DISSECT_IMAGE_PIN_PARTITION_DEVICES |
82 DISSECT_IMAGE_ADD_PARTITION_DEVICES;
aee36b4e 83static VeritySettings arg_verity_settings = VERITY_SETTINGS_DEFAULT;
6a01ea4a 84static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
17547fb5
LP
85static PagerFlags arg_pager_flags = 0;
86static bool arg_legend = true;
ac1f1adf 87static bool arg_rmdir = false;
6c07d570 88static bool arg_in_memory = false;
06186c4c 89static char **arg_argv = NULL;
236d1fa2 90static char *arg_loop_ref = NULL;
84be0c71 91static ImagePolicy* arg_image_policy = NULL;
12d58b6c 92static bool arg_mtree_hash = true;
a2ea3b2f 93
a3c3386e 94STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
2292fa1e 95STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
a3c3386e 96STATIC_DESTRUCTOR_REGISTER(arg_path, freep);
89e62e0b 97STATIC_DESTRUCTOR_REGISTER(arg_verity_settings, verity_settings_done);
06186c4c 98STATIC_DESTRUCTOR_REGISTER(arg_argv, strv_freep);
236d1fa2 99STATIC_DESTRUCTOR_REGISTER(arg_loop_ref, freep);
149afb45 100
5c05f062
LP
101static int help(void) {
102 _cleanup_free_ char *link = NULL;
103 int r;
104
5fdec397
ZJS
105 pager_open(arg_pager_flags);
106
5c05f062
LP
107 r = terminal_urlify_man("systemd-dissect", "1", &link);
108 if (r < 0)
109 return log_oom();
110
111 printf("%1$s [OPTIONS...] IMAGE\n"
33973b84 112 "%1$s [OPTIONS...] --mount IMAGE PATH\n"
1b967529 113 "%1$s [OPTIONS...] --umount PATH\n"
07d6072e
LP
114 "%1$s [OPTIONS...] --attach IMAGE\n"
115 "%1$s [OPTIONS...] --detach PATH\n"
0cf16924 116 "%1$s [OPTIONS...] --list IMAGE\n"
db02190e 117 "%1$s [OPTIONS...] --mtree IMAGE\n"
06186c4c 118 "%1$s [OPTIONS...] --with IMAGE [COMMAND…]\n"
33973b84 119 "%1$s [OPTIONS...] --copy-from IMAGE PATH [TARGET]\n"
d8def0f3 120 "%1$s [OPTIONS...] --copy-to IMAGE [SOURCE] PATH\n"
b68f4cad 121 "%1$s [OPTIONS...] --make-archive IMAGE [TARGET]\n"
d8def0f3
LP
122 "%1$s [OPTIONS...] --discover\n"
123 "%1$s [OPTIONS...] --validate IMAGE\n"
124 "\n%5$sDissect a Discoverable Disk Image (DDI).%6$s\n\n"
5c05f062 125 "%3$sOptions:%4$s\n"
17547fb5
LP
126 " --no-pager Do not pipe output into a pager\n"
127 " --no-legend Do not show the headers and footers\n"
e7cbe5cb
LB
128 " -r --read-only Mount read-only\n"
129 " --fsck=BOOL Run fsck before mounting\n"
74a54bae 130 " --growfs=BOOL Grow file system to partition size, if marked\n"
5c05f062 131 " --mkdir Make mount directory before mounting, if missing\n"
ac1f1adf 132 " --rmdir Remove mount directory after unmounting\n"
e7cbe5cb 133 " --discard=MODE Choose 'discard' mode (disabled, loop, all, crypto)\n"
6c07d570 134 " --in-memory Copy image into memory\n"
e7cbe5cb 135 " --root-hash=HASH Specify root hash for verity\n"
c2923fdc
LB
136 " --root-hash-sig=SIG Specify pkcs7 signature of root hash for verity\n"
137 " as a DER encoded PKCS7, either as a path to a file\n"
138 " or as an ASCII base64 encoded string prefixed by\n"
139 " 'base64:'\n"
e7cbe5cb 140 " --verity-data=PATH Specify data file with hash tree for verity if it is\n"
5c05f062 141 " not embedded in IMAGE\n"
84be0c71
LP
142 " --image-policy=POLICY\n"
143 " Specify image dissection policy\n"
de8231b0
LP
144 " --json=pretty|short|off\n"
145 " Generate JSON output\n"
236d1fa2 146 " --loop-ref=NAME Set reference string for loopback device\n"
12d58b6c 147 " --mtree-hash=BOOL Whether to include SHA256 hash in the mtree output\n"
5c05f062
LP
148 "\n%3$sCommands:%4$s\n"
149 " -h --help Show this help\n"
150 " --version Show package version\n"
151 " -m --mount Mount the image to the specified directory\n"
152 " -M Shortcut for --mount --mkdir\n"
ac1f1adf
DDM
153 " -u --umount Unmount the image from the specified directory\n"
154 " -U Shortcut for --umount --rmdir\n"
07d6072e 155 " --attach Attach the disk image to a loopback block device\n"
08c0b6ef 156 " --detach Detach a loopback block device again\n"
0cf16924
AAF
157 " -l --list List all the files and directories of the specified\n"
158 " OS image\n"
db02190e 159 " --mtree Show BSD mtree manifest of OS image\n"
06186c4c 160 " --with Mount, run command, unmount\n"
33973b84
LP
161 " -x --copy-from Copy files from image to host\n"
162 " -a --copy-to Copy files from host to image\n"
b68f4cad 163 " --make-archive Convert the DDI to an archive file\n"
0305cf6e 164 " --discover Discover DDIs in well known directories\n"
dee4a623 165 " --validate Validate image and image policy\n"
bc556335
DDM
166 "\nSee the %2$s for details.\n",
167 program_invocation_short_name,
168 link,
169 ansi_underline(),
170 ansi_normal(),
171 ansi_highlight(),
172 ansi_normal());
5c05f062
LP
173
174 return 0;
a2ea3b2f
LP
175}
176
06186c4c
LP
177static int patch_argv(int *argc, char ***argv, char ***buf) {
178 _cleanup_free_ char **l = NULL;
179 char **e;
180
181 assert(argc);
182 assert(*argc >= 0);
183 assert(argv);
184 assert(*argv);
185 assert(buf);
186
187 /* Ugly hack: if --with is included in command line, also insert "--" immediately after it, to make
188 * getopt_long() stop processing switches */
189
190 for (e = *argv + 1; e < *argv + *argc; e++) {
191 assert(*e);
192
193 if (streq(*e, "--with"))
194 break;
195 }
196
197 if (e >= *argv + *argc || streq_ptr(e[1], "--")) {
198 /* No --with used? Or already followed by "--"? Then don't do anything */
199 *buf = NULL;
200 return 0;
201 }
202
203 /* Insert the extra "--" right after the --with */
204 l = new(char*, *argc + 2);
205 if (!l)
206 return log_oom();
207
208 size_t idx = e - *argv + 1;
209 memcpy(l, *argv, sizeof(char*) * idx); /* copy everything up to and including the --with */
210 l[idx] = (char*) "--"; /* insert "--" */
211 memcpy(l + idx + 1, e + 1, sizeof(char*) * (*argc - idx + 1)); /* copy the rest, including trailing NULL entry */
212
213 (*argc)++;
214 (*argv) = l;
215
216 *buf = TAKE_PTR(l);
217 return 1;
218}
219
2292fa1e
DDM
220static int parse_image_path_argument(const char *path, char **ret_root, char **ret_image) {
221 _cleanup_free_ char *p = NULL;
222 struct stat st;
223 int r;
224
225 assert(ret_image);
226
227 r = parse_path_argument(path, /* suppress_root= */ false, &p);
228 if (r < 0)
229 return r;
230
231 if (stat(p, &st) < 0)
232 return log_error_errno(errno, "Failed to stat %s: %m", p);
233
234 if (S_ISDIR(st.st_mode)) {
235 if (!ret_root)
236 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "%s is not an image file.", p);
237
238 *ret_root = TAKE_PTR(p);
239 } else
240 *ret_image = TAKE_PTR(p);
241
242 return 0;
243}
244
a2ea3b2f
LP
245static int parse_argv(int argc, char *argv[]) {
246
247 enum {
248 ARG_VERSION = 0x100,
17547fb5
LP
249 ARG_NO_PAGER,
250 ARG_NO_LEGEND,
06186c4c 251 ARG_WITH,
18b5886e 252 ARG_DISCARD,
e475f729 253 ARG_FSCK,
74a54bae 254 ARG_GROWFS,
89e62e0b 255 ARG_ROOT_HASH,
c2923fdc 256 ARG_ROOT_HASH_SIG,
89e62e0b 257 ARG_VERITY_DATA,
5c05f062 258 ARG_MKDIR,
ac1f1adf 259 ARG_RMDIR,
6c07d570 260 ARG_IN_MEMORY,
de8231b0 261 ARG_JSON,
db02190e 262 ARG_MTREE,
0305cf6e 263 ARG_DISCOVER,
07d6072e
LP
264 ARG_ATTACH,
265 ARG_DETACH,
236d1fa2 266 ARG_LOOP_REF,
84be0c71 267 ARG_IMAGE_POLICY,
dee4a623 268 ARG_VALIDATE,
12d58b6c 269 ARG_MTREE_HASH,
b68f4cad 270 ARG_MAKE_ARCHIVE,
a2ea3b2f
LP
271 };
272
273 static const struct option options[] = {
c2923fdc
LB
274 { "help", no_argument, NULL, 'h' },
275 { "version", no_argument, NULL, ARG_VERSION },
17547fb5
LP
276 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
277 { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
c2923fdc 278 { "mount", no_argument, NULL, 'm' },
ac1f1adf 279 { "umount", no_argument, NULL, 'u' },
07d6072e
LP
280 { "attach", no_argument, NULL, ARG_ATTACH },
281 { "detach", no_argument, NULL, ARG_DETACH },
06186c4c 282 { "with", no_argument, NULL, ARG_WITH },
c2923fdc
LB
283 { "read-only", no_argument, NULL, 'r' },
284 { "discard", required_argument, NULL, ARG_DISCARD },
c2923fdc 285 { "fsck", required_argument, NULL, ARG_FSCK },
74a54bae 286 { "growfs", required_argument, NULL, ARG_GROWFS },
89e62e0b 287 { "root-hash", required_argument, NULL, ARG_ROOT_HASH },
c2923fdc 288 { "root-hash-sig", required_argument, NULL, ARG_ROOT_HASH_SIG },
89e62e0b 289 { "verity-data", required_argument, NULL, ARG_VERITY_DATA },
5c05f062 290 { "mkdir", no_argument, NULL, ARG_MKDIR },
ac1f1adf 291 { "rmdir", no_argument, NULL, ARG_RMDIR },
6c07d570 292 { "in-memory", no_argument, NULL, ARG_IN_MEMORY },
0cf16924 293 { "list", no_argument, NULL, 'l' },
db02190e 294 { "mtree", no_argument, NULL, ARG_MTREE },
33973b84
LP
295 { "copy-from", no_argument, NULL, 'x' },
296 { "copy-to", no_argument, NULL, 'a' },
de8231b0 297 { "json", required_argument, NULL, ARG_JSON },
0305cf6e 298 { "discover", no_argument, NULL, ARG_DISCOVER },
236d1fa2 299 { "loop-ref", required_argument, NULL, ARG_LOOP_REF },
84be0c71 300 { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
dee4a623 301 { "validate", no_argument, NULL, ARG_VALIDATE },
12d58b6c 302 { "mtree-hash", required_argument, NULL, ARG_MTREE_HASH },
b68f4cad 303 { "make-archive", no_argument, NULL, ARG_MAKE_ARCHIVE },
a2ea3b2f
LP
304 {}
305 };
306
06186c4c 307 _cleanup_free_ char **buf = NULL; /* we use free(), not strv_free() here, as we don't copy the strings here */
4623e8e6 308 int c, r;
a2ea3b2f
LP
309
310 assert(argc >= 0);
311 assert(argv);
312
06186c4c
LP
313 r = patch_argv(&argc, &argv, &buf);
314 if (r < 0)
315 return r;
316
0cf16924 317 while ((c = getopt_long(argc, argv, "hmurMUlxa", options, NULL)) >= 0) {
a2ea3b2f
LP
318
319 switch (c) {
320
321 case 'h':
5c05f062 322 return help();
a2ea3b2f
LP
323
324 case ARG_VERSION:
325 return version();
326
17547fb5
LP
327 case ARG_NO_PAGER:
328 arg_pager_flags |= PAGER_DISABLE;
329 break;
330
331 case ARG_NO_LEGEND:
332 arg_legend = false;
333 break;
334
a2ea3b2f
LP
335 case 'm':
336 arg_action = ACTION_MOUNT;
337 break;
338
5c05f062
LP
339 case ARG_MKDIR:
340 arg_flags |= DISSECT_IMAGE_MKDIR;
341 break;
342
343 case 'M':
344 /* Shortcut combination of the above two */
345 arg_action = ACTION_MOUNT;
346 arg_flags |= DISSECT_IMAGE_MKDIR;
347 break;
348
ac1f1adf
DDM
349 case 'u':
350 arg_action = ACTION_UMOUNT;
351 break;
352
353 case ARG_RMDIR:
354 arg_rmdir = true;
355 break;
356
357 case 'U':
358 /* Shortcut combination of the above two */
359 arg_action = ACTION_UMOUNT;
360 arg_rmdir = true;
361 break;
362
07d6072e
LP
363 case ARG_ATTACH:
364 arg_action = ACTION_ATTACH;
365 break;
366
367 case ARG_DETACH:
368 arg_action = ACTION_DETACH;
369 break;
370
0cf16924
AAF
371 case 'l':
372 arg_action = ACTION_LIST;
373 arg_flags |= DISSECT_IMAGE_READ_ONLY;
374 break;
375
db02190e
LP
376 case ARG_MTREE:
377 arg_action = ACTION_MTREE;
378 arg_flags |= DISSECT_IMAGE_READ_ONLY;
379 break;
380
06186c4c
LP
381 case ARG_WITH:
382 arg_action = ACTION_WITH;
383 break;
384
33973b84
LP
385 case 'x':
386 arg_action = ACTION_COPY_FROM;
387 arg_flags |= DISSECT_IMAGE_READ_ONLY;
388 break;
389
390 case 'a':
391 arg_action = ACTION_COPY_TO;
392 break;
393
a2ea3b2f 394 case 'r':
18b5886e
LP
395 arg_flags |= DISSECT_IMAGE_READ_ONLY;
396 break;
397
971e2ef0
ZJS
398 case ARG_DISCARD: {
399 DissectImageFlags flags;
400
18b5886e 401 if (streq(optarg, "disabled"))
971e2ef0 402 flags = 0;
18b5886e 403 else if (streq(optarg, "loop"))
971e2ef0 404 flags = DISSECT_IMAGE_DISCARD_ON_LOOP;
18b5886e 405 else if (streq(optarg, "all"))
971e2ef0 406 flags = DISSECT_IMAGE_DISCARD_ON_LOOP | DISSECT_IMAGE_DISCARD;
18b5886e 407 else if (streq(optarg, "crypt"))
971e2ef0 408 flags = DISSECT_IMAGE_DISCARD_ANY;
140788f7
LP
409 else if (streq(optarg, "list")) {
410 puts("disabled\n"
411 "all\n"
412 "crypt\n"
413 "loop");
414 return 0;
415 } else
baaa35ad
ZJS
416 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
417 "Unknown --discard= parameter: %s",
418 optarg);
971e2ef0 419 arg_flags = (arg_flags & ~DISSECT_IMAGE_DISCARD_ANY) | flags;
18b5886e 420
a2ea3b2f 421 break;
971e2ef0 422 }
a2ea3b2f 423
6c07d570
LP
424 case ARG_IN_MEMORY:
425 arg_in_memory = true;
426 break;
427
4623e8e6 428 case ARG_ROOT_HASH: {
89e62e0b 429 _cleanup_free_ void *p = NULL;
4623e8e6
LP
430 size_t l;
431
bdd2036e 432 r = unhexmem(optarg, &p, &l);
4623e8e6 433 if (r < 0)
63cf2d75 434 return log_error_errno(r, "Failed to parse root hash '%s': %m", optarg);
89e62e0b
LP
435 if (l < sizeof(sd_id128_t))
436 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
da890466 437 "Root hash must be at least 128-bit long: %s", optarg);
4623e8e6 438
89e62e0b
LP
439 free_and_replace(arg_verity_settings.root_hash, p);
440 arg_verity_settings.root_hash_size = l;
4623e8e6
LP
441 break;
442 }
443
c2923fdc
LB
444 case ARG_ROOT_HASH_SIG: {
445 char *value;
89e62e0b
LP
446 size_t l;
447 void *p;
c2923fdc
LB
448
449 if ((value = startswith(optarg, "base64:"))) {
bdd2036e 450 r = unbase64mem(value, &p, &l);
c2923fdc
LB
451 if (r < 0)
452 return log_error_errno(r, "Failed to parse root hash signature '%s': %m", optarg);
c2923fdc 453 } else {
89e62e0b 454 r = read_full_file(optarg, (char**) &p, &l);
c2923fdc 455 if (r < 0)
89e62e0b 456 return log_error_errno(r, "Failed to read root hash signature file '%s': %m", optarg);
c2923fdc
LB
457 }
458
89e62e0b
LP
459 free_and_replace(arg_verity_settings.root_hash_sig, p);
460 arg_verity_settings.root_hash_sig_size = l;
c2923fdc
LB
461 break;
462 }
463
89e62e0b 464 case ARG_VERITY_DATA:
614b022c 465 r = parse_path_argument(optarg, false, &arg_verity_settings.data_path);
89e62e0b
LP
466 if (r < 0)
467 return r;
468 break;
469
e475f729
LP
470 case ARG_FSCK:
471 r = parse_boolean(optarg);
472 if (r < 0)
473 return log_error_errno(r, "Failed to parse --fsck= parameter: %s", optarg);
474
475 SET_FLAG(arg_flags, DISSECT_IMAGE_FSCK, r);
476 break;
477
74a54bae
LP
478 case ARG_GROWFS:
479 r = parse_boolean(optarg);
480 if (r < 0)
481 return log_error_errno(r, "Failed to parse --growfs= parameter: %s", optarg);
482
483 SET_FLAG(arg_flags, DISSECT_IMAGE_GROWFS, r);
484 break;
485
de8231b0 486 case ARG_JSON:
b1e8f46c 487 r = parse_json_argument(optarg, &arg_json_format_flags);
6a01ea4a
LP
488 if (r <= 0)
489 return r;
de8231b0
LP
490
491 break;
492
0305cf6e
LP
493 case ARG_DISCOVER:
494 arg_action = ACTION_DISCOVER;
495 break;
496
236d1fa2
LP
497 case ARG_LOOP_REF:
498 if (isempty(optarg)) {
499 arg_loop_ref = mfree(arg_loop_ref);
500 break;
501 }
502
503 if (strlen(optarg) >= sizeof_field(struct loop_info64, lo_file_name))
504 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Loop device ref string '%s' is too long.", optarg);
505
506 r = free_and_strdup_warn(&arg_loop_ref, optarg);
507 if (r < 0)
508 return r;
509 break;
510
06e78680
YW
511 case ARG_IMAGE_POLICY:
512 r = parse_image_policy_argument(optarg, &arg_image_policy);
84be0c71 513 if (r < 0)
06e78680 514 return r;
84be0c71 515 break;
84be0c71 516
dee4a623
LP
517 case ARG_VALIDATE:
518 arg_action = ACTION_VALIDATE;
519 break;
520
12d58b6c
DDM
521 case ARG_MTREE_HASH:
522 r = parse_boolean_argument("--mtree-hash=", optarg, &arg_mtree_hash);
523 if (r < 0)
524 return r;
525 break;
526
b68f4cad
LP
527 case ARG_MAKE_ARCHIVE:
528
529 r = dlopen_libarchive();
530 if (r < 0)
531 return log_error_errno(r, "Archive support not available (compiled without libarchive, or libarchive not installed?).");
532
533 arg_action = ACTION_MAKE_ARCHIVE;
534 break;
535
a2ea3b2f
LP
536 case '?':
537 return -EINVAL;
538
539 default:
04499a70 540 assert_not_reached();
a2ea3b2f 541 }
a2ea3b2f
LP
542 }
543
544 switch (arg_action) {
545
546 case ACTION_DISSECT:
baaa35ad
ZJS
547 if (optind + 1 != argc)
548 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
33973b84 549 "Expected an image file path as only argument.");
a2ea3b2f 550
2292fa1e 551 r = parse_image_path_argument(argv[optind], NULL, &arg_image);
a3c3386e
LP
552 if (r < 0)
553 return r;
554
598fd4da
LP
555 /* when dumping image info be even more liberal than otherwise, do not even require a single valid partition */
556 arg_flags |= DISSECT_IMAGE_READ_ONLY|DISSECT_IMAGE_ALLOW_EMPTY;
a2ea3b2f
LP
557 break;
558
559 case ACTION_MOUNT:
baaa35ad
ZJS
560 if (optind + 2 != argc)
561 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
33973b84 562 "Expected an image file path and mount point path as only arguments.");
a2ea3b2f 563
2292fa1e 564 r = parse_image_path_argument(argv[optind], NULL, &arg_image);
a3c3386e
LP
565 if (r < 0)
566 return r;
567
568 r = parse_path_argument(argv[optind+1], /* suppress_root= */ false, &arg_path);
569 if (r < 0)
570 return r;
571
166ff731 572 arg_flags |= DISSECT_IMAGE_REQUIRE_ROOT;
a2ea3b2f
LP
573 break;
574
ac1f1adf
DDM
575 case ACTION_UMOUNT:
576 if (optind + 1 != argc)
577 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
578 "Expected a mount point path as only argument.");
579
a3c3386e
LP
580 r = parse_path_argument(argv[optind], /* suppress_root= */ false, &arg_path);
581 if (r < 0)
582 return r;
ac1f1adf
DDM
583 break;
584
07d6072e
LP
585 case ACTION_ATTACH:
586 if (optind + 1 != argc)
587 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
588 "Expected an image file path as only argument.");
589
2292fa1e 590 r = parse_image_path_argument(argv[optind], NULL, &arg_image);
a3c3386e
LP
591 if (r < 0)
592 return r;
07d6072e
LP
593 break;
594
595 case ACTION_DETACH:
596 if (optind + 1 != argc)
597 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
598 "Expected an image file path or loopback device as only argument.");
599
2292fa1e 600 r = parse_image_path_argument(argv[optind], NULL, &arg_image);
a3c3386e
LP
601 if (r < 0)
602 return r;
07d6072e
LP
603 break;
604
0cf16924 605 case ACTION_LIST:
db02190e 606 case ACTION_MTREE:
0cf16924
AAF
607 if (optind + 1 != argc)
608 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
2292fa1e 609 "Expected an image file or directory path as only argument.");
0cf16924 610
2292fa1e 611 r = parse_image_path_argument(argv[optind], &arg_root, &arg_image);
a3c3386e
LP
612 if (r < 0)
613 return r;
614
0cf16924
AAF
615 arg_flags |= DISSECT_IMAGE_READ_ONLY | DISSECT_IMAGE_REQUIRE_ROOT;
616 break;
617
b68f4cad
LP
618 case ACTION_MAKE_ARCHIVE:
619 if (argc < optind + 1 || argc > optind + 2)
620 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
621 "Expected an image file, and an optional target path as only arguments.");
622
623 r = parse_image_path_argument(argv[optind], &arg_root, &arg_image);
624 if (r < 0)
625 return r;
626
627 arg_target = argc > optind + 1 ? empty_or_dash_to_null(argv[optind + 1]) : NULL;
628 arg_flags |= DISSECT_IMAGE_READ_ONLY | DISSECT_IMAGE_REQUIRE_ROOT;
629 break;
630
33973b84
LP
631 case ACTION_COPY_FROM:
632 if (argc < optind + 2 || argc > optind + 3)
633 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
2292fa1e 634 "Expected an image file or directory path, a source path and an optional destination path as only arguments.");
33973b84 635
2292fa1e 636 r = parse_image_path_argument(argv[optind], &arg_root, &arg_image);
a3c3386e
LP
637 if (r < 0)
638 return r;
33973b84
LP
639 arg_source = argv[optind + 1];
640 arg_target = argc > optind + 2 ? argv[optind + 2] : "-" /* this means stdout */ ;
641
166ff731 642 arg_flags |= DISSECT_IMAGE_READ_ONLY | DISSECT_IMAGE_REQUIRE_ROOT;
33973b84
LP
643 break;
644
645 case ACTION_COPY_TO:
646 if (argc < optind + 2 || argc > optind + 3)
647 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
2292fa1e 648 "Expected an image file or directory path, an optional source path and a destination path as only arguments.");
33973b84 649
2292fa1e 650 r = parse_image_path_argument(argv[optind], &arg_root, &arg_image);
a3c3386e
LP
651 if (r < 0)
652 return r;
33973b84
LP
653
654 if (argc > optind + 2) {
655 arg_source = argv[optind + 1];
656 arg_target = argv[optind + 2];
657 } else {
658 arg_source = "-"; /* this means stdin */
659 arg_target = argv[optind + 1];
660 }
661
166ff731 662 arg_flags |= DISSECT_IMAGE_REQUIRE_ROOT;
33973b84
LP
663 break;
664
06186c4c
LP
665 case ACTION_WITH:
666 if (optind >= argc)
667 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
668 "Expected an image file path and an optional command line.");
669
2292fa1e 670 r = parse_image_path_argument(argv[optind], NULL, &arg_image);
a3c3386e
LP
671 if (r < 0)
672 return r;
673
06186c4c
LP
674 if (argc > optind + 1) {
675 arg_argv = strv_copy(argv + optind + 1);
676 if (!arg_argv)
677 return log_oom();
678 }
679
680 break;
681
0305cf6e
LP
682 case ACTION_DISCOVER:
683 if (optind != argc)
684 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
685 "Expected no argument.");
dee4a623
LP
686 break;
687
688 case ACTION_VALIDATE:
689 if (optind + 1 != argc)
690 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
691 "Expected an image file path as only argument.");
0305cf6e 692
2292fa1e 693 r = parse_image_path_argument(argv[optind], NULL, &arg_image);
dee4a623
LP
694 if (r < 0)
695 return r;
696
697 arg_flags |= DISSECT_IMAGE_READ_ONLY;
698 arg_flags &= ~(DISSECT_IMAGE_PIN_PARTITION_DEVICES|DISSECT_IMAGE_ADD_PARTITION_DEVICES);
0305cf6e
LP
699 break;
700
a2ea3b2f 701 default:
04499a70 702 assert_not_reached();
a2ea3b2f
LP
703 }
704
705 return 1;
706}
707
a164d9d5
LP
708static int parse_argv_as_mount_helper(int argc, char *argv[]) {
709 const char *options = NULL;
710 bool fake = false;
711 int c, r;
712
713 /* Implements util-linux "external helper" command line interface, as per mount(8) man page. */
714
715 while ((c = getopt(argc, argv, "sfnvN:o:t:")) >= 0) {
1d03d970 716 switch (c) {
a164d9d5
LP
717
718 case 'f':
719 fake = true;
720 break;
721
722 case 'o':
723 options = optarg;
724 break;
725
726 case 't':
727 if (!streq(optarg, "ddi"))
728 log_debug("Unexpected file system type '%s', ignoring.", optarg);
729 break;
730
731 case 's': /* sloppy mount options */
732 case 'n': /* aka --no-mtab */
733 case 'v': /* aka --verbose */
734 log_debug("Ignoring option -%c, not implemented.", c);
735 break;
736
737 case 'N': /* aka --namespace= */
738 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Option -%c is not implemented, refusing.", c);
739
740 case '?':
741 return -EINVAL;
742 }
743 }
744
745 if (optind + 2 != argc)
746 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
747 "Expected an image file path and target directory as only argument.");
748
749 for (const char *p = options;;) {
750 _cleanup_free_ char *word = NULL;
751
752 r = extract_first_word(&p, &word, ",", EXTRACT_KEEP_QUOTE);
753 if (r < 0)
754 return log_error_errno(r, "Failed to extract mount option: %m");
755 if (r == 0)
756 break;
757
758 if (streq(word, "ro"))
759 SET_FLAG(arg_flags, DISSECT_IMAGE_READ_ONLY, true);
760 else if (streq(word, "rw"))
761 SET_FLAG(arg_flags, DISSECT_IMAGE_READ_ONLY, false);
762 else if (streq(word, "discard"))
763 SET_FLAG(arg_flags, DISSECT_IMAGE_DISCARD_ANY, true);
764 else if (streq(word, "nodiscard"))
765 SET_FLAG(arg_flags, DISSECT_IMAGE_DISCARD_ANY, false);
766 else
767 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
768 "Unknown mount option '%s'.", word);
769 }
770
771 if (fake)
772 return 0;
773
a3c3386e
LP
774 r = parse_path_argument(argv[optind], /* suppress_root= */ false, &arg_image);
775 if (r < 0)
776 return r;
777
778 r = parse_path_argument(argv[optind+1], /* suppress_root= */ false, &arg_path);
779 if (r < 0)
780 return r;
a164d9d5
LP
781
782 arg_flags |= DISSECT_IMAGE_REQUIRE_ROOT;
783 arg_action = ACTION_MOUNT;
784 return 1;
785}
786
8570b98b 787static void strv_pair_print(char **l, const char *prefix) {
8570b98b
LP
788 assert(prefix);
789
7f5b2615 790 STRV_FOREACH_PAIR(p, q, l)
8570b98b
LP
791 if (p == l)
792 printf("%s %s=%s\n", prefix, *p, *q);
793 else
794 printf("%*s %s=%s\n", (int) strlen(prefix), "", *p, *q);
8570b98b
LP
795}
796
a81fe93e 797static int get_extension_scopes(DissectedImage *m, ImageClass class, char ***ret_scopes) {
994c9c70 798 _cleanup_strv_free_ char **l = NULL;
a81fe93e
LP
799 const char *e, *field_name;
800 char **release_data;
994c9c70
LP
801
802 assert(m);
803 assert(ret_scopes);
804
a81fe93e
LP
805 switch (class) {
806
807 case IMAGE_SYSEXT:
808 release_data = m->sysext_release;
809 field_name = "SYSEXT_SCOPE";
810 break;
811
812 case IMAGE_CONFEXT:
813 release_data = m->confext_release;
814 field_name = "CONFEXT_SCOPE";
815 break;
816
817 default:
818 return -EINVAL;
819 }
820
484d26da
MG
821 /* If there's no extension-release file its not a system extension. Otherwise the SYSEXT_SCOPE
822 * field for sysext images and the CONFEXT_SCOPE field for confext images indicates which scope
823 * it is for — and it defaults to "system" + "portable" if unset. */
a81fe93e
LP
824
825 if (!release_data) {
994c9c70
LP
826 *ret_scopes = NULL;
827 return 0;
828 }
829
a81fe93e 830 e = strv_env_pairs_get(release_data, field_name);
994c9c70
LP
831 if (e)
832 l = strv_split(e, WHITESPACE);
833 else
834 l = strv_new("system", "portable");
835 if (!l)
836 return -ENOMEM;
837
838 *ret_scopes = TAKE_PTR(l);
839 return 1;
840}
841
37e44c3f 842static int action_dissect(DissectedImage *m, LoopDevice *d) {
de8231b0 843 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
37e44c3f 844 _cleanup_(table_unrefp) Table *t = NULL;
cfb623b6 845 _cleanup_free_ char *bn = NULL;
de8231b0 846 uint64_t size = UINT64_MAX;
a2ea3b2f
LP
847 int r;
848
37e44c3f
LP
849 assert(m);
850 assert(d);
a2ea3b2f 851
cfb623b6
LP
852 r = path_extract_filename(arg_image, &bn);
853 if (r < 0)
854 return log_error_errno(r, "Failed to extract file name from image path '%s': %m", arg_image);
855
17547fb5 856 if (arg_json_format_flags & (JSON_FORMAT_OFF|JSON_FORMAT_PRETTY|JSON_FORMAT_PRETTY_AUTO))
384c2c32 857 pager_open(arg_pager_flags);
17547fb5 858
5b1b37c8 859 if (arg_json_format_flags & JSON_FORMAT_OFF) {
9603fd29 860 printf(" File Name: %s%s%s\n",
5b1b37c8 861 ansi_highlight(), bn, ansi_normal());
a2ea3b2f 862
5b1b37c8
LP
863 printf(" Size: %s\n",
864 FORMAT_BYTES(m->image_size));
a2ea3b2f 865
5b1b37c8
LP
866 printf(" Sec. Size: %" PRIu32 "\n",
867 m->sector_size);
1d93c003 868
66cd3537
LP
869 printf(" Arch.: %s\n",
870 strna(architecture_to_string(dissected_image_architecture(m))));
2348043f 871
de8231b0 872 putc('\n', stdout);
66cd3537
LP
873 fflush(stdout);
874 }
1d93c003 875
22847508 876 r = dissected_image_acquire_metadata(m, 0);
af8219d5
LP
877 if (r == -ENXIO)
878 return log_error_errno(r, "No root partition discovered.");
af8219d5
LP
879 if (r == -EUCLEAN)
880 return log_error_errno(r, "File system check of image failed.");
4f309abb
LP
881 if (r == -EMEDIUMTYPE)
882 log_warning_errno(r, "Not a valid OS image, no os-release file included. Proceeding anyway.");
883 else if (r == -EUNATCH)
af8219d5
LP
884 log_warning_errno(r, "OS image is encrypted, proceeding without showing OS image metadata.");
885 else if (r == -EBUSY)
886 log_warning_errno(r, "OS image is currently in use, proceeding without showing OS image metadata.");
887 else if (r < 0)
888 return log_error_errno(r, "Failed to acquire image metadata: %m");
6a01ea4a 889 else if (arg_json_format_flags & JSON_FORMAT_OFF) {
994c9c70 890
9603fd29
LP
891 if (m->image_name && !streq(m->image_name, bn))
892 printf("Image Name: %s\n", m->image_name);
893
b387778c
LP
894 if (!sd_id128_is_null(m->image_uuid))
895 printf("Image UUID: %s\n", SD_ID128_TO_UUID_STRING(m->image_uuid));
896
af8219d5
LP
897 if (m->hostname)
898 printf(" Hostname: %s\n", m->hostname);
a2ea3b2f 899
af8219d5
LP
900 if (!sd_id128_is_null(m->machine_id))
901 printf("Machine ID: " SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(m->machine_id));
a1edd22e 902
8570b98b
LP
903 strv_pair_print(m->machine_info,
904 "Mach. Info:");
905 strv_pair_print(m->os_release,
906 "OS Release:");
fab22946
LP
907 strv_pair_print(m->initrd_release,
908 "initrd R.:");
a81fe93e
LP
909 strv_pair_print(m->sysext_release,
910 " sysext R.:");
911 strv_pair_print(m->confext_release,
912 "confext R.:");
7718ac97 913
4f309abb
LP
914 if (m->hostname ||
915 !sd_id128_is_null(m->machine_id) ||
916 !strv_isempty(m->machine_info) ||
8570b98b 917 !strv_isempty(m->os_release) ||
fab22946 918 !strv_isempty(m->initrd_release) ||
a81fe93e
LP
919 !strv_isempty(m->sysext_release) ||
920 !strv_isempty(m->confext_release))
4f309abb 921 putc('\n', stdout);
994c9c70 922
b2466e05
LP
923 printf(" Use As: %s bootable system for UEFI\n",
924 COLOR_MARK_BOOL(dissected_image_is_bootable_uefi(m)));
925 printf(" %s bootable system for container\n",
926 COLOR_MARK_BOOL(dissected_image_is_bootable_os(m)));
994c9c70 927 printf(" %s portable service\n",
b2466e05 928 COLOR_MARK_BOOL(dissected_image_is_portable(m)));
fab22946 929 printf(" %s initrd\n",
b2466e05 930 COLOR_MARK_BOOL(dissected_image_is_initrd(m)));
994c9c70 931
a81fe93e
LP
932 for (ImageClass c = _IMAGE_CLASS_EXTENSION_FIRST; c <= _IMAGE_CLASS_EXTENSION_LAST; c++) {
933 const char *string_class = image_class_to_string(c);
934 _cleanup_strv_free_ char **extension_scopes = NULL;
994c9c70 935
a81fe93e
LP
936 r = get_extension_scopes(m, c, &extension_scopes);
937 if (r < 0)
938 return log_error_errno(r, "Failed to parse scopes: %m");
939
940 printf(" %s %s for system\n",
941 COLOR_MARK_BOOL(strv_contains(extension_scopes, "system")), string_class);
942 printf(" %s %s for portable service\n",
943 COLOR_MARK_BOOL(strv_contains(extension_scopes, "portable")), string_class);
944 printf(" %s %s for initrd\n",
945 COLOR_MARK_BOOL(strv_contains(extension_scopes, "initrd")), string_class);
946 }
994c9c70
LP
947
948 putc('\n', stdout);
4f309abb 949 } else {
a81fe93e
LP
950 _cleanup_strv_free_ char **sysext_scopes = NULL, **confext_scopes = NULL;
951
952 r = get_extension_scopes(m, IMAGE_SYSEXT, &sysext_scopes);
953 if (r < 0)
954 return log_error_errno(r, "Failed to parse sysext scopes: %m");
de8231b0 955
a81fe93e 956 r = get_extension_scopes(m, IMAGE_CONFEXT, &confext_scopes);
994c9c70 957 if (r < 0)
a81fe93e 958 return log_error_errno(r, "Failed to parse confext scopes: %m");
994c9c70 959
6e0b5cd3
LP
960 Architecture a = dissected_image_architecture(m);
961
de8231b0 962 r = json_build(&v, JSON_BUILD_OBJECT(
cfb623b6 963 JSON_BUILD_PAIR("name", JSON_BUILD_STRING(bn)),
1c5cc6c5 964 JSON_BUILD_PAIR_CONDITION(size != UINT64_MAX, "size", JSON_BUILD_INTEGER(size)),
1d93c003 965 JSON_BUILD_PAIR("sectorSize", JSON_BUILD_INTEGER(m->sector_size)),
6e0b5cd3 966 JSON_BUILD_PAIR_CONDITION(a >= 0, "architecture", JSON_BUILD_STRING(architecture_to_string(a))),
1c5cc6c5 967 JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(m->image_uuid), "imageUuid", JSON_BUILD_UUID(m->image_uuid)),
de8231b0
LP
968 JSON_BUILD_PAIR_CONDITION(m->hostname, "hostname", JSON_BUILD_STRING(m->hostname)),
969 JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(m->machine_id), "machineId", JSON_BUILD_ID128(m->machine_id)),
e83d902b
LP
970 JSON_BUILD_PAIR_CONDITION(!strv_isempty(m->machine_info), "machineInfo", JSON_BUILD_STRV_ENV_PAIR(m->machine_info)),
971 JSON_BUILD_PAIR_CONDITION(!strv_isempty(m->os_release), "osRelease", JSON_BUILD_STRV_ENV_PAIR(m->os_release)),
972 JSON_BUILD_PAIR_CONDITION(!strv_isempty(m->initrd_release), "initrdRelease", JSON_BUILD_STRV_ENV_PAIR(m->initrd_release)),
a81fe93e
LP
973 JSON_BUILD_PAIR_CONDITION(!strv_isempty(m->sysext_release), "sysextRelease", JSON_BUILD_STRV_ENV_PAIR(m->sysext_release)),
974 JSON_BUILD_PAIR_CONDITION(!strv_isempty(m->confext_release), "confextRelease", JSON_BUILD_STRV_ENV_PAIR(m->confext_release)),
b2466e05
LP
975 JSON_BUILD_PAIR("useBootableUefi", JSON_BUILD_BOOLEAN(dissected_image_is_bootable_uefi(m))),
976 JSON_BUILD_PAIR("useBootableContainer", JSON_BUILD_BOOLEAN(dissected_image_is_bootable_os(m))),
977 JSON_BUILD_PAIR("useInitrd", JSON_BUILD_BOOLEAN(dissected_image_is_initrd(m))),
978 JSON_BUILD_PAIR("usePortableService", JSON_BUILD_BOOLEAN(dissected_image_is_portable(m))),
a81fe93e
LP
979 JSON_BUILD_PAIR("useSystemExtension", JSON_BUILD_BOOLEAN(strv_contains(sysext_scopes, "system"))),
980 JSON_BUILD_PAIR("useInitRDSystemExtension", JSON_BUILD_BOOLEAN(strv_contains(sysext_scopes, "initrd"))),
981 JSON_BUILD_PAIR("usePortableSystemExtension", JSON_BUILD_BOOLEAN(strv_contains(sysext_scopes, "portable"))),
982 JSON_BUILD_PAIR("useConfigurationExtension", JSON_BUILD_BOOLEAN(strv_contains(confext_scopes, "system"))),
983 JSON_BUILD_PAIR("useInitRDConfigurationExtension", JSON_BUILD_BOOLEAN(strv_contains(confext_scopes, "initrd"))),
984 JSON_BUILD_PAIR("usePortableConfigurationExtension", JSON_BUILD_BOOLEAN(strv_contains(confext_scopes, "portable")))));
de8231b0
LP
985 if (r < 0)
986 return log_oom();
987 }
988
ee8e497d 989 t = table_new("rw", "designator", "partition uuid", "partition label", "fstype", "architecture", "verity", "growfs", "node", "partno");
37e44c3f
LP
990 if (!t)
991 return log_oom();
a1edd22e 992
c8b62cf6 993 table_set_ersatz_string(t, TABLE_ERSATZ_DASH);
748e87a7 994 (void) table_set_align_percent(t, table_get_cell(t, 0, 9), 100);
a2ea3b2f 995
64cd3d13
LP
996 /* Hide the device path if this is a loopback device that is not relinquished, since that means the
997 * device node is not going to be useful the instant our command exits */
998 if ((!d || d->created) && (arg_json_format_flags & JSON_FORMAT_OFF))
999 table_hide_column_from_display(t, 8);
1000
569a0e42 1001 for (PartitionDesignator i = 0; i < _PARTITION_DESIGNATOR_MAX; i++) {
37e44c3f 1002 DissectedPartition *p = m->partitions + i;
89d00f2e 1003
37e44c3f
LP
1004 if (!p->found)
1005 continue;
89d00f2e 1006
37e44c3f
LP
1007 r = table_add_many(
1008 t,
1009 TABLE_STRING, p->rw ? "rw" : "ro",
1010 TABLE_STRING, partition_designator_to_string(i));
1011 if (r < 0)
1012 return table_log_add_error(r);
89d00f2e 1013
37e44c3f
LP
1014 if (sd_id128_is_null(p->uuid))
1015 r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
1016 else
1017 r = table_add_cell(t, NULL, TABLE_UUID, &p->uuid);
1018 if (r < 0)
1019 return table_log_add_error(r);
89d00f2e 1020
37e44c3f
LP
1021 r = table_add_many(
1022 t,
1474d7ac 1023 TABLE_STRING, p->label,
37e44c3f
LP
1024 TABLE_STRING, p->fstype,
1025 TABLE_STRING, architecture_to_string(p->architecture));
1026 if (r < 0)
1027 return table_log_add_error(r);
1028
89e62e0b 1029 if (arg_verity_settings.data_path)
37e44c3f 1030 r = table_add_cell(t, NULL, TABLE_STRING, "external");
49536766 1031 else if (dissected_image_verity_candidate(m, i))
8ee9615e
LP
1032 r = table_add_cell(t, NULL, TABLE_STRING,
1033 dissected_image_verity_sig_ready(m, i) ? "signed" :
1034 yes_no(dissected_image_verity_ready(m, i)));
37e44c3f
LP
1035 else
1036 r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
1037 if (r < 0)
1038 return table_log_add_error(r);
89d00f2e 1039
ee8e497d
LP
1040 r = table_add_many(t, TABLE_BOOLEAN, (int) p->growfs);
1041 if (r < 0)
1042 return table_log_add_error(r);
1043
37e44c3f 1044 if (p->partno < 0) /* no partition table, naked file system */ {
a3c3386e 1045 r = table_add_cell(t, NULL, TABLE_PATH_BASENAME, arg_image);
89d00f2e
LP
1046 if (r < 0)
1047 return table_log_add_error(r);
1048
37e44c3f
LP
1049 r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
1050 } else {
1051 r = table_add_cell(t, NULL, TABLE_STRING, p->node);
89d00f2e
LP
1052 if (r < 0)
1053 return table_log_add_error(r);
1054
37e44c3f
LP
1055 r = table_add_cell(t, NULL, TABLE_INT, &p->partno);
1056 }
1057 if (r < 0)
1058 return table_log_add_error(r);
1059 }
89d00f2e 1060
6a01ea4a 1061 if (arg_json_format_flags & JSON_FORMAT_OFF) {
17547fb5
LP
1062 (void) table_set_header(t, arg_legend);
1063
1064 r = table_print(t, NULL);
6a01ea4a
LP
1065 if (r < 0)
1066 return table_log_print_error(r);
1067 } else {
de8231b0
LP
1068 _cleanup_(json_variant_unrefp) JsonVariant *jt = NULL;
1069
1070 r = table_to_json(t, &jt);
1071 if (r < 0)
1072 return log_error_errno(r, "Failed to convert table to JSON: %m");
1073
1074 r = json_variant_set_field(&v, "mounts", jt);
1075 if (r < 0)
1076 return log_oom();
1077
1078 json_variant_dump(v, arg_json_format_flags, stdout, NULL);
de8231b0 1079 }
89d00f2e 1080
37e44c3f
LP
1081 return 0;
1082}
89d00f2e 1083
37e44c3f 1084static int action_mount(DissectedImage *m, LoopDevice *d) {
37e44c3f 1085 int r;
89d00f2e 1086
37e44c3f
LP
1087 assert(m);
1088 assert(d);
94abea2a 1089 assert(arg_action == ACTION_MOUNT);
89d00f2e 1090
8d9a1d59
LP
1091 r = dissected_image_mount_and_warn(
1092 m,
1093 arg_path,
1094 /* uid_shift= */ UID_INVALID,
1095 /* uid_range= */ UID_INVALID,
1096 /* userns_fd= */ -EBADF,
1097 arg_flags);
37e44c3f 1098 if (r < 0)
af187ab2 1099 return r;
89d00f2e 1100
41bc4849
LP
1101 r = loop_device_flock(d, LOCK_UN);
1102 if (r < 0)
1103 return log_error_errno(r, "Failed to unlock loopback block device: %m");
1104
3044d343
YW
1105 r = dissected_image_relinquish(m);
1106 if (r < 0)
1107 return log_error_errno(r, "Failed to relinquish DM and loopback block devices: %m");
a2ea3b2f 1108
37e44c3f
LP
1109 return 0;
1110}
18b5886e 1111
0cf16924
AAF
1112static int list_print_item(
1113 RecurseDirEvent event,
1114 const char *path,
1115 int dir_fd,
1116 int inode_fd,
1117 const struct dirent *de,
1118 const struct statx *sx,
1119 void *userdata) {
1120
167f2c1a 1121 assert(path);
0cf16924
AAF
1122
1123 if (event == RECURSE_DIR_ENTER)
bb69ec95 1124 printf("%s%s/%s\n", path, ansi_grey(), ansi_normal());
0cf16924
AAF
1125 else if (event == RECURSE_DIR_ENTRY)
1126 printf("%s\n", path);
1127
1128 return RECURSE_DIR_CONTINUE;
1129}
1130
db02190e 1131static int get_file_sha256(int inode_fd, uint8_t ret[static SHA256_DIGEST_SIZE]) {
254d1313 1132 _cleanup_close_ int fd = -EBADF;
db02190e
LP
1133 struct sha256_ctx ctx;
1134
1135 /* convert O_PATH fd into a regular one */
1136 fd = fd_reopen(inode_fd, O_RDONLY|O_CLOEXEC);
1137 if (fd < 0)
1138 return fd;
1139
1140 /* Calculating the SHA sum might be slow, hence let's flush STDOUT first, to give user an idea where we are slow. */
1141 fflush(stdout);
1142
1143 sha256_init_ctx(&ctx);
1144
1145 for (;;) {
1146 uint8_t buffer[64 * 1024];
1147 ssize_t n;
1148
1149 n = read(fd, buffer, sizeof(buffer));
1150 if (n < 0)
1151 return -errno;
1152 if (n == 0)
1153 break;
1154
1155 sha256_process_bytes(buffer, n, &ctx);
1156 }
1157
1158 sha256_finish_ctx(&ctx, ret);
1159 return 0;
1160}
1161
12c0f4ff
LP
1162static const char *pick_color_for_uid_gid(uid_t uid) {
1163 if (uid == UID_NOBODY)
1164 return ansi_highlight_yellow4(); /* files should never be owned by 'nobody' (but might happen due to userns mapping) */
1165 if (uid_is_system(uid))
1166 return ansi_normal(); /* files in disk images are typically owned by root and other system users, no issue there */
1167 if (uid_is_dynamic(uid))
94d82b59 1168 return ansi_highlight_red(); /* files should never be owned persistently by dynamic users, and there are just no excuses */
12c0f4ff
LP
1169 if (uid_is_container(uid))
1170 return ansi_highlight_cyan();
1171
1172 return ansi_highlight();
1173}
1174
db02190e
LP
1175static int mtree_print_item(
1176 RecurseDirEvent event,
1177 const char *path,
1178 int dir_fd,
1179 int inode_fd,
1180 const struct dirent *de,
1181 const struct statx *sx,
1182 void *userdata) {
1183
01a33cf7 1184 _cleanup_free_ char *escaped = NULL;
db02190e
LP
1185 int r;
1186
167f2c1a 1187 assert(path);
db02190e 1188
01a33cf7
YW
1189 if (!IN_SET(event, RECURSE_DIR_ENTER, RECURSE_DIR_ENTRY))
1190 return RECURSE_DIR_CONTINUE;
db02190e 1191
01a33cf7 1192 assert(sx);
5ffa6a0a 1193
01a33cf7
YW
1194 if (isempty(path))
1195 path = ".";
1196 else {
1197 /* BSD mtree uses either C or octal escaping, and covers whitespace, comments and glob characters. We use C style escaping and follow suit */
1198 path = escaped = xescape(path, WHITESPACE COMMENTS GLOB_CHARS);
1199 if (!escaped)
1200 return log_oom();
1201 }
db02190e 1202
01a33cf7
YW
1203 printf("%s", isempty(path) ? "." : path);
1204
1205 if (FLAGS_SET(sx->stx_mask, STATX_TYPE)) {
1206 if (S_ISDIR(sx->stx_mode))
1207 printf("%s/%s", ansi_grey(), ansi_normal());
1208
1209 printf(" %stype=%s%s%s%s",
1210 ansi_grey(),
1211 ansi_normal(),
1212 S_ISDIR(sx->stx_mode) ? ansi_highlight_blue() :
1213 S_ISLNK(sx->stx_mode) ? ansi_highlight_cyan() :
1214 (S_ISFIFO(sx->stx_mode) || S_ISCHR(sx->stx_mode) || S_ISBLK(sx->stx_mode)) ? ansi_highlight_yellow4() :
1215 S_ISSOCK(sx->stx_mode) ? ansi_highlight_magenta() : "",
1216 ASSERT_PTR(S_ISDIR(sx->stx_mode) ? "dir" :
1217 S_ISREG(sx->stx_mode) ? "file" :
1218 S_ISLNK(sx->stx_mode) ? "link" :
1219 S_ISFIFO(sx->stx_mode) ? "fifo" :
1220 S_ISBLK(sx->stx_mode) ? "block" :
1221 S_ISCHR(sx->stx_mode) ? "char" :
1222 S_ISSOCK(sx->stx_mode) ? "socket" : NULL),
1223 ansi_normal());
1224 }
db02190e 1225
01a33cf7
YW
1226 if (FLAGS_SET(sx->stx_mask, STATX_MODE) && (!FLAGS_SET(sx->stx_mask, STATX_TYPE) || !S_ISLNK(sx->stx_mode)))
1227 printf(" %smode=%s%04o",
1228 ansi_grey(),
1229 ansi_normal(),
1230 (unsigned) (sx->stx_mode & 0777));
1231
1232 if (FLAGS_SET(sx->stx_mask, STATX_UID))
12c0f4ff 1233 printf(" %suid=%s" UID_FMT "%s",
01a33cf7 1234 ansi_grey(),
12c0f4ff
LP
1235 pick_color_for_uid_gid(sx->stx_uid),
1236 sx->stx_uid,
1237 ansi_normal());
01a33cf7
YW
1238
1239 if (FLAGS_SET(sx->stx_mask, STATX_GID))
12c0f4ff 1240 printf(" %sgid=%s" GID_FMT "%s",
01a33cf7 1241 ansi_grey(),
12c0f4ff
LP
1242 pick_color_for_uid_gid(sx->stx_gid),
1243 sx->stx_gid,
1244 ansi_normal());
01a33cf7
YW
1245
1246 if (FLAGS_SET(sx->stx_mask, STATX_TYPE|STATX_SIZE) && S_ISREG(sx->stx_mode)) {
1247 printf(" %ssize=%s%" PRIu64,
1248 ansi_grey(),
1249 ansi_normal(),
1250 (uint64_t) sx->stx_size);
1251
12d58b6c 1252 if (arg_mtree_hash && inode_fd >= 0 && sx->stx_size > 0) {
01a33cf7
YW
1253 uint8_t hash[SHA256_DIGEST_SIZE];
1254
1255 r = get_file_sha256(inode_fd, hash);
db02190e 1256 if (r < 0)
01a33cf7 1257 log_warning_errno(r, "Failed to calculate file SHA256 sum for '%s', ignoring: %m", path);
db02190e 1258 else {
01a33cf7 1259 _cleanup_free_ char *h = NULL;
db02190e 1260
01a33cf7
YW
1261 h = hexmem(hash, sizeof(hash));
1262 if (!h)
db02190e
LP
1263 return log_oom();
1264
01a33cf7 1265 printf(" %ssha256sum=%s%s",
db02190e
LP
1266 ansi_grey(),
1267 ansi_normal(),
01a33cf7 1268 h);
db02190e
LP
1269 }
1270 }
01a33cf7 1271 }
db02190e 1272
01a33cf7
YW
1273 if (FLAGS_SET(sx->stx_mask, STATX_TYPE) && S_ISLNK(sx->stx_mode) && inode_fd >= 0) {
1274 _cleanup_free_ char *target = NULL;
1275
1276 r = readlinkat_malloc(inode_fd, "", &target);
1277 if (r < 0)
1278 log_warning_errno(r, "Failed to read symlink '%s', ignoring: %m", path);
1279 else {
1280 _cleanup_free_ char *target_escaped = NULL;
1281
1282 target_escaped = xescape(target, WHITESPACE COMMENTS GLOB_CHARS);
1283 if (!target_escaped)
1284 return log_oom();
1285
1286 printf(" %slink=%s%s",
db02190e
LP
1287 ansi_grey(),
1288 ansi_normal(),
01a33cf7
YW
1289 target_escaped);
1290 }
db02190e
LP
1291 }
1292
01a33cf7
YW
1293 if (FLAGS_SET(sx->stx_mask, STATX_TYPE) && (S_ISBLK(sx->stx_mode) || S_ISCHR(sx->stx_mode)))
1294 printf(" %sdevice=%slinux,%" PRIu64 ",%" PRIu64,
1295 ansi_grey(),
1296 ansi_normal(),
1297 (uint64_t) sx->stx_rdev_major,
1298 (uint64_t) sx->stx_rdev_minor);
1299
1300 printf("\n");
1301
db02190e
LP
1302 return RECURSE_DIR_CONTINUE;
1303}
1304
b68f4cad
LP
1305#if HAVE_LIBARCHIVE
1306static int archive_item(
1307 RecurseDirEvent event,
1308 const char *path,
1309 int dir_fd,
1310 int inode_fd,
1311 const struct dirent *de,
1312 const struct statx *sx,
1313 void *userdata) {
1314
1315 struct archive *a = ASSERT_PTR(userdata);
1316 int r;
1317
1318 assert(path);
1319
1320 if (!IN_SET(event, RECURSE_DIR_ENTER, RECURSE_DIR_ENTRY))
1321 return RECURSE_DIR_CONTINUE;
1322
1323 assert(inode_fd >= 0);
1324 assert(sx);
1325
1326 log_debug("Archiving %s\n", path);
1327
1328 _cleanup_(sym_archive_entry_freep) struct archive_entry *entry = NULL;
1329 entry = sym_archive_entry_new();
1330 if (!entry)
1331 return log_oom();
1332
1333 assert(FLAGS_SET(sx->stx_mask, STATX_TYPE|STATX_MODE));
1334 sym_archive_entry_set_pathname(entry, path);
1335 sym_archive_entry_set_filetype(entry, sx->stx_mode);
1336
1337 if (!S_ISLNK(sx->stx_mode))
1338 sym_archive_entry_set_perm(entry, sx->stx_mode);
1339
1340 if (FLAGS_SET(sx->stx_mask, STATX_UID))
1341 sym_archive_entry_set_uid(entry, sx->stx_uid);
1342 if (FLAGS_SET(sx->stx_mask, STATX_GID))
1343 sym_archive_entry_set_gid(entry, sx->stx_gid);
1344
1345 if (S_ISREG(sx->stx_mode)) {
1346 if (!FLAGS_SET(sx->stx_mask, STATX_SIZE))
1347 return log_error_errno(SYNTHETIC_ERRNO(EIO), "Unable to determine file size of '%s'.", path);
1348
1349 sym_archive_entry_set_size(entry, sx->stx_size);
1350 }
1351
1352 if (S_ISCHR(sx->stx_mode) || S_ISBLK(sx->stx_mode)) {
1353 sym_archive_entry_set_rdevmajor(entry, sx->stx_rdev_major);
1354 sym_archive_entry_set_rdevminor(entry, sx->stx_rdev_minor);
1355 }
1356
1357 /* We care about a modicum of reproducibility here, hence we don't save atime/btime here */
1358 if (FLAGS_SET(sx->stx_mask, STATX_MTIME))
1359 sym_archive_entry_set_mtime(entry, sx->stx_mtime.tv_sec, sx->stx_mtime.tv_nsec);
1360 if (FLAGS_SET(sx->stx_mask, STATX_CTIME))
1361 sym_archive_entry_set_ctime(entry, sx->stx_ctime.tv_sec, sx->stx_ctime.tv_nsec);
1362
1363 if (S_ISLNK(sx->stx_mode)) {
1364 _cleanup_free_ char *s = NULL;
1365
1366 assert(dir_fd >= 0);
1367 assert(de);
1368
1369 r = readlinkat_malloc(dir_fd, de->d_name, &s);
1370 if (r < 0)
1371 return log_error_errno(r, "Failed to read symlink target of '%s': %m", path);
1372
1373 sym_archive_entry_set_symlink(entry, s);
1374 }
1375
1376 if (sym_archive_write_header(a, entry) != ARCHIVE_OK)
1377 return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to write archive entry header: %s", sym_archive_error_string(a));
1378
1379 if (S_ISREG(sx->stx_mode)) {
1380 _cleanup_close_ int data_fd = -EBADF;
1381
1382 /* Convert the O_PATH fd in a proper fd */
1383 data_fd = fd_reopen(inode_fd, O_RDONLY|O_CLOEXEC);
1384 if (data_fd < 0)
1385 return log_error_errno(data_fd, "Failed to open '%s': %m", path);
1386
1387 for (;;) {
1388 char buffer[64*1024];
1389 ssize_t l;
1390
1391 l = read(data_fd, buffer, sizeof(buffer));
1392 if (l < 0)
1393 return log_error_errno(errno, "Failed to read '%s': %m", path);
1394 if (l == 0)
1395 break;
1396
1397 la_ssize_t k;
1398 k = sym_archive_write_data(a, buffer, l);
1399 if (k < 0)
1400 return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to write archive data: %s", sym_archive_error_string(a));
1401 }
1402 }
1403
1404 return RECURSE_DIR_CONTINUE;
1405}
1406#endif
1407
1408static int action_list_or_mtree_or_copy_or_make_archive(DissectedImage *m, LoopDevice *d) {
37e44c3f 1409 _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
2d1e7d19 1410 _cleanup_free_ char *t = NULL;
2292fa1e 1411 const char *root;
37e44c3f 1412 int r;
a2ea3b2f 1413
b68f4cad 1414 assert(IN_SET(arg_action, ACTION_LIST, ACTION_MTREE, ACTION_COPY_FROM, ACTION_COPY_TO, ACTION_MAKE_ARCHIVE));
a2ea3b2f 1415
2292fa1e
DDM
1416 if (arg_image) {
1417 assert(m);
1418 assert(d);
33973b84 1419
2292fa1e
DDM
1420 r = detach_mount_namespace();
1421 if (r < 0)
1422 return log_error_errno(r, "Failed to detach mount namespace: %m");
33973b84 1423
2d1e7d19 1424 r = get_common_dissect_directory(&t);
2292fa1e 1425 if (r < 0)
2d1e7d19 1426 return log_error_errno(r, "Failed generate private mount directory: %m");
33973b84 1427
8d9a1d59
LP
1428 r = dissected_image_mount_and_warn(
1429 m,
2d1e7d19 1430 t,
8d9a1d59
LP
1431 /* uid_shift= */ UID_INVALID,
1432 /* uid_range= */ UID_INVALID,
1433 /* userns_fd= */ -EBADF,
1434 arg_flags);
2292fa1e
DDM
1435 if (r < 0)
1436 return r;
33973b84 1437
2d1e7d19 1438 mounted_dir = TAKE_PTR(t);
41bc4849 1439
2292fa1e
DDM
1440 r = loop_device_flock(d, LOCK_UN);
1441 if (r < 0)
1442 return log_error_errno(r, "Failed to unlock loopback block device: %m");
1443
1444 r = dissected_image_relinquish(m);
1445 if (r < 0)
1446 return log_error_errno(r, "Failed to relinquish DM and loopback block devices: %m");
1447 }
1448
1449 root = mounted_dir ?: arg_root;
33973b84 1450
b7df8622
LP
1451 switch (arg_action) {
1452
1453 case ACTION_COPY_FROM: {
254d1313 1454 _cleanup_close_ int source_fd = -EBADF, target_fd = -EBADF;
33973b84 1455
2292fa1e 1456 source_fd = chase_and_open(arg_source, root, CHASE_PREFIX_ROOT|CHASE_WARN, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL);
37e44c3f
LP
1457 if (source_fd < 0)
1458 return log_error_errno(source_fd, "Failed to open source path '%s' in image '%s': %m", arg_source, arg_image);
1459
1460 /* Copying to stdout? */
1461 if (streq(arg_target, "-")) {
f5fbe71d 1462 r = copy_bytes(source_fd, STDOUT_FILENO, UINT64_MAX, COPY_REFLINK);
33973b84 1463 if (r < 0)
37e44c3f
LP
1464 return log_error_errno(r, "Failed to copy bytes from %s in mage '%s' to stdout: %m", arg_source, arg_image);
1465
38db55ab 1466 /* When we copy to stdout we don't copy any attributes (i.e. no access mode, no ownership, no xattr, no times) */
37e44c3f 1467 return 0;
33973b84
LP
1468 }
1469
37e44c3f 1470 /* Try to copy as directory? */
f9f70e06 1471 r = copy_directory_at(source_fd, NULL, AT_FDCWD, arg_target, COPY_REFLINK|COPY_MERGE_EMPTY|COPY_SIGINT|COPY_HARDLINKS);
37e44c3f
LP
1472 if (r >= 0)
1473 return 0;
1474 if (r != -ENOTDIR)
1475 return log_error_errno(r, "Failed to copy %s in image '%s' to '%s': %m", arg_source, arg_image, arg_target);
1476
1477 r = fd_verify_regular(source_fd);
1478 if (r == -EISDIR)
1479 return log_error_errno(r, "Target '%s' exists already and is not a directory.", arg_target);
1480 if (r < 0)
1481 return log_error_errno(r, "Source path %s in image '%s' is neither regular file nor directory, refusing: %m", arg_source, arg_image);
33973b84 1482
37e44c3f
LP
1483 /* Nah, it's a plain file! */
1484 target_fd = open(arg_target, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0600);
1485 if (target_fd < 0)
1486 return log_error_errno(errno, "Failed to create regular file at target path '%s': %m", arg_target);
33973b84 1487
f5fbe71d 1488 r = copy_bytes(source_fd, target_fd, UINT64_MAX, COPY_REFLINK);
37e44c3f
LP
1489 if (r < 0)
1490 return log_error_errno(r, "Failed to copy bytes from %s in mage '%s' to '%s': %m", arg_source, arg_image, arg_target);
33973b84 1491
c17cfe6e 1492 (void) copy_xattr(source_fd, NULL, target_fd, NULL, 0);
37e44c3f
LP
1493 (void) copy_access(source_fd, target_fd);
1494 (void) copy_times(source_fd, target_fd, 0);
33973b84 1495
37e44c3f 1496 /* When this is a regular file we don't copy ownership! */
b7df8622
LP
1497 return 0;
1498 }
33973b84 1499
b7df8622 1500 case ACTION_COPY_TO: {
254d1313 1501 _cleanup_close_ int source_fd = -EBADF, target_fd = -EBADF, dfd = -EBADF;
cfb623b6 1502 _cleanup_free_ char *dn = NULL, *bn = NULL;
83802e9a 1503 bool is_dir;
33973b84 1504
45519d13
LP
1505 r = path_extract_directory(arg_target, &dn);
1506 if (r < 0)
cfb623b6
LP
1507 return log_error_errno(r, "Failed to extract directory from target path '%s': %m", arg_target);
1508 r = path_extract_filename(arg_target, &bn);
1509 if (r < 0)
1510 return log_error_errno(r, "Failed to extract filename from target path '%s': %m", arg_target);
83802e9a 1511 is_dir = r == O_DIRECTORY;
37e44c3f 1512
2292fa1e 1513 r = chase(dn, root, CHASE_PREFIX_ROOT|CHASE_WARN, NULL, &dfd);
37e44c3f
LP
1514 if (r < 0)
1515 return log_error_errno(r, "Failed to open '%s': %m", dn);
33973b84 1516
37e44c3f
LP
1517 /* Are we reading from stdin? */
1518 if (streq(arg_source, "-")) {
83802e9a
LP
1519 if (is_dir)
1520 return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Cannot copy STDIN to a directory, refusing.");
1521
cfb623b6 1522 target_fd = openat(dfd, bn, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_EXCL, 0644);
33973b84 1523 if (target_fd < 0)
37e44c3f 1524 return log_error_errno(errno, "Failed to open target file '%s': %m", arg_target);
33973b84 1525
f5fbe71d 1526 r = copy_bytes(STDIN_FILENO, target_fd, UINT64_MAX, COPY_REFLINK);
33973b84 1527 if (r < 0)
37e44c3f 1528 return log_error_errno(r, "Failed to copy bytes from stdin to '%s' in image '%s': %m", arg_target, arg_image);
33973b84 1529
37e44c3f
LP
1530 /* When we copy from stdin we don't copy any attributes (i.e. no access mode, no ownership, no xattr, no times) */
1531 return 0;
1532 }
33973b84 1533
37e44c3f
LP
1534 source_fd = open(arg_source, O_RDONLY|O_CLOEXEC|O_NOCTTY);
1535 if (source_fd < 0)
1536 return log_error_errno(source_fd, "Failed to open source path '%s': %m", arg_source);
33973b84 1537
37e44c3f
LP
1538 r = fd_verify_regular(source_fd);
1539 if (r < 0) {
1540 if (r != -EISDIR)
1541 return log_error_errno(r, "Source '%s' is neither regular file nor directory: %m", arg_source);
33973b84 1542
37e44c3f 1543 /* We are looking at a directory. */
33973b84 1544
cfb623b6 1545 target_fd = openat(dfd, bn, O_RDONLY|O_DIRECTORY|O_CLOEXEC);
37e44c3f
LP
1546 if (target_fd < 0) {
1547 if (errno != ENOENT)
1548 return log_error_errno(errno, "Failed to open destination '%s': %m", arg_target);
33973b84 1549
ad6fae7f 1550 r = copy_tree_at(source_fd, ".", dfd, bn, UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS, NULL, NULL);
37e44c3f 1551 } else
ad6fae7f 1552 r = copy_tree_at(source_fd, ".", target_fd, ".", UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS, NULL, NULL);
33973b84 1553 if (r < 0)
37e44c3f 1554 return log_error_errno(r, "Failed to copy '%s' to '%s' in image '%s': %m", arg_source, arg_target, arg_image);
33973b84 1555
37e44c3f
LP
1556 return 0;
1557 }
33973b84 1558
83802e9a
LP
1559 if (is_dir)
1560 return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Source is a regular file, but target is not, refusing.");
1561
37e44c3f 1562 /* We area looking at a regular file */
83802e9a 1563 target_fd = openat(dfd, bn, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_EXCL, 0600);
37e44c3f
LP
1564 if (target_fd < 0)
1565 return log_error_errno(errno, "Failed to open target file '%s': %m", arg_target);
33973b84 1566
f5fbe71d 1567 r = copy_bytes(source_fd, target_fd, UINT64_MAX, COPY_REFLINK);
37e44c3f
LP
1568 if (r < 0)
1569 return log_error_errno(r, "Failed to copy bytes from '%s' to '%s' in image '%s': %m", arg_source, arg_target, arg_image);
33973b84 1570
c17cfe6e 1571 (void) copy_xattr(source_fd, NULL, target_fd, NULL, 0);
37e44c3f
LP
1572 (void) copy_access(source_fd, target_fd);
1573 (void) copy_times(source_fd, target_fd, 0);
33973b84 1574
37e44c3f 1575 /* When this is a regular file we don't copy ownership! */
b7df8622
LP
1576 return 0;
1577 }
0cf16924 1578
b7df8622
LP
1579 case ACTION_LIST:
1580 case ACTION_MTREE: {
254d1313 1581 _cleanup_close_ int dfd = -EBADF;
0cf16924 1582
2292fa1e 1583 dfd = open(root, O_DIRECTORY|O_CLOEXEC|O_RDONLY);
0cf16924
AAF
1584 if (dfd < 0)
1585 return log_error_errno(errno, "Failed to open mount directory: %m");
1586
2e1f76f6
LP
1587 pager_open(arg_pager_flags);
1588
db02190e
LP
1589 if (arg_action == ACTION_LIST)
1590 r = recurse_dir(dfd, NULL, 0, UINT_MAX, RECURSE_DIR_SORT, list_print_item, NULL);
1591 else if (arg_action == ACTION_MTREE)
1592 r = recurse_dir(dfd, ".", STATX_TYPE|STATX_MODE|STATX_UID|STATX_GID|STATX_SIZE, UINT_MAX, RECURSE_DIR_SORT|RECURSE_DIR_INODE_FD|RECURSE_DIR_TOPLEVEL, mtree_print_item, NULL);
1593 else
1594 assert_not_reached();
0cf16924
AAF
1595 if (r < 0)
1596 return log_error_errno(r, "Failed to list image: %m");
b7df8622 1597 return 0;
37e44c3f 1598 }
33973b84 1599
b68f4cad
LP
1600 case ACTION_MAKE_ARCHIVE: {
1601#if HAVE_LIBARCHIVE
1602 _cleanup_(unlink_and_freep) char *tar = NULL;
1603 _cleanup_close_ int dfd = -EBADF;
1604 _cleanup_fclose_ FILE *f = NULL;
1605
1606 dfd = open(root, O_DIRECTORY|O_CLOEXEC|O_RDONLY);
1607 if (dfd < 0)
1608 return log_error_errno(errno, "Failed to open mount directory: %m");
1609
1610 _cleanup_(sym_archive_write_freep) struct archive *a = sym_archive_write_new();
1611 if (!a)
1612 return log_oom();
1613
1614 if (arg_target)
1615 r = sym_archive_write_set_format_filter_by_ext(a, arg_target);
1616 else
1617 r = sym_archive_write_set_format_gnutar(a);
1618 if (r != ARCHIVE_OK)
1619 return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to set libarchive output format: %s", sym_archive_error_string(a));
1620
1621 if (arg_target) {
1622 r = fopen_tmpfile_linkable(arg_target, O_WRONLY|O_CLOEXEC, &tar, &f);
1623 if (r < 0)
1624 return log_error_errno(r, "Failed to create target file '%s': %m", arg_target);
1625
1626 r = sym_archive_write_open_FILE(a, f);
1627 } else {
1628 if (isatty(STDOUT_FILENO))
1629 return log_error_errno(SYNTHETIC_ERRNO(EBADF), "Refusing to write archive to TTY.");
1630
1631 r = sym_archive_write_open_fd(a, STDOUT_FILENO);
1632 }
1633 if (r != ARCHIVE_OK)
1634 return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to set libarchive output file: %s", sym_archive_error_string(a));
1635
1636 r = recurse_dir(dfd,
1637 ".",
1638 STATX_TYPE|STATX_MODE|STATX_UID|STATX_GID|STATX_SIZE|STATX_ATIME|STATX_CTIME,
1639 UINT_MAX,
1640 RECURSE_DIR_SORT|RECURSE_DIR_INODE_FD|RECURSE_DIR_TOPLEVEL,
1641 archive_item,
1642 a);
1643 if (r < 0)
1644 return log_error_errno(r, "Failed to make archive: %m");
1645
1646 r = sym_archive_write_close(a);
1647 if (r != ARCHIVE_OK)
1648 return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unable to finish writing archive: %s", sym_archive_error_string(a));
1649
1650 if (arg_target) {
1651 r = flink_tmpfile(f, tar, arg_target, LINK_TMPFILE_REPLACE);
1652 if (r < 0)
1653 return log_error_errno(r, "Failed to move archive file into place: %m");
1654 }
1655
1656 return 0;
1657#else
1658 assert_not_reached();
1659#endif
1660 }
1661
b7df8622
LP
1662 default:
1663 assert_not_reached();
1664 }
37e44c3f 1665}
33973b84 1666
ac1f1adf 1667static int action_umount(const char *path) {
254d1313 1668 _cleanup_close_ int fd = -EBADF;
040d3439 1669 _cleanup_free_ char *canonical = NULL;
ac1f1adf 1670 _cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
040d3439 1671 _cleanup_(sd_device_unrefp) sd_device *dev = NULL;
e8383058 1672 int r;
ac1f1adf 1673
f461a28d 1674 fd = chase_and_open(path, NULL, 0, O_DIRECTORY, &canonical);
ac1f1adf
DDM
1675 if (fd == -ENOTDIR)
1676 return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "'%s' is not a directory", path);
1677 if (fd < 0)
1678 return log_error_errno(fd, "Failed to resolve path '%s': %m", path);
1679
1680 r = fd_is_mount_point(fd, NULL, 0);
1681 if (r == 0)
1682 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "'%s' is not a mount point", canonical);
1683 if (r < 0)
1684 return log_error_errno(r, "Failed to determine whether '%s' is a mount point: %m", canonical);
1685
040d3439 1686 r = block_device_new_from_fd(fd, BLOCK_DEVICE_LOOKUP_WHOLE_DISK | BLOCK_DEVICE_LOOKUP_BACKING, &dev);
41a95b18 1687 if (r < 0) {
254d1313 1688 _cleanup_close_ int usr_fd = -EBADF;
41a95b18
YW
1689
1690 /* The command `systemd-dissect --mount` expects that the image at least has the root or /usr
1691 * partition. If it does not have the root partition, then we mount the /usr partition on a
1692 * tmpfs. Hence, let's try to find the backing block device through the /usr partition. */
1693
1694 usr_fd = openat(fd, "usr", O_CLOEXEC | O_DIRECTORY | O_NOFOLLOW);
1695 if (usr_fd < 0)
1696 return log_error_errno(errno, "Failed to open '%s/usr': %m", canonical);
1697
1698 r = block_device_new_from_fd(usr_fd, BLOCK_DEVICE_LOOKUP_WHOLE_DISK | BLOCK_DEVICE_LOOKUP_BACKING, &dev);
1699 }
ac1f1adf
DDM
1700 if (r < 0)
1701 return log_error_errno(r, "Failed to find backing block device for '%s': %m", canonical);
1702
040d3439 1703 r = loop_device_open(dev, 0, LOCK_EX, &d);
ac1f1adf 1704 if (r < 0)
040d3439 1705 return log_device_error_errno(dev, r, "Failed to open loopback block device: %m");
ac1f1adf 1706
ac1f1adf
DDM
1707 /* We've locked the loop device, now we're ready to unmount. To allow the unmount to succeed, we have
1708 * to close the O_PATH fd we opened earlier. */
1709 fd = safe_close(fd);
1710
1711 r = umount_recursive(canonical, 0);
1712 if (r < 0)
1713 return log_error_errno(r, "Failed to unmount '%s': %m", canonical);
1714
0b75493d 1715 /* We managed to lock and unmount successfully? That means we can try to remove the loop device. */
ac1f1adf
DDM
1716 loop_device_unrelinquish(d);
1717
1718 if (arg_rmdir) {
e8383058
LP
1719 r = RET_NERRNO(rmdir(canonical));
1720 if (r < 0)
1721 return log_error_errno(r, "Failed to remove mount directory '%s': %m", canonical);
ac1f1adf 1722 }
ac1f1adf 1723
e8383058 1724 return 0;
ac1f1adf
DDM
1725}
1726
06186c4c
LP
1727static int action_with(DissectedImage *m, LoopDevice *d) {
1728 _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
1729 _cleanup_(rmdir_and_freep) char *created_dir = NULL;
1730 _cleanup_free_ char *temp = NULL;
1731 int r, rcode;
1732
94abea2a
LP
1733 assert(m);
1734 assert(d);
1735 assert(arg_action == ACTION_WITH);
06186c4c
LP
1736
1737 r = tempfn_random_child(NULL, program_invocation_short_name, &temp);
1738 if (r < 0)
1739 return log_error_errno(r, "Failed to generate temporary mount directory: %m");
1740
1741 r = mkdir_p(temp, 0700);
1742 if (r < 0)
1743 return log_error_errno(r, "Failed to create mount point: %m");
1744
1745 created_dir = TAKE_PTR(temp);
1746
8d9a1d59
LP
1747 r = dissected_image_mount_and_warn(
1748 m,
1749 created_dir,
1750 /* uid_shift= */ UID_INVALID,
1751 /* uid_range= */ UID_INVALID,
1752 /* userns_fd= */ -EBADF,
1753 arg_flags);
06186c4c
LP
1754 if (r < 0)
1755 return r;
1756
1757 mounted_dir = TAKE_PTR(created_dir);
1758
1759 r = dissected_image_relinquish(m);
1760 if (r < 0)
1761 return log_error_errno(r, "Failed to relinquish DM and loopback block devices: %m");
1762
1763 r = loop_device_flock(d, LOCK_UN);
1764 if (r < 0)
1765 return log_error_errno(r, "Failed to unlock loopback block device: %m");
1766
1767 rcode = safe_fork("(with)", FORK_CLOSE_ALL_FDS|FORK_LOG|FORK_WAIT, NULL);
1768 if (rcode == 0) {
1769 /* Child */
1770
1771 if (chdir(mounted_dir) < 0) {
1772 log_error_errno(errno, "Failed to change to '%s' directory: %m", mounted_dir);
1773 _exit(EXIT_FAILURE);
1774 }
1775
1776 if (setenv("SYSTEMD_DISSECT_ROOT", mounted_dir, /* overwrite= */ true) < 0) {
1777 log_error_errno(errno, "Failed to set $SYSTEMD_DISSECT_ROOT: %m");
1778 _exit(EXIT_FAILURE);
1779 }
1780
47838b55
DDM
1781 if (setenv("SYSTEMD_DISSECT_DEVICE", d->node, /* overwrite= */ true) < 0) {
1782 log_error_errno(errno, "Failed to set $SYSTEMD_DISSECT_DEVICE: %m");
1783 _exit(EXIT_FAILURE);
1784 }
1785
06186c4c
LP
1786 if (strv_isempty(arg_argv)) {
1787 const char *sh;
1788
1789 sh = secure_getenv("SHELL");
1790 if (sh) {
1791 execvp(sh, STRV_MAKE(sh));
1792 log_warning_errno(errno, "Failed to execute $SHELL, falling back to /bin/sh: %m");
1793 }
1794
1795 execl("/bin/sh", "sh", NULL);
1796 log_error_errno(errno, "Failed to invoke /bin/sh: %m");
1797 } else {
1798 execvp(arg_argv[0], arg_argv);
1799 log_error_errno(errno, "Failed to execute '%s': %m", arg_argv[0]);
1800 }
1801
1802 _exit(EXIT_FAILURE);
1803 }
1804
1805 /* Let's manually detach everything, to make things synchronous */
1806 r = loop_device_flock(d, LOCK_SH);
1807 if (r < 0)
1808 log_warning_errno(r, "Failed to lock loopback block device, ignoring: %m");
1809
1810 r = umount_recursive(mounted_dir, 0);
1811 if (r < 0)
1812 log_warning_errno(r, "Failed to unmount '%s', ignoring: %m", mounted_dir);
1813 else
1814 loop_device_unrelinquish(d); /* Let's try to destroy the loopback device */
1815
1816 created_dir = TAKE_PTR(mounted_dir);
1817
1818 if (rmdir(created_dir) < 0)
1819 log_warning_errno(r, "Failed to remove directory '%s', ignoring: %m", created_dir);
1820
1821 temp = TAKE_PTR(created_dir);
1822
1823 return rcode;
1824}
1825
0305cf6e 1826static int action_discover(void) {
5d2a48da 1827 _cleanup_hashmap_free_ Hashmap *images = NULL;
0305cf6e
LP
1828 _cleanup_(table_unrefp) Table *t = NULL;
1829 Image *img;
1830 int r;
1831
1832 images = hashmap_new(&image_hash_ops);
1833 if (!images)
1834 return log_oom();
1835
1836 for (ImageClass cl = 0; cl < _IMAGE_CLASS_MAX; cl++) {
1837 r = image_discover(cl, NULL, images);
1838 if (r < 0)
1839 return log_error_errno(r, "Failed to discover images: %m");
1840 }
1841
1842 if ((arg_json_format_flags & JSON_FORMAT_OFF) && hashmap_isempty(images)) {
1843 log_info("No images found.");
1844 return 0;
1845 }
1846
1847 t = table_new("name", "type", "class", "ro", "path", "time", "usage");
1848 if (!t)
1849 return log_oom();
1850
8f488d46 1851 table_set_align_percent(t, table_get_cell(t, 0, 6), 100);
ba113008 1852 table_set_ersatz_string(t, TABLE_ERSATZ_DASH);
8f488d46 1853
0305cf6e
LP
1854 HASHMAP_FOREACH(img, images) {
1855
1856 if (!IN_SET(img->type, IMAGE_RAW, IMAGE_BLOCK))
1857 continue;
1858
1859 r = table_add_many(
1860 t,
1861 TABLE_STRING, img->name,
1862 TABLE_STRING, image_type_to_string(img->type),
1863 TABLE_STRING, image_class_to_string(img->class),
1864 TABLE_BOOLEAN, img->read_only,
1865 TABLE_PATH, img->path,
1866 TABLE_TIMESTAMP, img->mtime != 0 ? img->mtime : img->crtime,
1867 TABLE_SIZE, img->usage);
1868 if (r < 0)
1869 return table_log_add_error(r);
1870 }
1871
1872 (void) table_set_sort(t, (size_t) 0);
1873
1874 return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
1875}
1876
07d6072e
LP
1877static int action_attach(DissectedImage *m, LoopDevice *d) {
1878 int r;
1879
1880 assert(m);
1881 assert(d);
1882
1883 r = loop_device_set_autoclear(d, false);
1884 if (r < 0)
1885 return log_error_errno(r, "Failed to disable auto-clear logic on loopback device: %m");
1886
1887 r = dissected_image_relinquish(m);
1888 if (r < 0)
1889 return log_error_errno(r, "Failed to relinquish DM and loopback block devices: %m");
1890
1891 puts(d->node);
1892 return 0;
1893}
1894
1895static int action_detach(const char *path) {
1896 _cleanup_(loop_device_unrefp) LoopDevice *loop = NULL;
1897 _cleanup_close_ int fd = -EBADF;
1898 struct stat st;
1899 int r;
1900
43108bf8
LB
1901 assert(path);
1902
07d6072e
LP
1903 fd = open(path, O_PATH|O_CLOEXEC);
1904 if (fd < 0)
1905 return log_error_errno(errno, "Failed to open '%s': %m", path);
1906
1907 if (fstat(fd, &st) < 0)
1908 return log_error_errno(errno, "Failed to stat '%s': %m", path);
1909
1910 if (S_ISBLK(st.st_mode)) {
1911 r = loop_device_open_from_fd(fd, O_RDONLY, LOCK_EX, &loop);
1912 if (r < 0)
1913 return log_error_errno(r, "Failed to open '%s' as loopback block device: %m", path);
1914
1915 } else if (S_ISREG(st.st_mode)) {
1916 _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
07d6072e
LP
1917
1918 /* If a regular file is specified, search for a loopback block device that is backed by it */
1919
1920 r = sd_device_enumerator_new(&e);
1921 if (r < 0)
1922 return log_error_errno(r, "Failed to allocate enumerator: %m");
1923
1924 r = sd_device_enumerator_add_match_subsystem(e, "block", true);
1925 if (r < 0)
1926 return log_error_errno(r, "Failed to match block devices: %m");
1927
1928 r = sd_device_enumerator_add_match_sysname(e, "loop*");
1929 if (r < 0)
1930 return log_error_errno(r, "Failed to match loopback block devices: %m");
1931
1932 (void) sd_device_enumerator_allow_uninitialized(e);
1933
1934 FOREACH_DEVICE(e, d) {
1935 _cleanup_(loop_device_unrefp) LoopDevice *entry_loop = NULL;
07d6072e 1936
fb53ee0a 1937 if (!device_is_devtype(d, "disk")) /* Filter out partition block devices */
07d6072e
LP
1938 continue;
1939
1940 r = loop_device_open(d, O_RDONLY, LOCK_SH, &entry_loop);
1941 if (r < 0) {
fb53ee0a 1942 log_device_warning_errno(d, r, "Failed to open loopback block device, skipping: %m");
07d6072e
LP
1943 continue;
1944 }
1945
1946 if (entry_loop->backing_devno == st.st_dev && entry_loop->backing_inode == st.st_ino) {
1947 /* Found it! The kernel allows attaching a single file to multiple loopback
1948 * devices. Let's destruct them in reverse order, i.e. find the last matching
1949 * loopback device here, rather than the first. */
1950
1951 loop_device_unref(loop);
1952 loop = TAKE_PTR(entry_loop);
1953 }
1954 }
1955
1956 if (!loop)
1957 return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "No loopback block device backed by '%s' found.", path);
1958
1959 r = loop_device_flock(loop, LOCK_EX);
1960 if (r < 0)
1961 return log_error_errno(r, "Failed to upgrade device lock: %m");
1962 }
1963
1964 r = loop_device_set_autoclear(loop, true);
1965 if (r < 0)
1966 log_warning_errno(r, "Failed to enable autoclear logic on '%s', ignoring: %m", loop->node);
1967
1968 loop_device_unrelinquish(loop);
1969 return 0;
1970}
1971
dee4a623
LP
1972static int action_validate(void) {
1973 int r;
1974
1975 r = dissect_image_file_and_warn(
1976 arg_image,
1977 &arg_verity_settings,
1978 NULL,
1979 arg_image_policy,
1980 arg_flags,
1981 NULL);
1982 if (r < 0)
1983 return r;
1984
1985 if (isatty(STDOUT_FILENO) && emoji_enabled())
1986 printf("%s ", special_glyph(SPECIAL_GLYPH_SPARKLES));
1987
1988 printf("%sOK%s", ansi_highlight_green(), ansi_normal());
1989
1990 if (isatty(STDOUT_FILENO) && emoji_enabled())
1991 printf(" %s", special_glyph(SPECIAL_GLYPH_SPARKLES));
1992
1993 putc('\n', stdout);
1994 return 0;
1995}
1996
37e44c3f
LP
1997static int run(int argc, char *argv[]) {
1998 _cleanup_(dissected_image_unrefp) DissectedImage *m = NULL;
1999 _cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
6c07d570
LP
2000 uint32_t loop_flags;
2001 int open_flags, r;
33973b84 2002
5acb31a6 2003 log_setup();
33973b84 2004
a164d9d5
LP
2005 if (invoked_as(argv, "mount.ddi"))
2006 r = parse_argv_as_mount_helper(argc, argv);
2007 else
2008 r = parse_argv(argc, argv);
37e44c3f
LP
2009 if (r <= 0)
2010 return r;
33973b84 2011
d7688568
LP
2012 if (arg_image) {
2013 r = path_pick_update_warn(
2014 &arg_image,
2015 &pick_filter_image_raw,
2016 PICK_ARCHITECTURE|PICK_TRIES,
2017 /* ret_result= */ NULL);
2018 if (r < 0)
2019 return r;
2020 }
2021
b7df8622
LP
2022 switch (arg_action) {
2023 case ACTION_UMOUNT:
ac1f1adf 2024 return action_umount(arg_path);
b7df8622
LP
2025
2026 case ACTION_DETACH:
07d6072e 2027 return action_detach(arg_image);
b7df8622
LP
2028
2029 case ACTION_DISCOVER:
0305cf6e 2030 return action_discover();
ac1f1adf 2031
b7df8622
LP
2032 default:
2033 /* All other actions need the image dissected */
2034 break;
2035 }
2036
2292fa1e
DDM
2037 if (arg_image) {
2038 r = verity_settings_load(
89e62e0b
LP
2039 &arg_verity_settings,
2040 arg_image, NULL, NULL);
236d1fa2 2041 if (r < 0)
2292fa1e 2042 return log_error_errno(r, "Failed to read verity artifacts for %s: %m", arg_image);
33973b84 2043
2292fa1e
DDM
2044 if (arg_verity_settings.data_path)
2045 arg_flags |= DISSECT_IMAGE_NO_PARTITION_TABLE; /* We only support Verity per file system,
2046 * hence if there's external Verity data
2047 * available we turn off partition table
2048 * support */
2049
2050 if (arg_action == ACTION_VALIDATE)
2051 return action_validate();
2052
2053 open_flags = FLAGS_SET(arg_flags, DISSECT_IMAGE_DEVICE_READ_ONLY) ? O_RDONLY : O_RDWR;
2054 loop_flags = FLAGS_SET(arg_flags, DISSECT_IMAGE_NO_PARTITION_TABLE) ? 0 : LO_FLAGS_PARTSCAN;
2055 if (arg_in_memory)
2056 r = loop_device_make_by_path_memory(arg_image, open_flags, /* sector_size= */ UINT32_MAX, loop_flags, LOCK_SH, &d);
2057 else
2058 r = loop_device_make_by_path(arg_image, open_flags, /* sector_size= */ UINT32_MAX, loop_flags, LOCK_SH, &d);
2059 if (r < 0)
2060 return log_error_errno(r, "Failed to set up loopback device for %s: %m", arg_image);
07d6072e 2061
2292fa1e
DDM
2062 if (arg_loop_ref) {
2063 r = loop_device_set_filename(d, arg_loop_ref);
2064 if (r < 0)
2065 log_warning_errno(r, "Failed to set loop reference string to '%s', ignoring: %m", arg_loop_ref);
2066 }
88b3300f 2067
2292fa1e
DDM
2068 r = dissect_loop_device_and_warn(
2069 d,
94abea2a 2070 &arg_verity_settings,
2292fa1e
DDM
2071 /* mount_options= */ NULL,
2072 arg_image_policy,
2073 arg_flags,
2074 &m);
94abea2a
LP
2075 if (r < 0)
2076 return r;
2292fa1e
DDM
2077
2078 if (arg_action == ACTION_ATTACH)
2079 return action_attach(m, d);
2080
2081 r = dissected_image_load_verity_sig_partition(
2082 m,
2083 d->fd,
2084 &arg_verity_settings);
2085 if (r < 0)
2086 return log_error_errno(r, "Failed to load verity signature partition: %m");
2087
2088 if (arg_action != ACTION_DISSECT) {
2089 r = dissected_image_decrypt_interactively(
2090 m, NULL,
2091 &arg_verity_settings,
2092 arg_flags);
2093 if (r < 0)
2094 return r;
2095 }
94abea2a
LP
2096 }
2097
37e44c3f 2098 switch (arg_action) {
33973b84 2099
37e44c3f 2100 case ACTION_DISSECT:
6f5ef9e4 2101 return action_dissect(m, d);
37e44c3f
LP
2102
2103 case ACTION_MOUNT:
6f5ef9e4 2104 return action_mount(m, d);
37e44c3f 2105
0cf16924 2106 case ACTION_LIST:
db02190e 2107 case ACTION_MTREE:
37e44c3f
LP
2108 case ACTION_COPY_FROM:
2109 case ACTION_COPY_TO:
b68f4cad
LP
2110 case ACTION_MAKE_ARCHIVE:
2111 return action_list_or_mtree_or_copy_or_make_archive(m, d);
33973b84 2112
06186c4c 2113 case ACTION_WITH:
6f5ef9e4 2114 return action_with(m, d);
06186c4c 2115
a2ea3b2f 2116 default:
04499a70 2117 assert_not_reached();
a2ea3b2f 2118 }
a2ea3b2f 2119}
149afb45
YW
2120
2121DEFINE_MAIN_FUNCTION(run);