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;
63 RawImport* raw_import_unref(RawImport *i) {
67 import_job_unref(i->raw_job);
69 curl_glue_unref(i->glue);
70 sd_event_unref(i->event);
73 (void) unlink(i->temp_path);
85 int raw_import_new(RawImport **ret, sd_event *event, const char *image_root, RawImportFinished on_finished, void *userdata) {
86 _cleanup_(raw_import_unrefp) RawImport *i = NULL;
91 i = new0(RawImport, 1);
95 i->on_finished = on_finished;
96 i->userdata = userdata;
98 i->image_root = strdup(image_root ?: "/var/lib/machines");
103 i->event = sd_event_ref(event);
105 r = sd_event_default(&i->event);
110 r = curl_glue_new(&i->glue, i->event);
114 i->glue->on_finished = import_job_curl_on_finished;
115 i->glue->userdata = i;
123 static int raw_import_maybe_convert_qcow2(RawImport *i) {
124 _cleanup_close_ int converted_fd = -1;
125 _cleanup_free_ char *t = NULL;
131 r = qcow2_detect(i->raw_job->disk_fd);
133 return log_error_errno(r, "Failed to detect whether this is a QCOW2 image: %m");
137 /* This is a QCOW2 image, let's convert it */
138 r = tempfn_random(i->final_path, &t);
142 converted_fd = open(t, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0644);
143 if (converted_fd < 0)
144 return log_error_errno(errno, "Failed to create %s: %m", t);
146 r = chattr_fd(converted_fd, true, FS_NOCOW_FL);
148 log_warning_errno(errno, "Failed to set file attributes on %s: %m", t);
150 log_info("Unpacking QCOW2 file.");
152 r = qcow2_convert(i->raw_job->disk_fd, converted_fd);
155 return log_error_errno(r, "Failed to convert qcow2 image: %m");
158 unlink(i->temp_path);
164 safe_close(i->raw_job->disk_fd);
165 i->raw_job->disk_fd = converted_fd;
171 static int raw_import_make_local_copy(RawImport *i) {
172 _cleanup_free_ char *tp = NULL;
173 _cleanup_close_ int dfd = -1;
183 if (i->raw_job->etag_exists) {
184 /* We have downloaded this one previously, reopen it */
186 assert(i->raw_job->disk_fd < 0);
188 if (!i->final_path) {
189 r = import_make_path(i->raw_job->url, i->raw_job->etag, i->image_root, ".raw-", ".raw", &i->final_path);
194 i->raw_job->disk_fd = open(i->final_path, O_RDONLY|O_NOCTTY|O_CLOEXEC);
195 if (i->raw_job->disk_fd < 0)
196 return log_error_errno(errno, "Failed to open vendor image: %m");
198 /* We freshly downloaded the image, use it */
200 assert(i->raw_job->disk_fd >= 0);
202 if (lseek(i->raw_job->disk_fd, SEEK_SET, 0) == (off_t) -1)
203 return log_error_errno(errno, "Failed to seek to beginning of vendor image: %m");
206 p = strappenda(i->image_root, "/", i->local, ".raw");
208 if (i->force_local) {
209 (void) btrfs_subvol_remove(p);
210 (void) rm_rf_dangerous(p, false, true, false);
213 r = tempfn_random(p, &tp);
217 dfd = open(tp, O_WRONLY|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664);
219 return log_error_errno(errno, "Failed to create writable copy of image: %m");
221 /* Turn off COW writing. This should greatly improve
222 * performance on COW file systems like btrfs, since it
223 * reduces fragmentation caused by not allowing in-place
225 r = chattr_fd(dfd, true, FS_NOCOW_FL);
227 log_warning_errno(errno, "Failed to set file attributes on %s: %m", tp);
229 r = copy_bytes(i->raw_job->disk_fd, dfd, (off_t) -1, true);
232 return log_error_errno(r, "Failed to make writable copy of image: %m");
235 (void) copy_times(i->raw_job->disk_fd, dfd);
236 (void) copy_xattr(i->raw_job->disk_fd, dfd);
238 dfd = safe_close(dfd);
243 return log_error_errno(errno, "Failed to move writable image into place: %m");
246 log_info("Created new local image '%s'.", i->local);
250 static int raw_import_verify_sha256sum(RawImport *i) {
251 _cleanup_free_ char *fn = NULL;
252 const char *p, *line;
256 assert(i->verify != IMPORT_VERIFY_NO);
259 assert(i->raw_job->sha256);
261 assert(i->sha256sums_job);
262 assert(i->sha256sums_job->payload);
263 assert(i->sha256sums_job->payload_size > 0);
265 r = import_url_last_component(i->raw_job->url, &fn);
269 if (!filename_is_valid(fn)) {
270 log_error("Cannot verify checksum, could not determine valid server-side file name.");
274 line = strappenda(i->raw_job->sha256, " *", fn, "\n");
276 p = memmem(i->sha256sums_job->payload,
277 i->sha256sums_job->payload_size,
281 if (!p || (p != (char*) i->sha256sums_job->payload && p[-1] != '\n')) {
282 log_error("Checksum did not check out, payload has been tempered with.");
286 log_info("SHA256 checksum of %s is valid.", i->raw_job->url);
291 static int raw_import_finalize(RawImport *i) {
296 if (!IMPORT_JOB_STATE_IS_COMPLETE(i->raw_job) ||
297 (i->verify != IMPORT_VERIFY_NO && !IMPORT_JOB_STATE_IS_COMPLETE(i->sha256sums_job)))
300 if (i->verify != IMPORT_VERIFY_NO &&
301 i->raw_job->etag_exists) {
303 assert(i->temp_path);
304 assert(i->final_path);
305 assert(i->raw_job->disk_fd >= 0);
307 r = raw_import_verify_sha256sum(i);
311 r = rename(i->temp_path, i->final_path);
313 return log_error_errno(errno, "Failed to move RAW file into place: %m");
319 r = raw_import_make_local_copy(i);
323 i->raw_job->disk_fd = safe_close(i->raw_job->disk_fd);
328 static void raw_import_invoke_finished(RawImport *i, int r) {
332 i->on_finished(i, r, i->userdata);
334 sd_event_exit(i->event, r);
337 static void raw_import_raw_job_on_finished(ImportJob *j) {
350 /* This is invoked if either the download completed
351 * successfully, or the download was skipped because we
352 * already have the etag. In this case ->etag_exists is
355 if (!j->etag_exists) {
356 assert(j->disk_fd >= 0);
358 r = raw_import_maybe_convert_qcow2(i);
362 r = import_make_read_only_fd(j->disk_fd);
367 r = raw_import_finalize(i);
376 raw_import_invoke_finished(i, r);
379 static void raw_import_sha256sums_job_on_finished(ImportJob *j) {
387 assert(i->verify != IMPORT_VERIFY_NO);
390 log_error_errno(j->error, "Failed to retrieve SHA256 checksum, cannot verify.");
395 r = raw_import_finalize(i);
403 raw_import_invoke_finished(i, r);
406 static int raw_import_raw_job_on_open_disk(ImportJob *j) {
415 r = import_make_path(j->url, j->etag, i->image_root, ".raw-", ".raw", &i->final_path);
419 r = tempfn_random(i->final_path, &i->temp_path);
423 mkdir_parents_label(i->temp_path, 0700);
425 j->disk_fd = open(i->temp_path, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0644);
427 return log_error_errno(errno, "Failed to create %s: %m", i->temp_path);
429 r = chattr_fd(j->disk_fd, true, FS_NOCOW_FL);
431 log_warning_errno(errno, "Failed to set file attributes on %s: %m", i->temp_path);
436 int raw_import_pull(RawImport *i, const char *url, const char *local, bool force_local, ImportVerify verify) {
437 _cleanup_free_ char *sha256sums_url = NULL;
441 assert(verify < _IMPORT_VERIFY_MAX);
447 if (!http_url_is_valid(url))
450 if (local && !machine_name_is_valid(local))
453 r = free_and_strdup(&i->local, local);
456 i->force_local = force_local;
459 /* Queue job for the image itself */
460 r = import_job_new(&i->raw_job, url, i->glue, i);
464 i->raw_job->on_finished = raw_import_raw_job_on_finished;
465 i->raw_job->on_open_disk = raw_import_raw_job_on_open_disk;
466 i->raw_job->calc_hash = true;
468 r = import_find_old_etags(url, i->image_root, DT_REG, ".raw-", ".raw", &i->raw_job->old_etags);
472 if (verify != IMPORT_VERIFY_NO) {
473 /* Queue job for the SHA256SUMS file for the image */
474 r = import_url_change_last_component(url, "SHA256SUMS", &sha256sums_url);
478 r = import_job_new(&i->sha256sums_job, sha256sums_url, i->glue, i);
482 i->sha256sums_job->on_finished = raw_import_sha256sums_job_on_finished;
483 i->sha256sums_job->uncompressed_max = i->sha256sums_job->compressed_max = 1ULL * 1024ULL * 1024ULL;
485 r = import_job_begin(i->sha256sums_job);
490 r = import_job_begin(i->raw_job);