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