]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/machine-pool.c
Add SPDX license identifiers to source files under the LGPL
[thirdparty/systemd.git] / src / shared / machine-pool.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 This file is part of systemd.
4
5 Copyright 2015 Lennart Poettering
6
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19 ***/
20
21 #include <errno.h>
22 #include <fcntl.h>
23 #include <linux/loop.h>
24 #include <signal.h>
25 #include <stdbool.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <sys/file.h>
29 #include <sys/ioctl.h>
30 #include <sys/mount.h>
31 #include <sys/prctl.h>
32 #include <sys/stat.h>
33 #include <sys/statfs.h>
34 #include <sys/statvfs.h>
35 #include <unistd.h>
36
37 #include "sd-bus-protocol.h"
38 #include "sd-bus.h"
39
40 #include "alloc-util.h"
41 #include "btrfs-util.h"
42 #include "fd-util.h"
43 #include "fileio.h"
44 #include "fs-util.h"
45 #include "lockfile-util.h"
46 #include "log.h"
47 #include "machine-pool.h"
48 #include "macro.h"
49 #include "missing.h"
50 #include "mkdir.h"
51 #include "mount-util.h"
52 #include "parse-util.h"
53 #include "path-util.h"
54 #include "process-util.h"
55 #include "signal-util.h"
56 #include "stat-util.h"
57 #include "string-util.h"
58
59 #define VAR_LIB_MACHINES_SIZE_START (1024UL*1024UL*500UL)
60 #define VAR_LIB_MACHINES_FREE_MIN (1024UL*1024UL*750UL)
61
62 static int check_btrfs(void) {
63 struct statfs sfs;
64
65 if (statfs("/var/lib/machines", &sfs) < 0) {
66 if (errno != ENOENT)
67 return -errno;
68
69 if (statfs("/var/lib", &sfs) < 0)
70 return -errno;
71 }
72
73 return F_TYPE_EQUAL(sfs.f_type, BTRFS_SUPER_MAGIC);
74 }
75
76 static int setup_machine_raw(uint64_t size, sd_bus_error *error) {
77 _cleanup_free_ char *tmp = NULL;
78 _cleanup_close_ int fd = -1;
79 struct statvfs ss;
80 pid_t pid = 0;
81 siginfo_t si;
82 int r;
83
84 /* We want to be able to make use of btrfs-specific file
85 * system features, in particular subvolumes, reflinks and
86 * quota. Hence, if we detect that /var/lib/machines.raw is
87 * not located on btrfs, let's create a loopback file, place a
88 * btrfs file system into it, and mount it to
89 * /var/lib/machines. */
90
91 fd = open("/var/lib/machines.raw", O_RDWR|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
92 if (fd >= 0) {
93 r = fd;
94 fd = -1;
95 return r;
96 }
97
98 if (errno != ENOENT)
99 return sd_bus_error_set_errnof(error, errno, "Failed to open /var/lib/machines.raw: %m");
100
101 r = tempfn_xxxxxx("/var/lib/machines.raw", NULL, &tmp);
102 if (r < 0)
103 return r;
104
105 (void) mkdir_p_label("/var/lib", 0755);
106 fd = open(tmp, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0600);
107 if (fd < 0)
108 return sd_bus_error_set_errnof(error, errno, "Failed to create /var/lib/machines.raw: %m");
109
110 if (fstatvfs(fd, &ss) < 0) {
111 r = sd_bus_error_set_errnof(error, errno, "Failed to determine free space on /var/lib/machines.raw: %m");
112 goto fail;
113 }
114
115 if (ss.f_bsize * ss.f_bavail < VAR_LIB_MACHINES_FREE_MIN) {
116 r = sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Not enough free disk space to set up /var/lib/machines.");
117 goto fail;
118 }
119
120 if (ftruncate(fd, size) < 0) {
121 r = sd_bus_error_set_errnof(error, errno, "Failed to enlarge /var/lib/machines.raw: %m");
122 goto fail;
123 }
124
125 pid = fork();
126 if (pid < 0) {
127 r = sd_bus_error_set_errnof(error, errno, "Failed to fork mkfs.btrfs: %m");
128 goto fail;
129 }
130
131 if (pid == 0) {
132
133 /* Child */
134
135 (void) reset_all_signal_handlers();
136 (void) reset_signal_mask();
137 assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0);
138
139 fd = safe_close(fd);
140
141 execlp("mkfs.btrfs", "-Lvar-lib-machines", tmp, NULL);
142 if (errno == ENOENT)
143 _exit(99);
144
145 _exit(EXIT_FAILURE);
146 }
147
148 r = wait_for_terminate(pid, &si);
149 if (r < 0) {
150 sd_bus_error_set_errnof(error, r, "Failed to wait for mkfs.btrfs: %m");
151 goto fail;
152 }
153
154 pid = 0;
155
156 if (si.si_code != CLD_EXITED) {
157 r = sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "mkfs.btrfs died abnormally.");
158 goto fail;
159 }
160 if (si.si_status == 99) {
161 r = sd_bus_error_set_errnof(error, ENOENT, "Cannot set up /var/lib/machines, mkfs.btrfs is missing");
162 goto fail;
163 }
164 if (si.si_status != 0) {
165 r = sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "mkfs.btrfs failed with error code %i", si.si_status);
166 goto fail;
167 }
168
169 r = rename_noreplace(AT_FDCWD, tmp, AT_FDCWD, "/var/lib/machines.raw");
170 if (r < 0) {
171 sd_bus_error_set_errnof(error, r, "Failed to move /var/lib/machines.raw into place: %m");
172 goto fail;
173 }
174
175 r = fd;
176 fd = -1;
177
178 return r;
179
180 fail:
181 unlink_noerrno(tmp);
182
183 if (pid > 1)
184 kill_and_sigcont(pid, SIGKILL);
185
186 return r;
187 }
188
189 int setup_machine_directory(uint64_t size, sd_bus_error *error) {
190 _cleanup_release_lock_file_ LockFile lock_file = LOCK_FILE_INIT;
191 struct loop_info64 info = {
192 .lo_flags = LO_FLAGS_AUTOCLEAR,
193 };
194 _cleanup_close_ int fd = -1, control = -1, loop = -1;
195 _cleanup_free_ char* loopdev = NULL;
196 char tmpdir[] = "/tmp/machine-pool.XXXXXX", *mntdir = NULL;
197 bool tmpdir_made = false, mntdir_made = false, mntdir_mounted = false;
198 char buf[FORMAT_BYTES_MAX];
199 int r, nr = -1;
200
201 /* btrfs cannot handle file systems < 16M, hence use this as minimum */
202 if (size == (uint64_t) -1)
203 size = VAR_LIB_MACHINES_SIZE_START;
204 else if (size < 16*1024*1024)
205 size = 16*1024*1024;
206
207 /* Make sure we only set the directory up once at a time */
208 r = make_lock_file("/run/systemd/machines.lock", LOCK_EX, &lock_file);
209 if (r < 0)
210 return r;
211
212 r = check_btrfs();
213 if (r < 0)
214 return sd_bus_error_set_errnof(error, r, "Failed to determine whether /var/lib/machines is located on btrfs: %m");
215 if (r > 0) {
216 (void) btrfs_subvol_make_label("/var/lib/machines");
217
218 r = btrfs_quota_enable("/var/lib/machines", true);
219 if (r < 0)
220 log_warning_errno(r, "Failed to enable quota for /var/lib/machines, ignoring: %m");
221
222 r = btrfs_subvol_auto_qgroup("/var/lib/machines", 0, true);
223 if (r < 0)
224 log_warning_errno(r, "Failed to set up default quota hierarchy for /var/lib/machines, ignoring: %m");
225
226 return 1;
227 }
228
229 if (path_is_mount_point("/var/lib/machines", NULL, AT_SYMLINK_FOLLOW) > 0) {
230 log_debug("/var/lib/machines is already a mount point, not creating loopback file for it.");
231 return 0;
232 }
233
234 r = dir_is_populated("/var/lib/machines");
235 if (r < 0 && r != -ENOENT)
236 return r;
237 if (r > 0) {
238 log_debug("/var/log/machines is already populated, not creating loopback file for it.");
239 return 0;
240 }
241
242 r = mkfs_exists("btrfs");
243 if (r == 0)
244 return sd_bus_error_set_errnof(error, ENOENT, "Cannot set up /var/lib/machines, mkfs.btrfs is missing");
245 if (r < 0)
246 return r;
247
248 fd = setup_machine_raw(size, error);
249 if (fd < 0)
250 return fd;
251
252 control = open("/dev/loop-control", O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
253 if (control < 0)
254 return sd_bus_error_set_errnof(error, errno, "Failed to open /dev/loop-control: %m");
255
256 nr = ioctl(control, LOOP_CTL_GET_FREE);
257 if (nr < 0)
258 return sd_bus_error_set_errnof(error, errno, "Failed to allocate loop device: %m");
259
260 if (asprintf(&loopdev, "/dev/loop%i", nr) < 0) {
261 r = -ENOMEM;
262 goto fail;
263 }
264
265 loop = open(loopdev, O_CLOEXEC|O_RDWR|O_NOCTTY|O_NONBLOCK);
266 if (loop < 0) {
267 r = sd_bus_error_set_errnof(error, errno, "Failed to open loopback device: %m");
268 goto fail;
269 }
270
271 if (ioctl(loop, LOOP_SET_FD, fd) < 0) {
272 r = sd_bus_error_set_errnof(error, errno, "Failed to bind loopback device: %m");
273 goto fail;
274 }
275
276 if (ioctl(loop, LOOP_SET_STATUS64, &info) < 0) {
277 r = sd_bus_error_set_errnof(error, errno, "Failed to enable auto-clear for loopback device: %m");
278 goto fail;
279 }
280
281 /* We need to make sure the new /var/lib/machines directory
282 * has an access mode of 0700 at the time it is first made
283 * available. mkfs will create it with 0755 however. Hence,
284 * let's mount the directory into an inaccessible directory
285 * below /tmp first, fix the access mode, and move it to the
286 * public place then. */
287
288 if (!mkdtemp(tmpdir)) {
289 r = sd_bus_error_set_errnof(error, errno, "Failed to create temporary mount parent directory: %m");
290 goto fail;
291 }
292 tmpdir_made = true;
293
294 mntdir = strjoina(tmpdir, "/mnt");
295 if (mkdir(mntdir, 0700) < 0) {
296 r = sd_bus_error_set_errnof(error, errno, "Failed to create temporary mount directory: %m");
297 goto fail;
298 }
299 mntdir_made = true;
300
301 if (mount(loopdev, mntdir, "btrfs", 0, NULL) < 0) {
302 r = sd_bus_error_set_errnof(error, errno, "Failed to mount loopback device: %m");
303 goto fail;
304 }
305 mntdir_mounted = true;
306
307 r = btrfs_quota_enable(mntdir, true);
308 if (r < 0)
309 log_warning_errno(r, "Failed to enable quota, ignoring: %m");
310
311 r = btrfs_subvol_auto_qgroup(mntdir, 0, true);
312 if (r < 0)
313 log_warning_errno(r, "Failed to set up default quota hierarchy, ignoring: %m");
314
315 if (chmod(mntdir, 0700) < 0) {
316 r = sd_bus_error_set_errnof(error, errno, "Failed to fix owner: %m");
317 goto fail;
318 }
319
320 (void) mkdir_p_label("/var/lib/machines", 0700);
321
322 if (mount(mntdir, "/var/lib/machines", NULL, MS_BIND, NULL) < 0) {
323 r = sd_bus_error_set_errnof(error, errno, "Failed to mount directory into right place: %m");
324 goto fail;
325 }
326
327 (void) syncfs(fd);
328
329 log_info("Set up /var/lib/machines as btrfs loopback file system of size %s mounted on /var/lib/machines.raw.", format_bytes(buf, sizeof(buf), size));
330
331 (void) umount2(mntdir, MNT_DETACH);
332 (void) rmdir(mntdir);
333 (void) rmdir(tmpdir);
334
335 return 1;
336
337 fail:
338 if (mntdir_mounted)
339 (void) umount2(mntdir, MNT_DETACH);
340
341 if (mntdir_made)
342 (void) rmdir(mntdir);
343 if (tmpdir_made)
344 (void) rmdir(tmpdir);
345
346 if (loop >= 0) {
347 (void) ioctl(loop, LOOP_CLR_FD);
348 loop = safe_close(loop);
349 }
350
351 if (control >= 0 && nr >= 0)
352 (void) ioctl(control, LOOP_CTL_REMOVE, nr);
353
354 return r;
355 }
356
357 static int sync_path(const char *p) {
358 _cleanup_close_ int fd = -1;
359
360 fd = open(p, O_RDONLY|O_CLOEXEC|O_NOCTTY);
361 if (fd < 0)
362 return -errno;
363
364 if (syncfs(fd) < 0)
365 return -errno;
366
367 return 0;
368 }
369
370 int grow_machine_directory(void) {
371 char buf[FORMAT_BYTES_MAX];
372 struct statvfs a, b;
373 uint64_t old_size, new_size, max_add;
374 int r;
375
376 /* Ensure the disk space data is accurate */
377 sync_path("/var/lib/machines");
378 sync_path("/var/lib/machines.raw");
379
380 if (statvfs("/var/lib/machines.raw", &a) < 0)
381 return -errno;
382
383 if (statvfs("/var/lib/machines", &b) < 0)
384 return -errno;
385
386 /* Don't grow if not enough disk space is available on the host */
387 if (((uint64_t) a.f_bavail * (uint64_t) a.f_bsize) <= VAR_LIB_MACHINES_FREE_MIN)
388 return 0;
389
390 /* Don't grow if at least 1/3th of the fs is still free */
391 if (b.f_bavail > b.f_blocks / 3)
392 return 0;
393
394 /* Calculate how much we are willing to add at most */
395 max_add = ((uint64_t) a.f_bavail * (uint64_t) a.f_bsize) - VAR_LIB_MACHINES_FREE_MIN;
396
397 /* Calculate the old size */
398 old_size = (uint64_t) b.f_blocks * (uint64_t) b.f_bsize;
399
400 /* Calculate the new size as three times the size of what is used right now */
401 new_size = ((uint64_t) b.f_blocks - (uint64_t) b.f_bavail) * (uint64_t) b.f_bsize * 3;
402
403 /* Always, grow at least to the start size */
404 if (new_size < VAR_LIB_MACHINES_SIZE_START)
405 new_size = VAR_LIB_MACHINES_SIZE_START;
406
407 /* If the new size is smaller than the old size, don't grow */
408 if (new_size < old_size)
409 return 0;
410
411 /* Ensure we never add more than the maximum */
412 if (new_size > old_size + max_add)
413 new_size = old_size + max_add;
414
415 r = btrfs_resize_loopback("/var/lib/machines", new_size, true);
416 if (r <= 0)
417 return r;
418
419 /* Also bump the quota, of both the subvolume leaf qgroup, as
420 * well as of any subtree quota group by the same id but a
421 * higher level, if it exists. */
422 (void) btrfs_qgroup_set_limit("/var/lib/machines", 0, new_size);
423 (void) btrfs_subvol_set_subtree_quota_limit("/var/lib/machines", 0, new_size);
424
425 log_info("Grew /var/lib/machines btrfs loopback file system to %s.", format_bytes(buf, sizeof(buf), new_size));
426 return 1;
427 }