chiark / gitweb /
shared: add process-util.[ch]
[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 "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 }