1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2014 Lennart Poettering
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
22 #include <curl/curl.h>
24 #include <sys/xattr.h>
26 #include "sd-daemon.h"
28 #include "btrfs-util.h"
30 #include "curl-util.h"
32 #include "hostname-util.h"
33 #include "import-common.h"
34 #include "import-util.h"
37 #include "path-util.h"
38 #include "pull-common.h"
41 #include "qcow2-util.h"
43 #include "string-util.h"
48 typedef enum RawProgress
{
63 PullJob
*settings_job
;
64 PullJob
*checksum_job
;
65 PullJob
*signature_job
;
67 RawPullFinished on_finished
;
72 bool grow_machine_directory
;
79 char *settings_temp_path
;
84 RawPull
* raw_pull_unref(RawPull
*i
) {
88 pull_job_unref(i
->raw_job
);
89 pull_job_unref(i
->settings_job
);
90 pull_job_unref(i
->checksum_job
);
91 pull_job_unref(i
->signature_job
);
93 curl_glue_unref(i
->glue
);
94 sd_event_unref(i
->event
);
97 (void) unlink(i
->temp_path
);
101 if (i
->settings_temp_path
) {
102 (void) unlink(i
->settings_temp_path
);
103 free(i
->settings_temp_path
);
107 free(i
->settings_path
);
118 const char *image_root
,
119 RawPullFinished on_finished
,
122 _cleanup_(raw_pull_unrefp
) RawPull
*i
= NULL
;
127 i
= new0(RawPull
, 1);
131 i
->on_finished
= on_finished
;
132 i
->userdata
= userdata
;
134 i
->image_root
= strdup(image_root
?: "/var/lib/machines");
138 i
->grow_machine_directory
= path_startswith(i
->image_root
, "/var/lib/machines");
141 i
->event
= sd_event_ref(event
);
143 r
= sd_event_default(&i
->event
);
148 r
= curl_glue_new(&i
->glue
, i
->event
);
152 i
->glue
->on_finished
= pull_job_curl_on_finished
;
153 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
->checksum_job
) {
179 percent
+= i
->checksum_job
->progress_percent
* 5 / 100;
183 if (i
->signature_job
) {
184 percent
+= i
->signature_job
->progress_percent
* 5 / 100;
189 percent
+= i
->raw_job
->progress_percent
* remain
/ 100;
210 assert_not_reached("Unknown progress state");
213 sd_notifyf(false, "X_IMPORT_PROGRESS=%u", percent
);
214 log_debug("Combined progress %u%%", percent
);
217 static int raw_pull_maybe_convert_qcow2(RawPull
*i
) {
218 _cleanup_close_
int converted_fd
= -1;
219 _cleanup_free_
char *t
= NULL
;
225 r
= qcow2_detect(i
->raw_job
->disk_fd
);
227 return log_error_errno(r
, "Failed to detect whether this is a QCOW2 image: %m");
231 /* This is a QCOW2 image, let's convert it */
232 r
= tempfn_random(i
->final_path
, NULL
, &t
);
236 converted_fd
= open(t
, O_RDWR
|O_CREAT
|O_EXCL
|O_NOCTTY
|O_CLOEXEC
, 0664);
237 if (converted_fd
< 0)
238 return log_error_errno(errno
, "Failed to create %s: %m", t
);
240 r
= chattr_fd(converted_fd
, FS_NOCOW_FL
, FS_NOCOW_FL
);
242 log_warning_errno(errno
, "Failed to set file attributes on %s: %m", t
);
244 log_info("Unpacking QCOW2 file.");
246 r
= qcow2_convert(i
->raw_job
->disk_fd
, converted_fd
);
249 return log_error_errno(r
, "Failed to convert qcow2 image: %m");
252 (void) unlink(i
->temp_path
);
257 safe_close(i
->raw_job
->disk_fd
);
258 i
->raw_job
->disk_fd
= converted_fd
;
264 static int raw_pull_make_local_copy(RawPull
*i
) {
265 _cleanup_free_
char *tp
= NULL
;
266 _cleanup_close_
int dfd
= -1;
276 if (!i
->final_path
) {
277 r
= pull_make_path(i
->raw_job
->url
, i
->raw_job
->etag
, i
->image_root
, ".raw-", ".raw", &i
->final_path
);
282 if (i
->raw_job
->etag_exists
) {
283 /* We have downloaded this one previously, reopen it */
285 assert(i
->raw_job
->disk_fd
< 0);
287 i
->raw_job
->disk_fd
= open(i
->final_path
, O_RDONLY
|O_NOCTTY
|O_CLOEXEC
);
288 if (i
->raw_job
->disk_fd
< 0)
289 return log_error_errno(errno
, "Failed to open vendor image: %m");
291 /* We freshly downloaded the image, use it */
293 assert(i
->raw_job
->disk_fd
>= 0);
295 if (lseek(i
->raw_job
->disk_fd
, SEEK_SET
, 0) == (off_t
) -1)
296 return log_error_errno(errno
, "Failed to seek to beginning of vendor image: %m");
299 p
= strjoina(i
->image_root
, "/", i
->local
, ".raw");
302 (void) rm_rf(p
, REMOVE_ROOT
|REMOVE_PHYSICAL
|REMOVE_SUBVOLUME
);
304 r
= tempfn_random(p
, NULL
, &tp
);
308 dfd
= open(tp
, O_WRONLY
|O_CREAT
|O_EXCL
|O_NOCTTY
|O_CLOEXEC
, 0664);
310 return log_error_errno(errno
, "Failed to create writable copy of image: %m");
312 /* Turn off COW writing. This should greatly improve
313 * performance on COW file systems like btrfs, since it
314 * reduces fragmentation caused by not allowing in-place
316 r
= chattr_fd(dfd
, FS_NOCOW_FL
, FS_NOCOW_FL
);
318 log_warning_errno(errno
, "Failed to set file attributes on %s: %m", tp
);
320 r
= copy_bytes(i
->raw_job
->disk_fd
, dfd
, (uint64_t) -1, true);
323 return log_error_errno(r
, "Failed to make writable copy of image: %m");
326 (void) copy_times(i
->raw_job
->disk_fd
, dfd
);
327 (void) copy_xattr(i
->raw_job
->disk_fd
, dfd
);
329 dfd
= safe_close(dfd
);
334 return log_error_errno(errno
, "Failed to move writable image into place: %m");
337 log_info("Created new local image '%s'.", i
->local
);
340 const char *local_settings
;
341 assert(i
->settings_job
);
343 if (!i
->settings_path
) {
344 r
= pull_make_path(i
->settings_job
->url
, i
->settings_job
->etag
, i
->image_root
, ".settings-", NULL
, &i
->settings_path
);
349 local_settings
= strjoina(i
->image_root
, "/", i
->local
, ".nspawn");
351 r
= copy_file_atomic(i
->settings_path
, local_settings
, 0644, i
->force_local
, 0);
353 log_warning_errno(r
, "Settings file %s already exists, not replacing.", local_settings
);
354 else if (r
< 0 && r
!= -ENOENT
)
355 log_warning_errno(r
, "Failed to copy settings files %s, ignoring: %m", local_settings
);
357 log_info("Created new settings file '%s.nspawn'", i
->local
);
363 static bool raw_pull_is_done(RawPull
*i
) {
367 if (!PULL_JOB_IS_COMPLETE(i
->raw_job
))
369 if (i
->settings_job
&& !PULL_JOB_IS_COMPLETE(i
->settings_job
))
371 if (i
->checksum_job
&& !PULL_JOB_IS_COMPLETE(i
->checksum_job
))
373 if (i
->signature_job
&& !PULL_JOB_IS_COMPLETE(i
->signature_job
))
379 static void raw_pull_job_on_finished(PullJob
*j
) {
387 if (j
== i
->settings_job
) {
389 log_info_errno(j
->error
, "Settings file could not be retrieved, proceeding without.");
390 } else if (j
->error
!= 0) {
391 if (j
== i
->checksum_job
)
392 log_error_errno(j
->error
, "Failed to retrieve SHA256 checksum, cannot verify. (Try --verify=no?)");
393 else if (j
== i
->signature_job
)
394 log_error_errno(j
->error
, "Failed to retrieve signature file, cannot verify. (Try --verify=no?)");
396 log_error_errno(j
->error
, "Failed to retrieve image file. (Wrong URL?)");
402 /* This is invoked if either the download completed
403 * successfully, or the download was skipped because we
404 * already have the etag. In this case ->etag_exists is
407 * We only do something when we got all three files */
409 if (!raw_pull_is_done(i
))
413 i
->settings_job
->disk_fd
= safe_close(i
->settings_job
->disk_fd
);
415 if (!i
->raw_job
->etag_exists
) {
416 /* This is a new download, verify it, and move it into place */
417 assert(i
->raw_job
->disk_fd
>= 0);
419 raw_pull_report_progress(i
, RAW_VERIFYING
);
421 r
= pull_verify(i
->raw_job
, i
->settings_job
, i
->checksum_job
, i
->signature_job
);
425 raw_pull_report_progress(i
, RAW_UNPACKING
);
427 r
= raw_pull_maybe_convert_qcow2(i
);
431 raw_pull_report_progress(i
, RAW_FINALIZING
);
433 r
= import_make_read_only_fd(i
->raw_job
->disk_fd
);
437 r
= rename_noreplace(AT_FDCWD
, i
->temp_path
, AT_FDCWD
, i
->final_path
);
439 log_error_errno(r
, "Failed to move RAW file into place: %m");
443 i
->temp_path
= mfree(i
->temp_path
);
445 if (i
->settings_job
&&
446 i
->settings_job
->error
== 0 &&
447 !i
->settings_job
->etag_exists
) {
449 assert(i
->settings_temp_path
);
450 assert(i
->settings_path
);
452 r
= import_make_read_only(i
->settings_temp_path
);
456 r
= rename_noreplace(AT_FDCWD
, i
->settings_temp_path
, AT_FDCWD
, i
->settings_path
);
458 log_error_errno(r
, "Failed to rename settings file: %m");
462 i
->settings_temp_path
= mfree(i
->settings_temp_path
);
466 raw_pull_report_progress(i
, RAW_COPYING
);
468 r
= raw_pull_make_local_copy(i
);
476 i
->on_finished(i
, r
, i
->userdata
);
478 sd_event_exit(i
->event
, r
);
481 static int raw_pull_job_on_open_disk_raw(PullJob
*j
) {
489 assert(i
->raw_job
== j
);
490 assert(!i
->final_path
);
491 assert(!i
->temp_path
);
493 r
= pull_make_path(j
->url
, j
->etag
, i
->image_root
, ".raw-", ".raw", &i
->final_path
);
497 r
= tempfn_random(i
->final_path
, NULL
, &i
->temp_path
);
501 (void) mkdir_parents_label(i
->temp_path
, 0700);
503 j
->disk_fd
= open(i
->temp_path
, O_RDWR
|O_CREAT
|O_EXCL
|O_NOCTTY
|O_CLOEXEC
, 0664);
505 return log_error_errno(errno
, "Failed to create %s: %m", i
->temp_path
);
507 r
= chattr_fd(j
->disk_fd
, FS_NOCOW_FL
, FS_NOCOW_FL
);
509 log_warning_errno(errno
, "Failed to set file attributes on %s: %m", i
->temp_path
);
514 static int raw_pull_job_on_open_disk_settings(PullJob
*j
) {
522 assert(i
->settings_job
== j
);
523 assert(!i
->settings_path
);
524 assert(!i
->settings_temp_path
);
526 r
= pull_make_path(j
->url
, j
->etag
, i
->image_root
, ".settings-", NULL
, &i
->settings_path
);
530 r
= tempfn_random(i
->settings_path
, NULL
, &i
->settings_temp_path
);
534 mkdir_parents_label(i
->settings_temp_path
, 0700);
536 j
->disk_fd
= open(i
->settings_temp_path
, O_RDWR
|O_CREAT
|O_EXCL
|O_NOCTTY
|O_CLOEXEC
, 0664);
538 return log_error_errno(errno
, "Failed to create %s: %m", i
->settings_temp_path
);
543 static void raw_pull_job_on_progress(PullJob
*j
) {
551 raw_pull_report_progress(i
, RAW_DOWNLOADING
);
565 assert(verify
< _IMPORT_VERIFY_MAX
);
568 if (!http_url_is_valid(url
))
571 if (local
&& !machine_name_is_valid(local
))
577 r
= free_and_strdup(&i
->local
, local
);
581 i
->force_local
= force_local
;
583 i
->settings
= settings
;
585 /* Queue job for the image itself */
586 r
= pull_job_new(&i
->raw_job
, url
, i
->glue
, i
);
590 i
->raw_job
->on_finished
= raw_pull_job_on_finished
;
591 i
->raw_job
->on_open_disk
= raw_pull_job_on_open_disk_raw
;
592 i
->raw_job
->on_progress
= raw_pull_job_on_progress
;
593 i
->raw_job
->calc_checksum
= verify
!= IMPORT_VERIFY_NO
;
594 i
->raw_job
->grow_machine_directory
= i
->grow_machine_directory
;
596 r
= pull_find_old_etags(url
, i
->image_root
, DT_REG
, ".raw-", ".raw", &i
->raw_job
->old_etags
);
601 r
= pull_make_settings_job(&i
->settings_job
, url
, i
->glue
, raw_pull_job_on_finished
, i
);
605 i
->settings_job
->on_open_disk
= raw_pull_job_on_open_disk_settings
;
606 i
->settings_job
->on_progress
= raw_pull_job_on_progress
;
607 i
->settings_job
->calc_checksum
= verify
!= IMPORT_VERIFY_NO
;
609 r
= pull_find_old_etags(i
->settings_job
->url
, i
->image_root
, DT_REG
, ".settings-", NULL
, &i
->settings_job
->old_etags
);
614 r
= pull_make_verification_jobs(&i
->checksum_job
, &i
->signature_job
, verify
, url
, i
->glue
, raw_pull_job_on_finished
, i
);
618 r
= pull_job_begin(i
->raw_job
);
622 if (i
->settings_job
) {
623 r
= pull_job_begin(i
->settings_job
);
628 if (i
->checksum_job
) {
629 i
->checksum_job
->on_progress
= raw_pull_job_on_progress
;
631 r
= pull_job_begin(i
->checksum_job
);
636 if (i
->signature_job
) {
637 i
->signature_job
->on_progress
= raw_pull_job_on_progress
;
639 r
= pull_job_begin(i
->signature_job
);