]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/import/import-raw.c
import-raw: when downloading raw images, generate sparse files if we can
[thirdparty/systemd.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 }