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