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