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