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"
34 #include "import-util.h"
35 #include "curl-util.h"
36 #include "qcow2-util.h"
37 #include "import-job.h"
38 #include "import-common.h"
39 #include "import-raw.h"
41 typedef enum RawProgress {
56 ImportJob *checksum_job;
57 ImportJob *signature_job;
59 RawImportFinished on_finished;
71 RawImport* raw_import_unref(RawImport *i) {
75 import_job_unref(i->raw_job);
76 import_job_unref(i->checksum_job);
77 import_job_unref(i->signature_job);
79 curl_glue_unref(i->glue);
80 sd_event_unref(i->event);
83 (void) unlink(i->temp_path);
98 const char *image_root,
99 RawImportFinished on_finished,
102 _cleanup_(raw_import_unrefp) RawImport *i = NULL;
107 i = new0(RawImport, 1);
111 i->on_finished = on_finished;
112 i->userdata = userdata;
114 i->image_root = strdup(image_root ?: "/var/lib/machines");
119 i->event = sd_event_ref(event);
121 r = sd_event_default(&i->event);
126 r = curl_glue_new(&i->glue, i->event);
130 i->glue->on_finished = import_job_curl_on_finished;
131 i->glue->userdata = i;
139 static void raw_import_report_progress(RawImport *i, RawProgress p) {
146 case RAW_DOWNLOADING: {
147 unsigned remain = 80;
151 if (i->checksum_job) {
152 percent += i->checksum_job->progress_percent * 5 / 100;
156 if (i->signature_job) {
157 percent += i->signature_job->progress_percent * 5 / 100;
162 percent += i->raw_job->progress_percent * remain / 100;
183 assert_not_reached("Unknown progress state");
186 sd_notifyf(false, "X_IMPORT_PROGRESS=%u", percent);
187 log_debug("Combined progress %u%%", percent);
190 static int raw_import_maybe_convert_qcow2(RawImport *i) {
191 _cleanup_close_ int converted_fd = -1;
192 _cleanup_free_ char *t = NULL;
198 r = qcow2_detect(i->raw_job->disk_fd);
200 return log_error_errno(r, "Failed to detect whether this is a QCOW2 image: %m");
204 /* This is a QCOW2 image, let's convert it */
205 r = tempfn_random(i->final_path, &t);
209 converted_fd = open(t, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0644);
210 if (converted_fd < 0)
211 return log_error_errno(errno, "Failed to create %s: %m", t);
213 r = chattr_fd(converted_fd, true, FS_NOCOW_FL);
215 log_warning_errno(errno, "Failed to set file attributes on %s: %m", t);
217 log_info("Unpacking QCOW2 file.");
219 r = qcow2_convert(i->raw_job->disk_fd, converted_fd);
222 return log_error_errno(r, "Failed to convert qcow2 image: %m");
225 unlink(i->temp_path);
231 safe_close(i->raw_job->disk_fd);
232 i->raw_job->disk_fd = converted_fd;
238 static int raw_import_make_local_copy(RawImport *i) {
239 _cleanup_free_ char *tp = NULL;
240 _cleanup_close_ int dfd = -1;
250 if (i->raw_job->etag_exists) {
251 /* We have downloaded this one previously, reopen it */
253 assert(i->raw_job->disk_fd < 0);
255 if (!i->final_path) {
256 r = import_make_path(i->raw_job->url, i->raw_job->etag, i->image_root, ".raw-", ".raw", &i->final_path);
261 i->raw_job->disk_fd = open(i->final_path, O_RDONLY|O_NOCTTY|O_CLOEXEC);
262 if (i->raw_job->disk_fd < 0)
263 return log_error_errno(errno, "Failed to open vendor image: %m");
265 /* We freshly downloaded the image, use it */
267 assert(i->raw_job->disk_fd >= 0);
269 if (lseek(i->raw_job->disk_fd, SEEK_SET, 0) == (off_t) -1)
270 return log_error_errno(errno, "Failed to seek to beginning of vendor image: %m");
273 p = strjoina(i->image_root, "/", i->local, ".raw");
275 if (i->force_local) {
276 (void) btrfs_subvol_remove(p);
277 (void) rm_rf_dangerous(p, false, true, false);
280 r = tempfn_random(p, &tp);
284 dfd = open(tp, O_WRONLY|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664);
286 return log_error_errno(errno, "Failed to create writable copy of image: %m");
288 /* Turn off COW writing. This should greatly improve
289 * performance on COW file systems like btrfs, since it
290 * reduces fragmentation caused by not allowing in-place
292 r = chattr_fd(dfd, true, FS_NOCOW_FL);
294 log_warning_errno(errno, "Failed to set file attributes on %s: %m", tp);
296 r = copy_bytes(i->raw_job->disk_fd, dfd, (off_t) -1, true);
299 return log_error_errno(r, "Failed to make writable copy of image: %m");
302 (void) copy_times(i->raw_job->disk_fd, dfd);
303 (void) copy_xattr(i->raw_job->disk_fd, dfd);
305 dfd = safe_close(dfd);
310 return log_error_errno(errno, "Failed to move writable image into place: %m");
313 log_info("Created new local image '%s'.", i->local);
317 static bool raw_import_is_done(RawImport *i) {
321 if (i->raw_job->state != IMPORT_JOB_DONE)
323 if (i->checksum_job && i->checksum_job->state != IMPORT_JOB_DONE)
325 if (i->signature_job && i->signature_job->state != IMPORT_JOB_DONE)
331 static void raw_import_job_on_finished(ImportJob *j) {
340 if (j == i->checksum_job)
341 log_error_errno(j->error, "Failed to retrieve SHA256 checksum, cannot verify. (Try --verify=no?)");
342 else if (j == i->signature_job)
343 log_error_errno(j->error, "Failed to retrieve signature file, cannot verify. (Try --verify=no?)");
345 log_error_errno(j->error, "Failed to retrieve image file. (Wrong URL?)");
351 /* This is invoked if either the download completed
352 * successfully, or the download was skipped because we
353 * already have the etag. In this case ->etag_exists is
356 * We only do something when we got all three files */
358 if (!raw_import_is_done(i))
361 if (!i->raw_job->etag_exists) {
362 /* This is a new download, verify it, and move it into place */
363 assert(i->raw_job->disk_fd >= 0);
365 raw_import_report_progress(i, RAW_VERIFYING);
367 r = import_verify(i->raw_job, i->checksum_job, i->signature_job);
371 raw_import_report_progress(i, RAW_UNPACKING);
373 r = raw_import_maybe_convert_qcow2(i);
377 raw_import_report_progress(i, RAW_FINALIZING);
379 r = import_make_read_only_fd(i->raw_job->disk_fd);
383 r = rename(i->temp_path, i->final_path);
385 r = log_error_errno(errno, "Failed to move RAW file into place: %m");
393 raw_import_report_progress(i, RAW_COPYING);
395 r = raw_import_make_local_copy(i);
403 i->on_finished(i, r, i->userdata);
405 sd_event_exit(i->event, r);
408 static int raw_import_job_on_open_disk(ImportJob *j) {
416 assert(i->raw_job == j);
417 assert(!i->final_path);
418 assert(!i->temp_path);
420 r = import_make_path(j->url, j->etag, i->image_root, ".raw-", ".raw", &i->final_path);
424 r = tempfn_random(i->final_path, &i->temp_path);
428 mkdir_parents_label(i->temp_path, 0700);
430 j->disk_fd = open(i->temp_path, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0644);
432 return log_error_errno(errno, "Failed to create %s: %m", i->temp_path);
434 r = chattr_fd(j->disk_fd, true, FS_NOCOW_FL);
436 log_warning_errno(errno, "Failed to set file attributes on %s: %m", i->temp_path);
441 static void raw_import_job_on_progress(ImportJob *j) {
449 raw_import_report_progress(i, RAW_DOWNLOADING);
452 int raw_import_pull(RawImport *i, const char *url, const char *local, bool force_local, ImportVerify verify) {
456 assert(verify < _IMPORT_VERIFY_MAX);
459 if (!http_url_is_valid(url))
462 if (local && !machine_name_is_valid(local))
468 r = free_and_strdup(&i->local, local);
471 i->force_local = force_local;
474 /* Queue job for the image itself */
475 r = import_job_new(&i->raw_job, url, i->glue, i);
479 i->raw_job->on_finished = raw_import_job_on_finished;
480 i->raw_job->on_open_disk = raw_import_job_on_open_disk;
481 i->raw_job->on_progress = raw_import_job_on_progress;
482 i->raw_job->calc_checksum = verify != IMPORT_VERIFY_NO;
484 r = import_find_old_etags(url, i->image_root, DT_REG, ".raw-", ".raw", &i->raw_job->old_etags);
488 r = import_make_verification_jobs(&i->checksum_job, &i->signature_job, verify, url, i->glue, raw_import_job_on_finished, i);
492 r = import_job_begin(i->raw_job);
496 if (i->checksum_job) {
497 i->checksum_job->on_progress = raw_import_job_on_progress;
499 r = import_job_begin(i->checksum_job);
504 if (i->signature_job) {
505 i->signature_job->on_progress = raw_import_job_on_progress;
507 r = import_job_begin(i->signature_job);