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