Line data Source code
1 : /* SPDX-License-Identifier: LGPL-2.1+ */
2 :
3 : #include <getopt.h>
4 : #include <locale.h>
5 :
6 : #include "alloc-util.h"
7 : #include "btrfs-util.h"
8 : #include "fd-util.h"
9 : #include "format-util.h"
10 : #include "fs-util.h"
11 : #include "hostname-util.h"
12 : #include "import-common.h"
13 : #include "import-util.h"
14 : #include "machine-image.h"
15 : #include "mkdir.h"
16 : #include "ratelimit.h"
17 : #include "rm-rf.h"
18 : #include "string-util.h"
19 : #include "tmpfile-util.h"
20 : #include "verbs.h"
21 :
22 : static bool arg_force = false;
23 : static bool arg_read_only = false;
24 : static const char *arg_image_root = "/var/lib/machines";
25 :
26 : typedef struct ProgressInfo {
27 : RateLimit limit;
28 : char *path;
29 : uint64_t size;
30 : bool started;
31 : bool logged_incomplete;
32 : } ProgressInfo;
33 :
34 : static volatile sig_atomic_t cancelled = false;
35 :
36 0 : static void sigterm_sigint(int sig) {
37 0 : cancelled = true;
38 0 : }
39 :
40 0 : static void progress_info_free(ProgressInfo *p) {
41 0 : free(p->path);
42 0 : }
43 :
44 0 : static void progress_show(ProgressInfo *p) {
45 0 : assert(p);
46 :
47 : /* Show progress only every now and then. */
48 0 : if (!ratelimit_below(&p->limit))
49 0 : return;
50 :
51 : /* Suppress the first message, start with the second one */
52 0 : if (!p->started) {
53 0 : p->started = true;
54 0 : return;
55 : }
56 :
57 : /* Mention the list is incomplete before showing first output. */
58 0 : if (!p->logged_incomplete) {
59 0 : log_notice("(Note, file list shown below is incomplete, and is intended as sporadic progress report only.)");
60 0 : p->logged_incomplete = true;
61 : }
62 :
63 0 : if (p->size == 0)
64 0 : log_info("Copying tree, currently at '%s'...", p->path);
65 : else {
66 : char buffer[FORMAT_BYTES_MAX];
67 :
68 0 : log_info("Copying tree, currently at '%s' (@%s)...", p->path, format_bytes(buffer, sizeof(buffer), p->size));
69 : }
70 : }
71 :
72 0 : static int progress_path(const char *path, const struct stat *st, void *userdata) {
73 0 : ProgressInfo *p = userdata;
74 : int r;
75 :
76 0 : assert(p);
77 :
78 0 : if (cancelled)
79 0 : return -EOWNERDEAD;
80 :
81 0 : r = free_and_strdup(&p->path, path);
82 0 : if (r < 0)
83 0 : return r;
84 :
85 0 : p->size = 0;
86 :
87 0 : progress_show(p);
88 0 : return 0;
89 : }
90 :
91 0 : static int progress_bytes(uint64_t nbytes, void *userdata) {
92 0 : ProgressInfo *p = userdata;
93 :
94 0 : assert(p);
95 0 : assert(p->size != UINT64_MAX);
96 :
97 0 : if (cancelled)
98 0 : return -EOWNERDEAD;
99 :
100 0 : p->size += nbytes;
101 :
102 0 : progress_show(p);
103 0 : return 0;
104 : }
105 :
106 0 : static int import_fs(int argc, char *argv[], void *userdata) {
107 0 : _cleanup_(rm_rf_subvolume_and_freep) char *temp_path = NULL;
108 0 : _cleanup_(progress_info_free) ProgressInfo progress = {};
109 0 : const char *path = NULL, *local = NULL, *final_path;
110 0 : _cleanup_close_ int open_fd = -1;
111 : struct sigaction old_sigint_sa, old_sigterm_sa;
112 : static const struct sigaction sa = {
113 : .sa_handler = sigterm_sigint,
114 : .sa_flags = SA_RESTART,
115 : };
116 : int r, fd;
117 :
118 0 : if (argc >= 2)
119 0 : path = argv[1];
120 0 : path = empty_or_dash_to_null(path);
121 :
122 0 : if (argc >= 3)
123 0 : local = argv[2];
124 0 : else if (path)
125 0 : local = basename(path);
126 0 : local = empty_or_dash_to_null(local);
127 :
128 0 : if (local) {
129 0 : if (!machine_name_is_valid(local)) {
130 0 : log_error("Local image name '%s' is not valid.", local);
131 0 : return -EINVAL;
132 : }
133 :
134 0 : if (!arg_force) {
135 0 : r = image_find(IMAGE_MACHINE, local, NULL);
136 0 : if (r < 0) {
137 0 : if (r != -ENOENT)
138 0 : return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local);
139 : } else {
140 0 : log_error("Image '%s' already exists.", local);
141 0 : return -EEXIST;
142 : }
143 : }
144 : } else
145 0 : local = "imported";
146 :
147 0 : if (path) {
148 0 : open_fd = open(path, O_DIRECTORY|O_RDONLY|O_CLOEXEC);
149 0 : if (open_fd < 0)
150 0 : return log_error_errno(errno, "Failed to open directory to import: %m");
151 :
152 0 : fd = open_fd;
153 :
154 0 : log_info("Importing '%s', saving as '%s'.", path, local);
155 : } else {
156 0 : _cleanup_free_ char *pretty = NULL;
157 :
158 0 : fd = STDIN_FILENO;
159 :
160 0 : (void) fd_get_path(fd, &pretty);
161 0 : log_info("Importing '%s', saving as '%s'.", strempty(pretty), local);
162 : }
163 :
164 0 : final_path = prefix_roota(arg_image_root, local);
165 :
166 0 : r = tempfn_random(final_path, NULL, &temp_path);
167 0 : if (r < 0)
168 0 : return log_oom();
169 :
170 0 : (void) mkdir_parents_label(temp_path, 0700);
171 :
172 0 : RATELIMIT_INIT(progress.limit, 200*USEC_PER_MSEC, 1);
173 :
174 : /* Hook into SIGINT/SIGTERM, so that we can cancel things then */
175 0 : assert(sigaction(SIGINT, &sa, &old_sigint_sa) >= 0);
176 0 : assert(sigaction(SIGTERM, &sa, &old_sigterm_sa) >= 0);
177 :
178 0 : r = btrfs_subvol_snapshot_fd_full(
179 : fd,
180 : temp_path,
181 : BTRFS_SNAPSHOT_FALLBACK_COPY|BTRFS_SNAPSHOT_RECURSIVE|BTRFS_SNAPSHOT_FALLBACK_DIRECTORY|BTRFS_SNAPSHOT_QUOTA,
182 : progress_path,
183 : progress_bytes,
184 : &progress);
185 0 : if (r == -EOWNERDEAD) { /* SIGINT + SIGTERM cause this, see signal handler above */
186 0 : log_error("Copy cancelled.");
187 0 : goto finish;
188 : }
189 0 : if (r < 0) {
190 0 : log_error_errno(r, "Failed to copy directory: %m");
191 0 : goto finish;
192 : }
193 :
194 0 : r = import_mangle_os_tree(temp_path);
195 0 : if (r < 0)
196 0 : goto finish;
197 :
198 0 : (void) import_assign_pool_quota_and_warn(temp_path);
199 :
200 0 : if (arg_read_only) {
201 0 : r = import_make_read_only(temp_path);
202 0 : if (r < 0) {
203 0 : log_error_errno(r, "Failed to make directory read-only: %m");
204 0 : goto finish;
205 : }
206 : }
207 :
208 0 : if (arg_force)
209 0 : (void) rm_rf(final_path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME);
210 :
211 0 : r = rename_noreplace(AT_FDCWD, temp_path, AT_FDCWD, final_path);
212 0 : if (r < 0) {
213 0 : log_error_errno(r, "Failed to move image into place: %m");
214 0 : goto finish;
215 : }
216 :
217 0 : temp_path = mfree(temp_path);
218 :
219 0 : log_info("Exiting.");
220 :
221 0 : finish:
222 : /* Put old signal handlers into place */
223 0 : assert(sigaction(SIGINT, &old_sigint_sa, NULL) >= 0);
224 0 : assert(sigaction(SIGTERM, &old_sigterm_sa, NULL) >= 0);
225 :
226 0 : return 0;
227 : }
228 :
229 3 : static int help(int argc, char *argv[], void *userdata) {
230 :
231 3 : printf("%s [OPTIONS...] {COMMAND} ...\n\n"
232 : "Import container images from a file system.\n\n"
233 : " -h --help Show this help\n"
234 : " --version Show package version\n"
235 : " --force Force creation of image\n"
236 : " --image-root=PATH Image root directory\n"
237 : " --read-only Create a read-only image\n\n"
238 : "Commands:\n"
239 : " run DIRECTORY [NAME] Import a directory\n",
240 : program_invocation_short_name);
241 :
242 3 : return 0;
243 : }
244 :
245 4 : static int parse_argv(int argc, char *argv[]) {
246 :
247 : enum {
248 : ARG_VERSION = 0x100,
249 : ARG_FORCE,
250 : ARG_IMAGE_ROOT,
251 : ARG_READ_ONLY,
252 : };
253 :
254 : static const struct option options[] = {
255 : { "help", no_argument, NULL, 'h' },
256 : { "version", no_argument, NULL, ARG_VERSION },
257 : { "force", no_argument, NULL, ARG_FORCE },
258 : { "image-root", required_argument, NULL, ARG_IMAGE_ROOT },
259 : { "read-only", no_argument, NULL, ARG_READ_ONLY },
260 : {}
261 : };
262 :
263 : int c;
264 :
265 4 : assert(argc >= 0);
266 4 : assert(argv);
267 :
268 4 : while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
269 :
270 4 : switch (c) {
271 :
272 3 : case 'h':
273 3 : return help(0, NULL, NULL);
274 :
275 0 : case ARG_VERSION:
276 0 : return version();
277 :
278 0 : case ARG_FORCE:
279 0 : arg_force = true;
280 0 : break;
281 :
282 0 : case ARG_IMAGE_ROOT:
283 0 : arg_image_root = optarg;
284 0 : break;
285 :
286 0 : case ARG_READ_ONLY:
287 0 : arg_read_only = true;
288 0 : break;
289 :
290 1 : case '?':
291 1 : return -EINVAL;
292 :
293 0 : default:
294 0 : assert_not_reached("Unhandled option");
295 : }
296 :
297 0 : return 1;
298 : }
299 :
300 0 : static int import_fs_main(int argc, char *argv[]) {
301 :
302 : static const Verb verbs[] = {
303 : { "help", VERB_ANY, VERB_ANY, 0, help },
304 : { "run", 2, 3, 0, import_fs },
305 : {}
306 : };
307 :
308 0 : return dispatch_verb(argc, argv, verbs, NULL);
309 : }
310 :
311 4 : int main(int argc, char *argv[]) {
312 : int r;
313 :
314 4 : setlocale(LC_ALL, "");
315 4 : log_parse_environment();
316 4 : log_open();
317 :
318 4 : r = parse_argv(argc, argv);
319 4 : if (r <= 0)
320 4 : goto finish;
321 :
322 0 : r = import_fs_main(argc, argv);
323 :
324 4 : finish:
325 4 : return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
326 : }
|