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