]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/import/pull-raw.c
tree-wide: use TAKE_PTR() and TAKE_FD() macros
[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 = TAKE_PTR(i);
170
171 return 0;
172 }
173
174 static void raw_pull_report_progress(RawPull *i, RawProgress p) {
175 unsigned percent;
176
177 assert(i);
178
179 switch (p) {
180
181 case RAW_DOWNLOADING: {
182 unsigned remain = 80;
183
184 percent = 0;
185
186 if (i->settings_job) {
187 percent += i->settings_job->progress_percent * 5 / 100;
188 remain -= 5;
189 }
190
191 if (i->roothash_job) {
192 percent += i->roothash_job->progress_percent * 5 / 100;
193 remain -= 5;
194 }
195
196 if (i->checksum_job) {
197 percent += i->checksum_job->progress_percent * 5 / 100;
198 remain -= 5;
199 }
200
201 if (i->signature_job) {
202 percent += i->signature_job->progress_percent * 5 / 100;
203 remain -= 5;
204 }
205
206 if (i->raw_job)
207 percent += i->raw_job->progress_percent * remain / 100;
208 break;
209 }
210
211 case RAW_VERIFYING:
212 percent = 80;
213 break;
214
215 case RAW_UNPACKING:
216 percent = 85;
217 break;
218
219 case RAW_FINALIZING:
220 percent = 90;
221 break;
222
223 case RAW_COPYING:
224 percent = 95;
225 break;
226
227 default:
228 assert_not_reached("Unknown progress state");
229 }
230
231 sd_notifyf(false, "X_IMPORT_PROGRESS=%u", percent);
232 log_debug("Combined progress %u%%", percent);
233 }
234
235 static int raw_pull_maybe_convert_qcow2(RawPull *i) {
236 _cleanup_close_ int converted_fd = -1;
237 _cleanup_free_ char *t = NULL;
238 int r;
239
240 assert(i);
241 assert(i->raw_job);
242
243 r = qcow2_detect(i->raw_job->disk_fd);
244 if (r < 0)
245 return log_error_errno(r, "Failed to detect whether this is a QCOW2 image: %m");
246 if (r == 0)
247 return 0;
248
249 /* This is a QCOW2 image, let's convert it */
250 r = tempfn_random(i->final_path, NULL, &t);
251 if (r < 0)
252 return log_oom();
253
254 converted_fd = open(t, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664);
255 if (converted_fd < 0)
256 return log_error_errno(errno, "Failed to create %s: %m", t);
257
258 r = chattr_fd(converted_fd, FS_NOCOW_FL, FS_NOCOW_FL);
259 if (r < 0)
260 log_warning_errno(r, "Failed to set file attributes on %s: %m", t);
261
262 log_info("Unpacking QCOW2 file.");
263
264 r = qcow2_convert(i->raw_job->disk_fd, converted_fd);
265 if (r < 0) {
266 unlink(t);
267 return log_error_errno(r, "Failed to convert qcow2 image: %m");
268 }
269
270 (void) unlink(i->temp_path);
271 free_and_replace(i->temp_path, t);
272
273 safe_close(i->raw_job->disk_fd);
274 i->raw_job->disk_fd = TAKE_FD(converted_fd);
275
276 return 1;
277 }
278
279 static int raw_pull_determine_path(RawPull *i, const char *suffix, char **field) {
280 int r;
281
282 assert(i);
283 assert(field);
284
285 if (*field)
286 return 0;
287
288 assert(i->raw_job);
289
290 r = pull_make_path(i->raw_job->url, i->raw_job->etag, i->image_root, ".raw-", suffix, field);
291 if (r < 0)
292 return log_oom();
293
294 return 1;
295 }
296
297 static int raw_pull_copy_auxiliary_file(
298 RawPull *i,
299 const char *suffix,
300 char **path) {
301
302 const char *local;
303 int r;
304
305 assert(i);
306 assert(suffix);
307 assert(path);
308
309 r = raw_pull_determine_path(i, suffix, path);
310 if (r < 0)
311 return r;
312
313 local = strjoina(i->image_root, "/", i->local, suffix);
314
315 r = copy_file_atomic(*path, local, 0644, 0, COPY_REFLINK | (i->force_local ? COPY_REPLACE : 0));
316 if (r == -EEXIST)
317 log_warning_errno(r, "File %s already exists, not replacing.", local);
318 else if (r == -ENOENT)
319 log_debug_errno(r, "Skipping creation of auxiliary file, since none was found.");
320 else if (r < 0)
321 log_warning_errno(r, "Failed to copy file %s, ignoring: %m", local);
322 else
323 log_info("Created new file %s.", local);
324
325 return 0;
326 }
327
328 static int raw_pull_make_local_copy(RawPull *i) {
329 _cleanup_free_ char *tp = NULL;
330 _cleanup_close_ int dfd = -1;
331 const char *p;
332 int r;
333
334 assert(i);
335 assert(i->raw_job);
336
337 if (!i->local)
338 return 0;
339
340 if (i->raw_job->etag_exists) {
341 /* We have downloaded this one previously, reopen it */
342
343 assert(i->raw_job->disk_fd < 0);
344
345 i->raw_job->disk_fd = open(i->final_path, O_RDONLY|O_NOCTTY|O_CLOEXEC);
346 if (i->raw_job->disk_fd < 0)
347 return log_error_errno(errno, "Failed to open vendor image: %m");
348 } else {
349 /* We freshly downloaded the image, use it */
350
351 assert(i->raw_job->disk_fd >= 0);
352
353 if (lseek(i->raw_job->disk_fd, SEEK_SET, 0) == (off_t) -1)
354 return log_error_errno(errno, "Failed to seek to beginning of vendor image: %m");
355 }
356
357 p = strjoina(i->image_root, "/", i->local, ".raw");
358
359 if (i->force_local)
360 (void) rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME);
361
362 r = tempfn_random(p, NULL, &tp);
363 if (r < 0)
364 return log_oom();
365
366 dfd = open(tp, O_WRONLY|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664);
367 if (dfd < 0)
368 return log_error_errno(errno, "Failed to create writable copy of image: %m");
369
370 /* Turn off COW writing. This should greatly improve
371 * performance on COW file systems like btrfs, since it
372 * reduces fragmentation caused by not allowing in-place
373 * writes. */
374 r = chattr_fd(dfd, FS_NOCOW_FL, FS_NOCOW_FL);
375 if (r < 0)
376 log_warning_errno(r, "Failed to set file attributes on %s: %m", tp);
377
378 r = copy_bytes(i->raw_job->disk_fd, dfd, (uint64_t) -1, COPY_REFLINK);
379 if (r < 0) {
380 unlink(tp);
381 return log_error_errno(r, "Failed to make writable copy of image: %m");
382 }
383
384 (void) copy_times(i->raw_job->disk_fd, dfd);
385 (void) copy_xattr(i->raw_job->disk_fd, dfd);
386
387 dfd = safe_close(dfd);
388
389 r = rename(tp, p);
390 if (r < 0) {
391 r = log_error_errno(errno, "Failed to move writable image into place: %m");
392 unlink(tp);
393 return r;
394 }
395
396 log_info("Created new local image '%s'.", i->local);
397
398 if (i->roothash) {
399 r = raw_pull_copy_auxiliary_file(i, ".roothash", &i->roothash_path);
400 if (r < 0)
401 return r;
402 }
403
404 if (i->settings) {
405 r = raw_pull_copy_auxiliary_file(i, ".nspawn", &i->settings_path);
406 if (r < 0)
407 return r;
408 }
409
410 return 0;
411 }
412
413 static bool raw_pull_is_done(RawPull *i) {
414 assert(i);
415 assert(i->raw_job);
416
417 if (!PULL_JOB_IS_COMPLETE(i->raw_job))
418 return false;
419 if (i->roothash_job && !PULL_JOB_IS_COMPLETE(i->roothash_job))
420 return false;
421 if (i->settings_job && !PULL_JOB_IS_COMPLETE(i->settings_job))
422 return false;
423 if (i->checksum_job && !PULL_JOB_IS_COMPLETE(i->checksum_job))
424 return false;
425 if (i->signature_job && !PULL_JOB_IS_COMPLETE(i->signature_job))
426 return false;
427
428 return true;
429 }
430
431 static int raw_pull_rename_auxiliary_file(
432 RawPull *i,
433 const char *suffix,
434 char **temp_path,
435 char **path) {
436
437 int r;
438
439 assert(i);
440 assert(temp_path);
441 assert(suffix);
442 assert(path);
443
444 /* Regenerate final name for this auxiliary file, we might know the etag of the file now, and we should
445 * incorporate it in the file name if we can */
446 *path = mfree(*path);
447 r = raw_pull_determine_path(i, suffix, path);
448 if (r < 0)
449 return r;
450
451 r = import_make_read_only(*temp_path);
452 if (r < 0)
453 return r;
454
455 r = rename_noreplace(AT_FDCWD, *temp_path, AT_FDCWD, *path);
456 if (r < 0)
457 return log_error_errno(r, "Failed to rename file %s to %s: %m", *temp_path, *path);
458
459 *temp_path = mfree(*temp_path);
460
461 return 1;
462 }
463
464 static void raw_pull_job_on_finished(PullJob *j) {
465 RawPull *i;
466 int r;
467
468 assert(j);
469 assert(j->userdata);
470
471 i = j->userdata;
472 if (j == i->roothash_job) {
473 if (j->error != 0)
474 log_info_errno(j->error, "Root hash file could not be retrieved, proceeding without.");
475 } else if (j == i->settings_job) {
476 if (j->error != 0)
477 log_info_errno(j->error, "Settings file could not be retrieved, proceeding without.");
478 } else if (j->error != 0 && j != i->signature_job) {
479 if (j == i->checksum_job)
480 log_error_errno(j->error, "Failed to retrieve SHA256 checksum, cannot verify. (Try --verify=no?)");
481 else
482 log_error_errno(j->error, "Failed to retrieve image file. (Wrong URL?)");
483
484 r = j->error;
485 goto finish;
486 }
487
488 /* This is invoked if either the download completed
489 * successfully, or the download was skipped because we
490 * already have the etag. In this case ->etag_exists is
491 * true.
492 *
493 * We only do something when we got all three files */
494
495 if (!raw_pull_is_done(i))
496 return;
497
498 if (i->signature_job && i->checksum_job->style == VERIFICATION_PER_DIRECTORY && i->signature_job->error != 0) {
499 log_error_errno(j->error, "Failed to retrieve signature file, cannot verify. (Try --verify=no?)");
500
501 r = i->signature_job->error;
502 goto finish;
503 }
504
505 if (i->roothash_job)
506 i->roothash_job->disk_fd = safe_close(i->roothash_job->disk_fd);
507 if (i->settings_job)
508 i->settings_job->disk_fd = safe_close(i->settings_job->disk_fd);
509
510 r = raw_pull_determine_path(i, ".raw", &i->final_path);
511 if (r < 0)
512 goto finish;
513
514 if (!i->raw_job->etag_exists) {
515 /* This is a new download, verify it, and move it into place */
516 assert(i->raw_job->disk_fd >= 0);
517
518 raw_pull_report_progress(i, RAW_VERIFYING);
519
520 r = pull_verify(i->raw_job, i->roothash_job, i->settings_job, i->checksum_job, i->signature_job);
521 if (r < 0)
522 goto finish;
523
524 raw_pull_report_progress(i, RAW_UNPACKING);
525
526 r = raw_pull_maybe_convert_qcow2(i);
527 if (r < 0)
528 goto finish;
529
530 raw_pull_report_progress(i, RAW_FINALIZING);
531
532 r = import_make_read_only_fd(i->raw_job->disk_fd);
533 if (r < 0)
534 goto finish;
535
536 r = rename_noreplace(AT_FDCWD, i->temp_path, AT_FDCWD, i->final_path);
537 if (r < 0) {
538 log_error_errno(r, "Failed to rename raw file to %s: %m", i->final_path);
539 goto finish;
540 }
541
542 i->temp_path = mfree(i->temp_path);
543
544 if (i->roothash_job &&
545 i->roothash_job->error == 0) {
546 r = raw_pull_rename_auxiliary_file(i, ".roothash", &i->roothash_temp_path, &i->roothash_path);
547 if (r < 0)
548 goto finish;
549 }
550
551 if (i->settings_job &&
552 i->settings_job->error == 0) {
553 r = raw_pull_rename_auxiliary_file(i, ".nspawn", &i->settings_temp_path, &i->settings_path);
554 if (r < 0)
555 goto finish;
556 }
557 }
558
559 raw_pull_report_progress(i, RAW_COPYING);
560
561 r = raw_pull_make_local_copy(i);
562 if (r < 0)
563 goto finish;
564
565 r = 0;
566
567 finish:
568 if (i->on_finished)
569 i->on_finished(i, r, i->userdata);
570 else
571 sd_event_exit(i->event, r);
572 }
573
574 static int raw_pull_job_on_open_disk_generic(
575 RawPull *i,
576 PullJob *j,
577 const char *extra,
578 char **temp_path) {
579
580 int r;
581
582 assert(i);
583 assert(j);
584 assert(extra);
585 assert(temp_path);
586
587 if (!*temp_path) {
588 r = tempfn_random_child(i->image_root, extra, temp_path);
589 if (r < 0)
590 return log_oom();
591 }
592
593 (void) mkdir_parents_label(*temp_path, 0700);
594
595 j->disk_fd = open(*temp_path, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664);
596 if (j->disk_fd < 0)
597 return log_error_errno(errno, "Failed to create %s: %m", *temp_path);
598
599 return 0;
600 }
601
602 static int raw_pull_job_on_open_disk_raw(PullJob *j) {
603 RawPull *i;
604 int r;
605
606 assert(j);
607 assert(j->userdata);
608
609 i = j->userdata;
610 assert(i->raw_job == j);
611
612 r = raw_pull_job_on_open_disk_generic(i, j, "raw", &i->temp_path);
613 if (r < 0)
614 return r;
615
616 r = chattr_fd(j->disk_fd, FS_NOCOW_FL, FS_NOCOW_FL);
617 if (r < 0)
618 log_warning_errno(r, "Failed to set file attributes on %s, ignoring: %m", i->temp_path);
619
620 return 0;
621 }
622
623 static int raw_pull_job_on_open_disk_roothash(PullJob *j) {
624 RawPull *i;
625
626 assert(j);
627 assert(j->userdata);
628
629 i = j->userdata;
630 assert(i->roothash_job == j);
631
632 return raw_pull_job_on_open_disk_generic(i, j, "roothash", &i->roothash_temp_path);
633 }
634
635 static int raw_pull_job_on_open_disk_settings(PullJob *j) {
636 RawPull *i;
637
638 assert(j);
639 assert(j->userdata);
640
641 i = j->userdata;
642 assert(i->settings_job == j);
643
644 return raw_pull_job_on_open_disk_generic(i, j, "settings", &i->settings_temp_path);
645 }
646
647 static void raw_pull_job_on_progress(PullJob *j) {
648 RawPull *i;
649
650 assert(j);
651 assert(j->userdata);
652
653 i = j->userdata;
654
655 raw_pull_report_progress(i, RAW_DOWNLOADING);
656 }
657
658 int raw_pull_start(
659 RawPull *i,
660 const char *url,
661 const char *local,
662 bool force_local,
663 ImportVerify verify,
664 bool settings,
665 bool roothash) {
666
667 int r;
668
669 assert(i);
670 assert(verify < _IMPORT_VERIFY_MAX);
671 assert(verify >= 0);
672
673 if (!http_url_is_valid(url))
674 return -EINVAL;
675
676 if (local && !machine_name_is_valid(local))
677 return -EINVAL;
678
679 if (i->raw_job)
680 return -EBUSY;
681
682 r = free_and_strdup(&i->local, local);
683 if (r < 0)
684 return r;
685
686 i->force_local = force_local;
687 i->verify = verify;
688 i->settings = settings;
689 i->roothash = roothash;
690
691 /* Queue job for the image itself */
692 r = pull_job_new(&i->raw_job, url, i->glue, i);
693 if (r < 0)
694 return r;
695
696 i->raw_job->on_finished = raw_pull_job_on_finished;
697 i->raw_job->on_open_disk = raw_pull_job_on_open_disk_raw;
698 i->raw_job->on_progress = raw_pull_job_on_progress;
699 i->raw_job->calc_checksum = verify != IMPORT_VERIFY_NO;
700 i->raw_job->grow_machine_directory = i->grow_machine_directory;
701
702 r = pull_find_old_etags(url, i->image_root, DT_REG, ".raw-", ".raw", &i->raw_job->old_etags);
703 if (r < 0)
704 return r;
705
706 if (roothash) {
707 r = pull_make_auxiliary_job(&i->roothash_job, url, raw_strip_suffixes, ".roothash", i->glue, raw_pull_job_on_finished, i);
708 if (r < 0)
709 return r;
710
711 i->roothash_job->on_open_disk = raw_pull_job_on_open_disk_roothash;
712 i->roothash_job->on_progress = raw_pull_job_on_progress;
713 i->roothash_job->calc_checksum = verify != IMPORT_VERIFY_NO;
714 }
715
716 if (settings) {
717 r = pull_make_auxiliary_job(&i->settings_job, url, raw_strip_suffixes, ".nspawn", i->glue, raw_pull_job_on_finished, i);
718 if (r < 0)
719 return r;
720
721 i->settings_job->on_open_disk = raw_pull_job_on_open_disk_settings;
722 i->settings_job->on_progress = raw_pull_job_on_progress;
723 i->settings_job->calc_checksum = verify != IMPORT_VERIFY_NO;
724 }
725
726 r = pull_make_verification_jobs(&i->checksum_job, &i->signature_job, verify, url, i->glue, raw_pull_job_on_finished, i);
727 if (r < 0)
728 return r;
729
730 r = pull_job_begin(i->raw_job);
731 if (r < 0)
732 return r;
733
734 if (i->roothash_job) {
735 r = pull_job_begin(i->roothash_job);
736 if (r < 0)
737 return r;
738 }
739
740 if (i->settings_job) {
741 r = pull_job_begin(i->settings_job);
742 if (r < 0)
743 return r;
744 }
745
746 if (i->checksum_job) {
747 i->checksum_job->on_progress = raw_pull_job_on_progress;
748 i->checksum_job->style = VERIFICATION_PER_FILE;
749
750 r = pull_job_begin(i->checksum_job);
751 if (r < 0)
752 return r;
753 }
754
755 if (i->signature_job) {
756 i->signature_job->on_progress = raw_pull_job_on_progress;
757
758 r = pull_job_begin(i->signature_job);
759 if (r < 0)
760 return r;
761 }
762
763 return 0;
764 }