Branch data 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 : : }
|