xref: /freebsd/lib/libbe/be.c (revision 6d4b1d241d7b3e471c61cae3775d511e6864e3b3)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright (c) 2017 Kyle J. Kneitinger <kyle@kneit.in>
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28 
29 #include <sys/cdefs.h>
30 __FBSDID("$FreeBSD$");
31 
32 #include <sys/stat.h>
33 #include <sys/types.h>
34 
35 #include <ctype.h>
36 #include <kenv.h>
37 #include <libgen.h>
38 #include <libzfs_core.h>
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <time.h>
42 #include <unistd.h>
43 
44 #include "be.h"
45 #include "be_impl.h"
46 
47 /*
48  * Iterator function for locating the rootfs amongst the children of the
49  * zfs_be_root set by loader(8).  data is expected to be a libbe_handle_t *.
50  */
51 static int
52 be_locate_rootfs(zfs_handle_t *chkds, void *data)
53 {
54 	libbe_handle_t *lbh;
55 	char *mntpoint;
56 
57 	lbh = (libbe_handle_t *)data;
58 	if (lbh == NULL)
59 		return (1);
60 
61 	if (zfs_is_mounted(chkds, &mntpoint) && strcmp(mntpoint, "/") == 0) {
62 		strncpy(lbh->rootfs, zfs_get_name(chkds), BE_MAXPATHLEN);
63 		return (1);
64 	}
65 
66 	return (0);
67 }
68 
69 /*
70  * Initializes the libbe context to operate in the root boot environment
71  * dataset, for example, zroot/ROOT.
72  */
73 libbe_handle_t *
74 libbe_init(void)
75 {
76 	struct stat sb;
77 	dev_t root_dev, boot_dev;
78 	libbe_handle_t *lbh;
79 	zfs_handle_t *rootds;
80 	char *poolname, *pos;
81 	int pnamelen;
82 
83 	lbh = NULL;
84 	poolname = pos = NULL;
85 	pnamelen = 0;
86 	rootds = NULL;
87 
88 	/* Verify that /boot and / are mounted on the same filesystem */
89 	/* TODO: use errno here?? */
90 	if (stat("/", &sb) != 0)
91 		goto err;
92 
93 	root_dev = sb.st_dev;
94 
95 	if (stat("/boot", &sb) != 0)
96 		goto err;
97 
98 	boot_dev = sb.st_dev;
99 
100 	if (root_dev != boot_dev) {
101 		fprintf(stderr, "/ and /boot not on same device, quitting\n");
102 		goto err;
103 	}
104 
105 	if ((lbh = calloc(1, sizeof(libbe_handle_t))) == NULL)
106 		goto err;
107 
108 	if ((lbh->lzh = libzfs_init()) == NULL)
109 		goto err;
110 
111 	/* Obtain path to boot environment root */
112 	if ((kenv(KENV_GET, "zfs_be_root", lbh->root, BE_MAXPATHLEN)) == -1)
113 		goto err;
114 
115 	/* Remove leading 'zfs:' if present, otherwise use value as-is */
116 	if (strcmp(lbh->root, "zfs:") == 0)
117 		strncpy(lbh->root, strchr(lbh->root, ':') + sizeof(char),
118 		    BE_MAXPATHLEN);
119 
120 	if ((pos = strchr(lbh->root, '/')) == NULL)
121 		goto err;
122 
123 	pnamelen = pos - lbh->root;
124 	poolname = malloc(pnamelen + 1);
125 	if (poolname == NULL)
126 		goto err;
127 
128 	strncpy(poolname, lbh->root, pnamelen);
129 	poolname[pnamelen] = '\0';
130 	if ((lbh->active_phandle = zpool_open(lbh->lzh, poolname)) == NULL)
131 		goto err;
132 
133 	if (zpool_get_prop(lbh->active_phandle, ZPOOL_PROP_BOOTFS, lbh->bootfs,
134 	    BE_MAXPATHLEN, NULL, true) != 0)
135 		goto err;
136 
137 	/* Obtain path to boot environment rootfs (currently booted) */
138 	/* XXX Get dataset mounted at / by kenv/GUID from mountroot? */
139 	if ((rootds = zfs_open(lbh->lzh, lbh->root, ZFS_TYPE_DATASET)) == NULL)
140 		goto err;
141 
142 	zfs_iter_filesystems(rootds, be_locate_rootfs, lbh);
143 	zfs_close(rootds);
144 	rootds = NULL;
145 	if (*lbh->rootfs == '\0')
146 		goto err;
147 
148 	return (lbh);
149 err:
150 	if (lbh != NULL) {
151 		if (lbh->active_phandle != NULL)
152 			zpool_close(lbh->active_phandle);
153 		if (lbh->lzh != NULL)
154 			libzfs_fini(lbh->lzh);
155 		free(lbh);
156 	}
157 	if (rootds != NULL)
158 		zfs_close(rootds);
159 	free(poolname);
160 	return (NULL);
161 }
162 
163 
164 /*
165  * Free memory allocated by libbe_init()
166  */
167 void
168 libbe_close(libbe_handle_t *lbh)
169 {
170 
171 	if (lbh->active_phandle != NULL)
172 		zpool_close(lbh->active_phandle);
173 	libzfs_fini(lbh->lzh);
174 	free(lbh);
175 }
176 
177 /*
178  * Proxy through to libzfs for the moment.
179  */
180 void
181 be_nicenum(uint64_t num, char *buf, size_t buflen)
182 {
183 
184 	zfs_nicenum(num, buf, buflen);
185 }
186 
187 static int
188 be_destroy_cb(zfs_handle_t *zfs_hdl, void *data)
189 {
190 	int err;
191 
192 	if ((err = zfs_iter_children(zfs_hdl, be_destroy_cb, data)) != 0)
193 		return (err);
194 	if ((err = zfs_destroy(zfs_hdl, false)) != 0)
195 		return (err);
196 	return (0);
197 }
198 
199 /*
200  * Destroy the boot environment or snapshot specified by the name
201  * parameter. Options are or'd together with the possible values:
202  * BE_DESTROY_FORCE : forces operation on mounted datasets
203  */
204 int
205 be_destroy(libbe_handle_t *lbh, const char *name, int options)
206 {
207 	zfs_handle_t *fs;
208 	char path[BE_MAXPATHLEN];
209 	char *p;
210 	int err, force, mounted;
211 
212 	p = path;
213 	force = options & BE_DESTROY_FORCE;
214 	err = BE_ERR_SUCCESS;
215 
216 	be_root_concat(lbh, name, path);
217 
218 	if (strchr(name, '@') == NULL) {
219 		if (!zfs_dataset_exists(lbh->lzh, path, ZFS_TYPE_FILESYSTEM))
220 			return (set_error(lbh, BE_ERR_NOENT));
221 
222 		if (strcmp(path, lbh->rootfs) == 0)
223 			return (set_error(lbh, BE_ERR_DESTROYACT));
224 
225 		fs = zfs_open(lbh->lzh, p, ZFS_TYPE_FILESYSTEM);
226 	} else {
227 
228 		if (!zfs_dataset_exists(lbh->lzh, path, ZFS_TYPE_SNAPSHOT))
229 			return (set_error(lbh, BE_ERR_NOENT));
230 
231 		fs = zfs_open(lbh->lzh, p, ZFS_TYPE_SNAPSHOT);
232 	}
233 
234 	if (fs == NULL)
235 		return (set_error(lbh, BE_ERR_ZFSOPEN));
236 
237 	/* Check if mounted, unmount if force is specified */
238 	if ((mounted = zfs_is_mounted(fs, NULL)) != 0) {
239 		if (force)
240 			zfs_unmount(fs, NULL, 0);
241 		else
242 			return (set_error(lbh, BE_ERR_DESTROYMNT));
243 	}
244 
245 	if ((err = be_destroy_cb(fs, NULL)) != 0) {
246 		/* Children are still present or the mount is referenced */
247 		if (err == EBUSY)
248 			return (set_error(lbh, BE_ERR_DESTROYMNT));
249 		return (set_error(lbh, BE_ERR_UNKNOWN));
250 	}
251 
252 	return (0);
253 }
254 
255 
256 int
257 be_snapshot(libbe_handle_t *lbh, const char *source, const char *snap_name,
258     bool recursive, char *result)
259 {
260 	char buf[BE_MAXPATHLEN];
261 	time_t rawtime;
262 	int len, err;
263 
264 	be_root_concat(lbh, source, buf);
265 
266 	if (!be_exists(lbh, buf))
267 		return (BE_ERR_NOENT);
268 
269 	if (snap_name != NULL) {
270 		strcat(buf, "@");
271 		strcat(buf, snap_name);
272 		if (result != NULL)
273 			snprintf(result, BE_MAXPATHLEN, "%s@%s", source,
274 			    snap_name);
275 	} else {
276 		time(&rawtime);
277 		len = strlen(buf);
278 		strftime(buf + len, BE_MAXPATHLEN - len,
279 		    "@%F-%T", localtime(&rawtime));
280 		if (result != NULL)
281 			strcpy(result, strrchr(buf, '/') + 1);
282 	}
283 
284 	if ((err = zfs_snapshot(lbh->lzh, buf, recursive, NULL)) != 0) {
285 		switch (err) {
286 		case EZFS_INVALIDNAME:
287 			return (set_error(lbh, BE_ERR_INVALIDNAME));
288 
289 		default:
290 			/*
291 			 * The other errors that zfs_ioc_snapshot might return
292 			 * shouldn't happen if we've set things up properly, so
293 			 * we'll gloss over them and call it UNKNOWN as it will
294 			 * require further triage.
295 			 */
296 			if (errno == ENOTSUP)
297 				return (set_error(lbh, BE_ERR_NOPOOL));
298 			return (set_error(lbh, BE_ERR_UNKNOWN));
299 		}
300 	}
301 
302 	return (BE_ERR_SUCCESS);
303 }
304 
305 
306 /*
307  * Create the boot environment specified by the name parameter
308  */
309 int
310 be_create(libbe_handle_t *lbh, const char *name)
311 {
312 	int err;
313 
314 	err = be_create_from_existing(lbh, name, be_active_path(lbh));
315 
316 	return (set_error(lbh, err));
317 }
318 
319 
320 static int
321 be_deep_clone_prop(int prop, void *cb)
322 {
323 	int err;
324         struct libbe_dccb *dccb;
325 	zprop_source_t src;
326 	char pval[BE_MAXPATHLEN];
327 	char source[BE_MAXPATHLEN];
328 
329 	dccb = cb;
330 	/* Skip some properties we don't want to touch */
331 	if (prop == ZFS_PROP_CANMOUNT)
332 		return (ZPROP_CONT);
333 
334 	/* Don't copy readonly properties */
335 	if (zfs_prop_readonly(prop))
336 		return (ZPROP_CONT);
337 
338 	if ((err = zfs_prop_get(dccb->zhp, prop, (char *)&pval,
339 	    sizeof(pval), &src, (char *)&source, sizeof(source), false)))
340 		/* Just continue if we fail to read a property */
341 		return (ZPROP_CONT);
342 
343 	/* Only copy locally defined properties */
344 	if (src != ZPROP_SRC_LOCAL)
345 		return (ZPROP_CONT);
346 
347 	nvlist_add_string(dccb->props, zfs_prop_to_name(prop), (char *)pval);
348 
349 	return (ZPROP_CONT);
350 }
351 
352 static int
353 be_deep_clone(zfs_handle_t *ds, void *data)
354 {
355 	int err;
356 	char be_path[BE_MAXPATHLEN];
357 	char snap_path[BE_MAXPATHLEN];
358 	const char *dspath;
359 	char *dsname;
360 	zfs_handle_t *snap_hdl;
361 	nvlist_t *props;
362 	struct libbe_deep_clone *isdc, sdc;
363 	struct libbe_dccb dccb;
364 
365 	isdc = (struct libbe_deep_clone *)data;
366 	dspath = zfs_get_name(ds);
367 	if ((dsname = strrchr(dspath, '/')) == NULL)
368 		return (BE_ERR_UNKNOWN);
369 	dsname++;
370 
371 	if (isdc->bename == NULL)
372 		snprintf(be_path, sizeof(be_path), "%s/%s", isdc->be_root, dsname);
373 	else
374 		snprintf(be_path, sizeof(be_path), "%s/%s", isdc->be_root, isdc->bename);
375 
376 	snprintf(snap_path, sizeof(snap_path), "%s@%s", dspath, isdc->snapname);
377 
378 	if (zfs_dataset_exists(isdc->lbh->lzh, be_path, ZFS_TYPE_DATASET))
379 		return (set_error(isdc->lbh, BE_ERR_EXISTS));
380 
381 	if ((snap_hdl =
382 	    zfs_open(isdc->lbh->lzh, snap_path, ZFS_TYPE_SNAPSHOT)) == NULL)
383 		return (set_error(isdc->lbh, BE_ERR_ZFSOPEN));
384 
385 	nvlist_alloc(&props, NV_UNIQUE_NAME, KM_SLEEP);
386 	nvlist_add_string(props, "canmount", "noauto");
387 
388 	dccb.zhp = ds;
389 	dccb.props = props;
390 	if (zprop_iter(be_deep_clone_prop, &dccb, B_FALSE, B_FALSE,
391 	    ZFS_TYPE_FILESYSTEM) == ZPROP_INVAL)
392 		return (-1);
393 
394 	if ((err = zfs_clone(snap_hdl, be_path, props)) != 0) {
395 		switch (err) {
396 		case EZFS_SUCCESS:
397 			err = BE_ERR_SUCCESS;
398 			break;
399 		default:
400 			err = BE_ERR_ZFSCLONE;
401 			break;
402 		}
403 	}
404 
405 	nvlist_free(props);
406 	zfs_close(snap_hdl);
407 
408 	sdc.lbh = isdc->lbh;
409 	sdc.bename = NULL;
410 	sdc.snapname = isdc->snapname;
411 	sdc.be_root = (char *)&be_path;
412 
413 	err = zfs_iter_filesystems(ds, be_deep_clone, &sdc);
414 
415 	return (err);
416 }
417 
418 /*
419  * Create the boot environment from pre-existing snapshot
420  */
421 int
422 be_create_from_existing_snap(libbe_handle_t *lbh, const char *name,
423     const char *snap)
424 {
425 	int err;
426 	char be_path[BE_MAXPATHLEN];
427 	char snap_path[BE_MAXPATHLEN];
428 	const char *bename;
429 	char *parentname, *snapname;
430 	zfs_handle_t *parent_hdl;
431 	struct libbe_deep_clone sdc;
432 
433 	if ((err = be_validate_name(lbh, name)) != 0)
434 		return (set_error(lbh, err));
435 	if ((err = be_root_concat(lbh, snap, snap_path)) != 0)
436 		return (set_error(lbh, err));
437 	if ((err = be_validate_snap(lbh, snap_path)) != 0)
438 		return (set_error(lbh, err));
439 
440 	if ((err = be_root_concat(lbh, name, be_path)) != 0)
441 		return (set_error(lbh, err));
442 
443 	if ((bename = strrchr(name, '/')) == NULL)
444 		bename = name;
445 	else
446 		bename++;
447 
448 	if ((parentname = strdup(snap_path)) == NULL) {
449 		err = BE_ERR_UNKNOWN;
450 		return (set_error(lbh, err));
451 	}
452 	snapname = strchr(parentname, '@');
453 	if (snapname == NULL) {
454 		err = BE_ERR_UNKNOWN;
455 		return (set_error(lbh, err));
456 	}
457 	*snapname = '\0';
458 	snapname++;
459 
460 	sdc.lbh = lbh;
461 	sdc.bename = bename;
462 	sdc.snapname = snapname;
463 	sdc.be_root = lbh->root;
464 
465 	parent_hdl = zfs_open(lbh->lzh, parentname, ZFS_TYPE_DATASET);
466 	err = be_deep_clone(parent_hdl, &sdc);
467 
468 	return (set_error(lbh, err));
469 }
470 
471 
472 /*
473  * Create a boot environment from an existing boot environment
474  */
475 int
476 be_create_from_existing(libbe_handle_t *lbh, const char *name, const char *old)
477 {
478 	int err;
479 	char buf[BE_MAXPATHLEN];
480 
481 	if ((err = be_snapshot(lbh, old, NULL, true, (char *)&buf)))
482 		return (set_error(lbh, err));
483 
484 	err = be_create_from_existing_snap(lbh, name, (char *)buf);
485 
486 	return (set_error(lbh, err));
487 }
488 
489 
490 /*
491  * Verifies that a snapshot has a valid name, exists, and has a mountpoint of
492  * '/'. Returns BE_ERR_SUCCESS (0), upon success, or the relevant BE_ERR_* upon
493  * failure. Does not set the internal library error state.
494  */
495 int
496 be_validate_snap(libbe_handle_t *lbh, const char *snap_name)
497 {
498 	zfs_handle_t *zfs_hdl;
499 	char buf[BE_MAXPATHLEN];
500 	char *delim_pos;
501 	int err = BE_ERR_SUCCESS;
502 
503 	if (strlen(snap_name) >= BE_MAXPATHLEN)
504 		return (BE_ERR_PATHLEN);
505 
506 	if (!zfs_dataset_exists(lbh->lzh, snap_name,
507 	    ZFS_TYPE_SNAPSHOT))
508 		return (BE_ERR_NOENT);
509 
510 	strncpy(buf, snap_name, BE_MAXPATHLEN);
511 
512 	/* Find the base filesystem of the snapshot */
513 	if ((delim_pos = strchr(buf, '@')) == NULL)
514 		return (BE_ERR_INVALIDNAME);
515 	*delim_pos = '\0';
516 
517 	if ((zfs_hdl =
518 	    zfs_open(lbh->lzh, buf, ZFS_TYPE_DATASET)) == NULL)
519 		return (BE_ERR_NOORIGIN);
520 
521 	if ((err = zfs_prop_get(zfs_hdl, ZFS_PROP_MOUNTPOINT, buf, BE_MAXPATHLEN,
522 	    NULL, NULL, 0, 1)) != 0)
523 		err = BE_ERR_INVORIGIN;
524 
525 	if ((err != 0) && (strncmp(buf, "/", BE_MAXPATHLEN) != 0))
526 		err = BE_ERR_INVORIGIN;
527 
528 	zfs_close(zfs_hdl);
529 
530 	return (err);
531 }
532 
533 
534 /*
535  * Idempotently appends the name argument to the root boot environment path
536  * and copies the resulting string into the result buffer (which is assumed
537  * to be at least BE_MAXPATHLEN characters long. Returns BE_ERR_SUCCESS upon
538  * success, BE_ERR_PATHLEN if the resulting path is longer than BE_MAXPATHLEN,
539  * or BE_ERR_INVALIDNAME if the name is a path that does not begin with
540  * zfs_be_root. Does not set internal library error state.
541  */
542 int
543 be_root_concat(libbe_handle_t *lbh, const char *name, char *result)
544 {
545 	size_t name_len, root_len;
546 
547 	name_len = strlen(name);
548 	root_len = strlen(lbh->root);
549 
550 	/* Act idempotently; return be name if it is already a full path */
551 	if (strrchr(name, '/') != NULL) {
552 		if (strstr(name, lbh->root) != name)
553 			return (BE_ERR_INVALIDNAME);
554 
555 		if (name_len >= BE_MAXPATHLEN)
556 			return (BE_ERR_PATHLEN);
557 
558 		strncpy(result, name, BE_MAXPATHLEN);
559 		return (BE_ERR_SUCCESS);
560 	} else if (name_len + root_len + 1 < BE_MAXPATHLEN) {
561 		snprintf(result, BE_MAXPATHLEN, "%s/%s", lbh->root,
562 		    name);
563 		return (BE_ERR_SUCCESS);
564 	}
565 
566 	return (BE_ERR_PATHLEN);
567 }
568 
569 
570 /*
571  * Verifies the validity of a boot environment name (A-Za-z0-9-_.). Returns
572  * BE_ERR_SUCCESS (0) if name is valid, otherwise returns BE_ERR_INVALIDNAME.
573  * Does not set internal library error state.
574  */
575 int
576 be_validate_name(libbe_handle_t *lbh __unused, const char *name)
577 {
578 	for (int i = 0; *name; i++) {
579 		char c = *(name++);
580 		if (isalnum(c) || (c == '-') || (c == '_') || (c == '.'))
581 			continue;
582 		return (BE_ERR_INVALIDNAME);
583 	}
584 
585 	return (BE_ERR_SUCCESS);
586 }
587 
588 
589 /*
590  * usage
591  */
592 int
593 be_rename(libbe_handle_t *lbh, const char *old, const char *new)
594 {
595 	char full_old[BE_MAXPATHLEN];
596 	char full_new[BE_MAXPATHLEN];
597 	zfs_handle_t *zfs_hdl;
598 	int err;
599 
600 	if ((err = be_root_concat(lbh, old, full_old)) != 0)
601 		return (set_error(lbh, err));
602 	if ((err = be_root_concat(lbh, new, full_new)) != 0)
603 		return (set_error(lbh, err));
604 
605 	if ((err = be_validate_name(lbh, new)) != 0)
606 		return (err);
607 
608 	/* Check if old is active BE */
609 	if (strcmp(full_old, be_active_path(lbh)) == 0)
610 		return (set_error(lbh, BE_ERR_MOUNTED));
611 
612 	if (!zfs_dataset_exists(lbh->lzh, full_old, ZFS_TYPE_DATASET))
613 		return (set_error(lbh, BE_ERR_NOENT));
614 
615 	if (zfs_dataset_exists(lbh->lzh, full_new, ZFS_TYPE_DATASET))
616 		return (set_error(lbh, BE_ERR_EXISTS));
617 
618 	if ((zfs_hdl = zfs_open(lbh->lzh, full_old,
619 	    ZFS_TYPE_FILESYSTEM)) == NULL)
620 		return (set_error(lbh, BE_ERR_ZFSOPEN));
621 
622 	/* XXX TODO: Allow a force flag */
623 	if (zfs_is_mounted(zfs_hdl, NULL)) {
624 		zfs_close(zfs_hdl);
625 		return (set_error(lbh, BE_ERR_MOUNTED));
626 	}
627 
628 	/* recurse, nounmount, forceunmount */
629 	struct renameflags flags = { 0, 0, 0 };
630 
631 	err = zfs_rename(zfs_hdl, NULL, full_new, flags);
632 
633 	zfs_close(zfs_hdl);
634 
635 	return (set_error(lbh, err));
636 }
637 
638 
639 int
640 be_export(libbe_handle_t *lbh, const char *bootenv, int fd)
641 {
642 	char snap_name[BE_MAXPATHLEN];
643 	char buf[BE_MAXPATHLEN];
644 	zfs_handle_t *zfs;
645 	int err;
646 
647 	if ((err = be_snapshot(lbh, bootenv, NULL, true, snap_name)) != 0)
648 		/* Use the error set by be_snapshot */
649 		return (err);
650 
651 	be_root_concat(lbh, snap_name, buf);
652 
653 	if ((zfs = zfs_open(lbh->lzh, buf, ZFS_TYPE_DATASET)) == NULL)
654 		return (set_error(lbh, BE_ERR_ZFSOPEN));
655 
656 	err = zfs_send_one(zfs, NULL, fd, 0);
657 	zfs_close(zfs);
658 
659 	return (err);
660 }
661 
662 
663 int
664 be_import(libbe_handle_t *lbh, const char *bootenv, int fd)
665 {
666 	char buf[BE_MAXPATHLEN];
667 	time_t rawtime;
668 	nvlist_t *props;
669 	zfs_handle_t *zfs;
670 	int err, len;
671 	char nbuf[24];
672 
673 	/*
674 	 * We don't need this to be incredibly random, just unique enough that
675 	 * it won't conflict with an existing dataset name.  Chopping time
676 	 * down to 32 bits is probably good enough for this.
677 	 */
678 	snprintf(nbuf, 24, "tmp%u",
679 	    (uint32_t)(time(NULL) & 0xFFFFFFFF));
680 	if ((err = be_root_concat(lbh, nbuf, buf)) != 0)
681 		/*
682 		 * Technically this is our problem, but we try to use short
683 		 * enough names that we won't run into problems except in
684 		 * worst-case BE root approaching MAXPATHLEN.
685 		 */
686 		return (set_error(lbh, BE_ERR_PATHLEN));
687 
688 	time(&rawtime);
689 	len = strlen(buf);
690 	strftime(buf + len, BE_MAXPATHLEN - len,
691 	    "@%F-%T", localtime(&rawtime));
692 
693 	/* lzc_receive(SNAPNAME, PROPS, ORIGIN, FORCE, fd)) { */
694 	if ((err = lzc_receive(buf, NULL, NULL, false, fd)) != 0) {
695 		switch (err) {
696 		case EINVAL:
697 			return (set_error(lbh, BE_ERR_NOORIGIN));
698 		case ENOENT:
699 			return (set_error(lbh, BE_ERR_NOENT));
700 		case EIO:
701 			return (set_error(lbh, BE_ERR_IO));
702 		default:
703 			return (set_error(lbh, BE_ERR_UNKNOWN));
704 		}
705 	}
706 
707 	if ((zfs = zfs_open(lbh->lzh, buf, ZFS_TYPE_SNAPSHOT)) == NULL)
708 		return (set_error(lbh, BE_ERR_ZFSOPEN));
709 
710 	nvlist_alloc(&props, NV_UNIQUE_NAME, KM_SLEEP);
711 	nvlist_add_string(props, "canmount", "noauto");
712 	nvlist_add_string(props, "mountpoint", "/");
713 
714 	be_root_concat(lbh, bootenv, buf);
715 
716 	err = zfs_clone(zfs, buf, props);
717 	zfs_close(zfs);
718 
719 	nvlist_free(props);
720 
721 	return (be_destroy(lbh, nbuf, 0));
722 }
723 
724 
725 int
726 be_add_child(libbe_handle_t *lbh, const char *child_path, bool cp_if_exists)
727 {
728 	struct stat sb;
729 	char active[BE_MAXPATHLEN];
730 	char buf[BE_MAXPATHLEN];
731 	nvlist_t *props;
732 	const char *s;
733 	zfs_handle_t *zfs;
734 	int err;
735 
736 	/* Require absolute paths */
737 	if (*child_path != '/')
738 		return (set_error(lbh, BE_ERR_BADPATH));
739 
740 	strlcpy(active, be_active_path(lbh), BE_MAXPATHLEN);
741 	strcpy(buf, active);
742 
743 	/* Create non-mountable parent dataset(s) */
744 	s = child_path;
745 	for (char *p; (p = strchr(s+1, '/')) != NULL; s = p) {
746 		size_t len = p - s;
747 		strncat(buf, s, len);
748 
749 		nvlist_alloc(&props, NV_UNIQUE_NAME, KM_SLEEP);
750 		nvlist_add_string(props, "canmount", "off");
751 		nvlist_add_string(props, "mountpoint", "none");
752 		zfs_create(lbh->lzh, buf, ZFS_TYPE_DATASET, props);
753 		nvlist_free(props);
754 	}
755 
756 	/* Path does not exist as a descendent of / yet */
757 	if (strlcat(active, child_path, BE_MAXPATHLEN) >= BE_MAXPATHLEN)
758 		return (set_error(lbh, BE_ERR_PATHLEN));
759 
760 	if (stat(child_path, &sb) != 0) {
761 		/* Verify that error is ENOENT */
762 		if (errno != ENOENT)
763 			return (set_error(lbh, BE_ERR_NOENT));
764 
765 		nvlist_alloc(&props, NV_UNIQUE_NAME, KM_SLEEP);
766 		nvlist_add_string(props, "canmount", "noauto");
767 		nvlist_add_string(props, "mountpoint", child_path);
768 
769 		/* Create */
770 		if ((err =
771 		    zfs_create(lbh->lzh, active, ZFS_TYPE_DATASET, props)) != 0)
772 			/* XXX TODO handle error */
773 			return (set_error(lbh, BE_ERR_UNKNOWN));
774 		nvlist_free(props);
775 
776 		if ((zfs =
777 		    zfs_open(lbh->lzh, active, ZFS_TYPE_DATASET)) == NULL)
778 			return (set_error(lbh, BE_ERR_ZFSOPEN));
779 
780 		/* Set props */
781 		if ((err = zfs_prop_set(zfs, "canmount", "noauto")) != 0)
782 			/* TODO handle error */
783 			return (set_error(lbh, BE_ERR_UNKNOWN));
784 	} else if (cp_if_exists) {
785 		/* Path is already a descendent of / and should be copied */
786 
787 		/* XXX TODO ? */
788 
789 		/*
790 		 * Establish if the existing path is a zfs dataset or just
791 		 * the subdirectory of one
792 		 */
793 		strlcpy(buf, "/tmp/be_snap.XXXXX", sizeof(buf));
794 		if (mktemp(buf) == NULL)
795 			return (set_error(lbh, BE_ERR_UNKNOWN));
796 
797 		if ((err = zfs_snapshot(lbh->lzh, buf, false, NULL)) != 0)
798 			/* XXX TODO correct error */
799 			return (set_error(lbh, BE_ERR_UNKNOWN));
800 
801 		/* Clone */
802 		if ((zfs =
803 		    zfs_open(lbh->lzh, buf, ZFS_TYPE_SNAPSHOT)) == NULL)
804 			return (BE_ERR_ZFSOPEN);
805 
806 		if ((err = zfs_clone(zfs, active, NULL)) != 0)
807 			/* XXX TODO correct error */
808 			return (set_error(lbh, BE_ERR_UNKNOWN));
809 
810 		/* set props */
811 		zfs_close(zfs);
812 	} else
813 		/* TODO: error code for exists, but not cp? */
814 		return (set_error(lbh, BE_ERR_EXISTS));
815 
816 	return (BE_ERR_SUCCESS);
817 }
818 
819 static int
820 be_set_nextboot(libbe_handle_t *lbh, nvlist_t *config, uint64_t pool_guid,
821     const char *zfsdev)
822 {
823 	nvlist_t **child;
824 	uint64_t vdev_guid;
825 	int c, children;
826 
827 	if (nvlist_lookup_nvlist_array(config, ZPOOL_CONFIG_CHILDREN, &child,
828 	    &children) == 0) {
829 		for (c = 0; c < children; ++c)
830 			if (be_set_nextboot(lbh, child[c], pool_guid, zfsdev) != 0)
831 				return (1);
832 		return (0);
833 	}
834 
835 	if (nvlist_lookup_uint64(config, ZPOOL_CONFIG_GUID,
836 	    &vdev_guid) != 0) {
837 		return (1);
838 	}
839 
840 	if (zpool_nextboot(lbh->lzh, pool_guid, vdev_guid, zfsdev) != 0) {
841 		perror("ZFS_IOC_NEXTBOOT failed");
842 		return (1);
843 	}
844 
845 	return (0);
846 }
847 
848 
849 int
850 be_activate(libbe_handle_t *lbh, const char *bootenv, bool temporary)
851 {
852 	char be_path[BE_MAXPATHLEN];
853 	char buf[BE_MAXPATHLEN];
854 	uint64_t pool_guid;
855 	nvlist_t *config, *vdevs;
856 	int err;
857 
858 	be_root_concat(lbh, bootenv, be_path);
859 
860 	/* Note: be_exists fails if mountpoint is not / */
861 	if (!be_exists(lbh, be_path))
862 		return (BE_ERR_NOENT);
863 
864 	if (temporary) {
865 		config = zpool_get_config(lbh->active_phandle, NULL);
866 		if (config == NULL) {
867 			printf("no config\n");
868 			return (1);
869 		}
870 
871 		if (nvlist_lookup_uint64(config, ZPOOL_CONFIG_POOL_GUID,
872 		    &pool_guid) != 0)
873 			return (1);
874 
875 		/* Expected format according to zfsbootcfg(8) man */
876 		strcpy(buf, "zfs:");
877 		strcat(buf, be_path);
878 		strcat(buf, ":");
879 
880 		if (nvlist_lookup_nvlist(config, ZPOOL_CONFIG_VDEV_TREE, &vdevs) != 0)
881 			return (1);
882 
883 		return (be_set_nextboot(lbh, vdevs, pool_guid, buf));
884 	} else {
885 		/* Obtain bootenv zpool */
886 		err = zpool_set_prop(lbh->active_phandle, "bootfs", be_path);
887 
888 		switch (err) {
889 		case 0:
890 			return (BE_ERR_SUCCESS);
891 
892 		default:
893 			/* XXX TODO correct errors */
894 			return (-1);
895 		}
896 	}
897 }
898