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