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;
65 gpt_import_on_finished on_finished;
71 #define FILENAME_ESCAPE "/.#\"\'"
73 static GptImportFile *gpt_import_file_unref(GptImportFile *f) {
78 curl_glue_remove_and_free(f->import->glue, f->curl);
79 curl_slist_free_all(f->request_header);
81 safe_close(f->disk_fd);
93 strv_free(f->old_etags);
99 DEFINE_TRIVIAL_CLEANUP_FUNC(GptImportFile*, gpt_import_file_unref);
101 static void gpt_import_finish(GptImport *import, int error) {
104 if (import->finished)
107 import->finished = true;
109 if (import->on_finished)
110 import->on_finished(import, error, import->userdata);
112 sd_event_exit(import->event, error);
115 static int gpt_import_file_make_final_path(GptImportFile *f) {
116 _cleanup_free_ char *escaped_url = NULL, *escaped_etag = NULL;
123 escaped_url = xescape(f->url, FILENAME_ESCAPE);
128 escaped_etag = xescape(f->etag, FILENAME_ESCAPE);
132 f->final_path = strjoin("/var/lib/container/.gpt-", escaped_url, ".", escaped_etag, ".gpt", NULL);
134 f->final_path = strjoin("/var/lib/container/.gpt-", escaped_url, ".gpt", NULL);
141 static void gpt_import_file_success(GptImportFile *f) {
149 _cleanup_free_ char *tp = NULL;
150 _cleanup_close_ int dfd = -1;
153 if (f->disk_fd >= 0) {
154 if (lseek(f->disk_fd, SEEK_SET, 0) == (off_t) -1) {
155 r = log_error_errno(errno, "Failed to seek to beginning of vendor image: %m");
159 r = gpt_import_file_make_final_path(f);
165 f->disk_fd = open(f->final_path, O_RDONLY|O_NOCTTY|O_CLOEXEC);
166 if (f->disk_fd < 0) {
167 r = log_error_errno(errno, "Failed top open vendor image: %m");
172 p = strappenda("/var/lib/container/", f->local, ".gpt");
174 (void) rm_rf_dangerous(p, false, true, false);
176 r = tempfn_random(p, &tp);
182 dfd = open(tp, O_WRONLY|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664);
184 r = log_error_errno(errno, "Failed to create writable copy of image: %m");
188 r = copy_bytes(f->disk_fd, dfd, (off_t) -1, true);
190 log_error_errno(r, "Failed to make writable copy of image: %m");
195 (void) copy_times(f->disk_fd, dfd);
196 (void) copy_xattr(f->disk_fd, dfd);
198 dfd = safe_close(dfd);
202 r = log_error_errno(errno, "Failed to move writable image into place: %m");
207 log_info("Created new local image %s.", p);
210 f->disk_fd = safe_close(f->disk_fd);
214 gpt_import_finish(f->import, r);
217 static void gpt_import_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result) {
218 GptImportFile *f = NULL;
224 if (curl_easy_getinfo(curl, CURLINFO_PRIVATE, &f) != CURLE_OK)
232 if (result != CURLE_OK) {
233 log_error("Transfer failed: %s", curl_easy_strerror(result));
238 code = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
239 if (code != CURLE_OK) {
240 log_error("Failed to retrieve response code: %s", curl_easy_strerror(code));
243 } else if (status == 304) {
244 log_info("Image already downloaded. Skipping download.");
245 gpt_import_file_success(f);
247 } else if (status >= 300) {
248 log_error("HTTP request to %s failed with code %li.", f->url, status);
251 } else if (status < 200) {
252 log_error("HTTP request to %s finished with unexpected code %li.", f->url, status);
257 if (f->disk_fd < 0) {
258 log_error("No data received.");
263 if (f->content_length != (uint64_t) -1 &&
264 f->content_length != f->written) {
265 log_error("Download truncated.");
271 (void) fsetxattr(f->disk_fd, "user.source_etag", f->etag, strlen(f->etag), 0);
273 (void) fsetxattr(f->disk_fd, "user.source_url", f->url, strlen(f->url), 0);
276 struct timespec ut[2];
278 timespec_store(&ut[0], f->mtime);
280 (void) futimens(f->disk_fd, ut);
282 fd_setcrtime(f->disk_fd, f->mtime);
285 if (fstat(f->disk_fd, &st) < 0) {
286 r = log_error_errno(errno, "Failed to stat file: %m");
291 (void) fchmod(f->disk_fd, st.st_mode & 07444);
293 assert(f->temp_path);
294 assert(f->final_path);
296 r = rename(f->temp_path, f->final_path);
298 r = log_error_errno(errno, "Failed to move GPT file into place: %m");
305 log_info("Completed writing vendor image %s.", f->final_path);
307 gpt_import_file_success(f);
311 gpt_import_finish(f->import, r);
314 static int gpt_import_file_open_disk_for_write(GptImportFile *f) {
322 r = gpt_import_file_make_final_path(f);
327 r = tempfn_random(f->final_path, &f->temp_path);
332 f->disk_fd = open(f->temp_path, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0644);
334 return log_error_errno(errno, "Failed to create %s: %m", f->temp_path);
339 static size_t gpt_import_file_write_callback(void *contents, size_t size, size_t nmemb, void *userdata) {
340 GptImportFile *f = userdata;
341 size_t sz = size * nmemb;
353 r = gpt_import_file_open_disk_for_write(f);
357 if (f->written + sz < f->written) {
358 log_error("File too large, overflow");
363 if (f->content_length != (uint64_t) -1 &&
364 f->written + sz > f->content_length) {
365 log_error("Content length incorrect.");
370 n = write(f->disk_fd, contents, sz);
372 log_error_errno(errno, "Failed to write file: %m");
376 if ((size_t) n < sz) {
377 log_error("Short write");
387 gpt_import_finish(f->import, r);
391 static size_t gpt_import_file_header_callback(void *contents, size_t size, size_t nmemb, void *userdata) {
392 GptImportFile *f = userdata;
393 size_t sz = size * nmemb;
394 _cleanup_free_ char *length = NULL, *last_modified = NULL;
406 r = curl_header_strdup(contents, sz, "ETag:", &etag);
415 if (strv_contains(f->old_etags, f->etag)) {
416 log_info("Image already downloaded. Skipping download.");
417 gpt_import_file_success(f);
424 r = curl_header_strdup(contents, sz, "Content-Length:", &length);
430 (void) safe_atou64(length, &f->content_length);
434 r = curl_header_strdup(contents, sz, "Last-Modified:", &last_modified);
440 (void) curl_parse_http_time(last_modified, &f->mtime);
447 gpt_import_finish(f->import, r);
451 static bool etag_is_valid(const char *etag) {
453 if (!endswith(etag, "\""))
456 if (!startswith(etag, "\"") && !startswith(etag, "W/\""))
462 static int gpt_import_file_find_old_etags(GptImportFile *f) {
463 _cleanup_free_ char *escaped_url = NULL;
464 _cleanup_closedir_ DIR *d = NULL;
468 escaped_url = xescape(f->url, FILENAME_ESCAPE);
472 d = opendir("/var/lib/container/");
480 FOREACH_DIRENT_ALL(de, d, return -errno) {
484 if (de->d_type != DT_UNKNOWN &&
485 de->d_type != DT_REG)
488 a = startswith(de->d_name, ".gpt-");
492 a = startswith(a, escaped_url);
496 a = startswith(a, ".");
500 b = endswith(de->d_name, ".gpt");
507 u = cunescape_length(a, b - a);
511 if (!etag_is_valid(u)) {
516 r = strv_consume(&f->old_etags, u);
524 static int gpt_import_file_begin(GptImportFile *f) {
530 log_info("Getting %s.", f->url);
532 r = gpt_import_file_find_old_etags(f);
536 r = curl_glue_make(&f->curl, f->url, f);
540 if (!strv_isempty(f->old_etags)) {
541 _cleanup_free_ char *cc = NULL, *hdr = NULL;
543 cc = strv_join(f->old_etags, ", ");
547 hdr = strappend("If-None-Match: ", cc);
551 f->request_header = curl_slist_new(hdr, NULL);
552 if (!f->request_header)
555 if (curl_easy_setopt(f->curl, CURLOPT_HTTPHEADER, f->request_header) != CURLE_OK)
559 if (curl_easy_setopt(f->curl, CURLOPT_WRITEFUNCTION, gpt_import_file_write_callback) != CURLE_OK)
562 if (curl_easy_setopt(f->curl, CURLOPT_WRITEDATA, f) != CURLE_OK)
565 if (curl_easy_setopt(f->curl, CURLOPT_HEADERFUNCTION, gpt_import_file_header_callback) != CURLE_OK)
568 if (curl_easy_setopt(f->curl, CURLOPT_HEADERDATA, f) != CURLE_OK)
571 r = curl_glue_add(f->import->glue, f->curl);
578 int gpt_import_new(GptImport **import, sd_event *event, gpt_import_on_finished on_finished, void *userdata) {
579 _cleanup_(gpt_import_unrefp) GptImport *i = NULL;
584 i = new0(GptImport, 1);
588 i->on_finished = on_finished;
589 i->userdata = userdata;
592 i->event = sd_event_ref(event);
594 r = sd_event_default(&i->event);
599 r = curl_glue_new(&i->glue, i->event);
603 i->glue->on_finished = gpt_import_curl_on_finished;
604 i->glue->userdata = i;
612 GptImport* gpt_import_unref(GptImport *import) {
618 while ((f = hashmap_steal_first(import->files)))
619 gpt_import_file_unref(f);
620 hashmap_free(import->files);
622 curl_glue_unref(import->glue);
623 sd_event_unref(import->event);
630 int gpt_import_cancel(GptImport *import, const char *url) {
636 f = hashmap_remove(import->files, url);
640 gpt_import_file_unref(f);
644 int gpt_import_pull(GptImport *import, const char *url, const char *local, bool force_local) {
645 _cleanup_(gpt_import_file_unrefp) GptImportFile *f = NULL;
649 assert(gpt_url_is_valid(url));
650 assert(!local || machine_name_is_valid(local));
652 if (hashmap_get(import->files, url))
655 r = hashmap_ensure_allocated(&import->files, &string_hash_ops);
659 f = new0(GptImportFile, 1);
665 f->content_length = (uint64_t) -1;
667 f->url = strdup(url);
672 f->local = strdup(local);
676 f->force_local = force_local;
679 r = hashmap_put(import->files, f->url, f);
683 r = gpt_import_file_begin(f);
685 gpt_import_cancel(import, f->url);
694 bool gpt_url_is_valid(const char *url) {
698 if (!startswith(url, "http://") &&
699 !startswith(url, "https://"))
702 return ascii_is_valid(url);