MFC bectl(8)/libbe(3): r337663-337664,337667,337697-337699,337800,337805,

337915-337918,337921,337924,337947,337993-337995,338221-338222,338303,
338417,339047,339972,339994,340334,340507-340508,340592-340594,
340635-340636,340722-340723,340974,342466,342849,342903,342911,343335,
343543,343977,343993-343994,344034,344067,344084,345302,345769,
345845-345846,345848,346082

There are simply too many small changes to enumerate; in summary:

bectl(8)/libbe(3) has been introduced from current state in -CURRENT and
added to the stable/11 rescue build. bectl(8) is a tool for managing ZFS
boot environments, largely inspired by beadm. It includes features such as
being able to jail a boot environment or easily mount it for modification.

Relnotes:	probably
This commit is contained in:
Kyle Evans 2019-04-20 04:16:51 +00:00
parent 60452bbf7a
commit d1886e7aef
25 changed files with 4773 additions and 2 deletions

View File

@ -2155,7 +2155,7 @@ _prebuild_libs= ${_kerberos5_lib_libasn1} \
${_cddl_lib_libumem} ${_cddl_lib_libnvpair} \
${_cddl_lib_libuutil} \
${_cddl_lib_libavl} \
${_cddl_lib_libzfs_core} \
${_cddl_lib_libzfs_core} ${_cddl_lib_libzfs} \
${_cddl_lib_libctf} \
lib/libutil lib/libpjdlog ${_lib_libypclnt} lib/libz lib/msun \
${_secure_lib_libcrypto} ${_lib_libldns} \
@ -2224,7 +2224,15 @@ _cddl_lib_libavl= cddl/lib/libavl
_cddl_lib_libuutil= cddl/lib/libuutil
.if ${MK_ZFS} != "no"
_cddl_lib_libzfs_core= cddl/lib/libzfs_core
_cddl_lib_libzfs= cddl/lib/libzfs
cddl/lib/libzfs_core__L: cddl/lib/libnvpair__L
cddl/lib/libzfs__L: cddl/lib/libzfs_core__L lib/msun__L lib/libutil__L
cddl/lib/libzfs__L: lib/libthr__L lib/libmd__L lib/libz__L cddl/lib/libumem__L
cddl/lib/libzfs__L: cddl/lib/libuutil__L cddl/lib/libavl__L lib/libgeom__L
lib/libbe__L: cddl/lib/libzfs__L
.endif
_cddl_lib_libctf= cddl/lib/libctf
_cddl_lib= cddl/lib

View File

@ -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)")

View File

@ -380,6 +380,8 @@
..
..
sbin
bectl
..
dhclient
..
devd

View File

@ -290,6 +290,7 @@ _libproc= libproc
_librtld_db= librtld_db
.endif
SUBDIR.${MK_OFED}+= ofed
SUBDIR.${MK_ZFS}+= libbe
SUBDIR.${MK_OPENMP}+= libomp

31
lib/libbe/Makefile Normal file
View File

@ -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 <bsd.lib.mk>

1038
lib/libbe/be.c Normal file

File diff suppressed because it is too large Load Diff

133
lib/libbe/be.h Normal file
View File

@ -0,0 +1,133 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (c) 2017 Kyle J. Kneitinger <kyle@kneit.in>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#ifndef _LIBBE_H
#define _LIBBE_H
#include <libnvpair.h>
#include <stdbool.h>
#define BE_MAXPATHLEN 512
typedef struct libbe_handle libbe_handle_t;
typedef enum be_error {
BE_ERR_SUCCESS = 0, /* No error */
BE_ERR_INVALIDNAME, /* invalid boot env name */
BE_ERR_EXISTS, /* boot env name already taken */
BE_ERR_NOENT, /* boot env doesn't exist */
BE_ERR_PERMS, /* insufficient permissions */
BE_ERR_DESTROYACT, /* cannot destroy active boot env */
BE_ERR_DESTROYMNT, /* destroying a mounted be requires force */
BE_ERR_BADPATH, /* path not suitable for operation */
BE_ERR_PATHBUSY, /* requested path is busy */
BE_ERR_PATHLEN, /* provided name exceeds maximum length limit */
BE_ERR_BADMOUNT, /* mountpoint is not '/' */
BE_ERR_NOORIGIN, /* could not open snapshot's origin */
BE_ERR_MOUNTED, /* boot environment is already mounted */
BE_ERR_NOMOUNT, /* boot environment is not mounted */
BE_ERR_ZFSOPEN, /* calling zfs_open() failed */
BE_ERR_ZFSCLONE, /* error when calling zfs_clone to create be */
BE_ERR_IO, /* error when doing some I/O operation */
BE_ERR_NOPOOL, /* operation not supported on this pool */
BE_ERR_NOMEM, /* insufficient memory */
BE_ERR_UNKNOWN, /* unknown error */
BE_ERR_INVORIGIN, /* invalid origin */
} be_error_t;
/* Library handling functions: be.c */
libbe_handle_t *libbe_init(const char *root);
void libbe_close(libbe_handle_t *);
/* Bootenv information functions: be_info.c */
const char *be_active_name(libbe_handle_t *);
const char *be_active_path(libbe_handle_t *);
const char *be_nextboot_name(libbe_handle_t *);
const char *be_nextboot_path(libbe_handle_t *);
const char *be_root_path(libbe_handle_t *);
int be_get_bootenv_props(libbe_handle_t *, nvlist_t *);
int be_get_dataset_props(libbe_handle_t *, const char *, nvlist_t *);
int be_get_dataset_snapshots(libbe_handle_t *, const char *, nvlist_t *);
int be_prop_list_alloc(nvlist_t **be_list);
void be_prop_list_free(nvlist_t *be_list);
int be_activate(libbe_handle_t *, const char *, bool);
/* Bootenv creation functions */
int be_create(libbe_handle_t *, const char *);
int be_create_from_existing(libbe_handle_t *, const char *, const char *);
int be_create_from_existing_snap(libbe_handle_t *, const char *, const char *);
int be_snapshot(libbe_handle_t *, const char *, const char *, bool, char *);
/* Bootenv manipulation functions */
int be_rename(libbe_handle_t *, const char *, const char *);
/* Bootenv removal functions */
typedef enum {
BE_DESTROY_FORCE = 1 << 0,
BE_DESTROY_ORIGIN = 1 << 1,
} be_destroy_opt_t;
int be_destroy(libbe_handle_t *, const char *, int);
/* Bootenv mounting functions: be_access.c */
typedef enum {
BE_MNT_FORCE = 1 << 0,
BE_MNT_DEEP = 1 << 1,
} be_mount_opt_t;
int be_mount(libbe_handle_t *, char *, char *, int, char *);
int be_unmount(libbe_handle_t *, char *, int);
int be_mounted_at(libbe_handle_t *, const char *path, nvlist_t *);
/* Error related functions: be_error.c */
int libbe_errno(libbe_handle_t *);
const char *libbe_error_description(libbe_handle_t *);
void libbe_print_on_error(libbe_handle_t *, bool);
/* Utility Functions */
int be_root_concat(libbe_handle_t *, const char *, char *);
int be_validate_name(libbe_handle_t * __unused, const char *);
int be_validate_snap(libbe_handle_t *, const char *);
int be_exists(libbe_handle_t *, char *);
int be_export(libbe_handle_t *, const char *, int fd);
int be_import(libbe_handle_t *, const char *, int fd);
#if SOON
int be_add_child(libbe_handle_t *, const char *, bool);
#endif
void be_nicenum(uint64_t num, char *buf, size_t buflen);
#endif /* _LIBBE_H */

316
lib/libbe/be_access.c Normal file
View File

@ -0,0 +1,316 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (c) 2017 Kyle J. Kneitinger <kyle@kneit.in>
* Copyright (c) 2018 Kyle Evans <kevans@FreeBSD.org>
* Copyright (c) 2019 Wes Maag <wes@jwmaag.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include "be.h"
#include "be_impl.h"
struct be_mountcheck_info {
const char *path;
char *name;
};
struct be_mount_info {
libbe_handle_t *lbh;
const char *be;
const char *mountpoint;
int mntflags;
int deepmount;
};
static int
be_mountcheck_cb(zfs_handle_t *zfs_hdl, void *data)
{
struct be_mountcheck_info *info;
char *mountpoint;
if (data == NULL)
return (1);
info = (struct be_mountcheck_info *)data;
if (!zfs_is_mounted(zfs_hdl, &mountpoint))
return (0);
if (strcmp(mountpoint, info->path) == 0) {
info->name = strdup(zfs_get_name(zfs_hdl));
free(mountpoint);
return (1);
}
free(mountpoint);
return (0);
}
/*
* Called from be_mount, uses the given zfs_handle and attempts to
* mount it at the passed mountpoint. If the deepmount flag is set, continue
* calling the function for each child dataset.
*/
static int
be_mount_iter(zfs_handle_t *zfs_hdl, void *data)
{
int err;
char *mountpoint;
char tmp[BE_MAXPATHLEN], zfs_mnt[BE_MAXPATHLEN];
struct be_mount_info *info;
info = (struct be_mount_info *)data;
if (zfs_is_mounted(zfs_hdl, &mountpoint)) {
free(mountpoint);
return (0);
}
if (zfs_prop_get_int(zfs_hdl, ZFS_PROP_CANMOUNT) == ZFS_CANMOUNT_OFF)
return (0);
if (zfs_prop_get(zfs_hdl, ZFS_PROP_MOUNTPOINT, zfs_mnt, BE_MAXPATHLEN,
NULL, NULL, 0, 1))
return (1);
if (strcmp("none", zfs_mnt) != 0) {
char opt = '\0';
mountpoint = be_mountpoint_augmented(info->lbh, zfs_mnt);
snprintf(tmp, BE_MAXPATHLEN, "%s%s", info->mountpoint,
mountpoint);
if ((err = zmount(zfs_get_name(zfs_hdl), tmp, info->mntflags,
__DECONST(char *, MNTTYPE_ZFS), NULL, 0, &opt, 1)) != 0) {
switch (errno) {
case ENAMETOOLONG:
return (set_error(info->lbh, BE_ERR_PATHLEN));
case ELOOP:
case ENOENT:
case ENOTDIR:
return (set_error(info->lbh, BE_ERR_BADPATH));
case EPERM:
return (set_error(info->lbh, BE_ERR_PERMS));
case EBUSY:
return (set_error(info->lbh, BE_ERR_PATHBUSY));
default:
return (set_error(info->lbh, BE_ERR_UNKNOWN));
}
}
}
if (!info->deepmount)
return (0);
return (zfs_iter_filesystems(zfs_hdl, be_mount_iter, info));
}
static int
be_umount_iter(zfs_handle_t *zfs_hdl, void *data)
{
int err;
char *mountpoint;
struct be_mount_info *info;
info = (struct be_mount_info *)data;
if((err = zfs_iter_filesystems(zfs_hdl, be_umount_iter, info)) != 0) {
return (err);
}
if (!zfs_is_mounted(zfs_hdl, &mountpoint)) {
return (0);
}
free(mountpoint);
if (zfs_unmount(zfs_hdl, NULL, info->mntflags) != 0) {
switch (errno) {
case ENAMETOOLONG:
return (set_error(info->lbh, BE_ERR_PATHLEN));
case ELOOP:
case ENOENT:
case ENOTDIR:
return (set_error(info->lbh, BE_ERR_BADPATH));
case EPERM:
return (set_error(info->lbh, BE_ERR_PERMS));
case EBUSY:
return (set_error(info->lbh, BE_ERR_PATHBUSY));
default:
return (set_error(info->lbh, BE_ERR_UNKNOWN));
}
}
return (0);
}
/*
* usage
*/
int
be_mounted_at(libbe_handle_t *lbh, const char *path, nvlist_t *details)
{
char be[BE_MAXPATHLEN];
zfs_handle_t *root_hdl;
struct be_mountcheck_info info;
prop_data_t propinfo;
bzero(&be, BE_MAXPATHLEN);
if ((root_hdl = zfs_open(lbh->lzh, lbh->root,
ZFS_TYPE_FILESYSTEM)) == NULL)
return (BE_ERR_ZFSOPEN);
info.path = path;
info.name = NULL;
zfs_iter_filesystems(root_hdl, be_mountcheck_cb, &info);
zfs_close(root_hdl);
if (info.name != NULL) {
if (details != NULL) {
if ((root_hdl = zfs_open(lbh->lzh, lbh->root,
ZFS_TYPE_FILESYSTEM)) == NULL) {
free(info.name);
return (BE_ERR_ZFSOPEN);
}
propinfo.lbh = lbh;
propinfo.list = details;
propinfo.single_object = false;
prop_list_builder_cb(root_hdl, &propinfo);
zfs_close(root_hdl);
}
free(info.name);
return (0);
}
return (1);
}
/*
* usage
*/
int
be_mount(libbe_handle_t *lbh, char *bootenv, char *mountpoint, int flags,
char *result_loc)
{
char be[BE_MAXPATHLEN];
char mnt_temp[BE_MAXPATHLEN];
int mntflags, mntdeep;
int err;
struct be_mount_info info;
zfs_handle_t *zhdl;
if ((err = be_root_concat(lbh, bootenv, be)) != 0)
return (set_error(lbh, err));
if ((err = be_exists(lbh, bootenv)) != 0)
return (set_error(lbh, err));
if (is_mounted(lbh->lzh, be, NULL))
return (set_error(lbh, BE_ERR_MOUNTED));
mntdeep = (flags & BE_MNT_DEEP) ? 1 : 0;
mntflags = (flags & BE_MNT_FORCE) ? MNT_FORCE : 0;
/* Create mountpoint if it is not specified */
if (mountpoint == NULL) {
strlcpy(mnt_temp, "/tmp/be_mount.XXXX", sizeof(mnt_temp));
if (mkdtemp(mnt_temp) == NULL)
return (set_error(lbh, BE_ERR_IO));
}
if ((zhdl = zfs_open(lbh->lzh, be, ZFS_TYPE_FILESYSTEM)) == NULL)
return (set_error(lbh, BE_ERR_ZFSOPEN));
info.lbh = lbh;
info.be = be;
info.mountpoint = (mountpoint == NULL) ? mnt_temp : mountpoint;
info.mntflags = mntflags;
info.deepmount = mntdeep;
if((err = be_mount_iter(zhdl, &info) != 0)) {
zfs_close(zhdl);
return (err);
}
zfs_close(zhdl);
if (result_loc != NULL)
strlcpy(result_loc, mountpoint == NULL ? mnt_temp : mountpoint,
BE_MAXPATHLEN);
return (BE_ERR_SUCCESS);
}
/*
* usage
*/
int
be_unmount(libbe_handle_t *lbh, char *bootenv, int flags)
{
int err;
char be[BE_MAXPATHLEN];
zfs_handle_t *root_hdl;
struct be_mount_info info;
if ((err = be_root_concat(lbh, bootenv, be)) != 0)
return (set_error(lbh, err));
if ((root_hdl = zfs_open(lbh->lzh, be, ZFS_TYPE_FILESYSTEM)) == NULL)
return (set_error(lbh, BE_ERR_ZFSOPEN));
info.lbh = lbh;
info.be = be;
info.mountpoint = NULL;
info.mntflags = (flags & BE_MNT_FORCE) ? MS_FORCE : 0;
if ((err = be_umount_iter(root_hdl, &info)) != 0) {
zfs_close(root_hdl);
return (err);
}
zfs_close(root_hdl);
return (BE_ERR_SUCCESS);
}
/*
* This function will blow away the input buffer as needed if we're discovered
* to be looking at a root-mount. If the mountpoint is naturally beyond the
* root, however, the buffer may be left intact and a pointer to the section
* past altroot will be returned instead for the caller's perusal.
*/
char *
be_mountpoint_augmented(libbe_handle_t *lbh, char *mountpoint)
{
if (lbh->altroot_len == 0)
return (mountpoint);
if (mountpoint == NULL || *mountpoint == '\0')
return (mountpoint);
if (mountpoint[lbh->altroot_len] == '\0') {
*(mountpoint + 1) = '\0';
return (mountpoint);
} else
return (mountpoint + lbh->altroot_len);
}

136
lib/libbe/be_error.c Normal file
View File

@ -0,0 +1,136 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (c) 2017 Kyle J. Kneitinger <kyle@kneit.in>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include "be.h"
#include "be_impl.h"
/*
* Usage
*/
int
libbe_errno(libbe_handle_t *lbh)
{
return (lbh->error);
}
const char *
libbe_error_description(libbe_handle_t *lbh)
{
switch (lbh->error) {
case BE_ERR_INVALIDNAME:
return ("invalid boot environment name");
case BE_ERR_EXISTS:
return ("boot environment name already taken");
case BE_ERR_NOENT:
return ("specified boot environment does not exist");
case BE_ERR_PERMS:
return ("insufficient permissions");
case BE_ERR_DESTROYACT:
return ("cannot destroy active boot environment");
case BE_ERR_DESTROYMNT:
return ("cannot destroy mounted boot env unless forced");
case BE_ERR_BADPATH:
return ("path not suitable for operation");
case BE_ERR_PATHBUSY:
return ("specified path is busy");
case BE_ERR_PATHLEN:
return ("provided path name exceeds maximum length limit");
case BE_ERR_BADMOUNT:
return ("mountpoint is not \"/\"");
case BE_ERR_NOORIGIN:
return ("could not open snapshot's origin");
case BE_ERR_MOUNTED:
return ("boot environment is already mounted");
case BE_ERR_NOMOUNT:
return ("boot environment is not mounted");
case BE_ERR_ZFSOPEN:
return ("calling zfs_open() failed");
case BE_ERR_ZFSCLONE:
return ("error when calling zfs_clone() to create boot env");
case BE_ERR_IO:
return ("input/output error");
case BE_ERR_NOPOOL:
return ("operation not supported on this pool");
case BE_ERR_NOMEM:
return ("insufficient memory");
case BE_ERR_UNKNOWN:
return ("unknown error");
case BE_ERR_INVORIGIN:
return ("invalid origin");
default:
assert(lbh->error == BE_ERR_SUCCESS);
return ("no error");
}
}
void
libbe_print_on_error(libbe_handle_t *lbh, bool val)
{
lbh->print_on_err = val;
libzfs_print_on_error(lbh->lzh, val);
}
int
set_error(libbe_handle_t *lbh, be_error_t err)
{
lbh->error = err;
if (lbh->print_on_err && (err != BE_ERR_SUCCESS))
fprintf(stderr, "%s\n", libbe_error_description(lbh));
return (err);
}

76
lib/libbe/be_impl.h Normal file
View File

@ -0,0 +1,76 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (c) 2017 Kyle J. Kneitinger <kyle@kneit.in>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#ifndef _LIBBE_IMPL_H
#define _LIBBE_IMPL_H
#include <libzfs.h>
#include "be.h"
struct libbe_handle {
char root[BE_MAXPATHLEN];
char rootfs[BE_MAXPATHLEN];
char bootfs[BE_MAXPATHLEN];
size_t altroot_len;
zpool_handle_t *active_phandle;
libzfs_handle_t *lzh;
be_error_t error;
bool print_on_err;
};
struct libbe_deep_clone {
libbe_handle_t *lbh;
const char *bename;
const char *snapname;
const char *be_root;
};
struct libbe_dccb {
libbe_handle_t *lbh;
zfs_handle_t *zhp;
nvlist_t *props;
};
typedef struct prop_data {
nvlist_t *list;
libbe_handle_t *lbh;
bool single_object; /* list will contain props directly */
} prop_data_t;
int prop_list_builder_cb(zfs_handle_t *, void *);
int be_proplist_update(prop_data_t *);
char *be_mountpoint_augmented(libbe_handle_t *lbh, char *mountpoint);
/* Clobbers any previous errors */
int set_error(libbe_handle_t *, be_error_t);
#endif /* _LIBBE_IMPL_H */

305
lib/libbe/be_info.c Normal file
View File

@ -0,0 +1,305 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (c) 2017 Kyle J. Kneitinger <kyle@kneit.in>
* Copyright (c) 2018 Kyle Evans <kevans@FreeBSD.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include "be.h"
#include "be_impl.h"
static int snapshot_proplist_update(zfs_handle_t *hdl, prop_data_t *data);
/*
* Returns the name of the active boot environment
*/
const char *
be_active_name(libbe_handle_t *lbh)
{
if (*lbh->rootfs != '\0')
return (strrchr(lbh->rootfs, '/') + sizeof(char));
else
return (lbh->rootfs);
}
/*
* Returns full path of the active boot environment
*/
const char *
be_active_path(libbe_handle_t *lbh)
{
return (lbh->rootfs);
}
/*
* Returns the name of the next active boot environment
*/
const char *
be_nextboot_name(libbe_handle_t *lbh)
{
if (*lbh->bootfs != '\0')
return (strrchr(lbh->bootfs, '/') + sizeof(char));
else
return (lbh->bootfs);
}
/*
* Returns full path of the active boot environment
*/
const char *
be_nextboot_path(libbe_handle_t *lbh)
{
return (lbh->bootfs);
}
/*
* Returns the path of the boot environment root dataset
*/
const char *
be_root_path(libbe_handle_t *lbh)
{
return (lbh->root);
}
/*
* Populates dsnvl with one nvlist per bootenv dataset describing the properties
* of that dataset that we've declared ourselves to care about.
*/
int
be_get_bootenv_props(libbe_handle_t *lbh, nvlist_t *dsnvl)
{
prop_data_t data;
data.lbh = lbh;
data.list = dsnvl;
data.single_object = false;
return (be_proplist_update(&data));
}
int
be_get_dataset_props(libbe_handle_t *lbh, const char *name, nvlist_t *props)
{
zfs_handle_t *snap_hdl;
prop_data_t data;
int ret;
data.lbh = lbh;
data.list = props;
data.single_object = true;
if ((snap_hdl = zfs_open(lbh->lzh, name,
ZFS_TYPE_FILESYSTEM | ZFS_TYPE_SNAPSHOT)) == NULL)
return (BE_ERR_ZFSOPEN);
ret = prop_list_builder_cb(snap_hdl, &data);
zfs_close(snap_hdl);
return (ret);
}
int
be_get_dataset_snapshots(libbe_handle_t *lbh, const char *name, nvlist_t *props)
{
zfs_handle_t *ds_hdl;
prop_data_t data;
int ret;
data.lbh = lbh;
data.list = props;
data.single_object = false;
if ((ds_hdl = zfs_open(lbh->lzh, name,
ZFS_TYPE_FILESYSTEM)) == NULL)
return (BE_ERR_ZFSOPEN);
ret = snapshot_proplist_update(ds_hdl, &data);
zfs_close(ds_hdl);
return (ret);
}
/*
* Internal callback function used by zfs_iter_filesystems. For each dataset in
* the bootenv root, populate an nvlist_t of its relevant properties.
*/
int
prop_list_builder_cb(zfs_handle_t *zfs_hdl, void *data_p)
{
char buf[512], *mountpoint;
prop_data_t *data;
libbe_handle_t *lbh;
nvlist_t *props;
const char *dataset, *name;
boolean_t mounted;
/*
* XXX TODO:
* some system for defining constants for the nvlist keys
* error checking
*/
data = (prop_data_t *)data_p;
lbh = data->lbh;
if (data->single_object)
props = data->list;
else
nvlist_alloc(&props, NV_UNIQUE_NAME, KM_SLEEP);
dataset = zfs_get_name(zfs_hdl);
nvlist_add_string(props, "dataset", dataset);
name = strrchr(dataset, '/') + 1;
nvlist_add_string(props, "name", name);
mounted = zfs_is_mounted(zfs_hdl, &mountpoint);
if (mounted)
nvlist_add_string(props, "mounted", mountpoint);
if (zfs_prop_get(zfs_hdl, ZFS_PROP_MOUNTPOINT, buf, 512,
NULL, NULL, 0, 1) == 0)
nvlist_add_string(props, "mountpoint", buf);
if (zfs_prop_get(zfs_hdl, ZFS_PROP_ORIGIN, buf, 512,
NULL, NULL, 0, 1) == 0)
nvlist_add_string(props, "origin", buf);
if (zfs_prop_get(zfs_hdl, ZFS_PROP_CREATION, buf, 512,
NULL, NULL, 0, 1) == 0)
nvlist_add_string(props, "creation", buf);
nvlist_add_boolean_value(props, "active",
(strcmp(be_active_path(lbh), dataset) == 0));
if (zfs_prop_get(zfs_hdl, ZFS_PROP_USED, buf, 512,
NULL, NULL, 0, 1) == 0)
nvlist_add_string(props, "used", buf);
if (zfs_prop_get(zfs_hdl, ZFS_PROP_USEDDS, buf, 512,
NULL, NULL, 0, 1) == 0)
nvlist_add_string(props, "usedds", buf);
if (zfs_prop_get(zfs_hdl, ZFS_PROP_USEDSNAP, buf, 512,
NULL, NULL, 0, 1) == 0)
nvlist_add_string(props, "usedsnap", buf);
if (zfs_prop_get(zfs_hdl, ZFS_PROP_USEDREFRESERV, buf, 512,
NULL, NULL, 0, 1) == 0)
nvlist_add_string(props, "usedrefreserv", buf);
if (zfs_prop_get(zfs_hdl, ZFS_PROP_REFERENCED, buf, 512,
NULL, NULL, 0, 1) == 0)
nvlist_add_string(props, "referenced", buf);
nvlist_add_boolean_value(props, "nextboot",
(strcmp(be_nextboot_path(lbh), dataset) == 0));
if (!data->single_object)
nvlist_add_nvlist(data->list, name, props);
return (0);
}
/*
* Updates the properties of each bootenv in the libbe handle
* XXX TODO: ensure that this is always consistent (run after adds, deletes,
* renames,etc
*/
int
be_proplist_update(prop_data_t *data)
{
zfs_handle_t *root_hdl;
if ((root_hdl = zfs_open(data->lbh->lzh, data->lbh->root,
ZFS_TYPE_FILESYSTEM)) == NULL)
return (BE_ERR_ZFSOPEN);
/* XXX TODO: some error checking here */
zfs_iter_filesystems(root_hdl, prop_list_builder_cb, data);
zfs_close(root_hdl);
return (0);
}
static int
snapshot_proplist_update(zfs_handle_t *hdl, prop_data_t *data)
{
return (zfs_iter_snapshots_sorted(hdl, prop_list_builder_cb, data));
}
int
be_prop_list_alloc(nvlist_t **be_list)
{
return (nvlist_alloc(be_list, NV_UNIQUE_NAME, KM_SLEEP));
}
/*
* frees property list and its children
*/
void
be_prop_list_free(nvlist_t *be_list)
{
nvlist_t *prop_list;
nvpair_t *be_pair;
be_pair = nvlist_next_nvpair(be_list, NULL);
if (nvpair_value_nvlist(be_pair, &prop_list) == 0)
nvlist_free(prop_list);
while ((be_pair = nvlist_next_nvpair(be_list, be_pair)) != NULL) {
if (nvpair_value_nvlist(be_pair, &prop_list) == 0)
nvlist_free(prop_list);
}
}
/*
* Usage
*/
int
be_exists(libbe_handle_t *lbh, char *be)
{
char buf[BE_MAXPATHLEN];
be_root_concat(lbh, be, buf);
if (!zfs_dataset_exists(lbh->lzh, buf, ZFS_TYPE_DATASET))
return (BE_ERR_NOENT);
return (BE_ERR_SUCCESS);
}

504
lib/libbe/libbe.3 Normal file
View File

@ -0,0 +1,504 @@
.\"
.\" SPDX-License-Identifier: BSD-2-Clause-FreeBSD
.\"
.\" Copyright (c) 2017 Kyle Kneitinger
.\" All rights reserved.
.\" Copyright (c) 2018 Kyle Evans <kevans@FreeBSD.org>
.\"
.\" Redistribution and use in source and binary forms, with or without
.\" modification, are permitted provided that the following conditions
.\" are met:
.\" 1. Redistributions of source code must retain the above copyright
.\" notice, this list of conditions and the following disclaimer.
.\" 2. Redistributions in binary form must reproduce the above copyright
.\" notice, this list of conditions and the following disclaimer in the
.\" documentation and/or other materials provided with the distribution.
.\"
.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.\" $FreeBSD$
.\"
.Dd February 12, 2019
.Dt LIBBE 3
.Os
.Sh NAME
.Nm libbe
.Nd library for creating, destroying and modifying ZFS boot environments
.Sh LIBRARY
.Lb libbe
.Sh SYNOPSIS
.In be.h
.Ft "libbe_handle_t *hdl" Ns
.Fn libbe_init "const char *be_root"
.Pp
.Ft void
.Fn libbe_close "libbe_handle_t *hdl"
.Pp
.Ft const char * Ns
.Fn be_active_name "libbe_handle_t *hdl"
.Pp
.Ft const char * Ns
.Fn be_active_path "libbe_handle_t *hdl"
.Pp
.Ft const char * Ns
.Fn be_nextboot_name "libbe_handle_t *hdl"
.Pp
.Ft const char * Ns
.Fn be_nextboot_path "libbe_handle_t *hdl"
.Pp
.Ft const char * Ns
.Fn be_root_path "libbe_handle_t *hdl"
.Pp
.Ft int
.Fn be_create "libbe_handle_t *hdl" "const char *be_name"
.Pp
.Ft int
.Fn be_create_from_existing "libbe_handle_t *hdl" "const char *be_name" "const char *be_origin"
.Pp
.Ft int
.Fn be_create_from_existing_snap "libbe_handle_t *hdl" "const char *be_name" "const char *snap"
.Pp
.Ft int
.Fn be_rename "libbe_handle_t *hdl" "const char *be_old" "const char *be_new"
.Pp
.Ft int
.Fn be_activate "libbe_handle_t *hdl" "const char *be_name" "bool temporary"
.Ft int
.Fn be_destroy "libbe_handle_t *hdl" "const char *be_name" "int options"
.Pp
.Ft void
.Fn be_nicenum "uint64_t num" "char *buf" "size_t bufsz"
.Pp
.\" TODO: Write up of mount options
.\" typedef enum {
.\" BE_MNT_FORCE = 1 << 0,
.\" BE_MNT_DEEP = 1 << 1,
.\" } be_mount_opt_t
.Ft int
.Fn be_mount "libbe_handle_t *hdl" "char *be_name" "char *mntpoint" "int flags" "char *result"
.Pp
.Ft int
.Fn be_mounted_at "libbe_handle_t *hdl" "const char *path" "nvlist_t *details"
.Pp
.Ft int
.Fn be_unmount "libbe_handle_t *hdl" "char *be_name" "int flags"
.Pp
.Ft int
.Fn libbe_errno "libbe_handle_t *hdl"
.Pp
.Ft const char * Ns
.Fn libbe_error_description "libbe_handle_t *hdl"
.Pp
.Ft void
.Fn libbe_print_on_error "libbe_handle_t *hdl" "bool doprint"
.Pp
.Ft int
.Fn be_root_concat "libbe_handle_t *hdl" "const char *be_name" "char *result"
.Pp
.Ft int
.Fn be_validate_name "libbe_handle_t *hdl" "const char *be_name"
.Pp
.Ft int
.Fn be_validate_snap "libbe_handle_t *hdl" "const char *snap"
.Pp
.Ft int
.Fn be_exists "libbe_handle_t *hdl" "char *be_name"
.Pp
.Ft int
.Fn be_export "libbe_handle_t *hdl" "const char *be_name" "int fd"
.Pp
.Ft int
.Fn be_import "libbe_handle_t *hdl" "const char *be_name" "int fd"
.Pp
.Ft int
.Fn be_prop_list_alloc "nvlist_t **prop_list"
.Pp
.Ft int
.Fn be_get_bootenv_props "libbe_handle_t *hdl" "nvlist_t *be_list"
.Pp
.Ft int
.Fn be_get_dataset_props "libbe_handle_t *hdl" "const char *ds_name" "nvlist_t *props"
.Pp
.Ft int
.Fn be_get_dataset_snapshots "libbe_handle_t *hdl" "const char *ds_name" "nvlist_t *snap_list"
.Pp
.Ft void
.Fn be_prop_list_free "nvlist_t *prop_list"
.Sh DESCRIPTION
.Nm
interfaces with libzfs to provide a set of functions for various operations
regarding ZFS boot environments including "deep" boot environments in which
a boot environments has child datasets.
.Pp
A context structure is passed to each function, allowing for a small amount
of state to be retained, such as errors from previous operations.
.Nm
may be configured to print the corresponding error message to
.Dv stderr
when an error is encountered with
.Fn libbe_print_on_error .
.Pp
All functions returning an
.Vt int
return 0 on success, or a
.Nm
errno otherwise as described in
.Sx DIAGNOSTICS .
.Pp
The
.Fn libbe_init
function takes an optional BE root and initializes
.Nm ,
returning a
.Vt "libbe_handle_t *"
on success, or
.Dv NULL
on error.
If a BE root is supplied,
.Nm
will only operate out of that pool and BE root.
An error may occur if:
.Bl -column
.It /boot and / are not on the same filesystem and device,
.It libzfs fails to initialize,
.It The system has not been properly booted with a ZFS boot
environment,
.It Nm
fails to open the zpool the active boot environment resides on, or
.It Nm
fails to locate the boot environment that is currently mounted.
.El
.Pp
The
.Fn libbe_close
function frees all resources previously acquired in
.Fn libbe_init ,
invalidating the handle in the process.
.Pp
The
.Fn be_active_name
function returns the name of the currently booted boot environment.
This boot environment may not belong to the same BE root as the root libbe
is operating on!
.Pp
The
.Fn be_active_path
function returns the full path of the currently booted boot environment.
This boot environment may not belong to the same BE root as the root libbe
is operating on!
.Pp
The
.Fn be_nextboot_name
function returns the name of the boot environment that will be active on reboot.
.Pp
The
.Fn be_nextboot_path
function returns the full path of the boot environment that will be
active on reboot.
.Pp
The
.Fn be_root_path
function returns the boot environment root path.
.Pp
The
.Fn be_create
function creates a boot environment with the given name.
It will be created from a snapshot of the currently booted boot environment.
.Pp
The
.Fn be_create_from_existing
function creates a boot environment with the given name from the name of an
existing boot environment.
A snapshot will be made of the base boot environment, and the new boot
environment will be created from that.
.Pp
The
.Fn be_create_from_existing_snap
function creates a boot environment with the given name from an existing
snapshot.
.Pp
The
.Fn be_rename
function renames a boot environment without unmounting it, as if renamed with
the
.Fl u
argument were passed to
.Nm zfs
.Cm rename
.Pp
The
.Fn be_activate
function makes a boot environment active on the next boot.
If the
.Fa temporary
flag is set, then it will be active for the next boot only, as done by
.Xr zfsbootcfg 8 .
Next boot functionality is currently only available when booting in x86 BIOS
mode.
.Pp
The
.Fn be_destroy
function will recursively destroy the given boot environment.
It will not destroy a mounted boot environment unless the
.Dv BE_DESTROY_FORCE
option is set in
.Fa options .
If the
.Dv BE_DESTROY_ORIGIN
option is set in
.Fa options ,
the
.Fn be_destroy
function will destroy the origin snapshot to this boot environment as well.
.Pp
The
.Fn be_nicenum
function will format
.Fa name
in a traditional ZFS humanized format, similar to
.Xr humanize_number 3 .
This function effectively proxies
.Fn zfs_nicenum
from libzfs.
.Pp
The
.Fn be_mount
function will mount the given boot environment.
If
.Fa mountpoint
is
.Dv NULL ,
a mount point will be generated in
.Pa /tmp
using
.Xr mkdtemp 3 .
If
.Fa result
is not
.Dv NULL ,
it should be large enough to accommodate
.Dv BE_MAXPATHLEN
including the null terminator.
the final mount point will be copied into it.
Setting the
.Dv BE_MNT_FORCE
flag will pass
.Dv MNT_FORCE
to the underlying
.Xr mount 2
call.
.Pp
The
.Fn be_mounted_at
function will check if there is a boot environment mounted at the given
.Fa path .
If
.Fa details
is not
.Dv NULL ,
it will be populated with a list of the mounted dataset's properties.
This list of properties matches the properties collected by
.Fn be_get_bootenv_props .
.Pp
The
.Fn be_unmount
function will unmount the given boot environment.
Setting the
.Dv BE_MNT_FORCE
flag will pass
.Dv MNT_FORCE
to the underlying
.Xr mount 2
call.
.Pp
The
.Fn libbe_errno
function returns the
.Nm
errno.
.Pp
The
.Fn libbe_error_description
function returns a string description of the currently set
.Nm
errno.
.Pp
The
.Fn libbe_print_on_error
function will change whether or not
.Nm
prints the description of any encountered error to
.Dv stderr ,
based on
.Fa doprint .
.Pp
The
.Fn be_root_concat
function will concatenate the boot environment root and the given boot
environment name into
.Fa result .
.Pp
The
.Fn be_validate_name
function will validate the given boot environment name for both length
restrictions as well as valid character restrictions.
This function does not set the internal library error state.
.Pp
The
.Fn be_validate_snap
function will validate the given snapshot name.
The snapshot must have a valid name, exist, and have a mountpoint of
.Pa / .
This function does not set the internal library error state.
.Pp
The
.Fn be_exists
function will check whether the given boot environment exists and has a
mountpoint of
.Pa / .
This function does not set the internal library error state, but will return
the appropriate error.
.Pp
The
.Fn be_export
function will export the given boot environment to the file specified by
.Fa fd .
A snapshot will be created of the boot environment prior to export.
.Pp
The
.Fn be_import
function will import the boot environment in the file specified by
.Fa fd ,
and give it the name
.Fa be_name .
.Pp
The
.Fn be_prop_list_alloc
function allocates a property list suitable for passing to
.Fn be_get_bootenv_props ,
.Fn be_get_dataset_props ,
or
.Fn be_get_dataset_snapshots .
It should be freed later by
.Fa be_prop_list_free .
.Pp
The
.Fn be_get_bootenv_props
function will populate
.Fa be_list
with
.Vt nvpair_t
of boot environment names paired with an
.Vt nvlist_t
of their properties.
The following properties are currently collected as appropriate:
.Bl -column "Returned name"
.It Sy Returned name Ta Sy Description
.It dataset Ta -
.It name Ta Boot environment name
.It mounted Ta Current mount point
.It mountpoint Ta Do mountpoint Dc property
.It origin Ta Do origin Dc property
.It creation Ta Do creation Dc property
.It active Ta Currently booted environment
.It used Ta Literal Do used Dc property
.It usedds Ta Literal Do usedds Dc property
.It usedsnap Ta Literal Do usedrefreserv Dc property
.It referenced Ta Literal Do referenced Dc property
.It nextboot Ta Active on next boot
.El
.Pp
Only the
.Dq dataset ,
.Dq name ,
.Dq active ,
and
.Dq nextboot
returned values will always be present.
All other properties may be omitted if not available.
.Pp
The
.Fn be_get_dataset_props
function will get properties of the specified dataset.
.Fa props
is populated directly with a list of the properties as returned by
.Fn be_get_bootenv_props .
.Pp
The
.Fn be_get_dataset_snapshots
function will retrieve all snapshots of the given dataset.
.Fa snap_list
will be populated with a list of
.Vt nvpair_t
exactly as specified by
.Fn be_get_bootenv_props .
.Pp
The
.Fn be_prop_list_free
function will free the property list.
.Sh DIAGNOSTICS
Upon error, one of the following values will be returned:
.Bl -dash -offset indent -compact
.It
BE_ERR_SUCCESS
.It
BE_ERR_INVALIDNAME
.It
BE_ERR_EXISTS
.It
BE_ERR_NOENT
.It
BE_ERR_PERMS
.It
BE_ERR_DESTROYACT
.It
BE_ERR_DESTROYMNT
.It
BE_ERR_BADPATH
.It
BE_ERR_PATHBUSY
.It
BE_ERR_PATHLEN
.It
BE_ERR_BADMOUNT
.It
BE_ERR_NOORIGIN
.It
BE_ERR_MOUNTED
.It
BE_ERR_NOMOUNT
.It
BE_ERR_ZFSOPEN
.It
BE_ERR_ZFSCLONE
.It
BE_ERR_IO
.It
BE_ERR_NOPOOL
.It
BE_ERR_NOMEM
.It
BE_ERR_UNKNOWN
.It
BE_ERR_INVORIGIN
.El
.Sh SEE ALSO
.Xr bectl 8
.Sh HISTORY
.Nm
and its corresponding command,
.Xr bectl 8 ,
were written as a 2017 Google Summer of Code project with Allan Jude serving
as a mentor.
Later work was done by
.An Kyle Evans Aq Mt kevans@FreeBSD.org .

View File

@ -123,6 +123,7 @@ CRUNCH_PROGS_sbin+= routed rtquery
.endif
.if ${MK_ZFS} != "no"
CRUNCH_PROGS_sbin+= bectl
CRUNCH_PROGS_sbin+= zfs
CRUNCH_PROGS_sbin+= zpool
CRUNCH_PROGS_usr.sbin+= zdb
@ -134,6 +135,7 @@ CRUNCH_PROGS_usr.sbin+= zdb
CRUNCH_LIBS+= -l80211 -lalias -lcam -lncursesw -ldevstat -lipsec -llzma
.if ${MK_ZFS} != "no"
CRUNCH_LIBS+= -lavl -lzpool -lzfs_core -lzfs -lnvpair -lpthread -luutil -lumem
CRUNCH_LIBS+= -lbe
.else
# liblzma needs pthread
CRUNCH_LIBS+= -lpthread

View File

@ -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

24
sbin/bectl/Makefile Normal file
View File

@ -0,0 +1,24 @@
# $FreeBSD$
.include <src.opts.mk>
PROG= bectl
MAN= bectl.8
SRCS= bectl.c bectl_jail.c bectl_list.c
LIBADD+= be
LIBADD+= jail
LIBADD+= nvpair
LIBADD+= util
CFLAGS+= -I${SRCTOP}/cddl/contrib/opensolaris/lib/libzfs/common
CFLAGS+= -I${SRCTOP}/sys/cddl/compat/opensolaris
CFLAGS+= -I${SRCTOP}/sys/cddl/contrib/opensolaris/uts/common
CFLAGS+= -DNEED_SOLARIS_BOOLEAN
HAS_TESTS= yes
SUBDIR.${MK_TESTS}+= tests
.include <bsd.prog.mk>

310
sbin/bectl/bectl.8 Normal file
View File

@ -0,0 +1,310 @@
.\"
.\" SPDX-License-Identifier: BSD-2-Clause-FreeBSD
.\"
.\" Copyright (c) 2017 Kyle J. Kneitinger <kyle@kneit.in>
.\" All rights reserved.
.\"
.\" Redistribution and use in source and binary forms, with or without
.\" modification, are permitted provided that the following conditions
.\" are met:
.\" 1. Redistributions of source code must retain the above copyright
.\" notice, this list of conditions and the following disclaimer.
.\" 2. Redistributions in binary form must reproduce the above copyright
.\" notice, this list of conditions and the following disclaimer in the
.\" documentation and/or other materials provided with the distribution.
.\"
.\"
.\" @(#)be.1
.\"
.\" $FreeBSD$
.\"
.Dd April 3, 2019
.Dt BECTL 8
.Os
.Sh NAME
.Nm bectl
.Nd Utility to manage boot environments on ZFS
.Sh SYNOPSIS
.Nm
.Cm activate
.Op Fl t
.Ar beName
.Nm
.Cm create
.Op Fl r
.Op Fl e Brq Ar nonActiveBe | Ar beName Ns Cm @ Ns Ar snapshot
.Ar newBeName
.Nm
.Cm destroy
.Op Fl \&Fo
.Ar beName Ns Op Cm @ Ns Ar snapshot
.Nm
.Cm export
.Ar sourceBe
.Nm
.Cm import
.Ar targetBe
.Nm
.Cm jail
.Op Fl bU
.Oo Bro Fl o Ar key Ns Cm = Ns Ar value | Fl u Ar key Brc Oc Ns ...
.Ar beName
.Op Ar utility Op Ar argument ...
.Nm
.Cm list
.Op Fl aDHs
.Nm
.Cm mount
.Ar beName
.Op Ar mountpoint
.Nm
.Cm rename
.Ar origBeName
.Ar newBeName
.Nm
.Brq Cm ujail | unjail
.Brq Ar jailId | jailName
.Ar beName
.Nm
.Brq Cm umount | unmount
.Op Fl f
.Ar beName
.Pp
.Nm
.Op Fl h\&?
.Sh DESCRIPTION
The
.Nm
command is used to setup and interact with ZFS boot environments, which are
bootable clones of datasets.
.Pp
Boot environments
allow the system to be upgraded, while preserving the old system environment in
a separate ZFS dataset.
.Pp
The following commands are supported by
.Nm :
.Bl -tag -width activate
.It Xo
.Cm activate
.Op Fl t
.Ar beName
.Xc
Activate the given
.Ar beName
as the default boot filesystem.
If the
.Op Fl t
flag is given, this takes effect only for the next boot.
.It Xo
.Cm create
.Op Fl r
.Op Fl e Brq Ar nonActiveBe | Ar beName Ns Cm @ Ns Ar snapshot
.Ar newBeName
.Xc
Create a new boot environment named
.Ar newBeName .
.Pp
If the
.Fl r
flag is given, a recursive boot environment will be made.
.Pp
If the
.Fl e
flag is specified, the new environment will be cloned from the given
.Ar nonActiveBe
or
.Ar beName Ns Cm @ Ns Ar snapshot .
Otherwise, the new environment will be created from the currently booted environment.
.Pp
If
.Nm
is creating from another boot environment, a snapshot of that boot environment will be created to clone from.
.It Xo
.Cm destroy
.Op Fl \&Fo
.Ar beName Ns Op Cm @ Ns Ar snapshot
.Xc
Destroy the given
.Ar beName
boot environment or
.Ar beName Ns Cm @ Ns Ar snapshot
snapshot without confirmation, unlike in
.Xr beadm 1 .
Specifying
.Fl F
will automatically unmount without confirmation.
.Pp
By default,
.Nm
will warn that it is not destroying the origin of
.Ar beName .
The
.Fl o
flag may be specified to destroy the origin as well.
.It Cm export Ar sourceBe
Export
.Ar sourceBe
to
.Xr stdout 4 .
.Xr stdout 4
must be piped or redirected to a file.
.It Cm import Ar targetBe
Import
.Ar targetBe
from
.Xr stdin 4 .
.It Xo
.Cm jail
.Op Fl bU
.Oo Bro Fl o Ar key Ns Cm = Ns Ar value | Fl u Ar key Brc Oc Ns ...
.Ar beName
.Op Ar utility Op Ar argument ...
.Xc
Create a jail of the given boot environment.
Multiple
.Fl o
and
.Fl u
arguments may be specified.
.Fl o
will set a jail parameter, and
.Fl u
will unset a jail parameter.
.Pp
By default, jails are created in interactive mode and
.Pa /bin/sh
is
executed within the jail.
If
.Ar utility
is specified, it will be executed instead of
.Pa /bin/sh .
The jail will be destroyed and the boot environment unmounted when the command
finishes executing, unless the
.Fl U
argument is specified.
.Pp
The
.Fl b
argument enables batch mode, thereby disabling interactive mode.
The
.Fl U
argument will be ignored in batch mode.
.Pp
The
.Va name ,
.Va host.hostname ,
and
.Va path
must be set, the default values are specified below.
.Pp
All
.Ar key Ns Cm = Ns Ar value
pairs are interpreted as jail parameters as described in
.Xr jail 8 .
The following default parameters are provided:
.Bl -column "allow.mount.devfs" ""
.It Va allow.mount Ta Cm true
.It Va allow.mount.devfs Ta Cm true
.It Va enforce_statfs Ta Cm 1
.It Va name Ta Set to jail ID.
.It Va host.hostname Ta Va bootenv
.It Va path Ta Set to a path in Pa /tmp
generated by
.Xr libbe 3 .
.El
.Pp
All default parameters may be overwritten.
.It Cm list Op Fl aDHs
Display all boot environments.
The
.Em Active
field indicates whether the boot environment is active now
.Pq Em \&N ;
active on reboot
.Pq Em \&R ;
or both
.Pq Em \&NR .
.Pp
If
.Fl a
is used, display all datasets.
If
.Fl D
is used, display the full space usage for each boot environment, assuming all
other boot environments were destroyed.
The
.Fl H
option is used for scripting.
It does not print headers and separate fields by a single tab instead of
arbitrary white space.
If
.Fl s
is used, display all snapshots as well.
.It Cm mount Ar beName Op Ar mountpoint
Temporarily mount the boot environment.
Mount at the specified
.Ar mountpoint
if provided.
.It Cm rename Ar origBeName newBeName
Rename the given
.Ar origBeName
to the given
.Ar newBeName .
The boot environment will not be unmounted in order for this rename to occur.
.It Cm ujail Bro Ar jailId | jailName Brc Ar beName
.It Cm unjail Bro Ar jailId | jailName Brc Ar beName
Destroy the jail created from the given boot environment.
.It Xo
.Cm umount
.Op Fl f
.Ar beName
.Xc
.It Xo
.Cm unmount
.Op Fl f
.Ar beName
.Xc
Unmount the given boot environment, if it is mounted.
Specifying
.Fl f
will force the unmount if busy.
.El
.Pp
.Nm
prints usage information if
.Fl h
or
.Fl \&?
is specified.
.Sh EXAMPLES
.Bl -bullet
.It
To fill in with jail upgrade example when behavior is firm.
.El
.Sh SEE ALSO
.Xr beinstall.sh 1 ,
.Xr libbe 3 ,
.Xr jail 8 ,
.Xr zfs 8 ,
.Xr zpool 8
.Sh HISTORY
.Nm
is based on
.Xr beadm 1
and was implemented as a project for the 2017 Summer of Code, along with
.Xr libbe 3 .
.Sh AUTHORS
.Nm
was written by
.An Kyle Kneitinger (kneitinger) Aq Mt kyle@kneit.in .
.Pp
.Xr beadm 1
was written and is maintained by
.An Slawomir Wojciech Wojtczak (vermaden) Aq Mt vermaden@interia.pl .
.Pp
.An Bryan Drewery (bdrewery) Aq Mt bryan@shatow.net
wrote the original
.Xr beadm 1
manual page that this one is derived from.

561
sbin/bectl/bectl.c Normal file
View File

@ -0,0 +1,561 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (c) 2017 Kyle J. Kneitinger <kyle@kneit.in>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/mount.h>
#include <errno.h>
#include <libutil.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <time.h>
#include <unistd.h>
#include <be.h>
#include "bectl.h"
static int bectl_cmd_activate(int argc, char *argv[]);
static int bectl_cmd_create(int argc, char *argv[]);
static int bectl_cmd_destroy(int argc, char *argv[]);
static int bectl_cmd_export(int argc, char *argv[]);
static int bectl_cmd_import(int argc, char *argv[]);
#if SOON
static int bectl_cmd_add(int argc, char *argv[]);
#endif
static int bectl_cmd_mount(int argc, char *argv[]);
static int bectl_cmd_rename(int argc, char *argv[]);
static int bectl_cmd_unmount(int argc, char *argv[]);
libbe_handle_t *be;
int
usage(bool explicit)
{
FILE *fp;
fp = explicit ? stdout : stderr;
fprintf(fp, "%s",
"usage:\tbectl {-h | -? | subcommand [args...]}\n"
#if SOON
"\tbectl add (path)*\n"
#endif
"\tbectl activate [-t] beName\n"
"\tbectl create [-r] [-e {nonActiveBe | beName@snapshot}] beName\n"
"\tbectl create [-r] beName@snapshot\n"
"\tbectl destroy [-F] {beName | beName@snapshot}\n"
"\tbectl export sourceBe\n"
"\tbectl import targetBe\n"
"\tbectl jail {-b | -U} [{-o key=value | -u key}]... "
"{jailID | jailName}\n"
"\t bootenv [utility [argument ...]]\n"
"\tbectl list [-DHas]\n"
"\tbectl mount beName [mountpoint]\n"
"\tbectl rename origBeName newBeName\n"
"\tbectl {ujail | unjail} {jailID | jailName} bootenv\n"
"\tbectl {umount | unmount} [-f] beName\n");
return (explicit ? 0 : EX_USAGE);
}
/*
* Represents a relationship between the command name and the parser action
* that handles it.
*/
struct command_map_entry {
const char *command;
int (*fn)(int argc, char *argv[]);
};
static struct command_map_entry command_map[] =
{
{ "activate", bectl_cmd_activate },
{ "create", bectl_cmd_create },
{ "destroy", bectl_cmd_destroy },
{ "export", bectl_cmd_export },
{ "import", bectl_cmd_import },
#if SOON
{ "add", bectl_cmd_add },
#endif
{ "jail", bectl_cmd_jail },
{ "list", bectl_cmd_list },
{ "mount", bectl_cmd_mount },
{ "rename", bectl_cmd_rename },
{ "unjail", bectl_cmd_unjail },
{ "unmount", bectl_cmd_unmount },
};
static int
get_cmd_index(const char *cmd, int *idx)
{
int map_size;
map_size = nitems(command_map);
for (int i = 0; i < map_size; ++i) {
if (strcmp(cmd, command_map[i].command) == 0) {
*idx = i;
return (0);
}
}
return (1);
}
static int
bectl_cmd_activate(int argc, char *argv[])
{
int err, opt;
bool temp;
temp = false;
while ((opt = getopt(argc, argv, "t")) != -1) {
switch (opt) {
case 't':
temp = true;
break;
default:
fprintf(stderr, "bectl activate: unknown option '-%c'\n",
optopt);
return (usage(false));
}
}
argc -= optind;
argv += optind;
if (argc != 1) {
fprintf(stderr, "bectl activate: wrong number of arguments\n");
return (usage(false));
}
/* activate logic goes here */
if ((err = be_activate(be, argv[0], temp)) != 0)
/* XXX TODO: more specific error msg based on err */
printf("did not successfully activate boot environment %s\n",
argv[0]);
else
printf("successfully activated boot environment %s\n", argv[0]);
if (temp)
printf("for next boot\n");
return (err);
}
/*
* TODO: when only one arg is given, and it contains an "@" the this should
* create that snapshot
*/
static int
bectl_cmd_create(int argc, char *argv[])
{
char *atpos, *bootenv, *snapname, *source;
int err, opt;
bool recursive;
snapname = NULL;
recursive = false;
while ((opt = getopt(argc, argv, "e:r")) != -1) {
switch (opt) {
case 'e':
snapname = optarg;
break;
case 'r':
recursive = true;
break;
default:
fprintf(stderr, "bectl create: unknown option '-%c'\n",
optopt);
return (usage(false));
}
}
argc -= optind;
argv += optind;
if (argc != 1) {
fprintf(stderr, "bectl create: wrong number of arguments\n");
return (usage(false));
}
bootenv = *argv;
if ((atpos = strchr(bootenv, '@')) != NULL) {
/*
* This is the "create a snapshot variant". No new boot
* environment is to be created here.
*/
*atpos++ = '\0';
err = be_snapshot(be, bootenv, atpos, recursive, NULL);
} else if (snapname != NULL) {
if (strchr(snapname, '@') != NULL)
err = be_create_from_existing_snap(be, bootenv,
snapname);
else
err = be_create_from_existing(be, bootenv, snapname);
} else {
if ((snapname = strchr(bootenv, '@')) != NULL) {
*(snapname++) = '\0';
if ((err = be_snapshot(be, be_active_path(be),
snapname, true, NULL)) != BE_ERR_SUCCESS)
fprintf(stderr, "failed to create snapshot\n");
asprintf(&source, "%s@%s", be_active_path(be), snapname);
err = be_create_from_existing_snap(be, bootenv,
source);
return (err);
} else
err = be_create(be, bootenv);
}
switch (err) {
case BE_ERR_SUCCESS:
break;
default:
if (atpos != NULL)
fprintf(stderr,
"failed to create a snapshot '%s' of '%s'\n",
atpos, bootenv);
else if (snapname == NULL)
fprintf(stderr,
"failed to create bootenv %s\n", bootenv);
else
fprintf(stderr,
"failed to create bootenv %s from snapshot %s\n",
bootenv, snapname);
}
return (err);
}
static int
bectl_cmd_export(int argc, char *argv[])
{
char *bootenv;
if (argc == 1) {
fprintf(stderr, "bectl export: missing boot environment name\n");
return (usage(false));
}
if (argc > 2) {
fprintf(stderr, "bectl export: extra arguments provided\n");
return (usage(false));
}
bootenv = argv[1];
if (isatty(STDOUT_FILENO)) {
fprintf(stderr, "bectl export: must redirect output\n");
return (EX_USAGE);
}
be_export(be, bootenv, STDOUT_FILENO);
return (0);
}
static int
bectl_cmd_import(int argc, char *argv[])
{
char *bootenv;
int err;
if (argc == 1) {
fprintf(stderr, "bectl import: missing boot environment name\n");
return (usage(false));
}
if (argc > 2) {
fprintf(stderr, "bectl import: extra arguments provided\n");
return (usage(false));
}
bootenv = argv[1];
if (isatty(STDIN_FILENO)) {
fprintf(stderr, "bectl import: input can not be from terminal\n");
return (EX_USAGE);
}
err = be_import(be, bootenv, STDIN_FILENO);
return (err);
}
#if SOON
static int
bectl_cmd_add(int argc, char *argv[])
{
if (argc < 2) {
fprintf(stderr, "bectl add: must provide at least one path\n");
return (usage(false));
}
for (int i = 1; i < argc; ++i) {
printf("arg %d: %s\n", i, argv[i]);
/* XXX TODO catch err */
be_add_child(be, argv[i], true);
}
return (0);
}
#endif
static int
bectl_cmd_destroy(int argc, char *argv[])
{
nvlist_t *props;
char *origin, *target, targetds[BE_MAXPATHLEN];
int err, flags, opt;
flags = 0;
while ((opt = getopt(argc, argv, "Fo")) != -1) {
switch (opt) {
case 'F':
flags |= BE_DESTROY_FORCE;
break;
case 'o':
flags |= BE_DESTROY_ORIGIN;
break;
default:
fprintf(stderr, "bectl destroy: unknown option '-%c'\n",
optopt);
return (usage(false));
}
}
argc -= optind;
argv += optind;
if (argc != 1) {
fprintf(stderr, "bectl destroy: wrong number of arguments\n");
return (usage(false));
}
target = argv[0];
/* We'll emit a notice if there's an origin to be cleaned up */
if ((flags & BE_DESTROY_ORIGIN) == 0 && strchr(target, '@') == NULL) {
if (be_root_concat(be, target, targetds) != 0)
goto destroy;
if (be_prop_list_alloc(&props) != 0)
goto destroy;
if (be_get_dataset_props(be, targetds, props) != 0) {
be_prop_list_free(props);
goto destroy;
}
if (nvlist_lookup_string(props, "origin", &origin) == 0)
fprintf(stderr, "bectl destroy: leaving origin '%s' intact\n",
origin);
be_prop_list_free(props);
}
destroy:
err = be_destroy(be, target, flags);
return (err);
}
static int
bectl_cmd_mount(int argc, char *argv[])
{
char result_loc[BE_MAXPATHLEN];
char *bootenv, *mountpoint;
int err, mntflags;
/* XXX TODO: Allow shallow */
mntflags = BE_MNT_DEEP;
if (argc < 2) {
fprintf(stderr, "bectl mount: missing argument(s)\n");
return (usage(false));
}
if (argc > 3) {
fprintf(stderr, "bectl mount: too many arguments\n");
return (usage(false));
}
bootenv = argv[1];
mountpoint = ((argc == 3) ? argv[2] : NULL);
err = be_mount(be, bootenv, mountpoint, mntflags, result_loc);
switch (err) {
case BE_ERR_SUCCESS:
printf("successfully mounted %s at %s\n", bootenv, result_loc);
break;
default:
fprintf(stderr,
(argc == 3) ? "failed to mount bootenv %s at %s\n" :
"failed to mount bootenv %s at temporary path %s\n",
bootenv, mountpoint);
}
return (err);
}
static int
bectl_cmd_rename(int argc, char *argv[])
{
char *dest, *src;
int err;
if (argc < 3) {
fprintf(stderr, "bectl rename: missing argument\n");
return (usage(false));
}
if (argc > 3) {
fprintf(stderr, "bectl rename: too many arguments\n");
return (usage(false));
}
src = argv[1];
dest = argv[2];
err = be_rename(be, src, dest);
switch (err) {
case BE_ERR_SUCCESS:
break;
default:
fprintf(stderr, "failed to rename bootenv %s to %s\n",
src, dest);
}
return (0);
}
static int
bectl_cmd_unmount(int argc, char *argv[])
{
char *bootenv, *cmd;
int err, flags, opt;
/* Store alias used */
cmd = argv[0];
flags = 0;
while ((opt = getopt(argc, argv, "f")) != -1) {
switch (opt) {
case 'f':
flags |= BE_MNT_FORCE;
break;
default:
fprintf(stderr, "bectl %s: unknown option '-%c'\n",
cmd, optopt);
return (usage(false));
}
}
argc -= optind;
argv += optind;
if (argc != 1) {
fprintf(stderr, "bectl %s: wrong number of arguments\n", cmd);
return (usage(false));
}
bootenv = argv[0];
err = be_unmount(be, bootenv, flags);
switch (err) {
case BE_ERR_SUCCESS:
break;
default:
fprintf(stderr, "failed to unmount bootenv %s\n", bootenv);
}
return (err);
}
int
main(int argc, char *argv[])
{
const char *command;
char *root;
int command_index, rc;
root = NULL;
if (argc < 2)
return (usage(false));
if (strcmp(argv[1], "-r") == 0) {
if (argc < 4)
return (usage(false));
root = strdup(argv[2]);
command = argv[3];
argc -= 3;
argv += 3;
} else {
command = argv[1];
argc -= 1;
argv += 1;
}
/* Handle command aliases */
if (strcmp(command, "umount") == 0)
command = "unmount";
if (strcmp(command, "ujail") == 0)
command = "unjail";
if ((strcmp(command, "-?") == 0) || (strcmp(command, "-h") == 0))
return (usage(true));
if (get_cmd_index(command, &command_index)) {
fprintf(stderr, "unknown command: %s\n", command);
return (usage(false));
}
if ((be = libbe_init(root)) == NULL)
return (-1);
libbe_print_on_error(be, true);
rc = command_map[command_index].fn(argc, argv);
libbe_close(be);
return (rc);
}

37
sbin/bectl/bectl.h Normal file
View File

@ -0,0 +1,37 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (c) 2018 Kyle Evans <kevans@FreeBSD.org>
*
* 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;

486
sbin/bectl/bectl_jail.c Normal file
View File

@ -0,0 +1,486 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (c) 2018 Kyle Evans <kevans@FreeBSD.org>
*
* 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 <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/jail.h>
#include <sys/mount.h>
#include <sys/wait.h>
#include <err.h>
#include <jail.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <be.h>
#include "bectl.h"
#define MNTTYPE_ZFS 222
static void jailparam_add(const char *name, const char *val);
static int jailparam_del(const char *name);
static bool jailparam_addarg(char *arg);
static int jailparam_delarg(char *arg);
static int bectl_search_jail_paths(const char *mnt);
static int bectl_locate_jail(const char *ident);
static int bectl_jail_cleanup(char *mountpoint, int jid);
static char mnt_loc[BE_MAXPATHLEN];
static nvlist_t *jailparams;
static const char *disabled_params[] = {
"command", "exec.start", "nopersist", "persist", NULL
};
static void
jailparam_add(const char *name, const char *val)
{
nvlist_add_string(jailparams, name, val);
}
static int
jailparam_del(const char *name)
{
nvlist_remove_all(jailparams, name);
return (0);
}
static bool
jailparam_addarg(char *arg)
{
char *name, *val;
size_t i, len;
if (arg == NULL)
return (false);
name = arg;
if ((val = strchr(arg, '=')) == NULL) {
fprintf(stderr, "bectl jail: malformed jail option '%s'\n",
arg);
return (false);
}
*val++ = '\0';
if (strcmp(name, "path") == 0) {
if (strlen(val) >= BE_MAXPATHLEN) {
fprintf(stderr,
"bectl jail: skipping too long path assignment '%s' (max length = %d)\n",
val, BE_MAXPATHLEN);
return (false);
}
strlcpy(mnt_loc, val, sizeof(mnt_loc));
}
for (i = 0; disabled_params[i] != NULL; i++) {
len = strlen(disabled_params[i]);
if (strncmp(disabled_params[i], name, len) == 0) {
fprintf(stderr, "invalid jail parameter: %s\n", name);
return (false);
}
}
jailparam_add(name, val);
return (true);
}
static int
jailparam_delarg(char *arg)
{
char *name, *val;
if (arg == NULL)
return (EINVAL);
name = arg;
if ((val = strchr(name, '=')) != NULL)
*val++ = '\0';
if (strcmp(name, "path") == 0)
*mnt_loc = '\0';
return (jailparam_del(name));
}
static int
build_jailcmd(char ***argvp, bool interactive, int argc, char *argv[])
{
char *cmd, **jargv, *name, *val;
nvpair_t *nvp;
size_t i, iarg, nargv;
cmd = NULL;
nvp = NULL;
iarg = i = 0;
if (nvlist_size(jailparams, &nargv, NV_ENCODE_NATIVE) != 0)
return (1);
/*
* Number of args + "/usr/sbin/jail", "-c", and ending NULL.
* If interactive also include command.
*/
nargv += 3;
if (interactive) {
if (argc == 0)
nargv++;
else
nargv += argc;
}
jargv = *argvp = calloc(nargv, sizeof(jargv));
if (jargv == NULL)
err(2, "calloc");
jargv[iarg++] = strdup("/usr/sbin/jail");
jargv[iarg++] = strdup("-c");
while ((nvp = nvlist_next_nvpair(jailparams, nvp)) != NULL) {
name = nvpair_name(nvp);
if (nvpair_value_string(nvp, &val) != 0)
continue;
if (asprintf(&jargv[iarg++], "%s=%s", name, val) < 0)
goto error;
}
if (interactive) {
if (argc < 1)
cmd = strdup("/bin/sh");
else {
cmd = argv[0];
argc--;
argv++;
}
if (asprintf(&jargv[iarg++], "command=%s", cmd) < 0) {
goto error;
}
if (argc < 1) {
free(cmd);
cmd = NULL;
}
for (; argc > 0; argc--) {
if (asprintf(&jargv[iarg++], "%s", argv[0]) < 0)
goto error;
argv++;
}
}
return (0);
error:
if (interactive && argc < 1)
free(cmd);
for (; i < iarg - 1; i++) {
free(jargv[i]);
}
free(jargv);
return (1);
}
/* Remove jail and cleanup any non zfs mounts. */
static int
bectl_jail_cleanup(char *mountpoint, int jid)
{
struct statfs *mntbuf;
size_t i, searchlen, mntsize;
if (jid >= 0 && jail_remove(jid) != 0) {
fprintf(stderr, "unable to remove jail");
return (1);
}
searchlen = strnlen(mountpoint, MAXPATHLEN);
mntsize = getmntinfo(&mntbuf, MNT_NOWAIT);
for (i = 0; i < mntsize; i++) {
if (strncmp(mountpoint, mntbuf[i].f_mntonname, searchlen) == 0 &&
mntbuf[i].f_type != MNTTYPE_ZFS) {
if (unmount(mntbuf[i].f_mntonname, 0) != 0) {
fprintf(stderr, "bectl jail: unable to unmount filesystem %s",
mntbuf[i].f_mntonname);
return (1);
}
}
}
return (0);
}
int
bectl_cmd_jail(int argc, char *argv[])
{
char *bootenv, **jargv, *mountpoint;
int i, jid, mntflags, opt, ret;
bool default_hostname, interactive, unjail;
pid_t pid;
/* XXX TODO: Allow shallow */
mntflags = BE_MNT_DEEP;
default_hostname = interactive = unjail = true;
if ((nvlist_alloc(&jailparams, NV_UNIQUE_NAME, 0)) != 0) {
fprintf(stderr, "nvlist_alloc() failed\n");
return (1);
}
jailparam_add("persist", "true");
jailparam_add("allow.mount", "true");
jailparam_add("allow.mount.devfs", "true");
jailparam_add("enforce_statfs", "1");
while ((opt = getopt(argc, argv, "bo:Uu:")) != -1) {
switch (opt) {
case 'b':
interactive = false;
break;
case 'o':
if (jailparam_addarg(optarg)) {
/*
* optarg has been modified to null terminate
* at the assignment operator.
*/
if (strcmp(optarg, "host.hostname") == 0)
default_hostname = false;
} else {
return (1);
}
break;
case 'U':
unjail = false;
break;
case 'u':
if ((ret = jailparam_delarg(optarg)) == 0) {
if (strcmp(optarg, "host.hostname") == 0)
default_hostname = true;
} else if (ret != ENOENT) {
fprintf(stderr,
"bectl jail: error unsetting \"%s\"\n",
optarg);
return (ret);
}
break;
default:
fprintf(stderr, "bectl jail: unknown option '-%c'\n",
optopt);
return (usage(false));
}
}
argc -= optind;
argv += optind;
if (argc < 1) {
fprintf(stderr, "bectl jail: missing boot environment name\n");
return (usage(false));
}
bootenv = argv[0];
argc--;
argv++;
/*
* XXX TODO: if its already mounted, perhaps there should be a flag to
* indicate its okay to proceed??
*/
if (*mnt_loc == '\0')
mountpoint = NULL;
else
mountpoint = mnt_loc;
if (be_mount(be, bootenv, mountpoint, mntflags, mnt_loc) != BE_ERR_SUCCESS) {
fprintf(stderr, "could not mount bootenv\n");
return (1);
}
if (default_hostname)
jailparam_add("host.hostname", bootenv);
/*
* This is our indicator that path was not set by the user, so we'll use
* the path that libbe generated for us.
*/
if (mountpoint == NULL) {
jailparam_add("path", mnt_loc);
mountpoint = mnt_loc;
}
if ((build_jailcmd(&jargv, interactive, argc, argv)) != 0) {
fprintf(stderr, "unable to build argument list for jail command\n");
return (1);
}
pid = fork();
switch (pid) {
case -1:
perror("fork");
return (1);
case 0:
execv("/usr/sbin/jail", jargv);
fprintf(stderr, "bectl jail: failed to execute\n");
default:
waitpid(pid, NULL, 0);
}
for (i = 0; jargv[i] != NULL; i++) {
free(jargv[i]);
}
free(jargv);
if (!interactive)
return (0);
if (unjail) {
/*
* We're not checking the jail id result here because in the
* case of invalid param, or last command in jail was an error
* the jail will not exist upon exit. bectl_jail_cleanup will
* only jail_remove if the jid is >= 0.
*/
jid = bectl_locate_jail(bootenv);
bectl_jail_cleanup(mountpoint, jid);
be_unmount(be, bootenv, 0);
}
return (0);
}
static int
bectl_search_jail_paths(const char *mnt)
{
int jid;
char lastjid[16];
char jailpath[MAXPATHLEN];
/* jail_getv expects name/value strings */
snprintf(lastjid, sizeof(lastjid), "%d", 0);
while ((jid = jail_getv(0, "lastjid", lastjid, "path", &jailpath,
NULL)) != -1) {
/* the jail we've been looking for */
if (strcmp(jailpath, mnt) == 0)
return (jid);
/* update lastjid and keep on looking */
snprintf(lastjid, sizeof(lastjid), "%d", jid);
}
return (-1);
}
/*
* Locate a jail based on an arbitrary identifier. This may be either a name,
* a jid, or a BE name. Returns the jid or -1 on failure.
*/
static int
bectl_locate_jail(const char *ident)
{
nvlist_t *belist, *props;
char *mnt;
int jid;
/* Try the easy-match first */
jid = jail_getid(ident);
if (jid != -1)
return (jid);
/* Attempt to try it as a BE name, first */
if (be_prop_list_alloc(&belist) != 0)
return (-1);
if (be_get_bootenv_props(be, belist) != 0)
return (-1);
if (nvlist_lookup_nvlist(belist, ident, &props) == 0) {
/* path where a boot environment is mounted */
if (nvlist_lookup_string(props, "mounted", &mnt) == 0) {
/* looking for a jail that matches our bootenv path */
jid = bectl_search_jail_paths(mnt);
be_prop_list_free(belist);
return (jid);
}
be_prop_list_free(belist);
}
return (-1);
}
int
bectl_cmd_unjail(int argc, char *argv[])
{
char path[MAXPATHLEN];
char *cmd, *name, *target;
int jid;
/* Store alias used */
cmd = argv[0];
if (argc != 2) {
fprintf(stderr, "bectl %s: wrong number of arguments\n", cmd);
return (usage(false));
}
target = argv[1];
/* Locate the jail */
if ((jid = bectl_locate_jail(target)) == -1) {
fprintf(stderr, "bectl %s: failed to locate BE by '%s'\n", cmd,
target);
return (1);
}
bzero(&path, MAXPATHLEN);
name = jail_getname(jid);
if (jail_getv(0, "name", name, "path", path, NULL) != jid) {
free(name);
fprintf(stderr,
"bectl %s: failed to get path for jail requested by '%s'\n",
cmd, target);
return (1);
}
free(name);
if (be_mounted_at(be, path, NULL) != 0) {
fprintf(stderr, "bectl %s: jail requested by '%s' not a BE\n",
cmd, target);
return (1);
}
bectl_jail_cleanup(path, jid);
be_unmount(be, target, 0);
return (0);
}

419
sbin/bectl/bectl_list.c Normal file
View File

@ -0,0 +1,419 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (c) 2018 Kyle Evans <kevans@FreeBSD.org>
*
* 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 <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <be.h>
#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);
}

View File

@ -0,0 +1,7 @@
# $FreeBSD$
PACKAGE= tests
ATF_TESTS_SH+= bectl_test
.include <bsd.test.mk>

368
sbin/bectl/tests/bectl_test.sh Executable file
View File

@ -0,0 +1,368 @@
#
# SPDX-License-Identifier: BSD-2-Clause-FreeBSD
#
# Copyright (c) 2018 Kyle Evans <kevans@FreeBSD.org>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
# $FreeBSD$
ZPOOL_NAME_FILE=zpool_name
get_zpool_name()
{
cat $ZPOOL_NAME_FILE
}
make_zpool_name()
{
mktemp -u bectl_test_XXXXXX > $ZPOOL_NAME_FILE
get_zpool_name
}
# Establishes a bectl_create zpool that can be used for some light testing; contains
# a 'default' BE and not much else.
bectl_create_setup()
{
zpool=$1
disk=$2
mnt=$3
# Sanity check to make sure `make_zpool_name` succeeded
atf_check test -n "$zpool"
kldload -n -q zfs || atf_skip "ZFS module not loaded on the current system"
atf_check mkdir -p ${mnt}
atf_check truncate -s 1G ${disk}
atf_check zpool create -o altroot=${mnt} ${zpool} ${disk}
atf_check zfs create -o mountpoint=none ${zpool}/ROOT
atf_check zfs create -o mountpoint=/ -o canmount=noauto \
${zpool}/ROOT/default
}
bectl_create_deep_setup()
{
zpool=$1
disk=$2
mnt=$3
# Sanity check to make sure `make_zpool_name` succeeded
atf_check test -n "$zpool"
bectl_create_setup ${zpool} ${disk} ${mnt}
atf_check mkdir -p ${root}
atf_check -o ignore bectl -r ${zpool}/ROOT mount default ${root}
atf_check mkdir -p ${root}/usr
atf_check zfs create -o mountpoint=/usr -o canmount=noauto \
${zpool}/ROOT/default/usr
atf_check -o ignore bectl -r ${zpool}/ROOT umount default
}
bectl_cleanup()
{
zpool=$1
if [ -z "$zpool" ]; then
echo "Skipping cleanup; zpool not set up"
elif zpool get health ${zpool} >/dev/null 2>&1; then
zpool destroy -f ${zpool}
fi
}
atf_test_case bectl_create cleanup
bectl_create_head()
{
atf_set "descr" "Check the various forms of bectl create"
atf_set "require.user" root
}
bectl_create_body()
{
cwd=$(realpath .)
zpool=$(make_zpool_name)
disk=${cwd}/disk.img
mount=${cwd}/mnt
bectl_create_setup ${zpool} ${disk} ${mount}
# Test standard creation, creation of a snapshot, and creation from a
# snapshot.
atf_check bectl -r ${zpool}/ROOT create -e default default2
atf_check bectl -r ${zpool}/ROOT create default2@test_snap
atf_check bectl -r ${zpool}/ROOT create -e default2@test_snap default3
}
bectl_create_cleanup()
{
bectl_cleanup $(get_zpool_name)
}
atf_test_case bectl_destroy cleanup
bectl_destroy_head()
{
atf_set "descr" "Check bectl destroy"
atf_set "require.user" root
}
bectl_destroy_body()
{
cwd=$(realpath .)
zpool=$(make_zpool_name)
disk=${cwd}/disk.img
mount=${cwd}/mnt
root=${mount}/root
bectl_create_setup ${zpool} ${disk} ${mount}
atf_check bectl -r ${zpool}/ROOT create -e default default2
atf_check -o not-empty zfs get mountpoint ${zpool}/ROOT/default2
atf_check -e ignore bectl -r ${zpool}/ROOT destroy default2
atf_check -e not-empty -s not-exit:0 zfs get mountpoint ${zpool}/ROOT/default2
# Test origin snapshot deletion when the snapshot to be destroyed
# belongs to a mounted dataset, see PR 236043.
atf_check mkdir -p ${root}
atf_check -o not-empty bectl -r ${zpool}/ROOT mount default ${root}
atf_check bectl -r ${zpool}/ROOT create -e default default3
atf_check bectl -r ${zpool}/ROOT destroy -o default3
atf_check bectl -r ${zpool}/ROOT unmount default
}
bectl_destroy_cleanup()
{
bectl_cleanup $(get_zpool_name)
}
atf_test_case bectl_export_import cleanup
bectl_export_import_head()
{
atf_set "descr" "Check bectl export and import"
atf_set "require.user" root
}
bectl_export_import_body()
{
cwd=$(realpath .)
zpool=$(make_zpool_name)
disk=${cwd}/disk.img
mount=${cwd}/mnt
bectl_create_setup ${zpool} ${disk} ${mount}
atf_check -o save:exported bectl -r ${zpool}/ROOT export default
atf_check -x "bectl -r ${zpool}/ROOT import default2 < exported"
atf_check -o not-empty zfs get mountpoint ${zpool}/ROOT/default2
atf_check -e ignore bectl -r ${zpool}/ROOT destroy default2
atf_check -e not-empty -s not-exit:0 zfs get mountpoint \
${zpool}/ROOT/default2
}
bectl_export_import_cleanup()
{
bectl_cleanup $(get_zpool_name)
}
atf_test_case bectl_list cleanup
bectl_list_head()
{
atf_set "descr" "Check bectl list"
atf_set "require.user" root
}
bectl_list_body()
{
cwd=$(realpath .)
zpool=$(make_zpool_name)
disk=${cwd}/disk.img
mount=${cwd}/mnt
bectl_create_setup ${zpool} ${disk} ${mount}
# Test the list functionality, including that BEs come and go away
# as they're created and destroyed. Creation and destruction tests
# use the 'zfs' utility to verify that they're actually created, so
# these are just light tests that 'list' is picking them up.
atf_check -o save:list.out bectl -r ${zpool}/ROOT list
atf_check -o not-empty grep 'default' list.out
atf_check bectl -r ${zpool}/ROOT create -e default default2
atf_check -o save:list.out bectl -r ${zpool}/ROOT list
atf_check -o not-empty grep 'default2' list.out
atf_check -e ignore bectl -r ${zpool}/ROOT destroy default2
atf_check -o save:list.out bectl -r ${zpool}/ROOT list
atf_check -s not-exit:0 grep 'default2' list.out
# XXX TODO: Formatting checks
}
bectl_list_cleanup()
{
bectl_cleanup $(get_zpool_name)
}
atf_test_case bectl_mount cleanup
bectl_mount_head()
{
atf_set "descr" "Check bectl mount/unmount"
atf_set "require.user" root
}
bectl_mount_body()
{
cwd=$(realpath .)
zpool=$(make_zpool_name)
disk=${cwd}/disk.img
mount=${cwd}/mnt
root=${mount}/root
bectl_create_deep_setup ${zpool} ${disk} ${mount}
atf_check mkdir -p ${root}
# Test unmount first...
atf_check -o not-empty bectl -r ${zpool}/ROOT mount default ${root}
atf_check -o not-empty -x "mount | grep '^${zpool}/ROOT/default'"
atf_check bectl -r ${zpool}/ROOT unmount default
atf_check -s not-exit:0 -x "mount | grep '^${zpool}/ROOT/default'"
# Then umount!
atf_check -o not-empty bectl -r ${zpool}/ROOT mount default ${root}
atf_check -o not-empty -x "mount | grep '^${zpool}/ROOT/default'"
atf_check bectl -r ${zpool}/ROOT umount default
atf_check -s not-exit:0 -x "mount | grep '^${zpool}/ROOT/default'"
}
bectl_mount_cleanup()
{
bectl_cleanup $(get_zpool_name)
}
atf_test_case bectl_rename cleanup
bectl_rename_head()
{
atf_set "descr" "Check bectl rename"
atf_set "require.user" root
}
bectl_rename_body()
{
cwd=$(realpath .)
zpool=$(make_zpool_name)
disk=${cwd}/disk.img
mount=${cwd}/mnt
bectl_create_setup ${zpool} ${disk} ${mount}
atf_check bectl -r ${zpool}/ROOT rename default default2
atf_check -o not-empty zfs get mountpoint ${zpool}/ROOT/default2
atf_check -e not-empty -s not-exit:0 zfs get mountpoint \
${zpool}/ROOT/default
}
bectl_rename_cleanup()
{
bectl_cleanup $(get_zpool_name)
}
atf_test_case bectl_jail cleanup
bectl_jail_head()
{
atf_set "descr" "Check bectl rename"
atf_set "require.user" root
}
bectl_jail_body()
{
cwd=$(realpath .)
zpool=$(make_zpool_name)
disk=${cwd}/disk.img
mount=${cwd}/mnt
root=${mount}/root
if [ ! -f /rescue/rescue ]; then
atf_skip "This test requires a rescue binary"
fi
bectl_create_deep_setup ${zpool} ${disk} ${mount}
# Prepare our minimal BE... plop a rescue binary into it
atf_check mkdir -p ${root}
atf_check -o ignore bectl -r ${zpool}/ROOT mount default ${root}
atf_check mkdir -p ${root}/rescue
atf_check cp /rescue/rescue ${root}/rescue/rescue
atf_check bectl -r ${zpool}/ROOT umount default
# Prepare a second boot environment
atf_check -o empty -s exit:0 bectl -r ${zpool}/ROOT create -e default target
# When a jail name is not explicit, it should match the jail id.
atf_check -o empty -s exit:0 bectl -r ${zpool}/ROOT jail -b -o jid=233637 default
atf_check -o inline:"233637\n" -s exit:0 -x "jls -j 233637 name"
atf_check -o empty -s exit:0 bectl -r ${zpool}/ROOT unjail default
# Basic command-mode tests, with and without jail cleanup
atf_check -o inline:"rescue\nusr\n" bectl -r ${zpool}/ROOT \
jail default /rescue/rescue ls -1
atf_check -o inline:"rescue\nusr\n" bectl -r ${zpool}/ROOT \
jail -Uo path=${root} default /rescue/rescue ls -1
atf_check [ -f ${root}/rescue/rescue ]
atf_check bectl -r ${zpool}/ROOT ujail default
# Batch mode tests
atf_check bectl -r ${zpool}/ROOT jail -bo path=${root} default
atf_check -o not-empty -x "jls | grep -F \"${root}\""
atf_check bectl -r ${zpool}/ROOT ujail default
atf_check -s not-exit:0 -x "jls | grep -F \"${root}\""
# 'unjail' naming
atf_check bectl -r ${zpool}/ROOT jail -b default
atf_check bectl -r ${zpool}/ROOT unjail default
atf_check -s not-exit:0 -x "jls | grep -F \"${root}\""
# 'unjail' by BE name. Force bectl to lookup jail id by the BE name.
atf_check -o empty -s exit:0 bectl -r ${zpool}/ROOT jail -b default
atf_check -o empty -s exit:0 bectl -r ${zpool}/ROOT jail -b -o name=bectl_test target
atf_check -o empty -s exit:0 bectl -r ${zpool}/ROOT unjail target
atf_check -o empty -s exit:0 bectl -r ${zpool}/ROOT unjail default
# cannot unjail an unjailed BE (by either command name)
atf_check -e ignore -s not-exit:0 bectl -r ${zpool}/ROOT ujail default
atf_check -e ignore -s not-exit:0 bectl -r ${zpool}/ROOT unjail default
# set+unset
atf_check bectl -r ${zpool}/ROOT jail -b -o path=${root} -u path default
# Ensure that it didn't mount at ${root}
atf_check -s not-exit:0 -x "mount | grep -F '${root}'"
atf_check bectl -r ${zpool}/ROOT ujail default
}
# If a test has failed, it's possible that the boot environment hasn't
# been 'unjail'ed. We want to remove the jail before 'bectl_cleanup'
# attempts to destroy the zpool.
bectl_jail_cleanup()
{
for bootenv in "default" "target"; do
# mountpoint of the boot environment
mountpoint="$(bectl -r bectl_test/ROOT list -H | grep ${bootenv} | awk '{print $3}')"
# see if any jail paths match the boot environment mountpoint
jailid="$(jls | grep ${mountpoint} | awk '{print $1}')"
if [ -z "$jailid" ]; then
continue;
fi
jail -r ${jailid}
done;
bectl_cleanup $(get_zpool_name)
}
atf_init_test_cases()
{
atf_add_test_case bectl_create
atf_add_test_case bectl_destroy
atf_add_test_case bectl_export_import
atf_add_test_case bectl_list
atf_add_test_case bectl_mount
atf_add_test_case bectl_rename
atf_add_test_case bectl_jail
}

View File

@ -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

View File

@ -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.

View File

@ -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