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