chiark / gitweb /
importd: add API for exporting container/VM images
[elogind.git] / src / import / export.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2015 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-util.h"
30 #include "export-tar.h"
31 #include "export-raw.h"
32
33 static ImportCompressType arg_compress = IMPORT_COMPRESS_UNKNOWN;
34
35 static void determine_compression_from_filename(const char *p) {
36
37         if (arg_compress != IMPORT_COMPRESS_UNKNOWN)
38                 return;
39
40         if (!p) {
41                 arg_compress = IMPORT_COMPRESS_UNCOMPRESSED;
42                 return;
43         }
44
45         if (endswith(p, ".xz"))
46                 arg_compress = IMPORT_COMPRESS_XZ;
47         else if (endswith(p, ".gz"))
48                 arg_compress = IMPORT_COMPRESS_GZIP;
49         else if (endswith(p, ".bz2"))
50                 arg_compress = IMPORT_COMPRESS_BZIP2;
51         else
52                 arg_compress = IMPORT_COMPRESS_UNCOMPRESSED;
53 }
54
55 static int interrupt_signal_handler(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) {
56         log_notice("Transfer aborted.");
57         sd_event_exit(sd_event_source_get_event(s), EINTR);
58         return 0;
59 }
60
61 static void on_tar_finished(TarExport *export, int error, void *userdata) {
62         sd_event *event = userdata;
63         assert(export);
64
65         if (error == 0)
66                 log_info("Operation completed successfully.");
67
68         sd_event_exit(event, abs(error));
69 }
70
71 static int export_tar(int argc, char *argv[], void *userdata) {
72         _cleanup_(tar_export_unrefp) TarExport *export = NULL;
73         _cleanup_event_unref_ sd_event *event = NULL;
74         _cleanup_(image_unrefp) Image *image = NULL;
75         const char *path = NULL, *local = NULL;
76         _cleanup_close_ int open_fd = -1;
77         int r, fd;
78
79         if (machine_name_is_valid(argv[1])) {
80                 r = image_find(argv[1], &image);
81                 if (r < 0)
82                         return log_error_errno(r, "Failed to look for machine %s: %m", argv[1]);
83                 if (r == 0) {
84                         log_error("Machine image %s not found.", argv[1]);
85                         return -ENOENT;
86                 }
87
88                 local = image->path;
89         } else
90                 local = argv[1];
91
92         if (argc >= 3)
93                 path = argv[2];
94         if (isempty(path) || streq(path, "-"))
95                 path = NULL;
96
97         determine_compression_from_filename(path);
98
99         if (path) {
100                 open_fd = open(path, O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC|O_NOCTTY, 0666);
101                 if (open_fd < 0)
102                         return log_error_errno(errno, "Failed to open tar image for export: %m");
103
104                 fd = open_fd;
105
106                 log_info("Exporting '%s', saving to '%s' with compression '%s'.", local, path, import_compress_type_to_string(arg_compress));
107         } else {
108                 _cleanup_free_ char *pretty = NULL;
109
110                 fd = STDOUT_FILENO;
111
112                 (void) readlink_malloc("/proc/self/fd/1", &pretty);
113                 log_info("Exporting '%s', saving to '%s' with compression '%s'.", local, strna(pretty), import_compress_type_to_string(arg_compress));
114         }
115
116         r = sd_event_default(&event);
117         if (r < 0)
118                 return log_error_errno(r, "Failed to allocate event loop: %m");
119
120         assert_se(sigprocmask_many(SIG_BLOCK, SIGTERM, SIGINT, -1) == 0);
121         sd_event_add_signal(event, NULL, SIGTERM, interrupt_signal_handler,  NULL);
122         sd_event_add_signal(event, NULL, SIGINT, interrupt_signal_handler, NULL);
123
124         r = tar_export_new(&export, event, on_tar_finished, event);
125         if (r < 0)
126                 return log_error_errno(r, "Failed to allocate exporter: %m");
127
128         r = tar_export_start(export, local, fd, arg_compress);
129         if (r < 0)
130                 return log_error_errno(r, "Failed to export image: %m");
131
132         r = sd_event_loop(event);
133         if (r < 0)
134                 return log_error_errno(r, "Failed to run event loop: %m");
135
136         log_info("Exiting.");
137         return -r;
138 }
139
140 static void on_raw_finished(RawExport *export, int error, void *userdata) {
141         sd_event *event = userdata;
142         assert(export);
143
144         if (error == 0)
145                 log_info("Operation completed successfully.");
146
147         sd_event_exit(event, abs(error));
148 }
149
150 static int export_raw(int argc, char *argv[], void *userdata) {
151         _cleanup_(raw_export_unrefp) RawExport *export = NULL;
152         _cleanup_event_unref_ sd_event *event = NULL;
153         _cleanup_(image_unrefp) Image *image = NULL;
154         const char *path = NULL, *local = NULL;
155         _cleanup_close_ int open_fd = -1;
156         int r, fd;
157
158         if (machine_name_is_valid(argv[1])) {
159                 r = image_find(argv[1], &image);
160                 if (r < 0)
161                         return log_error_errno(r, "Failed to look for machine %s: %m", argv[1]);
162                 if (r == 0) {
163                         log_error("Machine image %s not found.", argv[1]);
164                         return -ENOENT;
165                 }
166
167                 local = image->path;
168         } else
169                 local = argv[1];
170
171         if (argc >= 3)
172                 path = argv[2];
173         if (isempty(path) || streq(path, "-"))
174                 path = NULL;
175
176         determine_compression_from_filename(path);
177
178         if (path) {
179                 open_fd = open(path, O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC|O_NOCTTY, 0666);
180                 if (open_fd < 0)
181                         return log_error_errno(errno, "Failed to open raw image for export: %m");
182
183                 fd = open_fd;
184
185                 log_info("Exporting '%s', saving to '%s' with compression '%s'.", local, path, import_compress_type_to_string(arg_compress));
186         } else {
187                 _cleanup_free_ char *pretty = NULL;
188
189                 fd = STDOUT_FILENO;
190
191                 (void) readlink_malloc("/proc/self/fd/1", &pretty);
192                 log_info("Exporting '%s', saving to '%s' with compression '%s'.", local, strna(pretty), import_compress_type_to_string(arg_compress));
193         }
194
195         r = sd_event_default(&event);
196         if (r < 0)
197                 return log_error_errno(r, "Failed to allocate event loop: %m");
198
199         assert_se(sigprocmask_many(SIG_BLOCK, SIGTERM, SIGINT, -1) == 0);
200         sd_event_add_signal(event, NULL, SIGTERM, interrupt_signal_handler,  NULL);
201         sd_event_add_signal(event, NULL, SIGINT, interrupt_signal_handler, NULL);
202
203         r = raw_export_new(&export, event, on_raw_finished, event);
204         if (r < 0)
205                 return log_error_errno(r, "Failed to allocate exporter: %m");
206
207         r = raw_export_start(export, local, fd, arg_compress);
208         if (r < 0)
209                 return log_error_errno(r, "Failed to export image: %m");
210
211         r = sd_event_loop(event);
212         if (r < 0)
213                 return log_error_errno(r, "Failed to run event loop: %m");
214
215         log_info("Exiting.");
216         return -r;
217 }
218
219 static int help(int argc, char *argv[], void *userdata) {
220
221         printf("%s [OPTIONS...] {COMMAND} ...\n\n"
222                "Export container or virtual machine images.\n\n"
223                "  -h --help                    Show this help\n"
224                "     --version                 Show package version\n"
225                "     --format=FORMAT           Select format\n\n"
226                "Commands:\n"
227                "  tar NAME [FILE]              Export a TAR image\n"
228                "  raw NAME [FILE]              Export a RAW image\n",
229                program_invocation_short_name);
230
231         return 0;
232 }
233
234 static int parse_argv(int argc, char *argv[]) {
235
236         enum {
237                 ARG_VERSION = 0x100,
238                 ARG_FORMAT,
239         };
240
241         static const struct option options[] = {
242                 { "help",    no_argument,       NULL, 'h'         },
243                 { "version", no_argument,       NULL, ARG_VERSION },
244                 { "format",  required_argument, NULL, ARG_FORMAT  },
245                 {}
246         };
247
248         int c;
249
250         assert(argc >= 0);
251         assert(argv);
252
253         while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
254
255                 switch (c) {
256
257                 case 'h':
258                         return help(0, NULL, NULL);
259
260                 case ARG_VERSION:
261                         puts(PACKAGE_STRING);
262                         puts(SYSTEMD_FEATURES);
263                         return 0;
264
265                 case ARG_FORMAT:
266                         if (streq(optarg, "uncompressed"))
267                                 arg_compress = IMPORT_COMPRESS_UNCOMPRESSED;
268                         else if (streq(optarg, "xz"))
269                                 arg_compress = IMPORT_COMPRESS_XZ;
270                         else if (streq(optarg, "gzip"))
271                                 arg_compress = IMPORT_COMPRESS_GZIP;
272                         else if (streq(optarg, "bzip2"))
273                                 arg_compress = IMPORT_COMPRESS_BZIP2;
274                         else {
275                                 log_error("Unknown format: %s", optarg);
276                                 return -EINVAL;
277                         }
278                         break;
279
280                 case '?':
281                         return -EINVAL;
282
283                 default:
284                         assert_not_reached("Unhandled option");
285                 }
286
287         return 1;
288 }
289
290 static int export_main(int argc, char *argv[]) {
291
292         static const Verb verbs[] = {
293                 { "help", VERB_ANY, VERB_ANY, 0, help       },
294                 { "tar",  2,        3,        0, export_tar },
295                 { "raw",  2,        3,        0, export_raw },
296                 {}
297         };
298
299         return dispatch_verb(argc, argv, verbs, NULL);
300 }
301
302 int main(int argc, char *argv[]) {
303         int r;
304
305         setlocale(LC_ALL, "");
306         log_parse_environment();
307         log_open();
308
309         r = parse_argv(argc, argv);
310         if (r <= 0)
311                 goto finish;
312
313         ignore_signals(SIGPIPE, -1);
314
315         r = export_main(argc, argv);
316
317 finish:
318         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
319 }