]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/import/import-dkr.c
import: minor cleanups for the tar and raw importers
[thirdparty/systemd.git] / src / import / import-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
25#include "hashmap.h"
26#include "set.h"
27#include "json.h"
28#include "strv.h"
29#include "curl-util.h"
91f4347e 30#include "import-dkr.h"
72648326
LP
31#include "btrfs-util.h"
32#include "aufs-util.h"
91f4347e 33#include "utf8.h"
72648326
LP
34
35/* TODO:
36 - convert json bits
37 - man page
38 - fall back to btrfs loop pool device
39*/
40
91f4347e
LP
41typedef struct DkrImportJob DkrImportJob;
42typedef struct DkrImportName DkrImportName;
72648326 43
91f4347e
LP
44typedef enum DkrImportJobType {
45 DKR_IMPORT_JOB_IMAGES,
46 DKR_IMPORT_JOB_TAGS,
47 DKR_IMPORT_JOB_ANCESTRY,
48 DKR_IMPORT_JOB_JSON,
49 DKR_IMPORT_JOB_LAYER,
50} DkrImportJobType;
72648326 51
91f4347e
LP
52struct DkrImportJob {
53 DkrImport *import;
54 DkrImportJobType type;
72648326
LP
55 bool done;
56
57 char *url;
58
91f4347e 59 Set *needed_by; /* DkrImport Name objects */
72648326
LP
60
61 CURL *curl;
62 struct curl_slist *request_header;
63 void *payload;
64 size_t payload_size;
65
66 char *response_token;
67 char **response_registries;
68
69 char *temp_path;
70 char *final_path;
71
72 pid_t tar_pid;
73 FILE *tar_stream;
74};
75
91f4347e
LP
76struct DkrImportName {
77 DkrImport *import;
72648326
LP
78
79 char *name;
80 char *tag;
81 char *id;
82 char *local;
83
91f4347e 84 DkrImportJob *job_images, *job_tags, *job_ancestry, *job_json, *job_layer;
72648326
LP
85
86 char **ancestry;
87 unsigned current_ancestry;
88
89 bool force_local;
90};
91
91f4347e 92struct DkrImport {
72648326
LP
93 sd_event *event;
94 CurlGlue *glue;
95
ea1ae8c3 96 char *index_url;
087682d1 97 char *image_root;
ea1ae8c3 98
72648326
LP
99 Hashmap *names;
100 Hashmap *jobs;
101
91f4347e 102 dkr_import_on_finished on_finished;
72648326 103 void *userdata;
14ed8b92
LP
104
105 bool finished;
72648326
LP
106};
107
108#define PROTOCOL_PREFIX "https://"
72648326
LP
109
110#define HEADER_TOKEN "X-Do" /* the HTTP header for the auth token */ "cker-Token:"
111#define HEADER_REGISTRY "X-Do" /*the HTTP header for the registry */ "cker-Endpoints:"
112
113#define PAYLOAD_MAX (16*1024*1024)
114#define LAYERS_MAX 2048
115
91f4347e 116static int dkr_import_name_add_job(DkrImportName *name, DkrImportJobType type, const char *url, DkrImportJob **ret);
72648326 117
91f4347e 118static DkrImportJob *dkr_import_job_unref(DkrImportJob *job) {
72648326
LP
119 if (!job)
120 return NULL;
121
122 if (job->import)
123 curl_glue_remove_and_free(job->import->glue, job->curl);
124 curl_slist_free_all(job->request_header);
125
126 if (job->tar_stream)
127 fclose(job->tar_stream);
128
129 free(job->final_path);
130
131 if (job->temp_path) {
132 btrfs_subvol_remove(job->temp_path);
133 free(job->temp_path);
134 }
135
136 set_free(job->needed_by);
137
138 if (job->tar_pid > 0)
139 kill(job->tar_pid, SIGTERM);
140
141 free(job->url);
142 free(job->payload);
143 free(job->response_token);
144 strv_free(job->response_registries);
145
146 free(job);
147
148 return NULL;
149}
150
91f4347e 151static DkrImportName *dkr_import_name_unref(DkrImportName *name) {
72648326
LP
152 if (!name)
153 return NULL;
154
155 if (name->job_images)
156 set_remove(name->job_images->needed_by, name);
157
158 if (name->job_tags)
159 set_remove(name->job_tags->needed_by, name);
160
161 if (name->job_ancestry)
162 set_remove(name->job_ancestry->needed_by, name);
163
164 if (name->job_json)
165 set_remove(name->job_json->needed_by, name);
166
167 if (name->job_layer)
168 set_remove(name->job_layer->needed_by, name);
169
170 free(name->name);
171 free(name->id);
172 free(name->tag);
173 free(name->local);
174
175 strv_free(name->ancestry);
176 free(name);
177
178 return NULL;
179}
180
91f4347e
LP
181DEFINE_TRIVIAL_CLEANUP_FUNC(DkrImportJob*, dkr_import_job_unref);
182DEFINE_TRIVIAL_CLEANUP_FUNC(DkrImportName*, dkr_import_name_unref);
72648326 183
91f4347e 184static void dkr_import_finish(DkrImport *import, int error) {
72648326
LP
185 assert(import);
186
14ed8b92
LP
187 if (import->finished)
188 return;
189
190 import->finished = true;
191
72648326
LP
192 if (import->on_finished)
193 import->on_finished(import, error, import->userdata);
194 else
195 sd_event_exit(import->event, error);
196}
197
198static int parse_id(const void *payload, size_t size, char **ret) {
199 _cleanup_free_ char *buf = NULL, *id = NULL, *other = NULL;
200 union json_value v = {};
201 void *json_state = NULL;
202 const char *p;
203 int t;
204
205 assert(payload);
206 assert(ret);
207
208 if (size <= 0)
209 return -EBADMSG;
210
211 if (memchr(payload, 0, size))
212 return -EBADMSG;
213
214 buf = strndup(payload, size);
215 if (!buf)
216 return -ENOMEM;
217
218 p = buf;
219 t = json_tokenize(&p, &id, &v, &json_state, NULL);
220 if (t < 0)
221 return t;
222 if (t != JSON_STRING)
223 return -EBADMSG;
224
225 t = json_tokenize(&p, &other, &v, &json_state, NULL);
226 if (t < 0)
227 return t;
228 if (t != JSON_END)
229 return -EBADMSG;
230
91f4347e 231 if (!dkr_id_is_valid(id))
72648326
LP
232 return -EBADMSG;
233
234 *ret = id;
235 id = NULL;
236
237 return 0;
238}
239
240static int parse_ancestry(const void *payload, size_t size, char ***ret) {
241 _cleanup_free_ char *buf = NULL;
242 void *json_state = NULL;
243 const char *p;
244 enum {
245 STATE_BEGIN,
246 STATE_ITEM,
247 STATE_COMMA,
248 STATE_END,
249 } state = STATE_BEGIN;
250 _cleanup_strv_free_ char **l = NULL;
251 size_t n = 0, allocated = 0;
252
253 if (size <= 0)
254 return -EBADMSG;
255
256 if (memchr(payload, 0, size))
257 return -EBADMSG;
258
259 buf = strndup(payload, size);
260 if (!buf)
261 return -ENOMEM;
262
263 p = buf;
264 for (;;) {
265 _cleanup_free_ char *str;
266 union json_value v = {};
267 int t;
268
269 t = json_tokenize(&p, &str, &v, &json_state, NULL);
270 if (t < 0)
271 return t;
272
273 switch (state) {
274
275 case STATE_BEGIN:
276 if (t == JSON_ARRAY_OPEN)
277 state = STATE_ITEM;
278 else
279 return -EBADMSG;
280
281 break;
282
283 case STATE_ITEM:
284 if (t == JSON_STRING) {
91f4347e 285 if (!dkr_id_is_valid(str))
72648326
LP
286 return -EBADMSG;
287
288 if (n+1 > LAYERS_MAX)
289 return -EFBIG;
290
291 if (!GREEDY_REALLOC(l, allocated, n + 2))
292 return -ENOMEM;
293
294 l[n++] = str;
295 str = NULL;
296 l[n] = NULL;
297
298 state = STATE_COMMA;
299
300 } else if (t == JSON_ARRAY_CLOSE)
301 state = STATE_END;
302 else
303 return -EBADMSG;
304
305 break;
306
307 case STATE_COMMA:
308 if (t == JSON_COMMA)
309 state = STATE_ITEM;
310 else if (t == JSON_ARRAY_CLOSE)
311 state = STATE_END;
312 else
313 return -EBADMSG;
314 break;
315
316 case STATE_END:
317 if (t == JSON_END) {
318
319 if (strv_isempty(l))
320 return -EBADMSG;
321
322 if (!strv_is_uniq(l))
323 return -EBADMSG;
324
325 l = strv_reverse(l);
326
327 *ret = l;
328 l = NULL;
329 return 0;
330 } else
331 return -EBADMSG;
332 }
333
334 }
335}
336
91f4347e 337static const char *dkr_import_name_current_layer(DkrImportName *name) {
72648326
LP
338 assert(name);
339
340 if (strv_isempty(name->ancestry))
341 return NULL;
342
343 return name->ancestry[name->current_ancestry];
344}
345
91f4347e 346static const char *dkr_import_name_current_base_layer(DkrImportName *name) {
72648326
LP
347 assert(name);
348
349 if (strv_isempty(name->ancestry))
350 return NULL;
351
352 if (name->current_ancestry <= 0)
353 return NULL;
354
355 return name->ancestry[name->current_ancestry-1];
356}
357
91f4347e 358static char** dkr_import_name_get_registries(DkrImportName *name) {
72648326
LP
359 assert(name);
360
361 if (!name->job_images)
362 return NULL;
363
364 if (!name->job_images->done)
365 return NULL;
366
367 if (strv_isempty(name->job_images->response_registries))
368 return NULL;
369
370 return name->job_images->response_registries;
371}
372
91f4347e 373static const char*dkr_import_name_get_token(DkrImportName *name) {
72648326
LP
374 assert(name);
375
376 if (!name->job_images)
377 return NULL;
378
379 if (!name->job_images->done)
380 return NULL;
381
382 return name->job_images->response_token;
383}
384
91f4347e 385static void dkr_import_name_maybe_finish(DkrImportName *name) {
72648326
LP
386 int r;
387
388 assert(name);
389
390 if (!name->job_images || !name->job_images->done)
391 return;
392
393 if (!name->job_ancestry || !name->job_ancestry->done)
394 return;
395
396 if (!name->job_json || !name->job_json->done)
397 return;
398
399 if (name->job_layer && !name->job_json->done)
400 return;
401
91f4347e 402 if (dkr_import_name_current_layer(name))
72648326
LP
403 return;
404
405 if (name->local) {
406 const char *p, *q;
407
408 assert(name->id);
409
087682d1
LP
410 p = strappenda(name->import->image_root, "/", name->local);
411 q = strappenda(name->import->image_root, "/.dkr-", name->id);
72648326
LP
412
413 if (name->force_local) {
414 (void) btrfs_subvol_remove(p);
f0be89ee 415 (void) rm_rf_dangerous(p, false, true, false);
72648326
LP
416 }
417
418 r = btrfs_subvol_snapshot(q, p, false, false);
419 if (r < 0) {
f0be89ee 420 log_error_errno(r, "Failed to snapshot local image: %m");
91f4347e 421 dkr_import_finish(name->import, r);
72648326
LP
422 return;
423 }
424
56ebfaf1 425 log_info("Created new local image '%s'.", name->local);
72648326
LP
426 }
427
91f4347e 428 dkr_import_finish(name->import, 0);
72648326
LP
429}
430
91f4347e 431static int dkr_import_job_run_tar(DkrImportJob *job) {
72648326
LP
432 _cleanup_close_pair_ int pipefd[2] = { -1, -1 };
433 bool gzip;
434
435 assert(job);
436
437 /* A stream to run tar on? */
438 if (!job->temp_path)
439 return 0;
440
441 if (job->tar_stream)
442 return 0;
443
444 /* Maybe fork off tar, if we have enough to figure out that
445 * something is gzip compressed or not */
446
447 if (job->payload_size < 2)
448 return 0;
449
450 /* Detect gzip signature */
451 gzip = ((uint8_t*) job->payload)[0] == 0x1f &&
452 ((uint8_t*) job->payload)[1] == 0x8b;
453
454 assert(!job->tar_stream);
455 assert(job->tar_pid <= 0);
456
457 if (pipe2(pipefd, O_CLOEXEC) < 0)
458 return log_error_errno(errno, "Failed to create pipe for tar: %m");
459
460 job->tar_pid = fork();
461 if (job->tar_pid < 0)
462 return log_error_errno(errno, "Failed to fork off tar: %m");
463 if (job->tar_pid == 0) {
464 int null_fd;
465
466 reset_all_signal_handlers();
467 reset_signal_mask();
468 assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0);
469
470 pipefd[1] = safe_close(pipefd[1]);
471
472 if (dup2(pipefd[0], STDIN_FILENO) != STDIN_FILENO) {
473 log_error_errno(errno, "Failed to dup2() fd: %m");
474 _exit(EXIT_FAILURE);
475 }
476
477 if (pipefd[0] != STDIN_FILENO)
478 safe_close(pipefd[0]);
479 if (pipefd[1] != STDIN_FILENO)
480 safe_close(pipefd[1]);
481
482 null_fd = open("/dev/null", O_WRONLY|O_NOCTTY);
483 if (null_fd < 0) {
484 log_error_errno(errno, "Failed to open /dev/null: %m");
485 _exit(EXIT_FAILURE);
486 }
487
488 if (dup2(null_fd, STDOUT_FILENO) != STDOUT_FILENO) {
489 log_error_errno(errno, "Failed to dup2() fd: %m");
490 _exit(EXIT_FAILURE);
491 }
492
493 if (null_fd != STDOUT_FILENO)
494 safe_close(null_fd);
495
6da023a0 496 execlp("tar", "tar", "-C", job->temp_path, gzip ? "-xpz" : "-px", "--numeric-owner", NULL);
72648326
LP
497 _exit(EXIT_FAILURE);
498 }
499
500 pipefd[0] = safe_close(pipefd[0]);
501
502 job->tar_stream = fdopen(pipefd[1], "w");
503 if (!job->tar_stream)
504 return log_error_errno(errno, "Failed to allocate tar stream: %m");
505
506 pipefd[1] = -1;
507
508 if (fwrite(job->payload, 1, job->payload_size, job->tar_stream) != job->payload_size)
509 return log_error_errno(errno, "Couldn't write payload: %m");
510
511 free(job->payload);
512 job->payload = NULL;
513 job->payload_size = 0;
514
515 return 0;
516}
517
91f4347e 518static int dkr_import_name_pull_layer(DkrImportName *name) {
72648326
LP
519 _cleanup_free_ char *path = NULL, *temp = NULL;
520 const char *url, *layer = NULL, *base = NULL;
521 char **rg;
522 int r;
523
524 assert(name);
525
526 if (name->job_layer) {
527 set_remove(name->job_layer->needed_by, name);
528 name->job_layer = NULL;
529 }
530
531 for (;;) {
91f4347e 532 layer = dkr_import_name_current_layer(name);
72648326 533 if (!layer) {
91f4347e 534 dkr_import_name_maybe_finish(name);
72648326
LP
535 return 0;
536 }
537
087682d1 538 path = strjoin(name->import->image_root, "/.dkr-", layer, NULL);
72648326
LP
539 if (!path)
540 return log_oom();
541
542 if (laccess(path, F_OK) < 0) {
543 if (errno == ENOENT)
544 break;
545
546 return log_error_errno(errno, "Failed to check for container: %m");
547 }
548
549 log_info("Layer %s already exists, skipping.", layer);
550
551 name->current_ancestry++;
552
553 free(path);
554 path = NULL;
555 }
556
91f4347e 557 rg = dkr_import_name_get_registries(name);
72648326
LP
558 assert(rg && rg[0]);
559
560 url = strappenda(PROTOCOL_PREFIX, rg[0], "/v1/images/", layer, "/layer");
91f4347e 561 r = dkr_import_name_add_job(name, DKR_IMPORT_JOB_LAYER, url, &name->job_layer);
72648326
LP
562 if (r < 0) {
563 log_error_errno(r, "Failed to issue HTTP request: %m");
564 return r;
565 }
566 if (r == 0) /* Already downloading this one? */
567 return 0;
568
569 log_info("Pulling layer %s...", layer);
570
571 r = tempfn_random(path, &temp);
572 if (r < 0)
573 return log_oom();
574
91f4347e 575 base = dkr_import_name_current_base_layer(name);
72648326
LP
576 if (base) {
577 const char *base_path;
578
087682d1 579 base_path = strappenda(name->import->image_root, "/.dkr-", base);
72648326
LP
580 r = btrfs_subvol_snapshot(base_path, temp, false, true);
581 } else
582 r = btrfs_subvol_make(temp);
583
584 if (r < 0)
585 return log_error_errno(r, "Failed to make btrfs subvolume %s", temp);
586
587 name->job_layer->final_path = path;
588 name->job_layer->temp_path = temp;
589 path = temp = NULL;
590
591 return 0;
592}
593
91f4347e 594static void dkr_import_name_job_finished(DkrImportName *name, DkrImportJob *job) {
72648326
LP
595 int r;
596
597 assert(name);
598 assert(job);
599
600 if (name->job_images == job) {
601 const char *url;
602 char **rg;
603
604 assert(!name->job_tags);
605 assert(!name->job_ancestry);
606 assert(!name->job_json);
607 assert(!name->job_layer);
608
91f4347e 609 rg = dkr_import_name_get_registries(name);
72648326
LP
610 if (strv_isempty(rg)) {
611 log_error("Didn't get registry information.");
612 r = -EBADMSG;
613 goto fail;
614 }
615
616 log_info("Index lookup succeeded, directed to registry %s.", rg[0]);
617
618 url = strappenda(PROTOCOL_PREFIX, rg[0], "/v1/repositories/", name->name, "/tags/", name->tag);
619
91f4347e 620 r = dkr_import_name_add_job(name, DKR_IMPORT_JOB_TAGS, url, &name->job_tags);
72648326
LP
621 if (r < 0) {
622 log_error_errno(r, "Failed to issue HTTP request: %m");
623 goto fail;
624 }
625
626 } else if (name->job_tags == job) {
627 const char *url;
628 char *id = NULL, **rg;
629
630 assert(!name->job_ancestry);
631 assert(!name->job_json);
632 assert(!name->job_layer);
633
634 r = parse_id(job->payload, job->payload_size, &id);
635 if (r < 0) {
636 log_error_errno(r, "Failed to parse JSON id.");
637 goto fail;
638 }
639
640 free(name->id);
641 name->id = id;
642
91f4347e 643 rg = dkr_import_name_get_registries(name);
72648326
LP
644 assert(rg && rg[0]);
645
646 log_info("Tag lookup succeeded, resolved to layer %s.", name->id);
647
648 url = strappenda(PROTOCOL_PREFIX, rg[0], "/v1/images/", name->id, "/ancestry");
91f4347e 649 r = dkr_import_name_add_job(name, DKR_IMPORT_JOB_ANCESTRY, url, &name->job_ancestry);
72648326
LP
650 if (r < 0) {
651 log_error_errno(r, "Failed to issue HTTP request: %m");
652 goto fail;
653 }
654
655 url = strappenda(PROTOCOL_PREFIX, rg[0], "/v1/images/", name->id, "/json");
91f4347e 656 r = dkr_import_name_add_job(name, DKR_IMPORT_JOB_JSON, url, &name->job_json);
72648326
LP
657 if (r < 0) {
658 log_error_errno(r, "Failed to issue HTTP request: %m");
659 goto fail;
660 }
661
662 } else if (name->job_ancestry == job) {
663 char **ancestry = NULL, **i;
664 unsigned n;
665
666 r = parse_ancestry(job->payload, job->payload_size, &ancestry);
667 if (r < 0) {
668 log_error_errno(r, "Failed to parse JSON id.");
669 goto fail;
670 }
671
672 n = strv_length(ancestry);
673 if (n <= 0 || !streq(ancestry[n-1], name->id)) {
674 log_error("Ancestry doesn't end in main layer.");
675 r = -EBADMSG;
676 goto fail;
677 }
678
679 log_info("Ancestor lookup succeeded, requires layers:\n");
680 STRV_FOREACH(i, ancestry)
681 log_info("\t%s", *i);
682
683 strv_free(name->ancestry);
684 name->ancestry = ancestry;
685
686 name->current_ancestry = 0;
91f4347e 687 r = dkr_import_name_pull_layer(name);
72648326
LP
688 if (r < 0)
689 goto fail;
690
691 } else if (name->job_json == job) {
692
91f4347e 693 dkr_import_name_maybe_finish(name);
72648326
LP
694
695 } else if (name->job_layer == job) {
696
697 name->current_ancestry ++;
91f4347e 698 r = dkr_import_name_pull_layer(name);
72648326
LP
699 if (r < 0)
700 goto fail;
701
702 } else
703 assert_not_reached("Got finished event for unknown curl object");
704
705 return;
706
707fail:
91f4347e 708 dkr_import_finish(name->import, r);
72648326
LP
709}
710
91f4347e
LP
711static void dkr_import_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result) {
712 DkrImportJob *job = NULL;
72648326 713 CURLcode code;
91f4347e 714 DkrImportName *n;
72648326
LP
715 long status;
716 Iterator i;
717 int r;
718
719 if (curl_easy_getinfo(curl, CURLINFO_PRIVATE, &job) != CURLE_OK)
720 return;
721
e9d73334 722 if (!job || job->done)
72648326
LP
723 return;
724
725 job->done = true;
726
727 if (result != CURLE_OK) {
eac8e8c6 728 log_error("Transfer failed: %s", curl_easy_strerror(result));
72648326
LP
729 r = -EIO;
730 goto fail;
731 }
732
733 code = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
734 if (code != CURLE_OK) {
735 log_error("Failed to retrieve response code: %s", curl_easy_strerror(code));
736 r = -EIO;
737 goto fail;
738 } else if (status >= 300) {
739 log_error("HTTP request to %s failed with code %li.", job->url, status);
740 r = -EIO;
741 goto fail;
742 } else if (status < 200) {
743 log_error("HTTP request to %s finished with unexpected code %li.", job->url, status);
744 r = -EIO;
745 goto fail;
746 }
747
748 switch (job->type) {
749
91f4347e 750 case DKR_IMPORT_JOB_LAYER: {
72648326
LP
751 siginfo_t si;
752
753 if (!job->tar_stream) {
754 log_error("Downloaded layer too short.");
755 r = -EIO;
756 goto fail;
757 }
758
759 fclose(job->tar_stream);
760 job->tar_stream = NULL;
761
762 assert(job->tar_pid > 0);
763
764 r = wait_for_terminate(job->tar_pid, &si);
765 if (r < 0) {
766 log_error_errno(r, "Failed to wait for tar process: %m");
767 goto fail;
768 }
769
770 job->tar_pid = 0;
771
772 if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS) {
773 log_error_errno(r, "tar failed abnormally.");
774 r = -EIO;
775 goto fail;
776 }
777
778 r = aufs_resolve(job->temp_path);
779 if (r < 0) {
780 log_error_errno(r, "Couldn't resolve aufs whiteouts: %m");
781 goto fail;
782 }
783
10f9c755 784 r = btrfs_subvol_set_read_only(job->temp_path, true);
72648326
LP
785 if (r < 0) {
786 log_error_errno(r, "Failed to mark snapshot read-only: %m");
787 goto fail;
788 }
789
790 if (rename(job->temp_path, job->final_path) < 0) {
791 log_error_errno(r, "Failed to rename snapshot: %m");
792 goto fail;
793 }
794
795 log_info("Completed writing to layer %s", job->final_path);
796 break;
797 }
798
799 default:
800 ;
801 }
802
803 SET_FOREACH(n, job->needed_by, i)
91f4347e 804 dkr_import_name_job_finished(n, job);
72648326
LP
805
806 return;
807
808fail:
91f4347e 809 dkr_import_finish(job->import, r);
72648326
LP
810}
811
91f4347e
LP
812static size_t dkr_import_job_write_callback(void *contents, size_t size, size_t nmemb, void *userdata) {
813 DkrImportJob *j = userdata;
72648326
LP
814 size_t sz = size * nmemb;
815 char *p;
816 int r;
817
818 assert(contents);
819 assert(j);
820
e9d73334
LP
821 if (j->done) {
822 r = -ESTALE;
823 goto fail;
824 }
825
72648326
LP
826 if (j->tar_stream) {
827 size_t l;
828
829 l = fwrite(contents, size, nmemb, j->tar_stream);
830 if (l != nmemb) {
a36544cd 831 r = log_error_errno(errno, "Failed to write to tar: %m");
72648326
LP
832 goto fail;
833 }
834
835 return l;
836 }
837
838 if (j->payload_size + sz > PAYLOAD_MAX) {
a36544cd 839 log_error("Payload too large.");
72648326
LP
840 r = -EFBIG;
841 goto fail;
842 }
843
844 p = realloc(j->payload, j->payload_size + sz);
845 if (!p) {
a36544cd 846 r = log_oom();
72648326
LP
847 goto fail;
848 }
849
850 memcpy(p + j->payload_size, contents, sz);
851 j->payload_size += sz;
852 j->payload = p;
853
91f4347e 854 r = dkr_import_job_run_tar(j);
72648326
LP
855 if (r < 0)
856 goto fail;
857
858 return sz;
859
860fail:
91f4347e 861 dkr_import_finish(j->import, r);
72648326
LP
862 return 0;
863}
864
91f4347e 865static size_t dkr_import_job_header_callback(void *contents, size_t size, size_t nmemb, void *userdata) {
72648326
LP
866 _cleanup_free_ char *registry = NULL;
867 size_t sz = size * nmemb;
91f4347e 868 DkrImportJob *j = userdata;
72648326
LP
869 char *token;
870 int r;
871
872 assert(contents);
873 assert(j);
874
e9d73334
LP
875 if (j->done) {
876 r = -ESTALE;
877 goto fail;
878 }
879
72648326
LP
880 r = curl_header_strdup(contents, sz, HEADER_TOKEN, &token);
881 if (r < 0) {
882 log_oom();
883 goto fail;
884 }
885 if (r > 0) {
886 free(j->response_token);
887 j->response_token = token;
888 }
889
890 r = curl_header_strdup(contents, sz, HEADER_REGISTRY, &registry);
891 if (r < 0) {
892 log_oom();
893 goto fail;
894 }
895 if (r > 0) {
896 char **l, **i;
897
898 l = strv_split(registry, ",");
899 if (!l) {
900 r = log_oom();
901 goto fail;
902 }
903
904 STRV_FOREACH(i, l) {
905 if (!hostname_is_valid(*i)) {
906 log_error("Registry hostname is not valid.");
907 strv_free(l);
908 r = -EBADMSG;
909 goto fail;
910 }
911 }
912
913 strv_free(j->response_registries);
914 j->response_registries = l;
915 }
916
917 return sz;
918
919fail:
91f4347e 920 dkr_import_finish(j->import, r);
72648326
LP
921 return 0;
922}
923
91f4347e
LP
924static int dkr_import_name_add_job(DkrImportName *name, DkrImportJobType type, const char *url, DkrImportJob **ret) {
925 _cleanup_(dkr_import_job_unrefp) DkrImportJob *j = NULL;
926 DkrImportJob *f = NULL;
72648326
LP
927 const char *t, *token;
928 int r;
929
930 assert(name);
931 assert(url);
932 assert(ret);
933
934 log_info("Getting %s.", url);
935 f = hashmap_get(name->import->jobs, url);
936 if (f) {
937 if (f->type != type)
938 return -EINVAL;
939
940 r = set_put(f->needed_by, name);
941 if (r < 0)
942 return r;
943
944 return 0;
945 }
946
947 r = hashmap_ensure_allocated(&name->import->jobs, &string_hash_ops);
948 if (r < 0)
949 return r;
950
91f4347e 951 j = new0(DkrImportJob, 1);
72648326
LP
952 if (!j)
953 return -ENOMEM;
954
955 j->import = name->import;
956 j->type = type;
957 j->url = strdup(url);
958 if (!j->url)
959 return -ENOMEM;
960
961 r = set_ensure_allocated(&j->needed_by, &trivial_hash_ops);
962 if (r < 0)
963 return r;
964
965 r = curl_glue_make(&j->curl, j->url, j);
966 if (r < 0)
967 return r;
968
91f4347e 969 token = dkr_import_name_get_token(name);
72648326
LP
970 if (token)
971 t = strappenda("Authorization: Token ", token);
972 else
973 t = HEADER_TOKEN " true";
974
975 j->request_header = curl_slist_new("Accept: application/json", t, NULL);
976 if (!j->request_header)
977 return -ENOMEM;
978
979 if (curl_easy_setopt(j->curl, CURLOPT_HTTPHEADER, j->request_header) != CURLE_OK)
980 return -EIO;
981
91f4347e 982 if (curl_easy_setopt(j->curl, CURLOPT_WRITEFUNCTION, dkr_import_job_write_callback) != CURLE_OK)
72648326
LP
983 return -EIO;
984
985 if (curl_easy_setopt(j->curl, CURLOPT_WRITEDATA, j) != CURLE_OK)
986 return -EIO;
987
91f4347e 988 if (curl_easy_setopt(j->curl, CURLOPT_HEADERFUNCTION, dkr_import_job_header_callback) != CURLE_OK)
72648326
LP
989 return -EIO;
990
991 if (curl_easy_setopt(j->curl, CURLOPT_HEADERDATA, j) != CURLE_OK)
992 return -EIO;
993
994 r = curl_glue_add(name->import->glue, j->curl);
995 if (r < 0)
996 return r;
997
998 r = hashmap_put(name->import->jobs, j->url, j);
999 if (r < 0)
1000 return r;
1001
1002 r = set_put(j->needed_by, name);
1003 if (r < 0) {
1004 hashmap_remove(name->import->jobs, url);
1005 return r;
1006 }
1007
1008 *ret = j;
1009 j = NULL;
1010
1011 return 1;
1012}
1013
91f4347e 1014static int dkr_import_name_begin(DkrImportName *name) {
72648326
LP
1015 const char *url;
1016
1017 assert(name);
1018 assert(!name->job_images);
1019
ea1ae8c3 1020 url = strappenda(name->import->index_url, "/v1/repositories/", name->name, "/images");
72648326 1021
91f4347e 1022 return dkr_import_name_add_job(name, DKR_IMPORT_JOB_IMAGES, url, &name->job_images);
72648326
LP
1023}
1024
087682d1
LP
1025int dkr_import_new(
1026 DkrImport **import,
1027 sd_event *event,
1028 const char *index_url,
1029 const char *image_root,
1030 dkr_import_on_finished on_finished,
1031 void *userdata) {
1032
91f4347e 1033 _cleanup_(dkr_import_unrefp) DkrImport *i = NULL;
ea1ae8c3 1034 char *e;
72648326
LP
1035 int r;
1036
1037 assert(import);
ea1ae8c3 1038 assert(dkr_url_is_valid(index_url));
087682d1 1039 assert(image_root);
72648326 1040
91f4347e 1041 i = new0(DkrImport, 1);
72648326
LP
1042 if (!i)
1043 return -ENOMEM;
1044
1045 i->on_finished = on_finished;
1046 i->userdata = userdata;
1047
ea1ae8c3
LP
1048 i->index_url = strdup(index_url);
1049 if (!i->index_url)
1050 return -ENOMEM;
1051
087682d1
LP
1052 i->image_root = strdup(image_root);
1053 if (!i->image_root)
1054 return -ENOMEM;
1055
ea1ae8c3
LP
1056 e = endswith(i->index_url, "/");
1057 if (e)
1058 *e = 0;
1059
72648326
LP
1060 if (event)
1061 i->event = sd_event_ref(event);
1062 else {
1063 r = sd_event_default(&i->event);
1064 if (r < 0)
1065 return r;
1066 }
1067
1068 r = curl_glue_new(&i->glue, i->event);
1069 if (r < 0)
1070 return r;
1071
91f4347e 1072 i->glue->on_finished = dkr_import_curl_on_finished;
72648326
LP
1073 i->glue->userdata = i;
1074
1075 *import = i;
1076 i = NULL;
1077
1078 return 0;
1079}
1080
91f4347e
LP
1081DkrImport* dkr_import_unref(DkrImport *import) {
1082 DkrImportName *n;
1083 DkrImportJob *j;
72648326
LP
1084
1085 if (!import)
1086 return NULL;
1087
1088 while ((n = hashmap_steal_first(import->names)))
91f4347e 1089 dkr_import_name_unref(n);
72648326
LP
1090 hashmap_free(import->names);
1091
1092 while ((j = hashmap_steal_first(import->jobs)))
91f4347e 1093 dkr_import_job_unref(j);
72648326
LP
1094 hashmap_free(import->jobs);
1095
1096 curl_glue_unref(import->glue);
1097 sd_event_unref(import->event);
1098
ea1ae8c3 1099 free(import->index_url);
087682d1 1100 free(import->image_root);
72648326 1101 free(import);
91f4347e 1102
72648326
LP
1103 return NULL;
1104}
1105
91f4347e
LP
1106int dkr_import_cancel(DkrImport *import, const char *name) {
1107 DkrImportName *n;
72648326
LP
1108
1109 assert(import);
1110 assert(name);
1111
1112 n = hashmap_remove(import->names, name);
1113 if (!n)
1114 return 0;
1115
91f4347e 1116 dkr_import_name_unref(n);
72648326
LP
1117 return 1;
1118}
1119
ea1ae8c3 1120int dkr_import_pull(DkrImport *import, const char *name, const char *tag, const char *local, bool force_local) {
91f4347e 1121 _cleanup_(dkr_import_name_unrefp) DkrImportName *n = NULL;
72648326
LP
1122 int r;
1123
1124 assert(import);
91f4347e
LP
1125 assert(dkr_name_is_valid(name));
1126 assert(dkr_tag_is_valid(tag));
72648326
LP
1127 assert(!local || machine_name_is_valid(local));
1128
1129 if (hashmap_get(import->names, name))
1130 return -EEXIST;
1131
1132 r = hashmap_ensure_allocated(&import->names, &string_hash_ops);
1133 if (r < 0)
1134 return r;
1135
91f4347e 1136 n = new0(DkrImportName, 1);
72648326
LP
1137 if (!n)
1138 return -ENOMEM;
1139
1140 n->import = import;
1141
1142 n->name = strdup(name);
1143 if (!n->name)
1144 return -ENOMEM;
1145
1146 n->tag = strdup(tag);
1147 if (!n->tag)
1148 return -ENOMEM;
1149
1150 if (local) {
1151 n->local = strdup(local);
1152 if (!n->local)
1153 return -ENOMEM;
1154 n->force_local = force_local;
1155 }
1156
0c7bf33a 1157 r = hashmap_put(import->names, n->name, n);
72648326
LP
1158 if (r < 0)
1159 return r;
1160
91f4347e 1161 r = dkr_import_name_begin(n);
72648326 1162 if (r < 0) {
91f4347e 1163 dkr_import_cancel(import, n->name);
72648326
LP
1164 n = NULL;
1165 return r;
1166 }
1167
1168 n = NULL;
72648326
LP
1169 return 0;
1170}
1171
91f4347e 1172bool dkr_name_is_valid(const char *name) {
72648326
LP
1173 const char *slash, *p;
1174
1175 if (isempty(name))
1176 return false;
1177
1178 slash = strchr(name, '/');
1179 if (!slash)
1180 return false;
1181
1182 if (!filename_is_valid(slash + 1))
1183 return false;
1184
1185 p = strndupa(name, slash - name);
1186 if (!filename_is_valid(p))
1187 return false;
1188
1189 return true;
1190}
1191
91f4347e 1192bool dkr_id_is_valid(const char *id) {
72648326
LP
1193
1194 if (!filename_is_valid(id))
1195 return false;
1196
1197 if (!in_charset(id, "0123456789abcdef"))
1198 return false;
1199
1200 return true;
1201}
91f4347e
LP
1202
1203bool dkr_url_is_valid(const char *url) {
ea1ae8c3
LP
1204 if (isempty(url))
1205 return false;
91f4347e
LP
1206
1207 if (!startswith(url, "http://") &&
1208 !startswith(url, "https://"))
1209 return false;
1210
1211 return ascii_is_valid(url);
1212}