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