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