1 /*
2 * Copyright (c) 2017 Kyle J. Kneitinger <kyle@kneit.in>
3 * Copyright (c) 2018 Kyle Evans <kevans@FreeBSD.org>
4 * Copyright (c) 2019 Wes Maag <wes@jwmaag.org>
5 *
6 * SPDX-License-Identifier: BSD-2-Clause
7 */
8
9 #include <sys/cdefs.h>
10 #include <sys/mntent.h>
11
12 #include "be.h"
13 #include "be_impl.h"
14
15 #define LIBBE_MOUNT_PREFIX "be_mount."
16
17 struct be_mountcheck_info {
18 const char *path;
19 char *name;
20 };
21
22 struct be_mount_info {
23 libbe_handle_t *lbh;
24 const char *be;
25 const char *mountpoint;
26 int mntflags;
27 int deepmount;
28 int depth;
29 };
30
31 static int
be_mountcheck_cb(zfs_handle_t * zfs_hdl,void * data)32 be_mountcheck_cb(zfs_handle_t *zfs_hdl, void *data)
33 {
34 struct be_mountcheck_info *info;
35 char *mountpoint;
36
37 if (data == NULL)
38 return (1);
39 info = (struct be_mountcheck_info *)data;
40 if (!zfs_is_mounted(zfs_hdl, &mountpoint))
41 return (0);
42 if (strcmp(mountpoint, info->path) == 0) {
43 info->name = strdup(zfs_get_name(zfs_hdl));
44 free(mountpoint);
45 return (1);
46 }
47 free(mountpoint);
48 return (0);
49 }
50
51 /*
52 * Called from be_mount, uses the given zfs_handle and attempts to
53 * mount it at the passed mountpoint. If the deepmount flag is set, continue
54 * calling the function for each child dataset.
55 */
56 static int
be_mount_iter(zfs_handle_t * zfs_hdl,void * data)57 be_mount_iter(zfs_handle_t *zfs_hdl, void *data)
58 {
59 int err;
60 char *mountpoint;
61 char tmp[BE_MAXPATHLEN], zfs_mnt[BE_MAXPATHLEN];
62 struct be_mount_info *info;
63
64 info = (struct be_mount_info *)data;
65
66 if (zfs_is_mounted(zfs_hdl, &mountpoint)) {
67 free(mountpoint);
68 return (0);
69 }
70
71 /*
72 * canmount and mountpoint are both ignored for the BE dataset, because
73 * the rest of the system (kernel and loader) will effectively do the
74 * same.
75 */
76 if (info->depth == 0) {
77 snprintf(tmp, BE_MAXPATHLEN, "%s", info->mountpoint);
78 } else {
79 if (zfs_prop_get_int(zfs_hdl, ZFS_PROP_CANMOUNT) ==
80 ZFS_CANMOUNT_OFF)
81 return (0);
82
83 if (zfs_prop_get(zfs_hdl, ZFS_PROP_MOUNTPOINT, zfs_mnt,
84 BE_MAXPATHLEN, NULL, NULL, 0, 1))
85 return (1);
86
87 /*
88 * We've encountered mountpoint=none at some intermediate
89 * dataset (e.g. zroot/var) that will have children that may
90 * need to be mounted. Skip mounting it, but iterate through
91 * the children.
92 */
93 if (strcmp("none", zfs_mnt) == 0)
94 goto skipmount;
95
96 mountpoint = be_mountpoint_augmented(info->lbh, zfs_mnt);
97 snprintf(tmp, BE_MAXPATHLEN, "%s%s", info->mountpoint,
98 mountpoint);
99 }
100
101 if ((err = zfs_mount_at(zfs_hdl, NULL, info->mntflags, tmp)) != 0) {
102 switch (errno) {
103 case ENAMETOOLONG:
104 return (set_error(info->lbh, BE_ERR_PATHLEN));
105 case ELOOP:
106 case ENOENT:
107 case ENOTDIR:
108 return (set_error(info->lbh, BE_ERR_BADPATH));
109 case EPERM:
110 return (set_error(info->lbh, BE_ERR_PERMS));
111 case EBUSY:
112 return (set_error(info->lbh, BE_ERR_PATHBUSY));
113 default:
114 return (set_error(info->lbh, BE_ERR_UNKNOWN));
115 }
116 }
117
118 if (!info->deepmount)
119 return (0);
120
121 skipmount:
122 ++info->depth;
123 err = zfs_iter_filesystems(zfs_hdl, be_mount_iter, info);
124 --info->depth;
125 return (err);
126 }
127
128
129 static int
be_umount_iter(zfs_handle_t * zfs_hdl,void * data)130 be_umount_iter(zfs_handle_t *zfs_hdl, void *data)
131 {
132
133 int err;
134 char *mountpoint;
135 struct be_mount_info *info;
136
137 info = (struct be_mount_info *)data;
138
139 ++info->depth;
140 if((err = zfs_iter_filesystems(zfs_hdl, be_umount_iter, info)) != 0) {
141 return (err);
142 }
143 --info->depth;
144
145 if (!zfs_is_mounted(zfs_hdl, &mountpoint)) {
146 return (0);
147 }
148
149 if (info->depth == 0 && info->mountpoint == NULL)
150 info->mountpoint = mountpoint;
151 else
152 free(mountpoint);
153
154 if (zfs_unmount(zfs_hdl, NULL, info->mntflags) != 0) {
155 switch (errno) {
156 case ENAMETOOLONG:
157 return (set_error(info->lbh, BE_ERR_PATHLEN));
158 case ELOOP:
159 case ENOENT:
160 case ENOTDIR:
161 return (set_error(info->lbh, BE_ERR_BADPATH));
162 case EPERM:
163 return (set_error(info->lbh, BE_ERR_PERMS));
164 case EBUSY:
165 return (set_error(info->lbh, BE_ERR_PATHBUSY));
166 default:
167 return (set_error(info->lbh, BE_ERR_UNKNOWN));
168 }
169 }
170 return (0);
171 }
172
173 /*
174 * usage
175 */
176 int
be_mounted_at(libbe_handle_t * lbh,const char * path,nvlist_t * details)177 be_mounted_at(libbe_handle_t *lbh, const char *path, nvlist_t *details)
178 {
179 char be[BE_MAXPATHLEN];
180 zfs_handle_t *root_hdl;
181 struct be_mountcheck_info info;
182 prop_data_t propinfo;
183
184 bzero(&be, BE_MAXPATHLEN);
185 if ((root_hdl = zfs_open(lbh->lzh, lbh->root,
186 ZFS_TYPE_FILESYSTEM)) == NULL)
187 return (BE_ERR_ZFSOPEN);
188
189 info.path = path;
190 info.name = NULL;
191 zfs_iter_filesystems(root_hdl, be_mountcheck_cb, &info);
192 zfs_close(root_hdl);
193
194 if (info.name != NULL) {
195 if (details != NULL) {
196 if ((root_hdl = zfs_open(lbh->lzh, info.name,
197 ZFS_TYPE_FILESYSTEM)) == NULL) {
198 free(info.name);
199 return (BE_ERR_ZFSOPEN);
200 }
201
202 propinfo.lbh = lbh;
203 propinfo.list = details;
204 propinfo.single_object = false;
205 propinfo.bootonce = NULL;
206 prop_list_builder_cb(root_hdl, &propinfo);
207 zfs_close(root_hdl);
208 }
209 free(info.name);
210 return (0);
211 }
212 return (1);
213 }
214
215 /*
216 * usage
217 */
218 int
be_mount(libbe_handle_t * lbh,const char * bootenv,const char * mountpoint,int flags,char * result_loc)219 be_mount(libbe_handle_t *lbh, const char *bootenv, const char *mountpoint,
220 int flags, char *result_loc)
221 {
222 char be[BE_MAXPATHLEN];
223 char mnt_temp[BE_MAXPATHLEN];
224 int mntflags, mntdeep;
225 int err;
226 struct be_mount_info info;
227 zfs_handle_t *zhdl;
228
229 if ((err = be_root_concat(lbh, bootenv, be)) != 0)
230 return (set_error(lbh, err));
231
232 if ((err = be_exists(lbh, bootenv)) != 0)
233 return (set_error(lbh, err));
234
235 if (is_mounted(lbh->lzh, be, NULL))
236 return (set_error(lbh, BE_ERR_MOUNTED));
237
238 mntdeep = (flags & BE_MNT_DEEP) ? 1 : 0;
239 mntflags = (flags & BE_MNT_FORCE) ? MNT_FORCE : 0;
240
241 /* Create mountpoint if it is not specified */
242 if (mountpoint == NULL) {
243 const char *tmpdir;
244
245 tmpdir = getenv("TMPDIR");
246 if (tmpdir == NULL)
247 tmpdir = _PATH_TMP;
248
249 if (snprintf(mnt_temp, sizeof(mnt_temp), "%s%s%sXXXX", tmpdir,
250 tmpdir[strlen(tmpdir) - 1] == '/' ? "" : "/",
251 LIBBE_MOUNT_PREFIX) >= (int)sizeof(mnt_temp))
252 return (set_error(lbh, BE_ERR_PATHLEN));
253
254 if (mkdtemp(mnt_temp) == NULL)
255 return (set_error(lbh, BE_ERR_IO));
256 }
257
258 if ((zhdl = zfs_open(lbh->lzh, be, ZFS_TYPE_FILESYSTEM)) == NULL)
259 return (set_error(lbh, BE_ERR_ZFSOPEN));
260
261 info.lbh = lbh;
262 info.be = be;
263 info.mountpoint = (mountpoint == NULL) ? mnt_temp : mountpoint;
264 info.mntflags = mntflags;
265 info.deepmount = mntdeep;
266 info.depth = 0;
267
268 if((err = be_mount_iter(zhdl, &info) != 0)) {
269 zfs_close(zhdl);
270 return (err);
271 }
272 zfs_close(zhdl);
273
274 if (result_loc != NULL)
275 strlcpy(result_loc, mountpoint == NULL ? mnt_temp : mountpoint,
276 BE_MAXPATHLEN);
277
278 return (BE_ERR_SUCCESS);
279 }
280
281 /*
282 * usage
283 */
284 int
be_unmount(libbe_handle_t * lbh,const char * bootenv,int flags)285 be_unmount(libbe_handle_t *lbh, const char *bootenv, int flags)
286 {
287 int err;
288 char be[BE_MAXPATHLEN];
289 zfs_handle_t *root_hdl;
290 struct be_mount_info info;
291
292 if ((err = be_root_concat(lbh, bootenv, be)) != 0)
293 return (set_error(lbh, err));
294
295 if ((root_hdl = zfs_open(lbh->lzh, be, ZFS_TYPE_FILESYSTEM)) == NULL)
296 return (set_error(lbh, BE_ERR_ZFSOPEN));
297
298 info.lbh = lbh;
299 info.be = be;
300 info.mountpoint = NULL;
301 info.mntflags = (flags & BE_MNT_FORCE) ? MS_FORCE : 0;
302 info.depth = 0;
303
304 if ((err = be_umount_iter(root_hdl, &info)) != 0) {
305 free(__DECONST(char *, info.mountpoint));
306 zfs_close(root_hdl);
307 return (err);
308 }
309
310 /*
311 * We'll attempt to remove the directory if we created it on a
312 * best-effort basis. rmdir(2) failure will not be reported.
313 */
314 if (info.mountpoint != NULL) {
315 const char *mdir;
316
317 mdir = strrchr(info.mountpoint, '/');
318 if (mdir == NULL)
319 mdir = info.mountpoint;
320 else
321 mdir++;
322
323 if (strncmp(mdir, LIBBE_MOUNT_PREFIX,
324 sizeof(LIBBE_MOUNT_PREFIX) - 1) == 0) {
325 (void)rmdir(info.mountpoint);
326 }
327 }
328
329 free(__DECONST(char *, info.mountpoint));
330
331 zfs_close(root_hdl);
332 return (BE_ERR_SUCCESS);
333 }
334
335 /*
336 * This function will blow away the input buffer as needed if we're discovered
337 * to be looking at a root-mount. If the mountpoint is naturally beyond the
338 * root, however, the buffer may be left intact and a pointer to the section
339 * past altroot will be returned instead for the caller's perusal.
340 */
341 char *
be_mountpoint_augmented(libbe_handle_t * lbh,char * mountpoint)342 be_mountpoint_augmented(libbe_handle_t *lbh, char *mountpoint)
343 {
344
345 if (lbh->altroot_len == 0)
346 return (mountpoint);
347 if (mountpoint == NULL || *mountpoint == '\0')
348 return (mountpoint);
349
350 if (mountpoint[lbh->altroot_len] == '\0') {
351 *(mountpoint + 1) = '\0';
352 return (mountpoint);
353 } else
354 return (mountpoint + lbh->altroot_len);
355 }
356