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 "env-util.h"
12 #include "fd-util.h"
13 #include "fs-util.h"
14 #include "hostname-util.h"
15 #include "import-raw.h"
16 #include "import-tar.h"
17 #include "import-util.h"
18 #include "io-util.h"
19 #include "main-func.h"
20 #include "parse-argument.h"
21 #include "parse-util.h"
22 #include "signal-util.h"
23 #include "string-util.h"
24 #include "terminal-util.h"
25 #include "verbs.h"
26
27 static const char *arg_image_root = "/var/lib/machines";
28 static ImportFlags arg_import_flags = IMPORT_BTRFS_SUBVOL | IMPORT_BTRFS_QUOTA | IMPORT_CONVERT_QCOW2 | IMPORT_SYNC;
29 static uint64_t arg_offset = UINT64_MAX, arg_size_max = UINT64_MAX;
30
normalize_local(const char * local,char ** ret)31 static int normalize_local(const char *local, char **ret) {
32 _cleanup_free_ char *ll = NULL;
33 int r;
34
35 assert(ret);
36
37 if (arg_import_flags & IMPORT_DIRECT) {
38
39 if (!local)
40 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No local path specified.");
41
42 if (!path_is_absolute(local)) {
43 ll = path_join(arg_image_root, local);
44 if (!ll)
45 return log_oom();
46
47 local = ll;
48 }
49
50 if (!path_is_valid(local))
51 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
52 "Local path name '%s' is not valid.", local);
53 } else {
54 if (local) {
55 if (!hostname_is_valid(local, 0))
56 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
57 "Local image name '%s' is not valid.",
58 local);
59 } else
60 local = "imported";
61
62 if (!FLAGS_SET(arg_import_flags, IMPORT_FORCE)) {
63 r = image_find(IMAGE_MACHINE, local, NULL, NULL);
64 if (r < 0) {
65 if (r != -ENOENT)
66 return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local);
67 } else
68 return log_error_errno(SYNTHETIC_ERRNO(EEXIST),
69 "Image '%s' already exists.",
70 local);
71 }
72 }
73
74 if (!ll) {
75 ll = strdup(local);
76 if (!ll)
77 return log_oom();
78 }
79
80 *ret = TAKE_PTR(ll);
81 return 0;
82 }
83
open_source(const char * path,const char * local,int * ret_open_fd)84 static int open_source(const char *path, const char *local, int *ret_open_fd) {
85 _cleanup_close_ int open_fd = -1;
86 int retval;
87
88 assert(local);
89 assert(ret_open_fd);
90
91 if (path) {
92 open_fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY);
93 if (open_fd < 0)
94 return log_error_errno(errno, "Failed to open source file '%s': %m", path);
95
96 retval = open_fd;
97
98 if (arg_offset != UINT64_MAX)
99 log_info("Importing '%s', saving at offset %" PRIu64 " in '%s'.", path, arg_offset, local);
100 else
101 log_info("Importing '%s', saving as '%s'.", path, local);
102 } else {
103 _cleanup_free_ char *pretty = NULL;
104
105 retval = STDIN_FILENO;
106
107 (void) fd_get_path(STDIN_FILENO, &pretty);
108
109 if (arg_offset != UINT64_MAX)
110 log_info("Importing '%s', saving at offset %" PRIu64 " in '%s'.", strempty(pretty), arg_offset, local);
111 else
112 log_info("Importing '%s', saving as '%s'.", strempty(pretty), local);
113 }
114
115 *ret_open_fd = TAKE_FD(open_fd);
116 return retval;
117 }
118
on_tar_finished(TarImport * import,int error,void * userdata)119 static void on_tar_finished(TarImport *import, int error, void *userdata) {
120 sd_event *event = userdata;
121 assert(import);
122
123 if (error == 0)
124 log_info("Operation completed successfully.");
125
126 sd_event_exit(event, abs(error));
127 }
128
import_tar(int argc,char * argv[],void * userdata)129 static int import_tar(int argc, char *argv[], void *userdata) {
130 _cleanup_(tar_import_unrefp) TarImport *import = NULL;
131 _cleanup_free_ char *ll = NULL, *normalized = NULL;
132 _cleanup_(sd_event_unrefp) sd_event *event = NULL;
133 const char *path = NULL, *local = NULL;
134 _cleanup_close_ int open_fd = -1;
135 int r, fd;
136
137 if (argc >= 2)
138 path = empty_or_dash_to_null(argv[1]);
139
140 if (argc >= 3)
141 local = empty_or_dash_to_null(argv[2]);
142 else if (path) {
143 _cleanup_free_ char *l = NULL;
144
145 r = path_extract_filename(path, &l);
146 if (r < 0)
147 return log_error_errno(r, "Failed to extract filename from path '%s': %m", path);
148
149 r = tar_strip_suffixes(l, &ll);
150 if (r < 0)
151 return log_oom();
152
153 local = ll;
154 }
155
156 r = normalize_local(local, &normalized);
157 if (r < 0)
158 return r;
159
160 fd = open_source(path, normalized, &open_fd);
161 if (fd < 0)
162 return r;
163
164 r = import_allocate_event_with_signals(&event);
165 if (r < 0)
166 return r;
167
168 if (!FLAGS_SET(arg_import_flags, IMPORT_SYNC))
169 log_info("File system synchronization on completion is off.");
170
171 r = tar_import_new(&import, event, arg_image_root, on_tar_finished, event);
172 if (r < 0)
173 return log_error_errno(r, "Failed to allocate importer: %m");
174
175 r = tar_import_start(
176 import,
177 fd,
178 normalized,
179 arg_import_flags & IMPORT_FLAGS_MASK_TAR);
180 if (r < 0)
181 return log_error_errno(r, "Failed to import image: %m");
182
183 r = sd_event_loop(event);
184 if (r < 0)
185 return log_error_errno(r, "Failed to run event loop: %m");
186
187 log_info("Exiting.");
188 return -r;
189 }
190
on_raw_finished(RawImport * import,int error,void * userdata)191 static void on_raw_finished(RawImport *import, int error, void *userdata) {
192 sd_event *event = userdata;
193 assert(import);
194
195 if (error == 0)
196 log_info("Operation completed successfully.");
197
198 sd_event_exit(event, abs(error));
199 }
200
import_raw(int argc,char * argv[],void * userdata)201 static int import_raw(int argc, char *argv[], void *userdata) {
202 _cleanup_(raw_import_unrefp) RawImport *import = NULL;
203 _cleanup_free_ char *ll = NULL, *normalized = NULL;
204 _cleanup_(sd_event_unrefp) sd_event *event = NULL;
205 const char *path = NULL, *local = NULL;
206 _cleanup_close_ int open_fd = -1;
207 int r, fd;
208
209 if (argc >= 2)
210 path = empty_or_dash_to_null(argv[1]);
211
212 if (argc >= 3)
213 local = empty_or_dash_to_null(argv[2]);
214 else if (path) {
215 _cleanup_free_ char *l = NULL;
216
217 r = path_extract_filename(path, &l);
218 if (r < 0)
219 return log_error_errno(r, "Failed to extract filename from path '%s': %m", path);
220
221 r = raw_strip_suffixes(l, &ll);
222 if (r < 0)
223 return log_oom();
224
225 local = ll;
226 }
227
228 r = normalize_local(local, &normalized);
229 if (r < 0)
230 return r;
231
232 fd = open_source(path, normalized, &open_fd);
233 if (fd < 0)
234 return fd;
235
236 r = import_allocate_event_with_signals(&event);
237 if (r < 0)
238 return r;
239
240 if (!FLAGS_SET(arg_import_flags, IMPORT_SYNC))
241 log_info("File system synchronization on completion is off.");
242
243 r = raw_import_new(&import, event, arg_image_root, on_raw_finished, event);
244 if (r < 0)
245 return log_error_errno(r, "Failed to allocate importer: %m");
246
247 r = raw_import_start(
248 import,
249 fd,
250 normalized,
251 arg_offset,
252 arg_size_max,
253 arg_import_flags & IMPORT_FLAGS_MASK_RAW);
254 if (r < 0)
255 return log_error_errno(r, "Failed to import image: %m");
256
257 r = sd_event_loop(event);
258 if (r < 0)
259 return log_error_errno(r, "Failed to run event loop: %m");
260
261 log_info("Exiting.");
262 return -r;
263 }
264
help(int argc,char * argv[],void * userdata)265 static int help(int argc, char *argv[], void *userdata) {
266
267 printf("%1$s [OPTIONS...] {COMMAND} ...\n"
268 "\n%4$sImport container or virtual machine images.%5$s\n"
269 "\n%2$sCommands:%3$s\n"
270 " tar FILE [NAME] Import a TAR image\n"
271 " raw FILE [NAME] Import a RAW image\n"
272 "\n%2$sOptions:%3$s\n"
273 " -h --help Show this help\n"
274 " --version Show package version\n"
275 " --force Force creation of image\n"
276 " --image-root=PATH Image root directory\n"
277 " --read-only Create a read-only image\n"
278 " --direct Import directly to specified file\n"
279 " --btrfs-subvol=BOOL Controls whether to create a btrfs subvolume\n"
280 " instead of a directory\n"
281 " --btrfs-quota=BOOL Controls whether to set up quota for btrfs\n"
282 " subvolume\n"
283 " --convert-qcow2=BOOL Controls whether to convert QCOW2 images to\n"
284 " regular disk images\n"
285 " --sync=BOOL Controls whether to sync() before completing\n"
286 " --offset=BYTES Offset to seek to in destination\n"
287 " --size-max=BYTES Maximum number of bytes to write to destination\n",
288 program_invocation_short_name,
289 ansi_underline(),
290 ansi_normal(),
291 ansi_highlight(),
292 ansi_normal());
293
294 return 0;
295 }
296
parse_argv(int argc,char * argv[])297 static int parse_argv(int argc, char *argv[]) {
298
299 enum {
300 ARG_VERSION = 0x100,
301 ARG_FORCE,
302 ARG_IMAGE_ROOT,
303 ARG_READ_ONLY,
304 ARG_DIRECT,
305 ARG_BTRFS_SUBVOL,
306 ARG_BTRFS_QUOTA,
307 ARG_CONVERT_QCOW2,
308 ARG_SYNC,
309 ARG_OFFSET,
310 ARG_SIZE_MAX,
311 };
312
313 static const struct option options[] = {
314 { "help", no_argument, NULL, 'h' },
315 { "version", no_argument, NULL, ARG_VERSION },
316 { "force", no_argument, NULL, ARG_FORCE },
317 { "image-root", required_argument, NULL, ARG_IMAGE_ROOT },
318 { "read-only", no_argument, NULL, ARG_READ_ONLY },
319 { "direct", no_argument, NULL, ARG_DIRECT },
320 { "btrfs-subvol", required_argument, NULL, ARG_BTRFS_SUBVOL },
321 { "btrfs-quota", required_argument, NULL, ARG_BTRFS_QUOTA },
322 { "convert-qcow2", required_argument, NULL, ARG_CONVERT_QCOW2 },
323 { "sync", required_argument, NULL, ARG_SYNC },
324 { "offset", required_argument, NULL, ARG_OFFSET },
325 { "size-max", required_argument, NULL, ARG_SIZE_MAX },
326 {}
327 };
328
329 int r, c;
330
331 assert(argc >= 0);
332 assert(argv);
333
334 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
335
336 switch (c) {
337
338 case 'h':
339 return help(0, NULL, NULL);
340
341 case ARG_VERSION:
342 return version();
343
344 case ARG_FORCE:
345 arg_import_flags |= IMPORT_FORCE;
346 break;
347
348 case ARG_IMAGE_ROOT:
349 arg_image_root = optarg;
350 break;
351
352 case ARG_READ_ONLY:
353 arg_import_flags |= IMPORT_READ_ONLY;
354 break;
355
356 case ARG_DIRECT:
357 arg_import_flags |= IMPORT_DIRECT;
358 break;
359
360 case ARG_BTRFS_SUBVOL:
361 r = parse_boolean_argument("--btrfs-subvol=", optarg, NULL);
362 if (r < 0)
363 return r;
364
365 SET_FLAG(arg_import_flags, IMPORT_BTRFS_SUBVOL, r);
366 break;
367
368 case ARG_BTRFS_QUOTA:
369 r = parse_boolean_argument("--btrfs-quota=", optarg, NULL);
370 if (r < 0)
371 return r;
372
373 SET_FLAG(arg_import_flags, IMPORT_BTRFS_QUOTA, r);
374 break;
375
376 case ARG_CONVERT_QCOW2:
377 r = parse_boolean_argument("--convert-qcow2=", optarg, NULL);
378 if (r < 0)
379 return r;
380
381 SET_FLAG(arg_import_flags, IMPORT_CONVERT_QCOW2, r);
382 break;
383
384 case ARG_SYNC:
385 r = parse_boolean_argument("--sync=", optarg, NULL);
386 if (r < 0)
387 return r;
388
389 SET_FLAG(arg_import_flags, IMPORT_SYNC, r);
390 break;
391
392 case ARG_OFFSET: {
393 uint64_t u;
394
395 r = safe_atou64(optarg, &u);
396 if (r < 0)
397 return log_error_errno(r, "Failed to parse --offset= argument: %s", optarg);
398 if (!FILE_SIZE_VALID(u))
399 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argument to --offset= switch too large: %s", optarg);
400
401 arg_offset = u;
402 break;
403 }
404
405 case ARG_SIZE_MAX: {
406 uint64_t u;
407
408 r = parse_size(optarg, 1024, &u);
409 if (r < 0)
410 return log_error_errno(r, "Failed to parse --size-max= argument: %s", optarg);
411 if (!FILE_SIZE_VALID(u))
412 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argument to --size-max= switch too large: %s", optarg);
413
414 arg_size_max = u;
415 break;
416 }
417
418 case '?':
419 return -EINVAL;
420
421 default:
422 assert_not_reached();
423 }
424
425 /* Make sure offset+size is still in the valid range if both set */
426 if (arg_offset != UINT64_MAX && arg_size_max != UINT64_MAX &&
427 ((arg_size_max > (UINT64_MAX - arg_offset)) ||
428 !FILE_SIZE_VALID(arg_offset + arg_size_max)))
429 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File offset und maximum size out of range.");
430
431 if (arg_offset != UINT64_MAX && !FLAGS_SET(arg_import_flags, IMPORT_DIRECT))
432 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File offset only supported in --direct mode.");
433
434 return 1;
435 }
436
import_main(int argc,char * argv[])437 static int import_main(int argc, char *argv[]) {
438 static const Verb verbs[] = {
439 { "help", VERB_ANY, VERB_ANY, 0, help },
440 { "tar", 2, 3, 0, import_tar },
441 { "raw", 2, 3, 0, import_raw },
442 {}
443 };
444
445 return dispatch_verb(argc, argv, verbs, NULL);
446 }
447
parse_env(void)448 static void parse_env(void) {
449 int r;
450
451 /* Let's make these relatively low-level settings also controllable via env vars. User can then set
452 * them to systemd-import if they like to tweak behaviour */
453
454 r = getenv_bool("SYSTEMD_IMPORT_BTRFS_SUBVOL");
455 if (r >= 0)
456 SET_FLAG(arg_import_flags, IMPORT_BTRFS_SUBVOL, r);
457 else if (r != -ENXIO)
458 log_warning_errno(r, "Failed to parse $SYSTEMD_IMPORT_BTRFS_SUBVOL: %m");
459
460 r = getenv_bool("SYSTEMD_IMPORT_BTRFS_QUOTA");
461 if (r >= 0)
462 SET_FLAG(arg_import_flags, IMPORT_BTRFS_QUOTA, r);
463 else if (r != -ENXIO)
464 log_warning_errno(r, "Failed to parse $SYSTEMD_IMPORT_BTRFS_QUOTA: %m");
465
466 r = getenv_bool("SYSTEMD_IMPORT_SYNC");
467 if (r >= 0)
468 SET_FLAG(arg_import_flags, IMPORT_SYNC, r);
469 else if (r != -ENXIO)
470 log_warning_errno(r, "Failed to parse $SYSTEMD_IMPORT_SYNC: %m");
471 }
472
run(int argc,char * argv[])473 static int run(int argc, char *argv[]) {
474 int r;
475
476 setlocale(LC_ALL, "");
477 log_parse_environment();
478 log_open();
479
480 parse_env();
481
482 r = parse_argv(argc, argv);
483 if (r <= 0)
484 return r;
485
486 (void) ignore_signals(SIGPIPE);
487
488 return import_main(argc, argv);
489 }
490
491 DEFINE_MAIN_FUNCTION(run);
492