1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <getopt.h>
4 #include <locale.h>
5
6 #include "sd-event.h"
7 #include "sd-id128.h"
8
9 #include "alloc-util.h"
10 #include "discover-image.h"
11 #include "env-util.h"
12 #include "hexdecoct.h"
13 #include "hostname-util.h"
14 #include "import-common.h"
15 #include "import-util.h"
16 #include "io-util.h"
17 #include "main-func.h"
18 #include "parse-argument.h"
19 #include "parse-util.h"
20 #include "pull-raw.h"
21 #include "pull-tar.h"
22 #include "signal-util.h"
23 #include "string-util.h"
24 #include "terminal-util.h"
25 #include "verbs.h"
26 #include "web-util.h"
27
28 static const char *arg_image_root = "/var/lib/machines";
29 static ImportVerify arg_verify = IMPORT_VERIFY_SIGNATURE;
30 static PullFlags arg_pull_flags = PULL_SETTINGS | PULL_ROOTHASH | PULL_ROOTHASH_SIGNATURE | PULL_VERITY | PULL_BTRFS_SUBVOL | PULL_BTRFS_QUOTA | PULL_CONVERT_QCOW2 | PULL_SYNC;
31 static uint64_t arg_offset = UINT64_MAX, arg_size_max = UINT64_MAX;
32 static char *arg_checksum = NULL;
33
34 STATIC_DESTRUCTOR_REGISTER(arg_checksum, freep);
35
normalize_local(const char * local,const char * url,char ** ret)36 static int normalize_local(const char *local, const char *url, char **ret) {
37 _cleanup_free_ char *ll = NULL;
38 int r;
39
40 if (arg_pull_flags & PULL_DIRECT) {
41
42 if (!local)
43 log_debug("Writing downloaded data to STDOUT.");
44 else {
45 if (!path_is_absolute(local)) {
46 ll = path_join(arg_image_root, local);
47 if (!ll)
48 return log_oom();
49
50 local = ll;
51 }
52
53 if (!path_is_valid(local))
54 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
55 "Local path name '%s' is not valid.", local);
56 }
57
58 } else if (local) {
59
60 if (!hostname_is_valid(local, 0))
61 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
62 "Local image name '%s' is not valid.",
63 local);
64
65 if (!FLAGS_SET(arg_pull_flags, PULL_FORCE)) {
66 r = image_find(IMAGE_MACHINE, local, NULL, NULL);
67 if (r < 0) {
68 if (r != -ENOENT)
69 return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local);
70 } else
71 return log_error_errno(SYNTHETIC_ERRNO(EEXIST),
72 "Image '%s' already exists.",
73 local);
74 }
75 }
76
77 if (!ll && local) {
78 ll = strdup(local);
79 if (!ll)
80 return log_oom();
81 }
82
83 if (ll) {
84 if (arg_offset != UINT64_MAX)
85 log_info("Pulling '%s', saving at offset %" PRIu64 " in '%s'.", url, arg_offset, ll);
86 else
87 log_info("Pulling '%s', saving as '%s'.", url, ll);
88 } else
89 log_info("Pulling '%s'.", url);
90
91 *ret = TAKE_PTR(ll);
92 return 0;
93 }
94
on_tar_finished(TarPull * pull,int error,void * userdata)95 static void on_tar_finished(TarPull *pull, int error, void *userdata) {
96 sd_event *event = userdata;
97 assert(pull);
98
99 if (error == 0)
100 log_info("Operation completed successfully.");
101
102 sd_event_exit(event, abs(error));
103 }
104
pull_tar(int argc,char * argv[],void * userdata)105 static int pull_tar(int argc, char *argv[], void *userdata) {
106 _cleanup_free_ char *ll = NULL, *normalized = NULL;
107 _cleanup_(sd_event_unrefp) sd_event *event = NULL;
108 _cleanup_(tar_pull_unrefp) TarPull *pull = NULL;
109 const char *url, *local;
110 int r;
111
112 url = argv[1];
113 if (!http_url_is_valid(url) && !file_url_is_valid(url))
114 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "URL '%s' is not valid.", url);
115
116 if (argc >= 3)
117 local = empty_or_dash_to_null(argv[2]);
118 else {
119 _cleanup_free_ char *l = NULL;
120
121 r = import_url_last_component(url, &l);
122 if (r < 0)
123 return log_error_errno(r, "Failed to get final component of URL: %m");
124
125 r = tar_strip_suffixes(l, &ll);
126 if (r < 0)
127 return log_oom();
128
129 local = ll;
130 }
131
132 if (!local && FLAGS_SET(arg_pull_flags, PULL_DIRECT))
133 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Pulling tar images to STDOUT is not supported.");
134
135 r = normalize_local(local, url, &normalized);
136 if (r < 0)
137 return r;
138
139 r = import_allocate_event_with_signals(&event);
140 if (r < 0)
141 return r;
142
143 if (!FLAGS_SET(arg_pull_flags, PULL_SYNC))
144 log_info("File system synchronization on completion is off.");
145
146 r = tar_pull_new(&pull, event, arg_image_root, on_tar_finished, event);
147 if (r < 0)
148 return log_error_errno(r, "Failed to allocate puller: %m");
149
150 r = tar_pull_start(
151 pull,
152 url,
153 normalized,
154 arg_pull_flags & PULL_FLAGS_MASK_TAR,
155 arg_verify,
156 arg_checksum);
157 if (r < 0)
158 return log_error_errno(r, "Failed to pull image: %m");
159
160 r = sd_event_loop(event);
161 if (r < 0)
162 return log_error_errno(r, "Failed to run event loop: %m");
163
164 log_info("Exiting.");
165 return -r;
166 }
167
on_raw_finished(RawPull * pull,int error,void * userdata)168 static void on_raw_finished(RawPull *pull, int error, void *userdata) {
169 sd_event *event = userdata;
170 assert(pull);
171
172 if (error == 0)
173 log_info("Operation completed successfully.");
174
175 sd_event_exit(event, abs(error));
176 }
177
pull_raw(int argc,char * argv[],void * userdata)178 static int pull_raw(int argc, char *argv[], void *userdata) {
179 _cleanup_free_ char *ll = NULL, *normalized = NULL;
180 _cleanup_(sd_event_unrefp) sd_event *event = NULL;
181 _cleanup_(raw_pull_unrefp) RawPull *pull = NULL;
182 const char *url, *local;
183 int r;
184
185 url = argv[1];
186 if (!http_url_is_valid(url) && !file_url_is_valid(url))
187 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "URL '%s' is not valid.", url);
188
189 if (argc >= 3)
190 local = empty_or_dash_to_null(argv[2]);
191 else {
192 _cleanup_free_ char *l = NULL;
193
194 r = import_url_last_component(url, &l);
195 if (r < 0)
196 return log_error_errno(r, "Failed to get final component of URL: %m");
197
198 r = raw_strip_suffixes(l, &ll);
199 if (r < 0)
200 return log_oom();
201
202 local = ll;
203 }
204
205 r = normalize_local(local, url, &normalized);
206 if (r < 0)
207 return r;
208
209 r = import_allocate_event_with_signals(&event);
210 if (r < 0)
211 return r;
212
213 if (!FLAGS_SET(arg_pull_flags, PULL_SYNC))
214 log_info("File system synchronization on completion is off.");
215 r = raw_pull_new(&pull, event, arg_image_root, on_raw_finished, event);
216 if (r < 0)
217 return log_error_errno(r, "Failed to allocate puller: %m");
218
219 r = raw_pull_start(
220 pull,
221 url,
222 normalized,
223 arg_offset,
224 arg_size_max,
225 arg_pull_flags & PULL_FLAGS_MASK_RAW,
226 arg_verify,
227 arg_checksum);
228 if (r < 0)
229 return log_error_errno(r, "Failed to pull image: %m");
230
231 r = sd_event_loop(event);
232 if (r < 0)
233 return log_error_errno(r, "Failed to run event loop: %m");
234
235 log_info("Exiting.");
236 return -r;
237 }
238
help(int argc,char * argv[],void * userdata)239 static int help(int argc, char *argv[], void *userdata) {
240
241 printf("%1$s [OPTIONS...] {COMMAND} ...\n"
242 "\n%4$sDownload container or virtual machine images.%5$s\n"
243 "\n%2$sCommands:%3$s\n"
244 " tar URL [NAME] Download a TAR image\n"
245 " raw URL [NAME] Download a RAW image\n"
246 "\n%2$sOptions:%3$s\n"
247 " -h --help Show this help\n"
248 " --version Show package version\n"
249 " --force Force creation of image\n"
250 " --verify=MODE Verify downloaded image, one of: 'no',\n"
251 " 'checksum', 'signature' or literal SHA256 hash\n"
252 " --settings=BOOL Download settings file with image\n"
253 " --roothash=BOOL Download root hash file with image\n"
254 " --roothash-signature=BOOL\n"
255 " Download root hash signature file with image\n"
256 " --verity=BOOL Download verity file with image\n"
257 " --image-root=PATH Image root directory\n\n"
258 " --read-only Create a read-only image\n"
259 " --direct Download directly to specified file\n"
260 " --btrfs-subvol=BOOL Controls whether to create a btrfs subvolume\n"
261 " instead of a directory\n"
262 " --btrfs-quota=BOOL Controls whether to set up quota for btrfs\n"
263 " subvolume\n"
264 " --convert-qcow2=BOOL Controls whether to convert QCOW2 images to\n"
265 " regular disk images\n"
266 " --sync=BOOL Controls whether to sync() before completing\n"
267 " --offset=BYTES Offset to seek to in destination\n"
268 " --size-max=BYTES Maximum number of bytes to write to destination\n",
269 program_invocation_short_name,
270 ansi_underline(),
271 ansi_normal(),
272 ansi_highlight(),
273 ansi_normal());
274
275 return 0;
276 }
277
parse_argv(int argc,char * argv[])278 static int parse_argv(int argc, char *argv[]) {
279
280 enum {
281 ARG_VERSION = 0x100,
282 ARG_FORCE,
283 ARG_IMAGE_ROOT,
284 ARG_VERIFY,
285 ARG_SETTINGS,
286 ARG_ROOTHASH,
287 ARG_ROOTHASH_SIGNATURE,
288 ARG_VERITY,
289 ARG_READ_ONLY,
290 ARG_DIRECT,
291 ARG_BTRFS_SUBVOL,
292 ARG_BTRFS_QUOTA,
293 ARG_CONVERT_QCOW2,
294 ARG_SYNC,
295 ARG_OFFSET,
296 ARG_SIZE_MAX,
297 };
298
299 static const struct option options[] = {
300 { "help", no_argument, NULL, 'h' },
301 { "version", no_argument, NULL, ARG_VERSION },
302 { "force", no_argument, NULL, ARG_FORCE },
303 { "image-root", required_argument, NULL, ARG_IMAGE_ROOT },
304 { "verify", required_argument, NULL, ARG_VERIFY },
305 { "settings", required_argument, NULL, ARG_SETTINGS },
306 { "roothash", required_argument, NULL, ARG_ROOTHASH },
307 { "roothash-signature", required_argument, NULL, ARG_ROOTHASH_SIGNATURE },
308 { "verity", required_argument, NULL, ARG_VERITY },
309 { "read-only", no_argument, NULL, ARG_READ_ONLY },
310 { "direct", no_argument, NULL, ARG_DIRECT },
311 { "btrfs-subvol", required_argument, NULL, ARG_BTRFS_SUBVOL },
312 { "btrfs-quota", required_argument, NULL, ARG_BTRFS_QUOTA },
313 { "convert-qcow2", required_argument, NULL, ARG_CONVERT_QCOW2 },
314 { "sync", required_argument, NULL, ARG_SYNC },
315 { "offset", required_argument, NULL, ARG_OFFSET },
316 { "size-max", required_argument, NULL, ARG_SIZE_MAX },
317 {}
318 };
319
320 int c, r;
321
322 assert(argc >= 0);
323 assert(argv);
324
325 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
326
327 switch (c) {
328
329 case 'h':
330 return help(0, NULL, NULL);
331
332 case ARG_VERSION:
333 return version();
334
335 case ARG_FORCE:
336 arg_pull_flags |= PULL_FORCE;
337 break;
338
339 case ARG_IMAGE_ROOT:
340 arg_image_root = optarg;
341 break;
342
343 case ARG_VERIFY: {
344 ImportVerify v;
345
346 v = import_verify_from_string(optarg);
347 if (v < 0) {
348 _cleanup_free_ void *h = NULL;
349 char *hh;
350 size_t n;
351
352 /* If this is not a valid verification mode, maybe it's a literally specified
353 * SHA256 hash? We can handle that too... */
354
355 r = unhexmem(optarg, (size_t) -1, &h, &n);
356 if (r < 0 || n == 0)
357 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
358 "Invalid verification setting: %s", optarg);
359 if (n != 32)
360 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
361 "64 hex character SHA256 hash required when specifying explicit checksum, %zu specified", n * 2);
362
363 hh = hexmem(h, n); /* bring into canonical (lowercase) form */
364 if (!hh)
365 return log_oom();
366
367 free_and_replace(arg_checksum, hh);
368 arg_pull_flags &= ~(PULL_SETTINGS|PULL_ROOTHASH|PULL_ROOTHASH_SIGNATURE|PULL_VERITY);
369 arg_verify = _IMPORT_VERIFY_INVALID;
370 } else
371 arg_verify = v;
372
373 break;
374 }
375
376 case ARG_SETTINGS:
377 r = parse_boolean_argument("--settings=", optarg, NULL);
378 if (r < 0)
379 return r;
380
381 SET_FLAG(arg_pull_flags, PULL_SETTINGS, r);
382 break;
383
384 case ARG_ROOTHASH:
385 r = parse_boolean_argument("--roothash=", optarg, NULL);
386 if (r < 0)
387 return r;
388
389 SET_FLAG(arg_pull_flags, PULL_ROOTHASH, r);
390
391 /* If we were asked to turn off the root hash, implicitly also turn off the root hash signature */
392 if (!r)
393 SET_FLAG(arg_pull_flags, PULL_ROOTHASH_SIGNATURE, false);
394 break;
395
396 case ARG_ROOTHASH_SIGNATURE:
397 r = parse_boolean_argument("--roothash-signature=", optarg, NULL);
398 if (r < 0)
399 return r;
400
401 SET_FLAG(arg_pull_flags, PULL_ROOTHASH_SIGNATURE, r);
402 break;
403
404 case ARG_VERITY:
405 r = parse_boolean_argument("--verity=", optarg, NULL);
406 if (r < 0)
407 return r;
408
409 SET_FLAG(arg_pull_flags, PULL_VERITY, r);
410 break;
411
412 case ARG_READ_ONLY:
413 arg_pull_flags |= PULL_READ_ONLY;
414 break;
415
416 case ARG_DIRECT:
417 arg_pull_flags |= PULL_DIRECT;
418 arg_pull_flags &= ~(PULL_SETTINGS|PULL_ROOTHASH|PULL_ROOTHASH_SIGNATURE|PULL_VERITY);
419 break;
420
421 case ARG_BTRFS_SUBVOL:
422 r = parse_boolean_argument("--btrfs-subvol=", optarg, NULL);
423 if (r < 0)
424 return r;
425
426 SET_FLAG(arg_pull_flags, PULL_BTRFS_SUBVOL, r);
427 break;
428
429 case ARG_BTRFS_QUOTA:
430 r = parse_boolean_argument("--btrfs-quota=", optarg, NULL);
431 if (r < 0)
432 return r;
433
434 SET_FLAG(arg_pull_flags, PULL_BTRFS_QUOTA, r);
435 break;
436
437 case ARG_CONVERT_QCOW2:
438 r = parse_boolean_argument("--convert-qcow2=", optarg, NULL);
439 if (r < 0)
440 return r;
441
442 SET_FLAG(arg_pull_flags, PULL_CONVERT_QCOW2, r);
443 break;
444
445 case ARG_SYNC:
446 r = parse_boolean_argument("--sync=", optarg, NULL);
447 if (r < 0)
448 return r;
449
450 SET_FLAG(arg_pull_flags, PULL_SYNC, r);
451 break;
452
453 case ARG_OFFSET: {
454 uint64_t u;
455
456 r = safe_atou64(optarg, &u);
457 if (r < 0)
458 return log_error_errno(r, "Failed to parse --offset= argument: %s", optarg);
459 if (!FILE_SIZE_VALID(u))
460 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argument to --offset= switch too large: %s", optarg);
461
462 arg_offset = u;
463 break;
464 }
465
466 case ARG_SIZE_MAX: {
467 uint64_t u;
468
469 r = parse_size(optarg, 1024, &u);
470 if (r < 0)
471 return log_error_errno(r, "Failed to parse --size-max= argument: %s", optarg);
472 if (!FILE_SIZE_VALID(u))
473 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argument to --size-max= switch too large: %s", optarg);
474
475 arg_size_max = u;
476 break;
477 }
478
479 case '?':
480 return -EINVAL;
481
482 default:
483 assert_not_reached();
484 }
485
486 /* Make sure offset+size is still in the valid range if both set */
487 if (arg_offset != UINT64_MAX && arg_size_max != UINT64_MAX &&
488 ((arg_size_max > (UINT64_MAX - arg_offset)) ||
489 !FILE_SIZE_VALID(arg_offset + arg_size_max)))
490 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File offset und maximum size out of range.");
491
492 if (arg_offset != UINT64_MAX && !FLAGS_SET(arg_pull_flags, PULL_DIRECT))
493 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File offset only supported in --direct mode.");
494
495 if (arg_checksum && (arg_pull_flags & (PULL_SETTINGS|PULL_ROOTHASH|PULL_ROOTHASH_SIGNATURE|PULL_VERITY)) != 0)
496 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Literal checksum verification only supported if no associated files are downloaded.");
497
498 return 1;
499 }
500
parse_env(void)501 static void parse_env(void) {
502 int r;
503
504 /* Let's make these relatively low-level settings also controllable via env vars. User can then set
505 * them for systemd-importd.service if they like to tweak behaviour */
506
507 r = getenv_bool("SYSTEMD_IMPORT_BTRFS_SUBVOL");
508 if (r >= 0)
509 SET_FLAG(arg_pull_flags, PULL_BTRFS_SUBVOL, r);
510 else if (r != -ENXIO)
511 log_warning_errno(r, "Failed to parse $SYSTEMD_IMPORT_BTRFS_SUBVOL: %m");
512
513 r = getenv_bool("SYSTEMD_IMPORT_BTRFS_QUOTA");
514 if (r >= 0)
515 SET_FLAG(arg_pull_flags, PULL_BTRFS_QUOTA, r);
516 else if (r != -ENXIO)
517 log_warning_errno(r, "Failed to parse $SYSTEMD_IMPORT_BTRFS_QUOTA: %m");
518
519 r = getenv_bool("SYSTEMD_IMPORT_SYNC");
520 if (r >= 0)
521 SET_FLAG(arg_pull_flags, PULL_SYNC, r);
522 else if (r != -ENXIO)
523 log_warning_errno(r, "Failed to parse $SYSTEMD_IMPORT_SYNC: %m");
524 }
525
pull_main(int argc,char * argv[])526 static int pull_main(int argc, char *argv[]) {
527 static const Verb verbs[] = {
528 { "help", VERB_ANY, VERB_ANY, 0, help },
529 { "tar", 2, 3, 0, pull_tar },
530 { "raw", 2, 3, 0, pull_raw },
531 {}
532 };
533
534 return dispatch_verb(argc, argv, verbs, NULL);
535 }
536
run(int argc,char * argv[])537 static int run(int argc, char *argv[]) {
538 int r;
539
540 setlocale(LC_ALL, "");
541 log_parse_environment();
542 log_open();
543
544 parse_env();
545
546 r = parse_argv(argc, argv);
547 if (r <= 0)
548 return r;
549
550 (void) ignore_signals(SIGPIPE);
551
552 return pull_main(argc, argv);
553 }
554
555 DEFINE_MAIN_FUNCTION(run);
556