chiark / gitweb /
network: dhcp - split out the duid structure into a new header file
[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 "set.h"
26 #include "json.h"
27 #include "strv.h"
28 #include "btrfs-util.h"
29 #include "utf8.h"
30 #include "mkdir.h"
31 #include "curl-util.h"
32 #include "aufs-util.h"
33 #include "import-util.h"
34 #include "import-job.h"
35 #include "import-dkr.h"
36
37 struct DkrImport {
38         sd_event *event;
39         CurlGlue *glue;
40
41         char *index_url;
42         char *image_root;
43
44         ImportJob *images_job;
45         ImportJob *tags_job;
46         ImportJob *ancestry_job;
47         ImportJob *json_job;
48         ImportJob *layer_job;
49
50         char *name;
51         char *tag;
52         char *id;
53
54         char *response_token;
55         char **response_registries;
56
57         char **ancestry;
58         unsigned current_ancestry;
59
60         DkrImportFinished on_finished;
61         void *userdata;
62
63         char *local;
64         bool force_local;
65
66         char *temp_path;
67         char *final_path;
68
69         pid_t tar_pid;
70 };
71
72 #define PROTOCOL_PREFIX "https://"
73
74 #define HEADER_TOKEN "X-Do" /* the HTTP header for the auth token */ "cker-Token:"
75 #define HEADER_REGISTRY "X-Do" /*the HTTP header for the registry */ "cker-Endpoints:"
76
77 #define LAYERS_MAX 2048
78
79 static void dkr_import_job_on_finished(ImportJob *j);
80
81 DkrImport* dkr_import_unref(DkrImport *i) {
82         if (!i)
83                 return NULL;
84
85         if (i->tar_pid > 1) {
86                 (void) kill_and_sigcont(i->tar_pid, SIGKILL);
87                 (void) wait_for_terminate(i->tar_pid, NULL);
88         }
89
90         import_job_unref(i->images_job);
91         import_job_unref(i->tags_job);
92         import_job_unref(i->ancestry_job);
93         import_job_unref(i->json_job);
94         import_job_unref(i->layer_job);
95
96         curl_glue_unref(i->glue);
97         sd_event_unref(i->event);
98
99         if (i->temp_path) {
100                 (void) btrfs_subvol_remove(i->temp_path);
101                 (void) rm_rf_dangerous(i->temp_path, false, true, false);
102                 free(i->temp_path);
103         }
104
105         free(i->name);
106         free(i->tag);
107         free(i->id);
108         free(i->response_token);
109         free(i->response_registries);
110         strv_free(i->ancestry);
111         free(i->final_path);
112         free(i->index_url);
113         free(i->image_root);
114         free(i->local);
115         free(i);
116
117         return NULL;
118 }
119
120 int dkr_import_new(
121                 DkrImport **ret,
122                 sd_event *event,
123                 const char *index_url,
124                 const char *image_root,
125                 DkrImportFinished on_finished,
126                 void *userdata) {
127
128         _cleanup_(dkr_import_unrefp) DkrImport *i = NULL;
129         char *e;
130         int r;
131
132         assert(ret);
133         assert(index_url);
134
135         if (!http_url_is_valid(index_url))
136                 return -EINVAL;
137
138         i = new0(DkrImport, 1);
139         if (!i)
140                 return -ENOMEM;
141
142         i->on_finished = on_finished;
143         i->userdata = userdata;
144
145         i->image_root = strdup(image_root ?: "/var/lib/machines");
146         if (!i->image_root)
147                 return -ENOMEM;
148
149         i->index_url = strdup(index_url);
150         if (!i->index_url)
151                 return -ENOMEM;
152
153         e = endswith(i->index_url, "/");
154         if (e)
155                 *e = 0;
156
157         if (event)
158                 i->event = sd_event_ref(event);
159         else {
160                 r = sd_event_default(&i->event);
161                 if (r < 0)
162                         return r;
163         }
164
165         r = curl_glue_new(&i->glue, i->event);
166         if (r < 0)
167                 return r;
168
169         i->glue->on_finished = import_job_curl_on_finished;
170         i->glue->userdata = i;
171
172         *ret = i;
173         i = NULL;
174
175         return 0;
176 }
177
178 static int parse_id(const void *payload, size_t size, char **ret) {
179         _cleanup_free_ char *buf = NULL, *id = NULL, *other = NULL;
180         union json_value v = {};
181         void *json_state = NULL;
182         const char *p;
183         int t;
184
185         assert(payload);
186         assert(ret);
187
188         if (size <= 0)
189                 return -EBADMSG;
190
191         if (memchr(payload, 0, size))
192                 return -EBADMSG;
193
194         buf = strndup(payload, size);
195         if (!buf)
196                 return -ENOMEM;
197
198         p = buf;
199         t = json_tokenize(&p, &id, &v, &json_state, NULL);
200         if (t < 0)
201                 return t;
202         if (t != JSON_STRING)
203                 return -EBADMSG;
204
205         t = json_tokenize(&p, &other, &v, &json_state, NULL);
206         if (t < 0)
207                 return t;
208         if (t != JSON_END)
209                 return -EBADMSG;
210
211         if (!dkr_id_is_valid(id))
212                 return -EBADMSG;
213
214         *ret = id;
215         id = NULL;
216
217         return 0;
218 }
219
220 static int parse_ancestry(const void *payload, size_t size, char ***ret) {
221         _cleanup_free_ char *buf = NULL;
222         void *json_state = NULL;
223         const char *p;
224         enum {
225                 STATE_BEGIN,
226                 STATE_ITEM,
227                 STATE_COMMA,
228                 STATE_END,
229         } state = STATE_BEGIN;
230         _cleanup_strv_free_ char **l = NULL;
231         size_t n = 0, allocated = 0;
232
233         if (size <= 0)
234                 return -EBADMSG;
235
236         if (memchr(payload, 0, size))
237                 return -EBADMSG;
238
239         buf = strndup(payload, size);
240         if (!buf)
241                 return -ENOMEM;
242
243         p = buf;
244         for (;;) {
245                 _cleanup_free_ char *str;
246                 union json_value v = {};
247                 int t;
248
249                 t = json_tokenize(&p, &str, &v, &json_state, NULL);
250                 if (t < 0)
251                         return t;
252
253                 switch (state) {
254
255                 case STATE_BEGIN:
256                         if (t == JSON_ARRAY_OPEN)
257                                 state = STATE_ITEM;
258                         else
259                                 return -EBADMSG;
260
261                         break;
262
263                 case STATE_ITEM:
264                         if (t == JSON_STRING) {
265                                 if (!dkr_id_is_valid(str))
266                                         return -EBADMSG;
267
268                                 if (n+1 > LAYERS_MAX)
269                                         return -EFBIG;
270
271                                 if (!GREEDY_REALLOC(l, allocated, n + 2))
272                                         return -ENOMEM;
273
274                                 l[n++] = str;
275                                 str = NULL;
276                                 l[n] = NULL;
277
278                                 state = STATE_COMMA;
279
280                         } else if (t == JSON_ARRAY_CLOSE)
281                                 state = STATE_END;
282                         else
283                                 return -EBADMSG;
284
285                         break;
286
287                 case STATE_COMMA:
288                         if (t == JSON_COMMA)
289                                 state = STATE_ITEM;
290                         else if (t == JSON_ARRAY_CLOSE)
291                                 state = STATE_END;
292                         else
293                                 return -EBADMSG;
294                         break;
295
296                 case STATE_END:
297                         if (t == JSON_END) {
298
299                                 if (strv_isempty(l))
300                                         return -EBADMSG;
301
302                                 if (!strv_is_uniq(l))
303                                         return -EBADMSG;
304
305                                 l = strv_reverse(l);
306
307                                 *ret = l;
308                                 l = NULL;
309                                 return 0;
310                         } else
311                                 return -EBADMSG;
312                 }
313
314         }
315 }
316
317 static const char *dkr_import_current_layer(DkrImport *i) {
318         assert(i);
319
320         if (strv_isempty(i->ancestry))
321                 return NULL;
322
323         return i->ancestry[i->current_ancestry];
324 }
325
326 static const char *dkr_import_current_base_layer(DkrImport *i) {
327         assert(i);
328
329         if (strv_isempty(i->ancestry))
330                 return NULL;
331
332         if (i->current_ancestry <= 0)
333                 return NULL;
334
335         return i->ancestry[i->current_ancestry-1];
336 }
337
338 static int dkr_import_add_token(DkrImport *i, ImportJob *j) {
339         const char *t;
340
341         assert(i);
342         assert(j);
343
344         if (i->response_token)
345                 t = strappenda("Authorization: Token ", i->response_token);
346         else
347                 t = HEADER_TOKEN " true";
348
349         j->request_header = curl_slist_new("Accept: application/json", t, NULL);
350         if (!j->request_header)
351                 return -ENOMEM;
352
353         return 0;
354 }
355
356 static bool dkr_import_is_done(DkrImport *i) {
357         assert(i);
358         assert(i->images_job);
359
360         if (i->images_job->state != IMPORT_JOB_DONE)
361                 return false;
362
363         if (!i->tags_job || i->tags_job->state != IMPORT_JOB_DONE)
364                 return false;
365
366         if (!i->ancestry_job || i->ancestry_job->state != IMPORT_JOB_DONE)
367                 return false;
368
369         if (!i->json_job || i->json_job->state != IMPORT_JOB_DONE)
370                 return false;
371
372         if (i->layer_job && i->layer_job->state != IMPORT_JOB_DONE)
373                 return false;
374
375         if (dkr_import_current_layer(i))
376                 return false;
377
378         return true;
379 }
380
381 static int dkr_import_make_local_copy(DkrImport *i) {
382         int r;
383
384         assert(i);
385
386         if (!i->local)
387                 return 0;
388
389         if (!i->final_path) {
390                 i->final_path = strjoin(i->image_root, "/.dkr-", i->id, NULL);
391                 if (!i->final_path)
392                         return log_oom();
393         }
394
395         r = import_make_local_copy(i->final_path, i->image_root, i->local, i->force_local);
396         if (r < 0)
397                 return r;
398
399         return 0;
400 }
401
402 static int dkr_import_job_on_open_disk(ImportJob *j) {
403         _cleanup_close_pair_ int pipefd[2] = { -1, -1 };
404         const char *base;
405         DkrImport *i;
406         int r;
407
408         assert(j);
409         assert(j->userdata);
410
411         i = j->userdata;
412         assert(i->layer_job == j);
413         assert(i->final_path);
414         assert(!i->temp_path);
415         assert(i->tar_pid <= 0);
416
417         r = tempfn_random(i->final_path, &i->temp_path);
418         if (r < 0)
419                 return log_oom();
420
421         mkdir_parents_label(i->temp_path, 0700);
422
423         base = dkr_import_current_base_layer(i);
424         if (base) {
425                 const char *base_path;
426
427                 base_path = strappenda(i->image_root, "/.dkr-", base);
428                 r = btrfs_subvol_snapshot(base_path, i->temp_path, false, true);
429         } else
430                 r = btrfs_subvol_make(i->temp_path);
431         if (r < 0)
432                 return log_error_errno(r, "Failed to make btrfs subvolume %s: %m", i->temp_path);
433
434         if (pipe2(pipefd, O_CLOEXEC) < 0)
435                 return log_error_errno(errno, "Failed to create pipe for tar: %m");
436
437         i->tar_pid = fork();
438         if (i->tar_pid < 0)
439                 return log_error_errno(errno, "Failed to fork off tar: %m");
440         if (i->tar_pid == 0) {
441                 int null_fd;
442
443                 /* Child */
444
445                 reset_all_signal_handlers();
446                 reset_signal_mask();
447                 assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0);
448
449                 pipefd[1] = safe_close(pipefd[1]);
450
451                 if (dup2(pipefd[0], STDIN_FILENO) != STDIN_FILENO) {
452                         log_error_errno(errno, "Failed to dup2() fd: %m");
453                         _exit(EXIT_FAILURE);
454                 }
455
456                 if (pipefd[0] != STDIN_FILENO)
457                         safe_close(pipefd[0]);
458
459                 null_fd = open("/dev/null", O_WRONLY|O_NOCTTY);
460                 if (null_fd < 0) {
461                         log_error_errno(errno, "Failed to open /dev/null: %m");
462                         _exit(EXIT_FAILURE);
463                 }
464
465                 if (dup2(null_fd, STDOUT_FILENO) != STDOUT_FILENO) {
466                         log_error_errno(errno, "Failed to dup2() fd: %m");
467                         _exit(EXIT_FAILURE);
468                 }
469
470                 if (null_fd != STDOUT_FILENO)
471                         safe_close(null_fd);
472
473                 execlp("tar", "tar", "--numeric-owner", "-C", i->temp_path, "-px", NULL);
474                 log_error_errno(errno, "Failed to execute tar: %m");
475                 _exit(EXIT_FAILURE);
476         }
477
478         pipefd[0] = safe_close(pipefd[0]);
479
480         j->disk_fd = pipefd[1];
481         pipefd[1] = -1;
482
483         return 0;
484 }
485
486 static int dkr_import_pull_layer(DkrImport *i) {
487         _cleanup_free_ char *path = NULL;
488         const char *url, *layer = NULL;
489         int r;
490
491         assert(i);
492         assert(!i->layer_job);
493         assert(!i->temp_path);
494         assert(!i->final_path);
495
496         for (;;) {
497                 layer = dkr_import_current_layer(i);
498                 if (!layer)
499                         return 0; /* no more layers */
500
501                 path = strjoin(i->image_root, "/.dkr-", layer, NULL);
502                 if (!path)
503                         return log_oom();
504
505                 if (laccess(path, F_OK) < 0) {
506                         if (errno == ENOENT)
507                                 break;
508
509                         return log_error_errno(errno, "Failed to check for container: %m");
510                 }
511
512                 log_info("Layer %s already exists, skipping.", layer);
513
514                 i->current_ancestry++;
515
516                 free(path);
517                 path = NULL;
518         }
519
520         log_info("Pulling layer %s...", layer);
521
522         i->final_path = path;
523         path = NULL;
524
525         url = strappenda(PROTOCOL_PREFIX, i->response_registries[0], "/v1/images/", layer, "/layer");
526         r = import_job_new(&i->layer_job, url, i->glue, i);
527         if (r < 0)
528                 return log_error_errno(r, "Failed to allocate layer job: %m");
529
530         r = dkr_import_add_token(i, i->layer_job);
531         if (r < 0)
532                 return log_oom();
533
534         i->layer_job->on_finished = dkr_import_job_on_finished;
535         i->layer_job->on_open_disk = dkr_import_job_on_open_disk;
536
537         r = import_job_begin(i->layer_job);
538         if (r < 0)
539                 return log_error_errno(r, "Failed to start layer job: %m");
540
541         return 0;
542 }
543
544 static void dkr_import_job_on_finished(ImportJob *j) {
545         DkrImport *i;
546         int r;
547
548         assert(j);
549         assert(j->userdata);
550
551         i = j->userdata;
552         if (j->error != 0) {
553                 if (j == i->images_job)
554                         log_error_errno(j->error, "Failed to retrieve images list. (Wrong index URL?)");
555                 else if (j == i->tags_job)
556                         log_error_errno(j->error, "Failed to retrieve tags list.");
557                 else if (j == i->ancestry_job)
558                         log_error_errno(j->error, "Failed to retrieve ancestry list.");
559                 else if (j == i->json_job)
560                         log_error_errno(j->error, "Failed to retrieve json data.");
561                 else
562                         log_error_errno(j->error, "Failed to retrieve layer data.");
563
564                 r = j->error;
565                 goto finish;
566         }
567
568         if (i->images_job == j) {
569                 const char *url;
570
571                 assert(!i->tags_job);
572                 assert(!i->ancestry_job);
573                 assert(!i->json_job);
574                 assert(!i->layer_job);
575
576                 if (strv_isempty(i->response_registries)) {
577                         r = -EBADMSG;
578                         log_error("Didn't get registry information.");
579                         goto finish;
580                 }
581
582                 log_info("Index lookup succeeded, directed to registry %s.", i->response_registries[0]);
583
584                 url = strappenda(PROTOCOL_PREFIX, i->response_registries[0], "/v1/repositories/", i->name, "/tags/", i->tag);
585                 r = import_job_new(&i->tags_job, url, i->glue, i);
586                 if (r < 0) {
587                         log_error_errno(r, "Failed to allocate tags job: %m");
588                         goto finish;
589                 }
590
591                 r = dkr_import_add_token(i, i->tags_job);
592                 if (r < 0) {
593                         log_oom();
594                         goto finish;
595                 }
596
597                 i->tags_job->on_finished = dkr_import_job_on_finished;
598
599                 r = import_job_begin(i->tags_job);
600                 if (r < 0) {
601                         log_error_errno(r, "Failed to start tags job: %m");
602                         goto finish;
603                 }
604
605         } else if (i->tags_job == j) {
606                 const char *url;
607                 char *id = NULL;
608
609                 assert(!i->ancestry_job);
610                 assert(!i->json_job);
611                 assert(!i->layer_job);
612
613                 r = parse_id(j->payload, j->payload_size, &id);
614                 if (r < 0) {
615                         log_error_errno(r, "Failed to parse JSON id.");
616                         goto finish;
617                 }
618
619                 free(i->id);
620                 i->id = id;
621
622                 log_info("Tag lookup succeeded, resolved to layer %s.", i->id);
623
624                 url = strappenda(PROTOCOL_PREFIX, i->response_registries[0], "/v1/images/", i->id, "/ancestry");
625                 r = import_job_new(&i->ancestry_job, url, i->glue, i);
626                 if (r < 0) {
627                         log_error_errno(r, "Failed to allocate ancestry job: %m");
628                         goto finish;
629                 }
630
631                 r = dkr_import_add_token(i, i->ancestry_job);
632                 if (r < 0) {
633                         log_oom();
634                         goto finish;
635                 }
636
637                 i->ancestry_job->on_finished = dkr_import_job_on_finished;
638
639                 url = strappenda(PROTOCOL_PREFIX, i->response_registries[0], "/v1/images/", i->id, "/json");
640                 r = import_job_new(&i->json_job, url, i->glue, i);
641                 if (r < 0) {
642                         log_error_errno(r, "Failed to allocate json job: %m");
643                         goto finish;
644                 }
645
646                 r = dkr_import_add_token(i, i->json_job);
647                 if (r < 0) {
648                         log_oom();
649                         goto finish;
650                 }
651
652                 i->json_job->on_finished = dkr_import_job_on_finished;
653
654                 r = import_job_begin(i->ancestry_job);
655                 if (r < 0) {
656                         log_error_errno(r, "Failed to start ancestry job: %m");
657                         goto finish;
658                 }
659
660                 r = import_job_begin(i->json_job);
661                 if (r < 0) {
662                         log_error_errno(r, "Failed to start json job: %m");
663                         goto finish;
664                 }
665
666         } else if (i->ancestry_job == j) {
667                 char **ancestry = NULL, **k;
668                 unsigned n;
669
670                 assert(!i->layer_job);
671
672                 r = parse_ancestry(j->payload, j->payload_size, &ancestry);
673                 if (r < 0) {
674                         log_error_errno(r, "Failed to parse JSON id.");
675                         goto finish;
676                 }
677
678                 n = strv_length(ancestry);
679                 if (n <= 0 || !streq(ancestry[n-1], i->id)) {
680                         log_error("Ancestry doesn't end in main layer.");
681                         strv_free(ancestry);
682                         r = -EBADMSG;
683                         goto finish;
684                 }
685
686                 log_info("Ancestor lookup succeeded, requires layers:\n");
687                 STRV_FOREACH(k, ancestry)
688                         log_info("\t%s", *k);
689
690                 strv_free(i->ancestry);
691                 i->ancestry = ancestry;
692
693                 i->current_ancestry = 0;
694                 r = dkr_import_pull_layer(i);
695                 if (r < 0)
696                         goto finish;
697
698         } else if (i->layer_job == j) {
699                 assert(i->temp_path);
700                 assert(i->final_path);
701
702                 j->disk_fd = safe_close(j->disk_fd);
703
704                 if (i->tar_pid > 0) {
705                         r = wait_for_terminate_and_warn("tar", i->tar_pid, true);
706                         i->tar_pid = 0;
707                         if (r < 0)
708                                 goto finish;
709                 }
710
711                 r = aufs_resolve(i->temp_path);
712                 if (r < 0) {
713                         log_error_errno(r, "Failed to resolve aufs whiteouts: %m");
714                         goto finish;
715                 }
716
717                 r = btrfs_subvol_set_read_only(i->temp_path, true);
718                 if (r < 0) {
719                         log_error_errno(r, "Failed to mark snapshort read-only: %m");
720                         goto finish;
721                 }
722
723                 if (rename(i->temp_path, i->final_path) < 0) {
724                         log_error_errno(errno, "Failed to rename snaphsot: %m");
725                         goto finish;
726                 }
727
728                 log_info("Completed writing to layer %s.", i->final_path);
729
730                 i->layer_job = import_job_unref(i->layer_job);
731                 free(i->temp_path);
732                 i->temp_path = NULL;
733                 free(i->final_path);
734                 i->final_path = NULL;
735
736                 i->current_ancestry ++;
737                 r = dkr_import_pull_layer(i);
738                 if (r < 0)
739                         goto finish;
740
741         } else if (i->json_job != j)
742                 assert_not_reached("Got finished event for unknown curl object");
743
744         if (!dkr_import_is_done(i))
745                 return;
746
747         r = dkr_import_make_local_copy(i);
748         if (r < 0)
749                 goto finish;
750
751         r = 0;
752
753 finish:
754         if (i->on_finished)
755                 i->on_finished(i, r, i->userdata);
756         else
757                 sd_event_exit(i->event, r);
758 }
759
760 static int dkr_import_job_on_header(ImportJob *j, const char *header, size_t sz)  {
761         _cleanup_free_ char *registry = NULL;
762         char *token;
763         DkrImport *i;
764         int r;
765
766         assert(j);
767         assert(j->userdata);
768
769         i = j->userdata;
770
771         r = curl_header_strdup(header, sz, HEADER_TOKEN, &token);
772         if (r < 0)
773                 return log_oom();
774         if (r > 0) {
775                 free(i->response_token);
776                 i->response_token = token;
777                 return 0;
778         }
779
780         r = curl_header_strdup(header, sz, HEADER_REGISTRY, &registry);
781         if (r < 0)
782                 return log_oom();
783         if (r > 0) {
784                 char **l, **k;
785
786                 l = strv_split(registry, ",");
787                 if (!l)
788                         return log_oom();
789
790                 STRV_FOREACH(k, l) {
791                         if (!hostname_is_valid(*k)) {
792                                 log_error("Registry hostname is not valid.");
793                                 strv_free(l);
794                                 return -EBADMSG;
795                         }
796                 }
797
798                 strv_free(i->response_registries);
799                 i->response_registries = l;
800         }
801
802         return 0;
803 }
804
805 int dkr_import_pull(DkrImport *i, const char *name, const char *tag, const char *local, bool force_local) {
806         const char *url;
807         int r;
808
809         assert(i);
810
811         if (!dkr_name_is_valid(name))
812                 return -EINVAL;
813
814         if (tag && !dkr_tag_is_valid(tag))
815                 return -EINVAL;
816
817         if (local && !machine_name_is_valid(local))
818                 return -EINVAL;
819
820         if (i->images_job)
821                 return -EBUSY;
822
823         if (!tag)
824                 tag = "latest";
825
826         r = free_and_strdup(&i->local, local);
827         if (r < 0)
828                 return r;
829         i->force_local = force_local;
830
831         r = free_and_strdup(&i->name, name);
832         if (r < 0)
833                 return r;
834         r = free_and_strdup(&i->tag, tag);
835         if (r < 0)
836                 return r;
837
838         url = strappenda(i->index_url, "/v1/repositories/", name, "/images");
839
840         r = import_job_new(&i->images_job, url, i->glue, i);
841         if (r < 0)
842                 return r;
843
844         r = dkr_import_add_token(i, i->images_job);
845         if (r < 0)
846                 return r;
847
848         i->images_job->on_finished = dkr_import_job_on_finished;
849         i->images_job->on_header = dkr_import_job_on_header;
850
851         return import_job_begin(i->images_job);
852 }
853
854 bool dkr_name_is_valid(const char *name) {
855         const char *slash, *p;
856
857         if (isempty(name))
858                 return false;
859
860         slash = strchr(name, '/');
861         if (!slash)
862                 return false;
863
864         if (!filename_is_valid(slash + 1))
865                 return false;
866
867         p = strndupa(name, slash - name);
868         if (!filename_is_valid(p))
869                 return false;
870
871         return true;
872 }
873
874 bool dkr_id_is_valid(const char *id) {
875
876         if (!filename_is_valid(id))
877                 return false;
878
879         if (!in_charset(id, "0123456789abcdef"))
880                 return false;
881
882         return true;
883 }