chiark / gitweb /
861a4485839da2df7f4cbe5f8b2999e3946718b3
[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, *ll = 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                 r = url_final_component(url, &l);
242                 if (r < 0)
243                         return log_error_errno(r, "Failed get final component of URL: %m");
244
245                 local = l;
246         }
247
248         if (isempty(local) || streq(local, "-"))
249                 local = NULL;
250
251         if (local) {
252                 r = strip_raw_suffixes(local, &ll);
253                 if (r < 0)
254                         return log_oom();
255
256                 local = ll;
257
258                 if (!machine_name_is_valid(local)) {
259                         log_error("Local image name '%s' is not valid.", local);
260                         return -EINVAL;
261                 }
262
263                 if (!arg_force) {
264                         r = image_find(local, NULL);
265                         if (r < 0)
266                                 return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local);
267                         else if (r > 0) {
268                                 log_error_errno(EEXIST, "Image '%s' already exists.", local);
269                                 return -EEXIST;
270                         }
271                 }
272
273                 log_info("Pulling '%s', saving as '%s'.", url, local);
274         } else
275                 log_info("Pulling '%s'.", url);
276
277         r = sd_event_default(&event);
278         if (r < 0)
279                 return log_error_errno(r, "Failed to allocate event loop: %m");
280
281         assert_se(sigprocmask_many(SIG_BLOCK, SIGTERM, SIGINT, -1) == 0);
282         sd_event_add_signal(event, NULL, SIGTERM, NULL,  NULL);
283         sd_event_add_signal(event, NULL, SIGINT, NULL, NULL);
284
285         r = raw_import_new(&import, event, arg_image_root, on_raw_finished, event);
286         if (r < 0)
287                 return log_error_errno(r, "Failed to allocate importer: %m");
288
289         r = raw_import_pull(import, url, local, arg_force);
290         if (r < 0)
291                 return log_error_errno(r, "Failed to pull image: %m");
292
293         r = sd_event_loop(event);
294         if (r < 0)
295                 return log_error_errno(r, "Failed to run event loop: %m");
296
297         log_info("Exiting.");
298
299         return 0;
300 }
301
302 static void on_dkr_finished(DkrImport *import, int error, void *userdata) {
303         sd_event *event = userdata;
304         assert(import);
305
306         if (error == 0)
307                 log_info("Operation completed successfully.");
308         else
309                 log_error_errno(error, "Operation failed: %m");
310
311         sd_event_exit(event, error);
312 }
313
314 static int pull_dkr(int argc, char *argv[], void *userdata) {
315         _cleanup_(dkr_import_unrefp) DkrImport *import = NULL;
316         _cleanup_event_unref_ sd_event *event = NULL;
317         const char *name, *tag, *local;
318         int r;
319
320         if (!arg_dkr_index_url) {
321                 log_error("Please specify an index URL with --dkr-index-url=");
322                 return -EINVAL;
323         }
324
325         tag = strchr(argv[1], ':');
326         if (tag) {
327                 name = strndupa(argv[1], tag - argv[1]);
328                 tag++;
329         } else {
330                 name = argv[1];
331                 tag = "latest";
332         }
333
334         if (!dkr_name_is_valid(name)) {
335                 log_error("Remote name '%s' is not valid.", name);
336                 return -EINVAL;
337         }
338
339         if (!dkr_tag_is_valid(tag)) {
340                 log_error("Tag name '%s' is not valid.", tag);
341                 return -EINVAL;
342         }
343
344         if (argc >= 3)
345                 local = argv[2];
346         else {
347                 local = strchr(name, '/');
348                 if (local)
349                         local++;
350                 else
351                         local = name;
352         }
353
354         if (isempty(local) || streq(local, "-"))
355                 local = NULL;
356
357         if (local) {
358                 const char *p;
359
360                 if (!machine_name_is_valid(local)) {
361                         log_error("Local image name '%s' is not valid.", local);
362                         return -EINVAL;
363                 }
364
365                 p = strappenda(arg_image_root, "/", local);
366                 if (laccess(p, F_OK) >= 0) {
367                         if (!arg_force) {
368                                 log_info("Image '%s' already exists.", local);
369                                 return 0;
370                         }
371                 } else if (errno != ENOENT)
372                         return log_error_errno(errno, "Can't check if image '%s' already exists: %m", local);
373
374                 log_info("Pulling '%s' with tag '%s', saving as '%s'.", name, tag, local);
375         } else
376                 log_info("Pulling '%s' with tag '%s'.", name, tag);
377
378         r = sd_event_default(&event);
379         if (r < 0)
380                 return log_error_errno(r, "Failed to allocate event loop: %m");
381
382         assert_se(sigprocmask_many(SIG_BLOCK, SIGTERM, SIGINT, -1) == 0);
383         sd_event_add_signal(event, NULL, SIGTERM, NULL,  NULL);
384         sd_event_add_signal(event, NULL, SIGINT, NULL, NULL);
385
386         r = dkr_import_new(&import, event, arg_dkr_index_url, arg_image_root, on_dkr_finished, event);
387         if (r < 0)
388                 return log_error_errno(r, "Failed to allocate importer: %m");
389
390         r = dkr_import_pull(import, name, tag, local, arg_force);
391         if (r < 0)
392                 return log_error_errno(r, "Failed to pull image: %m");
393
394         r = sd_event_loop(event);
395         if (r < 0)
396                 return log_error_errno(r, "Failed to run event loop: %m");
397
398         log_info("Exiting.");
399
400         return 0;
401 }
402
403 static int help(int argc, char *argv[], void *userdata) {
404
405         printf("%s [OPTIONS...] {COMMAND} ...\n\n"
406                "Import container or virtual machine image.\n\n"
407                "  -h --help                   Show this help\n"
408                "     --version                Show package version\n"
409                "     --force                  Force creation of image\n"
410                "     --image-root=            Image root directory\n"
411                "     --dkr-index-url=URL      Specify index URL to use for downloads\n\n"
412                "Commands:\n"
413                "  pull-tar URL [NAME]         Download a TAR image\n"
414                "  pull-raw URL [NAME]         Download a RAW image\n"
415                "  pull-dkr REMOTE [NAME]      Download a DKR image\n",
416                program_invocation_short_name);
417
418         return 0;
419 }
420
421 static int parse_argv(int argc, char *argv[]) {
422
423         enum {
424                 ARG_VERSION = 0x100,
425                 ARG_FORCE,
426                 ARG_DKR_INDEX_URL,
427                 ARG_IMAGE_ROOT,
428         };
429
430         static const struct option options[] = {
431                 { "help",            no_argument,       NULL, 'h'                 },
432                 { "version",         no_argument,       NULL, ARG_VERSION         },
433                 { "force",           no_argument,       NULL, ARG_FORCE           },
434                 { "dkr-index-url",   required_argument, NULL, ARG_DKR_INDEX_URL   },
435                 { "image-root",      required_argument, NULL, ARG_IMAGE_ROOT      },
436                 {}
437         };
438
439         int c;
440
441         assert(argc >= 0);
442         assert(argv);
443
444         while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
445
446                 switch (c) {
447
448                 case 'h':
449                         return help(0, NULL, NULL);
450
451                 case ARG_VERSION:
452                         puts(PACKAGE_STRING);
453                         puts(SYSTEMD_FEATURES);
454                         return 0;
455
456                 case ARG_FORCE:
457                         arg_force = true;
458                         break;
459
460                 case ARG_DKR_INDEX_URL:
461                         if (!dkr_url_is_valid(optarg)) {
462                                 log_error("Index URL is not valid: %s", optarg);
463                                 return -EINVAL;
464                         }
465
466                         arg_dkr_index_url = optarg;
467                         break;
468
469                 case ARG_IMAGE_ROOT:
470                         arg_image_root = optarg;
471                         break;
472
473                 case '?':
474                         return -EINVAL;
475
476                 default:
477                         assert_not_reached("Unhandled option");
478                 }
479
480         return 1;
481 }
482
483 static int import_main(int argc, char *argv[]) {
484
485         static const Verb verbs[] = {
486                 { "help",     VERB_ANY, VERB_ANY, 0, help     },
487                 { "pull-tar", 2,        3,        0, pull_tar },
488                 { "pull-raw", 2,        3,        0, pull_raw },
489                 { "pull-dkr", 2,        3,        0, pull_dkr },
490                 {}
491         };
492
493         return dispatch_verb(argc, argv, verbs, NULL);
494 }
495
496 int main(int argc, char *argv[]) {
497         int r;
498
499         setlocale(LC_ALL, "");
500         log_parse_environment();
501         log_open();
502
503         r = parse_argv(argc, argv);
504         if (r <= 0)
505                 goto finish;
506
507         r = import_main(argc, argv);
508
509 finish:
510         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
511 }