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