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