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"
31 #include "hostname-util.h"
32 #include "import-common.h"
33 #include "import-util.h"
36 #include "path-util.h"
37 #include "pull-common.h"
39 #include "qcow2-util.h"
41 #include "string-util.h"
47 typedef enum RawProgress
{
62 PullJob
*settings_job
;
63 PullJob
*checksum_job
;
64 PullJob
*signature_job
;
66 RawPullFinished on_finished
;
71 bool grow_machine_directory
;
78 char *settings_temp_path
;
83 RawPull
* raw_pull_unref(RawPull
*i
) {
87 pull_job_unref(i
->raw_job
);
88 pull_job_unref(i
->settings_job
);
89 pull_job_unref(i
->checksum_job
);
90 pull_job_unref(i
->signature_job
);
92 curl_glue_unref(i
->glue
);
93 sd_event_unref(i
->event
);
96 (void) unlink(i
->temp_path
);
100 if (i
->settings_temp_path
) {
101 (void) unlink(i
->settings_temp_path
);
102 free(i
->settings_temp_path
);
106 free(i
->settings_path
);
117 const char *image_root
,
118 RawPullFinished on_finished
,
121 _cleanup_(raw_pull_unrefp
) RawPull
*i
= NULL
;
126 i
= new0(RawPull
, 1);
130 i
->on_finished
= on_finished
;
131 i
->userdata
= userdata
;
133 i
->image_root
= strdup(image_root
?: "/var/lib/machines");
137 i
->grow_machine_directory
= path_startswith(i
->image_root
, "/var/lib/machines");
140 i
->event
= sd_event_ref(event
);
142 r
= sd_event_default(&i
->event
);
147 r
= curl_glue_new(&i
->glue
, i
->event
);
151 i
->glue
->on_finished
= pull_job_curl_on_finished
;
152 i
->glue
->userdata
= i
;
160 static void raw_pull_report_progress(RawPull
*i
, RawProgress p
) {
167 case RAW_DOWNLOADING
: {
168 unsigned remain
= 80;
172 if (i
->settings_job
) {
173 percent
+= i
->settings_job
->progress_percent
* 5 / 100;
177 if (i
->checksum_job
) {
178 percent
+= i
->checksum_job
->progress_percent
* 5 / 100;
182 if (i
->signature_job
) {
183 percent
+= i
->signature_job
->progress_percent
* 5 / 100;
188 percent
+= i
->raw_job
->progress_percent
* remain
/ 100;
209 assert_not_reached("Unknown progress state");
212 sd_notifyf(false, "X_IMPORT_PROGRESS=%u", percent
);
213 log_debug("Combined progress %u%%", percent
);
216 static int raw_pull_maybe_convert_qcow2(RawPull
*i
) {
217 _cleanup_close_
int converted_fd
= -1;
218 _cleanup_free_
char *t
= NULL
;
224 r
= qcow2_detect(i
->raw_job
->disk_fd
);
226 return log_error_errno(r
, "Failed to detect whether this is a QCOW2 image: %m");
230 /* This is a QCOW2 image, let's convert it */
231 r
= tempfn_random(i
->final_path
, NULL
, &t
);
235 converted_fd
= open(t
, O_RDWR
|O_CREAT
|O_EXCL
|O_NOCTTY
|O_CLOEXEC
, 0664);
236 if (converted_fd
< 0)
237 return log_error_errno(errno
, "Failed to create %s: %m", t
);
239 r
= chattr_fd(converted_fd
, FS_NOCOW_FL
, FS_NOCOW_FL
);
241 log_warning_errno(errno
, "Failed to set file attributes on %s: %m", t
);
243 log_info("Unpacking QCOW2 file.");
245 r
= qcow2_convert(i
->raw_job
->disk_fd
, converted_fd
);
248 return log_error_errno(r
, "Failed to convert qcow2 image: %m");
251 (void) unlink(i
->temp_path
);
256 safe_close(i
->raw_job
->disk_fd
);
257 i
->raw_job
->disk_fd
= converted_fd
;
263 static int raw_pull_make_local_copy(RawPull
*i
) {
264 _cleanup_free_
char *tp
= NULL
;
265 _cleanup_close_
int dfd
= -1;
275 if (!i
->final_path
) {
276 r
= pull_make_path(i
->raw_job
->url
, i
->raw_job
->etag
, i
->image_root
, ".raw-", ".raw", &i
->final_path
);
281 if (i
->raw_job
->etag_exists
) {
282 /* We have downloaded this one previously, reopen it */
284 assert(i
->raw_job
->disk_fd
< 0);
286 i
->raw_job
->disk_fd
= open(i
->final_path
, O_RDONLY
|O_NOCTTY
|O_CLOEXEC
);
287 if (i
->raw_job
->disk_fd
< 0)
288 return log_error_errno(errno
, "Failed to open vendor image: %m");
290 /* We freshly downloaded the image, use it */
292 assert(i
->raw_job
->disk_fd
>= 0);
294 if (lseek(i
->raw_job
->disk_fd
, SEEK_SET
, 0) == (off_t
) -1)
295 return log_error_errno(errno
, "Failed to seek to beginning of vendor image: %m");
298 p
= strjoina(i
->image_root
, "/", i
->local
, ".raw");
301 (void) rm_rf(p
, REMOVE_ROOT
|REMOVE_PHYSICAL
|REMOVE_SUBVOLUME
);
303 r
= tempfn_random(p
, NULL
, &tp
);
307 dfd
= open(tp
, O_WRONLY
|O_CREAT
|O_EXCL
|O_NOCTTY
|O_CLOEXEC
, 0664);
309 return log_error_errno(errno
, "Failed to create writable copy of image: %m");
311 /* Turn off COW writing. This should greatly improve
312 * performance on COW file systems like btrfs, since it
313 * reduces fragmentation caused by not allowing in-place
315 r
= chattr_fd(dfd
, FS_NOCOW_FL
, FS_NOCOW_FL
);
317 log_warning_errno(errno
, "Failed to set file attributes on %s: %m", tp
);
319 r
= copy_bytes(i
->raw_job
->disk_fd
, dfd
, (uint64_t) -1, true);
322 return log_error_errno(r
, "Failed to make writable copy of image: %m");
325 (void) copy_times(i
->raw_job
->disk_fd
, dfd
);
326 (void) copy_xattr(i
->raw_job
->disk_fd
, dfd
);
328 dfd
= safe_close(dfd
);
333 return log_error_errno(errno
, "Failed to move writable image into place: %m");
336 log_info("Created new local image '%s'.", i
->local
);
339 const char *local_settings
;
340 assert(i
->settings_job
);
342 if (!i
->settings_path
) {
343 r
= pull_make_path(i
->settings_job
->url
, i
->settings_job
->etag
, i
->image_root
, ".settings-", NULL
, &i
->settings_path
);
348 local_settings
= strjoina(i
->image_root
, "/", i
->local
, ".nspawn");
350 r
= copy_file_atomic(i
->settings_path
, local_settings
, 0644, i
->force_local
, 0);
352 log_warning_errno(r
, "Settings file %s already exists, not replacing.", local_settings
);
353 else if (r
< 0 && r
!= -ENOENT
)
354 log_warning_errno(r
, "Failed to copy settings files %s, ignoring: %m", local_settings
);
356 log_info("Created new settings file '%s.nspawn'", i
->local
);
362 static bool raw_pull_is_done(RawPull
*i
) {
366 if (!PULL_JOB_IS_COMPLETE(i
->raw_job
))
368 if (i
->settings_job
&& !PULL_JOB_IS_COMPLETE(i
->settings_job
))
370 if (i
->checksum_job
&& !PULL_JOB_IS_COMPLETE(i
->checksum_job
))
372 if (i
->signature_job
&& !PULL_JOB_IS_COMPLETE(i
->signature_job
))
378 static void raw_pull_job_on_finished(PullJob
*j
) {
386 if (j
== i
->settings_job
) {
388 log_info_errno(j
->error
, "Settings file could not be retrieved, proceeding without.");
389 } else if (j
->error
!= 0) {
390 if (j
== i
->checksum_job
)
391 log_error_errno(j
->error
, "Failed to retrieve SHA256 checksum, cannot verify. (Try --verify=no?)");
392 else if (j
== i
->signature_job
)
393 log_error_errno(j
->error
, "Failed to retrieve signature file, cannot verify. (Try --verify=no?)");
395 log_error_errno(j
->error
, "Failed to retrieve image file. (Wrong URL?)");
401 /* This is invoked if either the download completed
402 * successfully, or the download was skipped because we
403 * already have the etag. In this case ->etag_exists is
406 * We only do something when we got all three files */
408 if (!raw_pull_is_done(i
))
412 i
->settings_job
->disk_fd
= safe_close(i
->settings_job
->disk_fd
);
414 if (!i
->raw_job
->etag_exists
) {
415 /* This is a new download, verify it, and move it into place */
416 assert(i
->raw_job
->disk_fd
>= 0);
418 raw_pull_report_progress(i
, RAW_VERIFYING
);
420 r
= pull_verify(i
->raw_job
, i
->settings_job
, i
->checksum_job
, i
->signature_job
);
424 raw_pull_report_progress(i
, RAW_UNPACKING
);
426 r
= raw_pull_maybe_convert_qcow2(i
);
430 raw_pull_report_progress(i
, RAW_FINALIZING
);
432 r
= import_make_read_only_fd(i
->raw_job
->disk_fd
);
436 r
= rename_noreplace(AT_FDCWD
, i
->temp_path
, AT_FDCWD
, i
->final_path
);
438 log_error_errno(r
, "Failed to move RAW file into place: %m");
442 i
->temp_path
= mfree(i
->temp_path
);
444 if (i
->settings_job
&&
445 i
->settings_job
->error
== 0 &&
446 !i
->settings_job
->etag_exists
) {
448 assert(i
->settings_temp_path
);
449 assert(i
->settings_path
);
451 r
= import_make_read_only(i
->settings_temp_path
);
455 r
= rename_noreplace(AT_FDCWD
, i
->settings_temp_path
, AT_FDCWD
, i
->settings_path
);
457 log_error_errno(r
, "Failed to rename settings file: %m");
461 i
->settings_temp_path
= mfree(i
->settings_temp_path
);
465 raw_pull_report_progress(i
, RAW_COPYING
);
467 r
= raw_pull_make_local_copy(i
);
475 i
->on_finished(i
, r
, i
->userdata
);
477 sd_event_exit(i
->event
, r
);
480 static int raw_pull_job_on_open_disk_raw(PullJob
*j
) {
488 assert(i
->raw_job
== j
);
489 assert(!i
->final_path
);
490 assert(!i
->temp_path
);
492 r
= pull_make_path(j
->url
, j
->etag
, i
->image_root
, ".raw-", ".raw", &i
->final_path
);
496 r
= tempfn_random(i
->final_path
, NULL
, &i
->temp_path
);
500 (void) mkdir_parents_label(i
->temp_path
, 0700);
502 j
->disk_fd
= open(i
->temp_path
, O_RDWR
|O_CREAT
|O_EXCL
|O_NOCTTY
|O_CLOEXEC
, 0664);
504 return log_error_errno(errno
, "Failed to create %s: %m", i
->temp_path
);
506 r
= chattr_fd(j
->disk_fd
, FS_NOCOW_FL
, FS_NOCOW_FL
);
508 log_warning_errno(errno
, "Failed to set file attributes on %s: %m", i
->temp_path
);
513 static int raw_pull_job_on_open_disk_settings(PullJob
*j
) {
521 assert(i
->settings_job
== j
);
522 assert(!i
->settings_path
);
523 assert(!i
->settings_temp_path
);
525 r
= pull_make_path(j
->url
, j
->etag
, i
->image_root
, ".settings-", NULL
, &i
->settings_path
);
529 r
= tempfn_random(i
->settings_path
, NULL
, &i
->settings_temp_path
);
533 mkdir_parents_label(i
->settings_temp_path
, 0700);
535 j
->disk_fd
= open(i
->settings_temp_path
, O_RDWR
|O_CREAT
|O_EXCL
|O_NOCTTY
|O_CLOEXEC
, 0664);
537 return log_error_errno(errno
, "Failed to create %s: %m", i
->settings_temp_path
);
542 static void raw_pull_job_on_progress(PullJob
*j
) {
550 raw_pull_report_progress(i
, RAW_DOWNLOADING
);
564 assert(verify
< _IMPORT_VERIFY_MAX
);
567 if (!http_url_is_valid(url
))
570 if (local
&& !machine_name_is_valid(local
))
576 r
= free_and_strdup(&i
->local
, local
);
580 i
->force_local
= force_local
;
582 i
->settings
= settings
;
584 /* Queue job for the image itself */
585 r
= pull_job_new(&i
->raw_job
, url
, i
->glue
, i
);
589 i
->raw_job
->on_finished
= raw_pull_job_on_finished
;
590 i
->raw_job
->on_open_disk
= raw_pull_job_on_open_disk_raw
;
591 i
->raw_job
->on_progress
= raw_pull_job_on_progress
;
592 i
->raw_job
->calc_checksum
= verify
!= IMPORT_VERIFY_NO
;
593 i
->raw_job
->grow_machine_directory
= i
->grow_machine_directory
;
595 r
= pull_find_old_etags(url
, i
->image_root
, DT_REG
, ".raw-", ".raw", &i
->raw_job
->old_etags
);
600 r
= pull_make_settings_job(&i
->settings_job
, url
, i
->glue
, raw_pull_job_on_finished
, i
);
604 i
->settings_job
->on_open_disk
= raw_pull_job_on_open_disk_settings
;
605 i
->settings_job
->on_progress
= raw_pull_job_on_progress
;
606 i
->settings_job
->calc_checksum
= verify
!= IMPORT_VERIFY_NO
;
608 r
= pull_find_old_etags(i
->settings_job
->url
, i
->image_root
, DT_REG
, ".settings-", NULL
, &i
->settings_job
->old_etags
);
613 r
= pull_make_verification_jobs(&i
->checksum_job
, &i
->signature_job
, verify
, url
, i
->glue
, raw_pull_job_on_finished
, i
);
617 r
= pull_job_begin(i
->raw_job
);
621 if (i
->settings_job
) {
622 r
= pull_job_begin(i
->settings_job
);
627 if (i
->checksum_job
) {
628 i
->checksum_job
->on_progress
= raw_pull_job_on_progress
;
630 r
= pull_job_begin(i
->checksum_job
);
635 if (i
->signature_job
) {
636 i
->signature_job
->on_progress
= raw_pull_job_on_progress
;
638 r
= pull_job_begin(i
->signature_job
);