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