xref: /freebsd/lib/libbe/be.c (revision 8e933d9c598ec847081c322e7ae9a46bf7897886)
1b179da01SKyle Evans /*-
2b179da01SKyle Evans  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
328f16a0fSKyle Evans  *
428f16a0fSKyle Evans  * Copyright (c) 2017 Kyle J. Kneitinger <kyle@kneit.in>
528f16a0fSKyle Evans  *
628f16a0fSKyle Evans  * Redistribution and use in source and binary forms, with or without
728f16a0fSKyle Evans  * modification, are permitted provided that the following conditions
828f16a0fSKyle Evans  * are met:
928f16a0fSKyle Evans  * 1. Redistributions of source code must retain the above copyright
1028f16a0fSKyle Evans  *    notice, this list of conditions and the following disclaimer.
1128f16a0fSKyle Evans  * 2. Redistributions in binary form must reproduce the above copyright
1228f16a0fSKyle Evans  *    notice, this list of conditions and the following disclaimer in the
1328f16a0fSKyle Evans  *    documentation and/or other materials provided with the distribution.
1428f16a0fSKyle Evans  *
1528f16a0fSKyle Evans  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
1628f16a0fSKyle Evans  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1728f16a0fSKyle Evans  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1828f16a0fSKyle Evans  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
1928f16a0fSKyle Evans  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2028f16a0fSKyle Evans  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2128f16a0fSKyle Evans  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2228f16a0fSKyle Evans  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2328f16a0fSKyle Evans  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2428f16a0fSKyle Evans  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2528f16a0fSKyle Evans  * SUCH DAMAGE.
2628f16a0fSKyle Evans  */
2728f16a0fSKyle Evans 
28b6e7c421SKyle Evans #include <sys/cdefs.h>
29b6e7c421SKyle Evans __FBSDID("$FreeBSD$");
30b6e7c421SKyle Evans 
3151aecc89SKyle Evans #include <sys/param.h>
3251aecc89SKyle Evans #include <sys/mount.h>
3328f16a0fSKyle Evans #include <sys/stat.h>
3451aecc89SKyle Evans #include <sys/ucred.h>
358f5c6c31SKyle Evans #include <sys/queue.h>
36485172f5SKyle Evans #include <sys/zfs_context.h>
37485172f5SKyle Evans #include <sys/mntent.h>
389e5787d2SMatt Macy #include <sys/zfs_ioctl.h>
39485172f5SKyle Evans 
409e5787d2SMatt Macy #include <libzutil.h>
4128f16a0fSKyle Evans #include <ctype.h>
4228f16a0fSKyle Evans #include <libgen.h>
4328f16a0fSKyle Evans #include <libzfs_core.h>
449e5787d2SMatt Macy #include <libzfs_impl.h>
4528f16a0fSKyle Evans #include <stdio.h>
4628f16a0fSKyle Evans #include <stdlib.h>
4728f16a0fSKyle Evans #include <time.h>
4828f16a0fSKyle Evans #include <unistd.h>
49e307eb94SToomas Soome #include <libzfsbootenv.h>
5028f16a0fSKyle Evans 
5128f16a0fSKyle Evans #include "be.h"
5228f16a0fSKyle Evans #include "be_impl.h"
5328f16a0fSKyle Evans 
548f5c6c31SKyle Evans struct promote_entry {
558f5c6c31SKyle Evans 	char				name[BE_MAXPATHLEN];
568f5c6c31SKyle Evans 	SLIST_ENTRY(promote_entry)	link;
578f5c6c31SKyle Evans };
588f5c6c31SKyle Evans 
59be7dd423SKyle Evans struct be_destroy_data {
60be7dd423SKyle Evans 	libbe_handle_t			*lbh;
618f5c6c31SKyle Evans 	char				target_name[BE_MAXPATHLEN];
62be7dd423SKyle Evans 	char				*snapname;
638f5c6c31SKyle Evans 	SLIST_HEAD(, promote_entry)	promotelist;
64be7dd423SKyle Evans };
65be7dd423SKyle Evans 
663d1a1f2cSKyle Evans #if SOON
67c65a2111SKyle Evans static int be_create_child_noent(libbe_handle_t *lbh, const char *active,
68c65a2111SKyle Evans     const char *child_path);
69c65a2111SKyle Evans static int be_create_child_cloned(libbe_handle_t *lbh, const char *active);
703d1a1f2cSKyle Evans #endif
71c65a2111SKyle Evans 
7290cf61e8SKyle Evans /* Arbitrary... should tune */
7390cf61e8SKyle Evans #define	BE_SNAP_SERIAL_MAX	1024
7490cf61e8SKyle Evans 
7528f16a0fSKyle Evans /*
76ee16b7c9SKyle Evans  * Iterator function for locating the rootfs amongst the children of the
77ee16b7c9SKyle Evans  * zfs_be_root set by loader(8).  data is expected to be a libbe_handle_t *.
78ee16b7c9SKyle Evans  */
79ee16b7c9SKyle Evans static int
8051aecc89SKyle Evans be_locate_rootfs(libbe_handle_t *lbh)
81ee16b7c9SKyle Evans {
824ab5187dSKyle Evans 	struct statfs sfs;
83485172f5SKyle Evans 	struct mnttab entry;
8451aecc89SKyle Evans 	zfs_handle_t *zfs;
85ee16b7c9SKyle Evans 
864ab5187dSKyle Evans 	/*
874ab5187dSKyle Evans 	 * Check first if root is ZFS; if not, we'll bail on rootfs capture.
884ab5187dSKyle Evans 	 * Unfortunately needed because zfs_path_to_zhandle will emit to
894ab5187dSKyle Evans 	 * stderr if / isn't actually a ZFS filesystem, which we'd like
904ab5187dSKyle Evans 	 * to avoid.
914ab5187dSKyle Evans 	 */
924ab5187dSKyle Evans 	if (statfs("/", &sfs) == 0) {
934ab5187dSKyle Evans 		statfs2mnttab(&sfs, &entry);
944ab5187dSKyle Evans 		if (strcmp(entry.mnt_fstype, MNTTYPE_ZFS) != 0)
954ab5187dSKyle Evans 			return (1);
964ab5187dSKyle Evans 	} else
974ab5187dSKyle Evans 		return (1);
9851aecc89SKyle Evans 	zfs = zfs_path_to_zhandle(lbh->lzh, "/", ZFS_TYPE_FILESYSTEM);
9951aecc89SKyle Evans 	if (zfs == NULL)
100ee16b7c9SKyle Evans 		return (1);
101ee16b7c9SKyle Evans 
10251aecc89SKyle Evans 	strlcpy(lbh->rootfs, zfs_get_name(zfs), sizeof(lbh->rootfs));
10351aecc89SKyle Evans 	zfs_close(zfs);
104ee16b7c9SKyle Evans 	return (0);
105ee16b7c9SKyle Evans }
106ee16b7c9SKyle Evans 
107ee16b7c9SKyle Evans /*
10828f16a0fSKyle Evans  * Initializes the libbe context to operate in the root boot environment
10928f16a0fSKyle Evans  * dataset, for example, zroot/ROOT.
11028f16a0fSKyle Evans  */
11128f16a0fSKyle Evans libbe_handle_t *
112cc624025SKyle Evans libbe_init(const char *root)
11328f16a0fSKyle Evans {
114fc13fc1cSKyle Evans 	char altroot[MAXPATHLEN];
11528f16a0fSKyle Evans 	libbe_handle_t *lbh;
116c3a34c08SKyle Evans 	char *poolname, *pos;
117c3a34c08SKyle Evans 	int pnamelen;
11828f16a0fSKyle Evans 
119c3a34c08SKyle Evans 	lbh = NULL;
120c3a34c08SKyle Evans 	poolname = pos = NULL;
12128f16a0fSKyle Evans 
122c3a34c08SKyle Evans 	if ((lbh = calloc(1, sizeof(libbe_handle_t))) == NULL)
123c3a34c08SKyle Evans 		goto err;
12428f16a0fSKyle Evans 
125c3a34c08SKyle Evans 	if ((lbh->lzh = libzfs_init()) == NULL)
126c3a34c08SKyle Evans 		goto err;
12728f16a0fSKyle Evans 
128cc624025SKyle Evans 	/*
129cc624025SKyle Evans 	 * Grab rootfs, we'll work backwards from there if an optional BE root
130cc624025SKyle Evans 	 * has not been passed in.
131cc624025SKyle Evans 	 */
1324ab5187dSKyle Evans 	if (be_locate_rootfs(lbh) != 0) {
1334ab5187dSKyle Evans 		if (root == NULL)
134c3a34c08SKyle Evans 			goto err;
1354ab5187dSKyle Evans 		*lbh->rootfs = '\0';
1364ab5187dSKyle Evans 	}
137cc624025SKyle Evans 	if (root == NULL) {
138cc624025SKyle Evans 		/* Strip off the final slash from rootfs to get the be root */
13951aecc89SKyle Evans 		strlcpy(lbh->root, lbh->rootfs, sizeof(lbh->root));
14051aecc89SKyle Evans 		pos = strrchr(lbh->root, '/');
14151aecc89SKyle Evans 		if (pos == NULL)
14251aecc89SKyle Evans 			goto err;
14351aecc89SKyle Evans 		*pos = '\0';
144cc624025SKyle Evans 	} else
145cc624025SKyle Evans 		strlcpy(lbh->root, root, sizeof(lbh->root));
146c3a34c08SKyle Evans 
147c3a34c08SKyle Evans 	if ((pos = strchr(lbh->root, '/')) == NULL)
148c3a34c08SKyle Evans 		goto err;
149c3a34c08SKyle Evans 
150c3a34c08SKyle Evans 	pnamelen = pos - lbh->root;
151c3a34c08SKyle Evans 	poolname = malloc(pnamelen + 1);
152c3a34c08SKyle Evans 	if (poolname == NULL)
153c3a34c08SKyle Evans 		goto err;
154c3a34c08SKyle Evans 
15555b0e92bSKyle Evans 	strlcpy(poolname, lbh->root, pnamelen + 1);
156c3a34c08SKyle Evans 	if ((lbh->active_phandle = zpool_open(lbh->lzh, poolname)) == NULL)
157c3a34c08SKyle Evans 		goto err;
158a8e44f4dSKyle Evans 	free(poolname);
159a8e44f4dSKyle Evans 	poolname = NULL;
160c3a34c08SKyle Evans 
161c3a34c08SKyle Evans 	if (zpool_get_prop(lbh->active_phandle, ZPOOL_PROP_BOOTFS, lbh->bootfs,
16255b0e92bSKyle Evans 	    sizeof(lbh->bootfs), NULL, true) != 0)
163c3a34c08SKyle Evans 		goto err;
164c3a34c08SKyle Evans 
165fc13fc1cSKyle Evans 	if (zpool_get_prop(lbh->active_phandle, ZPOOL_PROP_ALTROOT,
166fc13fc1cSKyle Evans 	    altroot, sizeof(altroot), NULL, true) == 0 &&
167fc13fc1cSKyle Evans 	    strcmp(altroot, "-") != 0)
168fc13fc1cSKyle Evans 		lbh->altroot_len = strlen(altroot);
169fc13fc1cSKyle Evans 
17028f16a0fSKyle Evans 	return (lbh);
171c3a34c08SKyle Evans err:
172c3a34c08SKyle Evans 	if (lbh != NULL) {
173c3a34c08SKyle Evans 		if (lbh->active_phandle != NULL)
174c3a34c08SKyle Evans 			zpool_close(lbh->active_phandle);
175c3a34c08SKyle Evans 		if (lbh->lzh != NULL)
176c3a34c08SKyle Evans 			libzfs_fini(lbh->lzh);
177c3a34c08SKyle Evans 		free(lbh);
178c3a34c08SKyle Evans 	}
179c3a34c08SKyle Evans 	free(poolname);
180c3a34c08SKyle Evans 	return (NULL);
18128f16a0fSKyle Evans }
18228f16a0fSKyle Evans 
18328f16a0fSKyle Evans 
18428f16a0fSKyle Evans /*
18528f16a0fSKyle Evans  * Free memory allocated by libbe_init()
18628f16a0fSKyle Evans  */
18728f16a0fSKyle Evans void
18828f16a0fSKyle Evans libbe_close(libbe_handle_t *lbh)
18928f16a0fSKyle Evans {
190bfe0869cSKyle Evans 
191c3a34c08SKyle Evans 	if (lbh->active_phandle != NULL)
192c3a34c08SKyle Evans 		zpool_close(lbh->active_phandle);
19328f16a0fSKyle Evans 	libzfs_fini(lbh->lzh);
19428f16a0fSKyle Evans 	free(lbh);
19528f16a0fSKyle Evans }
19628f16a0fSKyle Evans 
1979b1662e6SKyle Evans /*
1989b1662e6SKyle Evans  * Proxy through to libzfs for the moment.
1999b1662e6SKyle Evans  */
2009b1662e6SKyle Evans void
2019b1662e6SKyle Evans be_nicenum(uint64_t num, char *buf, size_t buflen)
2029b1662e6SKyle Evans {
2039b1662e6SKyle Evans 
2049b1662e6SKyle Evans 	zfs_nicenum(num, buf, buflen);
2059b1662e6SKyle Evans }
20628f16a0fSKyle Evans 
2078f5c6c31SKyle Evans static bool
2088f5c6c31SKyle Evans be_should_promote_clones(zfs_handle_t *zfs_hdl, struct be_destroy_data *bdd)
2098f5c6c31SKyle Evans {
2108f5c6c31SKyle Evans 	char *atpos;
2118f5c6c31SKyle Evans 
2128f5c6c31SKyle Evans 	if (zfs_get_type(zfs_hdl) != ZFS_TYPE_SNAPSHOT)
2138f5c6c31SKyle Evans 		return (false);
2148f5c6c31SKyle Evans 
2158f5c6c31SKyle Evans 	/*
2168f5c6c31SKyle Evans 	 * If we're deleting a snapshot, we need to make sure we only promote
2178f5c6c31SKyle Evans 	 * clones that are derived from one of the snapshots we're deleting,
2188f5c6c31SKyle Evans 	 * rather than that of a snapshot we're not touching.  This keeps stuff
2198f5c6c31SKyle Evans 	 * in a consistent state, making sure that we don't error out unless
2208f5c6c31SKyle Evans 	 * we really need to.
2218f5c6c31SKyle Evans 	 */
2228f5c6c31SKyle Evans 	if (bdd->snapname == NULL)
2238f5c6c31SKyle Evans 		return (true);
2248f5c6c31SKyle Evans 
2258f5c6c31SKyle Evans 	atpos = strchr(zfs_get_name(zfs_hdl), '@');
2268f5c6c31SKyle Evans 	return (strcmp(atpos + 1, bdd->snapname) == 0);
2278f5c6c31SKyle Evans }
2288f5c6c31SKyle Evans 
2298f5c6c31SKyle Evans /*
2308f5c6c31SKyle Evans  * This is executed from be_promote_dependent_clones via zfs_iter_dependents,
2318f5c6c31SKyle Evans  * It checks if the dependent type is a snapshot then attempts to find any
2328f5c6c31SKyle Evans  * clones associated with it. Any clones not related to the destroy target are
2338f5c6c31SKyle Evans  * added to the promote list.
2348f5c6c31SKyle Evans  */
2358f5c6c31SKyle Evans static int
2368f5c6c31SKyle Evans be_dependent_clone_cb(zfs_handle_t *zfs_hdl, void *data)
2378f5c6c31SKyle Evans {
2388f5c6c31SKyle Evans 	int err;
2398f5c6c31SKyle Evans 	bool found;
2408f5c6c31SKyle Evans 	char *name;
2418f5c6c31SKyle Evans 	struct nvlist *nvl;
2428f5c6c31SKyle Evans 	struct nvpair *nvp;
2438f5c6c31SKyle Evans 	struct be_destroy_data *bdd;
2448f5c6c31SKyle Evans 	struct promote_entry *entry, *newentry;
2458f5c6c31SKyle Evans 
2468f5c6c31SKyle Evans 	nvp = NULL;
2478f5c6c31SKyle Evans 	err = 0;
2488f5c6c31SKyle Evans 	bdd = (struct be_destroy_data *)data;
2498f5c6c31SKyle Evans 
2508f5c6c31SKyle Evans 	if (be_should_promote_clones(zfs_hdl, bdd) &&
2518f5c6c31SKyle Evans 	    (nvl = zfs_get_clones_nvl(zfs_hdl)) != NULL) {
2528f5c6c31SKyle Evans 		while ((nvp = nvlist_next_nvpair(nvl, nvp)) != NULL) {
2538f5c6c31SKyle Evans 			name = nvpair_name(nvp);
2548f5c6c31SKyle Evans 
2558f5c6c31SKyle Evans 			/*
2568f5c6c31SKyle Evans 			 * Skip if the clone is equal to, or a child of, the
2578f5c6c31SKyle Evans 			 * destroy target.
2588f5c6c31SKyle Evans 			 */
2598f5c6c31SKyle Evans 			if (strncmp(name, bdd->target_name,
2608f5c6c31SKyle Evans 			    strlen(bdd->target_name)) == 0 ||
2618f5c6c31SKyle Evans 			    strstr(name, bdd->target_name) == name) {
2628f5c6c31SKyle Evans 				continue;
2638f5c6c31SKyle Evans 			}
2648f5c6c31SKyle Evans 
2658f5c6c31SKyle Evans 			found = false;
2668f5c6c31SKyle Evans 			SLIST_FOREACH(entry, &bdd->promotelist, link) {
2678f5c6c31SKyle Evans 				if (strcmp(entry->name, name) == 0) {
2688f5c6c31SKyle Evans 					found = true;
2698f5c6c31SKyle Evans 					break;
2708f5c6c31SKyle Evans 				}
2718f5c6c31SKyle Evans 			}
2728f5c6c31SKyle Evans 
2738f5c6c31SKyle Evans 			if (found)
2748f5c6c31SKyle Evans 				continue;
2758f5c6c31SKyle Evans 
2768f5c6c31SKyle Evans 			newentry = malloc(sizeof(struct promote_entry));
2778f5c6c31SKyle Evans 			if (newentry == NULL) {
2788f5c6c31SKyle Evans 				err = ENOMEM;
2798f5c6c31SKyle Evans 				break;
2808f5c6c31SKyle Evans 			}
2818f5c6c31SKyle Evans 
2828f5c6c31SKyle Evans #define	BE_COPY_NAME(entry, src)	\
2838f5c6c31SKyle Evans 	strlcpy((entry)->name, (src), sizeof((entry)->name))
2848f5c6c31SKyle Evans 			if (BE_COPY_NAME(newentry, name) >=
2858f5c6c31SKyle Evans 			    sizeof(newentry->name)) {
2868f5c6c31SKyle Evans 				/* Shouldn't happen. */
2878f5c6c31SKyle Evans 				free(newentry);
2888f5c6c31SKyle Evans 				err = ENAMETOOLONG;
2898f5c6c31SKyle Evans 				break;
2908f5c6c31SKyle Evans 			}
2918f5c6c31SKyle Evans #undef BE_COPY_NAME
2928f5c6c31SKyle Evans 
2938f5c6c31SKyle Evans 			/*
2948f5c6c31SKyle Evans 			 * We're building up a SLIST here to make sure both that
2958f5c6c31SKyle Evans 			 * we get the order right and so that we don't
2968f5c6c31SKyle Evans 			 * inadvertently observe the wrong state by promoting
2978f5c6c31SKyle Evans 			 * datasets while we're still walking the tree.  The
2988f5c6c31SKyle Evans 			 * latter can lead to situations where we promote a BE
2998f5c6c31SKyle Evans 			 * then effectively demote it again.
3008f5c6c31SKyle Evans 			 */
3018f5c6c31SKyle Evans 			SLIST_INSERT_HEAD(&bdd->promotelist, newentry, link);
3028f5c6c31SKyle Evans 		}
3038f5c6c31SKyle Evans 		nvlist_free(nvl);
3048f5c6c31SKyle Evans 	}
3058f5c6c31SKyle Evans 	zfs_close(zfs_hdl);
3068f5c6c31SKyle Evans 	return (err);
3078f5c6c31SKyle Evans }
3088f5c6c31SKyle Evans 
3098f5c6c31SKyle Evans /*
3108f5c6c31SKyle Evans  * This is called before a destroy, so that any datasets(environments) that are
3118f5c6c31SKyle Evans  * dependent on this one get promoted before destroying the target.
3128f5c6c31SKyle Evans  */
3138f5c6c31SKyle Evans static int
3148f5c6c31SKyle Evans be_promote_dependent_clones(zfs_handle_t *zfs_hdl, struct be_destroy_data *bdd)
3158f5c6c31SKyle Evans {
3168f5c6c31SKyle Evans 	int err;
3178f5c6c31SKyle Evans 	zfs_handle_t *clone;
3188f5c6c31SKyle Evans 	struct promote_entry *entry;
3198f5c6c31SKyle Evans 
3208f5c6c31SKyle Evans 	snprintf(bdd->target_name, BE_MAXPATHLEN, "%s/", zfs_get_name(zfs_hdl));
32115f0b8c3SMartin Matuska 	err = zfs_iter_dependents(zfs_hdl, 0, true, be_dependent_clone_cb, bdd);
3228f5c6c31SKyle Evans 
3238f5c6c31SKyle Evans 	/*
3248f5c6c31SKyle Evans 	 * Drain the list and walk away from it if we're only deleting a
3258f5c6c31SKyle Evans 	 * snapshot.
3268f5c6c31SKyle Evans 	 */
3278f5c6c31SKyle Evans 	if (bdd->snapname != NULL && !SLIST_EMPTY(&bdd->promotelist))
3288f5c6c31SKyle Evans 		err = BE_ERR_HASCLONES;
3298f5c6c31SKyle Evans 	while (!SLIST_EMPTY(&bdd->promotelist)) {
3308f5c6c31SKyle Evans 		entry = SLIST_FIRST(&bdd->promotelist);
3318f5c6c31SKyle Evans 		SLIST_REMOVE_HEAD(&bdd->promotelist, link);
3328f5c6c31SKyle Evans 
3338f5c6c31SKyle Evans #define	ZFS_GRAB_CLONE()	\
3348f5c6c31SKyle Evans 	zfs_open(bdd->lbh->lzh, entry->name, ZFS_TYPE_FILESYSTEM)
3358f5c6c31SKyle Evans 		/*
3368f5c6c31SKyle Evans 		 * Just skip this part on error, we still want to clean up the
3378f5c6c31SKyle Evans 		 * promotion list after the first error.  We'll then preserve it
3388f5c6c31SKyle Evans 		 * all the way back.
3398f5c6c31SKyle Evans 		 */
3408f5c6c31SKyle Evans 		if (err == 0 && (clone = ZFS_GRAB_CLONE()) != NULL) {
3418f5c6c31SKyle Evans 			err = zfs_promote(clone);
3428f5c6c31SKyle Evans 			if (err != 0)
3438f5c6c31SKyle Evans 				err = BE_ERR_DESTROYMNT;
3448f5c6c31SKyle Evans 			zfs_close(clone);
3458f5c6c31SKyle Evans 		}
3468f5c6c31SKyle Evans #undef ZFS_GRAB_CLONE
3478f5c6c31SKyle Evans 		free(entry);
3488f5c6c31SKyle Evans 	}
3498f5c6c31SKyle Evans 
3508f5c6c31SKyle Evans 	return (err);
3518f5c6c31SKyle Evans }
3528f5c6c31SKyle Evans 
353920abf4dSKyle Evans static int
354920abf4dSKyle Evans be_destroy_cb(zfs_handle_t *zfs_hdl, void *data)
355920abf4dSKyle Evans {
356be7dd423SKyle Evans 	char path[BE_MAXPATHLEN];
357be7dd423SKyle Evans 	struct be_destroy_data *bdd;
358be7dd423SKyle Evans 	zfs_handle_t *snap;
359920abf4dSKyle Evans 	int err;
360920abf4dSKyle Evans 
361be7dd423SKyle Evans 	bdd = (struct be_destroy_data *)data;
362be7dd423SKyle Evans 	if (bdd->snapname == NULL) {
36315f0b8c3SMartin Matuska 		err = zfs_iter_children(zfs_hdl, 0, be_destroy_cb, data);
364be7dd423SKyle Evans 		if (err != 0)
365920abf4dSKyle Evans 			return (err);
366be7dd423SKyle Evans 		return (zfs_destroy(zfs_hdl, false));
367be7dd423SKyle Evans 	}
368be7dd423SKyle Evans 	/* If we're dealing with snapshots instead, delete that one alone */
36915f0b8c3SMartin Matuska 	err = zfs_iter_filesystems(zfs_hdl, 0, be_destroy_cb, data);
370be7dd423SKyle Evans 	if (err != 0)
371920abf4dSKyle Evans 		return (err);
372be7dd423SKyle Evans 	/*
373be7dd423SKyle Evans 	 * This part is intentionally glossing over any potential errors,
374be7dd423SKyle Evans 	 * because there's a lot less potential for errors when we're cleaning
375be7dd423SKyle Evans 	 * up snapshots rather than a full deep BE.  The primary error case
376be7dd423SKyle Evans 	 * here being if the snapshot doesn't exist in the first place, which
377be7dd423SKyle Evans 	 * the caller will likely deem insignificant as long as it doesn't
378be7dd423SKyle Evans 	 * exist after the call.  Thus, such a missing snapshot shouldn't jam
379be7dd423SKyle Evans 	 * up the destruction.
380be7dd423SKyle Evans 	 */
381be7dd423SKyle Evans 	snprintf(path, sizeof(path), "%s@%s", zfs_get_name(zfs_hdl),
382be7dd423SKyle Evans 	    bdd->snapname);
383be7dd423SKyle Evans 	if (!zfs_dataset_exists(bdd->lbh->lzh, path, ZFS_TYPE_SNAPSHOT))
384be7dd423SKyle Evans 		return (0);
385be7dd423SKyle Evans 	snap = zfs_open(bdd->lbh->lzh, path, ZFS_TYPE_SNAPSHOT);
386be7dd423SKyle Evans 	if (snap != NULL)
387be7dd423SKyle Evans 		zfs_destroy(snap, false);
388920abf4dSKyle Evans 	return (0);
389920abf4dSKyle Evans }
390920abf4dSKyle Evans 
3911dc85563SKyle Evans #define	BE_DESTROY_WANTORIGIN	(BE_DESTROY_ORIGIN | BE_DESTROY_AUTOORIGIN)
39228f16a0fSKyle Evans /*
39328f16a0fSKyle Evans  * Destroy the boot environment or snapshot specified by the name
39428f16a0fSKyle Evans  * parameter. Options are or'd together with the possible values:
39528f16a0fSKyle Evans  * BE_DESTROY_FORCE : forces operation on mounted datasets
396be7dd423SKyle Evans  * BE_DESTROY_ORIGIN: destroy the origin snapshot as well
39728f16a0fSKyle Evans  */
3988f5c6c31SKyle Evans static int
3998f5c6c31SKyle Evans be_destroy_internal(libbe_handle_t *lbh, const char *name, int options,
4008f5c6c31SKyle Evans     bool odestroyer)
40128f16a0fSKyle Evans {
402be7dd423SKyle Evans 	struct be_destroy_data bdd;
40313c62c50SKyle Evans 	char origin[BE_MAXPATHLEN], path[BE_MAXPATHLEN];
40428f16a0fSKyle Evans 	zfs_handle_t *fs;
405be7dd423SKyle Evans 	char *snapdelim;
406bfe0869cSKyle Evans 	int err, force, mounted;
407be7dd423SKyle Evans 	size_t rootlen;
40828f16a0fSKyle Evans 
409be7dd423SKyle Evans 	bdd.lbh = lbh;
410be7dd423SKyle Evans 	bdd.snapname = NULL;
4118f5c6c31SKyle Evans 	SLIST_INIT(&bdd.promotelist);
412bfe0869cSKyle Evans 	force = options & BE_DESTROY_FORCE;
41313c62c50SKyle Evans 	*origin = '\0';
41428f16a0fSKyle Evans 
41528f16a0fSKyle Evans 	be_root_concat(lbh, name, path);
41628f16a0fSKyle Evans 
417be7dd423SKyle Evans 	if ((snapdelim = strchr(path, '@')) == NULL) {
418bfe0869cSKyle Evans 		if (!zfs_dataset_exists(lbh->lzh, path, ZFS_TYPE_FILESYSTEM))
41928f16a0fSKyle Evans 			return (set_error(lbh, BE_ERR_NOENT));
42028f16a0fSKyle Evans 
421f08dac4eSKyle Evans 		if (strcmp(path, lbh->rootfs) == 0 ||
422f08dac4eSKyle Evans 		    strcmp(path, lbh->bootfs) == 0)
42328f16a0fSKyle Evans 			return (set_error(lbh, BE_ERR_DESTROYACT));
42428f16a0fSKyle Evans 
425be7dd423SKyle Evans 		fs = zfs_open(lbh->lzh, path, ZFS_TYPE_FILESYSTEM);
42613c62c50SKyle Evans 		if (fs == NULL)
42713c62c50SKyle Evans 			return (set_error(lbh, BE_ERR_ZFSOPEN));
428be7dd423SKyle Evans 
4298f5c6c31SKyle Evans 		/* Don't destroy a mounted dataset unless force is specified */
4308f5c6c31SKyle Evans 		if ((mounted = zfs_is_mounted(fs, NULL)) != 0) {
4318f5c6c31SKyle Evans 			if (force) {
4328f5c6c31SKyle Evans 				zfs_unmount(fs, NULL, 0);
4338f5c6c31SKyle Evans 			} else {
4348f5c6c31SKyle Evans 				free(bdd.snapname);
4358f5c6c31SKyle Evans 				return (set_error(lbh, BE_ERR_DESTROYMNT));
4368f5c6c31SKyle Evans 			}
4378f5c6c31SKyle Evans 		}
4388f5c6c31SKyle Evans 	} else {
4398f5c6c31SKyle Evans 		/*
4408f5c6c31SKyle Evans 		 * If we're initially destroying a snapshot, origin options do
4418f5c6c31SKyle Evans 		 * not make sense.  If we're destroying the origin snapshot of
4428f5c6c31SKyle Evans 		 * a BE, we want to maintain the options in case we need to
4438f5c6c31SKyle Evans 		 * fake success after failing to promote.
4448f5c6c31SKyle Evans 		 */
4458f5c6c31SKyle Evans 		if (!odestroyer)
4468f5c6c31SKyle Evans 			options &= ~BE_DESTROY_WANTORIGIN;
4478f5c6c31SKyle Evans 		if (!zfs_dataset_exists(lbh->lzh, path, ZFS_TYPE_SNAPSHOT))
4488f5c6c31SKyle Evans 			return (set_error(lbh, BE_ERR_NOENT));
4498f5c6c31SKyle Evans 
4508f5c6c31SKyle Evans 		bdd.snapname = strdup(snapdelim + 1);
4518f5c6c31SKyle Evans 		if (bdd.snapname == NULL)
4528f5c6c31SKyle Evans 			return (set_error(lbh, BE_ERR_NOMEM));
4538f5c6c31SKyle Evans 		*snapdelim = '\0';
4548f5c6c31SKyle Evans 		fs = zfs_open(lbh->lzh, path, ZFS_TYPE_DATASET);
4558f5c6c31SKyle Evans 		if (fs == NULL) {
4568f5c6c31SKyle Evans 			free(bdd.snapname);
4578f5c6c31SKyle Evans 			return (set_error(lbh, BE_ERR_ZFSOPEN));
4588f5c6c31SKyle Evans 		}
4598f5c6c31SKyle Evans 	}
4608f5c6c31SKyle Evans 
4618f5c6c31SKyle Evans 	/*
4628f5c6c31SKyle Evans 	 * Whether we're destroying a BE or a single snapshot, we need to walk
4638f5c6c31SKyle Evans 	 * the tree of what we're going to destroy and promote everything in our
4648f5c6c31SKyle Evans 	 * path so that we can make it happen.
4658f5c6c31SKyle Evans 	 */
4668f5c6c31SKyle Evans 	if ((err = be_promote_dependent_clones(fs, &bdd)) != 0) {
4678f5c6c31SKyle Evans 		free(bdd.snapname);
4688f5c6c31SKyle Evans 
4698f5c6c31SKyle Evans 		/*
4708f5c6c31SKyle Evans 		 * If we're just destroying the origin of some other dataset
4718f5c6c31SKyle Evans 		 * we were invoked to destroy, then we just ignore
4728f5c6c31SKyle Evans 		 * BE_ERR_HASCLONES and return success unless the caller wanted
4738f5c6c31SKyle Evans 		 * to force the issue.
4748f5c6c31SKyle Evans 		 */
4758f5c6c31SKyle Evans 		if (odestroyer && err == BE_ERR_HASCLONES &&
4768f5c6c31SKyle Evans 		    (options & BE_DESTROY_AUTOORIGIN) != 0)
4778f5c6c31SKyle Evans 			return (0);
4788f5c6c31SKyle Evans 		return (set_error(lbh, err));
4798f5c6c31SKyle Evans 	}
4808f5c6c31SKyle Evans 
4818f5c6c31SKyle Evans 	/*
4828f5c6c31SKyle Evans 	 * This was deferred until after we promote all of the derivatives so
4838f5c6c31SKyle Evans 	 * that we grab the new origin after everything's settled down.
4848f5c6c31SKyle Evans 	 */
4851dc85563SKyle Evans 	if ((options & BE_DESTROY_WANTORIGIN) != 0 &&
48613c62c50SKyle Evans 	    zfs_prop_get(fs, ZFS_PROP_ORIGIN, origin, sizeof(origin),
4871dc85563SKyle Evans 	    NULL, NULL, 0, 1) != 0 &&
4881dc85563SKyle Evans 	    (options & BE_DESTROY_ORIGIN) != 0)
48913c62c50SKyle Evans 		return (set_error(lbh, BE_ERR_NOORIGIN));
490e1ee6230SKyle Evans 
491455d8009SKyle Evans 	/*
492455d8009SKyle Evans 	 * If the caller wants auto-origin destruction and the origin
493455d8009SKyle Evans 	 * name matches one of our automatically created snapshot names
494455d8009SKyle Evans 	 * (i.e. strftime("%F-%T") with a serial at the end), then
495455d8009SKyle Evans 	 * we'll set the DESTROY_ORIGIN flag and nuke it
496455d8009SKyle Evans 	 * be_is_auto_snapshot_name is exported from libbe(3) so that
497455d8009SKyle Evans 	 * the caller can determine if it needs to warn about the origin
498455d8009SKyle Evans 	 * not being destroyed or not.
499455d8009SKyle Evans 	 */
5001dc85563SKyle Evans 	if ((options & BE_DESTROY_AUTOORIGIN) != 0 && *origin != '\0' &&
501455d8009SKyle Evans 	    be_is_auto_snapshot_name(lbh, origin))
502455d8009SKyle Evans 		options |= BE_DESTROY_ORIGIN;
503455d8009SKyle Evans 
504be7dd423SKyle Evans 	err = be_destroy_cb(fs, &bdd);
505be7dd423SKyle Evans 	zfs_close(fs);
506be7dd423SKyle Evans 	free(bdd.snapname);
507be7dd423SKyle Evans 	if (err != 0) {
508920abf4dSKyle Evans 		/* Children are still present or the mount is referenced */
509920abf4dSKyle Evans 		if (err == EBUSY)
510920abf4dSKyle Evans 			return (set_error(lbh, BE_ERR_DESTROYMNT));
511920abf4dSKyle Evans 		return (set_error(lbh, BE_ERR_UNKNOWN));
512920abf4dSKyle Evans 	}
51328f16a0fSKyle Evans 
514be7dd423SKyle Evans 	if ((options & BE_DESTROY_ORIGIN) == 0)
515920abf4dSKyle Evans 		return (0);
51628f16a0fSKyle Evans 
517be7dd423SKyle Evans 	/* The origin can't possibly be shorter than the BE root */
518be7dd423SKyle Evans 	rootlen = strlen(lbh->root);
519be7dd423SKyle Evans 	if (*origin == '\0' || strlen(origin) <= rootlen + 1)
520be7dd423SKyle Evans 		return (set_error(lbh, BE_ERR_INVORIGIN));
521be7dd423SKyle Evans 
522be7dd423SKyle Evans 	/*
523be7dd423SKyle Evans 	 * We'll be chopping off the BE root and running this back through
524be7dd423SKyle Evans 	 * be_destroy, so that we properly handle the origin snapshot whether
525be7dd423SKyle Evans 	 * it be that of a deep BE or not.
526be7dd423SKyle Evans 	 */
527be7dd423SKyle Evans 	if (strncmp(origin, lbh->root, rootlen) != 0 || origin[rootlen] != '/')
528be7dd423SKyle Evans 		return (0);
529be7dd423SKyle Evans 
5308f5c6c31SKyle Evans 	return (be_destroy_internal(lbh, origin + rootlen + 1,
5318f5c6c31SKyle Evans 	    options & ~BE_DESTROY_ORIGIN, true));
5328f5c6c31SKyle Evans }
5338f5c6c31SKyle Evans 
5348f5c6c31SKyle Evans int
5358f5c6c31SKyle Evans be_destroy(libbe_handle_t *lbh, const char *name, int options)
5368f5c6c31SKyle Evans {
5378f5c6c31SKyle Evans 
5388f5c6c31SKyle Evans 	/*
5398f5c6c31SKyle Evans 	 * The consumer must not set both BE_DESTROY_AUTOORIGIN and
5408f5c6c31SKyle Evans 	 * BE_DESTROY_ORIGIN.  Internally, we'll set the latter from the former.
5418f5c6c31SKyle Evans 	 * The latter should imply that we must succeed at destroying the
5428f5c6c31SKyle Evans 	 * origin, or complain otherwise.
5438f5c6c31SKyle Evans 	 */
5448f5c6c31SKyle Evans 	if ((options & BE_DESTROY_WANTORIGIN) == BE_DESTROY_WANTORIGIN)
5458f5c6c31SKyle Evans 		return (set_error(lbh, BE_ERR_UNKNOWN));
5468f5c6c31SKyle Evans 	return (be_destroy_internal(lbh, name, options, false));
547be7dd423SKyle Evans }
54828f16a0fSKyle Evans 
54990cf61e8SKyle Evans static void
55090cf61e8SKyle Evans be_setup_snapshot_name(libbe_handle_t *lbh, char *buf, size_t buflen)
55190cf61e8SKyle Evans {
55290cf61e8SKyle Evans 	time_t rawtime;
55390cf61e8SKyle Evans 	int len, serial;
55490cf61e8SKyle Evans 
55590cf61e8SKyle Evans 	time(&rawtime);
55690cf61e8SKyle Evans 	len = strlen(buf);
55790cf61e8SKyle Evans 	len += strftime(buf + len, buflen - len, "@%F-%T", localtime(&rawtime));
55890cf61e8SKyle Evans 	/* No room for serial... caller will do its best */
55990cf61e8SKyle Evans 	if (buflen - len < 2)
56090cf61e8SKyle Evans 		return;
56190cf61e8SKyle Evans 
56290cf61e8SKyle Evans 	for (serial = 0; serial < BE_SNAP_SERIAL_MAX; ++serial) {
56390cf61e8SKyle Evans 		snprintf(buf + len, buflen - len, "-%d", serial);
56490cf61e8SKyle Evans 		if (!zfs_dataset_exists(lbh->lzh, buf, ZFS_TYPE_SNAPSHOT))
56590cf61e8SKyle Evans 			return;
56690cf61e8SKyle Evans 	}
56790cf61e8SKyle Evans }
56890cf61e8SKyle Evans 
569455d8009SKyle Evans bool
5709d6d8bf8SKyle Evans be_is_auto_snapshot_name(libbe_handle_t *lbh __unused, const char *name)
571455d8009SKyle Evans {
572455d8009SKyle Evans 	const char *snap;
573455d8009SKyle Evans 	int day, hour, minute, month, second, serial, year;
574455d8009SKyle Evans 
575455d8009SKyle Evans 	if ((snap = strchr(name, '@')) == NULL)
576455d8009SKyle Evans 		return (false);
577455d8009SKyle Evans 	++snap;
578455d8009SKyle Evans 	/* We'll grab the individual components and do some light validation. */
579455d8009SKyle Evans 	if (sscanf(snap, "%d-%d-%d-%d:%d:%d-%d", &year, &month, &day, &hour,
580455d8009SKyle Evans 	    &minute, &second, &serial) != 7)
581455d8009SKyle Evans 		return (false);
582455d8009SKyle Evans 	return (year >= 1970) && (month >= 1 && month <= 12) &&
583455d8009SKyle Evans 	    (day >= 1 && day <= 31) && (hour >= 0 && hour <= 23) &&
584455d8009SKyle Evans 	    (minute >= 0 && minute <= 59) && (second >= 0 && second <= 60) &&
585455d8009SKyle Evans 	    serial >= 0;
586455d8009SKyle Evans }
587455d8009SKyle Evans 
58828f16a0fSKyle Evans int
589b29bf2f8SKyle Evans be_snapshot(libbe_handle_t *lbh, const char *source, const char *snap_name,
59028f16a0fSKyle Evans     bool recursive, char *result)
59128f16a0fSKyle Evans {
59228f16a0fSKyle Evans 	char buf[BE_MAXPATHLEN];
59390cf61e8SKyle Evans 	int err;
59428f16a0fSKyle Evans 
59528f16a0fSKyle Evans 	be_root_concat(lbh, source, buf);
59628f16a0fSKyle Evans 
597162ec569SKyle Evans 	if ((err = be_exists(lbh, buf)) != 0)
598162ec569SKyle Evans 		return (set_error(lbh, err));
59928f16a0fSKyle Evans 
60028f16a0fSKyle Evans 	if (snap_name != NULL) {
601a8e44f4dSKyle Evans 		if (strlcat(buf, "@", sizeof(buf)) >= sizeof(buf))
602a8e44f4dSKyle Evans 			return (set_error(lbh, BE_ERR_INVALIDNAME));
603a8e44f4dSKyle Evans 
604a8e44f4dSKyle Evans 		if (strlcat(buf, snap_name, sizeof(buf)) >= sizeof(buf))
605a8e44f4dSKyle Evans 			return (set_error(lbh, BE_ERR_INVALIDNAME));
606a8e44f4dSKyle Evans 
607bfe0869cSKyle Evans 		if (result != NULL)
60828f16a0fSKyle Evans 			snprintf(result, BE_MAXPATHLEN, "%s@%s", source,
60928f16a0fSKyle Evans 			    snap_name);
61028f16a0fSKyle Evans 	} else {
61190cf61e8SKyle Evans 		be_setup_snapshot_name(lbh, buf, sizeof(buf));
61290cf61e8SKyle Evans 
613a8e44f4dSKyle Evans 		if (result != NULL && strlcpy(result, strrchr(buf, '/') + 1,
614a8e44f4dSKyle Evans 		    sizeof(buf)) >= sizeof(buf))
615a8e44f4dSKyle Evans 			return (set_error(lbh, BE_ERR_INVALIDNAME));
61628f16a0fSKyle Evans 	}
617b29bf2f8SKyle Evans 	if ((err = zfs_snapshot(lbh->lzh, buf, recursive, NULL)) != 0) {
61828f16a0fSKyle Evans 		switch (err) {
61928f16a0fSKyle Evans 		case EZFS_INVALIDNAME:
62028f16a0fSKyle Evans 			return (set_error(lbh, BE_ERR_INVALIDNAME));
62128f16a0fSKyle Evans 
62228f16a0fSKyle Evans 		default:
6232989df09SKyle Evans 			/*
6242989df09SKyle Evans 			 * The other errors that zfs_ioc_snapshot might return
6252989df09SKyle Evans 			 * shouldn't happen if we've set things up properly, so
6262989df09SKyle Evans 			 * we'll gloss over them and call it UNKNOWN as it will
6272989df09SKyle Evans 			 * require further triage.
6282989df09SKyle Evans 			 */
6292989df09SKyle Evans 			if (errno == ENOTSUP)
6302989df09SKyle Evans 				return (set_error(lbh, BE_ERR_NOPOOL));
63128f16a0fSKyle Evans 			return (set_error(lbh, BE_ERR_UNKNOWN));
63228f16a0fSKyle Evans 		}
63328f16a0fSKyle Evans 	}
63428f16a0fSKyle Evans 
63528f16a0fSKyle Evans 	return (BE_ERR_SUCCESS);
63628f16a0fSKyle Evans }
63728f16a0fSKyle Evans 
63828f16a0fSKyle Evans 
63928f16a0fSKyle Evans /*
64028f16a0fSKyle Evans  * Create the boot environment specified by the name parameter
64128f16a0fSKyle Evans  */
64228f16a0fSKyle Evans int
64373c3d608SKyle Evans be_create(libbe_handle_t *lbh, const char *name)
64428f16a0fSKyle Evans {
64528f16a0fSKyle Evans 	int err;
64628f16a0fSKyle Evans 
647b29bf2f8SKyle Evans 	err = be_create_from_existing(lbh, name, be_active_path(lbh));
64828f16a0fSKyle Evans 
64928f16a0fSKyle Evans 	return (set_error(lbh, err));
65028f16a0fSKyle Evans }
65128f16a0fSKyle Evans 
65228f16a0fSKyle Evans static int
65328f16a0fSKyle Evans be_deep_clone_prop(int prop, void *cb)
65428f16a0fSKyle Evans {
65528f16a0fSKyle Evans 	int err;
656bfe0869cSKyle Evans         struct libbe_dccb *dccb;
65728f16a0fSKyle Evans 	zprop_source_t src;
65828f16a0fSKyle Evans 	char pval[BE_MAXPATHLEN];
65928f16a0fSKyle Evans 	char source[BE_MAXPATHLEN];
660af43c24dSKyle Evans 	char *val;
66128f16a0fSKyle Evans 
662bfe0869cSKyle Evans 	dccb = cb;
66328f16a0fSKyle Evans 	/* Skip some properties we don't want to touch */
66473c3d608SKyle Evans 	if (prop == ZFS_PROP_CANMOUNT)
66528f16a0fSKyle Evans 		return (ZPROP_CONT);
66628f16a0fSKyle Evans 
66728f16a0fSKyle Evans 	/* Don't copy readonly properties */
668bfe0869cSKyle Evans 	if (zfs_prop_readonly(prop))
66928f16a0fSKyle Evans 		return (ZPROP_CONT);
67028f16a0fSKyle Evans 
67128f16a0fSKyle Evans 	if ((err = zfs_prop_get(dccb->zhp, prop, (char *)&pval,
672bfe0869cSKyle Evans 	    sizeof(pval), &src, (char *)&source, sizeof(source), false)))
67328f16a0fSKyle Evans 		/* Just continue if we fail to read a property */
67428f16a0fSKyle Evans 		return (ZPROP_CONT);
675bfe0869cSKyle Evans 
676be13d48cSKyle Evans 	/*
677be13d48cSKyle Evans 	 * Only copy locally defined or received properties.  This continues
678be13d48cSKyle Evans 	 * to avoid temporary/default/local properties intentionally without
679be13d48cSKyle Evans 	 * breaking received datasets.
680be13d48cSKyle Evans 	 */
681be13d48cSKyle Evans 	if (src != ZPROP_SRC_LOCAL && src != ZPROP_SRC_RECEIVED)
68228f16a0fSKyle Evans 		return (ZPROP_CONT);
68328f16a0fSKyle Evans 
684af43c24dSKyle Evans 	/* Augment mountpoint with altroot, if needed */
685af43c24dSKyle Evans 	val = pval;
686fc13fc1cSKyle Evans 	if (prop == ZFS_PROP_MOUNTPOINT)
687fc13fc1cSKyle Evans 		val = be_mountpoint_augmented(dccb->lbh, val);
688fc13fc1cSKyle Evans 
689af43c24dSKyle Evans 	nvlist_add_string(dccb->props, zfs_prop_to_name(prop), val);
69028f16a0fSKyle Evans 
69128f16a0fSKyle Evans 	return (ZPROP_CONT);
69228f16a0fSKyle Evans }
69328f16a0fSKyle Evans 
694fa30d9edSKyle Evans /*
695fa30d9edSKyle Evans  * Return the corresponding boot environment path for a given
696fa30d9edSKyle Evans  * dataset path, the constructed path is placed in 'result'.
697fa30d9edSKyle Evans  *
698fa30d9edSKyle Evans  * example: say our new boot environment name is 'bootenv' and
699fa30d9edSKyle Evans  *          the dataset path is 'zroot/ROOT/default/data/set'.
700fa30d9edSKyle Evans  *
701fa30d9edSKyle Evans  * result should produce: 'zroot/ROOT/bootenv/data/set'
702fa30d9edSKyle Evans  */
70328f16a0fSKyle Evans static int
704fa30d9edSKyle Evans be_get_path(struct libbe_deep_clone *ldc, const char *dspath, char *result, int result_size)
705fa30d9edSKyle Evans {
706fa30d9edSKyle Evans 	char *pos;
707fa30d9edSKyle Evans 	char *child_dataset;
708fa30d9edSKyle Evans 
709fa30d9edSKyle Evans 	/* match the root path for the boot environments */
710fa30d9edSKyle Evans 	pos = strstr(dspath, ldc->lbh->root);
711fa30d9edSKyle Evans 
712fa30d9edSKyle Evans 	/* no match, different pools? */
713fa30d9edSKyle Evans 	if (pos == NULL)
714fa30d9edSKyle Evans 		return (BE_ERR_BADPATH);
715fa30d9edSKyle Evans 
716fa30d9edSKyle Evans 	/* root path of the new boot environment */
717fa30d9edSKyle Evans 	snprintf(result, result_size, "%s/%s", ldc->lbh->root, ldc->bename);
718fa30d9edSKyle Evans 
719fa30d9edSKyle Evans         /* gets us to the parent dataset, the +1 consumes a trailing slash */
720fa30d9edSKyle Evans 	pos += strlen(ldc->lbh->root) + 1;
721fa30d9edSKyle Evans 
722fa30d9edSKyle Evans 	/* skip the parent dataset */
723fa30d9edSKyle Evans 	if ((child_dataset = strchr(pos, '/')) != NULL)
724fa30d9edSKyle Evans 		strlcat(result, child_dataset, result_size);
725fa30d9edSKyle Evans 
726fa30d9edSKyle Evans 	return (BE_ERR_SUCCESS);
727fa30d9edSKyle Evans }
728fa30d9edSKyle Evans 
729fa30d9edSKyle Evans static int
730fa30d9edSKyle Evans be_clone_cb(zfs_handle_t *ds, void *data)
73128f16a0fSKyle Evans {
73228f16a0fSKyle Evans 	int err;
73328f16a0fSKyle Evans 	char be_path[BE_MAXPATHLEN];
73428f16a0fSKyle Evans 	char snap_path[BE_MAXPATHLEN];
73528f16a0fSKyle Evans 	const char *dspath;
73628f16a0fSKyle Evans 	zfs_handle_t *snap_hdl;
73728f16a0fSKyle Evans 	nvlist_t *props;
738fa30d9edSKyle Evans 	struct libbe_deep_clone *ldc;
73928f16a0fSKyle Evans 	struct libbe_dccb dccb;
74028f16a0fSKyle Evans 
741fa30d9edSKyle Evans 	ldc = (struct libbe_deep_clone *)data;
74228f16a0fSKyle Evans 	dspath = zfs_get_name(ds);
743bfe0869cSKyle Evans 
744fa30d9edSKyle Evans 	snprintf(snap_path, sizeof(snap_path), "%s@%s", dspath, ldc->snapname);
745bfe0869cSKyle Evans 
746fa30d9edSKyle Evans 	/* construct the boot environment path from the dataset we're cloning */
747fa30d9edSKyle Evans 	if (be_get_path(ldc, dspath, be_path, sizeof(be_path)) != BE_ERR_SUCCESS)
748*8e933d9cSJohn Grafton 		return (BE_ERR_UNKNOWN);
74928f16a0fSKyle Evans 
750fa30d9edSKyle Evans 	/* the dataset to be created (i.e. the boot environment) already exists */
751fa30d9edSKyle Evans 	if (zfs_dataset_exists(ldc->lbh->lzh, be_path, ZFS_TYPE_DATASET))
752*8e933d9cSJohn Grafton 		return (BE_ERR_EXISTS);
753fa30d9edSKyle Evans 
754fa30d9edSKyle Evans 	/* no snapshot found for this dataset, silently skip it */
755fa30d9edSKyle Evans 	if (!zfs_dataset_exists(ldc->lbh->lzh, snap_path, ZFS_TYPE_SNAPSHOT))
756fa30d9edSKyle Evans 		return (0);
75728f16a0fSKyle Evans 
75828f16a0fSKyle Evans 	if ((snap_hdl =
759fa30d9edSKyle Evans 	    zfs_open(ldc->lbh->lzh, snap_path, ZFS_TYPE_SNAPSHOT)) == NULL)
760*8e933d9cSJohn Grafton 		return (BE_ERR_ZFSOPEN);
76128f16a0fSKyle Evans 
76228f16a0fSKyle Evans 	nvlist_alloc(&props, NV_UNIQUE_NAME, KM_SLEEP);
76328f16a0fSKyle Evans 	nvlist_add_string(props, "canmount", "noauto");
76428f16a0fSKyle Evans 
765fa30d9edSKyle Evans 	dccb.lbh = ldc->lbh;
76628f16a0fSKyle Evans 	dccb.zhp = ds;
76728f16a0fSKyle Evans 	dccb.props = props;
76828f16a0fSKyle Evans 	if (zprop_iter(be_deep_clone_prop, &dccb, B_FALSE, B_FALSE,
769bfe0869cSKyle Evans 	    ZFS_TYPE_FILESYSTEM) == ZPROP_INVAL)
77028f16a0fSKyle Evans 		return (-1);
77128f16a0fSKyle Evans 
772cc4deabcSKyle Evans 	if ((err = zfs_clone(snap_hdl, be_path, props)) != 0)
773*8e933d9cSJohn Grafton 		return (BE_ERR_ZFSCLONE);
77428f16a0fSKyle Evans 
77528f16a0fSKyle Evans 	nvlist_free(props);
77628f16a0fSKyle Evans 	zfs_close(snap_hdl);
77728f16a0fSKyle Evans 
778fa30d9edSKyle Evans 	if (ldc->depth_limit == -1 || ldc->depth < ldc->depth_limit) {
779fa30d9edSKyle Evans 		ldc->depth++;
78015f0b8c3SMartin Matuska 		err = zfs_iter_filesystems(ds, 0, be_clone_cb, ldc);
781fa30d9edSKyle Evans 		ldc->depth--;
782fa30d9edSKyle Evans 	}
783cc4deabcSKyle Evans 
784*8e933d9cSJohn Grafton 	return (err);
78528f16a0fSKyle Evans }
78628f16a0fSKyle Evans 
78728f16a0fSKyle Evans /*
788fa30d9edSKyle Evans  * Create a boot environment with a given name from a given snapshot.
789fa30d9edSKyle Evans  * Snapshots can be in the format 'zroot/ROOT/default@snapshot' or
790fa30d9edSKyle Evans  * 'default@snapshot'. In the latter case, 'default@snapshot' will be prepended
791fa30d9edSKyle Evans  * with the root path that libbe was initailized with.
79228f16a0fSKyle Evans */
793fa30d9edSKyle Evans static int
794fa30d9edSKyle Evans be_clone(libbe_handle_t *lbh, const char *bename, const char *snapshot, int depth)
79528f16a0fSKyle Evans {
79628f16a0fSKyle Evans 	int err;
79728f16a0fSKyle Evans 	char snap_path[BE_MAXPATHLEN];
798b29bf2f8SKyle Evans 	char *parentname, *snapname;
79928f16a0fSKyle Evans 	zfs_handle_t *parent_hdl;
800fa30d9edSKyle Evans 	struct libbe_deep_clone ldc;
80128f16a0fSKyle Evans 
802fa30d9edSKyle Evans         /* ensure the boot environment name is valid */
803fa30d9edSKyle Evans 	if ((err = be_validate_name(lbh, bename)) != 0)
80428f16a0fSKyle Evans 		return (set_error(lbh, err));
805fa30d9edSKyle Evans 
806fa30d9edSKyle Evans 	/*
807fa30d9edSKyle Evans 	 * prepend the boot environment root path if we're
808fa30d9edSKyle Evans 	 * given a partial snapshot name.
809fa30d9edSKyle Evans 	 */
810fa30d9edSKyle Evans 	if ((err = be_root_concat(lbh, snapshot, snap_path)) != 0)
81128f16a0fSKyle Evans 		return (set_error(lbh, err));
812fa30d9edSKyle Evans 
813fa30d9edSKyle Evans 	/* ensure the snapshot exists */
814b29bf2f8SKyle Evans 	if ((err = be_validate_snap(lbh, snap_path)) != 0)
81528f16a0fSKyle Evans 		return (set_error(lbh, err));
81628f16a0fSKyle Evans 
817fa30d9edSKyle Evans         /* get a copy of the snapshot path so we can disect it */
818cc4deabcSKyle Evans 	if ((parentname = strdup(snap_path)) == NULL)
819cc4deabcSKyle Evans 		return (set_error(lbh, BE_ERR_UNKNOWN));
820cc4deabcSKyle Evans 
821fa30d9edSKyle Evans         /* split dataset name from snapshot name */
82228f16a0fSKyle Evans 	snapname = strchr(parentname, '@');
82328f16a0fSKyle Evans 	if (snapname == NULL) {
824cc4deabcSKyle Evans 		free(parentname);
825cc4deabcSKyle Evans 		return (set_error(lbh, BE_ERR_UNKNOWN));
82628f16a0fSKyle Evans 	}
82728f16a0fSKyle Evans 	*snapname = '\0';
82828f16a0fSKyle Evans 	snapname++;
82928f16a0fSKyle Evans 
830fa30d9edSKyle Evans         /* set-up the boot environment */
831fa30d9edSKyle Evans         ldc.lbh = lbh;
832fa30d9edSKyle Evans         ldc.bename = bename;
833fa30d9edSKyle Evans         ldc.snapname = snapname;
834fa30d9edSKyle Evans 	ldc.depth = 0;
835fa30d9edSKyle Evans 	ldc.depth_limit = depth;
83628f16a0fSKyle Evans 
837fa30d9edSKyle Evans         /* the boot environment will be cloned from this dataset */
83828f16a0fSKyle Evans 	parent_hdl = zfs_open(lbh->lzh, parentname, ZFS_TYPE_DATASET);
839fa30d9edSKyle Evans 
840fa30d9edSKyle Evans         /* create the boot environment */
841fa30d9edSKyle Evans 	err = be_clone_cb(parent_hdl, &ldc);
84228f16a0fSKyle Evans 
843cc4deabcSKyle Evans 	free(parentname);
84428f16a0fSKyle Evans 	return (set_error(lbh, err));
84528f16a0fSKyle Evans }
84628f16a0fSKyle Evans 
847fa30d9edSKyle Evans /*
848fa30d9edSKyle Evans  * Create a boot environment from pre-existing snapshot, specifying a depth.
849fa30d9edSKyle Evans  */
850fa30d9edSKyle Evans int be_create_depth(libbe_handle_t *lbh, const char *bename,
851fa30d9edSKyle Evans 		    const char *snap, int depth)
852fa30d9edSKyle Evans {
853fa30d9edSKyle Evans 	return (be_clone(lbh, bename, snap, depth));
854fa30d9edSKyle Evans }
855fa30d9edSKyle Evans 
856fa30d9edSKyle Evans /*
857fa30d9edSKyle Evans  * Create the boot environment from pre-existing snapshot
858fa30d9edSKyle Evans  */
859fa30d9edSKyle Evans int
860fa30d9edSKyle Evans be_create_from_existing_snap(libbe_handle_t *lbh, const char *bename,
861fa30d9edSKyle Evans     const char *snap)
862fa30d9edSKyle Evans {
863fa30d9edSKyle Evans 	return (be_clone(lbh, bename, snap, -1));
864fa30d9edSKyle Evans }
865fa30d9edSKyle Evans 
86628f16a0fSKyle Evans 
86728f16a0fSKyle Evans /*
86828f16a0fSKyle Evans  * Create a boot environment from an existing boot environment
86928f16a0fSKyle Evans  */
87028f16a0fSKyle Evans int
871fa30d9edSKyle Evans be_create_from_existing(libbe_handle_t *lbh, const char *bename, const char *old)
87228f16a0fSKyle Evans {
87328f16a0fSKyle Evans 	int err;
874fa30d9edSKyle Evans 	char snap[BE_MAXPATHLEN];
87528f16a0fSKyle Evans 
876fa30d9edSKyle Evans 	if ((err = be_snapshot(lbh, old, NULL, true, snap)) != 0)
87728f16a0fSKyle Evans 		return (set_error(lbh, err));
87828f16a0fSKyle Evans 
879fa30d9edSKyle Evans         err = be_clone(lbh, bename, snap, -1);
88028f16a0fSKyle Evans 
88128f16a0fSKyle Evans 	return (set_error(lbh, err));
88228f16a0fSKyle Evans }
88328f16a0fSKyle Evans 
88428f16a0fSKyle Evans 
88528f16a0fSKyle Evans /*
88628f16a0fSKyle Evans  * Verifies that a snapshot has a valid name, exists, and has a mountpoint of
88728f16a0fSKyle Evans  * '/'. Returns BE_ERR_SUCCESS (0), upon success, or the relevant BE_ERR_* upon
88828f16a0fSKyle Evans  * failure. Does not set the internal library error state.
88928f16a0fSKyle Evans  */
89028f16a0fSKyle Evans int
891b29bf2f8SKyle Evans be_validate_snap(libbe_handle_t *lbh, const char *snap_name)
89228f16a0fSKyle Evans {
89328f16a0fSKyle Evans 
894bfe0869cSKyle Evans 	if (strlen(snap_name) >= BE_MAXPATHLEN)
89528f16a0fSKyle Evans 		return (BE_ERR_PATHLEN);
89628f16a0fSKyle Evans 
897fcb47c42SKyle Evans 	if (!zfs_name_valid(snap_name, ZFS_TYPE_SNAPSHOT))
898fcb47c42SKyle Evans 		return (BE_ERR_INVALIDNAME);
899fcb47c42SKyle Evans 
90028f16a0fSKyle Evans 	if (!zfs_dataset_exists(lbh->lzh, snap_name,
901bfe0869cSKyle Evans 	    ZFS_TYPE_SNAPSHOT))
90228f16a0fSKyle Evans 		return (BE_ERR_NOENT);
90328f16a0fSKyle Evans 
90451aecc89SKyle Evans 	return (BE_ERR_SUCCESS);
90528f16a0fSKyle Evans }
90628f16a0fSKyle Evans 
90728f16a0fSKyle Evans 
90828f16a0fSKyle Evans /*
90928f16a0fSKyle Evans  * Idempotently appends the name argument to the root boot environment path
91028f16a0fSKyle Evans  * and copies the resulting string into the result buffer (which is assumed
91128f16a0fSKyle Evans  * to be at least BE_MAXPATHLEN characters long. Returns BE_ERR_SUCCESS upon
91228f16a0fSKyle Evans  * success, BE_ERR_PATHLEN if the resulting path is longer than BE_MAXPATHLEN,
91328f16a0fSKyle Evans  * or BE_ERR_INVALIDNAME if the name is a path that does not begin with
91428f16a0fSKyle Evans  * zfs_be_root. Does not set internal library error state.
91528f16a0fSKyle Evans  */
91628f16a0fSKyle Evans int
917b29bf2f8SKyle Evans be_root_concat(libbe_handle_t *lbh, const char *name, char *result)
91828f16a0fSKyle Evans {
91928f16a0fSKyle Evans 	size_t name_len, root_len;
92028f16a0fSKyle Evans 
92128f16a0fSKyle Evans 	name_len = strlen(name);
92228f16a0fSKyle Evans 	root_len = strlen(lbh->root);
92328f16a0fSKyle Evans 
92428f16a0fSKyle Evans 	/* Act idempotently; return be name if it is already a full path */
92528f16a0fSKyle Evans 	if (strrchr(name, '/') != NULL) {
926bfe0869cSKyle Evans 		if (strstr(name, lbh->root) != name)
92728f16a0fSKyle Evans 			return (BE_ERR_INVALIDNAME);
92828f16a0fSKyle Evans 
929bfe0869cSKyle Evans 		if (name_len >= BE_MAXPATHLEN)
93028f16a0fSKyle Evans 			return (BE_ERR_PATHLEN);
93128f16a0fSKyle Evans 
93255b0e92bSKyle Evans 		strlcpy(result, name, BE_MAXPATHLEN);
93328f16a0fSKyle Evans 		return (BE_ERR_SUCCESS);
93428f16a0fSKyle Evans 	} else if (name_len + root_len + 1 < BE_MAXPATHLEN) {
93528f16a0fSKyle Evans 		snprintf(result, BE_MAXPATHLEN, "%s/%s", lbh->root,
93628f16a0fSKyle Evans 		    name);
93728f16a0fSKyle Evans 		return (BE_ERR_SUCCESS);
93828f16a0fSKyle Evans 	}
93928f16a0fSKyle Evans 
94028f16a0fSKyle Evans 	return (BE_ERR_PATHLEN);
94128f16a0fSKyle Evans }
94228f16a0fSKyle Evans 
94328f16a0fSKyle Evans 
94428f16a0fSKyle Evans /*
94528f16a0fSKyle Evans  * Verifies the validity of a boot environment name (A-Za-z0-9-_.). Returns
9465b7803a9SKyle Evans  * BE_ERR_SUCCESS (0) if name is valid, otherwise returns BE_ERR_INVALIDNAME
9475b7803a9SKyle Evans  * or BE_ERR_PATHLEN.
94828f16a0fSKyle Evans  * Does not set internal library error state.
94928f16a0fSKyle Evans  */
95028f16a0fSKyle Evans int
9515b7803a9SKyle Evans be_validate_name(libbe_handle_t *lbh, const char *name)
95228f16a0fSKyle Evans {
95328f16a0fSKyle Evans 
9545b7803a9SKyle Evans 	/*
9555b7803a9SKyle Evans 	 * Impose the additional restriction that the entire dataset name must
9565b7803a9SKyle Evans 	 * not exceed the maximum length of a dataset, i.e. MAXNAMELEN.
9575b7803a9SKyle Evans 	 */
9585b7803a9SKyle Evans 	if (strlen(lbh->root) + 1 + strlen(name) > MAXNAMELEN)
9595b7803a9SKyle Evans 		return (BE_ERR_PATHLEN);
960fcb47c42SKyle Evans 
961fcb47c42SKyle Evans 	if (!zfs_name_valid(name, ZFS_TYPE_DATASET))
962fcb47c42SKyle Evans 		return (BE_ERR_INVALIDNAME);
963fcb47c42SKyle Evans 
964dadb9c70SKyle Evans 	/*
965dadb9c70SKyle Evans 	 * ZFS allows spaces in boot environment names, but the kernel can't
966dadb9c70SKyle Evans 	 * handle booting from such a dataset right now.  vfs.root.mountfrom
967dadb9c70SKyle Evans 	 * is defined to be a space-separated list, and there's no protocol for
968dadb9c70SKyle Evans 	 * escaping whitespace in the path component of a dev:path spec.  So
969dadb9c70SKyle Evans 	 * while loader can handle this situation alright, it can't safely pass
970dadb9c70SKyle Evans 	 * it on to mountroot.
971dadb9c70SKyle Evans 	 */
972dadb9c70SKyle Evans 	if (strchr(name, ' ') != NULL)
973dadb9c70SKyle Evans 		return (BE_ERR_INVALIDNAME);
974dadb9c70SKyle Evans 
97528f16a0fSKyle Evans 	return (BE_ERR_SUCCESS);
97628f16a0fSKyle Evans }
97728f16a0fSKyle Evans 
97828f16a0fSKyle Evans 
97928f16a0fSKyle Evans /*
98028f16a0fSKyle Evans  * usage
98128f16a0fSKyle Evans  */
98228f16a0fSKyle Evans int
98373c3d608SKyle Evans be_rename(libbe_handle_t *lbh, const char *old, const char *new)
98428f16a0fSKyle Evans {
98528f16a0fSKyle Evans 	char full_old[BE_MAXPATHLEN];
98628f16a0fSKyle Evans 	char full_new[BE_MAXPATHLEN];
98728f16a0fSKyle Evans 	zfs_handle_t *zfs_hdl;
98828f16a0fSKyle Evans 	int err;
98928f16a0fSKyle Evans 
9905b7803a9SKyle Evans 	/*
9915b7803a9SKyle Evans 	 * be_validate_name is documented not to set error state, so we should
9925b7803a9SKyle Evans 	 * do so here.
9935b7803a9SKyle Evans 	 */
9945b7803a9SKyle Evans 	if ((err = be_validate_name(lbh, new)) != 0)
9955b7803a9SKyle Evans 		return (set_error(lbh, err));
996b29bf2f8SKyle Evans 	if ((err = be_root_concat(lbh, old, full_old)) != 0)
99728f16a0fSKyle Evans 		return (set_error(lbh, err));
998b29bf2f8SKyle Evans 	if ((err = be_root_concat(lbh, new, full_new)) != 0)
99928f16a0fSKyle Evans 		return (set_error(lbh, err));
100028f16a0fSKyle Evans 
1001bfe0869cSKyle Evans 	if (!zfs_dataset_exists(lbh->lzh, full_old, ZFS_TYPE_DATASET))
10022989df09SKyle Evans 		return (set_error(lbh, BE_ERR_NOENT));
100328f16a0fSKyle Evans 
1004bfe0869cSKyle Evans 	if (zfs_dataset_exists(lbh->lzh, full_new, ZFS_TYPE_DATASET))
10052989df09SKyle Evans 		return (set_error(lbh, BE_ERR_EXISTS));
100628f16a0fSKyle Evans 
100728f16a0fSKyle Evans 	if ((zfs_hdl = zfs_open(lbh->lzh, full_old,
1008bfe0869cSKyle Evans 	    ZFS_TYPE_FILESYSTEM)) == NULL)
10092989df09SKyle Evans 		return (set_error(lbh, BE_ERR_ZFSOPEN));
101028f16a0fSKyle Evans 
1011eac7052fSMatt Macy 	/* recurse, nounmount, forceunmount */
1012eac7052fSMatt Macy 	struct renameflags flags = {
1013eac7052fSMatt Macy 		.nounmount = 1,
1014eac7052fSMatt Macy 	};
1015eac7052fSMatt Macy 	err = zfs_rename(zfs_hdl, full_new, flags);
101628f16a0fSKyle Evans 
101728f16a0fSKyle Evans 	zfs_close(zfs_hdl);
10185b7803a9SKyle Evans 	if (err != 0)
10195b7803a9SKyle Evans 		return (set_error(lbh, BE_ERR_UNKNOWN));
10205b7803a9SKyle Evans 	return (0);
102128f16a0fSKyle Evans }
102228f16a0fSKyle Evans 
102328f16a0fSKyle Evans 
102428f16a0fSKyle Evans int
102573c3d608SKyle Evans be_export(libbe_handle_t *lbh, const char *bootenv, int fd)
102628f16a0fSKyle Evans {
102728f16a0fSKyle Evans 	char snap_name[BE_MAXPATHLEN];
102828f16a0fSKyle Evans 	char buf[BE_MAXPATHLEN];
102928f16a0fSKyle Evans 	zfs_handle_t *zfs;
10308569a95eSAndriy Gapon 	sendflags_t flags = { 0 };
103128f16a0fSKyle Evans 	int err;
103228f16a0fSKyle Evans 
1033b29bf2f8SKyle Evans 	if ((err = be_snapshot(lbh, bootenv, NULL, true, snap_name)) != 0)
10346d4b1d24SKyle Evans 		/* Use the error set by be_snapshot */
10356d4b1d24SKyle Evans 		return (err);
103628f16a0fSKyle Evans 
103728f16a0fSKyle Evans 	be_root_concat(lbh, snap_name, buf);
103828f16a0fSKyle Evans 
1039bfe0869cSKyle Evans 	if ((zfs = zfs_open(lbh->lzh, buf, ZFS_TYPE_DATASET)) == NULL)
1040506f5fdfSKyle Evans 		return (set_error(lbh, BE_ERR_ZFSOPEN));
104128f16a0fSKyle Evans 
10429e5787d2SMatt Macy 	err = zfs_send_one(zfs, NULL, fd, &flags, /* redactbook */ NULL);
10436d4b1d24SKyle Evans 	zfs_close(zfs);
10446d4b1d24SKyle Evans 
104528f16a0fSKyle Evans 	return (err);
104628f16a0fSKyle Evans }
104728f16a0fSKyle Evans 
104828f16a0fSKyle Evans 
104928f16a0fSKyle Evans int
105073c3d608SKyle Evans be_import(libbe_handle_t *lbh, const char *bootenv, int fd)
105128f16a0fSKyle Evans {
105228f16a0fSKyle Evans 	char buf[BE_MAXPATHLEN];
105328f16a0fSKyle Evans 	nvlist_t *props;
105428f16a0fSKyle Evans 	zfs_handle_t *zfs;
105516ac0705SKyle Evans 	recvflags_t flags = { .nomount = 1 };
105616ac0705SKyle Evans 	int err;
105728f16a0fSKyle Evans 
105816ac0705SKyle Evans 	be_root_concat(lbh, bootenv, buf);
105928f16a0fSKyle Evans 
106016ac0705SKyle Evans 	if ((err = zfs_receive(lbh->lzh, buf, NULL, &flags, fd, NULL)) != 0) {
1061506f5fdfSKyle Evans 		switch (err) {
1062506f5fdfSKyle Evans 		case EINVAL:
1063506f5fdfSKyle Evans 			return (set_error(lbh, BE_ERR_NOORIGIN));
1064506f5fdfSKyle Evans 		case ENOENT:
1065506f5fdfSKyle Evans 			return (set_error(lbh, BE_ERR_NOENT));
1066506f5fdfSKyle Evans 		case EIO:
1067506f5fdfSKyle Evans 			return (set_error(lbh, BE_ERR_IO));
1068506f5fdfSKyle Evans 		default:
1069506f5fdfSKyle Evans 			return (set_error(lbh, BE_ERR_UNKNOWN));
1070506f5fdfSKyle Evans 		}
107128f16a0fSKyle Evans 	}
107228f16a0fSKyle Evans 
107316ac0705SKyle Evans 	if ((zfs = zfs_open(lbh->lzh, buf, ZFS_TYPE_FILESYSTEM)) == NULL)
1074506f5fdfSKyle Evans 		return (set_error(lbh, BE_ERR_ZFSOPEN));
107528f16a0fSKyle Evans 
107628f16a0fSKyle Evans 	nvlist_alloc(&props, NV_UNIQUE_NAME, KM_SLEEP);
107728f16a0fSKyle Evans 	nvlist_add_string(props, "canmount", "noauto");
1078ac34fe23SKyle Evans 	nvlist_add_string(props, "mountpoint", "none");
107928f16a0fSKyle Evans 
108016ac0705SKyle Evans 	err = zfs_prop_set_list(zfs, props);
108128f16a0fSKyle Evans 	nvlist_free(props);
108228f16a0fSKyle Evans 
10831b057aacSKyle Evans 	zfs_close(zfs);
10841b057aacSKyle Evans 
10851b057aacSKyle Evans 	if (err != 0)
10861b057aacSKyle Evans 		return (set_error(lbh, BE_ERR_UNKNOWN));
10871b057aacSKyle Evans 
108816ac0705SKyle Evans 	return (0);
108928f16a0fSKyle Evans }
109028f16a0fSKyle Evans 
10913d1a1f2cSKyle Evans #if SOON
1092c65a2111SKyle Evans static int
1093c65a2111SKyle Evans be_create_child_noent(libbe_handle_t *lbh, const char *active,
1094c65a2111SKyle Evans     const char *child_path)
1095c65a2111SKyle Evans {
1096c65a2111SKyle Evans 	nvlist_t *props;
1097c65a2111SKyle Evans 	zfs_handle_t *zfs;
1098c65a2111SKyle Evans 	int err;
1099c65a2111SKyle Evans 
1100c65a2111SKyle Evans 	nvlist_alloc(&props, NV_UNIQUE_NAME, KM_SLEEP);
1101c65a2111SKyle Evans 	nvlist_add_string(props, "canmount", "noauto");
1102c65a2111SKyle Evans 	nvlist_add_string(props, "mountpoint", child_path);
1103c65a2111SKyle Evans 
1104c65a2111SKyle Evans 	/* Create */
1105c65a2111SKyle Evans 	if ((err = zfs_create(lbh->lzh, active, ZFS_TYPE_DATASET,
1106c65a2111SKyle Evans 	    props)) != 0) {
1107c65a2111SKyle Evans 		switch (err) {
1108c65a2111SKyle Evans 		case EZFS_EXISTS:
1109c65a2111SKyle Evans 			return (set_error(lbh, BE_ERR_EXISTS));
1110c65a2111SKyle Evans 		case EZFS_NOENT:
1111c65a2111SKyle Evans 			return (set_error(lbh, BE_ERR_NOENT));
1112c65a2111SKyle Evans 		case EZFS_BADTYPE:
1113c65a2111SKyle Evans 		case EZFS_BADVERSION:
1114c65a2111SKyle Evans 			return (set_error(lbh, BE_ERR_NOPOOL));
1115c65a2111SKyle Evans 		case EZFS_BADPROP:
1116c65a2111SKyle Evans 		default:
1117c65a2111SKyle Evans 			/* We set something up wrong, probably... */
1118c65a2111SKyle Evans 			return (set_error(lbh, BE_ERR_UNKNOWN));
1119c65a2111SKyle Evans 		}
1120c65a2111SKyle Evans 	}
1121c65a2111SKyle Evans 	nvlist_free(props);
1122c65a2111SKyle Evans 
1123c65a2111SKyle Evans 	if ((zfs = zfs_open(lbh->lzh, active, ZFS_TYPE_DATASET)) == NULL)
1124c65a2111SKyle Evans 		return (set_error(lbh, BE_ERR_ZFSOPEN));
1125c65a2111SKyle Evans 
1126c65a2111SKyle Evans 	/* Set props */
1127c65a2111SKyle Evans 	if ((err = zfs_prop_set(zfs, "canmount", "noauto")) != 0) {
1128c65a2111SKyle Evans 		zfs_close(zfs);
1129c65a2111SKyle Evans 		/*
1130c65a2111SKyle Evans 		 * Similar to other cases, this shouldn't fail unless we've
1131c65a2111SKyle Evans 		 * done something wrong.  This is a new dataset that shouldn't
1132c65a2111SKyle Evans 		 * have been mounted anywhere between creation and now.
1133c65a2111SKyle Evans 		 */
1134c65a2111SKyle Evans 		if (err == EZFS_NOMEM)
1135c65a2111SKyle Evans 			return (set_error(lbh, BE_ERR_NOMEM));
1136c65a2111SKyle Evans 		return (set_error(lbh, BE_ERR_UNKNOWN));
1137c65a2111SKyle Evans 	}
1138c65a2111SKyle Evans 	zfs_close(zfs);
1139c65a2111SKyle Evans 	return (BE_ERR_SUCCESS);
1140c65a2111SKyle Evans }
1141c65a2111SKyle Evans 
1142c65a2111SKyle Evans static int
1143c65a2111SKyle Evans be_create_child_cloned(libbe_handle_t *lbh, const char *active)
1144c65a2111SKyle Evans {
11453d1a1f2cSKyle Evans 	char buf[BE_MAXPATHLEN], tmp[BE_MAXPATHLEN];;
1146c65a2111SKyle Evans 	zfs_handle_t *zfs;
1147c65a2111SKyle Evans 	int err;
1148c65a2111SKyle Evans 
1149c65a2111SKyle Evans 	/* XXX TODO ? */
1150c65a2111SKyle Evans 
1151c65a2111SKyle Evans 	/*
1152c65a2111SKyle Evans 	 * Establish if the existing path is a zfs dataset or just
1153c65a2111SKyle Evans 	 * the subdirectory of one
1154c65a2111SKyle Evans 	 */
11553d1a1f2cSKyle Evans 	strlcpy(tmp, "tmp/be_snap.XXXXX", sizeof(tmp));
11563d1a1f2cSKyle Evans 	if (mktemp(tmp) == NULL)
1157c65a2111SKyle Evans 		return (set_error(lbh, BE_ERR_UNKNOWN));
1158c65a2111SKyle Evans 
11593d1a1f2cSKyle Evans 	be_root_concat(lbh, tmp, buf);
11603d1a1f2cSKyle Evans 	printf("Here %s?\n", buf);
1161c65a2111SKyle Evans 	if ((err = zfs_snapshot(lbh->lzh, buf, false, NULL)) != 0) {
1162c65a2111SKyle Evans 		switch (err) {
1163c65a2111SKyle Evans 		case EZFS_INVALIDNAME:
1164c65a2111SKyle Evans 			return (set_error(lbh, BE_ERR_INVALIDNAME));
1165c65a2111SKyle Evans 
1166c65a2111SKyle Evans 		default:
1167c65a2111SKyle Evans 			/*
1168c65a2111SKyle Evans 			 * The other errors that zfs_ioc_snapshot might return
1169c65a2111SKyle Evans 			 * shouldn't happen if we've set things up properly, so
1170c65a2111SKyle Evans 			 * we'll gloss over them and call it UNKNOWN as it will
1171c65a2111SKyle Evans 			 * require further triage.
1172c65a2111SKyle Evans 			 */
1173c65a2111SKyle Evans 			if (errno == ENOTSUP)
1174c65a2111SKyle Evans 				return (set_error(lbh, BE_ERR_NOPOOL));
1175c65a2111SKyle Evans 			return (set_error(lbh, BE_ERR_UNKNOWN));
1176c65a2111SKyle Evans 		}
1177c65a2111SKyle Evans 	}
1178c65a2111SKyle Evans 
1179c65a2111SKyle Evans 	/* Clone */
1180c65a2111SKyle Evans 	if ((zfs = zfs_open(lbh->lzh, buf, ZFS_TYPE_SNAPSHOT)) == NULL)
1181c65a2111SKyle Evans 		return (BE_ERR_ZFSOPEN);
1182c65a2111SKyle Evans 
1183c65a2111SKyle Evans 	if ((err = zfs_clone(zfs, active, NULL)) != 0)
1184c65a2111SKyle Evans 		/* XXX TODO correct error */
1185c65a2111SKyle Evans 		return (set_error(lbh, BE_ERR_UNKNOWN));
1186c65a2111SKyle Evans 
1187c65a2111SKyle Evans 	/* set props */
1188c65a2111SKyle Evans 	zfs_close(zfs);
1189c65a2111SKyle Evans 	return (BE_ERR_SUCCESS);
1190c65a2111SKyle Evans }
119128f16a0fSKyle Evans 
119228f16a0fSKyle Evans int
119373c3d608SKyle Evans be_add_child(libbe_handle_t *lbh, const char *child_path, bool cp_if_exists)
119428f16a0fSKyle Evans {
119573c3d608SKyle Evans 	struct stat sb;
1196c65a2111SKyle Evans 	char active[BE_MAXPATHLEN], buf[BE_MAXPATHLEN];
119728f16a0fSKyle Evans 	nvlist_t *props;
119873c3d608SKyle Evans 	const char *s;
119928f16a0fSKyle Evans 
120028f16a0fSKyle Evans 	/* Require absolute paths */
1201bfe0869cSKyle Evans 	if (*child_path != '/')
12026d4b1d24SKyle Evans 		return (set_error(lbh, BE_ERR_BADPATH));
120328f16a0fSKyle Evans 
12046d4b1d24SKyle Evans 	strlcpy(active, be_active_path(lbh), BE_MAXPATHLEN);
120528f16a0fSKyle Evans 	strcpy(buf, active);
120628f16a0fSKyle Evans 
120728f16a0fSKyle Evans 	/* Create non-mountable parent dataset(s) */
120873c3d608SKyle Evans 	s = child_path;
120928f16a0fSKyle Evans 	for (char *p; (p = strchr(s+1, '/')) != NULL; s = p) {
121028f16a0fSKyle Evans 		size_t len = p - s;
121128f16a0fSKyle Evans 		strncat(buf, s, len);
121228f16a0fSKyle Evans 
121328f16a0fSKyle Evans 		nvlist_alloc(&props, NV_UNIQUE_NAME, KM_SLEEP);
121428f16a0fSKyle Evans 		nvlist_add_string(props, "canmount", "off");
121528f16a0fSKyle Evans 		nvlist_add_string(props, "mountpoint", "none");
121628f16a0fSKyle Evans 		zfs_create(lbh->lzh, buf, ZFS_TYPE_DATASET, props);
121728f16a0fSKyle Evans 		nvlist_free(props);
121828f16a0fSKyle Evans 	}
121928f16a0fSKyle Evans 
122028f16a0fSKyle Evans 	/* Path does not exist as a descendent of / yet */
12216d4b1d24SKyle Evans 	if (strlcat(active, child_path, BE_MAXPATHLEN) >= BE_MAXPATHLEN)
12226d4b1d24SKyle Evans 		return (set_error(lbh, BE_ERR_PATHLEN));
122328f16a0fSKyle Evans 
122428f16a0fSKyle Evans 	if (stat(child_path, &sb) != 0) {
122528f16a0fSKyle Evans 		/* Verify that error is ENOENT */
12266d4b1d24SKyle Evans 		if (errno != ENOENT)
12276d4b1d24SKyle Evans 			return (set_error(lbh, BE_ERR_UNKNOWN));
1228c65a2111SKyle Evans 		return (be_create_child_noent(lbh, active, child_path));
1229c65a2111SKyle Evans 	} else if (cp_if_exists)
123028f16a0fSKyle Evans 		/* Path is already a descendent of / and should be copied */
1231c65a2111SKyle Evans 		return (be_create_child_cloned(lbh, active));
12326d4b1d24SKyle Evans 	return (set_error(lbh, BE_ERR_EXISTS));
123328f16a0fSKyle Evans }
12343d1a1f2cSKyle Evans #endif	/* SOON */
123528f16a0fSKyle Evans 
12368d4ce358SKyle Evans /*
1237e307eb94SToomas Soome  * Deactivate old BE dataset; currently just sets canmount=noauto or
1238e307eb94SToomas Soome  * resets boot once configuration.
12398d4ce358SKyle Evans  */
1240e307eb94SToomas Soome int
1241e307eb94SToomas Soome be_deactivate(libbe_handle_t *lbh, const char *ds, bool temporary)
12428d4ce358SKyle Evans {
12438d4ce358SKyle Evans 	zfs_handle_t *zfs;
12448d4ce358SKyle Evans 
1245e307eb94SToomas Soome 	if (temporary) {
1246e307eb94SToomas Soome 		return (lzbe_set_boot_device(
1247e307eb94SToomas Soome 		    zpool_get_name(lbh->active_phandle), lzbe_add, NULL));
1248e307eb94SToomas Soome 	}
1249e307eb94SToomas Soome 
12508d4ce358SKyle Evans 	if ((zfs = zfs_open(lbh->lzh, ds, ZFS_TYPE_DATASET)) == NULL)
12518d4ce358SKyle Evans 		return (1);
12528d4ce358SKyle Evans 	if (zfs_prop_set(zfs, "canmount", "noauto") != 0)
12538d4ce358SKyle Evans 		return (1);
12548d4ce358SKyle Evans 	zfs_close(zfs);
12558d4ce358SKyle Evans 	return (0);
12568d4ce358SKyle Evans }
125728f16a0fSKyle Evans 
125828f16a0fSKyle Evans int
125973c3d608SKyle Evans be_activate(libbe_handle_t *lbh, const char *bootenv, bool temporary)
126028f16a0fSKyle Evans {
126128f16a0fSKyle Evans 	char be_path[BE_MAXPATHLEN];
1262e307eb94SToomas Soome 	nvlist_t *dsprops;
12634635676dSKyle Evans 	char *origin;
12640cadc427SKyle Evans 	zfs_handle_t *zhp;
126528f16a0fSKyle Evans 	int err;
126628f16a0fSKyle Evans 
126728f16a0fSKyle Evans 	be_root_concat(lbh, bootenv, be_path);
126828f16a0fSKyle Evans 
126928f16a0fSKyle Evans 	/* Note: be_exists fails if mountpoint is not / */
1270162ec569SKyle Evans 	if ((err = be_exists(lbh, be_path)) != 0)
1271162ec569SKyle Evans 		return (set_error(lbh, err));
127228f16a0fSKyle Evans 
127328f16a0fSKyle Evans 	if (temporary) {
1274e307eb94SToomas Soome 		return (lzbe_set_boot_device(
1275e307eb94SToomas Soome 		    zpool_get_name(lbh->active_phandle), lzbe_add, be_path));
127628f16a0fSKyle Evans 	} else {
12777edc1bd9SGleb Smirnoff 		if (strncmp(lbh->bootfs, "-", 1) != 0 &&
12787edc1bd9SGleb Smirnoff 		    be_deactivate(lbh, lbh->bootfs, false) != 0)
12798d4ce358SKyle Evans 			return (-1);
12808d4ce358SKyle Evans 
128128f16a0fSKyle Evans 		/* Obtain bootenv zpool */
1282c3a34c08SKyle Evans 		err = zpool_set_prop(lbh->active_phandle, "bootfs", be_path);
12830cadc427SKyle Evans 		if (err)
12840cadc427SKyle Evans 			return (-1);
128528f16a0fSKyle Evans 
12860cadc427SKyle Evans 		zhp = zfs_open(lbh->lzh, be_path, ZFS_TYPE_FILESYSTEM);
12870cadc427SKyle Evans 		if (zhp == NULL)
12880cadc427SKyle Evans 			return (-1);
128928f16a0fSKyle Evans 
12904635676dSKyle Evans 		if (be_prop_list_alloc(&dsprops) != 0)
12914635676dSKyle Evans 			return (-1);
12924635676dSKyle Evans 
12934635676dSKyle Evans 		if (be_get_dataset_props(lbh, be_path, dsprops) != 0) {
12944635676dSKyle Evans 			nvlist_free(dsprops);
12954635676dSKyle Evans 			return (-1);
12964635676dSKyle Evans 		}
12974635676dSKyle Evans 
12984635676dSKyle Evans 		if (nvlist_lookup_string(dsprops, "origin", &origin) == 0)
12990cadc427SKyle Evans 			err = zfs_promote(zhp);
13004635676dSKyle Evans 		nvlist_free(dsprops);
13014635676dSKyle Evans 
13020cadc427SKyle Evans 		zfs_close(zhp);
13030cadc427SKyle Evans 
13040cadc427SKyle Evans 		if (err)
130528f16a0fSKyle Evans 			return (-1);
130628f16a0fSKyle Evans 	}
13070cadc427SKyle Evans 
13080cadc427SKyle Evans 	return (BE_ERR_SUCCESS);
130928f16a0fSKyle Evans }
1310