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