]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/import/pull-raw.c
Merge pull request #7442 from poettering/scope-fixes
[thirdparty/systemd.git] / src / import / pull-raw.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 This file is part of systemd.
4
5 Copyright 2014 Lennart Poettering
6
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19 ***/
20
21 #include <curl/curl.h>
22 #include <linux/fs.h>
23 #include <sys/xattr.h>
24
25 #include "sd-daemon.h"
26
27 #include "alloc-util.h"
28 #include "btrfs-util.h"
29 #include "chattr-util.h"
30 #include "copy.h"
31 #include "curl-util.h"
32 #include "fd-util.h"
33 #include "fileio.h"
34 #include "fs-util.h"
35 #include "hostname-util.h"
36 #include "import-common.h"
37 #include "import-util.h"
38 #include "macro.h"
39 #include "mkdir.h"
40 #include "path-util.h"
41 #include "pull-common.h"
42 #include "pull-job.h"
43 #include "pull-raw.h"
44 #include "qcow2-util.h"
45 #include "rm-rf.h"
46 #include "string-util.h"
47 #include "strv.h"
48 #include "utf8.h"
49 #include "util.h"
50 #include "web-util.h"
51
52 typedef enum RawProgress {
53 RAW_DOWNLOADING,
54 RAW_VERIFYING,
55 RAW_UNPACKING,
56 RAW_FINALIZING,
57 RAW_COPYING,
58 } RawProgress;
59
60 struct RawPull {
61 sd_event *event;
62 CurlGlue *glue;
63
64 char *image_root;
65
66 PullJob *raw_job;
67 PullJob *roothash_job;
68 PullJob *settings_job;
69 PullJob *checksum_job;
70 PullJob *signature_job;
71
72 RawPullFinished on_finished;
73 void *userdata;
74
75 char *local;
76 bool force_local;
77 bool grow_machine_directory;
78 bool settings;
79 bool roothash;
80
81 char *final_path;
82 char *temp_path;
83
84 char *settings_path;
85 char *settings_temp_path;
86
87 char *roothash_path;
88 char *roothash_temp_path;
89
90 ImportVerify verify;
91 };
92
93 RawPull* raw_pull_unref(RawPull *i) {
94 if (!i)
95 return NULL;
96
97 pull_job_unref(i->raw_job);
98 pull_job_unref(i->settings_job);
99 pull_job_unref(i->roothash_job);
100 pull_job_unref(i->checksum_job);
101 pull_job_unref(i->signature_job);
102
103 curl_glue_unref(i->glue);
104 sd_event_unref(i->event);
105
106 if (i->temp_path) {
107 (void) unlink(i->temp_path);
108 free(i->temp_path);
109 }
110
111 if (i->roothash_temp_path) {
112 (void) unlink(i->roothash_temp_path);
113 free(i->roothash_temp_path);
114 }
115
116 if (i->settings_temp_path) {
117 (void) unlink(i->settings_temp_path);
118 free(i->settings_temp_path);
119 }
120
121 free(i->final_path);
122 free(i->roothash_path);
123 free(i->settings_path);
124 free(i->image_root);
125 free(i->local);
126 return mfree(i);
127 }
128
129 int raw_pull_new(
130 RawPull **ret,
131 sd_event *event,
132 const char *image_root,
133 RawPullFinished on_finished,
134 void *userdata) {
135
136 _cleanup_(raw_pull_unrefp) RawPull *i = NULL;
137 int r;
138
139 assert(ret);
140
141 i = new0(RawPull, 1);
142 if (!i)
143 return -ENOMEM;
144
145 i->on_finished = on_finished;
146 i->userdata = userdata;
147
148 i->image_root = strdup(image_root ?: "/var/lib/machines");
149 if (!i->image_root)
150 return -ENOMEM;
151
152 i->grow_machine_directory = path_startswith(i->image_root, "/var/lib/machines");
153
154 if (event)
155 i->event = sd_event_ref(event);
156 else {
157 r = sd_event_default(&i->event);
158 if (r < 0)
159 return r;
160 }
161
162 r = curl_glue_new(&i->glue, i->event);
163 if (r < 0)
164 return r;
165
166 i->glue->on_finished = pull_job_curl_on_finished;
167 i->glue->userdata = i;
168
169 *ret = i;
170 i = NULL;
171
172 return 0;
173 }
174
175 static void raw_pull_report_progress(RawPull *i, RawProgress p) {
176 unsigned percent;
177
178 assert(i);
179
180 switch (p) {
181
182 case RAW_DOWNLOADING: {
183 unsigned remain = 80;
184
185 percent = 0;
186
187 if (i->settings_job) {
188 percent += i->settings_job->progress_percent * 5 / 100;
189 remain -= 5;
190 }
191
192 if (i->roothash_job) {
193 percent += i->roothash_job->progress_percent * 5 / 100;
194 remain -= 5;
195 }
196
197 if (i->checksum_job) {
198 percent += i->checksum_job->progress_percent * 5 / 100;
199 remain -= 5;
200 }
201
202 if (i->signature_job) {
203 percent += i->signature_job->progress_percent * 5 / 100;
204 remain -= 5;
205 }
206
207 if (i->raw_job)
208 percent += i->raw_job->progress_percent * remain / 100;
209 break;
210 }
211
212 case RAW_VERIFYING:
213 percent = 80;
214 break;
215
216 case RAW_UNPACKING:
217 percent = 85;
218 break;
219
220 case RAW_FINALIZING:
221 percent = 90;
222 break;
223
224 case RAW_COPYING:
225 percent = 95;
226 break;
227
228 default:
229 assert_not_reached("Unknown progress state");
230 }
231
232 sd_notifyf(false, "X_IMPORT_PROGRESS=%u", percent);
233 log_debug("Combined progress %u%%", percent);
234 }
235
236 static int raw_pull_maybe_convert_qcow2(RawPull *i) {
237 _cleanup_close_ int converted_fd = -1;
238 _cleanup_free_ char *t = NULL;
239 int r;
240
241 assert(i);
242 assert(i->raw_job);
243
244 r = qcow2_detect(i->raw_job->disk_fd);
245 if (r < 0)
246 return log_error_errno(r, "Failed to detect whether this is a QCOW2 image: %m");
247 if (r == 0)
248 return 0;
249
250 /* This is a QCOW2 image, let's convert it */
251 r = tempfn_random(i->final_path, NULL, &t);
252 if (r < 0)
253 return log_oom();
254
255 converted_fd = open(t, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664);
256 if (converted_fd < 0)
257 return log_error_errno(errno, "Failed to create %s: %m", t);
258
259 r = chattr_fd(converted_fd, FS_NOCOW_FL, FS_NOCOW_FL);
260 if (r < 0)
261 log_warning_errno(r, "Failed to set file attributes on %s: %m", t);
262
263 log_info("Unpacking QCOW2 file.");
264
265 r = qcow2_convert(i->raw_job->disk_fd, converted_fd);
266 if (r < 0) {
267 unlink(t);
268 return log_error_errno(r, "Failed to convert qcow2 image: %m");
269 }
270
271 (void) unlink(i->temp_path);
272 free_and_replace(i->temp_path, t);
273
274 safe_close(i->raw_job->disk_fd);
275 i->raw_job->disk_fd = converted_fd;
276 converted_fd = -1;
277
278 return 1;
279 }
280
281 static int raw_pull_determine_path(RawPull *i, const char *suffix, char **field) {
282 int r;
283
284 assert(i);
285 assert(field);
286
287 if (*field)
288 return 0;
289
290 assert(i->raw_job);
291
292 r = pull_make_path(i->raw_job->url, i->raw_job->etag, i->image_root, ".raw-", suffix, field);
293 if (r < 0)
294 return log_oom();
295
296 return 1;
297 }
298
299 static int raw_pull_copy_auxiliary_file(
300 RawPull *i,
301 const char *suffix,
302 char **path) {
303
304 const char *local;
305 int r;
306
307 assert(i);
308 assert(suffix);
309 assert(path);
310
311 r = raw_pull_determine_path(i, suffix, path);
312 if (r < 0)
313 return r;
314
315 local = strjoina(i->image_root, "/", i->local, suffix);
316
317 r = copy_file_atomic(*path, local, 0644, 0, COPY_REFLINK | (i->force_local ? COPY_REPLACE : 0));
318 if (r == -EEXIST)
319 log_warning_errno(r, "File %s already exists, not replacing.", local);
320 else if (r == -ENOENT)
321 log_debug_errno(r, "Skipping creation of auxiliary file, since none was found.");
322 else if (r < 0)
323 log_warning_errno(r, "Failed to copy file %s, ignoring: %m", local);
324 else
325 log_info("Created new file %s.", local);
326
327 return 0;
328 }
329
330 static int raw_pull_make_local_copy(RawPull *i) {
331 _cleanup_free_ char *tp = NULL;
332 _cleanup_close_ int dfd = -1;
333 const char *p;
334 int r;
335
336 assert(i);
337 assert(i->raw_job);
338
339 if (!i->local)
340 return 0;
341
342 if (i->raw_job->etag_exists) {
343 /* We have downloaded this one previously, reopen it */
344
345 assert(i->raw_job->disk_fd < 0);
346
347 i->raw_job->disk_fd = open(i->final_path, O_RDONLY|O_NOCTTY|O_CLOEXEC);
348 if (i->raw_job->disk_fd < 0)
349 return log_error_errno(errno, "Failed to open vendor image: %m");
350 } else {
351 /* We freshly downloaded the image, use it */
352
353 assert(i->raw_job->disk_fd >= 0);
354
355 if (lseek(i->raw_job->disk_fd, SEEK_SET, 0) == (off_t) -1)
356 return log_error_errno(errno, "Failed to seek to beginning of vendor image: %m");
357 }
358
359 p = strjoina(i->image_root, "/", i->local, ".raw");
360
361 if (i->force_local)
362 (void) rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME);
363
364 r = tempfn_random(p, NULL, &tp);
365 if (r < 0)
366 return log_oom();
367
368 dfd = open(tp, O_WRONLY|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664);
369 if (dfd < 0)
370 return log_error_errno(errno, "Failed to create writable copy of image: %m");
371
372 /* Turn off COW writing. This should greatly improve
373 * performance on COW file systems like btrfs, since it
374 * reduces fragmentation caused by not allowing in-place
375 * writes. */
376 r = chattr_fd(dfd, FS_NOCOW_FL, FS_NOCOW_FL);
377 if (r < 0)
378 log_warning_errno(r, "Failed to set file attributes on %s: %m", tp);
379
380 r = copy_bytes(i->raw_job->disk_fd, dfd, (uint64_t) -1, COPY_REFLINK);
381 if (r < 0) {
382 unlink(tp);
383 return log_error_errno(r, "Failed to make writable copy of image: %m");
384 }
385
386 (void) copy_times(i->raw_job->disk_fd, dfd);
387 (void) copy_xattr(i->raw_job->disk_fd, dfd);
388
389 dfd = safe_close(dfd);
390
391 r = rename(tp, p);
392 if (r < 0) {
393 r = log_error_errno(errno, "Failed to move writable image into place: %m");
394 unlink(tp);
395 return r;
396 }
397
398 log_info("Created new local image '%s'.", i->local);
399
400 if (i->roothash) {
401 r = raw_pull_copy_auxiliary_file(i, ".roothash", &i->roothash_path);
402 if (r < 0)
403 return r;
404 }
405
406 if (i->settings) {
407 r = raw_pull_copy_auxiliary_file(i, ".nspawn", &i->settings_path);
408 if (r < 0)
409 return r;
410 }
411
412 return 0;
413 }
414
415 static bool raw_pull_is_done(RawPull *i) {
416 assert(i);
417 assert(i->raw_job);
418
419 if (!PULL_JOB_IS_COMPLETE(i->raw_job))
420 return false;
421 if (i->roothash_job && !PULL_JOB_IS_COMPLETE(i->roothash_job))
422 return false;
423 if (i->settings_job && !PULL_JOB_IS_COMPLETE(i->settings_job))
424 return false;
425 if (i->checksum_job && !PULL_JOB_IS_COMPLETE(i->checksum_job))
426 return false;
427 if (i->signature_job && !PULL_JOB_IS_COMPLETE(i->signature_job))
428 return false;
429
430 return true;
431 }
432
433 static int raw_pull_rename_auxiliary_file(
434 RawPull *i,
435 const char *suffix,
436 char **temp_path,
437 char **path) {
438
439 int r;
440
441 assert(i);
442 assert(temp_path);
443 assert(suffix);
444 assert(path);
445
446 /* Regenerate final name for this auxiliary file, we might know the etag of the file now, and we should
447 * incorporate it in the file name if we can */
448 *path = mfree(*path);
449 r = raw_pull_determine_path(i, suffix, path);
450 if (r < 0)
451 return r;
452
453 r = import_make_read_only(*temp_path);
454 if (r < 0)
455 return r;
456
457 r = rename_noreplace(AT_FDCWD, *temp_path, AT_FDCWD, *path);
458 if (r < 0)
459 return log_error_errno(r, "Failed to rename file %s to %s: %m", *temp_path, *path);
460
461 *temp_path = mfree(*temp_path);
462
463 return 1;
464 }
465
466 static void raw_pull_job_on_finished(PullJob *j) {
467 RawPull *i;
468 int r;
469
470 assert(j);
471 assert(j->userdata);
472
473 i = j->userdata;
474 if (j == i->roothash_job) {
475 if (j->error != 0)
476 log_info_errno(j->error, "Root hash file could not be retrieved, proceeding without.");
477 } else if (j == i->settings_job) {
478 if (j->error != 0)
479 log_info_errno(j->error, "Settings file could not be retrieved, proceeding without.");
480 } else if (j->error != 0 && j != i->signature_job) {
481 if (j == i->checksum_job)
482 log_error_errno(j->error, "Failed to retrieve SHA256 checksum, cannot verify. (Try --verify=no?)");
483 else
484 log_error_errno(j->error, "Failed to retrieve image file. (Wrong URL?)");
485
486 r = j->error;
487 goto finish;
488 }
489
490 /* This is invoked if either the download completed
491 * successfully, or the download was skipped because we
492 * already have the etag. In this case ->etag_exists is
493 * true.
494 *
495 * We only do something when we got all three files */
496
497 if (!raw_pull_is_done(i))
498 return;
499
500 if (i->signature_job && i->checksum_job->style == VERIFICATION_PER_DIRECTORY && i->signature_job->error != 0) {
501 log_error_errno(j->error, "Failed to retrieve signature file, cannot verify. (Try --verify=no?)");
502
503 r = i->signature_job->error;
504 goto finish;
505 }
506
507 if (i->roothash_job)
508 i->roothash_job->disk_fd = safe_close(i->roothash_job->disk_fd);
509 if (i->settings_job)
510 i->settings_job->disk_fd = safe_close(i->settings_job->disk_fd);
511
512 r = raw_pull_determine_path(i, ".raw", &i->final_path);
513 if (r < 0)
514 goto finish;
515
516 if (!i->raw_job->etag_exists) {
517 /* This is a new download, verify it, and move it into place */
518 assert(i->raw_job->disk_fd >= 0);
519
520 raw_pull_report_progress(i, RAW_VERIFYING);
521
522 r = pull_verify(i->raw_job, i->roothash_job, i->settings_job, i->checksum_job, i->signature_job);
523 if (r < 0)
524 goto finish;
525
526 raw_pull_report_progress(i, RAW_UNPACKING);
527
528 r = raw_pull_maybe_convert_qcow2(i);
529 if (r < 0)
530 goto finish;
531
532 raw_pull_report_progress(i, RAW_FINALIZING);
533
534 r = import_make_read_only_fd(i->raw_job->disk_fd);
535 if (r < 0)
536 goto finish;
537
538 r = rename_noreplace(AT_FDCWD, i->temp_path, AT_FDCWD, i->final_path);
539 if (r < 0) {
540 log_error_errno(r, "Failed to rename raw file to %s: %m", i->final_path);
541 goto finish;
542 }
543
544 i->temp_path = mfree(i->temp_path);
545
546 if (i->roothash_job &&
547 i->roothash_job->error == 0) {
548 r = raw_pull_rename_auxiliary_file(i, ".roothash", &i->roothash_temp_path, &i->roothash_path);
549 if (r < 0)
550 goto finish;
551 }
552
553 if (i->settings_job &&
554 i->settings_job->error == 0) {
555 r = raw_pull_rename_auxiliary_file(i, ".nspawn", &i->settings_temp_path, &i->settings_path);
556 if (r < 0)
557 goto finish;
558 }
559 }
560
561 raw_pull_report_progress(i, RAW_COPYING);
562
563 r = raw_pull_make_local_copy(i);
564 if (r < 0)
565 goto finish;
566
567 r = 0;
568
569 finish:
570 if (i->on_finished)
571 i->on_finished(i, r, i->userdata);
572 else
573 sd_event_exit(i->event, r);
574 }
575
576 static int raw_pull_job_on_open_disk_generic(
577 RawPull *i,
578 PullJob *j,
579 const char *extra,
580 char **temp_path) {
581
582 int r;
583
584 assert(i);
585 assert(j);
586 assert(extra);
587 assert(temp_path);
588
589 if (!*temp_path) {
590 r = tempfn_random_child(i->image_root, extra, temp_path);
591 if (r < 0)
592 return log_oom();
593 }
594
595 (void) mkdir_parents_label(*temp_path, 0700);
596
597 j->disk_fd = open(*temp_path, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664);
598 if (j->disk_fd < 0)
599 return log_error_errno(errno, "Failed to create %s: %m", *temp_path);
600
601 return 0;
602 }
603
604 static int raw_pull_job_on_open_disk_raw(PullJob *j) {
605 RawPull *i;
606 int r;
607
608 assert(j);
609 assert(j->userdata);
610
611 i = j->userdata;
612 assert(i->raw_job == j);
613
614 r = raw_pull_job_on_open_disk_generic(i, j, "raw", &i->temp_path);
615 if (r < 0)
616 return r;
617
618 r = chattr_fd(j->disk_fd, FS_NOCOW_FL, FS_NOCOW_FL);
619 if (r < 0)
620 log_warning_errno(r, "Failed to set file attributes on %s, ignoring: %m", i->temp_path);
621
622 return 0;
623 }
624
625 static int raw_pull_job_on_open_disk_roothash(PullJob *j) {
626 RawPull *i;
627
628 assert(j);
629 assert(j->userdata);
630
631 i = j->userdata;
632 assert(i->roothash_job == j);
633
634 return raw_pull_job_on_open_disk_generic(i, j, "roothash", &i->roothash_temp_path);
635 }
636
637 static int raw_pull_job_on_open_disk_settings(PullJob *j) {
638 RawPull *i;
639
640 assert(j);
641 assert(j->userdata);
642
643 i = j->userdata;
644 assert(i->settings_job == j);
645
646 return raw_pull_job_on_open_disk_generic(i, j, "settings", &i->settings_temp_path);
647 }
648
649 static void raw_pull_job_on_progress(PullJob *j) {
650 RawPull *i;
651
652 assert(j);
653 assert(j->userdata);
654
655 i = j->userdata;
656
657 raw_pull_report_progress(i, RAW_DOWNLOADING);
658 }
659
660 int raw_pull_start(
661 RawPull *i,
662 const char *url,
663 const char *local,
664 bool force_local,
665 ImportVerify verify,
666 bool settings,
667 bool roothash) {
668
669 int r;
670
671 assert(i);
672 assert(verify < _IMPORT_VERIFY_MAX);
673 assert(verify >= 0);
674
675 if (!http_url_is_valid(url))
676 return -EINVAL;
677
678 if (local && !machine_name_is_valid(local))
679 return -EINVAL;
680
681 if (i->raw_job)
682 return -EBUSY;
683
684 r = free_and_strdup(&i->local, local);
685 if (r < 0)
686 return r;
687
688 i->force_local = force_local;
689 i->verify = verify;
690 i->settings = settings;
691 i->roothash = roothash;
692
693 /* Queue job for the image itself */
694 r = pull_job_new(&i->raw_job, url, i->glue, i);
695 if (r < 0)
696 return r;
697
698 i->raw_job->on_finished = raw_pull_job_on_finished;
699 i->raw_job->on_open_disk = raw_pull_job_on_open_disk_raw;
700 i->raw_job->on_progress = raw_pull_job_on_progress;
701 i->raw_job->calc_checksum = verify != IMPORT_VERIFY_NO;
702 i->raw_job->grow_machine_directory = i->grow_machine_directory;
703
704 r = pull_find_old_etags(url, i->image_root, DT_REG, ".raw-", ".raw", &i->raw_job->old_etags);
705 if (r < 0)
706 return r;
707
708 if (roothash) {
709 r = pull_make_auxiliary_job(&i->roothash_job, url, raw_strip_suffixes, ".roothash", i->glue, raw_pull_job_on_finished, i);
710 if (r < 0)
711 return r;
712
713 i->roothash_job->on_open_disk = raw_pull_job_on_open_disk_roothash;
714 i->roothash_job->on_progress = raw_pull_job_on_progress;
715 i->roothash_job->calc_checksum = verify != IMPORT_VERIFY_NO;
716 }
717
718 if (settings) {
719 r = pull_make_auxiliary_job(&i->settings_job, url, raw_strip_suffixes, ".nspawn", i->glue, raw_pull_job_on_finished, i);
720 if (r < 0)
721 return r;
722
723 i->settings_job->on_open_disk = raw_pull_job_on_open_disk_settings;
724 i->settings_job->on_progress = raw_pull_job_on_progress;
725 i->settings_job->calc_checksum = verify != IMPORT_VERIFY_NO;
726 }
727
728 r = pull_make_verification_jobs(&i->checksum_job, &i->signature_job, verify, url, i->glue, raw_pull_job_on_finished, i);
729 if (r < 0)
730 return r;
731
732 r = pull_job_begin(i->raw_job);
733 if (r < 0)
734 return r;
735
736 if (i->roothash_job) {
737 r = pull_job_begin(i->roothash_job);
738 if (r < 0)
739 return r;
740 }
741
742 if (i->settings_job) {
743 r = pull_job_begin(i->settings_job);
744 if (r < 0)
745 return r;
746 }
747
748 if (i->checksum_job) {
749 i->checksum_job->on_progress = raw_pull_job_on_progress;
750 i->checksum_job->style = VERIFICATION_PER_FILE;
751
752 r = pull_job_begin(i->checksum_job);
753 if (r < 0)
754 return r;
755 }
756
757 if (i->signature_job) {
758 i->signature_job->on_progress = raw_pull_job_on_progress;
759
760 r = pull_job_begin(i->signature_job);
761 if (r < 0)
762 return r;
763 }
764
765 return 0;
766 }