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
);
121 const char *image_root
,
122 RawPullFinished on_finished
,
125 _cleanup_(raw_pull_unrefp
) RawPull
*i
= NULL
;
130 i
= new0(RawPull
, 1);
134 i
->on_finished
= on_finished
;
135 i
->userdata
= userdata
;
137 i
->image_root
= strdup(image_root
?: "/var/lib/machines");
141 i
->grow_machine_directory
= path_startswith(i
->image_root
, "/var/lib/machines");
144 i
->event
= sd_event_ref(event
);
146 r
= sd_event_default(&i
->event
);
151 r
= curl_glue_new(&i
->glue
, i
->event
);
155 i
->glue
->on_finished
= pull_job_curl_on_finished
;
156 i
->glue
->userdata
= i
;
164 static void raw_pull_report_progress(RawPull
*i
, RawProgress p
) {
171 case RAW_DOWNLOADING
: {
172 unsigned remain
= 80;
176 if (i
->settings_job
) {
177 percent
+= i
->settings_job
->progress_percent
* 5 / 100;
181 if (i
->checksum_job
) {
182 percent
+= i
->checksum_job
->progress_percent
* 5 / 100;
186 if (i
->signature_job
) {
187 percent
+= i
->signature_job
->progress_percent
* 5 / 100;
192 percent
+= i
->raw_job
->progress_percent
* remain
/ 100;
213 assert_not_reached("Unknown progress state");
216 sd_notifyf(false, "X_IMPORT_PROGRESS=%u", percent
);
217 log_debug("Combined progress %u%%", percent
);
220 static int raw_pull_maybe_convert_qcow2(RawPull
*i
) {
221 _cleanup_close_
int converted_fd
= -1;
222 _cleanup_free_
char *t
= NULL
;
228 r
= qcow2_detect(i
->raw_job
->disk_fd
);
230 return log_error_errno(r
, "Failed to detect whether this is a QCOW2 image: %m");
234 /* This is a QCOW2 image, let's convert it */
235 r
= tempfn_random(i
->final_path
, NULL
, &t
);
239 converted_fd
= open(t
, O_RDWR
|O_CREAT
|O_EXCL
|O_NOCTTY
|O_CLOEXEC
, 0664);
240 if (converted_fd
< 0)
241 return log_error_errno(errno
, "Failed to create %s: %m", t
);
243 r
= chattr_fd(converted_fd
, FS_NOCOW_FL
, FS_NOCOW_FL
);
245 log_warning_errno(r
, "Failed to set file attributes on %s: %m", t
);
247 log_info("Unpacking QCOW2 file.");
249 r
= qcow2_convert(i
->raw_job
->disk_fd
, converted_fd
);
252 return log_error_errno(r
, "Failed to convert qcow2 image: %m");
255 (void) unlink(i
->temp_path
);
260 safe_close(i
->raw_job
->disk_fd
);
261 i
->raw_job
->disk_fd
= converted_fd
;
267 static int raw_pull_make_local_copy(RawPull
*i
) {
268 _cleanup_free_
char *tp
= NULL
;
269 _cleanup_close_
int dfd
= -1;
279 if (!i
->final_path
) {
280 r
= pull_make_path(i
->raw_job
->url
, i
->raw_job
->etag
, i
->image_root
, ".raw-", ".raw", &i
->final_path
);
285 if (i
->raw_job
->etag_exists
) {
286 /* We have downloaded this one previously, reopen it */
288 assert(i
->raw_job
->disk_fd
< 0);
290 i
->raw_job
->disk_fd
= open(i
->final_path
, O_RDONLY
|O_NOCTTY
|O_CLOEXEC
);
291 if (i
->raw_job
->disk_fd
< 0)
292 return log_error_errno(errno
, "Failed to open vendor image: %m");
294 /* We freshly downloaded the image, use it */
296 assert(i
->raw_job
->disk_fd
>= 0);
298 if (lseek(i
->raw_job
->disk_fd
, SEEK_SET
, 0) == (off_t
) -1)
299 return log_error_errno(errno
, "Failed to seek to beginning of vendor image: %m");
302 p
= strjoina(i
->image_root
, "/", i
->local
, ".raw");
305 (void) rm_rf(p
, REMOVE_ROOT
|REMOVE_PHYSICAL
|REMOVE_SUBVOLUME
);
307 r
= tempfn_random(p
, NULL
, &tp
);
311 dfd
= open(tp
, O_WRONLY
|O_CREAT
|O_EXCL
|O_NOCTTY
|O_CLOEXEC
, 0664);
313 return log_error_errno(errno
, "Failed to create writable copy of image: %m");
315 /* Turn off COW writing. This should greatly improve
316 * performance on COW file systems like btrfs, since it
317 * reduces fragmentation caused by not allowing in-place
319 r
= chattr_fd(dfd
, FS_NOCOW_FL
, FS_NOCOW_FL
);
321 log_warning_errno(r
, "Failed to set file attributes on %s: %m", tp
);
323 r
= copy_bytes(i
->raw_job
->disk_fd
, dfd
, (uint64_t) -1, true);
326 return log_error_errno(r
, "Failed to make writable copy of image: %m");
329 (void) copy_times(i
->raw_job
->disk_fd
, dfd
);
330 (void) copy_xattr(i
->raw_job
->disk_fd
, dfd
);
332 dfd
= safe_close(dfd
);
336 r
= log_error_errno(errno
, "Failed to move writable image into place: %m");
341 log_info("Created new local image '%s'.", i
->local
);
344 const char *local_settings
;
345 assert(i
->settings_job
);
347 if (!i
->settings_path
) {
348 r
= pull_make_path(i
->settings_job
->url
, i
->settings_job
->etag
, i
->image_root
, ".settings-", NULL
, &i
->settings_path
);
353 local_settings
= strjoina(i
->image_root
, "/", i
->local
, ".nspawn");
355 r
= copy_file_atomic(i
->settings_path
, local_settings
, 0644, i
->force_local
, 0);
357 log_warning_errno(r
, "Settings file %s already exists, not replacing.", local_settings
);
358 else if (r
< 0 && r
!= -ENOENT
)
359 log_warning_errno(r
, "Failed to copy settings files %s, ignoring: %m", local_settings
);
361 log_info("Created new settings file '%s.nspawn'", i
->local
);
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
);