]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/import/pull-dkr.c
util: when creating temporary file names, allow including extra id string in it
[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 free(path);
596 path = NULL;
597 }
598
599 log_info("Pulling layer %s...", layer);
600
601 i->final_path = path;
602 path = NULL;
603
604 url = strjoina(PROTOCOL_PREFIX, i->response_registries[0], "/v2/", i->name, "/blobs/", layer);
605 r = pull_job_new(&i->layer_job, url, i->glue, i);
606 if (r < 0)
607 return log_error_errno(r, "Failed to allocate layer job: %m");
608
609 r = dkr_pull_add_bearer_token(i, i->layer_job);
610 if (r < 0)
611 return log_oom();
612
613 i->layer_job->on_finished = dkr_pull_job_on_finished_v2;
614 i->layer_job->on_open_disk = dkr_pull_job_on_open_disk;
615 i->layer_job->on_progress = dkr_pull_job_on_progress;
616 i->layer_job->grow_machine_directory = i->grow_machine_directory;
617
618 r = pull_job_begin(i->layer_job);
619 if (r < 0)
620 return log_error_errno(r, "Failed to start layer job: %m");
621
622 return 0;
623 }
624
625 static int dkr_pull_pull_layer(DkrPull *i) {
626 _cleanup_free_ char *path = NULL;
627 const char *url, *layer = NULL;
628 int r;
629
630 assert(i);
631 assert(!i->layer_job);
632 assert(!i->temp_path);
633 assert(!i->final_path);
634
635 for (;;) {
636 layer = dkr_pull_current_layer(i);
637 if (!layer)
638 return 0; /* no more layers */
639
640 path = strjoin(i->image_root, "/.dkr-", layer, NULL);
641 if (!path)
642 return log_oom();
643
644 if (laccess(path, F_OK) < 0) {
645 if (errno == ENOENT)
646 break;
647
648 return log_error_errno(errno, "Failed to check for container: %m");
649 }
650
651 log_info("Layer %s already exists, skipping.", layer);
652
653 i->current_ancestry++;
654
655 free(path);
656 path = NULL;
657 }
658
659 log_info("Pulling layer %s...", layer);
660
661 i->final_path = path;
662 path = NULL;
663
664 url = strjoina(PROTOCOL_PREFIX, i->response_registries[0], "/v1/images/", layer, "/layer");
665 r = pull_job_new(&i->layer_job, url, i->glue, i);
666 if (r < 0)
667 return log_error_errno(r, "Failed to allocate layer job: %m");
668
669 r = dkr_pull_add_token(i, i->layer_job);
670 if (r < 0)
671 return log_oom();
672
673 i->layer_job->on_finished = dkr_pull_job_on_finished;
674 i->layer_job->on_open_disk = dkr_pull_job_on_open_disk;
675 i->layer_job->on_progress = dkr_pull_job_on_progress;
676 i->layer_job->grow_machine_directory = i->grow_machine_directory;
677
678 r = pull_job_begin(i->layer_job);
679 if (r < 0)
680 return log_error_errno(r, "Failed to start layer job: %m");
681
682 return 0;
683 }
684
685 static int dkr_pull_job_on_header(PullJob *j, const char *header, size_t sz) {
686 _cleanup_free_ char *registry = NULL;
687 char *token, *digest;
688 DkrPull *i;
689 int r;
690
691 assert(j);
692 assert(j->userdata);
693
694 i = j->userdata;
695 r = curl_header_strdup(header, sz, HEADER_TOKEN, &token);
696 if (r < 0)
697 return log_oom();
698 if (r > 0) {
699 free(i->response_token);
700 i->response_token = token;
701 return 0;
702 }
703
704 r = curl_header_strdup(header, sz, HEADER_DIGEST, &digest);
705 if (r < 0)
706 return log_oom();
707 if (r > 0) {
708 free(i->response_digest);
709 i->response_digest = digest;
710 return 0;
711 }
712
713 r = curl_header_strdup(header, sz, HEADER_REGISTRY, &registry);
714 if (r < 0)
715 return log_oom();
716 if (r > 0) {
717 char **l, **k;
718
719 l = strv_split(registry, ",");
720 if (!l)
721 return log_oom();
722
723 STRV_FOREACH(k, l) {
724 if (!hostname_is_valid(*k)) {
725 log_error("Registry hostname is not valid.");
726 strv_free(l);
727 return -EBADMSG;
728 }
729 }
730
731 strv_free(i->response_registries);
732 i->response_registries = l;
733 }
734
735 return 0;
736 }
737
738 static void dkr_pull_job_on_finished_v2(PullJob *j) {
739 DkrPull *i;
740 int r;
741
742 assert(j);
743 assert(j->userdata);
744
745 i = j->userdata;
746 if (j->error != 0) {
747 if (j == i->images_job)
748 log_error_errno(j->error, "Failed to retrieve images list. (Wrong index URL?)");
749 else if (j == i->ancestry_job)
750 log_error_errno(j->error, "Failed to retrieve manifest.");
751 else if (j == i->json_job)
752 log_error_errno(j->error, "Failed to retrieve json data.");
753 else
754 log_error_errno(j->error, "Failed to retrieve layer data.");
755
756 r = j->error;
757 goto finish;
758 }
759
760 if (i->images_job == j) {
761 const char *url;
762
763 assert(!i->tags_job);
764 assert(!i->ancestry_job);
765 assert(!i->json_job);
766 assert(!i->layer_job);
767
768 if (strv_isempty(i->response_registries)) {
769 r = -EBADMSG;
770 log_error("Didn't get registry information.");
771 goto finish;
772 }
773
774 log_info("Index lookup succeeded, directed to registry %s.", i->response_registries[0]);
775 dkr_pull_report_progress(i, DKR_RESOLVING);
776
777 url = strjoina(i->index_protocol, "auth.", i->index_address, "/v2/token/?scope=repository:",
778 i->name, ":pull&service=registry.", i->index_address);
779 r = pull_job_new(&i->tags_job, url, i->glue, i);
780 if (r < 0) {
781 log_error_errno(r, "Failed to allocate tags job: %m");
782 goto finish;
783 }
784
785 i->tags_job->on_finished = dkr_pull_job_on_finished_v2;
786 i->tags_job->on_progress = dkr_pull_job_on_progress;
787
788 r = pull_job_begin(i->tags_job);
789 if (r < 0) {
790 log_error_errno(r, "Failed to start tags job: %m");
791 goto finish;
792 }
793
794 } else if (i->tags_job == j) {
795 const char *url;
796 _cleanup_free_ const char *buf;
797 _cleanup_json_variant_unref_ JsonVariant *doc = NULL;
798 JsonVariant *e = NULL;
799
800 assert(!i->ancestry_job);
801 assert(!i->json_job);
802 assert(!i->layer_job);
803
804 buf = strndup((const char *)j->payload, j->payload_size);
805 if (!buf) {
806 r = -ENOMEM;
807 log_oom();
808 goto finish;
809 }
810
811 r = json_parse(buf, &doc);
812 if (r < 0) {
813 log_error("Unable to parse bearer token\n%s", j->payload);
814 goto finish;
815 }
816
817 e = json_variant_value(doc, "token");
818 if (!e || e->type != JSON_VARIANT_STRING) {
819 r = -EBADMSG;
820 log_error("Invalid JSON format for Bearer token");
821 goto finish;
822 }
823
824 r = free_and_strdup(&i->response_token, json_variant_string(e));
825 if (r < 0) {
826 log_oom();
827 goto finish;
828 }
829
830 url = strjoina(PROTOCOL_PREFIX, i->response_registries[0], "/v2/", i->name, "/manifests/", i->reference);
831 r = pull_job_new(&i->ancestry_job, url, i->glue, i);
832 if (r < 0) {
833 log_error_errno(r, "Failed to allocate ancestry job: %m");
834 goto finish;
835 }
836
837 r = dkr_pull_add_bearer_token(i, i->ancestry_job);
838 if (r < 0)
839 goto finish;
840
841 i->ancestry_job->on_finished = dkr_pull_job_on_finished_v2;
842 i->ancestry_job->on_progress = dkr_pull_job_on_progress;
843 i->ancestry_job->on_header = dkr_pull_job_on_header;
844
845
846 r = pull_job_begin(i->ancestry_job);
847 if (r < 0) {
848 log_error_errno(r, "Failed to start ancestry job: %m");
849 goto finish;
850 }
851
852 } else if (i->ancestry_job == j) {
853
854 _cleanup_json_variant_unref_ JsonVariant *doc = NULL, *compat = NULL;
855 JsonVariant *e = NULL;
856 _cleanup_strv_free_ char **ancestry = NULL;
857 size_t allocated = 0, size = 0;
858 char *path = NULL, **k = NULL;
859
860 r = json_parse((const char *)j->payload, &doc);
861 if (r < 0) {
862 log_error("Invalid JSON Manifest");
863 goto finish;
864 }
865
866 e = json_variant_value(doc, "fsLayers");
867 if (!e || e->type != JSON_VARIANT_ARRAY || e->size == 0) {
868 r = -EBADMSG;
869 goto finish;
870 }
871
872 log_info("JSON manifest with schema v%"PRIi64" for %s parsed!",
873 json_variant_integer(json_variant_value(doc, "schemaVersion")),
874 json_variant_string(json_variant_value(doc, "name")));
875
876 for (unsigned z = 0; z < e->size; z++) {
877 JsonVariant *f = json_variant_element(e, z), *g = NULL;
878 const char *layer;
879 if (f->type != JSON_VARIANT_OBJECT) {
880 r = -EBADMSG;
881 goto finish;
882 }
883
884 g = json_variant_value(f, "blobSum");
885
886 layer = json_variant_string(g);
887 if (!dkr_digest_is_valid(layer)) {
888 r = -EBADMSG;
889 goto finish;
890 }
891
892 if (!GREEDY_REALLOC(ancestry, allocated, size + 2)) {
893 r = -ENOMEM;
894 log_oom();
895 goto finish;
896 }
897
898 ancestry[size] = strdup(layer);
899 if (!ancestry[size]) {
900 r = -ENOMEM;
901 log_oom();
902 goto finish;
903 }
904
905 ancestry[size+1] = NULL;
906 size += 1;
907 }
908
909 e = json_variant_value(doc, "history");
910 if (!e || e->type != JSON_VARIANT_ARRAY) {
911 r = -EBADMSG;
912 goto finish;
913 }
914
915 e = json_variant_element(e, 0);
916 e = json_variant_value(e, "v1Compatibility");
917 r = json_parse(json_variant_string(e), &compat);
918 if (r < 0) {
919 log_error("Invalid v1Compatibility JSON");
920 goto finish;
921 }
922
923 e = json_variant_value(compat, "id");
924
925 strv_free(i->ancestry);
926 i->ancestry = strv_reverse(strv_uniq(ancestry));
927 i->n_ancestry = strv_length(i->ancestry);
928 i->current_ancestry = 0;
929 i->id = strdup(i->ancestry[i->n_ancestry - 1]);
930 if (!i->id) {
931 r = -ENOMEM;
932 log_oom();
933 goto finish;
934 }
935 path = strjoin(i->image_root, "/.dkr-", json_variant_string(e), NULL);
936 if (!path) {
937 r = -ENOMEM;
938 log_oom();
939 goto finish;
940 }
941 free(i->image_root);
942 i->image_root = path;
943 ancestry = NULL;
944
945 log_info("Required layers:\n");
946 STRV_FOREACH(k, i->ancestry)
947 log_info("\t%s", *k);
948 log_info("\nProvenance:\n\tImageID: %s\n\tDigest: %s", json_variant_string(e), i->response_digest);
949
950 dkr_pull_report_progress(i, DKR_DOWNLOADING);
951
952 r = dkr_pull_pull_layer_v2(i);
953 if (r < 0)
954 goto finish;
955
956 } else if (i->layer_job == j) {
957 assert(i->temp_path);
958 assert(i->final_path);
959
960 j->disk_fd = safe_close(j->disk_fd);
961
962 if (i->tar_pid > 0) {
963 r = wait_for_terminate_and_warn("tar", i->tar_pid, true);
964 i->tar_pid = 0;
965 if (r < 0)
966 goto finish;
967 }
968
969 r = aufs_resolve(i->temp_path);
970 if (r < 0) {
971 log_error_errno(r, "Failed to resolve aufs whiteouts: %m");
972 goto finish;
973 }
974
975 r = btrfs_subvol_set_read_only(i->temp_path, true);
976 if (r < 0) {
977 log_error_errno(r, "Failed to mark snapshot read-only: %m");
978 goto finish;
979 }
980
981 if (rename(i->temp_path, i->final_path) < 0) {
982 log_error_errno(errno, "Failed to rename snaphsot: %m");
983 goto finish;
984 }
985
986 log_info("Completed writing to layer %s.", i->final_path);
987
988 i->layer_job = pull_job_unref(i->layer_job);
989 free(i->temp_path);
990 i->temp_path = NULL;
991 free(i->final_path);
992 i->final_path = NULL;
993
994 i->current_ancestry ++;
995 r = dkr_pull_pull_layer_v2(i);
996 if (r < 0)
997 goto finish;
998
999 } else if (i->json_job != j)
1000 assert_not_reached("Got finished event for unknown curl object");
1001
1002 if (!dkr_pull_is_done(i))
1003 return;
1004
1005 dkr_pull_report_progress(i, DKR_COPYING);
1006
1007 r = dkr_pull_make_local_copy(i, DKR_PULL_V2);
1008 if (r < 0)
1009 goto finish;
1010
1011 r = 0;
1012
1013 finish:
1014 if (i->on_finished)
1015 i->on_finished(i, r, i->userdata);
1016 else
1017 sd_event_exit(i->event, r);
1018
1019 }
1020
1021 static void dkr_pull_job_on_finished(PullJob *j) {
1022 DkrPull *i;
1023 int r;
1024
1025 assert(j);
1026 assert(j->userdata);
1027
1028 i = j->userdata;
1029 if (j->error != 0) {
1030 if (j == i->images_job)
1031 log_error_errno(j->error, "Failed to retrieve images list. (Wrong index URL?)");
1032 else if (j == i->tags_job)
1033 log_error_errno(j->error, "Failed to retrieve tags list.");
1034 else if (j == i->ancestry_job)
1035 log_error_errno(j->error, "Failed to retrieve ancestry list.");
1036 else if (j == i->json_job)
1037 log_error_errno(j->error, "Failed to retrieve json data.");
1038 else
1039 log_error_errno(j->error, "Failed to retrieve layer data.");
1040
1041 r = j->error;
1042 goto finish;
1043 }
1044
1045 if (i->images_job == j) {
1046 const char *url;
1047
1048 assert(!i->tags_job);
1049 assert(!i->ancestry_job);
1050 assert(!i->json_job);
1051 assert(!i->layer_job);
1052
1053 if (strv_isempty(i->response_registries)) {
1054 r = -EBADMSG;
1055 log_error("Didn't get registry information.");
1056 goto finish;
1057 }
1058
1059 log_info("Index lookup succeeded, directed to registry %s.", i->response_registries[0]);
1060 dkr_pull_report_progress(i, DKR_RESOLVING);
1061
1062 url = strjoina(PROTOCOL_PREFIX, i->response_registries[0], "/v1/repositories/", i->name, "/tags/", i->reference);
1063 r = pull_job_new(&i->tags_job, url, i->glue, i);
1064 if (r < 0) {
1065 log_error_errno(r, "Failed to allocate tags job: %m");
1066 goto finish;
1067 }
1068
1069 r = dkr_pull_add_token(i, i->tags_job);
1070 if (r < 0) {
1071 log_oom();
1072 goto finish;
1073 }
1074
1075 i->tags_job->on_finished = dkr_pull_job_on_finished;
1076 i->tags_job->on_progress = dkr_pull_job_on_progress;
1077
1078 r = pull_job_begin(i->tags_job);
1079 if (r < 0) {
1080 log_error_errno(r, "Failed to start tags job: %m");
1081 goto finish;
1082 }
1083
1084 } else if (i->tags_job == j) {
1085 const char *url;
1086 char *id = NULL;
1087
1088 assert(!i->ancestry_job);
1089 assert(!i->json_job);
1090 assert(!i->layer_job);
1091
1092 r = parse_id(j->payload, j->payload_size, &id);
1093 if (r < 0) {
1094 log_error_errno(r, "Failed to parse JSON id.");
1095 goto finish;
1096 }
1097
1098 free(i->id);
1099 i->id = id;
1100
1101 log_info("Tag lookup succeeded, resolved to layer %s.", i->id);
1102 dkr_pull_report_progress(i, DKR_METADATA);
1103
1104 url = strjoina(PROTOCOL_PREFIX, i->response_registries[0], "/v1/images/", i->id, "/ancestry");
1105 r = pull_job_new(&i->ancestry_job, url, i->glue, i);
1106 if (r < 0) {
1107 log_error_errno(r, "Failed to allocate ancestry job: %m");
1108 goto finish;
1109 }
1110
1111 r = dkr_pull_add_token(i, i->ancestry_job);
1112 if (r < 0) {
1113 log_oom();
1114 goto finish;
1115 }
1116
1117 i->ancestry_job->on_finished = dkr_pull_job_on_finished;
1118 i->ancestry_job->on_progress = dkr_pull_job_on_progress;
1119
1120 url = strjoina(PROTOCOL_PREFIX, i->response_registries[0], "/v1/images/", i->id, "/json");
1121 r = pull_job_new(&i->json_job, url, i->glue, i);
1122 if (r < 0) {
1123 log_error_errno(r, "Failed to allocate json job: %m");
1124 goto finish;
1125 }
1126
1127 r = dkr_pull_add_token(i, i->json_job);
1128 if (r < 0) {
1129 log_oom();
1130 goto finish;
1131 }
1132
1133 i->json_job->on_finished = dkr_pull_job_on_finished;
1134 i->json_job->on_progress = dkr_pull_job_on_progress;
1135
1136 r = pull_job_begin(i->ancestry_job);
1137 if (r < 0) {
1138 log_error_errno(r, "Failed to start ancestry job: %m");
1139 goto finish;
1140 }
1141
1142 r = pull_job_begin(i->json_job);
1143 if (r < 0) {
1144 log_error_errno(r, "Failed to start json job: %m");
1145 goto finish;
1146 }
1147
1148 } else if (i->ancestry_job == j) {
1149 char **ancestry = NULL, **k;
1150 unsigned n;
1151
1152 assert(!i->layer_job);
1153
1154 r = parse_ancestry(j->payload, j->payload_size, &ancestry);
1155 if (r < 0) {
1156 log_error_errno(r, "Failed to parse JSON id.");
1157 goto finish;
1158 }
1159
1160 n = strv_length(ancestry);
1161 if (n <= 0 || !streq(ancestry[n-1], i->id)) {
1162 log_error("Ancestry doesn't end in main layer.");
1163 strv_free(ancestry);
1164 r = -EBADMSG;
1165 goto finish;
1166 }
1167
1168 log_info("Ancestor lookup succeeded, requires layers:\n");
1169 STRV_FOREACH(k, ancestry)
1170 log_info("\t%s", *k);
1171
1172 strv_free(i->ancestry);
1173 i->ancestry = ancestry;
1174 i->n_ancestry = n;
1175 i->current_ancestry = 0;
1176
1177 dkr_pull_report_progress(i, DKR_DOWNLOADING);
1178
1179 r = dkr_pull_pull_layer(i);
1180 if (r < 0)
1181 goto finish;
1182
1183 } else if (i->layer_job == j) {
1184 assert(i->temp_path);
1185 assert(i->final_path);
1186
1187 j->disk_fd = safe_close(j->disk_fd);
1188
1189 if (i->tar_pid > 0) {
1190 r = wait_for_terminate_and_warn("tar", i->tar_pid, true);
1191 i->tar_pid = 0;
1192 if (r < 0)
1193 goto finish;
1194 }
1195
1196 r = aufs_resolve(i->temp_path);
1197 if (r < 0) {
1198 log_error_errno(r, "Failed to resolve aufs whiteouts: %m");
1199 goto finish;
1200 }
1201
1202 r = btrfs_subvol_set_read_only(i->temp_path, true);
1203 if (r < 0) {
1204 log_error_errno(r, "Failed to mark snapshot read-only: %m");
1205 goto finish;
1206 }
1207
1208 if (rename(i->temp_path, i->final_path) < 0) {
1209 log_error_errno(errno, "Failed to rename snaphsot: %m");
1210 goto finish;
1211 }
1212
1213 log_info("Completed writing to layer %s.", i->final_path);
1214
1215 i->layer_job = pull_job_unref(i->layer_job);
1216 free(i->temp_path);
1217 i->temp_path = NULL;
1218 free(i->final_path);
1219 i->final_path = NULL;
1220
1221 i->current_ancestry ++;
1222 r = dkr_pull_pull_layer(i);
1223 if (r < 0)
1224 goto finish;
1225
1226 } else if (i->json_job != j)
1227 assert_not_reached("Got finished event for unknown curl object");
1228
1229 if (!dkr_pull_is_done(i))
1230 return;
1231
1232 dkr_pull_report_progress(i, DKR_COPYING);
1233
1234 r = dkr_pull_make_local_copy(i, DKR_PULL_V1);
1235 if (r < 0)
1236 goto finish;
1237
1238 r = 0;
1239 finish:
1240 if (i->on_finished)
1241 i->on_finished(i, r, i->userdata);
1242 else
1243 sd_event_exit(i->event, r);
1244 }
1245
1246 static int get_protocol_address(char **protocol, char **address, const char *url) {
1247 const char *sep, *dot;
1248 _cleanup_free_ char *a = NULL, *p = NULL;
1249
1250 sep = strstr(url, "://");
1251 if (!sep)
1252 return -EINVAL;
1253
1254 dot = strrchr(url, '.');
1255 if (!dot)
1256 return -EINVAL;
1257 dot--;
1258
1259 p = strndup(url, (sep - url) + 3);
1260 if (!p)
1261 return log_oom();
1262
1263 while (dot > (sep + 3) && *dot != '.')
1264 dot--;
1265
1266 a = strdup(dot + 1);
1267 if (!a)
1268 return log_oom();
1269
1270 *address = a;
1271 *protocol = p;
1272 a = p = NULL;
1273
1274 return 0;
1275 }
1276
1277 int dkr_pull_start(DkrPull *i, const char *name, const char *reference, const char *local, bool force_local, DkrPullVersion version) {
1278 const char *url;
1279 int r;
1280
1281 assert(i);
1282
1283 if (!dkr_name_is_valid(name))
1284 return -EINVAL;
1285
1286 if (reference && !dkr_ref_is_valid(reference))
1287 return -EINVAL;
1288
1289 if (local && !machine_name_is_valid(local))
1290 return -EINVAL;
1291
1292 if (i->images_job)
1293 return -EBUSY;
1294
1295 if (!reference)
1296 reference = "latest";
1297
1298 free(i->index_protocol);
1299 free(i->index_address);
1300 r = get_protocol_address(&i->index_protocol, &i->index_address, i->index_url);
1301 if (r < 0)
1302 return r;
1303
1304 r = free_and_strdup(&i->local, local);
1305 if (r < 0)
1306 return r;
1307 i->force_local = force_local;
1308
1309 r = free_and_strdup(&i->name, name);
1310 if (r < 0)
1311 return r;
1312 r = free_and_strdup(&i->reference, reference);
1313 if (r < 0)
1314 return r;
1315
1316 url = strjoina(i->index_url, "/v1/repositories/", name, "/images");
1317
1318 r = pull_job_new(&i->images_job, url, i->glue, i);
1319 if (r < 0)
1320 return r;
1321
1322 r = dkr_pull_add_token(i, i->images_job);
1323 if (r < 0)
1324 return r;
1325
1326 if (version == DKR_PULL_V1)
1327 i->images_job->on_finished = dkr_pull_job_on_finished;
1328 else
1329 i->images_job->on_finished = dkr_pull_job_on_finished_v2;
1330
1331 i->images_job->on_header = dkr_pull_job_on_header;
1332 i->images_job->on_progress = dkr_pull_job_on_progress;
1333
1334 return pull_job_begin(i->images_job);
1335 }