]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/dissect/dissect.c
copy: Support passing a deny list of files/directories to not copy
[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-symlinks.h"
17 #include "copy.h"
18 #include "device-util.h"
19 #include "devnum-util.h"
20 #include "dissect-image.h"
21 #include "env-util.h"
22 #include "escape.h"
23 #include "fd-util.h"
24 #include "fileio.h"
25 #include "format-table.h"
26 #include "format-util.h"
27 #include "fs-util.h"
28 #include "hexdecoct.h"
29 #include "log.h"
30 #include "loop-util.h"
31 #include "main-func.h"
32 #include "mkdir.h"
33 #include "mount-util.h"
34 #include "mountpoint-util.h"
35 #include "namespace-util.h"
36 #include "parse-argument.h"
37 #include "parse-util.h"
38 #include "path-util.h"
39 #include "pretty-print.h"
40 #include "process-util.h"
41 #include "recurse-dir.h"
42 #include "sha256.h"
43 #include "stat-util.h"
44 #include "string-util.h"
45 #include "strv.h"
46 #include "terminal-util.h"
47 #include "tmpfile-util.h"
48 #include "user-util.h"
49
50 static enum {
51 ACTION_DISSECT,
52 ACTION_MOUNT,
53 ACTION_UMOUNT,
54 ACTION_LIST,
55 ACTION_MTREE,
56 ACTION_WITH,
57 ACTION_COPY_FROM,
58 ACTION_COPY_TO,
59 } arg_action = ACTION_DISSECT;
60 static const char *arg_image = NULL;
61 static const char *arg_path = NULL;
62 static const char *arg_source = NULL;
63 static const char *arg_target = NULL;
64 static DissectImageFlags arg_flags =
65 DISSECT_IMAGE_GENERIC_ROOT |
66 DISSECT_IMAGE_DISCARD_ON_LOOP |
67 DISSECT_IMAGE_RELAX_VAR_CHECK |
68 DISSECT_IMAGE_FSCK |
69 DISSECT_IMAGE_USR_NO_ROOT |
70 DISSECT_IMAGE_GROWFS;
71 static VeritySettings arg_verity_settings = VERITY_SETTINGS_DEFAULT;
72 static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
73 static PagerFlags arg_pager_flags = 0;
74 static bool arg_legend = true;
75 static bool arg_rmdir = false;
76 static char **arg_argv = NULL;
77
78 STATIC_DESTRUCTOR_REGISTER(arg_verity_settings, verity_settings_done);
79 STATIC_DESTRUCTOR_REGISTER(arg_argv, strv_freep);
80
81 static int help(void) {
82 _cleanup_free_ char *link = NULL;
83 int r;
84
85 r = terminal_urlify_man("systemd-dissect", "1", &link);
86 if (r < 0)
87 return log_oom();
88
89 printf("%1$s [OPTIONS...] IMAGE\n"
90 "%1$s [OPTIONS...] --mount IMAGE PATH\n"
91 "%1$s [OPTIONS...] --umount PATH\n"
92 "%1$s [OPTIONS...] --list IMAGE\n"
93 "%1$s [OPTIONS...] --mtree IMAGE\n"
94 "%1$s [OPTIONS...] --with IMAGE [COMMAND…]\n"
95 "%1$s [OPTIONS...] --copy-from IMAGE PATH [TARGET]\n"
96 "%1$s [OPTIONS...] --copy-to IMAGE [SOURCE] PATH\n\n"
97 "%5$sDissect a Discoverable Disk Image (DDI).%6$s\n\n"
98 "%3$sOptions:%4$s\n"
99 " --no-pager Do not pipe output into a pager\n"
100 " --no-legend Do not show the headers and footers\n"
101 " -r --read-only Mount read-only\n"
102 " --fsck=BOOL Run fsck before mounting\n"
103 " --growfs=BOOL Grow file system to partition size, if marked\n"
104 " --mkdir Make mount directory before mounting, if missing\n"
105 " --rmdir Remove mount directory after unmounting\n"
106 " --discard=MODE Choose 'discard' mode (disabled, loop, all, crypto)\n"
107 " --root-hash=HASH Specify root hash for verity\n"
108 " --root-hash-sig=SIG Specify pkcs7 signature of root hash for verity\n"
109 " as a DER encoded PKCS7, either as a path to a file\n"
110 " or as an ASCII base64 encoded string prefixed by\n"
111 " 'base64:'\n"
112 " --verity-data=PATH Specify data file with hash tree for verity if it is\n"
113 " not embedded in IMAGE\n"
114 " --json=pretty|short|off\n"
115 " Generate JSON output\n"
116 "\n%3$sCommands:%4$s\n"
117 " -h --help Show this help\n"
118 " --version Show package version\n"
119 " -m --mount Mount the image to the specified directory\n"
120 " -M Shortcut for --mount --mkdir\n"
121 " -u --umount Unmount the image from the specified directory\n"
122 " -U Shortcut for --umount --rmdir\n"
123 " -l --list List all the files and directories of the specified\n"
124 " OS image\n"
125 " --mtree Show BSD mtree manifest of OS image\n"
126 " --with Mount, run command, unmount\n"
127 " -x --copy-from Copy files from image to host\n"
128 " -a --copy-to Copy files from host to image\n"
129 "\nSee the %2$s for details.\n",
130 program_invocation_short_name,
131 link,
132 ansi_underline(),
133 ansi_normal(),
134 ansi_highlight(),
135 ansi_normal());
136
137 return 0;
138 }
139
140 static int patch_argv(int *argc, char ***argv, char ***buf) {
141 _cleanup_free_ char **l = NULL;
142 char **e;
143
144 assert(argc);
145 assert(*argc >= 0);
146 assert(argv);
147 assert(*argv);
148 assert(buf);
149
150 /* Ugly hack: if --with is included in command line, also insert "--" immediately after it, to make
151 * getopt_long() stop processing switches */
152
153 for (e = *argv + 1; e < *argv + *argc; e++) {
154 assert(*e);
155
156 if (streq(*e, "--with"))
157 break;
158 }
159
160 if (e >= *argv + *argc || streq_ptr(e[1], "--")) {
161 /* No --with used? Or already followed by "--"? Then don't do anything */
162 *buf = NULL;
163 return 0;
164 }
165
166 /* Insert the extra "--" right after the --with */
167 l = new(char*, *argc + 2);
168 if (!l)
169 return log_oom();
170
171 size_t idx = e - *argv + 1;
172 memcpy(l, *argv, sizeof(char*) * idx); /* copy everything up to and including the --with */
173 l[idx] = (char*) "--"; /* insert "--" */
174 memcpy(l + idx + 1, e + 1, sizeof(char*) * (*argc - idx + 1)); /* copy the rest, including trailing NULL entry */
175
176 (*argc)++;
177 (*argv) = l;
178
179 *buf = TAKE_PTR(l);
180 return 1;
181 }
182
183 static int parse_argv(int argc, char *argv[]) {
184
185 enum {
186 ARG_VERSION = 0x100,
187 ARG_NO_PAGER,
188 ARG_NO_LEGEND,
189 ARG_WITH,
190 ARG_DISCARD,
191 ARG_FSCK,
192 ARG_GROWFS,
193 ARG_ROOT_HASH,
194 ARG_ROOT_HASH_SIG,
195 ARG_VERITY_DATA,
196 ARG_MKDIR,
197 ARG_RMDIR,
198 ARG_JSON,
199 ARG_MTREE,
200 };
201
202 static const struct option options[] = {
203 { "help", no_argument, NULL, 'h' },
204 { "version", no_argument, NULL, ARG_VERSION },
205 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
206 { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
207 { "mount", no_argument, NULL, 'm' },
208 { "umount", no_argument, NULL, 'u' },
209 { "with", no_argument, NULL, ARG_WITH },
210 { "read-only", no_argument, NULL, 'r' },
211 { "discard", required_argument, NULL, ARG_DISCARD },
212 { "fsck", required_argument, NULL, ARG_FSCK },
213 { "growfs", required_argument, NULL, ARG_GROWFS },
214 { "root-hash", required_argument, NULL, ARG_ROOT_HASH },
215 { "root-hash-sig", required_argument, NULL, ARG_ROOT_HASH_SIG },
216 { "verity-data", required_argument, NULL, ARG_VERITY_DATA },
217 { "mkdir", no_argument, NULL, ARG_MKDIR },
218 { "rmdir", no_argument, NULL, ARG_RMDIR },
219 { "list", no_argument, NULL, 'l' },
220 { "mtree", no_argument, NULL, ARG_MTREE },
221 { "copy-from", no_argument, NULL, 'x' },
222 { "copy-to", no_argument, NULL, 'a' },
223 { "json", required_argument, NULL, ARG_JSON },
224 {}
225 };
226
227 _cleanup_free_ char **buf = NULL; /* we use free(), not strv_free() here, as we don't copy the strings here */
228 int c, r;
229
230 assert(argc >= 0);
231 assert(argv);
232
233 r = patch_argv(&argc, &argv, &buf);
234 if (r < 0)
235 return r;
236
237 while ((c = getopt_long(argc, argv, "hmurMUlxa", options, NULL)) >= 0) {
238
239 switch (c) {
240
241 case 'h':
242 return help();
243
244 case ARG_VERSION:
245 return version();
246
247 case ARG_NO_PAGER:
248 arg_pager_flags |= PAGER_DISABLE;
249 break;
250
251 case ARG_NO_LEGEND:
252 arg_legend = false;
253 break;
254
255 case 'm':
256 arg_action = ACTION_MOUNT;
257 break;
258
259 case ARG_MKDIR:
260 arg_flags |= DISSECT_IMAGE_MKDIR;
261 break;
262
263 case 'M':
264 /* Shortcut combination of the above two */
265 arg_action = ACTION_MOUNT;
266 arg_flags |= DISSECT_IMAGE_MKDIR;
267 break;
268
269 case 'u':
270 arg_action = ACTION_UMOUNT;
271 break;
272
273 case ARG_RMDIR:
274 arg_rmdir = true;
275 break;
276
277 case 'U':
278 /* Shortcut combination of the above two */
279 arg_action = ACTION_UMOUNT;
280 arg_rmdir = true;
281 break;
282
283 case 'l':
284 arg_action = ACTION_LIST;
285 arg_flags |= DISSECT_IMAGE_READ_ONLY;
286 break;
287
288 case ARG_MTREE:
289 arg_action = ACTION_MTREE;
290 arg_flags |= DISSECT_IMAGE_READ_ONLY;
291 break;
292
293 case ARG_WITH:
294 arg_action = ACTION_WITH;
295 break;
296
297 case 'x':
298 arg_action = ACTION_COPY_FROM;
299 arg_flags |= DISSECT_IMAGE_READ_ONLY;
300 break;
301
302 case 'a':
303 arg_action = ACTION_COPY_TO;
304 break;
305
306 case 'r':
307 arg_flags |= DISSECT_IMAGE_READ_ONLY;
308 break;
309
310 case ARG_DISCARD: {
311 DissectImageFlags flags;
312
313 if (streq(optarg, "disabled"))
314 flags = 0;
315 else if (streq(optarg, "loop"))
316 flags = DISSECT_IMAGE_DISCARD_ON_LOOP;
317 else if (streq(optarg, "all"))
318 flags = DISSECT_IMAGE_DISCARD_ON_LOOP | DISSECT_IMAGE_DISCARD;
319 else if (streq(optarg, "crypt"))
320 flags = DISSECT_IMAGE_DISCARD_ANY;
321 else if (streq(optarg, "list")) {
322 puts("disabled\n"
323 "all\n"
324 "crypt\n"
325 "loop");
326 return 0;
327 } else
328 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
329 "Unknown --discard= parameter: %s",
330 optarg);
331 arg_flags = (arg_flags & ~DISSECT_IMAGE_DISCARD_ANY) | flags;
332
333 break;
334 }
335
336 case ARG_ROOT_HASH: {
337 _cleanup_free_ void *p = NULL;
338 size_t l;
339
340 r = unhexmem(optarg, strlen(optarg), &p, &l);
341 if (r < 0)
342 return log_error_errno(r, "Failed to parse root hash '%s': %m", optarg);
343 if (l < sizeof(sd_id128_t))
344 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
345 "Root hash must be at least 128bit long: %s", optarg);
346
347 free_and_replace(arg_verity_settings.root_hash, p);
348 arg_verity_settings.root_hash_size = l;
349 break;
350 }
351
352 case ARG_ROOT_HASH_SIG: {
353 char *value;
354 size_t l;
355 void *p;
356
357 if ((value = startswith(optarg, "base64:"))) {
358 r = unbase64mem(value, strlen(value), &p, &l);
359 if (r < 0)
360 return log_error_errno(r, "Failed to parse root hash signature '%s': %m", optarg);
361 } else {
362 r = read_full_file(optarg, (char**) &p, &l);
363 if (r < 0)
364 return log_error_errno(r, "Failed to read root hash signature file '%s': %m", optarg);
365 }
366
367 free_and_replace(arg_verity_settings.root_hash_sig, p);
368 arg_verity_settings.root_hash_sig_size = l;
369 break;
370 }
371
372 case ARG_VERITY_DATA:
373 r = parse_path_argument(optarg, false, &arg_verity_settings.data_path);
374 if (r < 0)
375 return r;
376 break;
377
378 case ARG_FSCK:
379 r = parse_boolean(optarg);
380 if (r < 0)
381 return log_error_errno(r, "Failed to parse --fsck= parameter: %s", optarg);
382
383 SET_FLAG(arg_flags, DISSECT_IMAGE_FSCK, r);
384 break;
385
386 case ARG_GROWFS:
387 r = parse_boolean(optarg);
388 if (r < 0)
389 return log_error_errno(r, "Failed to parse --growfs= parameter: %s", optarg);
390
391 SET_FLAG(arg_flags, DISSECT_IMAGE_GROWFS, r);
392 break;
393
394 case ARG_JSON:
395 r = parse_json_argument(optarg, &arg_json_format_flags);
396 if (r <= 0)
397 return r;
398
399 break;
400
401 case '?':
402 return -EINVAL;
403
404 default:
405 assert_not_reached();
406 }
407 }
408
409 switch (arg_action) {
410
411 case ACTION_DISSECT:
412 if (optind + 1 != argc)
413 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
414 "Expected an image file path as only argument.");
415
416 arg_image = argv[optind];
417 arg_flags |= DISSECT_IMAGE_READ_ONLY;
418 break;
419
420 case ACTION_MOUNT:
421 if (optind + 2 != argc)
422 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
423 "Expected an image file path and mount point path as only arguments.");
424
425 arg_image = argv[optind];
426 arg_path = argv[optind + 1];
427 arg_flags |= DISSECT_IMAGE_REQUIRE_ROOT;
428 break;
429
430 case ACTION_UMOUNT:
431 if (optind + 1 != argc)
432 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
433 "Expected a mount point path as only argument.");
434
435 arg_path = argv[optind];
436 break;
437
438 case ACTION_LIST:
439 case ACTION_MTREE:
440 if (optind + 1 != argc)
441 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
442 "Expected an image file path as only argument.");
443
444 arg_image = argv[optind];
445 arg_flags |= DISSECT_IMAGE_READ_ONLY | DISSECT_IMAGE_REQUIRE_ROOT;
446 break;
447
448 case ACTION_COPY_FROM:
449 if (argc < optind + 2 || argc > optind + 3)
450 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
451 "Expected an image file path, a source path and an optional destination path as only arguments.");
452
453 arg_image = argv[optind];
454 arg_source = argv[optind + 1];
455 arg_target = argc > optind + 2 ? argv[optind + 2] : "-" /* this means stdout */ ;
456
457 arg_flags |= DISSECT_IMAGE_READ_ONLY | DISSECT_IMAGE_REQUIRE_ROOT;
458 break;
459
460 case ACTION_COPY_TO:
461 if (argc < optind + 2 || argc > optind + 3)
462 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
463 "Expected an image file path, an optional source path and a destination path as only arguments.");
464
465 arg_image = argv[optind];
466
467 if (argc > optind + 2) {
468 arg_source = argv[optind + 1];
469 arg_target = argv[optind + 2];
470 } else {
471 arg_source = "-"; /* this means stdin */
472 arg_target = argv[optind + 1];
473 }
474
475 arg_flags |= DISSECT_IMAGE_REQUIRE_ROOT;
476 break;
477
478 case ACTION_WITH:
479 if (optind >= argc)
480 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
481 "Expected an image file path and an optional command line.");
482
483 arg_image = argv[optind];
484 if (argc > optind + 1) {
485 arg_argv = strv_copy(argv + optind + 1);
486 if (!arg_argv)
487 return log_oom();
488 }
489
490 break;
491
492 default:
493 assert_not_reached();
494 }
495
496 return 1;
497 }
498
499 static int strv_pair_to_json(char **l, JsonVariant **ret) {
500 _cleanup_strv_free_ char **jl = NULL;
501
502 STRV_FOREACH_PAIR(a, b, l) {
503 char *j;
504
505 j = strjoin(*a, "=", *b);
506 if (!j)
507 return log_oom();
508
509 if (strv_consume(&jl, j) < 0)
510 return log_oom();
511 }
512
513 return json_variant_new_array_strv(ret, jl);
514 }
515
516 static void strv_pair_print(char **l, const char *prefix) {
517 assert(prefix);
518
519 STRV_FOREACH_PAIR(p, q, l)
520 if (p == l)
521 printf("%s %s=%s\n", prefix, *p, *q);
522 else
523 printf("%*s %s=%s\n", (int) strlen(prefix), "", *p, *q);
524 }
525
526 static int get_sysext_scopes(DissectedImage *m, char ***ret_scopes) {
527 _cleanup_strv_free_ char **l = NULL;
528 const char *e;
529
530 assert(m);
531 assert(ret_scopes);
532
533 /* If there's no extension-release file its not a system extension. Otherwise the SYSEXT_SCOPE field
534 * indicates which scope it is for — and it defaults to "system" + "portable" if unset. */
535
536 if (!m->extension_release) {
537 *ret_scopes = NULL;
538 return 0;
539 }
540
541 e = strv_env_pairs_get(m->extension_release, "SYSEXT_SCOPE");
542 if (e)
543 l = strv_split(e, WHITESPACE);
544 else
545 l = strv_new("system", "portable");
546 if (!l)
547 return -ENOMEM;
548
549 *ret_scopes = TAKE_PTR(l);
550 return 1;
551 }
552
553 static int action_dissect(DissectedImage *m, LoopDevice *d) {
554 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
555 _cleanup_(table_unrefp) Table *t = NULL;
556 _cleanup_free_ char *bn = NULL;
557 uint64_t size = UINT64_MAX;
558 int r;
559
560 assert(m);
561 assert(d);
562
563 r = path_extract_filename(arg_image, &bn);
564 if (r < 0)
565 return log_error_errno(r, "Failed to extract file name from image path '%s': %m", arg_image);
566
567 if (arg_json_format_flags & (JSON_FORMAT_OFF|JSON_FORMAT_PRETTY|JSON_FORMAT_PRETTY_AUTO))
568 pager_open(arg_pager_flags);
569
570 if (arg_json_format_flags & JSON_FORMAT_OFF)
571 printf(" Name: %s\n", bn);
572
573 if (ioctl(d->fd, BLKGETSIZE64, &size) < 0)
574 log_debug_errno(errno, "Failed to query size of loopback device: %m");
575 else if (arg_json_format_flags & JSON_FORMAT_OFF)
576 printf(" Size: %s\n", FORMAT_BYTES(size));
577
578 if (arg_json_format_flags & JSON_FORMAT_OFF)
579 putc('\n', stdout);
580
581 r = dissected_image_acquire_metadata(m, 0);
582 if (r == -ENXIO)
583 return log_error_errno(r, "No root partition discovered.");
584 if (r == -EUCLEAN)
585 return log_error_errno(r, "File system check of image failed.");
586 if (r == -EMEDIUMTYPE)
587 log_warning_errno(r, "Not a valid OS image, no os-release file included. Proceeding anyway.");
588 else if (r == -EUNATCH)
589 log_warning_errno(r, "OS image is encrypted, proceeding without showing OS image metadata.");
590 else if (r == -EBUSY)
591 log_warning_errno(r, "OS image is currently in use, proceeding without showing OS image metadata.");
592 else if (r < 0)
593 return log_error_errno(r, "Failed to acquire image metadata: %m");
594 else if (arg_json_format_flags & JSON_FORMAT_OFF) {
595 _cleanup_strv_free_ char **sysext_scopes = NULL;
596
597 if (m->hostname)
598 printf(" Hostname: %s\n", m->hostname);
599
600 if (!sd_id128_is_null(m->machine_id))
601 printf("Machine ID: " SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(m->machine_id));
602
603 strv_pair_print(m->machine_info,
604 "Mach. Info:");
605 strv_pair_print(m->os_release,
606 "OS Release:");
607 strv_pair_print(m->initrd_release,
608 "initrd R.:");
609 strv_pair_print(m->extension_release,
610 " Ext. Rel.:");
611
612 if (m->hostname ||
613 !sd_id128_is_null(m->machine_id) ||
614 !strv_isempty(m->machine_info) ||
615 !strv_isempty(m->os_release) ||
616 !strv_isempty(m->initrd_release) ||
617 !strv_isempty(m->extension_release))
618 putc('\n', stdout);
619
620 printf(" Use As: %s bootable system for UEFI\n", COLOR_MARK_BOOL(m->partitions[PARTITION_ESP].found));
621
622 if (m->has_init_system >= 0)
623 printf(" %s bootable system for container\n", COLOR_MARK_BOOL(m->has_init_system));
624
625 printf(" %s portable service\n",
626 COLOR_MARK_BOOL(strv_env_pairs_get(m->os_release, "PORTABLE_PREFIXES")));
627 printf(" %s initrd\n",
628 COLOR_MARK_BOOL(!strv_isempty(m->initrd_release)));
629
630 r = get_sysext_scopes(m, &sysext_scopes);
631 if (r < 0)
632 return log_error_errno(r, "Failed to parse SYSEXT_SCOPE: %m");
633
634 printf(" %s extension for system\n",
635 COLOR_MARK_BOOL(strv_contains(sysext_scopes, "system")));
636 printf(" %s extension for initrd\n",
637 COLOR_MARK_BOOL(strv_contains(sysext_scopes, "initrd")));
638 printf(" %s extension for portable service\n",
639 COLOR_MARK_BOOL(strv_contains(sysext_scopes, "portable")));
640
641 putc('\n', stdout);
642 } else {
643 _cleanup_(json_variant_unrefp) JsonVariant *mi = NULL, *osr = NULL, *irdr = NULL, *exr = NULL;
644 _cleanup_strv_free_ char **sysext_scopes = NULL;
645
646 if (!strv_isempty(m->machine_info)) {
647 r = strv_pair_to_json(m->machine_info, &mi);
648 if (r < 0)
649 return log_oom();
650 }
651
652 if (!strv_isempty(m->os_release)) {
653 r = strv_pair_to_json(m->os_release, &osr);
654 if (r < 0)
655 return log_oom();
656 }
657
658 if (!strv_isempty(m->initrd_release)) {
659 r = strv_pair_to_json(m->initrd_release, &irdr);
660 if (r < 0)
661 return log_oom();
662 }
663
664 if (!strv_isempty(m->extension_release)) {
665 r = strv_pair_to_json(m->extension_release, &exr);
666 if (r < 0)
667 return log_oom();
668 }
669
670 r = get_sysext_scopes(m, &sysext_scopes);
671 if (r < 0)
672 return log_error_errno(r, "Failed to parse SYSEXT_SCOPE: %m");
673
674 r = json_build(&v, JSON_BUILD_OBJECT(
675 JSON_BUILD_PAIR("name", JSON_BUILD_STRING(bn)),
676 JSON_BUILD_PAIR("size", JSON_BUILD_INTEGER(size)),
677 JSON_BUILD_PAIR_CONDITION(m->hostname, "hostname", JSON_BUILD_STRING(m->hostname)),
678 JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(m->machine_id), "machineId", JSON_BUILD_ID128(m->machine_id)),
679 JSON_BUILD_PAIR_CONDITION(mi, "machineInfo", JSON_BUILD_VARIANT(mi)),
680 JSON_BUILD_PAIR_CONDITION(osr, "osRelease", JSON_BUILD_VARIANT(osr)),
681 JSON_BUILD_PAIR_CONDITION(osr, "initrdRelease", JSON_BUILD_VARIANT(irdr)),
682 JSON_BUILD_PAIR_CONDITION(exr, "extensionRelease", JSON_BUILD_VARIANT(exr)),
683 JSON_BUILD_PAIR("useBootableUefi", JSON_BUILD_BOOLEAN(m->partitions[PARTITION_ESP].found)),
684 JSON_BUILD_PAIR_CONDITION(m->has_init_system >= 0, "useBootableContainer", JSON_BUILD_BOOLEAN(m->has_init_system)),
685 JSON_BUILD_PAIR("useInitrd", JSON_BUILD_BOOLEAN(!strv_isempty(m->initrd_release))),
686 JSON_BUILD_PAIR("usePortableService", JSON_BUILD_BOOLEAN(strv_env_pairs_get(m->os_release, "PORTABLE_MATCHES"))),
687 JSON_BUILD_PAIR("useSystemExtension", JSON_BUILD_BOOLEAN(strv_contains(sysext_scopes, "system"))),
688 JSON_BUILD_PAIR("useInitRDExtension", JSON_BUILD_BOOLEAN(strv_contains(sysext_scopes, "initrd"))),
689 JSON_BUILD_PAIR("usePortableExtension", JSON_BUILD_BOOLEAN(strv_contains(sysext_scopes, "portable")))));
690 if (r < 0)
691 return log_oom();
692 }
693
694 t = table_new("rw", "designator", "partition uuid", "partition label", "fstype", "architecture", "verity", "growfs", "node", "partno");
695 if (!t)
696 return log_oom();
697
698 table_set_ersatz_string(t, TABLE_ERSATZ_DASH);
699 (void) table_set_align_percent(t, table_get_cell(t, 0, 7), 100);
700
701 for (PartitionDesignator i = 0; i < _PARTITION_DESIGNATOR_MAX; i++) {
702 DissectedPartition *p = m->partitions + i;
703
704 if (!p->found)
705 continue;
706
707 r = table_add_many(
708 t,
709 TABLE_STRING, p->rw ? "rw" : "ro",
710 TABLE_STRING, partition_designator_to_string(i));
711 if (r < 0)
712 return table_log_add_error(r);
713
714 if (sd_id128_is_null(p->uuid))
715 r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
716 else
717 r = table_add_cell(t, NULL, TABLE_UUID, &p->uuid);
718 if (r < 0)
719 return table_log_add_error(r);
720
721 r = table_add_many(
722 t,
723 TABLE_STRING, p->label,
724 TABLE_STRING, p->fstype,
725 TABLE_STRING, architecture_to_string(p->architecture));
726 if (r < 0)
727 return table_log_add_error(r);
728
729 if (arg_verity_settings.data_path)
730 r = table_add_cell(t, NULL, TABLE_STRING, "external");
731 else if (dissected_image_verity_candidate(m, i))
732 r = table_add_cell(t, NULL, TABLE_STRING,
733 dissected_image_verity_sig_ready(m, i) ? "signed" :
734 yes_no(dissected_image_verity_ready(m, i)));
735 else
736 r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
737 if (r < 0)
738 return table_log_add_error(r);
739
740 r = table_add_many(t, TABLE_BOOLEAN, (int) p->growfs);
741 if (r < 0)
742 return table_log_add_error(r);
743
744 if (p->partno < 0) /* no partition table, naked file system */ {
745 r = table_add_cell(t, NULL, TABLE_STRING, arg_image);
746 if (r < 0)
747 return table_log_add_error(r);
748
749 r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
750 } else {
751 r = table_add_cell(t, NULL, TABLE_STRING, p->node);
752 if (r < 0)
753 return table_log_add_error(r);
754
755 r = table_add_cell(t, NULL, TABLE_INT, &p->partno);
756 }
757 if (r < 0)
758 return table_log_add_error(r);
759 }
760
761 if (arg_json_format_flags & JSON_FORMAT_OFF) {
762 (void) table_set_header(t, arg_legend);
763
764 r = table_print(t, NULL);
765 if (r < 0)
766 return table_log_print_error(r);
767 } else {
768 _cleanup_(json_variant_unrefp) JsonVariant *jt = NULL;
769
770 r = table_to_json(t, &jt);
771 if (r < 0)
772 return log_error_errno(r, "Failed to convert table to JSON: %m");
773
774 r = json_variant_set_field(&v, "mounts", jt);
775 if (r < 0)
776 return log_oom();
777
778 json_variant_dump(v, arg_json_format_flags, stdout, NULL);
779 }
780
781 return 0;
782 }
783
784 static int action_mount(DissectedImage *m, LoopDevice *d) {
785 int r;
786
787 assert(m);
788 assert(d);
789
790 r = dissected_image_decrypt_interactively(
791 m, NULL,
792 &arg_verity_settings,
793 arg_flags);
794 if (r < 0)
795 return r;
796
797 r = dissected_image_mount_and_warn(m, arg_path, UID_INVALID, UID_INVALID, arg_flags);
798 if (r < 0)
799 return r;
800
801 r = loop_device_flock(d, LOCK_UN);
802 if (r < 0)
803 return log_error_errno(r, "Failed to unlock loopback block device: %m");
804
805 r = dissected_image_relinquish(m);
806 if (r < 0)
807 return log_error_errno(r, "Failed to relinquish DM and loopback block devices: %m");
808
809 return 0;
810 }
811
812 static int list_print_item(
813 RecurseDirEvent event,
814 const char *path,
815 int dir_fd,
816 int inode_fd,
817 const struct dirent *de,
818 const struct statx *sx,
819 void *userdata) {
820
821 assert_se(path);
822
823 if (event == RECURSE_DIR_ENTER)
824 printf("%s%s/%s\n", path, ansi_grey(), ansi_normal());
825 else if (event == RECURSE_DIR_ENTRY)
826 printf("%s\n", path);
827
828 return RECURSE_DIR_CONTINUE;
829 }
830
831 static int get_file_sha256(int inode_fd, uint8_t ret[static SHA256_DIGEST_SIZE]) {
832 _cleanup_close_ int fd = -1;
833 struct sha256_ctx ctx;
834
835 /* convert O_PATH fd into a regular one */
836 fd = fd_reopen(inode_fd, O_RDONLY|O_CLOEXEC);
837 if (fd < 0)
838 return fd;
839
840 /* Calculating the SHA sum might be slow, hence let's flush STDOUT first, to give user an idea where we are slow. */
841 fflush(stdout);
842
843 sha256_init_ctx(&ctx);
844
845 for (;;) {
846 uint8_t buffer[64 * 1024];
847 ssize_t n;
848
849 n = read(fd, buffer, sizeof(buffer));
850 if (n < 0)
851 return -errno;
852 if (n == 0)
853 break;
854
855 sha256_process_bytes(buffer, n, &ctx);
856 }
857
858 sha256_finish_ctx(&ctx, ret);
859 return 0;
860 }
861
862 static int mtree_print_item(
863 RecurseDirEvent event,
864 const char *path,
865 int dir_fd,
866 int inode_fd,
867 const struct dirent *de,
868 const struct statx *sx,
869 void *userdata) {
870
871 int r;
872
873 assert_se(path);
874 assert_se(sx);
875
876 if (IN_SET(event, RECURSE_DIR_ENTER, RECURSE_DIR_ENTRY)) {
877 _cleanup_free_ char *escaped = NULL;
878
879 if (isempty(path))
880 path = ".";
881 else {
882 /* BSD mtree uses either C or octal escaping, and covers whitespace, comments and glob characters. We use C style escaping and follow suit */
883 escaped = xescape(path, WHITESPACE COMMENTS GLOB_CHARS);
884 if (!escaped)
885 return log_oom();
886
887 path = escaped;
888 }
889
890 printf("%s", isempty(path) ? "." : path);
891
892 if (FLAGS_SET(sx->stx_mask, STATX_TYPE)) {
893 if (S_ISDIR(sx->stx_mode))
894 printf("%s/%s", ansi_grey(), ansi_normal());
895
896 printf(" %stype=%s%s%s%s",
897 ansi_grey(),
898 ansi_normal(),
899 S_ISDIR(sx->stx_mode) ? ansi_highlight_blue() :
900 S_ISLNK(sx->stx_mode) ? ansi_highlight_cyan() :
901 (S_ISFIFO(sx->stx_mode) || S_ISCHR(sx->stx_mode) || S_ISBLK(sx->stx_mode)) ? ansi_highlight_yellow4() :
902 S_ISSOCK(sx->stx_mode) ? ansi_highlight_magenta() : "",
903 ASSERT_PTR(S_ISDIR(sx->stx_mode) ? "dir" :
904 S_ISREG(sx->stx_mode) ? "file" :
905 S_ISLNK(sx->stx_mode) ? "link" :
906 S_ISFIFO(sx->stx_mode) ? "fifo" :
907 S_ISBLK(sx->stx_mode) ? "block" :
908 S_ISCHR(sx->stx_mode) ? "char" :
909 S_ISSOCK(sx->stx_mode) ? "socket" : NULL),
910 ansi_normal());
911 }
912
913 if (FLAGS_SET(sx->stx_mask, STATX_MODE) && (!FLAGS_SET(sx->stx_mask, STATX_TYPE) || !S_ISLNK(sx->stx_mode)))
914 printf(" %smode=%s%04o",
915 ansi_grey(),
916 ansi_normal(),
917 (unsigned) (sx->stx_mode & 0777));
918
919 if (FLAGS_SET(sx->stx_mask, STATX_UID))
920 printf(" %suid=%s" UID_FMT,
921 ansi_grey(),
922 ansi_normal(),
923 sx->stx_uid);
924
925 if (FLAGS_SET(sx->stx_mask, STATX_GID))
926 printf(" %sgid=%s" GID_FMT,
927 ansi_grey(),
928 ansi_normal(),
929 sx->stx_gid);
930
931 if (FLAGS_SET(sx->stx_mask, STATX_TYPE|STATX_SIZE) && S_ISREG(sx->stx_mode)) {
932 printf(" %ssize=%s%" PRIu64,
933 ansi_grey(),
934 ansi_normal(),
935 (uint64_t) sx->stx_size);
936
937 if (inode_fd >= 0 && sx->stx_size > 0) {
938 uint8_t hash[SHA256_DIGEST_SIZE];
939
940 r = get_file_sha256(inode_fd, hash);
941 if (r < 0)
942 log_warning_errno(r, "Failed to calculate file SHA256 sum for '%s', ignoring: %m", path);
943 else {
944 _cleanup_free_ char *h = NULL;
945
946 h = hexmem(hash, sizeof(hash));
947 if (!h)
948 return log_oom();
949
950 printf(" %ssha256sum=%s%s",
951 ansi_grey(),
952 ansi_normal(),
953 h);
954 }
955 }
956 }
957
958 if (FLAGS_SET(sx->stx_mask, STATX_TYPE) && S_ISLNK(sx->stx_mode) && inode_fd >= 0) {
959 _cleanup_free_ char *target = NULL;
960
961 r = readlinkat_malloc(inode_fd, "", &target);
962 if (r < 0)
963 log_warning_errno(r, "Failed to read symlink '%s', ignoring: %m", path);
964 else {
965 _cleanup_free_ char *target_escaped = NULL;
966
967 target_escaped = xescape(target, WHITESPACE COMMENTS GLOB_CHARS);
968 if (!target_escaped)
969 return log_oom();
970
971 printf(" %slink=%s%s",
972 ansi_grey(),
973 ansi_normal(),
974 target_escaped);
975 }
976 }
977
978 if (FLAGS_SET(sx->stx_mask, STATX_TYPE) && (S_ISBLK(sx->stx_mode) || S_ISCHR(sx->stx_mode)))
979 printf(" %sdevice=%slinux,%" PRIu64 ",%" PRIu64,
980 ansi_grey(),
981 ansi_normal(),
982 (uint64_t) sx->stx_rdev_major,
983 (uint64_t) sx->stx_rdev_minor);
984
985 printf("\n");
986 }
987
988 return RECURSE_DIR_CONTINUE;
989 }
990
991 static int action_list_or_mtree_or_copy(DissectedImage *m, LoopDevice *d) {
992 _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
993 _cleanup_(rmdir_and_freep) char *created_dir = NULL;
994 _cleanup_free_ char *temp = NULL;
995 int r;
996
997 assert(m);
998 assert(d);
999
1000 r = dissected_image_decrypt_interactively(
1001 m, NULL,
1002 &arg_verity_settings,
1003 arg_flags);
1004 if (r < 0)
1005 return r;
1006
1007 r = detach_mount_namespace();
1008 if (r < 0)
1009 return log_error_errno(r, "Failed to detach mount namespace: %m");
1010
1011 r = tempfn_random_child(NULL, program_invocation_short_name, &temp);
1012 if (r < 0)
1013 return log_error_errno(r, "Failed to generate temporary mount directory: %m");
1014
1015 r = mkdir_p(temp, 0700);
1016 if (r < 0)
1017 return log_error_errno(r, "Failed to create mount point: %m");
1018
1019 created_dir = TAKE_PTR(temp);
1020
1021 r = dissected_image_mount_and_warn(m, created_dir, UID_INVALID, UID_INVALID, arg_flags);
1022 if (r < 0)
1023 return r;
1024
1025 mounted_dir = TAKE_PTR(created_dir);
1026
1027 r = loop_device_flock(d, LOCK_UN);
1028 if (r < 0)
1029 return log_error_errno(r, "Failed to unlock loopback block device: %m");
1030
1031 r = dissected_image_relinquish(m);
1032 if (r < 0)
1033 return log_error_errno(r, "Failed to relinquish DM and loopback block devices: %m");
1034
1035 if (arg_action == ACTION_COPY_FROM) {
1036 _cleanup_close_ int source_fd = -1, target_fd = -1;
1037
1038 source_fd = chase_symlinks_and_open(arg_source, mounted_dir, CHASE_PREFIX_ROOT|CHASE_WARN, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL);
1039 if (source_fd < 0)
1040 return log_error_errno(source_fd, "Failed to open source path '%s' in image '%s': %m", arg_source, arg_image);
1041
1042 /* Copying to stdout? */
1043 if (streq(arg_target, "-")) {
1044 r = copy_bytes(source_fd, STDOUT_FILENO, UINT64_MAX, COPY_REFLINK);
1045 if (r < 0)
1046 return log_error_errno(r, "Failed to copy bytes from %s in mage '%s' to stdout: %m", arg_source, arg_image);
1047
1048 /* When we copy to stdout we don't copy any attributes (i.e. no access mode, no ownership, no xattr, no times) */
1049 return 0;
1050 }
1051
1052 /* Try to copy as directory? */
1053 r = copy_directory_fd(source_fd, arg_target, COPY_REFLINK|COPY_MERGE_EMPTY|COPY_SIGINT|COPY_HARDLINKS);
1054 if (r >= 0)
1055 return 0;
1056 if (r != -ENOTDIR)
1057 return log_error_errno(r, "Failed to copy %s in image '%s' to '%s': %m", arg_source, arg_image, arg_target);
1058
1059 r = fd_verify_regular(source_fd);
1060 if (r == -EISDIR)
1061 return log_error_errno(r, "Target '%s' exists already and is not a directory.", arg_target);
1062 if (r < 0)
1063 return log_error_errno(r, "Source path %s in image '%s' is neither regular file nor directory, refusing: %m", arg_source, arg_image);
1064
1065 /* Nah, it's a plain file! */
1066 target_fd = open(arg_target, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0600);
1067 if (target_fd < 0)
1068 return log_error_errno(errno, "Failed to create regular file at target path '%s': %m", arg_target);
1069
1070 r = copy_bytes(source_fd, target_fd, UINT64_MAX, COPY_REFLINK);
1071 if (r < 0)
1072 return log_error_errno(r, "Failed to copy bytes from %s in mage '%s' to '%s': %m", arg_source, arg_image, arg_target);
1073
1074 (void) copy_xattr(source_fd, target_fd, 0);
1075 (void) copy_access(source_fd, target_fd);
1076 (void) copy_times(source_fd, target_fd, 0);
1077
1078 /* When this is a regular file we don't copy ownership! */
1079
1080 } else if (arg_action == ACTION_COPY_TO) {
1081 _cleanup_close_ int source_fd = -1, target_fd = -1, dfd = -1;
1082 _cleanup_free_ char *dn = NULL, *bn = NULL;
1083
1084 r = path_extract_directory(arg_target, &dn);
1085 if (r < 0)
1086 return log_error_errno(r, "Failed to extract directory from target path '%s': %m", arg_target);
1087 r = path_extract_filename(arg_target, &bn);
1088 if (r < 0)
1089 return log_error_errno(r, "Failed to extract filename from target path '%s': %m", arg_target);
1090
1091 r = chase_symlinks(dn, mounted_dir, CHASE_PREFIX_ROOT|CHASE_WARN, NULL, &dfd);
1092 if (r < 0)
1093 return log_error_errno(r, "Failed to open '%s': %m", dn);
1094
1095 /* Are we reading from stdin? */
1096 if (streq(arg_source, "-")) {
1097 target_fd = openat(dfd, bn, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_EXCL, 0644);
1098 if (target_fd < 0)
1099 return log_error_errno(errno, "Failed to open target file '%s': %m", arg_target);
1100
1101 r = copy_bytes(STDIN_FILENO, target_fd, UINT64_MAX, COPY_REFLINK);
1102 if (r < 0)
1103 return log_error_errno(r, "Failed to copy bytes from stdin to '%s' in image '%s': %m", arg_target, arg_image);
1104
1105 /* When we copy from stdin we don't copy any attributes (i.e. no access mode, no ownership, no xattr, no times) */
1106 return 0;
1107 }
1108
1109 source_fd = open(arg_source, O_RDONLY|O_CLOEXEC|O_NOCTTY);
1110 if (source_fd < 0)
1111 return log_error_errno(source_fd, "Failed to open source path '%s': %m", arg_source);
1112
1113 r = fd_verify_regular(source_fd);
1114 if (r < 0) {
1115 if (r != -EISDIR)
1116 return log_error_errno(r, "Source '%s' is neither regular file nor directory: %m", arg_source);
1117
1118 /* We are looking at a directory. */
1119
1120 target_fd = openat(dfd, bn, O_RDONLY|O_DIRECTORY|O_CLOEXEC);
1121 if (target_fd < 0) {
1122 if (errno != ENOENT)
1123 return log_error_errno(errno, "Failed to open destination '%s': %m", arg_target);
1124
1125 r = copy_tree_at(source_fd, ".", dfd, bn, UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS, NULL);
1126 } else
1127 r = copy_tree_at(source_fd, ".", target_fd, ".", UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS, NULL);
1128 if (r < 0)
1129 return log_error_errno(r, "Failed to copy '%s' to '%s' in image '%s': %m", arg_source, arg_target, arg_image);
1130
1131 return 0;
1132 }
1133
1134 /* We area looking at a regular file */
1135 target_fd = openat(dfd, basename(arg_target), O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_EXCL, 0600);
1136 if (target_fd < 0)
1137 return log_error_errno(errno, "Failed to open target file '%s': %m", arg_target);
1138
1139 r = copy_bytes(source_fd, target_fd, UINT64_MAX, COPY_REFLINK);
1140 if (r < 0)
1141 return log_error_errno(r, "Failed to copy bytes from '%s' to '%s' in image '%s': %m", arg_source, arg_target, arg_image);
1142
1143 (void) copy_xattr(source_fd, target_fd, 0);
1144 (void) copy_access(source_fd, target_fd);
1145 (void) copy_times(source_fd, target_fd, 0);
1146
1147 /* When this is a regular file we don't copy ownership! */
1148
1149 } else {
1150 _cleanup_close_ int dfd = -1;
1151
1152 dfd = open(mounted_dir, O_DIRECTORY|O_CLOEXEC|O_RDONLY);
1153 if (dfd < 0)
1154 return log_error_errno(errno, "Failed to open mount directory: %m");
1155
1156 pager_open(arg_pager_flags);
1157
1158 if (arg_action == ACTION_LIST)
1159 r = recurse_dir(dfd, NULL, 0, UINT_MAX, RECURSE_DIR_SORT, list_print_item, NULL);
1160 else if (arg_action == ACTION_MTREE)
1161 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);
1162 else
1163 assert_not_reached();
1164 if (r < 0)
1165 return log_error_errno(r, "Failed to list image: %m");
1166 }
1167
1168 return 0;
1169 }
1170
1171 static int action_umount(const char *path) {
1172 _cleanup_close_ int fd = -1;
1173 _cleanup_free_ char *canonical = NULL, *devname = NULL;
1174 _cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
1175 dev_t devno;
1176 int r;
1177
1178 fd = chase_symlinks_and_open(path, NULL, 0, O_DIRECTORY, &canonical);
1179 if (fd == -ENOTDIR)
1180 return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "'%s' is not a directory", path);
1181 if (fd < 0)
1182 return log_error_errno(fd, "Failed to resolve path '%s': %m", path);
1183
1184 r = fd_is_mount_point(fd, NULL, 0);
1185 if (r == 0)
1186 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "'%s' is not a mount point", canonical);
1187 if (r < 0)
1188 return log_error_errno(r, "Failed to determine whether '%s' is a mount point: %m", canonical);
1189
1190 r = fd_get_whole_disk(fd, /*backing=*/ true, &devno);
1191 if (r < 0)
1192 return log_error_errno(r, "Failed to find backing block device for '%s': %m", canonical);
1193
1194 r = devname_from_devnum(S_IFBLK, devno, &devname);
1195 if (r < 0)
1196 return log_error_errno(r, "Failed to get devname of block device " DEVNUM_FORMAT_STR ": %m",
1197 DEVNUM_FORMAT_VAL(devno));
1198
1199 r = loop_device_open_from_path(devname, 0, LOCK_EX, &d);
1200 if (r < 0)
1201 return log_error_errno(r, "Failed to open loop device '%s': %m", devname);
1202
1203 /* We've locked the loop device, now we're ready to unmount. To allow the unmount to succeed, we have
1204 * to close the O_PATH fd we opened earlier. */
1205 fd = safe_close(fd);
1206
1207 r = umount_recursive(canonical, 0);
1208 if (r < 0)
1209 return log_error_errno(r, "Failed to unmount '%s': %m", canonical);
1210
1211 /* We managed to lock and unmount successfully? That means we can try to remove the loop device.*/
1212 loop_device_unrelinquish(d);
1213
1214 if (arg_rmdir) {
1215 r = RET_NERRNO(rmdir(canonical));
1216 if (r < 0)
1217 return log_error_errno(r, "Failed to remove mount directory '%s': %m", canonical);
1218 }
1219
1220 return 0;
1221 }
1222
1223 static int action_with(DissectedImage *m, LoopDevice *d) {
1224 _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
1225 _cleanup_(rmdir_and_freep) char *created_dir = NULL;
1226 _cleanup_free_ char *temp = NULL;
1227 int r, rcode;
1228
1229 r = dissected_image_decrypt_interactively(
1230 m, NULL,
1231 &arg_verity_settings,
1232 arg_flags);
1233 if (r < 0)
1234 return r;
1235
1236 r = tempfn_random_child(NULL, program_invocation_short_name, &temp);
1237 if (r < 0)
1238 return log_error_errno(r, "Failed to generate temporary mount directory: %m");
1239
1240 r = mkdir_p(temp, 0700);
1241 if (r < 0)
1242 return log_error_errno(r, "Failed to create mount point: %m");
1243
1244 created_dir = TAKE_PTR(temp);
1245
1246 r = dissected_image_mount_and_warn(m, created_dir, UID_INVALID, UID_INVALID, arg_flags);
1247 if (r < 0)
1248 return r;
1249
1250 mounted_dir = TAKE_PTR(created_dir);
1251
1252 r = dissected_image_relinquish(m);
1253 if (r < 0)
1254 return log_error_errno(r, "Failed to relinquish DM and loopback block devices: %m");
1255
1256 r = loop_device_flock(d, LOCK_UN);
1257 if (r < 0)
1258 return log_error_errno(r, "Failed to unlock loopback block device: %m");
1259
1260 rcode = safe_fork("(with)", FORK_CLOSE_ALL_FDS|FORK_LOG|FORK_WAIT, NULL);
1261 if (rcode == 0) {
1262 /* Child */
1263
1264 if (chdir(mounted_dir) < 0) {
1265 log_error_errno(errno, "Failed to change to '%s' directory: %m", mounted_dir);
1266 _exit(EXIT_FAILURE);
1267 }
1268
1269 if (setenv("SYSTEMD_DISSECT_ROOT", mounted_dir, /* overwrite= */ true) < 0) {
1270 log_error_errno(errno, "Failed to set $SYSTEMD_DISSECT_ROOT: %m");
1271 _exit(EXIT_FAILURE);
1272 }
1273
1274 if (strv_isempty(arg_argv)) {
1275 const char *sh;
1276
1277 sh = secure_getenv("SHELL");
1278 if (sh) {
1279 execvp(sh, STRV_MAKE(sh));
1280 log_warning_errno(errno, "Failed to execute $SHELL, falling back to /bin/sh: %m");
1281 }
1282
1283 execl("/bin/sh", "sh", NULL);
1284 log_error_errno(errno, "Failed to invoke /bin/sh: %m");
1285 } else {
1286 execvp(arg_argv[0], arg_argv);
1287 log_error_errno(errno, "Failed to execute '%s': %m", arg_argv[0]);
1288 }
1289
1290 _exit(EXIT_FAILURE);
1291 }
1292
1293 /* Let's manually detach everything, to make things synchronous */
1294 r = loop_device_flock(d, LOCK_SH);
1295 if (r < 0)
1296 log_warning_errno(r, "Failed to lock loopback block device, ignoring: %m");
1297
1298 r = umount_recursive(mounted_dir, 0);
1299 if (r < 0)
1300 log_warning_errno(r, "Failed to unmount '%s', ignoring: %m", mounted_dir);
1301 else
1302 loop_device_unrelinquish(d); /* Let's try to destroy the loopback device */
1303
1304 created_dir = TAKE_PTR(mounted_dir);
1305
1306 if (rmdir(created_dir) < 0)
1307 log_warning_errno(r, "Failed to remove directory '%s', ignoring: %m", created_dir);
1308
1309 temp = TAKE_PTR(created_dir);
1310
1311 return rcode;
1312 }
1313
1314 static int run(int argc, char *argv[]) {
1315 _cleanup_(dissected_image_unrefp) DissectedImage *m = NULL;
1316 _cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
1317 int r;
1318
1319 log_parse_environment();
1320 log_open();
1321
1322 r = parse_argv(argc, argv);
1323 if (r <= 0)
1324 return r;
1325
1326 if (arg_action == ACTION_UMOUNT)
1327 return action_umount(arg_path);
1328
1329 r = verity_settings_load(
1330 &arg_verity_settings,
1331 arg_image, NULL, NULL);
1332 if (r < 0)
1333 return log_error_errno(r, "Failed to read verity artifacts for %s: %m", arg_image);
1334
1335 if (arg_verity_settings.data_path)
1336 arg_flags |= DISSECT_IMAGE_NO_PARTITION_TABLE; /* We only support Verity per file system,
1337 * hence if there's external Verity data
1338 * available we turn off partition table
1339 * support */
1340
1341 r = loop_device_make_by_path(
1342 arg_image,
1343 FLAGS_SET(arg_flags, DISSECT_IMAGE_DEVICE_READ_ONLY) ? O_RDONLY : O_RDWR,
1344 FLAGS_SET(arg_flags, DISSECT_IMAGE_NO_PARTITION_TABLE) ? 0 : LO_FLAGS_PARTSCAN,
1345 LOCK_SH,
1346 &d);
1347 if (r < 0)
1348 return log_error_errno(r, "Failed to set up loopback device for %s: %m", arg_image);
1349
1350 r = dissect_loop_device_and_warn(
1351 d,
1352 &arg_verity_settings,
1353 NULL,
1354 arg_flags,
1355 &m);
1356 if (r < 0)
1357 return r;
1358
1359 r = dissected_image_load_verity_sig_partition(
1360 m,
1361 d->fd,
1362 &arg_verity_settings);
1363 if (r < 0)
1364 return log_error_errno(r, "Failed to load verity signature partition: %m");
1365
1366 switch (arg_action) {
1367
1368 case ACTION_DISSECT:
1369 r = action_dissect(m, d);
1370 break;
1371
1372 case ACTION_MOUNT:
1373 r = action_mount(m, d);
1374 break;
1375
1376 case ACTION_LIST:
1377 case ACTION_MTREE:
1378 case ACTION_COPY_FROM:
1379 case ACTION_COPY_TO:
1380 r = action_list_or_mtree_or_copy(m, d);
1381 break;
1382
1383 case ACTION_WITH:
1384 r = action_with(m, d);
1385 break;
1386
1387 default:
1388 assert_not_reached();
1389 }
1390
1391 return r;
1392 }
1393
1394 DEFINE_MAIN_FUNCTION(run);