2 This file is part of systemd.
4 Copyright 2015 Lennart Poettering
6 systemd is free software; you can redistribute it and/or modify it
7 under the terms of the GNU Lesser General Public License as published by
8 the Free Software Foundation; either version 2.1 of the License, or
9 (at your option) any later version.
11 systemd is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
16 You should have received a copy of the GNU Lesser General Public License
17 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 #include <curl/curl.h>
21 #include <sys/prctl.h>
23 #include "sd-daemon.h"
25 #include "alloc-util.h"
26 #include "btrfs-util.h"
28 #include "curl-util.h"
32 #include "hostname-util.h"
33 #include "import-common.h"
34 #include "import-util.h"
37 #include "path-util.h"
38 #include "process-util.h"
39 #include "pull-common.h"
43 #include "string-util.h"
49 typedef enum TarProgress
{
63 PullJob
*settings_job
;
64 PullJob
*checksum_job
;
65 PullJob
*signature_job
;
67 TarPullFinished on_finished
;
72 bool grow_machine_directory
;
81 char *settings_temp_path
;
86 TarPull
* tar_pull_unref(TarPull
*i
) {
91 (void) kill_and_sigcont(i
->tar_pid
, SIGKILL
);
92 (void) wait_for_terminate(i
->tar_pid
, NULL
);
95 pull_job_unref(i
->tar_job
);
96 pull_job_unref(i
->settings_job
);
97 pull_job_unref(i
->checksum_job
);
98 pull_job_unref(i
->signature_job
);
100 curl_glue_unref(i
->glue
);
101 sd_event_unref(i
->event
);
104 (void) rm_rf(i
->temp_path
, REMOVE_ROOT
|REMOVE_PHYSICAL
|REMOVE_SUBVOLUME
);
108 if (i
->settings_temp_path
) {
109 (void) unlink(i
->settings_temp_path
);
110 free(i
->settings_temp_path
);
114 free(i
->settings_path
);
125 const char *image_root
,
126 TarPullFinished on_finished
,
129 _cleanup_(tar_pull_unrefp
) TarPull
*i
= NULL
;
134 i
= new0(TarPull
, 1);
138 i
->on_finished
= on_finished
;
139 i
->userdata
= userdata
;
141 i
->image_root
= strdup(image_root
?: "/var/lib/machines");
145 i
->grow_machine_directory
= path_startswith(i
->image_root
, "/var/lib/machines");
148 i
->event
= sd_event_ref(event
);
150 r
= sd_event_default(&i
->event
);
155 r
= curl_glue_new(&i
->glue
, i
->event
);
159 i
->glue
->on_finished
= pull_job_curl_on_finished
;
160 i
->glue
->userdata
= i
;
168 static void tar_pull_report_progress(TarPull
*i
, TarProgress p
) {
175 case TAR_DOWNLOADING
: {
176 unsigned remain
= 85;
180 if (i
->settings_job
) {
181 percent
+= i
->settings_job
->progress_percent
* 5 / 100;
185 if (i
->checksum_job
) {
186 percent
+= i
->checksum_job
->progress_percent
* 5 / 100;
190 if (i
->signature_job
) {
191 percent
+= i
->signature_job
->progress_percent
* 5 / 100;
196 percent
+= i
->tar_job
->progress_percent
* remain
/ 100;
213 assert_not_reached("Unknown progress state");
216 sd_notifyf(false, "X_IMPORT_PROGRESS=%u", percent
);
217 log_debug("Combined progress %u%%", percent
);
220 static int tar_pull_make_local_copy(TarPull
*i
) {
229 if (!i
->final_path
) {
230 r
= pull_make_path(i
->tar_job
->url
, i
->tar_job
->etag
, i
->image_root
, ".tar-", NULL
, &i
->final_path
);
235 r
= pull_make_local_copy(i
->final_path
, i
->image_root
, i
->local
, i
->force_local
);
240 const char *local_settings
;
241 assert(i
->settings_job
);
243 if (!i
->settings_path
) {
244 r
= pull_make_path(i
->settings_job
->url
, i
->settings_job
->etag
, i
->image_root
, ".settings-", NULL
, &i
->settings_path
);
249 local_settings
= strjoina(i
->image_root
, "/", i
->local
, ".nspawn");
251 r
= copy_file_atomic(i
->settings_path
, local_settings
, 0664, i
->force_local
, 0);
253 log_warning_errno(r
, "Settings file %s already exists, not replacing.", local_settings
);
254 else if (r
< 0 && r
!= -ENOENT
)
255 log_warning_errno(r
, "Failed to copy settings files %s, ignoring: %m", local_settings
);
257 log_info("Created new settings file '%s.nspawn'", i
->local
);
263 static bool tar_pull_is_done(TarPull
*i
) {
267 if (!PULL_JOB_IS_COMPLETE(i
->tar_job
))
269 if (i
->settings_job
&& !PULL_JOB_IS_COMPLETE(i
->settings_job
))
271 if (i
->checksum_job
&& !PULL_JOB_IS_COMPLETE(i
->checksum_job
))
273 if (i
->signature_job
&& !PULL_JOB_IS_COMPLETE(i
->signature_job
))
279 static void tar_pull_job_on_finished(PullJob
*j
) {
288 if (j
== i
->settings_job
) {
290 log_info_errno(j
->error
, "Settings file could not be retrieved, proceeding without.");
291 } else if (j
->error
!= 0) {
292 if (j
== i
->checksum_job
)
293 log_error_errno(j
->error
, "Failed to retrieve SHA256 checksum, cannot verify. (Try --verify=no?)");
294 else if (j
== i
->signature_job
)
295 log_error_errno(j
->error
, "Failed to retrieve signature file, cannot verify. (Try --verify=no?)");
297 log_error_errno(j
->error
, "Failed to retrieve image file. (Wrong URL?)");
303 /* This is invoked if either the download completed
304 * successfully, or the download was skipped because we
305 * already have the etag. */
307 if (!tar_pull_is_done(i
))
310 i
->tar_job
->disk_fd
= safe_close(i
->tar_job
->disk_fd
);
312 i
->settings_job
->disk_fd
= safe_close(i
->settings_job
->disk_fd
);
314 if (i
->tar_pid
> 0) {
315 r
= wait_for_terminate_and_warn("tar", i
->tar_pid
, true);
325 if (!i
->tar_job
->etag_exists
) {
326 /* This is a new download, verify it, and move it into place */
328 tar_pull_report_progress(i
, TAR_VERIFYING
);
330 r
= pull_verify(i
->tar_job
, i
->settings_job
, i
->checksum_job
, i
->signature_job
);
334 tar_pull_report_progress(i
, TAR_FINALIZING
);
336 r
= import_make_read_only(i
->temp_path
);
340 r
= rename_noreplace(AT_FDCWD
, i
->temp_path
, AT_FDCWD
, i
->final_path
);
342 log_error_errno(r
, "Failed to rename to final image name: %m");
346 i
->temp_path
= mfree(i
->temp_path
);
348 if (i
->settings_job
&&
349 i
->settings_job
->error
== 0 &&
350 !i
->settings_job
->etag_exists
) {
352 assert(i
->settings_temp_path
);
353 assert(i
->settings_path
);
355 /* Also move the settings file into place, if
356 * it exist. Note that we do so only if we
357 * also moved the tar file in place, to keep
358 * things strictly in sync. */
360 r
= import_make_read_only(i
->settings_temp_path
);
364 r
= rename_noreplace(AT_FDCWD
, i
->settings_temp_path
, AT_FDCWD
, i
->settings_path
);
366 log_error_errno(r
, "Failed to rename settings file: %m");
370 i
->settings_temp_path
= mfree(i
->settings_temp_path
);
374 tar_pull_report_progress(i
, TAR_COPYING
);
376 r
= tar_pull_make_local_copy(i
);
384 i
->on_finished(i
, r
, i
->userdata
);
386 sd_event_exit(i
->event
, r
);
389 static int tar_pull_job_on_open_disk_tar(PullJob
*j
) {
397 assert(i
->tar_job
== j
);
398 assert(!i
->final_path
);
399 assert(!i
->temp_path
);
400 assert(i
->tar_pid
<= 0);
402 r
= pull_make_path(j
->url
, j
->etag
, i
->image_root
, ".tar-", NULL
, &i
->final_path
);
406 r
= tempfn_random(i
->final_path
, NULL
, &i
->temp_path
);
410 mkdir_parents_label(i
->temp_path
, 0700);
412 r
= btrfs_subvol_make(i
->temp_path
);
414 if (mkdir(i
->temp_path
, 0755) < 0)
415 return log_error_errno(errno
, "Failed to create directory %s: %m", i
->temp_path
);
417 return log_error_errno(r
, "Failed to create subvolume %s: %m", i
->temp_path
);
419 (void) import_assign_pool_quota_and_warn(i
->temp_path
);
421 j
->disk_fd
= import_fork_tar_x(i
->temp_path
, &i
->tar_pid
);
428 static int tar_pull_job_on_open_disk_settings(PullJob
*j
) {
436 assert(i
->settings_job
== j
);
437 assert(!i
->settings_path
);
438 assert(!i
->settings_temp_path
);
440 r
= pull_make_path(j
->url
, j
->etag
, i
->image_root
, ".settings-", NULL
, &i
->settings_path
);
444 r
= tempfn_random(i
->settings_path
, NULL
, &i
->settings_temp_path
);
448 mkdir_parents_label(i
->settings_temp_path
, 0700);
450 j
->disk_fd
= open(i
->settings_temp_path
, O_RDWR
|O_CREAT
|O_EXCL
|O_NOCTTY
|O_CLOEXEC
, 0664);
452 return log_error_errno(errno
, "Failed to create %s: %m", i
->settings_temp_path
);
457 static void tar_pull_job_on_progress(PullJob
*j
) {
465 tar_pull_report_progress(i
, TAR_DOWNLOADING
);
479 assert(verify
< _IMPORT_VERIFY_MAX
);
482 if (!http_url_is_valid(url
))
485 if (local
&& !machine_name_is_valid(local
))
491 r
= free_and_strdup(&i
->local
, local
);
495 i
->force_local
= force_local
;
497 i
->settings
= settings
;
499 /* Set up download job for TAR file */
500 r
= pull_job_new(&i
->tar_job
, url
, i
->glue
, i
);
504 i
->tar_job
->on_finished
= tar_pull_job_on_finished
;
505 i
->tar_job
->on_open_disk
= tar_pull_job_on_open_disk_tar
;
506 i
->tar_job
->on_progress
= tar_pull_job_on_progress
;
507 i
->tar_job
->calc_checksum
= verify
!= IMPORT_VERIFY_NO
;
508 i
->tar_job
->grow_machine_directory
= i
->grow_machine_directory
;
510 r
= pull_find_old_etags(url
, i
->image_root
, DT_DIR
, ".tar-", NULL
, &i
->tar_job
->old_etags
);
514 /* Set up download job for the settings file (.nspawn) */
516 r
= pull_make_settings_job(&i
->settings_job
, url
, i
->glue
, tar_pull_job_on_finished
, i
);
520 i
->settings_job
->on_open_disk
= tar_pull_job_on_open_disk_settings
;
521 i
->settings_job
->on_progress
= tar_pull_job_on_progress
;
522 i
->settings_job
->calc_checksum
= verify
!= IMPORT_VERIFY_NO
;
524 r
= pull_find_old_etags(i
->settings_job
->url
, i
->image_root
, DT_REG
, ".settings-", NULL
, &i
->settings_job
->old_etags
);
529 /* Set up download of checksum/signature files */
530 r
= pull_make_verification_jobs(&i
->checksum_job
, &i
->signature_job
, verify
, url
, i
->glue
, tar_pull_job_on_finished
, i
);
534 r
= pull_job_begin(i
->tar_job
);
538 if (i
->settings_job
) {
539 r
= pull_job_begin(i
->settings_job
);
544 if (i
->checksum_job
) {
545 i
->checksum_job
->on_progress
= tar_pull_job_on_progress
;
547 r
= pull_job_begin(i
->checksum_job
);
552 if (i
->signature_job
) {
553 i
->signature_job
->on_progress
= tar_pull_job_on_progress
;
555 r
= pull_job_begin(i
->signature_job
);