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"
30 typedef struct GptImportFile GptImportFile;
32 struct GptImportFile {
39 struct curl_slist *request_header;
46 uint64_t content_length;
63 gpt_import_on_finished on_finished;
69 static GptImportFile *gpt_import_file_unref(GptImportFile *f) {
74 curl_glue_remove_and_free(f->import->glue, f->curl);
75 curl_slist_free_all(f->request_header);
77 safe_close(f->disk_fd);
95 DEFINE_TRIVIAL_CLEANUP_FUNC(GptImportFile*, gpt_import_file_unref);
97 static void gpt_import_finish(GptImport *import, int error) {
100 if (import->finished)
103 import->finished = true;
105 if (import->on_finished)
106 import->on_finished(import, error, import->userdata);
108 sd_event_exit(import->event, error);
111 static void gpt_import_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result) {
112 GptImportFile *f = NULL;
118 if (curl_easy_getinfo(curl, CURLINFO_PRIVATE, &f) != CURLE_OK)
126 if (result != CURLE_OK) {
127 log_error("Transfer failed: %s", curl_easy_strerror(result));
132 code = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
133 if (code != CURLE_OK) {
134 log_error("Failed to retrieve response code: %s", curl_easy_strerror(code));
137 } else if (status == 304) {
138 log_info("File unmodified.");
141 } else if (status >= 300) {
142 log_error("HTTP request to %s failed with code %li.", f->url, status);
145 } else if (status < 200) {
146 log_error("HTTP request to %s finished with unexpected code %li.", f->url, status);
151 if (f->disk_fd < 0) {
152 log_error("No data received.");
157 if (f->content_length != (uint64_t) -1 &&
158 f->content_length != f->written) {
159 log_error("Download truncated.");
165 (void) fsetxattr(f->disk_fd, "user.etag", f->etag, strlen(f->etag), XATTR_CREATE);
168 struct timespec ut[2];
170 timespec_store(&ut[0], (usec_t) f->mtime * USEC_PER_SEC);
173 (void) futimens(f->disk_fd, ut);
176 if (fstat(f->disk_fd, &st) < 0) {
177 r = log_error_errno(errno, "Failed to stat file: %m");
182 (void) fchmod(f->disk_fd, st.st_mode & 07444);
184 f->disk_fd = safe_close(f->disk_fd);
186 assert(f->temp_path);
187 assert(f->final_path);
189 r = rename(f->temp_path, f->final_path);
191 r = log_error_errno(errno, "Failed to move GPT file into place: %m");
198 gpt_import_finish(f->import, r);
201 static int gpt_import_file_open_disk(GptImportFile *f) {
209 assert(f->final_path);
212 r = tempfn_random(f->final_path, &f->temp_path);
217 f->disk_fd = open(f->temp_path, O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC|O_WRONLY, 0644);
219 return log_error_errno(errno, "Failed to create %s: %m", f->temp_path);
224 static size_t gpt_import_file_write_callback(void *contents, size_t size, size_t nmemb, void *userdata) {
225 GptImportFile *f = userdata;
226 size_t sz = size * nmemb;
233 r = gpt_import_file_open_disk(f);
237 if (f->written + sz < f->written) {
238 log_error("File too large, overflow");
243 if (f->content_length != (uint64_t) -1 &&
244 f->written + sz > f->content_length) {
245 log_error("Content length incorrect.");
250 n = write(f->disk_fd, contents, sz);
252 log_error_errno(errno, "Failed to write file: %m");
256 if ((size_t) n < sz) {
257 log_error("Short write");
267 gpt_import_finish(f->import, r);
271 static size_t gpt_import_file_header_callback(void *contents, size_t size, size_t nmemb, void *userdata) {
272 GptImportFile *f = userdata;
273 size_t sz = size * nmemb;
274 _cleanup_free_ char *length = NULL, *last_modified = NULL;
281 r = curl_header_strdup(contents, sz, "ETag:", &etag);
290 if (streq_ptr(f->old_etag, f->etag)) {
291 log_info("Image already up to date. Finishing.");
292 gpt_import_finish(f->import, 0);
299 r = curl_header_strdup(contents, sz, "Content-Length:", &length);
305 (void) safe_atou64(length, &f->content_length);
309 r = curl_header_strdup(contents, sz, "Last-Modified:", &last_modified);
315 (void) curl_parse_http_time(last_modified, &f->mtime);
322 gpt_import_finish(f->import, r);
326 static int gpt_import_file_begin(GptImportFile *f) {
332 log_info("Getting %s.", f->url);
334 r = curl_glue_make(&f->curl, f->url, f);
341 hdr = strappenda("If-None-Match: ", f->old_etag);
343 f->request_header = curl_slist_new(hdr, NULL);
344 if (!f->request_header)
347 if (curl_easy_setopt(f->curl, CURLOPT_HTTPHEADER, f->request_header) != CURLE_OK)
351 if (curl_easy_setopt(f->curl, CURLOPT_WRITEFUNCTION, gpt_import_file_write_callback) != CURLE_OK)
354 if (curl_easy_setopt(f->curl, CURLOPT_WRITEDATA, f) != CURLE_OK)
357 if (curl_easy_setopt(f->curl, CURLOPT_HEADERFUNCTION, gpt_import_file_header_callback) != CURLE_OK)
360 if (curl_easy_setopt(f->curl, CURLOPT_HEADERDATA, f) != CURLE_OK)
363 r = curl_glue_add(f->import->glue, f->curl);
370 int gpt_import_new(GptImport **import, sd_event *event, gpt_import_on_finished on_finished, void *userdata) {
371 _cleanup_(gpt_import_unrefp) GptImport *i = NULL;
376 i = new0(GptImport, 1);
380 i->on_finished = on_finished;
381 i->userdata = userdata;
384 i->event = sd_event_ref(event);
386 r = sd_event_default(&i->event);
391 r = curl_glue_new(&i->glue, i->event);
395 i->glue->on_finished = gpt_import_curl_on_finished;
396 i->glue->userdata = i;
404 GptImport* gpt_import_unref(GptImport *import) {
410 while ((f = hashmap_steal_first(import->files)))
411 gpt_import_file_unref(f);
412 hashmap_free(import->files);
414 curl_glue_unref(import->glue);
415 sd_event_unref(import->event);
422 int gpt_import_cancel(GptImport *import, const char *url) {
428 f = hashmap_remove(import->files, url);
432 gpt_import_file_unref(f);
436 int gpt_import_pull(GptImport *import, const char *url, const char *local, bool force_local) {
437 _cleanup_(gpt_import_file_unrefp) GptImportFile *f = NULL;
443 assert(gpt_url_is_valid(url));
444 assert(machine_name_is_valid(local));
446 if (hashmap_get(import->files, url))
449 r = hashmap_ensure_allocated(&import->files, &string_hash_ops);
453 f = new0(GptImportFile, 1);
459 f->content_length = (uint64_t) -1;
461 f->url = strdup(url);
465 f->local = strdup(local);
469 f->final_path = strjoin("/var/lib/container/", local, ".gpt", NULL);
473 n = getxattr(f->final_path, "user.etag", etag, sizeof(etag));
475 f->old_etag = strndup(etag, n);
480 r = hashmap_put(import->files, f->url, f);
484 r = gpt_import_file_begin(f);
486 gpt_import_cancel(import, f->url);
495 bool gpt_url_is_valid(const char *url) {
499 if (!startswith(url, "http://") &&
500 !startswith(url, "https://"))
503 return ascii_is_valid(url);