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
;
76 raw_import_on_finished on_finished
;
82 #define FILENAME_ESCAPE "/.#\"\'"
84 #define RAW_MAX_SIZE (1024LLU*1024LLU*1024LLU*8) /* 8 GB */
86 static RawImportFile
*raw_import_file_unref(RawImportFile
*f
) {
91 curl_glue_remove_and_free(f
->import
->glue
, f
->curl
);
92 curl_slist_free_all(f
->request_header
);
94 safe_close(f
->disk_fd
);
106 strv_free(f
->old_etags
);
113 DEFINE_TRIVIAL_CLEANUP_FUNC(RawImportFile
*, raw_import_file_unref
);
115 static void raw_import_finish(RawImport
*import
, int error
) {
118 if (import
->finished
)
121 import
->finished
= true;
123 if (import
->on_finished
)
124 import
->on_finished(import
, error
, import
->userdata
);
126 sd_event_exit(import
->event
, error
);
129 static int raw_import_file_make_final_path(RawImportFile
*f
) {
130 _cleanup_free_
char *escaped_url
= NULL
, *escaped_etag
= NULL
;
137 escaped_url
= xescape(f
->url
, FILENAME_ESCAPE
);
142 escaped_etag
= xescape(f
->etag
, FILENAME_ESCAPE
);
146 f
->final_path
= strjoin(f
->import
->image_root
, "/.raw-", escaped_url
, ".", escaped_etag
, ".raw", NULL
);
148 f
->final_path
= strjoin(f
->import
->image_root
, "/.raw-", escaped_url
, ".raw", NULL
);
155 static int raw_import_file_make_local_copy(RawImportFile
*f
) {
156 _cleanup_free_
char *tp
= NULL
;
157 _cleanup_close_
int dfd
= -1;
166 if (f
->disk_fd
>= 0) {
167 if (lseek(f
->disk_fd
, SEEK_SET
, 0) == (off_t
) -1)
168 return log_error_errno(errno
, "Failed to seek to beginning of vendor image: %m");
170 r
= raw_import_file_make_final_path(f
);
174 f
->disk_fd
= open(f
->final_path
, O_RDONLY
|O_NOCTTY
|O_CLOEXEC
);
176 return log_error_errno(errno
, "Failed to open vendor image: %m");
179 p
= strappenda(f
->import
->image_root
, "/", f
->local
, ".raw");
181 (void) rm_rf_dangerous(p
, false, true, false);
183 r
= tempfn_random(p
, &tp
);
187 dfd
= open(tp
, O_WRONLY
|O_CREAT
|O_EXCL
|O_NOCTTY
|O_CLOEXEC
, 0664);
189 return log_error_errno(errno
, "Failed to create writable copy of image: %m");
191 /* Turn off COW writing. This should greatly improve
192 * performance on COW file systems like btrfs, since it
193 * reduces fragmentation caused by not allowing in-place
195 r
= chattr_fd(dfd
, true, FS_NOCOW_FL
);
197 log_warning_errno(errno
, "Failed to set file attributes on %s: %m", tp
);
199 r
= copy_bytes(f
->disk_fd
, dfd
, (off_t
) -1, true);
202 return log_error_errno(r
, "Failed to make writable copy of image: %m");
205 (void) copy_times(f
->disk_fd
, dfd
);
206 (void) copy_xattr(f
->disk_fd
, dfd
);
208 dfd
= safe_close(dfd
);
213 return log_error_errno(errno
, "Failed to move writable image into place: %m");
216 log_info("Created new local image %s.", p
);
220 static void raw_import_file_success(RawImportFile
*f
) {
227 r
= raw_import_file_make_local_copy(f
);
231 f
->disk_fd
= safe_close(f
->disk_fd
);
235 raw_import_finish(f
->import
, r
);
238 static int raw_import_maybe_convert_qcow2(RawImportFile
*f
) {
239 _cleanup_close_
int converted_fd
= -1;
240 _cleanup_free_
char *t
= NULL
;
245 assert(f
->temp_path
);
247 r
= qcow2_detect(f
->disk_fd
);
249 return log_error_errno(r
, "Failed to detect whether this is a QCOW2 image: %m");
253 /* This is a QCOW2 image, let's convert it */
254 r
= tempfn_random(f
->final_path
, &t
);
258 converted_fd
= open(t
, O_RDWR
|O_CREAT
|O_EXCL
|O_NOCTTY
|O_CLOEXEC
, 0644);
259 if (converted_fd
< 0)
260 return log_error_errno(errno
, "Failed to create %s: %m", t
);
262 r
= qcow2_convert(f
->disk_fd
, converted_fd
);
265 return log_error_errno(r
, "Failed to convert qcow2 image: %m");
268 unlink(f
->temp_path
);
274 safe_close(f
->disk_fd
);
275 f
->disk_fd
= converted_fd
;
281 static void raw_import_curl_on_finished(CurlGlue
*g
, CURL
*curl
, CURLcode result
) {
282 RawImportFile
*f
= NULL
;
288 if (curl_easy_getinfo(curl
, CURLINFO_PRIVATE
, &f
) != CURLE_OK
)
296 if (result
!= CURLE_OK
) {
297 log_error("Transfer failed: %s", curl_easy_strerror(result
));
302 code
= curl_easy_getinfo(curl
, CURLINFO_RESPONSE_CODE
, &status
);
303 if (code
!= CURLE_OK
) {
304 log_error("Failed to retrieve response code: %s", curl_easy_strerror(code
));
307 } else if (status
== 304) {
308 log_info("Image already downloaded. Skipping download.");
309 raw_import_file_success(f
);
311 } else if (status
>= 300) {
312 log_error("HTTP request to %s failed with code %li.", f
->url
, status
);
315 } else if (status
< 200) {
316 log_error("HTTP request to %s finished with unexpected code %li.", f
->url
, status
);
321 if (f
->disk_fd
< 0) {
322 log_error("No data received.");
327 if (f
->content_length
!= (uint64_t) -1 &&
328 f
->content_length
!= f
->written_compressed
) {
329 log_error("Download truncated.");
334 /* Make sure the file size is right, in case the file was
335 * sparse and we just seeked for the last part */
336 if (ftruncate(f
->disk_fd
, f
->written_uncompressed
) < 0) {
337 log_error_errno(errno
, "Failed to truncate file: %m");
342 r
= raw_import_maybe_convert_qcow2(f
);
347 (void) fsetxattr(f
->disk_fd
, "user.source_etag", f
->etag
, strlen(f
->etag
), 0);
349 (void) fsetxattr(f
->disk_fd
, "user.source_url", f
->url
, strlen(f
->url
), 0);
352 struct timespec ut
[2];
354 timespec_store(&ut
[0], f
->mtime
);
356 (void) futimens(f
->disk_fd
, ut
);
358 fd_setcrtime(f
->disk_fd
, f
->mtime
);
361 if (fstat(f
->disk_fd
, &st
) < 0) {
362 r
= log_error_errno(errno
, "Failed to stat file: %m");
367 (void) fchmod(f
->disk_fd
, st
.st_mode
& 07444);
369 assert(f
->temp_path
);
370 assert(f
->final_path
);
372 r
= rename(f
->temp_path
, f
->final_path
);
374 r
= log_error_errno(errno
, "Failed to move RAW file into place: %m");
381 log_info("Completed writing vendor image %s.", f
->final_path
);
383 raw_import_file_success(f
);
387 raw_import_finish(f
->import
, r
);
390 static int raw_import_file_open_disk_for_write(RawImportFile
*f
) {
398 r
= raw_import_file_make_final_path(f
);
403 r
= tempfn_random(f
->final_path
, &f
->temp_path
);
408 f
->disk_fd
= open(f
->temp_path
, O_RDWR
|O_CREAT
|O_EXCL
|O_NOCTTY
|O_CLOEXEC
, 0644);
410 return log_error_errno(errno
, "Failed to create %s: %m", f
->temp_path
);
412 r
= chattr_fd(f
->disk_fd
, true, FS_NOCOW_FL
);
414 log_warning_errno(errno
, "Failed to set file attributes on %s: %m", f
->temp_path
);
419 static int raw_import_file_write_uncompressed(RawImportFile
*f
, void *p
, size_t sz
) {
425 assert(f
->disk_fd
>= 0);
427 if (f
->written_uncompressed
+ sz
< f
->written_uncompressed
) {
428 log_error("File too large, overflow");
432 if (f
->written_uncompressed
+ sz
> RAW_MAX_SIZE
) {
433 log_error("File overly large, refusing");
437 n
= sparse_write(f
->disk_fd
, p
, sz
, 64);
439 log_error_errno(errno
, "Failed to write file: %m");
442 if ((size_t) n
< sz
) {
443 log_error("Short write");
447 f
->written_uncompressed
+= sz
;
452 static int raw_import_file_write_compressed(RawImportFile
*f
, void *p
, size_t sz
) {
458 assert(f
->disk_fd
>= 0);
460 if (f
->written_compressed
+ sz
< f
->written_compressed
) {
461 log_error("File too large, overflow");
465 if (f
->content_length
!= (uint64_t) -1 &&
466 f
->written_compressed
+ sz
> f
->content_length
) {
467 log_error("Content length incorrect.");
471 if (!f
->compressed
) {
472 r
= raw_import_file_write_uncompressed(f
, p
, sz
);
477 f
->lzma
.avail_in
= sz
;
479 while (f
->lzma
.avail_in
> 0) {
480 uint8_t buffer
[16 * 1024];
483 f
->lzma
.next_out
= buffer
;
484 f
->lzma
.avail_out
= sizeof(buffer
);
486 lzr
= lzma_code(&f
->lzma
, LZMA_RUN
);
487 if (lzr
!= LZMA_OK
&& lzr
!= LZMA_STREAM_END
) {
488 log_error("Decompression error.");
492 r
= raw_import_file_write_uncompressed(f
, buffer
, sizeof(buffer
) - f
->lzma
.avail_out
);
498 f
->written_compressed
+= sz
;
503 static int raw_import_file_detect_xz(RawImportFile
*f
) {
504 static const uint8_t xz_signature
[] = {
505 '\xfd', '7', 'z', 'X', 'Z', '\x00'
512 if (f
->payload_size
< sizeof(xz_signature
))
515 f
->compressed
= memcmp(f
->payload
, xz_signature
, sizeof(xz_signature
)) == 0;
516 log_debug("Stream is XZ compressed: %s", yes_no(f
->compressed
));
519 lzr
= lzma_stream_decoder(&f
->lzma
, UINT64_MAX
, LZMA_TELL_UNSUPPORTED_CHECK
);
520 if (lzr
!= LZMA_OK
) {
521 log_error("Failed to initialize LZMA decoder.");
526 r
= raw_import_file_open_disk_for_write(f
);
530 r
= raw_import_file_write_compressed(f
, f
->payload
, f
->payload_size
);
541 static size_t raw_import_file_write_callback(void *contents
, size_t size
, size_t nmemb
, void *userdata
) {
542 RawImportFile
*f
= userdata
;
543 size_t sz
= size
* nmemb
;
554 if (f
->disk_fd
< 0) {
557 /* We haven't opened the file yet, let's first check what it actually is */
559 p
= realloc(f
->payload
, f
->payload_size
+ sz
);
565 memcpy(p
+ f
->payload_size
, contents
, sz
);
566 f
->payload_size
= sz
;
569 r
= raw_import_file_detect_xz(f
);
576 r
= raw_import_file_write_compressed(f
, contents
, sz
);
583 raw_import_finish(f
->import
, r
);
587 static size_t raw_import_file_header_callback(void *contents
, size_t size
, size_t nmemb
, void *userdata
) {
588 RawImportFile
*f
= userdata
;
589 size_t sz
= size
* nmemb
;
590 _cleanup_free_
char *length
= NULL
, *last_modified
= NULL
;
602 r
= curl_header_strdup(contents
, sz
, "ETag:", &etag
);
611 if (strv_contains(f
->old_etags
, f
->etag
)) {
612 log_info("Image already downloaded. Skipping download.");
613 raw_import_file_success(f
);
620 r
= curl_header_strdup(contents
, sz
, "Content-Length:", &length
);
626 (void) safe_atou64(length
, &f
->content_length
);
628 if (f
->content_length
!= (uint64_t) -1) {
629 char bytes
[FORMAT_BYTES_MAX
];
630 log_info("Downloading %s.", format_bytes(bytes
, sizeof(bytes
), f
->content_length
));
636 r
= curl_header_strdup(contents
, sz
, "Last-Modified:", &last_modified
);
642 (void) curl_parse_http_time(last_modified
, &f
->mtime
);
649 raw_import_finish(f
->import
, r
);
653 static bool etag_is_valid(const char *etag
) {
655 if (!endswith(etag
, "\""))
658 if (!startswith(etag
, "\"") && !startswith(etag
, "W/\""))
664 static int raw_import_file_find_old_etags(RawImportFile
*f
) {
665 _cleanup_free_
char *escaped_url
= NULL
;
666 _cleanup_closedir_
DIR *d
= NULL
;
670 escaped_url
= xescape(f
->url
, FILENAME_ESCAPE
);
674 d
= opendir(f
->import
->image_root
);
682 FOREACH_DIRENT_ALL(de
, d
, return -errno
) {
686 if (de
->d_type
!= DT_UNKNOWN
&&
687 de
->d_type
!= DT_REG
)
690 a
= startswith(de
->d_name
, ".raw-");
694 a
= startswith(a
, escaped_url
);
698 a
= startswith(a
, ".");
702 b
= endswith(de
->d_name
, ".raw");
709 u
= cunescape_length(a
, b
- a
);
713 if (!etag_is_valid(u
)) {
718 r
= strv_consume(&f
->old_etags
, u
);
726 static int raw_import_file_begin(RawImportFile
*f
) {
732 log_info("Getting %s.", f
->url
);
734 r
= raw_import_file_find_old_etags(f
);
738 r
= curl_glue_make(&f
->curl
, f
->url
, f
);
742 if (!strv_isempty(f
->old_etags
)) {
743 _cleanup_free_
char *cc
= NULL
, *hdr
= NULL
;
745 cc
= strv_join(f
->old_etags
, ", ");
749 hdr
= strappend("If-None-Match: ", cc
);
753 f
->request_header
= curl_slist_new(hdr
, NULL
);
754 if (!f
->request_header
)
757 if (curl_easy_setopt(f
->curl
, CURLOPT_HTTPHEADER
, f
->request_header
) != CURLE_OK
)
761 if (curl_easy_setopt(f
->curl
, CURLOPT_WRITEFUNCTION
, raw_import_file_write_callback
) != CURLE_OK
)
764 if (curl_easy_setopt(f
->curl
, CURLOPT_WRITEDATA
, f
) != CURLE_OK
)
767 if (curl_easy_setopt(f
->curl
, CURLOPT_HEADERFUNCTION
, raw_import_file_header_callback
) != CURLE_OK
)
770 if (curl_easy_setopt(f
->curl
, CURLOPT_HEADERDATA
, f
) != CURLE_OK
)
773 r
= curl_glue_add(f
->import
->glue
, f
->curl
);
780 int raw_import_new(RawImport
**import
, sd_event
*event
, const char *image_root
, raw_import_on_finished on_finished
, void *userdata
) {
781 _cleanup_(raw_import_unrefp
) RawImport
*i
= NULL
;
787 i
= new0(RawImport
, 1);
791 i
->on_finished
= on_finished
;
792 i
->userdata
= userdata
;
794 i
->image_root
= strdup(image_root
);
799 i
->event
= sd_event_ref(event
);
801 r
= sd_event_default(&i
->event
);
806 r
= curl_glue_new(&i
->glue
, i
->event
);
810 i
->glue
->on_finished
= raw_import_curl_on_finished
;
811 i
->glue
->userdata
= i
;
819 RawImport
* raw_import_unref(RawImport
*import
) {
825 while ((f
= hashmap_steal_first(import
->files
)))
826 raw_import_file_unref(f
);
827 hashmap_free(import
->files
);
829 curl_glue_unref(import
->glue
);
830 sd_event_unref(import
->event
);
832 free(import
->image_root
);
838 int raw_import_cancel(RawImport
*import
, const char *url
) {
844 f
= hashmap_remove(import
->files
, url
);
848 raw_import_file_unref(f
);
852 int raw_import_pull(RawImport
*import
, const char *url
, const char *local
, bool force_local
) {
853 _cleanup_(raw_import_file_unrefp
) RawImportFile
*f
= NULL
;
857 assert(raw_url_is_valid(url
));
858 assert(!local
|| machine_name_is_valid(local
));
860 if (hashmap_get(import
->files
, url
))
863 r
= hashmap_ensure_allocated(&import
->files
, &string_hash_ops
);
867 f
= new0(RawImportFile
, 1);
873 f
->content_length
= (uint64_t) -1;
875 f
->url
= strdup(url
);
880 f
->local
= strdup(local
);
884 f
->force_local
= force_local
;
887 r
= hashmap_put(import
->files
, f
->url
, f
);
891 r
= raw_import_file_begin(f
);
893 raw_import_cancel(import
, f
->url
);
902 bool raw_url_is_valid(const char *url
) {
906 if (!startswith(url
, "http://") &&
907 !startswith(url
, "https://"))
910 return ascii_is_valid(url
);