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