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