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 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 + * 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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + * 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 + +#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 + * 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 + * 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 + * 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 + +#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 + * 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 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 +.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 + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +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