1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2015 Lennart Poettering
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
21 #include <curl/curl.h>
22 #include <sys/prctl.h>
24 #include "sd-daemon.h"
26 #include "alloc-util.h"
27 #include "btrfs-util.h"
29 #include "curl-util.h"
33 #include "hostname-util.h"
34 #include "import-common.h"
35 #include "import-util.h"
38 #include "path-util.h"
39 #include "process-util.h"
40 #include "pull-common.h"
44 #include "string-util.h"
50 typedef enum TarProgress
{
64 PullJob
*settings_job
;
65 PullJob
*checksum_job
;
66 PullJob
*signature_job
;
68 TarPullFinished on_finished
;
73 bool grow_machine_directory
;
82 char *settings_temp_path
;
87 TarPull
* tar_pull_unref(TarPull
*i
) {
92 (void) kill_and_sigcont(i
->tar_pid
, SIGKILL
);
93 (void) wait_for_terminate(i
->tar_pid
, NULL
);
96 pull_job_unref(i
->tar_job
);
97 pull_job_unref(i
->settings_job
);
98 pull_job_unref(i
->checksum_job
);
99 pull_job_unref(i
->signature_job
);
101 curl_glue_unref(i
->glue
);
102 sd_event_unref(i
->event
);
105 (void) rm_rf(i
->temp_path
, REMOVE_ROOT
|REMOVE_PHYSICAL
|REMOVE_SUBVOLUME
);
109 if (i
->settings_temp_path
) {
110 (void) unlink(i
->settings_temp_path
);
111 free(i
->settings_temp_path
);
115 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
;
167 static void tar_pull_report_progress(TarPull
*i
, TarProgress p
) {
174 case TAR_DOWNLOADING
: {
175 unsigned remain
= 85;
179 if (i
->settings_job
) {
180 percent
+= i
->settings_job
->progress_percent
* 5 / 100;
184 if (i
->checksum_job
) {
185 percent
+= i
->checksum_job
->progress_percent
* 5 / 100;
189 if (i
->signature_job
) {
190 percent
+= i
->signature_job
->progress_percent
* 5 / 100;
195 percent
+= i
->tar_job
->progress_percent
* remain
/ 100;
212 assert_not_reached("Unknown progress state");
215 sd_notifyf(false, "X_IMPORT_PROGRESS=%u", percent
);
216 log_debug("Combined progress %u%%", percent
);
219 static int tar_pull_determine_path(TarPull
*i
, const char *suffix
, char **field
) {
230 r
= pull_make_path(i
->tar_job
->url
, i
->tar_job
->etag
, i
->image_root
, ".tar-", suffix
, field
);
237 static int tar_pull_make_local_copy(TarPull
*i
) {
246 r
= pull_make_local_copy(i
->final_path
, i
->image_root
, i
->local
, i
->force_local
);
251 const char *local_settings
;
252 assert(i
->settings_job
);
254 r
= tar_pull_determine_path(i
, ".nspawn", &i
->settings_path
);
258 local_settings
= strjoina(i
->image_root
, "/", i
->local
, ".nspawn");
260 r
= copy_file_atomic(i
->settings_path
, local_settings
, 0664, 0, COPY_REFLINK
| (i
->force_local
? COPY_REPLACE
: 0));
262 log_warning_errno(r
, "Settings file %s already exists, not replacing.", local_settings
);
263 else if (r
== -ENOENT
)
264 log_debug_errno(r
, "Skipping creation of settings file, since none was found.");
266 log_warning_errno(r
, "Failed to copy settings files %s, ignoring: %m", local_settings
);
268 log_info("Created new settings file %s.", local_settings
);
274 static bool tar_pull_is_done(TarPull
*i
) {
278 if (!PULL_JOB_IS_COMPLETE(i
->tar_job
))
280 if (i
->settings_job
&& !PULL_JOB_IS_COMPLETE(i
->settings_job
))
282 if (i
->checksum_job
&& !PULL_JOB_IS_COMPLETE(i
->checksum_job
))
284 if (i
->signature_job
&& !PULL_JOB_IS_COMPLETE(i
->signature_job
))
290 static void tar_pull_job_on_finished(PullJob
*j
) {
299 if (j
== i
->settings_job
) {
301 log_info_errno(j
->error
, "Settings file could not be retrieved, proceeding without.");
302 } else if (j
->error
!= 0 && j
!= i
->signature_job
) {
303 if (j
== i
->checksum_job
)
304 log_error_errno(j
->error
, "Failed to retrieve SHA256 checksum, cannot verify. (Try --verify=no?)");
306 log_error_errno(j
->error
, "Failed to retrieve image file. (Wrong URL?)");
312 /* This is invoked if either the download completed
313 * successfully, or the download was skipped because we
314 * already have the etag. */
316 if (!tar_pull_is_done(i
))
319 if (i
->signature_job
&& i
->checksum_job
->style
== VERIFICATION_PER_DIRECTORY
&& i
->signature_job
->error
!= 0) {
320 log_error_errno(j
->error
, "Failed to retrieve signature file, cannot verify. (Try --verify=no?)");
322 r
= i
->signature_job
->error
;
326 i
->tar_job
->disk_fd
= safe_close(i
->tar_job
->disk_fd
);
328 i
->settings_job
->disk_fd
= safe_close(i
->settings_job
->disk_fd
);
330 r
= tar_pull_determine_path(i
, NULL
, &i
->final_path
);
334 if (i
->tar_pid
> 0) {
335 r
= wait_for_terminate_and_check("tar", i
->tar_pid
, WAIT_LOG
);
339 if (r
!= EXIT_SUCCESS
) {
345 if (!i
->tar_job
->etag_exists
) {
346 /* This is a new download, verify it, and move it into place */
348 tar_pull_report_progress(i
, TAR_VERIFYING
);
350 r
= pull_verify(i
->tar_job
, NULL
, i
->settings_job
, i
->checksum_job
, i
->signature_job
);
354 tar_pull_report_progress(i
, TAR_FINALIZING
);
356 r
= import_make_read_only(i
->temp_path
);
360 r
= rename_noreplace(AT_FDCWD
, i
->temp_path
, AT_FDCWD
, i
->final_path
);
362 log_error_errno(r
, "Failed to rename to final image name to %s: %m", i
->final_path
);
366 i
->temp_path
= mfree(i
->temp_path
);
368 if (i
->settings_job
&&
369 i
->settings_job
->error
== 0) {
371 /* Also move the settings file into place, if it exists. Note that we do so only if we also
372 * moved the tar file in place, to keep things strictly in sync. */
373 assert(i
->settings_temp_path
);
375 /* Regenerate final name for this auxiliary file, we might know the etag of the file now, and
376 * we should incorporate it in the file name if we can */
377 i
->settings_path
= mfree(i
->settings_path
);
379 r
= tar_pull_determine_path(i
, ".nspawn", &i
->settings_path
);
383 r
= import_make_read_only(i
->settings_temp_path
);
387 r
= rename_noreplace(AT_FDCWD
, i
->settings_temp_path
, AT_FDCWD
, i
->settings_path
);
389 log_error_errno(r
, "Failed to rename settings file to %s: %m", i
->settings_path
);
393 i
->settings_temp_path
= mfree(i
->settings_temp_path
);
397 tar_pull_report_progress(i
, TAR_COPYING
);
399 r
= tar_pull_make_local_copy(i
);
407 i
->on_finished(i
, r
, i
->userdata
);
409 sd_event_exit(i
->event
, r
);
412 static int tar_pull_job_on_open_disk_tar(PullJob
*j
) {
420 assert(i
->tar_job
== j
);
421 assert(i
->tar_pid
<= 0);
424 r
= tempfn_random_child(i
->image_root
, "tar", &i
->temp_path
);
429 mkdir_parents_label(i
->temp_path
, 0700);
431 r
= btrfs_subvol_make(i
->temp_path
);
433 if (mkdir(i
->temp_path
, 0755) < 0)
434 return log_error_errno(errno
, "Failed to create directory %s: %m", i
->temp_path
);
436 return log_error_errno(r
, "Failed to create subvolume %s: %m", i
->temp_path
);
438 (void) import_assign_pool_quota_and_warn(i
->temp_path
);
440 j
->disk_fd
= import_fork_tar_x(i
->temp_path
, &i
->tar_pid
);
447 static int tar_pull_job_on_open_disk_settings(PullJob
*j
) {
455 assert(i
->settings_job
== j
);
457 if (!i
->settings_temp_path
) {
458 r
= tempfn_random_child(i
->image_root
, "settings", &i
->settings_temp_path
);
463 mkdir_parents_label(i
->settings_temp_path
, 0700);
465 j
->disk_fd
= open(i
->settings_temp_path
, O_RDWR
|O_CREAT
|O_EXCL
|O_NOCTTY
|O_CLOEXEC
, 0664);
467 return log_error_errno(errno
, "Failed to create %s: %m", i
->settings_temp_path
);
472 static void tar_pull_job_on_progress(PullJob
*j
) {
480 tar_pull_report_progress(i
, TAR_DOWNLOADING
);
494 assert(verify
< _IMPORT_VERIFY_MAX
);
497 if (!http_url_is_valid(url
))
500 if (local
&& !machine_name_is_valid(local
))
506 r
= free_and_strdup(&i
->local
, local
);
510 i
->force_local
= force_local
;
512 i
->settings
= settings
;
514 /* Set up download job for TAR file */
515 r
= pull_job_new(&i
->tar_job
, url
, i
->glue
, i
);
519 i
->tar_job
->on_finished
= tar_pull_job_on_finished
;
520 i
->tar_job
->on_open_disk
= tar_pull_job_on_open_disk_tar
;
521 i
->tar_job
->on_progress
= tar_pull_job_on_progress
;
522 i
->tar_job
->calc_checksum
= verify
!= IMPORT_VERIFY_NO
;
523 i
->tar_job
->grow_machine_directory
= i
->grow_machine_directory
;
525 r
= pull_find_old_etags(url
, i
->image_root
, DT_DIR
, ".tar-", NULL
, &i
->tar_job
->old_etags
);
529 /* Set up download job for the settings file (.nspawn) */
531 r
= pull_make_auxiliary_job(&i
->settings_job
, url
, tar_strip_suffixes
, ".nspawn", i
->glue
, tar_pull_job_on_finished
, i
);
535 i
->settings_job
->on_open_disk
= tar_pull_job_on_open_disk_settings
;
536 i
->settings_job
->on_progress
= tar_pull_job_on_progress
;
537 i
->settings_job
->calc_checksum
= verify
!= IMPORT_VERIFY_NO
;
540 /* Set up download of checksum/signature files */
541 r
= pull_make_verification_jobs(&i
->checksum_job
, &i
->signature_job
, verify
, url
, i
->glue
, tar_pull_job_on_finished
, i
);
545 r
= pull_job_begin(i
->tar_job
);
549 if (i
->settings_job
) {
550 r
= pull_job_begin(i
->settings_job
);
555 if (i
->checksum_job
) {
556 i
->checksum_job
->on_progress
= tar_pull_job_on_progress
;
557 i
->checksum_job
->style
= VERIFICATION_PER_FILE
;
559 r
= pull_job_begin(i
->checksum_job
);
564 if (i
->signature_job
) {
565 i
->signature_job
->on_progress
= tar_pull_job_on_progress
;
567 r
= pull_job_begin(i
->signature_job
);