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 "alloc-util.h"
29 #include "btrfs-util.h"
30 #include "chattr-util.h"
32 #include "curl-util.h"
36 #include "hostname-util.h"
37 #include "import-common.h"
38 #include "import-util.h"
41 #include "path-util.h"
42 #include "pull-common.h"
45 #include "qcow2-util.h"
47 #include "string-util.h"
53 typedef enum RawProgress
{
68 PullJob
*settings_job
;
69 PullJob
*checksum_job
;
70 PullJob
*signature_job
;
72 RawPullFinished on_finished
;
77 bool grow_machine_directory
;
84 char *settings_temp_path
;
89 RawPull
* raw_pull_unref(RawPull
*i
) {
93 pull_job_unref(i
->raw_job
);
94 pull_job_unref(i
->settings_job
);
95 pull_job_unref(i
->checksum_job
);
96 pull_job_unref(i
->signature_job
);
98 curl_glue_unref(i
->glue
);
99 sd_event_unref(i
->event
);
102 (void) unlink(i
->temp_path
);
106 if (i
->settings_temp_path
) {
107 (void) unlink(i
->settings_temp_path
);
108 free(i
->settings_temp_path
);
112 free(i
->settings_path
);
123 const char *image_root
,
124 RawPullFinished on_finished
,
127 _cleanup_(raw_pull_unrefp
) RawPull
*i
= NULL
;
132 i
= new0(RawPull
, 1);
136 i
->on_finished
= on_finished
;
137 i
->userdata
= userdata
;
139 i
->image_root
= strdup(image_root
?: "/var/lib/machines");
143 i
->grow_machine_directory
= path_startswith(i
->image_root
, "/var/lib/machines");
146 i
->event
= sd_event_ref(event
);
148 r
= sd_event_default(&i
->event
);
153 r
= curl_glue_new(&i
->glue
, i
->event
);
157 i
->glue
->on_finished
= pull_job_curl_on_finished
;
158 i
->glue
->userdata
= i
;
166 static void raw_pull_report_progress(RawPull
*i
, RawProgress p
) {
173 case RAW_DOWNLOADING
: {
174 unsigned remain
= 80;
178 if (i
->settings_job
) {
179 percent
+= i
->settings_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
);
247 log_warning_errno(errno
, "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
);
262 safe_close(i
->raw_job
->disk_fd
);
263 i
->raw_job
->disk_fd
= converted_fd
;
269 static int raw_pull_make_local_copy(RawPull
*i
) {
270 _cleanup_free_
char *tp
= NULL
;
271 _cleanup_close_
int dfd
= -1;
281 if (!i
->final_path
) {
282 r
= pull_make_path(i
->raw_job
->url
, i
->raw_job
->etag
, i
->image_root
, ".raw-", ".raw", &i
->final_path
);
287 if (i
->raw_job
->etag_exists
) {
288 /* We have downloaded this one previously, reopen it */
290 assert(i
->raw_job
->disk_fd
< 0);
292 i
->raw_job
->disk_fd
= open(i
->final_path
, O_RDONLY
|O_NOCTTY
|O_CLOEXEC
);
293 if (i
->raw_job
->disk_fd
< 0)
294 return log_error_errno(errno
, "Failed to open vendor image: %m");
296 /* We freshly downloaded the image, use it */
298 assert(i
->raw_job
->disk_fd
>= 0);
300 if (lseek(i
->raw_job
->disk_fd
, SEEK_SET
, 0) == (off_t
) -1)
301 return log_error_errno(errno
, "Failed to seek to beginning of vendor image: %m");
304 p
= strjoina(i
->image_root
, "/", i
->local
, ".raw");
307 (void) rm_rf(p
, REMOVE_ROOT
|REMOVE_PHYSICAL
|REMOVE_SUBVOLUME
);
309 r
= tempfn_random(p
, NULL
, &tp
);
313 dfd
= open(tp
, O_WRONLY
|O_CREAT
|O_EXCL
|O_NOCTTY
|O_CLOEXEC
, 0664);
315 return log_error_errno(errno
, "Failed to create writable copy of image: %m");
317 /* Turn off COW writing. This should greatly improve
318 * performance on COW file systems like btrfs, since it
319 * reduces fragmentation caused by not allowing in-place
321 r
= chattr_fd(dfd
, FS_NOCOW_FL
, FS_NOCOW_FL
);
323 log_warning_errno(errno
, "Failed to set file attributes on %s: %m", tp
);
325 r
= copy_bytes(i
->raw_job
->disk_fd
, dfd
, (uint64_t) -1, true);
328 return log_error_errno(r
, "Failed to make writable copy of image: %m");
331 (void) copy_times(i
->raw_job
->disk_fd
, dfd
);
332 (void) copy_xattr(i
->raw_job
->disk_fd
, dfd
);
334 dfd
= safe_close(dfd
);
339 return log_error_errno(errno
, "Failed to move writable image into place: %m");
342 log_info("Created new local image '%s'.", i
->local
);
345 const char *local_settings
;
346 assert(i
->settings_job
);
348 if (!i
->settings_path
) {
349 r
= pull_make_path(i
->settings_job
->url
, i
->settings_job
->etag
, i
->image_root
, ".settings-", NULL
, &i
->settings_path
);
354 local_settings
= strjoina(i
->image_root
, "/", i
->local
, ".nspawn");
356 r
= copy_file_atomic(i
->settings_path
, local_settings
, 0644, i
->force_local
, 0);
358 log_warning_errno(r
, "Settings file %s already exists, not replacing.", local_settings
);
359 else if (r
< 0 && r
!= -ENOENT
)
360 log_warning_errno(r
, "Failed to copy settings files %s, ignoring: %m", local_settings
);
362 log_info("Created new settings file '%s.nspawn'", i
->local
);
368 static bool raw_pull_is_done(RawPull
*i
) {
372 if (!PULL_JOB_IS_COMPLETE(i
->raw_job
))
374 if (i
->settings_job
&& !PULL_JOB_IS_COMPLETE(i
->settings_job
))
376 if (i
->checksum_job
&& !PULL_JOB_IS_COMPLETE(i
->checksum_job
))
378 if (i
->signature_job
&& !PULL_JOB_IS_COMPLETE(i
->signature_job
))
384 static void raw_pull_job_on_finished(PullJob
*j
) {
392 if (j
== i
->settings_job
) {
394 log_info_errno(j
->error
, "Settings file could not be retrieved, proceeding without.");
395 } else if (j
->error
!= 0) {
396 if (j
== i
->checksum_job
)
397 log_error_errno(j
->error
, "Failed to retrieve SHA256 checksum, cannot verify. (Try --verify=no?)");
398 else if (j
== i
->signature_job
)
399 log_error_errno(j
->error
, "Failed to retrieve signature file, cannot verify. (Try --verify=no?)");
401 log_error_errno(j
->error
, "Failed to retrieve image file. (Wrong URL?)");
407 /* This is invoked if either the download completed
408 * successfully, or the download was skipped because we
409 * already have the etag. In this case ->etag_exists is
412 * We only do something when we got all three files */
414 if (!raw_pull_is_done(i
))
418 i
->settings_job
->disk_fd
= safe_close(i
->settings_job
->disk_fd
);
420 if (!i
->raw_job
->etag_exists
) {
421 /* This is a new download, verify it, and move it into place */
422 assert(i
->raw_job
->disk_fd
>= 0);
424 raw_pull_report_progress(i
, RAW_VERIFYING
);
426 r
= pull_verify(i
->raw_job
, i
->settings_job
, i
->checksum_job
, i
->signature_job
);
430 raw_pull_report_progress(i
, RAW_UNPACKING
);
432 r
= raw_pull_maybe_convert_qcow2(i
);
436 raw_pull_report_progress(i
, RAW_FINALIZING
);
438 r
= import_make_read_only_fd(i
->raw_job
->disk_fd
);
442 r
= rename_noreplace(AT_FDCWD
, i
->temp_path
, AT_FDCWD
, i
->final_path
);
444 log_error_errno(r
, "Failed to move RAW file into place: %m");
448 i
->temp_path
= mfree(i
->temp_path
);
450 if (i
->settings_job
&&
451 i
->settings_job
->error
== 0 &&
452 !i
->settings_job
->etag_exists
) {
454 assert(i
->settings_temp_path
);
455 assert(i
->settings_path
);
457 r
= import_make_read_only(i
->settings_temp_path
);
461 r
= rename_noreplace(AT_FDCWD
, i
->settings_temp_path
, AT_FDCWD
, i
->settings_path
);
463 log_error_errno(r
, "Failed to rename settings file: %m");
467 i
->settings_temp_path
= mfree(i
->settings_temp_path
);
471 raw_pull_report_progress(i
, RAW_COPYING
);
473 r
= raw_pull_make_local_copy(i
);
481 i
->on_finished(i
, r
, i
->userdata
);
483 sd_event_exit(i
->event
, r
);
486 static int raw_pull_job_on_open_disk_raw(PullJob
*j
) {
494 assert(i
->raw_job
== j
);
495 assert(!i
->final_path
);
496 assert(!i
->temp_path
);
498 r
= pull_make_path(j
->url
, j
->etag
, i
->image_root
, ".raw-", ".raw", &i
->final_path
);
502 r
= tempfn_random(i
->final_path
, NULL
, &i
->temp_path
);
506 (void) mkdir_parents_label(i
->temp_path
, 0700);
508 j
->disk_fd
= open(i
->temp_path
, O_RDWR
|O_CREAT
|O_EXCL
|O_NOCTTY
|O_CLOEXEC
, 0664);
510 return log_error_errno(errno
, "Failed to create %s: %m", i
->temp_path
);
512 r
= chattr_fd(j
->disk_fd
, FS_NOCOW_FL
, FS_NOCOW_FL
);
514 log_warning_errno(errno
, "Failed to set file attributes on %s: %m", i
->temp_path
);
519 static int raw_pull_job_on_open_disk_settings(PullJob
*j
) {
527 assert(i
->settings_job
== j
);
528 assert(!i
->settings_path
);
529 assert(!i
->settings_temp_path
);
531 r
= pull_make_path(j
->url
, j
->etag
, i
->image_root
, ".settings-", NULL
, &i
->settings_path
);
535 r
= tempfn_random(i
->settings_path
, NULL
, &i
->settings_temp_path
);
539 mkdir_parents_label(i
->settings_temp_path
, 0700);
541 j
->disk_fd
= open(i
->settings_temp_path
, O_RDWR
|O_CREAT
|O_EXCL
|O_NOCTTY
|O_CLOEXEC
, 0664);
543 return log_error_errno(errno
, "Failed to create %s: %m", i
->settings_temp_path
);
548 static void raw_pull_job_on_progress(PullJob
*j
) {
556 raw_pull_report_progress(i
, RAW_DOWNLOADING
);
570 assert(verify
< _IMPORT_VERIFY_MAX
);
573 if (!http_url_is_valid(url
))
576 if (local
&& !machine_name_is_valid(local
))
582 r
= free_and_strdup(&i
->local
, local
);
586 i
->force_local
= force_local
;
588 i
->settings
= settings
;
590 /* Queue job for the image itself */
591 r
= pull_job_new(&i
->raw_job
, url
, i
->glue
, i
);
595 i
->raw_job
->on_finished
= raw_pull_job_on_finished
;
596 i
->raw_job
->on_open_disk
= raw_pull_job_on_open_disk_raw
;
597 i
->raw_job
->on_progress
= raw_pull_job_on_progress
;
598 i
->raw_job
->calc_checksum
= verify
!= IMPORT_VERIFY_NO
;
599 i
->raw_job
->grow_machine_directory
= i
->grow_machine_directory
;
601 r
= pull_find_old_etags(url
, i
->image_root
, DT_REG
, ".raw-", ".raw", &i
->raw_job
->old_etags
);
606 r
= pull_make_settings_job(&i
->settings_job
, url
, i
->glue
, raw_pull_job_on_finished
, i
);
610 i
->settings_job
->on_open_disk
= raw_pull_job_on_open_disk_settings
;
611 i
->settings_job
->on_progress
= raw_pull_job_on_progress
;
612 i
->settings_job
->calc_checksum
= verify
!= IMPORT_VERIFY_NO
;
614 r
= pull_find_old_etags(i
->settings_job
->url
, i
->image_root
, DT_REG
, ".settings-", NULL
, &i
->settings_job
->old_etags
);
619 r
= pull_make_verification_jobs(&i
->checksum_job
, &i
->signature_job
, verify
, url
, i
->glue
, raw_pull_job_on_finished
, i
);
623 r
= pull_job_begin(i
->raw_job
);
627 if (i
->settings_job
) {
628 r
= pull_job_begin(i
->settings_job
);
633 if (i
->checksum_job
) {
634 i
->checksum_job
->on_progress
= raw_pull_job_on_progress
;
636 r
= pull_job_begin(i
->checksum_job
);
641 if (i
->signature_job
) {
642 i
->signature_job
->on_progress
= raw_pull_job_on_progress
;
644 r
= pull_job_begin(i
->signature_job
);