2 This file is part of systemd.
4 Copyright 2014 Lennart Poettering
6 systemd is free software; you can redistribute it and/or modify it
7 under the terms of the GNU Lesser General Public License as published by
8 the Free Software Foundation; either version 2.1 of the License, or
9 (at your option) any later version.
11 systemd is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
16 You should have received a copy of the GNU Lesser General Public License
17 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 #include <curl/curl.h>
22 #include <sys/xattr.h>
24 #include "sd-daemon.h"
26 #include "alloc-util.h"
27 #include "btrfs-util.h"
28 #include "chattr-util.h"
30 #include "curl-util.h"
34 #include "hostname-util.h"
35 #include "import-common.h"
36 #include "import-util.h"
39 #include "path-util.h"
40 #include "pull-common.h"
43 #include "qcow2-util.h"
45 #include "string-util.h"
51 typedef enum RawProgress
{
66 PullJob
*settings_job
;
67 PullJob
*checksum_job
;
68 PullJob
*signature_job
;
70 RawPullFinished on_finished
;
75 bool grow_machine_directory
;
82 char *settings_temp_path
;
87 RawPull
* raw_pull_unref(RawPull
*i
) {
91 pull_job_unref(i
->raw_job
);
92 pull_job_unref(i
->settings_job
);
93 pull_job_unref(i
->checksum_job
);
94 pull_job_unref(i
->signature_job
);
96 curl_glue_unref(i
->glue
);
97 sd_event_unref(i
->event
);
100 (void) unlink(i
->temp_path
);
104 if (i
->settings_temp_path
) {
105 (void) unlink(i
->settings_temp_path
);
106 free(i
->settings_temp_path
);
110 free(i
->settings_path
);
119 const char *image_root
,
120 RawPullFinished on_finished
,
123 _cleanup_(raw_pull_unrefp
) RawPull
*i
= NULL
;
128 i
= new0(RawPull
, 1);
132 i
->on_finished
= on_finished
;
133 i
->userdata
= userdata
;
135 i
->image_root
= strdup(image_root
?: "/var/lib/machines");
139 i
->grow_machine_directory
= path_startswith(i
->image_root
, "/var/lib/machines");
142 i
->event
= sd_event_ref(event
);
144 r
= sd_event_default(&i
->event
);
149 r
= curl_glue_new(&i
->glue
, i
->event
);
153 i
->glue
->on_finished
= pull_job_curl_on_finished
;
154 i
->glue
->userdata
= i
;
162 static void raw_pull_report_progress(RawPull
*i
, RawProgress p
) {
169 case RAW_DOWNLOADING
: {
170 unsigned remain
= 80;
174 if (i
->settings_job
) {
175 percent
+= i
->settings_job
->progress_percent
* 5 / 100;
179 if (i
->checksum_job
) {
180 percent
+= i
->checksum_job
->progress_percent
* 5 / 100;
184 if (i
->signature_job
) {
185 percent
+= i
->signature_job
->progress_percent
* 5 / 100;
190 percent
+= i
->raw_job
->progress_percent
* remain
/ 100;
211 assert_not_reached("Unknown progress state");
214 sd_notifyf(false, "X_IMPORT_PROGRESS=%u", percent
);
215 log_debug("Combined progress %u%%", percent
);
218 static int raw_pull_maybe_convert_qcow2(RawPull
*i
) {
219 _cleanup_close_
int converted_fd
= -1;
220 _cleanup_free_
char *t
= NULL
;
226 r
= qcow2_detect(i
->raw_job
->disk_fd
);
228 return log_error_errno(r
, "Failed to detect whether this is a QCOW2 image: %m");
232 /* This is a QCOW2 image, let's convert it */
233 r
= tempfn_random(i
->final_path
, NULL
, &t
);
237 converted_fd
= open(t
, O_RDWR
|O_CREAT
|O_EXCL
|O_NOCTTY
|O_CLOEXEC
, 0664);
238 if (converted_fd
< 0)
239 return log_error_errno(errno
, "Failed to create %s: %m", t
);
241 r
= chattr_fd(converted_fd
, FS_NOCOW_FL
, FS_NOCOW_FL
);
243 log_warning_errno(r
, "Failed to set file attributes on %s: %m", t
);
245 log_info("Unpacking QCOW2 file.");
247 r
= qcow2_convert(i
->raw_job
->disk_fd
, converted_fd
);
250 return log_error_errno(r
, "Failed to convert qcow2 image: %m");
253 (void) unlink(i
->temp_path
);
258 safe_close(i
->raw_job
->disk_fd
);
259 i
->raw_job
->disk_fd
= converted_fd
;
265 static int raw_pull_make_local_copy(RawPull
*i
) {
266 _cleanup_free_
char *tp
= NULL
;
267 _cleanup_close_
int dfd
= -1;
277 if (!i
->final_path
) {
278 r
= pull_make_path(i
->raw_job
->url
, i
->raw_job
->etag
, i
->image_root
, ".raw-", ".raw", &i
->final_path
);
283 if (i
->raw_job
->etag_exists
) {
284 /* We have downloaded this one previously, reopen it */
286 assert(i
->raw_job
->disk_fd
< 0);
288 i
->raw_job
->disk_fd
= open(i
->final_path
, O_RDONLY
|O_NOCTTY
|O_CLOEXEC
);
289 if (i
->raw_job
->disk_fd
< 0)
290 return log_error_errno(errno
, "Failed to open vendor image: %m");
292 /* We freshly downloaded the image, use it */
294 assert(i
->raw_job
->disk_fd
>= 0);
296 if (lseek(i
->raw_job
->disk_fd
, SEEK_SET
, 0) == (off_t
) -1)
297 return log_error_errno(errno
, "Failed to seek to beginning of vendor image: %m");
300 p
= strjoina(i
->image_root
, "/", i
->local
, ".raw");
303 (void) rm_rf(p
, REMOVE_ROOT
|REMOVE_PHYSICAL
|REMOVE_SUBVOLUME
);
305 r
= tempfn_random(p
, NULL
, &tp
);
309 dfd
= open(tp
, O_WRONLY
|O_CREAT
|O_EXCL
|O_NOCTTY
|O_CLOEXEC
, 0664);
311 return log_error_errno(errno
, "Failed to create writable copy of image: %m");
313 /* Turn off COW writing. This should greatly improve
314 * performance on COW file systems like btrfs, since it
315 * reduces fragmentation caused by not allowing in-place
317 r
= chattr_fd(dfd
, FS_NOCOW_FL
, FS_NOCOW_FL
);
319 log_warning_errno(r
, "Failed to set file attributes on %s: %m", tp
);
321 r
= copy_bytes(i
->raw_job
->disk_fd
, dfd
, (uint64_t) -1, true);
324 return log_error_errno(r
, "Failed to make writable copy of image: %m");
327 (void) copy_times(i
->raw_job
->disk_fd
, dfd
);
328 (void) copy_xattr(i
->raw_job
->disk_fd
, dfd
);
330 dfd
= safe_close(dfd
);
334 r
= log_error_errno(errno
, "Failed to move writable image into place: %m");
339 log_info("Created new local image '%s'.", i
->local
);
342 const char *local_settings
;
343 assert(i
->settings_job
);
345 if (!i
->settings_path
) {
346 r
= pull_make_path(i
->settings_job
->url
, i
->settings_job
->etag
, i
->image_root
, ".settings-", NULL
, &i
->settings_path
);
351 local_settings
= strjoina(i
->image_root
, "/", i
->local
, ".nspawn");
353 r
= copy_file_atomic(i
->settings_path
, local_settings
, 0644, i
->force_local
, 0);
355 log_warning_errno(r
, "Settings file %s already exists, not replacing.", local_settings
);
356 else if (r
== -ENOENT
)
357 log_debug_errno(r
, "Skipping creation of settings file, since none was found.");
359 log_warning_errno(r
, "Failed to copy settings files %s, ignoring: %m", local_settings
);
361 log_info("Created new settings file %s.", local_settings
);
367 static bool raw_pull_is_done(RawPull
*i
) {
371 if (!PULL_JOB_IS_COMPLETE(i
->raw_job
))
373 if (i
->settings_job
&& !PULL_JOB_IS_COMPLETE(i
->settings_job
))
375 if (i
->checksum_job
&& !PULL_JOB_IS_COMPLETE(i
->checksum_job
))
377 if (i
->signature_job
&& !PULL_JOB_IS_COMPLETE(i
->signature_job
))
383 static void raw_pull_job_on_finished(PullJob
*j
) {
391 if (j
== i
->settings_job
) {
393 log_info_errno(j
->error
, "Settings file could not be retrieved, proceeding without.");
394 } else if (j
->error
!= 0) {
395 if (j
== i
->checksum_job
)
396 log_error_errno(j
->error
, "Failed to retrieve SHA256 checksum, cannot verify. (Try --verify=no?)");
397 else if (j
== i
->signature_job
)
398 log_error_errno(j
->error
, "Failed to retrieve signature file, cannot verify. (Try --verify=no?)");
400 log_error_errno(j
->error
, "Failed to retrieve image file. (Wrong URL?)");
406 /* This is invoked if either the download completed
407 * successfully, or the download was skipped because we
408 * already have the etag. In this case ->etag_exists is
411 * We only do something when we got all three files */
413 if (!raw_pull_is_done(i
))
417 i
->settings_job
->disk_fd
= safe_close(i
->settings_job
->disk_fd
);
419 if (!i
->raw_job
->etag_exists
) {
420 /* This is a new download, verify it, and move it into place */
421 assert(i
->raw_job
->disk_fd
>= 0);
423 raw_pull_report_progress(i
, RAW_VERIFYING
);
425 r
= pull_verify(i
->raw_job
, i
->settings_job
, i
->checksum_job
, i
->signature_job
);
429 raw_pull_report_progress(i
, RAW_UNPACKING
);
431 r
= raw_pull_maybe_convert_qcow2(i
);
435 raw_pull_report_progress(i
, RAW_FINALIZING
);
437 r
= import_make_read_only_fd(i
->raw_job
->disk_fd
);
441 r
= rename_noreplace(AT_FDCWD
, i
->temp_path
, AT_FDCWD
, i
->final_path
);
443 log_error_errno(r
, "Failed to move RAW file into place: %m");
447 i
->temp_path
= mfree(i
->temp_path
);
449 if (i
->settings_job
&&
450 i
->settings_job
->error
== 0 &&
451 !i
->settings_job
->etag_exists
) {
453 assert(i
->settings_temp_path
);
454 assert(i
->settings_path
);
456 r
= import_make_read_only(i
->settings_temp_path
);
460 r
= rename_noreplace(AT_FDCWD
, i
->settings_temp_path
, AT_FDCWD
, i
->settings_path
);
462 log_error_errno(r
, "Failed to rename settings file: %m");
466 i
->settings_temp_path
= mfree(i
->settings_temp_path
);
470 raw_pull_report_progress(i
, RAW_COPYING
);
472 r
= raw_pull_make_local_copy(i
);
480 i
->on_finished(i
, r
, i
->userdata
);
482 sd_event_exit(i
->event
, r
);
485 static int raw_pull_job_on_open_disk_raw(PullJob
*j
) {
493 assert(i
->raw_job
== j
);
494 assert(!i
->final_path
);
495 assert(!i
->temp_path
);
497 r
= pull_make_path(j
->url
, j
->etag
, i
->image_root
, ".raw-", ".raw", &i
->final_path
);
501 r
= tempfn_random(i
->final_path
, NULL
, &i
->temp_path
);
505 (void) mkdir_parents_label(i
->temp_path
, 0700);
507 j
->disk_fd
= open(i
->temp_path
, O_RDWR
|O_CREAT
|O_EXCL
|O_NOCTTY
|O_CLOEXEC
, 0664);
509 return log_error_errno(errno
, "Failed to create %s: %m", i
->temp_path
);
511 r
= chattr_fd(j
->disk_fd
, FS_NOCOW_FL
, FS_NOCOW_FL
);
513 log_warning_errno(r
, "Failed to set file attributes on %s: %m", i
->temp_path
);
518 static int raw_pull_job_on_open_disk_settings(PullJob
*j
) {
526 assert(i
->settings_job
== j
);
527 assert(!i
->settings_path
);
528 assert(!i
->settings_temp_path
);
530 r
= pull_make_path(j
->url
, j
->etag
, i
->image_root
, ".settings-", NULL
, &i
->settings_path
);
534 r
= tempfn_random(i
->settings_path
, NULL
, &i
->settings_temp_path
);
538 mkdir_parents_label(i
->settings_temp_path
, 0700);
540 j
->disk_fd
= open(i
->settings_temp_path
, O_RDWR
|O_CREAT
|O_EXCL
|O_NOCTTY
|O_CLOEXEC
, 0664);
542 return log_error_errno(errno
, "Failed to create %s: %m", i
->settings_temp_path
);
547 static void raw_pull_job_on_progress(PullJob
*j
) {
555 raw_pull_report_progress(i
, RAW_DOWNLOADING
);
569 assert(verify
< _IMPORT_VERIFY_MAX
);
572 if (!http_url_is_valid(url
))
575 if (local
&& !machine_name_is_valid(local
))
581 r
= free_and_strdup(&i
->local
, local
);
585 i
->force_local
= force_local
;
587 i
->settings
= settings
;
589 /* Queue job for the image itself */
590 r
= pull_job_new(&i
->raw_job
, url
, i
->glue
, i
);
594 i
->raw_job
->on_finished
= raw_pull_job_on_finished
;
595 i
->raw_job
->on_open_disk
= raw_pull_job_on_open_disk_raw
;
596 i
->raw_job
->on_progress
= raw_pull_job_on_progress
;
597 i
->raw_job
->calc_checksum
= verify
!= IMPORT_VERIFY_NO
;
598 i
->raw_job
->grow_machine_directory
= i
->grow_machine_directory
;
600 r
= pull_find_old_etags(url
, i
->image_root
, DT_REG
, ".raw-", ".raw", &i
->raw_job
->old_etags
);
605 r
= pull_make_settings_job(&i
->settings_job
, url
, i
->glue
, raw_pull_job_on_finished
, i
);
609 i
->settings_job
->on_open_disk
= raw_pull_job_on_open_disk_settings
;
610 i
->settings_job
->on_progress
= raw_pull_job_on_progress
;
611 i
->settings_job
->calc_checksum
= verify
!= IMPORT_VERIFY_NO
;
613 r
= pull_find_old_etags(i
->settings_job
->url
, i
->image_root
, DT_REG
, ".settings-", NULL
, &i
->settings_job
->old_etags
);
618 r
= pull_make_verification_jobs(&i
->checksum_job
, &i
->signature_job
, verify
, url
, i
->glue
, raw_pull_job_on_finished
, i
);
622 r
= pull_job_begin(i
->raw_job
);
626 if (i
->settings_job
) {
627 r
= pull_job_begin(i
->settings_job
);
632 if (i
->checksum_job
) {
633 i
->checksum_job
->on_progress
= raw_pull_job_on_progress
;
635 r
= pull_job_begin(i
->checksum_job
);
640 if (i
->signature_job
) {
641 i
->signature_job
->on_progress
= raw_pull_job_on_progress
;
643 r
= pull_job_begin(i
->signature_job
);