]> git.ipfire.org Git - thirdparty/systemd.git/blame_incremental - src/import/pull-tar.c
Merge pull request #20303 from andir/sysconfig-example
[thirdparty/systemd.git] / src / import / pull-tar.c
... / ...
CommitLineData
1/* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3#include <curl/curl.h>
4#include <sys/prctl.h>
5
6#include "sd-daemon.h"
7
8#include "alloc-util.h"
9#include "btrfs-util.h"
10#include "copy.h"
11#include "curl-util.h"
12#include "fd-util.h"
13#include "fs-util.h"
14#include "hostname-util.h"
15#include "import-common.h"
16#include "import-util.h"
17#include "install-file.h"
18#include "macro.h"
19#include "mkdir.h"
20#include "path-util.h"
21#include "process-util.h"
22#include "pull-common.h"
23#include "pull-job.h"
24#include "pull-tar.h"
25#include "rm-rf.h"
26#include "string-util.h"
27#include "strv.h"
28#include "tmpfile-util.h"
29#include "user-util.h"
30#include "utf8.h"
31#include "util.h"
32#include "web-util.h"
33
34typedef enum TarProgress {
35 TAR_DOWNLOADING,
36 TAR_VERIFYING,
37 TAR_FINALIZING,
38 TAR_COPYING,
39} TarProgress;
40
41struct TarPull {
42 sd_event *event;
43 CurlGlue *glue;
44
45 PullFlags flags;
46 ImportVerify verify;
47 char *image_root;
48
49 PullJob *tar_job;
50 PullJob *checksum_job;
51 PullJob *signature_job;
52 PullJob *settings_job;
53
54 TarPullFinished on_finished;
55 void *userdata;
56
57 char *local;
58
59 pid_t tar_pid;
60
61 char *final_path;
62 char *temp_path;
63
64 char *settings_path;
65 char *settings_temp_path;
66
67 char *checksum;
68};
69
70TarPull* tar_pull_unref(TarPull *i) {
71 if (!i)
72 return NULL;
73
74 if (i->tar_pid > 1) {
75 (void) kill_and_sigcont(i->tar_pid, SIGKILL);
76 (void) wait_for_terminate(i->tar_pid, NULL);
77 }
78
79 pull_job_unref(i->tar_job);
80 pull_job_unref(i->checksum_job);
81 pull_job_unref(i->signature_job);
82 pull_job_unref(i->settings_job);
83
84 curl_glue_unref(i->glue);
85 sd_event_unref(i->event);
86
87 rm_rf_subvolume_and_free(i->temp_path);
88 unlink_and_free(i->settings_temp_path);
89
90 free(i->final_path);
91 free(i->settings_path);
92 free(i->image_root);
93 free(i->local);
94 free(i->checksum);
95
96 return mfree(i);
97}
98
99int tar_pull_new(
100 TarPull **ret,
101 sd_event *event,
102 const char *image_root,
103 TarPullFinished on_finished,
104 void *userdata) {
105
106 _cleanup_(curl_glue_unrefp) CurlGlue *g = NULL;
107 _cleanup_(sd_event_unrefp) sd_event *e = NULL;
108 _cleanup_(tar_pull_unrefp) TarPull *i = NULL;
109 _cleanup_free_ char *root = NULL;
110 int r;
111
112 assert(ret);
113
114 root = strdup(image_root ?: "/var/lib/machines");
115 if (!root)
116 return -ENOMEM;
117
118 if (event)
119 e = sd_event_ref(event);
120 else {
121 r = sd_event_default(&e);
122 if (r < 0)
123 return r;
124 }
125
126 r = curl_glue_new(&g, e);
127 if (r < 0)
128 return r;
129
130 i = new(TarPull, 1);
131 if (!i)
132 return -ENOMEM;
133
134 *i = (TarPull) {
135 .on_finished = on_finished,
136 .userdata = userdata,
137 .image_root = TAKE_PTR(root),
138 .event = TAKE_PTR(e),
139 .glue = TAKE_PTR(g),
140 };
141
142 i->glue->on_finished = pull_job_curl_on_finished;
143 i->glue->userdata = i;
144
145 *ret = TAKE_PTR(i);
146
147 return 0;
148}
149
150static void tar_pull_report_progress(TarPull *i, TarProgress p) {
151 unsigned percent;
152
153 assert(i);
154
155 switch (p) {
156
157 case TAR_DOWNLOADING: {
158 unsigned remain = 85;
159
160 percent = 0;
161
162 if (i->checksum_job) {
163 percent += i->checksum_job->progress_percent * 5 / 100;
164 remain -= 5;
165 }
166
167 if (i->signature_job) {
168 percent += i->signature_job->progress_percent * 5 / 100;
169 remain -= 5;
170 }
171
172 if (i->settings_job) {
173 percent += i->settings_job->progress_percent * 5 / 100;
174 remain -= 5;
175 }
176
177 if (i->tar_job)
178 percent += i->tar_job->progress_percent * remain / 100;
179 break;
180 }
181
182 case TAR_VERIFYING:
183 percent = 85;
184 break;
185
186 case TAR_FINALIZING:
187 percent = 90;
188 break;
189
190 case TAR_COPYING:
191 percent = 95;
192 break;
193
194 default:
195 assert_not_reached();
196 }
197
198 sd_notifyf(false, "X_IMPORT_PROGRESS=%u", percent);
199 log_debug("Combined progress %u%%", percent);
200}
201
202static int tar_pull_determine_path(
203 TarPull *i,
204 const char *suffix,
205 char **field /* input + output (!) */) {
206 int r;
207
208 assert(i);
209 assert(field);
210
211 if (*field)
212 return 0;
213
214 assert(i->tar_job);
215
216 r = pull_make_path(i->tar_job->url, i->tar_job->etag, i->image_root, ".tar-", suffix, field);
217 if (r < 0)
218 return log_oom();
219
220 return 1;
221}
222
223static int tar_pull_make_local_copy(TarPull *i) {
224 _cleanup_(rm_rf_subvolume_and_freep) char *t = NULL;
225 const char *p;
226 int r;
227
228 assert(i);
229 assert(i->tar_job);
230
231 if (!i->local)
232 return 0;
233
234 assert(i->final_path);
235
236 p = prefix_roota(i->image_root, i->local);
237
238 r = tempfn_random(p, NULL, &t);
239 if (r < 0)
240 return log_error_errno(r, "Failed to generate temporary filename for %s: %m", p);
241
242 if (i->flags & PULL_BTRFS_SUBVOL)
243 r = btrfs_subvol_snapshot(
244 i->final_path,
245 t,
246 (i->flags & PULL_BTRFS_QUOTA ? BTRFS_SNAPSHOT_QUOTA : 0)|
247 BTRFS_SNAPSHOT_FALLBACK_COPY|
248 BTRFS_SNAPSHOT_FALLBACK_DIRECTORY|
249 BTRFS_SNAPSHOT_RECURSIVE);
250 else
251 r = copy_tree(i->final_path, t, UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_HARDLINKS);
252 if (r < 0)
253 return log_error_errno(r, "Failed to create local image: %m");
254
255 r = install_file(AT_FDCWD, t,
256 AT_FDCWD, p,
257 (i->flags & PULL_FORCE ? INSTALL_REPLACE : 0) |
258 (i->flags & PULL_READ_ONLY ? INSTALL_READ_ONLY : 0) |
259 (i->flags & PULL_SYNC ? INSTALL_SYNCFS : 0));
260 if (r < 0)
261 return log_error_errno(r, "Failed to install local image '%s': %m", p);
262
263 t = mfree(t);
264
265 log_info("Created new local image '%s'.", i->local);
266
267 if (FLAGS_SET(i->flags, PULL_SETTINGS)) {
268 const char *local_settings;
269 assert(i->settings_job);
270
271 r = tar_pull_determine_path(i, ".nspawn", &i->settings_path);
272 if (r < 0)
273 return r;
274
275 local_settings = strjoina(i->image_root, "/", i->local, ".nspawn");
276
277 r = copy_file_atomic(
278 i->settings_path,
279 local_settings,
280 0664,
281 0, 0,
282 COPY_REFLINK |
283 (FLAGS_SET(i->flags, PULL_FORCE) ? COPY_REPLACE : 0) |
284 (FLAGS_SET(i->flags, PULL_SYNC) ? COPY_FSYNC_FULL : 0));
285 if (r == -EEXIST)
286 log_warning_errno(r, "Settings file %s already exists, not replacing.", local_settings);
287 else if (r == -ENOENT)
288 log_debug_errno(r, "Skipping creation of settings file, since none was found.");
289 else if (r < 0)
290 log_warning_errno(r, "Failed to copy settings files %s, ignoring: %m", local_settings);
291 else
292 log_info("Created new settings file %s.", local_settings);
293 }
294
295 return 0;
296}
297
298static bool tar_pull_is_done(TarPull *i) {
299 assert(i);
300 assert(i->tar_job);
301
302 if (!PULL_JOB_IS_COMPLETE(i->tar_job))
303 return false;
304 if (i->checksum_job && !PULL_JOB_IS_COMPLETE(i->checksum_job))
305 return false;
306 if (i->signature_job && !PULL_JOB_IS_COMPLETE(i->signature_job))
307 return false;
308 if (i->settings_job && !PULL_JOB_IS_COMPLETE(i->settings_job))
309 return false;
310
311 return true;
312}
313
314static void tar_pull_job_on_finished(PullJob *j) {
315 TarPull *i;
316 int r;
317
318 assert(j);
319 assert(j->userdata);
320
321 i = j->userdata;
322
323 if (j->error != 0) {
324 if (j == i->tar_job) {
325 if (j->error == ENOMEDIUM) /* HTTP 404 */
326 r = log_error_errno(j->error, "Failed to retrieve image file. (Wrong URL?)");
327 else
328 r = log_error_errno(j->error, "Failed to retrieve image file.");
329 goto finish;
330 } else if (j == i->checksum_job) {
331 r = log_error_errno(j->error, "Failed to retrieve SHA256 checksum, cannot verify. (Try --verify=no?)");
332 goto finish;
333 } else if (j == i->signature_job)
334 log_debug_errno(j->error, "Signature job for %s failed, proceeding for now.", j->url);
335 else if (j == i->settings_job)
336 log_info_errno(j->error, "Settings file could not be retrieved, proceeding without.");
337 else
338 assert("unexpected job");
339 }
340
341 /* This is invoked if either the download completed successfully, or the download was skipped because
342 * we already have the etag. */
343
344 if (!tar_pull_is_done(i))
345 return;
346
347 if (i->signature_job && i->signature_job->error != 0) {
348 VerificationStyle style;
349
350 assert(i->checksum_job);
351
352 r = verification_style_from_url(i->checksum_job->url, &style);
353 if (r < 0) {
354 log_error_errno(r, "Failed to determine verification style from checksum URL: %m");
355 goto finish;
356 }
357
358 if (style == VERIFICATION_PER_DIRECTORY) { /* A failed signature file download only matters
359 * in per-directory verification mode, since only
360 * then the signature is detached, and thus a file
361 * of its own. */
362 r = log_error_errno(i->signature_job->error,
363 "Failed to retrieve signature file, cannot verify. (Try --verify=no?)");
364 goto finish;
365 }
366 }
367
368 pull_job_close_disk_fd(i->tar_job);
369 pull_job_close_disk_fd(i->settings_job);
370
371 if (i->tar_pid > 0) {
372 r = wait_for_terminate_and_check("tar", i->tar_pid, WAIT_LOG);
373 i->tar_pid = 0;
374 if (r < 0)
375 goto finish;
376 if (r != EXIT_SUCCESS) {
377 r = -EIO;
378 goto finish;
379 }
380 }
381
382 if (!i->tar_job->etag_exists) {
383 /* This is a new download, verify it, and move it into place */
384
385 tar_pull_report_progress(i, TAR_VERIFYING);
386
387 r = pull_verify(i->verify,
388 i->checksum,
389 i->tar_job,
390 i->checksum_job,
391 i->signature_job,
392 i->settings_job,
393 /* roothash_job = */ NULL,
394 /* roothash_signature_job = */ NULL,
395 /* verity_job = */ NULL);
396 if (r < 0)
397 goto finish;
398 }
399
400 if (i->flags & PULL_DIRECT) {
401 assert(!i->settings_job);
402 assert(i->local);
403 assert(!i->temp_path);
404
405 tar_pull_report_progress(i, TAR_FINALIZING);
406
407 r = import_mangle_os_tree(i->local);
408 if (r < 0)
409 goto finish;
410
411 r = install_file(
412 AT_FDCWD, i->local,
413 AT_FDCWD, NULL,
414 (i->flags & PULL_READ_ONLY) ? INSTALL_READ_ONLY : 0 |
415 (i->flags & PULL_SYNC ? INSTALL_SYNCFS : 0));
416 if (r < 0) {
417 log_error_errno(r, "Failed to finalize '%s': %m", i->local);
418 goto finish;
419 }
420 } else {
421 r = tar_pull_determine_path(i, NULL, &i->final_path);
422 if (r < 0)
423 goto finish;
424
425 if (!i->tar_job->etag_exists) {
426 /* This is a new download, verify it, and move it into place */
427
428 assert(i->temp_path);
429 assert(i->final_path);
430
431 tar_pull_report_progress(i, TAR_FINALIZING);
432
433 r = import_mangle_os_tree(i->temp_path);
434 if (r < 0)
435 goto finish;
436
437 r = install_file(
438 AT_FDCWD, i->temp_path,
439 AT_FDCWD, i->final_path,
440 INSTALL_READ_ONLY|
441 (i->flags & PULL_SYNC ? INSTALL_SYNCFS : 0));
442 if (r < 0) {
443 log_error_errno(r, "Failed to rename to final image name to %s: %m", i->final_path);
444 goto finish;
445 }
446
447 i->temp_path = mfree(i->temp_path);
448
449 if (i->settings_job &&
450 i->settings_job->error == 0) {
451
452 /* Also move the settings file into place, if it exists. Note that we do so only if we also
453 * moved the tar file in place, to keep things strictly in sync. */
454 assert(i->settings_temp_path);
455
456 /* Regenerate final name for this auxiliary file, we might know the etag of the file now, and
457 * we should incorporate it in the file name if we can */
458 i->settings_path = mfree(i->settings_path);
459
460 r = tar_pull_determine_path(i, ".nspawn", &i->settings_path);
461 if (r < 0)
462 goto finish;
463
464 r = install_file(
465 AT_FDCWD, i->settings_temp_path,
466 AT_FDCWD, i->settings_path,
467 INSTALL_READ_ONLY|
468 (i->flags & PULL_SYNC ? INSTALL_FSYNC_FULL : 0));
469 if (r < 0) {
470 log_error_errno(r, "Failed to rename settings file to %s: %m", i->settings_path);
471 goto finish;
472 }
473
474 i->settings_temp_path = mfree(i->settings_temp_path);
475 }
476 }
477
478 tar_pull_report_progress(i, TAR_COPYING);
479
480 r = tar_pull_make_local_copy(i);
481 if (r < 0)
482 goto finish;
483 }
484
485 r = 0;
486
487finish:
488 if (i->on_finished)
489 i->on_finished(i, r, i->userdata);
490 else
491 sd_event_exit(i->event, r);
492}
493
494static int tar_pull_job_on_open_disk_tar(PullJob *j) {
495 const char *where;
496 TarPull *i;
497 int r;
498
499 assert(j);
500 assert(j->userdata);
501
502 i = j->userdata;
503 assert(i->tar_job == j);
504 assert(i->tar_pid <= 0);
505
506 if (i->flags & PULL_DIRECT)
507 where = i->local;
508 else {
509 if (!i->temp_path) {
510 r = tempfn_random_child(i->image_root, "tar", &i->temp_path);
511 if (r < 0)
512 return log_oom();
513 }
514
515 where = i->temp_path;
516 }
517
518 (void) mkdir_parents_label(where, 0700);
519
520 if (FLAGS_SET(i->flags, PULL_DIRECT|PULL_FORCE))
521 (void) rm_rf(where, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME);
522
523 if (i->flags & PULL_BTRFS_SUBVOL)
524 r = btrfs_subvol_make_fallback(where, 0755);
525 else
526 r = mkdir(where, 0755) < 0 ? -errno : 0;
527 if (r == -EEXIST && (i->flags & PULL_DIRECT)) /* EEXIST is OK if in direct mode, but not otherwise,
528 * because in that case our temporary path collided */
529 r = 0;
530 if (r < 0)
531 return log_error_errno(r, "Failed to create directory/subvolume %s: %m", where);
532 if (r > 0 && (i->flags & PULL_BTRFS_QUOTA)) { /* actually btrfs subvol */
533 if (!(i->flags & PULL_DIRECT))
534 (void) import_assign_pool_quota_and_warn(i->image_root);
535 (void) import_assign_pool_quota_and_warn(where);
536 }
537
538 j->disk_fd = import_fork_tar_x(where, &i->tar_pid);
539 if (j->disk_fd < 0)
540 return j->disk_fd;
541
542 return 0;
543}
544
545static int tar_pull_job_on_open_disk_settings(PullJob *j) {
546 TarPull *i;
547 int r;
548
549 assert(j);
550 assert(j->userdata);
551
552 i = j->userdata;
553 assert(i->settings_job == j);
554
555 if (!i->settings_temp_path) {
556 r = tempfn_random_child(i->image_root, "settings", &i->settings_temp_path);
557 if (r < 0)
558 return log_oom();
559 }
560
561 (void) mkdir_parents_label(i->settings_temp_path, 0700);
562
563 j->disk_fd = open(i->settings_temp_path, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664);
564 if (j->disk_fd < 0)
565 return log_error_errno(errno, "Failed to create %s: %m", i->settings_temp_path);
566
567 return 0;
568}
569
570static void tar_pull_job_on_progress(PullJob *j) {
571 TarPull *i;
572
573 assert(j);
574 assert(j->userdata);
575
576 i = j->userdata;
577
578 tar_pull_report_progress(i, TAR_DOWNLOADING);
579}
580
581int tar_pull_start(
582 TarPull *i,
583 const char *url,
584 const char *local,
585 PullFlags flags,
586 ImportVerify verify,
587 const char *checksum) {
588
589 PullJob *j;
590 int r;
591
592 assert(i);
593 assert(verify == _IMPORT_VERIFY_INVALID || verify < _IMPORT_VERIFY_MAX);
594 assert(verify == _IMPORT_VERIFY_INVALID || verify >= 0);
595 assert((verify < 0) || !checksum);
596 assert(!(flags & ~PULL_FLAGS_MASK_TAR));
597 assert(!(flags & PULL_SETTINGS) || !(flags & PULL_DIRECT));
598 assert(!(flags & PULL_SETTINGS) || !checksum);
599
600 if (!http_url_is_valid(url) && !file_url_is_valid(url))
601 return -EINVAL;
602
603 if (local && !pull_validate_local(local, flags))
604 return -EINVAL;
605
606 if (i->tar_job)
607 return -EBUSY;
608
609 r = free_and_strdup(&i->local, local);
610 if (r < 0)
611 return r;
612
613 r = free_and_strdup(&i->checksum, checksum);
614 if (r < 0)
615 return r;
616
617 i->flags = flags;
618 i->verify = verify;
619
620 /* Set up download job for TAR file */
621 r = pull_job_new(&i->tar_job, url, i->glue, i);
622 if (r < 0)
623 return r;
624
625 i->tar_job->on_finished = tar_pull_job_on_finished;
626 i->tar_job->on_open_disk = tar_pull_job_on_open_disk_tar;
627 i->tar_job->calc_checksum = checksum || IN_SET(verify, IMPORT_VERIFY_CHECKSUM, IMPORT_VERIFY_SIGNATURE);
628
629 if (!FLAGS_SET(flags, PULL_DIRECT)) {
630 r = pull_find_old_etags(url, i->image_root, DT_DIR, ".tar-", NULL, &i->tar_job->old_etags);
631 if (r < 0)
632 return r;
633 }
634
635 /* Set up download of checksum/signature files */
636 r = pull_make_verification_jobs(
637 &i->checksum_job,
638 &i->signature_job,
639 verify,
640 checksum,
641 url,
642 i->glue,
643 tar_pull_job_on_finished,
644 i);
645 if (r < 0)
646 return r;
647
648 /* Set up download job for the settings file (.nspawn) */
649 if (FLAGS_SET(flags, PULL_SETTINGS)) {
650 r = pull_make_auxiliary_job(
651 &i->settings_job,
652 url,
653 tar_strip_suffixes,
654 ".nspawn",
655 verify,
656 i->glue,
657 tar_pull_job_on_open_disk_settings,
658 tar_pull_job_on_finished,
659 i);
660 if (r < 0)
661 return r;
662 }
663
664 FOREACH_POINTER(j,
665 i->tar_job,
666 i->checksum_job,
667 i->signature_job,
668 i->settings_job) {
669
670 if (!j)
671 continue;
672
673 j->on_progress = tar_pull_job_on_progress;
674 j->sync = FLAGS_SET(flags, PULL_SYNC);
675
676 r = pull_job_begin(j);
677 if (r < 0)
678 return r;
679 }
680
681 return 0;
682}