]>
Commit | Line | Data |
---|---|---|
1d7579c4 LP |
1 | /* SPDX-License-Identifier: LGPL-2.1+ */ |
2 | ||
3 | #include <getopt.h> | |
ca78ad1d | 4 | #include <locale.h> |
1d7579c4 LP |
5 | |
6 | #include "alloc-util.h" | |
7 | #include "btrfs-util.h" | |
8 | #include "fd-util.h" | |
1d7579c4 LP |
9 | #include "fs-util.h" |
10 | #include "hostname-util.h" | |
11 | #include "import-common.h" | |
12 | #include "import-util.h" | |
13 | #include "machine-image.h" | |
14 | #include "mkdir.h" | |
e4de7287 | 15 | #include "parse-util.h" |
1d7579c4 LP |
16 | #include "ratelimit.h" |
17 | #include "rm-rf.h" | |
18 | #include "string-util.h" | |
e4de7287 | 19 | #include "tmpfile-util.h" |
1d7579c4 | 20 | #include "verbs.h" |
1d7579c4 LP |
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 | static void sigterm_sigint(int sig) { | |
37 | cancelled = true; | |
38 | } | |
39 | ||
40 | static void progress_info_free(ProgressInfo *p) { | |
41 | free(p->path); | |
42 | } | |
43 | ||
44 | static void progress_show(ProgressInfo *p) { | |
45 | assert(p); | |
46 | ||
47 | /* Show progress only every now and then. */ | |
48 | if (!ratelimit_below(&p->limit)) | |
49 | return; | |
50 | ||
51 | /* Suppress the first message, start with the second one */ | |
52 | if (!p->started) { | |
53 | p->started = true; | |
54 | return; | |
55 | } | |
56 | ||
57 | /* Mention the list is incomplete before showing first output. */ | |
58 | if (!p->logged_incomplete) { | |
59 | log_notice("(Note, file list shown below is incomplete, and is intended as sporadic progress report only.)"); | |
60 | p->logged_incomplete = true; | |
61 | } | |
62 | ||
63 | if (p->size == 0) | |
64 | log_info("Copying tree, currently at '%s'...", p->path); | |
65 | else { | |
66 | char buffer[FORMAT_BYTES_MAX]; | |
67 | ||
68 | log_info("Copying tree, currently at '%s' (@%s)...", p->path, format_bytes(buffer, sizeof(buffer), p->size)); | |
69 | } | |
70 | } | |
71 | ||
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 | if (cancelled) | |
79 | return -EOWNERDEAD; | |
80 | ||
81 | r = free_and_strdup(&p->path, path); | |
82 | if (r < 0) | |
83 | return r; | |
84 | ||
85 | p->size = 0; | |
86 | ||
87 | progress_show(p); | |
88 | return 0; | |
89 | } | |
90 | ||
91 | static int progress_bytes(uint64_t nbytes, void *userdata) { | |
92 | ProgressInfo *p = userdata; | |
93 | ||
94 | assert(p); | |
95 | assert(p->size != UINT64_MAX); | |
96 | ||
97 | if (cancelled) | |
98 | return -EOWNERDEAD; | |
99 | ||
100 | p->size += nbytes; | |
101 | ||
102 | progress_show(p); | |
103 | return 0; | |
104 | } | |
105 | ||
106 | static int import_fs(int argc, char *argv[], void *userdata) { | |
107 | _cleanup_(rm_rf_subvolume_and_freep) char *temp_path = NULL; | |
108 | _cleanup_(progress_info_free) ProgressInfo progress = {}; | |
109 | const char *path = NULL, *local = NULL, *final_path; | |
110 | _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 | if (argc >= 2) | |
119 | path = argv[1]; | |
dc90e0fa | 120 | path = empty_or_dash_to_null(path); |
1d7579c4 LP |
121 | |
122 | if (argc >= 3) | |
123 | local = argv[2]; | |
124 | else if (path) | |
125 | local = basename(path); | |
dc90e0fa | 126 | local = empty_or_dash_to_null(local); |
1d7579c4 LP |
127 | |
128 | if (local) { | |
129 | if (!machine_name_is_valid(local)) { | |
130 | log_error("Local image name '%s' is not valid.", local); | |
131 | return -EINVAL; | |
132 | } | |
133 | ||
134 | if (!arg_force) { | |
135 | r = image_find(IMAGE_MACHINE, local, NULL); | |
136 | if (r < 0) { | |
137 | if (r != -ENOENT) | |
138 | return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local); | |
139 | } else { | |
140 | log_error("Image '%s' already exists.", local); | |
141 | return -EEXIST; | |
142 | } | |
143 | } | |
144 | } else | |
145 | local = "imported"; | |
146 | ||
147 | if (path) { | |
148 | open_fd = open(path, O_DIRECTORY|O_RDONLY|O_CLOEXEC); | |
149 | if (open_fd < 0) | |
150 | return log_error_errno(errno, "Failed to open directory to import: %m"); | |
151 | ||
152 | fd = open_fd; | |
153 | ||
154 | log_info("Importing '%s', saving as '%s'.", path, local); | |
155 | } else { | |
156 | _cleanup_free_ char *pretty = NULL; | |
157 | ||
158 | fd = STDIN_FILENO; | |
159 | ||
160 | (void) fd_get_path(fd, &pretty); | |
161 | log_info("Importing '%s', saving as '%s'.", strempty(pretty), local); | |
162 | } | |
163 | ||
164 | final_path = strjoina(arg_image_root, "/", local); | |
165 | ||
166 | r = tempfn_random(final_path, NULL, &temp_path); | |
167 | if (r < 0) | |
168 | return log_oom(); | |
169 | ||
170 | (void) mkdir_parents_label(temp_path, 0700); | |
171 | ||
172 | RATELIMIT_INIT(progress.limit, 200*USEC_PER_MSEC, 1); | |
173 | ||
174 | /* Hook into SIGINT/SIGTERM, so that we can cancel things then */ | |
175 | assert(sigaction(SIGINT, &sa, &old_sigint_sa) >= 0); | |
176 | assert(sigaction(SIGTERM, &sa, &old_sigterm_sa) >= 0); | |
177 | ||
178 | 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 | if (r == -EOWNERDEAD) { /* SIGINT + SIGTERM cause this, see signal handler above */ | |
186 | log_error("Copy cancelled."); | |
187 | goto finish; | |
188 | } | |
189 | if (r < 0) { | |
190 | log_error_errno(r, "Failed to copy directory: %m"); | |
191 | goto finish; | |
192 | } | |
193 | ||
e21b7229 LP |
194 | r = import_mangle_os_tree(temp_path); |
195 | if (r < 0) | |
196 | goto finish; | |
197 | ||
1d7579c4 LP |
198 | (void) import_assign_pool_quota_and_warn(temp_path); |
199 | ||
200 | if (arg_read_only) { | |
201 | r = import_make_read_only(temp_path); | |
202 | if (r < 0) { | |
203 | log_error_errno(r, "Failed to make directory read-only: %m"); | |
204 | goto finish; | |
205 | } | |
206 | } | |
207 | ||
208 | if (arg_force) | |
209 | (void) rm_rf(final_path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME); | |
210 | ||
211 | r = rename_noreplace(AT_FDCWD, temp_path, AT_FDCWD, final_path); | |
212 | if (r < 0) { | |
213 | log_error_errno(r, "Failed to move image into place: %m"); | |
214 | goto finish; | |
215 | } | |
216 | ||
217 | temp_path = mfree(temp_path); | |
218 | ||
219 | log_info("Exiting."); | |
220 | ||
221 | finish: | |
222 | /* Put old signal handlers into place */ | |
223 | assert(sigaction(SIGINT, &old_sigint_sa, NULL) >= 0); | |
224 | assert(sigaction(SIGTERM, &old_sigterm_sa, NULL) >= 0); | |
225 | ||
226 | return 0; | |
227 | } | |
228 | ||
229 | static int help(int argc, char *argv[], void *userdata) { | |
230 | ||
231 | 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 | return 0; | |
243 | } | |
244 | ||
245 | 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 | assert(argc >= 0); | |
266 | assert(argv); | |
267 | ||
268 | while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) | |
269 | ||
270 | switch (c) { | |
271 | ||
272 | case 'h': | |
273 | return help(0, NULL, NULL); | |
274 | ||
275 | case ARG_VERSION: | |
276 | return version(); | |
277 | ||
278 | case ARG_FORCE: | |
279 | arg_force = true; | |
280 | break; | |
281 | ||
282 | case ARG_IMAGE_ROOT: | |
283 | arg_image_root = optarg; | |
284 | break; | |
285 | ||
286 | case ARG_READ_ONLY: | |
287 | arg_read_only = true; | |
288 | break; | |
289 | ||
290 | case '?': | |
291 | return -EINVAL; | |
292 | ||
293 | default: | |
294 | assert_not_reached("Unhandled option"); | |
295 | } | |
296 | ||
297 | return 1; | |
298 | } | |
299 | ||
300 | 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 | return dispatch_verb(argc, argv, verbs, NULL); | |
309 | } | |
310 | ||
311 | int main(int argc, char *argv[]) { | |
312 | int r; | |
313 | ||
314 | setlocale(LC_ALL, ""); | |
315 | log_parse_environment(); | |
316 | log_open(); | |
317 | ||
318 | r = parse_argv(argc, argv); | |
319 | if (r <= 0) | |
320 | goto finish; | |
321 | ||
322 | r = import_fs_main(argc, argv); | |
323 | ||
324 | finish: | |
325 | return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; | |
326 | } |