]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/dissect/dissect.c
dissect-image: introduce new get_common_dissect_directory() helper
[thirdparty/systemd.git] / src / dissect / dissect.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
a2ea3b2f
LP
2
3#include <fcntl.h>
a2ea3b2f 4#include <getopt.h>
e08f94ac
LP
5#include <linux/loop.h>
6#include <stdio.h>
19df770f 7#include <sys/file.h>
16b74592
LP
8#include <sys/ioctl.h>
9#include <sys/mount.h>
a2ea3b2f 10
ac1f1adf
DDM
11#include "sd-device.h"
12
a2ea3b2f 13#include "architecture.h"
ac1f1adf 14#include "blockdev-util.h"
d6b4d1c7 15#include "build.h"
f461a28d 16#include "chase.h"
33973b84 17#include "copy.h"
ca822829
YW
18#include "device-util.h"
19#include "devnum-util.h"
0305cf6e 20#include "discover-image.h"
a2ea3b2f 21#include "dissect-image.h"
994c9c70 22#include "env-util.h"
db02190e 23#include "escape.h"
33973b84 24#include "fd-util.h"
89e62e0b 25#include "fileio.h"
89d00f2e 26#include "format-table.h"
16b74592 27#include "format-util.h"
33973b84 28#include "fs-util.h"
4623e8e6 29#include "hexdecoct.h"
a2ea3b2f
LP
30#include "log.h"
31#include "loop-util.h"
149afb45 32#include "main-func.h"
33973b84
LP
33#include "mkdir.h"
34#include "mount-util.h"
ac1f1adf 35#include "mountpoint-util.h"
33973b84 36#include "namespace-util.h"
614b022c 37#include "parse-argument.h"
e475f729 38#include "parse-util.h"
e7cbe5cb 39#include "path-util.h"
5c05f062 40#include "pretty-print.h"
06186c4c 41#include "process-util.h"
0cf16924 42#include "recurse-dir.h"
db02190e 43#include "sha256.h"
33973b84 44#include "stat-util.h"
a2ea3b2f 45#include "string-util.h"
a1edd22e 46#include "strv.h"
5c05f062 47#include "terminal-util.h"
33973b84 48#include "tmpfile-util.h"
8e1ac16b 49#include "uid-classification.h"
2d3a5a73 50#include "user-util.h"
d7688568 51#include "vpick.h"
a2ea3b2f
LP
52
53static enum {
54 ACTION_DISSECT,
55 ACTION_MOUNT,
ac1f1adf 56 ACTION_UMOUNT,
07d6072e
LP
57 ACTION_ATTACH,
58 ACTION_DETACH,
0cf16924 59 ACTION_LIST,
db02190e 60 ACTION_MTREE,
06186c4c 61 ACTION_WITH,
33973b84
LP
62 ACTION_COPY_FROM,
63 ACTION_COPY_TO,
0305cf6e 64 ACTION_DISCOVER,
dee4a623 65 ACTION_VALIDATE,
a2ea3b2f 66} arg_action = ACTION_DISSECT;
a3c3386e 67static char *arg_image = NULL;
2292fa1e 68static char *arg_root = NULL;
a3c3386e 69static char *arg_path = NULL;
33973b84
LP
70static const char *arg_source = NULL;
71static const char *arg_target = NULL;
4b5de5dd
LP
72static DissectImageFlags arg_flags =
73 DISSECT_IMAGE_GENERIC_ROOT |
4b5de5dd
LP
74 DISSECT_IMAGE_DISCARD_ON_LOOP |
75 DISSECT_IMAGE_RELAX_VAR_CHECK |
76 DISSECT_IMAGE_FSCK |
74a54bae 77 DISSECT_IMAGE_USR_NO_ROOT |
73d88b80
LP
78 DISSECT_IMAGE_GROWFS |
79 DISSECT_IMAGE_PIN_PARTITION_DEVICES |
80 DISSECT_IMAGE_ADD_PARTITION_DEVICES;
aee36b4e 81static VeritySettings arg_verity_settings = VERITY_SETTINGS_DEFAULT;
6a01ea4a 82static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
17547fb5
LP
83static PagerFlags arg_pager_flags = 0;
84static bool arg_legend = true;
ac1f1adf 85static bool arg_rmdir = false;
6c07d570 86static bool arg_in_memory = false;
06186c4c 87static char **arg_argv = NULL;
236d1fa2 88static char *arg_loop_ref = NULL;
84be0c71 89static ImagePolicy* arg_image_policy = NULL;
12d58b6c 90static bool arg_mtree_hash = true;
a2ea3b2f 91
a3c3386e 92STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
2292fa1e 93STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
a3c3386e 94STATIC_DESTRUCTOR_REGISTER(arg_path, freep);
89e62e0b 95STATIC_DESTRUCTOR_REGISTER(arg_verity_settings, verity_settings_done);
06186c4c 96STATIC_DESTRUCTOR_REGISTER(arg_argv, strv_freep);
236d1fa2 97STATIC_DESTRUCTOR_REGISTER(arg_loop_ref, freep);
149afb45 98
5c05f062
LP
99static int help(void) {
100 _cleanup_free_ char *link = NULL;
101 int r;
102
5fdec397
ZJS
103 pager_open(arg_pager_flags);
104
5c05f062
LP
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"
33973b84 110 "%1$s [OPTIONS...] --mount IMAGE PATH\n"
1b967529 111 "%1$s [OPTIONS...] --umount PATH\n"
07d6072e
LP
112 "%1$s [OPTIONS...] --attach IMAGE\n"
113 "%1$s [OPTIONS...] --detach PATH\n"
0cf16924 114 "%1$s [OPTIONS...] --list IMAGE\n"
db02190e 115 "%1$s [OPTIONS...] --mtree IMAGE\n"
06186c4c 116 "%1$s [OPTIONS...] --with IMAGE [COMMAND…]\n"
33973b84 117 "%1$s [OPTIONS...] --copy-from IMAGE PATH [TARGET]\n"
d8def0f3
LP
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"
5c05f062 122 "%3$sOptions:%4$s\n"
17547fb5
LP
123 " --no-pager Do not pipe output into a pager\n"
124 " --no-legend Do not show the headers and footers\n"
e7cbe5cb
LB
125 " -r --read-only Mount read-only\n"
126 " --fsck=BOOL Run fsck before mounting\n"
74a54bae 127 " --growfs=BOOL Grow file system to partition size, if marked\n"
5c05f062 128 " --mkdir Make mount directory before mounting, if missing\n"
ac1f1adf 129 " --rmdir Remove mount directory after unmounting\n"
e7cbe5cb 130 " --discard=MODE Choose 'discard' mode (disabled, loop, all, crypto)\n"
6c07d570 131 " --in-memory Copy image into memory\n"
e7cbe5cb 132 " --root-hash=HASH Specify root hash for verity\n"
c2923fdc
LB
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"
e7cbe5cb 137 " --verity-data=PATH Specify data file with hash tree for verity if it is\n"
5c05f062 138 " not embedded in IMAGE\n"
84be0c71
LP
139 " --image-policy=POLICY\n"
140 " Specify image dissection policy\n"
de8231b0
LP
141 " --json=pretty|short|off\n"
142 " Generate JSON output\n"
236d1fa2 143 " --loop-ref=NAME Set reference string for loopback device\n"
12d58b6c 144 " --mtree-hash=BOOL Whether to include SHA256 hash in the mtree output\n"
5c05f062
LP
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"
ac1f1adf
DDM
150 " -u --umount Unmount the image from the specified directory\n"
151 " -U Shortcut for --umount --rmdir\n"
07d6072e
LP
152 " --attach Attach the disk image to a loopback block device\n"
153 " --detach Detach a loopback block device gain\n"
0cf16924
AAF
154 " -l --list List all the files and directories of the specified\n"
155 " OS image\n"
db02190e 156 " --mtree Show BSD mtree manifest of OS image\n"
06186c4c 157 " --with Mount, run command, unmount\n"
33973b84
LP
158 " -x --copy-from Copy files from image to host\n"
159 " -a --copy-to Copy files from host to image\n"
0305cf6e 160 " --discover Discover DDIs in well known directories\n"
dee4a623 161 " --validate Validate image and image policy\n"
bc556335
DDM
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());
5c05f062
LP
169
170 return 0;
a2ea3b2f
LP
171}
172
06186c4c
LP
173static 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
2292fa1e
DDM
216static 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
a2ea3b2f
LP
241static int parse_argv(int argc, char *argv[]) {
242
243 enum {
244 ARG_VERSION = 0x100,
17547fb5
LP
245 ARG_NO_PAGER,
246 ARG_NO_LEGEND,
06186c4c 247 ARG_WITH,
18b5886e 248 ARG_DISCARD,
e475f729 249 ARG_FSCK,
74a54bae 250 ARG_GROWFS,
89e62e0b 251 ARG_ROOT_HASH,
c2923fdc 252 ARG_ROOT_HASH_SIG,
89e62e0b 253 ARG_VERITY_DATA,
5c05f062 254 ARG_MKDIR,
ac1f1adf 255 ARG_RMDIR,
6c07d570 256 ARG_IN_MEMORY,
de8231b0 257 ARG_JSON,
db02190e 258 ARG_MTREE,
0305cf6e 259 ARG_DISCOVER,
07d6072e
LP
260 ARG_ATTACH,
261 ARG_DETACH,
236d1fa2 262 ARG_LOOP_REF,
84be0c71 263 ARG_IMAGE_POLICY,
dee4a623 264 ARG_VALIDATE,
12d58b6c 265 ARG_MTREE_HASH,
a2ea3b2f
LP
266 };
267
268 static const struct option options[] = {
c2923fdc
LB
269 { "help", no_argument, NULL, 'h' },
270 { "version", no_argument, NULL, ARG_VERSION },
17547fb5
LP
271 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
272 { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
c2923fdc 273 { "mount", no_argument, NULL, 'm' },
ac1f1adf 274 { "umount", no_argument, NULL, 'u' },
07d6072e
LP
275 { "attach", no_argument, NULL, ARG_ATTACH },
276 { "detach", no_argument, NULL, ARG_DETACH },
06186c4c 277 { "with", no_argument, NULL, ARG_WITH },
c2923fdc
LB
278 { "read-only", no_argument, NULL, 'r' },
279 { "discard", required_argument, NULL, ARG_DISCARD },
c2923fdc 280 { "fsck", required_argument, NULL, ARG_FSCK },
74a54bae 281 { "growfs", required_argument, NULL, ARG_GROWFS },
89e62e0b 282 { "root-hash", required_argument, NULL, ARG_ROOT_HASH },
c2923fdc 283 { "root-hash-sig", required_argument, NULL, ARG_ROOT_HASH_SIG },
89e62e0b 284 { "verity-data", required_argument, NULL, ARG_VERITY_DATA },
5c05f062 285 { "mkdir", no_argument, NULL, ARG_MKDIR },
ac1f1adf 286 { "rmdir", no_argument, NULL, ARG_RMDIR },
6c07d570 287 { "in-memory", no_argument, NULL, ARG_IN_MEMORY },
0cf16924 288 { "list", no_argument, NULL, 'l' },
db02190e 289 { "mtree", no_argument, NULL, ARG_MTREE },
33973b84
LP
290 { "copy-from", no_argument, NULL, 'x' },
291 { "copy-to", no_argument, NULL, 'a' },
de8231b0 292 { "json", required_argument, NULL, ARG_JSON },
0305cf6e 293 { "discover", no_argument, NULL, ARG_DISCOVER },
236d1fa2 294 { "loop-ref", required_argument, NULL, ARG_LOOP_REF },
84be0c71 295 { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
dee4a623 296 { "validate", no_argument, NULL, ARG_VALIDATE },
12d58b6c 297 { "mtree-hash", required_argument, NULL, ARG_MTREE_HASH },
a2ea3b2f
LP
298 {}
299 };
300
06186c4c 301 _cleanup_free_ char **buf = NULL; /* we use free(), not strv_free() here, as we don't copy the strings here */
4623e8e6 302 int c, r;
a2ea3b2f
LP
303
304 assert(argc >= 0);
305 assert(argv);
306
06186c4c
LP
307 r = patch_argv(&argc, &argv, &buf);
308 if (r < 0)
309 return r;
310
0cf16924 311 while ((c = getopt_long(argc, argv, "hmurMUlxa", options, NULL)) >= 0) {
a2ea3b2f
LP
312
313 switch (c) {
314
315 case 'h':
5c05f062 316 return help();
a2ea3b2f
LP
317
318 case ARG_VERSION:
319 return version();
320
17547fb5
LP
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
a2ea3b2f
LP
329 case 'm':
330 arg_action = ACTION_MOUNT;
331 break;
332
5c05f062
LP
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
ac1f1adf
DDM
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
07d6072e
LP
357 case ARG_ATTACH:
358 arg_action = ACTION_ATTACH;
359 break;
360
361 case ARG_DETACH:
362 arg_action = ACTION_DETACH;
363 break;
364
0cf16924
AAF
365 case 'l':
366 arg_action = ACTION_LIST;
367 arg_flags |= DISSECT_IMAGE_READ_ONLY;
368 break;
369
db02190e
LP
370 case ARG_MTREE:
371 arg_action = ACTION_MTREE;
372 arg_flags |= DISSECT_IMAGE_READ_ONLY;
373 break;
374
06186c4c
LP
375 case ARG_WITH:
376 arg_action = ACTION_WITH;
377 break;
378
33973b84
LP
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
a2ea3b2f 388 case 'r':
18b5886e
LP
389 arg_flags |= DISSECT_IMAGE_READ_ONLY;
390 break;
391
971e2ef0
ZJS
392 case ARG_DISCARD: {
393 DissectImageFlags flags;
394
18b5886e 395 if (streq(optarg, "disabled"))
971e2ef0 396 flags = 0;
18b5886e 397 else if (streq(optarg, "loop"))
971e2ef0 398 flags = DISSECT_IMAGE_DISCARD_ON_LOOP;
18b5886e 399 else if (streq(optarg, "all"))
971e2ef0 400 flags = DISSECT_IMAGE_DISCARD_ON_LOOP | DISSECT_IMAGE_DISCARD;
18b5886e 401 else if (streq(optarg, "crypt"))
971e2ef0 402 flags = DISSECT_IMAGE_DISCARD_ANY;
140788f7
LP
403 else if (streq(optarg, "list")) {
404 puts("disabled\n"
405 "all\n"
406 "crypt\n"
407 "loop");
408 return 0;
409 } else
baaa35ad
ZJS
410 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
411 "Unknown --discard= parameter: %s",
412 optarg);
971e2ef0 413 arg_flags = (arg_flags & ~DISSECT_IMAGE_DISCARD_ANY) | flags;
18b5886e 414
a2ea3b2f 415 break;
971e2ef0 416 }
a2ea3b2f 417
6c07d570
LP
418 case ARG_IN_MEMORY:
419 arg_in_memory = true;
420 break;
421
4623e8e6 422 case ARG_ROOT_HASH: {
89e62e0b 423 _cleanup_free_ void *p = NULL;
4623e8e6
LP
424 size_t l;
425
bdd2036e 426 r = unhexmem(optarg, &p, &l);
4623e8e6 427 if (r < 0)
63cf2d75 428 return log_error_errno(r, "Failed to parse root hash '%s': %m", optarg);
89e62e0b
LP
429 if (l < sizeof(sd_id128_t))
430 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
da890466 431 "Root hash must be at least 128-bit long: %s", optarg);
4623e8e6 432
89e62e0b
LP
433 free_and_replace(arg_verity_settings.root_hash, p);
434 arg_verity_settings.root_hash_size = l;
4623e8e6
LP
435 break;
436 }
437
c2923fdc
LB
438 case ARG_ROOT_HASH_SIG: {
439 char *value;
89e62e0b
LP
440 size_t l;
441 void *p;
c2923fdc
LB
442
443 if ((value = startswith(optarg, "base64:"))) {
bdd2036e 444 r = unbase64mem(value, &p, &l);
c2923fdc
LB
445 if (r < 0)
446 return log_error_errno(r, "Failed to parse root hash signature '%s': %m", optarg);
c2923fdc 447 } else {
89e62e0b 448 r = read_full_file(optarg, (char**) &p, &l);
c2923fdc 449 if (r < 0)
89e62e0b 450 return log_error_errno(r, "Failed to read root hash signature file '%s': %m", optarg);
c2923fdc
LB
451 }
452
89e62e0b
LP
453 free_and_replace(arg_verity_settings.root_hash_sig, p);
454 arg_verity_settings.root_hash_sig_size = l;
c2923fdc
LB
455 break;
456 }
457
89e62e0b 458 case ARG_VERITY_DATA:
614b022c 459 r = parse_path_argument(optarg, false, &arg_verity_settings.data_path);
89e62e0b
LP
460 if (r < 0)
461 return r;
462 break;
463
e475f729
LP
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
74a54bae
LP
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
de8231b0 480 case ARG_JSON:
b1e8f46c 481 r = parse_json_argument(optarg, &arg_json_format_flags);
6a01ea4a
LP
482 if (r <= 0)
483 return r;
de8231b0
LP
484
485 break;
486
0305cf6e
LP
487 case ARG_DISCOVER:
488 arg_action = ACTION_DISCOVER;
489 break;
490
236d1fa2
LP
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
06e78680
YW
505 case ARG_IMAGE_POLICY:
506 r = parse_image_policy_argument(optarg, &arg_image_policy);
84be0c71 507 if (r < 0)
06e78680 508 return r;
84be0c71 509 break;
84be0c71 510
dee4a623
LP
511 case ARG_VALIDATE:
512 arg_action = ACTION_VALIDATE;
513 break;
514
12d58b6c
DDM
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
a2ea3b2f
LP
521 case '?':
522 return -EINVAL;
523
524 default:
04499a70 525 assert_not_reached();
a2ea3b2f 526 }
a2ea3b2f
LP
527 }
528
529 switch (arg_action) {
530
531 case ACTION_DISSECT:
baaa35ad
ZJS
532 if (optind + 1 != argc)
533 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
33973b84 534 "Expected an image file path as only argument.");
a2ea3b2f 535
2292fa1e 536 r = parse_image_path_argument(argv[optind], NULL, &arg_image);
a3c3386e
LP
537 if (r < 0)
538 return r;
539
598fd4da
LP
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;
a2ea3b2f
LP
542 break;
543
544 case ACTION_MOUNT:
baaa35ad
ZJS
545 if (optind + 2 != argc)
546 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
33973b84 547 "Expected an image file path and mount point path as only arguments.");
a2ea3b2f 548
2292fa1e 549 r = parse_image_path_argument(argv[optind], NULL, &arg_image);
a3c3386e
LP
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
166ff731 557 arg_flags |= DISSECT_IMAGE_REQUIRE_ROOT;
a2ea3b2f
LP
558 break;
559
ac1f1adf
DDM
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
a3c3386e
LP
565 r = parse_path_argument(argv[optind], /* suppress_root= */ false, &arg_path);
566 if (r < 0)
567 return r;
ac1f1adf
DDM
568 break;
569
07d6072e
LP
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
2292fa1e 575 r = parse_image_path_argument(argv[optind], NULL, &arg_image);
a3c3386e
LP
576 if (r < 0)
577 return r;
07d6072e
LP
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
2292fa1e 585 r = parse_image_path_argument(argv[optind], NULL, &arg_image);
a3c3386e
LP
586 if (r < 0)
587 return r;
07d6072e
LP
588 break;
589
0cf16924 590 case ACTION_LIST:
db02190e 591 case ACTION_MTREE:
0cf16924
AAF
592 if (optind + 1 != argc)
593 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
2292fa1e 594 "Expected an image file or directory path as only argument.");
0cf16924 595
2292fa1e 596 r = parse_image_path_argument(argv[optind], &arg_root, &arg_image);
a3c3386e
LP
597 if (r < 0)
598 return r;
599
0cf16924
AAF
600 arg_flags |= DISSECT_IMAGE_READ_ONLY | DISSECT_IMAGE_REQUIRE_ROOT;
601 break;
602
33973b84
LP
603 case ACTION_COPY_FROM:
604 if (argc < optind + 2 || argc > optind + 3)
605 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
2292fa1e 606 "Expected an image file or directory path, a source path and an optional destination path as only arguments.");
33973b84 607
2292fa1e 608 r = parse_image_path_argument(argv[optind], &arg_root, &arg_image);
a3c3386e
LP
609 if (r < 0)
610 return r;
33973b84
LP
611 arg_source = argv[optind + 1];
612 arg_target = argc > optind + 2 ? argv[optind + 2] : "-" /* this means stdout */ ;
613
166ff731 614 arg_flags |= DISSECT_IMAGE_READ_ONLY | DISSECT_IMAGE_REQUIRE_ROOT;
33973b84
LP
615 break;
616
617 case ACTION_COPY_TO:
618 if (argc < optind + 2 || argc > optind + 3)
619 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
2292fa1e 620 "Expected an image file or directory path, an optional source path and a destination path as only arguments.");
33973b84 621
2292fa1e 622 r = parse_image_path_argument(argv[optind], &arg_root, &arg_image);
a3c3386e
LP
623 if (r < 0)
624 return r;
33973b84
LP
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
166ff731 634 arg_flags |= DISSECT_IMAGE_REQUIRE_ROOT;
33973b84
LP
635 break;
636
06186c4c
LP
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
2292fa1e 642 r = parse_image_path_argument(argv[optind], NULL, &arg_image);
a3c3386e
LP
643 if (r < 0)
644 return r;
645
06186c4c
LP
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
0305cf6e
LP
654 case ACTION_DISCOVER:
655 if (optind != argc)
656 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
657 "Expected no argument.");
dee4a623
LP
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.");
0305cf6e 664
2292fa1e 665 r = parse_image_path_argument(argv[optind], NULL, &arg_image);
dee4a623
LP
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);
0305cf6e
LP
671 break;
672
a2ea3b2f 673 default:
04499a70 674 assert_not_reached();
a2ea3b2f
LP
675 }
676
677 return 1;
678}
679
a164d9d5
LP
680static 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) {
1d03d970 688 switch (c) {
a164d9d5
LP
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
a3c3386e
LP
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;
a164d9d5
LP
753
754 arg_flags |= DISSECT_IMAGE_REQUIRE_ROOT;
755 arg_action = ACTION_MOUNT;
756 return 1;
757}
758
8570b98b 759static void strv_pair_print(char **l, const char *prefix) {
8570b98b
LP
760 assert(prefix);
761
7f5b2615 762 STRV_FOREACH_PAIR(p, q, l)
8570b98b
LP
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);
8570b98b
LP
767}
768
a81fe93e 769static int get_extension_scopes(DissectedImage *m, ImageClass class, char ***ret_scopes) {
994c9c70 770 _cleanup_strv_free_ char **l = NULL;
a81fe93e
LP
771 const char *e, *field_name;
772 char **release_data;
994c9c70
LP
773
774 assert(m);
775 assert(ret_scopes);
776
a81fe93e
LP
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
484d26da
MG
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. */
a81fe93e
LP
796
797 if (!release_data) {
994c9c70
LP
798 *ret_scopes = NULL;
799 return 0;
800 }
801
a81fe93e 802 e = strv_env_pairs_get(release_data, field_name);
994c9c70
LP
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
37e44c3f 814static int action_dissect(DissectedImage *m, LoopDevice *d) {
de8231b0 815 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
37e44c3f 816 _cleanup_(table_unrefp) Table *t = NULL;
cfb623b6 817 _cleanup_free_ char *bn = NULL;
de8231b0 818 uint64_t size = UINT64_MAX;
a2ea3b2f
LP
819 int r;
820
37e44c3f
LP
821 assert(m);
822 assert(d);
a2ea3b2f 823
cfb623b6
LP
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
17547fb5 828 if (arg_json_format_flags & (JSON_FORMAT_OFF|JSON_FORMAT_PRETTY|JSON_FORMAT_PRETTY_AUTO))
384c2c32 829 pager_open(arg_pager_flags);
17547fb5 830
5b1b37c8
LP
831 if (arg_json_format_flags & JSON_FORMAT_OFF) {
832 printf(" Name: %s%s%s\n",
833 ansi_highlight(), bn, ansi_normal());
a2ea3b2f 834
5b1b37c8
LP
835 printf(" Size: %s\n",
836 FORMAT_BYTES(m->image_size));
a2ea3b2f 837
5b1b37c8
LP
838 printf(" Sec. Size: %" PRIu32 "\n",
839 m->sector_size);
1d93c003 840
66cd3537
LP
841 printf(" Arch.: %s\n",
842 strna(architecture_to_string(dissected_image_architecture(m))));
2348043f 843
de8231b0 844 putc('\n', stdout);
66cd3537
LP
845 fflush(stdout);
846 }
1d93c003 847
22847508 848 r = dissected_image_acquire_metadata(m, 0);
af8219d5
LP
849 if (r == -ENXIO)
850 return log_error_errno(r, "No root partition discovered.");
af8219d5
LP
851 if (r == -EUCLEAN)
852 return log_error_errno(r, "File system check of image failed.");
4f309abb
LP
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)
af8219d5
LP
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");
6a01ea4a 861 else if (arg_json_format_flags & JSON_FORMAT_OFF) {
994c9c70 862
b387778c
LP
863 if (!sd_id128_is_null(m->image_uuid))
864 printf("Image UUID: %s\n", SD_ID128_TO_UUID_STRING(m->image_uuid));
865
af8219d5
LP
866 if (m->hostname)
867 printf(" Hostname: %s\n", m->hostname);
a2ea3b2f 868
af8219d5
LP
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));
a1edd22e 871
8570b98b
LP
872 strv_pair_print(m->machine_info,
873 "Mach. Info:");
874 strv_pair_print(m->os_release,
875 "OS Release:");
fab22946
LP
876 strv_pair_print(m->initrd_release,
877 "initrd R.:");
a81fe93e
LP
878 strv_pair_print(m->sysext_release,
879 " sysext R.:");
880 strv_pair_print(m->confext_release,
881 "confext R.:");
7718ac97 882
4f309abb
LP
883 if (m->hostname ||
884 !sd_id128_is_null(m->machine_id) ||
885 !strv_isempty(m->machine_info) ||
8570b98b 886 !strv_isempty(m->os_release) ||
fab22946 887 !strv_isempty(m->initrd_release) ||
a81fe93e
LP
888 !strv_isempty(m->sysext_release) ||
889 !strv_isempty(m->confext_release))
4f309abb 890 putc('\n', stdout);
994c9c70 891
b2466e05
LP
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)));
994c9c70 896 printf(" %s portable service\n",
b2466e05 897 COLOR_MARK_BOOL(dissected_image_is_portable(m)));
fab22946 898 printf(" %s initrd\n",
b2466e05 899 COLOR_MARK_BOOL(dissected_image_is_initrd(m)));
994c9c70 900
a81fe93e
LP
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;
994c9c70 904
a81fe93e
LP
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 }
994c9c70
LP
916
917 putc('\n', stdout);
4f309abb 918 } else {
a81fe93e
LP
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");
de8231b0 924
a81fe93e 925 r = get_extension_scopes(m, IMAGE_CONFEXT, &confext_scopes);
994c9c70 926 if (r < 0)
a81fe93e 927 return log_error_errno(r, "Failed to parse confext scopes: %m");
994c9c70 928
6e0b5cd3
LP
929 Architecture a = dissected_image_architecture(m);
930
de8231b0 931 r = json_build(&v, JSON_BUILD_OBJECT(
cfb623b6 932 JSON_BUILD_PAIR("name", JSON_BUILD_STRING(bn)),
1c5cc6c5 933 JSON_BUILD_PAIR_CONDITION(size != UINT64_MAX, "size", JSON_BUILD_INTEGER(size)),
1d93c003 934 JSON_BUILD_PAIR("sectorSize", JSON_BUILD_INTEGER(m->sector_size)),
6e0b5cd3 935 JSON_BUILD_PAIR_CONDITION(a >= 0, "architecture", JSON_BUILD_STRING(architecture_to_string(a))),
1c5cc6c5 936 JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(m->image_uuid), "imageUuid", JSON_BUILD_UUID(m->image_uuid)),
de8231b0
LP
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)),
e83d902b
LP
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)),
a81fe93e
LP
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)),
b2466e05
LP
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))),
a81fe93e
LP
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")))));
de8231b0
LP
954 if (r < 0)
955 return log_oom();
956 }
957
ee8e497d 958 t = table_new("rw", "designator", "partition uuid", "partition label", "fstype", "architecture", "verity", "growfs", "node", "partno");
37e44c3f
LP
959 if (!t)
960 return log_oom();
a1edd22e 961
c8b62cf6 962 table_set_ersatz_string(t, TABLE_ERSATZ_DASH);
748e87a7 963 (void) table_set_align_percent(t, table_get_cell(t, 0, 9), 100);
a2ea3b2f 964
64cd3d13
LP
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
569a0e42 970 for (PartitionDesignator i = 0; i < _PARTITION_DESIGNATOR_MAX; i++) {
37e44c3f 971 DissectedPartition *p = m->partitions + i;
89d00f2e 972
37e44c3f
LP
973 if (!p->found)
974 continue;
89d00f2e 975
37e44c3f
LP
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);
89d00f2e 982
37e44c3f
LP
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);
89d00f2e 989
37e44c3f
LP
990 r = table_add_many(
991 t,
1474d7ac 992 TABLE_STRING, p->label,
37e44c3f
LP
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
89e62e0b 998 if (arg_verity_settings.data_path)
37e44c3f 999 r = table_add_cell(t, NULL, TABLE_STRING, "external");
49536766 1000 else if (dissected_image_verity_candidate(m, i))
8ee9615e
LP
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)));
37e44c3f
LP
1004 else
1005 r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
1006 if (r < 0)
1007 return table_log_add_error(r);
89d00f2e 1008
ee8e497d
LP
1009 r = table_add_many(t, TABLE_BOOLEAN, (int) p->growfs);
1010 if (r < 0)
1011 return table_log_add_error(r);
1012
37e44c3f 1013 if (p->partno < 0) /* no partition table, naked file system */ {
a3c3386e 1014 r = table_add_cell(t, NULL, TABLE_PATH_BASENAME, arg_image);
89d00f2e
LP
1015 if (r < 0)
1016 return table_log_add_error(r);
1017
37e44c3f
LP
1018 r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
1019 } else {
1020 r = table_add_cell(t, NULL, TABLE_STRING, p->node);
89d00f2e
LP
1021 if (r < 0)
1022 return table_log_add_error(r);
1023
37e44c3f
LP
1024 r = table_add_cell(t, NULL, TABLE_INT, &p->partno);
1025 }
1026 if (r < 0)
1027 return table_log_add_error(r);
1028 }
89d00f2e 1029
6a01ea4a 1030 if (arg_json_format_flags & JSON_FORMAT_OFF) {
17547fb5
LP
1031 (void) table_set_header(t, arg_legend);
1032
1033 r = table_print(t, NULL);
6a01ea4a
LP
1034 if (r < 0)
1035 return table_log_print_error(r);
1036 } else {
de8231b0
LP
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);
de8231b0 1048 }
89d00f2e 1049
37e44c3f
LP
1050 return 0;
1051}
89d00f2e 1052
37e44c3f 1053static int action_mount(DissectedImage *m, LoopDevice *d) {
37e44c3f 1054 int r;
89d00f2e 1055
37e44c3f
LP
1056 assert(m);
1057 assert(d);
94abea2a 1058 assert(arg_action == ACTION_MOUNT);
89d00f2e 1059
8d9a1d59
LP
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);
37e44c3f 1067 if (r < 0)
af187ab2 1068 return r;
89d00f2e 1069
41bc4849
LP
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
3044d343
YW
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");
a2ea3b2f 1077
37e44c3f
LP
1078 return 0;
1079}
18b5886e 1080
0cf16924
AAF
1081static 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
167f2c1a 1090 assert(path);
0cf16924
AAF
1091
1092 if (event == RECURSE_DIR_ENTER)
bb69ec95 1093 printf("%s%s/%s\n", path, ansi_grey(), ansi_normal());
0cf16924
AAF
1094 else if (event == RECURSE_DIR_ENTRY)
1095 printf("%s\n", path);
1096
1097 return RECURSE_DIR_CONTINUE;
1098}
1099
db02190e 1100static int get_file_sha256(int inode_fd, uint8_t ret[static SHA256_DIGEST_SIZE]) {
254d1313 1101 _cleanup_close_ int fd = -EBADF;
db02190e
LP
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
12c0f4ff
LP
1131static 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))
94d82b59 1137 return ansi_highlight_red(); /* files should never be owned persistently by dynamic users, and there are just no excuses */
12c0f4ff
LP
1138 if (uid_is_container(uid))
1139 return ansi_highlight_cyan();
1140
1141 return ansi_highlight();
1142}
1143
db02190e
LP
1144static 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
01a33cf7 1153 _cleanup_free_ char *escaped = NULL;
db02190e
LP
1154 int r;
1155
167f2c1a 1156 assert(path);
db02190e 1157
01a33cf7
YW
1158 if (!IN_SET(event, RECURSE_DIR_ENTER, RECURSE_DIR_ENTRY))
1159 return RECURSE_DIR_CONTINUE;
db02190e 1160
01a33cf7 1161 assert(sx);
5ffa6a0a 1162
01a33cf7
YW
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 }
db02190e 1171
01a33cf7
YW
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 }
db02190e 1194
01a33cf7
YW
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))
12c0f4ff 1202 printf(" %suid=%s" UID_FMT "%s",
01a33cf7 1203 ansi_grey(),
12c0f4ff
LP
1204 pick_color_for_uid_gid(sx->stx_uid),
1205 sx->stx_uid,
1206 ansi_normal());
01a33cf7
YW
1207
1208 if (FLAGS_SET(sx->stx_mask, STATX_GID))
12c0f4ff 1209 printf(" %sgid=%s" GID_FMT "%s",
01a33cf7 1210 ansi_grey(),
12c0f4ff
LP
1211 pick_color_for_uid_gid(sx->stx_gid),
1212 sx->stx_gid,
1213 ansi_normal());
01a33cf7
YW
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
12d58b6c 1221 if (arg_mtree_hash && inode_fd >= 0 && sx->stx_size > 0) {
01a33cf7
YW
1222 uint8_t hash[SHA256_DIGEST_SIZE];
1223
1224 r = get_file_sha256(inode_fd, hash);
db02190e 1225 if (r < 0)
01a33cf7 1226 log_warning_errno(r, "Failed to calculate file SHA256 sum for '%s', ignoring: %m", path);
db02190e 1227 else {
01a33cf7 1228 _cleanup_free_ char *h = NULL;
db02190e 1229
01a33cf7
YW
1230 h = hexmem(hash, sizeof(hash));
1231 if (!h)
db02190e
LP
1232 return log_oom();
1233
01a33cf7 1234 printf(" %ssha256sum=%s%s",
db02190e
LP
1235 ansi_grey(),
1236 ansi_normal(),
01a33cf7 1237 h);
db02190e
LP
1238 }
1239 }
01a33cf7 1240 }
db02190e 1241
01a33cf7
YW
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",
db02190e
LP
1256 ansi_grey(),
1257 ansi_normal(),
01a33cf7
YW
1258 target_escaped);
1259 }
db02190e
LP
1260 }
1261
01a33cf7
YW
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
db02190e
LP
1271 return RECURSE_DIR_CONTINUE;
1272}
1273
1274static int action_list_or_mtree_or_copy(DissectedImage *m, LoopDevice *d) {
37e44c3f 1275 _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
2d1e7d19 1276 _cleanup_free_ char *t = NULL;
2292fa1e 1277 const char *root;
37e44c3f 1278 int r;
a2ea3b2f 1279
94abea2a 1280 assert(IN_SET(arg_action, ACTION_LIST, ACTION_MTREE, ACTION_COPY_FROM, ACTION_COPY_TO));
a2ea3b2f 1281
2292fa1e
DDM
1282 if (arg_image) {
1283 assert(m);
1284 assert(d);
33973b84 1285
2292fa1e
DDM
1286 r = detach_mount_namespace();
1287 if (r < 0)
1288 return log_error_errno(r, "Failed to detach mount namespace: %m");
33973b84 1289
2d1e7d19 1290 r = get_common_dissect_directory(&t);
2292fa1e 1291 if (r < 0)
2d1e7d19 1292 return log_error_errno(r, "Failed generate private mount directory: %m");
33973b84 1293
8d9a1d59
LP
1294 r = dissected_image_mount_and_warn(
1295 m,
2d1e7d19 1296 t,
8d9a1d59
LP
1297 /* uid_shift= */ UID_INVALID,
1298 /* uid_range= */ UID_INVALID,
1299 /* userns_fd= */ -EBADF,
1300 arg_flags);
2292fa1e
DDM
1301 if (r < 0)
1302 return r;
33973b84 1303
2d1e7d19 1304 mounted_dir = TAKE_PTR(t);
41bc4849 1305
2292fa1e
DDM
1306 r = loop_device_flock(d, LOCK_UN);
1307 if (r < 0)
1308 return log_error_errno(r, "Failed to unlock loopback block device: %m");
1309
1310 r = dissected_image_relinquish(m);
1311 if (r < 0)
1312 return log_error_errno(r, "Failed to relinquish DM and loopback block devices: %m");
1313 }
1314
1315 root = mounted_dir ?: arg_root;
33973b84 1316
b7df8622
LP
1317 switch (arg_action) {
1318
1319 case ACTION_COPY_FROM: {
254d1313 1320 _cleanup_close_ int source_fd = -EBADF, target_fd = -EBADF;
33973b84 1321
2292fa1e 1322 source_fd = chase_and_open(arg_source, root, CHASE_PREFIX_ROOT|CHASE_WARN, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL);
37e44c3f
LP
1323 if (source_fd < 0)
1324 return log_error_errno(source_fd, "Failed to open source path '%s' in image '%s': %m", arg_source, arg_image);
1325
1326 /* Copying to stdout? */
1327 if (streq(arg_target, "-")) {
f5fbe71d 1328 r = copy_bytes(source_fd, STDOUT_FILENO, UINT64_MAX, COPY_REFLINK);
33973b84 1329 if (r < 0)
37e44c3f
LP
1330 return log_error_errno(r, "Failed to copy bytes from %s in mage '%s' to stdout: %m", arg_source, arg_image);
1331
38db55ab 1332 /* When we copy to stdout we don't copy any attributes (i.e. no access mode, no ownership, no xattr, no times) */
37e44c3f 1333 return 0;
33973b84
LP
1334 }
1335
37e44c3f 1336 /* Try to copy as directory? */
f9f70e06 1337 r = copy_directory_at(source_fd, NULL, AT_FDCWD, arg_target, COPY_REFLINK|COPY_MERGE_EMPTY|COPY_SIGINT|COPY_HARDLINKS);
37e44c3f
LP
1338 if (r >= 0)
1339 return 0;
1340 if (r != -ENOTDIR)
1341 return log_error_errno(r, "Failed to copy %s in image '%s' to '%s': %m", arg_source, arg_image, arg_target);
1342
1343 r = fd_verify_regular(source_fd);
1344 if (r == -EISDIR)
1345 return log_error_errno(r, "Target '%s' exists already and is not a directory.", arg_target);
1346 if (r < 0)
1347 return log_error_errno(r, "Source path %s in image '%s' is neither regular file nor directory, refusing: %m", arg_source, arg_image);
33973b84 1348
37e44c3f
LP
1349 /* Nah, it's a plain file! */
1350 target_fd = open(arg_target, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0600);
1351 if (target_fd < 0)
1352 return log_error_errno(errno, "Failed to create regular file at target path '%s': %m", arg_target);
33973b84 1353
f5fbe71d 1354 r = copy_bytes(source_fd, target_fd, UINT64_MAX, COPY_REFLINK);
37e44c3f
LP
1355 if (r < 0)
1356 return log_error_errno(r, "Failed to copy bytes from %s in mage '%s' to '%s': %m", arg_source, arg_image, arg_target);
33973b84 1357
c17cfe6e 1358 (void) copy_xattr(source_fd, NULL, target_fd, NULL, 0);
37e44c3f
LP
1359 (void) copy_access(source_fd, target_fd);
1360 (void) copy_times(source_fd, target_fd, 0);
33973b84 1361
37e44c3f 1362 /* When this is a regular file we don't copy ownership! */
b7df8622
LP
1363 return 0;
1364 }
33973b84 1365
b7df8622 1366 case ACTION_COPY_TO: {
254d1313 1367 _cleanup_close_ int source_fd = -EBADF, target_fd = -EBADF, dfd = -EBADF;
cfb623b6 1368 _cleanup_free_ char *dn = NULL, *bn = NULL;
83802e9a 1369 bool is_dir;
33973b84 1370
45519d13
LP
1371 r = path_extract_directory(arg_target, &dn);
1372 if (r < 0)
cfb623b6
LP
1373 return log_error_errno(r, "Failed to extract directory from target path '%s': %m", arg_target);
1374 r = path_extract_filename(arg_target, &bn);
1375 if (r < 0)
1376 return log_error_errno(r, "Failed to extract filename from target path '%s': %m", arg_target);
83802e9a 1377 is_dir = r == O_DIRECTORY;
37e44c3f 1378
2292fa1e 1379 r = chase(dn, root, CHASE_PREFIX_ROOT|CHASE_WARN, NULL, &dfd);
37e44c3f
LP
1380 if (r < 0)
1381 return log_error_errno(r, "Failed to open '%s': %m", dn);
33973b84 1382
37e44c3f
LP
1383 /* Are we reading from stdin? */
1384 if (streq(arg_source, "-")) {
83802e9a
LP
1385 if (is_dir)
1386 return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Cannot copy STDIN to a directory, refusing.");
1387
cfb623b6 1388 target_fd = openat(dfd, bn, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_EXCL, 0644);
33973b84 1389 if (target_fd < 0)
37e44c3f 1390 return log_error_errno(errno, "Failed to open target file '%s': %m", arg_target);
33973b84 1391
f5fbe71d 1392 r = copy_bytes(STDIN_FILENO, target_fd, UINT64_MAX, COPY_REFLINK);
33973b84 1393 if (r < 0)
37e44c3f 1394 return log_error_errno(r, "Failed to copy bytes from stdin to '%s' in image '%s': %m", arg_target, arg_image);
33973b84 1395
37e44c3f
LP
1396 /* When we copy from stdin we don't copy any attributes (i.e. no access mode, no ownership, no xattr, no times) */
1397 return 0;
1398 }
33973b84 1399
37e44c3f
LP
1400 source_fd = open(arg_source, O_RDONLY|O_CLOEXEC|O_NOCTTY);
1401 if (source_fd < 0)
1402 return log_error_errno(source_fd, "Failed to open source path '%s': %m", arg_source);
33973b84 1403
37e44c3f
LP
1404 r = fd_verify_regular(source_fd);
1405 if (r < 0) {
1406 if (r != -EISDIR)
1407 return log_error_errno(r, "Source '%s' is neither regular file nor directory: %m", arg_source);
33973b84 1408
37e44c3f 1409 /* We are looking at a directory. */
33973b84 1410
cfb623b6 1411 target_fd = openat(dfd, bn, O_RDONLY|O_DIRECTORY|O_CLOEXEC);
37e44c3f
LP
1412 if (target_fd < 0) {
1413 if (errno != ENOENT)
1414 return log_error_errno(errno, "Failed to open destination '%s': %m", arg_target);
33973b84 1415
ad6fae7f 1416 r = copy_tree_at(source_fd, ".", dfd, bn, UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS, NULL, NULL);
37e44c3f 1417 } else
ad6fae7f 1418 r = copy_tree_at(source_fd, ".", target_fd, ".", UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS, NULL, NULL);
33973b84 1419 if (r < 0)
37e44c3f 1420 return log_error_errno(r, "Failed to copy '%s' to '%s' in image '%s': %m", arg_source, arg_target, arg_image);
33973b84 1421
37e44c3f
LP
1422 return 0;
1423 }
33973b84 1424
83802e9a
LP
1425 if (is_dir)
1426 return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Source is a regular file, but target is not, refusing.");
1427
37e44c3f 1428 /* We area looking at a regular file */
83802e9a 1429 target_fd = openat(dfd, bn, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_EXCL, 0600);
37e44c3f
LP
1430 if (target_fd < 0)
1431 return log_error_errno(errno, "Failed to open target file '%s': %m", arg_target);
33973b84 1432
f5fbe71d 1433 r = copy_bytes(source_fd, target_fd, UINT64_MAX, COPY_REFLINK);
37e44c3f
LP
1434 if (r < 0)
1435 return log_error_errno(r, "Failed to copy bytes from '%s' to '%s' in image '%s': %m", arg_source, arg_target, arg_image);
33973b84 1436
c17cfe6e 1437 (void) copy_xattr(source_fd, NULL, target_fd, NULL, 0);
37e44c3f
LP
1438 (void) copy_access(source_fd, target_fd);
1439 (void) copy_times(source_fd, target_fd, 0);
33973b84 1440
37e44c3f 1441 /* When this is a regular file we don't copy ownership! */
b7df8622
LP
1442 return 0;
1443 }
0cf16924 1444
b7df8622
LP
1445 case ACTION_LIST:
1446 case ACTION_MTREE: {
254d1313 1447 _cleanup_close_ int dfd = -EBADF;
0cf16924 1448
2292fa1e 1449 dfd = open(root, O_DIRECTORY|O_CLOEXEC|O_RDONLY);
0cf16924
AAF
1450 if (dfd < 0)
1451 return log_error_errno(errno, "Failed to open mount directory: %m");
1452
2e1f76f6
LP
1453 pager_open(arg_pager_flags);
1454
db02190e
LP
1455 if (arg_action == ACTION_LIST)
1456 r = recurse_dir(dfd, NULL, 0, UINT_MAX, RECURSE_DIR_SORT, list_print_item, NULL);
1457 else if (arg_action == ACTION_MTREE)
1458 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);
1459 else
1460 assert_not_reached();
0cf16924
AAF
1461 if (r < 0)
1462 return log_error_errno(r, "Failed to list image: %m");
b7df8622 1463 return 0;
37e44c3f 1464 }
33973b84 1465
b7df8622
LP
1466 default:
1467 assert_not_reached();
1468 }
37e44c3f 1469}
33973b84 1470
ac1f1adf 1471static int action_umount(const char *path) {
254d1313 1472 _cleanup_close_ int fd = -EBADF;
040d3439 1473 _cleanup_free_ char *canonical = NULL;
ac1f1adf 1474 _cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
040d3439 1475 _cleanup_(sd_device_unrefp) sd_device *dev = NULL;
e8383058 1476 int r;
ac1f1adf 1477
f461a28d 1478 fd = chase_and_open(path, NULL, 0, O_DIRECTORY, &canonical);
ac1f1adf
DDM
1479 if (fd == -ENOTDIR)
1480 return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "'%s' is not a directory", path);
1481 if (fd < 0)
1482 return log_error_errno(fd, "Failed to resolve path '%s': %m", path);
1483
1484 r = fd_is_mount_point(fd, NULL, 0);
1485 if (r == 0)
1486 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "'%s' is not a mount point", canonical);
1487 if (r < 0)
1488 return log_error_errno(r, "Failed to determine whether '%s' is a mount point: %m", canonical);
1489
040d3439 1490 r = block_device_new_from_fd(fd, BLOCK_DEVICE_LOOKUP_WHOLE_DISK | BLOCK_DEVICE_LOOKUP_BACKING, &dev);
41a95b18 1491 if (r < 0) {
254d1313 1492 _cleanup_close_ int usr_fd = -EBADF;
41a95b18
YW
1493
1494 /* The command `systemd-dissect --mount` expects that the image at least has the root or /usr
1495 * partition. If it does not have the root partition, then we mount the /usr partition on a
1496 * tmpfs. Hence, let's try to find the backing block device through the /usr partition. */
1497
1498 usr_fd = openat(fd, "usr", O_CLOEXEC | O_DIRECTORY | O_NOFOLLOW);
1499 if (usr_fd < 0)
1500 return log_error_errno(errno, "Failed to open '%s/usr': %m", canonical);
1501
1502 r = block_device_new_from_fd(usr_fd, BLOCK_DEVICE_LOOKUP_WHOLE_DISK | BLOCK_DEVICE_LOOKUP_BACKING, &dev);
1503 }
ac1f1adf
DDM
1504 if (r < 0)
1505 return log_error_errno(r, "Failed to find backing block device for '%s': %m", canonical);
1506
040d3439 1507 r = loop_device_open(dev, 0, LOCK_EX, &d);
ac1f1adf 1508 if (r < 0)
040d3439 1509 return log_device_error_errno(dev, r, "Failed to open loopback block device: %m");
ac1f1adf 1510
ac1f1adf
DDM
1511 /* We've locked the loop device, now we're ready to unmount. To allow the unmount to succeed, we have
1512 * to close the O_PATH fd we opened earlier. */
1513 fd = safe_close(fd);
1514
1515 r = umount_recursive(canonical, 0);
1516 if (r < 0)
1517 return log_error_errno(r, "Failed to unmount '%s': %m", canonical);
1518
0b75493d 1519 /* We managed to lock and unmount successfully? That means we can try to remove the loop device. */
ac1f1adf
DDM
1520 loop_device_unrelinquish(d);
1521
1522 if (arg_rmdir) {
e8383058
LP
1523 r = RET_NERRNO(rmdir(canonical));
1524 if (r < 0)
1525 return log_error_errno(r, "Failed to remove mount directory '%s': %m", canonical);
ac1f1adf 1526 }
ac1f1adf 1527
e8383058 1528 return 0;
ac1f1adf
DDM
1529}
1530
06186c4c
LP
1531static int action_with(DissectedImage *m, LoopDevice *d) {
1532 _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
1533 _cleanup_(rmdir_and_freep) char *created_dir = NULL;
1534 _cleanup_free_ char *temp = NULL;
1535 int r, rcode;
1536
94abea2a
LP
1537 assert(m);
1538 assert(d);
1539 assert(arg_action == ACTION_WITH);
06186c4c
LP
1540
1541 r = tempfn_random_child(NULL, program_invocation_short_name, &temp);
1542 if (r < 0)
1543 return log_error_errno(r, "Failed to generate temporary mount directory: %m");
1544
1545 r = mkdir_p(temp, 0700);
1546 if (r < 0)
1547 return log_error_errno(r, "Failed to create mount point: %m");
1548
1549 created_dir = TAKE_PTR(temp);
1550
8d9a1d59
LP
1551 r = dissected_image_mount_and_warn(
1552 m,
1553 created_dir,
1554 /* uid_shift= */ UID_INVALID,
1555 /* uid_range= */ UID_INVALID,
1556 /* userns_fd= */ -EBADF,
1557 arg_flags);
06186c4c
LP
1558 if (r < 0)
1559 return r;
1560
1561 mounted_dir = TAKE_PTR(created_dir);
1562
1563 r = dissected_image_relinquish(m);
1564 if (r < 0)
1565 return log_error_errno(r, "Failed to relinquish DM and loopback block devices: %m");
1566
1567 r = loop_device_flock(d, LOCK_UN);
1568 if (r < 0)
1569 return log_error_errno(r, "Failed to unlock loopback block device: %m");
1570
1571 rcode = safe_fork("(with)", FORK_CLOSE_ALL_FDS|FORK_LOG|FORK_WAIT, NULL);
1572 if (rcode == 0) {
1573 /* Child */
1574
1575 if (chdir(mounted_dir) < 0) {
1576 log_error_errno(errno, "Failed to change to '%s' directory: %m", mounted_dir);
1577 _exit(EXIT_FAILURE);
1578 }
1579
1580 if (setenv("SYSTEMD_DISSECT_ROOT", mounted_dir, /* overwrite= */ true) < 0) {
1581 log_error_errno(errno, "Failed to set $SYSTEMD_DISSECT_ROOT: %m");
1582 _exit(EXIT_FAILURE);
1583 }
1584
47838b55
DDM
1585 if (setenv("SYSTEMD_DISSECT_DEVICE", d->node, /* overwrite= */ true) < 0) {
1586 log_error_errno(errno, "Failed to set $SYSTEMD_DISSECT_DEVICE: %m");
1587 _exit(EXIT_FAILURE);
1588 }
1589
06186c4c
LP
1590 if (strv_isempty(arg_argv)) {
1591 const char *sh;
1592
1593 sh = secure_getenv("SHELL");
1594 if (sh) {
1595 execvp(sh, STRV_MAKE(sh));
1596 log_warning_errno(errno, "Failed to execute $SHELL, falling back to /bin/sh: %m");
1597 }
1598
1599 execl("/bin/sh", "sh", NULL);
1600 log_error_errno(errno, "Failed to invoke /bin/sh: %m");
1601 } else {
1602 execvp(arg_argv[0], arg_argv);
1603 log_error_errno(errno, "Failed to execute '%s': %m", arg_argv[0]);
1604 }
1605
1606 _exit(EXIT_FAILURE);
1607 }
1608
1609 /* Let's manually detach everything, to make things synchronous */
1610 r = loop_device_flock(d, LOCK_SH);
1611 if (r < 0)
1612 log_warning_errno(r, "Failed to lock loopback block device, ignoring: %m");
1613
1614 r = umount_recursive(mounted_dir, 0);
1615 if (r < 0)
1616 log_warning_errno(r, "Failed to unmount '%s', ignoring: %m", mounted_dir);
1617 else
1618 loop_device_unrelinquish(d); /* Let's try to destroy the loopback device */
1619
1620 created_dir = TAKE_PTR(mounted_dir);
1621
1622 if (rmdir(created_dir) < 0)
1623 log_warning_errno(r, "Failed to remove directory '%s', ignoring: %m", created_dir);
1624
1625 temp = TAKE_PTR(created_dir);
1626
1627 return rcode;
1628}
1629
0305cf6e 1630static int action_discover(void) {
5d2a48da 1631 _cleanup_hashmap_free_ Hashmap *images = NULL;
0305cf6e
LP
1632 _cleanup_(table_unrefp) Table *t = NULL;
1633 Image *img;
1634 int r;
1635
1636 images = hashmap_new(&image_hash_ops);
1637 if (!images)
1638 return log_oom();
1639
1640 for (ImageClass cl = 0; cl < _IMAGE_CLASS_MAX; cl++) {
1641 r = image_discover(cl, NULL, images);
1642 if (r < 0)
1643 return log_error_errno(r, "Failed to discover images: %m");
1644 }
1645
1646 if ((arg_json_format_flags & JSON_FORMAT_OFF) && hashmap_isempty(images)) {
1647 log_info("No images found.");
1648 return 0;
1649 }
1650
1651 t = table_new("name", "type", "class", "ro", "path", "time", "usage");
1652 if (!t)
1653 return log_oom();
1654
8f488d46 1655 table_set_align_percent(t, table_get_cell(t, 0, 6), 100);
ba113008 1656 table_set_ersatz_string(t, TABLE_ERSATZ_DASH);
8f488d46 1657
0305cf6e
LP
1658 HASHMAP_FOREACH(img, images) {
1659
1660 if (!IN_SET(img->type, IMAGE_RAW, IMAGE_BLOCK))
1661 continue;
1662
1663 r = table_add_many(
1664 t,
1665 TABLE_STRING, img->name,
1666 TABLE_STRING, image_type_to_string(img->type),
1667 TABLE_STRING, image_class_to_string(img->class),
1668 TABLE_BOOLEAN, img->read_only,
1669 TABLE_PATH, img->path,
1670 TABLE_TIMESTAMP, img->mtime != 0 ? img->mtime : img->crtime,
1671 TABLE_SIZE, img->usage);
1672 if (r < 0)
1673 return table_log_add_error(r);
1674 }
1675
1676 (void) table_set_sort(t, (size_t) 0);
1677
1678 return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
1679}
1680
07d6072e
LP
1681static int action_attach(DissectedImage *m, LoopDevice *d) {
1682 int r;
1683
1684 assert(m);
1685 assert(d);
1686
1687 r = loop_device_set_autoclear(d, false);
1688 if (r < 0)
1689 return log_error_errno(r, "Failed to disable auto-clear logic on loopback device: %m");
1690
1691 r = dissected_image_relinquish(m);
1692 if (r < 0)
1693 return log_error_errno(r, "Failed to relinquish DM and loopback block devices: %m");
1694
1695 puts(d->node);
1696 return 0;
1697}
1698
1699static int action_detach(const char *path) {
1700 _cleanup_(loop_device_unrefp) LoopDevice *loop = NULL;
1701 _cleanup_close_ int fd = -EBADF;
1702 struct stat st;
1703 int r;
1704
43108bf8
LB
1705 assert(path);
1706
07d6072e
LP
1707 fd = open(path, O_PATH|O_CLOEXEC);
1708 if (fd < 0)
1709 return log_error_errno(errno, "Failed to open '%s': %m", path);
1710
1711 if (fstat(fd, &st) < 0)
1712 return log_error_errno(errno, "Failed to stat '%s': %m", path);
1713
1714 if (S_ISBLK(st.st_mode)) {
1715 r = loop_device_open_from_fd(fd, O_RDONLY, LOCK_EX, &loop);
1716 if (r < 0)
1717 return log_error_errno(r, "Failed to open '%s' as loopback block device: %m", path);
1718
1719 } else if (S_ISREG(st.st_mode)) {
1720 _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
07d6072e
LP
1721
1722 /* If a regular file is specified, search for a loopback block device that is backed by it */
1723
1724 r = sd_device_enumerator_new(&e);
1725 if (r < 0)
1726 return log_error_errno(r, "Failed to allocate enumerator: %m");
1727
1728 r = sd_device_enumerator_add_match_subsystem(e, "block", true);
1729 if (r < 0)
1730 return log_error_errno(r, "Failed to match block devices: %m");
1731
1732 r = sd_device_enumerator_add_match_sysname(e, "loop*");
1733 if (r < 0)
1734 return log_error_errno(r, "Failed to match loopback block devices: %m");
1735
1736 (void) sd_device_enumerator_allow_uninitialized(e);
1737
1738 FOREACH_DEVICE(e, d) {
1739 _cleanup_(loop_device_unrefp) LoopDevice *entry_loop = NULL;
07d6072e 1740
fb53ee0a 1741 if (!device_is_devtype(d, "disk")) /* Filter out partition block devices */
07d6072e
LP
1742 continue;
1743
1744 r = loop_device_open(d, O_RDONLY, LOCK_SH, &entry_loop);
1745 if (r < 0) {
fb53ee0a 1746 log_device_warning_errno(d, r, "Failed to open loopback block device, skipping: %m");
07d6072e
LP
1747 continue;
1748 }
1749
1750 if (entry_loop->backing_devno == st.st_dev && entry_loop->backing_inode == st.st_ino) {
1751 /* Found it! The kernel allows attaching a single file to multiple loopback
1752 * devices. Let's destruct them in reverse order, i.e. find the last matching
1753 * loopback device here, rather than the first. */
1754
1755 loop_device_unref(loop);
1756 loop = TAKE_PTR(entry_loop);
1757 }
1758 }
1759
1760 if (!loop)
1761 return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "No loopback block device backed by '%s' found.", path);
1762
1763 r = loop_device_flock(loop, LOCK_EX);
1764 if (r < 0)
1765 return log_error_errno(r, "Failed to upgrade device lock: %m");
1766 }
1767
1768 r = loop_device_set_autoclear(loop, true);
1769 if (r < 0)
1770 log_warning_errno(r, "Failed to enable autoclear logic on '%s', ignoring: %m", loop->node);
1771
1772 loop_device_unrelinquish(loop);
1773 return 0;
1774}
1775
dee4a623
LP
1776static int action_validate(void) {
1777 int r;
1778
1779 r = dissect_image_file_and_warn(
1780 arg_image,
1781 &arg_verity_settings,
1782 NULL,
1783 arg_image_policy,
1784 arg_flags,
1785 NULL);
1786 if (r < 0)
1787 return r;
1788
1789 if (isatty(STDOUT_FILENO) && emoji_enabled())
1790 printf("%s ", special_glyph(SPECIAL_GLYPH_SPARKLES));
1791
1792 printf("%sOK%s", ansi_highlight_green(), ansi_normal());
1793
1794 if (isatty(STDOUT_FILENO) && emoji_enabled())
1795 printf(" %s", special_glyph(SPECIAL_GLYPH_SPARKLES));
1796
1797 putc('\n', stdout);
1798 return 0;
1799}
1800
37e44c3f
LP
1801static int run(int argc, char *argv[]) {
1802 _cleanup_(dissected_image_unrefp) DissectedImage *m = NULL;
1803 _cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
6c07d570
LP
1804 uint32_t loop_flags;
1805 int open_flags, r;
33973b84 1806
5acb31a6 1807 log_setup();
33973b84 1808
a164d9d5
LP
1809 if (invoked_as(argv, "mount.ddi"))
1810 r = parse_argv_as_mount_helper(argc, argv);
1811 else
1812 r = parse_argv(argc, argv);
37e44c3f
LP
1813 if (r <= 0)
1814 return r;
33973b84 1815
d7688568
LP
1816 if (arg_image) {
1817 r = path_pick_update_warn(
1818 &arg_image,
1819 &pick_filter_image_raw,
1820 PICK_ARCHITECTURE|PICK_TRIES,
1821 /* ret_result= */ NULL);
1822 if (r < 0)
1823 return r;
1824 }
1825
b7df8622
LP
1826 switch (arg_action) {
1827 case ACTION_UMOUNT:
ac1f1adf 1828 return action_umount(arg_path);
b7df8622
LP
1829
1830 case ACTION_DETACH:
07d6072e 1831 return action_detach(arg_image);
b7df8622
LP
1832
1833 case ACTION_DISCOVER:
0305cf6e 1834 return action_discover();
ac1f1adf 1835
b7df8622
LP
1836 default:
1837 /* All other actions need the image dissected */
1838 break;
1839 }
1840
2292fa1e
DDM
1841 if (arg_image) {
1842 r = verity_settings_load(
89e62e0b
LP
1843 &arg_verity_settings,
1844 arg_image, NULL, NULL);
236d1fa2 1845 if (r < 0)
2292fa1e 1846 return log_error_errno(r, "Failed to read verity artifacts for %s: %m", arg_image);
33973b84 1847
2292fa1e
DDM
1848 if (arg_verity_settings.data_path)
1849 arg_flags |= DISSECT_IMAGE_NO_PARTITION_TABLE; /* We only support Verity per file system,
1850 * hence if there's external Verity data
1851 * available we turn off partition table
1852 * support */
1853
1854 if (arg_action == ACTION_VALIDATE)
1855 return action_validate();
1856
1857 open_flags = FLAGS_SET(arg_flags, DISSECT_IMAGE_DEVICE_READ_ONLY) ? O_RDONLY : O_RDWR;
1858 loop_flags = FLAGS_SET(arg_flags, DISSECT_IMAGE_NO_PARTITION_TABLE) ? 0 : LO_FLAGS_PARTSCAN;
1859 if (arg_in_memory)
1860 r = loop_device_make_by_path_memory(arg_image, open_flags, /* sector_size= */ UINT32_MAX, loop_flags, LOCK_SH, &d);
1861 else
1862 r = loop_device_make_by_path(arg_image, open_flags, /* sector_size= */ UINT32_MAX, loop_flags, LOCK_SH, &d);
1863 if (r < 0)
1864 return log_error_errno(r, "Failed to set up loopback device for %s: %m", arg_image);
07d6072e 1865
2292fa1e
DDM
1866 if (arg_loop_ref) {
1867 r = loop_device_set_filename(d, arg_loop_ref);
1868 if (r < 0)
1869 log_warning_errno(r, "Failed to set loop reference string to '%s', ignoring: %m", arg_loop_ref);
1870 }
88b3300f 1871
2292fa1e
DDM
1872 r = dissect_loop_device_and_warn(
1873 d,
94abea2a 1874 &arg_verity_settings,
2292fa1e
DDM
1875 /* mount_options= */ NULL,
1876 arg_image_policy,
1877 arg_flags,
1878 &m);
94abea2a
LP
1879 if (r < 0)
1880 return r;
2292fa1e
DDM
1881
1882 if (arg_action == ACTION_ATTACH)
1883 return action_attach(m, d);
1884
1885 r = dissected_image_load_verity_sig_partition(
1886 m,
1887 d->fd,
1888 &arg_verity_settings);
1889 if (r < 0)
1890 return log_error_errno(r, "Failed to load verity signature partition: %m");
1891
1892 if (arg_action != ACTION_DISSECT) {
1893 r = dissected_image_decrypt_interactively(
1894 m, NULL,
1895 &arg_verity_settings,
1896 arg_flags);
1897 if (r < 0)
1898 return r;
1899 }
94abea2a
LP
1900 }
1901
37e44c3f 1902 switch (arg_action) {
33973b84 1903
37e44c3f 1904 case ACTION_DISSECT:
6f5ef9e4 1905 return action_dissect(m, d);
37e44c3f
LP
1906
1907 case ACTION_MOUNT:
6f5ef9e4 1908 return action_mount(m, d);
37e44c3f 1909
0cf16924 1910 case ACTION_LIST:
db02190e 1911 case ACTION_MTREE:
37e44c3f
LP
1912 case ACTION_COPY_FROM:
1913 case ACTION_COPY_TO:
6f5ef9e4 1914 return action_list_or_mtree_or_copy(m, d);
33973b84 1915
06186c4c 1916 case ACTION_WITH:
6f5ef9e4 1917 return action_with(m, d);
06186c4c 1918
a2ea3b2f 1919 default:
04499a70 1920 assert_not_reached();
a2ea3b2f 1921 }
a2ea3b2f 1922}
149afb45
YW
1923
1924DEFINE_MAIN_FUNCTION(run);