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