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