diff --git a/Makefile.inc1 b/Makefile.inc1 index 641ab4e8b9c5..97a5f62e6eed 100644 --- a/Makefile.inc1 +++ b/Makefile.inc1 @@ -2155,7 +2155,7 @@ _prebuild_libs= ${_kerberos5_lib_libasn1} \ ${_cddl_lib_libumem} ${_cddl_lib_libnvpair} \ ${_cddl_lib_libuutil} \ ${_cddl_lib_libavl} \ - ${_cddl_lib_libzfs_core} \ + ${_cddl_lib_libzfs_core} ${_cddl_lib_libzfs} \ ${_cddl_lib_libctf} \ lib/libutil lib/libpjdlog ${_lib_libypclnt} lib/libz lib/msun \ ${_secure_lib_libcrypto} ${_lib_libldns} \ @@ -2224,7 +2224,15 @@ _cddl_lib_libavl= cddl/lib/libavl _cddl_lib_libuutil= cddl/lib/libuutil .if ${MK_ZFS} != "no" _cddl_lib_libzfs_core= cddl/lib/libzfs_core +_cddl_lib_libzfs= cddl/lib/libzfs + cddl/lib/libzfs_core__L: cddl/lib/libnvpair__L + +cddl/lib/libzfs__L: cddl/lib/libzfs_core__L lib/msun__L lib/libutil__L +cddl/lib/libzfs__L: lib/libthr__L lib/libmd__L lib/libz__L cddl/lib/libumem__L +cddl/lib/libzfs__L: cddl/lib/libuutil__L cddl/lib/libavl__L lib/libgeom__L + +lib/libbe__L: cddl/lib/libzfs__L .endif _cddl_lib_libctf= cddl/lib/libctf _cddl_lib= cddl/lib diff --git a/contrib/mdocml/lib.in b/contrib/mdocml/lib.in index 76691ecf01c8..646c9a2407f4 100644 --- a/contrib/mdocml/lib.in +++ b/contrib/mdocml/lib.in @@ -28,6 +28,7 @@ LINE("lib80211", "802.11 Wireless Network Management Library (lib80211, \\-l8021 LINE("libarchive", "Streaming Archive Library (libarchive, \\-larchive)") LINE("libarm", "ARM Architecture Library (libarm, \\-larm)") LINE("libarm32", "ARM32 Architecture Library (libarm32, \\-larm32)") +LINE("libbe", "Boot Environment Library (libbe, \\-lbe)") LINE("libbluetooth", "Bluetooth Library (libbluetooth, \\-lbluetooth)") LINE("libbsm", "Basic Security Module Library (libbsm, \\-lbsm)") LINE("libc", "Standard C\\~Library (libc, \\-lc)") diff --git a/etc/mtree/BSD.tests.dist b/etc/mtree/BSD.tests.dist index 172d6a1959ad..84e6604e4739 100644 --- a/etc/mtree/BSD.tests.dist +++ b/etc/mtree/BSD.tests.dist @@ -380,6 +380,8 @@ .. .. sbin + bectl + .. dhclient .. devd diff --git a/lib/Makefile b/lib/Makefile index 38de67952b9f..2b0938ce7d8f 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -290,6 +290,7 @@ _libproc= libproc _librtld_db= librtld_db .endif SUBDIR.${MK_OFED}+= ofed +SUBDIR.${MK_ZFS}+= libbe SUBDIR.${MK_OPENMP}+= libomp diff --git a/lib/libbe/Makefile b/lib/libbe/Makefile new file mode 100644 index 000000000000..5fada3204fb9 --- /dev/null +++ b/lib/libbe/Makefile @@ -0,0 +1,31 @@ +# $FreeBSD$ + +PACKAGE= lib${LIB} +LIB= be +SHLIBDIR?= /lib +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?= 2 +IGNORE_PRAGMA= yes + +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 + +CFLAGS+= -DNEED_SOLARIS_BOOLEAN + +.include diff --git a/lib/libbe/be.c b/lib/libbe/be.c new file mode 100644 index 000000000000..93cee2640424 --- /dev/null +++ b/lib/libbe/be.c @@ -0,0 +1,1038 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "be.h" +#include "be_impl.h" + +struct be_destroy_data { + libbe_handle_t *lbh; + char *snapname; +}; + +#if SOON +static int be_create_child_noent(libbe_handle_t *lbh, const char *active, + const char *child_path); +static int be_create_child_cloned(libbe_handle_t *lbh, const char *active); +#endif + +/* Arbitrary... should tune */ +#define BE_SNAP_SERIAL_MAX 1024 + +/* + * Iterator function for locating the rootfs amongst the children of the + * zfs_be_root set by loader(8). data is expected to be a libbe_handle_t *. + */ +static int +be_locate_rootfs(libbe_handle_t *lbh) +{ + struct statfs sfs; + struct extmnttab entry; + zfs_handle_t *zfs; + + /* + * Check first if root is ZFS; if not, we'll bail on rootfs capture. + * Unfortunately needed because zfs_path_to_zhandle will emit to + * stderr if / isn't actually a ZFS filesystem, which we'd like + * to avoid. + */ + if (statfs("/", &sfs) == 0) { + statfs2mnttab(&sfs, &entry); + if (strcmp(entry.mnt_fstype, MNTTYPE_ZFS) != 0) + return (1); + } else + return (1); + zfs = zfs_path_to_zhandle(lbh->lzh, "/", ZFS_TYPE_FILESYSTEM); + if (zfs == NULL) + return (1); + + strlcpy(lbh->rootfs, zfs_get_name(zfs), sizeof(lbh->rootfs)); + zfs_close(zfs); + return (0); +} + +/* + * Initializes the libbe context to operate in the root boot environment + * dataset, for example, zroot/ROOT. + */ +libbe_handle_t * +libbe_init(const char *root) +{ + char altroot[MAXPATHLEN]; + libbe_handle_t *lbh; + char *poolname, *pos; + int pnamelen; + + lbh = NULL; + poolname = pos = NULL; + + if ((lbh = calloc(1, sizeof(libbe_handle_t))) == NULL) + goto err; + + if ((lbh->lzh = libzfs_init()) == NULL) + goto err; + + /* + * Grab rootfs, we'll work backwards from there if an optional BE root + * has not been passed in. + */ + if (be_locate_rootfs(lbh) != 0) { + if (root == NULL) + goto err; + *lbh->rootfs = '\0'; + } + if (root == NULL) { + /* Strip off the final slash from rootfs to get the be root */ + strlcpy(lbh->root, lbh->rootfs, sizeof(lbh->root)); + pos = strrchr(lbh->root, '/'); + if (pos == NULL) + goto err; + *pos = '\0'; + } else + strlcpy(lbh->root, root, sizeof(lbh->root)); + + if ((pos = strchr(lbh->root, '/')) == NULL) + goto err; + + pnamelen = pos - lbh->root; + poolname = malloc(pnamelen + 1); + if (poolname == NULL) + goto err; + + strlcpy(poolname, lbh->root, pnamelen + 1); + if ((lbh->active_phandle = zpool_open(lbh->lzh, poolname)) == NULL) + goto err; + free(poolname); + poolname = NULL; + + if (zpool_get_prop(lbh->active_phandle, ZPOOL_PROP_BOOTFS, lbh->bootfs, + sizeof(lbh->bootfs), NULL, true) != 0) + goto err; + + if (zpool_get_prop(lbh->active_phandle, ZPOOL_PROP_ALTROOT, + altroot, sizeof(altroot), NULL, true) == 0 && + strcmp(altroot, "-") != 0) + lbh->altroot_len = strlen(altroot); + + return (lbh); +err: + if (lbh != NULL) { + if (lbh->active_phandle != NULL) + zpool_close(lbh->active_phandle); + if (lbh->lzh != NULL) + libzfs_fini(lbh->lzh); + free(lbh); + } + free(poolname); + return (NULL); +} + + +/* + * Free memory allocated by libbe_init() + */ +void +libbe_close(libbe_handle_t *lbh) +{ + + if (lbh->active_phandle != NULL) + zpool_close(lbh->active_phandle); + libzfs_fini(lbh->lzh); + free(lbh); +} + +/* + * Proxy through to libzfs for the moment. + */ +void +be_nicenum(uint64_t num, char *buf, size_t buflen) +{ + + zfs_nicenum(num, buf, buflen); +} + +static int +be_destroy_cb(zfs_handle_t *zfs_hdl, void *data) +{ + char path[BE_MAXPATHLEN]; + struct be_destroy_data *bdd; + zfs_handle_t *snap; + int err; + + bdd = (struct be_destroy_data *)data; + if (bdd->snapname == NULL) { + err = zfs_iter_children(zfs_hdl, be_destroy_cb, data); + if (err != 0) + return (err); + return (zfs_destroy(zfs_hdl, false)); + } + /* If we're dealing with snapshots instead, delete that one alone */ + err = zfs_iter_filesystems(zfs_hdl, be_destroy_cb, data); + if (err != 0) + return (err); + /* + * This part is intentionally glossing over any potential errors, + * because there's a lot less potential for errors when we're cleaning + * up snapshots rather than a full deep BE. The primary error case + * here being if the snapshot doesn't exist in the first place, which + * the caller will likely deem insignificant as long as it doesn't + * exist after the call. Thus, such a missing snapshot shouldn't jam + * up the destruction. + */ + snprintf(path, sizeof(path), "%s@%s", zfs_get_name(zfs_hdl), + bdd->snapname); + if (!zfs_dataset_exists(bdd->lbh->lzh, path, ZFS_TYPE_SNAPSHOT)) + return (0); + snap = zfs_open(bdd->lbh->lzh, path, ZFS_TYPE_SNAPSHOT); + if (snap != NULL) + zfs_destroy(snap, false); + return (0); +} + +/* + * 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 + * BE_DESTROY_ORIGIN: destroy the origin snapshot as well + */ +int +be_destroy(libbe_handle_t *lbh, const char *name, int options) +{ + struct be_destroy_data bdd; + char origin[BE_MAXPATHLEN], path[BE_MAXPATHLEN]; + zfs_handle_t *fs; + char *snapdelim; + int err, force, mounted; + size_t rootlen; + + bdd.lbh = lbh; + bdd.snapname = NULL; + force = options & BE_DESTROY_FORCE; + *origin = '\0'; + + be_root_concat(lbh, name, path); + + if ((snapdelim = strchr(path, '@')) == NULL) { + if (!zfs_dataset_exists(lbh->lzh, path, ZFS_TYPE_FILESYSTEM)) + return (set_error(lbh, BE_ERR_NOENT)); + + if (strcmp(path, lbh->rootfs) == 0 || + strcmp(path, lbh->bootfs) == 0) + return (set_error(lbh, BE_ERR_DESTROYACT)); + + fs = zfs_open(lbh->lzh, path, ZFS_TYPE_FILESYSTEM); + if (fs == NULL) + return (set_error(lbh, BE_ERR_ZFSOPEN)); + + if ((options & BE_DESTROY_ORIGIN) != 0 && + zfs_prop_get(fs, ZFS_PROP_ORIGIN, origin, sizeof(origin), + NULL, NULL, 0, 1) != 0) + return (set_error(lbh, BE_ERR_NOORIGIN)); + + /* Don't destroy a mounted dataset unless force is specified */ + if ((mounted = zfs_is_mounted(fs, NULL)) != 0) { + if (force) { + zfs_unmount(fs, NULL, 0); + } else { + free(bdd.snapname); + return (set_error(lbh, BE_ERR_DESTROYMNT)); + } + } + } else { + if (!zfs_dataset_exists(lbh->lzh, path, ZFS_TYPE_SNAPSHOT)) + return (set_error(lbh, BE_ERR_NOENT)); + + bdd.snapname = strdup(snapdelim + 1); + if (bdd.snapname == NULL) + return (set_error(lbh, BE_ERR_NOMEM)); + *snapdelim = '\0'; + fs = zfs_open(lbh->lzh, path, ZFS_TYPE_DATASET); + if (fs == NULL) { + free(bdd.snapname); + return (set_error(lbh, BE_ERR_ZFSOPEN)); + } + } + + err = be_destroy_cb(fs, &bdd); + zfs_close(fs); + free(bdd.snapname); + if (err != 0) { + /* Children are still present or the mount is referenced */ + if (err == EBUSY) + return (set_error(lbh, BE_ERR_DESTROYMNT)); + return (set_error(lbh, BE_ERR_UNKNOWN)); + } + + if ((options & BE_DESTROY_ORIGIN) == 0) + return (0); + + /* The origin can't possibly be shorter than the BE root */ + rootlen = strlen(lbh->root); + if (*origin == '\0' || strlen(origin) <= rootlen + 1) + return (set_error(lbh, BE_ERR_INVORIGIN)); + + /* + * We'll be chopping off the BE root and running this back through + * be_destroy, so that we properly handle the origin snapshot whether + * it be that of a deep BE or not. + */ + if (strncmp(origin, lbh->root, rootlen) != 0 || origin[rootlen] != '/') + return (0); + + return (be_destroy(lbh, origin + rootlen + 1, + options & ~BE_DESTROY_ORIGIN)); +} + +static void +be_setup_snapshot_name(libbe_handle_t *lbh, char *buf, size_t buflen) +{ + time_t rawtime; + int len, serial; + + time(&rawtime); + len = strlen(buf); + len += strftime(buf + len, buflen - len, "@%F-%T", localtime(&rawtime)); + /* No room for serial... caller will do its best */ + if (buflen - len < 2) + return; + + for (serial = 0; serial < BE_SNAP_SERIAL_MAX; ++serial) { + snprintf(buf + len, buflen - len, "-%d", serial); + if (!zfs_dataset_exists(lbh->lzh, buf, ZFS_TYPE_SNAPSHOT)) + return; + } +} + +int +be_snapshot(libbe_handle_t *lbh, const char *source, const char *snap_name, + bool recursive, char *result) +{ + char buf[BE_MAXPATHLEN]; + int err; + + be_root_concat(lbh, source, buf); + + if ((err = be_exists(lbh, buf)) != 0) + return (set_error(lbh, err)); + + if (snap_name != NULL) { + if (strlcat(buf, "@", sizeof(buf)) >= sizeof(buf)) + return (set_error(lbh, BE_ERR_INVALIDNAME)); + + if (strlcat(buf, snap_name, sizeof(buf)) >= sizeof(buf)) + return (set_error(lbh, BE_ERR_INVALIDNAME)); + + if (result != NULL) + snprintf(result, BE_MAXPATHLEN, "%s@%s", source, + snap_name); + } else { + be_setup_snapshot_name(lbh, buf, sizeof(buf)); + + if (result != NULL && strlcpy(result, strrchr(buf, '/') + 1, + 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: + return (set_error(lbh, BE_ERR_INVALIDNAME)); + + default: + /* + * The other errors that zfs_ioc_snapshot might return + * shouldn't happen if we've set things up properly, so + * we'll gloss over them and call it UNKNOWN as it will + * require further triage. + */ + if (errno == ENOTSUP) + return (set_error(lbh, BE_ERR_NOPOOL)); + 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, const char *name) +{ + int err; + + err = be_create_from_existing(lbh, name, 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; + zprop_source_t src; + char pval[BE_MAXPATHLEN]; + char source[BE_MAXPATHLEN]; + char *val; + + dccb = cb; + /* Skip some properties we don't want to touch */ + if (prop == ZFS_PROP_CANMOUNT) + return (ZPROP_CONT); + + /* 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); + + /* Augment mountpoint with altroot, if needed */ + val = pval; + if (prop == ZFS_PROP_MOUNTPOINT) + val = be_mountpoint_augmented(dccb->lbh, val); + + nvlist_add_string(dccb->props, zfs_prop_to_name(prop), val); + + 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]; + const char *dspath; + char *dsname; + zfs_handle_t *snap_hdl; + nvlist_t *props; + struct libbe_deep_clone *isdc, sdc; + struct libbe_dccb dccb; + + isdc = (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, 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.lbh = isdc->lbh; + 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)) != 0) + err = 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); + + return (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) +{ + 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; + + 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) + return (set_error(lbh, err)); + + if ((err = be_root_concat(lbh, name, be_path)) != 0) + return (set_error(lbh, err)); + + if ((bename = strrchr(name, '/')) == NULL) + bename = name; + else + bename++; + + if ((parentname = strdup(snap_path)) == NULL) + return (set_error(lbh, BE_ERR_UNKNOWN)); + + snapname = strchr(parentname, '@'); + if (snapname == NULL) { + free(parentname); + return (set_error(lbh, BE_ERR_UNKNOWN)); + } + *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); + + free(parentname); + return (set_error(lbh, err)); +} + + +/* + * Create a boot environment from an existing boot environment + */ +int +be_create_from_existing(libbe_handle_t *lbh, const char *name, const char *old) +{ + int err; + char buf[BE_MAXPATHLEN]; + + if ((err = be_snapshot(lbh, old, NULL, true, (char *)&buf)) != 0) + 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, const char *snap_name) +{ + + if (strlen(snap_name) >= BE_MAXPATHLEN) + return (BE_ERR_PATHLEN); + + if (!zfs_name_valid(snap_name, ZFS_TYPE_SNAPSHOT)) + return (BE_ERR_INVALIDNAME); + + if (!zfs_dataset_exists(lbh->lzh, snap_name, + ZFS_TYPE_SNAPSHOT)) + return (BE_ERR_NOENT); + + return (BE_ERR_SUCCESS); +} + + +/* + * 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, const 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); + + strlcpy(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 + * or BE_ERR_PATHLEN. + * Does not set internal library error state. + */ +int +be_validate_name(libbe_handle_t *lbh, const char *name) +{ + + /* + * Impose the additional restriction that the entire dataset name must + * not exceed the maximum length of a dataset, i.e. MAXNAMELEN. + */ + if (strlen(lbh->root) + 1 + strlen(name) > MAXNAMELEN) + return (BE_ERR_PATHLEN); + + if (!zfs_name_valid(name, ZFS_TYPE_DATASET)) + return (BE_ERR_INVALIDNAME); + + return (BE_ERR_SUCCESS); +} + + +/* + * usage + */ +int +be_rename(libbe_handle_t *lbh, const char *old, const char *new) +{ + char full_old[BE_MAXPATHLEN]; + char full_new[BE_MAXPATHLEN]; + zfs_handle_t *zfs_hdl; + int err; + + /* + * be_validate_name is documented not to set error state, so we should + * do so here. + */ + if ((err = be_validate_name(lbh, new)) != 0) + return (set_error(lbh, err)); + if ((err = be_root_concat(lbh, old, full_old)) != 0) + return (set_error(lbh, err)); + if ((err = be_root_concat(lbh, new, full_new)) != 0) + return (set_error(lbh, err)); + + if (!zfs_dataset_exists(lbh->lzh, full_old, ZFS_TYPE_DATASET)) + return (set_error(lbh, BE_ERR_NOENT)); + + if (zfs_dataset_exists(lbh->lzh, full_new, ZFS_TYPE_DATASET)) + return (set_error(lbh, BE_ERR_EXISTS)); + + if ((zfs_hdl = zfs_open(lbh->lzh, full_old, + ZFS_TYPE_FILESYSTEM)) == NULL) + return (set_error(lbh, BE_ERR_ZFSOPEN)); + + /* recurse, nounmount, forceunmount */ + struct renameflags flags = { + .nounmount = 1, + }; + + err = zfs_rename(zfs_hdl, NULL, full_new, flags); + + zfs_close(zfs_hdl); + if (err != 0) + return (set_error(lbh, BE_ERR_UNKNOWN)); + return (0); +} + + +int +be_export(libbe_handle_t *lbh, const 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)) != 0) + /* Use the error set by be_snapshot */ + return (err); + + be_root_concat(lbh, snap_name, buf); + + if ((zfs = zfs_open(lbh->lzh, buf, ZFS_TYPE_DATASET)) == NULL) + return (set_error(lbh, BE_ERR_ZFSOPEN)); + + err = zfs_send_one(zfs, NULL, fd, 0); + zfs_close(zfs); + + return (err); +} + + +int +be_import(libbe_handle_t *lbh, const char *bootenv, int fd) +{ + char buf[BE_MAXPATHLEN]; + nvlist_t *props; + zfs_handle_t *zfs; + recvflags_t flags = { .nomount = 1 }; + int err; + + be_root_concat(lbh, bootenv, buf); + + if ((err = zfs_receive(lbh->lzh, buf, NULL, &flags, fd, NULL)) != 0) { + switch (err) { + case EINVAL: + return (set_error(lbh, BE_ERR_NOORIGIN)); + case ENOENT: + return (set_error(lbh, BE_ERR_NOENT)); + case EIO: + return (set_error(lbh, BE_ERR_IO)); + default: + return (set_error(lbh, BE_ERR_UNKNOWN)); + } + } + + if ((zfs = zfs_open(lbh->lzh, buf, ZFS_TYPE_FILESYSTEM)) == NULL) + return (set_error(lbh, BE_ERR_ZFSOPEN)); + + nvlist_alloc(&props, NV_UNIQUE_NAME, KM_SLEEP); + nvlist_add_string(props, "canmount", "noauto"); + nvlist_add_string(props, "mountpoint", "/"); + + err = zfs_prop_set_list(zfs, props); + nvlist_free(props); + + zfs_close(zfs); + + if (err != 0) + return (set_error(lbh, BE_ERR_UNKNOWN)); + + return (0); +} + +#if SOON +static int +be_create_child_noent(libbe_handle_t *lbh, const char *active, + const char *child_path) +{ + nvlist_t *props; + zfs_handle_t *zfs; + int err; + + 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)) != 0) { + switch (err) { + case EZFS_EXISTS: + return (set_error(lbh, BE_ERR_EXISTS)); + case EZFS_NOENT: + return (set_error(lbh, BE_ERR_NOENT)); + case EZFS_BADTYPE: + case EZFS_BADVERSION: + return (set_error(lbh, BE_ERR_NOPOOL)); + case EZFS_BADPROP: + default: + /* We set something up wrong, probably... */ + return (set_error(lbh, BE_ERR_UNKNOWN)); + } + } + nvlist_free(props); + + if ((zfs = zfs_open(lbh->lzh, active, ZFS_TYPE_DATASET)) == NULL) + return (set_error(lbh, BE_ERR_ZFSOPEN)); + + /* Set props */ + if ((err = zfs_prop_set(zfs, "canmount", "noauto")) != 0) { + zfs_close(zfs); + /* + * Similar to other cases, this shouldn't fail unless we've + * done something wrong. This is a new dataset that shouldn't + * have been mounted anywhere between creation and now. + */ + if (err == EZFS_NOMEM) + return (set_error(lbh, BE_ERR_NOMEM)); + return (set_error(lbh, BE_ERR_UNKNOWN)); + } + zfs_close(zfs); + return (BE_ERR_SUCCESS); +} + +static int +be_create_child_cloned(libbe_handle_t *lbh, const char *active) +{ + char buf[BE_MAXPATHLEN], tmp[BE_MAXPATHLEN];; + zfs_handle_t *zfs; + int err; + + /* XXX TODO ? */ + + /* + * Establish if the existing path is a zfs dataset or just + * the subdirectory of one + */ + strlcpy(tmp, "tmp/be_snap.XXXXX", sizeof(tmp)); + if (mktemp(tmp) == NULL) + return (set_error(lbh, BE_ERR_UNKNOWN)); + + be_root_concat(lbh, tmp, buf); + printf("Here %s?\n", buf); + if ((err = zfs_snapshot(lbh->lzh, buf, false, NULL)) != 0) { + switch (err) { + case EZFS_INVALIDNAME: + return (set_error(lbh, BE_ERR_INVALIDNAME)); + + default: + /* + * The other errors that zfs_ioc_snapshot might return + * shouldn't happen if we've set things up properly, so + * we'll gloss over them and call it UNKNOWN as it will + * require further triage. + */ + if (errno == ENOTSUP) + return (set_error(lbh, BE_ERR_NOPOOL)); + return (set_error(lbh, BE_ERR_UNKNOWN)); + } + } + + /* Clone */ + if ((zfs = zfs_open(lbh->lzh, buf, ZFS_TYPE_SNAPSHOT)) == NULL) + return (BE_ERR_ZFSOPEN); + + if ((err = zfs_clone(zfs, active, NULL)) != 0) + /* XXX TODO correct error */ + return (set_error(lbh, BE_ERR_UNKNOWN)); + + /* set props */ + zfs_close(zfs); + return (BE_ERR_SUCCESS); +} + +int +be_add_child(libbe_handle_t *lbh, const char *child_path, bool cp_if_exists) +{ + struct stat sb; + char active[BE_MAXPATHLEN], buf[BE_MAXPATHLEN]; + nvlist_t *props; + const char *s; + + /* Require absolute paths */ + if (*child_path != '/') + return (set_error(lbh, BE_ERR_BADPATH)); + + strlcpy(active, be_active_path(lbh), BE_MAXPATHLEN); + strcpy(buf, active); + + /* Create non-mountable parent dataset(s) */ + 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 */ + if (strlcat(active, child_path, BE_MAXPATHLEN) >= BE_MAXPATHLEN) + return (set_error(lbh, BE_ERR_PATHLEN)); + + if (stat(child_path, &sb) != 0) { + /* Verify that error is ENOENT */ + if (errno != ENOENT) + return (set_error(lbh, BE_ERR_UNKNOWN)); + return (be_create_child_noent(lbh, active, child_path)); + } else if (cp_if_exists) + /* Path is already a descendent of / and should be copied */ + return (be_create_child_cloned(lbh, active)); + return (set_error(lbh, BE_ERR_EXISTS)); +} +#endif /* SOON */ + +static int +be_set_nextboot(libbe_handle_t *lbh, nvlist_t *config, uint64_t pool_guid, + const char *zfsdev) +{ + nvlist_t **child; + uint64_t vdev_guid; + int c, children; + + if (nvlist_lookup_nvlist_array(config, ZPOOL_CONFIG_CHILDREN, &child, + &children) == 0) { + for (c = 0; c < children; ++c) + if (be_set_nextboot(lbh, child[c], pool_guid, zfsdev) != 0) + return (1); + return (0); + } + + if (nvlist_lookup_uint64(config, ZPOOL_CONFIG_GUID, + &vdev_guid) != 0) { + return (1); + } + + if (zpool_nextboot(lbh->lzh, pool_guid, vdev_guid, zfsdev) != 0) { + perror("ZFS_IOC_NEXTBOOT failed"); + return (1); + } + + return (0); +} + +/* + * Deactivate old BE dataset; currently just sets canmount=noauto + */ +static int +be_deactivate(libbe_handle_t *lbh, const char *ds) +{ + zfs_handle_t *zfs; + + if ((zfs = zfs_open(lbh->lzh, ds, ZFS_TYPE_DATASET)) == NULL) + return (1); + if (zfs_prop_set(zfs, "canmount", "noauto") != 0) + return (1); + zfs_close(zfs); + return (0); +} + +int +be_activate(libbe_handle_t *lbh, const char *bootenv, bool temporary) +{ + char be_path[BE_MAXPATHLEN]; + char buf[BE_MAXPATHLEN]; + nvlist_t *config, *dsprops, *vdevs; + char *origin; + uint64_t pool_guid; + zfs_handle_t *zhp; + int err; + + be_root_concat(lbh, bootenv, be_path); + + /* Note: be_exists fails if mountpoint is not / */ + if ((err = be_exists(lbh, be_path)) != 0) + return (set_error(lbh, err)); + + if (temporary) { + config = zpool_get_config(lbh->active_phandle, NULL); + if (config == NULL) + /* config should be fetchable... */ + return (set_error(lbh, BE_ERR_UNKNOWN)); + + if (nvlist_lookup_uint64(config, ZPOOL_CONFIG_POOL_GUID, + &pool_guid) != 0) + /* Similarly, it shouldn't be possible */ + return (set_error(lbh, BE_ERR_UNKNOWN)); + + /* Expected format according to zfsbootcfg(8) man */ + snprintf(buf, sizeof(buf), "zfs:%s:", be_path); + + /* We have no config tree */ + if (nvlist_lookup_nvlist(config, ZPOOL_CONFIG_VDEV_TREE, + &vdevs) != 0) + return (set_error(lbh, BE_ERR_NOPOOL)); + + return (be_set_nextboot(lbh, vdevs, pool_guid, buf)); + } else { + if (be_deactivate(lbh, lbh->bootfs) != 0) + return (-1); + + /* Obtain bootenv zpool */ + err = zpool_set_prop(lbh->active_phandle, "bootfs", be_path); + if (err) + return (-1); + + zhp = zfs_open(lbh->lzh, be_path, ZFS_TYPE_FILESYSTEM); + if (zhp == NULL) + return (-1); + + if (be_prop_list_alloc(&dsprops) != 0) + return (-1); + + if (be_get_dataset_props(lbh, be_path, dsprops) != 0) { + nvlist_free(dsprops); + return (-1); + } + + if (nvlist_lookup_string(dsprops, "origin", &origin) == 0) + err = zfs_promote(zhp); + nvlist_free(dsprops); + + zfs_close(zhp); + + if (err) + return (-1); + } + + return (BE_ERR_SUCCESS); +} diff --git a/lib/libbe/be.h b/lib/libbe/be.h new file mode 100644 index 000000000000..e1aced8d983d --- /dev/null +++ b/lib/libbe/be.h @@ -0,0 +1,133 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * 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. + * + * $FreeBSD$ + */ + +#ifndef _LIBBE_H +#define _LIBBE_H + +#include +#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_BADPATH, /* path not suitable for operation */ + BE_ERR_PATHBUSY, /* requested path is busy */ + BE_ERR_PATHLEN, /* provided name exceeds maximum length limit */ + BE_ERR_BADMOUNT, /* 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_IO, /* error when doing some I/O operation */ + BE_ERR_NOPOOL, /* operation not supported on this pool */ + BE_ERR_NOMEM, /* insufficient memory */ + BE_ERR_UNKNOWN, /* unknown error */ + BE_ERR_INVORIGIN, /* invalid origin */ +} be_error_t; + + +/* Library handling functions: be.c */ +libbe_handle_t *libbe_init(const char *root); +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_nextboot_name(libbe_handle_t *); +const char *be_nextboot_path(libbe_handle_t *); +const char *be_root_path(libbe_handle_t *); + +int be_get_bootenv_props(libbe_handle_t *, nvlist_t *); +int be_get_dataset_props(libbe_handle_t *, const char *, nvlist_t *); +int be_get_dataset_snapshots(libbe_handle_t *, const char *, nvlist_t *); +int be_prop_list_alloc(nvlist_t **be_list); +void be_prop_list_free(nvlist_t *be_list); + +int be_activate(libbe_handle_t *, const char *, bool); + +/* Bootenv creation functions */ +int be_create(libbe_handle_t *, const char *); +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 *); + +/* Bootenv manipulation functions */ +int be_rename(libbe_handle_t *, const char *, const char *); + +/* Bootenv removal functions */ + +typedef enum { + BE_DESTROY_FORCE = 1 << 0, + BE_DESTROY_ORIGIN = 1 << 1, +} be_destroy_opt_t; + +int be_destroy(libbe_handle_t *, const 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); +int be_mounted_at(libbe_handle_t *, const char *path, nvlist_t *); + +/* 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 *, const char *, char *); +int be_validate_name(libbe_handle_t * __unused, const char *); +int be_validate_snap(libbe_handle_t *, const char *); +int be_exists(libbe_handle_t *, char *); + +int be_export(libbe_handle_t *, const char *, int fd); +int be_import(libbe_handle_t *, const char *, int fd); + +#if SOON +int be_add_child(libbe_handle_t *, const char *, bool); +#endif +void be_nicenum(uint64_t num, char *buf, size_t buflen); + +#endif /* _LIBBE_H */ diff --git a/lib/libbe/be_access.c b/lib/libbe/be_access.c new file mode 100644 index 000000000000..af46bd900b71 --- /dev/null +++ b/lib/libbe/be_access.c @@ -0,0 +1,316 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2017 Kyle J. Kneitinger + * Copyright (c) 2018 Kyle Evans + * Copyright (c) 2019 Wes Maag + * 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 +__FBSDID("$FreeBSD$"); + +#include "be.h" +#include "be_impl.h" + +struct be_mountcheck_info { + const char *path; + char *name; +}; + +struct be_mount_info { + libbe_handle_t *lbh; + const char *be; + const char *mountpoint; + int mntflags; + int deepmount; +}; + +static int +be_mountcheck_cb(zfs_handle_t *zfs_hdl, void *data) +{ + struct be_mountcheck_info *info; + char *mountpoint; + + if (data == NULL) + return (1); + info = (struct be_mountcheck_info *)data; + if (!zfs_is_mounted(zfs_hdl, &mountpoint)) + return (0); + if (strcmp(mountpoint, info->path) == 0) { + info->name = strdup(zfs_get_name(zfs_hdl)); + free(mountpoint); + return (1); + } + free(mountpoint); + return (0); +} + +/* + * Called from be_mount, uses the given zfs_handle and attempts to + * mount it at the passed mountpoint. If the deepmount flag is set, continue + * calling the function for each child dataset. + */ +static int +be_mount_iter(zfs_handle_t *zfs_hdl, void *data) +{ + int err; + char *mountpoint; + char tmp[BE_MAXPATHLEN], zfs_mnt[BE_MAXPATHLEN]; + struct be_mount_info *info; + + info = (struct be_mount_info *)data; + + if (zfs_is_mounted(zfs_hdl, &mountpoint)) { + free(mountpoint); + return (0); + } + + if (zfs_prop_get_int(zfs_hdl, ZFS_PROP_CANMOUNT) == ZFS_CANMOUNT_OFF) + return (0); + + if (zfs_prop_get(zfs_hdl, ZFS_PROP_MOUNTPOINT, zfs_mnt, BE_MAXPATHLEN, + NULL, NULL, 0, 1)) + return (1); + + if (strcmp("none", zfs_mnt) != 0) { + char opt = '\0'; + + mountpoint = be_mountpoint_augmented(info->lbh, zfs_mnt); + + snprintf(tmp, BE_MAXPATHLEN, "%s%s", info->mountpoint, + mountpoint); + + if ((err = zmount(zfs_get_name(zfs_hdl), tmp, info->mntflags, + __DECONST(char *, MNTTYPE_ZFS), NULL, 0, &opt, 1)) != 0) { + switch (errno) { + case ENAMETOOLONG: + return (set_error(info->lbh, BE_ERR_PATHLEN)); + case ELOOP: + case ENOENT: + case ENOTDIR: + return (set_error(info->lbh, BE_ERR_BADPATH)); + case EPERM: + return (set_error(info->lbh, BE_ERR_PERMS)); + case EBUSY: + return (set_error(info->lbh, BE_ERR_PATHBUSY)); + default: + return (set_error(info->lbh, BE_ERR_UNKNOWN)); + } + } + } + + if (!info->deepmount) + return (0); + + return (zfs_iter_filesystems(zfs_hdl, be_mount_iter, info)); +} + + +static int +be_umount_iter(zfs_handle_t *zfs_hdl, void *data) +{ + + int err; + char *mountpoint; + struct be_mount_info *info; + + info = (struct be_mount_info *)data; + + if((err = zfs_iter_filesystems(zfs_hdl, be_umount_iter, info)) != 0) { + return (err); + } + + if (!zfs_is_mounted(zfs_hdl, &mountpoint)) { + return (0); + } + free(mountpoint); + + if (zfs_unmount(zfs_hdl, NULL, info->mntflags) != 0) { + switch (errno) { + case ENAMETOOLONG: + return (set_error(info->lbh, BE_ERR_PATHLEN)); + case ELOOP: + case ENOENT: + case ENOTDIR: + return (set_error(info->lbh, BE_ERR_BADPATH)); + case EPERM: + return (set_error(info->lbh, BE_ERR_PERMS)); + case EBUSY: + return (set_error(info->lbh, BE_ERR_PATHBUSY)); + default: + return (set_error(info->lbh, BE_ERR_UNKNOWN)); + } + } + return (0); +} + +/* + * usage + */ +int +be_mounted_at(libbe_handle_t *lbh, const char *path, nvlist_t *details) +{ + char be[BE_MAXPATHLEN]; + zfs_handle_t *root_hdl; + struct be_mountcheck_info info; + prop_data_t propinfo; + + bzero(&be, BE_MAXPATHLEN); + if ((root_hdl = zfs_open(lbh->lzh, lbh->root, + ZFS_TYPE_FILESYSTEM)) == NULL) + return (BE_ERR_ZFSOPEN); + + info.path = path; + info.name = NULL; + zfs_iter_filesystems(root_hdl, be_mountcheck_cb, &info); + zfs_close(root_hdl); + + if (info.name != NULL) { + if (details != NULL) { + if ((root_hdl = zfs_open(lbh->lzh, lbh->root, + ZFS_TYPE_FILESYSTEM)) == NULL) { + free(info.name); + return (BE_ERR_ZFSOPEN); + } + + propinfo.lbh = lbh; + propinfo.list = details; + propinfo.single_object = false; + prop_list_builder_cb(root_hdl, &propinfo); + zfs_close(root_hdl); + } + free(info.name); + return (0); + } + return (1); +} + +/* + * 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]; + int mntflags, mntdeep; + int err; + struct be_mount_info info; + zfs_handle_t *zhdl; + + if ((err = be_root_concat(lbh, bootenv, be)) != 0) + return (set_error(lbh, err)); + + if ((err = be_exists(lbh, bootenv)) != 0) + return (set_error(lbh, err)); + + if (is_mounted(lbh->lzh, be, NULL)) + return (set_error(lbh, BE_ERR_MOUNTED)); + + mntdeep = (flags & BE_MNT_DEEP) ? 1 : 0; + mntflags = (flags & BE_MNT_FORCE) ? MNT_FORCE : 0; + + /* Create mountpoint if it is not specified */ + if (mountpoint == NULL) { + strlcpy(mnt_temp, "/tmp/be_mount.XXXX", sizeof(mnt_temp)); + if (mkdtemp(mnt_temp) == NULL) + return (set_error(lbh, BE_ERR_IO)); + } + + if ((zhdl = zfs_open(lbh->lzh, be, ZFS_TYPE_FILESYSTEM)) == NULL) + return (set_error(lbh, BE_ERR_ZFSOPEN)); + + info.lbh = lbh; + info.be = be; + info.mountpoint = (mountpoint == NULL) ? mnt_temp : mountpoint; + info.mntflags = mntflags; + info.deepmount = mntdeep; + + if((err = be_mount_iter(zhdl, &info) != 0)) { + zfs_close(zhdl); + return (err); + } + zfs_close(zhdl); + + if (result_loc != NULL) + strlcpy(result_loc, mountpoint == NULL ? mnt_temp : mountpoint, + BE_MAXPATHLEN); + + return (BE_ERR_SUCCESS); +} + +/* + * usage + */ +int +be_unmount(libbe_handle_t *lbh, char *bootenv, int flags) +{ + int err; + char be[BE_MAXPATHLEN]; + zfs_handle_t *root_hdl; + struct be_mount_info info; + + if ((err = be_root_concat(lbh, bootenv, be)) != 0) + return (set_error(lbh, err)); + + if ((root_hdl = zfs_open(lbh->lzh, be, ZFS_TYPE_FILESYSTEM)) == NULL) + return (set_error(lbh, BE_ERR_ZFSOPEN)); + + info.lbh = lbh; + info.be = be; + info.mountpoint = NULL; + info.mntflags = (flags & BE_MNT_FORCE) ? MS_FORCE : 0; + + if ((err = be_umount_iter(root_hdl, &info)) != 0) { + zfs_close(root_hdl); + return (err); + } + + zfs_close(root_hdl); + return (BE_ERR_SUCCESS); +} + +/* + * This function will blow away the input buffer as needed if we're discovered + * to be looking at a root-mount. If the mountpoint is naturally beyond the + * root, however, the buffer may be left intact and a pointer to the section + * past altroot will be returned instead for the caller's perusal. + */ +char * +be_mountpoint_augmented(libbe_handle_t *lbh, char *mountpoint) +{ + + if (lbh->altroot_len == 0) + return (mountpoint); + if (mountpoint == NULL || *mountpoint == '\0') + return (mountpoint); + + if (mountpoint[lbh->altroot_len] == '\0') { + *(mountpoint + 1) = '\0'; + return (mountpoint); + } else + return (mountpoint + lbh->altroot_len); +} diff --git a/lib/libbe/be_error.c b/lib/libbe/be_error.c new file mode 100644 index 000000000000..04e062936a9d --- /dev/null +++ b/lib/libbe/be_error.c @@ -0,0 +1,136 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * 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 +__FBSDID("$FreeBSD$"); + +#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_BADPATH: + return ("path not suitable for operation"); + + case BE_ERR_PATHBUSY: + return ("specified path is busy"); + + case BE_ERR_PATHLEN: + return ("provided path name exceeds maximum length limit"); + + case BE_ERR_BADMOUNT: + return ("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_IO: + return ("input/output error"); + + case BE_ERR_NOPOOL: + return ("operation not supported on this pool"); + + case BE_ERR_NOMEM: + return ("insufficient memory"); + + case BE_ERR_UNKNOWN: + return ("unknown error"); + + case BE_ERR_INVORIGIN: + return ("invalid origin"); + + 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) +{ + + 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..e3a6baea5959 --- /dev/null +++ b/lib/libbe/be_impl.h @@ -0,0 +1,76 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * 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. + * + * $FreeBSD$ + */ + +#ifndef _LIBBE_IMPL_H +#define _LIBBE_IMPL_H + +#include + +#include "be.h" + +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; +}; + +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; +}; + +typedef struct prop_data { + nvlist_t *list; + libbe_handle_t *lbh; + bool single_object; /* list will contain props directly */ +} prop_data_t; + +int prop_list_builder_cb(zfs_handle_t *, void *); +int be_proplist_update(prop_data_t *); + +char *be_mountpoint_augmented(libbe_handle_t *lbh, char *mountpoint); + +/* Clobbers any previous errors */ +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..e8869e6f94c8 --- /dev/null +++ b/lib/libbe/be_info.c @@ -0,0 +1,305 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2017 Kyle J. Kneitinger + * Copyright (c) 2018 Kyle Evans + * 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 +__FBSDID("$FreeBSD$"); + +#include "be.h" +#include "be_impl.h" + +static int snapshot_proplist_update(zfs_handle_t *hdl, prop_data_t *data); + +/* + * Returns the name of the active boot environment + */ +const char * +be_active_name(libbe_handle_t *lbh) +{ + + if (*lbh->rootfs != '\0') + return (strrchr(lbh->rootfs, '/') + sizeof(char)); + else + return (lbh->rootfs); +} + + +/* + * Returns full path of the active boot environment + */ +const char * +be_active_path(libbe_handle_t *lbh) +{ + + return (lbh->rootfs); +} + +/* + * Returns the name of the next active boot environment + */ +const char * +be_nextboot_name(libbe_handle_t *lbh) +{ + + if (*lbh->bootfs != '\0') + return (strrchr(lbh->bootfs, '/') + sizeof(char)); + else + return (lbh->bootfs); +} + + +/* + * Returns full path of the active boot environment + */ +const char * +be_nextboot_path(libbe_handle_t *lbh) +{ + + return (lbh->bootfs); +} + + +/* + * Returns the path of the boot environment root dataset + */ +const char * +be_root_path(libbe_handle_t *lbh) +{ + + return (lbh->root); +} + + +/* + * Populates dsnvl with one nvlist per bootenv dataset describing the properties + * of that dataset that we've declared ourselves to care about. + */ +int +be_get_bootenv_props(libbe_handle_t *lbh, nvlist_t *dsnvl) +{ + prop_data_t data; + + data.lbh = lbh; + data.list = dsnvl; + data.single_object = false; + return (be_proplist_update(&data)); +} + +int +be_get_dataset_props(libbe_handle_t *lbh, const char *name, nvlist_t *props) +{ + zfs_handle_t *snap_hdl; + prop_data_t data; + int ret; + + data.lbh = lbh; + data.list = props; + data.single_object = true; + if ((snap_hdl = zfs_open(lbh->lzh, name, + ZFS_TYPE_FILESYSTEM | ZFS_TYPE_SNAPSHOT)) == NULL) + return (BE_ERR_ZFSOPEN); + + ret = prop_list_builder_cb(snap_hdl, &data); + zfs_close(snap_hdl); + return (ret); +} + +int +be_get_dataset_snapshots(libbe_handle_t *lbh, const char *name, nvlist_t *props) +{ + zfs_handle_t *ds_hdl; + prop_data_t data; + int ret; + + data.lbh = lbh; + data.list = props; + data.single_object = false; + if ((ds_hdl = zfs_open(lbh->lzh, name, + ZFS_TYPE_FILESYSTEM)) == NULL) + return (BE_ERR_ZFSOPEN); + + ret = snapshot_proplist_update(ds_hdl, &data); + zfs_close(ds_hdl); + return (ret); +} + +/* + * Internal callback function used by zfs_iter_filesystems. For each dataset in + * the bootenv root, populate an nvlist_t of its relevant properties. + */ +int +prop_list_builder_cb(zfs_handle_t *zfs_hdl, void *data_p) +{ + char buf[512], *mountpoint; + prop_data_t *data; + libbe_handle_t *lbh; + nvlist_t *props; + const char *dataset, *name; + boolean_t mounted; + + /* + * XXX TODO: + * some system for defining constants for the nvlist keys + * error checking + */ + data = (prop_data_t *)data_p; + lbh = data->lbh; + + if (data->single_object) + props = data->list; + else + nvlist_alloc(&props, NV_UNIQUE_NAME, KM_SLEEP); + + dataset = zfs_get_name(zfs_hdl); + nvlist_add_string(props, "dataset", dataset); + + name = strrchr(dataset, '/') + 1; + nvlist_add_string(props, "name", name); + + mounted = zfs_is_mounted(zfs_hdl, &mountpoint); + + if (mounted) + nvlist_add_string(props, "mounted", mountpoint); + + if (zfs_prop_get(zfs_hdl, ZFS_PROP_MOUNTPOINT, buf, 512, + NULL, NULL, 0, 1) == 0) + nvlist_add_string(props, "mountpoint", buf); + + if (zfs_prop_get(zfs_hdl, ZFS_PROP_ORIGIN, buf, 512, + NULL, NULL, 0, 1) == 0) + nvlist_add_string(props, "origin", buf); + + if (zfs_prop_get(zfs_hdl, ZFS_PROP_CREATION, buf, 512, + NULL, NULL, 0, 1) == 0) + 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) == 0) + nvlist_add_string(props, "used", buf); + + if (zfs_prop_get(zfs_hdl, ZFS_PROP_USEDDS, buf, 512, + NULL, NULL, 0, 1) == 0) + nvlist_add_string(props, "usedds", buf); + + if (zfs_prop_get(zfs_hdl, ZFS_PROP_USEDSNAP, buf, 512, + NULL, NULL, 0, 1) == 0) + nvlist_add_string(props, "usedsnap", buf); + + if (zfs_prop_get(zfs_hdl, ZFS_PROP_USEDREFRESERV, buf, 512, + NULL, NULL, 0, 1) == 0) + nvlist_add_string(props, "usedrefreserv", buf); + + if (zfs_prop_get(zfs_hdl, ZFS_PROP_REFERENCED, buf, 512, + NULL, NULL, 0, 1) == 0) + nvlist_add_string(props, "referenced", buf); + + nvlist_add_boolean_value(props, "nextboot", + (strcmp(be_nextboot_path(lbh), dataset) == 0)); + + if (!data->single_object) + nvlist_add_nvlist(data->list, name, props); + + return (0); +} + + +/* + * Updates the properties of each bootenv in the libbe handle + * XXX TODO: ensure that this is always consistent (run after adds, deletes, + * renames,etc + */ +int +be_proplist_update(prop_data_t *data) +{ + zfs_handle_t *root_hdl; + + if ((root_hdl = zfs_open(data->lbh->lzh, data->lbh->root, + ZFS_TYPE_FILESYSTEM)) == NULL) + return (BE_ERR_ZFSOPEN); + + /* XXX TODO: some error checking here */ + zfs_iter_filesystems(root_hdl, prop_list_builder_cb, data); + + zfs_close(root_hdl); + + return (0); +} + +static int +snapshot_proplist_update(zfs_handle_t *hdl, prop_data_t *data) +{ + + return (zfs_iter_snapshots_sorted(hdl, prop_list_builder_cb, data)); +} + + +int +be_prop_list_alloc(nvlist_t **be_list) +{ + + return (nvlist_alloc(be_list, NV_UNIQUE_NAME, KM_SLEEP)); +} + +/* + * frees property list and its children + */ +void +be_prop_list_free(nvlist_t *be_list) +{ + nvlist_t *prop_list; + nvpair_t *be_pair; + + 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 + */ +int +be_exists(libbe_handle_t *lbh, char *be) +{ + char buf[BE_MAXPATHLEN]; + + be_root_concat(lbh, be, buf); + + if (!zfs_dataset_exists(lbh->lzh, buf, ZFS_TYPE_DATASET)) + return (BE_ERR_NOENT); + + return (BE_ERR_SUCCESS); +} diff --git a/lib/libbe/libbe.3 b/lib/libbe/libbe.3 new file mode 100644 index 000000000000..750a6ec261e5 --- /dev/null +++ b/lib/libbe/libbe.3 @@ -0,0 +1,504 @@ +.\" +.\" SPDX-License-Identifier: BSD-2-Clause-FreeBSD +.\" +.\" Copyright (c) 2017 Kyle Kneitinger +.\" All rights reserved. +.\" Copyright (c) 2018 Kyle Evans +.\" +.\" 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$ +.\" +.Dd February 12, 2019 +.Dt LIBBE 3 +.Os +.Sh NAME +.Nm libbe +.Nd library for creating, destroying and modifying ZFS boot environments +.Sh LIBRARY +.Lb libbe +.Sh SYNOPSIS +.In be.h +.Ft "libbe_handle_t *hdl" Ns +.Fn libbe_init "const char *be_root" +.Pp +.Ft void +.Fn libbe_close "libbe_handle_t *hdl" +.Pp +.Ft const char * Ns +.Fn be_active_name "libbe_handle_t *hdl" +.Pp +.Ft const char * Ns +.Fn be_active_path "libbe_handle_t *hdl" +.Pp +.Ft const char * Ns +.Fn be_nextboot_name "libbe_handle_t *hdl" +.Pp +.Ft const char * Ns +.Fn be_nextboot_path "libbe_handle_t *hdl" +.Pp +.Ft const char * Ns +.Fn be_root_path "libbe_handle_t *hdl" +.Pp +.Ft int +.Fn be_create "libbe_handle_t *hdl" "const char *be_name" +.Pp +.Ft int +.Fn be_create_from_existing "libbe_handle_t *hdl" "const char *be_name" "const char *be_origin" +.Pp +.Ft int +.Fn be_create_from_existing_snap "libbe_handle_t *hdl" "const char *be_name" "const char *snap" +.Pp +.Ft int +.Fn be_rename "libbe_handle_t *hdl" "const char *be_old" "const char *be_new" +.Pp +.Ft int +.Fn be_activate "libbe_handle_t *hdl" "const char *be_name" "bool temporary" +.Ft int +.Fn be_destroy "libbe_handle_t *hdl" "const char *be_name" "int options" +.Pp +.Ft void +.Fn be_nicenum "uint64_t num" "char *buf" "size_t bufsz" +.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 *hdl" "char *be_name" "char *mntpoint" "int flags" "char *result" +.Pp +.Ft int +.Fn be_mounted_at "libbe_handle_t *hdl" "const char *path" "nvlist_t *details" +.Pp +.Ft int +.Fn be_unmount "libbe_handle_t *hdl" "char *be_name" "int flags" +.Pp +.Ft int +.Fn libbe_errno "libbe_handle_t *hdl" +.Pp +.Ft const char * Ns +.Fn libbe_error_description "libbe_handle_t *hdl" +.Pp +.Ft void +.Fn libbe_print_on_error "libbe_handle_t *hdl" "bool doprint" +.Pp +.Ft int +.Fn be_root_concat "libbe_handle_t *hdl" "const char *be_name" "char *result" +.Pp +.Ft int +.Fn be_validate_name "libbe_handle_t *hdl" "const char *be_name" +.Pp +.Ft int +.Fn be_validate_snap "libbe_handle_t *hdl" "const char *snap" +.Pp +.Ft int +.Fn be_exists "libbe_handle_t *hdl" "char *be_name" +.Pp +.Ft int +.Fn be_export "libbe_handle_t *hdl" "const char *be_name" "int fd" +.Pp +.Ft int +.Fn be_import "libbe_handle_t *hdl" "const char *be_name" "int fd" +.Pp +.Ft int +.Fn be_prop_list_alloc "nvlist_t **prop_list" +.Pp +.Ft int +.Fn be_get_bootenv_props "libbe_handle_t *hdl" "nvlist_t *be_list" +.Pp +.Ft int +.Fn be_get_dataset_props "libbe_handle_t *hdl" "const char *ds_name" "nvlist_t *props" +.Pp +.Ft int +.Fn be_get_dataset_snapshots "libbe_handle_t *hdl" "const char *ds_name" "nvlist_t *snap_list" +.Pp +.Ft void +.Fn be_prop_list_free "nvlist_t *prop_list" +.Sh DESCRIPTION +.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. +.Nm +may be configured to print the corresponding error message to +.Dv stderr +when an error is encountered with +.Fn libbe_print_on_error . +.Pp +All functions returning an +.Vt int +return 0 on success, or a +.Nm +errno otherwise as described in +.Sx DIAGNOSTICS . +.Pp +The +.Fn libbe_init +function takes an optional BE root and initializes +.Nm , +returning a +.Vt "libbe_handle_t *" +on success, or +.Dv NULL +on error. +If a BE root is supplied, +.Nm +will only operate out of that pool and BE root. +An error may occur if: +.Bl -column +.It /boot and / are not on the same filesystem and device, +.It libzfs fails to initialize, +.It The system has not been properly booted with a ZFS boot +environment, +.It Nm +fails to open the zpool the active boot environment resides on, or +.It Nm +fails to locate the boot environment that is currently mounted. +.El +.Pp +The +.Fn libbe_close +function frees all resources previously acquired in +.Fn libbe_init , +invalidating the handle in the process. +.Pp +The +.Fn be_active_name +function returns the name of the currently booted boot environment. +This boot environment may not belong to the same BE root as the root libbe +is operating on! +.Pp +The +.Fn be_active_path +function returns the full path of the currently booted boot environment. +This boot environment may not belong to the same BE root as the root libbe +is operating on! +.Pp +The +.Fn be_nextboot_name +function returns the name of the boot environment that will be active on reboot. +.Pp +The +.Fn be_nextboot_path +function returns the full path of the boot environment that will be +active on reboot. +.Pp +The +.Fn be_root_path +function returns the boot environment root path. +.Pp +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. +.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. +.Pp +The +.Fn be_create_from_existing_snap +function creates a boot environment with the given name from an existing +snapshot. +.Pp +The +.Fn be_rename +function renames a boot environment without unmounting it, as if renamed with +the +.Fl u +argument were passed to +.Nm zfs +.Cm rename +.Pp +The +.Fn be_activate +function makes a boot environment active on the next boot. +If the +.Fa temporary +flag is set, then it will be active for the next boot only, as done by +.Xr zfsbootcfg 8 . +Next boot functionality is currently only available when booting in x86 BIOS +mode. +.Pp +The +.Fn be_destroy +function will recursively destroy the given boot environment. +It will not destroy a mounted boot environment unless the +.Dv BE_DESTROY_FORCE +option is set in +.Fa options . +If the +.Dv BE_DESTROY_ORIGIN +option is set in +.Fa options , +the +.Fn be_destroy +function will destroy the origin snapshot to this boot environment as well. +.Pp +The +.Fn be_nicenum +function will format +.Fa name +in a traditional ZFS humanized format, similar to +.Xr humanize_number 3 . +This function effectively proxies +.Fn zfs_nicenum +from libzfs. +.Pp +The +.Fn be_mount +function will mount the given boot environment. +If +.Fa mountpoint +is +.Dv NULL , +a mount point will be generated in +.Pa /tmp +using +.Xr mkdtemp 3 . +If +.Fa result +is not +.Dv NULL , +it should be large enough to accommodate +.Dv BE_MAXPATHLEN +including the null terminator. +the final mount point will be copied into it. +Setting the +.Dv BE_MNT_FORCE +flag will pass +.Dv MNT_FORCE +to the underlying +.Xr mount 2 +call. +.Pp +The +.Fn be_mounted_at +function will check if there is a boot environment mounted at the given +.Fa path . +If +.Fa details +is not +.Dv NULL , +it will be populated with a list of the mounted dataset's properties. +This list of properties matches the properties collected by +.Fn be_get_bootenv_props . +.Pp +The +.Fn be_unmount +function will unmount the given boot environment. +Setting the +.Dv BE_MNT_FORCE +flag will pass +.Dv MNT_FORCE +to the underlying +.Xr mount 2 +call. +.Pp +The +.Fn libbe_errno +function returns the +.Nm +errno. +.Pp +The +.Fn libbe_error_description +function returns a string description of the currently set +.Nm +errno. +.Pp +The +.Fn libbe_print_on_error +function will change whether or not +.Nm +prints the description of any encountered error to +.Dv stderr , +based on +.Fa doprint . +.Pp +The +.Fn be_root_concat +function will concatenate the boot environment root and the given boot +environment name into +.Fa result . +.Pp +The +.Fn be_validate_name +function will validate the given boot environment name for both length +restrictions as well as valid character restrictions. +This function does not set the internal library error state. +.Pp +The +.Fn be_validate_snap +function will validate the given snapshot name. +The snapshot must have a valid name, exist, and have a mountpoint of +.Pa / . +This function does not set the internal library error state. +.Pp +The +.Fn be_exists +function will check whether the given boot environment exists and has a +mountpoint of +.Pa / . +This function does not set the internal library error state, but will return +the appropriate error. +.Pp +The +.Fn be_export +function will export the given boot environment to the file specified by +.Fa fd . +A snapshot will be created of the boot environment prior to export. +.Pp +The +.Fn be_import +function will import the boot environment in the file specified by +.Fa fd , +and give it the name +.Fa be_name . +.Pp +The +.Fn be_prop_list_alloc +function allocates a property list suitable for passing to +.Fn be_get_bootenv_props , +.Fn be_get_dataset_props , +or +.Fn be_get_dataset_snapshots . +It should be freed later by +.Fa be_prop_list_free . +.Pp +The +.Fn be_get_bootenv_props +function will populate +.Fa be_list +with +.Vt nvpair_t +of boot environment names paired with an +.Vt nvlist_t +of their properties. +The following properties are currently collected as appropriate: +.Bl -column "Returned name" +.It Sy Returned name Ta Sy Description +.It dataset Ta - +.It name Ta Boot environment name +.It mounted Ta Current mount point +.It mountpoint Ta Do mountpoint Dc property +.It origin Ta Do origin Dc property +.It creation Ta Do creation Dc property +.It active Ta Currently booted environment +.It used Ta Literal Do used Dc property +.It usedds Ta Literal Do usedds Dc property +.It usedsnap Ta Literal Do usedrefreserv Dc property +.It referenced Ta Literal Do referenced Dc property +.It nextboot Ta Active on next boot +.El +.Pp +Only the +.Dq dataset , +.Dq name , +.Dq active , +and +.Dq nextboot +returned values will always be present. +All other properties may be omitted if not available. +.Pp +The +.Fn be_get_dataset_props +function will get properties of the specified dataset. +.Fa props +is populated directly with a list of the properties as returned by +.Fn be_get_bootenv_props . +.Pp +The +.Fn be_get_dataset_snapshots +function will retrieve all snapshots of the given dataset. +.Fa snap_list +will be populated with a list of +.Vt nvpair_t +exactly as specified by +.Fn be_get_bootenv_props . +.Pp +The +.Fn be_prop_list_free +function will free the property list. +.Sh DIAGNOSTICS +Upon error, one of the following values will be returned: +.Bl -dash -offset indent -compact +.It +BE_ERR_SUCCESS +.It +BE_ERR_INVALIDNAME +.It +BE_ERR_EXISTS +.It +BE_ERR_NOENT +.It +BE_ERR_PERMS +.It +BE_ERR_DESTROYACT +.It +BE_ERR_DESTROYMNT +.It +BE_ERR_BADPATH +.It +BE_ERR_PATHBUSY +.It +BE_ERR_PATHLEN +.It +BE_ERR_BADMOUNT +.It +BE_ERR_NOORIGIN +.It +BE_ERR_MOUNTED +.It +BE_ERR_NOMOUNT +.It +BE_ERR_ZFSOPEN +.It +BE_ERR_ZFSCLONE +.It +BE_ERR_IO +.It +BE_ERR_NOPOOL +.It +BE_ERR_NOMEM +.It +BE_ERR_UNKNOWN +.It +BE_ERR_INVORIGIN +.El +.Sh SEE ALSO +.Xr bectl 8 +.Sh HISTORY +.Nm +and its corresponding command, +.Xr bectl 8 , +were written as a 2017 Google Summer of Code project with Allan Jude serving +as a mentor. +Later work was done by +.An Kyle Evans Aq Mt kevans@FreeBSD.org . diff --git a/rescue/rescue/Makefile b/rescue/rescue/Makefile index 4717ac1a4370..110f37f3d65d 100644 --- a/rescue/rescue/Makefile +++ b/rescue/rescue/Makefile @@ -123,6 +123,7 @@ CRUNCH_PROGS_sbin+= routed rtquery .endif .if ${MK_ZFS} != "no" +CRUNCH_PROGS_sbin+= bectl CRUNCH_PROGS_sbin+= zfs CRUNCH_PROGS_sbin+= zpool CRUNCH_PROGS_usr.sbin+= zdb @@ -134,6 +135,7 @@ CRUNCH_PROGS_usr.sbin+= zdb CRUNCH_LIBS+= -l80211 -lalias -lcam -lncursesw -ldevstat -lipsec -llzma .if ${MK_ZFS} != "no" CRUNCH_LIBS+= -lavl -lzpool -lzfs_core -lzfs -lnvpair -lpthread -luutil -lumem +CRUNCH_LIBS+= -lbe .else # liblzma needs pthread CRUNCH_LIBS+= -lpthread diff --git a/sbin/Makefile b/sbin/Makefile index d75da5e84275..8033867e3616 100644 --- a/sbin/Makefile +++ b/sbin/Makefile @@ -87,6 +87,7 @@ SUBDIR.${MK_PF}+= pfctl SUBDIR.${MK_PF}+= pflogd SUBDIR.${MK_QUOTAS}+= quotacheck SUBDIR.${MK_ROUTED}+= routed +SUBDIR.${MK_ZFS}+= bectl SUBDIR.${MK_ZFS}+= zfsbootcfg SUBDIR.${MK_TESTS}+= tests diff --git a/sbin/bectl/Makefile b/sbin/bectl/Makefile new file mode 100644 index 000000000000..9fc66a097cd8 --- /dev/null +++ b/sbin/bectl/Makefile @@ -0,0 +1,24 @@ +# $FreeBSD$ + +.include + +PROG= bectl +MAN= bectl.8 + +SRCS= bectl.c bectl_jail.c bectl_list.c + +LIBADD+= be +LIBADD+= jail +LIBADD+= nvpair +LIBADD+= util + +CFLAGS+= -I${SRCTOP}/cddl/contrib/opensolaris/lib/libzfs/common +CFLAGS+= -I${SRCTOP}/sys/cddl/compat/opensolaris +CFLAGS+= -I${SRCTOP}/sys/cddl/contrib/opensolaris/uts/common + +CFLAGS+= -DNEED_SOLARIS_BOOLEAN + +HAS_TESTS= yes +SUBDIR.${MK_TESTS}+= tests + +.include diff --git a/sbin/bectl/bectl.8 b/sbin/bectl/bectl.8 new file mode 100644 index 000000000000..2308faf3d4d2 --- /dev/null +++ b/sbin/bectl/bectl.8 @@ -0,0 +1,310 @@ +.\" +.\" SPDX-License-Identifier: BSD-2-Clause-FreeBSD +.\" +.\" 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. +.\" +.\" +.\" @(#)be.1 +.\" +.\" $FreeBSD$ +.\" +.Dd April 3, 2019 +.Dt BECTL 8 +.Os +.Sh NAME +.Nm bectl +.Nd Utility to manage boot environments on ZFS +.Sh SYNOPSIS +.Nm +.Cm activate +.Op Fl t +.Ar beName +.Nm +.Cm create +.Op Fl r +.Op Fl e Brq Ar nonActiveBe | Ar beName Ns Cm @ Ns Ar snapshot +.Ar newBeName +.Nm +.Cm destroy +.Op Fl \&Fo +.Ar beName Ns Op Cm @ Ns Ar snapshot +.Nm +.Cm export +.Ar sourceBe +.Nm +.Cm import +.Ar targetBe +.Nm +.Cm jail +.Op Fl bU +.Oo Bro Fl o Ar key Ns Cm = Ns Ar value | Fl u Ar key Brc Oc Ns ... +.Ar beName +.Op Ar utility Op Ar argument ... +.Nm +.Cm list +.Op Fl aDHs +.Nm +.Cm mount +.Ar beName +.Op Ar mountpoint +.Nm +.Cm rename +.Ar origBeName +.Ar newBeName +.Nm +.Brq Cm ujail | unjail +.Brq Ar jailId | jailName +.Ar beName +.Nm +.Brq Cm umount | unmount +.Op Fl f +.Ar beName +.Pp +.Nm +.Op Fl h\&? +.Sh DESCRIPTION +The +.Nm +command is used to setup and interact with ZFS boot environments, which are +bootable clones of datasets. +.Pp +Boot environments +allow the system to be upgraded, while preserving the old system environment in +a separate ZFS dataset. +.Pp +The following commands are supported by +.Nm : +.Bl -tag -width activate +.It Xo +.Cm activate +.Op Fl t +.Ar beName +.Xc +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. +.It Xo +.Cm create +.Op Fl r +.Op Fl e Brq Ar nonActiveBe | Ar beName Ns Cm @ Ns Ar snapshot +.Ar newBeName +.Xc +Create a new boot environment named +.Ar newBeName . +.Pp +If the +.Fl r +flag is given, a recursive boot environment will be made. +.Pp +If the +.Fl e +flag is specified, the new environment will be cloned from the given +.Ar nonActiveBe +or +.Ar beName Ns Cm @ Ns Ar snapshot . +Otherwise, the new environment will be created from the currently booted environment. +.Pp +If +.Nm +is creating from another boot environment, a snapshot of that boot environment will be created to clone from. +.It Xo +.Cm destroy +.Op Fl \&Fo +.Ar beName Ns Op Cm @ Ns Ar snapshot +.Xc +Destroy the given +.Ar beName +boot environment or +.Ar beName Ns Cm @ Ns Ar snapshot +snapshot without confirmation, unlike in +.Xr beadm 1 . +Specifying +.Fl F +will automatically unmount without confirmation. +.Pp +By default, +.Nm +will warn that it is not destroying the origin of +.Ar beName . +The +.Fl o +flag may be specified to destroy the origin as well. +.It Cm export Ar sourceBe +Export +.Ar sourceBe +to +.Xr stdout 4 . +.Xr stdout 4 +must be piped or redirected to a file. +.It Cm import Ar targetBe +Import +.Ar targetBe +from +.Xr stdin 4 . +.It Xo +.Cm jail +.Op Fl bU +.Oo Bro Fl o Ar key Ns Cm = Ns Ar value | Fl u Ar key Brc Oc Ns ... +.Ar beName +.Op Ar utility Op Ar argument ... +.Xc +Create a jail of the given boot environment. +Multiple +.Fl o +and +.Fl u +arguments may be specified. +.Fl o +will set a jail parameter, and +.Fl u +will unset a jail parameter. +.Pp +By default, jails are created in interactive mode and +.Pa /bin/sh +is +executed within the jail. +If +.Ar utility +is specified, it will be executed instead of +.Pa /bin/sh . +The jail will be destroyed and the boot environment unmounted when the command +finishes executing, unless the +.Fl U +argument is specified. +.Pp +The +.Fl b +argument enables batch mode, thereby disabling interactive mode. +The +.Fl U +argument will be ignored in batch mode. +.Pp +The +.Va name , +.Va host.hostname , +and +.Va path +must be set, the default values are specified below. +.Pp +All +.Ar key Ns Cm = Ns Ar value +pairs are interpreted as jail parameters as described in +.Xr jail 8 . +The following default parameters are provided: +.Bl -column "allow.mount.devfs" "" +.It Va allow.mount Ta Cm true +.It Va allow.mount.devfs Ta Cm true +.It Va enforce_statfs Ta Cm 1 +.It Va name Ta Set to jail ID. +.It Va host.hostname Ta Va bootenv +.It Va path Ta Set to a path in Pa /tmp +generated by +.Xr libbe 3 . +.El +.Pp +All default parameters may be overwritten. +.It Cm list Op Fl aDHs +Display all boot environments. +The +.Em Active +field indicates whether the boot environment is active now +.Pq Em \&N ; +active on reboot +.Pq Em \&R ; +or both +.Pq Em \&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. +.It Cm mount Ar beName Op Ar mountpoint +Temporarily mount the boot environment. +Mount at the specified +.Ar mountpoint +if provided. +.It Cm rename Ar origBeName newBeName +Rename the given +.Ar origBeName +to the given +.Ar newBeName . +The boot environment will not be unmounted in order for this rename to occur. +.It Cm ujail Bro Ar jailId | jailName Brc Ar beName +.It Cm unjail Bro Ar jailId | jailName Brc Ar beName +Destroy the jail created from the given boot environment. +.It Xo +.Cm umount +.Op Fl f +.Ar beName +.Xc +.It Xo +.Cm unmount +.Op Fl f +.Ar beName +.Xc +Unmount the given boot environment, if it is mounted. +Specifying +.Fl f +will force the unmount if busy. +.El +.Pp +.Nm +prints usage information if +.Fl h +or +.Fl \&? +is specified. +.Sh EXAMPLES +.Bl -bullet +.It +To fill in with jail upgrade example when behavior is firm. +.El +.Sh SEE ALSO +.Xr beinstall.sh 1 , +.Xr libbe 3 , +.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 +.Nm +was written by +.An Kyle Kneitinger (kneitinger) Aq Mt kyle@kneit.in . +.Pp +.Xr beadm 1 +was written and is maintained by +.An Slawomir Wojciech Wojtczak (vermaden) Aq Mt vermaden@interia.pl . +.Pp +.An Bryan Drewery (bdrewery) Aq Mt bryan@shatow.net +wrote the original +.Xr beadm 1 +manual page that this one is derived from. diff --git a/sbin/bectl/bectl.c b/sbin/bectl/bectl.c new file mode 100644 index 000000000000..366fa048a893 --- /dev/null +++ b/sbin/bectl/bectl.c @@ -0,0 +1,561 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "bectl.h" + +static int bectl_cmd_activate(int argc, char *argv[]); +static int bectl_cmd_create(int argc, char *argv[]); +static int bectl_cmd_destroy(int argc, char *argv[]); +static int bectl_cmd_export(int argc, char *argv[]); +static int bectl_cmd_import(int argc, char *argv[]); +#if SOON +static int bectl_cmd_add(int argc, char *argv[]); +#endif +static int bectl_cmd_mount(int argc, char *argv[]); +static int bectl_cmd_rename(int argc, char *argv[]); +static int bectl_cmd_unmount(int argc, char *argv[]); + +libbe_handle_t *be; + +int +usage(bool explicit) +{ + FILE *fp; + + fp = explicit ? stdout : stderr; + fprintf(fp, "%s", + "usage:\tbectl {-h | -? | subcommand [args...]}\n" +#if SOON + "\tbectl add (path)*\n" +#endif + "\tbectl activate [-t] beName\n" + "\tbectl create [-r] [-e {nonActiveBe | beName@snapshot}] beName\n" + "\tbectl create [-r] beName@snapshot\n" + "\tbectl destroy [-F] {beName | beName@snapshot}\n" + "\tbectl export sourceBe\n" + "\tbectl import targetBe\n" + "\tbectl jail {-b | -U} [{-o key=value | -u key}]... " + "{jailID | jailName}\n" + "\t bootenv [utility [argument ...]]\n" + "\tbectl list [-DHas]\n" + "\tbectl mount beName [mountpoint]\n" + "\tbectl rename origBeName newBeName\n" + "\tbectl {ujail | unjail} {jailID | jailName} bootenv\n" + "\tbectl {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", bectl_cmd_activate }, + { "create", bectl_cmd_create }, + { "destroy", bectl_cmd_destroy }, + { "export", bectl_cmd_export }, + { "import", bectl_cmd_import }, +#if SOON + { "add", bectl_cmd_add }, +#endif + { "jail", bectl_cmd_jail }, + { "list", bectl_cmd_list }, + { "mount", bectl_cmd_mount }, + { "rename", bectl_cmd_rename }, + { "unjail", bectl_cmd_unjail }, + { "unmount", bectl_cmd_unmount }, +}; + +static int +get_cmd_index(const char *cmd, int *idx) +{ + int map_size; + + map_size = nitems(command_map); + for (int i = 0; i < map_size; ++i) { + if (strcmp(cmd, command_map[i].command) == 0) { + *idx = i; + return (0); + } + } + + return (1); +} + + +static int +bectl_cmd_activate(int argc, char *argv[]) +{ + int err, opt; + bool temp; + + temp = false; + while ((opt = getopt(argc, argv, "t")) != -1) { + switch (opt) { + case 't': + temp = true; + break; + default: + fprintf(stderr, "bectl activate: unknown option '-%c'\n", + optopt); + return (usage(false)); + } + } + + argc -= optind; + argv += optind; + + if (argc != 1) { + fprintf(stderr, "bectl activate: wrong number of arguments\n"); + return (usage(false)); + } + + + /* activate logic goes here */ + if ((err = be_activate(be, argv[0], temp)) != 0) + /* XXX 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 +bectl_cmd_create(int argc, char *argv[]) +{ + char *atpos, *bootenv, *snapname, *source; + int err, opt; + bool recursive; + + snapname = NULL; + recursive = false; + while ((opt = getopt(argc, argv, "e:r")) != -1) { + switch (opt) { + case 'e': + snapname = optarg; + break; + case 'r': + recursive = true; + break; + default: + fprintf(stderr, "bectl create: unknown option '-%c'\n", + optopt); + return (usage(false)); + } + } + + argc -= optind; + argv += optind; + + if (argc != 1) { + fprintf(stderr, "bectl create: wrong number of arguments\n"); + return (usage(false)); + } + + bootenv = *argv; + if ((atpos = strchr(bootenv, '@')) != NULL) { + /* + * This is the "create a snapshot variant". No new boot + * environment is to be created here. + */ + *atpos++ = '\0'; + err = be_snapshot(be, bootenv, atpos, recursive, NULL); + } else 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, 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 (atpos != NULL) + fprintf(stderr, + "failed to create a snapshot '%s' of '%s'\n", + atpos, bootenv); + else 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 +bectl_cmd_export(int argc, char *argv[]) +{ + char *bootenv; + + if (argc == 1) { + fprintf(stderr, "bectl export: missing boot environment name\n"); + return (usage(false)); + } + + if (argc > 2) { + fprintf(stderr, "bectl export: extra arguments provided\n"); + return (usage(false)); + } + + bootenv = argv[1]; + + if (isatty(STDOUT_FILENO)) { + fprintf(stderr, "bectl export: must redirect output\n"); + return (EX_USAGE); + } + + be_export(be, bootenv, STDOUT_FILENO); + + return (0); +} + + +static int +bectl_cmd_import(int argc, char *argv[]) +{ + char *bootenv; + int err; + + if (argc == 1) { + fprintf(stderr, "bectl import: missing boot environment name\n"); + return (usage(false)); + } + + if (argc > 2) { + fprintf(stderr, "bectl import: extra arguments provided\n"); + return (usage(false)); + } + + bootenv = argv[1]; + + if (isatty(STDIN_FILENO)) { + fprintf(stderr, "bectl import: input can not be from terminal\n"); + return (EX_USAGE); + } + + err = be_import(be, bootenv, STDIN_FILENO); + + return (err); +} + +#if SOON +static int +bectl_cmd_add(int argc, char *argv[]) +{ + + if (argc < 2) { + fprintf(stderr, "bectl 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]); + /* XXX TODO catch err */ + be_add_child(be, argv[i], true); + } + + return (0); +} +#endif + +static int +bectl_cmd_destroy(int argc, char *argv[]) +{ + nvlist_t *props; + char *origin, *target, targetds[BE_MAXPATHLEN]; + int err, flags, opt; + + flags = 0; + while ((opt = getopt(argc, argv, "Fo")) != -1) { + switch (opt) { + case 'F': + flags |= BE_DESTROY_FORCE; + break; + case 'o': + flags |= BE_DESTROY_ORIGIN; + break; + default: + fprintf(stderr, "bectl destroy: unknown option '-%c'\n", + optopt); + return (usage(false)); + } + } + + argc -= optind; + argv += optind; + + if (argc != 1) { + fprintf(stderr, "bectl destroy: wrong number of arguments\n"); + return (usage(false)); + } + + target = argv[0]; + + /* We'll emit a notice if there's an origin to be cleaned up */ + if ((flags & BE_DESTROY_ORIGIN) == 0 && strchr(target, '@') == NULL) { + if (be_root_concat(be, target, targetds) != 0) + goto destroy; + if (be_prop_list_alloc(&props) != 0) + goto destroy; + if (be_get_dataset_props(be, targetds, props) != 0) { + be_prop_list_free(props); + goto destroy; + } + if (nvlist_lookup_string(props, "origin", &origin) == 0) + fprintf(stderr, "bectl destroy: leaving origin '%s' intact\n", + origin); + be_prop_list_free(props); + } + +destroy: + err = be_destroy(be, target, flags); + + return (err); +} + +static int +bectl_cmd_mount(int argc, char *argv[]) +{ + char result_loc[BE_MAXPATHLEN]; + char *bootenv, *mountpoint; + int err, mntflags; + + /* XXX TODO: Allow shallow */ + mntflags = BE_MNT_DEEP; + if (argc < 2) { + fprintf(stderr, "bectl mount: missing argument(s)\n"); + return (usage(false)); + } + + if (argc > 3) { + fprintf(stderr, "bectl mount: too many arguments\n"); + return (usage(false)); + } + + bootenv = argv[1]; + mountpoint = ((argc == 3) ? argv[2] : NULL); + + err = be_mount(be, bootenv, mountpoint, mntflags, 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 +bectl_cmd_rename(int argc, char *argv[]) +{ + char *dest, *src; + int err; + + if (argc < 3) { + fprintf(stderr, "bectl rename: missing argument\n"); + return (usage(false)); + } + + if (argc > 3) { + fprintf(stderr, "bectl 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 +bectl_cmd_unmount(int argc, char *argv[]) +{ + char *bootenv, *cmd; + int err, flags, opt; + + /* 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, "bectl %s: unknown option '-%c'\n", + cmd, optopt); + return (usage(false)); + } + } + + argc -= optind; + argv += optind; + + if (argc != 1) { + fprintf(stderr, "bectl %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[]) +{ + const char *command; + char *root; + int command_index, rc; + + root = NULL; + if (argc < 2) + return (usage(false)); + + if (strcmp(argv[1], "-r") == 0) { + if (argc < 4) + return (usage(false)); + root = strdup(argv[2]); + command = argv[3]; + argc -= 3; + argv += 3; + } else { + command = argv[1]; + argc -= 1; + 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(root)) == NULL) + return (-1); + + libbe_print_on_error(be, true); + + rc = command_map[command_index].fn(argc, argv); + + libbe_close(be); + return (rc); +} diff --git a/sbin/bectl/bectl.h b/sbin/bectl/bectl.h new file mode 100644 index 000000000000..878af24f0cbb --- /dev/null +++ b/sbin/bectl/bectl.h @@ -0,0 +1,37 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2018 Kyle Evans + * + * 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 ``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 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$ + */ + +int usage(bool explicit); + +int bectl_cmd_jail(int argc, char *argv[]); +int bectl_cmd_unjail(int argc, char *argv[]); + +int bectl_cmd_list(int argc, char *argv[]); + +extern libbe_handle_t *be; diff --git a/sbin/bectl/bectl_jail.c b/sbin/bectl/bectl_jail.c new file mode 100644 index 000000000000..f3b2d0f53cb7 --- /dev/null +++ b/sbin/bectl/bectl_jail.c @@ -0,0 +1,486 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2018 Kyle Evans + * + * 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 ``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 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "bectl.h" + +#define MNTTYPE_ZFS 222 + +static void jailparam_add(const char *name, const char *val); +static int jailparam_del(const char *name); +static bool jailparam_addarg(char *arg); +static int jailparam_delarg(char *arg); + +static int bectl_search_jail_paths(const char *mnt); +static int bectl_locate_jail(const char *ident); +static int bectl_jail_cleanup(char *mountpoint, int jid); + +static char mnt_loc[BE_MAXPATHLEN]; +static nvlist_t *jailparams; + +static const char *disabled_params[] = { + "command", "exec.start", "nopersist", "persist", NULL +}; + + +static void +jailparam_add(const char *name, const char *val) +{ + + nvlist_add_string(jailparams, name, val); +} + +static int +jailparam_del(const char *name) +{ + + nvlist_remove_all(jailparams, name); + return (0); +} + +static bool +jailparam_addarg(char *arg) +{ + char *name, *val; + size_t i, len; + + if (arg == NULL) + return (false); + name = arg; + if ((val = strchr(arg, '=')) == NULL) { + fprintf(stderr, "bectl jail: malformed jail option '%s'\n", + arg); + return (false); + } + + *val++ = '\0'; + if (strcmp(name, "path") == 0) { + if (strlen(val) >= BE_MAXPATHLEN) { + fprintf(stderr, + "bectl jail: skipping too long path assignment '%s' (max length = %d)\n", + val, BE_MAXPATHLEN); + return (false); + } + strlcpy(mnt_loc, val, sizeof(mnt_loc)); + } + + for (i = 0; disabled_params[i] != NULL; i++) { + len = strlen(disabled_params[i]); + if (strncmp(disabled_params[i], name, len) == 0) { + fprintf(stderr, "invalid jail parameter: %s\n", name); + return (false); + } + } + + jailparam_add(name, val); + return (true); +} + +static int +jailparam_delarg(char *arg) +{ + char *name, *val; + + if (arg == NULL) + return (EINVAL); + name = arg; + if ((val = strchr(name, '=')) != NULL) + *val++ = '\0'; + + if (strcmp(name, "path") == 0) + *mnt_loc = '\0'; + return (jailparam_del(name)); +} + +static int +build_jailcmd(char ***argvp, bool interactive, int argc, char *argv[]) +{ + char *cmd, **jargv, *name, *val; + nvpair_t *nvp; + size_t i, iarg, nargv; + + cmd = NULL; + nvp = NULL; + iarg = i = 0; + if (nvlist_size(jailparams, &nargv, NV_ENCODE_NATIVE) != 0) + return (1); + + /* + * Number of args + "/usr/sbin/jail", "-c", and ending NULL. + * If interactive also include command. + */ + nargv += 3; + if (interactive) { + if (argc == 0) + nargv++; + else + nargv += argc; + } + + jargv = *argvp = calloc(nargv, sizeof(jargv)); + if (jargv == NULL) + err(2, "calloc"); + + jargv[iarg++] = strdup("/usr/sbin/jail"); + jargv[iarg++] = strdup("-c"); + while ((nvp = nvlist_next_nvpair(jailparams, nvp)) != NULL) { + name = nvpair_name(nvp); + if (nvpair_value_string(nvp, &val) != 0) + continue; + + if (asprintf(&jargv[iarg++], "%s=%s", name, val) < 0) + goto error; + } + if (interactive) { + if (argc < 1) + cmd = strdup("/bin/sh"); + else { + cmd = argv[0]; + argc--; + argv++; + } + + if (asprintf(&jargv[iarg++], "command=%s", cmd) < 0) { + goto error; + } + if (argc < 1) { + free(cmd); + cmd = NULL; + } + + for (; argc > 0; argc--) { + if (asprintf(&jargv[iarg++], "%s", argv[0]) < 0) + goto error; + argv++; + } + } + + return (0); + +error: + if (interactive && argc < 1) + free(cmd); + for (; i < iarg - 1; i++) { + free(jargv[i]); + } + free(jargv); + return (1); +} + +/* Remove jail and cleanup any non zfs mounts. */ +static int +bectl_jail_cleanup(char *mountpoint, int jid) +{ + struct statfs *mntbuf; + size_t i, searchlen, mntsize; + + if (jid >= 0 && jail_remove(jid) != 0) { + fprintf(stderr, "unable to remove jail"); + return (1); + } + + searchlen = strnlen(mountpoint, MAXPATHLEN); + mntsize = getmntinfo(&mntbuf, MNT_NOWAIT); + for (i = 0; i < mntsize; i++) { + if (strncmp(mountpoint, mntbuf[i].f_mntonname, searchlen) == 0 && + mntbuf[i].f_type != MNTTYPE_ZFS) { + + if (unmount(mntbuf[i].f_mntonname, 0) != 0) { + fprintf(stderr, "bectl jail: unable to unmount filesystem %s", + mntbuf[i].f_mntonname); + return (1); + } + } + } + + return (0); +} + +int +bectl_cmd_jail(int argc, char *argv[]) +{ + char *bootenv, **jargv, *mountpoint; + int i, jid, mntflags, opt, ret; + bool default_hostname, interactive, unjail; + pid_t pid; + + + /* XXX TODO: Allow shallow */ + mntflags = BE_MNT_DEEP; + default_hostname = interactive = unjail = true; + + if ((nvlist_alloc(&jailparams, NV_UNIQUE_NAME, 0)) != 0) { + fprintf(stderr, "nvlist_alloc() failed\n"); + return (1); + } + + jailparam_add("persist", "true"); + jailparam_add("allow.mount", "true"); + jailparam_add("allow.mount.devfs", "true"); + jailparam_add("enforce_statfs", "1"); + + while ((opt = getopt(argc, argv, "bo:Uu:")) != -1) { + switch (opt) { + case 'b': + interactive = false; + break; + case 'o': + if (jailparam_addarg(optarg)) { + /* + * optarg has been modified to null terminate + * at the assignment operator. + */ + if (strcmp(optarg, "host.hostname") == 0) + default_hostname = false; + } else { + return (1); + } + break; + case 'U': + unjail = false; + break; + case 'u': + if ((ret = jailparam_delarg(optarg)) == 0) { + if (strcmp(optarg, "host.hostname") == 0) + default_hostname = true; + } else if (ret != ENOENT) { + fprintf(stderr, + "bectl jail: error unsetting \"%s\"\n", + optarg); + return (ret); + } + break; + default: + fprintf(stderr, "bectl jail: unknown option '-%c'\n", + optopt); + return (usage(false)); + } + } + + argc -= optind; + argv += optind; + + if (argc < 1) { + fprintf(stderr, "bectl jail: missing boot environment name\n"); + return (usage(false)); + } + + bootenv = argv[0]; + argc--; + argv++; + + /* + * XXX TODO: if its already mounted, perhaps there should be a flag to + * indicate its okay to proceed?? + */ + if (*mnt_loc == '\0') + mountpoint = NULL; + else + mountpoint = mnt_loc; + if (be_mount(be, bootenv, mountpoint, mntflags, mnt_loc) != BE_ERR_SUCCESS) { + fprintf(stderr, "could not mount bootenv\n"); + return (1); + } + + if (default_hostname) + jailparam_add("host.hostname", bootenv); + + /* + * This is our indicator that path was not set by the user, so we'll use + * the path that libbe generated for us. + */ + if (mountpoint == NULL) { + jailparam_add("path", mnt_loc); + mountpoint = mnt_loc; + } + + if ((build_jailcmd(&jargv, interactive, argc, argv)) != 0) { + fprintf(stderr, "unable to build argument list for jail command\n"); + return (1); + } + + pid = fork(); + + switch (pid) { + case -1: + perror("fork"); + return (1); + case 0: + execv("/usr/sbin/jail", jargv); + fprintf(stderr, "bectl jail: failed to execute\n"); + default: + waitpid(pid, NULL, 0); + } + + for (i = 0; jargv[i] != NULL; i++) { + free(jargv[i]); + } + free(jargv); + + if (!interactive) + return (0); + + if (unjail) { + /* + * We're not checking the jail id result here because in the + * case of invalid param, or last command in jail was an error + * the jail will not exist upon exit. bectl_jail_cleanup will + * only jail_remove if the jid is >= 0. + */ + jid = bectl_locate_jail(bootenv); + bectl_jail_cleanup(mountpoint, jid); + be_unmount(be, bootenv, 0); + } + + return (0); +} + +static int +bectl_search_jail_paths(const char *mnt) +{ + int jid; + char lastjid[16]; + char jailpath[MAXPATHLEN]; + + /* jail_getv expects name/value strings */ + snprintf(lastjid, sizeof(lastjid), "%d", 0); + + while ((jid = jail_getv(0, "lastjid", lastjid, "path", &jailpath, + NULL)) != -1) { + + /* the jail we've been looking for */ + if (strcmp(jailpath, mnt) == 0) + return (jid); + + /* update lastjid and keep on looking */ + snprintf(lastjid, sizeof(lastjid), "%d", jid); + } + + return (-1); +} + +/* + * Locate a jail based on an arbitrary identifier. This may be either a name, + * a jid, or a BE name. Returns the jid or -1 on failure. + */ +static int +bectl_locate_jail(const char *ident) +{ + nvlist_t *belist, *props; + char *mnt; + int jid; + + /* Try the easy-match first */ + jid = jail_getid(ident); + if (jid != -1) + return (jid); + + /* Attempt to try it as a BE name, first */ + if (be_prop_list_alloc(&belist) != 0) + return (-1); + + if (be_get_bootenv_props(be, belist) != 0) + return (-1); + + if (nvlist_lookup_nvlist(belist, ident, &props) == 0) { + + /* path where a boot environment is mounted */ + if (nvlist_lookup_string(props, "mounted", &mnt) == 0) { + + /* looking for a jail that matches our bootenv path */ + jid = bectl_search_jail_paths(mnt); + be_prop_list_free(belist); + return (jid); + } + + be_prop_list_free(belist); + } + + return (-1); +} + +int +bectl_cmd_unjail(int argc, char *argv[]) +{ + char path[MAXPATHLEN]; + char *cmd, *name, *target; + int jid; + + /* Store alias used */ + cmd = argv[0]; + + if (argc != 2) { + fprintf(stderr, "bectl %s: wrong number of arguments\n", cmd); + return (usage(false)); + } + + target = argv[1]; + + /* Locate the jail */ + if ((jid = bectl_locate_jail(target)) == -1) { + fprintf(stderr, "bectl %s: failed to locate BE by '%s'\n", cmd, + target); + return (1); + } + + bzero(&path, MAXPATHLEN); + name = jail_getname(jid); + if (jail_getv(0, "name", name, "path", path, NULL) != jid) { + free(name); + fprintf(stderr, + "bectl %s: failed to get path for jail requested by '%s'\n", + cmd, target); + return (1); + } + + free(name); + + if (be_mounted_at(be, path, NULL) != 0) { + fprintf(stderr, "bectl %s: jail requested by '%s' not a BE\n", + cmd, target); + return (1); + } + + bectl_jail_cleanup(path, jid); + be_unmount(be, target, 0); + + return (0); +} diff --git a/sbin/bectl/bectl_list.c b/sbin/bectl/bectl_list.c new file mode 100644 index 000000000000..2276ac8a21a5 --- /dev/null +++ b/sbin/bectl/bectl_list.c @@ -0,0 +1,419 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2018 Kyle Evans + * + * 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 ``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 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include + +#include + +#include "bectl.h" + +struct printc { + int active_colsz_def; + int be_colsz; + int current_indent; + int mount_colsz; + int space_colsz; + bool script_fmt; + bool show_all_datasets; + bool show_snaps; + bool show_space; +}; + +static const char *get_origin_props(nvlist_t *dsprops, nvlist_t **originprops); +static void print_padding(const char *fval, int colsz, struct printc *pc); +static int print_snapshots(const char *dsname, struct printc *pc); +static void print_info(const char *name, nvlist_t *dsprops, struct printc *pc); +static void print_headers(nvlist_t *props, struct printc *pc); +static unsigned long long dataset_space(const char *oname); + +#define HEADER_BE "BE" +#define HEADER_BEPLUS "BE/Dataset/Snapshot" +#define HEADER_ACTIVE "Active" +#define HEADER_MOUNT "Mountpoint" +#define HEADER_SPACE "Space" +#define HEADER_CREATED "Created" + +/* Spaces */ +#define INDENT_INCREMENT 2 + +/* + * Given a set of dataset properties (for a BE dataset), populate originprops + * with the origin's properties. + */ +static const char * +get_origin_props(nvlist_t *dsprops, nvlist_t **originprops) +{ + char *propstr; + + if (nvlist_lookup_string(dsprops, "origin", &propstr) == 0) { + if (be_prop_list_alloc(originprops) != 0) { + fprintf(stderr, + "bectl list: failed to allocate origin prop nvlist\n"); + return (NULL); + } + if (be_get_dataset_props(be, propstr, *originprops) != 0) { + /* XXX TODO: Real errors */ + fprintf(stderr, + "bectl list: failed to fetch origin properties\n"); + return (NULL); + } + + return (propstr); + } + return (NULL); +} + +static void +print_padding(const char *fval, int colsz, struct printc *pc) +{ + + /* -H flag handling; all delimiters/padding are a single tab */ + if (pc->script_fmt) { + printf("\t"); + return; + } + + if (fval != NULL) + colsz -= strlen(fval); + printf("%*s ", colsz, ""); +} + +static unsigned long long +dataset_space(const char *oname) +{ + unsigned long long space; + char *dsname, *propstr, *sep; + nvlist_t *dsprops; + + space = 0; + dsname = strdup(oname); + if (dsname == NULL) + return (0); + + /* Truncate snapshot to dataset name, as needed */ + if ((sep = strchr(dsname, '@')) != NULL) + *sep = '\0'; + + if (be_prop_list_alloc(&dsprops) != 0) { + free(dsname); + return (0); + } + + if (be_get_dataset_props(be, dsname, dsprops) != 0) { + nvlist_free(dsprops); + free(dsname); + return (0); + } + + if (nvlist_lookup_string(dsprops, "used", &propstr) == 0) + space = strtoull(propstr, NULL, 10); + + nvlist_free(dsprops); + free(dsname); + return (space); +} + +static int +print_snapshots(const char *dsname, struct printc *pc) +{ + nvpair_t *cur; + nvlist_t *props, *sprops; + + if (be_prop_list_alloc(&props) != 0) { + fprintf(stderr, "bectl list: failed to allocate snapshot nvlist\n"); + return (1); + } + if (be_get_dataset_snapshots(be, dsname, props) != 0) { + fprintf(stderr, "bectl list: failed to fetch boot ds snapshots\n"); + return (1); + } + for (cur = nvlist_next_nvpair(props, NULL); cur != NULL; + cur = nvlist_next_nvpair(props, cur)) { + nvpair_value_nvlist(cur, &sprops); + print_info(nvpair_name(cur), sprops, pc); + } + return (0); +} + +static void +print_info(const char *name, nvlist_t *dsprops, struct printc *pc) +{ +#define BUFSZ 64 + char buf[BUFSZ]; + unsigned long long ctimenum, space; + nvlist_t *originprops; + const char *oname; + char *dsname, *propstr; + int active_colsz; + boolean_t active_now, active_reboot; + + dsname = NULL; + originprops = NULL; + printf("%*s%s", pc->current_indent, "", name); + nvlist_lookup_string(dsprops, "dataset", &dsname); + + /* Recurse at the base level if we're breaking info down */ + if (pc->current_indent == 0 && (pc->show_all_datasets || + pc->show_snaps)) { + printf("\n"); + if (dsname == NULL) + /* XXX TODO: Error? */ + return; + /* + * Whether we're dealing with -a or -s, we'll always print the + * dataset name/information followed by its origin. For -s, we + * additionally iterate through all snapshots of this boot + * environment and also print their information. + */ + pc->current_indent += INDENT_INCREMENT; + print_info(dsname, dsprops, pc); + pc->current_indent += INDENT_INCREMENT; + if ((oname = get_origin_props(dsprops, &originprops)) != NULL) { + print_info(oname, originprops, pc); + nvlist_free(originprops); + } + + /* Back up a level; snapshots at the same level as dataset */ + pc->current_indent -= INDENT_INCREMENT; + if (pc->show_snaps) + print_snapshots(dsname, pc); + pc->current_indent = 0; + return; + } else + print_padding(name, pc->be_colsz - pc->current_indent, pc); + + active_colsz = pc->active_colsz_def; + if (nvlist_lookup_boolean_value(dsprops, "active", + &active_now) == 0 && active_now) { + printf("N"); + active_colsz--; + } + if (nvlist_lookup_boolean_value(dsprops, "nextboot", + &active_reboot) == 0 && active_reboot) { + printf("R"); + active_colsz--; + } + if (active_colsz == pc->active_colsz_def) { + printf("-"); + active_colsz--; + } + print_padding(NULL, active_colsz, pc); + if (nvlist_lookup_string(dsprops, "mounted", &propstr) == 0) { + printf("%s", propstr); + print_padding(propstr, pc->mount_colsz, pc); + } else { + printf("%s", "-"); + print_padding("-", pc->mount_colsz, pc); + } + + oname = get_origin_props(dsprops, &originprops); + if (nvlist_lookup_string(dsprops, "used", &propstr) == 0) { + /* + * The space used column is some composition of: + * - The "used" property of the dataset + * - The "used" property of the origin snapshot (not -a or -s) + * - The "used" property of the origin dataset (-D flag only) + * + * The -D flag is ignored if -a or -s are specified. + */ + space = strtoull(propstr, NULL, 10); + + if (!pc->show_all_datasets && !pc->show_snaps && + originprops != NULL && + nvlist_lookup_string(originprops, "used", &propstr) == 0) + space += strtoull(propstr, NULL, 10); + + if (pc->show_space && oname != NULL) + space += dataset_space(oname); + + /* Alas, there's more to it,. */ + be_nicenum(space, buf, 6); + printf("%s", buf); + print_padding(buf, pc->space_colsz, pc); + } else { + printf("-"); + print_padding("-", pc->space_colsz, pc); + } + + if (nvlist_lookup_string(dsprops, "creation", &propstr) == 0) { + ctimenum = strtoull(propstr, NULL, 10); + strftime(buf, BUFSZ, "%Y-%m-%d %H:%M", + localtime((time_t *)&ctimenum)); + printf("%s", buf); + } + + printf("\n"); + if (originprops != NULL) + be_prop_list_free(originprops); +#undef BUFSZ +} + +static void +print_headers(nvlist_t *props, struct printc *pc) +{ + const char *chosen_be_header; + nvpair_t *cur; + nvlist_t *dsprops; + char *propstr; + size_t be_maxcol; + + if (pc->show_all_datasets || pc->show_snaps) + chosen_be_header = HEADER_BEPLUS; + else + chosen_be_header = HEADER_BE; + be_maxcol = strlen(chosen_be_header); + for (cur = nvlist_next_nvpair(props, NULL); cur != NULL; + cur = nvlist_next_nvpair(props, cur)) { + be_maxcol = MAX(be_maxcol, strlen(nvpair_name(cur))); + if (!pc->show_all_datasets && !pc->show_snaps) + continue; + nvpair_value_nvlist(cur, &dsprops); + if (nvlist_lookup_string(dsprops, "dataset", &propstr) != 0) + continue; + be_maxcol = MAX(be_maxcol, strlen(propstr) + INDENT_INCREMENT); + if (nvlist_lookup_string(dsprops, "origin", &propstr) != 0) + continue; + be_maxcol = MAX(be_maxcol, + strlen(propstr) + INDENT_INCREMENT * 2); + } + + pc->be_colsz = be_maxcol; + pc->active_colsz_def = strlen(HEADER_ACTIVE); + pc->mount_colsz = strlen(HEADER_MOUNT); + pc->space_colsz = strlen(HEADER_SPACE); + printf("%*s %s %s %s %s\n", -pc->be_colsz, chosen_be_header, + HEADER_ACTIVE, HEADER_MOUNT, HEADER_SPACE, HEADER_CREATED); + + /* + * All other invocations in which we aren't using the default header + * will produce quite a bit of input. Throw an extra blank line after + * the header to make it look nicer. + */ + if (strcmp(chosen_be_header, HEADER_BE) != 0) + printf("\n"); +} + +int +bectl_cmd_list(int argc, char *argv[]) +{ + struct printc pc; + nvpair_t *cur; + nvlist_t *dsprops, *props; + int opt, printed; + boolean_t active_now, active_reboot; + + props = NULL; + printed = 0; + bzero(&pc, sizeof(pc)); + while ((opt = getopt(argc, argv, "aDHs")) != -1) { + switch (opt) { + case 'a': + pc.show_all_datasets = true; + break; + case 'D': + pc.show_space = true; + break; + case 'H': + pc.script_fmt = true; + break; + case 's': + pc.show_snaps = true; + break; + default: + fprintf(stderr, "bectl list: unknown option '-%c'\n", + optopt); + return (usage(false)); + } + } + + argc -= optind; + + if (argc != 0) { + fprintf(stderr, "bectl list: extra argument provided\n"); + return (usage(false)); + } + + if (be_prop_list_alloc(&props) != 0) { + fprintf(stderr, "bectl list: failed to allocate prop nvlist\n"); + return (1); + } + if (be_get_bootenv_props(be, props) != 0) { + /* XXX TODO: Real errors */ + fprintf(stderr, "bectl list: failed to fetch boot environments\n"); + return (1); + } + + /* Force -D off if either -a or -s are specified */ + if (pc.show_all_datasets || pc.show_snaps) + pc.show_space = false; + if (!pc.script_fmt) + print_headers(props, &pc); + /* Do a first pass to print active and next active first */ + for (cur = nvlist_next_nvpair(props, NULL); cur != NULL; + cur = nvlist_next_nvpair(props, cur)) { + nvpair_value_nvlist(cur, &dsprops); + active_now = active_reboot = false; + + nvlist_lookup_boolean_value(dsprops, "active", &active_now); + nvlist_lookup_boolean_value(dsprops, "nextboot", + &active_reboot); + if (!active_now && !active_reboot) + continue; + if (printed > 0 && (pc.show_all_datasets || pc.show_snaps)) + printf("\n"); + print_info(nvpair_name(cur), dsprops, &pc); + printed++; + } + + /* Now pull everything else */ + for (cur = nvlist_next_nvpair(props, NULL); cur != NULL; + cur = nvlist_next_nvpair(props, cur)) { + nvpair_value_nvlist(cur, &dsprops); + active_now = active_reboot = false; + + nvlist_lookup_boolean_value(dsprops, "active", &active_now); + nvlist_lookup_boolean_value(dsprops, "nextboot", + &active_reboot); + if (active_now || active_reboot) + continue; + if (printed > 0 && (pc.show_all_datasets || pc.show_snaps)) + printf("\n"); + print_info(nvpair_name(cur), dsprops, &pc); + printed++; + } + be_prop_list_free(props); + + return (0); +} + diff --git a/sbin/bectl/tests/Makefile b/sbin/bectl/tests/Makefile new file mode 100644 index 000000000000..10e23336d345 --- /dev/null +++ b/sbin/bectl/tests/Makefile @@ -0,0 +1,7 @@ +# $FreeBSD$ + +PACKAGE= tests + +ATF_TESTS_SH+= bectl_test + +.include diff --git a/sbin/bectl/tests/bectl_test.sh b/sbin/bectl/tests/bectl_test.sh new file mode 100755 index 000000000000..d1aa7fe8c18a --- /dev/null +++ b/sbin/bectl/tests/bectl_test.sh @@ -0,0 +1,368 @@ +# +# SPDX-License-Identifier: BSD-2-Clause-FreeBSD +# +# Copyright (c) 2018 Kyle Evans +# +# 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$ + +ZPOOL_NAME_FILE=zpool_name +get_zpool_name() +{ + cat $ZPOOL_NAME_FILE +} +make_zpool_name() +{ + mktemp -u bectl_test_XXXXXX > $ZPOOL_NAME_FILE + get_zpool_name +} + +# Establishes a bectl_create zpool that can be used for some light testing; contains +# a 'default' BE and not much else. +bectl_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 +} +bectl_create_deep_setup() +{ + zpool=$1 + disk=$2 + mnt=$3 + + # Sanity check to make sure `make_zpool_name` succeeded + atf_check test -n "$zpool" + + bectl_create_setup ${zpool} ${disk} ${mnt} + atf_check mkdir -p ${root} + atf_check -o ignore bectl -r ${zpool}/ROOT mount default ${root} + atf_check mkdir -p ${root}/usr + atf_check zfs create -o mountpoint=/usr -o canmount=noauto \ + ${zpool}/ROOT/default/usr + atf_check -o ignore bectl -r ${zpool}/ROOT umount default +} + +bectl_cleanup() +{ + zpool=$1 + 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 +} + +atf_test_case bectl_create cleanup +bectl_create_head() +{ + + atf_set "descr" "Check the various forms of bectl create" + atf_set "require.user" root +} +bectl_create_body() +{ + cwd=$(realpath .) + zpool=$(make_zpool_name) + disk=${cwd}/disk.img + mount=${cwd}/mnt + + bectl_create_setup ${zpool} ${disk} ${mount} + # Test standard creation, creation of a snapshot, and creation from a + # snapshot. + atf_check bectl -r ${zpool}/ROOT create -e default default2 + atf_check bectl -r ${zpool}/ROOT create default2@test_snap + atf_check bectl -r ${zpool}/ROOT create -e default2@test_snap default3 +} +bectl_create_cleanup() +{ + bectl_cleanup $(get_zpool_name) +} + +atf_test_case bectl_destroy cleanup +bectl_destroy_head() +{ + + atf_set "descr" "Check bectl destroy" + atf_set "require.user" root +} +bectl_destroy_body() +{ + cwd=$(realpath .) + zpool=$(make_zpool_name) + disk=${cwd}/disk.img + mount=${cwd}/mnt + root=${mount}/root + + bectl_create_setup ${zpool} ${disk} ${mount} + atf_check bectl -r ${zpool}/ROOT create -e default default2 + atf_check -o not-empty zfs get mountpoint ${zpool}/ROOT/default2 + atf_check -e ignore bectl -r ${zpool}/ROOT destroy default2 + atf_check -e not-empty -s not-exit:0 zfs get mountpoint ${zpool}/ROOT/default2 + + # Test origin snapshot deletion when the snapshot to be destroyed + # belongs to a mounted dataset, see PR 236043. + atf_check mkdir -p ${root} + atf_check -o not-empty bectl -r ${zpool}/ROOT mount default ${root} + atf_check bectl -r ${zpool}/ROOT create -e default default3 + atf_check bectl -r ${zpool}/ROOT destroy -o default3 + atf_check bectl -r ${zpool}/ROOT unmount default +} +bectl_destroy_cleanup() +{ + + bectl_cleanup $(get_zpool_name) +} + +atf_test_case bectl_export_import cleanup +bectl_export_import_head() +{ + + atf_set "descr" "Check bectl export and import" + atf_set "require.user" root +} +bectl_export_import_body() +{ + cwd=$(realpath .) + zpool=$(make_zpool_name) + disk=${cwd}/disk.img + mount=${cwd}/mnt + + bectl_create_setup ${zpool} ${disk} ${mount} + atf_check -o save:exported bectl -r ${zpool}/ROOT export default + atf_check -x "bectl -r ${zpool}/ROOT import default2 < exported" + atf_check -o not-empty zfs get mountpoint ${zpool}/ROOT/default2 + atf_check -e ignore bectl -r ${zpool}/ROOT destroy default2 + atf_check -e not-empty -s not-exit:0 zfs get mountpoint \ + ${zpool}/ROOT/default2 +} +bectl_export_import_cleanup() +{ + + bectl_cleanup $(get_zpool_name) +} + +atf_test_case bectl_list cleanup +bectl_list_head() +{ + + atf_set "descr" "Check bectl list" + atf_set "require.user" root +} +bectl_list_body() +{ + cwd=$(realpath .) + zpool=$(make_zpool_name) + disk=${cwd}/disk.img + mount=${cwd}/mnt + + bectl_create_setup ${zpool} ${disk} ${mount} + # Test the list functionality, including that BEs come and go away + # as they're created and destroyed. Creation and destruction tests + # use the 'zfs' utility to verify that they're actually created, so + # these are just light tests that 'list' is picking them up. + atf_check -o save:list.out bectl -r ${zpool}/ROOT list + atf_check -o not-empty grep 'default' list.out + atf_check bectl -r ${zpool}/ROOT create -e default default2 + atf_check -o save:list.out bectl -r ${zpool}/ROOT list + atf_check -o not-empty grep 'default2' list.out + atf_check -e ignore bectl -r ${zpool}/ROOT destroy default2 + atf_check -o save:list.out bectl -r ${zpool}/ROOT list + atf_check -s not-exit:0 grep 'default2' list.out + # XXX TODO: Formatting checks +} +bectl_list_cleanup() +{ + + bectl_cleanup $(get_zpool_name) +} + +atf_test_case bectl_mount cleanup +bectl_mount_head() +{ + + atf_set "descr" "Check bectl mount/unmount" + atf_set "require.user" root +} +bectl_mount_body() +{ + cwd=$(realpath .) + zpool=$(make_zpool_name) + disk=${cwd}/disk.img + mount=${cwd}/mnt + root=${mount}/root + + bectl_create_deep_setup ${zpool} ${disk} ${mount} + atf_check mkdir -p ${root} + # Test unmount first... + atf_check -o not-empty bectl -r ${zpool}/ROOT mount default ${root} + atf_check -o not-empty -x "mount | grep '^${zpool}/ROOT/default'" + atf_check bectl -r ${zpool}/ROOT unmount default + atf_check -s not-exit:0 -x "mount | grep '^${zpool}/ROOT/default'" + # Then umount! + atf_check -o not-empty bectl -r ${zpool}/ROOT mount default ${root} + atf_check -o not-empty -x "mount | grep '^${zpool}/ROOT/default'" + atf_check bectl -r ${zpool}/ROOT umount default + atf_check -s not-exit:0 -x "mount | grep '^${zpool}/ROOT/default'" +} +bectl_mount_cleanup() +{ + + bectl_cleanup $(get_zpool_name) +} + +atf_test_case bectl_rename cleanup +bectl_rename_head() +{ + + atf_set "descr" "Check bectl rename" + atf_set "require.user" root +} +bectl_rename_body() +{ + cwd=$(realpath .) + zpool=$(make_zpool_name) + disk=${cwd}/disk.img + mount=${cwd}/mnt + + bectl_create_setup ${zpool} ${disk} ${mount} + atf_check bectl -r ${zpool}/ROOT rename default default2 + atf_check -o not-empty zfs get mountpoint ${zpool}/ROOT/default2 + atf_check -e not-empty -s not-exit:0 zfs get mountpoint \ + ${zpool}/ROOT/default +} +bectl_rename_cleanup() +{ + + bectl_cleanup $(get_zpool_name) +} + +atf_test_case bectl_jail cleanup +bectl_jail_head() +{ + + atf_set "descr" "Check bectl rename" + atf_set "require.user" root +} +bectl_jail_body() +{ + cwd=$(realpath .) + zpool=$(make_zpool_name) + disk=${cwd}/disk.img + mount=${cwd}/mnt + root=${mount}/root + + if [ ! -f /rescue/rescue ]; then + atf_skip "This test requires a rescue binary" + fi + bectl_create_deep_setup ${zpool} ${disk} ${mount} + # Prepare our minimal BE... plop a rescue binary into it + atf_check mkdir -p ${root} + atf_check -o ignore bectl -r ${zpool}/ROOT mount default ${root} + atf_check mkdir -p ${root}/rescue + atf_check cp /rescue/rescue ${root}/rescue/rescue + atf_check bectl -r ${zpool}/ROOT umount default + + # Prepare a second boot environment + atf_check -o empty -s exit:0 bectl -r ${zpool}/ROOT create -e default target + + # When a jail name is not explicit, it should match the jail id. + atf_check -o empty -s exit:0 bectl -r ${zpool}/ROOT jail -b -o jid=233637 default + atf_check -o inline:"233637\n" -s exit:0 -x "jls -j 233637 name" + atf_check -o empty -s exit:0 bectl -r ${zpool}/ROOT unjail default + + # Basic command-mode tests, with and without jail cleanup + atf_check -o inline:"rescue\nusr\n" bectl -r ${zpool}/ROOT \ + jail default /rescue/rescue ls -1 + atf_check -o inline:"rescue\nusr\n" bectl -r ${zpool}/ROOT \ + jail -Uo path=${root} default /rescue/rescue ls -1 + atf_check [ -f ${root}/rescue/rescue ] + atf_check bectl -r ${zpool}/ROOT ujail default + + # Batch mode tests + atf_check bectl -r ${zpool}/ROOT jail -bo path=${root} default + atf_check -o not-empty -x "jls | grep -F \"${root}\"" + atf_check bectl -r ${zpool}/ROOT ujail default + atf_check -s not-exit:0 -x "jls | grep -F \"${root}\"" + # 'unjail' naming + atf_check bectl -r ${zpool}/ROOT jail -b default + atf_check bectl -r ${zpool}/ROOT unjail default + atf_check -s not-exit:0 -x "jls | grep -F \"${root}\"" + # 'unjail' by BE name. Force bectl to lookup jail id by the BE name. + atf_check -o empty -s exit:0 bectl -r ${zpool}/ROOT jail -b default + atf_check -o empty -s exit:0 bectl -r ${zpool}/ROOT jail -b -o name=bectl_test target + atf_check -o empty -s exit:0 bectl -r ${zpool}/ROOT unjail target + atf_check -o empty -s exit:0 bectl -r ${zpool}/ROOT unjail default + # cannot unjail an unjailed BE (by either command name) + atf_check -e ignore -s not-exit:0 bectl -r ${zpool}/ROOT ujail default + atf_check -e ignore -s not-exit:0 bectl -r ${zpool}/ROOT unjail default + + # set+unset + atf_check bectl -r ${zpool}/ROOT jail -b -o path=${root} -u path default + # Ensure that it didn't mount at ${root} + atf_check -s not-exit:0 -x "mount | grep -F '${root}'" + atf_check bectl -r ${zpool}/ROOT ujail default +} + +# If a test has failed, it's possible that the boot environment hasn't +# been 'unjail'ed. We want to remove the jail before 'bectl_cleanup' +# attempts to destroy the zpool. +bectl_jail_cleanup() +{ + for bootenv in "default" "target"; do + # mountpoint of the boot environment + mountpoint="$(bectl -r bectl_test/ROOT list -H | grep ${bootenv} | awk '{print $3}')" + + # see if any jail paths match the boot environment mountpoint + jailid="$(jls | grep ${mountpoint} | awk '{print $1}')" + + if [ -z "$jailid" ]; then + continue; + fi + jail -r ${jailid} + done; + + bectl_cleanup $(get_zpool_name) +} + +atf_init_test_cases() +{ + atf_add_test_case bectl_create + atf_add_test_case bectl_destroy + atf_add_test_case bectl_export_import + atf_add_test_case bectl_list + atf_add_test_case bectl_mount + atf_add_test_case bectl_rename + atf_add_test_case bectl_jail +} diff --git a/share/mk/bsd.libnames.mk b/share/mk/bsd.libnames.mk index e09b2a6214f9..a0d394b576b2 100644 --- a/share/mk/bsd.libnames.mk +++ b/share/mk/bsd.libnames.mk @@ -21,6 +21,7 @@ LIBASN1?= ${DESTDIR}${LIBDIR_BASE}/libasn1.a LIBATM?= ${DESTDIR}${LIBDIR_BASE}/libatm.a LIBAUDITD?= ${DESTDIR}${LIBDIR_BASE}/libauditd.a LIBAVL?= ${DESTDIR}${LIBDIR_BASE}/libavl.a +LIBBE?= ${DESTDIR}${LIBDIR_BASE}/libbe.a LIBBEGEMOT?= ${DESTDIR}${LIBDIR_BASE}/libbegemot.a LIBBLACKLIST?= ${DESTDIR}${LIBDIR_BASE}/libblacklist.a LIBBLUETOOTH?= ${DESTDIR}${LIBDIR_BASE}/libbluetooth.a diff --git a/share/mk/src.libnames.mk b/share/mk/src.libnames.mk index f227353372bb..c6b6aa7dbae9 100644 --- a/share/mk/src.libnames.mk +++ b/share/mk/src.libnames.mk @@ -59,6 +59,7 @@ _LIBRARIES= \ asn1 \ auditd \ avl \ + be \ begemot \ bluetooth \ bsdxml \ @@ -326,6 +327,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" @@ -466,6 +468,8 @@ LIBBSNMPTOOLS?= ${LIBBSNMPTOOLSDIR}/libbsnmptools.a LIBAMUDIR= ${OBJTOP}/usr.sbin/amd/libamu LIBAMU?= ${LIBAMUDIR}/libamu.a +LIBBE?= ${LIBBEDIR}/libbe.a + # Define a directory for each library. This is useful for adding -L in when # not using a --sysroot or for meta mode bootstrapping when there is no # Makefile.depend. These are sorted by directory. diff --git a/tools/build/mk/OptionalObsoleteFiles.inc b/tools/build/mk/OptionalObsoleteFiles.inc index b9432c3a75ba..d83e50cb7c0c 100644 --- a/tools/build/mk/OptionalObsoleteFiles.inc +++ b/tools/build/mk/OptionalObsoleteFiles.inc @@ -1330,7 +1330,7 @@ OLD_FILES+=usr/bin/ztest OLD_FILES+=usr/lib/libbe.a OLD_FILES+=usr/lib/libbe_p.a OLD_FILES+=usr/lib/libbe.so -OLD_LIBS+=usr/lib/libbe.so.1 +OLD_LIBS+=lib/libbe.so.1 OLD_FILES+=usr/lib/libzfs.a OLD_LIBS+=usr/lib/libzfs.so OLD_FILES+=usr/lib/libzfs_core.a