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