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>
29 #include "btrfs-util.h"
33 #include "import-util.h"
34 #include "curl-util.h"
35 #include "qcow2-util.h"
36 #include "import-job.h"
37 #include "import-common.h"
38 #include "import-raw.h"
40 typedef struct RawImportFile RawImportFile;
49 ImportJob *checksum_job;
50 ImportJob *signature_job;
52 RawImportFinished on_finished;
64 RawImport* raw_import_unref(RawImport *i) {
68 import_job_unref(i->raw_job);
69 import_job_unref(i->checksum_job);
70 import_job_unref(i->signature_job);
72 curl_glue_unref(i->glue);
73 sd_event_unref(i->event);
76 (void) unlink(i->temp_path);
91 const char *image_root,
92 RawImportFinished on_finished,
95 _cleanup_(raw_import_unrefp) RawImport *i = NULL;
100 i = new0(RawImport, 1);
104 i->on_finished = on_finished;
105 i->userdata = userdata;
107 i->image_root = strdup(image_root ?: "/var/lib/machines");
112 i->event = sd_event_ref(event);
114 r = sd_event_default(&i->event);
119 r = curl_glue_new(&i->glue, i->event);
123 i->glue->on_finished = import_job_curl_on_finished;
124 i->glue->userdata = i;
132 static int raw_import_maybe_convert_qcow2(RawImport *i) {
133 _cleanup_close_ int converted_fd = -1;
134 _cleanup_free_ char *t = NULL;
140 r = qcow2_detect(i->raw_job->disk_fd);
142 return log_error_errno(r, "Failed to detect whether this is a QCOW2 image: %m");
146 /* This is a QCOW2 image, let's convert it */
147 r = tempfn_random(i->final_path, &t);
151 converted_fd = open(t, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0644);
152 if (converted_fd < 0)
153 return log_error_errno(errno, "Failed to create %s: %m", t);
155 r = chattr_fd(converted_fd, true, FS_NOCOW_FL);
157 log_warning_errno(errno, "Failed to set file attributes on %s: %m", t);
159 log_info("Unpacking QCOW2 file.");
161 r = qcow2_convert(i->raw_job->disk_fd, converted_fd);
164 return log_error_errno(r, "Failed to convert qcow2 image: %m");
167 unlink(i->temp_path);
173 safe_close(i->raw_job->disk_fd);
174 i->raw_job->disk_fd = converted_fd;
180 static int raw_import_make_local_copy(RawImport *i) {
181 _cleanup_free_ char *tp = NULL;
182 _cleanup_close_ int dfd = -1;
192 if (i->raw_job->etag_exists) {
193 /* We have downloaded this one previously, reopen it */
195 assert(i->raw_job->disk_fd < 0);
197 if (!i->final_path) {
198 r = import_make_path(i->raw_job->url, i->raw_job->etag, i->image_root, ".raw-", ".raw", &i->final_path);
203 i->raw_job->disk_fd = open(i->final_path, O_RDONLY|O_NOCTTY|O_CLOEXEC);
204 if (i->raw_job->disk_fd < 0)
205 return log_error_errno(errno, "Failed to open vendor image: %m");
207 /* We freshly downloaded the image, use it */
209 assert(i->raw_job->disk_fd >= 0);
211 if (lseek(i->raw_job->disk_fd, SEEK_SET, 0) == (off_t) -1)
212 return log_error_errno(errno, "Failed to seek to beginning of vendor image: %m");
215 p = strappenda(i->image_root, "/", i->local, ".raw");
217 if (i->force_local) {
218 (void) btrfs_subvol_remove(p);
219 (void) rm_rf_dangerous(p, false, true, false);
222 r = tempfn_random(p, &tp);
226 dfd = open(tp, O_WRONLY|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664);
228 return log_error_errno(errno, "Failed to create writable copy of image: %m");
230 /* Turn off COW writing. This should greatly improve
231 * performance on COW file systems like btrfs, since it
232 * reduces fragmentation caused by not allowing in-place
234 r = chattr_fd(dfd, true, FS_NOCOW_FL);
236 log_warning_errno(errno, "Failed to set file attributes on %s: %m", tp);
238 r = copy_bytes(i->raw_job->disk_fd, dfd, (off_t) -1, true);
241 return log_error_errno(r, "Failed to make writable copy of image: %m");
244 (void) copy_times(i->raw_job->disk_fd, dfd);
245 (void) copy_xattr(i->raw_job->disk_fd, dfd);
247 dfd = safe_close(dfd);
252 return log_error_errno(errno, "Failed to move writable image into place: %m");
255 log_info("Created new local image '%s'.", i->local);
259 static bool raw_import_is_done(RawImport *i) {
263 if (i->raw_job->state != IMPORT_JOB_DONE)
265 if (i->checksum_job && i->checksum_job->state != IMPORT_JOB_DONE)
267 if (i->signature_job && i->signature_job->state != IMPORT_JOB_DONE)
273 static void raw_import_job_on_finished(ImportJob *j) {
282 if (j == i->checksum_job)
283 log_error_errno(j->error, "Failed to retrieve SHA256 checksum, cannot verify. (Try --verify=no?)");
284 else if (j == i->signature_job)
285 log_error_errno(j->error, "Failed to retrieve signature file, cannot verify. (Try --verify=no?)");
287 log_error_errno(j->error, "Failed to retrieve image file. (Wrong URL?)");
293 /* This is invoked if either the download completed
294 * successfully, or the download was skipped because we
295 * already have the etag. In this case ->etag_exists is
298 * We only do something when we got all three files */
300 if (!raw_import_is_done(i))
303 if (!i->raw_job->etag_exists) {
304 /* This is a new download, verify it, and move it into place */
305 assert(i->raw_job->disk_fd >= 0);
307 r = import_verify(i->raw_job, i->checksum_job, i->signature_job);
311 r = raw_import_maybe_convert_qcow2(i);
315 r = import_make_read_only_fd(i->raw_job->disk_fd);
319 r = rename(i->temp_path, i->final_path);
321 r = log_error_errno(errno, "Failed to move RAW file into place: %m");
329 r = raw_import_make_local_copy(i);
337 i->on_finished(i, r, i->userdata);
339 sd_event_exit(i->event, r);
342 static int raw_import_job_on_open_disk(ImportJob *j) {
350 assert(i->raw_job == j);
351 assert(!i->final_path);
352 assert(!i->temp_path);
354 r = import_make_path(j->url, j->etag, i->image_root, ".raw-", ".raw", &i->final_path);
358 r = tempfn_random(i->final_path, &i->temp_path);
362 mkdir_parents_label(i->temp_path, 0700);
364 j->disk_fd = open(i->temp_path, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0644);
366 return log_error_errno(errno, "Failed to create %s: %m", i->temp_path);
368 r = chattr_fd(j->disk_fd, true, FS_NOCOW_FL);
370 log_warning_errno(errno, "Failed to set file attributes on %s: %m", i->temp_path);
375 int raw_import_pull(RawImport *i, const char *url, const char *local, bool force_local, ImportVerify verify) {
379 assert(verify < _IMPORT_VERIFY_MAX);
382 if (!http_url_is_valid(url))
385 if (local && !machine_name_is_valid(local))
391 r = free_and_strdup(&i->local, local);
394 i->force_local = force_local;
397 /* Queue job for the image itself */
398 r = import_job_new(&i->raw_job, url, i->glue, i);
402 i->raw_job->on_finished = raw_import_job_on_finished;
403 i->raw_job->on_open_disk = raw_import_job_on_open_disk;
404 i->raw_job->calc_checksum = verify != IMPORT_VERIFY_NO;
406 r = import_find_old_etags(url, i->image_root, DT_REG, ".raw-", ".raw", &i->raw_job->old_etags);
410 r = import_make_verification_jobs(&i->checksum_job, &i->signature_job, verify, url, i->glue, raw_import_job_on_finished, i);
414 r = import_job_begin(i->raw_job);
418 if (i->checksum_job) {
419 r = import_job_begin(i->checksum_job);
424 if (i->signature_job) {
425 r = import_job_begin(i->signature_job);