From 939d63f00d924e3594e6194dc87fff7ccedfe050 Mon Sep 17 00:00:00 2001 From: kevans Date: Sun, 6 Jan 2019 02:13:16 +0000 Subject: [PATCH] MFC r342362-r342363: config(8) duplicate option handling r342362: config(8): Allow duplicate options to be specified config(8)'s option handling has been written to allow duplicate options; if the value changes, then the latest value is used and an informative message is printed to stderr like so: /usr/src/sys/amd64/conf/TEST: option "VERBOSE_SYSINIT" redefined from 0 to 1 Currently, this is only a possibility for cpu types, MAXUSERS, and MACHINE_ARCH. Anything else duplicated in a config file will use the first value set and error about duplicated options on subsequent appearances, which is arguably unfriendly since one could specify: include GENERIC nooptions VERBOSE_SYSINIT options VERBOSE_SYSINIT to redefine the value later anyways. Reported by: mmacy r342363: config(8): Remove all instances of an option when opting out Quick follow-up to r342362: options can appear multiple times now, so clean up all of them as needed. For non-OPTIONS options, this has no effect since they're already de-duplicated. --- Makefile.inc1 | 10 +- ObsoleteFiles.inc | 2 + contrib/mdocml/lib.in | 1 + lib/Makefile | 1 + lib/libbe/Makefile | 31 + lib/libbe/be.c | 1010 ++++++++++++++++++++++ lib/libbe/be.h | 131 +++ lib/libbe/be_access.c | 215 +++++ lib/libbe/be_error.c | 133 +++ lib/libbe/be_impl.h | 72 ++ lib/libbe/be_info.c | 318 +++++++ lib/libbe/libbe.3 | 469 ++++++++++ sbin/Makefile | 1 + sbin/bectl/Makefile | 19 + sbin/bectl/bectl.8 | 287 ++++++ sbin/bectl/bectl.c | 525 +++++++++++ sbin/bectl/bectl.h | 37 + sbin/bectl/bectl_jail.c | 416 +++++++++ sbin/bectl/bectl_list.c | 419 +++++++++ share/mk/bsd.libnames.mk | 1 + share/mk/src.libnames.mk | 4 + sys/amd64/conf/GENERIC | 41 + sys/amd64/conf/MINIMAL | 3 + tools/build/mk/OptionalObsoleteFiles.inc | 2 +- usr.sbin/config/config.y | 37 +- 25 files changed, 4167 insertions(+), 18 deletions(-) create mode 100644 lib/libbe/Makefile create mode 100644 lib/libbe/be.c create mode 100644 lib/libbe/be.h create mode 100644 lib/libbe/be_access.c create mode 100644 lib/libbe/be_error.c create mode 100644 lib/libbe/be_impl.h create mode 100644 lib/libbe/be_info.c create mode 100644 lib/libbe/libbe.3 create mode 100644 sbin/bectl/Makefile create mode 100644 sbin/bectl/bectl.8 create mode 100644 sbin/bectl/bectl.c create mode 100644 sbin/bectl/bectl.h create mode 100644 sbin/bectl/bectl_jail.c create mode 100644 sbin/bectl/bectl_list.c diff --git a/Makefile.inc1 b/Makefile.inc1 index 0d0e0b639d7..2205ece00f2 100644 --- a/Makefile.inc1 +++ b/Makefile.inc1 @@ -2144,7 +2144,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} \ @@ -2213,7 +2213,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/ObsoleteFiles.inc b/ObsoleteFiles.inc index 536f447f510..bcc99de7541 100644 --- a/ObsoleteFiles.inc +++ b/ObsoleteFiles.inc @@ -38,6 +38,8 @@ # xargs -n1 | sort | uniq -d; # done +# 20181115: libbe(3) SHLIBDIR fixed to reflect correct location +OLD_LIBS+=usr/lib/libbe.so.1 # 20180812: move of libmlx5.so.1 and libibverbs.so.1 OLD_LIBS+=usr/lib/libmlx5.so.1 OLD_LIBS+=usr/lib/libibverbs.so.1 diff --git a/contrib/mdocml/lib.in b/contrib/mdocml/lib.in index 76691ecf01c..646c9a2407f 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/lib/Makefile b/lib/Makefile index a2287b1f10a..5d22d0ac009 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -289,6 +289,7 @@ _libproc= libproc _librtld_db= librtld_db .endif SUBDIR.${MK_OFED}+= ofed +SUBDIR.${MK_ZFS}+= libbe .if ${MK_OPENSSL} != "no" _libmp= libmp diff --git a/lib/libbe/Makefile b/lib/libbe/Makefile new file mode 100644 index 00000000000..5fada3204fb --- /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 00000000000..540fe44ea1e --- /dev/null +++ b/lib/libbe/be.c @@ -0,0 +1,1010 @@ +/*- + * 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 "be.h" +#include "be_impl.h" + +#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 + +/* + * 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(zfs_handle_t *chkds, void *data) +{ + libbe_handle_t *lbh; + char *mntpoint; + + lbh = (libbe_handle_t *)data; + if (lbh == NULL) + return (1); + + mntpoint = NULL; + if (zfs_is_mounted(chkds, &mntpoint) && strcmp(mntpoint, "/") == 0) { + strlcpy(lbh->rootfs, zfs_get_name(chkds), sizeof(lbh->rootfs)); + free(mntpoint); + return (1); + } else if(mntpoint != NULL) + free(mntpoint); + + return (0); +} + +/* + * Initializes the libbe context to operate in the root boot environment + * dataset, for example, zroot/ROOT. + */ +libbe_handle_t * +libbe_init(void) +{ + struct stat sb; + dev_t root_dev, boot_dev; + libbe_handle_t *lbh; + zfs_handle_t *rootds; + char *poolname, *pos; + int pnamelen; + + lbh = NULL; + poolname = pos = NULL; + rootds = NULL; + + /* Verify that /boot and / are mounted on the same filesystem */ + /* TODO: use errno here?? */ + if (stat("/", &sb) != 0) + goto err; + + root_dev = sb.st_dev; + + if (stat("/boot", &sb) != 0) + goto err; + + boot_dev = sb.st_dev; + + if (root_dev != boot_dev) { + fprintf(stderr, "/ and /boot not on same device, quitting\n"); + goto err; + } + + if ((lbh = calloc(1, sizeof(libbe_handle_t))) == NULL) + goto err; + + if ((lbh->lzh = libzfs_init()) == NULL) + goto err; + + /* Obtain path to boot environment root */ + if ((kenv(KENV_GET, "zfs_be_root", lbh->root, + sizeof(lbh->root))) == -1) + goto err; + + /* Remove leading 'zfs:' if present, otherwise use value as-is */ + if (strcmp(lbh->root, "zfs:") == 0) + strlcpy(lbh->root, strchr(lbh->root, ':') + sizeof(char), + 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; + + /* Obtain path to boot environment rootfs (currently booted) */ + /* XXX Get dataset mounted at / by kenv/GUID from mountroot? */ + if ((rootds = zfs_open(lbh->lzh, lbh->root, ZFS_TYPE_DATASET)) == NULL) + goto err; + + zfs_iter_filesystems(rootds, be_locate_rootfs, lbh); + zfs_close(rootds); + rootds = NULL; + if (*lbh->rootfs == '\0') + goto err; + + 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) +{ + int err; + + if ((err = zfs_iter_children(zfs_hdl, be_destroy_cb, data)) != 0) + return (err); + if ((err = zfs_destroy(zfs_hdl, false)) != 0) + return (err); + 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 + */ +int +be_destroy(libbe_handle_t *lbh, const char *name, int options) +{ + zfs_handle_t *fs; + char path[BE_MAXPATHLEN]; + char *p; + int err, force, mounted; + + p = path; + force = options & BE_DESTROY_FORCE; + + be_root_concat(lbh, name, path); + + if (strchr(name, '@') == NULL) { + if (!zfs_dataset_exists(lbh->lzh, path, ZFS_TYPE_FILESYSTEM)) + return (set_error(lbh, BE_ERR_NOENT)); + + if (strcmp(path, lbh->rootfs) == 0) + return (set_error(lbh, BE_ERR_DESTROYACT)); + + fs = zfs_open(lbh->lzh, p, ZFS_TYPE_FILESYSTEM); + } else { + + if (!zfs_dataset_exists(lbh->lzh, path, ZFS_TYPE_SNAPSHOT)) + return (set_error(lbh, BE_ERR_NOENT)); + + fs = zfs_open(lbh->lzh, p, ZFS_TYPE_SNAPSHOT); + } + + if (fs == NULL) + return (set_error(lbh, BE_ERR_ZFSOPEN)); + + /* Check if mounted, unmount if force is specified */ + if ((mounted = zfs_is_mounted(fs, NULL)) != 0) { + if (force) + zfs_unmount(fs, NULL, 0); + else + return (set_error(lbh, BE_ERR_DESTROYMNT)); + } + + if ((err = be_destroy_cb(fs, NULL)) != 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)); + } + + return (0); +} + + +int +be_snapshot(libbe_handle_t *lbh, const char *source, const char *snap_name, + bool recursive, char *result) +{ + char buf[BE_MAXPATHLEN]; + time_t rawtime; + int len, 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 { + time(&rawtime); + len = strlen(buf); + strftime(buf + len, sizeof(buf) - len, + "@%F-%T", localtime(&rawtime)); + 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]; + + 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); + + nvlist_add_string(dccb->props, zfs_prop_to_name(prop), (char *)pval); + + return (ZPROP_CONT); +} + +static int +be_deep_clone(zfs_handle_t *ds, void *data) +{ + int err; + char be_path[BE_MAXPATHLEN]; + char snap_path[BE_MAXPATHLEN]; + 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.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) +{ + zfs_handle_t *zfs_hdl; + char buf[BE_MAXPATHLEN]; + char *delim_pos; + int err = BE_ERR_SUCCESS; + + if (strlen(snap_name) >= BE_MAXPATHLEN) + return (BE_ERR_PATHLEN); + + if (!zfs_dataset_exists(lbh->lzh, snap_name, + ZFS_TYPE_SNAPSHOT)) + return (BE_ERR_NOENT); + + strlcpy(buf, snap_name, sizeof(buf)); + + /* Find the base filesystem of the snapshot */ + if ((delim_pos = strchr(buf, '@')) == NULL) + return (BE_ERR_INVALIDNAME); + *delim_pos = '\0'; + + if ((zfs_hdl = + zfs_open(lbh->lzh, buf, ZFS_TYPE_DATASET)) == NULL) + return (BE_ERR_NOORIGIN); + + if ((err = zfs_prop_get(zfs_hdl, ZFS_PROP_MOUNTPOINT, buf, + sizeof(buf), NULL, NULL, 0, 1)) != 0) + err = BE_ERR_BADMOUNT; + + if ((err != 0) && (strncmp(buf, "/", sizeof(buf)) != 0)) + err = BE_ERR_BADMOUNT; + + zfs_close(zfs_hdl); + + return (err); +} + + +/* + * Idempotently appends the name argument to the root boot environment path + * and copies the resulting string into the result buffer (which is assumed + * to be at least BE_MAXPATHLEN characters long. Returns BE_ERR_SUCCESS upon + * success, BE_ERR_PATHLEN if the resulting path is longer than BE_MAXPATHLEN, + * or BE_ERR_INVALIDNAME if the name is a path that does not begin with + * zfs_be_root. Does not set internal library error state. + */ +int +be_root_concat(libbe_handle_t *lbh, 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) +{ + for (int i = 0; *name; i++) { + char c = *(name++); + if (isalnum(c) || (c == '-') || (c == '_') || (c == '.')) + continue; + return (BE_ERR_INVALIDNAME); + } + + /* + * 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); + 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]; + time_t rawtime; + nvlist_t *props; + zfs_handle_t *zfs; + int err, len; + char nbuf[24]; + + /* + * We don't need this to be incredibly random, just unique enough that + * it won't conflict with an existing dataset name. Chopping time + * down to 32 bits is probably good enough for this. + */ + snprintf(nbuf, 24, "tmp%u", + (uint32_t)(time(NULL) & 0xFFFFFFFF)); + if ((err = be_root_concat(lbh, nbuf, buf)) != 0) + /* + * Technically this is our problem, but we try to use short + * enough names that we won't run into problems except in + * worst-case BE root approaching MAXPATHLEN. + */ + return (set_error(lbh, BE_ERR_PATHLEN)); + + time(&rawtime); + len = strlen(buf); + strftime(buf + len, sizeof(buf) - len, "@%F-%T", localtime(&rawtime)); + + if ((err = lzc_receive(buf, NULL, NULL, false, fd)) != 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_SNAPSHOT)) == 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", "/"); + + be_root_concat(lbh, bootenv, buf); + + err = zfs_clone(zfs, buf, props); + zfs_close(zfs); + nvlist_free(props); + + if (err != 0) + return (set_error(lbh, BE_ERR_UNKNOWN)); + + /* + * Finally, we open up the dataset we just cloned the snapshot so that + * we may promote it. This is necessary in order to clean up the ghost + * snapshot that doesn't need to be seen after the operation is + * complete. + */ + if ((zfs = zfs_open(lbh->lzh, buf, ZFS_TYPE_DATASET)) == NULL) + return (set_error(lbh, BE_ERR_ZFSOPEN)); + + err = zfs_promote(zfs); + zfs_close(zfs); + + if (err != 0) + return (set_error(lbh, BE_ERR_UNKNOWN)); + + /* Clean up the temporary snapshot */ + return (be_destroy(lbh, nbuf, 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 00000000000..ab43e9ba564 --- /dev/null +++ b/lib/libbe/be.h @@ -0,0 +1,131 @@ +/*- + * 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_error_t; + + +/* Library handling functions: be.c */ +libbe_handle_t *libbe_init(void); +void libbe_close(libbe_handle_t *); + +/* Bootenv information functions: be_info.c */ +const char *be_active_name(libbe_handle_t *); +const char *be_active_path(libbe_handle_t *); +const char *be_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_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 00000000000..328326f7147 --- /dev/null +++ b/lib/libbe/be_access.c @@ -0,0 +1,215 @@ +/*- + * 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" + +struct be_mountcheck_info { + const char *path; + char *name; +}; + +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); +} + +/* + * 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; + int err; + + 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)); + + 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)); + } + + char opt = '\0'; + if ((err = zmount(be, (mountpoint == NULL) ? mnt_temp : mountpoint, + mntflags, __DECONST(char *, MNTTYPE_ZFS), NULL, 0, &opt, 1)) != 0) { + switch (errno) { + case ENAMETOOLONG: + return (set_error(lbh, BE_ERR_PATHLEN)); + case ELOOP: + case ENOENT: + case ENOTDIR: + return (set_error(lbh, BE_ERR_BADPATH)); + case EPERM: + return (set_error(lbh, BE_ERR_PERMS)); + case EBUSY: + return (set_error(lbh, BE_ERR_PATHBUSY)); + default: + return (set_error(lbh, BE_ERR_UNKNOWN)); + } + } + + 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, mntflags; + char be[BE_MAXPATHLEN]; + struct statfs *mntbuf; + int mntsize; + char *mntpath; + + if ((err = be_root_concat(lbh, bootenv, be)) != 0) + return (set_error(lbh, err)); + + if ((mntsize = getmntinfo(&mntbuf, MNT_NOWAIT)) == 0) { + if (errno == EIO) + return (set_error(lbh, BE_ERR_IO)); + return (set_error(lbh, BE_ERR_NOMOUNT)); + } + + mntpath = NULL; + for (int i = 0; i < mntsize; ++i) { + /* 0x000000de is the type number of zfs */ + if (mntbuf[i].f_type != 0x000000de) + continue; + + if (strcmp(mntbuf[i].f_mntfromname, be) == 0) { + mntpath = mntbuf[i].f_mntonname; + break; + } + } + + if (mntpath == NULL) + return (set_error(lbh, BE_ERR_NOMOUNT)); + + mntflags = (flags & BE_MNT_FORCE) ? MNT_FORCE : 0; + + if ((err = unmount(mntpath, mntflags)) != 0) { + switch (errno) { + case ENAMETOOLONG: + return (set_error(lbh, BE_ERR_PATHLEN)); + case ELOOP: + case ENOENT: + case ENOTDIR: + return (set_error(lbh, BE_ERR_BADPATH)); + case EPERM: + return (set_error(lbh, BE_ERR_PERMS)); + case EBUSY: + return (set_error(lbh, BE_ERR_PATHBUSY)); + default: + return (set_error(lbh, BE_ERR_UNKNOWN)); + } + } + + return (set_error(lbh, BE_ERR_SUCCESS)); +} diff --git a/lib/libbe/be_error.c b/lib/libbe/be_error.c new file mode 100644 index 00000000000..746d873f8a3 --- /dev/null +++ b/lib/libbe/be_error.c @@ -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. + */ + +#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"); + + 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 00000000000..99c783a1a64 --- /dev/null +++ b/lib/libbe/be_impl.h @@ -0,0 +1,72 @@ +/*- + * 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 { + libzfs_handle_t *lzh; + zpool_handle_t *active_phandle; + char root[BE_MAXPATHLEN]; + char rootfs[BE_MAXPATHLEN]; + char bootfs[BE_MAXPATHLEN]; + 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 { + 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 *); + +/* 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 00000000000..dba718cbac8 --- /dev/null +++ b/lib/libbe/be_info.c @@ -0,0 +1,318 @@ +/*- + * 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) +{ + + return (strrchr(lbh->rootfs, '/') + sizeof(char)); +} + + +/* + * 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) +{ + + return (strrchr(lbh->bootfs, '/') + sizeof(char)); +} + + +/* + * 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]; + nvlist_t *dsprops; + char *mntpoint; + bool valid; + + be_root_concat(lbh, be, buf); + + if (!zfs_dataset_exists(lbh->lzh, buf, ZFS_TYPE_DATASET)) + return (BE_ERR_NOENT); + + /* Also check if it's mounted at / */ + if (be_prop_list_alloc(&dsprops) != 0) + return (BE_ERR_UNKNOWN); + + if (be_get_dataset_props(lbh, buf, dsprops) != 0) { + nvlist_free(dsprops); + return (BE_ERR_UNKNOWN); + } + + if (nvlist_lookup_string(dsprops, "mountpoint", &mntpoint) == 0) { + valid = (strcmp(mntpoint, "/") == 0); + nvlist_free(dsprops); + return (valid ? BE_ERR_SUCCESS : BE_ERR_BADMOUNT); + } + + nvlist_free(dsprops); + return (BE_ERR_BADMOUNT); +} diff --git a/lib/libbe/libbe.3 b/lib/libbe/libbe.3 new file mode 100644 index 00000000000..9e3dbef23df --- /dev/null +++ b/lib/libbe/libbe.3 @@ -0,0 +1,469 @@ +.\" +.\" 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 August 31, 2018 +.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 void +.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 initializes +.Nm , +returning a +.Vt "libbe_handle_t *" +on success, or +.Dv NULL +on error. +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, +.Pp +The +.Fn be_active_path +function returns the full path of the currently booted boot environment. +.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 . +.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. +.\" TODO: make each entry on its own line. +.Bd -ragged -offset indent +BE_ERR_SUCCESS, +BE_ERR_INVALIDNAME, +BE_ERR_EXISTS, +BE_ERR_NOENT, +BE_ERR_PERMS, +BE_ERR_DESTROYACT, +BE_ERR_DESTROYMNT, +BE_ERR_BADPATH, +BE_ERR_PATHBUSY, +BE_ERR_PATHLEN, +BE_ERR_BADMOUNT, +BE_ERR_NOORIGIN, +BE_ERR_MOUNTED, +BE_ERR_NOMOUNT, +BE_ERR_ZFSOPEN, +BE_ERR_ZFSCLONE, +BE_ERR_IO, +BE_ERR_NOPOOL, +BE_ERR_NOMEM, +BE_ERR_UNKNOWN +.Ed +.Sh SEE ALSO +.Xr be 1 +.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/sbin/Makefile b/sbin/Makefile index d75da5e8427..8033867e361 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 00000000000..0014f964261 --- /dev/null +++ b/sbin/bectl/Makefile @@ -0,0 +1,19 @@ +# $FreeBSD$ + +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 + +.include diff --git a/sbin/bectl/bectl.8 b/sbin/bectl/bectl.8 new file mode 100644 index 00000000000..56ff28c71e1 --- /dev/null +++ b/sbin/bectl/bectl.8 @@ -0,0 +1,287 @@ +.\" +.\" 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 August 24, 2018 +.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 | beName@snapshot +.Ar beName +.Nm +.Cm create +.Op Fl r +.Ar beName@snapshot +.Nm +.Cm destroy +.Op Fl F +.Brq Ar beName | beName@snapshot +.Nm +.Cm export +.Ar sourceBe +.Nm +.Cm import +.Ar targetBe +.Nm +.Cm jail +.Brq Fl b | Fl U +.Oo Bro Fl o Ar key Ns = Ns Ar value | Fl u Ar key Brc Oc Ns ... +.Brq Ar jailID | jailName +.Ar bootenv +.Op Ar utility Op Ar argument ... +.Nm +.Cm list +.Op Fl DHas +.Nm +.Cm mount +.Ar beName +.Op mountpoint +.Nm +.Cm rename +.Ar origBeName +.Ar newBeName +.Nm +.Brq Cm ujail | unjail +.Brq Ar jailID | jailName +.Ar bootenv +.Nm +.Brq Cm umount | unmount +.Op Fl f +.Ar beName +.Sh DESCRIPTION +The +.Nm +command is used to setup and interact with ZFS boot environments, which are +bootable clones of datasets. +.Pp +.Em Boot Environments +allows the system to be upgraded, while preserving the old system environment in +a separate ZFS dataset. +.Sh COMMANDS +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 | beName@snapshot +.Ar beName +.Xc +Creates a new boot environment named +.Ar beName . +If the +.Fl e +argument is specified, the new environment will be cloned from the given +.Brq Ar nonActiveBe | Ar beName@snapshot . +If the +.Fl r +flag is given, a recursive boot environment will be made. +.It Xo +.Cm create +.Op Fl r +.Ar beName@snapshot +.Xc +Creates a snapshot of the existing boot environment named +.Ar beName . +If the +.Fl r +flag is given, a recursive boot environment will be made. +.It Xo +.Cm destroy +.Op Fl F +.Brq Ar beName | beName@snapshot +.Xc +Destroys the given +.Ar beName +boot environment or +.Ar beName@snapshot +snapshot without confirmation, unlike in +.Nm beadm . +Specifying +.Fl F +will automatically unmount without confirmation. +.It Cm export Ar sourceBe +Export +.Ar sourceBe +to +.Dv stdout . +.Dv stdout +must be piped or redirected to a file. +.It Cm import Ar targetBe +Import +.Ar targetBe +from +.Dv stdin . +.It Xo +.Cm jail +.Brq Fl b | Fl U +.Oo Bro Fl o Ar key Ns = Ns Ar value | Fl u Ar key Brc Oc Ns ... +.Brq Ar jailID | jailName +.Ao Ar bootenv Ac +.Op Ar utility Op Ar argument ... +.Xc +Creates 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 +may not actually be unset. +Attempts to unset any of these will revert them to the default values specified +below, if they have been overwritten by +.Fl o . +.Pp +All +.Ar key Ns = 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 Va bootenv +.It Va host.hostname Ta Va bootenv +.It Va path Ta Set to a path in /tmp generated by +.Xr libbe 3 . +.El +.Pp +All default parameters may be overwritten. +.It Cm list Op Fl DHas +Displays all boot environments. +The Active field indicates whether the boot environment is active now (N); +active on reboot (R); or both (NR). +.Pp +If +.Fl a +is used, display all datasets. +If +.Fl D +is used, display the full space usage for each boot environment, assuming all +other boot environments were destroyed. +The +.Fl H +option is used for scripting. +It does not print headers and separate fields by a single tab instead of +arbitrary white space. +If +.Fl s +is used, display all snapshots as well. +.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 +Renames 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 unjail Brq Ar jailID | jailName | beName +Destroys the jail created from the given boot environment. +.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 +.Sh EXAMPLES +.Bl -bullet +.It +To fill in with jail upgrade example when behavior is firm. +.El +.Sh SEE ALSO +.Xr jail 8 , +.Xr zfs 8 , +.Xr zpool 8 +.Sh HISTORY +.Nm +is based on +.Nm beadm +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 +.Nm beadm +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 +.Nm beadm +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 00000000000..89a90e4af02 --- /dev/null +++ b/sbin/bectl/bectl.c @@ -0,0 +1,525 @@ +/*- + * 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, + "usage:\tbectl {-h | -? | subcommand [args...]}\n" + "\tbectl activate [-t] beName\n" + "\tbectl create [-e {nonActiveBe | -e beName@snapshot}] beName\n" + "\tbectl create beName@snapshot\n" + "\tbectl destroy [-F] {beName | beName@snapshot}\n" + "\tbectl export sourceBe\n" + "\tbectl import targetBe\n" +#if SOON + "\tbectl add (path)*\n" +#endif + "\tbectl jail [{-b | -U}] [{-o key=value | -u key}]... bootenv [utility [argument ...]]\n" + "\tbectl list [-a] [-D] [-H] [-s]\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[]) +{ + char *target; + int opt, err; + bool force; + + force = false; + while ((opt = getopt(argc, argv, "F")) != -1) { + switch (opt) { + case 'F': + force = true; + 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]; + + err = be_destroy(be, target, force); + + return (err); +} + +static int +bectl_cmd_mount(int argc, char *argv[]) +{ + char result_loc[BE_MAXPATHLEN]; + char *bootenv, *mountpoint; + int err; + + 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, 0, result_loc); + + switch (err) { + case BE_ERR_SUCCESS: + printf("successfully mounted %s at %s\n", bootenv, result_loc); + break; + default: + fprintf(stderr, + (argc == 3) ? "failed to mount bootenv %s at %s\n" : + "failed to mount bootenv %s at temporary path %s\n", + bootenv, mountpoint); + } + + return (err); +} + + +static int +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; + int command_index, rc; + + if (argc < 2) + return (usage(false)); + + command = argv[1]; + + /* Handle command aliases */ + if (strcmp(command, "umount") == 0) + command = "unmount"; + + if (strcmp(command, "ujail") == 0) + command = "unjail"; + + if ((strcmp(command, "-?") == 0) || (strcmp(command, "-h") == 0)) + return (usage(true)); + + if (get_cmd_index(command, &command_index)) { + fprintf(stderr, "unknown command: %s\n", command); + return (usage(false)); + } + + + if ((be = libbe_init()) == NULL) + return (-1); + + libbe_print_on_error(be, true); + + /* XXX TODO: can be simplified if offset by 2 instead of one */ + rc = command_map[command_index].fn(argc-1, argv+1); + + libbe_close(be); + return (rc); +} diff --git a/sbin/bectl/bectl.h b/sbin/bectl/bectl.h new file mode 100644 index 00000000000..878af24f0cb --- /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 00000000000..c60cec5fa68 --- /dev/null +++ b/sbin/bectl/bectl_jail.c @@ -0,0 +1,416 @@ +/*- + * 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" + +static void jailparam_grow(void); +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); + +/* We'll start with 8 parameters initially and grow as needed. */ +#define INIT_PARAMCOUNT 8 + +static struct jailparam *jp; +static int jpcnt; +static int jpused; +static char mnt_loc[BE_MAXPATHLEN]; + +static void +jailparam_grow(void) +{ + + jpcnt *= 2; + jp = realloc(jp, jpcnt * sizeof(*jp)); + if (jp == NULL) + err(2, "realloc"); +} + +static void +jailparam_add(const char *name, const char *val) +{ + int i; + + for (i = 0; i < jpused; ++i) { + if (strcmp(name, jp[i].jp_name) == 0) + break; + } + + if (i < jpused) + jailparam_free(&jp[i], 1); + else if (jpused == jpcnt) + /* The next slot isn't allocated yet */ + jailparam_grow(); + + if (jailparam_init(&jp[i], name) != 0) + return; + if (jailparam_import(&jp[i], val) != 0) + return; + ++jpused; +} + +static int +jailparam_del(const char *name) +{ + int i; + char *val; + + for (i = 0; i < jpused; ++i) { + if (strcmp(name, jp[i].jp_name) == 0) + break; + } + + if (i == jpused) + return (ENOENT); + + for (; i < jpused - 1; ++i) { + val = jailparam_export(&jp[i + 1]); + + jailparam_free(&jp[i], 1); + /* + * Given the context, the following will really only fail if + * they can't allocate the copy of the name or value. + */ + if (jailparam_init(&jp[i], jp[i + 1].jp_name) != 0) { + free(val); + return (ENOMEM); + } + if (jailparam_import(&jp[i], val) != 0) { + jailparam_free(&jp[i], 1); + free(val); + return (ENOMEM); + } + free(val); + } + + jailparam_free(&jp[i], 1); + --jpused; + return (0); +} + +static bool +jailparam_addarg(char *arg) +{ + char *name, *val; + + 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)); + } + 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)); +} + +int +bectl_cmd_jail(int argc, char *argv[]) +{ + char *bootenv, *mountpoint; + int jid, opt, ret; + bool default_hostname, default_name, interactive, unjail; + pid_t pid; + + default_hostname = default_name = interactive = unjail = true; + jpcnt = INIT_PARAMCOUNT; + jp = malloc(jpcnt * sizeof(*jp)); + if (jp == NULL) + err(2, "malloc"); + + 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, "name") == 0) + default_name = false; + if (strcmp(optarg, "host.hostname") == 0) + default_hostname = false; + } + break; + case 'U': + unjail = false; + break; + case 'u': + if ((ret = jailparam_delarg(optarg)) == 0) { + if (strcmp(optarg, "name") == 0) + default_name = true; + 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; + + /* struct jail be_jail = { 0 }; */ + if (argc < 1) { + fprintf(stderr, "bectl jail: missing boot environment name\n"); + return (usage(false)); + } + + bootenv = argv[0]; + + /* + * 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, 0, mnt_loc) != BE_ERR_SUCCESS) { + fprintf(stderr, "could not mount bootenv\n"); + return (1); + } + + if (default_name) + jailparam_add("name", bootenv); + 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); + /* Create the jail for now, attach later as-needed */ + jid = jailparam_set(jp, jpused, JAIL_CREATE); + if (jid == -1) { + fprintf(stderr, "unable to create jail. error: %d\n", errno); + return (1); + } + + jailparam_free(jp, jpused); + free(jp); + + /* We're not interactive, nothing more to do here. */ + if (!interactive) + return (0); + + pid = fork(); + switch(pid) { + case -1: + perror("fork"); + return (1); + case 0: + jail_attach(jid); + /* We're attached within the jail... good bye! */ + chdir("/"); + if (argc > 1) + execve(argv[1], &argv[1], NULL); + else + execl("/bin/sh", "/bin/sh", NULL); + fprintf(stderr, "bectl jail: failed to execute %s\n", + (argc > 1 ? argv[1] : "/bin/sh")); + _exit(1); + default: + /* Wait for the child to get back, see if we need to unjail */ + waitpid(pid, NULL, 0); + } + + if (unjail) { + jail_remove(jid); + unmount(mnt_loc, 0); + } + + return (0); +} + +static int +bectl_search_jail_paths(const char *mnt) +{ + char jailpath[MAXPATHLEN]; + int jid; + + jid = 0; + (void)mnt; + while ((jid = jail_getv(0, "lastjid", &jid, "path", &jailpath, + NULL)) != -1) { + if (strcmp(jailpath, mnt) == 0) + return (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) { + /* We'll attempt to resolve the jid by way of mountpoint */ + if (nvlist_lookup_string(props, "mountpoint", &mnt) == 0) { + 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); + } + + jail_remove(jid); + unmount(path, 0); + + return (0); +} diff --git a/sbin/bectl/bectl_list.c b/sbin/bectl/bectl_list.c new file mode 100644 index 00000000000..2276ac8a21a --- /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/share/mk/bsd.libnames.mk b/share/mk/bsd.libnames.mk index e09b2a6214f..a0d394b576b 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 f227353372b..c6b6aa7dbae 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/sys/amd64/conf/GENERIC b/sys/amd64/conf/GENERIC index ea1652ff45b..8ba1e4848b3 100644 --- a/sys/amd64/conf/GENERIC +++ b/sys/amd64/conf/GENERIC @@ -21,6 +21,47 @@ cpu HAMMER ident GENERIC +envvar static_env_included=1 +envvar boot_serial=1 +envvar console=userboot +envvar currdev=disk0s2a: +envvar vfs.root.mountfrom=ufs:vtbd0s2a +envvar loader_env.disabled=0 +envvar vfs.mountroot.timeout=3 +envvar hint.acpi_throttle.0.disabled=1 +envvar hint.atkbd.0.at=atkbdc +envvar hint.atkbd.0.irq=1 +envvar hint.atkbdc.0.at=isa +envvar hint.atkbdc.0.port=0x060 +envvar hint.atrtc.0.at=isa +envvar hint.atrtc.0.irq=8 +envvar hint.atrtc.0.port=0x70 +envvar hint.attimer.0.at=isa +envvar hint.attimer.0.irq=0 +envvar hint.attimer.0.port=0x40 +envvar hint.fd.0.at=fdc0 +envvar hint.fd.0.drive=0 +envvar hint.fd.1.at=fdc0 +envvar hint.fd.1.drive=1 +envvar hint.fdc.0.at=isa +envvar hint.fdc.0.drq=2 +envvar hint.fdc.0.irq=6 +envvar hint.fdc.0.port=0x3F0 +envvar hint.p4tcc.0.disabled=1 +envvar hint.ppc.0.at=isa +envvar hint.ppc.0.irq=7 +envvar hint.psm.0.at=atkbdc +envvar hint.psm.0.irq=12 +envvar hint.sc.0.at=isa +envvar hint.sc.0.flags=0x100 +envvar hint.uart.0.at=isa +envvar hint.uart.0.flags=0x10 +envvar hint.uart.0.irq=4 +envvar hint.uart.0.port=0x3F8 + +hints "hints.kyle" + + makeoptions DEBUG=-g # Build kernel with gdb(1) debug symbols makeoptions WITH_CTF=1 # Run ctfconvert(1) for DTrace support diff --git a/sys/amd64/conf/MINIMAL b/sys/amd64/conf/MINIMAL index 7e7b0377cd7..676c712920b 100644 --- a/sys/amd64/conf/MINIMAL +++ b/sys/amd64/conf/MINIMAL @@ -35,6 +35,9 @@ cpu HAMMER ident MINIMAL +envvar static_env_included=1 +hints "hints.kyle" + makeoptions DEBUG=-g # Build kernel with gdb(1) debug symbols makeoptions WITH_CTF=1 # Run ctfconvert(1) for DTrace support diff --git a/tools/build/mk/OptionalObsoleteFiles.inc b/tools/build/mk/OptionalObsoleteFiles.inc index 6de579e7489..2ed4bf1c182 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 diff --git a/usr.sbin/config/config.y b/usr.sbin/config/config.y index e5296a27156..26bf4c160c9 100644 --- a/usr.sbin/config/config.y +++ b/usr.sbin/config/config.y @@ -97,7 +97,7 @@ static void newdev(char *name); static void newfile(char *name); static void newenvvar(char *name, bool is_file); static void rmdev_schedule(struct device_head *dh, char *name); -static void newopt(struct opt_head *list, char *name, char *value, int append); +static void newopt(struct opt_head *list, char *name, char *value, int append, int dupe); static void rmopt_schedule(struct opt_head *list, char *name); static char * @@ -210,7 +210,7 @@ System_spec: ; System_id: - Save_id { newopt(&mkopt, ns("KERNEL"), $1, 0); }; + Save_id { newopt(&mkopt, ns("KERNEL"), $1, 0, 0); }; System_parameter_list: System_parameter_list ID @@ -230,13 +230,13 @@ NoOpt_list: ; Option: Save_id { - newopt(&opt, $1, NULL, 0); + newopt(&opt, $1, NULL, 0, 1); if (strchr($1, '=') != NULL) errx(1, "%s:%d: The `=' in options should not be " "quoted", yyfile, yyline); } | Save_id EQUALS Opt_value { - newopt(&opt, $1, $3, 0); + newopt(&opt, $1, $3, 0, 1); } ; NoOption: @@ -264,10 +264,10 @@ Mkopt_list: ; Mkoption: - Save_id { newopt(&mkopt, $1, ns(""), 0); } | - Save_id EQUALS { newopt(&mkopt, $1, ns(""), 0); } | - Save_id EQUALS Opt_value { newopt(&mkopt, $1, $3, 0); } | - Save_id PLUSEQUALS Opt_value { newopt(&mkopt, $1, $3, 1); } ; + Save_id { newopt(&mkopt, $1, ns(""), 0, 0); } | + Save_id EQUALS { newopt(&mkopt, $1, ns(""), 0, 0); } | + Save_id EQUALS Opt_value { newopt(&mkopt, $1, $3, 0, 0); } | + Save_id PLUSEQUALS Opt_value { newopt(&mkopt, $1, $3, 1, 0); } ; Dev: ID { $$ = $1; } @@ -293,7 +293,7 @@ NoDev_list: Device: Dev { - newopt(&opt, devopt($1), ns("1"), 0); + newopt(&opt, devopt($1), ns("1"), 0, 0); /* and the device part */ newdev($1); } @@ -430,7 +430,7 @@ findopt(struct opt_head *list, char *name) * Add an option to the list of options. */ static void -newopt(struct opt_head *list, char *name, char *value, int append) +newopt(struct opt_head *list, char *name, char *value, int append, int dupe) { struct opt *op, *op2; @@ -443,7 +443,7 @@ newopt(struct opt_head *list, char *name, char *value, int append) } op2 = findopt(list, name); - if (op2 != NULL && !append) { + if (op2 != NULL && !append && !dupe) { fprintf(stderr, "WARNING: duplicate option `%s' encountered.\n", name); return; @@ -456,9 +456,15 @@ newopt(struct opt_head *list, char *name, char *value, int append) op->op_ownfile = 0; op->op_value = value; if (op2 != NULL) { - while (SLIST_NEXT(op2, op_append) != NULL) - op2 = SLIST_NEXT(op2, op_append); - SLIST_NEXT(op2, op_append) = op; + if (append) { + while (SLIST_NEXT(op2, op_append) != NULL) + op2 = SLIST_NEXT(op2, op_append); + SLIST_NEXT(op2, op_append) = op; + } else { + while (SLIST_NEXT(op2, op_next) != NULL) + op2 = SLIST_NEXT(op2, op_next); + SLIST_NEXT(op2, op_next) = op; + } } else SLIST_INSERT_HEAD(list, op, op_next); } @@ -471,8 +477,7 @@ rmopt_schedule(struct opt_head *list, char *name) { struct opt *op; - op = findopt(list, name); - if (op != NULL) { + while ((op = findopt(list, name)) != NULL) { SLIST_REMOVE(list, op, opt, op_next); free(op->op_name); free(op); -- 2.45.0