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