xref: /freebsd/lib/libbe/be_access.c (revision 54d2737e7fe48226c908dcccfbda2ca1c08e07fc)
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