chiark / gitweb /
Prep v221: Update and clean up build system to sync with upstream
[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 "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", NULL, &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                 (void) reset_all_signal_handlers();
113                 (void) 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 }