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 "curl-util.h"
30 #include "qcow2-util.h"
31 #include "import-raw.h"
35 typedef struct RawImportFile RawImportFile;
37 struct RawImportFile {
44 struct curl_slist *request_header;
51 uint64_t content_length;
52 uint64_t written_compressed;
53 uint64_t written_uncompressed;
68 unsigned progress_percent;
70 usec_t last_status_usec;
80 raw_import_on_finished on_finished;
86 #define FILENAME_ESCAPE "/.#\"\'"
88 #define RAW_MAX_SIZE (1024LLU*1024LLU*1024LLU*8) /* 8 GB */
90 static RawImportFile *raw_import_file_unref(RawImportFile *f) {
95 curl_glue_remove_and_free(f->import->glue, f->curl);
96 curl_slist_free_all(f->request_header);
98 safe_close(f->disk_fd);
103 unlink(f->temp_path);
110 strv_free(f->old_etags);
117 DEFINE_TRIVIAL_CLEANUP_FUNC(RawImportFile*, raw_import_file_unref);
119 static void raw_import_finish(RawImport *import, int error) {
122 if (import->finished)
125 import->finished = true;
127 if (import->on_finished)
128 import->on_finished(import, error, import->userdata);
130 sd_event_exit(import->event, error);
133 static int raw_import_file_make_final_path(RawImportFile *f) {
134 _cleanup_free_ char *escaped_url = NULL, *escaped_etag = NULL;
141 escaped_url = xescape(f->url, FILENAME_ESCAPE);
146 escaped_etag = xescape(f->etag, FILENAME_ESCAPE);
150 f->final_path = strjoin(f->import->image_root, "/.raw-", escaped_url, ".", escaped_etag, ".raw", NULL);
152 f->final_path = strjoin(f->import->image_root, "/.raw-", escaped_url, ".raw", NULL);
159 static int raw_import_file_make_local_copy(RawImportFile *f) {
160 _cleanup_free_ char *tp = NULL;
161 _cleanup_close_ int dfd = -1;
170 if (f->disk_fd >= 0) {
171 if (lseek(f->disk_fd, SEEK_SET, 0) == (off_t) -1)
172 return log_error_errno(errno, "Failed to seek to beginning of vendor image: %m");
174 r = raw_import_file_make_final_path(f);
178 f->disk_fd = open(f->final_path, O_RDONLY|O_NOCTTY|O_CLOEXEC);
180 return log_error_errno(errno, "Failed to open vendor image: %m");
183 p = strappenda(f->import->image_root, "/", f->local, ".raw");
185 (void) rm_rf_dangerous(p, false, true, false);
187 r = tempfn_random(p, &tp);
191 dfd = open(tp, O_WRONLY|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664);
193 return log_error_errno(errno, "Failed to create writable copy of image: %m");
195 /* Turn off COW writing. This should greatly improve
196 * performance on COW file systems like btrfs, since it
197 * reduces fragmentation caused by not allowing in-place
199 r = chattr_fd(dfd, true, FS_NOCOW_FL);
201 log_warning_errno(errno, "Failed to set file attributes on %s: %m", tp);
203 r = copy_bytes(f->disk_fd, dfd, (off_t) -1, true);
206 return log_error_errno(r, "Failed to make writable copy of image: %m");
209 (void) copy_times(f->disk_fd, dfd);
210 (void) copy_xattr(f->disk_fd, dfd);
212 dfd = safe_close(dfd);
217 return log_error_errno(errno, "Failed to move writable image into place: %m");
220 log_info("Created new local image %s.", p);
224 static void raw_import_file_success(RawImportFile *f) {
231 r = raw_import_file_make_local_copy(f);
235 f->disk_fd = safe_close(f->disk_fd);
239 raw_import_finish(f->import, r);
242 static int raw_import_maybe_convert_qcow2(RawImportFile *f) {
243 _cleanup_close_ int converted_fd = -1;
244 _cleanup_free_ char *t = NULL;
249 assert(f->temp_path);
251 r = qcow2_detect(f->disk_fd);
253 return log_error_errno(r, "Failed to detect whether this is a QCOW2 image: %m");
257 /* This is a QCOW2 image, let's convert it */
258 r = tempfn_random(f->final_path, &t);
262 converted_fd = open(t, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0644);
263 if (converted_fd < 0)
264 return log_error_errno(errno, "Failed to create %s: %m", t);
266 r = qcow2_convert(f->disk_fd, converted_fd);
269 return log_error_errno(r, "Failed to convert qcow2 image: %m");
272 unlink(f->temp_path);
278 safe_close(f->disk_fd);
279 f->disk_fd = converted_fd;
285 static void raw_import_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result) {
286 RawImportFile *f = NULL;
292 if (curl_easy_getinfo(curl, CURLINFO_PRIVATE, &f) != CURLE_OK)
300 if (result != CURLE_OK) {
301 log_error("Transfer failed: %s", curl_easy_strerror(result));
306 code = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
307 if (code != CURLE_OK) {
308 log_error("Failed to retrieve response code: %s", curl_easy_strerror(code));
311 } else if (status == 304) {
312 log_info("Image already downloaded. Skipping download.");
313 raw_import_file_success(f);
315 } else if (status >= 300) {
316 log_error("HTTP request to %s failed with code %li.", f->url, status);
319 } else if (status < 200) {
320 log_error("HTTP request to %s finished with unexpected code %li.", f->url, status);
325 if (f->disk_fd < 0) {
326 log_error("No data received.");
331 if (f->content_length != (uint64_t) -1 &&
332 f->content_length != f->written_compressed) {
333 log_error("Download truncated.");
338 /* Make sure the file size is right, in case the file was
339 * sparse and we just seeked for the last part */
340 if (ftruncate(f->disk_fd, f->written_uncompressed) < 0) {
341 log_error_errno(errno, "Failed to truncate file: %m");
346 r = raw_import_maybe_convert_qcow2(f);
351 (void) fsetxattr(f->disk_fd, "user.source_etag", f->etag, strlen(f->etag), 0);
353 (void) fsetxattr(f->disk_fd, "user.source_url", f->url, strlen(f->url), 0);
356 struct timespec ut[2];
358 timespec_store(&ut[0], f->mtime);
360 (void) futimens(f->disk_fd, ut);
362 fd_setcrtime(f->disk_fd, f->mtime);
365 if (fstat(f->disk_fd, &st) < 0) {
366 r = log_error_errno(errno, "Failed to stat file: %m");
371 (void) fchmod(f->disk_fd, st.st_mode & 07444);
373 assert(f->temp_path);
374 assert(f->final_path);
376 r = rename(f->temp_path, f->final_path);
378 r = log_error_errno(errno, "Failed to move RAW file into place: %m");
385 log_info("Completed writing vendor image %s.", f->final_path);
387 raw_import_file_success(f);
391 raw_import_finish(f->import, r);
394 static int raw_import_file_open_disk_for_write(RawImportFile *f) {
402 r = raw_import_file_make_final_path(f);
407 r = tempfn_random(f->final_path, &f->temp_path);
412 f->disk_fd = open(f->temp_path, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0644);
414 return log_error_errno(errno, "Failed to create %s: %m", f->temp_path);
416 r = chattr_fd(f->disk_fd, true, FS_NOCOW_FL);
418 log_warning_errno(errno, "Failed to set file attributes on %s: %m", f->temp_path);
423 static int raw_import_file_write_uncompressed(RawImportFile *f, void *p, size_t sz) {
429 assert(f->disk_fd >= 0);
431 if (f->written_uncompressed + sz < f->written_uncompressed) {
432 log_error("File too large, overflow");
436 if (f->written_uncompressed + sz > RAW_MAX_SIZE) {
437 log_error("File overly large, refusing");
441 n = sparse_write(f->disk_fd, p, sz, 64);
443 log_error_errno(errno, "Failed to write file: %m");
446 if ((size_t) n < sz) {
447 log_error("Short write");
451 f->written_uncompressed += sz;
456 static int raw_import_file_write_compressed(RawImportFile *f, void *p, size_t sz) {
462 assert(f->disk_fd >= 0);
464 if (f->written_compressed + sz < f->written_compressed) {
465 log_error("File too large, overflow");
469 if (f->content_length != (uint64_t) -1 &&
470 f->written_compressed + sz > f->content_length) {
471 log_error("Content length incorrect.");
475 if (!f->compressed) {
476 r = raw_import_file_write_uncompressed(f, p, sz);
481 f->lzma.avail_in = sz;
483 while (f->lzma.avail_in > 0) {
484 uint8_t buffer[16 * 1024];
487 f->lzma.next_out = buffer;
488 f->lzma.avail_out = sizeof(buffer);
490 lzr = lzma_code(&f->lzma, LZMA_RUN);
491 if (lzr != LZMA_OK && lzr != LZMA_STREAM_END) {
492 log_error("Decompression error.");
496 r = raw_import_file_write_uncompressed(f, buffer, sizeof(buffer) - f->lzma.avail_out);
502 f->written_compressed += sz;
507 static int raw_import_file_detect_xz(RawImportFile *f) {
508 static const uint8_t xz_signature[] = {
509 '\xfd', '7', 'z', 'X', 'Z', '\x00'
516 if (f->payload_size < sizeof(xz_signature))
519 f->compressed = memcmp(f->payload, xz_signature, sizeof(xz_signature)) == 0;
520 log_debug("Stream is XZ compressed: %s", yes_no(f->compressed));
523 lzr = lzma_stream_decoder(&f->lzma, UINT64_MAX, LZMA_TELL_UNSUPPORTED_CHECK);
524 if (lzr != LZMA_OK) {
525 log_error("Failed to initialize LZMA decoder.");
530 r = raw_import_file_open_disk_for_write(f);
534 r = raw_import_file_write_compressed(f, f->payload, f->payload_size);
545 static size_t raw_import_file_write_callback(void *contents, size_t size, size_t nmemb, void *userdata) {
546 RawImportFile *f = userdata;
547 size_t sz = size * nmemb;
558 if (f->disk_fd < 0) {
561 /* We haven't opened the file yet, let's first check what it actually is */
563 p = realloc(f->payload, f->payload_size + sz);
569 memcpy(p + f->payload_size, contents, sz);
570 f->payload_size = sz;
573 r = raw_import_file_detect_xz(f);
580 r = raw_import_file_write_compressed(f, contents, sz);
587 raw_import_finish(f->import, r);
591 static size_t raw_import_file_header_callback(void *contents, size_t size, size_t nmemb, void *userdata) {
592 RawImportFile *f = userdata;
593 size_t sz = size * nmemb;
594 _cleanup_free_ char *length = NULL, *last_modified = NULL;
606 r = curl_header_strdup(contents, sz, "ETag:", &etag);
615 if (strv_contains(f->old_etags, f->etag)) {
616 log_info("Image already downloaded. Skipping download.");
617 raw_import_file_success(f);
624 r = curl_header_strdup(contents, sz, "Content-Length:", &length);
630 (void) safe_atou64(length, &f->content_length);
632 if (f->content_length != (uint64_t) -1) {
633 char bytes[FORMAT_BYTES_MAX];
634 log_info("Downloading %s.", format_bytes(bytes, sizeof(bytes), f->content_length));
640 r = curl_header_strdup(contents, sz, "Last-Modified:", &last_modified);
646 (void) curl_parse_http_time(last_modified, &f->mtime);
653 raw_import_finish(f->import, r);
657 static int raw_import_file_progress_callback(void *userdata, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) {
658 RawImportFile *f = userdata;
667 percent = ((100 * dlnow) / dltotal);
668 n = now(CLOCK_MONOTONIC);
670 if (n > f->last_status_usec + USEC_PER_SEC &&
671 percent != f->progress_percent) {
672 char buf[FORMAT_TIMESPAN_MAX];
674 if (n - f->start_usec > USEC_PER_SEC && dlnow > 0) {
677 done = n - f->start_usec;
678 left = (usec_t) (((double) done * (double) dltotal) / dlnow) - done;
680 log_info("Got %u%%. %s left.", percent, format_timespan(buf, sizeof(buf), left, USEC_PER_SEC));
682 log_info("Got %u%%.", percent);
684 f->progress_percent = percent;
685 f->last_status_usec = n;
691 static bool etag_is_valid(const char *etag) {
693 if (!endswith(etag, "\""))
696 if (!startswith(etag, "\"") && !startswith(etag, "W/\""))
702 static int raw_import_file_find_old_etags(RawImportFile *f) {
703 _cleanup_free_ char *escaped_url = NULL;
704 _cleanup_closedir_ DIR *d = NULL;
708 escaped_url = xescape(f->url, FILENAME_ESCAPE);
712 d = opendir(f->import->image_root);
720 FOREACH_DIRENT_ALL(de, d, return -errno) {
724 if (de->d_type != DT_UNKNOWN &&
725 de->d_type != DT_REG)
728 a = startswith(de->d_name, ".raw-");
732 a = startswith(a, escaped_url);
736 a = startswith(a, ".");
740 b = endswith(de->d_name, ".raw");
747 u = cunescape_length(a, b - a);
751 if (!etag_is_valid(u)) {
756 r = strv_consume(&f->old_etags, u);
764 static int raw_import_file_begin(RawImportFile *f) {
770 log_info("Getting %s.", f->url);
772 r = raw_import_file_find_old_etags(f);
776 r = curl_glue_make(&f->curl, f->url, f);
780 if (!strv_isempty(f->old_etags)) {
781 _cleanup_free_ char *cc = NULL, *hdr = NULL;
783 cc = strv_join(f->old_etags, ", ");
787 hdr = strappend("If-None-Match: ", cc);
791 f->request_header = curl_slist_new(hdr, NULL);
792 if (!f->request_header)
795 if (curl_easy_setopt(f->curl, CURLOPT_HTTPHEADER, f->request_header) != CURLE_OK)
799 if (curl_easy_setopt(f->curl, CURLOPT_WRITEFUNCTION, raw_import_file_write_callback) != CURLE_OK)
802 if (curl_easy_setopt(f->curl, CURLOPT_WRITEDATA, f) != CURLE_OK)
805 if (curl_easy_setopt(f->curl, CURLOPT_HEADERFUNCTION, raw_import_file_header_callback) != CURLE_OK)
808 if (curl_easy_setopt(f->curl, CURLOPT_HEADERDATA, f) != CURLE_OK)
811 if (curl_easy_setopt(f->curl, CURLOPT_XFERINFOFUNCTION, raw_import_file_progress_callback) != CURLE_OK)
814 if (curl_easy_setopt(f->curl, CURLOPT_XFERINFODATA, f) != CURLE_OK)
817 if (curl_easy_setopt(f->curl, CURLOPT_NOPROGRESS, 0) != CURLE_OK)
820 r = curl_glue_add(f->import->glue, f->curl);
827 int raw_import_new(RawImport **import, sd_event *event, const char *image_root, raw_import_on_finished on_finished, void *userdata) {
828 _cleanup_(raw_import_unrefp) RawImport *i = NULL;
834 i = new0(RawImport, 1);
838 i->on_finished = on_finished;
839 i->userdata = userdata;
841 i->image_root = strdup(image_root);
846 i->event = sd_event_ref(event);
848 r = sd_event_default(&i->event);
853 r = curl_glue_new(&i->glue, i->event);
857 i->glue->on_finished = raw_import_curl_on_finished;
858 i->glue->userdata = i;
866 RawImport* raw_import_unref(RawImport *import) {
872 while ((f = hashmap_steal_first(import->files)))
873 raw_import_file_unref(f);
874 hashmap_free(import->files);
876 curl_glue_unref(import->glue);
877 sd_event_unref(import->event);
879 free(import->image_root);
885 int raw_import_cancel(RawImport *import, const char *url) {
891 f = hashmap_remove(import->files, url);
895 raw_import_file_unref(f);
899 int raw_import_pull(RawImport *import, const char *url, const char *local, bool force_local) {
900 _cleanup_(raw_import_file_unrefp) RawImportFile *f = NULL;
904 assert(raw_url_is_valid(url));
905 assert(!local || machine_name_is_valid(local));
907 if (hashmap_get(import->files, url))
910 r = hashmap_ensure_allocated(&import->files, &string_hash_ops);
914 f = new0(RawImportFile, 1);
920 f->content_length = (uint64_t) -1;
921 f->start_usec = now(CLOCK_MONOTONIC);
923 f->url = strdup(url);
928 f->local = strdup(local);
932 f->force_local = force_local;
935 r = hashmap_put(import->files, f->url, f);
939 r = raw_import_file_begin(f);
941 raw_import_cancel(import, f->url);
950 bool raw_url_is_valid(const char *url) {
954 if (!startswith(url, "http://") &&
955 !startswith(url, "https://"))
958 return ascii_is_valid(url);