chiark / gitweb /
util: rework rm_rf() logic
[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         r = rename_noreplace(AT_FDCWD, tmp, AT_FDCWD, "/var/lib/machines.raw");
144         if (r < 0) {
145                 sd_bus_error_set_errnof(error, r, "Failed to move /var/lib/machines.raw into place: %m");
146                 goto fail;
147         }
148
149         r = fd;
150         fd = -1;
151
152         return r;
153
154 fail:
155         unlink_noerrno(tmp);
156
157         if (pid > 1)
158                 kill_and_sigcont(pid, SIGKILL);
159
160         return r;
161 }
162
163 int setup_machine_directory(uint64_t size, sd_bus_error *error) {
164         _cleanup_release_lock_file_ LockFile lock_file = LOCK_FILE_INIT;
165         struct loop_info64 info = {
166                 .lo_flags = LO_FLAGS_AUTOCLEAR,
167         };
168         _cleanup_close_ int fd = -1, control = -1, loop = -1;
169         _cleanup_free_ char* loopdev = NULL;
170         char tmpdir[] = "/tmp/import-mount.XXXXXX", *mntdir = NULL;
171         bool tmpdir_made = false, mntdir_made = false, mntdir_mounted = false;
172         char buf[FORMAT_BYTES_MAX];
173         int r, nr = -1;
174
175         /* btrfs cannot handle file systems < 16M, hence use this as minimum */
176         if (size == (uint64_t) -1)
177                 size = VAR_LIB_MACHINES_SIZE_START;
178         else if (size < 16*1024*1024)
179                 size = 16*1024*1024;
180
181         /* Make sure we only set the directory up once at a time */
182         r = make_lock_file("/run/systemd/machines.lock", LOCK_EX, &lock_file);
183         if (r < 0)
184                 return r;
185
186         r = check_btrfs();
187         if (r < 0)
188                 return sd_bus_error_set_errnof(error, r, "Failed to determine whether /var/lib/machines is located on btrfs: %m");
189         if (r > 0) {
190                 (void) btrfs_subvol_make_label("/var/lib/machines");
191
192                 r = btrfs_quota_enable("/var/lib/machines", true);
193                 if (r < 0)
194                         log_warning_errno(r, "Failed to enable quota, ignoring: %m");
195
196                 return 0;
197         }
198
199         if (path_is_mount_point("/var/lib/machines", true) > 0 ||
200             dir_is_empty("/var/lib/machines") == 0)
201                 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.");
202
203         fd = setup_machine_raw(size, error);
204         if (fd < 0)
205                 return fd;
206
207         control = open("/dev/loop-control", O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
208         if (control < 0)
209                 return sd_bus_error_set_errnof(error, errno, "Failed to open /dev/loop-control: %m");
210
211         nr = ioctl(control, LOOP_CTL_GET_FREE);
212         if (nr < 0)
213                 return sd_bus_error_set_errnof(error, errno, "Failed to allocate loop device: %m");
214
215         if (asprintf(&loopdev, "/dev/loop%i", nr) < 0) {
216                 r = -ENOMEM;
217                 goto fail;
218         }
219
220         loop = open(loopdev, O_CLOEXEC|O_RDWR|O_NOCTTY|O_NONBLOCK);
221         if (loop < 0) {
222                 r = sd_bus_error_set_errnof(error, errno, "Failed to open loopback device: %m");
223                 goto fail;
224         }
225
226         if (ioctl(loop, LOOP_SET_FD, fd) < 0) {
227                 r = sd_bus_error_set_errnof(error, errno, "Failed to bind loopback device: %m");
228                 goto fail;
229         }
230
231         if (ioctl(loop, LOOP_SET_STATUS64, &info) < 0) {
232                 r = sd_bus_error_set_errnof(error, errno, "Failed to enable auto-clear for loopback device: %m");
233                 goto fail;
234         }
235
236         /* We need to make sure the new /var/lib/machines directory
237          * has an access mode of 0700 at the time it is first made
238          * available. mkfs will create it with 0755 however. Hence,
239          * let's mount the directory into an inaccessible directory
240          * below /tmp first, fix the access mode, and move it to the
241          * public place then. */
242
243         if (!mkdtemp(tmpdir)) {
244                 r = sd_bus_error_set_errnof(error, errno, "Failed to create temporary mount parent directory: %m");
245                 goto fail;
246         }
247         tmpdir_made = true;
248
249         mntdir = strjoina(tmpdir, "/mnt");
250         if (mkdir(mntdir, 0700) < 0) {
251                 r = sd_bus_error_set_errnof(error, errno, "Failed to create temporary mount directory: %m");
252                 goto fail;
253         }
254         mntdir_made = true;
255
256         if (mount(loopdev, mntdir, "btrfs", 0, NULL) < 0) {
257                 r = sd_bus_error_set_errnof(error, errno, "Failed to mount loopback device: %m");
258                 goto fail;
259         }
260         mntdir_mounted = true;
261
262         r = btrfs_quota_enable(mntdir, true);
263         if (r < 0)
264                 log_warning_errno(r, "Failed to enable quota, ignoring: %m");
265
266         if (chmod(mntdir, 0700) < 0) {
267                 r = sd_bus_error_set_errnof(error, errno, "Failed to fix owner: %m");
268                 goto fail;
269         }
270
271         (void) mkdir_p_label("/var/lib/machines", 0700);
272
273         if (mount(mntdir, "/var/lib/machines", NULL, MS_BIND, NULL) < 0) {
274                 r = sd_bus_error_set_errnof(error, errno, "Failed to mount directory into right place: %m");
275                 goto fail;
276         }
277
278         (void) syncfs(fd);
279
280         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));
281
282         (void) umount2(mntdir, MNT_DETACH);
283         (void) rmdir(mntdir);
284         (void) rmdir(tmpdir);
285
286         return 0;
287
288 fail:
289         if (mntdir_mounted)
290                 (void) umount2(mntdir, MNT_DETACH);
291
292         if (mntdir_made)
293                 (void) rmdir(mntdir);
294         if (tmpdir_made)
295                 (void) rmdir(tmpdir);
296
297         if (loop >= 0) {
298                 (void) ioctl(loop, LOOP_CLR_FD);
299                 loop = safe_close(loop);
300         }
301
302         if (control >= 0 && nr >= 0)
303                 (void) ioctl(control, LOOP_CTL_REMOVE, nr);
304
305         return r;
306 }
307
308 static int sync_path(const char *p) {
309         _cleanup_close_ int fd = -1;
310
311         fd = open(p, O_RDONLY|O_CLOEXEC|O_NOCTTY);
312         if (fd < 0)
313                 return -errno;
314
315         if (syncfs(fd) < 0)
316                 return -errno;
317
318         return 0;
319 }
320
321 int grow_machine_directory(void) {
322         char buf[FORMAT_BYTES_MAX];
323         struct statvfs a, b;
324         uint64_t old_size, new_size, max_add;
325         int r;
326
327         /* Ensure the disk space data is accurate */
328         sync_path("/var/lib/machines");
329         sync_path("/var/lib/machines.raw");
330
331         if (statvfs("/var/lib/machines.raw", &a) < 0)
332                 return -errno;
333
334         if (statvfs("/var/lib/machines", &b) < 0)
335                 return -errno;
336
337         /* Don't grow if not enough disk space is available on the host */
338         if (((uint64_t) a.f_bavail * (uint64_t) a.f_bsize) <= VAR_LIB_MACHINES_FREE_MIN)
339                 return 0;
340
341         /* Don't grow if at least 1/3th of the fs is still free */
342         if (b.f_bavail > b.f_blocks / 3)
343                 return 0;
344
345         /* Calculate how much we are willing to add at maximum */
346         max_add = ((uint64_t) a.f_bavail * (uint64_t) a.f_bsize) - VAR_LIB_MACHINES_FREE_MIN;
347
348         /* Calculate the old size */
349         old_size = (uint64_t) b.f_blocks * (uint64_t) b.f_bsize;
350
351         /* Calculate the new size as three times the size of what is used right now */
352         new_size = ((uint64_t) b.f_blocks - (uint64_t) b.f_bavail) * (uint64_t) b.f_bsize * 3;
353
354         /* Always, grow at least to the start size */
355         if (new_size < VAR_LIB_MACHINES_SIZE_START)
356                 new_size = VAR_LIB_MACHINES_SIZE_START;
357
358         /* If the new size is smaller than the old size, don't grow */
359         if (new_size < old_size)
360                 return 0;
361
362         /* Ensure we never add more than the maximum */
363         if (new_size > old_size + max_add)
364                 new_size = old_size + max_add;
365
366         r = btrfs_resize_loopback("/var/lib/machines", new_size, true);
367         if (r <= 0)
368                 return r;
369
370         r = btrfs_quota_limit("/var/lib/machines", new_size);
371         if (r < 0)
372                 return r;
373
374         log_info("Grew /var/lib/machines btrfs loopback file system to %s.", format_bytes(buf, sizeof(buf), new_size));
375         return 1;
376 }