chiark / gitweb /
f830ba47ff8950a4b2944cbae9c29928a96592e4
[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         r = raw_import_maybe_convert_qcow2(f);
336         if (r < 0)
337                 goto fail;
338
339         if (f->etag)
340                 (void) fsetxattr(f->disk_fd, "user.source_etag", f->etag, strlen(f->etag), 0);
341         if (f->url)
342                 (void) fsetxattr(f->disk_fd, "user.source_url", f->url, strlen(f->url), 0);
343
344         if (f->mtime != 0) {
345                 struct timespec ut[2];
346
347                 timespec_store(&ut[0], f->mtime);
348                 ut[1] = ut[0];
349                 (void) futimens(f->disk_fd, ut);
350
351                 fd_setcrtime(f->disk_fd, f->mtime);
352         }
353
354         if (fstat(f->disk_fd, &st) < 0) {
355                 r = log_error_errno(errno, "Failed to stat file: %m");
356                 goto fail;
357         }
358
359         /* Mark read-only */
360         (void) fchmod(f->disk_fd, st.st_mode & 07444);
361
362         assert(f->temp_path);
363         assert(f->final_path);
364
365         r = rename(f->temp_path, f->final_path);
366         if (r < 0) {
367                 r = log_error_errno(errno, "Failed to move RAW file into place: %m");
368                 goto fail;
369         }
370
371         free(f->temp_path);
372         f->temp_path = NULL;
373
374         log_info("Completed writing vendor image %s.", f->final_path);
375
376         raw_import_file_success(f);
377         return;
378
379 fail:
380         raw_import_finish(f->import, r);
381 }
382
383 static int raw_import_file_open_disk_for_write(RawImportFile *f) {
384         int r;
385
386         assert(f);
387
388         if (f->disk_fd >= 0)
389                 return 0;
390
391         r = raw_import_file_make_final_path(f);
392         if (r < 0)
393                 return log_oom();
394
395         if (!f->temp_path) {
396                 r = tempfn_random(f->final_path, &f->temp_path);
397                 if (r < 0)
398                         return log_oom();
399         }
400
401         f->disk_fd = open(f->temp_path, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0644);
402         if (f->disk_fd < 0)
403                 return log_error_errno(errno, "Failed to create %s: %m", f->temp_path);
404
405         r = chattr_fd(f->disk_fd, true, FS_NOCOW_FL);
406         if (r < 0)
407                 log_warning_errno(errno, "Failed to set file attributes on %s: %m", f->temp_path);
408
409         return 0;
410 }
411
412 static int raw_import_file_write_uncompressed(RawImportFile *f, void *p, size_t sz) {
413         ssize_t n;
414
415         assert(f);
416         assert(p);
417         assert(sz > 0);
418         assert(f->disk_fd >= 0);
419
420         if (f->written_uncompressed + sz < f->written_uncompressed) {
421                 log_error("File too large, overflow");
422                 return -EOVERFLOW;
423         }
424
425         if (f->written_uncompressed + sz > RAW_MAX_SIZE) {
426                 log_error("File overly large, refusing");
427                 return -EFBIG;
428         }
429
430         n = write(f->disk_fd, p, sz);
431         if (n < 0) {
432                 log_error_errno(errno, "Failed to write file: %m");
433                 return -errno;
434         }
435         if ((size_t) n < sz) {
436                 log_error("Short write");
437                 return -EIO;
438         }
439
440         f->written_uncompressed += sz;
441
442         return 0;
443 }
444
445 static int raw_import_file_write_compressed(RawImportFile *f, void *p, size_t sz) {
446         int r;
447
448         assert(f);
449         assert(p);
450         assert(sz > 0);
451         assert(f->disk_fd >= 0);
452
453         if (f->written_compressed + sz < f->written_compressed) {
454                 log_error("File too large, overflow");
455                 return -EOVERFLOW;
456         }
457
458         if (f->content_length != (uint64_t) -1 &&
459             f->written_compressed + sz > f->content_length) {
460                 log_error("Content length incorrect.");
461                 return -EFBIG;
462         }
463
464         if (!f->compressed) {
465                 r = raw_import_file_write_uncompressed(f, p, sz);
466                 if (r < 0)
467                         return r;
468         } else {
469                 f->lzma.next_in = p;
470                 f->lzma.avail_in = sz;
471
472                 while (f->lzma.avail_in > 0) {
473                         uint8_t buffer[16 * 1024];
474                         lzma_ret lzr;
475
476                         f->lzma.next_out = buffer;
477                         f->lzma.avail_out = sizeof(buffer);
478
479                         lzr = lzma_code(&f->lzma, LZMA_RUN);
480                         if (lzr != LZMA_OK && lzr != LZMA_STREAM_END) {
481                                 log_error("Decompression error.");
482                                 return -EIO;
483                         }
484
485                         r = raw_import_file_write_uncompressed(f, buffer, sizeof(buffer) - f->lzma.avail_out);
486                         if (r < 0)
487                                 return r;
488                 }
489         }
490
491         f->written_compressed += sz;
492
493         return 0;
494 }
495
496 static int raw_import_file_detect_xz(RawImportFile *f) {
497         static const uint8_t xz_signature[] = {
498                 '\xfd', '7', 'z', 'X', 'Z', '\x00'
499         };
500         lzma_ret lzr;
501         int r;
502
503         assert(f);
504
505         if (f->payload_size < sizeof(xz_signature))
506                 return 0;
507
508         f->compressed = memcmp(f->payload, xz_signature, sizeof(xz_signature)) == 0;
509         log_debug("Stream is XZ compressed: %s", yes_no(f->compressed));
510
511         if (f->compressed) {
512                 lzr = lzma_stream_decoder(&f->lzma, UINT64_MAX, LZMA_TELL_UNSUPPORTED_CHECK);
513                 if (lzr != LZMA_OK) {
514                         log_error("Failed to initialize LZMA decoder.");
515                         return -EIO;
516                 }
517         }
518
519         r = raw_import_file_open_disk_for_write(f);
520         if (r < 0)
521                 return r;
522
523         r = raw_import_file_write_compressed(f, f->payload, f->payload_size);
524         if (r < 0)
525                 return r;
526
527         free(f->payload);
528         f->payload = NULL;
529         f->payload_size = 0;
530
531         return 0;
532 }
533
534 static size_t raw_import_file_write_callback(void *contents, size_t size, size_t nmemb, void *userdata) {
535         RawImportFile *f = userdata;
536         size_t sz = size * nmemb;
537         int r;
538
539         assert(contents);
540         assert(f);
541
542         if (f->done) {
543                 r = -ESTALE;
544                 goto fail;
545         }
546
547         if (f->disk_fd < 0) {
548                 uint8_t *p;
549
550                 /* We haven't opened the file yet, let's first check what it actually is */
551
552                 p = realloc(f->payload, f->payload_size + sz);
553                 if (!p) {
554                         r = log_oom();
555                         goto fail;
556                 }
557
558                 memcpy(p + f->payload_size, contents, sz);
559                 f->payload_size = sz;
560                 f->payload = p;
561
562                 r = raw_import_file_detect_xz(f);
563                 if (r < 0)
564                         goto fail;
565
566                 return sz;
567         }
568
569         r = raw_import_file_write_compressed(f, contents, sz);
570         if (r < 0)
571                 goto fail;
572
573         return sz;
574
575 fail:
576         raw_import_finish(f->import, r);
577         return 0;
578 }
579
580 static size_t raw_import_file_header_callback(void *contents, size_t size, size_t nmemb, void *userdata) {
581         RawImportFile *f = userdata;
582         size_t sz = size * nmemb;
583         _cleanup_free_ char *length = NULL, *last_modified = NULL;
584         char *etag;
585         int r;
586
587         assert(contents);
588         assert(f);
589
590         if (f->done) {
591                 r = -ESTALE;
592                 goto fail;
593         }
594
595         r = curl_header_strdup(contents, sz, "ETag:", &etag);
596         if (r < 0) {
597                 log_oom();
598                 goto fail;
599         }
600         if (r > 0) {
601                 free(f->etag);
602                 f->etag = etag;
603
604                 if (strv_contains(f->old_etags, f->etag)) {
605                         log_info("Image already downloaded. Skipping download.");
606                         raw_import_file_success(f);
607                         return sz;
608                 }
609
610                 return sz;
611         }
612
613         r = curl_header_strdup(contents, sz, "Content-Length:", &length);
614         if (r < 0) {
615                 log_oom();
616                 goto fail;
617         }
618         if (r > 0) {
619                 (void) safe_atou64(length, &f->content_length);
620
621                 if (f->content_length != (uint64_t) -1) {
622                         char bytes[FORMAT_BYTES_MAX];
623                         log_info("Downloading %s.", format_bytes(bytes, sizeof(bytes), f->content_length));
624                 }
625
626                 return sz;
627         }
628
629         r = curl_header_strdup(contents, sz, "Last-Modified:", &last_modified);
630         if (r < 0) {
631                 log_oom();
632                 goto fail;
633         }
634         if (r > 0) {
635                 (void) curl_parse_http_time(last_modified, &f->mtime);
636                 return sz;
637         }
638
639         return sz;
640
641 fail:
642         raw_import_finish(f->import, r);
643         return 0;
644 }
645
646 static bool etag_is_valid(const char *etag) {
647
648         if (!endswith(etag, "\""))
649                 return false;
650
651         if (!startswith(etag, "\"") && !startswith(etag, "W/\""))
652                 return false;
653
654         return true;
655 }
656
657 static int raw_import_file_find_old_etags(RawImportFile *f) {
658         _cleanup_free_ char *escaped_url = NULL;
659         _cleanup_closedir_ DIR *d = NULL;
660         struct dirent *de;
661         int r;
662
663         escaped_url = xescape(f->url, FILENAME_ESCAPE);
664         if (!escaped_url)
665                 return -ENOMEM;
666
667         d = opendir(f->import->image_root);
668         if (!d) {
669                 if (errno == ENOENT)
670                         return 0;
671
672                 return -errno;
673         }
674
675         FOREACH_DIRENT_ALL(de, d, return -errno) {
676                 const char *a, *b;
677                 char *u;
678
679                 if (de->d_type != DT_UNKNOWN &&
680                     de->d_type != DT_REG)
681                         continue;
682
683                 a = startswith(de->d_name, ".raw-");
684                 if (!a)
685                         continue;
686
687                 a = startswith(a, escaped_url);
688                 if (!a)
689                         continue;
690
691                 a = startswith(a, ".");
692                 if (!a)
693                         continue;
694
695                 b = endswith(de->d_name, ".raw");
696                 if (!b)
697                         continue;
698
699                 if (a >= b)
700                         continue;
701
702                 u = cunescape_length(a, b - a);
703                 if (!u)
704                         return -ENOMEM;
705
706                 if (!etag_is_valid(u)) {
707                         free(u);
708                         continue;
709                 }
710
711                 r = strv_consume(&f->old_etags, u);
712                 if (r < 0)
713                         return r;
714         }
715
716         return 0;
717 }
718
719 static int raw_import_file_begin(RawImportFile *f) {
720         int r;
721
722         assert(f);
723         assert(!f->curl);
724
725         log_info("Getting %s.", f->url);
726
727         r = raw_import_file_find_old_etags(f);
728         if (r < 0)
729                 return r;
730
731         r = curl_glue_make(&f->curl, f->url, f);
732         if (r < 0)
733                 return r;
734
735         if (!strv_isempty(f->old_etags)) {
736                 _cleanup_free_ char *cc = NULL, *hdr = NULL;
737
738                 cc = strv_join(f->old_etags, ", ");
739                 if (!cc)
740                         return -ENOMEM;
741
742                 hdr = strappend("If-None-Match: ", cc);
743                 if (!hdr)
744                         return -ENOMEM;
745
746                 f->request_header = curl_slist_new(hdr, NULL);
747                 if (!f->request_header)
748                         return -ENOMEM;
749
750                 if (curl_easy_setopt(f->curl, CURLOPT_HTTPHEADER, f->request_header) != CURLE_OK)
751                         return -EIO;
752         }
753
754         if (curl_easy_setopt(f->curl, CURLOPT_WRITEFUNCTION, raw_import_file_write_callback) != CURLE_OK)
755                 return -EIO;
756
757         if (curl_easy_setopt(f->curl, CURLOPT_WRITEDATA, f) != CURLE_OK)
758                 return -EIO;
759
760         if (curl_easy_setopt(f->curl, CURLOPT_HEADERFUNCTION, raw_import_file_header_callback) != CURLE_OK)
761                 return -EIO;
762
763         if (curl_easy_setopt(f->curl, CURLOPT_HEADERDATA, f) != CURLE_OK)
764                 return -EIO;
765
766         r = curl_glue_add(f->import->glue, f->curl);
767         if (r < 0)
768                 return r;
769
770         return 0;
771 }
772
773 int raw_import_new(RawImport **import, sd_event *event, const char *image_root, raw_import_on_finished on_finished, void *userdata) {
774         _cleanup_(raw_import_unrefp) RawImport *i = NULL;
775         int r;
776
777         assert(import);
778         assert(image_root);
779
780         i = new0(RawImport, 1);
781         if (!i)
782                 return -ENOMEM;
783
784         i->on_finished = on_finished;
785         i->userdata = userdata;
786
787         i->image_root = strdup(image_root);
788         if (!i->image_root)
789                 return -ENOMEM;
790
791         if (event)
792                 i->event = sd_event_ref(event);
793         else {
794                 r = sd_event_default(&i->event);
795                 if (r < 0)
796                         return r;
797         }
798
799         r = curl_glue_new(&i->glue, i->event);
800         if (r < 0)
801                 return r;
802
803         i->glue->on_finished = raw_import_curl_on_finished;
804         i->glue->userdata = i;
805
806         *import = i;
807         i = NULL;
808
809         return 0;
810 }
811
812 RawImport* raw_import_unref(RawImport *import) {
813         RawImportFile *f;
814
815         if (!import)
816                 return NULL;
817
818         while ((f = hashmap_steal_first(import->files)))
819                 raw_import_file_unref(f);
820         hashmap_free(import->files);
821
822         curl_glue_unref(import->glue);
823         sd_event_unref(import->event);
824
825         free(import->image_root);
826         free(import);
827
828         return NULL;
829 }
830
831 int raw_import_cancel(RawImport *import, const char *url) {
832         RawImportFile *f;
833
834         assert(import);
835         assert(url);
836
837         f = hashmap_remove(import->files, url);
838         if (!f)
839                 return 0;
840
841         raw_import_file_unref(f);
842         return 1;
843 }
844
845 int raw_import_pull(RawImport *import, const char *url, const char *local, bool force_local) {
846         _cleanup_(raw_import_file_unrefp) RawImportFile *f = NULL;
847         int r;
848
849         assert(import);
850         assert(raw_url_is_valid(url));
851         assert(!local || machine_name_is_valid(local));
852
853         if (hashmap_get(import->files, url))
854                 return -EEXIST;
855
856         r = hashmap_ensure_allocated(&import->files, &string_hash_ops);
857         if (r < 0)
858                 return r;
859
860         f = new0(RawImportFile, 1);
861         if (!f)
862                 return -ENOMEM;
863
864         f->import = import;
865         f->disk_fd = -1;
866         f->content_length = (uint64_t) -1;
867
868         f->url = strdup(url);
869         if (!f->url)
870                 return -ENOMEM;
871
872         if (local) {
873                 f->local = strdup(local);
874                 if (!f->local)
875                         return -ENOMEM;
876
877                 f->force_local = force_local;
878         }
879
880         r = hashmap_put(import->files, f->url, f);
881         if (r < 0)
882                 return r;
883
884         r = raw_import_file_begin(f);
885         if (r < 0) {
886                 raw_import_cancel(import, f->url);
887                 f = NULL;
888                 return r;
889         }
890
891         f = NULL;
892         return 0;
893 }
894
895 bool raw_url_is_valid(const char *url) {
896         if (isempty(url))
897                 return false;
898
899         if (!startswith(url, "http://") &&
900             !startswith(url, "https://"))
901                 return false;
902
903         return ascii_is_valid(url);
904 }