Index: etc/mtree/BSD.tests.dist =================================================================== --- etc/mtree/BSD.tests.dist +++ etc/mtree/BSD.tests.dist @@ -280,6 +280,8 @@ .. libarchive .. + libbe + .. libc c063 .. Index: lib/libbe/Makefile =================================================================== --- lib/libbe/Makefile +++ lib/libbe/Makefile @@ -1,8 +1,10 @@ # $FreeBSD$ +.include + PACKAGE= lib${LIB} LIB= be -SHLIBDIR?= /lib +SHLIBDIR= /lib SHLIB_MAJOR= 1 SHLIB_MINOR= 0 @@ -28,4 +30,7 @@ CFLAGS+= -DNEED_SOLARIS_BOOLEAN +HAS_TESTS= YES +SUBDIR.${MK_TESTS}+= tests + .include Index: lib/libbe/be.h =================================================================== --- lib/libbe/be.h +++ lib/libbe/be.h @@ -84,6 +84,7 @@ /* Bootenv creation functions */ int be_create(libbe_handle_t *, const char *); +int be_create_depth(libbe_handle_t *, const char *, const char *, int); int be_create_from_existing(libbe_handle_t *, const char *, const char *); int be_create_from_existing_snap(libbe_handle_t *, const char *, const char *); int be_snapshot(libbe_handle_t *, const char *, const char *, bool, char *); Index: lib/libbe/be.c =================================================================== --- lib/libbe/be.c +++ lib/libbe/be.c @@ -352,7 +352,6 @@ sizeof(buf)) >= sizeof(buf)) return (set_error(lbh, BE_ERR_INVALIDNAME)); } - if ((err = zfs_snapshot(lbh->lzh, buf, recursive, NULL)) != 0) { switch (err) { case EZFS_INVALIDNAME: @@ -426,43 +425,78 @@ return (ZPROP_CONT); } +/* + * Return the corresponding boot environment path for a given + * dataset path, the constructed path is placed in 'result'. + * + * example: say our new boot environment name is 'bootenv' and + * the dataset path is 'zroot/ROOT/default/data/set'. + * + * result should produce: 'zroot/ROOT/bootenv/data/set' + */ static int -be_deep_clone(zfs_handle_t *ds, void *data) +be_get_path(struct libbe_deep_clone *ldc, const char *dspath, char *result, int result_size) +{ + char *pos; + char *child_dataset; + + /* match the root path for the boot environments */ + pos = strstr(dspath, ldc->lbh->root); + + /* no match, different pools? */ + if (pos == NULL) + return (BE_ERR_BADPATH); + + /* root path of the new boot environment */ + snprintf(result, result_size, "%s/%s", ldc->lbh->root, ldc->bename); + + /* gets us to the parent dataset, the +1 consumes a trailing slash */ + pos += strlen(ldc->lbh->root) + 1; + + /* skip the parent dataset */ + if ((child_dataset = strchr(pos, '/')) != NULL) + strlcat(result, child_dataset, result_size); + + return (BE_ERR_SUCCESS); +} + +static int +be_clone_cb(zfs_handle_t *ds, void *data) { int err; char be_path[BE_MAXPATHLEN]; char snap_path[BE_MAXPATHLEN]; const char *dspath; - char *dsname; zfs_handle_t *snap_hdl; nvlist_t *props; - struct libbe_deep_clone *isdc, sdc; + struct libbe_deep_clone *ldc; struct libbe_dccb dccb; - isdc = (struct libbe_deep_clone *)data; + ldc = (struct libbe_deep_clone *)data; 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, ldc->snapname); - snprintf(snap_path, sizeof(snap_path), "%s@%s", dspath, isdc->snapname); + /* construct the boot environment path from the dataset we're cloning */ + if (be_get_path(ldc, dspath, be_path, sizeof(be_path)) != BE_ERR_SUCCESS) + return (set_error(ldc->lbh, BE_ERR_UNKNOWN)); - if (zfs_dataset_exists(isdc->lbh->lzh, be_path, ZFS_TYPE_DATASET)) - return (set_error(isdc->lbh, BE_ERR_EXISTS)); + /* the dataset to be created (i.e. the boot environment) already exists */ + if (zfs_dataset_exists(ldc->lbh->lzh, be_path, ZFS_TYPE_DATASET)) + return (set_error(ldc->lbh, BE_ERR_EXISTS)); + + /* no snapshot found for this dataset, silently skip it */ + if (!zfs_dataset_exists(ldc->lbh->lzh, snap_path, ZFS_TYPE_SNAPSHOT)) + return (0); if ((snap_hdl = - zfs_open(isdc->lbh->lzh, snap_path, ZFS_TYPE_SNAPSHOT)) == NULL) - return (set_error(isdc->lbh, BE_ERR_ZFSOPEN)); + zfs_open(ldc->lbh->lzh, snap_path, ZFS_TYPE_SNAPSHOT)) == NULL) + return (set_error(ldc->lbh, BE_ERR_ZFSOPEN)); nvlist_alloc(&props, NV_UNIQUE_NAME, KM_SLEEP); nvlist_add_string(props, "canmount", "noauto"); - dccb.lbh = isdc->lbh; + dccb.lbh = ldc->lbh; dccb.zhp = ds; dccb.props = props; if (zprop_iter(be_deep_clone_prop, &dccb, B_FALSE, B_FALSE, @@ -470,58 +504,55 @@ return (-1); if ((err = zfs_clone(snap_hdl, be_path, props)) != 0) - err = BE_ERR_ZFSCLONE; + return (set_error(ldc->lbh, BE_ERR_ZFSCLONE)); nvlist_free(props); zfs_close(snap_hdl); - /* Failed to clone */ - if (err != BE_ERR_SUCCESS) - return (set_error(isdc->lbh, err)); - - 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); + if (ldc->depth_limit == -1 || ldc->depth < ldc->depth_limit) { + ldc->depth++; + err = zfs_iter_filesystems(ds, be_clone_cb, ldc); + ldc->depth--; + } - return (err); + return (set_error(ldc->lbh, err)); } /* - * Create the boot environment from pre-existing snapshot - */ -int -be_create_from_existing_snap(libbe_handle_t *lbh, const char *name, - const char *snap) + * Create a boot environment with a given name from a given snapshot. + * Snapshots can be in the format 'zroot/ROOT/default@snapshot' or + * 'default@snapshot'. In the latter case, 'default@snapshot' will be prepended + * with the root path that libbe was initailized with. +*/ +static int +be_clone(libbe_handle_t *lbh, const char *bename, const char *snapshot, int depth) { int err; - char be_path[BE_MAXPATHLEN]; char snap_path[BE_MAXPATHLEN]; - const char *bename; char *parentname, *snapname; zfs_handle_t *parent_hdl; - struct libbe_deep_clone sdc; + struct libbe_deep_clone ldc; - if ((err = be_validate_name(lbh, name)) != 0) - return (set_error(lbh, err)); - if ((err = be_root_concat(lbh, snap, snap_path)) != 0) - return (set_error(lbh, err)); - if ((err = be_validate_snap(lbh, snap_path)) != 0) + /* ensure the boot environment name is valid */ + if ((err = be_validate_name(lbh, bename)) != 0) return (set_error(lbh, err)); - if ((err = be_root_concat(lbh, name, be_path)) != 0) + /* + * prepend the boot environment root path if we're + * given a partial snapshot name. + */ + if ((err = be_root_concat(lbh, snapshot, snap_path)) != 0) return (set_error(lbh, err)); - if ((bename = strrchr(name, '/')) == NULL) - bename = name; - else - bename++; + /* ensure the snapshot exists */ + if ((err = be_validate_snap(lbh, snap_path)) != 0) + return (set_error(lbh, err)); + /* get a copy of the snapshot path so we can disect it */ if ((parentname = strdup(snap_path)) == NULL) return (set_error(lbh, BE_ERR_UNKNOWN)); + /* split dataset name from snapshot name */ snapname = strchr(parentname, '@'); if (snapname == NULL) { free(parentname); @@ -530,32 +561,56 @@ *snapname = '\0'; snapname++; - sdc.lbh = lbh; - sdc.bename = bename; - sdc.snapname = snapname; - sdc.be_root = lbh->root; + /* set-up the boot environment */ + ldc.lbh = lbh; + ldc.bename = bename; + ldc.snapname = snapname; + ldc.depth = 0; + ldc.depth_limit = depth; + /* the boot environment will be cloned from this dataset */ parent_hdl = zfs_open(lbh->lzh, parentname, ZFS_TYPE_DATASET); - err = be_deep_clone(parent_hdl, &sdc); + + /* create the boot environment */ + err = be_clone_cb(parent_hdl, &ldc); free(parentname); return (set_error(lbh, err)); } +/* + * Create a boot environment from pre-existing snapshot, specifying a depth. + */ +int be_create_depth(libbe_handle_t *lbh, const char *bename, + const char *snap, int depth) +{ + return (be_clone(lbh, bename, snap, depth)); +} + +/* + * Create the boot environment from pre-existing snapshot + */ +int +be_create_from_existing_snap(libbe_handle_t *lbh, const char *bename, + const char *snap) +{ + return (be_clone(lbh, bename, snap, -1)); +} + /* * Create a boot environment from an existing boot environment */ int -be_create_from_existing(libbe_handle_t *lbh, const char *name, const char *old) +be_create_from_existing(libbe_handle_t *lbh, const char *bename, const char *old) { int err; - char buf[BE_MAXPATHLEN]; + char snap[BE_MAXPATHLEN]; - if ((err = be_snapshot(lbh, old, NULL, true, (char *)&buf)) != 0) + if ((err = be_snapshot(lbh, old, NULL, true, snap)) != 0) return (set_error(lbh, err)); - err = be_create_from_existing_snap(lbh, name, (char *)buf); + err = be_clone(lbh, bename, snap, -1); return (set_error(lbh, err)); } Index: lib/libbe/be_impl.h =================================================================== --- lib/libbe/be_impl.h +++ lib/libbe/be_impl.h @@ -50,7 +50,8 @@ libbe_handle_t *lbh; const char *bename; const char *snapname; - const char *be_root; + int depth; + int depth_limit; }; struct libbe_dccb { Index: lib/libbe/libbe.3 =================================================================== --- lib/libbe/libbe.3 +++ lib/libbe/libbe.3 @@ -63,6 +63,9 @@ .Fn be_create "libbe_handle_t *hdl" "const char *be_name" .Pp .Ft int +.Fn be_create_depth "libbe_handle_t *hdl" "const char *be_name" "const char *snap" "int depth" +.Pp +.Ft int .Fn be_create_from_existing "libbe_handle_t *hdl" "const char *be_name" "const char *be_origin" .Pp .Ft int @@ -213,18 +216,25 @@ The .Fn be_create function creates a boot environment with the given name. -It will be created from a snapshot of the currently booted boot environment. +The new boot environment will be created from a recursive snapshot of the currently +booted boot environment. +.Pp +The +.Fn be_create_depth +function creates a boot environment with the given name from an existing snapshot. +The depth parameter specifies the depth of recursion that will be cloned from the +existing snapshot. A depth of '0' is no recursion and '-1' is unlimited (i.e. a recursive +boot environment). .Pp The .Fn be_create_from_existing function creates a boot environment with the given name from the name of an -existing boot environment. -A snapshot will be made of the base boot environment, and the new boot -environment will be created from that. +existing boot environment. A recursive snapshot will be made of the origin +boot environment, and the new boot environment will be created from that. .Pp The .Fn be_create_from_existing_snap -function creates a boot environment with the given name from an existing +function creates a recursive boot environment with the given name from an existing snapshot. .Pp The Index: lib/libbe/tests/Makefile =================================================================== --- /dev/null +++ lib/libbe/tests/Makefile @@ -0,0 +1,21 @@ +# $FreeBSD$ + +PACKAGE= tests + +ATF_TESTS_SH+= be_create + +PROGS= target_prog +SRCS_target_prog= target_prog.c +BINDIR_target_prog= ${TESTSDIR} + +LIBADD+= zfs +LIBADD+= nvpair +LIBADD+= be + +CFLAGS+= -I${SRCTOP}/lib/libbe +CFLAGS+= -I${SRCTOP}/sys/cddl/compat/opensolaris +CFLAGS+= -I${SRCTOP}/sys/cddl/contrib/opensolaris/uts/common + +CFLAGS+= -DNEED_SOLARIS_BOOLEAN + +.include Index: lib/libbe/tests/be_create.sh =================================================================== --- /dev/null +++ lib/libbe/tests/be_create.sh @@ -0,0 +1,178 @@ +# +# SPDX-License-Identifier: BSD-2-Clause-FreeBSD +# +# Copyright (c) 2019 Rob Wing +# +# 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. +# +# $FreeBSD$ + +# The code for the following tests was copied from the +# bectl tests found in src/sbin/bectl/tests, modified as needed. + +ZPOOL_NAME_FILE=zpool_name +get_zpool_name() +{ + cat $ZPOOL_NAME_FILE +} +make_zpool_name() +{ + mktemp -u libbe_test_XXXXXX > $ZPOOL_NAME_FILE + get_zpool_name +} + +# Establishes a libbe zpool that can be used for some light testing; contains +# a 'default' BE and not much else. +libbe_create_setup() +{ + zpool=$1 + disk=$2 + mnt=$3 + + # Sanity check to make sure `make_zpool_name` succeeded + atf_check test -n "$zpool" + + kldload -n -q zfs || atf_skip "ZFS module not loaded on the current system" + atf_check mkdir -p ${mnt} + atf_check truncate -s 1G ${disk} + atf_check zpool create -o altroot=${mnt} ${zpool} ${disk} + atf_check zfs create -o mountpoint=none ${zpool}/ROOT + atf_check zfs create -o mountpoint=/ -o canmount=noauto \ + ${zpool}/ROOT/default + atf_check zfs create -o mountpoint=/usr -o canmount=noauto \ + ${zpool}/ROOT/default/usr + atf_check zfs create -o mountpoint=/usr/obj -o canmount=noauto \ + ${zpool}/ROOT/default/usr/obj +} + +libbe_cleanup() +{ + zpool=$1 + cwd=$(atf_get_srcdir) + + if [ -z "$zpool" ]; then + echo "Skipping cleanup; zpool not set up" + elif zpool get health ${zpool} >/dev/null 2>&1; then + zpool destroy -f ${zpool} + fi + + if [ -f "${cwd}/disk.img" ]; then + rm ${cwd}/disk.img + fi +} + +atf_test_case libbe_create cleanup +libbe_create_head() +{ + atf_set "descr" "check _be_create from libbe" + atf_set "require.user" root +} +libbe_create_body() +{ + cwd=$(atf_get_srcdir) + zpool=$(make_zpool_name) + disk=${cwd}/disk.img + mount=${cwd}/mnt + prog=${cwd}/./target_prog + + # preliminary setup/checks + atf_require_prog $prog + libbe_create_setup ${zpool} ${disk} ${mount} + + # a recursive and non-recursive snapshot to test against + atf_check zfs snapshot ${zpool}/ROOT/default@non-recursive + atf_check zfs snapshot -r ${zpool}/ROOT/default@recursive + + # create a dataset after snapshots were taken + atf_check zfs create -o mountpoint=/usr/src -o canmount=noauto \ + ${zpool}/ROOT/default/usr/src + + # test boot environment creation with depth of 0 (i.e. a non-recursive boot environment). + atf_check $prog "${zpool}/ROOT" \ + nonrecursive \ + "${zpool}/ROOT/default@non-recursive" \ + 0 + # the dataset should exist + atf_check -o not-empty \ + zfs list "${zpool}/ROOT/nonrecursive" + # the child dataset should not exist. + atf_check -e not-empty -s not-exit:0 \ + zfs list "${zpool}/ROOT/nonrecursive/usr" + + # test boot environment creation with unlimited depth (i.e. a recursive boot environment). + atf_check $prog "${zpool}/ROOT" \ + recursive \ + "${zpool}/ROOT/default@recursive" \ + -1 + # the dataset should exist + atf_check -o not-empty \ + zfs list "${zpool}/ROOT/recursive" + # the child dataset should exist + atf_check -o not-empty \ + zfs list "${zpool}/ROOT/recursive/usr" + # the child dataset should exist + atf_check -o not-empty \ + zfs list "${zpool}/ROOT/recursive/usr/obj" + # the child dataset should not exist. + atf_check -e not-empty -s not-exit:0 \ + zfs list "${zpool}/ROOT/recursive/usr/src" + + # test boot environment creation with a depth of 1 + atf_check $prog "${zpool}/ROOT" \ + depth \ + "${zpool}/ROOT/default@recursive" \ + 1 + # the child dataset should exist + atf_check -o not-empty \ + zfs list "${zpool}/ROOT/depth/usr" + # the child dataset should not exist. + atf_check -e not-empty -s not-exit:0 \ + zfs list "${zpool}/ROOT/depth/usr/obj" + # the child dataset should not exist. + atf_check -e not-empty -s not-exit:0 \ + zfs list "${zpool}/ROOT/depth/usr/src" + + + # create a recursive boot environment named 'relative-snap'. + # This test is to ensure that a relative snapshot label can be used, + # (i.e. the format: 'bootenvironment@snapshot') + atf_check $prog "${zpool}/ROOT" \ + relative-snap \ + default@recursive \ + -1 + # the dataset should exist + atf_check -o not-empty \ + zfs list "${zpool}/ROOT/relative-snap" + # the child dataset should exist + atf_check -o not-empty \ + zfs list "${zpool}/ROOT/relative-snap/usr" +} + +libbe_create_cleanup() +{ + libbe_cleanup $(get_zpool_name) +} + +atf_init_test_cases() +{ + atf_add_test_case libbe_create +} Index: lib/libbe/tests/target_prog.c =================================================================== --- lib/libbe/tests/target_prog.c +++ lib/libbe/tests/target_prog.c @@ -1,7 +1,7 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * - * Copyright (c) 2017 Kyle J. Kneitinger + * Copyright (c) 2019 Rob Wing * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -24,53 +24,30 @@ * 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. - * - * $FreeBSD$ */ -#ifndef _LIBBE_IMPL_H -#define _LIBBE_IMPL_H - -#include - -#include "be.h" +#include +__FBSDID("$FreeBSD$"); -struct libbe_handle { - char root[BE_MAXPATHLEN]; - char rootfs[BE_MAXPATHLEN]; - char bootfs[BE_MAXPATHLEN]; - size_t altroot_len; - zpool_handle_t *active_phandle; - libzfs_handle_t *lzh; - be_error_t error; - bool print_on_err; -}; +#include -struct libbe_deep_clone { - libbe_handle_t *lbh; - const char *bename; - const char *snapname; - const char *be_root; -}; - -struct libbe_dccb { - libbe_handle_t *lbh; - zfs_handle_t *zhp; - nvlist_t *props; -}; +/* + * argv[1] = root boot environment (e.g. zroot/ROOT), + * argv[2] = name of boot environment to create + * argv[3] = snapshot to create boot environment from + * argv[4] = depth + */ +int main(int argc, char *argv[]) { -typedef struct prop_data { - nvlist_t *list; - libbe_handle_t *lbh; - bool single_object; /* list will contain props directly */ -} prop_data_t; + libbe_handle_t *lbh; -int prop_list_builder_cb(zfs_handle_t *, void *); -int be_proplist_update(prop_data_t *); + if (argc != 5) + return -1; -char *be_mountpoint_augmented(libbe_handle_t *lbh, char *mountpoint); + if ((lbh = libbe_init(argv[1])) == NULL) + return -1; -/* Clobbers any previous errors */ -int set_error(libbe_handle_t *, be_error_t); + libbe_print_on_error(lbh, true); -#endif /* _LIBBE_IMPL_H */ + return (be_create_depth(lbh, argv[2], argv[3], atoi(argv[4]))); +}