1 /* SPDX-License-Identifier: LGPL-2.1+ */
9 #include "alloc-util.h"
10 #include "btrfs-util.h"
11 #include "chattr-util.h"
13 #include "curl-util.h"
16 #include "hostname-util.h"
17 #include "import-common.h"
18 #include "import-util.h"
21 #include "path-util.h"
22 #include "pull-common.h"
25 #include "qcow2-util.h"
27 #include "string-util.h"
29 #include "tmpfile-util.h"
34 typedef enum RawProgress
{
49 PullJob
*roothash_job
;
50 PullJob
*settings_job
;
51 PullJob
*checksum_job
;
52 PullJob
*signature_job
;
54 RawPullFinished on_finished
;
66 char *settings_temp_path
;
69 char *roothash_temp_path
;
74 RawPull
* raw_pull_unref(RawPull
*i
) {
78 pull_job_unref(i
->raw_job
);
79 pull_job_unref(i
->settings_job
);
80 pull_job_unref(i
->roothash_job
);
81 pull_job_unref(i
->checksum_job
);
82 pull_job_unref(i
->signature_job
);
84 curl_glue_unref(i
->glue
);
85 sd_event_unref(i
->event
);
88 (void) unlink(i
->temp_path
);
92 if (i
->roothash_temp_path
) {
93 (void) unlink(i
->roothash_temp_path
);
94 free(i
->roothash_temp_path
);
97 if (i
->settings_temp_path
) {
98 (void) unlink(i
->settings_temp_path
);
99 free(i
->settings_temp_path
);
103 free(i
->roothash_path
);
104 free(i
->settings_path
);
113 const char *image_root
,
114 RawPullFinished on_finished
,
117 _cleanup_(curl_glue_unrefp
) CurlGlue
*g
= NULL
;
118 _cleanup_(sd_event_unrefp
) sd_event
*e
= NULL
;
119 _cleanup_(raw_pull_unrefp
) RawPull
*i
= NULL
;
120 _cleanup_free_
char *root
= NULL
;
125 root
= strdup(image_root
?: "/var/lib/machines");
130 e
= sd_event_ref(event
);
132 r
= sd_event_default(&e
);
137 r
= curl_glue_new(&g
, e
);
146 .on_finished
= on_finished
,
147 .userdata
= userdata
,
148 .image_root
= TAKE_PTR(root
),
149 .event
= TAKE_PTR(e
),
153 i
->glue
->on_finished
= pull_job_curl_on_finished
;
154 i
->glue
->userdata
= i
;
161 static void raw_pull_report_progress(RawPull
*i
, RawProgress p
) {
168 case RAW_DOWNLOADING
: {
169 unsigned remain
= 80;
173 if (i
->settings_job
) {
174 percent
+= i
->settings_job
->progress_percent
* 5 / 100;
178 if (i
->roothash_job
) {
179 percent
+= i
->roothash_job
->progress_percent
* 5 / 100;
183 if (i
->checksum_job
) {
184 percent
+= i
->checksum_job
->progress_percent
* 5 / 100;
188 if (i
->signature_job
) {
189 percent
+= i
->signature_job
->progress_percent
* 5 / 100;
194 percent
+= i
->raw_job
->progress_percent
* remain
/ 100;
215 assert_not_reached("Unknown progress state");
218 sd_notifyf(false, "X_IMPORT_PROGRESS=%u", percent
);
219 log_debug("Combined progress %u%%", percent
);
222 static int raw_pull_maybe_convert_qcow2(RawPull
*i
) {
223 _cleanup_close_
int converted_fd
= -1;
224 _cleanup_free_
char *t
= NULL
;
230 r
= qcow2_detect(i
->raw_job
->disk_fd
);
232 return log_error_errno(r
, "Failed to detect whether this is a QCOW2 image: %m");
236 /* This is a QCOW2 image, let's convert it */
237 r
= tempfn_random(i
->final_path
, NULL
, &t
);
241 converted_fd
= open(t
, O_RDWR
|O_CREAT
|O_EXCL
|O_NOCTTY
|O_CLOEXEC
, 0664);
242 if (converted_fd
< 0)
243 return log_error_errno(errno
, "Failed to create %s: %m", t
);
245 r
= chattr_fd(converted_fd
, FS_NOCOW_FL
, FS_NOCOW_FL
, NULL
);
247 log_warning_errno(r
, "Failed to set file attributes on %s: %m", t
);
249 log_info("Unpacking QCOW2 file.");
251 r
= qcow2_convert(i
->raw_job
->disk_fd
, converted_fd
);
254 return log_error_errno(r
, "Failed to convert qcow2 image: %m");
257 (void) unlink(i
->temp_path
);
258 free_and_replace(i
->temp_path
, t
);
260 safe_close(i
->raw_job
->disk_fd
);
261 i
->raw_job
->disk_fd
= TAKE_FD(converted_fd
);
266 static int raw_pull_determine_path(RawPull
*i
, const char *suffix
, char **field
) {
277 r
= pull_make_path(i
->raw_job
->url
, i
->raw_job
->etag
, i
->image_root
, ".raw-", suffix
, field
);
284 static int raw_pull_copy_auxiliary_file(
296 r
= raw_pull_determine_path(i
, suffix
, path
);
300 local
= strjoina(i
->image_root
, "/", i
->local
, suffix
);
302 r
= copy_file_atomic(*path
, local
, 0644, 0, COPY_REFLINK
| (i
->force_local
? COPY_REPLACE
: 0));
304 log_warning_errno(r
, "File %s already exists, not replacing.", local
);
305 else if (r
== -ENOENT
)
306 log_debug_errno(r
, "Skipping creation of auxiliary file, since none was found.");
308 log_warning_errno(r
, "Failed to copy file %s, ignoring: %m", local
);
310 log_info("Created new file %s.", local
);
315 static int raw_pull_make_local_copy(RawPull
*i
) {
316 _cleanup_free_
char *tp
= NULL
;
317 _cleanup_close_
int dfd
= -1;
327 if (i
->raw_job
->etag_exists
) {
328 /* We have downloaded this one previously, reopen it */
330 assert(i
->raw_job
->disk_fd
< 0);
332 i
->raw_job
->disk_fd
= open(i
->final_path
, O_RDONLY
|O_NOCTTY
|O_CLOEXEC
);
333 if (i
->raw_job
->disk_fd
< 0)
334 return log_error_errno(errno
, "Failed to open vendor image: %m");
336 /* We freshly downloaded the image, use it */
338 assert(i
->raw_job
->disk_fd
>= 0);
340 if (lseek(i
->raw_job
->disk_fd
, SEEK_SET
, 0) == (off_t
) -1)
341 return log_error_errno(errno
, "Failed to seek to beginning of vendor image: %m");
344 p
= strjoina(i
->image_root
, "/", i
->local
, ".raw");
347 (void) rm_rf(p
, REMOVE_ROOT
|REMOVE_PHYSICAL
|REMOVE_SUBVOLUME
);
349 r
= tempfn_random(p
, NULL
, &tp
);
353 dfd
= open(tp
, O_WRONLY
|O_CREAT
|O_EXCL
|O_NOCTTY
|O_CLOEXEC
, 0664);
355 return log_error_errno(errno
, "Failed to create writable copy of image: %m");
357 /* Turn off COW writing. This should greatly improve
358 * performance on COW file systems like btrfs, since it
359 * reduces fragmentation caused by not allowing in-place
361 r
= chattr_fd(dfd
, FS_NOCOW_FL
, FS_NOCOW_FL
, NULL
);
363 log_warning_errno(r
, "Failed to set file attributes on %s: %m", tp
);
365 r
= copy_bytes(i
->raw_job
->disk_fd
, dfd
, (uint64_t) -1, COPY_REFLINK
);
368 return log_error_errno(r
, "Failed to make writable copy of image: %m");
371 (void) copy_times(i
->raw_job
->disk_fd
, dfd
);
372 (void) copy_xattr(i
->raw_job
->disk_fd
, dfd
);
374 dfd
= safe_close(dfd
);
378 r
= log_error_errno(errno
, "Failed to move writable image into place: %m");
383 log_info("Created new local image '%s'.", i
->local
);
386 r
= raw_pull_copy_auxiliary_file(i
, ".roothash", &i
->roothash_path
);
392 r
= raw_pull_copy_auxiliary_file(i
, ".nspawn", &i
->settings_path
);
400 static bool raw_pull_is_done(RawPull
*i
) {
404 if (!PULL_JOB_IS_COMPLETE(i
->raw_job
))
406 if (i
->roothash_job
&& !PULL_JOB_IS_COMPLETE(i
->roothash_job
))
408 if (i
->settings_job
&& !PULL_JOB_IS_COMPLETE(i
->settings_job
))
410 if (i
->checksum_job
&& !PULL_JOB_IS_COMPLETE(i
->checksum_job
))
412 if (i
->signature_job
&& !PULL_JOB_IS_COMPLETE(i
->signature_job
))
418 static int raw_pull_rename_auxiliary_file(
431 /* Regenerate final name for this auxiliary file, we might know the etag of the file now, and we should
432 * incorporate it in the file name if we can */
433 *path
= mfree(*path
);
434 r
= raw_pull_determine_path(i
, suffix
, path
);
438 r
= import_make_read_only(*temp_path
);
442 r
= rename_noreplace(AT_FDCWD
, *temp_path
, AT_FDCWD
, *path
);
444 return log_error_errno(r
, "Failed to rename file %s to %s: %m", *temp_path
, *path
);
446 *temp_path
= mfree(*temp_path
);
451 static void raw_pull_job_on_finished(PullJob
*j
) {
459 if (j
== i
->roothash_job
) {
461 log_info_errno(j
->error
, "Root hash file could not be retrieved, proceeding without.");
462 } else if (j
== i
->settings_job
) {
464 log_info_errno(j
->error
, "Settings file could not be retrieved, proceeding without.");
465 } else if (j
->error
!= 0 && j
!= i
->signature_job
) {
466 if (j
== i
->checksum_job
)
467 log_error_errno(j
->error
, "Failed to retrieve SHA256 checksum, cannot verify. (Try --verify=no?)");
469 log_error_errno(j
->error
, "Failed to retrieve image file. (Wrong URL?)");
475 /* This is invoked if either the download completed
476 * successfully, or the download was skipped because we
477 * already have the etag. In this case ->etag_exists is
480 * We only do something when we got all three files */
482 if (!raw_pull_is_done(i
))
485 if (i
->signature_job
&& i
->checksum_job
->style
== VERIFICATION_PER_DIRECTORY
&& i
->signature_job
->error
!= 0) {
486 log_error_errno(j
->error
, "Failed to retrieve signature file, cannot verify. (Try --verify=no?)");
488 r
= i
->signature_job
->error
;
493 i
->roothash_job
->disk_fd
= safe_close(i
->roothash_job
->disk_fd
);
495 i
->settings_job
->disk_fd
= safe_close(i
->settings_job
->disk_fd
);
497 r
= raw_pull_determine_path(i
, ".raw", &i
->final_path
);
501 if (!i
->raw_job
->etag_exists
) {
502 /* This is a new download, verify it, and move it into place */
503 assert(i
->raw_job
->disk_fd
>= 0);
505 raw_pull_report_progress(i
, RAW_VERIFYING
);
507 r
= pull_verify(i
->raw_job
, i
->roothash_job
, i
->settings_job
, i
->checksum_job
, i
->signature_job
);
511 raw_pull_report_progress(i
, RAW_UNPACKING
);
513 r
= raw_pull_maybe_convert_qcow2(i
);
517 raw_pull_report_progress(i
, RAW_FINALIZING
);
519 r
= import_make_read_only_fd(i
->raw_job
->disk_fd
);
523 r
= rename_noreplace(AT_FDCWD
, i
->temp_path
, AT_FDCWD
, i
->final_path
);
525 log_error_errno(r
, "Failed to rename raw file to %s: %m", i
->final_path
);
529 i
->temp_path
= mfree(i
->temp_path
);
531 if (i
->roothash_job
&&
532 i
->roothash_job
->error
== 0) {
533 r
= raw_pull_rename_auxiliary_file(i
, ".roothash", &i
->roothash_temp_path
, &i
->roothash_path
);
538 if (i
->settings_job
&&
539 i
->settings_job
->error
== 0) {
540 r
= raw_pull_rename_auxiliary_file(i
, ".nspawn", &i
->settings_temp_path
, &i
->settings_path
);
546 raw_pull_report_progress(i
, RAW_COPYING
);
548 r
= raw_pull_make_local_copy(i
);
556 i
->on_finished(i
, r
, i
->userdata
);
558 sd_event_exit(i
->event
, r
);
561 static int raw_pull_job_on_open_disk_generic(
575 r
= tempfn_random_child(i
->image_root
, extra
, temp_path
);
580 (void) mkdir_parents_label(*temp_path
, 0700);
582 j
->disk_fd
= open(*temp_path
, O_RDWR
|O_CREAT
|O_EXCL
|O_NOCTTY
|O_CLOEXEC
, 0664);
584 return log_error_errno(errno
, "Failed to create %s: %m", *temp_path
);
589 static int raw_pull_job_on_open_disk_raw(PullJob
*j
) {
597 assert(i
->raw_job
== j
);
599 r
= raw_pull_job_on_open_disk_generic(i
, j
, "raw", &i
->temp_path
);
603 r
= chattr_fd(j
->disk_fd
, FS_NOCOW_FL
, FS_NOCOW_FL
, NULL
);
605 log_warning_errno(r
, "Failed to set file attributes on %s, ignoring: %m", i
->temp_path
);
610 static int raw_pull_job_on_open_disk_roothash(PullJob
*j
) {
617 assert(i
->roothash_job
== j
);
619 return raw_pull_job_on_open_disk_generic(i
, j
, "roothash", &i
->roothash_temp_path
);
622 static int raw_pull_job_on_open_disk_settings(PullJob
*j
) {
629 assert(i
->settings_job
== j
);
631 return raw_pull_job_on_open_disk_generic(i
, j
, "settings", &i
->settings_temp_path
);
634 static void raw_pull_job_on_progress(PullJob
*j
) {
642 raw_pull_report_progress(i
, RAW_DOWNLOADING
);
657 assert(verify
< _IMPORT_VERIFY_MAX
);
660 if (!http_url_is_valid(url
))
663 if (local
&& !machine_name_is_valid(local
))
669 r
= free_and_strdup(&i
->local
, local
);
673 i
->force_local
= force_local
;
675 i
->settings
= settings
;
676 i
->roothash
= roothash
;
678 /* Queue job for the image itself */
679 r
= pull_job_new(&i
->raw_job
, url
, i
->glue
, i
);
683 i
->raw_job
->on_finished
= raw_pull_job_on_finished
;
684 i
->raw_job
->on_open_disk
= raw_pull_job_on_open_disk_raw
;
685 i
->raw_job
->on_progress
= raw_pull_job_on_progress
;
686 i
->raw_job
->calc_checksum
= verify
!= IMPORT_VERIFY_NO
;
688 r
= pull_find_old_etags(url
, i
->image_root
, DT_REG
, ".raw-", ".raw", &i
->raw_job
->old_etags
);
693 r
= pull_make_auxiliary_job(&i
->roothash_job
, url
, raw_strip_suffixes
, ".roothash", i
->glue
, raw_pull_job_on_finished
, i
);
697 i
->roothash_job
->on_open_disk
= raw_pull_job_on_open_disk_roothash
;
698 i
->roothash_job
->on_progress
= raw_pull_job_on_progress
;
699 i
->roothash_job
->calc_checksum
= verify
!= IMPORT_VERIFY_NO
;
703 r
= pull_make_auxiliary_job(&i
->settings_job
, url
, raw_strip_suffixes
, ".nspawn", i
->glue
, raw_pull_job_on_finished
, i
);
707 i
->settings_job
->on_open_disk
= raw_pull_job_on_open_disk_settings
;
708 i
->settings_job
->on_progress
= raw_pull_job_on_progress
;
709 i
->settings_job
->calc_checksum
= verify
!= IMPORT_VERIFY_NO
;
712 r
= pull_make_verification_jobs(&i
->checksum_job
, &i
->signature_job
, verify
, url
, i
->glue
, raw_pull_job_on_finished
, i
);
716 r
= pull_job_begin(i
->raw_job
);
720 if (i
->roothash_job
) {
721 r
= pull_job_begin(i
->roothash_job
);
726 if (i
->settings_job
) {
727 r
= pull_job_begin(i
->settings_job
);
732 if (i
->checksum_job
) {
733 i
->checksum_job
->on_progress
= raw_pull_job_on_progress
;
734 i
->checksum_job
->style
= VERIFICATION_PER_FILE
;
736 r
= pull_job_begin(i
->checksum_job
);
741 if (i
->signature_job
) {
742 i
->signature_job
->on_progress
= raw_pull_job_on_progress
;
744 r
= pull_job_begin(i
->signature_job
);