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