chiark / gitweb /
shared/machine-pool: remove unnecessary check
[elogind.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 "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
36 static 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
50 static int setup_machine_raw(uint64_t size, sd_bus_error *error) {
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
94         if (ftruncate(fd, size) < 0) {
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
153 fail:
154         unlink_noerrno(tmp);
155
156         if (pid > 1)
157                 kill_and_sigcont(pid, SIGKILL);
158
159         return r;
160 }
161
162 int setup_machine_directory(uint64_t size, sd_bus_error *error) {
163         _cleanup_release_lock_file_ LockFile lock_file = LOCK_FILE_INIT;
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;
171         char buf[FORMAT_BYTES_MAX];
172         int r, nr = -1;
173
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
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
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
202         fd = setup_machine_raw(size, error);
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
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
281         (void) umount2(mntdir, MNT_DETACH);
282         (void) rmdir(mntdir);
283         (void) rmdir(tmpdir);
284
285         return 0;
286
287 fail:
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 }
306
307 static 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
320 int 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 }