Line data Source code
1 : /* SPDX-License-Identifier: LGPL-2.1+ */
2 :
3 : #include <curl/curl.h>
4 : #include <sys/prctl.h>
5 :
6 : #include "sd-daemon.h"
7 :
8 : #include "alloc-util.h"
9 : #include "btrfs-util.h"
10 : #include "copy.h"
11 : #include "curl-util.h"
12 : #include "fd-util.h"
13 : #include "fs-util.h"
14 : #include "hostname-util.h"
15 : #include "import-common.h"
16 : #include "import-util.h"
17 : #include "macro.h"
18 : #include "mkdir.h"
19 : #include "path-util.h"
20 : #include "process-util.h"
21 : #include "pull-common.h"
22 : #include "pull-job.h"
23 : #include "pull-tar.h"
24 : #include "rm-rf.h"
25 : #include "string-util.h"
26 : #include "strv.h"
27 : #include "tmpfile-util.h"
28 : #include "utf8.h"
29 : #include "util.h"
30 : #include "web-util.h"
31 :
32 : typedef enum TarProgress {
33 : TAR_DOWNLOADING,
34 : TAR_VERIFYING,
35 : TAR_FINALIZING,
36 : TAR_COPYING,
37 : } TarProgress;
38 :
39 : struct TarPull {
40 : sd_event *event;
41 : CurlGlue *glue;
42 :
43 : char *image_root;
44 :
45 : PullJob *tar_job;
46 : PullJob *settings_job;
47 : PullJob *checksum_job;
48 : PullJob *signature_job;
49 :
50 : TarPullFinished on_finished;
51 : void *userdata;
52 :
53 : char *local;
54 : bool force_local;
55 : bool settings;
56 :
57 : pid_t tar_pid;
58 :
59 : char *final_path;
60 : char *temp_path;
61 :
62 : char *settings_path;
63 : char *settings_temp_path;
64 :
65 : ImportVerify verify;
66 : };
67 :
68 0 : TarPull* tar_pull_unref(TarPull *i) {
69 0 : if (!i)
70 0 : return NULL;
71 :
72 0 : if (i->tar_pid > 1) {
73 0 : (void) kill_and_sigcont(i->tar_pid, SIGKILL);
74 0 : (void) wait_for_terminate(i->tar_pid, NULL);
75 : }
76 :
77 0 : pull_job_unref(i->tar_job);
78 0 : pull_job_unref(i->settings_job);
79 0 : pull_job_unref(i->checksum_job);
80 0 : pull_job_unref(i->signature_job);
81 :
82 0 : curl_glue_unref(i->glue);
83 0 : sd_event_unref(i->event);
84 :
85 0 : if (i->temp_path) {
86 0 : (void) rm_rf(i->temp_path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME);
87 0 : free(i->temp_path);
88 : }
89 :
90 0 : if (i->settings_temp_path) {
91 0 : (void) unlink(i->settings_temp_path);
92 0 : free(i->settings_temp_path);
93 : }
94 :
95 0 : free(i->final_path);
96 0 : free(i->settings_path);
97 0 : free(i->image_root);
98 0 : free(i->local);
99 :
100 0 : return mfree(i);
101 : }
102 :
103 0 : int tar_pull_new(
104 : TarPull **ret,
105 : sd_event *event,
106 : const char *image_root,
107 : TarPullFinished on_finished,
108 : void *userdata) {
109 :
110 0 : _cleanup_(curl_glue_unrefp) CurlGlue *g = NULL;
111 0 : _cleanup_(sd_event_unrefp) sd_event *e = NULL;
112 0 : _cleanup_(tar_pull_unrefp) TarPull *i = NULL;
113 0 : _cleanup_free_ char *root = NULL;
114 : int r;
115 :
116 0 : assert(ret);
117 :
118 0 : root = strdup(image_root ?: "/var/lib/machines");
119 0 : if (!root)
120 0 : return -ENOMEM;
121 :
122 0 : if (event)
123 0 : e = sd_event_ref(event);
124 : else {
125 0 : r = sd_event_default(&e);
126 0 : if (r < 0)
127 0 : return r;
128 : }
129 :
130 0 : r = curl_glue_new(&g, e);
131 0 : if (r < 0)
132 0 : return r;
133 :
134 0 : i = new(TarPull, 1);
135 0 : if (!i)
136 0 : return -ENOMEM;
137 :
138 0 : *i = (TarPull) {
139 : .on_finished = on_finished,
140 : .userdata = userdata,
141 0 : .image_root = TAKE_PTR(root),
142 0 : .event = TAKE_PTR(e),
143 0 : .glue = TAKE_PTR(g),
144 : };
145 :
146 0 : i->glue->on_finished = pull_job_curl_on_finished;
147 0 : i->glue->userdata = i;
148 :
149 0 : *ret = TAKE_PTR(i);
150 :
151 0 : return 0;
152 : }
153 :
154 0 : static void tar_pull_report_progress(TarPull *i, TarProgress p) {
155 : unsigned percent;
156 :
157 0 : assert(i);
158 :
159 0 : switch (p) {
160 :
161 0 : case TAR_DOWNLOADING: {
162 0 : unsigned remain = 85;
163 :
164 0 : percent = 0;
165 :
166 0 : if (i->settings_job) {
167 0 : percent += i->settings_job->progress_percent * 5 / 100;
168 0 : remain -= 5;
169 : }
170 :
171 0 : if (i->checksum_job) {
172 0 : percent += i->checksum_job->progress_percent * 5 / 100;
173 0 : remain -= 5;
174 : }
175 :
176 0 : if (i->signature_job) {
177 0 : percent += i->signature_job->progress_percent * 5 / 100;
178 0 : remain -= 5;
179 : }
180 :
181 0 : if (i->tar_job)
182 0 : percent += i->tar_job->progress_percent * remain / 100;
183 0 : break;
184 : }
185 :
186 0 : case TAR_VERIFYING:
187 0 : percent = 85;
188 0 : break;
189 :
190 0 : case TAR_FINALIZING:
191 0 : percent = 90;
192 0 : break;
193 :
194 0 : case TAR_COPYING:
195 0 : percent = 95;
196 0 : break;
197 :
198 0 : default:
199 0 : assert_not_reached("Unknown progress state");
200 : }
201 :
202 0 : sd_notifyf(false, "X_IMPORT_PROGRESS=%u", percent);
203 0 : log_debug("Combined progress %u%%", percent);
204 0 : }
205 :
206 0 : static int tar_pull_determine_path(TarPull *i, const char *suffix, char **field) {
207 : int r;
208 :
209 0 : assert(i);
210 0 : assert(field);
211 :
212 0 : if (*field)
213 0 : return 0;
214 :
215 0 : assert(i->tar_job);
216 :
217 0 : r = pull_make_path(i->tar_job->url, i->tar_job->etag, i->image_root, ".tar-", suffix, field);
218 0 : if (r < 0)
219 0 : return log_oom();
220 :
221 0 : return 1;
222 : }
223 :
224 0 : static int tar_pull_make_local_copy(TarPull *i) {
225 : int r;
226 :
227 0 : assert(i);
228 0 : assert(i->tar_job);
229 :
230 0 : if (!i->local)
231 0 : return 0;
232 :
233 0 : r = pull_make_local_copy(i->final_path, i->image_root, i->local, i->force_local);
234 0 : if (r < 0)
235 0 : return r;
236 :
237 0 : if (i->settings) {
238 : const char *local_settings;
239 0 : assert(i->settings_job);
240 :
241 0 : r = tar_pull_determine_path(i, ".nspawn", &i->settings_path);
242 0 : if (r < 0)
243 0 : return r;
244 :
245 0 : local_settings = strjoina(i->image_root, "/", i->local, ".nspawn");
246 :
247 0 : r = copy_file_atomic(i->settings_path, local_settings, 0664, 0, 0, COPY_REFLINK | (i->force_local ? COPY_REPLACE : 0));
248 0 : if (r == -EEXIST)
249 0 : log_warning_errno(r, "Settings file %s already exists, not replacing.", local_settings);
250 0 : else if (r == -ENOENT)
251 0 : log_debug_errno(r, "Skipping creation of settings file, since none was found.");
252 0 : else if (r < 0)
253 0 : log_warning_errno(r, "Failed to copy settings files %s, ignoring: %m", local_settings);
254 : else
255 0 : log_info("Created new settings file %s.", local_settings);
256 : }
257 :
258 0 : return 0;
259 : }
260 :
261 0 : static bool tar_pull_is_done(TarPull *i) {
262 0 : assert(i);
263 0 : assert(i->tar_job);
264 :
265 0 : if (!PULL_JOB_IS_COMPLETE(i->tar_job))
266 0 : return false;
267 0 : if (i->settings_job && !PULL_JOB_IS_COMPLETE(i->settings_job))
268 0 : return false;
269 0 : if (i->checksum_job && !PULL_JOB_IS_COMPLETE(i->checksum_job))
270 0 : return false;
271 0 : if (i->signature_job && !PULL_JOB_IS_COMPLETE(i->signature_job))
272 0 : return false;
273 :
274 0 : return true;
275 : }
276 :
277 0 : static void tar_pull_job_on_finished(PullJob *j) {
278 : TarPull *i;
279 : int r;
280 :
281 0 : assert(j);
282 0 : assert(j->userdata);
283 :
284 0 : i = j->userdata;
285 :
286 0 : if (j == i->settings_job) {
287 0 : if (j->error != 0)
288 0 : log_info_errno(j->error, "Settings file could not be retrieved, proceeding without.");
289 0 : } else if (j->error != 0 && j != i->signature_job) {
290 0 : if (j == i->checksum_job)
291 0 : log_error_errno(j->error, "Failed to retrieve SHA256 checksum, cannot verify. (Try --verify=no?)");
292 : else
293 0 : log_error_errno(j->error, "Failed to retrieve image file. (Wrong URL?)");
294 :
295 0 : r = j->error;
296 0 : goto finish;
297 : }
298 :
299 : /* This is invoked if either the download completed
300 : * successfully, or the download was skipped because we
301 : * already have the etag. */
302 :
303 0 : if (!tar_pull_is_done(i))
304 0 : return;
305 :
306 0 : if (i->signature_job && i->checksum_job->style == VERIFICATION_PER_DIRECTORY && i->signature_job->error != 0) {
307 0 : log_error_errno(j->error, "Failed to retrieve signature file, cannot verify. (Try --verify=no?)");
308 :
309 0 : r = i->signature_job->error;
310 0 : goto finish;
311 : }
312 :
313 0 : i->tar_job->disk_fd = safe_close(i->tar_job->disk_fd);
314 0 : if (i->settings_job)
315 0 : i->settings_job->disk_fd = safe_close(i->settings_job->disk_fd);
316 :
317 0 : r = tar_pull_determine_path(i, NULL, &i->final_path);
318 0 : if (r < 0)
319 0 : goto finish;
320 :
321 0 : if (i->tar_pid > 0) {
322 0 : r = wait_for_terminate_and_check("tar", i->tar_pid, WAIT_LOG);
323 0 : i->tar_pid = 0;
324 0 : if (r < 0)
325 0 : goto finish;
326 0 : if (r != EXIT_SUCCESS) {
327 0 : r = -EIO;
328 0 : goto finish;
329 : }
330 : }
331 :
332 0 : if (!i->tar_job->etag_exists) {
333 : /* This is a new download, verify it, and move it into place */
334 :
335 0 : tar_pull_report_progress(i, TAR_VERIFYING);
336 :
337 0 : r = pull_verify(i->tar_job, NULL, i->settings_job, i->checksum_job, i->signature_job);
338 0 : if (r < 0)
339 0 : goto finish;
340 :
341 0 : tar_pull_report_progress(i, TAR_FINALIZING);
342 :
343 0 : r = import_make_read_only(i->temp_path);
344 0 : if (r < 0)
345 0 : goto finish;
346 :
347 0 : r = rename_noreplace(AT_FDCWD, i->temp_path, AT_FDCWD, i->final_path);
348 0 : if (r < 0) {
349 0 : log_error_errno(r, "Failed to rename to final image name to %s: %m", i->final_path);
350 0 : goto finish;
351 : }
352 :
353 0 : i->temp_path = mfree(i->temp_path);
354 :
355 0 : if (i->settings_job &&
356 0 : i->settings_job->error == 0) {
357 :
358 : /* Also move the settings file into place, if it exists. Note that we do so only if we also
359 : * moved the tar file in place, to keep things strictly in sync. */
360 0 : assert(i->settings_temp_path);
361 :
362 : /* Regenerate final name for this auxiliary file, we might know the etag of the file now, and
363 : * we should incorporate it in the file name if we can */
364 0 : i->settings_path = mfree(i->settings_path);
365 :
366 0 : r = tar_pull_determine_path(i, ".nspawn", &i->settings_path);
367 0 : if (r < 0)
368 0 : goto finish;
369 :
370 0 : r = import_make_read_only(i->settings_temp_path);
371 0 : if (r < 0)
372 0 : goto finish;
373 :
374 0 : r = rename_noreplace(AT_FDCWD, i->settings_temp_path, AT_FDCWD, i->settings_path);
375 0 : if (r < 0) {
376 0 : log_error_errno(r, "Failed to rename settings file to %s: %m", i->settings_path);
377 0 : goto finish;
378 : }
379 :
380 0 : i->settings_temp_path = mfree(i->settings_temp_path);
381 : }
382 : }
383 :
384 0 : tar_pull_report_progress(i, TAR_COPYING);
385 :
386 0 : r = tar_pull_make_local_copy(i);
387 0 : if (r < 0)
388 0 : goto finish;
389 :
390 0 : r = 0;
391 :
392 0 : finish:
393 0 : if (i->on_finished)
394 0 : i->on_finished(i, r, i->userdata);
395 : else
396 0 : sd_event_exit(i->event, r);
397 : }
398 :
399 0 : static int tar_pull_job_on_open_disk_tar(PullJob *j) {
400 : TarPull *i;
401 : int r;
402 :
403 0 : assert(j);
404 0 : assert(j->userdata);
405 :
406 0 : i = j->userdata;
407 0 : assert(i->tar_job == j);
408 0 : assert(i->tar_pid <= 0);
409 :
410 0 : if (!i->temp_path) {
411 0 : r = tempfn_random_child(i->image_root, "tar", &i->temp_path);
412 0 : if (r < 0)
413 0 : return log_oom();
414 : }
415 :
416 0 : mkdir_parents_label(i->temp_path, 0700);
417 :
418 0 : r = btrfs_subvol_make(i->temp_path);
419 0 : if (r == -ENOTTY) {
420 0 : if (mkdir(i->temp_path, 0755) < 0)
421 0 : return log_error_errno(errno, "Failed to create directory %s: %m", i->temp_path);
422 0 : } else if (r < 0)
423 0 : return log_error_errno(r, "Failed to create subvolume %s: %m", i->temp_path);
424 : else
425 0 : (void) import_assign_pool_quota_and_warn(i->temp_path);
426 :
427 0 : j->disk_fd = import_fork_tar_x(i->temp_path, &i->tar_pid);
428 0 : if (j->disk_fd < 0)
429 0 : return j->disk_fd;
430 :
431 0 : return 0;
432 : }
433 :
434 0 : static int tar_pull_job_on_open_disk_settings(PullJob *j) {
435 : TarPull *i;
436 : int r;
437 :
438 0 : assert(j);
439 0 : assert(j->userdata);
440 :
441 0 : i = j->userdata;
442 0 : assert(i->settings_job == j);
443 :
444 0 : if (!i->settings_temp_path) {
445 0 : r = tempfn_random_child(i->image_root, "settings", &i->settings_temp_path);
446 0 : if (r < 0)
447 0 : return log_oom();
448 : }
449 :
450 0 : mkdir_parents_label(i->settings_temp_path, 0700);
451 :
452 0 : j->disk_fd = open(i->settings_temp_path, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664);
453 0 : if (j->disk_fd < 0)
454 0 : return log_error_errno(errno, "Failed to create %s: %m", i->settings_temp_path);
455 :
456 0 : return 0;
457 : }
458 :
459 0 : static void tar_pull_job_on_progress(PullJob *j) {
460 : TarPull *i;
461 :
462 0 : assert(j);
463 0 : assert(j->userdata);
464 :
465 0 : i = j->userdata;
466 :
467 0 : tar_pull_report_progress(i, TAR_DOWNLOADING);
468 0 : }
469 :
470 0 : int tar_pull_start(
471 : TarPull *i,
472 : const char *url,
473 : const char *local,
474 : bool force_local,
475 : ImportVerify verify,
476 : bool settings) {
477 :
478 : int r;
479 :
480 0 : assert(i);
481 0 : assert(verify < _IMPORT_VERIFY_MAX);
482 0 : assert(verify >= 0);
483 :
484 0 : if (!http_url_is_valid(url))
485 0 : return -EINVAL;
486 :
487 0 : if (local && !machine_name_is_valid(local))
488 0 : return -EINVAL;
489 :
490 0 : if (i->tar_job)
491 0 : return -EBUSY;
492 :
493 0 : r = free_and_strdup(&i->local, local);
494 0 : if (r < 0)
495 0 : return r;
496 :
497 0 : i->force_local = force_local;
498 0 : i->verify = verify;
499 0 : i->settings = settings;
500 :
501 : /* Set up download job for TAR file */
502 0 : r = pull_job_new(&i->tar_job, url, i->glue, i);
503 0 : if (r < 0)
504 0 : return r;
505 :
506 0 : i->tar_job->on_finished = tar_pull_job_on_finished;
507 0 : i->tar_job->on_open_disk = tar_pull_job_on_open_disk_tar;
508 0 : i->tar_job->on_progress = tar_pull_job_on_progress;
509 0 : i->tar_job->calc_checksum = verify != IMPORT_VERIFY_NO;
510 :
511 0 : r = pull_find_old_etags(url, i->image_root, DT_DIR, ".tar-", NULL, &i->tar_job->old_etags);
512 0 : if (r < 0)
513 0 : return r;
514 :
515 : /* Set up download job for the settings file (.nspawn) */
516 0 : if (settings) {
517 0 : r = pull_make_auxiliary_job(&i->settings_job, url, tar_strip_suffixes, ".nspawn", i->glue, tar_pull_job_on_finished, i);
518 0 : if (r < 0)
519 0 : return r;
520 :
521 0 : i->settings_job->on_open_disk = tar_pull_job_on_open_disk_settings;
522 0 : i->settings_job->on_progress = tar_pull_job_on_progress;
523 0 : i->settings_job->calc_checksum = verify != IMPORT_VERIFY_NO;
524 : }
525 :
526 : /* Set up download of checksum/signature files */
527 0 : r = pull_make_verification_jobs(&i->checksum_job, &i->signature_job, verify, url, i->glue, tar_pull_job_on_finished, i);
528 0 : if (r < 0)
529 0 : return r;
530 :
531 0 : r = pull_job_begin(i->tar_job);
532 0 : if (r < 0)
533 0 : return r;
534 :
535 0 : if (i->settings_job) {
536 0 : r = pull_job_begin(i->settings_job);
537 0 : if (r < 0)
538 0 : return r;
539 : }
540 :
541 0 : if (i->checksum_job) {
542 0 : i->checksum_job->on_progress = tar_pull_job_on_progress;
543 0 : i->checksum_job->style = VERIFICATION_PER_FILE;
544 :
545 0 : r = pull_job_begin(i->checksum_job);
546 0 : if (r < 0)
547 0 : return r;
548 : }
549 :
550 0 : if (i->signature_job) {
551 0 : i->signature_job->on_progress = tar_pull_job_on_progress;
552 :
553 0 : r = pull_job_begin(i->signature_job);
554 0 : if (r < 0)
555 0 : return r;
556 : }
557 :
558 0 : return 0;
559 : }
|