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