chiark / gitweb /
import: minor improvements to dkr importer
[elogind.git] / src / import / import-dkr.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2014 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <curl/curl.h>
23 #include <sys/prctl.h>
24
25 #include "hashmap.h"
26 #include "set.h"
27 #include "json.h"
28 #include "strv.h"
29 #include "curl-util.h"
30 #include "import-dkr.h"
31 #include "btrfs-util.h"
32 #include "aufs-util.h"
33 #include "utf8.h"
34
35 /* TODO:
36   - convert json bits
37   - man page
38   - fall back to btrfs loop pool device
39 */
40
41 typedef struct DkrImportJob DkrImportJob;
42 typedef struct DkrImportName DkrImportName;
43
44 typedef 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;
51
52 struct DkrImportJob {
53         DkrImport *import;
54         DkrImportJobType type;
55         bool done;
56
57         char *url;
58
59         Set *needed_by; /* DkrImport Name objects */
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
76 struct DkrImportName {
77         DkrImport *import;
78
79         char *name;
80         char *tag;
81         char *id;
82         char *local;
83
84         DkrImportJob *job_images, *job_tags, *job_ancestry, *job_json, *job_layer;
85
86         char **ancestry;
87         unsigned current_ancestry;
88
89         bool force_local;
90 };
91
92 struct DkrImport {
93         sd_event *event;
94         CurlGlue *glue;
95
96         char *index_url;
97
98         Hashmap *names;
99         Hashmap *jobs;
100
101         dkr_import_on_finished on_finished;
102         void *userdata;
103
104         bool finished;
105 };
106
107 #define PROTOCOL_PREFIX "https://"
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
115 static int dkr_import_name_add_job(DkrImportName *name, DkrImportJobType type, const char *url, DkrImportJob **ret);
116
117 static DkrImportJob *dkr_import_job_unref(DkrImportJob *job) {
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
150 static DkrImportName *dkr_import_name_unref(DkrImportName *name) {
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
180 DEFINE_TRIVIAL_CLEANUP_FUNC(DkrImportJob*, dkr_import_job_unref);
181 DEFINE_TRIVIAL_CLEANUP_FUNC(DkrImportName*, dkr_import_name_unref);
182
183 static void dkr_import_finish(DkrImport *import, int error) {
184         assert(import);
185
186         if (import->finished)
187                 return;
188
189         import->finished = true;
190
191         if (import->on_finished)
192                 import->on_finished(import, error, import->userdata);
193         else
194                 sd_event_exit(import->event, error);
195 }
196
197 static 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
230         if (!dkr_id_is_valid(id))
231                 return -EBADMSG;
232
233         *ret = id;
234         id = NULL;
235
236         return 0;
237 }
238
239 static 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) {
284                                 if (!dkr_id_is_valid(str))
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
336 static const char *dkr_import_name_current_layer(DkrImportName *name) {
337         assert(name);
338
339         if (strv_isempty(name->ancestry))
340                 return NULL;
341
342         return name->ancestry[name->current_ancestry];
343 }
344
345 static const char *dkr_import_name_current_base_layer(DkrImportName *name) {
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
357 static char** dkr_import_name_get_registries(DkrImportName *name) {
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
372 static const char*dkr_import_name_get_token(DkrImportName *name) {
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
384 static void dkr_import_name_maybe_finish(DkrImportName *name) {
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
401         if (dkr_import_name_current_layer(name))
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);
410                 q = strappenda("/var/lib/container/.dkr-", name->id);
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");
420                         dkr_import_finish(name->import, r);
421                         return;
422                 }
423
424                 log_info("Created new local image %s.", p);
425         }
426
427         dkr_import_finish(name->import, 0);
428 }
429
430 static int dkr_import_job_run_tar(DkrImportJob *job) {
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
517 static int dkr_import_name_pull_layer(DkrImportName *name) {
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 (;;) {
531                 layer = dkr_import_name_current_layer(name);
532                 if (!layer) {
533                         dkr_import_name_maybe_finish(name);
534                         return 0;
535                 }
536
537                 path = strjoin("/var/lib/container/.dkr-", layer, NULL);
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
556         rg = dkr_import_name_get_registries(name);
557         assert(rg && rg[0]);
558
559         url = strappenda(PROTOCOL_PREFIX, rg[0], "/v1/images/", layer, "/layer");
560         r = dkr_import_name_add_job(name, DKR_IMPORT_JOB_LAYER, url, &name->job_layer);
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
574         base = dkr_import_name_current_base_layer(name);
575         if (base) {
576                 const char *base_path;
577
578                 base_path = strappend("/var/lib/container/.dkr-", base);
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
593 static void dkr_import_name_job_finished(DkrImportName *name, DkrImportJob *job) {
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
608                 rg = dkr_import_name_get_registries(name);
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
619                 r = dkr_import_name_add_job(name, DKR_IMPORT_JOB_TAGS, url, &name->job_tags);
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
642                 rg = dkr_import_name_get_registries(name);
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");
648                 r = dkr_import_name_add_job(name, DKR_IMPORT_JOB_ANCESTRY, url, &name->job_ancestry);
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");
655                 r = dkr_import_name_add_job(name, DKR_IMPORT_JOB_JSON, url, &name->job_json);
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;
686                 r = dkr_import_name_pull_layer(name);
687                 if (r < 0)
688                         goto fail;
689
690         } else if (name->job_json == job) {
691
692                 dkr_import_name_maybe_finish(name);
693
694         } else if (name->job_layer == job) {
695
696                 name->current_ancestry ++;
697                 r = dkr_import_name_pull_layer(name);
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
706 fail:
707         dkr_import_finish(name->import, r);
708 }
709
710 static void dkr_import_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result) {
711         DkrImportJob *job = NULL;
712         CURLcode code;
713         DkrImportName *n;
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 || job->done)
722                 return;
723
724         job->done = true;
725
726         if (result != CURLE_OK) {
727                 log_error("Transfer failed: %s", curl_easy_strerror(result));
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
749         case DKR_IMPORT_JOB_LAYER: {
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_set_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)
803                 dkr_import_name_job_finished(n, job);
804
805         return;
806
807 fail:
808         dkr_import_finish(job->import, r);
809 }
810
811 static size_t dkr_import_job_write_callback(void *contents, size_t size, size_t nmemb, void *userdata) {
812         DkrImportJob *j = userdata;
813         size_t sz = size * nmemb;
814         char *p;
815         int r;
816
817         assert(contents);
818         assert(j);
819
820         if (j->done) {
821                 r = -ESTALE;
822                 goto fail;
823         }
824
825         if (j->tar_stream) {
826                 size_t l;
827
828                 l = fwrite(contents, size, nmemb, j->tar_stream);
829                 if (l != nmemb) {
830                         r = log_error_errno(errno, "Failed to write to tar: %m");
831                         goto fail;
832                 }
833
834                 return l;
835         }
836
837         if (j->payload_size + sz > PAYLOAD_MAX) {
838                 log_error("Payload too large.");
839                 r = -EFBIG;
840                 goto fail;
841         }
842
843         p = realloc(j->payload, j->payload_size + sz);
844         if (!p) {
845                 r = log_oom();
846                 goto fail;
847         }
848
849         memcpy(p + j->payload_size, contents, sz);
850         j->payload_size += sz;
851         j->payload = p;
852
853         r = dkr_import_job_run_tar(j);
854         if (r < 0)
855                 goto fail;
856
857         return sz;
858
859 fail:
860         dkr_import_finish(j->import, r);
861         return 0;
862 }
863
864 static size_t dkr_import_job_header_callback(void *contents, size_t size, size_t nmemb, void *userdata) {
865         _cleanup_free_ char *registry = NULL;
866         size_t sz = size * nmemb;
867         DkrImportJob *j = userdata;
868         char *token;
869         int r;
870
871         assert(contents);
872         assert(j);
873
874         if (j->done) {
875                 r = -ESTALE;
876                 goto fail;
877         }
878
879         r = curl_header_strdup(contents, sz, HEADER_TOKEN, &token);
880         if (r < 0) {
881                 log_oom();
882                 goto fail;
883         }
884         if (r > 0) {
885                 free(j->response_token);
886                 j->response_token = token;
887         }
888
889         r = curl_header_strdup(contents, sz, HEADER_REGISTRY, &registry);
890         if (r < 0) {
891                 log_oom();
892                 goto fail;
893         }
894         if (r > 0) {
895                 char **l, **i;
896
897                 l = strv_split(registry, ",");
898                 if (!l) {
899                         r = log_oom();
900                         goto fail;
901                 }
902
903                 STRV_FOREACH(i, l) {
904                         if (!hostname_is_valid(*i)) {
905                                 log_error("Registry hostname is not valid.");
906                                 strv_free(l);
907                                 r = -EBADMSG;
908                                 goto fail;
909                         }
910                 }
911
912                 strv_free(j->response_registries);
913                 j->response_registries = l;
914         }
915
916         return sz;
917
918 fail:
919         dkr_import_finish(j->import, r);
920         return 0;
921 }
922
923 static int dkr_import_name_add_job(DkrImportName *name, DkrImportJobType type, const char *url, DkrImportJob **ret) {
924         _cleanup_(dkr_import_job_unrefp) DkrImportJob *j = NULL;
925         DkrImportJob *f = NULL;
926         const char *t, *token;
927         int r;
928
929         assert(name);
930         assert(url);
931         assert(ret);
932
933         log_info("Getting %s.", url);
934         f = hashmap_get(name->import->jobs, url);
935         if (f) {
936                 if (f->type != type)
937                         return -EINVAL;
938
939                 r = set_put(f->needed_by, name);
940                 if (r < 0)
941                         return r;
942
943                 return 0;
944         }
945
946         r = hashmap_ensure_allocated(&name->import->jobs, &string_hash_ops);
947         if (r < 0)
948                 return r;
949
950         j = new0(DkrImportJob, 1);
951         if (!j)
952                 return -ENOMEM;
953
954         j->import = name->import;
955         j->type = type;
956         j->url = strdup(url);
957         if (!j->url)
958                 return -ENOMEM;
959
960         r = set_ensure_allocated(&j->needed_by, &trivial_hash_ops);
961         if (r < 0)
962                 return r;
963
964         r = curl_glue_make(&j->curl, j->url, j);
965         if (r < 0)
966                 return r;
967
968         token = dkr_import_name_get_token(name);
969         if (token)
970                 t = strappenda("Authorization: Token ", token);
971         else
972                 t = HEADER_TOKEN " true";
973
974         j->request_header = curl_slist_new("Accept: application/json", t, NULL);
975         if (!j->request_header)
976                 return -ENOMEM;
977
978         if (curl_easy_setopt(j->curl, CURLOPT_HTTPHEADER, j->request_header) != CURLE_OK)
979                 return -EIO;
980
981         if (curl_easy_setopt(j->curl, CURLOPT_WRITEFUNCTION, dkr_import_job_write_callback) != CURLE_OK)
982                 return -EIO;
983
984         if (curl_easy_setopt(j->curl, CURLOPT_WRITEDATA, j) != CURLE_OK)
985                 return -EIO;
986
987         if (curl_easy_setopt(j->curl, CURLOPT_HEADERFUNCTION, dkr_import_job_header_callback) != CURLE_OK)
988                 return -EIO;
989
990         if (curl_easy_setopt(j->curl, CURLOPT_HEADERDATA, j) != CURLE_OK)
991                 return -EIO;
992
993         r = curl_glue_add(name->import->glue, j->curl);
994         if (r < 0)
995                 return r;
996
997         r = hashmap_put(name->import->jobs, j->url, j);
998         if (r < 0)
999                 return r;
1000
1001         r = set_put(j->needed_by, name);
1002         if (r < 0) {
1003                 hashmap_remove(name->import->jobs, url);
1004                 return r;
1005         }
1006
1007         *ret = j;
1008         j = NULL;
1009
1010         return 1;
1011 }
1012
1013 static int dkr_import_name_begin(DkrImportName *name) {
1014         const char *url;
1015
1016         assert(name);
1017         assert(!name->job_images);
1018
1019         url = strappenda(name->import->index_url, "/v1/repositories/", name->name, "/images");
1020
1021         return dkr_import_name_add_job(name, DKR_IMPORT_JOB_IMAGES, url, &name->job_images);
1022 }
1023
1024 int dkr_import_new(DkrImport **import, sd_event *event, const char *index_url, dkr_import_on_finished on_finished, void *userdata) {
1025         _cleanup_(dkr_import_unrefp) DkrImport *i = NULL;
1026         char *e;
1027         int r;
1028
1029         assert(import);
1030         assert(dkr_url_is_valid(index_url));
1031
1032         i = new0(DkrImport, 1);
1033         if (!i)
1034                 return -ENOMEM;
1035
1036         i->on_finished = on_finished;
1037         i->userdata = userdata;
1038
1039         i->index_url = strdup(index_url);
1040         if (!i->index_url)
1041                 return -ENOMEM;
1042
1043         e = endswith(i->index_url, "/");
1044         if (e)
1045                 *e = 0;
1046
1047         if (event)
1048                 i->event = sd_event_ref(event);
1049         else {
1050                 r = sd_event_default(&i->event);
1051                 if (r < 0)
1052                         return r;
1053         }
1054
1055         r = curl_glue_new(&i->glue, i->event);
1056         if (r < 0)
1057                 return r;
1058
1059         i->glue->on_finished = dkr_import_curl_on_finished;
1060         i->glue->userdata = i;
1061
1062         *import = i;
1063         i = NULL;
1064
1065         return 0;
1066 }
1067
1068 DkrImport* dkr_import_unref(DkrImport *import) {
1069         DkrImportName *n;
1070         DkrImportJob *j;
1071
1072         if (!import)
1073                 return NULL;
1074
1075         while ((n = hashmap_steal_first(import->names)))
1076                dkr_import_name_unref(n);
1077         hashmap_free(import->names);
1078
1079         while ((j = hashmap_steal_first(import->jobs)))
1080                 dkr_import_job_unref(j);
1081         hashmap_free(import->jobs);
1082
1083         curl_glue_unref(import->glue);
1084         sd_event_unref(import->event);
1085
1086         free(import->index_url);
1087
1088         free(import);
1089
1090         return NULL;
1091 }
1092
1093 int dkr_import_cancel(DkrImport *import, const char *name) {
1094         DkrImportName *n;
1095
1096         assert(import);
1097         assert(name);
1098
1099         n = hashmap_remove(import->names, name);
1100         if (!n)
1101                 return 0;
1102
1103         dkr_import_name_unref(n);
1104         return 1;
1105 }
1106
1107 int dkr_import_pull(DkrImport *import, const char *name, const char *tag, const char *local, bool force_local) {
1108         _cleanup_(dkr_import_name_unrefp) DkrImportName *n = NULL;
1109         int r;
1110
1111         assert(import);
1112         assert(dkr_name_is_valid(name));
1113         assert(dkr_tag_is_valid(tag));
1114         assert(!local || machine_name_is_valid(local));
1115
1116         if (hashmap_get(import->names, name))
1117                 return -EEXIST;
1118
1119         r = hashmap_ensure_allocated(&import->names, &string_hash_ops);
1120         if (r < 0)
1121                 return r;
1122
1123         n = new0(DkrImportName, 1);
1124         if (!n)
1125                 return -ENOMEM;
1126
1127         n->import = import;
1128
1129         n->name = strdup(name);
1130         if (!n->name)
1131                 return -ENOMEM;
1132
1133         n->tag = strdup(tag);
1134         if (!n->tag)
1135                 return -ENOMEM;
1136
1137         if (local) {
1138                 n->local = strdup(local);
1139                 if (!n->local)
1140                         return -ENOMEM;
1141                 n->force_local = force_local;
1142         }
1143
1144         r = hashmap_put(import->names, n->name, n);
1145         if (r < 0)
1146                 return r;
1147
1148         r = dkr_import_name_begin(n);
1149         if (r < 0) {
1150                 dkr_import_cancel(import, n->name);
1151                 n = NULL;
1152                 return r;
1153         }
1154
1155         n = NULL;
1156         return 0;
1157 }
1158
1159 bool dkr_name_is_valid(const char *name) {
1160         const char *slash, *p;
1161
1162         if (isempty(name))
1163                 return false;
1164
1165         slash = strchr(name, '/');
1166         if (!slash)
1167                 return false;
1168
1169         if (!filename_is_valid(slash + 1))
1170                 return false;
1171
1172         p = strndupa(name, slash - name);
1173         if (!filename_is_valid(p))
1174                 return false;
1175
1176         return true;
1177 }
1178
1179 bool dkr_id_is_valid(const char *id) {
1180
1181         if (!filename_is_valid(id))
1182                 return false;
1183
1184         if (!in_charset(id, "0123456789abcdef"))
1185                 return false;
1186
1187         return true;
1188 }
1189
1190 bool dkr_url_is_valid(const char *url) {
1191         if (isempty(url))
1192                 return false;
1193
1194         if (!startswith(url, "http://") &&
1195             !startswith(url, "https://"))
1196                 return false;
1197
1198         return ascii_is_valid(url);
1199 }