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