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