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