Branch data 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 : 12 : static int help(int argc, char *argv[], void *userdata) {
230 : :
231 : 12 : 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 : 12 : return 0;
243 : : }
244 : :
245 : 16 : 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 [ - + ]: 16 : assert(argc >= 0);
266 [ - + ]: 16 : assert(argv);
267 : :
268 [ + - ]: 16 : while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
269 : :
270 [ + - - - : 16 : switch (c) {
- + - ]
271 : :
272 : 12 : case 'h':
273 : 12 : 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 : 4 : case '?':
291 : 4 : 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 : 16 : int main(int argc, char *argv[]) {
312 : : int r;
313 : :
314 : 16 : setlocale(LC_ALL, "");
315 : 16 : log_parse_environment();
316 : 16 : log_open();
317 : :
318 : 16 : r = parse_argv(argc, argv);
319 [ + - ]: 16 : if (r <= 0)
320 : 16 : goto finish;
321 : :
322 : 0 : r = import_fs_main(argc, argv);
323 : :
324 : 16 : finish:
325 : 16 : return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
326 : : }
|