]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/import/import-raw.c
build-sys: add libsystemd-fw where needed
[thirdparty/systemd.git] / src / import / import-raw.c
CommitLineData
90199220
LP
1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3/***
4 This file is part of systemd.
5
6 Copyright 2014 Lennart Poettering
7
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.
12
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.
17
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/>.
20***/
21
22#include <sys/xattr.h>
dfd1520d 23#include <linux/fs.h>
90199220
LP
24#include <curl/curl.h>
25
26#include "hashmap.h"
27#include "utf8.h"
28#include "curl-util.h"
aceac2f0 29#include "import-raw.h"
8620a9a3
LP
30#include "strv.h"
31#include "copy.h"
90199220 32
aceac2f0 33typedef struct RawImportFile RawImportFile;
90199220 34
aceac2f0
LP
35struct RawImportFile {
36 RawImport *import;
90199220
LP
37
38 char *url;
39 char *local;
40
41 CURL *curl;
42 struct curl_slist *request_header;
43
44 char *temp_path;
45 char *final_path;
46 char *etag;
8620a9a3 47 char **old_etags;
90199220
LP
48
49 uint64_t content_length;
50 uint64_t written;
51
5fa89b2c 52 usec_t mtime;
90199220
LP
53
54 bool force_local;
55 bool done;
56
57 int disk_fd;
58};
59
aceac2f0 60struct RawImport {
90199220
LP
61 sd_event *event;
62 CurlGlue *glue;
63
087682d1 64 char *image_root;
90199220
LP
65 Hashmap *files;
66
aceac2f0 67 raw_import_on_finished on_finished;
90199220
LP
68 void *userdata;
69
70 bool finished;
71};
72
8620a9a3
LP
73#define FILENAME_ESCAPE "/.#\"\'"
74
aceac2f0 75static RawImportFile *raw_import_file_unref(RawImportFile *f) {
90199220
LP
76 if (!f)
77 return NULL;
78
79 if (f->import)
80 curl_glue_remove_and_free(f->import->glue, f->curl);
81 curl_slist_free_all(f->request_header);
82
83 safe_close(f->disk_fd);
84
85 free(f->final_path);
86
87 if (f->temp_path) {
88 unlink(f->temp_path);
89 free(f->temp_path);
90 }
91
92 free(f->url);
93 free(f->local);
94 free(f->etag);
8620a9a3 95 strv_free(f->old_etags);
90199220
LP
96 free(f);
97
98 return NULL;
99}
100
aceac2f0 101DEFINE_TRIVIAL_CLEANUP_FUNC(RawImportFile*, raw_import_file_unref);
90199220 102
aceac2f0 103static void raw_import_finish(RawImport *import, int error) {
90199220
LP
104 assert(import);
105
106 if (import->finished)
107 return;
108
109 import->finished = true;
110
111 if (import->on_finished)
112 import->on_finished(import, error, import->userdata);
113 else
114 sd_event_exit(import->event, error);
115}
116
aceac2f0 117static int raw_import_file_make_final_path(RawImportFile *f) {
8620a9a3
LP
118 _cleanup_free_ char *escaped_url = NULL, *escaped_etag = NULL;
119
120 assert(f);
121
122 if (f->final_path)
123 return 0;
124
125 escaped_url = xescape(f->url, FILENAME_ESCAPE);
126 if (!escaped_url)
127 return -ENOMEM;
128
129 if (f->etag) {
130 escaped_etag = xescape(f->etag, FILENAME_ESCAPE);
131 if (!escaped_etag)
132 return -ENOMEM;
133
aceac2f0 134 f->final_path = strjoin(f->import->image_root, "/.raw-", escaped_url, ".", escaped_etag, ".raw", NULL);
8620a9a3 135 } else
aceac2f0 136 f->final_path = strjoin(f->import->image_root, "/.raw-", escaped_url, ".raw", NULL);
8620a9a3
LP
137 if (!f->final_path)
138 return -ENOMEM;
139
140 return 0;
141}
142
aceac2f0 143static void raw_import_file_success(RawImportFile *f) {
8620a9a3
LP
144 int r;
145
146 assert(f);
147
148 f->done = true;
149
150 if (f->local) {
151 _cleanup_free_ char *tp = NULL;
152 _cleanup_close_ int dfd = -1;
153 const char *p;
154
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");
158 goto finish;
159 }
160 } else {
aceac2f0 161 r = raw_import_file_make_final_path(f);
8620a9a3
LP
162 if (r < 0) {
163 log_oom();
164 goto finish;
165 }
166
167 f->disk_fd = open(f->final_path, O_RDONLY|O_NOCTTY|O_CLOEXEC);
168 if (f->disk_fd < 0) {
dfd1520d 169 r = log_error_errno(errno, "Failed to open vendor image: %m");
8620a9a3
LP
170 goto finish;
171 }
172 }
173
aceac2f0 174 p = strappenda(f->import->image_root, "/", f->local, ".raw");
8620a9a3
LP
175 if (f->force_local)
176 (void) rm_rf_dangerous(p, false, true, false);
177
178 r = tempfn_random(p, &tp);
179 if (r < 0) {
180 log_oom();
181 goto finish;
182 }
183
184 dfd = open(tp, O_WRONLY|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664);
185 if (dfd < 0) {
186 r = log_error_errno(errno, "Failed to create writable copy of image: %m");
187 goto finish;
188 }
189
dfd1520d
LP
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
193 * writes. */
194 r = chattr_fd(dfd, true, FS_NOCOW_FL);
195 if (r < 0)
196 log_warning_errno(errno, "Failed to set file attributes on %s: %m", f->temp_path);
197
8620a9a3
LP
198 r = copy_bytes(f->disk_fd, dfd, (off_t) -1, true);
199 if (r < 0) {
200 log_error_errno(r, "Failed to make writable copy of image: %m");
201 unlink(tp);
202 goto finish;
203 }
204
205 (void) copy_times(f->disk_fd, dfd);
206 (void) copy_xattr(f->disk_fd, dfd);
207
208 dfd = safe_close(dfd);
209
210 r = rename(tp, p);
211 if (r < 0) {
212 r = log_error_errno(errno, "Failed to move writable image into place: %m");
213 unlink(tp);
214 goto finish;
215 }
216
217 log_info("Created new local image %s.", p);
218 }
219
220 f->disk_fd = safe_close(f->disk_fd);
221 r = 0;
222
223finish:
aceac2f0 224 raw_import_finish(f->import, r);
8620a9a3
LP
225}
226
aceac2f0
LP
227static void raw_import_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result) {
228 RawImportFile *f = NULL;
90199220
LP
229 struct stat st;
230 CURLcode code;
231 long status;
232 int r;
233
234 if (curl_easy_getinfo(curl, CURLINFO_PRIVATE, &f) != CURLE_OK)
235 return;
236
8620a9a3 237 if (!f || f->done)
90199220
LP
238 return;
239
240 f->done = true;
241
242 if (result != CURLE_OK) {
243 log_error("Transfer failed: %s", curl_easy_strerror(result));
244 r = -EIO;
245 goto fail;
246 }
247
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));
251 r = -EIO;
252 goto fail;
253 } else if (status == 304) {
8620a9a3 254 log_info("Image already downloaded. Skipping download.");
aceac2f0 255 raw_import_file_success(f);
8620a9a3 256 return;
90199220
LP
257 } else if (status >= 300) {
258 log_error("HTTP request to %s failed with code %li.", f->url, status);
259 r = -EIO;
260 goto fail;
261 } else if (status < 200) {
262 log_error("HTTP request to %s finished with unexpected code %li.", f->url, status);
263 r = -EIO;
264 goto fail;
265 }
266
267 if (f->disk_fd < 0) {
268 log_error("No data received.");
269 r = -EIO;
270 goto fail;
271 }
272
273 if (f->content_length != (uint64_t) -1 &&
274 f->content_length != f->written) {
275 log_error("Download truncated.");
276 r = -EIO;
277 goto fail;
278 }
279
280 if (f->etag)
8620a9a3
LP
281 (void) fsetxattr(f->disk_fd, "user.source_etag", f->etag, strlen(f->etag), 0);
282 if (f->url)
283 (void) fsetxattr(f->disk_fd, "user.source_url", f->url, strlen(f->url), 0);
90199220
LP
284
285 if (f->mtime != 0) {
286 struct timespec ut[2];
287
5fa89b2c 288 timespec_store(&ut[0], f->mtime);
90199220 289 ut[1] = ut[0];
90199220 290 (void) futimens(f->disk_fd, ut);
10f9c755
LP
291
292 fd_setcrtime(f->disk_fd, f->mtime);
90199220
LP
293 }
294
295 if (fstat(f->disk_fd, &st) < 0) {
296 r = log_error_errno(errno, "Failed to stat file: %m");
297 goto fail;
298 }
299
300 /* Mark read-only */
301 (void) fchmod(f->disk_fd, st.st_mode & 07444);
302
90199220
LP
303 assert(f->temp_path);
304 assert(f->final_path);
305
306 r = rename(f->temp_path, f->final_path);
307 if (r < 0) {
aceac2f0 308 r = log_error_errno(errno, "Failed to move RAW file into place: %m");
90199220
LP
309 goto fail;
310 }
311
8620a9a3
LP
312 free(f->temp_path);
313 f->temp_path = NULL;
314
315 log_info("Completed writing vendor image %s.", f->final_path);
316
aceac2f0 317 raw_import_file_success(f);
8620a9a3 318 return;
90199220
LP
319
320fail:
aceac2f0 321 raw_import_finish(f->import, r);
90199220
LP
322}
323
aceac2f0 324static int raw_import_file_open_disk_for_write(RawImportFile *f) {
90199220
LP
325 int r;
326
327 assert(f);
328
329 if (f->disk_fd >= 0)
330 return 0;
331
aceac2f0 332 r = raw_import_file_make_final_path(f);
8620a9a3
LP
333 if (r < 0)
334 return log_oom();
90199220
LP
335
336 if (!f->temp_path) {
337 r = tempfn_random(f->final_path, &f->temp_path);
338 if (r < 0)
339 return log_oom();
340 }
341
8620a9a3 342 f->disk_fd = open(f->temp_path, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0644);
90199220
LP
343 if (f->disk_fd < 0)
344 return log_error_errno(errno, "Failed to create %s: %m", f->temp_path);
345
346 return 0;
347}
348
aceac2f0
LP
349static size_t raw_import_file_write_callback(void *contents, size_t size, size_t nmemb, void *userdata) {
350 RawImportFile *f = userdata;
90199220
LP
351 size_t sz = size * nmemb;
352 ssize_t n;
353 int r;
354
355 assert(contents);
356 assert(f);
357
8620a9a3
LP
358 if (f->done) {
359 r = -ESTALE;
360 goto fail;
361 }
362
aceac2f0 363 r = raw_import_file_open_disk_for_write(f);
90199220
LP
364 if (r < 0)
365 goto fail;
366
367 if (f->written + sz < f->written) {
368 log_error("File too large, overflow");
369 r = -EOVERFLOW;
370 goto fail;
371 }
372
373 if (f->content_length != (uint64_t) -1 &&
374 f->written + sz > f->content_length) {
375 log_error("Content length incorrect.");
376 r = -EFBIG;
377 goto fail;
378 }
379
380 n = write(f->disk_fd, contents, sz);
381 if (n < 0) {
382 log_error_errno(errno, "Failed to write file: %m");
383 goto fail;
384 }
385
386 if ((size_t) n < sz) {
387 log_error("Short write");
388 r = -EIO;
389 goto fail;
390 }
391
392 f->written += sz;
393
394 return sz;
395
396fail:
aceac2f0 397 raw_import_finish(f->import, r);
90199220
LP
398 return 0;
399}
400
aceac2f0
LP
401static size_t raw_import_file_header_callback(void *contents, size_t size, size_t nmemb, void *userdata) {
402 RawImportFile *f = userdata;
90199220
LP
403 size_t sz = size * nmemb;
404 _cleanup_free_ char *length = NULL, *last_modified = NULL;
405 char *etag;
406 int r;
407
408 assert(contents);
409 assert(f);
410
8620a9a3
LP
411 if (f->done) {
412 r = -ESTALE;
413 goto fail;
414 }
415
90199220
LP
416 r = curl_header_strdup(contents, sz, "ETag:", &etag);
417 if (r < 0) {
418 log_oom();
419 goto fail;
420 }
421 if (r > 0) {
422 free(f->etag);
423 f->etag = etag;
424
8620a9a3
LP
425 if (strv_contains(f->old_etags, f->etag)) {
426 log_info("Image already downloaded. Skipping download.");
aceac2f0 427 raw_import_file_success(f);
90199220
LP
428 return sz;
429 }
430
431 return sz;
432 }
433
434 r = curl_header_strdup(contents, sz, "Content-Length:", &length);
435 if (r < 0) {
436 log_oom();
437 goto fail;
438 }
439 if (r > 0) {
440 (void) safe_atou64(length, &f->content_length);
441 return sz;
442 }
443
444 r = curl_header_strdup(contents, sz, "Last-Modified:", &last_modified);
445 if (r < 0) {
446 log_oom();
447 goto fail;
448 }
449 if (r > 0) {
450 (void) curl_parse_http_time(last_modified, &f->mtime);
451 return sz;
452 }
453
454 return sz;
455
456fail:
aceac2f0 457 raw_import_finish(f->import, r);
90199220
LP
458 return 0;
459}
460
8620a9a3
LP
461static bool etag_is_valid(const char *etag) {
462
463 if (!endswith(etag, "\""))
464 return false;
465
466 if (!startswith(etag, "\"") && !startswith(etag, "W/\""))
467 return false;
468
469 return true;
470}
471
aceac2f0 472static int raw_import_file_find_old_etags(RawImportFile *f) {
8620a9a3
LP
473 _cleanup_free_ char *escaped_url = NULL;
474 _cleanup_closedir_ DIR *d = NULL;
475 struct dirent *de;
476 int r;
477
478 escaped_url = xescape(f->url, FILENAME_ESCAPE);
479 if (!escaped_url)
480 return -ENOMEM;
481
087682d1 482 d = opendir(f->import->image_root);
8620a9a3
LP
483 if (!d) {
484 if (errno == ENOENT)
485 return 0;
486
487 return -errno;
488 }
489
490 FOREACH_DIRENT_ALL(de, d, return -errno) {
491 const char *a, *b;
492 char *u;
493
494 if (de->d_type != DT_UNKNOWN &&
495 de->d_type != DT_REG)
496 continue;
497
aceac2f0 498 a = startswith(de->d_name, ".raw-");
8620a9a3
LP
499 if (!a)
500 continue;
501
502 a = startswith(a, escaped_url);
503 if (!a)
504 continue;
505
506 a = startswith(a, ".");
507 if (!a)
508 continue;
509
aceac2f0 510 b = endswith(de->d_name, ".raw");
8620a9a3
LP
511 if (!b)
512 continue;
513
514 if (a >= b)
515 continue;
516
517 u = cunescape_length(a, b - a);
518 if (!u)
519 return -ENOMEM;
520
521 if (!etag_is_valid(u)) {
522 free(u);
523 continue;
524 }
525
526 r = strv_consume(&f->old_etags, u);
527 if (r < 0)
528 return r;
529 }
530
531 return 0;
532}
533
aceac2f0 534static int raw_import_file_begin(RawImportFile *f) {
90199220
LP
535 int r;
536
537 assert(f);
538 assert(!f->curl);
539
540 log_info("Getting %s.", f->url);
541
aceac2f0 542 r = raw_import_file_find_old_etags(f);
8620a9a3
LP
543 if (r < 0)
544 return r;
545
90199220
LP
546 r = curl_glue_make(&f->curl, f->url, f);
547 if (r < 0)
548 return r;
549
8620a9a3
LP
550 if (!strv_isempty(f->old_etags)) {
551 _cleanup_free_ char *cc = NULL, *hdr = NULL;
90199220 552
8620a9a3
LP
553 cc = strv_join(f->old_etags, ", ");
554 if (!cc)
555 return -ENOMEM;
556
557 hdr = strappend("If-None-Match: ", cc);
558 if (!hdr)
559 return -ENOMEM;
90199220
LP
560
561 f->request_header = curl_slist_new(hdr, NULL);
562 if (!f->request_header)
563 return -ENOMEM;
564
565 if (curl_easy_setopt(f->curl, CURLOPT_HTTPHEADER, f->request_header) != CURLE_OK)
566 return -EIO;
567 }
568
aceac2f0 569 if (curl_easy_setopt(f->curl, CURLOPT_WRITEFUNCTION, raw_import_file_write_callback) != CURLE_OK)
90199220
LP
570 return -EIO;
571
572 if (curl_easy_setopt(f->curl, CURLOPT_WRITEDATA, f) != CURLE_OK)
573 return -EIO;
574
aceac2f0 575 if (curl_easy_setopt(f->curl, CURLOPT_HEADERFUNCTION, raw_import_file_header_callback) != CURLE_OK)
90199220
LP
576 return -EIO;
577
578 if (curl_easy_setopt(f->curl, CURLOPT_HEADERDATA, f) != CURLE_OK)
579 return -EIO;
580
581 r = curl_glue_add(f->import->glue, f->curl);
582 if (r < 0)
583 return r;
584
585 return 0;
586}
587
aceac2f0
LP
588int raw_import_new(RawImport **import, sd_event *event, const char *image_root, raw_import_on_finished on_finished, void *userdata) {
589 _cleanup_(raw_import_unrefp) RawImport *i = NULL;
90199220
LP
590 int r;
591
592 assert(import);
087682d1 593 assert(image_root);
90199220 594
aceac2f0 595 i = new0(RawImport, 1);
90199220
LP
596 if (!i)
597 return -ENOMEM;
598
599 i->on_finished = on_finished;
600 i->userdata = userdata;
601
087682d1
LP
602 i->image_root = strdup(image_root);
603 if (!i->image_root)
604 return -ENOMEM;
605
90199220
LP
606 if (event)
607 i->event = sd_event_ref(event);
608 else {
609 r = sd_event_default(&i->event);
610 if (r < 0)
611 return r;
612 }
613
614 r = curl_glue_new(&i->glue, i->event);
615 if (r < 0)
616 return r;
617
aceac2f0 618 i->glue->on_finished = raw_import_curl_on_finished;
90199220
LP
619 i->glue->userdata = i;
620
621 *import = i;
622 i = NULL;
623
624 return 0;
625}
626
aceac2f0
LP
627RawImport* raw_import_unref(RawImport *import) {
628 RawImportFile *f;
90199220
LP
629
630 if (!import)
631 return NULL;
632
633 while ((f = hashmap_steal_first(import->files)))
aceac2f0 634 raw_import_file_unref(f);
90199220
LP
635 hashmap_free(import->files);
636
637 curl_glue_unref(import->glue);
638 sd_event_unref(import->event);
639
087682d1 640 free(import->image_root);
90199220
LP
641 free(import);
642
643 return NULL;
644}
645
aceac2f0
LP
646int raw_import_cancel(RawImport *import, const char *url) {
647 RawImportFile *f;
90199220
LP
648
649 assert(import);
650 assert(url);
651
652 f = hashmap_remove(import->files, url);
653 if (!f)
654 return 0;
655
aceac2f0 656 raw_import_file_unref(f);
90199220
LP
657 return 1;
658}
659
aceac2f0
LP
660int raw_import_pull(RawImport *import, const char *url, const char *local, bool force_local) {
661 _cleanup_(raw_import_file_unrefp) RawImportFile *f = NULL;
90199220
LP
662 int r;
663
664 assert(import);
aceac2f0 665 assert(raw_url_is_valid(url));
8620a9a3 666 assert(!local || machine_name_is_valid(local));
90199220
LP
667
668 if (hashmap_get(import->files, url))
669 return -EEXIST;
670
671 r = hashmap_ensure_allocated(&import->files, &string_hash_ops);
672 if (r < 0)
673 return r;
674
aceac2f0 675 f = new0(RawImportFile, 1);
90199220
LP
676 if (!f)
677 return -ENOMEM;
678
679 f->import = import;
680 f->disk_fd = -1;
681 f->content_length = (uint64_t) -1;
682
683 f->url = strdup(url);
684 if (!f->url)
685 return -ENOMEM;
686
8620a9a3
LP
687 if (local) {
688 f->local = strdup(local);
689 if (!f->local)
90199220 690 return -ENOMEM;
8620a9a3
LP
691
692 f->force_local = force_local;
90199220
LP
693 }
694
695 r = hashmap_put(import->files, f->url, f);
696 if (r < 0)
697 return r;
698
aceac2f0 699 r = raw_import_file_begin(f);
90199220 700 if (r < 0) {
aceac2f0 701 raw_import_cancel(import, f->url);
90199220
LP
702 f = NULL;
703 return r;
704 }
705
706 f = NULL;
707 return 0;
708}
709
aceac2f0 710bool raw_url_is_valid(const char *url) {
90199220
LP
711 if (isempty(url))
712 return false;
713
714 if (!startswith(url, "http://") &&
715 !startswith(url, "https://"))
716 return false;
717
718 return ascii_is_valid(url);
719}