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