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>
23 #include <curl/curl.h>
27 #include "curl-util.h"
28 #include "import-gpt.h"
32 typedef struct GptImportFile GptImportFile;
34 struct GptImportFile {
41 struct curl_slist *request_header;
48 uint64_t content_length;
66 gpt_import_on_finished on_finished;
72 #define FILENAME_ESCAPE "/.#\"\'"
74 static GptImportFile *gpt_import_file_unref(GptImportFile *f) {
79 curl_glue_remove_and_free(f->import->glue, f->curl);
80 curl_slist_free_all(f->request_header);
82 safe_close(f->disk_fd);
94 strv_free(f->old_etags);
100 DEFINE_TRIVIAL_CLEANUP_FUNC(GptImportFile*, gpt_import_file_unref);
102 static void gpt_import_finish(GptImport *import, int error) {
105 if (import->finished)
108 import->finished = true;
110 if (import->on_finished)
111 import->on_finished(import, error, import->userdata);
113 sd_event_exit(import->event, error);
116 static int gpt_import_file_make_final_path(GptImportFile *f) {
117 _cleanup_free_ char *escaped_url = NULL, *escaped_etag = NULL;
124 escaped_url = xescape(f->url, FILENAME_ESCAPE);
129 escaped_etag = xescape(f->etag, FILENAME_ESCAPE);
133 f->final_path = strjoin(f->import->image_root, "/.gpt-", escaped_url, ".", escaped_etag, ".gpt", NULL);
135 f->final_path = strjoin(f->import->image_root, "/.gpt-", escaped_url, ".gpt", NULL);
142 static void gpt_import_file_success(GptImportFile *f) {
150 _cleanup_free_ char *tp = NULL;
151 _cleanup_close_ int dfd = -1;
154 if (f->disk_fd >= 0) {
155 if (lseek(f->disk_fd, SEEK_SET, 0) == (off_t) -1) {
156 r = log_error_errno(errno, "Failed to seek to beginning of vendor image: %m");
160 r = gpt_import_file_make_final_path(f);
166 f->disk_fd = open(f->final_path, O_RDONLY|O_NOCTTY|O_CLOEXEC);
167 if (f->disk_fd < 0) {
168 r = log_error_errno(errno, "Failed top open vendor image: %m");
173 p = strappenda(f->import->image_root, "/", f->local, ".gpt");
175 (void) rm_rf_dangerous(p, false, true, false);
177 r = tempfn_random(p, &tp);
183 dfd = open(tp, O_WRONLY|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664);
185 r = log_error_errno(errno, "Failed to create writable copy of image: %m");
189 r = copy_bytes(f->disk_fd, dfd, (off_t) -1, true);
191 log_error_errno(r, "Failed to make writable copy of image: %m");
196 (void) copy_times(f->disk_fd, dfd);
197 (void) copy_xattr(f->disk_fd, dfd);
199 dfd = safe_close(dfd);
203 r = log_error_errno(errno, "Failed to move writable image into place: %m");
208 log_info("Created new local image %s.", p);
211 f->disk_fd = safe_close(f->disk_fd);
215 gpt_import_finish(f->import, r);
218 static void gpt_import_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result) {
219 GptImportFile *f = NULL;
225 if (curl_easy_getinfo(curl, CURLINFO_PRIVATE, &f) != CURLE_OK)
233 if (result != CURLE_OK) {
234 log_error("Transfer failed: %s", curl_easy_strerror(result));
239 code = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
240 if (code != CURLE_OK) {
241 log_error("Failed to retrieve response code: %s", curl_easy_strerror(code));
244 } else if (status == 304) {
245 log_info("Image already downloaded. Skipping download.");
246 gpt_import_file_success(f);
248 } else if (status >= 300) {
249 log_error("HTTP request to %s failed with code %li.", f->url, status);
252 } else if (status < 200) {
253 log_error("HTTP request to %s finished with unexpected code %li.", f->url, status);
258 if (f->disk_fd < 0) {
259 log_error("No data received.");
264 if (f->content_length != (uint64_t) -1 &&
265 f->content_length != f->written) {
266 log_error("Download truncated.");
272 (void) fsetxattr(f->disk_fd, "user.source_etag", f->etag, strlen(f->etag), 0);
274 (void) fsetxattr(f->disk_fd, "user.source_url", f->url, strlen(f->url), 0);
277 struct timespec ut[2];
279 timespec_store(&ut[0], f->mtime);
281 (void) futimens(f->disk_fd, ut);
283 fd_setcrtime(f->disk_fd, f->mtime);
286 if (fstat(f->disk_fd, &st) < 0) {
287 r = log_error_errno(errno, "Failed to stat file: %m");
292 (void) fchmod(f->disk_fd, st.st_mode & 07444);
294 assert(f->temp_path);
295 assert(f->final_path);
297 r = rename(f->temp_path, f->final_path);
299 r = log_error_errno(errno, "Failed to move GPT file into place: %m");
306 log_info("Completed writing vendor image %s.", f->final_path);
308 gpt_import_file_success(f);
312 gpt_import_finish(f->import, r);
315 static int gpt_import_file_open_disk_for_write(GptImportFile *f) {
323 r = gpt_import_file_make_final_path(f);
328 r = tempfn_random(f->final_path, &f->temp_path);
333 f->disk_fd = open(f->temp_path, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0644);
335 return log_error_errno(errno, "Failed to create %s: %m", f->temp_path);
340 static size_t gpt_import_file_write_callback(void *contents, size_t size, size_t nmemb, void *userdata) {
341 GptImportFile *f = userdata;
342 size_t sz = size * nmemb;
354 r = gpt_import_file_open_disk_for_write(f);
358 if (f->written + sz < f->written) {
359 log_error("File too large, overflow");
364 if (f->content_length != (uint64_t) -1 &&
365 f->written + sz > f->content_length) {
366 log_error("Content length incorrect.");
371 n = write(f->disk_fd, contents, sz);
373 log_error_errno(errno, "Failed to write file: %m");
377 if ((size_t) n < sz) {
378 log_error("Short write");
388 gpt_import_finish(f->import, r);
392 static size_t gpt_import_file_header_callback(void *contents, size_t size, size_t nmemb, void *userdata) {
393 GptImportFile *f = userdata;
394 size_t sz = size * nmemb;
395 _cleanup_free_ char *length = NULL, *last_modified = NULL;
407 r = curl_header_strdup(contents, sz, "ETag:", &etag);
416 if (strv_contains(f->old_etags, f->etag)) {
417 log_info("Image already downloaded. Skipping download.");
418 gpt_import_file_success(f);
425 r = curl_header_strdup(contents, sz, "Content-Length:", &length);
431 (void) safe_atou64(length, &f->content_length);
435 r = curl_header_strdup(contents, sz, "Last-Modified:", &last_modified);
441 (void) curl_parse_http_time(last_modified, &f->mtime);
448 gpt_import_finish(f->import, r);
452 static bool etag_is_valid(const char *etag) {
454 if (!endswith(etag, "\""))
457 if (!startswith(etag, "\"") && !startswith(etag, "W/\""))
463 static int gpt_import_file_find_old_etags(GptImportFile *f) {
464 _cleanup_free_ char *escaped_url = NULL;
465 _cleanup_closedir_ DIR *d = NULL;
469 escaped_url = xescape(f->url, FILENAME_ESCAPE);
473 d = opendir(f->import->image_root);
481 FOREACH_DIRENT_ALL(de, d, return -errno) {
485 if (de->d_type != DT_UNKNOWN &&
486 de->d_type != DT_REG)
489 a = startswith(de->d_name, ".gpt-");
493 a = startswith(a, escaped_url);
497 a = startswith(a, ".");
501 b = endswith(de->d_name, ".gpt");
508 u = cunescape_length(a, b - a);
512 if (!etag_is_valid(u)) {
517 r = strv_consume(&f->old_etags, u);
525 static int gpt_import_file_begin(GptImportFile *f) {
531 log_info("Getting %s.", f->url);
533 r = gpt_import_file_find_old_etags(f);
537 r = curl_glue_make(&f->curl, f->url, f);
541 if (!strv_isempty(f->old_etags)) {
542 _cleanup_free_ char *cc = NULL, *hdr = NULL;
544 cc = strv_join(f->old_etags, ", ");
548 hdr = strappend("If-None-Match: ", cc);
552 f->request_header = curl_slist_new(hdr, NULL);
553 if (!f->request_header)
556 if (curl_easy_setopt(f->curl, CURLOPT_HTTPHEADER, f->request_header) != CURLE_OK)
560 if (curl_easy_setopt(f->curl, CURLOPT_WRITEFUNCTION, gpt_import_file_write_callback) != CURLE_OK)
563 if (curl_easy_setopt(f->curl, CURLOPT_WRITEDATA, f) != CURLE_OK)
566 if (curl_easy_setopt(f->curl, CURLOPT_HEADERFUNCTION, gpt_import_file_header_callback) != CURLE_OK)
569 if (curl_easy_setopt(f->curl, CURLOPT_HEADERDATA, f) != CURLE_OK)
572 r = curl_glue_add(f->import->glue, f->curl);
579 int gpt_import_new(GptImport **import, sd_event *event, const char *image_root, gpt_import_on_finished on_finished, void *userdata) {
580 _cleanup_(gpt_import_unrefp) GptImport *i = NULL;
586 i = new0(GptImport, 1);
590 i->on_finished = on_finished;
591 i->userdata = userdata;
593 i->image_root = strdup(image_root);
598 i->event = sd_event_ref(event);
600 r = sd_event_default(&i->event);
605 r = curl_glue_new(&i->glue, i->event);
609 i->glue->on_finished = gpt_import_curl_on_finished;
610 i->glue->userdata = i;
618 GptImport* gpt_import_unref(GptImport *import) {
624 while ((f = hashmap_steal_first(import->files)))
625 gpt_import_file_unref(f);
626 hashmap_free(import->files);
628 curl_glue_unref(import->glue);
629 sd_event_unref(import->event);
631 free(import->image_root);
637 int gpt_import_cancel(GptImport *import, const char *url) {
643 f = hashmap_remove(import->files, url);
647 gpt_import_file_unref(f);
651 int gpt_import_pull(GptImport *import, const char *url, const char *local, bool force_local) {
652 _cleanup_(gpt_import_file_unrefp) GptImportFile *f = NULL;
656 assert(gpt_url_is_valid(url));
657 assert(!local || machine_name_is_valid(local));
659 if (hashmap_get(import->files, url))
662 r = hashmap_ensure_allocated(&import->files, &string_hash_ops);
666 f = new0(GptImportFile, 1);
672 f->content_length = (uint64_t) -1;
674 f->url = strdup(url);
679 f->local = strdup(local);
683 f->force_local = force_local;
686 r = hashmap_put(import->files, f->url, f);
690 r = gpt_import_file_begin(f);
692 gpt_import_cancel(import, f->url);
701 bool gpt_url_is_valid(const char *url) {
705 if (!startswith(url, "http://") &&
706 !startswith(url, "https://"))
709 return ascii_is_valid(url);