1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2014 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 <curl/curl.h>
23 #include <sys/prctl.h>
25 #include "sd-daemon.h"
28 #include "btrfs-util.h"
32 #include "path-util.h"
33 #include "import-util.h"
34 #include "curl-util.h"
35 #include "aufs-util.h"
37 #include "pull-common.h"
38 #include "import-common.h"
40 #include "process-util.h"
41 #include "hostname-util.h"
43 typedef enum DkrProgress
{
60 PullJob
*ancestry_job
;
69 char **response_registries
;
73 unsigned current_ancestry
;
75 DkrPullFinished on_finished
;
80 bool grow_machine_directory
;
88 #define PROTOCOL_PREFIX "https://"
90 #define HEADER_TOKEN "X-Do" /* the HTTP header for the auth token */ "cker-Token:"
91 #define HEADER_REGISTRY "X-Do" /*the HTTP header for the registry */ "cker-Endpoints:"
93 #define LAYERS_MAX 2048
95 static void dkr_pull_job_on_finished(PullJob
*j
);
97 DkrPull
* dkr_pull_unref(DkrPull
*i
) {
101 if (i
->tar_pid
> 1) {
102 (void) kill_and_sigcont(i
->tar_pid
, SIGKILL
);
103 (void) wait_for_terminate(i
->tar_pid
, NULL
);
106 pull_job_unref(i
->images_job
);
107 pull_job_unref(i
->tags_job
);
108 pull_job_unref(i
->ancestry_job
);
109 pull_job_unref(i
->json_job
);
110 pull_job_unref(i
->layer_job
);
112 curl_glue_unref(i
->glue
);
113 sd_event_unref(i
->event
);
116 (void) rm_rf(i
->temp_path
, REMOVE_ROOT
|REMOVE_PHYSICAL
|REMOVE_SUBVOLUME
);
123 free(i
->response_token
);
124 free(i
->response_registries
);
125 strv_free(i
->ancestry
);
138 const char *index_url
,
139 const char *image_root
,
140 DkrPullFinished on_finished
,
143 _cleanup_(dkr_pull_unrefp
) DkrPull
*i
= NULL
;
150 if (!http_url_is_valid(index_url
))
153 i
= new0(DkrPull
, 1);
157 i
->on_finished
= on_finished
;
158 i
->userdata
= userdata
;
160 i
->image_root
= strdup(image_root
?: "/var/lib/machines");
164 i
->grow_machine_directory
= path_startswith(i
->image_root
, "/var/lib/machines");
166 i
->index_url
= strdup(index_url
);
170 e
= endswith(i
->index_url
, "/");
175 i
->event
= sd_event_ref(event
);
177 r
= sd_event_default(&i
->event
);
182 r
= curl_glue_new(&i
->glue
, i
->event
);
186 i
->glue
->on_finished
= pull_job_curl_on_finished
;
187 i
->glue
->userdata
= i
;
195 static void dkr_pull_report_progress(DkrPull
*i
, DkrProgress p
) {
205 percent
+= i
->images_job
->progress_percent
* 5 / 100;
211 percent
+= i
->tags_job
->progress_percent
* 5 / 100;
217 percent
+= i
->ancestry_job
->progress_percent
* 5 / 100;
219 percent
+= i
->json_job
->progress_percent
* 5 / 100;
222 case DKR_DOWNLOADING
:
224 percent
+= 75 * i
->current_ancestry
/ MAX(1U, i
->n_ancestry
);
226 percent
+= i
->layer_job
->progress_percent
* 75 / MAX(1U, i
->n_ancestry
) / 100;
235 assert_not_reached("Unknown progress state");
238 sd_notifyf(false, "X_IMPORT_PROGRESS=%u", percent
);
239 log_debug("Combined progress %u%%", percent
);
242 static int parse_id(const void *payload
, size_t size
, char **ret
) {
243 _cleanup_free_
char *buf
= NULL
, *id
= NULL
, *other
= NULL
;
244 union json_value v
= {};
245 void *json_state
= NULL
;
255 if (memchr(payload
, 0, size
))
258 buf
= strndup(payload
, size
);
263 t
= json_tokenize(&p
, &id
, &v
, &json_state
, NULL
);
266 if (t
!= JSON_STRING
)
269 t
= json_tokenize(&p
, &other
, &v
, &json_state
, NULL
);
275 if (!dkr_id_is_valid(id
))
284 static int parse_ancestry(const void *payload
, size_t size
, char ***ret
) {
285 _cleanup_free_
char *buf
= NULL
;
286 void *json_state
= NULL
;
293 } state
= STATE_BEGIN
;
294 _cleanup_strv_free_
char **l
= NULL
;
295 size_t n
= 0, allocated
= 0;
300 if (memchr(payload
, 0, size
))
303 buf
= strndup(payload
, size
);
309 _cleanup_free_
char *str
;
310 union json_value v
= {};
313 t
= json_tokenize(&p
, &str
, &v
, &json_state
, NULL
);
320 if (t
== JSON_ARRAY_OPEN
)
328 if (t
== JSON_STRING
) {
329 if (!dkr_id_is_valid(str
))
332 if (n
+1 > LAYERS_MAX
)
335 if (!GREEDY_REALLOC(l
, allocated
, n
+ 2))
344 } else if (t
== JSON_ARRAY_CLOSE
)
354 else if (t
== JSON_ARRAY_CLOSE
)
366 if (!strv_is_uniq(l
))
381 static const char *dkr_pull_current_layer(DkrPull
*i
) {
384 if (strv_isempty(i
->ancestry
))
387 return i
->ancestry
[i
->current_ancestry
];
390 static const char *dkr_pull_current_base_layer(DkrPull
*i
) {
393 if (strv_isempty(i
->ancestry
))
396 if (i
->current_ancestry
<= 0)
399 return i
->ancestry
[i
->current_ancestry
-1];
402 static int dkr_pull_add_token(DkrPull
*i
, PullJob
*j
) {
408 if (i
->response_token
)
409 t
= strjoina("Authorization: Token ", i
->response_token
);
411 t
= HEADER_TOKEN
" true";
413 j
->request_header
= curl_slist_new("Accept: application/json", t
, NULL
);
414 if (!j
->request_header
)
420 static bool dkr_pull_is_done(DkrPull
*i
) {
422 assert(i
->images_job
);
424 if (i
->images_job
->state
!= PULL_JOB_DONE
)
427 if (!i
->tags_job
|| i
->tags_job
->state
!= PULL_JOB_DONE
)
430 if (!i
->ancestry_job
|| i
->ancestry_job
->state
!= PULL_JOB_DONE
)
433 if (!i
->json_job
|| i
->json_job
->state
!= PULL_JOB_DONE
)
436 if (i
->layer_job
&& i
->layer_job
->state
!= PULL_JOB_DONE
)
439 if (dkr_pull_current_layer(i
))
445 static int dkr_pull_make_local_copy(DkrPull
*i
) {
453 if (!i
->final_path
) {
454 i
->final_path
= strjoin(i
->image_root
, "/.dkr-", i
->id
, NULL
);
459 r
= pull_make_local_copy(i
->final_path
, i
->image_root
, i
->local
, i
->force_local
);
466 static int dkr_pull_job_on_open_disk(PullJob
*j
) {
475 assert(i
->layer_job
== j
);
476 assert(i
->final_path
);
477 assert(!i
->temp_path
);
478 assert(i
->tar_pid
<= 0);
480 r
= tempfn_random(i
->final_path
, &i
->temp_path
);
484 mkdir_parents_label(i
->temp_path
, 0700);
486 base
= dkr_pull_current_base_layer(i
);
488 const char *base_path
;
490 base_path
= strjoina(i
->image_root
, "/.dkr-", base
);
491 r
= btrfs_subvol_snapshot(base_path
, i
->temp_path
, BTRFS_SNAPSHOT_FALLBACK_COPY
);
493 r
= btrfs_subvol_make(i
->temp_path
);
495 return log_error_errno(r
, "Failed to make btrfs subvolume %s: %m", i
->temp_path
);
497 j
->disk_fd
= import_fork_tar_x(i
->temp_path
, &i
->tar_pid
);
504 static void dkr_pull_job_on_progress(PullJob
*j
) {
512 dkr_pull_report_progress(
514 j
== i
->images_job
? DKR_SEARCHING
:
515 j
== i
->tags_job
? DKR_RESOLVING
:
516 j
== i
->ancestry_job
|| j
== i
->json_job
? DKR_METADATA
:
520 static int dkr_pull_pull_layer(DkrPull
*i
) {
521 _cleanup_free_
char *path
= NULL
;
522 const char *url
, *layer
= NULL
;
526 assert(!i
->layer_job
);
527 assert(!i
->temp_path
);
528 assert(!i
->final_path
);
531 layer
= dkr_pull_current_layer(i
);
533 return 0; /* no more layers */
535 path
= strjoin(i
->image_root
, "/.dkr-", layer
, NULL
);
539 if (laccess(path
, F_OK
) < 0) {
543 return log_error_errno(errno
, "Failed to check for container: %m");
546 log_info("Layer %s already exists, skipping.", layer
);
548 i
->current_ancestry
++;
554 log_info("Pulling layer %s...", layer
);
556 i
->final_path
= path
;
559 url
= strjoina(PROTOCOL_PREFIX
, i
->response_registries
[0], "/v1/images/", layer
, "/layer");
560 r
= pull_job_new(&i
->layer_job
, url
, i
->glue
, i
);
562 return log_error_errno(r
, "Failed to allocate layer job: %m");
564 r
= dkr_pull_add_token(i
, i
->layer_job
);
568 i
->layer_job
->on_finished
= dkr_pull_job_on_finished
;
569 i
->layer_job
->on_open_disk
= dkr_pull_job_on_open_disk
;
570 i
->layer_job
->on_progress
= dkr_pull_job_on_progress
;
571 i
->layer_job
->grow_machine_directory
= i
->grow_machine_directory
;
573 r
= pull_job_begin(i
->layer_job
);
575 return log_error_errno(r
, "Failed to start layer job: %m");
580 static void dkr_pull_job_on_finished(PullJob
*j
) {
589 if (j
== i
->images_job
)
590 log_error_errno(j
->error
, "Failed to retrieve images list. (Wrong index URL?)");
591 else if (j
== i
->tags_job
)
592 log_error_errno(j
->error
, "Failed to retrieve tags list.");
593 else if (j
== i
->ancestry_job
)
594 log_error_errno(j
->error
, "Failed to retrieve ancestry list.");
595 else if (j
== i
->json_job
)
596 log_error_errno(j
->error
, "Failed to retrieve json data.");
598 log_error_errno(j
->error
, "Failed to retrieve layer data.");
604 if (i
->images_job
== j
) {
607 assert(!i
->tags_job
);
608 assert(!i
->ancestry_job
);
609 assert(!i
->json_job
);
610 assert(!i
->layer_job
);
612 if (strv_isempty(i
->response_registries
)) {
614 log_error("Didn't get registry information.");
618 log_info("Index lookup succeeded, directed to registry %s.", i
->response_registries
[0]);
619 dkr_pull_report_progress(i
, DKR_RESOLVING
);
621 url
= strjoina(PROTOCOL_PREFIX
, i
->response_registries
[0], "/v1/repositories/", i
->name
, "/tags/", i
->tag
);
622 r
= pull_job_new(&i
->tags_job
, url
, i
->glue
, i
);
624 log_error_errno(r
, "Failed to allocate tags job: %m");
628 r
= dkr_pull_add_token(i
, i
->tags_job
);
634 i
->tags_job
->on_finished
= dkr_pull_job_on_finished
;
635 i
->tags_job
->on_progress
= dkr_pull_job_on_progress
;
637 r
= pull_job_begin(i
->tags_job
);
639 log_error_errno(r
, "Failed to start tags job: %m");
643 } else if (i
->tags_job
== j
) {
647 assert(!i
->ancestry_job
);
648 assert(!i
->json_job
);
649 assert(!i
->layer_job
);
651 r
= parse_id(j
->payload
, j
->payload_size
, &id
);
653 log_error_errno(r
, "Failed to parse JSON id.");
660 log_info("Tag lookup succeeded, resolved to layer %s.", i
->id
);
661 dkr_pull_report_progress(i
, DKR_METADATA
);
663 url
= strjoina(PROTOCOL_PREFIX
, i
->response_registries
[0], "/v1/images/", i
->id
, "/ancestry");
664 r
= pull_job_new(&i
->ancestry_job
, url
, i
->glue
, i
);
666 log_error_errno(r
, "Failed to allocate ancestry job: %m");
670 r
= dkr_pull_add_token(i
, i
->ancestry_job
);
676 i
->ancestry_job
->on_finished
= dkr_pull_job_on_finished
;
677 i
->ancestry_job
->on_progress
= dkr_pull_job_on_progress
;
679 url
= strjoina(PROTOCOL_PREFIX
, i
->response_registries
[0], "/v1/images/", i
->id
, "/json");
680 r
= pull_job_new(&i
->json_job
, url
, i
->glue
, i
);
682 log_error_errno(r
, "Failed to allocate json job: %m");
686 r
= dkr_pull_add_token(i
, i
->json_job
);
692 i
->json_job
->on_finished
= dkr_pull_job_on_finished
;
693 i
->json_job
->on_progress
= dkr_pull_job_on_progress
;
695 r
= pull_job_begin(i
->ancestry_job
);
697 log_error_errno(r
, "Failed to start ancestry job: %m");
701 r
= pull_job_begin(i
->json_job
);
703 log_error_errno(r
, "Failed to start json job: %m");
707 } else if (i
->ancestry_job
== j
) {
708 char **ancestry
= NULL
, **k
;
711 assert(!i
->layer_job
);
713 r
= parse_ancestry(j
->payload
, j
->payload_size
, &ancestry
);
715 log_error_errno(r
, "Failed to parse JSON id.");
719 n
= strv_length(ancestry
);
720 if (n
<= 0 || !streq(ancestry
[n
-1], i
->id
)) {
721 log_error("Ancestry doesn't end in main layer.");
727 log_info("Ancestor lookup succeeded, requires layers:\n");
728 STRV_FOREACH(k
, ancestry
)
729 log_info("\t%s", *k
);
731 strv_free(i
->ancestry
);
732 i
->ancestry
= ancestry
;
734 i
->current_ancestry
= 0;
736 dkr_pull_report_progress(i
, DKR_DOWNLOADING
);
738 r
= dkr_pull_pull_layer(i
);
742 } else if (i
->layer_job
== j
) {
743 assert(i
->temp_path
);
744 assert(i
->final_path
);
746 j
->disk_fd
= safe_close(j
->disk_fd
);
748 if (i
->tar_pid
> 0) {
749 r
= wait_for_terminate_and_warn("tar", i
->tar_pid
, true);
755 r
= aufs_resolve(i
->temp_path
);
757 log_error_errno(r
, "Failed to resolve aufs whiteouts: %m");
761 r
= btrfs_subvol_set_read_only(i
->temp_path
, true);
763 log_error_errno(r
, "Failed to mark snapshot read-only: %m");
767 if (rename(i
->temp_path
, i
->final_path
) < 0) {
768 log_error_errno(errno
, "Failed to rename snaphsot: %m");
772 log_info("Completed writing to layer %s.", i
->final_path
);
774 i
->layer_job
= pull_job_unref(i
->layer_job
);
778 i
->final_path
= NULL
;
780 i
->current_ancestry
++;
781 r
= dkr_pull_pull_layer(i
);
785 } else if (i
->json_job
!= j
)
786 assert_not_reached("Got finished event for unknown curl object");
788 if (!dkr_pull_is_done(i
))
791 dkr_pull_report_progress(i
, DKR_COPYING
);
793 r
= dkr_pull_make_local_copy(i
);
801 i
->on_finished(i
, r
, i
->userdata
);
803 sd_event_exit(i
->event
, r
);
806 static int dkr_pull_job_on_header(PullJob
*j
, const char *header
, size_t sz
) {
807 _cleanup_free_
char *registry
= NULL
;
817 r
= curl_header_strdup(header
, sz
, HEADER_TOKEN
, &token
);
821 free(i
->response_token
);
822 i
->response_token
= token
;
826 r
= curl_header_strdup(header
, sz
, HEADER_REGISTRY
, ®istry
);
832 l
= strv_split(registry
, ",");
837 if (!hostname_is_valid(*k
)) {
838 log_error("Registry hostname is not valid.");
844 strv_free(i
->response_registries
);
845 i
->response_registries
= l
;
851 int dkr_pull_start(DkrPull
*i
, const char *name
, const char *tag
, const char *local
, bool force_local
) {
857 if (!dkr_name_is_valid(name
))
860 if (tag
&& !dkr_tag_is_valid(tag
))
863 if (local
&& !machine_name_is_valid(local
))
872 r
= free_and_strdup(&i
->local
, local
);
875 i
->force_local
= force_local
;
877 r
= free_and_strdup(&i
->name
, name
);
880 r
= free_and_strdup(&i
->tag
, tag
);
884 url
= strjoina(i
->index_url
, "/v1/repositories/", name
, "/images");
886 r
= pull_job_new(&i
->images_job
, url
, i
->glue
, i
);
890 r
= dkr_pull_add_token(i
, i
->images_job
);
894 i
->images_job
->on_finished
= dkr_pull_job_on_finished
;
895 i
->images_job
->on_header
= dkr_pull_job_on_header
;
896 i
->images_job
->on_progress
= dkr_pull_job_on_progress
;
898 return pull_job_begin(i
->images_job
);