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