chiark / gitweb /
importd: split out setup logic for /var/lib/machines into its own API file
[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(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, VAR_LIB_MACHINES_SIZE_START) < 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(sd_bus_error *error) {
164         struct loop_info64 info = {
165                 .lo_flags = LO_FLAGS_AUTOCLEAR,
166         };
167         _cleanup_close_ int fd = -1, control = -1, loop = -1;
168         _cleanup_free_ char* loopdev = NULL;
169         char tmpdir[] = "/tmp/import-mount.XXXXXX", *mntdir = NULL;
170         bool tmpdir_made = false, mntdir_made = false, mntdir_mounted = false;
171         int r, nr = -1;
172
173         r = check_btrfs();
174         if (r < 0)
175                 return sd_bus_error_set_errnof(error, r, "Failed to determine whether /var/lib/machines is located on btrfs: %m");
176         if (r > 0) {
177                 (void) btrfs_subvol_make_label("/var/lib/machines");
178
179                 r = btrfs_quota_enable("/var/lib/machines", true);
180                 if (r < 0)
181                         log_warning_errno(r, "Failed to enable quota, ignoring: %m");
182
183                 return 0;
184         }
185
186         if (path_is_mount_point("/var/lib/machines", true) > 0 ||
187             dir_is_empty("/var/lib/machines") == 0)
188                 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.");
189
190         fd = setup_machine_raw(error);
191         if (fd < 0)
192                 return fd;
193
194         control = open("/dev/loop-control", O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
195         if (control < 0)
196                 return sd_bus_error_set_errnof(error, errno, "Failed to open /dev/loop-control: %m");
197
198         nr = ioctl(control, LOOP_CTL_GET_FREE);
199         if (nr < 0)
200                 return sd_bus_error_set_errnof(error, errno, "Failed to allocate loop device: %m");
201
202         if (asprintf(&loopdev, "/dev/loop%i", nr) < 0) {
203                 r = -ENOMEM;
204                 goto fail;
205         }
206
207         loop = open(loopdev, O_CLOEXEC|O_RDWR|O_NOCTTY|O_NONBLOCK);
208         if (loop < 0) {
209                 r = sd_bus_error_set_errnof(error, errno, "Failed to open loopback device: %m");
210                 goto fail;
211         }
212
213         if (ioctl(loop, LOOP_SET_FD, fd) < 0) {
214                 r = sd_bus_error_set_errnof(error, errno, "Failed to bind loopback device: %m");
215                 goto fail;
216         }
217
218         if (ioctl(loop, LOOP_SET_STATUS64, &info) < 0) {
219                 r = sd_bus_error_set_errnof(error, errno, "Failed to enable auto-clear for loopback device: %m");
220                 goto fail;
221         }
222
223         /* We need to make sure the new /var/lib/machines directory
224          * has an access mode of 0700 at the time it is first made
225          * available. mkfs will create it with 0755 however. Hence,
226          * let's mount the directory into an inaccessible directory
227          * below /tmp first, fix the access mode, and move it to the
228          * public place then. */
229
230         if (!mkdtemp(tmpdir)) {
231                 r = sd_bus_error_set_errnof(error, errno, "Failed to create temporary mount parent directory: %m");
232                 goto fail;
233         }
234         tmpdir_made = true;
235
236         mntdir = strjoina(tmpdir, "/mnt");
237         if (mkdir(mntdir, 0700) < 0) {
238                 r = sd_bus_error_set_errnof(error, errno, "Failed to create temporary mount directory: %m");
239                 goto fail;
240         }
241         mntdir_made = true;
242
243         if (mount(loopdev, mntdir, "btrfs", 0, NULL) < 0) {
244                 r = sd_bus_error_set_errnof(error, errno, "Failed to mount loopback device: %m");
245                 goto fail;
246         }
247         mntdir_mounted = true;
248
249         r = btrfs_quota_enable(mntdir, true);
250         if (r < 0)
251                 log_warning_errno(r, "Failed to enable quota, ignoring: %m");
252
253         if (chmod(mntdir, 0700) < 0) {
254                 r = sd_bus_error_set_errnof(error, errno, "Failed to fix owner: %m");
255                 goto fail;
256         }
257
258         (void) mkdir_p_label("/var/lib/machines", 0700);
259
260         if (mount(mntdir, "/var/lib/machines", NULL, MS_BIND, NULL) < 0) {
261                 r = sd_bus_error_set_errnof(error, errno, "Failed to mount directory into right place: %m");
262                 goto fail;
263         }
264
265         (void) umount2(mntdir, MNT_DETACH);
266         (void) rmdir(mntdir);
267         (void) rmdir(tmpdir);
268
269         return 0;
270
271 fail:
272         if (mntdir_mounted)
273                 (void) umount2(mntdir, MNT_DETACH);
274
275         if (mntdir_made)
276                 (void) rmdir(mntdir);
277         if (tmpdir_made)
278                 (void) rmdir(tmpdir);
279
280         if (loop >= 0) {
281                 (void) ioctl(loop, LOOP_CLR_FD);
282                 loop = safe_close(loop);
283         }
284
285         if (control >= 0 && nr >= 0)
286                 (void) ioctl(control, LOOP_CTL_REMOVE, nr);
287
288         return r;
289 }