chiark / gitweb /
import: add support for pulling raw tar balls as containers
[elogind.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 }