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 <sys/xattr.h>
24 #include <curl/curl.h>
26 #include "sd-daemon.h"
30 #include "btrfs-util.h"
35 #include "path-util.h"
36 #include "hostname-util.h"
37 #include "import-util.h"
38 #include "import-common.h"
39 #include "curl-util.h"
40 #include "qcow2-util.h"
42 #include "pull-common.h"
45 typedef enum RawProgress
{
60 PullJob
*settings_job
;
61 PullJob
*checksum_job
;
62 PullJob
*signature_job
;
64 RawPullFinished on_finished
;
69 bool grow_machine_directory
;
76 char *settings_temp_path
;
81 RawPull
* raw_pull_unref(RawPull
*i
) {
85 pull_job_unref(i
->raw_job
);
86 pull_job_unref(i
->settings_job
);
87 pull_job_unref(i
->checksum_job
);
88 pull_job_unref(i
->signature_job
);
90 curl_glue_unref(i
->glue
);
91 sd_event_unref(i
->event
);
94 (void) unlink(i
->temp_path
);
98 if (i
->settings_temp_path
) {
99 (void) unlink(i
->settings_temp_path
);
100 free(i
->settings_temp_path
);
104 free(i
->settings_path
);
115 const char *image_root
,
116 RawPullFinished on_finished
,
119 _cleanup_(raw_pull_unrefp
) RawPull
*i
= NULL
;
124 i
= new0(RawPull
, 1);
128 i
->on_finished
= on_finished
;
129 i
->userdata
= userdata
;
131 i
->image_root
= strdup(image_root
?: "/var/lib/machines");
135 i
->grow_machine_directory
= path_startswith(i
->image_root
, "/var/lib/machines");
138 i
->event
= sd_event_ref(event
);
140 r
= sd_event_default(&i
->event
);
145 r
= curl_glue_new(&i
->glue
, i
->event
);
149 i
->glue
->on_finished
= pull_job_curl_on_finished
;
150 i
->glue
->userdata
= i
;
158 static void raw_pull_report_progress(RawPull
*i
, RawProgress p
) {
165 case RAW_DOWNLOADING
: {
166 unsigned remain
= 80;
170 if (i
->settings_job
) {
171 percent
+= i
->settings_job
->progress_percent
* 5 / 100;
175 if (i
->checksum_job
) {
176 percent
+= i
->checksum_job
->progress_percent
* 5 / 100;
180 if (i
->signature_job
) {
181 percent
+= i
->signature_job
->progress_percent
* 5 / 100;
186 percent
+= i
->raw_job
->progress_percent
* remain
/ 100;
207 assert_not_reached("Unknown progress state");
210 sd_notifyf(false, "X_IMPORT_PROGRESS=%u", percent
);
211 log_debug("Combined progress %u%%", percent
);
214 static int raw_pull_maybe_convert_qcow2(RawPull
*i
) {
215 _cleanup_close_
int converted_fd
= -1;
216 _cleanup_free_
char *t
= NULL
;
222 r
= qcow2_detect(i
->raw_job
->disk_fd
);
224 return log_error_errno(r
, "Failed to detect whether this is a QCOW2 image: %m");
228 /* This is a QCOW2 image, let's convert it */
229 r
= tempfn_random(i
->final_path
, NULL
, &t
);
233 converted_fd
= open(t
, O_RDWR
|O_CREAT
|O_EXCL
|O_NOCTTY
|O_CLOEXEC
, 0664);
234 if (converted_fd
< 0)
235 return log_error_errno(errno
, "Failed to create %s: %m", t
);
237 r
= chattr_fd(converted_fd
, FS_NOCOW_FL
, FS_NOCOW_FL
);
239 log_warning_errno(errno
, "Failed to set file attributes on %s: %m", t
);
241 log_info("Unpacking QCOW2 file.");
243 r
= qcow2_convert(i
->raw_job
->disk_fd
, converted_fd
);
246 return log_error_errno(r
, "Failed to convert qcow2 image: %m");
249 (void) unlink(i
->temp_path
);
254 safe_close(i
->raw_job
->disk_fd
);
255 i
->raw_job
->disk_fd
= converted_fd
;
261 static int raw_pull_make_local_copy(RawPull
*i
) {
262 _cleanup_free_
char *tp
= NULL
;
263 _cleanup_close_
int dfd
= -1;
273 if (!i
->final_path
) {
274 r
= pull_make_path(i
->raw_job
->url
, i
->raw_job
->etag
, i
->image_root
, ".raw-", ".raw", &i
->final_path
);
279 if (i
->raw_job
->etag_exists
) {
280 /* We have downloaded this one previously, reopen it */
282 assert(i
->raw_job
->disk_fd
< 0);
284 i
->raw_job
->disk_fd
= open(i
->final_path
, O_RDONLY
|O_NOCTTY
|O_CLOEXEC
);
285 if (i
->raw_job
->disk_fd
< 0)
286 return log_error_errno(errno
, "Failed to open vendor image: %m");
288 /* We freshly downloaded the image, use it */
290 assert(i
->raw_job
->disk_fd
>= 0);
292 if (lseek(i
->raw_job
->disk_fd
, SEEK_SET
, 0) == (off_t
) -1)
293 return log_error_errno(errno
, "Failed to seek to beginning of vendor image: %m");
296 p
= strjoina(i
->image_root
, "/", i
->local
, ".raw");
299 (void) rm_rf(p
, REMOVE_ROOT
|REMOVE_PHYSICAL
|REMOVE_SUBVOLUME
);
301 r
= tempfn_random(p
, NULL
, &tp
);
305 dfd
= open(tp
, O_WRONLY
|O_CREAT
|O_EXCL
|O_NOCTTY
|O_CLOEXEC
, 0664);
307 return log_error_errno(errno
, "Failed to create writable copy of image: %m");
309 /* Turn off COW writing. This should greatly improve
310 * performance on COW file systems like btrfs, since it
311 * reduces fragmentation caused by not allowing in-place
313 r
= chattr_fd(dfd
, FS_NOCOW_FL
, FS_NOCOW_FL
);
315 log_warning_errno(errno
, "Failed to set file attributes on %s: %m", tp
);
317 r
= copy_bytes(i
->raw_job
->disk_fd
, dfd
, (uint64_t) -1, true);
320 return log_error_errno(r
, "Failed to make writable copy of image: %m");
323 (void) copy_times(i
->raw_job
->disk_fd
, dfd
);
324 (void) copy_xattr(i
->raw_job
->disk_fd
, dfd
);
326 dfd
= safe_close(dfd
);
331 return log_error_errno(errno
, "Failed to move writable image into place: %m");
334 log_info("Created new local image '%s'.", i
->local
);
337 const char *local_settings
;
338 assert(i
->settings_job
);
340 if (!i
->settings_path
) {
341 r
= pull_make_path(i
->settings_job
->url
, i
->settings_job
->etag
, i
->image_root
, ".settings-", NULL
, &i
->settings_path
);
346 local_settings
= strjoina(i
->image_root
, "/", i
->local
, ".nspawn");
348 r
= copy_file_atomic(i
->settings_path
, local_settings
, 0644, i
->force_local
, 0);
350 log_warning_errno(r
, "Settings file %s already exists, not replacing.", local_settings
);
351 else if (r
< 0 && r
!= -ENOENT
)
352 log_warning_errno(r
, "Failed to copy settings files %s, ignoring: %m", local_settings
);
354 log_info("Created new settings file '%s.nspawn'", i
->local
);
360 static bool raw_pull_is_done(RawPull
*i
) {
364 if (!PULL_JOB_IS_COMPLETE(i
->raw_job
))
366 if (i
->settings_job
&& !PULL_JOB_IS_COMPLETE(i
->settings_job
))
368 if (i
->checksum_job
&& !PULL_JOB_IS_COMPLETE(i
->checksum_job
))
370 if (i
->signature_job
&& !PULL_JOB_IS_COMPLETE(i
->signature_job
))
376 static void raw_pull_job_on_finished(PullJob
*j
) {
384 if (j
== i
->settings_job
) {
386 log_info_errno(j
->error
, "Settings file could not be retrieved, proceeding without.");
387 } else if (j
->error
!= 0) {
388 if (j
== i
->checksum_job
)
389 log_error_errno(j
->error
, "Failed to retrieve SHA256 checksum, cannot verify. (Try --verify=no?)");
390 else if (j
== i
->signature_job
)
391 log_error_errno(j
->error
, "Failed to retrieve signature file, cannot verify. (Try --verify=no?)");
393 log_error_errno(j
->error
, "Failed to retrieve image file. (Wrong URL?)");
399 /* This is invoked if either the download completed
400 * successfully, or the download was skipped because we
401 * already have the etag. In this case ->etag_exists is
404 * We only do something when we got all three files */
406 if (!raw_pull_is_done(i
))
410 i
->settings_job
->disk_fd
= safe_close(i
->settings_job
->disk_fd
);
412 if (!i
->raw_job
->etag_exists
) {
413 /* This is a new download, verify it, and move it into place */
414 assert(i
->raw_job
->disk_fd
>= 0);
416 raw_pull_report_progress(i
, RAW_VERIFYING
);
418 r
= pull_verify(i
->raw_job
, i
->settings_job
, i
->checksum_job
, i
->signature_job
);
422 raw_pull_report_progress(i
, RAW_UNPACKING
);
424 r
= raw_pull_maybe_convert_qcow2(i
);
428 raw_pull_report_progress(i
, RAW_FINALIZING
);
430 r
= import_make_read_only_fd(i
->raw_job
->disk_fd
);
434 r
= rename_noreplace(AT_FDCWD
, i
->temp_path
, AT_FDCWD
, i
->final_path
);
436 log_error_errno(r
, "Failed to move RAW file into place: %m");
440 i
->temp_path
= mfree(i
->temp_path
);
442 if (i
->settings_job
&&
443 i
->settings_job
->error
== 0 &&
444 !i
->settings_job
->etag_exists
) {
446 assert(i
->settings_temp_path
);
447 assert(i
->settings_path
);
449 r
= import_make_read_only(i
->settings_temp_path
);
453 r
= rename_noreplace(AT_FDCWD
, i
->settings_temp_path
, AT_FDCWD
, i
->settings_path
);
455 log_error_errno(r
, "Failed to rename settings file: %m");
459 i
->settings_temp_path
= mfree(i
->settings_temp_path
);
463 raw_pull_report_progress(i
, RAW_COPYING
);
465 r
= raw_pull_make_local_copy(i
);
473 i
->on_finished(i
, r
, i
->userdata
);
475 sd_event_exit(i
->event
, r
);
478 static int raw_pull_job_on_open_disk_raw(PullJob
*j
) {
486 assert(i
->raw_job
== j
);
487 assert(!i
->final_path
);
488 assert(!i
->temp_path
);
490 r
= pull_make_path(j
->url
, j
->etag
, i
->image_root
, ".raw-", ".raw", &i
->final_path
);
494 r
= tempfn_random(i
->final_path
, NULL
, &i
->temp_path
);
498 (void) mkdir_parents_label(i
->temp_path
, 0700);
500 j
->disk_fd
= open(i
->temp_path
, O_RDWR
|O_CREAT
|O_EXCL
|O_NOCTTY
|O_CLOEXEC
, 0664);
502 return log_error_errno(errno
, "Failed to create %s: %m", i
->temp_path
);
504 r
= chattr_fd(j
->disk_fd
, FS_NOCOW_FL
, FS_NOCOW_FL
);
506 log_warning_errno(errno
, "Failed to set file attributes on %s: %m", i
->temp_path
);
511 static int raw_pull_job_on_open_disk_settings(PullJob
*j
) {
519 assert(i
->settings_job
== j
);
520 assert(!i
->settings_path
);
521 assert(!i
->settings_temp_path
);
523 r
= pull_make_path(j
->url
, j
->etag
, i
->image_root
, ".settings-", NULL
, &i
->settings_path
);
527 r
= tempfn_random(i
->settings_path
, NULL
, &i
->settings_temp_path
);
531 mkdir_parents_label(i
->settings_temp_path
, 0700);
533 j
->disk_fd
= open(i
->settings_temp_path
, O_RDWR
|O_CREAT
|O_EXCL
|O_NOCTTY
|O_CLOEXEC
, 0664);
535 return log_error_errno(errno
, "Failed to create %s: %m", i
->settings_temp_path
);
540 static void raw_pull_job_on_progress(PullJob
*j
) {
548 raw_pull_report_progress(i
, RAW_DOWNLOADING
);
562 assert(verify
< _IMPORT_VERIFY_MAX
);
565 if (!http_url_is_valid(url
))
568 if (local
&& !machine_name_is_valid(local
))
574 r
= free_and_strdup(&i
->local
, local
);
578 i
->force_local
= force_local
;
580 i
->settings
= settings
;
582 /* Queue job for the image itself */
583 r
= pull_job_new(&i
->raw_job
, url
, i
->glue
, i
);
587 i
->raw_job
->on_finished
= raw_pull_job_on_finished
;
588 i
->raw_job
->on_open_disk
= raw_pull_job_on_open_disk_raw
;
589 i
->raw_job
->on_progress
= raw_pull_job_on_progress
;
590 i
->raw_job
->calc_checksum
= verify
!= IMPORT_VERIFY_NO
;
591 i
->raw_job
->grow_machine_directory
= i
->grow_machine_directory
;
593 r
= pull_find_old_etags(url
, i
->image_root
, DT_REG
, ".raw-", ".raw", &i
->raw_job
->old_etags
);
598 r
= pull_make_settings_job(&i
->settings_job
, url
, i
->glue
, raw_pull_job_on_finished
, i
);
602 i
->settings_job
->on_open_disk
= raw_pull_job_on_open_disk_settings
;
603 i
->settings_job
->on_progress
= raw_pull_job_on_progress
;
604 i
->settings_job
->calc_checksum
= verify
!= IMPORT_VERIFY_NO
;
606 r
= pull_find_old_etags(i
->settings_job
->url
, i
->image_root
, DT_REG
, ".settings-", NULL
, &i
->settings_job
->old_etags
);
611 r
= pull_make_verification_jobs(&i
->checksum_job
, &i
->signature_job
, verify
, url
, i
->glue
, raw_pull_job_on_finished
, i
);
615 r
= pull_job_begin(i
->raw_job
);
619 if (i
->settings_job
) {
620 r
= pull_job_begin(i
->settings_job
);
625 if (i
->checksum_job
) {
626 i
->checksum_job
->on_progress
= raw_pull_job_on_progress
;
628 r
= pull_job_begin(i
->checksum_job
);
633 if (i
->signature_job
) {
634 i
->signature_job
->on_progress
= raw_pull_job_on_progress
;
636 r
= pull_job_begin(i
->signature_job
);