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>
30 #include "btrfs-util.h"
34 #include "curl-util.h"
35 #include "qcow2-util.h"
36 #include "import-job.h"
37 #include "import-util.h"
38 #include "import-raw.h"
40 typedef struct RawImportFile RawImportFile;
49 ImportJob *sha256sums_job;
51 RawImportFinished on_finished;
61 RawImport* raw_import_unref(RawImport *i) {
65 import_job_unref(i->raw_job);
67 curl_glue_unref(i->glue);
68 sd_event_unref(i->event);
71 (void) unlink(i->temp_path);
83 int raw_import_new(RawImport **ret, sd_event *event, const char *image_root, RawImportFinished on_finished, void *userdata) {
84 _cleanup_(raw_import_unrefp) RawImport *i = NULL;
89 i = new0(RawImport, 1);
93 i->on_finished = on_finished;
94 i->userdata = userdata;
96 i->image_root = strdup(image_root ?: "/var/lib/machines");
101 i->event = sd_event_ref(event);
103 r = sd_event_default(&i->event);
108 r = curl_glue_new(&i->glue, i->event);
112 i->glue->on_finished = import_job_curl_on_finished;
113 i->glue->userdata = i;
121 static int raw_import_maybe_convert_qcow2(RawImport *i) {
122 _cleanup_close_ int converted_fd = -1;
123 _cleanup_free_ char *t = NULL;
129 r = qcow2_detect(i->raw_job->disk_fd);
131 return log_error_errno(r, "Failed to detect whether this is a QCOW2 image: %m");
135 /* This is a QCOW2 image, let's convert it */
136 r = tempfn_random(i->final_path, &t);
140 converted_fd = open(t, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0644);
141 if (converted_fd < 0)
142 return log_error_errno(errno, "Failed to create %s: %m", t);
144 r = chattr_fd(converted_fd, true, FS_NOCOW_FL);
146 log_warning_errno(errno, "Failed to set file attributes on %s: %m", t);
148 log_info("Unpacking QCOW2 file.");
150 r = qcow2_convert(i->raw_job->disk_fd, converted_fd);
153 return log_error_errno(r, "Failed to convert qcow2 image: %m");
156 unlink(i->temp_path);
162 safe_close(i->raw_job->disk_fd);
163 i->raw_job->disk_fd = converted_fd;
169 static int raw_import_make_local_copy(RawImport *i) {
170 _cleanup_free_ char *tp = NULL;
171 _cleanup_close_ int dfd = -1;
181 if (i->raw_job->etag_exists) {
182 /* We have downloaded this one previously, reopen it */
184 assert(i->raw_job->disk_fd < 0);
186 if (!i->final_path) {
187 r = import_make_path(i->raw_job->url, i->raw_job->etag, i->image_root, ".raw-", ".raw", &i->final_path);
192 i->raw_job->disk_fd = open(i->final_path, O_RDONLY|O_NOCTTY|O_CLOEXEC);
193 if (i->raw_job->disk_fd < 0)
194 return log_error_errno(errno, "Failed to open vendor image: %m");
196 /* We freshly downloaded the image, use it */
198 assert(i->raw_job->disk_fd >= 0);
200 if (lseek(i->raw_job->disk_fd, SEEK_SET, 0) == (off_t) -1)
201 return log_error_errno(errno, "Failed to seek to beginning of vendor image: %m");
204 p = strappenda(i->image_root, "/", i->local, ".raw");
206 if (i->force_local) {
207 (void) btrfs_subvol_remove(p);
208 (void) rm_rf_dangerous(p, false, true, false);
211 r = tempfn_random(p, &tp);
215 dfd = open(tp, O_WRONLY|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664);
217 return log_error_errno(errno, "Failed to create writable copy of image: %m");
219 /* Turn off COW writing. This should greatly improve
220 * performance on COW file systems like btrfs, since it
221 * reduces fragmentation caused by not allowing in-place
223 r = chattr_fd(dfd, true, FS_NOCOW_FL);
225 log_warning_errno(errno, "Failed to set file attributes on %s: %m", tp);
227 r = copy_bytes(i->raw_job->disk_fd, dfd, (off_t) -1, true);
230 return log_error_errno(r, "Failed to make writable copy of image: %m");
233 (void) copy_times(i->raw_job->disk_fd, dfd);
234 (void) copy_xattr(i->raw_job->disk_fd, dfd);
236 dfd = safe_close(dfd);
241 return log_error_errno(errno, "Failed to move writable image into place: %m");
244 log_info("Created new local image '%s'.", i->local);
248 static int raw_import_verify_sha256sum(RawImport *i) {
249 _cleanup_free_ char *fn = NULL;
250 const char *p, *line;
256 assert(i->raw_job->sha256);
258 assert(i->sha256sums_job);
259 assert(i->sha256sums_job->payload);
260 assert(i->sha256sums_job->payload_size > 0);
262 r = import_url_last_component(i->raw_job->url, &fn);
266 if (!filename_is_valid(fn)) {
267 log_error("Cannot verify checksum, could not determine valid server-side file name.");
271 line = strappenda(i->raw_job->sha256, " *", fn, "\n");
273 p = memmem(i->sha256sums_job->payload,
274 i->sha256sums_job->payload_size,
278 if (!p || (p != (char*) i->sha256sums_job->payload && p[-1] != '\n')) {
279 log_error("Checksum did not check out, payload has been tempered with.");
283 log_info("SHA256 checksum of %s is valid.", i->raw_job->url);
288 static int raw_import_finalize(RawImport *i) {
293 if (!IMPORT_JOB_STATE_IS_COMPLETE(i->raw_job) ||
294 !IMPORT_JOB_STATE_IS_COMPLETE(i->sha256sums_job))
297 if (!i->raw_job->etag_exists) {
298 assert(i->temp_path);
299 assert(i->final_path);
300 assert(i->raw_job->disk_fd >= 0);
302 r = raw_import_verify_sha256sum(i);
306 r = rename(i->temp_path, i->final_path);
308 return log_error_errno(errno, "Failed to move RAW file into place: %m");
314 r = raw_import_make_local_copy(i);
318 i->raw_job->disk_fd = safe_close(i->raw_job->disk_fd);
323 static void raw_import_invoke_finished(RawImport *i, int r) {
327 i->on_finished(i, r, i->userdata);
329 sd_event_exit(i->event, r);
332 static void raw_import_raw_job_on_finished(ImportJob *j) {
345 /* This is invoked if either the download completed
346 * successfully, or the download was skipped because we
347 * already have the etag. In this case ->etag_exists is
350 if (!j->etag_exists) {
351 assert(j->disk_fd >= 0);
353 r = raw_import_maybe_convert_qcow2(i);
357 r = import_make_read_only_fd(j->disk_fd);
362 r = raw_import_finalize(i);
371 raw_import_invoke_finished(i, r);
374 static void raw_import_sha256sums_job_on_finished(ImportJob *j) {
387 r = raw_import_finalize(i);
395 raw_import_invoke_finished(i, r);
398 static int raw_import_raw_job_on_open_disk(ImportJob *j) {
407 r = import_make_path(j->url, j->etag, i->image_root, ".raw-", ".raw", &i->final_path);
411 r = tempfn_random(i->final_path, &i->temp_path);
415 mkdir_parents_label(i->temp_path, 0700);
417 j->disk_fd = open(i->temp_path, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0644);
419 return log_error_errno(errno, "Failed to create %s: %m", i->temp_path);
421 r = chattr_fd(j->disk_fd, true, FS_NOCOW_FL);
423 log_warning_errno(errno, "Failed to set file attributes on %s: %m", i->temp_path);
428 int raw_import_pull(RawImport *i, const char *url, const char *local, bool force_local) {
429 _cleanup_free_ char *sha256sums_url = NULL;
437 if (!http_url_is_valid(url))
440 if (local && !machine_name_is_valid(local))
443 r = free_and_strdup(&i->local, local);
446 i->force_local = force_local;
448 /* Queue job for the image itself */
449 r = import_job_new(&i->raw_job, url, i->glue, i);
453 i->raw_job->on_finished = raw_import_raw_job_on_finished;
454 i->raw_job->on_open_disk = raw_import_raw_job_on_open_disk;
455 i->raw_job->calc_hash = true;
457 r = import_find_old_etags(url, i->image_root, DT_REG, ".raw-", ".raw", &i->raw_job->old_etags);
461 /* Queue job for the SHA256SUMS file for the image */
462 r = import_url_change_last_component(url, "SHA256SUMS", &sha256sums_url);
466 r = import_job_new(&i->sha256sums_job, sha256sums_url, i->glue, i);
470 i->sha256sums_job->on_finished = raw_import_sha256sums_job_on_finished;
471 i->sha256sums_job->uncompressed_max = i->sha256sums_job->compressed_max = 1ULL * 1024ULL * 1024ULL;
473 r = import_job_begin(i->raw_job);
477 r = import_job_begin(i->sha256sums_job);