]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/import/import.c
import: add support for pulling raw tar balls as containers
[thirdparty/systemd.git] / src / import / import.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 <getopt.h>
23
24 #include "sd-event.h"
25 #include "event-util.h"
26 #include "verbs.h"
27 #include "build.h"
28 #include "machine-image.h"
29 #include "import-tar.h"
30 #include "import-raw.h"
31 #include "import-dkr.h"
32
33 static bool arg_force = false;
34 static const char *arg_image_root = "/var/lib/machines";
35
36 static const char* arg_dkr_index_url = DEFAULT_DKR_INDEX_URL;
37
38 static void on_tar_finished(TarImport *import, int error, void *userdata) {
39 sd_event *event = userdata;
40 assert(import);
41
42 if (error == 0)
43 log_info("Operation completed successfully.");
44 else
45 log_error_errno(error, "Operation failed: %m");
46
47 sd_event_exit(event, error);
48 }
49
50 static int url_final_component(const char *url, char **ret) {
51 const char *e, *p;
52 char *s;
53
54 e = strchrnul(url, '?');
55
56 while (e > url && e[-1] == '/')
57 e--;
58
59 p = e;
60 while (p > url && p[-1] != '/')
61 p--;
62
63 if (e <= p)
64 return -EINVAL;
65
66 s = strndup(p, e - p);
67 if (!s)
68 return -ENOMEM;
69
70 *ret = s;
71 return 0;
72 }
73
74 static int strip_tar_suffixes(const char *name, char **ret) {
75 const char *e;
76 char *s;
77
78 e = endswith(name, ".tar");
79 if (!e)
80 e = endswith(name, ".tar.gz");
81 if (!e)
82 e = endswith(name, ".tar.xz");
83 if (!e)
84 e = endswith(name, ".tgz");
85 if (!e)
86 e = strchr(name, 0);
87
88 if (e <= name)
89 return -EINVAL;
90
91 s = strndup(name, e - name);
92 if (!s)
93 return -ENOMEM;
94
95 *ret = s;
96 return 0;
97 }
98
99 static int pull_tar(int argc, char *argv[], void *userdata) {
100 _cleanup_(tar_import_unrefp) TarImport *import = NULL;
101 _cleanup_event_unref_ sd_event *event = NULL;
102 const char *url, *local;
103 _cleanup_free_ char *l = NULL, *ll = NULL;
104 int r;
105
106 url = argv[1];
107 if (!http_url_is_valid(url)) {
108 log_error("URL '%s' is not valid.", url);
109 return -EINVAL;
110 }
111
112 if (argc >= 3)
113 local = argv[2];
114 else {
115 r = url_final_component(url, &l);
116 if (r < 0)
117 return log_error_errno(r, "Failed get final component of URL: %m");
118
119 local = l;
120 }
121
122 if (isempty(local) || streq(local, "-"))
123 local = NULL;
124
125 if (local) {
126 r = strip_tar_suffixes(local, &ll);
127 if (r < 0)
128 return log_oom();
129
130 local = ll;
131
132 if (!machine_name_is_valid(local)) {
133 log_error("Local image name '%s' is not valid.", local);
134 return -EINVAL;
135 }
136
137 if (!arg_force) {
138 r = image_find(local, NULL);
139 if (r < 0)
140 return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local);
141 else if (r > 0) {
142 log_error_errno(EEXIST, "Image '%s' already exists.", local);
143 return -EEXIST;
144 }
145 }
146
147 log_info("Pulling '%s', saving as '%s'.", url, local);
148 } else
149 log_info("Pulling '%s'.", url);
150
151 r = sd_event_default(&event);
152 if (r < 0)
153 return log_error_errno(r, "Failed to allocate event loop: %m");
154
155 assert_se(sigprocmask_many(SIG_BLOCK, SIGTERM, SIGINT, -1) == 0);
156 sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL);
157 sd_event_add_signal(event, NULL, SIGINT, NULL, NULL);
158
159 r = tar_import_new(&import, event, arg_image_root, on_tar_finished, event);
160 if (r < 0)
161 return log_error_errno(r, "Failed to allocate importer: %m");
162
163 r = tar_import_pull(import, url, local, arg_force);
164 if (r < 0)
165 return log_error_errno(r, "Failed to pull image: %m");
166
167 r = sd_event_loop(event);
168 if (r < 0)
169 return log_error_errno(r, "Failed to run event loop: %m");
170
171 log_info("Exiting.");
172
173 return 0;
174 }
175
176 static void on_raw_finished(RawImport *import, int error, void *userdata) {
177 sd_event *event = userdata;
178 assert(import);
179
180 if (error == 0)
181 log_info("Operation completed successfully.");
182 else
183 log_error_errno(error, "Operation failed: %m");
184
185 sd_event_exit(event, error);
186 }
187
188 static int strip_raw_suffixes(const char *p, char **ret) {
189 static const char suffixes[] =
190 ".xz\0"
191 ".raw\0"
192 ".qcow2\0"
193 ".img\0";
194
195 _cleanup_free_ char *q = NULL;
196
197 q = strdup(p);
198 if (!q)
199 return -ENOMEM;
200
201 for (;;) {
202 const char *sfx;
203 bool changed = false;
204
205 NULSTR_FOREACH(sfx, suffixes) {
206 char *e;
207
208 e = endswith(q, sfx);
209 if (e) {
210 *e = 0;
211 changed = true;
212 }
213 }
214
215 if (!changed)
216 break;
217 }
218
219 *ret = q;
220 q = NULL;
221
222 return 0;
223 }
224
225 static int pull_raw(int argc, char *argv[], void *userdata) {
226 _cleanup_(raw_import_unrefp) RawImport *import = NULL;
227 _cleanup_event_unref_ sd_event *event = NULL;
228 const char *url, *local;
229 _cleanup_free_ char *l = NULL;
230 int r;
231
232 url = argv[1];
233 if (!http_url_is_valid(url)) {
234 log_error("URL '%s' is not valid.", url);
235 return -EINVAL;
236 }
237
238 if (argc >= 3)
239 local = argv[2];
240 else {
241 const char *e, *p;
242
243 e = url + strlen(url);
244 while (e > url && e[-1] == '/')
245 e--;
246
247 p = e;
248 while (p > url && p[-1] != '/')
249 p--;
250
251 local = strndupa(p, e - p);
252 }
253
254 if (isempty(local) || streq(local, "-"))
255 local = NULL;
256
257 if (local) {
258 const char *p;
259
260 r = strip_raw_suffixes(local, &l);
261 if (r < 0)
262 return log_oom();
263
264 local = l;
265
266 if (!machine_name_is_valid(local)) {
267 log_error("Local image name '%s' is not valid.", local);
268 return -EINVAL;
269 }
270
271 p = strappenda(arg_image_root, "/", local, ".raw");
272 if (laccess(p, F_OK) >= 0) {
273 if (!arg_force) {
274 log_info("Image '%s' already exists.", local);
275 return 0;
276 }
277 } else if (errno != ENOENT)
278 return log_error_errno(errno, "Can't check if image '%s' already exists: %m", local);
279
280 log_info("Pulling '%s', saving as '%s'.", url, local);
281 } else
282 log_info("Pulling '%s'.", url);
283
284 r = sd_event_default(&event);
285 if (r < 0)
286 return log_error_errno(r, "Failed to allocate event loop: %m");
287
288 assert_se(sigprocmask_many(SIG_BLOCK, SIGTERM, SIGINT, -1) == 0);
289 sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL);
290 sd_event_add_signal(event, NULL, SIGINT, NULL, NULL);
291
292 r = raw_import_new(&import, event, arg_image_root, on_raw_finished, event);
293 if (r < 0)
294 return log_error_errno(r, "Failed to allocate importer: %m");
295
296 r = raw_import_pull(import, url, local, arg_force);
297 if (r < 0)
298 return log_error_errno(r, "Failed to pull image: %m");
299
300 r = sd_event_loop(event);
301 if (r < 0)
302 return log_error_errno(r, "Failed to run event loop: %m");
303
304 log_info("Exiting.");
305
306 return 0;
307 }
308
309 static void on_dkr_finished(DkrImport *import, int error, void *userdata) {
310 sd_event *event = userdata;
311 assert(import);
312
313 if (error == 0)
314 log_info("Operation completed successfully.");
315 else
316 log_error_errno(error, "Operation failed: %m");
317
318 sd_event_exit(event, error);
319 }
320
321 static int pull_dkr(int argc, char *argv[], void *userdata) {
322 _cleanup_(dkr_import_unrefp) DkrImport *import = NULL;
323 _cleanup_event_unref_ sd_event *event = NULL;
324 const char *name, *tag, *local;
325 int r;
326
327 if (!arg_dkr_index_url) {
328 log_error("Please specify an index URL with --dkr-index-url=");
329 return -EINVAL;
330 }
331
332 tag = strchr(argv[1], ':');
333 if (tag) {
334 name = strndupa(argv[1], tag - argv[1]);
335 tag++;
336 } else {
337 name = argv[1];
338 tag = "latest";
339 }
340
341 if (!dkr_name_is_valid(name)) {
342 log_error("Remote name '%s' is not valid.", name);
343 return -EINVAL;
344 }
345
346 if (!dkr_tag_is_valid(tag)) {
347 log_error("Tag name '%s' is not valid.", tag);
348 return -EINVAL;
349 }
350
351 if (argc >= 3)
352 local = argv[2];
353 else {
354 local = strchr(name, '/');
355 if (local)
356 local++;
357 else
358 local = name;
359 }
360
361 if (isempty(local) || streq(local, "-"))
362 local = NULL;
363
364 if (local) {
365 const char *p;
366
367 if (!machine_name_is_valid(local)) {
368 log_error("Local image name '%s' is not valid.", local);
369 return -EINVAL;
370 }
371
372 p = strappenda(arg_image_root, "/", local);
373 if (laccess(p, F_OK) >= 0) {
374 if (!arg_force) {
375 log_info("Image '%s' already exists.", local);
376 return 0;
377 }
378 } else if (errno != ENOENT)
379 return log_error_errno(errno, "Can't check if image '%s' already exists: %m", local);
380
381 log_info("Pulling '%s' with tag '%s', saving as '%s'.", name, tag, local);
382 } else
383 log_info("Pulling '%s' with tag '%s'.", name, tag);
384
385 r = sd_event_default(&event);
386 if (r < 0)
387 return log_error_errno(r, "Failed to allocate event loop: %m");
388
389 assert_se(sigprocmask_many(SIG_BLOCK, SIGTERM, SIGINT, -1) == 0);
390 sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL);
391 sd_event_add_signal(event, NULL, SIGINT, NULL, NULL);
392
393 r = dkr_import_new(&import, event, arg_dkr_index_url, arg_image_root, on_dkr_finished, event);
394 if (r < 0)
395 return log_error_errno(r, "Failed to allocate importer: %m");
396
397 r = dkr_import_pull(import, name, tag, local, arg_force);
398 if (r < 0)
399 return log_error_errno(r, "Failed to pull image: %m");
400
401 r = sd_event_loop(event);
402 if (r < 0)
403 return log_error_errno(r, "Failed to run event loop: %m");
404
405 log_info("Exiting.");
406
407 return 0;
408 }
409
410 static int help(int argc, char *argv[], void *userdata) {
411
412 printf("%s [OPTIONS...] {COMMAND} ...\n\n"
413 "Import container or virtual machine image.\n\n"
414 " -h --help Show this help\n"
415 " --version Show package version\n"
416 " --force Force creation of image\n"
417 " --image-root= Image root directory\n"
418 " --dkr-index-url=URL Specify index URL to use for downloads\n\n"
419 "Commands:\n"
420 " pull-tar URL Download a TAR image\n"
421 " pull-raw URL [NAME] Download a RAW image\n"
422 " pull-dkr REMOTE [NAME] Download a DKR image\n",
423 program_invocation_short_name);
424
425 return 0;
426 }
427
428 static int parse_argv(int argc, char *argv[]) {
429
430 enum {
431 ARG_VERSION = 0x100,
432 ARG_FORCE,
433 ARG_DKR_INDEX_URL,
434 ARG_IMAGE_ROOT,
435 };
436
437 static const struct option options[] = {
438 { "help", no_argument, NULL, 'h' },
439 { "version", no_argument, NULL, ARG_VERSION },
440 { "force", no_argument, NULL, ARG_FORCE },
441 { "dkr-index-url", required_argument, NULL, ARG_DKR_INDEX_URL },
442 { "image-root", required_argument, NULL, ARG_IMAGE_ROOT },
443 {}
444 };
445
446 int c;
447
448 assert(argc >= 0);
449 assert(argv);
450
451 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
452
453 switch (c) {
454
455 case 'h':
456 return help(0, NULL, NULL);
457
458 case ARG_VERSION:
459 puts(PACKAGE_STRING);
460 puts(SYSTEMD_FEATURES);
461 return 0;
462
463 case ARG_FORCE:
464 arg_force = true;
465 break;
466
467 case ARG_DKR_INDEX_URL:
468 if (!dkr_url_is_valid(optarg)) {
469 log_error("Index URL is not valid: %s", optarg);
470 return -EINVAL;
471 }
472
473 arg_dkr_index_url = optarg;
474 break;
475
476 case ARG_IMAGE_ROOT:
477 arg_image_root = optarg;
478 break;
479
480 case '?':
481 return -EINVAL;
482
483 default:
484 assert_not_reached("Unhandled option");
485 }
486
487 return 1;
488 }
489
490 static int import_main(int argc, char *argv[]) {
491
492 static const Verb verbs[] = {
493 { "help", VERB_ANY, VERB_ANY, 0, help },
494 { "pull-tar", 2, 3, 0, pull_tar },
495 { "pull-raw", 2, 3, 0, pull_raw },
496 { "pull-dkr", 2, 3, 0, pull_dkr },
497 {}
498 };
499
500 return dispatch_verb(argc, argv, verbs, NULL);
501 }
502
503 int main(int argc, char *argv[]) {
504 int r;
505
506 setlocale(LC_ALL, "");
507 log_parse_environment();
508 log_open();
509
510 r = parse_argv(argc, argv);
511 if (r <= 0)
512 goto finish;
513
514 r = import_main(argc, argv);
515
516 finish:
517 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
518 }