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