chiark / gitweb /
machined: beef up machined image listing with creation/modification times of subvolumes
[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_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->tar_stream) {
821                 size_t l;
822
823                 l = fwrite(contents, size, nmemb, j->tar_stream);
824                 if (l != nmemb) {
825                         r = log_error_errno(errno, "Failed to write to tar: %m");
826                         goto fail;
827                 }
828
829                 return l;
830         }
831
832         if (j->payload_size + sz > PAYLOAD_MAX) {
833                 log_error("Payload too large.");
834                 r = -EFBIG;
835                 goto fail;
836         }
837
838         p = realloc(j->payload, j->payload_size + sz);
839         if (!p) {
840                 r = log_oom();
841                 goto fail;
842         }
843
844         memcpy(p + j->payload_size, contents, sz);
845         j->payload_size += sz;
846         j->payload = p;
847
848         r = dkr_import_job_run_tar(j);
849         if (r < 0)
850                 goto fail;
851
852         return sz;
853
854 fail:
855         dkr_import_finish(j->import, r);
856         return 0;
857 }
858
859 static size_t dkr_import_job_header_callback(void *contents, size_t size, size_t nmemb, void *userdata) {
860         _cleanup_free_ char *registry = NULL;
861         size_t sz = size * nmemb;
862         DkrImportJob *j = userdata;
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
908 fail:
909         dkr_import_finish(j->import, r);
910         return 0;
911 }
912
913 static 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;
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
940         j = new0(DkrImportJob, 1);
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
958         token = dkr_import_name_get_token(name);
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
971         if (curl_easy_setopt(j->curl, CURLOPT_WRITEFUNCTION, dkr_import_job_write_callback) != CURLE_OK)
972                 return -EIO;
973
974         if (curl_easy_setopt(j->curl, CURLOPT_WRITEDATA, j) != CURLE_OK)
975                 return -EIO;
976
977         if (curl_easy_setopt(j->curl, CURLOPT_HEADERFUNCTION, dkr_import_job_header_callback) != CURLE_OK)
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
1003 static int dkr_import_name_begin(DkrImportName *name) {
1004         const char *url;
1005
1006         assert(name);
1007         assert(!name->job_images);
1008
1009         url = strappenda(name->import->index_url, "/v1/repositories/", name->name, "/images");
1010
1011         return dkr_import_name_add_job(name, DKR_IMPORT_JOB_IMAGES, url, &name->job_images);
1012 }
1013
1014 int dkr_import_new(DkrImport **import, sd_event *event, const char *index_url, dkr_import_on_finished on_finished, void *userdata) {
1015         _cleanup_(dkr_import_unrefp) DkrImport *i = NULL;
1016         char *e;
1017         int r;
1018
1019         assert(import);
1020         assert(dkr_url_is_valid(index_url));
1021
1022         i = new0(DkrImport, 1);
1023         if (!i)
1024                 return -ENOMEM;
1025
1026         i->on_finished = on_finished;
1027         i->userdata = userdata;
1028
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
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
1049         i->glue->on_finished = dkr_import_curl_on_finished;
1050         i->glue->userdata = i;
1051
1052         *import = i;
1053         i = NULL;
1054
1055         return 0;
1056 }
1057
1058 DkrImport* dkr_import_unref(DkrImport *import) {
1059         DkrImportName *n;
1060         DkrImportJob *j;
1061
1062         if (!import)
1063                 return NULL;
1064
1065         while ((n = hashmap_steal_first(import->names)))
1066                dkr_import_name_unref(n);
1067         hashmap_free(import->names);
1068
1069         while ((j = hashmap_steal_first(import->jobs)))
1070                 dkr_import_job_unref(j);
1071         hashmap_free(import->jobs);
1072
1073         curl_glue_unref(import->glue);
1074         sd_event_unref(import->event);
1075
1076         free(import->index_url);
1077
1078         free(import);
1079
1080         return NULL;
1081 }
1082
1083 int dkr_import_cancel(DkrImport *import, const char *name) {
1084         DkrImportName *n;
1085
1086         assert(import);
1087         assert(name);
1088
1089         n = hashmap_remove(import->names, name);
1090         if (!n)
1091                 return 0;
1092
1093         dkr_import_name_unref(n);
1094         return 1;
1095 }
1096
1097 int dkr_import_pull(DkrImport *import, const char *name, const char *tag, const char *local, bool force_local) {
1098         _cleanup_(dkr_import_name_unrefp) DkrImportName *n = NULL;
1099         int r;
1100
1101         assert(import);
1102         assert(dkr_name_is_valid(name));
1103         assert(dkr_tag_is_valid(tag));
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
1113         n = new0(DkrImportName, 1);
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
1134         r = hashmap_put(import->names, n->name, n);
1135         if (r < 0)
1136                 return r;
1137
1138         r = dkr_import_name_begin(n);
1139         if (r < 0) {
1140                 dkr_import_cancel(import, n->name);
1141                 n = NULL;
1142                 return r;
1143         }
1144
1145         n = NULL;
1146         return 0;
1147 }
1148
1149 bool dkr_name_is_valid(const char *name) {
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
1169 bool dkr_id_is_valid(const char *id) {
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 }
1179
1180 bool dkr_url_is_valid(const char *url) {
1181         if (isempty(url))
1182                 return false;
1183
1184         if (!startswith(url, "http://") &&
1185             !startswith(url, "https://"))
1186                 return false;
1187
1188         return ascii_is_valid(url);
1189 }