1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
8 #include "alloc-util.h"
9 #include "btrfs-util.h"
11 #include "curl-util.h"
12 #include "errno-util.h"
14 #include "import-common.h"
15 #include "import-util.h"
16 #include "install-file.h"
18 #include "mkdir-label.h"
19 #include "path-util.h"
20 #include "process-util.h"
21 #include "pull-common.h"
25 #include "string-util.h"
26 #include "tmpfile-util.h"
29 typedef enum TarProgress
{
36 typedef struct TarPull
{
45 PullJob
*checksum_job
;
46 PullJob
*signature_job
;
47 PullJob
*settings_job
;
49 TarPullFinished on_finished
;
60 char *settings_temp_path
;
65 TarPull
* tar_pull_unref(TarPull
*i
) {
70 sigkill_wait(i
->tar_pid
);
72 pull_job_unref(i
->tar_job
);
73 pull_job_unref(i
->checksum_job
);
74 pull_job_unref(i
->signature_job
);
75 pull_job_unref(i
->settings_job
);
77 curl_glue_unref(i
->glue
);
78 sd_event_unref(i
->event
);
80 rm_rf_subvolume_and_free(i
->temp_path
);
81 unlink_and_free(i
->settings_temp_path
);
84 free(i
->settings_path
);
95 const char *image_root
,
96 TarPullFinished on_finished
,
99 _cleanup_(curl_glue_unrefp
) CurlGlue
*g
= NULL
;
100 _cleanup_(sd_event_unrefp
) sd_event
*e
= NULL
;
101 _cleanup_(tar_pull_unrefp
) TarPull
*i
= NULL
;
102 _cleanup_free_
char *root
= NULL
;
108 root
= strdup(image_root
);
113 e
= sd_event_ref(event
);
115 r
= sd_event_default(&e
);
120 r
= curl_glue_new(&g
, e
);
129 .on_finished
= on_finished
,
130 .userdata
= userdata
,
131 .image_root
= TAKE_PTR(root
),
132 .event
= TAKE_PTR(e
),
136 i
->glue
->on_finished
= pull_job_curl_on_finished
;
137 i
->glue
->userdata
= i
;
144 static void tar_pull_report_progress(TarPull
*i
, TarProgress p
) {
151 case TAR_DOWNLOADING
: {
152 unsigned remain
= 85;
156 if (i
->checksum_job
) {
157 percent
+= i
->checksum_job
->progress_percent
* 5 / 100;
161 if (i
->signature_job
) {
162 percent
+= i
->signature_job
->progress_percent
* 5 / 100;
166 if (i
->settings_job
) {
167 percent
+= i
->settings_job
->progress_percent
* 5 / 100;
172 percent
+= i
->tar_job
->progress_percent
* remain
/ 100;
189 assert_not_reached();
192 sd_notifyf(false, "X_IMPORT_PROGRESS=%u%%", percent
);
193 log_debug("Combined progress %u%%", percent
);
196 static int tar_pull_determine_path(
199 char **field
/* input + output (!) */) {
210 r
= pull_make_path(i
->tar_job
->url
, i
->tar_job
->etag
, i
->image_root
, ".tar-", suffix
, field
);
217 static int tar_pull_make_local_copy(TarPull
*i
) {
218 _cleanup_(rm_rf_subvolume_and_freep
) char *t
= NULL
;
219 _cleanup_free_
char *p
= NULL
;
229 assert(i
->final_path
);
231 p
= path_join(i
->image_root
, i
->local
);
235 if (FLAGS_SET(i
->flags
, IMPORT_PULL_KEEP_DOWNLOAD
)) {
236 r
= tempfn_random(p
, NULL
, &t
);
238 return log_error_errno(r
, "Failed to generate temporary filename for %s: %m", p
);
240 if (i
->flags
& IMPORT_BTRFS_SUBVOL
)
241 r
= btrfs_subvol_snapshot_at(
242 AT_FDCWD
, i
->final_path
,
244 (i
->flags
& IMPORT_BTRFS_QUOTA
? BTRFS_SNAPSHOT_QUOTA
: 0)|
245 BTRFS_SNAPSHOT_FALLBACK_COPY
|
246 BTRFS_SNAPSHOT_FALLBACK_DIRECTORY
|
247 BTRFS_SNAPSHOT_RECURSIVE
);
249 r
= copy_tree(i
->final_path
, t
, UID_INVALID
, GID_INVALID
, COPY_REFLINK
|COPY_HARDLINKS
, NULL
, NULL
);
251 return log_error_errno(r
, "Failed to create local image: %m");
255 source
= i
->final_path
;
257 r
= install_file(AT_FDCWD
, source
,
259 (i
->flags
& IMPORT_FORCE
? INSTALL_REPLACE
: 0) |
260 (i
->flags
& IMPORT_READ_ONLY
? INSTALL_READ_ONLY
: 0) |
261 (i
->flags
& IMPORT_SYNC
? INSTALL_SYNCFS
: 0));
263 return log_error_errno(r
, "Failed to install local image '%s': %m", p
);
267 log_info("Created new local image '%s'.", i
->local
);
269 if (FLAGS_SET(i
->flags
, IMPORT_PULL_SETTINGS
)) {
270 _cleanup_free_
char *local_settings
= NULL
;
271 assert(i
->settings_job
);
273 r
= tar_pull_determine_path(i
, ".nspawn", &i
->settings_path
);
277 local_settings
= strjoin(i
->image_root
, "/", i
->local
, ".nspawn");
281 if (FLAGS_SET(i
->flags
, IMPORT_PULL_KEEP_DOWNLOAD
))
282 r
= copy_file_atomic(
287 (FLAGS_SET(i
->flags
, IMPORT_FORCE
) ? COPY_REPLACE
: 0) |
288 (FLAGS_SET(i
->flags
, IMPORT_SYNC
) ? COPY_FSYNC_FULL
: 0));
290 r
= install_file(AT_FDCWD
, i
->settings_path
,
291 AT_FDCWD
, local_settings
,
292 (i
->flags
& IMPORT_FORCE
? INSTALL_REPLACE
: 0) |
293 (i
->flags
& IMPORT_SYNC
? INSTALL_SYNCFS
: 0));
295 log_warning_errno(r
, "Settings file %s already exists, not replacing.", local_settings
);
296 else if (r
== -ENOENT
)
297 log_debug_errno(r
, "Skipping creation of settings file, since none was found.");
299 log_warning_errno(r
, "Failed to install settings files %s, ignoring: %m", local_settings
);
301 log_info("Created new settings file %s.", local_settings
);
307 static bool tar_pull_is_done(TarPull
*i
) {
311 if (!PULL_JOB_IS_COMPLETE(i
->tar_job
))
313 if (i
->checksum_job
&& !PULL_JOB_IS_COMPLETE(i
->checksum_job
))
315 if (i
->signature_job
&& !PULL_JOB_IS_COMPLETE(i
->signature_job
))
317 if (i
->settings_job
&& !PULL_JOB_IS_COMPLETE(i
->settings_job
))
323 static void tar_pull_job_on_finished(PullJob
*j
) {
333 if (j
== i
->tar_job
) {
334 if (j
->error
== ENOMEDIUM
) /* HTTP 404 */
335 r
= log_error_errno(j
->error
, "Failed to retrieve image file. (Wrong URL?)");
337 r
= log_error_errno(j
->error
, "Failed to retrieve image file.");
339 } else if (j
== i
->checksum_job
) {
340 r
= log_error_errno(j
->error
, "Failed to retrieve SHA256 checksum, cannot verify. (Try --verify=no?)");
342 } else if (j
== i
->signature_job
)
343 log_debug_errno(j
->error
, "Signature job for %s failed, proceeding for now.", j
->url
);
344 else if (j
== i
->settings_job
)
345 log_info_errno(j
->error
, "Settings file could not be retrieved, proceeding without.");
347 assert("unexpected job");
350 /* This is invoked if either the download completed successfully, or the download was skipped because
351 * we already have the etag. */
353 if (!tar_pull_is_done(i
))
356 if (i
->signature_job
&& i
->signature_job
->error
!= 0) {
357 VerificationStyle style
;
359 assert(i
->checksum_job
);
361 r
= verification_style_from_url(i
->checksum_job
->url
, &style
);
363 log_error_errno(r
, "Failed to determine verification style from checksum URL: %m");
367 if (style
== VERIFICATION_PER_DIRECTORY
) { /* A failed signature file download only matters
368 * in per-directory verification mode, since only
369 * then the signature is detached, and thus a file
371 r
= log_error_errno(i
->signature_job
->error
,
372 "Failed to retrieve signature file, cannot verify. (Try --verify=no?)");
377 pull_job_close_disk_fd(i
->tar_job
);
378 pull_job_close_disk_fd(i
->settings_job
);
380 if (i
->tar_pid
> 0) {
381 r
= wait_for_terminate_and_check("tar", TAKE_PID(i
->tar_pid
), WAIT_LOG
);
384 if (r
!= EXIT_SUCCESS
) {
390 if (!i
->tar_job
->etag_exists
) {
391 /* This is a new download, verify it, and move it into place */
393 tar_pull_report_progress(i
, TAR_VERIFYING
);
395 r
= pull_verify(i
->verify
,
401 /* roothash_job = */ NULL
,
402 /* roothash_signature_job = */ NULL
,
403 /* verity_job = */ NULL
);
408 if (i
->flags
& IMPORT_DIRECT
) {
409 assert(!i
->settings_job
);
411 assert(!i
->temp_path
);
413 tar_pull_report_progress(i
, TAR_FINALIZING
);
415 r
= import_mangle_os_tree(i
->local
);
422 (i
->flags
& IMPORT_READ_ONLY
? INSTALL_READ_ONLY
: 0) |
423 (i
->flags
& IMPORT_SYNC
? INSTALL_SYNCFS
: 0));
425 log_error_errno(r
, "Failed to finalize '%s': %m", i
->local
);
429 r
= tar_pull_determine_path(i
, NULL
, &i
->final_path
);
433 if (!i
->tar_job
->etag_exists
) {
434 /* This is a new download, verify it, and move it into place */
436 assert(i
->temp_path
);
437 assert(i
->final_path
);
439 tar_pull_report_progress(i
, TAR_FINALIZING
);
441 r
= import_mangle_os_tree(i
->temp_path
);
446 AT_FDCWD
, i
->temp_path
,
447 AT_FDCWD
, i
->final_path
,
448 (i
->flags
& IMPORT_PULL_KEEP_DOWNLOAD
? INSTALL_READ_ONLY
: 0) |
449 (i
->flags
& IMPORT_SYNC
? INSTALL_SYNCFS
: 0));
451 log_error_errno(r
, "Failed to rename to final image name to %s: %m", i
->final_path
);
455 i
->temp_path
= mfree(i
->temp_path
);
457 if (i
->settings_job
&&
458 i
->settings_job
->error
== 0) {
460 /* Also move the settings file into place, if it exists. Note that we do so only if we also
461 * moved the tar file in place, to keep things strictly in sync. */
462 assert(i
->settings_temp_path
);
464 /* Regenerate final name for this auxiliary file, we might know the etag of the file now, and
465 * we should incorporate it in the file name if we can */
466 i
->settings_path
= mfree(i
->settings_path
);
468 r
= tar_pull_determine_path(i
, ".nspawn", &i
->settings_path
);
473 AT_FDCWD
, i
->settings_temp_path
,
474 AT_FDCWD
, i
->settings_path
,
476 (i
->flags
& IMPORT_SYNC
? INSTALL_FSYNC_FULL
: 0));
478 log_error_errno(r
, "Failed to rename settings file to %s: %m", i
->settings_path
);
482 i
->settings_temp_path
= mfree(i
->settings_temp_path
);
486 tar_pull_report_progress(i
, TAR_COPYING
);
488 r
= tar_pull_make_local_copy(i
);
497 i
->on_finished(i
, r
, i
->userdata
);
499 sd_event_exit(i
->event
, r
);
502 static int tar_pull_job_on_open_disk_tar(PullJob
*j
) {
511 assert(i
->tar_job
== j
);
512 assert(i
->tar_pid
<= 0);
514 if (i
->flags
& IMPORT_DIRECT
)
518 r
= tempfn_random_child(i
->image_root
, "tar", &i
->temp_path
);
523 where
= i
->temp_path
;
526 (void) mkdir_parents_label(where
, 0700);
528 if (FLAGS_SET(i
->flags
, IMPORT_DIRECT
|IMPORT_FORCE
))
529 (void) rm_rf(where
, REMOVE_ROOT
|REMOVE_PHYSICAL
|REMOVE_SUBVOLUME
);
531 if (i
->flags
& IMPORT_BTRFS_SUBVOL
)
532 r
= btrfs_subvol_make_fallback(AT_FDCWD
, where
, 0755);
534 r
= RET_NERRNO(mkdir(where
, 0755));
535 if (r
== -EEXIST
&& (i
->flags
& IMPORT_DIRECT
)) /* EEXIST is OK if in direct mode, but not otherwise,
536 * because in that case our temporary path collided */
539 return log_error_errno(r
, "Failed to create directory/subvolume %s: %m", where
);
540 if (r
> 0 && (i
->flags
& IMPORT_BTRFS_QUOTA
)) { /* actually btrfs subvol */
541 if (!(i
->flags
& IMPORT_DIRECT
))
542 (void) import_assign_pool_quota_and_warn(i
->image_root
);
543 (void) import_assign_pool_quota_and_warn(where
);
546 j
->disk_fd
= import_fork_tar_x(where
, &i
->tar_pid
);
553 static int tar_pull_job_on_open_disk_settings(PullJob
*j
) {
561 assert(i
->settings_job
== j
);
563 if (!i
->settings_temp_path
) {
564 r
= tempfn_random_child(i
->image_root
, "settings", &i
->settings_temp_path
);
569 (void) mkdir_parents_label(i
->settings_temp_path
, 0700);
571 j
->disk_fd
= open(i
->settings_temp_path
, O_RDWR
|O_CREAT
|O_EXCL
|O_NOCTTY
|O_CLOEXEC
, 0664);
573 return log_error_errno(errno
, "Failed to create %s: %m", i
->settings_temp_path
);
578 static void tar_pull_job_on_progress(PullJob
*j
) {
586 tar_pull_report_progress(i
, TAR_DOWNLOADING
);
595 const char *checksum
) {
600 assert(verify
== _IMPORT_VERIFY_INVALID
|| verify
< _IMPORT_VERIFY_MAX
);
601 assert(verify
== _IMPORT_VERIFY_INVALID
|| verify
>= 0);
602 assert((verify
< 0) || !checksum
);
603 assert(!(flags
& ~IMPORT_PULL_FLAGS_MASK_TAR
));
604 assert(!(flags
& IMPORT_PULL_SETTINGS
) || !(flags
& IMPORT_DIRECT
));
605 assert(!(flags
& IMPORT_PULL_SETTINGS
) || !checksum
);
607 if (!http_url_is_valid(url
) && !file_url_is_valid(url
))
610 if (local
&& !pull_validate_local(local
, flags
))
616 r
= free_and_strdup(&i
->local
, local
);
620 r
= free_and_strdup(&i
->checksum
, checksum
);
627 /* Set up download job for TAR file */
628 r
= pull_job_new(&i
->tar_job
, url
, i
->glue
, i
);
632 i
->tar_job
->on_finished
= tar_pull_job_on_finished
;
633 i
->tar_job
->on_open_disk
= tar_pull_job_on_open_disk_tar
;
634 i
->tar_job
->calc_checksum
= checksum
|| IN_SET(verify
, IMPORT_VERIFY_CHECKSUM
, IMPORT_VERIFY_SIGNATURE
);
636 if (!FLAGS_SET(flags
, IMPORT_DIRECT
)) {
637 r
= pull_find_old_etags(url
, i
->image_root
, DT_DIR
, ".tar-", NULL
, &i
->tar_job
->old_etags
);
642 /* Set up download of checksum/signature files */
643 r
= pull_make_verification_jobs(
650 tar_pull_job_on_finished
,
655 /* Set up download job for the settings file (.nspawn) */
656 if (FLAGS_SET(flags
, IMPORT_PULL_SETTINGS
)) {
657 r
= pull_make_auxiliary_job(
664 tar_pull_job_on_open_disk_settings
,
665 tar_pull_job_on_finished
,
681 j
->on_progress
= tar_pull_job_on_progress
;
682 j
->sync
= FLAGS_SET(flags
, IMPORT_SYNC
);
684 r
= pull_job_begin(j
);