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