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