]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/machine-pool.c
Merge pull request #7191 from Mic92/systemd
[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 "label.h"
46 #include "lockfile-util.h"
47 #include "log.h"
48 #include "machine-pool.h"
49 #include "macro.h"
50 #include "missing.h"
51 #include "mkdir.h"
52 #include "mount-util.h"
53 #include "parse-util.h"
54 #include "path-util.h"
55 #include "process-util.h"
56 #include "signal-util.h"
57 #include "stat-util.h"
58 #include "string-util.h"
59
60 #define VAR_LIB_MACHINES_SIZE_START (1024UL*1024UL*500UL)
61 #define VAR_LIB_MACHINES_FREE_MIN (1024UL*1024UL*750UL)
62
63 static int check_btrfs(void) {
64 struct statfs sfs;
65
66 if (statfs("/var/lib/machines", &sfs) < 0) {
67 if (errno != ENOENT)
68 return -errno;
69
70 if (statfs("/var/lib", &sfs) < 0)
71 return -errno;
72 }
73
74 return F_TYPE_EQUAL(sfs.f_type, BTRFS_SUPER_MAGIC);
75 }
76
77 static int setup_machine_raw(uint64_t size, sd_bus_error *error) {
78 _cleanup_free_ char *tmp = NULL;
79 _cleanup_close_ int fd = -1;
80 struct statvfs ss;
81 pid_t pid = 0;
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 r = safe_fork("(mkfs)", FORK_RESET_SIGNALS|FORK_DEATHSIG, &pid);
126 if (r < 0) {
127 sd_bus_error_set_errnof(error, r, "Failed to fork mkfs.btrfs: %m");
128 goto fail;
129 }
130 if (r == 0) {
131
132 /* Child */
133
134 fd = safe_close(fd);
135
136 execlp("mkfs.btrfs", "-Lvar-lib-machines", tmp, NULL);
137 if (errno == ENOENT)
138 _exit(99);
139
140 _exit(EXIT_FAILURE);
141 }
142
143 r = wait_for_terminate_and_check("mkfs", pid, 0);
144 pid = 0;
145
146 if (r < 0) {
147 sd_bus_error_set_errnof(error, r, "Failed to wait for mkfs.btrfs: %m");
148 goto fail;
149 }
150 if (r == 99) {
151 r = sd_bus_error_set_errnof(error, ENOENT, "Cannot set up /var/lib/machines, mkfs.btrfs is missing");
152 goto fail;
153 }
154 if (r != EXIT_SUCCESS) {
155 r = sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "mkfs.btrfs failed with error code %i", r);
156 goto fail;
157 }
158
159 r = rename_noreplace(AT_FDCWD, tmp, AT_FDCWD, "/var/lib/machines.raw");
160 if (r < 0) {
161 sd_bus_error_set_errnof(error, r, "Failed to move /var/lib/machines.raw into place: %m");
162 goto fail;
163 }
164
165 r = fd;
166 fd = -1;
167
168 return r;
169
170 fail:
171 unlink_noerrno(tmp);
172
173 if (pid > 1)
174 kill_and_sigcont(pid, SIGKILL);
175
176 return r;
177 }
178
179 int setup_machine_directory(uint64_t size, sd_bus_error *error) {
180 _cleanup_release_lock_file_ LockFile lock_file = LOCK_FILE_INIT;
181 struct loop_info64 info = {
182 .lo_flags = LO_FLAGS_AUTOCLEAR,
183 };
184 _cleanup_close_ int fd = -1, control = -1, loop = -1;
185 _cleanup_free_ char* loopdev = NULL;
186 char tmpdir[] = "/tmp/machine-pool.XXXXXX", *mntdir = NULL;
187 bool tmpdir_made = false, mntdir_made = false, mntdir_mounted = false;
188 char buf[FORMAT_BYTES_MAX];
189 int r, nr = -1;
190
191 /* btrfs cannot handle file systems < 16M, hence use this as minimum */
192 if (size == (uint64_t) -1)
193 size = VAR_LIB_MACHINES_SIZE_START;
194 else if (size < 16*1024*1024)
195 size = 16*1024*1024;
196
197 /* Make sure we only set the directory up once at a time */
198 r = make_lock_file("/run/systemd/machines.lock", LOCK_EX, &lock_file);
199 if (r < 0)
200 return r;
201
202 r = check_btrfs();
203 if (r < 0)
204 return sd_bus_error_set_errnof(error, r, "Failed to determine whether /var/lib/machines is located on btrfs: %m");
205 if (r > 0) {
206 (void) btrfs_subvol_make_label("/var/lib/machines");
207
208 r = btrfs_quota_enable("/var/lib/machines", true);
209 if (r < 0)
210 log_warning_errno(r, "Failed to enable quota for /var/lib/machines, ignoring: %m");
211
212 r = btrfs_subvol_auto_qgroup("/var/lib/machines", 0, true);
213 if (r < 0)
214 log_warning_errno(r, "Failed to set up default quota hierarchy for /var/lib/machines, ignoring: %m");
215
216 return 1;
217 }
218
219 if (path_is_mount_point("/var/lib/machines", NULL, AT_SYMLINK_FOLLOW) > 0) {
220 log_debug("/var/lib/machines is already a mount point, not creating loopback file for it.");
221 return 0;
222 }
223
224 r = dir_is_populated("/var/lib/machines");
225 if (r < 0 && r != -ENOENT)
226 return r;
227 if (r > 0) {
228 log_debug("/var/log/machines is already populated, not creating loopback file for it.");
229 return 0;
230 }
231
232 r = mkfs_exists("btrfs");
233 if (r == 0)
234 return sd_bus_error_set_errnof(error, ENOENT, "Cannot set up /var/lib/machines, mkfs.btrfs is missing");
235 if (r < 0)
236 return r;
237
238 fd = setup_machine_raw(size, error);
239 if (fd < 0)
240 return fd;
241
242 control = open("/dev/loop-control", O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
243 if (control < 0)
244 return sd_bus_error_set_errnof(error, errno, "Failed to open /dev/loop-control: %m");
245
246 nr = ioctl(control, LOOP_CTL_GET_FREE);
247 if (nr < 0)
248 return sd_bus_error_set_errnof(error, errno, "Failed to allocate loop device: %m");
249
250 if (asprintf(&loopdev, "/dev/loop%i", nr) < 0) {
251 r = -ENOMEM;
252 goto fail;
253 }
254
255 loop = open(loopdev, O_CLOEXEC|O_RDWR|O_NOCTTY|O_NONBLOCK);
256 if (loop < 0) {
257 r = sd_bus_error_set_errnof(error, errno, "Failed to open loopback device: %m");
258 goto fail;
259 }
260
261 if (ioctl(loop, LOOP_SET_FD, fd) < 0) {
262 r = sd_bus_error_set_errnof(error, errno, "Failed to bind loopback device: %m");
263 goto fail;
264 }
265
266 if (ioctl(loop, LOOP_SET_STATUS64, &info) < 0) {
267 r = sd_bus_error_set_errnof(error, errno, "Failed to enable auto-clear for loopback device: %m");
268 goto fail;
269 }
270
271 /* We need to make sure the new /var/lib/machines directory
272 * has an access mode of 0700 at the time it is first made
273 * available. mkfs will create it with 0755 however. Hence,
274 * let's mount the directory into an inaccessible directory
275 * below /tmp first, fix the access mode, and move it to the
276 * public place then. */
277
278 if (!mkdtemp(tmpdir)) {
279 r = sd_bus_error_set_errnof(error, errno, "Failed to create temporary mount parent directory: %m");
280 goto fail;
281 }
282 tmpdir_made = true;
283
284 mntdir = strjoina(tmpdir, "/mnt");
285 if (mkdir(mntdir, 0700) < 0) {
286 r = sd_bus_error_set_errnof(error, errno, "Failed to create temporary mount directory: %m");
287 goto fail;
288 }
289 mntdir_made = true;
290
291 if (mount(loopdev, mntdir, "btrfs", 0, NULL) < 0) {
292 r = sd_bus_error_set_errnof(error, errno, "Failed to mount loopback device: %m");
293 goto fail;
294 }
295 mntdir_mounted = true;
296
297 r = btrfs_quota_enable(mntdir, true);
298 if (r < 0)
299 log_warning_errno(r, "Failed to enable quota, ignoring: %m");
300
301 r = btrfs_subvol_auto_qgroup(mntdir, 0, true);
302 if (r < 0)
303 log_warning_errno(r, "Failed to set up default quota hierarchy, ignoring: %m");
304
305 if (chmod(mntdir, 0700) < 0) {
306 r = sd_bus_error_set_errnof(error, errno, "Failed to fix owner: %m");
307 goto fail;
308 }
309
310 (void) mkdir_p_label("/var/lib/machines", 0700);
311
312 if (mount(mntdir, "/var/lib/machines", NULL, MS_BIND, NULL) < 0) {
313 r = sd_bus_error_set_errnof(error, errno, "Failed to mount directory into right place: %m");
314 goto fail;
315 }
316
317 (void) syncfs(fd);
318
319 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));
320
321 (void) umount2(mntdir, MNT_DETACH);
322 (void) rmdir(mntdir);
323 (void) rmdir(tmpdir);
324
325 return 1;
326
327 fail:
328 if (mntdir_mounted)
329 (void) umount2(mntdir, MNT_DETACH);
330
331 if (mntdir_made)
332 (void) rmdir(mntdir);
333 if (tmpdir_made)
334 (void) rmdir(tmpdir);
335
336 if (loop >= 0) {
337 (void) ioctl(loop, LOOP_CLR_FD);
338 loop = safe_close(loop);
339 }
340
341 if (control >= 0 && nr >= 0)
342 (void) ioctl(control, LOOP_CTL_REMOVE, nr);
343
344 return r;
345 }
346
347 static int sync_path(const char *p) {
348 _cleanup_close_ int fd = -1;
349
350 fd = open(p, O_RDONLY|O_CLOEXEC|O_NOCTTY);
351 if (fd < 0)
352 return -errno;
353
354 if (syncfs(fd) < 0)
355 return -errno;
356
357 return 0;
358 }
359
360 int grow_machine_directory(void) {
361 char buf[FORMAT_BYTES_MAX];
362 struct statvfs a, b;
363 uint64_t old_size, new_size, max_add;
364 int r;
365
366 /* Ensure the disk space data is accurate */
367 sync_path("/var/lib/machines");
368 sync_path("/var/lib/machines.raw");
369
370 if (statvfs("/var/lib/machines.raw", &a) < 0)
371 return -errno;
372
373 if (statvfs("/var/lib/machines", &b) < 0)
374 return -errno;
375
376 /* Don't grow if not enough disk space is available on the host */
377 if (((uint64_t) a.f_bavail * (uint64_t) a.f_bsize) <= VAR_LIB_MACHINES_FREE_MIN)
378 return 0;
379
380 /* Don't grow if at least 1/3th of the fs is still free */
381 if (b.f_bavail > b.f_blocks / 3)
382 return 0;
383
384 /* Calculate how much we are willing to add at most */
385 max_add = ((uint64_t) a.f_bavail * (uint64_t) a.f_bsize) - VAR_LIB_MACHINES_FREE_MIN;
386
387 /* Calculate the old size */
388 old_size = (uint64_t) b.f_blocks * (uint64_t) b.f_bsize;
389
390 /* Calculate the new size as three times the size of what is used right now */
391 new_size = ((uint64_t) b.f_blocks - (uint64_t) b.f_bavail) * (uint64_t) b.f_bsize * 3;
392
393 /* Always, grow at least to the start size */
394 if (new_size < VAR_LIB_MACHINES_SIZE_START)
395 new_size = VAR_LIB_MACHINES_SIZE_START;
396
397 /* If the new size is smaller than the old size, don't grow */
398 if (new_size < old_size)
399 return 0;
400
401 /* Ensure we never add more than the maximum */
402 if (new_size > old_size + max_add)
403 new_size = old_size + max_add;
404
405 r = btrfs_resize_loopback("/var/lib/machines", new_size, true);
406 if (r <= 0)
407 return r;
408
409 /* Also bump the quota, of both the subvolume leaf qgroup, as
410 * well as of any subtree quota group by the same id but a
411 * higher level, if it exists. */
412 (void) btrfs_qgroup_set_limit("/var/lib/machines", 0, new_size);
413 (void) btrfs_subvol_set_subtree_quota_limit("/var/lib/machines", 0, new_size);
414
415 log_info("Grew /var/lib/machines btrfs loopback file system to %s.", format_bytes(buf, sizeof(buf), new_size));
416 return 1;
417 }