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