chiark / gitweb /
e7dbe0f6626e75c9f3ee4b8cb47a31b825aef651
[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 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)
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_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->tar_stream) {
821                 size_t l;
822
823                 l = fwrite(contents, size, nmemb, j->tar_stream);
824                 if (l != nmemb) {
825                         r = -errno;
826                         goto fail;
827                 }
828
829                 return l;
830         }
831
832         if (j->payload_size + sz > PAYLOAD_MAX) {
833                 r = -EFBIG;
834                 goto fail;
835         }
836
837         p = realloc(j->payload, j->payload_size + sz);
838         if (!p) {
839                 r = -ENOMEM;
840                 goto fail;
841         }
842
843         memcpy(p + j->payload_size, contents, sz);
844         j->payload_size += sz;
845         j->payload = p;
846
847         r = dkr_import_job_run_tar(j);
848         if (r < 0)
849                 goto fail;
850
851         return sz;
852
853 fail:
854         dkr_import_finish(j->import, r);
855         return 0;
856 }
857
858 static size_t dkr_import_job_header_callback(void *contents, size_t size, size_t nmemb, void *userdata) {
859         _cleanup_free_ char *registry = NULL;
860         size_t sz = size * nmemb;
861         DkrImportJob *j = userdata;
862         char *token;
863         int r;
864
865         assert(contents);
866         assert(j);
867
868         r = curl_header_strdup(contents, sz, HEADER_TOKEN, &token);
869         if (r < 0) {
870                 log_oom();
871                 goto fail;
872         }
873         if (r > 0) {
874                 free(j->response_token);
875                 j->response_token = token;
876         }
877
878         r = curl_header_strdup(contents, sz, HEADER_REGISTRY, &registry);
879         if (r < 0) {
880                 log_oom();
881                 goto fail;
882         }
883         if (r > 0) {
884                 char **l, **i;
885
886                 l = strv_split(registry, ",");
887                 if (!l) {
888                         r = log_oom();
889                         goto fail;
890                 }
891
892                 STRV_FOREACH(i, l) {
893                         if (!hostname_is_valid(*i)) {
894                                 log_error("Registry hostname is not valid.");
895                                 strv_free(l);
896                                 r = -EBADMSG;
897                                 goto fail;
898                         }
899                 }
900
901                 strv_free(j->response_registries);
902                 j->response_registries = l;
903         }
904
905         return sz;
906
907 fail:
908         dkr_import_finish(j->import, r);
909         return 0;
910 }
911
912 static int dkr_import_name_add_job(DkrImportName *name, DkrImportJobType type, const char *url, DkrImportJob **ret) {
913         _cleanup_(dkr_import_job_unrefp) DkrImportJob *j = NULL;
914         DkrImportJob *f = NULL;
915         const char *t, *token;
916         int r;
917
918         assert(name);
919         assert(url);
920         assert(ret);
921
922         log_info("Getting %s.", url);
923         f = hashmap_get(name->import->jobs, url);
924         if (f) {
925                 if (f->type != type)
926                         return -EINVAL;
927
928                 r = set_put(f->needed_by, name);
929                 if (r < 0)
930                         return r;
931
932                 return 0;
933         }
934
935         r = hashmap_ensure_allocated(&name->import->jobs, &string_hash_ops);
936         if (r < 0)
937                 return r;
938
939         j = new0(DkrImportJob, 1);
940         if (!j)
941                 return -ENOMEM;
942
943         j->import = name->import;
944         j->type = type;
945         j->url = strdup(url);
946         if (!j->url)
947                 return -ENOMEM;
948
949         r = set_ensure_allocated(&j->needed_by, &trivial_hash_ops);
950         if (r < 0)
951                 return r;
952
953         r = curl_glue_make(&j->curl, j->url, j);
954         if (r < 0)
955                 return r;
956
957         token = dkr_import_name_get_token(name);
958         if (token)
959                 t = strappenda("Authorization: Token ", token);
960         else
961                 t = HEADER_TOKEN " true";
962
963         j->request_header = curl_slist_new("Accept: application/json", t, NULL);
964         if (!j->request_header)
965                 return -ENOMEM;
966
967         if (curl_easy_setopt(j->curl, CURLOPT_HTTPHEADER, j->request_header) != CURLE_OK)
968                 return -EIO;
969
970         if (curl_easy_setopt(j->curl, CURLOPT_WRITEFUNCTION, dkr_import_job_write_callback) != CURLE_OK)
971                 return -EIO;
972
973         if (curl_easy_setopt(j->curl, CURLOPT_WRITEDATA, j) != CURLE_OK)
974                 return -EIO;
975
976         if (curl_easy_setopt(j->curl, CURLOPT_HEADERFUNCTION, dkr_import_job_header_callback) != CURLE_OK)
977                 return -EIO;
978
979         if (curl_easy_setopt(j->curl, CURLOPT_HEADERDATA, j) != CURLE_OK)
980                 return -EIO;
981
982         r = curl_glue_add(name->import->glue, j->curl);
983         if (r < 0)
984                 return r;
985
986         r = hashmap_put(name->import->jobs, j->url, j);
987         if (r < 0)
988                 return r;
989
990         r = set_put(j->needed_by, name);
991         if (r < 0) {
992                 hashmap_remove(name->import->jobs, url);
993                 return r;
994         }
995
996         *ret = j;
997         j = NULL;
998
999         return 1;
1000 }
1001
1002 static int dkr_import_name_begin(DkrImportName *name) {
1003         const char *url;
1004
1005         assert(name);
1006         assert(!name->job_images);
1007
1008         url = strappenda(name->import->index_url, "/v1/repositories/", name->name, "/images");
1009
1010         return dkr_import_name_add_job(name, DKR_IMPORT_JOB_IMAGES, url, &name->job_images);
1011 }
1012
1013 int dkr_import_new(DkrImport **import, sd_event *event, const char *index_url, dkr_import_on_finished on_finished, void *userdata) {
1014         _cleanup_(dkr_import_unrefp) DkrImport *i = NULL;
1015         char *e;
1016         int r;
1017
1018         assert(import);
1019         assert(dkr_url_is_valid(index_url));
1020
1021         i = new0(DkrImport, 1);
1022         if (!i)
1023                 return -ENOMEM;
1024
1025         i->on_finished = on_finished;
1026         i->userdata = userdata;
1027
1028         i->index_url = strdup(index_url);
1029         if (!i->index_url)
1030                 return -ENOMEM;
1031
1032         e = endswith(i->index_url, "/");
1033         if (e)
1034                 *e = 0;
1035
1036         if (event)
1037                 i->event = sd_event_ref(event);
1038         else {
1039                 r = sd_event_default(&i->event);
1040                 if (r < 0)
1041                         return r;
1042         }
1043
1044         r = curl_glue_new(&i->glue, i->event);
1045         if (r < 0)
1046                 return r;
1047
1048         i->glue->on_finished = dkr_import_curl_on_finished;
1049         i->glue->userdata = i;
1050
1051         *import = i;
1052         i = NULL;
1053
1054         return 0;
1055 }
1056
1057 DkrImport* dkr_import_unref(DkrImport *import) {
1058         DkrImportName *n;
1059         DkrImportJob *j;
1060
1061         if (!import)
1062                 return NULL;
1063
1064         while ((n = hashmap_steal_first(import->names)))
1065                dkr_import_name_unref(n);
1066         hashmap_free(import->names);
1067
1068         while ((j = hashmap_steal_first(import->jobs)))
1069                 dkr_import_job_unref(j);
1070         hashmap_free(import->jobs);
1071
1072         curl_glue_unref(import->glue);
1073         sd_event_unref(import->event);
1074
1075         free(import->index_url);
1076
1077         free(import);
1078
1079         return NULL;
1080 }
1081
1082 int dkr_import_cancel(DkrImport *import, const char *name) {
1083         DkrImportName *n;
1084
1085         assert(import);
1086         assert(name);
1087
1088         n = hashmap_remove(import->names, name);
1089         if (!n)
1090                 return 0;
1091
1092         dkr_import_name_unref(n);
1093         return 1;
1094 }
1095
1096 int dkr_import_pull(DkrImport *import, const char *name, const char *tag, const char *local, bool force_local) {
1097         _cleanup_(dkr_import_name_unrefp) DkrImportName *n = NULL;
1098         int r;
1099
1100         assert(import);
1101         assert(dkr_name_is_valid(name));
1102         assert(dkr_tag_is_valid(tag));
1103         assert(!local || machine_name_is_valid(local));
1104
1105         if (hashmap_get(import->names, name))
1106                 return -EEXIST;
1107
1108         r = hashmap_ensure_allocated(&import->names, &string_hash_ops);
1109         if (r < 0)
1110                 return r;
1111
1112         n = new0(DkrImportName, 1);
1113         if (!n)
1114                 return -ENOMEM;
1115
1116         n->import = import;
1117
1118         n->name = strdup(name);
1119         if (!n->name)
1120                 return -ENOMEM;
1121
1122         n->tag = strdup(tag);
1123         if (!n->tag)
1124                 return -ENOMEM;
1125
1126         if (local) {
1127                 n->local = strdup(local);
1128                 if (!n->local)
1129                         return -ENOMEM;
1130                 n->force_local = force_local;
1131         }
1132
1133         r = hashmap_put(import->names, name, n);
1134         if (r < 0)
1135                 return r;
1136
1137         r = dkr_import_name_begin(n);
1138         if (r < 0) {
1139                 dkr_import_cancel(import, n->name);
1140                 n = NULL;
1141                 return r;
1142         }
1143
1144         n = NULL;
1145         return 0;
1146 }
1147
1148 bool dkr_name_is_valid(const char *name) {
1149         const char *slash, *p;
1150
1151         if (isempty(name))
1152                 return false;
1153
1154         slash = strchr(name, '/');
1155         if (!slash)
1156                 return false;
1157
1158         if (!filename_is_valid(slash + 1))
1159                 return false;
1160
1161         p = strndupa(name, slash - name);
1162         if (!filename_is_valid(p))
1163                 return false;
1164
1165         return true;
1166 }
1167
1168 bool dkr_id_is_valid(const char *id) {
1169
1170         if (!filename_is_valid(id))
1171                 return false;
1172
1173         if (!in_charset(id, "0123456789abcdef"))
1174                 return false;
1175
1176         return true;
1177 }
1178
1179 bool dkr_url_is_valid(const char *url) {
1180         if (isempty(url))
1181                 return false;
1182
1183         if (!startswith(url, "http://") &&
1184             !startswith(url, "https://"))
1185                 return false;
1186
1187         return ascii_is_valid(url);
1188 }