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