]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/machine-pool.c
Merge pull request #2054 from keszybz/nss-link-less-2
[thirdparty/systemd.git] / src / shared / machine-pool.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4 This file is part of systemd.
5
6 Copyright 2015 Lennart Poettering
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <linux/loop.h>
25 #include <signal.h>
26 #include <stdbool.h>
27 #include <stdio.h>
28 #include <stdlib.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 return 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", 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 == -ENOENT) {
244 log_debug("mkfs.btrfs is missing, cannot create loopback file for /var/lib/machines.");
245 return 0;
246 }
247 if (r < 0)
248 return r;
249
250 fd = setup_machine_raw(size, error);
251 if (fd < 0)
252 return fd;
253
254 control = open("/dev/loop-control", O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
255 if (control < 0)
256 return sd_bus_error_set_errnof(error, errno, "Failed to open /dev/loop-control: %m");
257
258 nr = ioctl(control, LOOP_CTL_GET_FREE);
259 if (nr < 0)
260 return sd_bus_error_set_errnof(error, errno, "Failed to allocate loop device: %m");
261
262 if (asprintf(&loopdev, "/dev/loop%i", nr) < 0) {
263 r = -ENOMEM;
264 goto fail;
265 }
266
267 loop = open(loopdev, O_CLOEXEC|O_RDWR|O_NOCTTY|O_NONBLOCK);
268 if (loop < 0) {
269 r = sd_bus_error_set_errnof(error, errno, "Failed to open loopback device: %m");
270 goto fail;
271 }
272
273 if (ioctl(loop, LOOP_SET_FD, fd) < 0) {
274 r = sd_bus_error_set_errnof(error, errno, "Failed to bind loopback device: %m");
275 goto fail;
276 }
277
278 if (ioctl(loop, LOOP_SET_STATUS64, &info) < 0) {
279 r = sd_bus_error_set_errnof(error, errno, "Failed to enable auto-clear for loopback device: %m");
280 goto fail;
281 }
282
283 /* We need to make sure the new /var/lib/machines directory
284 * has an access mode of 0700 at the time it is first made
285 * available. mkfs will create it with 0755 however. Hence,
286 * let's mount the directory into an inaccessible directory
287 * below /tmp first, fix the access mode, and move it to the
288 * public place then. */
289
290 if (!mkdtemp(tmpdir)) {
291 r = sd_bus_error_set_errnof(error, errno, "Failed to create temporary mount parent directory: %m");
292 goto fail;
293 }
294 tmpdir_made = true;
295
296 mntdir = strjoina(tmpdir, "/mnt");
297 if (mkdir(mntdir, 0700) < 0) {
298 r = sd_bus_error_set_errnof(error, errno, "Failed to create temporary mount directory: %m");
299 goto fail;
300 }
301 mntdir_made = true;
302
303 if (mount(loopdev, mntdir, "btrfs", 0, NULL) < 0) {
304 r = sd_bus_error_set_errnof(error, errno, "Failed to mount loopback device: %m");
305 goto fail;
306 }
307 mntdir_mounted = true;
308
309 r = btrfs_quota_enable(mntdir, true);
310 if (r < 0)
311 log_warning_errno(r, "Failed to enable quota, ignoring: %m");
312
313 r = btrfs_subvol_auto_qgroup(mntdir, 0, true);
314 if (r < 0)
315 log_warning_errno(r, "Failed to set up default quota hierarchy, ignoring: %m");
316
317 if (chmod(mntdir, 0700) < 0) {
318 r = sd_bus_error_set_errnof(error, errno, "Failed to fix owner: %m");
319 goto fail;
320 }
321
322 (void) mkdir_p_label("/var/lib/machines", 0700);
323
324 if (mount(mntdir, "/var/lib/machines", NULL, MS_BIND, NULL) < 0) {
325 r = sd_bus_error_set_errnof(error, errno, "Failed to mount directory into right place: %m");
326 goto fail;
327 }
328
329 (void) syncfs(fd);
330
331 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));
332
333 (void) umount2(mntdir, MNT_DETACH);
334 (void) rmdir(mntdir);
335 (void) rmdir(tmpdir);
336
337 return 1;
338
339 fail:
340 if (mntdir_mounted)
341 (void) umount2(mntdir, MNT_DETACH);
342
343 if (mntdir_made)
344 (void) rmdir(mntdir);
345 if (tmpdir_made)
346 (void) rmdir(tmpdir);
347
348 if (loop >= 0) {
349 (void) ioctl(loop, LOOP_CLR_FD);
350 loop = safe_close(loop);
351 }
352
353 if (control >= 0 && nr >= 0)
354 (void) ioctl(control, LOOP_CTL_REMOVE, nr);
355
356 return r;
357 }
358
359 static int sync_path(const char *p) {
360 _cleanup_close_ int fd = -1;
361
362 fd = open(p, O_RDONLY|O_CLOEXEC|O_NOCTTY);
363 if (fd < 0)
364 return -errno;
365
366 if (syncfs(fd) < 0)
367 return -errno;
368
369 return 0;
370 }
371
372 int grow_machine_directory(void) {
373 char buf[FORMAT_BYTES_MAX];
374 struct statvfs a, b;
375 uint64_t old_size, new_size, max_add;
376 int r;
377
378 /* Ensure the disk space data is accurate */
379 sync_path("/var/lib/machines");
380 sync_path("/var/lib/machines.raw");
381
382 if (statvfs("/var/lib/machines.raw", &a) < 0)
383 return -errno;
384
385 if (statvfs("/var/lib/machines", &b) < 0)
386 return -errno;
387
388 /* Don't grow if not enough disk space is available on the host */
389 if (((uint64_t) a.f_bavail * (uint64_t) a.f_bsize) <= VAR_LIB_MACHINES_FREE_MIN)
390 return 0;
391
392 /* Don't grow if at least 1/3th of the fs is still free */
393 if (b.f_bavail > b.f_blocks / 3)
394 return 0;
395
396 /* Calculate how much we are willing to add at most */
397 max_add = ((uint64_t) a.f_bavail * (uint64_t) a.f_bsize) - VAR_LIB_MACHINES_FREE_MIN;
398
399 /* Calculate the old size */
400 old_size = (uint64_t) b.f_blocks * (uint64_t) b.f_bsize;
401
402 /* Calculate the new size as three times the size of what is used right now */
403 new_size = ((uint64_t) b.f_blocks - (uint64_t) b.f_bavail) * (uint64_t) b.f_bsize * 3;
404
405 /* Always, grow at least to the start size */
406 if (new_size < VAR_LIB_MACHINES_SIZE_START)
407 new_size = VAR_LIB_MACHINES_SIZE_START;
408
409 /* If the new size is smaller than the old size, don't grow */
410 if (new_size < old_size)
411 return 0;
412
413 /* Ensure we never add more than the maximum */
414 if (new_size > old_size + max_add)
415 new_size = old_size + max_add;
416
417 r = btrfs_resize_loopback("/var/lib/machines", new_size, true);
418 if (r <= 0)
419 return r;
420
421 /* Also bump the quota, of both the subvolume leaf qgroup, as
422 * well as of any subtree quota group by the same id but a
423 * higher level, if it exists. */
424 (void) btrfs_qgroup_set_limit("/var/lib/machines", 0, new_size);
425 (void) btrfs_subvol_set_subtree_quota_limit("/var/lib/machines", 0, new_size);
426
427 log_info("Grew /var/lib/machines btrfs loopback file system to %s.", format_bytes(buf, sizeof(buf), new_size));
428 return 1;
429 }