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