]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/import/import-raw.c
import-raw: when downloading raw images, generate sparse files if we can
[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 24#include <curl/curl.h>
49bb233b 25#include <lzma.h>
90199220
LP
26
27#include "hashmap.h"
28#include "utf8.h"
29#include "curl-util.h"
edce2aed 30#include "qcow2-util.h"
aceac2f0 31#include "import-raw.h"
8620a9a3
LP
32#include "strv.h"
33#include "copy.h"
90199220 34
aceac2f0 35typedef struct RawImportFile RawImportFile;
90199220 36
aceac2f0
LP
37struct RawImportFile {
38 RawImport *import;
90199220
LP
39
40 char *url;
41 char *local;
42
43 CURL *curl;
44 struct curl_slist *request_header;
45
46 char *temp_path;
47 char *final_path;
48 char *etag;
8620a9a3 49 char **old_etags;
90199220
LP
50
51 uint64_t content_length;
49bb233b
LP
52 uint64_t written_compressed;
53 uint64_t written_uncompressed;
54
55 void *payload;
56 size_t payload_size;
90199220 57
5fa89b2c 58 usec_t mtime;
90199220
LP
59
60 bool force_local;
61 bool done;
62
63 int disk_fd;
49bb233b
LP
64
65 lzma_stream lzma;
66 bool compressed;
90199220
LP
67};
68
aceac2f0 69struct RawImport {
90199220
LP
70 sd_event *event;
71 CurlGlue *glue;
72
087682d1 73 char *image_root;
90199220
LP
74 Hashmap *files;
75
aceac2f0 76 raw_import_on_finished on_finished;
90199220
LP
77 void *userdata;
78
79 bool finished;
80};
81
8620a9a3
LP
82#define FILENAME_ESCAPE "/.#\"\'"
83
49bb233b
LP
84#define RAW_MAX_SIZE (1024LLU*1024LLU*1024LLU*8) /* 8 GB */
85
aceac2f0 86static RawImportFile *raw_import_file_unref(RawImportFile *f) {
90199220
LP
87 if (!f)
88 return NULL;
89
90 if (f->import)
91 curl_glue_remove_and_free(f->import->glue, f->curl);
92 curl_slist_free_all(f->request_header);
93
94 safe_close(f->disk_fd);
95
96 free(f->final_path);
97
98 if (f->temp_path) {
99 unlink(f->temp_path);
100 free(f->temp_path);
101 }
102
103 free(f->url);
104 free(f->local);
105 free(f->etag);
8620a9a3 106 strv_free(f->old_etags);
49bb233b 107 free(f->payload);
90199220
LP
108 free(f);
109
110 return NULL;
111}
112
aceac2f0 113DEFINE_TRIVIAL_CLEANUP_FUNC(RawImportFile*, raw_import_file_unref);
90199220 114
aceac2f0 115static void raw_import_finish(RawImport *import, int error) {
90199220
LP
116 assert(import);
117
118 if (import->finished)
119 return;
120
121 import->finished = true;
122
123 if (import->on_finished)
124 import->on_finished(import, error, import->userdata);
125 else
126 sd_event_exit(import->event, error);
127}
128
aceac2f0 129static int raw_import_file_make_final_path(RawImportFile *f) {
8620a9a3
LP
130 _cleanup_free_ char *escaped_url = NULL, *escaped_etag = NULL;
131
132 assert(f);
133
134 if (f->final_path)
135 return 0;
136
137 escaped_url = xescape(f->url, FILENAME_ESCAPE);
138 if (!escaped_url)
139 return -ENOMEM;
140
141 if (f->etag) {
142 escaped_etag = xescape(f->etag, FILENAME_ESCAPE);
143 if (!escaped_etag)
144 return -ENOMEM;
145
aceac2f0 146 f->final_path = strjoin(f->import->image_root, "/.raw-", escaped_url, ".", escaped_etag, ".raw", NULL);
8620a9a3 147 } else
aceac2f0 148 f->final_path = strjoin(f->import->image_root, "/.raw-", escaped_url, ".raw", NULL);
8620a9a3
LP
149 if (!f->final_path)
150 return -ENOMEM;
151
152 return 0;
153}
154
aceac2f0 155static void raw_import_file_success(RawImportFile *f) {
8620a9a3
LP
156 int r;
157
158 assert(f);
159
160 f->done = true;
161
162 if (f->local) {
163 _cleanup_free_ char *tp = NULL;
164 _cleanup_close_ int dfd = -1;
165 const char *p;
166
167 if (f->disk_fd >= 0) {
168 if (lseek(f->disk_fd, SEEK_SET, 0) == (off_t) -1) {
169 r = log_error_errno(errno, "Failed to seek to beginning of vendor image: %m");
170 goto finish;
171 }
172 } else {
aceac2f0 173 r = raw_import_file_make_final_path(f);
8620a9a3
LP
174 if (r < 0) {
175 log_oom();
176 goto finish;
177 }
178
179 f->disk_fd = open(f->final_path, O_RDONLY|O_NOCTTY|O_CLOEXEC);
180 if (f->disk_fd < 0) {
dfd1520d 181 r = log_error_errno(errno, "Failed to open vendor image: %m");
8620a9a3
LP
182 goto finish;
183 }
184 }
185
aceac2f0 186 p = strappenda(f->import->image_root, "/", f->local, ".raw");
8620a9a3
LP
187 if (f->force_local)
188 (void) rm_rf_dangerous(p, false, true, false);
189
190 r = tempfn_random(p, &tp);
191 if (r < 0) {
192 log_oom();
193 goto finish;
194 }
195
196 dfd = open(tp, O_WRONLY|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664);
197 if (dfd < 0) {
198 r = log_error_errno(errno, "Failed to create writable copy of image: %m");
199 goto finish;
200 }
201
dfd1520d
LP
202 /* Turn off COW writing. This should greatly improve
203 * performance on COW file systems like btrfs, since it
204 * reduces fragmentation caused by not allowing in-place
205 * writes. */
206 r = chattr_fd(dfd, true, FS_NOCOW_FL);
207 if (r < 0)
47bc4fd8 208 log_warning_errno(errno, "Failed to set file attributes on %s: %m", tp);
dfd1520d 209
8620a9a3
LP
210 r = copy_bytes(f->disk_fd, dfd, (off_t) -1, true);
211 if (r < 0) {
212 log_error_errno(r, "Failed to make writable copy of image: %m");
213 unlink(tp);
214 goto finish;
215 }
216
217 (void) copy_times(f->disk_fd, dfd);
218 (void) copy_xattr(f->disk_fd, dfd);
219
220 dfd = safe_close(dfd);
221
222 r = rename(tp, p);
223 if (r < 0) {
224 r = log_error_errno(errno, "Failed to move writable image into place: %m");
225 unlink(tp);
226 goto finish;
227 }
228
229 log_info("Created new local image %s.", p);
230 }
231
232 f->disk_fd = safe_close(f->disk_fd);
233 r = 0;
234
235finish:
aceac2f0 236 raw_import_finish(f->import, r);
8620a9a3
LP
237}
238
edce2aed
LP
239static int raw_import_maybe_convert_qcow2(RawImportFile *f) {
240 _cleanup_close_ int converted_fd = -1;
241 _cleanup_free_ char *t = NULL;
242 int r;
243
244 assert(f);
245 assert(f->disk_fd);
246 assert(f->temp_path);
247
248 r = qcow2_detect(f->disk_fd);
249 if (r < 0)
250 return log_error_errno(r, "Failed to detect whether this is a QCOW2 image: %m");
251 if (r == 0)
252 return 0;
253
254 /* This is a QCOW2 image, let's convert it */
255 r = tempfn_random(f->final_path, &t);
256 if (r < 0)
257 return log_oom();
258
259 converted_fd = open(t, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0644);
260 if (converted_fd < 0)
261 return log_error_errno(errno, "Failed to create %s: %m", t);
262
263 r = qcow2_convert(f->disk_fd, converted_fd);
264 if (r < 0) {
265 unlink(t);
266 return log_error_errno(r, "Failed to convert qcow2 image: %m");
267 }
268
269 unlink(f->temp_path);
270 free(f->temp_path);
271
272 f->temp_path = t;
273 t = NULL;
274
275 safe_close(f->disk_fd);
276 f->disk_fd = converted_fd;
277 converted_fd = -1;
278
279 return 1;
280}
281
aceac2f0
LP
282static void raw_import_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result) {
283 RawImportFile *f = NULL;
90199220
LP
284 struct stat st;
285 CURLcode code;
286 long status;
287 int r;
288
289 if (curl_easy_getinfo(curl, CURLINFO_PRIVATE, &f) != CURLE_OK)
290 return;
291
8620a9a3 292 if (!f || f->done)
90199220
LP
293 return;
294
295 f->done = true;
296
297 if (result != CURLE_OK) {
298 log_error("Transfer failed: %s", curl_easy_strerror(result));
299 r = -EIO;
300 goto fail;
301 }
302
303 code = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
304 if (code != CURLE_OK) {
305 log_error("Failed to retrieve response code: %s", curl_easy_strerror(code));
306 r = -EIO;
307 goto fail;
308 } else if (status == 304) {
8620a9a3 309 log_info("Image already downloaded. Skipping download.");
aceac2f0 310 raw_import_file_success(f);
8620a9a3 311 return;
90199220
LP
312 } else if (status >= 300) {
313 log_error("HTTP request to %s failed with code %li.", f->url, status);
314 r = -EIO;
315 goto fail;
316 } else if (status < 200) {
317 log_error("HTTP request to %s finished with unexpected code %li.", f->url, status);
318 r = -EIO;
319 goto fail;
320 }
321
322 if (f->disk_fd < 0) {
323 log_error("No data received.");
324 r = -EIO;
325 goto fail;
326 }
327
328 if (f->content_length != (uint64_t) -1 &&
49bb233b 329 f->content_length != f->written_compressed) {
90199220
LP
330 log_error("Download truncated.");
331 r = -EIO;
332 goto fail;
333 }
334
ff6a7460
LP
335 /* Make sure the file size is right, in case the file was
336 * sparse and we just seeked for the last part */
337 if (ftruncate(f->disk_fd, f->written_uncompressed) < 0) {
338 log_error_errno(errno, "Failed to truncate file: %m");
339 r = -errno;
340 goto fail;
341 }
342
edce2aed
LP
343 r = raw_import_maybe_convert_qcow2(f);
344 if (r < 0)
345 goto fail;
346
90199220 347 if (f->etag)
8620a9a3
LP
348 (void) fsetxattr(f->disk_fd, "user.source_etag", f->etag, strlen(f->etag), 0);
349 if (f->url)
350 (void) fsetxattr(f->disk_fd, "user.source_url", f->url, strlen(f->url), 0);
90199220
LP
351
352 if (f->mtime != 0) {
353 struct timespec ut[2];
354
5fa89b2c 355 timespec_store(&ut[0], f->mtime);
90199220 356 ut[1] = ut[0];
90199220 357 (void) futimens(f->disk_fd, ut);
10f9c755
LP
358
359 fd_setcrtime(f->disk_fd, f->mtime);
90199220
LP
360 }
361
362 if (fstat(f->disk_fd, &st) < 0) {
363 r = log_error_errno(errno, "Failed to stat file: %m");
364 goto fail;
365 }
366
367 /* Mark read-only */
368 (void) fchmod(f->disk_fd, st.st_mode & 07444);
369
90199220
LP
370 assert(f->temp_path);
371 assert(f->final_path);
372
373 r = rename(f->temp_path, f->final_path);
374 if (r < 0) {
aceac2f0 375 r = log_error_errno(errno, "Failed to move RAW file into place: %m");
90199220
LP
376 goto fail;
377 }
378
8620a9a3
LP
379 free(f->temp_path);
380 f->temp_path = NULL;
381
382 log_info("Completed writing vendor image %s.", f->final_path);
383
aceac2f0 384 raw_import_file_success(f);
8620a9a3 385 return;
90199220
LP
386
387fail:
aceac2f0 388 raw_import_finish(f->import, r);
90199220
LP
389}
390
aceac2f0 391static int raw_import_file_open_disk_for_write(RawImportFile *f) {
90199220
LP
392 int r;
393
394 assert(f);
395
396 if (f->disk_fd >= 0)
397 return 0;
398
aceac2f0 399 r = raw_import_file_make_final_path(f);
8620a9a3
LP
400 if (r < 0)
401 return log_oom();
90199220
LP
402
403 if (!f->temp_path) {
404 r = tempfn_random(f->final_path, &f->temp_path);
405 if (r < 0)
406 return log_oom();
407 }
408
8620a9a3 409 f->disk_fd = open(f->temp_path, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0644);
90199220
LP
410 if (f->disk_fd < 0)
411 return log_error_errno(errno, "Failed to create %s: %m", f->temp_path);
412
47bc4fd8
LP
413 r = chattr_fd(f->disk_fd, true, FS_NOCOW_FL);
414 if (r < 0)
415 log_warning_errno(errno, "Failed to set file attributes on %s: %m", f->temp_path);
416
90199220
LP
417 return 0;
418}
419
49bb233b 420static int raw_import_file_write_uncompressed(RawImportFile *f, void *p, size_t sz) {
90199220 421 ssize_t n;
90199220 422
90199220 423 assert(f);
49bb233b
LP
424 assert(p);
425 assert(sz > 0);
426 assert(f->disk_fd >= 0);
90199220 427
49bb233b
LP
428 if (f->written_uncompressed + sz < f->written_uncompressed) {
429 log_error("File too large, overflow");
430 return -EOVERFLOW;
8620a9a3
LP
431 }
432
49bb233b
LP
433 if (f->written_uncompressed + sz > RAW_MAX_SIZE) {
434 log_error("File overly large, refusing");
435 return -EFBIG;
436 }
437
ff6a7460 438 n = sparse_write(f->disk_fd, p, sz, 64);
49bb233b
LP
439 if (n < 0) {
440 log_error_errno(errno, "Failed to write file: %m");
441 return -errno;
442 }
443 if ((size_t) n < sz) {
444 log_error("Short write");
445 return -EIO;
446 }
90199220 447
49bb233b
LP
448 f->written_uncompressed += sz;
449
450 return 0;
451}
452
453static int raw_import_file_write_compressed(RawImportFile *f, void *p, size_t sz) {
454 int r;
455
456 assert(f);
457 assert(p);
458 assert(sz > 0);
459 assert(f->disk_fd >= 0);
460
461 if (f->written_compressed + sz < f->written_compressed) {
90199220 462 log_error("File too large, overflow");
49bb233b 463 return -EOVERFLOW;
90199220
LP
464 }
465
466 if (f->content_length != (uint64_t) -1 &&
49bb233b 467 f->written_compressed + sz > f->content_length) {
90199220 468 log_error("Content length incorrect.");
49bb233b 469 return -EFBIG;
90199220
LP
470 }
471
49bb233b
LP
472 if (!f->compressed) {
473 r = raw_import_file_write_uncompressed(f, p, sz);
474 if (r < 0)
475 return r;
476 } else {
477 f->lzma.next_in = p;
478 f->lzma.avail_in = sz;
479
480 while (f->lzma.avail_in > 0) {
481 uint8_t buffer[16 * 1024];
482 lzma_ret lzr;
483
484 f->lzma.next_out = buffer;
485 f->lzma.avail_out = sizeof(buffer);
486
487 lzr = lzma_code(&f->lzma, LZMA_RUN);
488 if (lzr != LZMA_OK && lzr != LZMA_STREAM_END) {
489 log_error("Decompression error.");
490 return -EIO;
491 }
492
493 r = raw_import_file_write_uncompressed(f, buffer, sizeof(buffer) - f->lzma.avail_out);
494 if (r < 0)
495 return r;
496 }
90199220
LP
497 }
498
49bb233b
LP
499 f->written_compressed += sz;
500
501 return 0;
502}
503
504static int raw_import_file_detect_xz(RawImportFile *f) {
505 static const uint8_t xz_signature[] = {
506 '\xfd', '7', 'z', 'X', 'Z', '\x00'
507 };
508 lzma_ret lzr;
509 int r;
510
511 assert(f);
512
513 if (f->payload_size < sizeof(xz_signature))
514 return 0;
515
516 f->compressed = memcmp(f->payload, xz_signature, sizeof(xz_signature)) == 0;
517 log_debug("Stream is XZ compressed: %s", yes_no(f->compressed));
518
519 if (f->compressed) {
520 lzr = lzma_stream_decoder(&f->lzma, UINT64_MAX, LZMA_TELL_UNSUPPORTED_CHECK);
521 if (lzr != LZMA_OK) {
522 log_error("Failed to initialize LZMA decoder.");
523 return -EIO;
524 }
525 }
526
527 r = raw_import_file_open_disk_for_write(f);
528 if (r < 0)
529 return r;
530
531 r = raw_import_file_write_compressed(f, f->payload, f->payload_size);
532 if (r < 0)
533 return r;
534
535 free(f->payload);
536 f->payload = NULL;
537 f->payload_size = 0;
538
539 return 0;
540}
541
542static size_t raw_import_file_write_callback(void *contents, size_t size, size_t nmemb, void *userdata) {
543 RawImportFile *f = userdata;
544 size_t sz = size * nmemb;
545 int r;
546
547 assert(contents);
548 assert(f);
549
550 if (f->done) {
551 r = -ESTALE;
90199220
LP
552 goto fail;
553 }
554
49bb233b
LP
555 if (f->disk_fd < 0) {
556 uint8_t *p;
557
558 /* We haven't opened the file yet, let's first check what it actually is */
559
560 p = realloc(f->payload, f->payload_size + sz);
561 if (!p) {
562 r = log_oom();
563 goto fail;
564 }
565
566 memcpy(p + f->payload_size, contents, sz);
567 f->payload_size = sz;
568 f->payload = p;
569
570 r = raw_import_file_detect_xz(f);
571 if (r < 0)
572 goto fail;
573
574 return sz;
575 }
576
577 r = raw_import_file_write_compressed(f, contents, sz);
578 if (r < 0)
579 goto fail;
90199220
LP
580
581 return sz;
582
583fail:
aceac2f0 584 raw_import_finish(f->import, r);
90199220
LP
585 return 0;
586}
587
aceac2f0
LP
588static size_t raw_import_file_header_callback(void *contents, size_t size, size_t nmemb, void *userdata) {
589 RawImportFile *f = userdata;
90199220
LP
590 size_t sz = size * nmemb;
591 _cleanup_free_ char *length = NULL, *last_modified = NULL;
592 char *etag;
593 int r;
594
595 assert(contents);
596 assert(f);
597
8620a9a3
LP
598 if (f->done) {
599 r = -ESTALE;
600 goto fail;
601 }
602
90199220
LP
603 r = curl_header_strdup(contents, sz, "ETag:", &etag);
604 if (r < 0) {
605 log_oom();
606 goto fail;
607 }
608 if (r > 0) {
609 free(f->etag);
610 f->etag = etag;
611
8620a9a3
LP
612 if (strv_contains(f->old_etags, f->etag)) {
613 log_info("Image already downloaded. Skipping download.");
aceac2f0 614 raw_import_file_success(f);
90199220
LP
615 return sz;
616 }
617
618 return sz;
619 }
620
621 r = curl_header_strdup(contents, sz, "Content-Length:", &length);
622 if (r < 0) {
623 log_oom();
624 goto fail;
625 }
626 if (r > 0) {
627 (void) safe_atou64(length, &f->content_length);
49bb233b
LP
628
629 if (f->content_length != (uint64_t) -1) {
630 char bytes[FORMAT_BYTES_MAX];
631 log_info("Downloading %s.", format_bytes(bytes, sizeof(bytes), f->content_length));
632 }
633
90199220
LP
634 return sz;
635 }
636
637 r = curl_header_strdup(contents, sz, "Last-Modified:", &last_modified);
638 if (r < 0) {
639 log_oom();
640 goto fail;
641 }
642 if (r > 0) {
643 (void) curl_parse_http_time(last_modified, &f->mtime);
644 return sz;
645 }
646
647 return sz;
648
649fail:
aceac2f0 650 raw_import_finish(f->import, r);
90199220
LP
651 return 0;
652}
653
8620a9a3
LP
654static bool etag_is_valid(const char *etag) {
655
656 if (!endswith(etag, "\""))
657 return false;
658
659 if (!startswith(etag, "\"") && !startswith(etag, "W/\""))
660 return false;
661
662 return true;
663}
664
aceac2f0 665static int raw_import_file_find_old_etags(RawImportFile *f) {
8620a9a3
LP
666 _cleanup_free_ char *escaped_url = NULL;
667 _cleanup_closedir_ DIR *d = NULL;
668 struct dirent *de;
669 int r;
670
671 escaped_url = xescape(f->url, FILENAME_ESCAPE);
672 if (!escaped_url)
673 return -ENOMEM;
674
087682d1 675 d = opendir(f->import->image_root);
8620a9a3
LP
676 if (!d) {
677 if (errno == ENOENT)
678 return 0;
679
680 return -errno;
681 }
682
683 FOREACH_DIRENT_ALL(de, d, return -errno) {
684 const char *a, *b;
685 char *u;
686
687 if (de->d_type != DT_UNKNOWN &&
688 de->d_type != DT_REG)
689 continue;
690
aceac2f0 691 a = startswith(de->d_name, ".raw-");
8620a9a3
LP
692 if (!a)
693 continue;
694
695 a = startswith(a, escaped_url);
696 if (!a)
697 continue;
698
699 a = startswith(a, ".");
700 if (!a)
701 continue;
702
aceac2f0 703 b = endswith(de->d_name, ".raw");
8620a9a3
LP
704 if (!b)
705 continue;
706
707 if (a >= b)
708 continue;
709
710 u = cunescape_length(a, b - a);
711 if (!u)
712 return -ENOMEM;
713
714 if (!etag_is_valid(u)) {
715 free(u);
716 continue;
717 }
718
719 r = strv_consume(&f->old_etags, u);
720 if (r < 0)
721 return r;
722 }
723
724 return 0;
725}
726
aceac2f0 727static int raw_import_file_begin(RawImportFile *f) {
90199220
LP
728 int r;
729
730 assert(f);
731 assert(!f->curl);
732
733 log_info("Getting %s.", f->url);
734
aceac2f0 735 r = raw_import_file_find_old_etags(f);
8620a9a3
LP
736 if (r < 0)
737 return r;
738
90199220
LP
739 r = curl_glue_make(&f->curl, f->url, f);
740 if (r < 0)
741 return r;
742
8620a9a3
LP
743 if (!strv_isempty(f->old_etags)) {
744 _cleanup_free_ char *cc = NULL, *hdr = NULL;
90199220 745
8620a9a3
LP
746 cc = strv_join(f->old_etags, ", ");
747 if (!cc)
748 return -ENOMEM;
749
750 hdr = strappend("If-None-Match: ", cc);
751 if (!hdr)
752 return -ENOMEM;
90199220
LP
753
754 f->request_header = curl_slist_new(hdr, NULL);
755 if (!f->request_header)
756 return -ENOMEM;
757
758 if (curl_easy_setopt(f->curl, CURLOPT_HTTPHEADER, f->request_header) != CURLE_OK)
759 return -EIO;
760 }
761
aceac2f0 762 if (curl_easy_setopt(f->curl, CURLOPT_WRITEFUNCTION, raw_import_file_write_callback) != CURLE_OK)
90199220
LP
763 return -EIO;
764
765 if (curl_easy_setopt(f->curl, CURLOPT_WRITEDATA, f) != CURLE_OK)
766 return -EIO;
767
aceac2f0 768 if (curl_easy_setopt(f->curl, CURLOPT_HEADERFUNCTION, raw_import_file_header_callback) != CURLE_OK)
90199220
LP
769 return -EIO;
770
771 if (curl_easy_setopt(f->curl, CURLOPT_HEADERDATA, f) != CURLE_OK)
772 return -EIO;
773
774 r = curl_glue_add(f->import->glue, f->curl);
775 if (r < 0)
776 return r;
777
778 return 0;
779}
780
aceac2f0
LP
781int raw_import_new(RawImport **import, sd_event *event, const char *image_root, raw_import_on_finished on_finished, void *userdata) {
782 _cleanup_(raw_import_unrefp) RawImport *i = NULL;
90199220
LP
783 int r;
784
785 assert(import);
087682d1 786 assert(image_root);
90199220 787
aceac2f0 788 i = new0(RawImport, 1);
90199220
LP
789 if (!i)
790 return -ENOMEM;
791
792 i->on_finished = on_finished;
793 i->userdata = userdata;
794
087682d1
LP
795 i->image_root = strdup(image_root);
796 if (!i->image_root)
797 return -ENOMEM;
798
90199220
LP
799 if (event)
800 i->event = sd_event_ref(event);
801 else {
802 r = sd_event_default(&i->event);
803 if (r < 0)
804 return r;
805 }
806
807 r = curl_glue_new(&i->glue, i->event);
808 if (r < 0)
809 return r;
810
aceac2f0 811 i->glue->on_finished = raw_import_curl_on_finished;
90199220
LP
812 i->glue->userdata = i;
813
814 *import = i;
815 i = NULL;
816
817 return 0;
818}
819
aceac2f0
LP
820RawImport* raw_import_unref(RawImport *import) {
821 RawImportFile *f;
90199220
LP
822
823 if (!import)
824 return NULL;
825
826 while ((f = hashmap_steal_first(import->files)))
aceac2f0 827 raw_import_file_unref(f);
90199220
LP
828 hashmap_free(import->files);
829
830 curl_glue_unref(import->glue);
831 sd_event_unref(import->event);
832
087682d1 833 free(import->image_root);
90199220
LP
834 free(import);
835
836 return NULL;
837}
838
aceac2f0
LP
839int raw_import_cancel(RawImport *import, const char *url) {
840 RawImportFile *f;
90199220
LP
841
842 assert(import);
843 assert(url);
844
845 f = hashmap_remove(import->files, url);
846 if (!f)
847 return 0;
848
aceac2f0 849 raw_import_file_unref(f);
90199220
LP
850 return 1;
851}
852
aceac2f0
LP
853int raw_import_pull(RawImport *import, const char *url, const char *local, bool force_local) {
854 _cleanup_(raw_import_file_unrefp) RawImportFile *f = NULL;
90199220
LP
855 int r;
856
857 assert(import);
aceac2f0 858 assert(raw_url_is_valid(url));
8620a9a3 859 assert(!local || machine_name_is_valid(local));
90199220
LP
860
861 if (hashmap_get(import->files, url))
862 return -EEXIST;
863
864 r = hashmap_ensure_allocated(&import->files, &string_hash_ops);
865 if (r < 0)
866 return r;
867
aceac2f0 868 f = new0(RawImportFile, 1);
90199220
LP
869 if (!f)
870 return -ENOMEM;
871
872 f->import = import;
873 f->disk_fd = -1;
874 f->content_length = (uint64_t) -1;
875
876 f->url = strdup(url);
877 if (!f->url)
878 return -ENOMEM;
879
8620a9a3
LP
880 if (local) {
881 f->local = strdup(local);
882 if (!f->local)
90199220 883 return -ENOMEM;
8620a9a3
LP
884
885 f->force_local = force_local;
90199220
LP
886 }
887
888 r = hashmap_put(import->files, f->url, f);
889 if (r < 0)
890 return r;
891
aceac2f0 892 r = raw_import_file_begin(f);
90199220 893 if (r < 0) {
aceac2f0 894 raw_import_cancel(import, f->url);
90199220
LP
895 f = NULL;
896 return r;
897 }
898
899 f = NULL;
900 return 0;
901}
902
aceac2f0 903bool raw_url_is_valid(const char *url) {
90199220
LP
904 if (isempty(url))
905 return false;
906
907 if (!startswith(url, "http://") &&
908 !startswith(url, "https://"))
909 return false;
910
911 return ascii_is_valid(url);
912}