1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2015 Lennart Poettering
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.
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.
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/>.
22 #include <sys/prctl.h>
23 #include <curl/curl.h>
25 #include "sd-daemon.h"
29 #include "btrfs-util.h"
34 #include "path-util.h"
35 #include "process-util.h"
36 #include "hostname-util.h"
37 #include "import-util.h"
38 #include "import-common.h"
39 #include "curl-util.h"
41 #include "pull-common.h"
44 typedef enum TarProgress
{
58 PullJob
*settings_job
;
59 PullJob
*checksum_job
;
60 PullJob
*signature_job
;
62 TarPullFinished on_finished
;
67 bool grow_machine_directory
;
76 char *settings_temp_path
;
81 TarPull
* tar_pull_unref(TarPull
*i
) {
86 (void) kill_and_sigcont(i
->tar_pid
, SIGKILL
);
87 (void) wait_for_terminate(i
->tar_pid
, NULL
);
90 pull_job_unref(i
->tar_job
);
91 pull_job_unref(i
->settings_job
);
92 pull_job_unref(i
->checksum_job
);
93 pull_job_unref(i
->signature_job
);
95 curl_glue_unref(i
->glue
);
96 sd_event_unref(i
->event
);
99 (void) rm_rf(i
->temp_path
, REMOVE_ROOT
|REMOVE_PHYSICAL
|REMOVE_SUBVOLUME
);
103 if (i
->settings_temp_path
) {
104 (void) unlink(i
->settings_temp_path
);
105 free(i
->settings_temp_path
);
109 free(i
->settings_path
);
120 const char *image_root
,
121 TarPullFinished on_finished
,
124 _cleanup_(tar_pull_unrefp
) TarPull
*i
= NULL
;
129 i
= new0(TarPull
, 1);
133 i
->on_finished
= on_finished
;
134 i
->userdata
= userdata
;
136 i
->image_root
= strdup(image_root
?: "/var/lib/machines");
140 i
->grow_machine_directory
= path_startswith(i
->image_root
, "/var/lib/machines");
143 i
->event
= sd_event_ref(event
);
145 r
= sd_event_default(&i
->event
);
150 r
= curl_glue_new(&i
->glue
, i
->event
);
154 i
->glue
->on_finished
= pull_job_curl_on_finished
;
155 i
->glue
->userdata
= i
;
163 static void tar_pull_report_progress(TarPull
*i
, TarProgress p
) {
170 case TAR_DOWNLOADING
: {
171 unsigned remain
= 85;
175 if (i
->settings_job
) {
176 percent
+= i
->settings_job
->progress_percent
* 5 / 100;
180 if (i
->checksum_job
) {
181 percent
+= i
->checksum_job
->progress_percent
* 5 / 100;
185 if (i
->signature_job
) {
186 percent
+= i
->signature_job
->progress_percent
* 5 / 100;
191 percent
+= i
->tar_job
->progress_percent
* remain
/ 100;
208 assert_not_reached("Unknown progress state");
211 sd_notifyf(false, "X_IMPORT_PROGRESS=%u", percent
);
212 log_debug("Combined progress %u%%", percent
);
215 static int tar_pull_make_local_copy(TarPull
*i
) {
224 if (!i
->final_path
) {
225 r
= pull_make_path(i
->tar_job
->url
, i
->tar_job
->etag
, i
->image_root
, ".tar-", NULL
, &i
->final_path
);
230 r
= pull_make_local_copy(i
->final_path
, i
->image_root
, i
->local
, i
->force_local
);
235 const char *local_settings
;
236 assert(i
->settings_job
);
238 if (!i
->settings_path
) {
239 r
= pull_make_path(i
->settings_job
->url
, i
->settings_job
->etag
, i
->image_root
, ".settings-", NULL
, &i
->settings_path
);
244 local_settings
= strjoina(i
->image_root
, "/", i
->local
, ".nspawn");
246 r
= copy_file_atomic(i
->settings_path
, local_settings
, 0664, i
->force_local
, 0);
248 log_warning_errno(r
, "Settings file %s already exists, not replacing.", local_settings
);
249 else if (r
< 0 && r
!= -ENOENT
)
250 log_warning_errno(r
, "Failed to copy settings files %s, ignoring: %m", local_settings
);
252 log_info("Created new settings file '%s.nspawn'", i
->local
);
258 static bool tar_pull_is_done(TarPull
*i
) {
262 if (!PULL_JOB_IS_COMPLETE(i
->tar_job
))
264 if (i
->settings_job
&& !PULL_JOB_IS_COMPLETE(i
->settings_job
))
266 if (i
->checksum_job
&& !PULL_JOB_IS_COMPLETE(i
->checksum_job
))
268 if (i
->signature_job
&& !PULL_JOB_IS_COMPLETE(i
->signature_job
))
274 static void tar_pull_job_on_finished(PullJob
*j
) {
283 if (j
== i
->settings_job
) {
285 log_info_errno(j
->error
, "Settings file could not be retrieved, proceeding without.");
286 } else if (j
->error
!= 0) {
287 if (j
== i
->checksum_job
)
288 log_error_errno(j
->error
, "Failed to retrieve SHA256 checksum, cannot verify. (Try --verify=no?)");
289 else if (j
== i
->signature_job
)
290 log_error_errno(j
->error
, "Failed to retrieve signature file, cannot verify. (Try --verify=no?)");
292 log_error_errno(j
->error
, "Failed to retrieve image file. (Wrong URL?)");
298 /* This is invoked if either the download completed
299 * successfully, or the download was skipped because we
300 * already have the etag. */
302 if (!tar_pull_is_done(i
))
305 i
->tar_job
->disk_fd
= safe_close(i
->tar_job
->disk_fd
);
307 i
->settings_job
->disk_fd
= safe_close(i
->settings_job
->disk_fd
);
309 if (i
->tar_pid
> 0) {
310 r
= wait_for_terminate_and_warn("tar", i
->tar_pid
, true);
320 if (!i
->tar_job
->etag_exists
) {
321 /* This is a new download, verify it, and move it into place */
323 tar_pull_report_progress(i
, TAR_VERIFYING
);
325 r
= pull_verify(i
->tar_job
, i
->settings_job
, i
->checksum_job
, i
->signature_job
);
329 tar_pull_report_progress(i
, TAR_FINALIZING
);
331 r
= import_make_read_only(i
->temp_path
);
335 r
= rename_noreplace(AT_FDCWD
, i
->temp_path
, AT_FDCWD
, i
->final_path
);
337 log_error_errno(r
, "Failed to rename to final image name: %m");
341 i
->temp_path
= mfree(i
->temp_path
);
343 if (i
->settings_job
&&
344 i
->settings_job
->error
== 0 &&
345 !i
->settings_job
->etag_exists
) {
347 assert(i
->settings_temp_path
);
348 assert(i
->settings_path
);
350 /* Also move the settings file into place, if
351 * it exist. Note that we do so only if we
352 * also moved the tar file in place, to keep
353 * things strictly in sync. */
355 r
= import_make_read_only(i
->settings_temp_path
);
359 r
= rename_noreplace(AT_FDCWD
, i
->settings_temp_path
, AT_FDCWD
, i
->settings_path
);
361 log_error_errno(r
, "Failed to rename settings file: %m");
365 i
->settings_temp_path
= mfree(i
->settings_temp_path
);
369 tar_pull_report_progress(i
, TAR_COPYING
);
371 r
= tar_pull_make_local_copy(i
);
379 i
->on_finished(i
, r
, i
->userdata
);
381 sd_event_exit(i
->event
, r
);
384 static int tar_pull_job_on_open_disk_tar(PullJob
*j
) {
392 assert(i
->tar_job
== j
);
393 assert(!i
->final_path
);
394 assert(!i
->temp_path
);
395 assert(i
->tar_pid
<= 0);
397 r
= pull_make_path(j
->url
, j
->etag
, i
->image_root
, ".tar-", NULL
, &i
->final_path
);
401 r
= tempfn_random(i
->final_path
, NULL
, &i
->temp_path
);
405 mkdir_parents_label(i
->temp_path
, 0700);
407 r
= btrfs_subvol_make(i
->temp_path
);
409 if (mkdir(i
->temp_path
, 0755) < 0)
410 return log_error_errno(errno
, "Failed to create directory %s: %m", i
->temp_path
);
412 return log_error_errno(errno
, "Failed to create subvolume %s: %m", i
->temp_path
);
414 (void) import_assign_pool_quota_and_warn(i
->temp_path
);
416 j
->disk_fd
= import_fork_tar_x(i
->temp_path
, &i
->tar_pid
);
423 static int tar_pull_job_on_open_disk_settings(PullJob
*j
) {
431 assert(i
->settings_job
== j
);
432 assert(!i
->settings_path
);
433 assert(!i
->settings_temp_path
);
435 r
= pull_make_path(j
->url
, j
->etag
, i
->image_root
, ".settings-", NULL
, &i
->settings_path
);
439 r
= tempfn_random(i
->settings_path
, NULL
, &i
->settings_temp_path
);
443 mkdir_parents_label(i
->settings_temp_path
, 0700);
445 j
->disk_fd
= open(i
->settings_temp_path
, O_RDWR
|O_CREAT
|O_EXCL
|O_NOCTTY
|O_CLOEXEC
, 0664);
447 return log_error_errno(errno
, "Failed to create %s: %m", i
->settings_temp_path
);
452 static void tar_pull_job_on_progress(PullJob
*j
) {
460 tar_pull_report_progress(i
, TAR_DOWNLOADING
);
474 assert(verify
< _IMPORT_VERIFY_MAX
);
477 if (!http_url_is_valid(url
))
480 if (local
&& !machine_name_is_valid(local
))
486 r
= free_and_strdup(&i
->local
, local
);
490 i
->force_local
= force_local
;
492 i
->settings
= settings
;
494 /* Set up download job for TAR file */
495 r
= pull_job_new(&i
->tar_job
, url
, i
->glue
, i
);
499 i
->tar_job
->on_finished
= tar_pull_job_on_finished
;
500 i
->tar_job
->on_open_disk
= tar_pull_job_on_open_disk_tar
;
501 i
->tar_job
->on_progress
= tar_pull_job_on_progress
;
502 i
->tar_job
->calc_checksum
= verify
!= IMPORT_VERIFY_NO
;
503 i
->tar_job
->grow_machine_directory
= i
->grow_machine_directory
;
505 r
= pull_find_old_etags(url
, i
->image_root
, DT_DIR
, ".tar-", NULL
, &i
->tar_job
->old_etags
);
509 /* Set up download job for the settings file (.nspawn) */
511 r
= pull_make_settings_job(&i
->settings_job
, url
, i
->glue
, tar_pull_job_on_finished
, i
);
515 i
->settings_job
->on_open_disk
= tar_pull_job_on_open_disk_settings
;
516 i
->settings_job
->on_progress
= tar_pull_job_on_progress
;
517 i
->settings_job
->calc_checksum
= verify
!= IMPORT_VERIFY_NO
;
519 r
= pull_find_old_etags(i
->settings_job
->url
, i
->image_root
, DT_REG
, ".settings-", NULL
, &i
->settings_job
->old_etags
);
524 /* Set up download of checksum/signature files */
525 r
= pull_make_verification_jobs(&i
->checksum_job
, &i
->signature_job
, verify
, url
, i
->glue
, tar_pull_job_on_finished
, i
);
529 r
= pull_job_begin(i
->tar_job
);
533 if (i
->settings_job
) {
534 r
= pull_job_begin(i
->settings_job
);
539 if (i
->checksum_job
) {
540 i
->checksum_job
->on_progress
= tar_pull_job_on_progress
;
542 r
= pull_job_begin(i
->checksum_job
);
547 if (i
->signature_job
) {
548 i
->signature_job
->on_progress
= tar_pull_job_on_progress
;
550 r
= pull_job_begin(i
->signature_job
);