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>
28 #include "curl-util.h"
29 #include "import-gpt.h"
33 typedef struct GptImportFile GptImportFile;
35 struct GptImportFile {
42 struct curl_slist *request_header;
49 uint64_t content_length;
67 gpt_import_on_finished on_finished;
73 #define FILENAME_ESCAPE "/.#\"\'"
75 static GptImportFile *gpt_import_file_unref(GptImportFile *f) {
80 curl_glue_remove_and_free(f->import->glue, f->curl);
81 curl_slist_free_all(f->request_header);
83 safe_close(f->disk_fd);
95 strv_free(f->old_etags);
101 DEFINE_TRIVIAL_CLEANUP_FUNC(GptImportFile*, gpt_import_file_unref);
103 static void gpt_import_finish(GptImport *import, int error) {
106 if (import->finished)
109 import->finished = true;
111 if (import->on_finished)
112 import->on_finished(import, error, import->userdata);
114 sd_event_exit(import->event, error);
117 static int gpt_import_file_make_final_path(GptImportFile *f) {
118 _cleanup_free_ char *escaped_url = NULL, *escaped_etag = NULL;
125 escaped_url = xescape(f->url, FILENAME_ESCAPE);
130 escaped_etag = xescape(f->etag, FILENAME_ESCAPE);
134 f->final_path = strjoin(f->import->image_root, "/.gpt-", escaped_url, ".", escaped_etag, ".gpt", NULL);
136 f->final_path = strjoin(f->import->image_root, "/.gpt-", escaped_url, ".gpt", NULL);
143 static void gpt_import_file_success(GptImportFile *f) {
151 _cleanup_free_ char *tp = NULL;
152 _cleanup_close_ int dfd = -1;
155 if (f->disk_fd >= 0) {
156 if (lseek(f->disk_fd, SEEK_SET, 0) == (off_t) -1) {
157 r = log_error_errno(errno, "Failed to seek to beginning of vendor image: %m");
161 r = gpt_import_file_make_final_path(f);
167 f->disk_fd = open(f->final_path, O_RDONLY|O_NOCTTY|O_CLOEXEC);
168 if (f->disk_fd < 0) {
169 r = log_error_errno(errno, "Failed to open vendor image: %m");
174 p = strappenda(f->import->image_root, "/", f->local, ".gpt");
176 (void) rm_rf_dangerous(p, false, true, false);
178 r = tempfn_random(p, &tp);
184 dfd = open(tp, O_WRONLY|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664);
186 r = log_error_errno(errno, "Failed to create writable copy of image: %m");
190 /* Turn off COW writing. This should greatly improve
191 * performance on COW file systems like btrfs, since it
192 * reduces fragmentation caused by not allowing in-place
194 r = chattr_fd(dfd, true, FS_NOCOW_FL);
196 log_warning_errno(errno, "Failed to set file attributes on %s: %m", f->temp_path);
198 r = copy_bytes(f->disk_fd, dfd, (off_t) -1, true);
200 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);
212 r = log_error_errno(errno, "Failed to move writable image into place: %m");
217 log_info("Created new local image %s.", p);
220 f->disk_fd = safe_close(f->disk_fd);
224 gpt_import_finish(f->import, r);
227 static void gpt_import_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result) {
228 GptImportFile *f = NULL;
234 if (curl_easy_getinfo(curl, CURLINFO_PRIVATE, &f) != CURLE_OK)
242 if (result != CURLE_OK) {
243 log_error("Transfer failed: %s", curl_easy_strerror(result));
248 code = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
249 if (code != CURLE_OK) {
250 log_error("Failed to retrieve response code: %s", curl_easy_strerror(code));
253 } else if (status == 304) {
254 log_info("Image already downloaded. Skipping download.");
255 gpt_import_file_success(f);
257 } else if (status >= 300) {
258 log_error("HTTP request to %s failed with code %li.", f->url, status);
261 } else if (status < 200) {
262 log_error("HTTP request to %s finished with unexpected code %li.", f->url, status);
267 if (f->disk_fd < 0) {
268 log_error("No data received.");
273 if (f->content_length != (uint64_t) -1 &&
274 f->content_length != f->written) {
275 log_error("Download truncated.");
281 (void) fsetxattr(f->disk_fd, "user.source_etag", f->etag, strlen(f->etag), 0);
283 (void) fsetxattr(f->disk_fd, "user.source_url", f->url, strlen(f->url), 0);
286 struct timespec ut[2];
288 timespec_store(&ut[0], f->mtime);
290 (void) futimens(f->disk_fd, ut);
292 fd_setcrtime(f->disk_fd, f->mtime);
295 if (fstat(f->disk_fd, &st) < 0) {
296 r = log_error_errno(errno, "Failed to stat file: %m");
301 (void) fchmod(f->disk_fd, st.st_mode & 07444);
303 assert(f->temp_path);
304 assert(f->final_path);
306 r = rename(f->temp_path, f->final_path);
308 r = log_error_errno(errno, "Failed to move GPT file into place: %m");
315 log_info("Completed writing vendor image %s.", f->final_path);
317 gpt_import_file_success(f);
321 gpt_import_finish(f->import, r);
324 static int gpt_import_file_open_disk_for_write(GptImportFile *f) {
332 r = gpt_import_file_make_final_path(f);
337 r = tempfn_random(f->final_path, &f->temp_path);
342 f->disk_fd = open(f->temp_path, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0644);
344 return log_error_errno(errno, "Failed to create %s: %m", f->temp_path);
349 static size_t gpt_import_file_write_callback(void *contents, size_t size, size_t nmemb, void *userdata) {
350 GptImportFile *f = userdata;
351 size_t sz = size * nmemb;
363 r = gpt_import_file_open_disk_for_write(f);
367 if (f->written + sz < f->written) {
368 log_error("File too large, overflow");
373 if (f->content_length != (uint64_t) -1 &&
374 f->written + sz > f->content_length) {
375 log_error("Content length incorrect.");
380 n = write(f->disk_fd, contents, sz);
382 log_error_errno(errno, "Failed to write file: %m");
386 if ((size_t) n < sz) {
387 log_error("Short write");
397 gpt_import_finish(f->import, r);
401 static size_t gpt_import_file_header_callback(void *contents, size_t size, size_t nmemb, void *userdata) {
402 GptImportFile *f = userdata;
403 size_t sz = size * nmemb;
404 _cleanup_free_ char *length = NULL, *last_modified = NULL;
416 r = curl_header_strdup(contents, sz, "ETag:", &etag);
425 if (strv_contains(f->old_etags, f->etag)) {
426 log_info("Image already downloaded. Skipping download.");
427 gpt_import_file_success(f);
434 r = curl_header_strdup(contents, sz, "Content-Length:", &length);
440 (void) safe_atou64(length, &f->content_length);
444 r = curl_header_strdup(contents, sz, "Last-Modified:", &last_modified);
450 (void) curl_parse_http_time(last_modified, &f->mtime);
457 gpt_import_finish(f->import, r);
461 static bool etag_is_valid(const char *etag) {
463 if (!endswith(etag, "\""))
466 if (!startswith(etag, "\"") && !startswith(etag, "W/\""))
472 static int gpt_import_file_find_old_etags(GptImportFile *f) {
473 _cleanup_free_ char *escaped_url = NULL;
474 _cleanup_closedir_ DIR *d = NULL;
478 escaped_url = xescape(f->url, FILENAME_ESCAPE);
482 d = opendir(f->import->image_root);
490 FOREACH_DIRENT_ALL(de, d, return -errno) {
494 if (de->d_type != DT_UNKNOWN &&
495 de->d_type != DT_REG)
498 a = startswith(de->d_name, ".gpt-");
502 a = startswith(a, escaped_url);
506 a = startswith(a, ".");
510 b = endswith(de->d_name, ".gpt");
517 u = cunescape_length(a, b - a);
521 if (!etag_is_valid(u)) {
526 r = strv_consume(&f->old_etags, u);
534 static int gpt_import_file_begin(GptImportFile *f) {
540 log_info("Getting %s.", f->url);
542 r = gpt_import_file_find_old_etags(f);
546 r = curl_glue_make(&f->curl, f->url, f);
550 if (!strv_isempty(f->old_etags)) {
551 _cleanup_free_ char *cc = NULL, *hdr = NULL;
553 cc = strv_join(f->old_etags, ", ");
557 hdr = strappend("If-None-Match: ", cc);
561 f->request_header = curl_slist_new(hdr, NULL);
562 if (!f->request_header)
565 if (curl_easy_setopt(f->curl, CURLOPT_HTTPHEADER, f->request_header) != CURLE_OK)
569 if (curl_easy_setopt(f->curl, CURLOPT_WRITEFUNCTION, gpt_import_file_write_callback) != CURLE_OK)
572 if (curl_easy_setopt(f->curl, CURLOPT_WRITEDATA, f) != CURLE_OK)
575 if (curl_easy_setopt(f->curl, CURLOPT_HEADERFUNCTION, gpt_import_file_header_callback) != CURLE_OK)
578 if (curl_easy_setopt(f->curl, CURLOPT_HEADERDATA, f) != CURLE_OK)
581 r = curl_glue_add(f->import->glue, f->curl);
588 int gpt_import_new(GptImport **import, sd_event *event, const char *image_root, gpt_import_on_finished on_finished, void *userdata) {
589 _cleanup_(gpt_import_unrefp) GptImport *i = NULL;
595 i = new0(GptImport, 1);
599 i->on_finished = on_finished;
600 i->userdata = userdata;
602 i->image_root = strdup(image_root);
607 i->event = sd_event_ref(event);
609 r = sd_event_default(&i->event);
614 r = curl_glue_new(&i->glue, i->event);
618 i->glue->on_finished = gpt_import_curl_on_finished;
619 i->glue->userdata = i;
627 GptImport* gpt_import_unref(GptImport *import) {
633 while ((f = hashmap_steal_first(import->files)))
634 gpt_import_file_unref(f);
635 hashmap_free(import->files);
637 curl_glue_unref(import->glue);
638 sd_event_unref(import->event);
640 free(import->image_root);
646 int gpt_import_cancel(GptImport *import, const char *url) {
652 f = hashmap_remove(import->files, url);
656 gpt_import_file_unref(f);
660 int gpt_import_pull(GptImport *import, const char *url, const char *local, bool force_local) {
661 _cleanup_(gpt_import_file_unrefp) GptImportFile *f = NULL;
665 assert(gpt_url_is_valid(url));
666 assert(!local || machine_name_is_valid(local));
668 if (hashmap_get(import->files, url))
671 r = hashmap_ensure_allocated(&import->files, &string_hash_ops);
675 f = new0(GptImportFile, 1);
681 f->content_length = (uint64_t) -1;
683 f->url = strdup(url);
688 f->local = strdup(local);
692 f->force_local = force_local;
695 r = hashmap_put(import->files, f->url, f);
699 r = gpt_import_file_begin(f);
701 gpt_import_cancel(import, f->url);
710 bool gpt_url_is_valid(const char *url) {
714 if (!startswith(url, "http://") &&
715 !startswith(url, "https://"))
718 return ascii_is_valid(url);