chiark / gitweb /
impot: minor cleanups
[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                         pipefd[0] = 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                         null_fd = safe_close(null_fd);
472
473                 fd_cloexec(STDIN_FILENO, false);
474                 fd_cloexec(STDOUT_FILENO, false);
475                 fd_cloexec(STDERR_FILENO, false);
476
477                 execlp("tar", "tar", "--numeric-owner", "-C", i->temp_path, "-px", NULL);
478                 log_error_errno(errno, "Failed to execute tar: %m");
479                 _exit(EXIT_FAILURE);
480         }
481
482         pipefd[0] = safe_close(pipefd[0]);
483
484         j->disk_fd = pipefd[1];
485         pipefd[1] = -1;
486
487         return 0;
488 }
489
490 static int dkr_import_pull_layer(DkrImport *i) {
491         _cleanup_free_ char *path = NULL;
492         const char *url, *layer = NULL;
493         int r;
494
495         assert(i);
496         assert(!i->layer_job);
497         assert(!i->temp_path);
498         assert(!i->final_path);
499
500         for (;;) {
501                 layer = dkr_import_current_layer(i);
502                 if (!layer)
503                         return 0; /* no more layers */
504
505                 path = strjoin(i->image_root, "/.dkr-", layer, NULL);
506                 if (!path)
507                         return log_oom();
508
509                 if (laccess(path, F_OK) < 0) {
510                         if (errno == ENOENT)
511                                 break;
512
513                         return log_error_errno(errno, "Failed to check for container: %m");
514                 }
515
516                 log_info("Layer %s already exists, skipping.", layer);
517
518                 i->current_ancestry++;
519
520                 free(path);
521                 path = NULL;
522         }
523
524         log_info("Pulling layer %s...", layer);
525
526         i->final_path = path;
527         path = NULL;
528
529         url = strappenda(PROTOCOL_PREFIX, i->response_registries[0], "/v1/images/", layer, "/layer");
530         r = import_job_new(&i->layer_job, url, i->glue, i);
531         if (r < 0)
532                 return log_error_errno(r, "Failed to allocate layer job: %m");
533
534         r = dkr_import_add_token(i, i->layer_job);
535         if (r < 0)
536                 return log_oom();
537
538         i->layer_job->on_finished = dkr_import_job_on_finished;
539         i->layer_job->on_open_disk = dkr_import_job_on_open_disk;
540
541         r = import_job_begin(i->layer_job);
542         if (r < 0)
543                 return log_error_errno(r, "Failed to start layer job: %m");
544
545         return 0;
546 }
547
548 static void dkr_import_job_on_finished(ImportJob *j) {
549         DkrImport *i;
550         int r;
551
552         assert(j);
553         assert(j->userdata);
554
555         i = j->userdata;
556         if (j->error != 0) {
557                 if (j == i->images_job)
558                         log_error_errno(j->error, "Failed to retrieve images list. (Wrong index URL?)");
559                 else if (j == i->tags_job)
560                         log_error_errno(j->error, "Failed to retrieve tags list.");
561                 else if (j == i->ancestry_job)
562                         log_error_errno(j->error, "Failed to retrieve ancestry list.");
563                 else if (j == i->json_job)
564                         log_error_errno(j->error, "Failed to retrieve json data.");
565                 else
566                         log_error_errno(j->error, "Failed to retrieve layer data.");
567
568                 r = j->error;
569                 goto finish;
570         }
571
572         if (i->images_job == j) {
573                 const char *url;
574
575                 assert(!i->tags_job);
576                 assert(!i->ancestry_job);
577                 assert(!i->json_job);
578                 assert(!i->layer_job);
579
580                 if (strv_isempty(i->response_registries)) {
581                         r = -EBADMSG;
582                         log_error("Didn't get registry information.");
583                         goto finish;
584                 }
585
586                 log_info("Index lookup succeeded, directed to registry %s.", i->response_registries[0]);
587
588                 url = strappenda(PROTOCOL_PREFIX, i->response_registries[0], "/v1/repositories/", i->name, "/tags/", i->tag);
589                 r = import_job_new(&i->tags_job, url, i->glue, i);
590                 if (r < 0) {
591                         log_error_errno(r, "Failed to allocate tags job: %m");
592                         goto finish;
593                 }
594
595                 r = dkr_import_add_token(i, i->tags_job);
596                 if (r < 0) {
597                         log_oom();
598                         goto finish;
599                 }
600
601                 i->tags_job->on_finished = dkr_import_job_on_finished;
602
603                 r = import_job_begin(i->tags_job);
604                 if (r < 0) {
605                         log_error_errno(r, "Failed to start tags job: %m");
606                         goto finish;
607                 }
608
609         } else if (i->tags_job == j) {
610                 const char *url;
611                 char *id = NULL;
612
613                 assert(!i->ancestry_job);
614                 assert(!i->json_job);
615                 assert(!i->layer_job);
616
617                 r = parse_id(j->payload, j->payload_size, &id);
618                 if (r < 0) {
619                         log_error_errno(r, "Failed to parse JSON id.");
620                         goto finish;
621                 }
622
623                 free(i->id);
624                 i->id = id;
625
626                 log_info("Tag lookup succeeded, resolved to layer %s.", i->id);
627
628                 url = strappenda(PROTOCOL_PREFIX, i->response_registries[0], "/v1/images/", i->id, "/ancestry");
629                 r = import_job_new(&i->ancestry_job, url, i->glue, i);
630                 if (r < 0) {
631                         log_error_errno(r, "Failed to allocate ancestry job: %m");
632                         goto finish;
633                 }
634
635                 r = dkr_import_add_token(i, i->ancestry_job);
636                 if (r < 0) {
637                         log_oom();
638                         goto finish;
639                 }
640
641                 i->ancestry_job->on_finished = dkr_import_job_on_finished;
642
643                 url = strappenda(PROTOCOL_PREFIX, i->response_registries[0], "/v1/images/", i->id, "/json");
644                 r = import_job_new(&i->json_job, url, i->glue, i);
645                 if (r < 0) {
646                         log_error_errno(r, "Failed to allocate json job: %m");
647                         goto finish;
648                 }
649
650                 r = dkr_import_add_token(i, i->json_job);
651                 if (r < 0) {
652                         log_oom();
653                         goto finish;
654                 }
655
656                 i->json_job->on_finished = dkr_import_job_on_finished;
657
658                 r = import_job_begin(i->ancestry_job);
659                 if (r < 0) {
660                         log_error_errno(r, "Failed to start ancestry job: %m");
661                         goto finish;
662                 }
663
664                 r = import_job_begin(i->json_job);
665                 if (r < 0) {
666                         log_error_errno(r, "Failed to start json job: %m");
667                         goto finish;
668                 }
669
670         } else if (i->ancestry_job == j) {
671                 char **ancestry = NULL, **k;
672                 unsigned n;
673
674                 assert(!i->layer_job);
675
676                 r = parse_ancestry(j->payload, j->payload_size, &ancestry);
677                 if (r < 0) {
678                         log_error_errno(r, "Failed to parse JSON id.");
679                         goto finish;
680                 }
681
682                 n = strv_length(ancestry);
683                 if (n <= 0 || !streq(ancestry[n-1], i->id)) {
684                         log_error("Ancestry doesn't end in main layer.");
685                         strv_free(ancestry);
686                         r = -EBADMSG;
687                         goto finish;
688                 }
689
690                 log_info("Ancestor lookup succeeded, requires layers:\n");
691                 STRV_FOREACH(k, ancestry)
692                         log_info("\t%s", *k);
693
694                 strv_free(i->ancestry);
695                 i->ancestry = ancestry;
696
697                 i->current_ancestry = 0;
698                 r = dkr_import_pull_layer(i);
699                 if (r < 0)
700                         goto finish;
701
702         } else if (i->layer_job == j) {
703                 assert(i->temp_path);
704                 assert(i->final_path);
705
706                 j->disk_fd = safe_close(j->disk_fd);
707
708                 if (i->tar_pid > 0) {
709                         r = wait_for_terminate_and_warn("tar", i->tar_pid, true);
710                         i->tar_pid = 0;
711                         if (r < 0)
712                                 goto finish;
713                 }
714
715                 r = aufs_resolve(i->temp_path);
716                 if (r < 0) {
717                         log_error_errno(r, "Failed to resolve aufs whiteouts: %m");
718                         goto finish;
719                 }
720
721                 r = btrfs_subvol_set_read_only(i->temp_path, true);
722                 if (r < 0) {
723                         log_error_errno(r, "Failed to mark snapshort read-only: %m");
724                         goto finish;
725                 }
726
727                 if (rename(i->temp_path, i->final_path) < 0) {
728                         log_error_errno(errno, "Failed to rename snaphsot: %m");
729                         goto finish;
730                 }
731
732                 log_info("Completed writing to layer %s.", i->final_path);
733
734                 i->layer_job = import_job_unref(i->layer_job);
735                 free(i->temp_path);
736                 i->temp_path = NULL;
737                 free(i->final_path);
738                 i->final_path = NULL;
739
740                 i->current_ancestry ++;
741                 r = dkr_import_pull_layer(i);
742                 if (r < 0)
743                         goto finish;
744
745         } else if (i->json_job != j)
746                 assert_not_reached("Got finished event for unknown curl object");
747
748         if (!dkr_import_is_done(i))
749                 return;
750
751         r = dkr_import_make_local_copy(i);
752         if (r < 0)
753                 goto finish;
754
755         r = 0;
756
757 finish:
758         if (i->on_finished)
759                 i->on_finished(i, r, i->userdata);
760         else
761                 sd_event_exit(i->event, r);
762 }
763
764 static int dkr_import_job_on_header(ImportJob *j, const char *header, size_t sz)  {
765         _cleanup_free_ char *registry = NULL;
766         char *token;
767         DkrImport *i;
768         int r;
769
770         assert(j);
771         assert(j->userdata);
772
773         i = j->userdata;
774
775         r = curl_header_strdup(header, sz, HEADER_TOKEN, &token);
776         if (r < 0)
777                 return log_oom();
778         if (r > 0) {
779                 free(i->response_token);
780                 i->response_token = token;
781                 return 0;
782         }
783
784         r = curl_header_strdup(header, sz, HEADER_REGISTRY, &registry);
785         if (r < 0)
786                 return log_oom();
787         if (r > 0) {
788                 char **l, **k;
789
790                 l = strv_split(registry, ",");
791                 if (!l)
792                         return log_oom();
793
794                 STRV_FOREACH(k, l) {
795                         if (!hostname_is_valid(*k)) {
796                                 log_error("Registry hostname is not valid.");
797                                 strv_free(l);
798                                 return -EBADMSG;
799                         }
800                 }
801
802                 strv_free(i->response_registries);
803                 i->response_registries = l;
804         }
805
806         return 0;
807 }
808
809 int dkr_import_pull(DkrImport *i, const char *name, const char *tag, const char *local, bool force_local) {
810         const char *url;
811         int r;
812
813         assert(i);
814
815         if (!dkr_name_is_valid(name))
816                 return -EINVAL;
817
818         if (tag && !dkr_tag_is_valid(tag))
819                 return -EINVAL;
820
821         if (local && !machine_name_is_valid(local))
822                 return -EINVAL;
823
824         if (i->images_job)
825                 return -EBUSY;
826
827         if (!tag)
828                 tag = "latest";
829
830         r = free_and_strdup(&i->local, local);
831         if (r < 0)
832                 return r;
833         i->force_local = force_local;
834
835         r = free_and_strdup(&i->name, name);
836         if (r < 0)
837                 return r;
838         r = free_and_strdup(&i->tag, tag);
839         if (r < 0)
840                 return r;
841
842         url = strappenda(i->index_url, "/v1/repositories/", name, "/images");
843
844         r = import_job_new(&i->images_job, url, i->glue, i);
845         if (r < 0)
846                 return r;
847
848         r = dkr_import_add_token(i, i->images_job);
849         if (r < 0)
850                 return r;
851
852         i->images_job->on_finished = dkr_import_job_on_finished;
853         i->images_job->on_header = dkr_import_job_on_header;
854
855         return import_job_begin(i->images_job);
856 }
857
858 bool dkr_name_is_valid(const char *name) {
859         const char *slash, *p;
860
861         if (isempty(name))
862                 return false;
863
864         slash = strchr(name, '/');
865         if (!slash)
866                 return false;
867
868         if (!filename_is_valid(slash + 1))
869                 return false;
870
871         p = strndupa(name, slash - name);
872         if (!filename_is_valid(p))
873                 return false;
874
875         return true;
876 }
877
878 bool dkr_id_is_valid(const char *id) {
879
880         if (!filename_is_valid(id))
881                 return false;
882
883         if (!in_charset(id, "0123456789abcdef"))
884                 return false;
885
886         return true;
887 }