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"
41 typedef enum DkrProgress
{
58 PullJob
*ancestry_job
;
67 char **response_registries
;
71 unsigned current_ancestry
;
73 DkrPullFinished on_finished
;
78 bool grow_machine_directory
;
86 #define PROTOCOL_PREFIX "https://"
88 #define HEADER_TOKEN "X-Do" /* the HTTP header for the auth token */ "cker-Token:"
89 #define HEADER_REGISTRY "X-Do" /*the HTTP header for the registry */ "cker-Endpoints:"
91 #define LAYERS_MAX 2048
93 static void dkr_pull_job_on_finished(PullJob
*j
);
95 DkrPull
* dkr_pull_unref(DkrPull
*i
) {
100 (void) kill_and_sigcont(i
->tar_pid
, SIGKILL
);
101 (void) wait_for_terminate(i
->tar_pid
, NULL
);
104 pull_job_unref(i
->images_job
);
105 pull_job_unref(i
->tags_job
);
106 pull_job_unref(i
->ancestry_job
);
107 pull_job_unref(i
->json_job
);
108 pull_job_unref(i
->layer_job
);
110 curl_glue_unref(i
->glue
);
111 sd_event_unref(i
->event
);
114 (void) btrfs_subvol_remove(i
->temp_path
);
115 (void) rm_rf(i
->temp_path
, REMOVE_ROOT
|REMOVE_PHYSICAL
);
122 free(i
->response_token
);
123 free(i
->response_registries
);
124 strv_free(i
->ancestry
);
137 const char *index_url
,
138 const char *image_root
,
139 DkrPullFinished on_finished
,
142 _cleanup_(dkr_pull_unrefp
) DkrPull
*i
= NULL
;
149 if (!http_url_is_valid(index_url
))
152 i
= new0(DkrPull
, 1);
156 i
->on_finished
= on_finished
;
157 i
->userdata
= userdata
;
159 i
->image_root
= strdup(image_root
?: "/var/lib/machines");
163 i
->grow_machine_directory
= path_startswith(i
->image_root
, "/var/lib/machines");
165 i
->index_url
= strdup(index_url
);
169 e
= endswith(i
->index_url
, "/");
174 i
->event
= sd_event_ref(event
);
176 r
= sd_event_default(&i
->event
);
181 r
= curl_glue_new(&i
->glue
, i
->event
);
185 i
->glue
->on_finished
= pull_job_curl_on_finished
;
186 i
->glue
->userdata
= i
;
194 static void dkr_pull_report_progress(DkrPull
*i
, DkrProgress p
) {
204 percent
+= i
->images_job
->progress_percent
* 5 / 100;
210 percent
+= i
->tags_job
->progress_percent
* 5 / 100;
216 percent
+= i
->ancestry_job
->progress_percent
* 5 / 100;
218 percent
+= i
->json_job
->progress_percent
* 5 / 100;
221 case DKR_DOWNLOADING
:
223 percent
+= 75 * i
->current_ancestry
/ MAX(1U, i
->n_ancestry
);
225 percent
+= i
->layer_job
->progress_percent
* 75 / MAX(1U, i
->n_ancestry
) / 100;
234 assert_not_reached("Unknown progress state");
237 sd_notifyf(false, "X_IMPORT_PROGRESS=%u", percent
);
238 log_debug("Combined progress %u%%", percent
);
241 static int parse_id(const void *payload
, size_t size
, char **ret
) {
242 _cleanup_free_
char *buf
= NULL
, *id
= NULL
, *other
= NULL
;
243 union json_value v
= {};
244 void *json_state
= NULL
;
254 if (memchr(payload
, 0, size
))
257 buf
= strndup(payload
, size
);
262 t
= json_tokenize(&p
, &id
, &v
, &json_state
, NULL
);
265 if (t
!= JSON_STRING
)
268 t
= json_tokenize(&p
, &other
, &v
, &json_state
, NULL
);
274 if (!dkr_id_is_valid(id
))
283 static int parse_ancestry(const void *payload
, size_t size
, char ***ret
) {
284 _cleanup_free_
char *buf
= NULL
;
285 void *json_state
= NULL
;
292 } state
= STATE_BEGIN
;
293 _cleanup_strv_free_
char **l
= NULL
;
294 size_t n
= 0, allocated
= 0;
299 if (memchr(payload
, 0, size
))
302 buf
= strndup(payload
, size
);
308 _cleanup_free_
char *str
;
309 union json_value v
= {};
312 t
= json_tokenize(&p
, &str
, &v
, &json_state
, NULL
);
319 if (t
== JSON_ARRAY_OPEN
)
327 if (t
== JSON_STRING
) {
328 if (!dkr_id_is_valid(str
))
331 if (n
+1 > LAYERS_MAX
)
334 if (!GREEDY_REALLOC(l
, allocated
, n
+ 2))
343 } else if (t
== JSON_ARRAY_CLOSE
)
353 else if (t
== JSON_ARRAY_CLOSE
)
365 if (!strv_is_uniq(l
))
380 static const char *dkr_pull_current_layer(DkrPull
*i
) {
383 if (strv_isempty(i
->ancestry
))
386 return i
->ancestry
[i
->current_ancestry
];
389 static const char *dkr_pull_current_base_layer(DkrPull
*i
) {
392 if (strv_isempty(i
->ancestry
))
395 if (i
->current_ancestry
<= 0)
398 return i
->ancestry
[i
->current_ancestry
-1];
401 static int dkr_pull_add_token(DkrPull
*i
, PullJob
*j
) {
407 if (i
->response_token
)
408 t
= strjoina("Authorization: Token ", i
->response_token
);
410 t
= HEADER_TOKEN
" true";
412 j
->request_header
= curl_slist_new("Accept: application/json", t
, NULL
);
413 if (!j
->request_header
)
419 static bool dkr_pull_is_done(DkrPull
*i
) {
421 assert(i
->images_job
);
423 if (i
->images_job
->state
!= PULL_JOB_DONE
)
426 if (!i
->tags_job
|| i
->tags_job
->state
!= PULL_JOB_DONE
)
429 if (!i
->ancestry_job
|| i
->ancestry_job
->state
!= PULL_JOB_DONE
)
432 if (!i
->json_job
|| i
->json_job
->state
!= PULL_JOB_DONE
)
435 if (i
->layer_job
&& i
->layer_job
->state
!= PULL_JOB_DONE
)
438 if (dkr_pull_current_layer(i
))
444 static int dkr_pull_make_local_copy(DkrPull
*i
) {
452 if (!i
->final_path
) {
453 i
->final_path
= strjoin(i
->image_root
, "/.dkr-", i
->id
, NULL
);
458 r
= pull_make_local_copy(i
->final_path
, i
->image_root
, i
->local
, i
->force_local
);
465 static int dkr_pull_job_on_open_disk(PullJob
*j
) {
474 assert(i
->layer_job
== j
);
475 assert(i
->final_path
);
476 assert(!i
->temp_path
);
477 assert(i
->tar_pid
<= 0);
479 r
= tempfn_random(i
->final_path
, &i
->temp_path
);
483 mkdir_parents_label(i
->temp_path
, 0700);
485 base
= dkr_pull_current_base_layer(i
);
487 const char *base_path
;
489 base_path
= strjoina(i
->image_root
, "/.dkr-", base
);
490 r
= btrfs_subvol_snapshot(base_path
, i
->temp_path
, false, true);
492 r
= btrfs_subvol_make(i
->temp_path
);
494 return log_error_errno(r
, "Failed to make btrfs subvolume %s: %m", i
->temp_path
);
496 j
->disk_fd
= import_fork_tar_x(i
->temp_path
, &i
->tar_pid
);
503 static void dkr_pull_job_on_progress(PullJob
*j
) {
511 dkr_pull_report_progress(
513 j
== i
->images_job
? DKR_SEARCHING
:
514 j
== i
->tags_job
? DKR_RESOLVING
:
515 j
== i
->ancestry_job
|| j
== i
->json_job
? DKR_METADATA
:
519 static int dkr_pull_pull_layer(DkrPull
*i
) {
520 _cleanup_free_
char *path
= NULL
;
521 const char *url
, *layer
= NULL
;
525 assert(!i
->layer_job
);
526 assert(!i
->temp_path
);
527 assert(!i
->final_path
);
530 layer
= dkr_pull_current_layer(i
);
532 return 0; /* no more layers */
534 path
= strjoin(i
->image_root
, "/.dkr-", layer
, NULL
);
538 if (laccess(path
, F_OK
) < 0) {
542 return log_error_errno(errno
, "Failed to check for container: %m");
545 log_info("Layer %s already exists, skipping.", layer
);
547 i
->current_ancestry
++;
553 log_info("Pulling layer %s...", layer
);
555 i
->final_path
= path
;
558 url
= strjoina(PROTOCOL_PREFIX
, i
->response_registries
[0], "/v1/images/", layer
, "/layer");
559 r
= pull_job_new(&i
->layer_job
, url
, i
->glue
, i
);
561 return log_error_errno(r
, "Failed to allocate layer job: %m");
563 r
= dkr_pull_add_token(i
, i
->layer_job
);
567 i
->layer_job
->on_finished
= dkr_pull_job_on_finished
;
568 i
->layer_job
->on_open_disk
= dkr_pull_job_on_open_disk
;
569 i
->layer_job
->on_progress
= dkr_pull_job_on_progress
;
570 i
->layer_job
->grow_machine_directory
= i
->grow_machine_directory
;
572 r
= pull_job_begin(i
->layer_job
);
574 return log_error_errno(r
, "Failed to start layer job: %m");
579 static void dkr_pull_job_on_finished(PullJob
*j
) {
588 if (j
== i
->images_job
)
589 log_error_errno(j
->error
, "Failed to retrieve images list. (Wrong index URL?)");
590 else if (j
== i
->tags_job
)
591 log_error_errno(j
->error
, "Failed to retrieve tags list.");
592 else if (j
== i
->ancestry_job
)
593 log_error_errno(j
->error
, "Failed to retrieve ancestry list.");
594 else if (j
== i
->json_job
)
595 log_error_errno(j
->error
, "Failed to retrieve json data.");
597 log_error_errno(j
->error
, "Failed to retrieve layer data.");
603 if (i
->images_job
== j
) {
606 assert(!i
->tags_job
);
607 assert(!i
->ancestry_job
);
608 assert(!i
->json_job
);
609 assert(!i
->layer_job
);
611 if (strv_isempty(i
->response_registries
)) {
613 log_error("Didn't get registry information.");
617 log_info("Index lookup succeeded, directed to registry %s.", i
->response_registries
[0]);
618 dkr_pull_report_progress(i
, DKR_RESOLVING
);
620 url
= strjoina(PROTOCOL_PREFIX
, i
->response_registries
[0], "/v1/repositories/", i
->name
, "/tags/", i
->tag
);
621 r
= pull_job_new(&i
->tags_job
, url
, i
->glue
, i
);
623 log_error_errno(r
, "Failed to allocate tags job: %m");
627 r
= dkr_pull_add_token(i
, i
->tags_job
);
633 i
->tags_job
->on_finished
= dkr_pull_job_on_finished
;
634 i
->tags_job
->on_progress
= dkr_pull_job_on_progress
;
636 r
= pull_job_begin(i
->tags_job
);
638 log_error_errno(r
, "Failed to start tags job: %m");
642 } else if (i
->tags_job
== j
) {
646 assert(!i
->ancestry_job
);
647 assert(!i
->json_job
);
648 assert(!i
->layer_job
);
650 r
= parse_id(j
->payload
, j
->payload_size
, &id
);
652 log_error_errno(r
, "Failed to parse JSON id.");
659 log_info("Tag lookup succeeded, resolved to layer %s.", i
->id
);
660 dkr_pull_report_progress(i
, DKR_METADATA
);
662 url
= strjoina(PROTOCOL_PREFIX
, i
->response_registries
[0], "/v1/images/", i
->id
, "/ancestry");
663 r
= pull_job_new(&i
->ancestry_job
, url
, i
->glue
, i
);
665 log_error_errno(r
, "Failed to allocate ancestry job: %m");
669 r
= dkr_pull_add_token(i
, i
->ancestry_job
);
675 i
->ancestry_job
->on_finished
= dkr_pull_job_on_finished
;
676 i
->ancestry_job
->on_progress
= dkr_pull_job_on_progress
;
678 url
= strjoina(PROTOCOL_PREFIX
, i
->response_registries
[0], "/v1/images/", i
->id
, "/json");
679 r
= pull_job_new(&i
->json_job
, url
, i
->glue
, i
);
681 log_error_errno(r
, "Failed to allocate json job: %m");
685 r
= dkr_pull_add_token(i
, i
->json_job
);
691 i
->json_job
->on_finished
= dkr_pull_job_on_finished
;
692 i
->json_job
->on_progress
= dkr_pull_job_on_progress
;
694 r
= pull_job_begin(i
->ancestry_job
);
696 log_error_errno(r
, "Failed to start ancestry job: %m");
700 r
= pull_job_begin(i
->json_job
);
702 log_error_errno(r
, "Failed to start json job: %m");
706 } else if (i
->ancestry_job
== j
) {
707 char **ancestry
= NULL
, **k
;
710 assert(!i
->layer_job
);
712 r
= parse_ancestry(j
->payload
, j
->payload_size
, &ancestry
);
714 log_error_errno(r
, "Failed to parse JSON id.");
718 n
= strv_length(ancestry
);
719 if (n
<= 0 || !streq(ancestry
[n
-1], i
->id
)) {
720 log_error("Ancestry doesn't end in main layer.");
726 log_info("Ancestor lookup succeeded, requires layers:\n");
727 STRV_FOREACH(k
, ancestry
)
728 log_info("\t%s", *k
);
730 strv_free(i
->ancestry
);
731 i
->ancestry
= ancestry
;
733 i
->current_ancestry
= 0;
735 dkr_pull_report_progress(i
, DKR_DOWNLOADING
);
737 r
= dkr_pull_pull_layer(i
);
741 } else if (i
->layer_job
== j
) {
742 assert(i
->temp_path
);
743 assert(i
->final_path
);
745 j
->disk_fd
= safe_close(j
->disk_fd
);
747 if (i
->tar_pid
> 0) {
748 r
= wait_for_terminate_and_warn("tar", i
->tar_pid
, true);
754 r
= aufs_resolve(i
->temp_path
);
756 log_error_errno(r
, "Failed to resolve aufs whiteouts: %m");
760 r
= btrfs_subvol_set_read_only(i
->temp_path
, true);
762 log_error_errno(r
, "Failed to mark snapshot read-only: %m");
766 if (rename(i
->temp_path
, i
->final_path
) < 0) {
767 log_error_errno(errno
, "Failed to rename snaphsot: %m");
771 log_info("Completed writing to layer %s.", i
->final_path
);
773 i
->layer_job
= pull_job_unref(i
->layer_job
);
777 i
->final_path
= NULL
;
779 i
->current_ancestry
++;
780 r
= dkr_pull_pull_layer(i
);
784 } else if (i
->json_job
!= j
)
785 assert_not_reached("Got finished event for unknown curl object");
787 if (!dkr_pull_is_done(i
))
790 dkr_pull_report_progress(i
, DKR_COPYING
);
792 r
= dkr_pull_make_local_copy(i
);
800 i
->on_finished(i
, r
, i
->userdata
);
802 sd_event_exit(i
->event
, r
);
805 static int dkr_pull_job_on_header(PullJob
*j
, const char *header
, size_t sz
) {
806 _cleanup_free_
char *registry
= NULL
;
816 r
= curl_header_strdup(header
, sz
, HEADER_TOKEN
, &token
);
820 free(i
->response_token
);
821 i
->response_token
= token
;
825 r
= curl_header_strdup(header
, sz
, HEADER_REGISTRY
, ®istry
);
831 l
= strv_split(registry
, ",");
836 if (!hostname_is_valid(*k
)) {
837 log_error("Registry hostname is not valid.");
843 strv_free(i
->response_registries
);
844 i
->response_registries
= l
;
850 int dkr_pull_start(DkrPull
*i
, const char *name
, const char *tag
, const char *local
, bool force_local
) {
856 if (!dkr_name_is_valid(name
))
859 if (tag
&& !dkr_tag_is_valid(tag
))
862 if (local
&& !machine_name_is_valid(local
))
871 r
= free_and_strdup(&i
->local
, local
);
874 i
->force_local
= force_local
;
876 r
= free_and_strdup(&i
->name
, name
);
879 r
= free_and_strdup(&i
->tag
, tag
);
883 url
= strjoina(i
->index_url
, "/v1/repositories/", name
, "/images");
885 r
= pull_job_new(&i
->images_job
, url
, i
->glue
, i
);
889 r
= dkr_pull_add_token(i
, i
->images_job
);
893 i
->images_job
->on_finished
= dkr_pull_job_on_finished
;
894 i
->images_job
->on_header
= dkr_pull_job_on_header
;
895 i
->images_job
->on_progress
= dkr_pull_job_on_progress
;
897 return pull_job_begin(i
->images_job
);