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