]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/import/export.c
importd: add API for exporting container/VM images
[thirdparty/systemd.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 }