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