]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/import/pull-dkr.c
tree-wide: introduce mfree()
[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"
72648326
LP
26#include "json.h"
27#include "strv.h"
72648326 28#include "btrfs-util.h"
91f4347e 29#include "utf8.h"
ff2670ad 30#include "mkdir.h"
c6878637 31#include "rm-rf.h"
26166c88 32#include "path-util.h"
3d7415f4 33#include "import-util.h"
ff2670ad
LP
34#include "curl-util.h"
35#include "aufs-util.h"
dc2c282b
LP
36#include "pull-job.h"
37#include "pull-common.h"
b6e676ce 38#include "import-common.h"
dc2c282b 39#include "pull-dkr.h"
0b452006 40#include "process-util.h"
958b66ea 41#include "hostname-util.h"
72648326 42
7079cfef
LP
43typedef enum DkrProgress {
44 DKR_SEARCHING,
45 DKR_RESOLVING,
46 DKR_METADATA,
47 DKR_DOWNLOADING,
48 DKR_COPYING,
49} DkrProgress;
50
dc2c282b 51struct DkrPull {
ff2670ad
LP
52 sd_event *event;
53 CurlGlue *glue;
72648326 54
2c4fb0ea
PO
55 char *index_protocol;
56 char *index_address;
57
ff2670ad
LP
58 char *index_url;
59 char *image_root;
72648326 60
dc2c282b
LP
61 PullJob *images_job;
62 PullJob *tags_job;
63 PullJob *ancestry_job;
64 PullJob *json_job;
65 PullJob *layer_job;
72648326
LP
66
67 char *name;
2c4fb0ea 68 char *reference;
72648326 69 char *id;
72648326 70
2c4fb0ea 71 char *response_digest;
ff2670ad
LP
72 char *response_token;
73 char **response_registries;
72648326
LP
74
75 char **ancestry;
7079cfef 76 unsigned n_ancestry;
72648326
LP
77 unsigned current_ancestry;
78
dc2c282b 79 DkrPullFinished on_finished;
ff2670ad 80 void *userdata;
ea1ae8c3 81
ff2670ad
LP
82 char *local;
83 bool force_local;
26166c88 84 bool grow_machine_directory;
72648326 85
ff2670ad
LP
86 char *temp_path;
87 char *final_path;
14ed8b92 88
ff2670ad 89 pid_t tar_pid;
72648326
LP
90};
91
92#define PROTOCOL_PREFIX "https://"
72648326
LP
93
94#define HEADER_TOKEN "X-Do" /* the HTTP header for the auth token */ "cker-Token:"
2c4fb0ea
PO
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
72648326 98
dc2c282b 99static void dkr_pull_job_on_finished(PullJob *j);
72648326 100
dc2c282b 101DkrPull* dkr_pull_unref(DkrPull *i) {
ff2670ad 102 if (!i)
72648326
LP
103 return NULL;
104
ff2670ad
LP
105 if (i->tar_pid > 1) {
106 (void) kill_and_sigcont(i->tar_pid, SIGKILL);
107 (void) wait_for_terminate(i->tar_pid, NULL);
72648326
LP
108 }
109
dc2c282b
LP
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);
72648326 115
ff2670ad
LP
116 curl_glue_unref(i->glue);
117 sd_event_unref(i->event);
72648326 118
ff2670ad 119 if (i->temp_path) {
d9e2daaf 120 (void) rm_rf(i->temp_path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME);
ff2670ad
LP
121 free(i->temp_path);
122 }
72648326 123
ff2670ad 124 free(i->name);
2c4fb0ea 125 free(i->reference);
ff2670ad
LP
126 free(i->id);
127 free(i->response_token);
ff2670ad
LP
128 strv_free(i->ancestry);
129 free(i->final_path);
2c4fb0ea
PO
130 free(i->index_address);
131 free(i->index_protocol);
ff2670ad
LP
132 free(i->index_url);
133 free(i->image_root);
134 free(i->local);
135 free(i);
72648326
LP
136
137 return NULL;
138}
139
dc2c282b
LP
140int dkr_pull_new(
141 DkrPull **ret,
ff2670ad
LP
142 sd_event *event,
143 const char *index_url,
144 const char *image_root,
dc2c282b 145 DkrPullFinished on_finished,
ff2670ad 146 void *userdata) {
72648326 147
dc2c282b 148 _cleanup_(dkr_pull_unrefp) DkrPull *i = NULL;
ff2670ad
LP
149 char *e;
150 int r;
72648326 151
ff2670ad
LP
152 assert(ret);
153 assert(index_url);
72648326 154
ff2670ad
LP
155 if (!http_url_is_valid(index_url))
156 return -EINVAL;
72648326 157
dc2c282b 158 i = new0(DkrPull, 1);
ff2670ad
LP
159 if (!i)
160 return -ENOMEM;
72648326 161
ff2670ad
LP
162 i->on_finished = on_finished;
163 i->userdata = userdata;
72648326 164
ff2670ad
LP
165 i->image_root = strdup(image_root ?: "/var/lib/machines");
166 if (!i->image_root)
167 return -ENOMEM;
72648326 168
26166c88
LP
169 i->grow_machine_directory = path_startswith(i->image_root, "/var/lib/machines");
170
ff2670ad
LP
171 i->index_url = strdup(index_url);
172 if (!i->index_url)
173 return -ENOMEM;
72648326 174
ff2670ad
LP
175 e = endswith(i->index_url, "/");
176 if (e)
177 *e = 0;
72648326 178
ff2670ad
LP
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 }
72648326 186
ff2670ad
LP
187 r = curl_glue_new(&i->glue, i->event);
188 if (r < 0)
189 return r;
72648326 190
dc2c282b 191 i->glue->on_finished = pull_job_curl_on_finished;
ff2670ad 192 i->glue->userdata = i;
14ed8b92 193
ff2670ad
LP
194 *ret = i;
195 i = NULL;
14ed8b92 196
ff2670ad 197 return 0;
72648326
LP
198}
199
dc2c282b 200static void dkr_pull_report_progress(DkrPull *i, DkrProgress p) {
7079cfef
LP
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
72648326
LP
247static 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
91f4347e 280 if (!dkr_id_is_valid(id))
72648326
LP
281 return -EBADMSG;
282
283 *ret = id;
284 id = NULL;
285
286 return 0;
287}
288
289static 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) {
91f4347e 334 if (!dkr_id_is_valid(str))
72648326
LP
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
dc2c282b 386static const char *dkr_pull_current_layer(DkrPull *i) {
ff2670ad 387 assert(i);
72648326 388
ff2670ad 389 if (strv_isempty(i->ancestry))
72648326
LP
390 return NULL;
391
ff2670ad 392 return i->ancestry[i->current_ancestry];
72648326
LP
393}
394
dc2c282b 395static const char *dkr_pull_current_base_layer(DkrPull *i) {
ff2670ad 396 assert(i);
72648326 397
ff2670ad 398 if (strv_isempty(i->ancestry))
72648326
LP
399 return NULL;
400
ff2670ad 401 if (i->current_ancestry <= 0)
72648326
LP
402 return NULL;
403
ff2670ad 404 return i->ancestry[i->current_ancestry-1];
72648326
LP
405}
406
dc2c282b 407static int dkr_pull_add_token(DkrPull *i, PullJob *j) {
ff2670ad 408 const char *t;
72648326 409
ff2670ad
LP
410 assert(i);
411 assert(j);
72648326 412
ff2670ad 413 if (i->response_token)
63c372cb 414 t = strjoina("Authorization: Token ", i->response_token);
ff2670ad
LP
415 else
416 t = HEADER_TOKEN " true";
72648326 417
ff2670ad
LP
418 j->request_header = curl_slist_new("Accept: application/json", t, NULL);
419 if (!j->request_header)
420 return -ENOMEM;
72648326 421
ff2670ad 422 return 0;
72648326
LP
423}
424
2c4fb0ea
PO
425static 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
dc2c282b 443static bool dkr_pull_is_done(DkrPull *i) {
ff2670ad
LP
444 assert(i);
445 assert(i->images_job);
dc2c282b 446 if (i->images_job->state != PULL_JOB_DONE)
ff2670ad 447 return false;
72648326 448
dc2c282b 449 if (!i->tags_job || i->tags_job->state != PULL_JOB_DONE)
ff2670ad 450 return false;
72648326 451
dc2c282b 452 if (!i->ancestry_job || i->ancestry_job->state != PULL_JOB_DONE)
ff2670ad 453 return false;
72648326 454
2c4fb0ea 455 if (i->json_job && i->json_job->state != PULL_JOB_DONE)
ff2670ad 456 return false;
72648326 457
dc2c282b 458 if (i->layer_job && i->layer_job->state != PULL_JOB_DONE)
ff2670ad 459 return false;
72648326 460
dc2c282b 461 if (dkr_pull_current_layer(i))
ff2670ad 462 return false;
72648326 463
ff2670ad
LP
464 return true;
465}
72648326 466
2c4fb0ea 467static int dkr_pull_make_local_copy(DkrPull *i, DkrPullVersion version) {
ff2670ad 468 int r;
2c4fb0ea 469 _cleanup_free_ char *p = NULL;
72648326 470
ff2670ad 471 assert(i);
72648326 472
ff2670ad
LP
473 if (!i->local)
474 return 0;
72648326 475
ff2670ad
LP
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();
72648326
LP
480 }
481
2c4fb0ea
PO
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);
ff2670ad
LP
489 if (r < 0)
490 return r;
491
2c4fb0ea
PO
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
ff2670ad 506 return 0;
72648326
LP
507}
508
dc2c282b 509static int dkr_pull_job_on_open_disk(PullJob *j) {
ff2670ad 510 const char *base;
dc2c282b 511 DkrPull *i;
ff2670ad 512 int r;
72648326 513
ff2670ad
LP
514 assert(j);
515 assert(j->userdata);
72648326 516
ff2670ad
LP
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);
72648326 522
14bcf25c 523 r = tempfn_random(i->final_path, NULL, &i->temp_path);
ff2670ad
LP
524 if (r < 0)
525 return log_oom();
72648326 526
ff2670ad 527 mkdir_parents_label(i->temp_path, 0700);
72648326 528
dc2c282b 529 base = dkr_pull_current_base_layer(i);
ff2670ad
LP
530 if (base) {
531 const char *base_path;
72648326 532
63c372cb 533 base_path = strjoina(i->image_root, "/.dkr-", base);
e9bc1871 534 r = btrfs_subvol_snapshot(base_path, i->temp_path, BTRFS_SNAPSHOT_FALLBACK_COPY);
ff2670ad
LP
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);
72648326 539
587fec42 540 j->disk_fd = import_fork_tar_x(i->temp_path, &i->tar_pid);
2c140ded
LP
541 if (j->disk_fd < 0)
542 return j->disk_fd;
72648326 543
72648326
LP
544 return 0;
545}
546
dc2c282b
LP
547static void dkr_pull_job_on_progress(PullJob *j) {
548 DkrPull *i;
7079cfef
LP
549
550 assert(j);
551 assert(j->userdata);
552
553 i = j->userdata;
554
dc2c282b 555 dkr_pull_report_progress(
7079cfef
LP
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
2c4fb0ea
PO
563static void dkr_pull_job_on_finished_v2(PullJob *j);
564
565static 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
97b11eed 595 path = mfree(path);
2c4fb0ea
PO
596 }
597
598 log_info("Pulling layer %s...", layer);
599
600 i->final_path = path;
601 path = NULL;
602
603 url = strjoina(PROTOCOL_PREFIX, i->response_registries[0], "/v2/", i->name, "/blobs/", layer);
604 r = pull_job_new(&i->layer_job, url, i->glue, i);
605 if (r < 0)
606 return log_error_errno(r, "Failed to allocate layer job: %m");
607
608 r = dkr_pull_add_bearer_token(i, i->layer_job);
609 if (r < 0)
610 return log_oom();
611
612 i->layer_job->on_finished = dkr_pull_job_on_finished_v2;
613 i->layer_job->on_open_disk = dkr_pull_job_on_open_disk;
614 i->layer_job->on_progress = dkr_pull_job_on_progress;
615 i->layer_job->grow_machine_directory = i->grow_machine_directory;
616
617 r = pull_job_begin(i->layer_job);
618 if (r < 0)
619 return log_error_errno(r, "Failed to start layer job: %m");
620
621 return 0;
622}
623
dc2c282b 624static int dkr_pull_pull_layer(DkrPull *i) {
ff2670ad
LP
625 _cleanup_free_ char *path = NULL;
626 const char *url, *layer = NULL;
72648326
LP
627 int r;
628
ff2670ad
LP
629 assert(i);
630 assert(!i->layer_job);
631 assert(!i->temp_path);
632 assert(!i->final_path);
72648326
LP
633
634 for (;;) {
dc2c282b 635 layer = dkr_pull_current_layer(i);
ff2670ad
LP
636 if (!layer)
637 return 0; /* no more layers */
72648326 638
ff2670ad 639 path = strjoin(i->image_root, "/.dkr-", layer, NULL);
72648326
LP
640 if (!path)
641 return log_oom();
642
643 if (laccess(path, F_OK) < 0) {
644 if (errno == ENOENT)
645 break;
646
647 return log_error_errno(errno, "Failed to check for container: %m");
648 }
649
650 log_info("Layer %s already exists, skipping.", layer);
651
ff2670ad 652 i->current_ancestry++;
72648326 653
97b11eed 654 path = mfree(path);
72648326
LP
655 }
656
ff2670ad 657 log_info("Pulling layer %s...", layer);
72648326 658
ff2670ad
LP
659 i->final_path = path;
660 path = NULL;
72648326 661
63c372cb 662 url = strjoina(PROTOCOL_PREFIX, i->response_registries[0], "/v1/images/", layer, "/layer");
dc2c282b 663 r = pull_job_new(&i->layer_job, url, i->glue, i);
ff2670ad
LP
664 if (r < 0)
665 return log_error_errno(r, "Failed to allocate layer job: %m");
72648326 666
dc2c282b 667 r = dkr_pull_add_token(i, i->layer_job);
72648326
LP
668 if (r < 0)
669 return log_oom();
670
dc2c282b
LP
671 i->layer_job->on_finished = dkr_pull_job_on_finished;
672 i->layer_job->on_open_disk = dkr_pull_job_on_open_disk;
673 i->layer_job->on_progress = dkr_pull_job_on_progress;
26166c88 674 i->layer_job->grow_machine_directory = i->grow_machine_directory;
72648326 675
dc2c282b 676 r = pull_job_begin(i->layer_job);
72648326 677 if (r < 0)
ff2670ad 678 return log_error_errno(r, "Failed to start layer job: %m");
72648326
LP
679
680 return 0;
681}
682
2c4fb0ea
PO
683static int dkr_pull_job_on_header(PullJob *j, const char *header, size_t sz) {
684 _cleanup_free_ char *registry = NULL;
685 char *token, *digest;
686 DkrPull *i;
687 int r;
688
689 assert(j);
690 assert(j->userdata);
691
692 i = j->userdata;
693 r = curl_header_strdup(header, sz, HEADER_TOKEN, &token);
694 if (r < 0)
695 return log_oom();
696 if (r > 0) {
697 free(i->response_token);
698 i->response_token = token;
699 return 0;
700 }
701
702 r = curl_header_strdup(header, sz, HEADER_DIGEST, &digest);
703 if (r < 0)
704 return log_oom();
705 if (r > 0) {
706 free(i->response_digest);
707 i->response_digest = digest;
708 return 0;
709 }
710
711 r = curl_header_strdup(header, sz, HEADER_REGISTRY, &registry);
712 if (r < 0)
713 return log_oom();
714 if (r > 0) {
715 char **l, **k;
716
717 l = strv_split(registry, ",");
718 if (!l)
719 return log_oom();
720
721 STRV_FOREACH(k, l) {
722 if (!hostname_is_valid(*k)) {
723 log_error("Registry hostname is not valid.");
724 strv_free(l);
725 return -EBADMSG;
726 }
727 }
728
729 strv_free(i->response_registries);
730 i->response_registries = l;
731 }
732
733 return 0;
734}
735
736static void dkr_pull_job_on_finished_v2(PullJob *j) {
737 DkrPull *i;
738 int r;
739
740 assert(j);
741 assert(j->userdata);
742
743 i = j->userdata;
744 if (j->error != 0) {
745 if (j == i->images_job)
746 log_error_errno(j->error, "Failed to retrieve images list. (Wrong index URL?)");
747 else if (j == i->ancestry_job)
748 log_error_errno(j->error, "Failed to retrieve manifest.");
749 else if (j == i->json_job)
750 log_error_errno(j->error, "Failed to retrieve json data.");
751 else
752 log_error_errno(j->error, "Failed to retrieve layer data.");
753
754 r = j->error;
755 goto finish;
756 }
757
758 if (i->images_job == j) {
759 const char *url;
760
761 assert(!i->tags_job);
762 assert(!i->ancestry_job);
763 assert(!i->json_job);
764 assert(!i->layer_job);
765
766 if (strv_isempty(i->response_registries)) {
767 r = -EBADMSG;
768 log_error("Didn't get registry information.");
769 goto finish;
770 }
771
772 log_info("Index lookup succeeded, directed to registry %s.", i->response_registries[0]);
773 dkr_pull_report_progress(i, DKR_RESOLVING);
774
775 url = strjoina(i->index_protocol, "auth.", i->index_address, "/v2/token/?scope=repository:",
776 i->name, ":pull&service=registry.", i->index_address);
777 r = pull_job_new(&i->tags_job, url, i->glue, i);
778 if (r < 0) {
779 log_error_errno(r, "Failed to allocate tags job: %m");
780 goto finish;
781 }
782
783 i->tags_job->on_finished = dkr_pull_job_on_finished_v2;
784 i->tags_job->on_progress = dkr_pull_job_on_progress;
785
786 r = pull_job_begin(i->tags_job);
787 if (r < 0) {
788 log_error_errno(r, "Failed to start tags job: %m");
789 goto finish;
790 }
791
792 } else if (i->tags_job == j) {
793 const char *url;
45d9a304 794 _cleanup_free_ char *buf;
dde8bb32 795 _cleanup_json_variant_unref_ JsonVariant *doc = NULL;
2c4fb0ea
PO
796 JsonVariant *e = NULL;
797
798 assert(!i->ancestry_job);
799 assert(!i->json_job);
800 assert(!i->layer_job);
801
802 buf = strndup((const char *)j->payload, j->payload_size);
803 if (!buf) {
804 r = -ENOMEM;
805 log_oom();
806 goto finish;
807 }
808
809 r = json_parse(buf, &doc);
810 if (r < 0) {
811 log_error("Unable to parse bearer token\n%s", j->payload);
812 goto finish;
813 }
814
815 e = json_variant_value(doc, "token");
816 if (!e || e->type != JSON_VARIANT_STRING) {
817 r = -EBADMSG;
818 log_error("Invalid JSON format for Bearer token");
819 goto finish;
820 }
821
822 r = free_and_strdup(&i->response_token, json_variant_string(e));
823 if (r < 0) {
824 log_oom();
825 goto finish;
826 }
827
828 url = strjoina(PROTOCOL_PREFIX, i->response_registries[0], "/v2/", i->name, "/manifests/", i->reference);
829 r = pull_job_new(&i->ancestry_job, url, i->glue, i);
830 if (r < 0) {
831 log_error_errno(r, "Failed to allocate ancestry job: %m");
832 goto finish;
833 }
834
835 r = dkr_pull_add_bearer_token(i, i->ancestry_job);
836 if (r < 0)
837 goto finish;
838
839 i->ancestry_job->on_finished = dkr_pull_job_on_finished_v2;
840 i->ancestry_job->on_progress = dkr_pull_job_on_progress;
841 i->ancestry_job->on_header = dkr_pull_job_on_header;
842
843
844 r = pull_job_begin(i->ancestry_job);
845 if (r < 0) {
846 log_error_errno(r, "Failed to start ancestry job: %m");
847 goto finish;
848 }
849
850 } else if (i->ancestry_job == j) {
851
dde8bb32 852 _cleanup_json_variant_unref_ JsonVariant *doc = NULL, *compat = NULL;
2c4fb0ea
PO
853 JsonVariant *e = NULL;
854 _cleanup_strv_free_ char **ancestry = NULL;
855 size_t allocated = 0, size = 0;
856 char *path = NULL, **k = NULL;
857
858 r = json_parse((const char *)j->payload, &doc);
859 if (r < 0) {
860 log_error("Invalid JSON Manifest");
861 goto finish;
862 }
863
864 e = json_variant_value(doc, "fsLayers");
37591152 865 if (!e || e->type != JSON_VARIANT_ARRAY || e->size == 0) {
2c4fb0ea
PO
866 r = -EBADMSG;
867 goto finish;
868 }
869
870 log_info("JSON manifest with schema v%"PRIi64" for %s parsed!",
871 json_variant_integer(json_variant_value(doc, "schemaVersion")),
872 json_variant_string(json_variant_value(doc, "name")));
873
874 for (unsigned z = 0; z < e->size; z++) {
875 JsonVariant *f = json_variant_element(e, z), *g = NULL;
876 const char *layer;
877 if (f->type != JSON_VARIANT_OBJECT) {
878 r = -EBADMSG;
879 goto finish;
880 }
881
882 g = json_variant_value(f, "blobSum");
883
884 layer = json_variant_string(g);
885 if (!dkr_digest_is_valid(layer)) {
886 r = -EBADMSG;
887 goto finish;
888 }
889
890 if (!GREEDY_REALLOC(ancestry, allocated, size + 2)) {
891 r = -ENOMEM;
892 log_oom();
893 goto finish;
894 }
895
896 ancestry[size] = strdup(layer);
897 if (!ancestry[size]) {
898 r = -ENOMEM;
899 log_oom();
900 goto finish;
901 }
902
903 ancestry[size+1] = NULL;
904 size += 1;
905 }
906
907 e = json_variant_value(doc, "history");
908 if (!e || e->type != JSON_VARIANT_ARRAY) {
909 r = -EBADMSG;
910 goto finish;
911 }
912
913 e = json_variant_element(e, 0);
914 e = json_variant_value(e, "v1Compatibility");
915 r = json_parse(json_variant_string(e), &compat);
916 if (r < 0) {
917 log_error("Invalid v1Compatibility JSON");
918 goto finish;
919 }
920
921 e = json_variant_value(compat, "id");
922
923 strv_free(i->ancestry);
924 i->ancestry = strv_reverse(strv_uniq(ancestry));
925 i->n_ancestry = strv_length(i->ancestry);
926 i->current_ancestry = 0;
927 i->id = strdup(i->ancestry[i->n_ancestry - 1]);
928 if (!i->id) {
929 r = -ENOMEM;
930 log_oom();
931 goto finish;
932 }
933 path = strjoin(i->image_root, "/.dkr-", json_variant_string(e), NULL);
934 if (!path) {
935 r = -ENOMEM;
936 log_oom();
937 goto finish;
938 }
939 free(i->image_root);
940 i->image_root = path;
941 ancestry = NULL;
942
943 log_info("Required layers:\n");
944 STRV_FOREACH(k, i->ancestry)
945 log_info("\t%s", *k);
946 log_info("\nProvenance:\n\tImageID: %s\n\tDigest: %s", json_variant_string(e), i->response_digest);
947
948 dkr_pull_report_progress(i, DKR_DOWNLOADING);
949
950 r = dkr_pull_pull_layer_v2(i);
951 if (r < 0)
952 goto finish;
953
954 } else if (i->layer_job == j) {
955 assert(i->temp_path);
956 assert(i->final_path);
957
958 j->disk_fd = safe_close(j->disk_fd);
959
960 if (i->tar_pid > 0) {
961 r = wait_for_terminate_and_warn("tar", i->tar_pid, true);
962 i->tar_pid = 0;
963 if (r < 0)
964 goto finish;
965 }
966
967 r = aufs_resolve(i->temp_path);
968 if (r < 0) {
969 log_error_errno(r, "Failed to resolve aufs whiteouts: %m");
970 goto finish;
971 }
972
973 r = btrfs_subvol_set_read_only(i->temp_path, true);
974 if (r < 0) {
975 log_error_errno(r, "Failed to mark snapshot read-only: %m");
976 goto finish;
977 }
978
979 if (rename(i->temp_path, i->final_path) < 0) {
980 log_error_errno(errno, "Failed to rename snaphsot: %m");
981 goto finish;
982 }
983
984 log_info("Completed writing to layer %s.", i->final_path);
985
986 i->layer_job = pull_job_unref(i->layer_job);
987 free(i->temp_path);
988 i->temp_path = NULL;
989 free(i->final_path);
990 i->final_path = NULL;
991
992 i->current_ancestry ++;
993 r = dkr_pull_pull_layer_v2(i);
994 if (r < 0)
995 goto finish;
996
997 } else if (i->json_job != j)
998 assert_not_reached("Got finished event for unknown curl object");
999
1000 if (!dkr_pull_is_done(i))
1001 return;
1002
1003 dkr_pull_report_progress(i, DKR_COPYING);
1004
1005 r = dkr_pull_make_local_copy(i, DKR_PULL_V2);
1006 if (r < 0)
1007 goto finish;
1008
1009 r = 0;
1010
1011finish:
1012 if (i->on_finished)
1013 i->on_finished(i, r, i->userdata);
1014 else
1015 sd_event_exit(i->event, r);
1016
1017}
1018
dc2c282b
LP
1019static void dkr_pull_job_on_finished(PullJob *j) {
1020 DkrPull *i;
72648326
LP
1021 int r;
1022
ff2670ad
LP
1023 assert(j);
1024 assert(j->userdata);
1025
1026 i = j->userdata;
1027 if (j->error != 0) {
1028 if (j == i->images_job)
1029 log_error_errno(j->error, "Failed to retrieve images list. (Wrong index URL?)");
1030 else if (j == i->tags_job)
1031 log_error_errno(j->error, "Failed to retrieve tags list.");
1032 else if (j == i->ancestry_job)
1033 log_error_errno(j->error, "Failed to retrieve ancestry list.");
1034 else if (j == i->json_job)
1035 log_error_errno(j->error, "Failed to retrieve json data.");
1036 else
1037 log_error_errno(j->error, "Failed to retrieve layer data.");
1038
1039 r = j->error;
1040 goto finish;
1041 }
72648326 1042
ff2670ad 1043 if (i->images_job == j) {
72648326 1044 const char *url;
72648326 1045
ff2670ad
LP
1046 assert(!i->tags_job);
1047 assert(!i->ancestry_job);
1048 assert(!i->json_job);
1049 assert(!i->layer_job);
72648326 1050
ff2670ad 1051 if (strv_isempty(i->response_registries)) {
72648326 1052 r = -EBADMSG;
ff2670ad
LP
1053 log_error("Didn't get registry information.");
1054 goto finish;
72648326
LP
1055 }
1056
ff2670ad 1057 log_info("Index lookup succeeded, directed to registry %s.", i->response_registries[0]);
dc2c282b 1058 dkr_pull_report_progress(i, DKR_RESOLVING);
72648326 1059
2c4fb0ea 1060 url = strjoina(PROTOCOL_PREFIX, i->response_registries[0], "/v1/repositories/", i->name, "/tags/", i->reference);
dc2c282b 1061 r = pull_job_new(&i->tags_job, url, i->glue, i);
ff2670ad
LP
1062 if (r < 0) {
1063 log_error_errno(r, "Failed to allocate tags job: %m");
1064 goto finish;
1065 }
1066
dc2c282b 1067 r = dkr_pull_add_token(i, i->tags_job);
ff2670ad
LP
1068 if (r < 0) {
1069 log_oom();
1070 goto finish;
1071 }
1072
dc2c282b
LP
1073 i->tags_job->on_finished = dkr_pull_job_on_finished;
1074 i->tags_job->on_progress = dkr_pull_job_on_progress;
72648326 1075
dc2c282b 1076 r = pull_job_begin(i->tags_job);
72648326 1077 if (r < 0) {
ff2670ad
LP
1078 log_error_errno(r, "Failed to start tags job: %m");
1079 goto finish;
72648326
LP
1080 }
1081
ff2670ad 1082 } else if (i->tags_job == j) {
72648326 1083 const char *url;
ff2670ad 1084 char *id = NULL;
72648326 1085
ff2670ad
LP
1086 assert(!i->ancestry_job);
1087 assert(!i->json_job);
1088 assert(!i->layer_job);
72648326 1089
ff2670ad 1090 r = parse_id(j->payload, j->payload_size, &id);
72648326
LP
1091 if (r < 0) {
1092 log_error_errno(r, "Failed to parse JSON id.");
ff2670ad 1093 goto finish;
72648326
LP
1094 }
1095
ff2670ad
LP
1096 free(i->id);
1097 i->id = id;
72648326 1098
ff2670ad 1099 log_info("Tag lookup succeeded, resolved to layer %s.", i->id);
dc2c282b 1100 dkr_pull_report_progress(i, DKR_METADATA);
ff2670ad 1101
63c372cb 1102 url = strjoina(PROTOCOL_PREFIX, i->response_registries[0], "/v1/images/", i->id, "/ancestry");
dc2c282b 1103 r = pull_job_new(&i->ancestry_job, url, i->glue, i);
ff2670ad
LP
1104 if (r < 0) {
1105 log_error_errno(r, "Failed to allocate ancestry job: %m");
1106 goto finish;
1107 }
1108
dc2c282b 1109 r = dkr_pull_add_token(i, i->ancestry_job);
ff2670ad
LP
1110 if (r < 0) {
1111 log_oom();
1112 goto finish;
1113 }
72648326 1114
dc2c282b
LP
1115 i->ancestry_job->on_finished = dkr_pull_job_on_finished;
1116 i->ancestry_job->on_progress = dkr_pull_job_on_progress;
72648326 1117
63c372cb 1118 url = strjoina(PROTOCOL_PREFIX, i->response_registries[0], "/v1/images/", i->id, "/json");
dc2c282b 1119 r = pull_job_new(&i->json_job, url, i->glue, i);
72648326 1120 if (r < 0) {
ff2670ad
LP
1121 log_error_errno(r, "Failed to allocate json job: %m");
1122 goto finish;
72648326
LP
1123 }
1124
dc2c282b 1125 r = dkr_pull_add_token(i, i->json_job);
72648326 1126 if (r < 0) {
ff2670ad
LP
1127 log_oom();
1128 goto finish;
72648326
LP
1129 }
1130
dc2c282b
LP
1131 i->json_job->on_finished = dkr_pull_job_on_finished;
1132 i->json_job->on_progress = dkr_pull_job_on_progress;
ff2670ad 1133
dc2c282b 1134 r = pull_job_begin(i->ancestry_job);
ff2670ad
LP
1135 if (r < 0) {
1136 log_error_errno(r, "Failed to start ancestry job: %m");
1137 goto finish;
1138 }
1139
dc2c282b 1140 r = pull_job_begin(i->json_job);
ff2670ad
LP
1141 if (r < 0) {
1142 log_error_errno(r, "Failed to start json job: %m");
1143 goto finish;
1144 }
1145
1146 } else if (i->ancestry_job == j) {
1147 char **ancestry = NULL, **k;
72648326
LP
1148 unsigned n;
1149
ff2670ad
LP
1150 assert(!i->layer_job);
1151
1152 r = parse_ancestry(j->payload, j->payload_size, &ancestry);
72648326
LP
1153 if (r < 0) {
1154 log_error_errno(r, "Failed to parse JSON id.");
ff2670ad 1155 goto finish;
72648326
LP
1156 }
1157
1158 n = strv_length(ancestry);
ff2670ad 1159 if (n <= 0 || !streq(ancestry[n-1], i->id)) {
72648326 1160 log_error("Ancestry doesn't end in main layer.");
ff2670ad 1161 strv_free(ancestry);
72648326 1162 r = -EBADMSG;
ff2670ad 1163 goto finish;
72648326
LP
1164 }
1165
1166 log_info("Ancestor lookup succeeded, requires layers:\n");
ff2670ad
LP
1167 STRV_FOREACH(k, ancestry)
1168 log_info("\t%s", *k);
72648326 1169
ff2670ad
LP
1170 strv_free(i->ancestry);
1171 i->ancestry = ancestry;
7079cfef 1172 i->n_ancestry = n;
ff2670ad 1173 i->current_ancestry = 0;
7079cfef 1174
dc2c282b 1175 dkr_pull_report_progress(i, DKR_DOWNLOADING);
7079cfef 1176
dc2c282b 1177 r = dkr_pull_pull_layer(i);
72648326 1178 if (r < 0)
ff2670ad 1179 goto finish;
72648326 1180
ff2670ad
LP
1181 } else if (i->layer_job == j) {
1182 assert(i->temp_path);
1183 assert(i->final_path);
72648326 1184
ff2670ad 1185 j->disk_fd = safe_close(j->disk_fd);
72648326 1186
ff2670ad
LP
1187 if (i->tar_pid > 0) {
1188 r = wait_for_terminate_and_warn("tar", i->tar_pid, true);
1189 i->tar_pid = 0;
1190 if (r < 0)
1191 goto finish;
72648326
LP
1192 }
1193
ff2670ad 1194 r = aufs_resolve(i->temp_path);
72648326 1195 if (r < 0) {
ff2670ad
LP
1196 log_error_errno(r, "Failed to resolve aufs whiteouts: %m");
1197 goto finish;
72648326
LP
1198 }
1199
ff2670ad 1200 r = btrfs_subvol_set_read_only(i->temp_path, true);
72648326 1201 if (r < 0) {
cc98b302 1202 log_error_errno(r, "Failed to mark snapshot read-only: %m");
ff2670ad 1203 goto finish;
72648326
LP
1204 }
1205
ff2670ad
LP
1206 if (rename(i->temp_path, i->final_path) < 0) {
1207 log_error_errno(errno, "Failed to rename snaphsot: %m");
1208 goto finish;
72648326
LP
1209 }
1210
ff2670ad 1211 log_info("Completed writing to layer %s.", i->final_path);
72648326 1212
dc2c282b 1213 i->layer_job = pull_job_unref(i->layer_job);
ff2670ad
LP
1214 free(i->temp_path);
1215 i->temp_path = NULL;
1216 free(i->final_path);
1217 i->final_path = NULL;
72648326 1218
ff2670ad 1219 i->current_ancestry ++;
dc2c282b 1220 r = dkr_pull_pull_layer(i);
ff2670ad
LP
1221 if (r < 0)
1222 goto finish;
72648326 1223
ff2670ad
LP
1224 } else if (i->json_job != j)
1225 assert_not_reached("Got finished event for unknown curl object");
72648326 1226
dc2c282b 1227 if (!dkr_pull_is_done(i))
ff2670ad 1228 return;
72648326 1229
dc2c282b 1230 dkr_pull_report_progress(i, DKR_COPYING);
7079cfef 1231
2c4fb0ea 1232 r = dkr_pull_make_local_copy(i, DKR_PULL_V1);
72648326 1233 if (r < 0)
ff2670ad 1234 goto finish;
72648326 1235
ff2670ad 1236 r = 0;
ff2670ad
LP
1237finish:
1238 if (i->on_finished)
1239 i->on_finished(i, r, i->userdata);
1240 else
1241 sd_event_exit(i->event, r);
72648326
LP
1242}
1243
2c4fb0ea
PO
1244static int get_protocol_address(char **protocol, char **address, const char *url) {
1245 const char *sep, *dot;
6d9bad91 1246 _cleanup_free_ char *a = NULL, *p = NULL;
72648326 1247
2c4fb0ea
PO
1248 sep = strstr(url, "://");
1249 if (!sep)
1250 return -EINVAL;
e9d73334 1251
2c4fb0ea
PO
1252 dot = strrchr(url, '.');
1253 if (!dot)
1254 return -EINVAL;
1255 dot--;
72648326 1256
2c4fb0ea
PO
1257 p = strndup(url, (sep - url) + 3);
1258 if (!p)
ff2670ad 1259 return log_oom();
72648326 1260
2c4fb0ea
PO
1261 while (dot > (sep + 3) && *dot != '.')
1262 dot--;
72648326 1263
2c4fb0ea
PO
1264 a = strdup(dot + 1);
1265 if (!a)
1266 return log_oom();
72648326 1267
2c4fb0ea
PO
1268 *address = a;
1269 *protocol = p;
6d9bad91 1270 a = p = NULL;
72648326 1271
72648326
LP
1272 return 0;
1273}
1274
2c4fb0ea 1275int dkr_pull_start(DkrPull *i, const char *name, const char *reference, const char *local, bool force_local, DkrPullVersion version) {
ff2670ad 1276 const char *url;
72648326
LP
1277 int r;
1278
ff2670ad 1279 assert(i);
72648326 1280
ff2670ad
LP
1281 if (!dkr_name_is_valid(name))
1282 return -EINVAL;
72648326 1283
2c4fb0ea 1284 if (reference && !dkr_ref_is_valid(reference))
ff2670ad 1285 return -EINVAL;
72648326 1286
ff2670ad
LP
1287 if (local && !machine_name_is_valid(local))
1288 return -EINVAL;
72648326 1289
ff2670ad
LP
1290 if (i->images_job)
1291 return -EBUSY;
72648326 1292
2c4fb0ea
PO
1293 if (!reference)
1294 reference = "latest";
1295
1296 free(i->index_protocol);
1297 free(i->index_address);
1298 r = get_protocol_address(&i->index_protocol, &i->index_address, i->index_url);
1299 if (r < 0)
1300 return r;
72648326 1301
ff2670ad 1302 r = free_and_strdup(&i->local, local);
72648326
LP
1303 if (r < 0)
1304 return r;
ff2670ad 1305 i->force_local = force_local;
72648326 1306
ff2670ad 1307 r = free_and_strdup(&i->name, name);
72648326
LP
1308 if (r < 0)
1309 return r;
2c4fb0ea 1310 r = free_and_strdup(&i->reference, reference);
72648326
LP
1311 if (r < 0)
1312 return r;
1313
63c372cb 1314 url = strjoina(i->index_url, "/v1/repositories/", name, "/images");
72648326 1315
dc2c282b 1316 r = pull_job_new(&i->images_job, url, i->glue, i);
72648326
LP
1317 if (r < 0)
1318 return r;
1319
dc2c282b 1320 r = dkr_pull_add_token(i, i->images_job);
72648326
LP
1321 if (r < 0)
1322 return r;
1323
2c4fb0ea
PO
1324 if (version == DKR_PULL_V1)
1325 i->images_job->on_finished = dkr_pull_job_on_finished;
1326 else
1327 i->images_job->on_finished = dkr_pull_job_on_finished_v2;
1328
dc2c282b
LP
1329 i->images_job->on_header = dkr_pull_job_on_header;
1330 i->images_job->on_progress = dkr_pull_job_on_progress;
72648326 1331
dc2c282b 1332 return pull_job_begin(i->images_job);
72648326 1333}