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], f->mtime);
173 (void) futimens(f->disk_fd, ut);
175 fd_setcrtime(f->disk_fd, f->mtime);
178 if (fstat(f->disk_fd, &st) < 0) {
179 r = log_error_errno(errno, "Failed to stat file: %m");
184 (void) fchmod(f->disk_fd, st.st_mode & 07444);
186 f->disk_fd = safe_close(f->disk_fd);
188 assert(f->temp_path);
189 assert(f->final_path);
191 r = rename(f->temp_path, f->final_path);
193 r = log_error_errno(errno, "Failed to move GPT file into place: %m");
200 gpt_import_finish(f->import, r);
203 static int gpt_import_file_open_disk(GptImportFile *f) {
211 assert(f->final_path);
214 r = tempfn_random(f->final_path, &f->temp_path);
219 f->disk_fd = open(f->temp_path, O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC|O_WRONLY, 0644);
221 return log_error_errno(errno, "Failed to create %s: %m", f->temp_path);
226 static size_t gpt_import_file_write_callback(void *contents, size_t size, size_t nmemb, void *userdata) {
227 GptImportFile *f = userdata;
228 size_t sz = size * nmemb;
235 r = gpt_import_file_open_disk(f);
239 if (f->written + sz < f->written) {
240 log_error("File too large, overflow");
245 if (f->content_length != (uint64_t) -1 &&
246 f->written + sz > f->content_length) {
247 log_error("Content length incorrect.");
252 n = write(f->disk_fd, contents, sz);
254 log_error_errno(errno, "Failed to write file: %m");
258 if ((size_t) n < sz) {
259 log_error("Short write");
269 gpt_import_finish(f->import, r);
273 static size_t gpt_import_file_header_callback(void *contents, size_t size, size_t nmemb, void *userdata) {
274 GptImportFile *f = userdata;
275 size_t sz = size * nmemb;
276 _cleanup_free_ char *length = NULL, *last_modified = NULL;
283 r = curl_header_strdup(contents, sz, "ETag:", &etag);
292 if (streq_ptr(f->old_etag, f->etag)) {
293 log_info("Image already up to date. Finishing.");
294 gpt_import_finish(f->import, 0);
301 r = curl_header_strdup(contents, sz, "Content-Length:", &length);
307 (void) safe_atou64(length, &f->content_length);
311 r = curl_header_strdup(contents, sz, "Last-Modified:", &last_modified);
317 (void) curl_parse_http_time(last_modified, &f->mtime);
324 gpt_import_finish(f->import, r);
328 static int gpt_import_file_begin(GptImportFile *f) {
334 log_info("Getting %s.", f->url);
336 r = curl_glue_make(&f->curl, f->url, f);
343 hdr = strappenda("If-None-Match: ", f->old_etag);
345 f->request_header = curl_slist_new(hdr, NULL);
346 if (!f->request_header)
349 if (curl_easy_setopt(f->curl, CURLOPT_HTTPHEADER, f->request_header) != CURLE_OK)
353 if (curl_easy_setopt(f->curl, CURLOPT_WRITEFUNCTION, gpt_import_file_write_callback) != CURLE_OK)
356 if (curl_easy_setopt(f->curl, CURLOPT_WRITEDATA, f) != CURLE_OK)
359 if (curl_easy_setopt(f->curl, CURLOPT_HEADERFUNCTION, gpt_import_file_header_callback) != CURLE_OK)
362 if (curl_easy_setopt(f->curl, CURLOPT_HEADERDATA, f) != CURLE_OK)
365 r = curl_glue_add(f->import->glue, f->curl);
372 int gpt_import_new(GptImport **import, sd_event *event, gpt_import_on_finished on_finished, void *userdata) {
373 _cleanup_(gpt_import_unrefp) GptImport *i = NULL;
378 i = new0(GptImport, 1);
382 i->on_finished = on_finished;
383 i->userdata = userdata;
386 i->event = sd_event_ref(event);
388 r = sd_event_default(&i->event);
393 r = curl_glue_new(&i->glue, i->event);
397 i->glue->on_finished = gpt_import_curl_on_finished;
398 i->glue->userdata = i;
406 GptImport* gpt_import_unref(GptImport *import) {
412 while ((f = hashmap_steal_first(import->files)))
413 gpt_import_file_unref(f);
414 hashmap_free(import->files);
416 curl_glue_unref(import->glue);
417 sd_event_unref(import->event);
424 int gpt_import_cancel(GptImport *import, const char *url) {
430 f = hashmap_remove(import->files, url);
434 gpt_import_file_unref(f);
438 int gpt_import_pull(GptImport *import, const char *url, const char *local, bool force_local) {
439 _cleanup_(gpt_import_file_unrefp) GptImportFile *f = NULL;
445 assert(gpt_url_is_valid(url));
446 assert(machine_name_is_valid(local));
448 if (hashmap_get(import->files, url))
451 r = hashmap_ensure_allocated(&import->files, &string_hash_ops);
455 f = new0(GptImportFile, 1);
461 f->content_length = (uint64_t) -1;
463 f->url = strdup(url);
467 f->local = strdup(local);
471 f->final_path = strjoin("/var/lib/container/", local, ".gpt", NULL);
475 n = getxattr(f->final_path, "user.etag", etag, sizeof(etag));
477 f->old_etag = strndup(etag, n);
482 r = hashmap_put(import->files, f->url, f);
486 r = gpt_import_file_begin(f);
488 gpt_import_cancel(import, f->url);
497 bool gpt_url_is_valid(const char *url) {
501 if (!startswith(url, "http://") &&
502 !startswith(url, "https://"))
505 return ascii_is_valid(url);