1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2014 Lennart Poettering
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
21 #include <curl/curl.h>
23 #include <sys/xattr.h>
25 #include "sd-daemon.h"
27 #include "alloc-util.h"
28 #include "btrfs-util.h"
29 #include "chattr-util.h"
31 #include "curl-util.h"
35 #include "hostname-util.h"
36 #include "import-common.h"
37 #include "import-util.h"
40 #include "path-util.h"
41 #include "pull-common.h"
44 #include "qcow2-util.h"
46 #include "string-util.h"
52 typedef enum RawProgress
{
67 PullJob
*roothash_job
;
68 PullJob
*settings_job
;
69 PullJob
*checksum_job
;
70 PullJob
*signature_job
;
72 RawPullFinished on_finished
;
77 bool grow_machine_directory
;
85 char *settings_temp_path
;
88 char *roothash_temp_path
;
93 RawPull
* raw_pull_unref(RawPull
*i
) {
97 pull_job_unref(i
->raw_job
);
98 pull_job_unref(i
->settings_job
);
99 pull_job_unref(i
->roothash_job
);
100 pull_job_unref(i
->checksum_job
);
101 pull_job_unref(i
->signature_job
);
103 curl_glue_unref(i
->glue
);
104 sd_event_unref(i
->event
);
107 (void) unlink(i
->temp_path
);
111 if (i
->roothash_temp_path
) {
112 (void) unlink(i
->roothash_temp_path
);
113 free(i
->roothash_temp_path
);
116 if (i
->settings_temp_path
) {
117 (void) unlink(i
->settings_temp_path
);
118 free(i
->settings_temp_path
);
122 free(i
->roothash_path
);
123 free(i
->settings_path
);
132 const char *image_root
,
133 RawPullFinished on_finished
,
136 _cleanup_(raw_pull_unrefp
) RawPull
*i
= NULL
;
141 i
= new0(RawPull
, 1);
145 i
->on_finished
= on_finished
;
146 i
->userdata
= userdata
;
148 i
->image_root
= strdup(image_root
?: "/var/lib/machines");
152 i
->grow_machine_directory
= path_startswith(i
->image_root
, "/var/lib/machines");
155 i
->event
= sd_event_ref(event
);
157 r
= sd_event_default(&i
->event
);
162 r
= curl_glue_new(&i
->glue
, i
->event
);
166 i
->glue
->on_finished
= pull_job_curl_on_finished
;
167 i
->glue
->userdata
= i
;
174 static void raw_pull_report_progress(RawPull
*i
, RawProgress p
) {
181 case RAW_DOWNLOADING
: {
182 unsigned remain
= 80;
186 if (i
->settings_job
) {
187 percent
+= i
->settings_job
->progress_percent
* 5 / 100;
191 if (i
->roothash_job
) {
192 percent
+= i
->roothash_job
->progress_percent
* 5 / 100;
196 if (i
->checksum_job
) {
197 percent
+= i
->checksum_job
->progress_percent
* 5 / 100;
201 if (i
->signature_job
) {
202 percent
+= i
->signature_job
->progress_percent
* 5 / 100;
207 percent
+= i
->raw_job
->progress_percent
* remain
/ 100;
228 assert_not_reached("Unknown progress state");
231 sd_notifyf(false, "X_IMPORT_PROGRESS=%u", percent
);
232 log_debug("Combined progress %u%%", percent
);
235 static int raw_pull_maybe_convert_qcow2(RawPull
*i
) {
236 _cleanup_close_
int converted_fd
= -1;
237 _cleanup_free_
char *t
= NULL
;
243 r
= qcow2_detect(i
->raw_job
->disk_fd
);
245 return log_error_errno(r
, "Failed to detect whether this is a QCOW2 image: %m");
249 /* This is a QCOW2 image, let's convert it */
250 r
= tempfn_random(i
->final_path
, NULL
, &t
);
254 converted_fd
= open(t
, O_RDWR
|O_CREAT
|O_EXCL
|O_NOCTTY
|O_CLOEXEC
, 0664);
255 if (converted_fd
< 0)
256 return log_error_errno(errno
, "Failed to create %s: %m", t
);
258 r
= chattr_fd(converted_fd
, FS_NOCOW_FL
, FS_NOCOW_FL
);
260 log_warning_errno(r
, "Failed to set file attributes on %s: %m", t
);
262 log_info("Unpacking QCOW2 file.");
264 r
= qcow2_convert(i
->raw_job
->disk_fd
, converted_fd
);
267 return log_error_errno(r
, "Failed to convert qcow2 image: %m");
270 (void) unlink(i
->temp_path
);
271 free_and_replace(i
->temp_path
, t
);
273 safe_close(i
->raw_job
->disk_fd
);
274 i
->raw_job
->disk_fd
= TAKE_FD(converted_fd
);
279 static int raw_pull_determine_path(RawPull
*i
, const char *suffix
, char **field
) {
290 r
= pull_make_path(i
->raw_job
->url
, i
->raw_job
->etag
, i
->image_root
, ".raw-", suffix
, field
);
297 static int raw_pull_copy_auxiliary_file(
309 r
= raw_pull_determine_path(i
, suffix
, path
);
313 local
= strjoina(i
->image_root
, "/", i
->local
, suffix
);
315 r
= copy_file_atomic(*path
, local
, 0644, 0, COPY_REFLINK
| (i
->force_local
? COPY_REPLACE
: 0));
317 log_warning_errno(r
, "File %s already exists, not replacing.", local
);
318 else if (r
== -ENOENT
)
319 log_debug_errno(r
, "Skipping creation of auxiliary file, since none was found.");
321 log_warning_errno(r
, "Failed to copy file %s, ignoring: %m", local
);
323 log_info("Created new file %s.", local
);
328 static int raw_pull_make_local_copy(RawPull
*i
) {
329 _cleanup_free_
char *tp
= NULL
;
330 _cleanup_close_
int dfd
= -1;
340 if (i
->raw_job
->etag_exists
) {
341 /* We have downloaded this one previously, reopen it */
343 assert(i
->raw_job
->disk_fd
< 0);
345 i
->raw_job
->disk_fd
= open(i
->final_path
, O_RDONLY
|O_NOCTTY
|O_CLOEXEC
);
346 if (i
->raw_job
->disk_fd
< 0)
347 return log_error_errno(errno
, "Failed to open vendor image: %m");
349 /* We freshly downloaded the image, use it */
351 assert(i
->raw_job
->disk_fd
>= 0);
353 if (lseek(i
->raw_job
->disk_fd
, SEEK_SET
, 0) == (off_t
) -1)
354 return log_error_errno(errno
, "Failed to seek to beginning of vendor image: %m");
357 p
= strjoina(i
->image_root
, "/", i
->local
, ".raw");
360 (void) rm_rf(p
, REMOVE_ROOT
|REMOVE_PHYSICAL
|REMOVE_SUBVOLUME
);
362 r
= tempfn_random(p
, NULL
, &tp
);
366 dfd
= open(tp
, O_WRONLY
|O_CREAT
|O_EXCL
|O_NOCTTY
|O_CLOEXEC
, 0664);
368 return log_error_errno(errno
, "Failed to create writable copy of image: %m");
370 /* Turn off COW writing. This should greatly improve
371 * performance on COW file systems like btrfs, since it
372 * reduces fragmentation caused by not allowing in-place
374 r
= chattr_fd(dfd
, FS_NOCOW_FL
, FS_NOCOW_FL
);
376 log_warning_errno(r
, "Failed to set file attributes on %s: %m", tp
);
378 r
= copy_bytes(i
->raw_job
->disk_fd
, dfd
, (uint64_t) -1, COPY_REFLINK
);
381 return log_error_errno(r
, "Failed to make writable copy of image: %m");
384 (void) copy_times(i
->raw_job
->disk_fd
, dfd
);
385 (void) copy_xattr(i
->raw_job
->disk_fd
, dfd
);
387 dfd
= safe_close(dfd
);
391 r
= log_error_errno(errno
, "Failed to move writable image into place: %m");
396 log_info("Created new local image '%s'.", i
->local
);
399 r
= raw_pull_copy_auxiliary_file(i
, ".roothash", &i
->roothash_path
);
405 r
= raw_pull_copy_auxiliary_file(i
, ".nspawn", &i
->settings_path
);
413 static bool raw_pull_is_done(RawPull
*i
) {
417 if (!PULL_JOB_IS_COMPLETE(i
->raw_job
))
419 if (i
->roothash_job
&& !PULL_JOB_IS_COMPLETE(i
->roothash_job
))
421 if (i
->settings_job
&& !PULL_JOB_IS_COMPLETE(i
->settings_job
))
423 if (i
->checksum_job
&& !PULL_JOB_IS_COMPLETE(i
->checksum_job
))
425 if (i
->signature_job
&& !PULL_JOB_IS_COMPLETE(i
->signature_job
))
431 static int raw_pull_rename_auxiliary_file(
444 /* Regenerate final name for this auxiliary file, we might know the etag of the file now, and we should
445 * incorporate it in the file name if we can */
446 *path
= mfree(*path
);
447 r
= raw_pull_determine_path(i
, suffix
, path
);
451 r
= import_make_read_only(*temp_path
);
455 r
= rename_noreplace(AT_FDCWD
, *temp_path
, AT_FDCWD
, *path
);
457 return log_error_errno(r
, "Failed to rename file %s to %s: %m", *temp_path
, *path
);
459 *temp_path
= mfree(*temp_path
);
464 static void raw_pull_job_on_finished(PullJob
*j
) {
472 if (j
== i
->roothash_job
) {
474 log_info_errno(j
->error
, "Root hash file could not be retrieved, proceeding without.");
475 } else if (j
== i
->settings_job
) {
477 log_info_errno(j
->error
, "Settings file could not be retrieved, proceeding without.");
478 } else if (j
->error
!= 0 && j
!= i
->signature_job
) {
479 if (j
== i
->checksum_job
)
480 log_error_errno(j
->error
, "Failed to retrieve SHA256 checksum, cannot verify. (Try --verify=no?)");
482 log_error_errno(j
->error
, "Failed to retrieve image file. (Wrong URL?)");
488 /* This is invoked if either the download completed
489 * successfully, or the download was skipped because we
490 * already have the etag. In this case ->etag_exists is
493 * We only do something when we got all three files */
495 if (!raw_pull_is_done(i
))
498 if (i
->signature_job
&& i
->checksum_job
->style
== VERIFICATION_PER_DIRECTORY
&& i
->signature_job
->error
!= 0) {
499 log_error_errno(j
->error
, "Failed to retrieve signature file, cannot verify. (Try --verify=no?)");
501 r
= i
->signature_job
->error
;
506 i
->roothash_job
->disk_fd
= safe_close(i
->roothash_job
->disk_fd
);
508 i
->settings_job
->disk_fd
= safe_close(i
->settings_job
->disk_fd
);
510 r
= raw_pull_determine_path(i
, ".raw", &i
->final_path
);
514 if (!i
->raw_job
->etag_exists
) {
515 /* This is a new download, verify it, and move it into place */
516 assert(i
->raw_job
->disk_fd
>= 0);
518 raw_pull_report_progress(i
, RAW_VERIFYING
);
520 r
= pull_verify(i
->raw_job
, i
->roothash_job
, i
->settings_job
, i
->checksum_job
, i
->signature_job
);
524 raw_pull_report_progress(i
, RAW_UNPACKING
);
526 r
= raw_pull_maybe_convert_qcow2(i
);
530 raw_pull_report_progress(i
, RAW_FINALIZING
);
532 r
= import_make_read_only_fd(i
->raw_job
->disk_fd
);
536 r
= rename_noreplace(AT_FDCWD
, i
->temp_path
, AT_FDCWD
, i
->final_path
);
538 log_error_errno(r
, "Failed to rename raw file to %s: %m", i
->final_path
);
542 i
->temp_path
= mfree(i
->temp_path
);
544 if (i
->roothash_job
&&
545 i
->roothash_job
->error
== 0) {
546 r
= raw_pull_rename_auxiliary_file(i
, ".roothash", &i
->roothash_temp_path
, &i
->roothash_path
);
551 if (i
->settings_job
&&
552 i
->settings_job
->error
== 0) {
553 r
= raw_pull_rename_auxiliary_file(i
, ".nspawn", &i
->settings_temp_path
, &i
->settings_path
);
559 raw_pull_report_progress(i
, RAW_COPYING
);
561 r
= raw_pull_make_local_copy(i
);
569 i
->on_finished(i
, r
, i
->userdata
);
571 sd_event_exit(i
->event
, r
);
574 static int raw_pull_job_on_open_disk_generic(
588 r
= tempfn_random_child(i
->image_root
, extra
, temp_path
);
593 (void) mkdir_parents_label(*temp_path
, 0700);
595 j
->disk_fd
= open(*temp_path
, O_RDWR
|O_CREAT
|O_EXCL
|O_NOCTTY
|O_CLOEXEC
, 0664);
597 return log_error_errno(errno
, "Failed to create %s: %m", *temp_path
);
602 static int raw_pull_job_on_open_disk_raw(PullJob
*j
) {
610 assert(i
->raw_job
== j
);
612 r
= raw_pull_job_on_open_disk_generic(i
, j
, "raw", &i
->temp_path
);
616 r
= chattr_fd(j
->disk_fd
, FS_NOCOW_FL
, FS_NOCOW_FL
);
618 log_warning_errno(r
, "Failed to set file attributes on %s, ignoring: %m", i
->temp_path
);
623 static int raw_pull_job_on_open_disk_roothash(PullJob
*j
) {
630 assert(i
->roothash_job
== j
);
632 return raw_pull_job_on_open_disk_generic(i
, j
, "roothash", &i
->roothash_temp_path
);
635 static int raw_pull_job_on_open_disk_settings(PullJob
*j
) {
642 assert(i
->settings_job
== j
);
644 return raw_pull_job_on_open_disk_generic(i
, j
, "settings", &i
->settings_temp_path
);
647 static void raw_pull_job_on_progress(PullJob
*j
) {
655 raw_pull_report_progress(i
, RAW_DOWNLOADING
);
670 assert(verify
< _IMPORT_VERIFY_MAX
);
673 if (!http_url_is_valid(url
))
676 if (local
&& !machine_name_is_valid(local
))
682 r
= free_and_strdup(&i
->local
, local
);
686 i
->force_local
= force_local
;
688 i
->settings
= settings
;
689 i
->roothash
= roothash
;
691 /* Queue job for the image itself */
692 r
= pull_job_new(&i
->raw_job
, url
, i
->glue
, i
);
696 i
->raw_job
->on_finished
= raw_pull_job_on_finished
;
697 i
->raw_job
->on_open_disk
= raw_pull_job_on_open_disk_raw
;
698 i
->raw_job
->on_progress
= raw_pull_job_on_progress
;
699 i
->raw_job
->calc_checksum
= verify
!= IMPORT_VERIFY_NO
;
700 i
->raw_job
->grow_machine_directory
= i
->grow_machine_directory
;
702 r
= pull_find_old_etags(url
, i
->image_root
, DT_REG
, ".raw-", ".raw", &i
->raw_job
->old_etags
);
707 r
= pull_make_auxiliary_job(&i
->roothash_job
, url
, raw_strip_suffixes
, ".roothash", i
->glue
, raw_pull_job_on_finished
, i
);
711 i
->roothash_job
->on_open_disk
= raw_pull_job_on_open_disk_roothash
;
712 i
->roothash_job
->on_progress
= raw_pull_job_on_progress
;
713 i
->roothash_job
->calc_checksum
= verify
!= IMPORT_VERIFY_NO
;
717 r
= pull_make_auxiliary_job(&i
->settings_job
, url
, raw_strip_suffixes
, ".nspawn", i
->glue
, raw_pull_job_on_finished
, i
);
721 i
->settings_job
->on_open_disk
= raw_pull_job_on_open_disk_settings
;
722 i
->settings_job
->on_progress
= raw_pull_job_on_progress
;
723 i
->settings_job
->calc_checksum
= verify
!= IMPORT_VERIFY_NO
;
726 r
= pull_make_verification_jobs(&i
->checksum_job
, &i
->signature_job
, verify
, url
, i
->glue
, raw_pull_job_on_finished
, i
);
730 r
= pull_job_begin(i
->raw_job
);
734 if (i
->roothash_job
) {
735 r
= pull_job_begin(i
->roothash_job
);
740 if (i
->settings_job
) {
741 r
= pull_job_begin(i
->settings_job
);
746 if (i
->checksum_job
) {
747 i
->checksum_job
->on_progress
= raw_pull_job_on_progress
;
748 i
->checksum_job
->style
= VERIFICATION_PER_FILE
;
750 r
= pull_job_begin(i
->checksum_job
);
755 if (i
->signature_job
) {
756 i
->signature_job
->on_progress
= raw_pull_job_on_progress
;
758 r
= pull_job_begin(i
->signature_job
);