aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKyle Evans <kevans@FreeBSD.org>2018-07-24 13:17:40 +0000
committerKyle Evans <kevans@FreeBSD.org>2018-07-24 13:17:40 +0000
commit28f16a0f1985b32a97075a540236e8abebaeadf1 (patch)
tree8d66eb11454d96ff8c0c11e2714e4ffe4f0cb5de
parent2011986f09e0d66b91291908cd4dfc7aff616647 (diff)
downloadsrc-28f16a0f1985b32a97075a540236e8abebaeadf1.tar.gz
src-28f16a0f1985b32a97075a540236e8abebaeadf1.zip
Import libbe(3)/be(1) from socsvn/soc2017/kneitinger/libbe-head
Notes
Notes: svn path=/projects/bectl/; revision=336668
-rw-r--r--lib/libbe/Makefile53
-rw-r--r--lib/libbe/be.c876
-rw-r--r--lib/libbe/be.h113
-rw-r--r--lib/libbe/be_access.c132
-rw-r--r--lib/libbe/be_error.c115
-rw-r--r--lib/libbe/be_impl.h59
-rw-r--r--lib/libbe/be_info.c245
-rw-r--r--lib/libbe/libbe.3192
-rw-r--r--sbin/Makefile1
-rw-r--r--sbin/be/Makefile10
-rw-r--r--sbin/be/be.1220
-rw-r--r--sbin/be/be.c664
-rw-r--r--share/mk/src.libnames.mk4
13 files changed, 2684 insertions, 0 deletions
diff --git a/lib/libbe/Makefile b/lib/libbe/Makefile
new file mode 100644
index 000000000000..cbb40af821c8
--- /dev/null
+++ b/lib/libbe/Makefile
@@ -0,0 +1,53 @@
+# $FreeBSD$
+#
+# Copyright (c) 2010 Hans Petter Selasky. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+
+PACKAGE= lib${LIB}
+LIB= be
+SHLIB_MAJOR= 1
+SHLIB_MINOR= 0
+SRCS= be.c be_access.c be_error.c be_info.c
+INCS= be.h
+MAN= libbe.3
+
+WARNS?= 1
+
+LIBADD+= zfs
+LIBADD+= nvpair
+
+CFLAGS+= -I${SRCTOP}/cddl/contrib/opensolaris/lib/libzfs/common
+CFLAGS+= -I${SRCTOP}/sys/cddl/compat/opensolaris
+CFLAGS+= -I${SRCTOP}/cddl/compat/opensolaris/include
+CFLAGS+= -I${SRCTOP}/cddl/compat/opensolaris/lib/libumem
+CFLAGS+= -I${SRCTOP}/cddl/contrib/opensolaris/lib/libzpool/common
+CFLAGS+= -I${SRCTOP}/sys/cddl/contrib/opensolaris/common/zfs
+CFLAGS+= -I${SRCTOP}/sys/cddl/contrib/opensolaris/uts/common/fs/zfs
+CFLAGS+= -I${SRCTOP}/sys/cddl/contrib/opensolaris/uts/common
+CFLAGS+= -I${SRCTOP}/cddl/contrib/opensolaris/head
+LDFLAGS+= -v
+
+CFLAGS+= -DNEED_SOLARIS_BOOLEAN
+
+.include <bsd.lib.mk>
diff --git a/lib/libbe/be.c b/lib/libbe/be.c
new file mode 100644
index 000000000000..f286cc37b11f
--- /dev/null
+++ b/lib/libbe/be.c
@@ -0,0 +1,876 @@
+/*
+ * be.c
+ *
+ * Copyright (c) 2017 Kyle J. Kneitinger <kyle@kneit.in>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <kenv.h>
+#include <libgen.h>
+#include <libzfs_core.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "be.h"
+#include "be_impl.h"
+
+/*
+ * Initializes the libbe context to operate in the root boot environment
+ * dataset, for example, zroot/ROOT.
+ */
+libbe_handle_t *
+libbe_init(void)
+{
+ char buf[BE_MAXPATHLEN];
+ struct stat sb;
+ dev_t root_dev, boot_dev;
+ libbe_handle_t *lbh;
+ char *pos;
+
+ // TODO: use errno here??
+
+ /* Verify that /boot and / are mounted on the same filesystem */
+ if (stat("/", &sb) != 0) {
+ return (NULL);
+ }
+
+ root_dev = sb.st_dev;
+
+ if (stat("/boot", &sb) != 0) {
+ return (NULL);
+ }
+
+ boot_dev = sb.st_dev;
+
+ if (root_dev != boot_dev) {
+ fprintf(stderr, "/ and /boot not on same device, quitting\n");
+ return (NULL);
+ }
+
+ if ((lbh = calloc(1, sizeof(libbe_handle_t))) == NULL) {
+ return (NULL);
+ }
+
+ if ((lbh->lzh = libzfs_init()) == NULL) {
+ free(lbh);
+ return (NULL);
+ }
+
+ /* Obtain path to active boot environment */
+ if ((kenv(KENV_GET, "zfs_be_active", lbh->active,
+ BE_MAXPATHLEN)) == -1) {
+ libzfs_fini(lbh->lzh);
+ free(lbh);
+ return (NULL);
+ }
+
+ /* Remove leading 'zfs:' if present, otherwise use value as-is */
+ if ((pos = strrchr(lbh->active, ':')) != NULL) {
+ strncpy(lbh->active, pos + sizeof(char), BE_MAXPATHLEN);
+ }
+
+ /* Obtain path to boot environment root */
+ if ((kenv(KENV_GET, "zfs_be_root", lbh->root, BE_MAXPATHLEN)) == -1) {
+ libzfs_fini(lbh->lzh);
+ free(lbh);
+ return (NULL);
+ }
+
+ /* Remove leading 'zfs:' if present, otherwise use value as-is */
+ if ((pos = strrchr(lbh->root, ':')) != NULL) {
+ strncpy(lbh->root, pos + sizeof(char), BE_MAXPATHLEN);
+ }
+
+ return (lbh);
+}
+
+
+/*
+ * Free memory allocated by libbe_init()
+ */
+void
+libbe_close(libbe_handle_t *lbh)
+{
+ libzfs_fini(lbh->lzh);
+ free(lbh);
+}
+
+
+/*
+ * Destroy the boot environment or snapshot specified by the name
+ * parameter. Options are or'd together with the possible values:
+ * BE_DESTROY_FORCE : forces operation on mounted datasets
+ * TODO: Test destroying a non active but mounted be
+ */
+int
+be_destroy(libbe_handle_t *lbh, char *name, int options)
+{
+ zfs_handle_t *fs;
+ char path[BE_MAXPATHLEN];
+ char *p = path;
+ int mounted;
+ int force = options & BE_DESTROY_FORCE;
+
+ int err = BE_ERR_SUCCESS;
+
+ be_root_concat(lbh, name, path);
+ printf("path: %s\n", path);
+
+ if (strchr(name, '@') == NULL) {
+ if (!zfs_dataset_exists(lbh->lzh, path, ZFS_TYPE_FILESYSTEM)) {
+ return (set_error(lbh, BE_ERR_NOENT));
+ }
+
+ if (strcmp(path, lbh->active) == 0) {
+ return (set_error(lbh, BE_ERR_DESTROYACT));
+ }
+
+ fs = zfs_open(lbh->lzh, p, ZFS_TYPE_FILESYSTEM);
+ } else {
+
+ if (!zfs_dataset_exists(lbh->lzh, path, ZFS_TYPE_SNAPSHOT)) {
+ return (set_error(lbh, BE_ERR_NOENT));
+ }
+
+ fs = zfs_open(lbh->lzh, p, ZFS_TYPE_SNAPSHOT);
+ }
+
+ if (fs == NULL)
+ return (set_error(lbh, BE_ERR_ZFSOPEN));
+
+ /* Check if mounted, unmount if force is specified */
+ if (mounted = zfs_is_mounted(fs, NULL)) {
+ if (force) {
+ zfs_unmount(fs, NULL, 0);
+ } else {
+ return (set_error(lbh, BE_ERR_DESTROYMNT));
+ }
+ }
+
+
+ // TODO: convert this to use zfs_iter_children first for deep bes
+ // XXX note: errno 16 (device busy) occurs when chilren are present
+ if ((err = zfs_destroy(fs, false)) != 0) {
+ fprintf(stderr, "delete failed errno: %d\n", errno);
+ }
+
+ return (err);
+}
+
+
+int
+be_snapshot(libbe_handle_t *lbh, char *source, char *snap_name,
+ bool recursive, char *result)
+{
+ char buf[BE_MAXPATHLEN];
+ time_t rawtime;
+ int len, err;
+
+ be_root_concat(lbh, source, buf);
+
+ if (!be_exists(lbh, buf)) {
+ return (BE_ERR_NOENT);
+ }
+
+ if (snap_name != NULL) {
+ strcat(buf, "@");
+ strcat(buf, snap_name);
+ if (result != NULL) {
+ snprintf(result, BE_MAXPATHLEN, "%s@%s", source,
+ snap_name);
+ }
+ } else {
+ time(&rawtime);
+ len = strlen(buf);
+ strftime(buf + len, BE_MAXPATHLEN - len,
+ "@%F-%T", localtime(&rawtime));
+ if (result != NULL) {
+ strcpy(result, strrchr(buf, '/') + 1);
+ }
+ }
+
+ if (err = zfs_snapshot(lbh->lzh, buf, recursive, NULL) != 0) {
+ switch (err) {
+ case EZFS_INVALIDNAME:
+ return (set_error(lbh, BE_ERR_INVALIDNAME));
+
+ default:
+ // TODO: elaborate return codes
+ return (set_error(lbh, BE_ERR_UNKNOWN));
+ }
+ }
+
+ return (BE_ERR_SUCCESS);
+}
+
+
+/*
+ * Create the boot environment specified by the name parameter
+ */
+int
+be_create(libbe_handle_t *lbh, char *name)
+{
+ int err;
+
+ err = be_create_from_existing(lbh, name, (char *)be_active_path(lbh));
+
+ return (set_error(lbh, err));
+}
+
+
+static int
+be_deep_clone_prop(int prop, void *cb)
+{
+ int err;
+ struct libbe_dccb *dccb = cb;
+ zprop_source_t src;
+ char pval[BE_MAXPATHLEN];
+ char source[BE_MAXPATHLEN];
+
+ /* Skip some properties we don't want to touch */
+ switch (prop) {
+ case ZFS_PROP_CANMOUNT:
+ return (ZPROP_CONT);
+ break;
+ }
+
+ /* Don't copy readonly properties */
+ if (zfs_prop_readonly(prop)) {
+ return (ZPROP_CONT);
+ }
+
+ if ((err = zfs_prop_get(dccb->zhp, prop, (char *)&pval,
+ sizeof(pval), &src, (char *)&source, sizeof(source), false))) {
+ /* Just continue if we fail to read a property */
+ return (ZPROP_CONT);
+ }
+ /* Only copy locally defined properties */
+ if (src != ZPROP_SRC_LOCAL) {
+ return (ZPROP_CONT);
+ }
+
+ nvlist_add_string(dccb->props, zfs_prop_to_name(prop), (char *)pval);
+
+ return (ZPROP_CONT);
+}
+
+static int
+be_deep_clone(zfs_handle_t *ds, void *data)
+{
+ int err;
+ char be_path[BE_MAXPATHLEN];
+ char snap_path[BE_MAXPATHLEN];
+ char mp[BE_MAXPATHLEN];
+ const char *dspath;
+ char *dsname;
+ zfs_handle_t *snap_hdl;
+ nvlist_t *props;
+ struct libbe_deep_clone sdc;
+ struct libbe_deep_clone *isdc = (struct libbe_deep_clone *)data;
+ struct libbe_dccb dccb;
+
+ dspath = zfs_get_name(ds);
+ if ((dsname = strrchr(dspath, '/')) == NULL) {
+ return (BE_ERR_UNKNOWN);
+ }
+ dsname++;
+ if (isdc->bename == NULL) {
+ snprintf(be_path, sizeof(be_path), "%s/%s", isdc->be_root, dsname);
+ } else {
+ snprintf(be_path, sizeof(be_path), "%s/%s", isdc->be_root, isdc->bename);
+ }
+ snprintf(snap_path, sizeof(snap_path), "%s@%s", dspath, isdc->snapname);
+
+ if (zfs_dataset_exists(isdc->lbh->lzh, be_path, ZFS_TYPE_DATASET)) {
+ return (set_error(isdc->lbh, BE_ERR_EXISTS));
+ }
+
+ if ((snap_hdl =
+ zfs_open(isdc->lbh->lzh, snap_path, ZFS_TYPE_SNAPSHOT)) == NULL) {
+ return (set_error(isdc->lbh, BE_ERR_ZFSOPEN));
+ }
+
+ nvlist_alloc(&props, NV_UNIQUE_NAME, KM_SLEEP);
+ nvlist_add_string(props, "canmount", "noauto");
+
+ dccb.zhp = ds;
+ dccb.props = props;
+ if (zprop_iter(be_deep_clone_prop, &dccb, B_FALSE, B_FALSE,
+ ZFS_TYPE_FILESYSTEM) == ZPROP_INVAL) {
+ return (-1);
+ }
+
+ if (err = zfs_clone(snap_hdl, be_path, props)) {
+ switch (err) {
+ case EZFS_SUCCESS:
+ err = BE_ERR_SUCCESS;
+ break;
+ default:
+ err = BE_ERR_ZFSCLONE;
+ break;
+ }
+ }
+
+ nvlist_free(props);
+ zfs_close(snap_hdl);
+
+ sdc.lbh = isdc->lbh;
+ sdc.bename = NULL;
+ sdc.snapname = isdc->snapname;
+ sdc.be_root = (char *)&be_path;
+
+ err = zfs_iter_filesystems(ds, be_deep_clone, &sdc);
+
+ return (err);
+}
+
+/*
+ * Create the boot environment from pre-existing snapshot
+ */
+int
+be_create_from_existing_snap(libbe_handle_t *lbh, char *name, char *snap)
+{
+ int err;
+ char be_path[BE_MAXPATHLEN];
+ char snap_path[BE_MAXPATHLEN];
+ char *parentname, *bename, *snapname;
+ zfs_handle_t *parent_hdl;
+ struct libbe_deep_clone sdc;
+
+ if (err = be_validate_name(lbh, name)) {
+ return (set_error(lbh, err));
+ }
+
+ if (err = be_root_concat(lbh, snap, snap_path)) {
+ return (set_error(lbh, err));
+ }
+
+ if (err = be_validate_snap(lbh, snap_path)) {
+ return (set_error(lbh, err));
+ }
+
+ if (err = be_root_concat(lbh, name, be_path)) {
+ return (set_error(lbh, err));
+ }
+
+ if ((bename = strrchr(name, '/')) == NULL) {
+ bename = name;
+ } else {
+ bename++;
+ }
+ if ((parentname = strdup(snap_path)) == NULL) {
+ err = BE_ERR_UNKNOWN;
+ return (set_error(lbh, err));
+ }
+ snapname = strchr(parentname, '@');
+ if (snapname == NULL) {
+ err = BE_ERR_UNKNOWN;
+ return (set_error(lbh, err));
+ }
+ *snapname = '\0';
+ snapname++;
+
+ sdc.lbh = lbh;
+ sdc.bename = bename;
+ sdc.snapname = snapname;
+ sdc.be_root = lbh->root;
+
+ parent_hdl = zfs_open(lbh->lzh, parentname, ZFS_TYPE_DATASET);
+ err = be_deep_clone(parent_hdl, &sdc);
+
+ return (set_error(lbh, err));
+}
+
+
+/*
+ * Create a boot environment from an existing boot environment
+ */
+int
+be_create_from_existing(libbe_handle_t *lbh, char *name, char *old)
+{
+ int err;
+ char buf[BE_MAXPATHLEN];
+
+ if ((err = be_snapshot(lbh, old, NULL, true, (char *)&buf))) {
+ return (set_error(lbh, err));
+ }
+
+ err = be_create_from_existing_snap(lbh, name, (char *)buf);
+
+ return (set_error(lbh, err));
+}
+
+
+/*
+ * Verifies that a snapshot has a valid name, exists, and has a mountpoint of
+ * '/'. Returns BE_ERR_SUCCESS (0), upon success, or the relevant BE_ERR_* upon
+ * failure. Does not set the internal library error state.
+ */
+int
+be_validate_snap(libbe_handle_t *lbh, char *snap_name)
+{
+ zfs_handle_t *zfs_hdl;
+ char buf[BE_MAXPATHLEN];
+ char *delim_pos;
+ char *mountpoint;
+ int err = BE_ERR_SUCCESS;
+
+ if (strlen(snap_name) >= BE_MAXPATHLEN) {
+ return (BE_ERR_PATHLEN);
+ }
+
+ if (!zfs_dataset_exists(lbh->lzh, snap_name,
+ ZFS_TYPE_SNAPSHOT)) {
+ return (BE_ERR_NOENT);
+ }
+
+ strncpy(buf, snap_name, BE_MAXPATHLEN);
+
+ /* Find the base filesystem of the snapshot */
+ if ((delim_pos = strchr(buf, '@')) == NULL) {
+ return (BE_ERR_INVALIDNAME);
+ }
+ *delim_pos = '\0';
+
+ if ((zfs_hdl =
+ zfs_open(lbh->lzh, buf, ZFS_TYPE_DATASET)) == NULL) {
+ return (BE_ERR_NOORIGIN);
+ }
+
+ if (err =
+ zfs_prop_get(zfs_hdl, ZFS_PROP_MOUNTPOINT, buf,
+ BE_MAXPATHLEN,
+ NULL, NULL, 0, 1)) {
+ err = BE_ERR_INVORIGIN;
+ }
+
+ if ((err != 0) && (strncmp(buf, "/", BE_MAXPATHLEN) != 0)) {
+ err = BE_ERR_INVORIGIN;
+ }
+
+ zfs_close(zfs_hdl);
+
+ return (err);
+}
+
+
+/*
+ * Idempotently appends the name argument to the root boot environment path
+ * and copies the resulting string into the result buffer (which is assumed
+ * to be at least BE_MAXPATHLEN characters long. Returns BE_ERR_SUCCESS upon
+ * success, BE_ERR_PATHLEN if the resulting path is longer than BE_MAXPATHLEN,
+ * or BE_ERR_INVALIDNAME if the name is a path that does not begin with
+ * zfs_be_root. Does not set internal library error state.
+ */
+int
+be_root_concat(libbe_handle_t *lbh, char *name, char *result)
+{
+ size_t name_len, root_len;
+
+ name_len = strlen(name);
+ root_len = strlen(lbh->root);
+
+ /* Act idempotently; return be name if it is already a full path */
+ if (strrchr(name, '/') != NULL) {
+ if (strstr(name, lbh->root) != name) {
+ return (BE_ERR_INVALIDNAME);
+ }
+
+ if (name_len >= BE_MAXPATHLEN) {
+ return (BE_ERR_PATHLEN);
+ }
+
+ strncpy(result, name, BE_MAXPATHLEN);
+ return (BE_ERR_SUCCESS);
+ } else if (name_len + root_len + 1 < BE_MAXPATHLEN) {
+ snprintf(result, BE_MAXPATHLEN, "%s/%s", lbh->root,
+ name);
+ return (BE_ERR_SUCCESS);
+ }
+
+ return (BE_ERR_PATHLEN);
+}
+
+
+/*
+ * Verifies the validity of a boot environment name (A-Za-z0-9-_.). Returns
+ * BE_ERR_SUCCESS (0) if name is valid, otherwise returns BE_ERR_INVALIDNAME.
+ * Does not set internal library error state.
+ */
+int
+be_validate_name(libbe_handle_t *lbh, char *name)
+{
+ for (int i = 0; *name; i++) {
+ char c = *(name++);
+ if (isalnum(c) || (c == '-') || (c == '_') ||
+ (c == '.')) {
+ continue;
+ }
+ return (BE_ERR_INVALIDNAME);
+ }
+
+ return (BE_ERR_SUCCESS);
+}
+
+
+/*
+ * usage
+ */
+int
+be_rename(libbe_handle_t *lbh, char *old, char *new)
+{
+ char full_old[BE_MAXPATHLEN];
+ char full_new[BE_MAXPATHLEN];
+ zfs_handle_t *zfs_hdl;
+ int err;
+
+
+ if (err = be_root_concat(lbh, old, full_old)) {
+ return (set_error(lbh, err));
+ }
+ if (err = be_root_concat(lbh, new, full_new)) {
+ return (set_error(lbh, err));
+ }
+
+ if (be_validate_name(lbh, new)) {
+ return (BE_ERR_UNKNOWN);
+ // TODO set and return correct error
+ }
+
+ // check if old is active be
+ if (strcmp(full_new, be_active_path(lbh)) == 0) {
+ return (BE_ERR_UNKNOWN);
+ // TODO set and return correct error
+ }
+
+ if (!zfs_dataset_exists(lbh->lzh, full_old, ZFS_TYPE_DATASET)) {
+ return (BE_ERR_UNKNOWN);
+ // TODO set and return correct error
+ }
+
+ if (zfs_dataset_exists(lbh->lzh, full_new, ZFS_TYPE_DATASET)) {
+ return (BE_ERR_UNKNOWN);
+ // TODO set and return correct error
+ }
+
+ // TODO: what about mounted bes?
+ // - if mounted error out unless a force flag is set?
+
+
+ if ((zfs_hdl = zfs_open(lbh->lzh, full_old,
+ ZFS_TYPE_FILESYSTEM)) == NULL) {
+ return (BE_ERR_UNKNOWN);
+ // TODO set and return correct error
+ }
+
+
+ // recurse, nounmount, forceunmount
+ struct renameflags flags = { 0, 0, 0 };
+
+ // TODO: error log on this call
+ err = zfs_rename(zfs_hdl, NULL, full_new, flags);
+
+ zfs_close(zfs_hdl);
+
+ return (set_error(lbh, err));
+}
+
+
+int
+be_export(libbe_handle_t *lbh, char *bootenv, int fd)
+{
+ char snap_name[BE_MAXPATHLEN];
+ char buf[BE_MAXPATHLEN];
+ zfs_handle_t *zfs;
+ int err;
+
+ if (err = be_snapshot(lbh, bootenv, NULL, true, snap_name)) {
+ // TODO error handle
+ return (-1);
+ }
+
+ be_root_concat(lbh, snap_name, buf);
+
+ if ((zfs = zfs_open(lbh->lzh, buf, ZFS_TYPE_DATASET)) == NULL) {
+ return (BE_ERR_ZFSOPEN);
+ }
+
+ err = zfs_send_one(zfs, NULL, fd, 0);
+ return (err);
+}
+
+
+int
+be_import(libbe_handle_t *lbh, char *bootenv, int fd)
+{
+ char buf[BE_MAXPATHLEN];
+ time_t rawtime;
+ nvlist_t *props;
+ zfs_handle_t *zfs;
+ int err, len;
+
+ // TODO: this is a very likely name for someone to already have used
+ if (err = be_root_concat(lbh, "be_import_temp", buf)) {
+ // TODO error handle
+ return (-1);
+ }
+
+ time(&rawtime);
+ len = strlen(buf);
+ strftime(buf + len, BE_MAXPATHLEN - len,
+ "@%F-%T", localtime(&rawtime));
+
+
+ // lzc_receive(SNAPNAME, PROPS, ORIGIN, FORCE, fd)) {
+ if (err = lzc_receive(buf, NULL, NULL, false, fd)) {
+ /* TODO: go through libzfs_core's recv_impl and find returned
+ * errors and set appropriate BE_ERR
+ * edit: errors are not in libzfs_core, my assumption is
+ * that they use libzfs errors
+ * note: 17 is err for dataset already existing */
+ return (err);
+ }
+
+ if ((zfs = zfs_open(lbh->lzh, buf, ZFS_TYPE_SNAPSHOT)) == NULL) {
+ // TODO correct error
+ return (-1);
+ }
+
+ nvlist_alloc(&props, NV_UNIQUE_NAME, KM_SLEEP);
+ nvlist_add_string(props, "canmount", "noauto");
+ nvlist_add_string(props, "mountpoint", "/");
+
+ be_root_concat(lbh, bootenv, buf);
+
+ err = zfs_clone(zfs, buf, props);
+ zfs_close(zfs);
+
+ nvlist_free(props);
+
+ // TODO: recursively delete be_import_temp dataset
+
+ return (err);
+}
+
+
+int
+be_add_child(libbe_handle_t *lbh, char *child_path, bool cp_if_exists)
+{
+ char active[BE_MAXPATHLEN];
+ char buf[BE_MAXPATHLEN];
+ nvlist_t *props;
+ zfs_handle_t *zfs;
+ struct stat sb;
+ int err;
+
+ /* Require absolute paths */
+ if (*child_path != '/') {
+ /* TODO: create appropriate error */
+ return (-1);
+ }
+
+ strncpy(active, be_active_path(lbh), BE_MAXPATHLEN);
+ strcpy(buf, active);
+
+ /* Create non-mountable parent dataset(s) */
+ char *s = child_path;
+ for (char *p; (p = strchr(s+1, '/')) != NULL; s = p) {
+ size_t len = p - s;
+ strncat(buf, s, len);
+
+ nvlist_alloc(&props, NV_UNIQUE_NAME, KM_SLEEP);
+ nvlist_add_string(props, "canmount", "off");
+ nvlist_add_string(props, "mountpoint", "none");
+ zfs_create(lbh->lzh, buf, ZFS_TYPE_DATASET, props);
+ nvlist_free(props);
+ }
+
+
+ /* Path does not exist as a descendent of / yet */
+ int pos = strlen(active);
+
+ /* TODO: Verify that resulting str is less than BE_MAXPATHLEN */
+ strncpy(&active[pos], child_path, BE_MAXPATHLEN-pos);
+
+
+ if (stat(child_path, &sb) != 0) {
+ /* Verify that error is ENOENT */
+ if (errno != 2) {
+ /* TODO: create appropriate error */
+ return (-1);
+ }
+
+ nvlist_alloc(&props, NV_UNIQUE_NAME, KM_SLEEP);
+ nvlist_add_string(props, "canmount", "noauto");
+ nvlist_add_string(props, "mountpoint", child_path);
+
+ // create
+ if (err =
+ zfs_create(lbh->lzh, active, ZFS_TYPE_DATASET, props)) {
+ /* TODO handle error */
+ return (-1);
+ }
+ nvlist_free(props);
+
+ if ((zfs =
+ zfs_open(lbh->lzh, active, ZFS_TYPE_DATASET)) == NULL) {
+ /* TODO handle error */
+ return (-1);
+ }
+
+ // set props
+ if (err = zfs_prop_set(zfs, "canmount", "noauto")) {
+ /* TODO handle error */
+ return (-1);
+ }
+ } else if (cp_if_exists) {
+ /* Path is already a descendent of / and should be copied */
+
+
+
+ // TODO
+
+ /*
+ * Establish if the existing path is a zfs dataset or just
+ * the subdirectory of one
+ */
+
+
+ // TODO: use mktemp
+ long int snap_name = random();
+
+ snprintf(buf, BE_MAXPATHLEN, "%s@%ld", child_path, snap_name);
+
+ if (err = zfs_snapshot(lbh->lzh, buf, false, NULL)) {
+ // TODO correct error
+ return (-1);
+ }
+
+ // clone
+ if ((zfs =
+ zfs_open(lbh->lzh, buf, ZFS_TYPE_SNAPSHOT)) == NULL) {
+ // TODO correct error
+ return (-1);
+ }
+
+ if (err = zfs_clone(zfs, active, NULL)) {
+ // TODO correct error
+ return (-1);
+ }
+
+
+ // set props
+ } else {
+ /* TODO: error code for exists, but not cp? */
+ return (-1);
+ }
+
+
+ return (BE_ERR_SUCCESS);
+}
+
+
+int
+be_activate(libbe_handle_t *lbh, char *bootenv, bool temporary)
+{
+ char be_path[BE_MAXPATHLEN];
+ char buf[BE_MAXPATHLEN];
+ zpool_handle_t *zph;
+ uint64_t pool_guid;
+ uint64_t vdev_guid;
+ int zfs_fd;
+ int len;
+ int err;
+
+ be_root_concat(lbh, bootenv, be_path);
+
+
+ /* Note: be_exists fails if mountpoint is not / */
+ if (!be_exists(lbh, be_path)) {
+ return (BE_ERR_NOENT);
+ }
+
+ if (temporary) {
+ // TODO: give proper attribution to author(s) of zfsbootcfg
+ // for this snippet
+
+ if (kenv(KENV_GET, "vfs.zfs.boot.primary_pool", buf,
+ sizeof(buf)) <= 0) {
+ return (1);
+ }
+ pool_guid = strtoumax(buf, NULL, 10);
+ if (pool_guid == 0) {
+ return (1);
+ }
+
+ if (kenv(KENV_GET, "vfs.zfs.boot.primary_vdev", buf,
+ sizeof(buf)) <= 0) {
+ return (1);
+ }
+ vdev_guid = strtoumax(buf, NULL, 10);
+ if (vdev_guid == 0) {
+ return (1);
+ }
+
+ /* Expected format according to zfsbootcfg(8) man */
+ strcpy(buf, "zfs:");
+ strcat(buf, be_path);
+ strcat(buf, ":");
+
+ if (zpool_nextboot(lbh->lzh, pool_guid, vdev_guid, buf) != 0) {
+ perror("ZFS_IOC_NEXTBOOT failed");
+ return (1);
+ }
+
+ return (BE_ERR_SUCCESS);
+ } else {
+ /* Obtain bootenv zpool */
+ strncpy(buf, be_path, BE_MAXPATHLEN);
+ *(strchr(buf, '/')) = '\0';
+
+ if ((zph = zpool_open(lbh->lzh, buf)) == NULL) {
+ // TODO: create error for this
+ return (-1);
+ }
+ printf("asdf\n");
+
+ err = zpool_set_prop(zph, "bootfs", be_path);
+ zpool_close(zph);
+
+ switch (err) {
+ case 0:
+ return (BE_ERR_SUCCESS);
+
+ default:
+ // TODO correct errors
+ return (-1);
+ }
+ }
+}
diff --git a/lib/libbe/be.h b/lib/libbe/be.h
new file mode 100644
index 000000000000..8a352ad9a642
--- /dev/null
+++ b/lib/libbe/be.h
@@ -0,0 +1,113 @@
+/*
+ * be.h
+ *
+ * Copyright (c) 2017 Kyle J. Kneitinger <kyle@kneit.in>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _LIBBE_H
+#define _LIBBE_H
+
+#include <stdbool.h>
+
+#define BE_MAXPATHLEN 512
+
+typedef struct libbe_handle libbe_handle_t;
+
+typedef enum be_error {
+ BE_ERR_SUCCESS = 0, /* No error */
+ BE_ERR_INVALIDNAME, /* invalid boot env name */
+ BE_ERR_EXISTS, /* boot env name already taken */
+ BE_ERR_NOENT, /* boot env doesn't exist */
+ BE_ERR_PERMS, /* insufficient permissions */
+ BE_ERR_DESTROYACT, /* cannot destroy active boot env */
+ BE_ERR_DESTROYMNT, /* destroying a mounted be requires force */
+ BE_ERR_PATHLEN, /* provided name exceeds maximum length limit */
+ BE_ERR_INVORIGIN, /* snapshot origin's mountpoint is not '/' */
+ BE_ERR_NOORIGIN, /* could not open snapshot's origin */
+ BE_ERR_MOUNTED, /* boot environment is already mounted */
+ BE_ERR_NOMOUNT, /* boot environment is not mounted */
+ BE_ERR_ZFSOPEN, /* calling zfs_open() failed */
+ BE_ERR_ZFSCLONE, /* error when calling zfs_clone to create be */
+ BE_ERR_UNKNOWN, /* unknown error */
+} be_error_t;
+
+
+/* Library handling functions: be.c */
+libbe_handle_t *libbe_init(void);
+void libbe_close(libbe_handle_t *);
+
+/* Bootenv information functions: be_info.c */
+const char *be_active_name(libbe_handle_t *);
+const char *be_active_path(libbe_handle_t *);
+const char *be_root_path(libbe_handle_t *);
+
+/* nvlist_t *be_get_bootenv_props(libbe_handle_t *); */
+
+int be_activate(libbe_handle_t *, char *, bool);
+
+/* Bootenv creation functions */
+int be_create(libbe_handle_t *, char *);
+int be_create_from_existing(libbe_handle_t *, char *, char *);
+int be_create_from_existing_snap(libbe_handle_t *, char *, char *);
+int be_snapshot(libbe_handle_t *, char *, char *, bool, char *);
+
+/* Bootenv manipulation functions */
+int be_rename(libbe_handle_t *, char *, char *);
+
+/* Bootenv removal functions */
+
+typedef enum {
+ BE_DESTROY_FORCE = 1 << 0,
+} be_destroy_opt_t;
+
+int be_destroy(libbe_handle_t *, char *, int);
+
+/* Bootenv mounting functions: be_access.c */
+
+typedef enum {
+ BE_MNT_FORCE = 1 << 0,
+ BE_MNT_DEEP = 1 << 1,
+} be_mount_opt_t;
+
+int be_mount(libbe_handle_t *, char *, char *, int, char *);
+int be_unmount(libbe_handle_t *, char *, int);
+
+/* Error related functions: be_error.c */
+int libbe_errno(libbe_handle_t *);
+const char *libbe_error_description(libbe_handle_t *);
+void libbe_print_on_error(libbe_handle_t *, bool);
+
+/* Utility Functions */
+int be_root_concat(libbe_handle_t *, char *, char *);
+int be_validate_name(libbe_handle_t *, char *);
+int be_validate_snap(libbe_handle_t *, char *);
+bool be_exists(libbe_handle_t *, char *);
+
+int be_export(libbe_handle_t *, char *, int fd);
+int be_import(libbe_handle_t *, char *, int fd);
+
+int be_add_child(libbe_handle_t *, char *, bool);
+
+#endif /* _LIBBE_H */
diff --git a/lib/libbe/be_access.c b/lib/libbe/be_access.c
new file mode 100644
index 000000000000..3fa5b04c76f5
--- /dev/null
+++ b/lib/libbe/be_access.c
@@ -0,0 +1,132 @@
+/*
+ * be_access.c
+ *
+ * Copyright (c) 2017 Kyle J. Kneitinger <kyle@kneit.in>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "be.h"
+#include "be_impl.h"
+
+/*
+ * usage
+ */
+int
+be_mount(libbe_handle_t *lbh, char *bootenv, char *mountpoint, int flags,
+ char *result_loc)
+{
+ char be[BE_MAXPATHLEN];
+ char mnt_temp[BE_MAXPATHLEN];
+ zfs_handle_t *zfs_hdl;
+ char *path;
+ int mntflags;
+ int err;
+
+ if (err = be_root_concat(lbh, bootenv, be)) {
+ return (set_error(lbh, err));
+ }
+
+ if (!be_exists(lbh, bootenv)) {
+ return (set_error(lbh, BE_ERR_NOENT));
+ }
+
+ if (is_mounted(lbh->lzh, be, &path)) {
+ return (set_error(lbh, BE_ERR_MOUNTED));
+ }
+
+
+ mntflags = (flags & BE_MNT_FORCE) ? MNT_FORCE : 0;
+
+ /* Create mountpoint if it is not specified */
+ if (mountpoint == NULL) {
+ strcpy(mnt_temp, "/tmp/be_mount.XXXX");
+ if (mkdtemp(mnt_temp) == NULL) {
+ // TODO: create error for this
+ return (set_error(lbh, BE_ERR_UNKNOWN));
+ }
+ }
+
+ char opt = '\0';
+ if (err = zmount(be, (mountpoint == NULL) ? mnt_temp : mountpoint,
+ mntflags, MNTTYPE_ZFS, NULL, 0, &opt, 1)) {
+ // TODO: zmount returns the nmount error, look into what kind of
+ // errors we can report from that
+ return (set_error(lbh, BE_ERR_UNKNOWN));
+ }
+
+ if (result_loc != NULL) {
+ strcpy(result_loc, mountpoint == NULL ? mnt_temp : mountpoint);
+ }
+
+ return (BE_ERR_SUCCESS);
+}
+
+
+/*
+ * usage
+ */
+int
+be_unmount(libbe_handle_t *lbh, char *bootenv, int flags)
+{
+ int err, mntflags;
+ char be[BE_MAXPATHLEN];
+ struct statfs *mntbuf;
+ int mntsize;
+ char *mntpath;
+
+ if (err = be_root_concat(lbh, bootenv, be)) {
+ return (set_error(lbh, err));
+ }
+
+ if ((mntsize = getmntinfo(&mntbuf, MNT_NOWAIT)) == 0) {
+ // TODO correct error
+ return (set_error(lbh, BE_ERR_NOMOUNT));
+ }
+
+ mntpath = NULL;
+ for (int i = 0; i < mntsize; ++i) {
+ /* 0x000000de is the type number of zfs */
+ if (mntbuf[i].f_type != 0x000000de) {
+ continue;
+ }
+
+ if (strcmp(mntbuf[i].f_mntfromname, be) == 0) {
+ mntpath = mntbuf[i].f_mntonname;
+ break;
+ }
+ }
+
+ if (mntpath == NULL) {
+ return (set_error(lbh, BE_ERR_NOMOUNT));
+ }
+
+ mntflags = (flags & BE_MNT_FORCE) ? MNT_FORCE : 0;
+
+ if (err = unmount(mntpath, mntflags)) {
+ // TODO correct error
+ return (set_error(lbh, BE_ERR_NOMOUNT));
+ }
+
+ return (set_error(lbh, BE_ERR_SUCCESS));
+}
diff --git a/lib/libbe/be_error.c b/lib/libbe/be_error.c
new file mode 100644
index 000000000000..af5f339dc485
--- /dev/null
+++ b/lib/libbe/be_error.c
@@ -0,0 +1,115 @@
+/*
+ * be_error.c
+ *
+ * Copyright (c) 2017 Kyle J. Kneitinger <kyle@kneit.in>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "be.h"
+#include "be_impl.h"
+
+/*
+ * Usage
+ */
+int
+libbe_errno(libbe_handle_t *lbh)
+{
+ return (lbh->error);
+}
+
+
+const char *
+libbe_error_description(libbe_handle_t *lbh)
+{
+ switch (lbh->error) {
+ case BE_ERR_INVALIDNAME:
+ return ("invalid boot environment name");
+
+ case BE_ERR_EXISTS:
+ return ("boot environment name already taken");
+
+ case BE_ERR_NOENT:
+ return ("specified boot environment does not exist");
+
+ case BE_ERR_PERMS:
+ return ("insufficient permissions");
+
+ case BE_ERR_DESTROYACT:
+ return ("cannot destroy active boot environment");
+
+ case BE_ERR_DESTROYMNT:
+ return ("cannot destroy mounted boot env unless forced");
+
+ case BE_ERR_PATHLEN:
+ return ("provided path name exceeds maximum length limit");
+
+ case BE_ERR_INVORIGIN:
+ return ("snapshot origin's mountpoint is not \"/\"");
+
+ case BE_ERR_NOORIGIN:
+ return ("could not open snapshot's origin");
+
+ case BE_ERR_MOUNTED:
+ return ("boot environment is already mounted");
+
+ case BE_ERR_NOMOUNT:
+ return ("boot environment is not mounted");
+
+ case BE_ERR_ZFSOPEN:
+ return ("calling zfs_open() failed");
+
+ case BE_ERR_ZFSCLONE:
+ return ("error when calling zfs_clone() to create boot env");
+
+ case BE_ERR_UNKNOWN:
+ return ("unknown error");
+
+ default:
+ assert(lbh->error == BE_ERR_SUCCESS);
+ return ("no error");
+ }
+}
+
+
+void
+libbe_print_on_error(libbe_handle_t *lbh, bool val)
+{
+ lbh->print_on_err = val;
+ libzfs_print_on_error(lbh->lzh, val);
+}
+
+
+int
+set_error(libbe_handle_t *lbh, be_error_t err)
+{
+ // TODO: should the old error be overwritten or no?
+
+ lbh->error = err;
+
+ if (lbh->print_on_err && (err != BE_ERR_SUCCESS)) {
+ fprintf(stderr, "%s\n", libbe_error_description(lbh));
+ }
+
+ return (err);
+}
diff --git a/lib/libbe/be_impl.h b/lib/libbe/be_impl.h
new file mode 100644
index 000000000000..8766e6d09918
--- /dev/null
+++ b/lib/libbe/be_impl.h
@@ -0,0 +1,59 @@
+/*
+ * be_impl.h
+ *
+ * Copyright (c) 2017 Kyle J. Kneitinger <kyle@kneit.in>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _LIBBE_IMPL_H
+#define _LIBBE_IMPL_H
+
+#include <libzfs.h>
+
+#include "be.h"
+
+
+struct libbe_handle {
+ libzfs_handle_t *lzh;
+ char root[BE_MAXPATHLEN];
+ char active[BE_MAXPATHLEN];
+ be_error_t error;
+ bool print_on_err;
+};
+
+struct libbe_deep_clone {
+ libbe_handle_t *lbh;
+ char *bename;
+ char *snapname;
+ char *be_root;
+};
+
+struct libbe_dccb {
+ zfs_handle_t *zhp;
+ nvlist_t *props;
+};
+
+int set_error(libbe_handle_t *, be_error_t);
+
+#endif /* _LIBBE_IMPL_H */
diff --git a/lib/libbe/be_info.c b/lib/libbe/be_info.c
new file mode 100644
index 000000000000..747155916a95
--- /dev/null
+++ b/lib/libbe/be_info.c
@@ -0,0 +1,245 @@
+/*
+ * be_info.c
+ *
+ * Copyright (c) 2017 Kyle J. Kneitinger <kyle@kneit.in>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "be.h"
+#include "be_impl.h"
+
+typedef struct prop_data {
+ nvlist_t *list;
+ libbe_handle_t *lbh;
+} prop_data_t;
+
+static int prop_list_builder_cb(zfs_handle_t *, void *);
+static int prop_list_builder(prop_data_t *);
+
+/*
+ * Returns the name of the active boot environment
+ */
+const char *
+be_active_name(libbe_handle_t *lbh)
+{
+ return (strrchr(lbh->active, '/') + sizeof(char));
+}
+
+
+/*
+ * Returns full path of the active boot environment
+ */
+const char *
+be_active_path(libbe_handle_t *lbh)
+{
+ return (lbh->active);
+}
+
+
+/*
+ * Returns the path of the boot environment root dataset
+ */
+const char *
+be_root_path(libbe_handle_t *lbh)
+{
+ return (lbh->root);
+}
+
+
+/*
+ * Returns an nvlist of the bootenv's properties
+ * TODO: the nvlist should be passed as a param and ints should return status
+ */
+nvlist_t *
+be_get_bootenv_props(libbe_handle_t *lbh)
+{
+ prop_data_t data;
+
+ data.lbh = lbh;
+ prop_list_builder(&data);
+
+ return (data.list);
+}
+
+
+/*
+ * Internal callback function used by zfs_iter_filesystems. For each dataset in
+ * the bootenv root, populate an nvlist_t of its relevant properties.
+ * TODO: should any other properties be included?
+ */
+static int
+prop_list_builder_cb(zfs_handle_t *zfs_hdl, void *data_p)
+{
+ /*
+ * TODO:
+ * some system for defining constants for the nvlist keys
+ * error checking
+ */
+
+ char buf[512];
+ prop_data_t *data;
+ boolean_t mounted, active, nextboot;
+
+
+ data = (prop_data_t *)data_p;
+
+
+ nvlist_t *props;
+
+ libbe_handle_t *lbh = data->lbh;
+
+
+ nvlist_alloc(&props, NV_UNIQUE_NAME, KM_SLEEP);
+
+ const char *dataset = zfs_get_name(zfs_hdl);
+ nvlist_add_string(props, "dataset", dataset);
+
+ const char *name = strrchr(dataset, '/') + 1;
+ nvlist_add_string(props, "name", name);
+
+
+ mounted = zfs_prop_get_int(zfs_hdl, ZFS_PROP_MOUNTED);
+ nvlist_add_boolean_value(props, "mounted", mounted);
+
+ // TODO: NOT CORRECT! Must use is_mounted
+ if (mounted) {
+ zfs_prop_get(zfs_hdl, ZFS_PROP_MOUNTPOINT, buf, 512,
+ NULL, NULL, 0, 1);
+ nvlist_add_string(props, "mountpoint", buf);
+ }
+
+ if (zfs_prop_get(zfs_hdl, ZFS_PROP_ORIGIN, buf, 512,
+ NULL, NULL, 0, 1)) {
+ nvlist_add_string(props, "origin", buf);
+ }
+
+ if (zfs_prop_get(zfs_hdl, ZFS_PROP_CREATION, buf, 512,
+ NULL, NULL, 0, 1)) {
+ nvlist_add_string(props, "creation", buf);
+ }
+
+ nvlist_add_boolean_value(props, "active",
+ (strcmp(be_active_path(lbh), dataset) == 0));
+
+ if (zfs_prop_get(zfs_hdl, ZFS_PROP_USED, buf, 512,
+ NULL, NULL, 0, 1)) {
+ nvlist_add_string(props, "used", buf);
+ }
+
+ if (zfs_prop_get(zfs_hdl, ZFS_PROP_USEDDS, buf, 512,
+ NULL, NULL, 0, 1)) {
+ nvlist_add_string(props, "usedds", buf);
+ }
+
+ if (zfs_prop_get(zfs_hdl, ZFS_PROP_USEDSNAP, buf, 512,
+ NULL, NULL, 0, 1)) {
+ nvlist_add_string(props, "usedsnap", buf);
+ }
+
+ if (zfs_prop_get(zfs_hdl, ZFS_PROP_USEDREFRESERV, buf, 512,
+ NULL, NULL, 0, 1)) {
+ nvlist_add_string(props, "usedrefreserv", buf);
+ }
+
+ if (zfs_prop_get(zfs_hdl, ZFS_PROP_REFERENCED, buf, 512,
+ NULL, NULL, 0, 1)) {
+ nvlist_add_string(props, "referenced", buf);
+ }
+
+ /* TODO figure out how to read nextboot (set in libzfs_pool.c) */
+
+ nvlist_add_nvlist(data->list, name, props);
+
+ return (0);
+}
+
+
+/*
+ * Updates the properties of each bootenv in the libbe handle
+ * TODO: rename to be_proplist_update
+ * TODO: ensure that this is always consistent (run after adds, deletes,
+ * renames,etc
+ */
+static int
+prop_list_builder(prop_data_t *data)
+{
+ zfs_handle_t *root_hdl;
+
+ if (nvlist_alloc(&(data->list), NV_UNIQUE_NAME, KM_SLEEP) != 0) {
+ /* TODO: actually handle error */
+ return (1);
+ }
+
+
+ if ((root_hdl =
+ zfs_open(data->lbh->lzh, data->lbh->root,
+ ZFS_TYPE_FILESYSTEM)) == NULL) {
+ return (BE_ERR_ZFSOPEN);
+ }
+
+ // TODO: some error checking here
+ zfs_iter_filesystems(root_hdl, prop_list_builder_cb, data);
+
+ zfs_close(root_hdl);
+
+ return (0);
+}
+
+
+/*
+ * frees property list and its children
+ */
+void
+be_prop_list_free(nvlist_t *be_list)
+{
+ nvlist_t *prop_list;
+
+ nvpair_t *be_pair = nvlist_next_nvpair(be_list, NULL);
+
+ if (nvpair_value_nvlist(be_pair, &prop_list) == 0) {
+ nvlist_free(prop_list);
+ }
+
+ while ((be_pair = nvlist_next_nvpair(be_list, be_pair)) != NULL) {
+ if (nvpair_value_nvlist(be_pair, &prop_list) == 0) {
+ nvlist_free(prop_list);
+ }
+ }
+}
+
+
+/*
+ * Usage
+ */
+bool
+be_exists(libbe_handle_t *lbh, char *be)
+{
+ char buf[BE_MAXPATHLEN];
+
+ be_root_concat(lbh, be, buf);
+
+ // TODO: check mountpoint prop and see if its /, AND that result with below
+ // expression
+ return (zfs_dataset_exists(lbh->lzh, buf, ZFS_TYPE_DATASET));
+}
diff --git a/lib/libbe/libbe.3 b/lib/libbe/libbe.3
new file mode 100644
index 000000000000..191840b60338
--- /dev/null
+++ b/lib/libbe/libbe.3
@@ -0,0 +1,192 @@
+.\"
+.\" Copyright (c) 2017 Kyle Kneitinger
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" This manual page is based on the mp(3X) manual page from Sun Release
+.\" 4.1, dated 7 September 1989. It's an old, crufty, and relatively ugly
+.\" manual page, but it does document what appears to be the "traditional"
+.\" libmp interface.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd August 28, 2017
+.Dt LIBBE 3
+.Os
+.Sh NAME
+.Nm libbe
+.Nd library for creating, destroying and modifying ZFS boot environments.
+.Sh SYNOPSIS
+.In be.h
+.Pp
+Function prototypes are given in the main body of the text.
+.Pp
+Applications using this interface must be linked with
+.Fl l Ns Ar be
+.Sh DESCRIPTION
+.Pp
+.Nm
+interfaces with libzfs to provide a set of functions for various operations
+regarding ZFS boot environments including "deep" boot environments in which
+a boot environments has child datasets.
+.Pp
+A context structure is passed to each function, allowing for a small amount
+of state to be retained, such as errors from previous operations.
+.\" TODO: describe break on err functionality
+.Pp
+.Ft "libbe_handle_t *" Ns
+.Fn libbe_init "void" ;
+.Pp
+.Ft void
+.Fn libbe_close "libbe_handle_t *" ;
+.Pp
+.Ft "const char *" Ns
+.Fn be_active_name "libbe_handle_t *" ;
+.Pp
+.Ft "const char *" Ns
+.Fn be_active_path "libbe_handle_t *" ;
+.Pp
+.Ft "const char *" Ns
+.Fn be_root_path "libbe_handle_t *" ;
+.Pp
+.Ft "nvlist_t *" Ns
+.Fn libbe_handle_t "libbe_handle_t " ;
+.Pp
+.Ft int
+.Fn be_create "libbe_handle_t *, char *" ;
+.Pp
+.Ft int
+.Fn be_create_from_existing "libbe_handle_t *, char *, char *" ;
+.Pp
+.Ft int
+.Fn be_rename "libbe_handle_t *, char *, char *" ;
+.Pp
+.\" TODO: Write up of destroy options
+.\" typedef enum {
+.\" BE_DESTROY_FORCE = 1 << 0,
+.\" } be_destroy_opt_t;
+.Ft int
+.Fn be_destroy "libbe_handle_t *, char *, int" ;
+.Pp
+.\" TODO: Write up of mount options
+.\" typedef enum {
+.\" BE_MNT_FORCE = 1 << 0,
+.\" BE_MNT_DEEP = 1 << 1,
+.\" } be_mount_opt_t;
+.Ft int
+.Fn be_mount "libbe_handle_t *, char *, char *, int" ;
+.Pp
+.Ft int
+.Fn be_unmount "libbe_handle_t *, char *, int" ;
+.Pp
+.Ft int
+.Fn libbe_errno "libbe_handle_t *" ;
+.Pp
+.Ft "const char *" Ns
+.Fn libbe_error_description "libbe_handle_t *" ;
+.Pp
+.Ft void
+.Fn libbe_print_on_error "libbe_handle_t *, bool" ;
+.Pp
+.Ft int
+.Fn be_root_concat "libbe_handle_t *, char *, char *" ;
+.Pp
+.Ft int
+.Fn be_validate_name "libbe_handle_t *, char *" ;
+.Pp
+.Ft int
+.Fn be_validate_snap "libbe_handle_t *, char *" ;
+.Pp
+.Ft bool
+.Fn be_exists "libbe_handle_t *, char *" ;
+.Pp
+.Ft int
+.Fn be_export "libbe_handle_t *, char *, int fd" ;
+.Pp
+.Ft int
+.Fn be_import "libbe_handle_t *, char *, int fd" ;
+.Pp
+.Ft int
+.Fn be_add_child "libbe_handle_t *, char *, bool" ;
+.Pp
+.\" .Ft void
+.\" .Fn mp_mfree "MINT *mp" ;
+.\" .Bd -ragged -offset indent
+.\" .Fn mp_itom
+.\" returns an
+.\" .Vt MINT
+.\" with the value of
+.\" .Fa n .
+.\" .Fn mp_xtom
+.\" returns an
+.\" .Vt MINT
+.\" with the value of
+.\" .Fa s ,
+.\" which is treated to be in hexadecimal.
+.\" The return values from
+.\" .Fn mp_itom
+.\" and
+.\" .Fn mp_xtom
+.\" must be released with
+.\" .Fn mp_mfree
+.\" when they are no longer needed.
+.\" .Fn mp_mtox
+.\" returns a null-terminated hexadecimal string having the value of
+.\" .Fa mp ;
+.\" its return value must be released with
+.\" .Fn free
+.\" .Pq Xr free 3
+.\" when it is no longer needed.
+.\" .Ed
+.\" .Pp
+.Sh DIAGNOSTICS
+Upon error, one of the following values will be returned.
+.\" TODO: make each entry on its own line.
+.Bd -ragged -offset indent
+BE_ERR_SUCCESS,
+BE_ERR_INVALIDNAME,
+BE_ERR_EXISTS,
+BE_ERR_NOENT,
+BE_ERR_PERMS,
+BE_ERR_DESTROYACT,
+BE_ERR_DESTROYMNT,
+BE_ERR_PATHLEN,
+BE_ERR_INVORIGIN,
+BE_ERR_NOORIGIN,
+BE_ERR_MOUNTED,
+BE_ERR_NOMOUNT,
+BE_ERR_ZFSOPEN,
+BE_ERR_ZFSCLONE,
+BE_ERR_UNKNOWN
+.Ed
+.Sh SEE ALSO
+.Xr be 1 ,
+.Sh HISTORY
+.Nm
+and it's corresponding command,
+.Xr be 3 ,
+were written as a 2017 Google Summer of Code project with Allan Jude serving
+as a mentor.
+.\" TODO: update when implementation complete.
+.\" .Sh BUGS
+
diff --git a/sbin/Makefile b/sbin/Makefile
index 1effb4f3cf15..13c1c0745bcc 100644
--- a/sbin/Makefile
+++ b/sbin/Makefile
@@ -86,6 +86,7 @@ SUBDIR.${MK_PF}+= pfctl
SUBDIR.${MK_PF}+= pflogd
SUBDIR.${MK_QUOTAS}+= quotacheck
SUBDIR.${MK_ROUTED}+= routed
+SUBDIR.${MK_ZFS}+= be
SUBDIR.${MK_ZFS}+= zfsbootcfg
SUBDIR.${MK_TESTS}+= tests
diff --git a/sbin/be/Makefile b/sbin/be/Makefile
new file mode 100644
index 000000000000..3d3cc4e2314b
--- /dev/null
+++ b/sbin/be/Makefile
@@ -0,0 +1,10 @@
+# @(#)Makefile 8.1 (Berkeley) 6/6/93
+
+PROG= be
+WARNS?= 1
+MAN= be.1
+
+LIBADD+= be
+LIBADD+= nvpair
+
+.include <bsd.prog.mk>
diff --git a/sbin/be/be.1 b/sbin/be/be.1
new file mode 100644
index 000000000000..2dd7b3d9d633
--- /dev/null
+++ b/sbin/be/be.1
@@ -0,0 +1,220 @@
+.\"
+.\" be - Utility to manage Boot Environments on the ZFS filesystem
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\"
+.\" @(#)be.1
+.\" $FreeBSD$
+.\"
+.Dd June 15, 2017
+.Dt BE 1
+.Os FreeBSD
+.Sh NAME
+.Nm be
+.Nd Utility to manage Boot Environments on ZFS
+.Sh SYNOPSIS
+.Nm
+activate
+.Op Fl t
+.Ao Ar beName Ac
+.Nm
+create
+.Op Fl r
+.Op Fl e Ar nonActiveBe | Fl e Ar beName@snapshot
+.Ao Ar beName Ac
+.Nm
+create
+.Op Fl r
+.Ao Ar beName@snapshot Ac
+.Nm
+destroy
+.Op Fl F
+.Ao Ar beName | beName@snapshot Ac
+.Nm
+jail
+.Ao Ar jailID | jailName Ac
+.Ao Ar bootenv Ac
+.Nm
+list
+.Op Fl a
+.Op Fl D
+.Op Fl H
+.Op Fl s
+.Nm
+mount
+.Ao Ar beName Ac
+.Op mountpoint
+.Nm
+rename
+.Ao Ar origBeName Ac
+.Ao Ar newBeName Ac
+.Nm
+{ ujail | unjail }
+.Ao Ar jailID | jailName Ac
+.Ao Ar bootenv Ac
+.Nm
+{ umount | unmount }
+.Op Fl f
+.Ao Ar beName Ac
+.Sh DESCRIPTION
+The
+.Nm
+command is used to setup and interact with ZFS boot environments, which are bootable clones of datasets.
+.Pp
+.Em Boot Environments
+allows the system to be upgraded, while preserving the old system environment in a separate ZFS dataset.
+.Pp
+.Sh COMMANDS
+The following commands are supported by
+.Nm :
+.Bl -tag -width activate
+.It Ic activate
+.Op Fl t
+.Ar <beName>
+.Pp
+Activate the given
+.Ar beName
+as the default boot filesystem. If the
+.Op Fl t
+flag is given, this takes effect only for the next boot.
+.Pp
+.It Ic create
+.Op Fl r
+.Op Fl e Ar nonActiveBe | Fl e Ar beName@snapshot
+.Ao Ar beName Ac
+.Pp
+Creates a new boot environment named
+.Ar beName .
+If the -e param is specified, the new environment will be cloned from the given
+.Ar nonActiveBe | Ar beName@snapshot .
+If the
+.Op Fl r
+flag is given, a recursive boot environment will be made.
+.Pp
+.It Ic create
+.Op Fl r
+.Ao Ar beName@snapshot Ac
+.Pp
+Creates a snapshot of the existing boot environment named
+.Ar beName .
+If the
+.Op Fl r
+flag is given, a recursive boot environment will be made.
+.Pp
+.It Ic destroy
+.Op Fl F
+.Ao Ar beName | beName@snapshot Ac
+.Pp
+Destroys the given
+.Ar beName
+boot environment or
+.Ar beName@snapshot
+snapshot.
+Specifying
+.Fl F
+will automatically unmount without confirmation.
+.Pp
+.It Ic jail
+.Ao Ar jailID | jailName Ac
+.Ao Ar bootenv Ac
+.Pp
+Creates a jail of the given boot environment.
+.Pp
+.It Ic list
+.Op Fl a
+.Op Fl D
+.Op Fl H
+.Op Fl s
+.Pp
+Displays all boot environments.
+The Active field indicates whether the boot environment is active now (N); active on reboot (R); or both (NR).
+.Pp
+If
+.Fl a
+is used, display all datasets.
+If
+.Fl D
+is used, display the full space usage for each boot environment, assuming all other boot environments were destroyed.
+The
+.Fl H
+option is used for scripting. It does not print headers and separate fields by a single tab instead of arbitrary white space.
+If
+.Fl s
+is used, display all snapshots as well.
+.Pp
+.It Ic mount
+.Ao Ar beName Ac
+.Op mountpoint
+.Pp
+Temporarily mount the boot environment.
+Mount at the specified
+.Ar mountpoint
+if provided.
+.Pp
+.It Ic rename Ao Ar origBeName Ac Ao Ar newBeName Ac
+.Pp
+Renames the given nonactive
+.Ar origBeName
+to the given
+.Ar newBeName
+.Pp
+.It Ic unmount
+.Op Fl f
+.Ao Ar beName Ac
+.Pp
+Unmount the given boot environment, if it is mounted.
+Specifying
+.Fl f
+will force the unmount if busy.
+.Pp
+.It Ic unjail
+.Ao Ar beName Ac
+.Pp
+Destroys the jail created from the given boot environment.
+.Pp
+.El
+.Sh EXAMPLES
+.Bl -bullet
+.It
+To fill in with jail upgrade example when behavior is firm.
+.Pp
+.Sh SEE ALSO
+.Xr jail 8 ,
+.Xr zfs 8 ,
+.Xr zpool 8
+.Sh HISTORY
+.Nm
+is based on
+.Xr beadm 1
+and was implemented as a project for the 2017 Summer of Code, along with
+.Xr libbe 3 .
+.Sh AUTHORS
+.Bl -bullet
+.It
+Kyle Kneitinger (kneitinger)
+.Ar kyle@kneit.in
+.Pp
+Creator of
+.Nm .
+.It
+Slawomir Wojciech Wojtczak (vermaden)
+.Ar vermaden@interia.pl
+.Pp
+Creator and maintainer of
+.Xr beadm 1 .
+.It
+Bryan Drewery (bdrewery)
+.Ar bryan@shatow.net
+.Pp
+Wrote the original
+.Xr beadm 1
+manual page that this one is derived from.
+.El
diff --git a/sbin/be/be.c b/sbin/be/be.c
new file mode 100644
index 000000000000..a4803b17cd9e
--- /dev/null
+++ b/sbin/be/be.c
@@ -0,0 +1,664 @@
+/*
+ * be.c
+ *
+ * Copyright (c) 2017 Kyle J. Kneitinger <kyle@kneit.in>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/param.h>
+#include <sys/jail.h>
+#include <sys/mount.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include <sys/nv.h>
+#include <be.h>
+
+static int be_cmd_activate(int argc, char *argv[]);
+static int be_cmd_create(int argc, char *argv[]);
+static int be_cmd_destroy(int argc, char *argv[]);
+static int be_cmd_export(int argc, char *argv[]);
+static int be_cmd_import(int argc, char *argv[]);
+static int be_cmd_add(int argc, char *argv[]);
+static int be_cmd_jail(int argc, char *argv[]);
+static int be_cmd_list(int argc, char *argv[]);
+static int be_cmd_mount(int argc, char *argv[]);
+static int be_cmd_rename(int argc, char *argv[]);
+static int be_cmd_unjail(int argc, char *argv[]);
+static int be_cmd_unmount(int argc, char *argv[]);
+
+libbe_handle_t *be;
+
+static int
+usage(bool explicit)
+{
+ FILE *fp = explicit ? stdout : stderr;
+
+ fprintf(fp,
+ "usage:\tbe ( -h | -? | subcommand [args...] )\n"
+ "\tbe activate [-t] beName\n"
+ "\tbe create [-e nonActiveBe | -e beName@snapshot] beName\n"
+ "\tbe create beName@snapshot\n"
+ "\tbe destroy [-F] beName | beName@snapshot⟩\n"
+ "\tbe export sourceBe\n"
+ "\tbe import targetBe\n"
+ "\tbe add (path)*\n"
+ "\tbe jail bootenv\n"
+ "\tbe list [-a] [-D] [-H] [-s]\n"
+ "\tbe mount beName [mountpoint]\n"
+ "\tbe rename origBeName newBeName\n"
+ "\tbe { ujail | unjail } ⟨jailID | jailName⟩ bootenv\n"
+ "\tbe { umount | unmount } [-f] beName\n");
+
+ return (explicit ? 0 : EX_USAGE);
+}
+
+
+/*
+ * Represents a relationship between the command name and the parser action
+ * that handles it.
+ */
+struct command_map_entry {
+ const char *command;
+ int (*fn)(int argc, char *argv[]);
+};
+
+static struct command_map_entry command_map[] =
+{
+ { "activate", be_cmd_activate },
+ { "create", be_cmd_create },
+ { "destroy", be_cmd_destroy },
+ { "export", be_cmd_export },
+ { "import", be_cmd_import },
+ { "add", be_cmd_add },
+ { "jail", be_cmd_jail },
+ { "list", be_cmd_list },
+ { "mount", be_cmd_mount },
+ { "rename", be_cmd_rename },
+ { "unjail", be_cmd_unjail },
+ { "unmount", be_cmd_unmount },
+};
+
+static int
+get_cmd_index(char *cmd, int *index)
+{
+ int map_size = sizeof(command_map) / sizeof(struct command_map_entry);
+
+ for (int i = 0; i < map_size; ++i) {
+ if (strcmp(cmd, command_map[i].command) == 0) {
+ *index = i;
+ return (0);
+ }
+ }
+
+ return (1);
+}
+
+
+static int
+be_cmd_activate(int argc, char *argv[])
+{
+ int err, opt;
+ bool temp;
+ char *bootenv;
+
+ temp = false;
+ while ((opt = getopt(argc, argv, "t")) != -1) {
+ switch (opt) {
+ case 't':
+ temp = true;
+ break;
+ default:
+ fprintf(stderr, "be activate: unknown option '-%c'\n",
+ optopt);
+ return (usage(false));
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 1) {
+ fprintf(stderr, "be activate: wrong number of arguments\n");
+ return (usage(false));
+ }
+
+
+ /* activate logic goes here */
+ if ((err = be_activate(be, argv[0], temp)) != 0) {
+ // TODO: more specific error msg based on err
+ printf("did not successfully activate boot environment %s\n",
+ argv[0]);
+ } else {
+ printf("successfully activated boot environment %s\n", argv[0]);
+ }
+
+ if (temp) {
+ printf("for next boot\n");
+ }
+
+ return (err);
+}
+
+
+// TODO: when only one arg is given, and it contains an "@" the this should
+// create that snapshot
+static int
+be_cmd_create(int argc, char *argv[])
+{
+ int err, opt;
+ char *snapname;
+ char *bootenv;
+ char *source;
+
+ snapname = NULL;
+ while ((opt = getopt(argc, argv, "e:")) != -1) {
+ switch (opt) {
+ case 'e':
+ snapname = optarg;
+ break;
+ default:
+ fprintf(stderr, "be create: unknown option '-%c'\n",
+ optopt);
+ return (usage(false));
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 1) {
+ fprintf(stderr, "be create: wrong number of arguments\n");
+ return (usage(false));
+ }
+
+ bootenv = *argv;
+
+
+ if (snapname != NULL) {
+ if (strchr(snapname, '@') != NULL) {
+ err = be_create_from_existing_snap(be, bootenv,
+ snapname);
+ } else {
+ err = be_create_from_existing(be, bootenv, snapname);
+ }
+ } else {
+ if ((snapname = strchr(bootenv, '@')) != NULL) {
+ *(snapname++) = '\0';
+ if ((err = be_snapshot(be, (char *)be_active_path(be),
+ snapname, true, NULL)) != BE_ERR_SUCCESS) {
+ fprintf(stderr, "failed to create snapshot\n");
+ }
+ asprintf(&source, "%s@%s", be_active_path(be), snapname);
+ err = be_create_from_existing_snap(be, bootenv,
+ source);
+ return (err);
+ } else {
+ err = be_create(be, bootenv);
+ }
+ }
+
+ switch (err) {
+ case BE_ERR_SUCCESS:
+ break;
+ default:
+ if (snapname == NULL) {
+ fprintf(stderr,
+ "failed to create bootenv %s\n", bootenv);
+ } else {
+ fprintf(stderr,
+ "failed to create bootenv %s from snapshot %s\n",
+ bootenv, snapname);
+ }
+ }
+
+ return (err);
+}
+
+
+static int
+be_cmd_export(int argc, char *argv[])
+{
+ int opt;
+ char *bootenv;
+
+
+ if (argc == 1) {
+ fprintf(stderr, "be export: missing boot environment name\n");
+ return (usage(false));
+ }
+
+ if (argc > 2) {
+ fprintf(stderr, "be export: extra arguments provided\n");
+ return (usage(false));
+ }
+
+ bootenv = argv[1];
+
+ if (isatty(STDOUT_FILENO)) {
+ fprintf(stderr, "be export: must redirect output\n");
+ return (EX_USAGE);
+ }
+
+ be_export(be, bootenv, STDOUT_FILENO);
+
+ return (0);
+}
+
+
+static int
+be_cmd_import(int argc, char *argv[])
+{
+ char *bootenv;
+ int err;
+
+
+ if (argc == 1) {
+ fprintf(stderr, "be import: missing boot environment name\n");
+ return (usage(false));
+ }
+
+
+ if (argc > 2) {
+ fprintf(stderr, "be import: extra arguments provided\n");
+ return (usage(false));
+ }
+
+ bootenv = argv[1];
+
+ if (isatty(STDIN_FILENO)) {
+ fprintf(stderr, "be import: input can not be from terminal\n");
+ return (EX_USAGE);
+ }
+
+ err = be_import(be, bootenv, STDIN_FILENO);
+
+ return (err);
+}
+
+
+static int
+be_cmd_add(int argc, char *argv[])
+{
+ char *bootenv;
+
+ if (argc < 2) {
+ fprintf(stderr, "be add: must provide at least one path\n");
+ return (usage(false));
+ }
+
+ for (int i = 1; i < argc; ++i) {
+ printf("arg %d: %s\n", i, argv[i]);
+ // TODO catch err
+ be_add_child(be, argv[i], true);
+ }
+
+ return (0);
+}
+
+
+static int
+be_cmd_destroy(int argc, char *argv[])
+{
+ int opt, err;
+ bool force;
+ char *target;
+
+ force = false;
+ while ((opt = getopt(argc, argv, "F")) != -1) {
+ switch (opt) {
+ case 'F':
+ force = true;
+ break;
+ default:
+ fprintf(stderr, "be destroy: unknown option '-%c'\n",
+ optopt);
+ return (usage(false));
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 1) {
+ fprintf(stderr, "be destroy: wrong number of arguments\n");
+ return (usage(false));
+ }
+
+ target = argv[0];
+
+ err = be_destroy(be, target, force);
+
+ return (err);
+}
+
+
+static int
+be_cmd_jail(int argc, char *argv[])
+{
+ char *bootenv;
+ char mnt_loc[BE_MAXPATHLEN];
+ char buf[BE_MAXPATHLEN*2];
+ int err, jid;
+
+ //struct jail be_jail = { 0 };
+
+ if (argc == 1) {
+ fprintf(stderr, "be jail: missing boot environment name\n");
+ return (usage(false));
+ }
+ if (argc > 2) {
+ fprintf(stderr, "be jail: too many arguments\n");
+ return (usage(false));
+ }
+
+ bootenv = argv[1];
+
+ // TODO: if its already mounted, perhaps there should be a flag to
+ // indicate its okay to proceed??
+ if ((err = be_mount(be, bootenv, NULL, 0, mnt_loc)) != BE_ERR_SUCCESS) {
+ fprintf(stderr, "could not mount bootenv\n");
+ }
+
+ // NOTE: this is not quite functional:
+ // see https://github.com/vermaden/beadm/blob/master/HOWTO.htm on
+ // neccesary modifications to correctly boot the jail
+
+ //snprintf(buf, BE_MAXPATHLEN*2, "jail %s %s %s /bin/sh /etc/rc", mnt_loc, bootenv, "192.168.1.123");
+ snprintf(buf, BE_MAXPATHLEN*2, "jail %s %s %s /bin/sh", mnt_loc,
+ bootenv, "192.168.1.123");
+ system(buf);
+
+ unmount(mnt_loc, 0);
+
+ /*
+ * be_jail.version = JAIL_API_VERSION;
+ * be_jail.path = "/tmp/be_mount.hCCk";
+ * be_jail.jailname = "sdfs";
+ *
+ * if ((jid = jail(&be_jail)) != -1) {
+ * printf("jail %d created at %s\n", jid, mnt_loc);
+ * err = 0;
+ * } else {
+ * fprintf(stderr, "unable to create jail. error: %d\n", errno);
+ * err = errno;
+ * }
+ */
+
+ return (0);
+}
+
+
+static int
+be_cmd_list(int argc, char *argv[])
+{
+ int opt;
+ bool show_all_datasets, show_space, hide_headers, show_snaps;
+ char *bootenv;
+ nvlist_t *props;
+
+ show_all_datasets = show_space = hide_headers = show_snaps = false;
+ while ((opt = getopt(argc, argv, "aDHs")) != -1) {
+ switch (opt) {
+ case 'a':
+ show_all_datasets = true;
+ break;
+ case 'D':
+ show_space = true;
+ break;
+ case 'H':
+ hide_headers = true;
+ break;
+ case 's':
+ show_space = true;
+ break;
+ default:
+ fprintf(stderr, "be list: unknown option '-%c'\n",
+ optopt);
+ return (usage(false));
+ }
+ }
+
+ argc -= optind;
+
+ if (argc != 0) {
+ fprintf(stderr, "be list: extra argument provided\n");
+ return (usage(false));
+ }
+
+ //props = be_get_bootenv_props(be);
+
+ return (0);
+}
+
+
+static int
+be_cmd_mount(int argc, char *argv[])
+{
+ int err;
+ char result_loc[BE_MAXPATHLEN];
+ char *bootenv;
+ char *mountpoint;
+
+ if (argc < 2) {
+ fprintf(stderr, "be mount: missing argument(s)\n");
+ return (usage(false));
+ }
+
+ if (argc > 3) {
+ fprintf(stderr, "be mount: too many arguments\n");
+ return (usage(false));
+ }
+
+ bootenv = argv[1];
+ mountpoint = ((argc == 3) ? argv[2] : NULL);
+
+
+ err = be_mount(be, bootenv, mountpoint, 0, result_loc);
+
+ switch (err) {
+ case BE_ERR_SUCCESS:
+ printf("successfully mounted %s at %s\n", bootenv, result_loc);
+ break;
+ default:
+ fprintf(stderr,
+ (argc == 3) ? "failed to mount bootenv %s at %s\n" :
+ "failed to mount bootenv %s at temporary path %s\n",
+ bootenv, mountpoint);
+ }
+
+ return (err);
+}
+
+
+static int
+be_cmd_rename(int argc, char *argv[])
+{
+ char *src;
+ char *dest;
+ int err;
+
+ if (argc < 3) {
+ fprintf(stderr, "be rename: missing argument\n");
+ return (usage(false));
+ }
+
+ if (argc > 3) {
+ fprintf(stderr, "be rename: too many arguments\n");
+ return (usage(false));
+ }
+
+ src = argv[1];
+ dest = argv[2];
+
+ err = be_rename(be, src, dest);
+
+ switch (err) {
+ case BE_ERR_SUCCESS:
+ break;
+ default:
+ fprintf(stderr, "failed to rename bootenv %s to %s\n",
+ src, dest);
+ }
+
+ return (0);
+}
+
+
+static int
+be_cmd_unjail(int argc, char *argv[])
+{
+ int opt;
+ char *cmd, *target;
+ bool force;
+
+ /* Store alias used */
+ cmd = argv[0];
+
+ force = false;
+ while ((opt = getopt(argc, argv, "f")) != -1) {
+ switch (opt) {
+ case 'f':
+ force = true;
+ break;
+ default:
+ fprintf(stderr, "be %s: unknown option '-%c'\n",
+ cmd, optopt);
+ return (usage(false));
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 1) {
+ fprintf(stderr, "be %s: wrong number of arguments\n", cmd);
+ return (usage(false));
+ }
+
+ target = argv[0];
+
+ /* unjail logic goes here */
+
+ return (0);
+}
+
+
+static int
+be_cmd_unmount(int argc, char *argv[])
+{
+ int err, flags, opt;
+ char *cmd, *bootenv;
+
+ /* Store alias used */
+ cmd = argv[0];
+
+ flags = 0;
+ while ((opt = getopt(argc, argv, "f")) != -1) {
+ switch (opt) {
+ case 'f':
+ flags |= BE_MNT_FORCE;
+ break;
+ default:
+ fprintf(stderr, "be %s: unknown option '-%c'\n",
+ cmd, optopt);
+ return (usage(false));
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 1) {
+ fprintf(stderr, "be %s: wrong number of arguments\n", cmd);
+ return (usage(false));
+ }
+
+ bootenv = argv[0];
+
+ err = be_unmount(be, bootenv, flags);
+
+ switch (err) {
+ case BE_ERR_SUCCESS:
+ break;
+ default:
+ fprintf(stderr, "failed to unmount bootenv %s\n", bootenv);
+ }
+
+ return (err);
+}
+
+
+int
+main(int argc, char *argv[])
+{
+ char *command;
+ int command_index, rc;
+
+ if (argc < 2) {
+ fprintf(stderr, "missing command\n");
+ return (usage(false));
+ }
+
+ command = argv[1];
+
+ /* Handle command aliases */
+ if (strcmp(command, "umount") == 0) {
+ command = "unmount";
+ }
+
+ if (strcmp(command, "ujail") == 0) {
+ command = "unjail";
+ }
+
+ if ((strcmp(command, "-?") == 0) || (strcmp(command, "-h") == 0)) {
+ return (usage(true));
+ }
+
+ if (get_cmd_index(command, &command_index)) {
+ fprintf(stderr, "unknown command: %s\n", command);
+ return (usage(false));
+ }
+
+
+ if ((be = libbe_init()) == NULL) {
+ return (-1);
+ }
+
+ libbe_print_on_error(be, true);
+
+ /* TODO: can be simplified if offset by 2 instead of one */
+ rc = command_map[command_index].fn(argc-1, argv+1);
+
+ libbe_close(be);
+
+
+ return (rc);
+}
diff --git a/share/mk/src.libnames.mk b/share/mk/src.libnames.mk
index 1ff6ca3ed22b..65c29ef96ac7 100644
--- a/share/mk/src.libnames.mk
+++ b/share/mk/src.libnames.mk
@@ -62,6 +62,7 @@ _LIBRARIES= \
asn1 \
auditd \
avl \
+ be \
begemot \
bluetooth \
bsdxml \
@@ -335,6 +336,7 @@ _DP_zfs= md pthread umem util uutil m nvpair avl bsdxml geom nvpair z \
zfs_core
_DP_zfs_core= nvpair
_DP_zpool= md pthread z nvpair avl umem
+_DP_be= zfs nvpair
# OFED support
.if ${MK_OFED} != "no"
@@ -472,6 +474,8 @@ LIBBSNMPTOOLS?= ${LIBBSNMPTOOLSDIR}/libbsnmptools.a
LIBAMUDIR= ${OBJTOP}/usr.sbin/amd/libamu
LIBAMU?= ${LIBAMUDIR}/libamu.a
+LIBBE?= ${DESTDIR}${LIBDIR}/libbe.a
+
LIBPMCSTATDIR= ${OBJTOP}/lib/libpmcstat
LIBPMCSTAT?= ${LIBPMCSTATDIR}/libpmcstat.a