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