chiark / gitweb /
machined: also set up /var/lib/machines as btrfs, if "machinectl set-limit" is called
[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         if (tmp)
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         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) umount2(mntdir, MNT_DETACH);
278         (void) rmdir(mntdir);
279         (void) rmdir(tmpdir);
280
281         return 0;
282
283 fail:
284         if (mntdir_mounted)
285                 (void) umount2(mntdir, MNT_DETACH);
286
287         if (mntdir_made)
288                 (void) rmdir(mntdir);
289         if (tmpdir_made)
290                 (void) rmdir(tmpdir);
291
292         if (loop >= 0) {
293                 (void) ioctl(loop, LOOP_CLR_FD);
294                 loop = safe_close(loop);
295         }
296
297         if (control >= 0 && nr >= 0)
298                 (void) ioctl(control, LOOP_CTL_REMOVE, nr);
299
300         return r;
301 }