]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/dissect/dissect.c
hexdecoct: make unbase64mem and unhexmem always use SIZE_MAX
[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"
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;
37e44c3f
LP
1276 _cleanup_(rmdir_and_freep) char *created_dir = NULL;
1277 _cleanup_free_ char *temp = NULL;
2292fa1e 1278 const char *root;
37e44c3f 1279 int r;
a2ea3b2f 1280
94abea2a 1281 assert(IN_SET(arg_action, ACTION_LIST, ACTION_MTREE, ACTION_COPY_FROM, ACTION_COPY_TO));
a2ea3b2f 1282
2292fa1e
DDM
1283 if (arg_image) {
1284 assert(m);
1285 assert(d);
33973b84 1286
2292fa1e
DDM
1287 r = detach_mount_namespace();
1288 if (r < 0)
1289 return log_error_errno(r, "Failed to detach mount namespace: %m");
33973b84 1290
2292fa1e
DDM
1291 r = tempfn_random_child(NULL, program_invocation_short_name, &temp);
1292 if (r < 0)
1293 return log_error_errno(r, "Failed to generate temporary mount directory: %m");
33973b84 1294
2292fa1e
DDM
1295 r = mkdir_p(temp, 0700);
1296 if (r < 0)
1297 return log_error_errno(r, "Failed to create mount point: %m");
37e44c3f 1298
2292fa1e 1299 created_dir = TAKE_PTR(temp);
33973b84 1300
8d9a1d59
LP
1301 r = dissected_image_mount_and_warn(
1302 m,
1303 created_dir,
1304 /* uid_shift= */ UID_INVALID,
1305 /* uid_range= */ UID_INVALID,
1306 /* userns_fd= */ -EBADF,
1307 arg_flags);
2292fa1e
DDM
1308 if (r < 0)
1309 return r;
33973b84 1310
2292fa1e 1311 mounted_dir = TAKE_PTR(created_dir);
41bc4849 1312
2292fa1e
DDM
1313 r = loop_device_flock(d, LOCK_UN);
1314 if (r < 0)
1315 return log_error_errno(r, "Failed to unlock loopback block device: %m");
1316
1317 r = dissected_image_relinquish(m);
1318 if (r < 0)
1319 return log_error_errno(r, "Failed to relinquish DM and loopback block devices: %m");
1320 }
1321
1322 root = mounted_dir ?: arg_root;
33973b84 1323
b7df8622
LP
1324 switch (arg_action) {
1325
1326 case ACTION_COPY_FROM: {
254d1313 1327 _cleanup_close_ int source_fd = -EBADF, target_fd = -EBADF;
33973b84 1328
2292fa1e 1329 source_fd = chase_and_open(arg_source, root, CHASE_PREFIX_ROOT|CHASE_WARN, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL);
37e44c3f
LP
1330 if (source_fd < 0)
1331 return log_error_errno(source_fd, "Failed to open source path '%s' in image '%s': %m", arg_source, arg_image);
1332
1333 /* Copying to stdout? */
1334 if (streq(arg_target, "-")) {
f5fbe71d 1335 r = copy_bytes(source_fd, STDOUT_FILENO, UINT64_MAX, COPY_REFLINK);
33973b84 1336 if (r < 0)
37e44c3f
LP
1337 return log_error_errno(r, "Failed to copy bytes from %s in mage '%s' to stdout: %m", arg_source, arg_image);
1338
38db55ab 1339 /* When we copy to stdout we don't copy any attributes (i.e. no access mode, no ownership, no xattr, no times) */
37e44c3f 1340 return 0;
33973b84
LP
1341 }
1342
37e44c3f 1343 /* Try to copy as directory? */
f9f70e06 1344 r = copy_directory_at(source_fd, NULL, AT_FDCWD, arg_target, COPY_REFLINK|COPY_MERGE_EMPTY|COPY_SIGINT|COPY_HARDLINKS);
37e44c3f
LP
1345 if (r >= 0)
1346 return 0;
1347 if (r != -ENOTDIR)
1348 return log_error_errno(r, "Failed to copy %s in image '%s' to '%s': %m", arg_source, arg_image, arg_target);
1349
1350 r = fd_verify_regular(source_fd);
1351 if (r == -EISDIR)
1352 return log_error_errno(r, "Target '%s' exists already and is not a directory.", arg_target);
1353 if (r < 0)
1354 return log_error_errno(r, "Source path %s in image '%s' is neither regular file nor directory, refusing: %m", arg_source, arg_image);
33973b84 1355
37e44c3f
LP
1356 /* Nah, it's a plain file! */
1357 target_fd = open(arg_target, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0600);
1358 if (target_fd < 0)
1359 return log_error_errno(errno, "Failed to create regular file at target path '%s': %m", arg_target);
33973b84 1360
f5fbe71d 1361 r = copy_bytes(source_fd, target_fd, UINT64_MAX, COPY_REFLINK);
37e44c3f
LP
1362 if (r < 0)
1363 return log_error_errno(r, "Failed to copy bytes from %s in mage '%s' to '%s': %m", arg_source, arg_image, arg_target);
33973b84 1364
c17cfe6e 1365 (void) copy_xattr(source_fd, NULL, target_fd, NULL, 0);
37e44c3f
LP
1366 (void) copy_access(source_fd, target_fd);
1367 (void) copy_times(source_fd, target_fd, 0);
33973b84 1368
37e44c3f 1369 /* When this is a regular file we don't copy ownership! */
b7df8622
LP
1370 return 0;
1371 }
33973b84 1372
b7df8622 1373 case ACTION_COPY_TO: {
254d1313 1374 _cleanup_close_ int source_fd = -EBADF, target_fd = -EBADF, dfd = -EBADF;
cfb623b6 1375 _cleanup_free_ char *dn = NULL, *bn = NULL;
83802e9a 1376 bool is_dir;
33973b84 1377
45519d13
LP
1378 r = path_extract_directory(arg_target, &dn);
1379 if (r < 0)
cfb623b6
LP
1380 return log_error_errno(r, "Failed to extract directory from target path '%s': %m", arg_target);
1381 r = path_extract_filename(arg_target, &bn);
1382 if (r < 0)
1383 return log_error_errno(r, "Failed to extract filename from target path '%s': %m", arg_target);
83802e9a 1384 is_dir = r == O_DIRECTORY;
37e44c3f 1385
2292fa1e 1386 r = chase(dn, root, CHASE_PREFIX_ROOT|CHASE_WARN, NULL, &dfd);
37e44c3f
LP
1387 if (r < 0)
1388 return log_error_errno(r, "Failed to open '%s': %m", dn);
33973b84 1389
37e44c3f
LP
1390 /* Are we reading from stdin? */
1391 if (streq(arg_source, "-")) {
83802e9a
LP
1392 if (is_dir)
1393 return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Cannot copy STDIN to a directory, refusing.");
1394
cfb623b6 1395 target_fd = openat(dfd, bn, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_EXCL, 0644);
33973b84 1396 if (target_fd < 0)
37e44c3f 1397 return log_error_errno(errno, "Failed to open target file '%s': %m", arg_target);
33973b84 1398
f5fbe71d 1399 r = copy_bytes(STDIN_FILENO, target_fd, UINT64_MAX, COPY_REFLINK);
33973b84 1400 if (r < 0)
37e44c3f 1401 return log_error_errno(r, "Failed to copy bytes from stdin to '%s' in image '%s': %m", arg_target, arg_image);
33973b84 1402
37e44c3f
LP
1403 /* When we copy from stdin we don't copy any attributes (i.e. no access mode, no ownership, no xattr, no times) */
1404 return 0;
1405 }
33973b84 1406
37e44c3f
LP
1407 source_fd = open(arg_source, O_RDONLY|O_CLOEXEC|O_NOCTTY);
1408 if (source_fd < 0)
1409 return log_error_errno(source_fd, "Failed to open source path '%s': %m", arg_source);
33973b84 1410
37e44c3f
LP
1411 r = fd_verify_regular(source_fd);
1412 if (r < 0) {
1413 if (r != -EISDIR)
1414 return log_error_errno(r, "Source '%s' is neither regular file nor directory: %m", arg_source);
33973b84 1415
37e44c3f 1416 /* We are looking at a directory. */
33973b84 1417
cfb623b6 1418 target_fd = openat(dfd, bn, O_RDONLY|O_DIRECTORY|O_CLOEXEC);
37e44c3f
LP
1419 if (target_fd < 0) {
1420 if (errno != ENOENT)
1421 return log_error_errno(errno, "Failed to open destination '%s': %m", arg_target);
33973b84 1422
ad6fae7f 1423 r = copy_tree_at(source_fd, ".", dfd, bn, UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS, NULL, NULL);
37e44c3f 1424 } else
ad6fae7f 1425 r = copy_tree_at(source_fd, ".", target_fd, ".", UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS, NULL, NULL);
33973b84 1426 if (r < 0)
37e44c3f 1427 return log_error_errno(r, "Failed to copy '%s' to '%s' in image '%s': %m", arg_source, arg_target, arg_image);
33973b84 1428
37e44c3f
LP
1429 return 0;
1430 }
33973b84 1431
83802e9a
LP
1432 if (is_dir)
1433 return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Source is a regular file, but target is not, refusing.");
1434
37e44c3f 1435 /* We area looking at a regular file */
83802e9a 1436 target_fd = openat(dfd, bn, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_EXCL, 0600);
37e44c3f
LP
1437 if (target_fd < 0)
1438 return log_error_errno(errno, "Failed to open target file '%s': %m", arg_target);
33973b84 1439
f5fbe71d 1440 r = copy_bytes(source_fd, target_fd, UINT64_MAX, COPY_REFLINK);
37e44c3f
LP
1441 if (r < 0)
1442 return log_error_errno(r, "Failed to copy bytes from '%s' to '%s' in image '%s': %m", arg_source, arg_target, arg_image);
33973b84 1443
c17cfe6e 1444 (void) copy_xattr(source_fd, NULL, target_fd, NULL, 0);
37e44c3f
LP
1445 (void) copy_access(source_fd, target_fd);
1446 (void) copy_times(source_fd, target_fd, 0);
33973b84 1447
37e44c3f 1448 /* When this is a regular file we don't copy ownership! */
b7df8622
LP
1449 return 0;
1450 }
0cf16924 1451
b7df8622
LP
1452 case ACTION_LIST:
1453 case ACTION_MTREE: {
254d1313 1454 _cleanup_close_ int dfd = -EBADF;
0cf16924 1455
2292fa1e 1456 dfd = open(root, O_DIRECTORY|O_CLOEXEC|O_RDONLY);
0cf16924
AAF
1457 if (dfd < 0)
1458 return log_error_errno(errno, "Failed to open mount directory: %m");
1459
2e1f76f6
LP
1460 pager_open(arg_pager_flags);
1461
db02190e
LP
1462 if (arg_action == ACTION_LIST)
1463 r = recurse_dir(dfd, NULL, 0, UINT_MAX, RECURSE_DIR_SORT, list_print_item, NULL);
1464 else if (arg_action == ACTION_MTREE)
1465 r = recurse_dir(dfd, ".", STATX_TYPE|STATX_MODE|STATX_UID|STATX_GID|STATX_SIZE, UINT_MAX, RECURSE_DIR_SORT|RECURSE_DIR_INODE_FD|RECURSE_DIR_TOPLEVEL, mtree_print_item, NULL);
1466 else
1467 assert_not_reached();
0cf16924
AAF
1468 if (r < 0)
1469 return log_error_errno(r, "Failed to list image: %m");
b7df8622 1470 return 0;
37e44c3f 1471 }
33973b84 1472
b7df8622
LP
1473 default:
1474 assert_not_reached();
1475 }
37e44c3f 1476}
33973b84 1477
ac1f1adf 1478static int action_umount(const char *path) {
254d1313 1479 _cleanup_close_ int fd = -EBADF;
040d3439 1480 _cleanup_free_ char *canonical = NULL;
ac1f1adf 1481 _cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
040d3439 1482 _cleanup_(sd_device_unrefp) sd_device *dev = NULL;
e8383058 1483 int r;
ac1f1adf 1484
f461a28d 1485 fd = chase_and_open(path, NULL, 0, O_DIRECTORY, &canonical);
ac1f1adf
DDM
1486 if (fd == -ENOTDIR)
1487 return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "'%s' is not a directory", path);
1488 if (fd < 0)
1489 return log_error_errno(fd, "Failed to resolve path '%s': %m", path);
1490
1491 r = fd_is_mount_point(fd, NULL, 0);
1492 if (r == 0)
1493 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "'%s' is not a mount point", canonical);
1494 if (r < 0)
1495 return log_error_errno(r, "Failed to determine whether '%s' is a mount point: %m", canonical);
1496
040d3439 1497 r = block_device_new_from_fd(fd, BLOCK_DEVICE_LOOKUP_WHOLE_DISK | BLOCK_DEVICE_LOOKUP_BACKING, &dev);
41a95b18 1498 if (r < 0) {
254d1313 1499 _cleanup_close_ int usr_fd = -EBADF;
41a95b18
YW
1500
1501 /* The command `systemd-dissect --mount` expects that the image at least has the root or /usr
1502 * partition. If it does not have the root partition, then we mount the /usr partition on a
1503 * tmpfs. Hence, let's try to find the backing block device through the /usr partition. */
1504
1505 usr_fd = openat(fd, "usr", O_CLOEXEC | O_DIRECTORY | O_NOFOLLOW);
1506 if (usr_fd < 0)
1507 return log_error_errno(errno, "Failed to open '%s/usr': %m", canonical);
1508
1509 r = block_device_new_from_fd(usr_fd, BLOCK_DEVICE_LOOKUP_WHOLE_DISK | BLOCK_DEVICE_LOOKUP_BACKING, &dev);
1510 }
ac1f1adf
DDM
1511 if (r < 0)
1512 return log_error_errno(r, "Failed to find backing block device for '%s': %m", canonical);
1513
040d3439 1514 r = loop_device_open(dev, 0, LOCK_EX, &d);
ac1f1adf 1515 if (r < 0)
040d3439 1516 return log_device_error_errno(dev, r, "Failed to open loopback block device: %m");
ac1f1adf 1517
ac1f1adf
DDM
1518 /* We've locked the loop device, now we're ready to unmount. To allow the unmount to succeed, we have
1519 * to close the O_PATH fd we opened earlier. */
1520 fd = safe_close(fd);
1521
1522 r = umount_recursive(canonical, 0);
1523 if (r < 0)
1524 return log_error_errno(r, "Failed to unmount '%s': %m", canonical);
1525
0b75493d 1526 /* We managed to lock and unmount successfully? That means we can try to remove the loop device. */
ac1f1adf
DDM
1527 loop_device_unrelinquish(d);
1528
1529 if (arg_rmdir) {
e8383058
LP
1530 r = RET_NERRNO(rmdir(canonical));
1531 if (r < 0)
1532 return log_error_errno(r, "Failed to remove mount directory '%s': %m", canonical);
ac1f1adf 1533 }
ac1f1adf 1534
e8383058 1535 return 0;
ac1f1adf
DDM
1536}
1537
06186c4c
LP
1538static int action_with(DissectedImage *m, LoopDevice *d) {
1539 _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
1540 _cleanup_(rmdir_and_freep) char *created_dir = NULL;
1541 _cleanup_free_ char *temp = NULL;
1542 int r, rcode;
1543
94abea2a
LP
1544 assert(m);
1545 assert(d);
1546 assert(arg_action == ACTION_WITH);
06186c4c
LP
1547
1548 r = tempfn_random_child(NULL, program_invocation_short_name, &temp);
1549 if (r < 0)
1550 return log_error_errno(r, "Failed to generate temporary mount directory: %m");
1551
1552 r = mkdir_p(temp, 0700);
1553 if (r < 0)
1554 return log_error_errno(r, "Failed to create mount point: %m");
1555
1556 created_dir = TAKE_PTR(temp);
1557
8d9a1d59
LP
1558 r = dissected_image_mount_and_warn(
1559 m,
1560 created_dir,
1561 /* uid_shift= */ UID_INVALID,
1562 /* uid_range= */ UID_INVALID,
1563 /* userns_fd= */ -EBADF,
1564 arg_flags);
06186c4c
LP
1565 if (r < 0)
1566 return r;
1567
1568 mounted_dir = TAKE_PTR(created_dir);
1569
1570 r = dissected_image_relinquish(m);
1571 if (r < 0)
1572 return log_error_errno(r, "Failed to relinquish DM and loopback block devices: %m");
1573
1574 r = loop_device_flock(d, LOCK_UN);
1575 if (r < 0)
1576 return log_error_errno(r, "Failed to unlock loopback block device: %m");
1577
1578 rcode = safe_fork("(with)", FORK_CLOSE_ALL_FDS|FORK_LOG|FORK_WAIT, NULL);
1579 if (rcode == 0) {
1580 /* Child */
1581
1582 if (chdir(mounted_dir) < 0) {
1583 log_error_errno(errno, "Failed to change to '%s' directory: %m", mounted_dir);
1584 _exit(EXIT_FAILURE);
1585 }
1586
1587 if (setenv("SYSTEMD_DISSECT_ROOT", mounted_dir, /* overwrite= */ true) < 0) {
1588 log_error_errno(errno, "Failed to set $SYSTEMD_DISSECT_ROOT: %m");
1589 _exit(EXIT_FAILURE);
1590 }
1591
47838b55
DDM
1592 if (setenv("SYSTEMD_DISSECT_DEVICE", d->node, /* overwrite= */ true) < 0) {
1593 log_error_errno(errno, "Failed to set $SYSTEMD_DISSECT_DEVICE: %m");
1594 _exit(EXIT_FAILURE);
1595 }
1596
06186c4c
LP
1597 if (strv_isempty(arg_argv)) {
1598 const char *sh;
1599
1600 sh = secure_getenv("SHELL");
1601 if (sh) {
1602 execvp(sh, STRV_MAKE(sh));
1603 log_warning_errno(errno, "Failed to execute $SHELL, falling back to /bin/sh: %m");
1604 }
1605
1606 execl("/bin/sh", "sh", NULL);
1607 log_error_errno(errno, "Failed to invoke /bin/sh: %m");
1608 } else {
1609 execvp(arg_argv[0], arg_argv);
1610 log_error_errno(errno, "Failed to execute '%s': %m", arg_argv[0]);
1611 }
1612
1613 _exit(EXIT_FAILURE);
1614 }
1615
1616 /* Let's manually detach everything, to make things synchronous */
1617 r = loop_device_flock(d, LOCK_SH);
1618 if (r < 0)
1619 log_warning_errno(r, "Failed to lock loopback block device, ignoring: %m");
1620
1621 r = umount_recursive(mounted_dir, 0);
1622 if (r < 0)
1623 log_warning_errno(r, "Failed to unmount '%s', ignoring: %m", mounted_dir);
1624 else
1625 loop_device_unrelinquish(d); /* Let's try to destroy the loopback device */
1626
1627 created_dir = TAKE_PTR(mounted_dir);
1628
1629 if (rmdir(created_dir) < 0)
1630 log_warning_errno(r, "Failed to remove directory '%s', ignoring: %m", created_dir);
1631
1632 temp = TAKE_PTR(created_dir);
1633
1634 return rcode;
1635}
1636
0305cf6e 1637static int action_discover(void) {
5d2a48da 1638 _cleanup_hashmap_free_ Hashmap *images = NULL;
0305cf6e
LP
1639 _cleanup_(table_unrefp) Table *t = NULL;
1640 Image *img;
1641 int r;
1642
1643 images = hashmap_new(&image_hash_ops);
1644 if (!images)
1645 return log_oom();
1646
1647 for (ImageClass cl = 0; cl < _IMAGE_CLASS_MAX; cl++) {
1648 r = image_discover(cl, NULL, images);
1649 if (r < 0)
1650 return log_error_errno(r, "Failed to discover images: %m");
1651 }
1652
1653 if ((arg_json_format_flags & JSON_FORMAT_OFF) && hashmap_isempty(images)) {
1654 log_info("No images found.");
1655 return 0;
1656 }
1657
1658 t = table_new("name", "type", "class", "ro", "path", "time", "usage");
1659 if (!t)
1660 return log_oom();
1661
8f488d46 1662 table_set_align_percent(t, table_get_cell(t, 0, 6), 100);
ba113008 1663 table_set_ersatz_string(t, TABLE_ERSATZ_DASH);
8f488d46 1664
0305cf6e
LP
1665 HASHMAP_FOREACH(img, images) {
1666
1667 if (!IN_SET(img->type, IMAGE_RAW, IMAGE_BLOCK))
1668 continue;
1669
1670 r = table_add_many(
1671 t,
1672 TABLE_STRING, img->name,
1673 TABLE_STRING, image_type_to_string(img->type),
1674 TABLE_STRING, image_class_to_string(img->class),
1675 TABLE_BOOLEAN, img->read_only,
1676 TABLE_PATH, img->path,
1677 TABLE_TIMESTAMP, img->mtime != 0 ? img->mtime : img->crtime,
1678 TABLE_SIZE, img->usage);
1679 if (r < 0)
1680 return table_log_add_error(r);
1681 }
1682
1683 (void) table_set_sort(t, (size_t) 0);
1684
1685 return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
1686}
1687
07d6072e
LP
1688static int action_attach(DissectedImage *m, LoopDevice *d) {
1689 int r;
1690
1691 assert(m);
1692 assert(d);
1693
1694 r = loop_device_set_autoclear(d, false);
1695 if (r < 0)
1696 return log_error_errno(r, "Failed to disable auto-clear logic on loopback device: %m");
1697
1698 r = dissected_image_relinquish(m);
1699 if (r < 0)
1700 return log_error_errno(r, "Failed to relinquish DM and loopback block devices: %m");
1701
1702 puts(d->node);
1703 return 0;
1704}
1705
1706static int action_detach(const char *path) {
1707 _cleanup_(loop_device_unrefp) LoopDevice *loop = NULL;
1708 _cleanup_close_ int fd = -EBADF;
1709 struct stat st;
1710 int r;
1711
43108bf8
LB
1712 assert(path);
1713
07d6072e
LP
1714 fd = open(path, O_PATH|O_CLOEXEC);
1715 if (fd < 0)
1716 return log_error_errno(errno, "Failed to open '%s': %m", path);
1717
1718 if (fstat(fd, &st) < 0)
1719 return log_error_errno(errno, "Failed to stat '%s': %m", path);
1720
1721 if (S_ISBLK(st.st_mode)) {
1722 r = loop_device_open_from_fd(fd, O_RDONLY, LOCK_EX, &loop);
1723 if (r < 0)
1724 return log_error_errno(r, "Failed to open '%s' as loopback block device: %m", path);
1725
1726 } else if (S_ISREG(st.st_mode)) {
1727 _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
07d6072e
LP
1728
1729 /* If a regular file is specified, search for a loopback block device that is backed by it */
1730
1731 r = sd_device_enumerator_new(&e);
1732 if (r < 0)
1733 return log_error_errno(r, "Failed to allocate enumerator: %m");
1734
1735 r = sd_device_enumerator_add_match_subsystem(e, "block", true);
1736 if (r < 0)
1737 return log_error_errno(r, "Failed to match block devices: %m");
1738
1739 r = sd_device_enumerator_add_match_sysname(e, "loop*");
1740 if (r < 0)
1741 return log_error_errno(r, "Failed to match loopback block devices: %m");
1742
1743 (void) sd_device_enumerator_allow_uninitialized(e);
1744
1745 FOREACH_DEVICE(e, d) {
1746 _cleanup_(loop_device_unrefp) LoopDevice *entry_loop = NULL;
07d6072e 1747
fb53ee0a 1748 if (!device_is_devtype(d, "disk")) /* Filter out partition block devices */
07d6072e
LP
1749 continue;
1750
1751 r = loop_device_open(d, O_RDONLY, LOCK_SH, &entry_loop);
1752 if (r < 0) {
fb53ee0a 1753 log_device_warning_errno(d, r, "Failed to open loopback block device, skipping: %m");
07d6072e
LP
1754 continue;
1755 }
1756
1757 if (entry_loop->backing_devno == st.st_dev && entry_loop->backing_inode == st.st_ino) {
1758 /* Found it! The kernel allows attaching a single file to multiple loopback
1759 * devices. Let's destruct them in reverse order, i.e. find the last matching
1760 * loopback device here, rather than the first. */
1761
1762 loop_device_unref(loop);
1763 loop = TAKE_PTR(entry_loop);
1764 }
1765 }
1766
1767 if (!loop)
1768 return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "No loopback block device backed by '%s' found.", path);
1769
1770 r = loop_device_flock(loop, LOCK_EX);
1771 if (r < 0)
1772 return log_error_errno(r, "Failed to upgrade device lock: %m");
1773 }
1774
1775 r = loop_device_set_autoclear(loop, true);
1776 if (r < 0)
1777 log_warning_errno(r, "Failed to enable autoclear logic on '%s', ignoring: %m", loop->node);
1778
1779 loop_device_unrelinquish(loop);
1780 return 0;
1781}
1782
dee4a623
LP
1783static int action_validate(void) {
1784 int r;
1785
1786 r = dissect_image_file_and_warn(
1787 arg_image,
1788 &arg_verity_settings,
1789 NULL,
1790 arg_image_policy,
1791 arg_flags,
1792 NULL);
1793 if (r < 0)
1794 return r;
1795
1796 if (isatty(STDOUT_FILENO) && emoji_enabled())
1797 printf("%s ", special_glyph(SPECIAL_GLYPH_SPARKLES));
1798
1799 printf("%sOK%s", ansi_highlight_green(), ansi_normal());
1800
1801 if (isatty(STDOUT_FILENO) && emoji_enabled())
1802 printf(" %s", special_glyph(SPECIAL_GLYPH_SPARKLES));
1803
1804 putc('\n', stdout);
1805 return 0;
1806}
1807
37e44c3f
LP
1808static int run(int argc, char *argv[]) {
1809 _cleanup_(dissected_image_unrefp) DissectedImage *m = NULL;
1810 _cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
6c07d570
LP
1811 uint32_t loop_flags;
1812 int open_flags, r;
33973b84 1813
5acb31a6 1814 log_setup();
33973b84 1815
a164d9d5
LP
1816 if (invoked_as(argv, "mount.ddi"))
1817 r = parse_argv_as_mount_helper(argc, argv);
1818 else
1819 r = parse_argv(argc, argv);
37e44c3f
LP
1820 if (r <= 0)
1821 return r;
33973b84 1822
d7688568
LP
1823 if (arg_image) {
1824 r = path_pick_update_warn(
1825 &arg_image,
1826 &pick_filter_image_raw,
1827 PICK_ARCHITECTURE|PICK_TRIES,
1828 /* ret_result= */ NULL);
1829 if (r < 0)
1830 return r;
1831 }
1832
b7df8622
LP
1833 switch (arg_action) {
1834 case ACTION_UMOUNT:
ac1f1adf 1835 return action_umount(arg_path);
b7df8622
LP
1836
1837 case ACTION_DETACH:
07d6072e 1838 return action_detach(arg_image);
b7df8622
LP
1839
1840 case ACTION_DISCOVER:
0305cf6e 1841 return action_discover();
ac1f1adf 1842
b7df8622
LP
1843 default:
1844 /* All other actions need the image dissected */
1845 break;
1846 }
1847
2292fa1e
DDM
1848 if (arg_image) {
1849 r = verity_settings_load(
89e62e0b
LP
1850 &arg_verity_settings,
1851 arg_image, NULL, NULL);
236d1fa2 1852 if (r < 0)
2292fa1e 1853 return log_error_errno(r, "Failed to read verity artifacts for %s: %m", arg_image);
33973b84 1854
2292fa1e
DDM
1855 if (arg_verity_settings.data_path)
1856 arg_flags |= DISSECT_IMAGE_NO_PARTITION_TABLE; /* We only support Verity per file system,
1857 * hence if there's external Verity data
1858 * available we turn off partition table
1859 * support */
1860
1861 if (arg_action == ACTION_VALIDATE)
1862 return action_validate();
1863
1864 open_flags = FLAGS_SET(arg_flags, DISSECT_IMAGE_DEVICE_READ_ONLY) ? O_RDONLY : O_RDWR;
1865 loop_flags = FLAGS_SET(arg_flags, DISSECT_IMAGE_NO_PARTITION_TABLE) ? 0 : LO_FLAGS_PARTSCAN;
1866 if (arg_in_memory)
1867 r = loop_device_make_by_path_memory(arg_image, open_flags, /* sector_size= */ UINT32_MAX, loop_flags, LOCK_SH, &d);
1868 else
1869 r = loop_device_make_by_path(arg_image, open_flags, /* sector_size= */ UINT32_MAX, loop_flags, LOCK_SH, &d);
1870 if (r < 0)
1871 return log_error_errno(r, "Failed to set up loopback device for %s: %m", arg_image);
07d6072e 1872
2292fa1e
DDM
1873 if (arg_loop_ref) {
1874 r = loop_device_set_filename(d, arg_loop_ref);
1875 if (r < 0)
1876 log_warning_errno(r, "Failed to set loop reference string to '%s', ignoring: %m", arg_loop_ref);
1877 }
88b3300f 1878
2292fa1e
DDM
1879 r = dissect_loop_device_and_warn(
1880 d,
94abea2a 1881 &arg_verity_settings,
2292fa1e
DDM
1882 /* mount_options= */ NULL,
1883 arg_image_policy,
1884 arg_flags,
1885 &m);
94abea2a
LP
1886 if (r < 0)
1887 return r;
2292fa1e
DDM
1888
1889 if (arg_action == ACTION_ATTACH)
1890 return action_attach(m, d);
1891
1892 r = dissected_image_load_verity_sig_partition(
1893 m,
1894 d->fd,
1895 &arg_verity_settings);
1896 if (r < 0)
1897 return log_error_errno(r, "Failed to load verity signature partition: %m");
1898
1899 if (arg_action != ACTION_DISSECT) {
1900 r = dissected_image_decrypt_interactively(
1901 m, NULL,
1902 &arg_verity_settings,
1903 arg_flags);
1904 if (r < 0)
1905 return r;
1906 }
94abea2a
LP
1907 }
1908
37e44c3f 1909 switch (arg_action) {
33973b84 1910
37e44c3f 1911 case ACTION_DISSECT:
6f5ef9e4 1912 return action_dissect(m, d);
37e44c3f
LP
1913
1914 case ACTION_MOUNT:
6f5ef9e4 1915 return action_mount(m, d);
37e44c3f 1916
0cf16924 1917 case ACTION_LIST:
db02190e 1918 case ACTION_MTREE:
37e44c3f
LP
1919 case ACTION_COPY_FROM:
1920 case ACTION_COPY_TO:
6f5ef9e4 1921 return action_list_or_mtree_or_copy(m, d);
33973b84 1922
06186c4c 1923 case ACTION_WITH:
6f5ef9e4 1924 return action_with(m, d);
06186c4c 1925
a2ea3b2f 1926 default:
04499a70 1927 assert_not_reached();
a2ea3b2f 1928 }
a2ea3b2f 1929}
149afb45
YW
1930
1931DEFINE_MAIN_FUNCTION(run);