]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/import/import-fs.c
Drop RATELIMIT macros
[thirdparty/systemd.git] / src / import / import-fs.c
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 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];
120 path = empty_or_dash_to_null(path);
121
122 if (argc >= 3)
123 local = argv[2];
124 else if (path)
125 local = basename(path);
126 local = empty_or_dash_to_null(local);
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 = prefix_roota(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 progress.limit = (RateLimit) { 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
194 r = import_mangle_os_tree(temp_path);
195 if (r < 0)
196 goto finish;
197
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 }