]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/import/import-fs.c
ca5d33c00821b0669f41a027fac52404fb207682
[thirdparty/systemd.git] / src / import / import-fs.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <getopt.h>
4 #include <locale.h>
5
6 #include "alloc-util.h"
7 #include "build.h"
8 #include "btrfs-util.h"
9 #include "discover-image.h"
10 #include "fd-util.h"
11 #include "format-util.h"
12 #include "fs-util.h"
13 #include "hostname-util.h"
14 #include "import-common.h"
15 #include "import-util.h"
16 #include "install-file.h"
17 #include "main-func.h"
18 #include "mkdir-label.h"
19 #include "parse-argument.h"
20 #include "ratelimit.h"
21 #include "rm-rf.h"
22 #include "signal-util.h"
23 #include "string-util.h"
24 #include "terminal-util.h"
25 #include "tmpfile-util.h"
26 #include "verbs.h"
27
28 static bool arg_force = false;
29 static bool arg_read_only = false;
30 static bool arg_btrfs_subvol = true;
31 static bool arg_btrfs_quota = true;
32 static bool arg_sync = true;
33 static bool arg_direct = false;
34 static const char *arg_image_root = "/var/lib/machines";
35
36 typedef struct ProgressInfo {
37 RateLimit limit;
38 char *path;
39 uint64_t size;
40 bool started;
41 bool logged_incomplete;
42 } ProgressInfo;
43
44 static void progress_info_free(ProgressInfo *p) {
45 free(p->path);
46 }
47
48 static void progress_show(ProgressInfo *p) {
49 assert(p);
50
51 /* Show progress only every now and then. */
52 if (!ratelimit_below(&p->limit))
53 return;
54
55 /* Suppress the first message, start with the second one */
56 if (!p->started) {
57 p->started = true;
58 return;
59 }
60
61 /* Mention the list is incomplete before showing first output. */
62 if (!p->logged_incomplete) {
63 log_notice("(Note: file list shown below is incomplete, and is intended as sporadic progress report only.)");
64 p->logged_incomplete = true;
65 }
66
67 if (p->size == 0)
68 log_info("Copying tree, currently at '%s'...", p->path);
69 else
70 log_info("Copying tree, currently at '%s' (@%s)...", p->path, FORMAT_BYTES(p->size));
71 }
72
73 static int progress_path(const char *path, const struct stat *st, void *userdata) {
74 ProgressInfo *p = ASSERT_PTR(userdata);
75 int r;
76
77 r = free_and_strdup(&p->path, path);
78 if (r < 0)
79 return r;
80
81 p->size = 0;
82
83 progress_show(p);
84 return 0;
85 }
86
87 static int progress_bytes(uint64_t nbytes, void *userdata) {
88 ProgressInfo *p = ASSERT_PTR(userdata);
89
90 assert(p->size != UINT64_MAX);
91
92 p->size += nbytes;
93
94 progress_show(p);
95 return 0;
96 }
97
98 static int import_fs(int argc, char *argv[], void *userdata) {
99 _cleanup_(rm_rf_subvolume_and_freep) char *temp_path = NULL;
100 _cleanup_(progress_info_free) ProgressInfo progress = {};
101 _cleanup_free_ char *l = NULL, *final_path = NULL;
102 const char *path = NULL, *local = NULL, *dest = NULL;
103 _cleanup_close_ int open_fd = -1;
104 int r, fd;
105
106 if (argc >= 2)
107 path = empty_or_dash_to_null(argv[1]);
108
109 if (argc >= 3)
110 local = empty_or_dash_to_null(argv[2]);
111 else if (path) {
112 r = path_extract_filename(path, &l);
113 if (r < 0)
114 return log_error_errno(r, "Failed to extract filename from path '%s': %m", path);
115
116 local = l;
117 }
118
119 if (arg_direct) {
120 if (!local)
121 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No local path specified.");
122
123 if (path_is_absolute(local))
124 final_path = strdup(local);
125 else
126 final_path = path_join(arg_image_root, local);
127 if (!final_path)
128 return log_oom();
129
130 if (!path_is_valid(final_path))
131 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
132 "Local path name '%s' is not valid.", final_path);
133 } else {
134 if (local) {
135 if (!hostname_is_valid(local, 0))
136 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
137 "Local image name '%s' is not valid.", local);
138 } else
139 local = "imported";
140
141 final_path = path_join(arg_image_root, local);
142 if (!final_path)
143 return log_oom();
144
145 if (!arg_force) {
146 r = image_find(IMAGE_MACHINE, local, NULL, NULL);
147 if (r < 0) {
148 if (r != -ENOENT)
149 return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local);
150 } else
151 return log_error_errno(SYNTHETIC_ERRNO(EEXIST),
152 "Image '%s' already exists.", local);
153 }
154 }
155
156 if (path) {
157 open_fd = open(path, O_DIRECTORY|O_RDONLY|O_CLOEXEC);
158 if (open_fd < 0)
159 return log_error_errno(errno, "Failed to open directory to import: %m");
160
161 fd = open_fd;
162
163 log_info("Importing '%s', saving as '%s'.", path, local);
164 } else {
165 _cleanup_free_ char *pretty = NULL;
166
167 fd = STDIN_FILENO;
168
169 (void) fd_get_path(fd, &pretty);
170 log_info("Importing '%s', saving as '%s'.", strempty(pretty), local);
171 }
172
173 if (!arg_sync)
174 log_info("File system synchronization on completion is off.");
175
176 if (arg_direct) {
177 if (arg_force)
178 (void) rm_rf(final_path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME);
179
180 dest = final_path;
181 } else {
182 r = tempfn_random(final_path, NULL, &temp_path);
183 if (r < 0)
184 return log_oom();
185
186 dest = temp_path;
187 }
188
189 (void) mkdir_parents_label(dest, 0700);
190
191 progress.limit = (const RateLimit) { 200*USEC_PER_MSEC, 1 };
192
193 {
194 BLOCK_SIGNALS(SIGINT, SIGTERM);
195
196 if (arg_btrfs_subvol)
197 r = btrfs_subvol_snapshot_fd_full(
198 fd,
199 dest,
200 BTRFS_SNAPSHOT_FALLBACK_COPY|
201 BTRFS_SNAPSHOT_FALLBACK_DIRECTORY|
202 BTRFS_SNAPSHOT_RECURSIVE|
203 BTRFS_SNAPSHOT_SIGINT|
204 BTRFS_SNAPSHOT_SIGTERM,
205 progress_path,
206 progress_bytes,
207 &progress);
208 else
209 r = copy_directory_fd_full(
210 fd,
211 dest,
212 COPY_REFLINK|
213 COPY_SAME_MOUNT|
214 COPY_HARDLINKS|
215 COPY_SIGINT|
216 COPY_SIGTERM|
217 (arg_direct ? COPY_MERGE_EMPTY : 0),
218 progress_path,
219 progress_bytes,
220 &progress);
221 if (r == -EINTR) /* SIGINT/SIGTERM hit */
222 return log_error_errno(r, "Copy cancelled.");
223 if (r < 0)
224 return log_error_errno(r, "Failed to copy directory: %m");
225 }
226
227 r = import_mangle_os_tree(dest);
228 if (r < 0)
229 return r;
230
231 if (arg_btrfs_quota) {
232 if (!arg_direct)
233 (void) import_assign_pool_quota_and_warn(arg_image_root);
234 (void) import_assign_pool_quota_and_warn(dest);
235 }
236
237 r = install_file(AT_FDCWD, dest,
238 AT_FDCWD, arg_direct ? NULL : final_path, /* pass NULL as target in case of direct
239 * mode since file is already in place */
240 (arg_force ? INSTALL_REPLACE : 0) |
241 (arg_read_only ? INSTALL_READ_ONLY : 0) |
242 (arg_sync ? INSTALL_SYNCFS : 0));
243 if (r < 0)
244 return log_error_errno(r, "Failed install directory as '%s': %m", final_path);
245
246 temp_path = mfree(temp_path);
247
248 log_info("Directory '%s successfully installed. Exiting.", final_path);
249 return 0;
250 }
251
252 static int help(int argc, char *argv[], void *userdata) {
253
254 printf("%1$s [OPTIONS...] {COMMAND} ...\n"
255 "\n%4$sImport container images from a file system directories.%5$s\n"
256 "\n%2$sCommands:%3$s\n"
257 " run DIRECTORY [NAME] Import a directory\n"
258 "\n%2$sOptions:%3$s\n"
259 " -h --help Show this help\n"
260 " --version Show package version\n"
261 " --force Force creation of image\n"
262 " --image-root=PATH Image root directory\n"
263 " --read-only Create a read-only image\n"
264 " --direct Import directly to specified directory\n"
265 " --btrfs-subvol=BOOL Controls whether to create a btrfs subvolume\n"
266 " instead of a directory\n"
267 " --btrfs-quota=BOOL Controls whether to set up quota for btrfs\n"
268 " subvolume\n"
269 " --sync=BOOL Controls whether to sync() before completing\n",
270 program_invocation_short_name,
271 ansi_underline(),
272 ansi_normal(),
273 ansi_highlight(),
274 ansi_normal());
275
276 return 0;
277 }
278
279 static int parse_argv(int argc, char *argv[]) {
280
281 enum {
282 ARG_VERSION = 0x100,
283 ARG_FORCE,
284 ARG_IMAGE_ROOT,
285 ARG_READ_ONLY,
286 ARG_DIRECT,
287 ARG_BTRFS_SUBVOL,
288 ARG_BTRFS_QUOTA,
289 ARG_SYNC,
290 };
291
292 static const struct option options[] = {
293 { "help", no_argument, NULL, 'h' },
294 { "version", no_argument, NULL, ARG_VERSION },
295 { "force", no_argument, NULL, ARG_FORCE },
296 { "image-root", required_argument, NULL, ARG_IMAGE_ROOT },
297 { "read-only", no_argument, NULL, ARG_READ_ONLY },
298 { "direct", no_argument, NULL, ARG_DIRECT },
299 { "btrfs-subvol", required_argument, NULL, ARG_BTRFS_SUBVOL },
300 { "btrfs-quota", required_argument, NULL, ARG_BTRFS_QUOTA },
301 { "sync", required_argument, NULL, ARG_SYNC },
302 {}
303 };
304
305 int c, r;
306
307 assert(argc >= 0);
308 assert(argv);
309
310 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
311
312 switch (c) {
313
314 case 'h':
315 return help(0, NULL, NULL);
316
317 case ARG_VERSION:
318 return version();
319
320 case ARG_FORCE:
321 arg_force = true;
322 break;
323
324 case ARG_IMAGE_ROOT:
325 arg_image_root = optarg;
326 break;
327
328 case ARG_READ_ONLY:
329 arg_read_only = true;
330 break;
331
332 case ARG_DIRECT:
333 arg_direct = true;
334 break;
335
336 case ARG_BTRFS_SUBVOL:
337 r = parse_boolean_argument("--btrfs-subvol=", optarg, &arg_btrfs_subvol);
338 if (r < 0)
339 return r;
340
341 break;
342
343 case ARG_BTRFS_QUOTA:
344 r = parse_boolean_argument("--btrfs-quota=", optarg, &arg_btrfs_quota);
345 if (r < 0)
346 return r;
347
348 break;
349
350 case ARG_SYNC:
351 r = parse_boolean_argument("--sync=", optarg, &arg_sync);
352 if (r < 0)
353 return r;
354
355 break;
356
357 case '?':
358 return -EINVAL;
359
360 default:
361 assert_not_reached();
362 }
363
364 return 1;
365 }
366
367 static int import_fs_main(int argc, char *argv[]) {
368
369 static const Verb verbs[] = {
370 { "help", VERB_ANY, VERB_ANY, 0, help },
371 { "run", 2, 3, 0, import_fs },
372 {}
373 };
374
375 return dispatch_verb(argc, argv, verbs, NULL);
376 }
377
378 static int run(int argc, char *argv[]) {
379 int r;
380
381 setlocale(LC_ALL, "");
382 log_parse_environment();
383 log_open();
384
385 r = parse_argv(argc, argv);
386 if (r <= 0)
387 return r;
388
389 return import_fs_main(argc, argv);
390 }
391
392 DEFINE_MAIN_FUNCTION(run);