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 "curl-util.h"
34 #include "qcow2-util.h"
35 #include "import-job.h"
36 #include "import-util.h"
37 #include "import-raw.h"
39 typedef struct RawImportFile RawImportFile;
48 ImportJob *checksum_job;
49 ImportJob *signature_job;
51 RawImportFinished on_finished;
63 RawImport* raw_import_unref(RawImport *i) {
67 import_job_unref(i->raw_job);
68 import_job_unref(i->checksum_job);
69 import_job_unref(i->signature_job);
71 curl_glue_unref(i->glue);
72 sd_event_unref(i->event);
75 (void) unlink(i->temp_path);
90 const char *image_root,
91 RawImportFinished on_finished,
94 _cleanup_(raw_import_unrefp) RawImport *i = NULL;
99 i = new0(RawImport, 1);
103 i->on_finished = on_finished;
104 i->userdata = userdata;
106 i->image_root = strdup(image_root ?: "/var/lib/machines");
111 i->event = sd_event_ref(event);
113 r = sd_event_default(&i->event);
118 r = curl_glue_new(&i->glue, i->event);
122 i->glue->on_finished = import_job_curl_on_finished;
123 i->glue->userdata = i;
131 static int raw_import_maybe_convert_qcow2(RawImport *i) {
132 _cleanup_close_ int converted_fd = -1;
133 _cleanup_free_ char *t = NULL;
139 r = qcow2_detect(i->raw_job->disk_fd);
141 return log_error_errno(r, "Failed to detect whether this is a QCOW2 image: %m");
145 /* This is a QCOW2 image, let's convert it */
146 r = tempfn_random(i->final_path, &t);
150 converted_fd = open(t, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0644);
151 if (converted_fd < 0)
152 return log_error_errno(errno, "Failed to create %s: %m", t);
154 r = chattr_fd(converted_fd, true, FS_NOCOW_FL);
156 log_warning_errno(errno, "Failed to set file attributes on %s: %m", t);
158 log_info("Unpacking QCOW2 file.");
160 r = qcow2_convert(i->raw_job->disk_fd, converted_fd);
163 return log_error_errno(r, "Failed to convert qcow2 image: %m");
166 unlink(i->temp_path);
172 safe_close(i->raw_job->disk_fd);
173 i->raw_job->disk_fd = converted_fd;
179 static int raw_import_make_local_copy(RawImport *i) {
180 _cleanup_free_ char *tp = NULL;
181 _cleanup_close_ int dfd = -1;
191 if (i->raw_job->etag_exists) {
192 /* We have downloaded this one previously, reopen it */
194 assert(i->raw_job->disk_fd < 0);
196 if (!i->final_path) {
197 r = import_make_path(i->raw_job->url, i->raw_job->etag, i->image_root, ".raw-", ".raw", &i->final_path);
202 i->raw_job->disk_fd = open(i->final_path, O_RDONLY|O_NOCTTY|O_CLOEXEC);
203 if (i->raw_job->disk_fd < 0)
204 return log_error_errno(errno, "Failed to open vendor image: %m");
206 /* We freshly downloaded the image, use it */
208 assert(i->raw_job->disk_fd >= 0);
210 if (lseek(i->raw_job->disk_fd, SEEK_SET, 0) == (off_t) -1)
211 return log_error_errno(errno, "Failed to seek to beginning of vendor image: %m");
214 p = strappenda(i->image_root, "/", i->local, ".raw");
216 if (i->force_local) {
217 (void) btrfs_subvol_remove(p);
218 (void) rm_rf_dangerous(p, false, true, false);
221 r = tempfn_random(p, &tp);
225 dfd = open(tp, O_WRONLY|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664);
227 return log_error_errno(errno, "Failed to create writable copy of image: %m");
229 /* Turn off COW writing. This should greatly improve
230 * performance on COW file systems like btrfs, since it
231 * reduces fragmentation caused by not allowing in-place
233 r = chattr_fd(dfd, true, FS_NOCOW_FL);
235 log_warning_errno(errno, "Failed to set file attributes on %s: %m", tp);
237 r = copy_bytes(i->raw_job->disk_fd, dfd, (off_t) -1, true);
240 return log_error_errno(r, "Failed to make writable copy of image: %m");
243 (void) copy_times(i->raw_job->disk_fd, dfd);
244 (void) copy_xattr(i->raw_job->disk_fd, dfd);
246 dfd = safe_close(dfd);
251 return log_error_errno(errno, "Failed to move writable image into place: %m");
254 log_info("Created new local image '%s'.", i->local);
258 static bool raw_import_is_done(RawImport *i) {
262 if (i->raw_job->state != IMPORT_JOB_DONE)
264 if (i->checksum_job && i->checksum_job->state != IMPORT_JOB_DONE)
266 if (i->signature_job && i->signature_job->state != IMPORT_JOB_DONE)
272 static void raw_import_job_on_finished(ImportJob *j) {
281 if (j == i->checksum_job)
282 log_error_errno(j->error, "Failed to retrieve SHA256 checksum, cannot verify. (Try --verify=no?)");
283 else if (j == i->signature_job)
284 log_error_errno(j->error, "Failed to retrieve signature file, cannot verify. (Try --verify=no?)");
286 log_error_errno(j->error, "Failed to retrieve image file. (Wrong URL?)");
292 /* This is invoked if either the download completed
293 * successfully, or the download was skipped because we
294 * already have the etag. In this case ->etag_exists is
297 * We only do something when we got all three files */
299 if (!raw_import_is_done(i))
302 if (!i->raw_job->etag_exists) {
303 /* This is a new download, verify it, and move it into place */
304 assert(i->raw_job->disk_fd >= 0);
306 r = import_verify(i->raw_job, i->checksum_job, i->signature_job);
310 r = raw_import_maybe_convert_qcow2(i);
314 r = import_make_read_only_fd(i->raw_job->disk_fd);
318 r = rename(i->temp_path, i->final_path);
320 r = log_error_errno(errno, "Failed to move RAW file into place: %m");
328 r = raw_import_make_local_copy(i);
336 i->on_finished(i, r, i->userdata);
338 sd_event_exit(i->event, r);
341 static int raw_import_job_on_open_disk(ImportJob *j) {
349 assert(i->raw_job == j);
350 assert(!i->final_path);
351 assert(!i->temp_path);
353 r = import_make_path(j->url, j->etag, i->image_root, ".raw-", ".raw", &i->final_path);
357 r = tempfn_random(i->final_path, &i->temp_path);
361 mkdir_parents_label(i->temp_path, 0700);
363 j->disk_fd = open(i->temp_path, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0644);
365 return log_error_errno(errno, "Failed to create %s: %m", i->temp_path);
367 r = chattr_fd(j->disk_fd, true, FS_NOCOW_FL);
369 log_warning_errno(errno, "Failed to set file attributes on %s: %m", i->temp_path);
374 int raw_import_pull(RawImport *i, const char *url, const char *local, bool force_local, ImportVerify verify) {
378 assert(verify < _IMPORT_VERIFY_MAX);
381 if (!http_url_is_valid(url))
384 if (local && !machine_name_is_valid(local))
390 r = free_and_strdup(&i->local, local);
393 i->force_local = force_local;
396 /* Queue job for the image itself */
397 r = import_job_new(&i->raw_job, url, i->glue, i);
401 i->raw_job->on_finished = raw_import_job_on_finished;
402 i->raw_job->on_open_disk = raw_import_job_on_open_disk;
403 i->raw_job->calc_checksum = verify != IMPORT_VERIFY_NO;
405 r = import_find_old_etags(url, i->image_root, DT_REG, ".raw-", ".raw", &i->raw_job->old_etags);
409 r = import_make_verification_jobs(&i->checksum_job, &i->signature_job, verify, url, i->glue, raw_import_job_on_finished, i);
413 r = import_job_begin(i->raw_job);
417 if (i->checksum_job) {
418 r = import_job_begin(i->checksum_job);
423 if (i->signature_job) {
424 r = import_job_begin(i->signature_job);