]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/import/import-fs.c
calendarspec: rename free_chain() to chain_free()
[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 "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"
15 #include "parse-util.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 if (isempty(path) || streq(path, "-"))
121 path = NULL;
122
123 if (argc >= 3)
124 local = argv[2];
125 else if (path)
126 local = basename(path);
127 if (isempty(local) || streq(local, "-"))
128 local = NULL;
129
130 if (local) {
131 if (!machine_name_is_valid(local)) {
132 log_error("Local image name '%s' is not valid.", local);
133 return -EINVAL;
134 }
135
136 if (!arg_force) {
137 r = image_find(IMAGE_MACHINE, local, NULL);
138 if (r < 0) {
139 if (r != -ENOENT)
140 return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local);
141 } else {
142 log_error("Image '%s' already exists.", local);
143 return -EEXIST;
144 }
145 }
146 } else
147 local = "imported";
148
149 if (path) {
150 open_fd = open(path, O_DIRECTORY|O_RDONLY|O_CLOEXEC);
151 if (open_fd < 0)
152 return log_error_errno(errno, "Failed to open directory to import: %m");
153
154 fd = open_fd;
155
156 log_info("Importing '%s', saving as '%s'.", path, local);
157 } else {
158 _cleanup_free_ char *pretty = NULL;
159
160 fd = STDIN_FILENO;
161
162 (void) fd_get_path(fd, &pretty);
163 log_info("Importing '%s', saving as '%s'.", strempty(pretty), local);
164 }
165
166 final_path = strjoina(arg_image_root, "/", local);
167
168 r = tempfn_random(final_path, NULL, &temp_path);
169 if (r < 0)
170 return log_oom();
171
172 (void) mkdir_parents_label(temp_path, 0700);
173
174 RATELIMIT_INIT(progress.limit, 200*USEC_PER_MSEC, 1);
175
176 /* Hook into SIGINT/SIGTERM, so that we can cancel things then */
177 assert(sigaction(SIGINT, &sa, &old_sigint_sa) >= 0);
178 assert(sigaction(SIGTERM, &sa, &old_sigterm_sa) >= 0);
179
180 r = btrfs_subvol_snapshot_fd_full(
181 fd,
182 temp_path,
183 BTRFS_SNAPSHOT_FALLBACK_COPY|BTRFS_SNAPSHOT_RECURSIVE|BTRFS_SNAPSHOT_FALLBACK_DIRECTORY|BTRFS_SNAPSHOT_QUOTA,
184 progress_path,
185 progress_bytes,
186 &progress);
187 if (r == -EOWNERDEAD) { /* SIGINT + SIGTERM cause this, see signal handler above */
188 log_error("Copy cancelled.");
189 goto finish;
190 }
191 if (r < 0) {
192 log_error_errno(r, "Failed to copy directory: %m");
193 goto finish;
194 }
195
196 r = import_mangle_os_tree(temp_path);
197 if (r < 0)
198 goto finish;
199
200 (void) import_assign_pool_quota_and_warn(temp_path);
201
202 if (arg_read_only) {
203 r = import_make_read_only(temp_path);
204 if (r < 0) {
205 log_error_errno(r, "Failed to make directory read-only: %m");
206 goto finish;
207 }
208 }
209
210 if (arg_force)
211 (void) rm_rf(final_path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME);
212
213 r = rename_noreplace(AT_FDCWD, temp_path, AT_FDCWD, final_path);
214 if (r < 0) {
215 log_error_errno(r, "Failed to move image into place: %m");
216 goto finish;
217 }
218
219 temp_path = mfree(temp_path);
220
221 log_info("Exiting.");
222
223 finish:
224 /* Put old signal handlers into place */
225 assert(sigaction(SIGINT, &old_sigint_sa, NULL) >= 0);
226 assert(sigaction(SIGTERM, &old_sigterm_sa, NULL) >= 0);
227
228 return 0;
229 }
230
231 static int help(int argc, char *argv[], void *userdata) {
232
233 printf("%s [OPTIONS...] {COMMAND} ...\n\n"
234 "Import container images from a file system.\n\n"
235 " -h --help Show this help\n"
236 " --version Show package version\n"
237 " --force Force creation of image\n"
238 " --image-root=PATH Image root directory\n"
239 " --read-only Create a read-only image\n\n"
240 "Commands:\n"
241 " run DIRECTORY [NAME] Import a directory\n",
242 program_invocation_short_name);
243
244 return 0;
245 }
246
247 static int parse_argv(int argc, char *argv[]) {
248
249 enum {
250 ARG_VERSION = 0x100,
251 ARG_FORCE,
252 ARG_IMAGE_ROOT,
253 ARG_READ_ONLY,
254 };
255
256 static const struct option options[] = {
257 { "help", no_argument, NULL, 'h' },
258 { "version", no_argument, NULL, ARG_VERSION },
259 { "force", no_argument, NULL, ARG_FORCE },
260 { "image-root", required_argument, NULL, ARG_IMAGE_ROOT },
261 { "read-only", no_argument, NULL, ARG_READ_ONLY },
262 {}
263 };
264
265 int c;
266
267 assert(argc >= 0);
268 assert(argv);
269
270 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
271
272 switch (c) {
273
274 case 'h':
275 return help(0, NULL, NULL);
276
277 case ARG_VERSION:
278 return version();
279
280 case ARG_FORCE:
281 arg_force = true;
282 break;
283
284 case ARG_IMAGE_ROOT:
285 arg_image_root = optarg;
286 break;
287
288 case ARG_READ_ONLY:
289 arg_read_only = true;
290 break;
291
292 case '?':
293 return -EINVAL;
294
295 default:
296 assert_not_reached("Unhandled option");
297 }
298
299 return 1;
300 }
301
302 static int import_fs_main(int argc, char *argv[]) {
303
304 static const Verb verbs[] = {
305 { "help", VERB_ANY, VERB_ANY, 0, help },
306 { "run", 2, 3, 0, import_fs },
307 {}
308 };
309
310 return dispatch_verb(argc, argv, verbs, NULL);
311 }
312
313 int main(int argc, char *argv[]) {
314 int r;
315
316 setlocale(LC_ALL, "");
317 log_parse_environment();
318 log_open();
319
320 r = parse_argv(argc, argv);
321 if (r <= 0)
322 goto finish;
323
324 r = import_fs_main(argc, argv);
325
326 finish:
327 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
328 }