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