]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/import/pull-dkr.c
Merge branch 'hostnamectl-dot-v2'
[thirdparty/systemd.git] / src / import / pull-dkr.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4 This file is part of systemd.
5
6 Copyright 2014 Lennart Poettering
7
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.
12
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.
17
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/>.
20 ***/
21
22 #include <curl/curl.h>
23 #include <sys/prctl.h>
24
25 #include "sd-daemon.h"
26 #include "json.h"
27 #include "strv.h"
28 #include "btrfs-util.h"
29 #include "utf8.h"
30 #include "mkdir.h"
31 #include "rm-rf.h"
32 #include "path-util.h"
33 #include "import-util.h"
34 #include "curl-util.h"
35 #include "aufs-util.h"
36 #include "pull-job.h"
37 #include "pull-common.h"
38 #include "import-common.h"
39 #include "pull-dkr.h"
40 #include "process-util.h"
41 #include "hostname-util.h"
42
43 typedef enum DkrProgress {
44 DKR_SEARCHING,
45 DKR_RESOLVING,
46 DKR_METADATA,
47 DKR_DOWNLOADING,
48 DKR_COPYING,
49 } DkrProgress;
50
51 struct DkrPull {
52 sd_event *event;
53 CurlGlue *glue;
54
55 char *index_protocol;
56 char *index_address;
57
58 char *index_url;
59 char *image_root;
60
61 PullJob *images_job;
62 PullJob *tags_job;
63 PullJob *ancestry_job;
64 PullJob *json_job;
65 PullJob *layer_job;
66
67 char *name;
68 char *reference;
69 char *id;
70
71 char *response_digest;
72 char *response_token;
73 char **response_registries;
74
75 char **ancestry;
76 unsigned n_ancestry;
77 unsigned current_ancestry;
78
79 DkrPullFinished on_finished;
80 void *userdata;
81
82 char *local;
83 bool force_local;
84 bool grow_machine_directory;
85
86 char *temp_path;
87 char *final_path;
88
89 pid_t tar_pid;
90 };
91
92 #define PROTOCOL_PREFIX "https://"
93
94 #define HEADER_TOKEN "X-Do" /* the HTTP header for the auth token */ "cker-Token:"
95 #define HEADER_REGISTRY "X-Do" /* the HTTP header for the registry */ "cker-Endpoints:"
96 #define HEADER_DIGEST "Do" /* the HTTP header for the manifest digest */ "cker-Content-Digest:"
97 #define LAYERS_MAX 127
98
99 static void dkr_pull_job_on_finished(PullJob *j);
100
101 DkrPull* dkr_pull_unref(DkrPull *i) {
102 if (!i)
103 return NULL;
104
105 if (i->tar_pid > 1) {
106 (void) kill_and_sigcont(i->tar_pid, SIGKILL);
107 (void) wait_for_terminate(i->tar_pid, NULL);
108 }
109
110 pull_job_unref(i->images_job);
111 pull_job_unref(i->tags_job);
112 pull_job_unref(i->ancestry_job);
113 pull_job_unref(i->json_job);
114 pull_job_unref(i->layer_job);
115
116 curl_glue_unref(i->glue);
117 sd_event_unref(i->event);
118
119 if (i->temp_path) {
120 (void) rm_rf(i->temp_path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME);
121 free(i->temp_path);
122 }
123
124 free(i->name);
125 free(i->reference);
126 free(i->id);
127 free(i->response_token);
128 strv_free(i->ancestry);
129 free(i->final_path);
130 free(i->index_address);
131 free(i->index_protocol);
132 free(i->index_url);
133 free(i->image_root);
134 free(i->local);
135 free(i);
136
137 return NULL;
138 }
139
140 int dkr_pull_new(
141 DkrPull **ret,
142 sd_event *event,
143 const char *index_url,
144 const char *image_root,
145 DkrPullFinished on_finished,
146 void *userdata) {
147
148 _cleanup_(dkr_pull_unrefp) DkrPull *i = NULL;
149 char *e;
150 int r;
151
152 assert(ret);
153 assert(index_url);
154
155 if (!http_url_is_valid(index_url))
156 return -EINVAL;
157
158 i = new0(DkrPull, 1);
159 if (!i)
160 return -ENOMEM;
161
162 i->on_finished = on_finished;
163 i->userdata = userdata;
164
165 i->image_root = strdup(image_root ?: "/var/lib/machines");
166 if (!i->image_root)
167 return -ENOMEM;
168
169 i->grow_machine_directory = path_startswith(i->image_root, "/var/lib/machines");
170
171 i->index_url = strdup(index_url);
172 if (!i->index_url)
173 return -ENOMEM;
174
175 e = endswith(i->index_url, "/");
176 if (e)
177 *e = 0;
178
179 if (event)
180 i->event = sd_event_ref(event);
181 else {
182 r = sd_event_default(&i->event);
183 if (r < 0)
184 return r;
185 }
186
187 r = curl_glue_new(&i->glue, i->event);
188 if (r < 0)
189 return r;
190
191 i->glue->on_finished = pull_job_curl_on_finished;
192 i->glue->userdata = i;
193
194 *ret = i;
195 i = NULL;
196
197 return 0;
198 }
199
200 static void dkr_pull_report_progress(DkrPull *i, DkrProgress p) {
201 unsigned percent;
202
203 assert(i);
204
205 switch (p) {
206
207 case DKR_SEARCHING:
208 percent = 0;
209 if (i->images_job)
210 percent += i->images_job->progress_percent * 5 / 100;
211 break;
212
213 case DKR_RESOLVING:
214 percent = 5;
215 if (i->tags_job)
216 percent += i->tags_job->progress_percent * 5 / 100;
217 break;
218
219 case DKR_METADATA:
220 percent = 10;
221 if (i->ancestry_job)
222 percent += i->ancestry_job->progress_percent * 5 / 100;
223 if (i->json_job)
224 percent += i->json_job->progress_percent * 5 / 100;
225 break;
226
227 case DKR_DOWNLOADING:
228 percent = 20;
229 percent += 75 * i->current_ancestry / MAX(1U, i->n_ancestry);
230 if (i->layer_job)
231 percent += i->layer_job->progress_percent * 75 / MAX(1U, i->n_ancestry) / 100;
232
233 break;
234
235 case DKR_COPYING:
236 percent = 95;
237 break;
238
239 default:
240 assert_not_reached("Unknown progress state");
241 }
242
243 sd_notifyf(false, "X_IMPORT_PROGRESS=%u", percent);
244 log_debug("Combined progress %u%%", percent);
245 }
246
247 static int parse_id(const void *payload, size_t size, char **ret) {
248 _cleanup_free_ char *buf = NULL, *id = NULL, *other = NULL;
249 union json_value v = {};
250 void *json_state = NULL;
251 const char *p;
252 int t;
253
254 assert(payload);
255 assert(ret);
256
257 if (size <= 0)
258 return -EBADMSG;
259
260 if (memchr(payload, 0, size))
261 return -EBADMSG;
262
263 buf = strndup(payload, size);
264 if (!buf)
265 return -ENOMEM;
266
267 p = buf;
268 t = json_tokenize(&p, &id, &v, &json_state, NULL);
269 if (t < 0)
270 return t;
271 if (t != JSON_STRING)
272 return -EBADMSG;
273
274 t = json_tokenize(&p, &other, &v, &json_state, NULL);
275 if (t < 0)
276 return t;
277 if (t != JSON_END)
278 return -EBADMSG;
279
280 if (!dkr_id_is_valid(id))
281 return -EBADMSG;
282
283 *ret = id;
284 id = NULL;
285
286 return 0;
287 }
288
289 static int parse_ancestry(const void *payload, size_t size, char ***ret) {
290 _cleanup_free_ char *buf = NULL;
291 void *json_state = NULL;
292 const char *p;
293 enum {
294 STATE_BEGIN,
295 STATE_ITEM,
296 STATE_COMMA,
297 STATE_END,
298 } state = STATE_BEGIN;
299 _cleanup_strv_free_ char **l = NULL;
300 size_t n = 0, allocated = 0;
301
302 if (size <= 0)
303 return -EBADMSG;
304
305 if (memchr(payload, 0, size))
306 return -EBADMSG;
307
308 buf = strndup(payload, size);
309 if (!buf)
310 return -ENOMEM;
311
312 p = buf;
313 for (;;) {
314 _cleanup_free_ char *str;
315 union json_value v = {};
316 int t;
317
318 t = json_tokenize(&p, &str, &v, &json_state, NULL);
319 if (t < 0)
320 return t;
321
322 switch (state) {
323
324 case STATE_BEGIN:
325 if (t == JSON_ARRAY_OPEN)
326 state = STATE_ITEM;
327 else
328 return -EBADMSG;
329
330 break;
331
332 case STATE_ITEM:
333 if (t == JSON_STRING) {
334 if (!dkr_id_is_valid(str))
335 return -EBADMSG;
336
337 if (n+1 > LAYERS_MAX)
338 return -EFBIG;
339
340 if (!GREEDY_REALLOC(l, allocated, n + 2))
341 return -ENOMEM;
342
343 l[n++] = str;
344 str = NULL;
345 l[n] = NULL;
346
347 state = STATE_COMMA;
348
349 } else if (t == JSON_ARRAY_CLOSE)
350 state = STATE_END;
351 else
352 return -EBADMSG;
353
354 break;
355
356 case STATE_COMMA:
357 if (t == JSON_COMMA)
358 state = STATE_ITEM;
359 else if (t == JSON_ARRAY_CLOSE)
360 state = STATE_END;
361 else
362 return -EBADMSG;
363 break;
364
365 case STATE_END:
366 if (t == JSON_END) {
367
368 if (strv_isempty(l))
369 return -EBADMSG;
370
371 if (!strv_is_uniq(l))
372 return -EBADMSG;
373
374 l = strv_reverse(l);
375
376 *ret = l;
377 l = NULL;
378 return 0;
379 } else
380 return -EBADMSG;
381 }
382
383 }
384 }
385
386 static const char *dkr_pull_current_layer(DkrPull *i) {
387 assert(i);
388
389 if (strv_isempty(i->ancestry))
390 return NULL;
391
392 return i->ancestry[i->current_ancestry];
393 }
394
395 static const char *dkr_pull_current_base_layer(DkrPull *i) {
396 assert(i);
397
398 if (strv_isempty(i->ancestry))
399 return NULL;
400
401 if (i->current_ancestry <= 0)
402 return NULL;
403
404 return i->ancestry[i->current_ancestry-1];
405 }
406
407 static int dkr_pull_add_token(DkrPull *i, PullJob *j) {
408 const char *t;
409
410 assert(i);
411 assert(j);
412
413 if (i->response_token)
414 t = strjoina("Authorization: Token ", i->response_token);
415 else
416 t = HEADER_TOKEN " true";
417
418 j->request_header = curl_slist_new("Accept: application/json", t, NULL);
419 if (!j->request_header)
420 return -ENOMEM;
421
422 return 0;
423 }
424
425 static int dkr_pull_add_bearer_token(DkrPull *i, PullJob *j) {
426 const char *t = NULL;
427
428 assert(i);
429 assert(j);
430
431 if (i->response_token)
432 t = strjoina("Authorization: Bearer ", i->response_token);
433 else
434 return -EINVAL;
435
436 j->request_header = curl_slist_new("Accept: application/json", t, NULL);
437 if (!j->request_header)
438 return -ENOMEM;
439
440 return 0;
441 }
442
443 static bool dkr_pull_is_done(DkrPull *i) {
444 assert(i);
445 assert(i->images_job);
446 if (i->images_job->state != PULL_JOB_DONE)
447 return false;
448
449 if (!i->tags_job || i->tags_job->state != PULL_JOB_DONE)
450 return false;
451
452 if (!i->ancestry_job || i->ancestry_job->state != PULL_JOB_DONE)
453 return false;
454
455 if (i->json_job && i->json_job->state != PULL_JOB_DONE)
456 return false;
457
458 if (i->layer_job && i->layer_job->state != PULL_JOB_DONE)
459 return false;
460
461 if (dkr_pull_current_layer(i))
462 return false;
463
464 return true;
465 }
466
467 static int dkr_pull_make_local_copy(DkrPull *i, DkrPullVersion version) {
468 int r;
469 _cleanup_free_ char *p = NULL;
470
471 assert(i);
472
473 if (!i->local)
474 return 0;
475
476 if (!i->final_path) {
477 i->final_path = strjoin(i->image_root, "/.dkr-", i->id, NULL);
478 if (!i->final_path)
479 return log_oom();
480 }
481
482 if (version == DKR_PULL_V2) {
483 r = path_get_parent(i->image_root, &p);
484 if (r < 0)
485 return r;
486 }
487
488 r = pull_make_local_copy(i->final_path, p ?: i->image_root, i->local, i->force_local);
489 if (r < 0)
490 return r;
491
492 if (version == DKR_PULL_V2) {
493 char **k = NULL;
494 STRV_FOREACH(k, i->ancestry) {
495 _cleanup_free_ char *d = strjoin(i->image_root, "/.dkr-", *k, NULL);
496 r = btrfs_subvol_remove(d, false);
497 if (r < 0)
498 return r;
499 }
500
501 r = rmdir(i->image_root);
502 if (r < 0)
503 return r;
504 }
505
506 return 0;
507 }
508
509 static int dkr_pull_job_on_open_disk(PullJob *j) {
510 const char *base;
511 DkrPull *i;
512 int r;
513
514 assert(j);
515 assert(j->userdata);
516
517 i = j->userdata;
518 assert(i->layer_job == j);
519 assert(i->final_path);
520 assert(!i->temp_path);
521 assert(i->tar_pid <= 0);
522
523 r = tempfn_random(i->final_path, NULL, &i->temp_path);
524 if (r < 0)
525 return log_oom();
526
527 mkdir_parents_label(i->temp_path, 0700);
528
529 base = dkr_pull_current_base_layer(i);
530 if (base) {
531 const char *base_path;
532
533 base_path = strjoina(i->image_root, "/.dkr-", base);
534 r = btrfs_subvol_snapshot(base_path, i->temp_path, BTRFS_SNAPSHOT_FALLBACK_COPY);
535 } else
536 r = btrfs_subvol_make(i->temp_path);
537 if (r < 0)
538 return log_error_errno(r, "Failed to make btrfs subvolume %s: %m", i->temp_path);
539
540 j->disk_fd = import_fork_tar_x(i->temp_path, &i->tar_pid);
541 if (j->disk_fd < 0)
542 return j->disk_fd;
543
544 return 0;
545 }
546
547 static void dkr_pull_job_on_progress(PullJob *j) {
548 DkrPull *i;
549
550 assert(j);
551 assert(j->userdata);
552
553 i = j->userdata;
554
555 dkr_pull_report_progress(
556 i,
557 j == i->images_job ? DKR_SEARCHING :
558 j == i->tags_job ? DKR_RESOLVING :
559 j == i->ancestry_job || j == i->json_job ? DKR_METADATA :
560 DKR_DOWNLOADING);
561 }
562
563 static void dkr_pull_job_on_finished_v2(PullJob *j);
564
565 static int dkr_pull_pull_layer_v2(DkrPull *i) {
566 _cleanup_free_ char *path = NULL;
567 const char *url, *layer = NULL;
568 int r;
569
570 assert(i);
571 assert(!i->layer_job);
572 assert(!i->temp_path);
573 assert(!i->final_path);
574
575 for (;;) {
576 layer = dkr_pull_current_layer(i);
577 if (!layer)
578 return 0; /* no more layers */
579
580 path = strjoin(i->image_root, "/.dkr-", layer, NULL);
581 if (!path)
582 return log_oom();
583
584 if (laccess(path, F_OK) < 0) {
585 if (errno == ENOENT)
586 break;
587
588 return log_error_errno(errno, "Failed to check for container: %m");
589 }
590
591 log_info("Layer %s already exists, skipping.", layer);
592
593 i->current_ancestry++;
594
595 path = mfree(path);
596 }
597
598 log_info("Pulling layer %s...", layer);
599
600 i->final_path = path;
601 path = NULL;
602
603 url = strjoina(PROTOCOL_PREFIX, i->response_registries[0], "/v2/", i->name, "/blobs/", layer);
604 r = pull_job_new(&i->layer_job, url, i->glue, i);
605 if (r < 0)
606 return log_error_errno(r, "Failed to allocate layer job: %m");
607
608 r = dkr_pull_add_bearer_token(i, i->layer_job);
609 if (r < 0)
610 return log_oom();
611
612 i->layer_job->on_finished = dkr_pull_job_on_finished_v2;
613 i->layer_job->on_open_disk = dkr_pull_job_on_open_disk;
614 i->layer_job->on_progress = dkr_pull_job_on_progress;
615 i->layer_job->grow_machine_directory = i->grow_machine_directory;
616
617 r = pull_job_begin(i->layer_job);
618 if (r < 0)
619 return log_error_errno(r, "Failed to start layer job: %m");
620
621 return 0;
622 }
623
624 static int dkr_pull_pull_layer(DkrPull *i) {
625 _cleanup_free_ char *path = NULL;
626 const char *url, *layer = NULL;
627 int r;
628
629 assert(i);
630 assert(!i->layer_job);
631 assert(!i->temp_path);
632 assert(!i->final_path);
633
634 for (;;) {
635 layer = dkr_pull_current_layer(i);
636 if (!layer)
637 return 0; /* no more layers */
638
639 path = strjoin(i->image_root, "/.dkr-", layer, NULL);
640 if (!path)
641 return log_oom();
642
643 if (laccess(path, F_OK) < 0) {
644 if (errno == ENOENT)
645 break;
646
647 return log_error_errno(errno, "Failed to check for container: %m");
648 }
649
650 log_info("Layer %s already exists, skipping.", layer);
651
652 i->current_ancestry++;
653
654 path = mfree(path);
655 }
656
657 log_info("Pulling layer %s...", layer);
658
659 i->final_path = path;
660 path = NULL;
661
662 url = strjoina(PROTOCOL_PREFIX, i->response_registries[0], "/v1/images/", layer, "/layer");
663 r = pull_job_new(&i->layer_job, url, i->glue, i);
664 if (r < 0)
665 return log_error_errno(r, "Failed to allocate layer job: %m");
666
667 r = dkr_pull_add_token(i, i->layer_job);
668 if (r < 0)
669 return log_oom();
670
671 i->layer_job->on_finished = dkr_pull_job_on_finished;
672 i->layer_job->on_open_disk = dkr_pull_job_on_open_disk;
673 i->layer_job->on_progress = dkr_pull_job_on_progress;
674 i->layer_job->grow_machine_directory = i->grow_machine_directory;
675
676 r = pull_job_begin(i->layer_job);
677 if (r < 0)
678 return log_error_errno(r, "Failed to start layer job: %m");
679
680 return 0;
681 }
682
683 static int dkr_pull_job_on_header(PullJob *j, const char *header, size_t sz) {
684 _cleanup_free_ char *registry = NULL;
685 char *token, *digest;
686 DkrPull *i;
687 int r;
688
689 assert(j);
690 assert(j->userdata);
691
692 i = j->userdata;
693 r = curl_header_strdup(header, sz, HEADER_TOKEN, &token);
694 if (r < 0)
695 return log_oom();
696 if (r > 0) {
697 free(i->response_token);
698 i->response_token = token;
699 return 0;
700 }
701
702 r = curl_header_strdup(header, sz, HEADER_DIGEST, &digest);
703 if (r < 0)
704 return log_oom();
705 if (r > 0) {
706 free(i->response_digest);
707 i->response_digest = digest;
708 return 0;
709 }
710
711 r = curl_header_strdup(header, sz, HEADER_REGISTRY, &registry);
712 if (r < 0)
713 return log_oom();
714 if (r > 0) {
715 char **l, **k;
716
717 l = strv_split(registry, ",");
718 if (!l)
719 return log_oom();
720
721 STRV_FOREACH(k, l) {
722 if (!hostname_is_valid(*k, false)) {
723 log_error("Registry hostname is not valid.");
724 strv_free(l);
725 return -EBADMSG;
726 }
727 }
728
729 strv_free(i->response_registries);
730 i->response_registries = l;
731 }
732
733 return 0;
734 }
735
736 static void dkr_pull_job_on_finished_v2(PullJob *j) {
737 DkrPull *i;
738 int r;
739
740 assert(j);
741 assert(j->userdata);
742
743 i = j->userdata;
744 if (j->error != 0) {
745 if (j == i->images_job)
746 log_error_errno(j->error, "Failed to retrieve images list. (Wrong index URL?)");
747 else if (j == i->ancestry_job)
748 log_error_errno(j->error, "Failed to retrieve manifest.");
749 else if (j == i->json_job)
750 log_error_errno(j->error, "Failed to retrieve json data.");
751 else
752 log_error_errno(j->error, "Failed to retrieve layer data.");
753
754 r = j->error;
755 goto finish;
756 }
757
758 if (i->images_job == j) {
759 const char *url;
760
761 assert(!i->tags_job);
762 assert(!i->ancestry_job);
763 assert(!i->json_job);
764 assert(!i->layer_job);
765
766 if (strv_isempty(i->response_registries)) {
767 r = -EBADMSG;
768 log_error("Didn't get registry information.");
769 goto finish;
770 }
771
772 log_info("Index lookup succeeded, directed to registry %s.", i->response_registries[0]);
773 dkr_pull_report_progress(i, DKR_RESOLVING);
774
775 url = strjoina(i->index_protocol, "auth.", i->index_address, "/v2/token/?scope=repository:",
776 i->name, ":pull&service=registry.", i->index_address);
777 r = pull_job_new(&i->tags_job, url, i->glue, i);
778 if (r < 0) {
779 log_error_errno(r, "Failed to allocate tags job: %m");
780 goto finish;
781 }
782
783 i->tags_job->on_finished = dkr_pull_job_on_finished_v2;
784 i->tags_job->on_progress = dkr_pull_job_on_progress;
785
786 r = pull_job_begin(i->tags_job);
787 if (r < 0) {
788 log_error_errno(r, "Failed to start tags job: %m");
789 goto finish;
790 }
791
792 } else if (i->tags_job == j) {
793 const char *url;
794 _cleanup_free_ char *buf;
795 _cleanup_json_variant_unref_ JsonVariant *doc = NULL;
796 JsonVariant *e = NULL;
797
798 assert(!i->ancestry_job);
799 assert(!i->json_job);
800 assert(!i->layer_job);
801
802 buf = strndup((const char *)j->payload, j->payload_size);
803 if (!buf) {
804 r = -ENOMEM;
805 log_oom();
806 goto finish;
807 }
808
809 r = json_parse(buf, &doc);
810 if (r < 0) {
811 log_error("Unable to parse bearer token\n%s", j->payload);
812 goto finish;
813 }
814
815 e = json_variant_value(doc, "token");
816 if (!e || e->type != JSON_VARIANT_STRING) {
817 r = -EBADMSG;
818 log_error("Invalid JSON format for Bearer token");
819 goto finish;
820 }
821
822 r = free_and_strdup(&i->response_token, json_variant_string(e));
823 if (r < 0) {
824 log_oom();
825 goto finish;
826 }
827
828 url = strjoina(PROTOCOL_PREFIX, i->response_registries[0], "/v2/", i->name, "/manifests/", i->reference);
829 r = pull_job_new(&i->ancestry_job, url, i->glue, i);
830 if (r < 0) {
831 log_error_errno(r, "Failed to allocate ancestry job: %m");
832 goto finish;
833 }
834
835 r = dkr_pull_add_bearer_token(i, i->ancestry_job);
836 if (r < 0)
837 goto finish;
838
839 i->ancestry_job->on_finished = dkr_pull_job_on_finished_v2;
840 i->ancestry_job->on_progress = dkr_pull_job_on_progress;
841 i->ancestry_job->on_header = dkr_pull_job_on_header;
842
843
844 r = pull_job_begin(i->ancestry_job);
845 if (r < 0) {
846 log_error_errno(r, "Failed to start ancestry job: %m");
847 goto finish;
848 }
849
850 } else if (i->ancestry_job == j) {
851
852 _cleanup_json_variant_unref_ JsonVariant *doc = NULL, *compat = NULL;
853 JsonVariant *e = NULL;
854 _cleanup_strv_free_ char **ancestry = NULL;
855 size_t allocated = 0, size = 0;
856 char *path = NULL, **k = NULL;
857
858 r = json_parse((const char *)j->payload, &doc);
859 if (r < 0) {
860 log_error("Invalid JSON Manifest");
861 goto finish;
862 }
863
864 e = json_variant_value(doc, "fsLayers");
865 if (!e || e->type != JSON_VARIANT_ARRAY || e->size == 0) {
866 r = -EBADMSG;
867 goto finish;
868 }
869
870 log_info("JSON manifest with schema v%"PRIi64" for %s parsed!",
871 json_variant_integer(json_variant_value(doc, "schemaVersion")),
872 json_variant_string(json_variant_value(doc, "name")));
873
874 for (unsigned z = 0; z < e->size; z++) {
875 JsonVariant *f = json_variant_element(e, z), *g = NULL;
876 const char *layer;
877 if (f->type != JSON_VARIANT_OBJECT) {
878 r = -EBADMSG;
879 goto finish;
880 }
881
882 g = json_variant_value(f, "blobSum");
883
884 layer = json_variant_string(g);
885 if (!dkr_digest_is_valid(layer)) {
886 r = -EBADMSG;
887 goto finish;
888 }
889
890 if (!GREEDY_REALLOC(ancestry, allocated, size + 2)) {
891 r = -ENOMEM;
892 log_oom();
893 goto finish;
894 }
895
896 ancestry[size] = strdup(layer);
897 if (!ancestry[size]) {
898 r = -ENOMEM;
899 log_oom();
900 goto finish;
901 }
902
903 ancestry[size+1] = NULL;
904 size += 1;
905 }
906
907 e = json_variant_value(doc, "history");
908 if (!e || e->type != JSON_VARIANT_ARRAY) {
909 r = -EBADMSG;
910 goto finish;
911 }
912
913 e = json_variant_element(e, 0);
914 e = json_variant_value(e, "v1Compatibility");
915 r = json_parse(json_variant_string(e), &compat);
916 if (r < 0) {
917 log_error("Invalid v1Compatibility JSON");
918 goto finish;
919 }
920
921 e = json_variant_value(compat, "id");
922
923 strv_free(i->ancestry);
924 i->ancestry = strv_reverse(strv_uniq(ancestry));
925 i->n_ancestry = strv_length(i->ancestry);
926 i->current_ancestry = 0;
927 i->id = strdup(i->ancestry[i->n_ancestry - 1]);
928 if (!i->id) {
929 r = -ENOMEM;
930 log_oom();
931 goto finish;
932 }
933 path = strjoin(i->image_root, "/.dkr-", json_variant_string(e), NULL);
934 if (!path) {
935 r = -ENOMEM;
936 log_oom();
937 goto finish;
938 }
939 free(i->image_root);
940 i->image_root = path;
941 ancestry = NULL;
942
943 log_info("Required layers:\n");
944 STRV_FOREACH(k, i->ancestry)
945 log_info("\t%s", *k);
946 log_info("\nProvenance:\n\tImageID: %s\n\tDigest: %s", json_variant_string(e), i->response_digest);
947
948 dkr_pull_report_progress(i, DKR_DOWNLOADING);
949
950 r = dkr_pull_pull_layer_v2(i);
951 if (r < 0)
952 goto finish;
953
954 } else if (i->layer_job == j) {
955 assert(i->temp_path);
956 assert(i->final_path);
957
958 j->disk_fd = safe_close(j->disk_fd);
959
960 if (i->tar_pid > 0) {
961 r = wait_for_terminate_and_warn("tar", i->tar_pid, true);
962 i->tar_pid = 0;
963 if (r < 0)
964 goto finish;
965 }
966
967 r = aufs_resolve(i->temp_path);
968 if (r < 0) {
969 log_error_errno(r, "Failed to resolve aufs whiteouts: %m");
970 goto finish;
971 }
972
973 r = btrfs_subvol_set_read_only(i->temp_path, true);
974 if (r < 0) {
975 log_error_errno(r, "Failed to mark snapshot read-only: %m");
976 goto finish;
977 }
978
979 if (rename(i->temp_path, i->final_path) < 0) {
980 log_error_errno(errno, "Failed to rename snaphsot: %m");
981 goto finish;
982 }
983
984 log_info("Completed writing to layer %s.", i->final_path);
985
986 i->layer_job = pull_job_unref(i->layer_job);
987 free(i->temp_path);
988 i->temp_path = NULL;
989 free(i->final_path);
990 i->final_path = NULL;
991
992 i->current_ancestry ++;
993 r = dkr_pull_pull_layer_v2(i);
994 if (r < 0)
995 goto finish;
996
997 } else if (i->json_job != j)
998 assert_not_reached("Got finished event for unknown curl object");
999
1000 if (!dkr_pull_is_done(i))
1001 return;
1002
1003 dkr_pull_report_progress(i, DKR_COPYING);
1004
1005 r = dkr_pull_make_local_copy(i, DKR_PULL_V2);
1006 if (r < 0)
1007 goto finish;
1008
1009 r = 0;
1010
1011 finish:
1012 if (i->on_finished)
1013 i->on_finished(i, r, i->userdata);
1014 else
1015 sd_event_exit(i->event, r);
1016
1017 }
1018
1019 static void dkr_pull_job_on_finished(PullJob *j) {
1020 DkrPull *i;
1021 int r;
1022
1023 assert(j);
1024 assert(j->userdata);
1025
1026 i = j->userdata;
1027 if (j->error != 0) {
1028 if (j == i->images_job)
1029 log_error_errno(j->error, "Failed to retrieve images list. (Wrong index URL?)");
1030 else if (j == i->tags_job)
1031 log_error_errno(j->error, "Failed to retrieve tags list.");
1032 else if (j == i->ancestry_job)
1033 log_error_errno(j->error, "Failed to retrieve ancestry list.");
1034 else if (j == i->json_job)
1035 log_error_errno(j->error, "Failed to retrieve json data.");
1036 else
1037 log_error_errno(j->error, "Failed to retrieve layer data.");
1038
1039 r = j->error;
1040 goto finish;
1041 }
1042
1043 if (i->images_job == j) {
1044 const char *url;
1045
1046 assert(!i->tags_job);
1047 assert(!i->ancestry_job);
1048 assert(!i->json_job);
1049 assert(!i->layer_job);
1050
1051 if (strv_isempty(i->response_registries)) {
1052 r = -EBADMSG;
1053 log_error("Didn't get registry information.");
1054 goto finish;
1055 }
1056
1057 log_info("Index lookup succeeded, directed to registry %s.", i->response_registries[0]);
1058 dkr_pull_report_progress(i, DKR_RESOLVING);
1059
1060 url = strjoina(PROTOCOL_PREFIX, i->response_registries[0], "/v1/repositories/", i->name, "/tags/", i->reference);
1061 r = pull_job_new(&i->tags_job, url, i->glue, i);
1062 if (r < 0) {
1063 log_error_errno(r, "Failed to allocate tags job: %m");
1064 goto finish;
1065 }
1066
1067 r = dkr_pull_add_token(i, i->tags_job);
1068 if (r < 0) {
1069 log_oom();
1070 goto finish;
1071 }
1072
1073 i->tags_job->on_finished = dkr_pull_job_on_finished;
1074 i->tags_job->on_progress = dkr_pull_job_on_progress;
1075
1076 r = pull_job_begin(i->tags_job);
1077 if (r < 0) {
1078 log_error_errno(r, "Failed to start tags job: %m");
1079 goto finish;
1080 }
1081
1082 } else if (i->tags_job == j) {
1083 const char *url;
1084 char *id = NULL;
1085
1086 assert(!i->ancestry_job);
1087 assert(!i->json_job);
1088 assert(!i->layer_job);
1089
1090 r = parse_id(j->payload, j->payload_size, &id);
1091 if (r < 0) {
1092 log_error_errno(r, "Failed to parse JSON id.");
1093 goto finish;
1094 }
1095
1096 free(i->id);
1097 i->id = id;
1098
1099 log_info("Tag lookup succeeded, resolved to layer %s.", i->id);
1100 dkr_pull_report_progress(i, DKR_METADATA);
1101
1102 url = strjoina(PROTOCOL_PREFIX, i->response_registries[0], "/v1/images/", i->id, "/ancestry");
1103 r = pull_job_new(&i->ancestry_job, url, i->glue, i);
1104 if (r < 0) {
1105 log_error_errno(r, "Failed to allocate ancestry job: %m");
1106 goto finish;
1107 }
1108
1109 r = dkr_pull_add_token(i, i->ancestry_job);
1110 if (r < 0) {
1111 log_oom();
1112 goto finish;
1113 }
1114
1115 i->ancestry_job->on_finished = dkr_pull_job_on_finished;
1116 i->ancestry_job->on_progress = dkr_pull_job_on_progress;
1117
1118 url = strjoina(PROTOCOL_PREFIX, i->response_registries[0], "/v1/images/", i->id, "/json");
1119 r = pull_job_new(&i->json_job, url, i->glue, i);
1120 if (r < 0) {
1121 log_error_errno(r, "Failed to allocate json job: %m");
1122 goto finish;
1123 }
1124
1125 r = dkr_pull_add_token(i, i->json_job);
1126 if (r < 0) {
1127 log_oom();
1128 goto finish;
1129 }
1130
1131 i->json_job->on_finished = dkr_pull_job_on_finished;
1132 i->json_job->on_progress = dkr_pull_job_on_progress;
1133
1134 r = pull_job_begin(i->ancestry_job);
1135 if (r < 0) {
1136 log_error_errno(r, "Failed to start ancestry job: %m");
1137 goto finish;
1138 }
1139
1140 r = pull_job_begin(i->json_job);
1141 if (r < 0) {
1142 log_error_errno(r, "Failed to start json job: %m");
1143 goto finish;
1144 }
1145
1146 } else if (i->ancestry_job == j) {
1147 char **ancestry = NULL, **k;
1148 unsigned n;
1149
1150 assert(!i->layer_job);
1151
1152 r = parse_ancestry(j->payload, j->payload_size, &ancestry);
1153 if (r < 0) {
1154 log_error_errno(r, "Failed to parse JSON id.");
1155 goto finish;
1156 }
1157
1158 n = strv_length(ancestry);
1159 if (n <= 0 || !streq(ancestry[n-1], i->id)) {
1160 log_error("Ancestry doesn't end in main layer.");
1161 strv_free(ancestry);
1162 r = -EBADMSG;
1163 goto finish;
1164 }
1165
1166 log_info("Ancestor lookup succeeded, requires layers:\n");
1167 STRV_FOREACH(k, ancestry)
1168 log_info("\t%s", *k);
1169
1170 strv_free(i->ancestry);
1171 i->ancestry = ancestry;
1172 i->n_ancestry = n;
1173 i->current_ancestry = 0;
1174
1175 dkr_pull_report_progress(i, DKR_DOWNLOADING);
1176
1177 r = dkr_pull_pull_layer(i);
1178 if (r < 0)
1179 goto finish;
1180
1181 } else if (i->layer_job == j) {
1182 assert(i->temp_path);
1183 assert(i->final_path);
1184
1185 j->disk_fd = safe_close(j->disk_fd);
1186
1187 if (i->tar_pid > 0) {
1188 r = wait_for_terminate_and_warn("tar", i->tar_pid, true);
1189 i->tar_pid = 0;
1190 if (r < 0)
1191 goto finish;
1192 }
1193
1194 r = aufs_resolve(i->temp_path);
1195 if (r < 0) {
1196 log_error_errno(r, "Failed to resolve aufs whiteouts: %m");
1197 goto finish;
1198 }
1199
1200 r = btrfs_subvol_set_read_only(i->temp_path, true);
1201 if (r < 0) {
1202 log_error_errno(r, "Failed to mark snapshot read-only: %m");
1203 goto finish;
1204 }
1205
1206 if (rename(i->temp_path, i->final_path) < 0) {
1207 log_error_errno(errno, "Failed to rename snaphsot: %m");
1208 goto finish;
1209 }
1210
1211 log_info("Completed writing to layer %s.", i->final_path);
1212
1213 i->layer_job = pull_job_unref(i->layer_job);
1214 free(i->temp_path);
1215 i->temp_path = NULL;
1216 free(i->final_path);
1217 i->final_path = NULL;
1218
1219 i->current_ancestry ++;
1220 r = dkr_pull_pull_layer(i);
1221 if (r < 0)
1222 goto finish;
1223
1224 } else if (i->json_job != j)
1225 assert_not_reached("Got finished event for unknown curl object");
1226
1227 if (!dkr_pull_is_done(i))
1228 return;
1229
1230 dkr_pull_report_progress(i, DKR_COPYING);
1231
1232 r = dkr_pull_make_local_copy(i, DKR_PULL_V1);
1233 if (r < 0)
1234 goto finish;
1235
1236 r = 0;
1237 finish:
1238 if (i->on_finished)
1239 i->on_finished(i, r, i->userdata);
1240 else
1241 sd_event_exit(i->event, r);
1242 }
1243
1244 static int get_protocol_address(char **protocol, char **address, const char *url) {
1245 const char *sep, *dot;
1246 _cleanup_free_ char *a = NULL, *p = NULL;
1247
1248 sep = strstr(url, "://");
1249 if (!sep)
1250 return -EINVAL;
1251
1252 dot = strrchr(url, '.');
1253 if (!dot)
1254 return -EINVAL;
1255 dot--;
1256
1257 p = strndup(url, (sep - url) + 3);
1258 if (!p)
1259 return log_oom();
1260
1261 while (dot > (sep + 3) && *dot != '.')
1262 dot--;
1263
1264 a = strdup(dot + 1);
1265 if (!a)
1266 return log_oom();
1267
1268 *address = a;
1269 *protocol = p;
1270 a = p = NULL;
1271
1272 return 0;
1273 }
1274
1275 int dkr_pull_start(DkrPull *i, const char *name, const char *reference, const char *local, bool force_local, DkrPullVersion version) {
1276 const char *url;
1277 int r;
1278
1279 assert(i);
1280
1281 if (!dkr_name_is_valid(name))
1282 return -EINVAL;
1283
1284 if (reference && !dkr_ref_is_valid(reference))
1285 return -EINVAL;
1286
1287 if (local && !machine_name_is_valid(local))
1288 return -EINVAL;
1289
1290 if (i->images_job)
1291 return -EBUSY;
1292
1293 if (!reference)
1294 reference = "latest";
1295
1296 free(i->index_protocol);
1297 free(i->index_address);
1298 r = get_protocol_address(&i->index_protocol, &i->index_address, i->index_url);
1299 if (r < 0)
1300 return r;
1301
1302 r = free_and_strdup(&i->local, local);
1303 if (r < 0)
1304 return r;
1305 i->force_local = force_local;
1306
1307 r = free_and_strdup(&i->name, name);
1308 if (r < 0)
1309 return r;
1310 r = free_and_strdup(&i->reference, reference);
1311 if (r < 0)
1312 return r;
1313
1314 url = strjoina(i->index_url, "/v1/repositories/", name, "/images");
1315
1316 r = pull_job_new(&i->images_job, url, i->glue, i);
1317 if (r < 0)
1318 return r;
1319
1320 r = dkr_pull_add_token(i, i->images_job);
1321 if (r < 0)
1322 return r;
1323
1324 if (version == DKR_PULL_V1)
1325 i->images_job->on_finished = dkr_pull_job_on_finished;
1326 else
1327 i->images_job->on_finished = dkr_pull_job_on_finished_v2;
1328
1329 i->images_job->on_header = dkr_pull_job_on_header;
1330 i->images_job->on_progress = dkr_pull_job_on_progress;
1331
1332 return pull_job_begin(i->images_job);
1333 }