]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/dissect/dissect.c
strv: make iterator in STRV_FOREACH() declaread in the loop
[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>
16b74592
LP
7#include <sys/ioctl.h>
8#include <sys/mount.h>
a2ea3b2f
LP
9
10#include "architecture.h"
f4351959 11#include "chase-symlinks.h"
33973b84 12#include "copy.h"
a2ea3b2f 13#include "dissect-image.h"
994c9c70 14#include "env-util.h"
33973b84 15#include "fd-util.h"
89e62e0b 16#include "fileio.h"
89d00f2e 17#include "format-table.h"
16b74592 18#include "format-util.h"
33973b84 19#include "fs-util.h"
4623e8e6 20#include "hexdecoct.h"
a2ea3b2f
LP
21#include "log.h"
22#include "loop-util.h"
149afb45 23#include "main-func.h"
33973b84
LP
24#include "mkdir.h"
25#include "mount-util.h"
26#include "namespace-util.h"
614b022c 27#include "parse-argument.h"
e475f729 28#include "parse-util.h"
e7cbe5cb 29#include "path-util.h"
5c05f062 30#include "pretty-print.h"
33973b84 31#include "stat-util.h"
a2ea3b2f 32#include "string-util.h"
a1edd22e 33#include "strv.h"
5c05f062 34#include "terminal-util.h"
33973b84 35#include "tmpfile-util.h"
2d3a5a73 36#include "user-util.h"
a2ea3b2f
LP
37#include "util.h"
38
39static enum {
40 ACTION_DISSECT,
41 ACTION_MOUNT,
33973b84
LP
42 ACTION_COPY_FROM,
43 ACTION_COPY_TO,
a2ea3b2f
LP
44} arg_action = ACTION_DISSECT;
45static const char *arg_image = NULL;
46static const char *arg_path = NULL;
33973b84
LP
47static const char *arg_source = NULL;
48static const char *arg_target = NULL;
4b5de5dd
LP
49static DissectImageFlags arg_flags =
50 DISSECT_IMAGE_GENERIC_ROOT |
4b5de5dd
LP
51 DISSECT_IMAGE_DISCARD_ON_LOOP |
52 DISSECT_IMAGE_RELAX_VAR_CHECK |
53 DISSECT_IMAGE_FSCK |
74a54bae
LP
54 DISSECT_IMAGE_USR_NO_ROOT |
55 DISSECT_IMAGE_GROWFS;
aee36b4e 56static VeritySettings arg_verity_settings = VERITY_SETTINGS_DEFAULT;
6a01ea4a 57static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
17547fb5
LP
58static PagerFlags arg_pager_flags = 0;
59static bool arg_legend = true;
a2ea3b2f 60
89e62e0b 61STATIC_DESTRUCTOR_REGISTER(arg_verity_settings, verity_settings_done);
149afb45 62
5c05f062
LP
63static int help(void) {
64 _cleanup_free_ char *link = NULL;
65 int r;
66
67 r = terminal_urlify_man("systemd-dissect", "1", &link);
68 if (r < 0)
69 return log_oom();
70
71 printf("%1$s [OPTIONS...] IMAGE\n"
33973b84
LP
72 "%1$s [OPTIONS...] --mount IMAGE PATH\n"
73 "%1$s [OPTIONS...] --copy-from IMAGE PATH [TARGET]\n"
74 "%1$s [OPTIONS...] --copy-to IMAGE [SOURCE] PATH\n\n"
5c05f062
LP
75 "%5$sDissect a file system OS image.%6$s\n\n"
76 "%3$sOptions:%4$s\n"
17547fb5
LP
77 " --no-pager Do not pipe output into a pager\n"
78 " --no-legend Do not show the headers and footers\n"
e7cbe5cb
LB
79 " -r --read-only Mount read-only\n"
80 " --fsck=BOOL Run fsck before mounting\n"
74a54bae 81 " --growfs=BOOL Grow file system to partition size, if marked\n"
5c05f062 82 " --mkdir Make mount directory before mounting, if missing\n"
e7cbe5cb
LB
83 " --discard=MODE Choose 'discard' mode (disabled, loop, all, crypto)\n"
84 " --root-hash=HASH Specify root hash for verity\n"
c2923fdc
LB
85 " --root-hash-sig=SIG Specify pkcs7 signature of root hash for verity\n"
86 " as a DER encoded PKCS7, either as a path to a file\n"
87 " or as an ASCII base64 encoded string prefixed by\n"
88 " 'base64:'\n"
e7cbe5cb 89 " --verity-data=PATH Specify data file with hash tree for verity if it is\n"
5c05f062 90 " not embedded in IMAGE\n"
de8231b0
LP
91 " --json=pretty|short|off\n"
92 " Generate JSON output\n"
5c05f062
LP
93 "\n%3$sCommands:%4$s\n"
94 " -h --help Show this help\n"
95 " --version Show package version\n"
96 " -m --mount Mount the image to the specified directory\n"
97 " -M Shortcut for --mount --mkdir\n"
33973b84
LP
98 " -x --copy-from Copy files from image to host\n"
99 " -a --copy-to Copy files from host to image\n"
bc556335
DDM
100 "\nSee the %2$s for details.\n",
101 program_invocation_short_name,
102 link,
103 ansi_underline(),
104 ansi_normal(),
105 ansi_highlight(),
106 ansi_normal());
5c05f062
LP
107
108 return 0;
a2ea3b2f
LP
109}
110
111static int parse_argv(int argc, char *argv[]) {
112
113 enum {
114 ARG_VERSION = 0x100,
17547fb5
LP
115 ARG_NO_PAGER,
116 ARG_NO_LEGEND,
18b5886e 117 ARG_DISCARD,
e475f729 118 ARG_FSCK,
74a54bae 119 ARG_GROWFS,
89e62e0b 120 ARG_ROOT_HASH,
c2923fdc 121 ARG_ROOT_HASH_SIG,
89e62e0b 122 ARG_VERITY_DATA,
5c05f062 123 ARG_MKDIR,
de8231b0 124 ARG_JSON,
a2ea3b2f
LP
125 };
126
127 static const struct option options[] = {
c2923fdc
LB
128 { "help", no_argument, NULL, 'h' },
129 { "version", no_argument, NULL, ARG_VERSION },
17547fb5
LP
130 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
131 { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
c2923fdc
LB
132 { "mount", no_argument, NULL, 'm' },
133 { "read-only", no_argument, NULL, 'r' },
134 { "discard", required_argument, NULL, ARG_DISCARD },
c2923fdc 135 { "fsck", required_argument, NULL, ARG_FSCK },
74a54bae 136 { "growfs", required_argument, NULL, ARG_GROWFS },
89e62e0b 137 { "root-hash", required_argument, NULL, ARG_ROOT_HASH },
c2923fdc 138 { "root-hash-sig", required_argument, NULL, ARG_ROOT_HASH_SIG },
89e62e0b 139 { "verity-data", required_argument, NULL, ARG_VERITY_DATA },
5c05f062 140 { "mkdir", no_argument, NULL, ARG_MKDIR },
33973b84
LP
141 { "copy-from", no_argument, NULL, 'x' },
142 { "copy-to", no_argument, NULL, 'a' },
de8231b0 143 { "json", required_argument, NULL, ARG_JSON },
a2ea3b2f
LP
144 {}
145 };
146
4623e8e6 147 int c, r;
a2ea3b2f
LP
148
149 assert(argc >= 0);
150 assert(argv);
151
33973b84 152 while ((c = getopt_long(argc, argv, "hmrMxa", options, NULL)) >= 0) {
a2ea3b2f
LP
153
154 switch (c) {
155
156 case 'h':
5c05f062 157 return help();
a2ea3b2f
LP
158
159 case ARG_VERSION:
160 return version();
161
17547fb5
LP
162 case ARG_NO_PAGER:
163 arg_pager_flags |= PAGER_DISABLE;
164 break;
165
166 case ARG_NO_LEGEND:
167 arg_legend = false;
168 break;
169
a2ea3b2f
LP
170 case 'm':
171 arg_action = ACTION_MOUNT;
172 break;
173
5c05f062
LP
174 case ARG_MKDIR:
175 arg_flags |= DISSECT_IMAGE_MKDIR;
176 break;
177
178 case 'M':
179 /* Shortcut combination of the above two */
180 arg_action = ACTION_MOUNT;
181 arg_flags |= DISSECT_IMAGE_MKDIR;
182 break;
183
33973b84
LP
184 case 'x':
185 arg_action = ACTION_COPY_FROM;
186 arg_flags |= DISSECT_IMAGE_READ_ONLY;
187 break;
188
189 case 'a':
190 arg_action = ACTION_COPY_TO;
191 break;
192
a2ea3b2f 193 case 'r':
18b5886e
LP
194 arg_flags |= DISSECT_IMAGE_READ_ONLY;
195 break;
196
971e2ef0
ZJS
197 case ARG_DISCARD: {
198 DissectImageFlags flags;
199
18b5886e 200 if (streq(optarg, "disabled"))
971e2ef0 201 flags = 0;
18b5886e 202 else if (streq(optarg, "loop"))
971e2ef0 203 flags = DISSECT_IMAGE_DISCARD_ON_LOOP;
18b5886e 204 else if (streq(optarg, "all"))
971e2ef0 205 flags = DISSECT_IMAGE_DISCARD_ON_LOOP | DISSECT_IMAGE_DISCARD;
18b5886e 206 else if (streq(optarg, "crypt"))
971e2ef0 207 flags = DISSECT_IMAGE_DISCARD_ANY;
140788f7
LP
208 else if (streq(optarg, "list")) {
209 puts("disabled\n"
210 "all\n"
211 "crypt\n"
212 "loop");
213 return 0;
214 } else
baaa35ad
ZJS
215 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
216 "Unknown --discard= parameter: %s",
217 optarg);
971e2ef0 218 arg_flags = (arg_flags & ~DISSECT_IMAGE_DISCARD_ANY) | flags;
18b5886e 219
a2ea3b2f 220 break;
971e2ef0 221 }
a2ea3b2f 222
4623e8e6 223 case ARG_ROOT_HASH: {
89e62e0b 224 _cleanup_free_ void *p = NULL;
4623e8e6
LP
225 size_t l;
226
227 r = unhexmem(optarg, strlen(optarg), &p, &l);
228 if (r < 0)
63cf2d75 229 return log_error_errno(r, "Failed to parse root hash '%s': %m", optarg);
89e62e0b
LP
230 if (l < sizeof(sd_id128_t))
231 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
232 "Root hash must be at least 128bit long: %s", optarg);
4623e8e6 233
89e62e0b
LP
234 free_and_replace(arg_verity_settings.root_hash, p);
235 arg_verity_settings.root_hash_size = l;
4623e8e6
LP
236 break;
237 }
238
c2923fdc
LB
239 case ARG_ROOT_HASH_SIG: {
240 char *value;
89e62e0b
LP
241 size_t l;
242 void *p;
c2923fdc
LB
243
244 if ((value = startswith(optarg, "base64:"))) {
c2923fdc
LB
245 r = unbase64mem(value, strlen(value), &p, &l);
246 if (r < 0)
247 return log_error_errno(r, "Failed to parse root hash signature '%s': %m", optarg);
c2923fdc 248 } else {
89e62e0b 249 r = read_full_file(optarg, (char**) &p, &l);
c2923fdc 250 if (r < 0)
89e62e0b 251 return log_error_errno(r, "Failed to read root hash signature file '%s': %m", optarg);
c2923fdc
LB
252 }
253
89e62e0b
LP
254 free_and_replace(arg_verity_settings.root_hash_sig, p);
255 arg_verity_settings.root_hash_sig_size = l;
c2923fdc
LB
256 break;
257 }
258
89e62e0b 259 case ARG_VERITY_DATA:
614b022c 260 r = parse_path_argument(optarg, false, &arg_verity_settings.data_path);
89e62e0b
LP
261 if (r < 0)
262 return r;
263 break;
264
e475f729
LP
265 case ARG_FSCK:
266 r = parse_boolean(optarg);
267 if (r < 0)
268 return log_error_errno(r, "Failed to parse --fsck= parameter: %s", optarg);
269
270 SET_FLAG(arg_flags, DISSECT_IMAGE_FSCK, r);
271 break;
272
74a54bae
LP
273 case ARG_GROWFS:
274 r = parse_boolean(optarg);
275 if (r < 0)
276 return log_error_errno(r, "Failed to parse --growfs= parameter: %s", optarg);
277
278 SET_FLAG(arg_flags, DISSECT_IMAGE_GROWFS, r);
279 break;
280
de8231b0 281 case ARG_JSON:
b1e8f46c 282 r = parse_json_argument(optarg, &arg_json_format_flags);
6a01ea4a
LP
283 if (r <= 0)
284 return r;
de8231b0
LP
285
286 break;
287
a2ea3b2f
LP
288 case '?':
289 return -EINVAL;
290
291 default:
04499a70 292 assert_not_reached();
a2ea3b2f
LP
293 }
294
295 }
296
297 switch (arg_action) {
298
299 case ACTION_DISSECT:
baaa35ad
ZJS
300 if (optind + 1 != argc)
301 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
33973b84 302 "Expected an image file path as only argument.");
a2ea3b2f
LP
303
304 arg_image = argv[optind];
18b5886e 305 arg_flags |= DISSECT_IMAGE_READ_ONLY;
a2ea3b2f
LP
306 break;
307
308 case ACTION_MOUNT:
baaa35ad
ZJS
309 if (optind + 2 != argc)
310 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
33973b84 311 "Expected an image file path and mount point path as only arguments.");
a2ea3b2f
LP
312
313 arg_image = argv[optind];
314 arg_path = argv[optind + 1];
166ff731 315 arg_flags |= DISSECT_IMAGE_REQUIRE_ROOT;
a2ea3b2f
LP
316 break;
317
33973b84
LP
318 case ACTION_COPY_FROM:
319 if (argc < optind + 2 || argc > optind + 3)
320 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
321 "Expected an image file path, a source path and an optional destination path as only arguments.");
322
323 arg_image = argv[optind];
324 arg_source = argv[optind + 1];
325 arg_target = argc > optind + 2 ? argv[optind + 2] : "-" /* this means stdout */ ;
326
166ff731 327 arg_flags |= DISSECT_IMAGE_READ_ONLY | DISSECT_IMAGE_REQUIRE_ROOT;
33973b84
LP
328 break;
329
330 case ACTION_COPY_TO:
331 if (argc < optind + 2 || argc > optind + 3)
332 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
333 "Expected an image file path, an optional source path and a destination path as only arguments.");
334
335 arg_image = argv[optind];
336
337 if (argc > optind + 2) {
338 arg_source = argv[optind + 1];
339 arg_target = argv[optind + 2];
340 } else {
341 arg_source = "-"; /* this means stdin */
342 arg_target = argv[optind + 1];
343 }
344
166ff731 345 arg_flags |= DISSECT_IMAGE_REQUIRE_ROOT;
33973b84
LP
346 break;
347
a2ea3b2f 348 default:
04499a70 349 assert_not_reached();
a2ea3b2f
LP
350 }
351
352 return 1;
353}
354
de8231b0
LP
355static int strv_pair_to_json(char **l, JsonVariant **ret) {
356 _cleanup_strv_free_ char **jl = NULL;
de8231b0
LP
357
358 STRV_FOREACH_PAIR(a, b, l) {
359 char *j;
360
361 j = strjoin(*a, "=", *b);
362 if (!j)
363 return log_oom();
364
365 if (strv_consume(&jl, j) < 0)
366 return log_oom();
367 }
368
369 return json_variant_new_array_strv(ret, jl);
370}
371
8570b98b 372static void strv_pair_print(char **l, const char *prefix) {
8570b98b
LP
373 assert(prefix);
374
375 STRV_FOREACH_PAIR(p, q, l) {
376 if (p == l)
377 printf("%s %s=%s\n", prefix, *p, *q);
378 else
379 printf("%*s %s=%s\n", (int) strlen(prefix), "", *p, *q);
380 }
381}
382
994c9c70
LP
383static int get_sysext_scopes(DissectedImage *m, char ***ret_scopes) {
384 _cleanup_strv_free_ char **l = NULL;
385 const char *e;
386
387 assert(m);
388 assert(ret_scopes);
389
390 /* If there's no extension-release file its not a system extension. Otherwise the SYSEXT_SCOPE field
391 * indicates which scope it is for — and it defaults to "system" + "portable" if unset. */
392
393 if (!m->extension_release) {
394 *ret_scopes = NULL;
395 return 0;
396 }
397
398 e = strv_env_pairs_get(m->extension_release, "SYSEXT_SCOPE");
399 if (e)
400 l = strv_split(e, WHITESPACE);
401 else
402 l = strv_new("system", "portable");
403 if (!l)
404 return -ENOMEM;
405
406 *ret_scopes = TAKE_PTR(l);
407 return 1;
408}
409
37e44c3f 410static int action_dissect(DissectedImage *m, LoopDevice *d) {
de8231b0 411 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
37e44c3f 412 _cleanup_(table_unrefp) Table *t = NULL;
de8231b0 413 uint64_t size = UINT64_MAX;
a2ea3b2f
LP
414 int r;
415
37e44c3f
LP
416 assert(m);
417 assert(d);
a2ea3b2f 418
17547fb5 419 if (arg_json_format_flags & (JSON_FORMAT_OFF|JSON_FORMAT_PRETTY|JSON_FORMAT_PRETTY_AUTO))
384c2c32 420 pager_open(arg_pager_flags);
17547fb5 421
6a01ea4a 422 if (arg_json_format_flags & JSON_FORMAT_OFF)
de8231b0 423 printf(" Name: %s\n", basename(arg_image));
a2ea3b2f 424
37e44c3f
LP
425 if (ioctl(d->fd, BLKGETSIZE64, &size) < 0)
426 log_debug_errno(errno, "Failed to query size of loopback device: %m");
2b59bf51
ZJS
427 else if (arg_json_format_flags & JSON_FORMAT_OFF)
428 printf(" Size: %s\n", FORMAT_BYTES(size));
a2ea3b2f 429
6a01ea4a 430 if (arg_json_format_flags & JSON_FORMAT_OFF)
de8231b0 431 putc('\n', stdout);
af8219d5 432
22847508 433 r = dissected_image_acquire_metadata(m, 0);
af8219d5
LP
434 if (r == -ENXIO)
435 return log_error_errno(r, "No root partition discovered.");
af8219d5
LP
436 if (r == -EUCLEAN)
437 return log_error_errno(r, "File system check of image failed.");
4f309abb
LP
438 if (r == -EMEDIUMTYPE)
439 log_warning_errno(r, "Not a valid OS image, no os-release file included. Proceeding anyway.");
440 else if (r == -EUNATCH)
af8219d5
LP
441 log_warning_errno(r, "OS image is encrypted, proceeding without showing OS image metadata.");
442 else if (r == -EBUSY)
443 log_warning_errno(r, "OS image is currently in use, proceeding without showing OS image metadata.");
444 else if (r < 0)
445 return log_error_errno(r, "Failed to acquire image metadata: %m");
6a01ea4a 446 else if (arg_json_format_flags & JSON_FORMAT_OFF) {
994c9c70
LP
447 _cleanup_strv_free_ char **sysext_scopes = NULL;
448
af8219d5
LP
449 if (m->hostname)
450 printf(" Hostname: %s\n", m->hostname);
a2ea3b2f 451
af8219d5
LP
452 if (!sd_id128_is_null(m->machine_id))
453 printf("Machine ID: " SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(m->machine_id));
a1edd22e 454
8570b98b
LP
455 strv_pair_print(m->machine_info,
456 "Mach. Info:");
457 strv_pair_print(m->os_release,
458 "OS Release:");
459 strv_pair_print(m->extension_release,
460 " Ext. Rel.:");
7718ac97 461
4f309abb
LP
462 if (m->hostname ||
463 !sd_id128_is_null(m->machine_id) ||
464 !strv_isempty(m->machine_info) ||
8570b98b
LP
465 !strv_isempty(m->os_release) ||
466 !strv_isempty(m->extension_release))
4f309abb 467 putc('\n', stdout);
994c9c70
LP
468
469 printf(" Use As: %s bootable system for UEFI\n", COLOR_MARK_BOOL(m->partitions[PARTITION_ESP].found));
470
471 if (m->has_init_system >= 0)
472 printf(" %s bootable system for container\n", COLOR_MARK_BOOL(m->has_init_system));
473
474 printf(" %s portable service\n",
475 COLOR_MARK_BOOL(strv_env_pairs_get(m->os_release, "PORTABLE_PREFIXES")));
476
477 r = get_sysext_scopes(m, &sysext_scopes);
478 if (r < 0)
479 return log_error_errno(r, "Failed to parse SYSEXT_SCOPE: %m");
480
481 printf(" %s extension for system\n",
482 COLOR_MARK_BOOL(strv_contains(sysext_scopes, "system")));
483 printf(" %s extension for initrd\n",
484 COLOR_MARK_BOOL(strv_contains(sysext_scopes, "initrd")));
485 printf(" %s extension for portable service\n",
486 COLOR_MARK_BOOL(strv_contains(sysext_scopes, "portable")));
487
488 putc('\n', stdout);
4f309abb 489 } else {
7718ac97 490 _cleanup_(json_variant_unrefp) JsonVariant *mi = NULL, *osr = NULL, *exr = NULL;
994c9c70 491 _cleanup_strv_free_ char **sysext_scopes = NULL;
de8231b0
LP
492
493 if (!strv_isempty(m->machine_info)) {
494 r = strv_pair_to_json(m->machine_info, &mi);
495 if (r < 0)
496 return log_oom();
497 }
498
499 if (!strv_isempty(m->os_release)) {
500 r = strv_pair_to_json(m->os_release, &osr);
501 if (r < 0)
502 return log_oom();
503 }
504
7718ac97
LB
505 if (!strv_isempty(m->extension_release)) {
506 r = strv_pair_to_json(m->extension_release, &exr);
507 if (r < 0)
508 return log_oom();
509 }
510
994c9c70
LP
511 r = get_sysext_scopes(m, &sysext_scopes);
512 if (r < 0)
513 return log_error_errno(r, "Failed to parse SYSEXT_SCOPE: %m");
514
de8231b0
LP
515 r = json_build(&v, JSON_BUILD_OBJECT(
516 JSON_BUILD_PAIR("name", JSON_BUILD_STRING(basename(arg_image))),
517 JSON_BUILD_PAIR("size", JSON_BUILD_INTEGER(size)),
518 JSON_BUILD_PAIR_CONDITION(m->hostname, "hostname", JSON_BUILD_STRING(m->hostname)),
519 JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(m->machine_id), "machineId", JSON_BUILD_ID128(m->machine_id)),
520 JSON_BUILD_PAIR_CONDITION(mi, "machineInfo", JSON_BUILD_VARIANT(mi)),
7718ac97 521 JSON_BUILD_PAIR_CONDITION(osr, "osRelease", JSON_BUILD_VARIANT(osr)),
994c9c70
LP
522 JSON_BUILD_PAIR_CONDITION(exr, "extensionRelease", JSON_BUILD_VARIANT(exr)),
523 JSON_BUILD_PAIR("useBootableUefi", JSON_BUILD_BOOLEAN(m->partitions[PARTITION_ESP].found)),
524 JSON_BUILD_PAIR_CONDITION(m->has_init_system >= 0, "useBootableContainer", JSON_BUILD_BOOLEAN(m->has_init_system)),
525 JSON_BUILD_PAIR("usePortableService", JSON_BUILD_BOOLEAN(strv_env_pairs_get(m->os_release, "PORTABLE_MATCHES"))),
526 JSON_BUILD_PAIR("useSystemExtension", JSON_BUILD_BOOLEAN(strv_contains(sysext_scopes, "system"))),
527 JSON_BUILD_PAIR("useInitRDExtension", JSON_BUILD_BOOLEAN(strv_contains(sysext_scopes, "initrd"))),
528 JSON_BUILD_PAIR("usePortableExtension", JSON_BUILD_BOOLEAN(strv_contains(sysext_scopes, "portable")))));
de8231b0
LP
529 if (r < 0)
530 return log_oom();
531 }
532
ee8e497d 533 t = table_new("rw", "designator", "partition uuid", "partition label", "fstype", "architecture", "verity", "growfs", "node", "partno");
37e44c3f
LP
534 if (!t)
535 return log_oom();
a1edd22e 536
37e44c3f
LP
537 (void) table_set_empty_string(t, "-");
538 (void) table_set_align_percent(t, table_get_cell(t, 0, 7), 100);
a2ea3b2f 539
569a0e42 540 for (PartitionDesignator i = 0; i < _PARTITION_DESIGNATOR_MAX; i++) {
37e44c3f 541 DissectedPartition *p = m->partitions + i;
89d00f2e 542
37e44c3f
LP
543 if (!p->found)
544 continue;
89d00f2e 545
37e44c3f
LP
546 r = table_add_many(
547 t,
548 TABLE_STRING, p->rw ? "rw" : "ro",
549 TABLE_STRING, partition_designator_to_string(i));
550 if (r < 0)
551 return table_log_add_error(r);
89d00f2e 552
37e44c3f
LP
553 if (sd_id128_is_null(p->uuid))
554 r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
555 else
556 r = table_add_cell(t, NULL, TABLE_UUID, &p->uuid);
557 if (r < 0)
558 return table_log_add_error(r);
89d00f2e 559
37e44c3f
LP
560 r = table_add_many(
561 t,
1474d7ac 562 TABLE_STRING, p->label,
37e44c3f
LP
563 TABLE_STRING, p->fstype,
564 TABLE_STRING, architecture_to_string(p->architecture));
565 if (r < 0)
566 return table_log_add_error(r);
567
89e62e0b 568 if (arg_verity_settings.data_path)
37e44c3f 569 r = table_add_cell(t, NULL, TABLE_STRING, "external");
49536766 570 else if (dissected_image_verity_candidate(m, i))
8ee9615e
LP
571 r = table_add_cell(t, NULL, TABLE_STRING,
572 dissected_image_verity_sig_ready(m, i) ? "signed" :
573 yes_no(dissected_image_verity_ready(m, i)));
37e44c3f
LP
574 else
575 r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
576 if (r < 0)
577 return table_log_add_error(r);
89d00f2e 578
ee8e497d
LP
579 r = table_add_many(t, TABLE_BOOLEAN, (int) p->growfs);
580 if (r < 0)
581 return table_log_add_error(r);
582
37e44c3f
LP
583 if (p->partno < 0) /* no partition table, naked file system */ {
584 r = table_add_cell(t, NULL, TABLE_STRING, arg_image);
89d00f2e
LP
585 if (r < 0)
586 return table_log_add_error(r);
587
37e44c3f
LP
588 r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
589 } else {
590 r = table_add_cell(t, NULL, TABLE_STRING, p->node);
89d00f2e
LP
591 if (r < 0)
592 return table_log_add_error(r);
593
37e44c3f
LP
594 r = table_add_cell(t, NULL, TABLE_INT, &p->partno);
595 }
596 if (r < 0)
597 return table_log_add_error(r);
598 }
89d00f2e 599
6a01ea4a 600 if (arg_json_format_flags & JSON_FORMAT_OFF) {
17547fb5
LP
601 (void) table_set_header(t, arg_legend);
602
603 r = table_print(t, NULL);
6a01ea4a
LP
604 if (r < 0)
605 return table_log_print_error(r);
606 } else {
de8231b0
LP
607 _cleanup_(json_variant_unrefp) JsonVariant *jt = NULL;
608
609 r = table_to_json(t, &jt);
610 if (r < 0)
611 return log_error_errno(r, "Failed to convert table to JSON: %m");
612
613 r = json_variant_set_field(&v, "mounts", jt);
614 if (r < 0)
615 return log_oom();
616
617 json_variant_dump(v, arg_json_format_flags, stdout, NULL);
de8231b0 618 }
89d00f2e 619
37e44c3f
LP
620 return 0;
621}
89d00f2e 622
37e44c3f
LP
623static int action_mount(DissectedImage *m, LoopDevice *d) {
624 _cleanup_(decrypted_image_unrefp) DecryptedImage *di = NULL;
625 int r;
89d00f2e 626
37e44c3f
LP
627 assert(m);
628 assert(d);
89d00f2e 629
37e44c3f
LP
630 r = dissected_image_decrypt_interactively(
631 m, NULL,
89e62e0b 632 &arg_verity_settings,
37e44c3f
LP
633 arg_flags,
634 &di);
635 if (r < 0)
636 return r;
89d00f2e 637
21b61b1d 638 r = dissected_image_mount_and_warn(m, arg_path, UID_INVALID, UID_INVALID, arg_flags);
37e44c3f 639 if (r < 0)
af187ab2 640 return r;
89d00f2e 641
37e44c3f
LP
642 if (di) {
643 r = decrypted_image_relinquish(di);
644 if (r < 0)
645 return log_error_errno(r, "Failed to relinquish DM devices: %m");
a2ea3b2f
LP
646 }
647
37e44c3f
LP
648 loop_device_relinquish(d);
649 return 0;
650}
18b5886e 651
37e44c3f
LP
652static int action_copy(DissectedImage *m, LoopDevice *d) {
653 _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
654 _cleanup_(decrypted_image_unrefp) DecryptedImage *di = NULL;
655 _cleanup_(rmdir_and_freep) char *created_dir = NULL;
656 _cleanup_free_ char *temp = NULL;
657 int r;
a2ea3b2f 658
37e44c3f
LP
659 assert(m);
660 assert(d);
18b5886e 661
37e44c3f
LP
662 r = dissected_image_decrypt_interactively(
663 m, NULL,
89e62e0b 664 &arg_verity_settings,
37e44c3f
LP
665 arg_flags,
666 &di);
667 if (r < 0)
668 return r;
a2ea3b2f 669
37e44c3f
LP
670 r = detach_mount_namespace();
671 if (r < 0)
672 return log_error_errno(r, "Failed to detach mount namespace: %m");
33973b84 673
37e44c3f
LP
674 r = tempfn_random_child(NULL, program_invocation_short_name, &temp);
675 if (r < 0)
676 return log_error_errno(r, "Failed to generate temporary mount directory: %m");
33973b84 677
37e44c3f
LP
678 r = mkdir_p(temp, 0700);
679 if (r < 0)
680 return log_error_errno(r, "Failed to create mount point: %m");
33973b84 681
37e44c3f
LP
682 created_dir = TAKE_PTR(temp);
683
21b61b1d 684 r = dissected_image_mount_and_warn(m, created_dir, UID_INVALID, UID_INVALID, arg_flags);
37e44c3f 685 if (r < 0)
af187ab2 686 return r;
33973b84 687
37e44c3f 688 mounted_dir = TAKE_PTR(created_dir);
33973b84 689
37e44c3f
LP
690 if (di) {
691 r = decrypted_image_relinquish(di);
33973b84 692 if (r < 0)
37e44c3f
LP
693 return log_error_errno(r, "Failed to relinquish DM devices: %m");
694 }
695
696 loop_device_relinquish(d);
33973b84 697
37e44c3f
LP
698 if (arg_action == ACTION_COPY_FROM) {
699 _cleanup_close_ int source_fd = -1, target_fd = -1;
33973b84 700
37e44c3f
LP
701 source_fd = chase_symlinks_and_open(arg_source, mounted_dir, CHASE_PREFIX_ROOT|CHASE_WARN, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL);
702 if (source_fd < 0)
703 return log_error_errno(source_fd, "Failed to open source path '%s' in image '%s': %m", arg_source, arg_image);
704
705 /* Copying to stdout? */
706 if (streq(arg_target, "-")) {
f5fbe71d 707 r = copy_bytes(source_fd, STDOUT_FILENO, UINT64_MAX, COPY_REFLINK);
33973b84 708 if (r < 0)
37e44c3f
LP
709 return log_error_errno(r, "Failed to copy bytes from %s in mage '%s' to stdout: %m", arg_source, arg_image);
710
38db55ab 711 /* When we copy to stdout we don't copy any attributes (i.e. no access mode, no ownership, no xattr, no times) */
37e44c3f 712 return 0;
33973b84
LP
713 }
714
37e44c3f 715 /* Try to copy as directory? */
dd480f78 716 r = copy_directory_fd(source_fd, arg_target, COPY_REFLINK|COPY_MERGE_EMPTY|COPY_SIGINT|COPY_HARDLINKS);
37e44c3f
LP
717 if (r >= 0)
718 return 0;
719 if (r != -ENOTDIR)
720 return log_error_errno(r, "Failed to copy %s in image '%s' to '%s': %m", arg_source, arg_image, arg_target);
721
722 r = fd_verify_regular(source_fd);
723 if (r == -EISDIR)
724 return log_error_errno(r, "Target '%s' exists already and is not a directory.", arg_target);
725 if (r < 0)
726 return log_error_errno(r, "Source path %s in image '%s' is neither regular file nor directory, refusing: %m", arg_source, arg_image);
33973b84 727
37e44c3f
LP
728 /* Nah, it's a plain file! */
729 target_fd = open(arg_target, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0600);
730 if (target_fd < 0)
731 return log_error_errno(errno, "Failed to create regular file at target path '%s': %m", arg_target);
33973b84 732
f5fbe71d 733 r = copy_bytes(source_fd, target_fd, UINT64_MAX, COPY_REFLINK);
37e44c3f
LP
734 if (r < 0)
735 return log_error_errno(r, "Failed to copy bytes from %s in mage '%s' to '%s': %m", arg_source, arg_image, arg_target);
33973b84 736
23e026de 737 (void) copy_xattr(source_fd, target_fd, 0);
37e44c3f
LP
738 (void) copy_access(source_fd, target_fd);
739 (void) copy_times(source_fd, target_fd, 0);
33973b84 740
37e44c3f 741 /* When this is a regular file we don't copy ownership! */
33973b84 742
37e44c3f
LP
743 } else {
744 _cleanup_close_ int source_fd = -1, target_fd = -1;
745 _cleanup_close_ int dfd = -1;
746 _cleanup_free_ char *dn = NULL;
33973b84 747
37e44c3f
LP
748 assert(arg_action == ACTION_COPY_TO);
749
750 dn = dirname_malloc(arg_target);
751 if (!dn)
752 return log_oom();
753
754 r = chase_symlinks(dn, mounted_dir, CHASE_PREFIX_ROOT|CHASE_WARN, NULL, &dfd);
755 if (r < 0)
756 return log_error_errno(r, "Failed to open '%s': %m", dn);
33973b84 757
37e44c3f
LP
758 /* Are we reading from stdin? */
759 if (streq(arg_source, "-")) {
760 target_fd = openat(dfd, basename(arg_target), O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_EXCL, 0644);
33973b84 761 if (target_fd < 0)
37e44c3f 762 return log_error_errno(errno, "Failed to open target file '%s': %m", arg_target);
33973b84 763
f5fbe71d 764 r = copy_bytes(STDIN_FILENO, target_fd, UINT64_MAX, COPY_REFLINK);
33973b84 765 if (r < 0)
37e44c3f 766 return log_error_errno(r, "Failed to copy bytes from stdin to '%s' in image '%s': %m", arg_target, arg_image);
33973b84 767
37e44c3f
LP
768 /* When we copy from stdin we don't copy any attributes (i.e. no access mode, no ownership, no xattr, no times) */
769 return 0;
770 }
33973b84 771
37e44c3f
LP
772 source_fd = open(arg_source, O_RDONLY|O_CLOEXEC|O_NOCTTY);
773 if (source_fd < 0)
774 return log_error_errno(source_fd, "Failed to open source path '%s': %m", arg_source);
33973b84 775
37e44c3f
LP
776 r = fd_verify_regular(source_fd);
777 if (r < 0) {
778 if (r != -EISDIR)
779 return log_error_errno(r, "Source '%s' is neither regular file nor directory: %m", arg_source);
33973b84 780
37e44c3f 781 /* We are looking at a directory. */
33973b84 782
37e44c3f
LP
783 target_fd = openat(dfd, basename(arg_target), O_RDONLY|O_DIRECTORY|O_CLOEXEC);
784 if (target_fd < 0) {
785 if (errno != ENOENT)
786 return log_error_errno(errno, "Failed to open destination '%s': %m", arg_target);
33973b84 787
dd480f78 788 r = copy_tree_at(source_fd, ".", dfd, basename(arg_target), UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS);
37e44c3f 789 } else
dd480f78 790 r = copy_tree_at(source_fd, ".", target_fd, ".", UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS);
33973b84 791 if (r < 0)
37e44c3f 792 return log_error_errno(r, "Failed to copy '%s' to '%s' in image '%s': %m", arg_source, arg_target, arg_image);
33973b84 793
37e44c3f
LP
794 return 0;
795 }
33973b84 796
37e44c3f
LP
797 /* We area looking at a regular file */
798 target_fd = openat(dfd, basename(arg_target), O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_EXCL, 0600);
799 if (target_fd < 0)
800 return log_error_errno(errno, "Failed to open target file '%s': %m", arg_target);
33973b84 801
f5fbe71d 802 r = copy_bytes(source_fd, target_fd, UINT64_MAX, COPY_REFLINK);
37e44c3f
LP
803 if (r < 0)
804 return log_error_errno(r, "Failed to copy bytes from '%s' to '%s' in image '%s': %m", arg_source, arg_target, arg_image);
33973b84 805
23e026de 806 (void) copy_xattr(source_fd, target_fd, 0);
37e44c3f
LP
807 (void) copy_access(source_fd, target_fd);
808 (void) copy_times(source_fd, target_fd, 0);
33973b84 809
37e44c3f
LP
810 /* When this is a regular file we don't copy ownership! */
811 }
33973b84 812
37e44c3f
LP
813 return 0;
814}
33973b84 815
37e44c3f
LP
816static int run(int argc, char *argv[]) {
817 _cleanup_(dissected_image_unrefp) DissectedImage *m = NULL;
818 _cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
819 int r;
33973b84 820
37e44c3f
LP
821 log_parse_environment();
822 log_open();
33973b84 823
37e44c3f
LP
824 r = parse_argv(argc, argv);
825 if (r <= 0)
826 return r;
33973b84 827
89e62e0b
LP
828 r = verity_settings_load(
829 &arg_verity_settings,
830 arg_image, NULL, NULL);
37e44c3f
LP
831 if (r < 0)
832 return log_error_errno(r, "Failed to read verity artifacts for %s: %m", arg_image);
33973b84 833
89e62e0b
LP
834 if (arg_verity_settings.data_path)
835 arg_flags |= DISSECT_IMAGE_NO_PARTITION_TABLE; /* We only support Verity per file system,
836 * hence if there's external Verity data
837 * available we turn off partition table
838 * support */
839
37e44c3f
LP
840 r = loop_device_make_by_path(
841 arg_image,
ef9c184d 842 FLAGS_SET(arg_flags, DISSECT_IMAGE_DEVICE_READ_ONLY) ? O_RDONLY : O_RDWR,
89e62e0b 843 FLAGS_SET(arg_flags, DISSECT_IMAGE_NO_PARTITION_TABLE) ? 0 : LO_FLAGS_PARTSCAN,
37e44c3f
LP
844 &d);
845 if (r < 0)
7b87fe4c 846 return log_error_errno(r, "Failed to set up loopback device for %s: %m", arg_image);
33973b84 847
37e44c3f
LP
848 r = dissect_image_and_warn(
849 d->fd,
850 arg_image,
89e62e0b 851 &arg_verity_settings,
37e44c3f 852 NULL,
a3642997 853 d->diskseq,
75dc190d 854 d->uevent_seqnum_not_before,
4a62257d 855 d->timestamp_not_before,
37e44c3f
LP
856 arg_flags,
857 &m);
858 if (r < 0)
859 return r;
33973b84 860
88b3300f
LP
861 r = dissected_image_load_verity_sig_partition(
862 m,
863 d->fd,
864 &arg_verity_settings);
865 if (r < 0)
866 return r;
867
37e44c3f 868 switch (arg_action) {
33973b84 869
37e44c3f
LP
870 case ACTION_DISSECT:
871 r = action_dissect(m, d);
872 break;
873
874 case ACTION_MOUNT:
875 r = action_mount(m, d);
876 break;
877
878 case ACTION_COPY_FROM:
879 case ACTION_COPY_TO:
880 r = action_copy(m, d);
33973b84 881 break;
33973b84 882
a2ea3b2f 883 default:
04499a70 884 assert_not_reached();
a2ea3b2f
LP
885 }
886
37e44c3f 887 return r;
a2ea3b2f 888}
149afb45
YW
889
890DEFINE_MAIN_FUNCTION(run);