Merge libbe(3)/bectl(8) from projects/bectl into head
bectl(8) is an administrative interface for working with ZFS boot environments, intended to provide a superset of the functionality provided by sysutils/beadm. libbe(3) is the back-end library that the required functionality has been pulled out into for later reuse. These were originally written for GSoC 2017 under the mentorship of allanjude@. bectl(8) has proven pretty stable in my testing, with the known bug documented in the man page. Relnotes: yes
This commit is contained in:
commit
c506761767
@ -2524,7 +2524,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/libufs \
|
||||
lib/libutil lib/libpjdlog ${_lib_libypclnt} lib/libz lib/msun \
|
||||
@ -2597,7 +2597,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
|
||||
|
||||
cddl/lib/libbe__L: cddl/lib/libzfs__L
|
||||
.endif
|
||||
_cddl_lib_libctf= cddl/lib/libctf
|
||||
_cddl_lib= cddl/lib
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
SUBDIR= ${_drti} \
|
||||
libavl \
|
||||
${_libbe} \
|
||||
libctf \
|
||||
${_libdtrace} \
|
||||
libnvpair \
|
||||
@ -16,6 +17,7 @@ SUBDIR= ${_drti} \
|
||||
SUBDIR.${MK_TESTS}+= tests
|
||||
|
||||
.if ${MK_ZFS} != "no"
|
||||
_libbe= libbe
|
||||
_libzfs_core= libzfs_core
|
||||
_libzfs= libzfs
|
||||
.if ${MK_LIBTHR} != "no"
|
||||
@ -28,6 +30,7 @@ _drti= drti
|
||||
_libdtrace= libdtrace
|
||||
.endif
|
||||
|
||||
SUBDIR_DEPEND_libbe= libnvpair libzfs
|
||||
SUBDIR_DEPEND_libdtrace= libctf
|
||||
SUBDIR_DEPEND_libzfs_core= libnvpair
|
||||
SUBDIR_DEPEND_libzfs= libavl libnvpair libumem libuutil libzfs_core
|
||||
|
35
cddl/lib/libbe/Makefile
Normal file
35
cddl/lib/libbe/Makefile
Normal file
@ -0,0 +1,35 @@
|
||||
# $FreeBSD$
|
||||
|
||||
PACKAGE= lib${LIB}
|
||||
LIB= be
|
||||
SHLIB_MAJOR= 1
|
||||
SHLIB_MINOR= 0
|
||||
LIBBE_SRC= ${SRCTOP}/lib/libbe
|
||||
|
||||
.PATH: ${LIBBE_SRC}
|
||||
SRCS= be.c be_access.c be_error.c be_info.c
|
||||
INCS= be.h
|
||||
MAN= libbe.3
|
||||
|
||||
WARNS?= 2
|
||||
|
||||
LIBADD+= zfs
|
||||
LIBADD+= nvpair
|
||||
|
||||
CFLAGS+= -I${LIBBE_SRC}
|
||||
CFLAGS+= -I${SRCTOP}/cddl/contrib/opensolaris/lib/libnvpair
|
||||
CFLAGS+= -I${SRCTOP}/cddl/contrib/opensolaris/lib/libzfs/common
|
||||
CFLAGS+= -I${SRCTOP}/cddl/contrib/opensolaris/lib/libzfs_core/common
|
||||
CFLAGS+= -I${SRCTOP}/sys/cddl/compat/opensolaris
|
||||
CFLAGS+= -I${SRCTOP}/cddl/compat/opensolaris/include
|
||||
CFLAGS+= -I${SRCTOP}/cddl/compat/opensolaris/lib/libumem
|
||||
CFLAGS+= -I${SRCTOP}/cddl/contrib/opensolaris/lib/libzpool/common
|
||||
CFLAGS+= -I${SRCTOP}/sys/cddl/contrib/opensolaris/common/zfs
|
||||
CFLAGS+= -I${SRCTOP}/sys/cddl/contrib/opensolaris/uts/common/fs/zfs
|
||||
CFLAGS+= -I${SRCTOP}/sys/cddl/contrib/opensolaris/uts/common
|
||||
CFLAGS+= -I${SRCTOP}/cddl/contrib/opensolaris/head
|
||||
LDFLAGS+= -v
|
||||
|
||||
CFLAGS+= -DNEED_SOLARIS_BOOLEAN
|
||||
|
||||
.include <bsd.lib.mk>
|
@ -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)")
|
||||
|
955
lib/libbe/be.c
Normal file
955
lib/libbe/be.c
Normal file
@ -0,0 +1,955 @@
|
||||
/*-
|
||||
* 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/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <ctype.h>
|
||||
#include <kenv.h>
|
||||
#include <libgen.h>
|
||||
#include <libzfs_core.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "be.h"
|
||||
#include "be_impl.h"
|
||||
|
||||
#if SOON
|
||||
static int be_create_child_noent(libbe_handle_t *lbh, const char *active,
|
||||
const char *child_path);
|
||||
static int be_create_child_cloned(libbe_handle_t *lbh, const char *active);
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Iterator function for locating the rootfs amongst the children of the
|
||||
* zfs_be_root set by loader(8). data is expected to be a libbe_handle_t *.
|
||||
*/
|
||||
static int
|
||||
be_locate_rootfs(zfs_handle_t *chkds, void *data)
|
||||
{
|
||||
libbe_handle_t *lbh;
|
||||
char *mntpoint;
|
||||
|
||||
lbh = (libbe_handle_t *)data;
|
||||
if (lbh == NULL)
|
||||
return (1);
|
||||
|
||||
if (zfs_is_mounted(chkds, &mntpoint) && strcmp(mntpoint, "/") == 0) {
|
||||
strncpy(lbh->rootfs, zfs_get_name(chkds), BE_MAXPATHLEN);
|
||||
return (1);
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Initializes the libbe context to operate in the root boot environment
|
||||
* dataset, for example, zroot/ROOT.
|
||||
*/
|
||||
libbe_handle_t *
|
||||
libbe_init(void)
|
||||
{
|
||||
struct stat sb;
|
||||
dev_t root_dev, boot_dev;
|
||||
libbe_handle_t *lbh;
|
||||
zfs_handle_t *rootds;
|
||||
char *poolname, *pos;
|
||||
int pnamelen;
|
||||
|
||||
lbh = NULL;
|
||||
poolname = pos = NULL;
|
||||
pnamelen = 0;
|
||||
rootds = NULL;
|
||||
|
||||
/* Verify that /boot and / are mounted on the same filesystem */
|
||||
/* TODO: use errno here?? */
|
||||
if (stat("/", &sb) != 0)
|
||||
goto err;
|
||||
|
||||
root_dev = sb.st_dev;
|
||||
|
||||
if (stat("/boot", &sb) != 0)
|
||||
goto err;
|
||||
|
||||
boot_dev = sb.st_dev;
|
||||
|
||||
if (root_dev != boot_dev) {
|
||||
fprintf(stderr, "/ and /boot not on same device, quitting\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if ((lbh = calloc(1, sizeof(libbe_handle_t))) == NULL)
|
||||
goto err;
|
||||
|
||||
if ((lbh->lzh = libzfs_init()) == NULL)
|
||||
goto err;
|
||||
|
||||
/* Obtain path to boot environment root */
|
||||
if ((kenv(KENV_GET, "zfs_be_root", lbh->root, BE_MAXPATHLEN)) == -1)
|
||||
goto err;
|
||||
|
||||
/* Remove leading 'zfs:' if present, otherwise use value as-is */
|
||||
if (strcmp(lbh->root, "zfs:") == 0)
|
||||
strncpy(lbh->root, strchr(lbh->root, ':') + sizeof(char),
|
||||
BE_MAXPATHLEN);
|
||||
|
||||
if ((pos = strchr(lbh->root, '/')) == NULL)
|
||||
goto err;
|
||||
|
||||
pnamelen = pos - lbh->root;
|
||||
poolname = malloc(pnamelen + 1);
|
||||
if (poolname == NULL)
|
||||
goto err;
|
||||
|
||||
strncpy(poolname, lbh->root, pnamelen);
|
||||
poolname[pnamelen] = '\0';
|
||||
if ((lbh->active_phandle = zpool_open(lbh->lzh, poolname)) == NULL)
|
||||
goto err;
|
||||
|
||||
if (zpool_get_prop(lbh->active_phandle, ZPOOL_PROP_BOOTFS, lbh->bootfs,
|
||||
BE_MAXPATHLEN, NULL, true) != 0)
|
||||
goto err;
|
||||
|
||||
/* Obtain path to boot environment rootfs (currently booted) */
|
||||
/* XXX Get dataset mounted at / by kenv/GUID from mountroot? */
|
||||
if ((rootds = zfs_open(lbh->lzh, lbh->root, ZFS_TYPE_DATASET)) == NULL)
|
||||
goto err;
|
||||
|
||||
zfs_iter_filesystems(rootds, be_locate_rootfs, lbh);
|
||||
zfs_close(rootds);
|
||||
rootds = NULL;
|
||||
if (*lbh->rootfs == '\0')
|
||||
goto err;
|
||||
|
||||
return (lbh);
|
||||
err:
|
||||
if (lbh != NULL) {
|
||||
if (lbh->active_phandle != NULL)
|
||||
zpool_close(lbh->active_phandle);
|
||||
if (lbh->lzh != NULL)
|
||||
libzfs_fini(lbh->lzh);
|
||||
free(lbh);
|
||||
}
|
||||
if (rootds != NULL)
|
||||
zfs_close(rootds);
|
||||
free(poolname);
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Free memory allocated by libbe_init()
|
||||
*/
|
||||
void
|
||||
libbe_close(libbe_handle_t *lbh)
|
||||
{
|
||||
|
||||
if (lbh->active_phandle != NULL)
|
||||
zpool_close(lbh->active_phandle);
|
||||
libzfs_fini(lbh->lzh);
|
||||
free(lbh);
|
||||
}
|
||||
|
||||
/*
|
||||
* Proxy through to libzfs for the moment.
|
||||
*/
|
||||
void
|
||||
be_nicenum(uint64_t num, char *buf, size_t buflen)
|
||||
{
|
||||
|
||||
zfs_nicenum(num, buf, buflen);
|
||||
}
|
||||
|
||||
static int
|
||||
be_destroy_cb(zfs_handle_t *zfs_hdl, void *data)
|
||||
{
|
||||
int err;
|
||||
|
||||
if ((err = zfs_iter_children(zfs_hdl, be_destroy_cb, data)) != 0)
|
||||
return (err);
|
||||
if ((err = zfs_destroy(zfs_hdl, false)) != 0)
|
||||
return (err);
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Destroy the boot environment or snapshot specified by the name
|
||||
* parameter. Options are or'd together with the possible values:
|
||||
* BE_DESTROY_FORCE : forces operation on mounted datasets
|
||||
*/
|
||||
int
|
||||
be_destroy(libbe_handle_t *lbh, const char *name, int options)
|
||||
{
|
||||
zfs_handle_t *fs;
|
||||
char path[BE_MAXPATHLEN];
|
||||
char *p;
|
||||
int err, force, mounted;
|
||||
|
||||
p = path;
|
||||
force = options & BE_DESTROY_FORCE;
|
||||
err = BE_ERR_SUCCESS;
|
||||
|
||||
be_root_concat(lbh, name, path);
|
||||
|
||||
if (strchr(name, '@') == NULL) {
|
||||
if (!zfs_dataset_exists(lbh->lzh, path, ZFS_TYPE_FILESYSTEM))
|
||||
return (set_error(lbh, BE_ERR_NOENT));
|
||||
|
||||
if (strcmp(path, lbh->rootfs) == 0)
|
||||
return (set_error(lbh, BE_ERR_DESTROYACT));
|
||||
|
||||
fs = zfs_open(lbh->lzh, p, ZFS_TYPE_FILESYSTEM);
|
||||
} else {
|
||||
|
||||
if (!zfs_dataset_exists(lbh->lzh, path, ZFS_TYPE_SNAPSHOT))
|
||||
return (set_error(lbh, BE_ERR_NOENT));
|
||||
|
||||
fs = zfs_open(lbh->lzh, p, ZFS_TYPE_SNAPSHOT);
|
||||
}
|
||||
|
||||
if (fs == NULL)
|
||||
return (set_error(lbh, BE_ERR_ZFSOPEN));
|
||||
|
||||
/* Check if mounted, unmount if force is specified */
|
||||
if ((mounted = zfs_is_mounted(fs, NULL)) != 0) {
|
||||
if (force)
|
||||
zfs_unmount(fs, NULL, 0);
|
||||
else
|
||||
return (set_error(lbh, BE_ERR_DESTROYMNT));
|
||||
}
|
||||
|
||||
if ((err = be_destroy_cb(fs, NULL)) != 0) {
|
||||
/* Children are still present or the mount is referenced */
|
||||
if (err == EBUSY)
|
||||
return (set_error(lbh, BE_ERR_DESTROYMNT));
|
||||
return (set_error(lbh, BE_ERR_UNKNOWN));
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
be_snapshot(libbe_handle_t *lbh, const char *source, const char *snap_name,
|
||||
bool recursive, char *result)
|
||||
{
|
||||
char buf[BE_MAXPATHLEN];
|
||||
time_t rawtime;
|
||||
int len, err;
|
||||
|
||||
be_root_concat(lbh, source, buf);
|
||||
|
||||
if (!be_exists(lbh, buf))
|
||||
return (BE_ERR_NOENT);
|
||||
|
||||
if (snap_name != NULL) {
|
||||
strcat(buf, "@");
|
||||
strcat(buf, snap_name);
|
||||
if (result != NULL)
|
||||
snprintf(result, BE_MAXPATHLEN, "%s@%s", source,
|
||||
snap_name);
|
||||
} else {
|
||||
time(&rawtime);
|
||||
len = strlen(buf);
|
||||
strftime(buf + len, BE_MAXPATHLEN - len,
|
||||
"@%F-%T", localtime(&rawtime));
|
||||
if (result != NULL)
|
||||
strcpy(result, strrchr(buf, '/') + 1);
|
||||
}
|
||||
|
||||
if ((err = zfs_snapshot(lbh->lzh, buf, recursive, NULL)) != 0) {
|
||||
switch (err) {
|
||||
case EZFS_INVALIDNAME:
|
||||
return (set_error(lbh, BE_ERR_INVALIDNAME));
|
||||
|
||||
default:
|
||||
/*
|
||||
* The other errors that zfs_ioc_snapshot might return
|
||||
* shouldn't happen if we've set things up properly, so
|
||||
* we'll gloss over them and call it UNKNOWN as it will
|
||||
* require further triage.
|
||||
*/
|
||||
if (errno == ENOTSUP)
|
||||
return (set_error(lbh, BE_ERR_NOPOOL));
|
||||
return (set_error(lbh, BE_ERR_UNKNOWN));
|
||||
}
|
||||
}
|
||||
|
||||
return (BE_ERR_SUCCESS);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Create the boot environment specified by the name parameter
|
||||
*/
|
||||
int
|
||||
be_create(libbe_handle_t *lbh, const char *name)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = be_create_from_existing(lbh, name, be_active_path(lbh));
|
||||
|
||||
return (set_error(lbh, err));
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
be_deep_clone_prop(int prop, void *cb)
|
||||
{
|
||||
int err;
|
||||
struct libbe_dccb *dccb;
|
||||
zprop_source_t src;
|
||||
char pval[BE_MAXPATHLEN];
|
||||
char source[BE_MAXPATHLEN];
|
||||
|
||||
dccb = cb;
|
||||
/* Skip some properties we don't want to touch */
|
||||
if (prop == ZFS_PROP_CANMOUNT)
|
||||
return (ZPROP_CONT);
|
||||
|
||||
/* Don't copy readonly properties */
|
||||
if (zfs_prop_readonly(prop))
|
||||
return (ZPROP_CONT);
|
||||
|
||||
if ((err = zfs_prop_get(dccb->zhp, prop, (char *)&pval,
|
||||
sizeof(pval), &src, (char *)&source, sizeof(source), false)))
|
||||
/* Just continue if we fail to read a property */
|
||||
return (ZPROP_CONT);
|
||||
|
||||
/* Only copy locally defined properties */
|
||||
if (src != ZPROP_SRC_LOCAL)
|
||||
return (ZPROP_CONT);
|
||||
|
||||
nvlist_add_string(dccb->props, zfs_prop_to_name(prop), (char *)pval);
|
||||
|
||||
return (ZPROP_CONT);
|
||||
}
|
||||
|
||||
static int
|
||||
be_deep_clone(zfs_handle_t *ds, void *data)
|
||||
{
|
||||
int err;
|
||||
char be_path[BE_MAXPATHLEN];
|
||||
char snap_path[BE_MAXPATHLEN];
|
||||
const char *dspath;
|
||||
char *dsname;
|
||||
zfs_handle_t *snap_hdl;
|
||||
nvlist_t *props;
|
||||
struct libbe_deep_clone *isdc, sdc;
|
||||
struct libbe_dccb dccb;
|
||||
|
||||
isdc = (struct libbe_deep_clone *)data;
|
||||
dspath = zfs_get_name(ds);
|
||||
if ((dsname = strrchr(dspath, '/')) == NULL)
|
||||
return (BE_ERR_UNKNOWN);
|
||||
dsname++;
|
||||
|
||||
if (isdc->bename == NULL)
|
||||
snprintf(be_path, sizeof(be_path), "%s/%s", isdc->be_root, dsname);
|
||||
else
|
||||
snprintf(be_path, sizeof(be_path), "%s/%s", isdc->be_root, isdc->bename);
|
||||
|
||||
snprintf(snap_path, sizeof(snap_path), "%s@%s", dspath, isdc->snapname);
|
||||
|
||||
if (zfs_dataset_exists(isdc->lbh->lzh, be_path, ZFS_TYPE_DATASET))
|
||||
return (set_error(isdc->lbh, BE_ERR_EXISTS));
|
||||
|
||||
if ((snap_hdl =
|
||||
zfs_open(isdc->lbh->lzh, snap_path, ZFS_TYPE_SNAPSHOT)) == NULL)
|
||||
return (set_error(isdc->lbh, BE_ERR_ZFSOPEN));
|
||||
|
||||
nvlist_alloc(&props, NV_UNIQUE_NAME, KM_SLEEP);
|
||||
nvlist_add_string(props, "canmount", "noauto");
|
||||
|
||||
dccb.zhp = ds;
|
||||
dccb.props = props;
|
||||
if (zprop_iter(be_deep_clone_prop, &dccb, B_FALSE, B_FALSE,
|
||||
ZFS_TYPE_FILESYSTEM) == ZPROP_INVAL)
|
||||
return (-1);
|
||||
|
||||
if ((err = zfs_clone(snap_hdl, be_path, props)) != 0) {
|
||||
switch (err) {
|
||||
case EZFS_SUCCESS:
|
||||
err = BE_ERR_SUCCESS;
|
||||
break;
|
||||
default:
|
||||
err = BE_ERR_ZFSCLONE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
nvlist_free(props);
|
||||
zfs_close(snap_hdl);
|
||||
|
||||
sdc.lbh = isdc->lbh;
|
||||
sdc.bename = NULL;
|
||||
sdc.snapname = isdc->snapname;
|
||||
sdc.be_root = (char *)&be_path;
|
||||
|
||||
err = zfs_iter_filesystems(ds, be_deep_clone, &sdc);
|
||||
|
||||
return (err);
|
||||
}
|
||||
|
||||
/*
|
||||
* Create the boot environment from pre-existing snapshot
|
||||
*/
|
||||
int
|
||||
be_create_from_existing_snap(libbe_handle_t *lbh, const char *name,
|
||||
const char *snap)
|
||||
{
|
||||
int err;
|
||||
char be_path[BE_MAXPATHLEN];
|
||||
char snap_path[BE_MAXPATHLEN];
|
||||
const char *bename;
|
||||
char *parentname, *snapname;
|
||||
zfs_handle_t *parent_hdl;
|
||||
struct libbe_deep_clone sdc;
|
||||
|
||||
if ((err = be_validate_name(lbh, name)) != 0)
|
||||
return (set_error(lbh, err));
|
||||
if ((err = be_root_concat(lbh, snap, snap_path)) != 0)
|
||||
return (set_error(lbh, err));
|
||||
if ((err = be_validate_snap(lbh, snap_path)) != 0)
|
||||
return (set_error(lbh, err));
|
||||
|
||||
if ((err = be_root_concat(lbh, name, be_path)) != 0)
|
||||
return (set_error(lbh, err));
|
||||
|
||||
if ((bename = strrchr(name, '/')) == NULL)
|
||||
bename = name;
|
||||
else
|
||||
bename++;
|
||||
|
||||
if ((parentname = strdup(snap_path)) == NULL) {
|
||||
err = BE_ERR_UNKNOWN;
|
||||
return (set_error(lbh, err));
|
||||
}
|
||||
snapname = strchr(parentname, '@');
|
||||
if (snapname == NULL) {
|
||||
err = BE_ERR_UNKNOWN;
|
||||
return (set_error(lbh, err));
|
||||
}
|
||||
*snapname = '\0';
|
||||
snapname++;
|
||||
|
||||
sdc.lbh = lbh;
|
||||
sdc.bename = bename;
|
||||
sdc.snapname = snapname;
|
||||
sdc.be_root = lbh->root;
|
||||
|
||||
parent_hdl = zfs_open(lbh->lzh, parentname, ZFS_TYPE_DATASET);
|
||||
err = be_deep_clone(parent_hdl, &sdc);
|
||||
|
||||
return (set_error(lbh, err));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Create a boot environment from an existing boot environment
|
||||
*/
|
||||
int
|
||||
be_create_from_existing(libbe_handle_t *lbh, const char *name, const char *old)
|
||||
{
|
||||
int err;
|
||||
char buf[BE_MAXPATHLEN];
|
||||
|
||||
if ((err = be_snapshot(lbh, old, NULL, true, (char *)&buf)))
|
||||
return (set_error(lbh, err));
|
||||
|
||||
err = be_create_from_existing_snap(lbh, name, (char *)buf);
|
||||
|
||||
return (set_error(lbh, err));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Verifies that a snapshot has a valid name, exists, and has a mountpoint of
|
||||
* '/'. Returns BE_ERR_SUCCESS (0), upon success, or the relevant BE_ERR_* upon
|
||||
* failure. Does not set the internal library error state.
|
||||
*/
|
||||
int
|
||||
be_validate_snap(libbe_handle_t *lbh, const char *snap_name)
|
||||
{
|
||||
zfs_handle_t *zfs_hdl;
|
||||
char buf[BE_MAXPATHLEN];
|
||||
char *delim_pos;
|
||||
int err = BE_ERR_SUCCESS;
|
||||
|
||||
if (strlen(snap_name) >= BE_MAXPATHLEN)
|
||||
return (BE_ERR_PATHLEN);
|
||||
|
||||
if (!zfs_dataset_exists(lbh->lzh, snap_name,
|
||||
ZFS_TYPE_SNAPSHOT))
|
||||
return (BE_ERR_NOENT);
|
||||
|
||||
strncpy(buf, snap_name, BE_MAXPATHLEN);
|
||||
|
||||
/* Find the base filesystem of the snapshot */
|
||||
if ((delim_pos = strchr(buf, '@')) == NULL)
|
||||
return (BE_ERR_INVALIDNAME);
|
||||
*delim_pos = '\0';
|
||||
|
||||
if ((zfs_hdl =
|
||||
zfs_open(lbh->lzh, buf, ZFS_TYPE_DATASET)) == NULL)
|
||||
return (BE_ERR_NOORIGIN);
|
||||
|
||||
if ((err = zfs_prop_get(zfs_hdl, ZFS_PROP_MOUNTPOINT, buf, BE_MAXPATHLEN,
|
||||
NULL, NULL, 0, 1)) != 0)
|
||||
err = BE_ERR_INVORIGIN;
|
||||
|
||||
if ((err != 0) && (strncmp(buf, "/", BE_MAXPATHLEN) != 0))
|
||||
err = BE_ERR_INVORIGIN;
|
||||
|
||||
zfs_close(zfs_hdl);
|
||||
|
||||
return (err);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Idempotently appends the name argument to the root boot environment path
|
||||
* and copies the resulting string into the result buffer (which is assumed
|
||||
* to be at least BE_MAXPATHLEN characters long. Returns BE_ERR_SUCCESS upon
|
||||
* success, BE_ERR_PATHLEN if the resulting path is longer than BE_MAXPATHLEN,
|
||||
* or BE_ERR_INVALIDNAME if the name is a path that does not begin with
|
||||
* zfs_be_root. Does not set internal library error state.
|
||||
*/
|
||||
int
|
||||
be_root_concat(libbe_handle_t *lbh, const char *name, char *result)
|
||||
{
|
||||
size_t name_len, root_len;
|
||||
|
||||
name_len = strlen(name);
|
||||
root_len = strlen(lbh->root);
|
||||
|
||||
/* Act idempotently; return be name if it is already a full path */
|
||||
if (strrchr(name, '/') != NULL) {
|
||||
if (strstr(name, lbh->root) != name)
|
||||
return (BE_ERR_INVALIDNAME);
|
||||
|
||||
if (name_len >= BE_MAXPATHLEN)
|
||||
return (BE_ERR_PATHLEN);
|
||||
|
||||
strncpy(result, name, BE_MAXPATHLEN);
|
||||
return (BE_ERR_SUCCESS);
|
||||
} else if (name_len + root_len + 1 < BE_MAXPATHLEN) {
|
||||
snprintf(result, BE_MAXPATHLEN, "%s/%s", lbh->root,
|
||||
name);
|
||||
return (BE_ERR_SUCCESS);
|
||||
}
|
||||
|
||||
return (BE_ERR_PATHLEN);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Verifies the validity of a boot environment name (A-Za-z0-9-_.). Returns
|
||||
* BE_ERR_SUCCESS (0) if name is valid, otherwise returns BE_ERR_INVALIDNAME.
|
||||
* Does not set internal library error state.
|
||||
*/
|
||||
int
|
||||
be_validate_name(libbe_handle_t *lbh __unused, const char *name)
|
||||
{
|
||||
for (int i = 0; *name; i++) {
|
||||
char c = *(name++);
|
||||
if (isalnum(c) || (c == '-') || (c == '_') || (c == '.'))
|
||||
continue;
|
||||
return (BE_ERR_INVALIDNAME);
|
||||
}
|
||||
|
||||
return (BE_ERR_SUCCESS);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* usage
|
||||
*/
|
||||
int
|
||||
be_rename(libbe_handle_t *lbh, const char *old, const char *new)
|
||||
{
|
||||
char full_old[BE_MAXPATHLEN];
|
||||
char full_new[BE_MAXPATHLEN];
|
||||
zfs_handle_t *zfs_hdl;
|
||||
int err;
|
||||
|
||||
if ((err = be_root_concat(lbh, old, full_old)) != 0)
|
||||
return (set_error(lbh, err));
|
||||
if ((err = be_root_concat(lbh, new, full_new)) != 0)
|
||||
return (set_error(lbh, err));
|
||||
|
||||
if ((err = be_validate_name(lbh, new)) != 0)
|
||||
return (err);
|
||||
|
||||
/* Check if old is active BE */
|
||||
if (strcmp(full_old, be_active_path(lbh)) == 0)
|
||||
return (set_error(lbh, BE_ERR_MOUNTED));
|
||||
|
||||
if (!zfs_dataset_exists(lbh->lzh, full_old, ZFS_TYPE_DATASET))
|
||||
return (set_error(lbh, BE_ERR_NOENT));
|
||||
|
||||
if (zfs_dataset_exists(lbh->lzh, full_new, ZFS_TYPE_DATASET))
|
||||
return (set_error(lbh, BE_ERR_EXISTS));
|
||||
|
||||
if ((zfs_hdl = zfs_open(lbh->lzh, full_old,
|
||||
ZFS_TYPE_FILESYSTEM)) == NULL)
|
||||
return (set_error(lbh, BE_ERR_ZFSOPEN));
|
||||
|
||||
/* XXX TODO: Allow a force flag */
|
||||
if (zfs_is_mounted(zfs_hdl, NULL)) {
|
||||
zfs_close(zfs_hdl);
|
||||
return (set_error(lbh, BE_ERR_MOUNTED));
|
||||
}
|
||||
|
||||
/* recurse, nounmount, forceunmount */
|
||||
struct renameflags flags = { 0, 0, 0 };
|
||||
|
||||
err = zfs_rename(zfs_hdl, NULL, full_new, flags);
|
||||
|
||||
zfs_close(zfs_hdl);
|
||||
|
||||
return (set_error(lbh, err));
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
be_export(libbe_handle_t *lbh, const char *bootenv, int fd)
|
||||
{
|
||||
char snap_name[BE_MAXPATHLEN];
|
||||
char buf[BE_MAXPATHLEN];
|
||||
zfs_handle_t *zfs;
|
||||
int err;
|
||||
|
||||
if ((err = be_snapshot(lbh, bootenv, NULL, true, snap_name)) != 0)
|
||||
/* Use the error set by be_snapshot */
|
||||
return (err);
|
||||
|
||||
be_root_concat(lbh, snap_name, buf);
|
||||
|
||||
if ((zfs = zfs_open(lbh->lzh, buf, ZFS_TYPE_DATASET)) == NULL)
|
||||
return (set_error(lbh, BE_ERR_ZFSOPEN));
|
||||
|
||||
err = zfs_send_one(zfs, NULL, fd, 0);
|
||||
zfs_close(zfs);
|
||||
|
||||
return (err);
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
be_import(libbe_handle_t *lbh, const char *bootenv, int fd)
|
||||
{
|
||||
char buf[BE_MAXPATHLEN];
|
||||
time_t rawtime;
|
||||
nvlist_t *props;
|
||||
zfs_handle_t *zfs;
|
||||
int err, len;
|
||||
char nbuf[24];
|
||||
|
||||
/*
|
||||
* We don't need this to be incredibly random, just unique enough that
|
||||
* it won't conflict with an existing dataset name. Chopping time
|
||||
* down to 32 bits is probably good enough for this.
|
||||
*/
|
||||
snprintf(nbuf, 24, "tmp%u",
|
||||
(uint32_t)(time(NULL) & 0xFFFFFFFF));
|
||||
if ((err = be_root_concat(lbh, nbuf, buf)) != 0)
|
||||
/*
|
||||
* Technically this is our problem, but we try to use short
|
||||
* enough names that we won't run into problems except in
|
||||
* worst-case BE root approaching MAXPATHLEN.
|
||||
*/
|
||||
return (set_error(lbh, BE_ERR_PATHLEN));
|
||||
|
||||
time(&rawtime);
|
||||
len = strlen(buf);
|
||||
strftime(buf + len, BE_MAXPATHLEN - len,
|
||||
"@%F-%T", localtime(&rawtime));
|
||||
|
||||
if ((err = lzc_receive(buf, NULL, NULL, false, fd)) != 0) {
|
||||
switch (err) {
|
||||
case EINVAL:
|
||||
return (set_error(lbh, BE_ERR_NOORIGIN));
|
||||
case ENOENT:
|
||||
return (set_error(lbh, BE_ERR_NOENT));
|
||||
case EIO:
|
||||
return (set_error(lbh, BE_ERR_IO));
|
||||
default:
|
||||
return (set_error(lbh, BE_ERR_UNKNOWN));
|
||||
}
|
||||
}
|
||||
|
||||
if ((zfs = zfs_open(lbh->lzh, buf, ZFS_TYPE_SNAPSHOT)) == NULL)
|
||||
return (set_error(lbh, BE_ERR_ZFSOPEN));
|
||||
|
||||
nvlist_alloc(&props, NV_UNIQUE_NAME, KM_SLEEP);
|
||||
nvlist_add_string(props, "canmount", "noauto");
|
||||
nvlist_add_string(props, "mountpoint", "/");
|
||||
|
||||
be_root_concat(lbh, bootenv, buf);
|
||||
|
||||
err = zfs_clone(zfs, buf, props);
|
||||
zfs_close(zfs);
|
||||
|
||||
nvlist_free(props);
|
||||
|
||||
/* XXX TODO: Figure out how to destroy the ghost... */
|
||||
return (BE_ERR_SUCCESS);
|
||||
}
|
||||
|
||||
#if SOON
|
||||
static int
|
||||
be_create_child_noent(libbe_handle_t *lbh, const char *active,
|
||||
const char *child_path)
|
||||
{
|
||||
nvlist_t *props;
|
||||
zfs_handle_t *zfs;
|
||||
int err;
|
||||
|
||||
nvlist_alloc(&props, NV_UNIQUE_NAME, KM_SLEEP);
|
||||
nvlist_add_string(props, "canmount", "noauto");
|
||||
nvlist_add_string(props, "mountpoint", child_path);
|
||||
|
||||
/* Create */
|
||||
if ((err = zfs_create(lbh->lzh, active, ZFS_TYPE_DATASET,
|
||||
props)) != 0) {
|
||||
switch (err) {
|
||||
case EZFS_EXISTS:
|
||||
return (set_error(lbh, BE_ERR_EXISTS));
|
||||
case EZFS_NOENT:
|
||||
return (set_error(lbh, BE_ERR_NOENT));
|
||||
case EZFS_BADTYPE:
|
||||
case EZFS_BADVERSION:
|
||||
return (set_error(lbh, BE_ERR_NOPOOL));
|
||||
case EZFS_BADPROP:
|
||||
default:
|
||||
/* We set something up wrong, probably... */
|
||||
return (set_error(lbh, BE_ERR_UNKNOWN));
|
||||
}
|
||||
}
|
||||
nvlist_free(props);
|
||||
|
||||
if ((zfs = zfs_open(lbh->lzh, active, ZFS_TYPE_DATASET)) == NULL)
|
||||
return (set_error(lbh, BE_ERR_ZFSOPEN));
|
||||
|
||||
/* Set props */
|
||||
if ((err = zfs_prop_set(zfs, "canmount", "noauto")) != 0) {
|
||||
zfs_close(zfs);
|
||||
/*
|
||||
* Similar to other cases, this shouldn't fail unless we've
|
||||
* done something wrong. This is a new dataset that shouldn't
|
||||
* have been mounted anywhere between creation and now.
|
||||
*/
|
||||
if (err == EZFS_NOMEM)
|
||||
return (set_error(lbh, BE_ERR_NOMEM));
|
||||
return (set_error(lbh, BE_ERR_UNKNOWN));
|
||||
}
|
||||
zfs_close(zfs);
|
||||
return (BE_ERR_SUCCESS);
|
||||
}
|
||||
|
||||
static int
|
||||
be_create_child_cloned(libbe_handle_t *lbh, const char *active)
|
||||
{
|
||||
char buf[BE_MAXPATHLEN], tmp[BE_MAXPATHLEN];;
|
||||
zfs_handle_t *zfs;
|
||||
int err;
|
||||
|
||||
/* XXX TODO ? */
|
||||
|
||||
/*
|
||||
* Establish if the existing path is a zfs dataset or just
|
||||
* the subdirectory of one
|
||||
*/
|
||||
strlcpy(tmp, "tmp/be_snap.XXXXX", sizeof(tmp));
|
||||
if (mktemp(tmp) == NULL)
|
||||
return (set_error(lbh, BE_ERR_UNKNOWN));
|
||||
|
||||
be_root_concat(lbh, tmp, buf);
|
||||
printf("Here %s?\n", buf);
|
||||
if ((err = zfs_snapshot(lbh->lzh, buf, false, NULL)) != 0) {
|
||||
switch (err) {
|
||||
case EZFS_INVALIDNAME:
|
||||
return (set_error(lbh, BE_ERR_INVALIDNAME));
|
||||
|
||||
default:
|
||||
/*
|
||||
* The other errors that zfs_ioc_snapshot might return
|
||||
* shouldn't happen if we've set things up properly, so
|
||||
* we'll gloss over them and call it UNKNOWN as it will
|
||||
* require further triage.
|
||||
*/
|
||||
if (errno == ENOTSUP)
|
||||
return (set_error(lbh, BE_ERR_NOPOOL));
|
||||
return (set_error(lbh, BE_ERR_UNKNOWN));
|
||||
}
|
||||
}
|
||||
|
||||
/* Clone */
|
||||
if ((zfs = zfs_open(lbh->lzh, buf, ZFS_TYPE_SNAPSHOT)) == NULL)
|
||||
return (BE_ERR_ZFSOPEN);
|
||||
|
||||
if ((err = zfs_clone(zfs, active, NULL)) != 0)
|
||||
/* XXX TODO correct error */
|
||||
return (set_error(lbh, BE_ERR_UNKNOWN));
|
||||
|
||||
/* set props */
|
||||
zfs_close(zfs);
|
||||
return (BE_ERR_SUCCESS);
|
||||
}
|
||||
|
||||
int
|
||||
be_add_child(libbe_handle_t *lbh, const char *child_path, bool cp_if_exists)
|
||||
{
|
||||
struct stat sb;
|
||||
char active[BE_MAXPATHLEN], buf[BE_MAXPATHLEN];
|
||||
nvlist_t *props;
|
||||
const char *s;
|
||||
|
||||
/* Require absolute paths */
|
||||
if (*child_path != '/')
|
||||
return (set_error(lbh, BE_ERR_BADPATH));
|
||||
|
||||
strlcpy(active, be_active_path(lbh), BE_MAXPATHLEN);
|
||||
strcpy(buf, active);
|
||||
|
||||
/* Create non-mountable parent dataset(s) */
|
||||
s = child_path;
|
||||
for (char *p; (p = strchr(s+1, '/')) != NULL; s = p) {
|
||||
size_t len = p - s;
|
||||
strncat(buf, s, len);
|
||||
|
||||
nvlist_alloc(&props, NV_UNIQUE_NAME, KM_SLEEP);
|
||||
nvlist_add_string(props, "canmount", "off");
|
||||
nvlist_add_string(props, "mountpoint", "none");
|
||||
zfs_create(lbh->lzh, buf, ZFS_TYPE_DATASET, props);
|
||||
nvlist_free(props);
|
||||
}
|
||||
|
||||
/* Path does not exist as a descendent of / yet */
|
||||
if (strlcat(active, child_path, BE_MAXPATHLEN) >= BE_MAXPATHLEN)
|
||||
return (set_error(lbh, BE_ERR_PATHLEN));
|
||||
|
||||
if (stat(child_path, &sb) != 0) {
|
||||
/* Verify that error is ENOENT */
|
||||
if (errno != ENOENT)
|
||||
return (set_error(lbh, BE_ERR_UNKNOWN));
|
||||
return (be_create_child_noent(lbh, active, child_path));
|
||||
} else if (cp_if_exists)
|
||||
/* Path is already a descendent of / and should be copied */
|
||||
return (be_create_child_cloned(lbh, active));
|
||||
return (set_error(lbh, BE_ERR_EXISTS));
|
||||
}
|
||||
#endif /* SOON */
|
||||
|
||||
static int
|
||||
be_set_nextboot(libbe_handle_t *lbh, nvlist_t *config, uint64_t pool_guid,
|
||||
const char *zfsdev)
|
||||
{
|
||||
nvlist_t **child;
|
||||
uint64_t vdev_guid;
|
||||
int c, children;
|
||||
|
||||
if (nvlist_lookup_nvlist_array(config, ZPOOL_CONFIG_CHILDREN, &child,
|
||||
&children) == 0) {
|
||||
for (c = 0; c < children; ++c)
|
||||
if (be_set_nextboot(lbh, child[c], pool_guid, zfsdev) != 0)
|
||||
return (1);
|
||||
return (0);
|
||||
}
|
||||
|
||||
if (nvlist_lookup_uint64(config, ZPOOL_CONFIG_GUID,
|
||||
&vdev_guid) != 0) {
|
||||
return (1);
|
||||
}
|
||||
|
||||
if (zpool_nextboot(lbh->lzh, pool_guid, vdev_guid, zfsdev) != 0) {
|
||||
perror("ZFS_IOC_NEXTBOOT failed");
|
||||
return (1);
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
be_activate(libbe_handle_t *lbh, const char *bootenv, bool temporary)
|
||||
{
|
||||
char be_path[BE_MAXPATHLEN];
|
||||
char buf[BE_MAXPATHLEN];
|
||||
uint64_t pool_guid;
|
||||
nvlist_t *config, *vdevs;
|
||||
int err;
|
||||
|
||||
be_root_concat(lbh, bootenv, be_path);
|
||||
|
||||
/* Note: be_exists fails if mountpoint is not / */
|
||||
if (!be_exists(lbh, be_path))
|
||||
return (BE_ERR_NOENT);
|
||||
|
||||
if (temporary) {
|
||||
config = zpool_get_config(lbh->active_phandle, NULL);
|
||||
if (config == NULL)
|
||||
/* config should be fetchable... */
|
||||
return (set_error(lbh, BE_ERR_UNKNOWN));
|
||||
|
||||
if (nvlist_lookup_uint64(config, ZPOOL_CONFIG_POOL_GUID,
|
||||
&pool_guid) != 0)
|
||||
/* Similarly, it shouldn't be possible */
|
||||
return (set_error(lbh, BE_ERR_UNKNOWN));
|
||||
|
||||
/* Expected format according to zfsbootcfg(8) man */
|
||||
strcpy(buf, "zfs:");
|
||||
strcat(buf, be_path);
|
||||
strcat(buf, ":");
|
||||
|
||||
/* We have no config tree */
|
||||
if (nvlist_lookup_nvlist(config, ZPOOL_CONFIG_VDEV_TREE,
|
||||
&vdevs) != 0)
|
||||
return (set_error(lbh, BE_ERR_NOPOOL));
|
||||
|
||||
return (be_set_nextboot(lbh, vdevs, pool_guid, buf));
|
||||
} else {
|
||||
/* Obtain bootenv zpool */
|
||||
err = zpool_set_prop(lbh->active_phandle, "bootfs", be_path);
|
||||
|
||||
switch (err) {
|
||||
case 0:
|
||||
return (BE_ERR_SUCCESS);
|
||||
|
||||
default:
|
||||
/* XXX TODO correct errors */
|
||||
return (-1);
|
||||
}
|
||||
}
|
||||
}
|
131
lib/libbe/be.h
Normal file
131
lib/libbe/be.h
Normal file
@ -0,0 +1,131 @@
|
||||
/*-
|
||||
* 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_INVORIGIN, /* snapshot origin's mountpoint is not '/' */
|
||||
BE_ERR_NOORIGIN, /* could not open snapshot's origin */
|
||||
BE_ERR_MOUNTED, /* boot environment is already mounted */
|
||||
BE_ERR_NOMOUNT, /* boot environment is not mounted */
|
||||
BE_ERR_ZFSOPEN, /* calling zfs_open() failed */
|
||||
BE_ERR_ZFSCLONE, /* error when calling zfs_clone to create be */
|
||||
BE_ERR_IO, /* error when doing some I/O operation */
|
||||
BE_ERR_NOPOOL, /* operation not supported on this pool */
|
||||
BE_ERR_NOMEM, /* insufficient memory */
|
||||
BE_ERR_UNKNOWN, /* unknown error */
|
||||
} be_error_t;
|
||||
|
||||
|
||||
/* Library handling functions: be.c */
|
||||
libbe_handle_t *libbe_init(void);
|
||||
void libbe_close(libbe_handle_t *);
|
||||
|
||||
/* Bootenv information functions: be_info.c */
|
||||
const char *be_active_name(libbe_handle_t *);
|
||||
const char *be_active_path(libbe_handle_t *);
|
||||
const char *be_nextboot_name(libbe_handle_t *);
|
||||
const char *be_nextboot_path(libbe_handle_t *);
|
||||
const char *be_root_path(libbe_handle_t *);
|
||||
|
||||
int be_get_bootenv_props(libbe_handle_t *, nvlist_t *);
|
||||
int be_get_dataset_props(libbe_handle_t *, const char *, nvlist_t *);
|
||||
int be_get_dataset_snapshots(libbe_handle_t *, const char *, nvlist_t *);
|
||||
int be_prop_list_alloc(nvlist_t **be_list);
|
||||
void be_prop_list_free(nvlist_t *be_list);
|
||||
|
||||
int be_activate(libbe_handle_t *, const char *, bool);
|
||||
|
||||
/* Bootenv creation functions */
|
||||
int be_create(libbe_handle_t *, const char *);
|
||||
int be_create_from_existing(libbe_handle_t *, const char *, const char *);
|
||||
int be_create_from_existing_snap(libbe_handle_t *, const char *, const char *);
|
||||
int be_snapshot(libbe_handle_t *, const char *, const char *, bool, char *);
|
||||
|
||||
/* Bootenv manipulation functions */
|
||||
int be_rename(libbe_handle_t *, const char *, const char *);
|
||||
|
||||
/* Bootenv removal functions */
|
||||
|
||||
typedef enum {
|
||||
BE_DESTROY_FORCE = 1 << 0,
|
||||
} be_destroy_opt_t;
|
||||
|
||||
int be_destroy(libbe_handle_t *, const char *, int);
|
||||
|
||||
/* Bootenv mounting functions: be_access.c */
|
||||
|
||||
typedef enum {
|
||||
BE_MNT_FORCE = 1 << 0,
|
||||
BE_MNT_DEEP = 1 << 1,
|
||||
} be_mount_opt_t;
|
||||
|
||||
int be_mount(libbe_handle_t *, char *, char *, int, char *);
|
||||
int be_unmount(libbe_handle_t *, char *, int);
|
||||
int be_mounted_at(libbe_handle_t *, const char *path, nvlist_t *);
|
||||
|
||||
/* Error related functions: be_error.c */
|
||||
int libbe_errno(libbe_handle_t *);
|
||||
const char *libbe_error_description(libbe_handle_t *);
|
||||
void libbe_print_on_error(libbe_handle_t *, bool);
|
||||
|
||||
/* Utility Functions */
|
||||
int be_root_concat(libbe_handle_t *, const char *, char *);
|
||||
int be_validate_name(libbe_handle_t * __unused, const char *);
|
||||
int be_validate_snap(libbe_handle_t *, const char *);
|
||||
bool 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 */
|
213
lib/libbe/be_access.c
Normal file
213
lib/libbe/be_access.c
Normal file
@ -0,0 +1,213 @@
|
||||
/*-
|
||||
* 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"
|
||||
|
||||
struct be_mountcheck_info {
|
||||
const char *path;
|
||||
char *name;
|
||||
};
|
||||
|
||||
static int
|
||||
be_mountcheck_cb(zfs_handle_t *zfs_hdl, void *data)
|
||||
{
|
||||
struct be_mountcheck_info *info;
|
||||
char *mountpoint;
|
||||
|
||||
if (data == NULL)
|
||||
return (1);
|
||||
info = (struct be_mountcheck_info *)data;
|
||||
if (!zfs_is_mounted(zfs_hdl, &mountpoint))
|
||||
return (0);
|
||||
if (strcmp(mountpoint, info->path) == 0) {
|
||||
info->name = strdup(zfs_get_name(zfs_hdl));
|
||||
return (1);
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* usage
|
||||
*/
|
||||
int
|
||||
be_mounted_at(libbe_handle_t *lbh, const char *path, nvlist_t *details)
|
||||
{
|
||||
char be[BE_MAXPATHLEN + 1];
|
||||
zfs_handle_t *root_hdl;
|
||||
struct be_mountcheck_info info;
|
||||
prop_data_t propinfo;
|
||||
|
||||
bzero(&be, BE_MAXPATHLEN + 1);
|
||||
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];
|
||||
char *path;
|
||||
int mntflags;
|
||||
int err;
|
||||
|
||||
if ((err = be_root_concat(lbh, bootenv, be)) != 0)
|
||||
return (set_error(lbh, err));
|
||||
|
||||
if (!be_exists(lbh, bootenv))
|
||||
return (set_error(lbh, BE_ERR_NOENT));
|
||||
|
||||
if (is_mounted(lbh->lzh, be, &path))
|
||||
return (set_error(lbh, BE_ERR_MOUNTED));
|
||||
|
||||
mntflags = (flags & BE_MNT_FORCE) ? MNT_FORCE : 0;
|
||||
|
||||
/* Create mountpoint if it is not specified */
|
||||
if (mountpoint == NULL) {
|
||||
strcpy(mnt_temp, "/tmp/be_mount.XXXX");
|
||||
if (mkdtemp(mnt_temp) == NULL)
|
||||
return (set_error(lbh, BE_ERR_IO));
|
||||
}
|
||||
|
||||
char opt = '\0';
|
||||
if ((err = zmount(be, (mountpoint == NULL) ? mnt_temp : mountpoint,
|
||||
mntflags, __DECONST(char *, MNTTYPE_ZFS), NULL, 0, &opt, 1)) != 0) {
|
||||
switch (errno) {
|
||||
case ENAMETOOLONG:
|
||||
return (set_error(lbh, BE_ERR_PATHLEN));
|
||||
case ELOOP:
|
||||
case ENOENT:
|
||||
case ENOTDIR:
|
||||
return (set_error(lbh, BE_ERR_BADPATH));
|
||||
case EPERM:
|
||||
return (set_error(lbh, BE_ERR_PERMS));
|
||||
case EBUSY:
|
||||
return (set_error(lbh, BE_ERR_PATHBUSY));
|
||||
default:
|
||||
return (set_error(lbh, BE_ERR_UNKNOWN));
|
||||
}
|
||||
}
|
||||
|
||||
if (result_loc != NULL)
|
||||
strcpy(result_loc, mountpoint == NULL ? mnt_temp : mountpoint);
|
||||
|
||||
return (BE_ERR_SUCCESS);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* usage
|
||||
*/
|
||||
int
|
||||
be_unmount(libbe_handle_t *lbh, char *bootenv, int flags)
|
||||
{
|
||||
int err, mntflags;
|
||||
char be[BE_MAXPATHLEN];
|
||||
struct statfs *mntbuf;
|
||||
int mntsize;
|
||||
char *mntpath;
|
||||
|
||||
if ((err = be_root_concat(lbh, bootenv, be)) != 0)
|
||||
return (set_error(lbh, err));
|
||||
|
||||
if ((mntsize = getmntinfo(&mntbuf, MNT_NOWAIT)) == 0) {
|
||||
if (errno == EIO)
|
||||
return (set_error(lbh, BE_ERR_IO));
|
||||
return (set_error(lbh, BE_ERR_NOMOUNT));
|
||||
}
|
||||
|
||||
mntpath = NULL;
|
||||
for (int i = 0; i < mntsize; ++i) {
|
||||
/* 0x000000de is the type number of zfs */
|
||||
if (mntbuf[i].f_type != 0x000000de)
|
||||
continue;
|
||||
|
||||
if (strcmp(mntbuf[i].f_mntfromname, be) == 0) {
|
||||
mntpath = mntbuf[i].f_mntonname;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (mntpath == NULL)
|
||||
return (set_error(lbh, BE_ERR_NOMOUNT));
|
||||
|
||||
mntflags = (flags & BE_MNT_FORCE) ? MNT_FORCE : 0;
|
||||
|
||||
if ((err = unmount(mntpath, mntflags)) != 0) {
|
||||
switch (errno) {
|
||||
case ENAMETOOLONG:
|
||||
return (set_error(lbh, BE_ERR_PATHLEN));
|
||||
case ELOOP:
|
||||
case ENOENT:
|
||||
case ENOTDIR:
|
||||
return (set_error(lbh, BE_ERR_BADPATH));
|
||||
case EPERM:
|
||||
return (set_error(lbh, BE_ERR_PERMS));
|
||||
case EBUSY:
|
||||
return (set_error(lbh, BE_ERR_PATHBUSY));
|
||||
default:
|
||||
return (set_error(lbh, BE_ERR_UNKNOWN));
|
||||
}
|
||||
}
|
||||
|
||||
return (set_error(lbh, BE_ERR_SUCCESS));
|
||||
}
|
133
lib/libbe/be_error.c
Normal file
133
lib/libbe/be_error.c
Normal 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.
|
||||
*/
|
||||
|
||||
#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_INVORIGIN:
|
||||
return ("snapshot origin's mountpoint is not \"/\"");
|
||||
|
||||
case BE_ERR_NOORIGIN:
|
||||
return ("could not open snapshot's origin");
|
||||
|
||||
case BE_ERR_MOUNTED:
|
||||
return ("boot environment is already mounted");
|
||||
|
||||
case BE_ERR_NOMOUNT:
|
||||
return ("boot environment is not mounted");
|
||||
|
||||
case BE_ERR_ZFSOPEN:
|
||||
return ("calling zfs_open() failed");
|
||||
|
||||
case BE_ERR_ZFSCLONE:
|
||||
return ("error when calling zfs_clone() to create boot env");
|
||||
|
||||
case BE_ERR_IO:
|
||||
return ("input/output error");
|
||||
|
||||
case BE_ERR_NOPOOL:
|
||||
return ("operation not supported on this pool");
|
||||
|
||||
case BE_ERR_NOMEM:
|
||||
return ("insufficient memory");
|
||||
|
||||
case BE_ERR_UNKNOWN:
|
||||
return ("unknown error");
|
||||
|
||||
default:
|
||||
assert(lbh->error == BE_ERR_SUCCESS);
|
||||
return ("no error");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
libbe_print_on_error(libbe_handle_t *lbh, bool val)
|
||||
{
|
||||
|
||||
lbh->print_on_err = val;
|
||||
libzfs_print_on_error(lbh->lzh, val);
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
set_error(libbe_handle_t *lbh, be_error_t err)
|
||||
{
|
||||
|
||||
lbh->error = err;
|
||||
if (lbh->print_on_err && (err != BE_ERR_SUCCESS))
|
||||
fprintf(stderr, "%s\n", libbe_error_description(lbh));
|
||||
|
||||
return (err);
|
||||
}
|
72
lib/libbe/be_impl.h
Normal file
72
lib/libbe/be_impl.h
Normal file
@ -0,0 +1,72 @@
|
||||
/*-
|
||||
* 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 {
|
||||
libzfs_handle_t *lzh;
|
||||
zpool_handle_t *active_phandle;
|
||||
char root[BE_MAXPATHLEN];
|
||||
char rootfs[BE_MAXPATHLEN];
|
||||
char bootfs[BE_MAXPATHLEN];
|
||||
be_error_t error;
|
||||
bool print_on_err;
|
||||
};
|
||||
|
||||
struct libbe_deep_clone {
|
||||
libbe_handle_t *lbh;
|
||||
const char *bename;
|
||||
const char *snapname;
|
||||
const char *be_root;
|
||||
};
|
||||
|
||||
struct libbe_dccb {
|
||||
zfs_handle_t *zhp;
|
||||
nvlist_t *props;
|
||||
};
|
||||
|
||||
typedef struct prop_data {
|
||||
nvlist_t *list;
|
||||
libbe_handle_t *lbh;
|
||||
bool single_object; /* list will contain props directly */
|
||||
} prop_data_t;
|
||||
|
||||
int prop_list_builder_cb(zfs_handle_t *, void *);
|
||||
int be_proplist_update(prop_data_t *);
|
||||
|
||||
/* Clobbers any previous errors */
|
||||
int set_error(libbe_handle_t *, be_error_t);
|
||||
|
||||
#endif /* _LIBBE_IMPL_H */
|
320
lib/libbe/be_info.c
Normal file
320
lib/libbe/be_info.c
Normal file
@ -0,0 +1,320 @@
|
||||
/*-
|
||||
* 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)
|
||||
{
|
||||
|
||||
return (strrchr(lbh->rootfs, '/') + sizeof(char));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Returns full path of the active boot environment
|
||||
*/
|
||||
const char *
|
||||
be_active_path(libbe_handle_t *lbh)
|
||||
{
|
||||
|
||||
return (lbh->rootfs);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the name of the next active boot environment
|
||||
*/
|
||||
const char *
|
||||
be_nextboot_name(libbe_handle_t *lbh)
|
||||
{
|
||||
|
||||
return (strrchr(lbh->bootfs, '/') + sizeof(char));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Returns full path of the active boot environment
|
||||
*/
|
||||
const char *
|
||||
be_nextboot_path(libbe_handle_t *lbh)
|
||||
{
|
||||
|
||||
return (lbh->bootfs);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Returns the path of the boot environment root dataset
|
||||
*/
|
||||
const char *
|
||||
be_root_path(libbe_handle_t *lbh)
|
||||
{
|
||||
|
||||
return (lbh->root);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Populates dsnvl with one nvlist per bootenv dataset describing the properties
|
||||
* of that dataset that we've declared ourselves to care about.
|
||||
*/
|
||||
int
|
||||
be_get_bootenv_props(libbe_handle_t *lbh, nvlist_t *dsnvl)
|
||||
{
|
||||
prop_data_t data;
|
||||
|
||||
data.lbh = lbh;
|
||||
data.list = dsnvl;
|
||||
data.single_object = false;
|
||||
return (be_proplist_update(&data));
|
||||
}
|
||||
|
||||
int
|
||||
be_get_dataset_props(libbe_handle_t *lbh, const char *name, nvlist_t *props)
|
||||
{
|
||||
zfs_handle_t *snap_hdl;
|
||||
prop_data_t data;
|
||||
int ret;
|
||||
|
||||
data.lbh = lbh;
|
||||
data.list = props;
|
||||
data.single_object = true;
|
||||
if ((snap_hdl = zfs_open(lbh->lzh, name,
|
||||
ZFS_TYPE_FILESYSTEM | ZFS_TYPE_SNAPSHOT)) == NULL)
|
||||
return (BE_ERR_ZFSOPEN);
|
||||
|
||||
ret = prop_list_builder_cb(snap_hdl, &data);
|
||||
zfs_close(snap_hdl);
|
||||
return (ret);
|
||||
}
|
||||
|
||||
int
|
||||
be_get_dataset_snapshots(libbe_handle_t *lbh, const char *name, nvlist_t *props)
|
||||
{
|
||||
zfs_handle_t *ds_hdl;
|
||||
prop_data_t data;
|
||||
int ret;
|
||||
|
||||
data.lbh = lbh;
|
||||
data.list = props;
|
||||
data.single_object = false;
|
||||
if ((ds_hdl = zfs_open(lbh->lzh, name,
|
||||
ZFS_TYPE_FILESYSTEM)) == NULL)
|
||||
return (BE_ERR_ZFSOPEN);
|
||||
|
||||
ret = snapshot_proplist_update(ds_hdl, &data);
|
||||
zfs_close(ds_hdl);
|
||||
return (ret);
|
||||
}
|
||||
|
||||
/*
|
||||
* Internal callback function used by zfs_iter_filesystems. For each dataset in
|
||||
* the bootenv root, populate an nvlist_t of its relevant properties.
|
||||
*/
|
||||
int
|
||||
prop_list_builder_cb(zfs_handle_t *zfs_hdl, void *data_p)
|
||||
{
|
||||
char buf[512], *mountpoint;
|
||||
prop_data_t *data;
|
||||
libbe_handle_t *lbh;
|
||||
nvlist_t *props;
|
||||
const char *dataset, *name;
|
||||
boolean_t mounted;
|
||||
|
||||
/*
|
||||
* XXX TODO:
|
||||
* some system for defining constants for the nvlist keys
|
||||
* error checking
|
||||
*/
|
||||
data = (prop_data_t *)data_p;
|
||||
lbh = data->lbh;
|
||||
|
||||
if (data->single_object)
|
||||
props = data->list;
|
||||
else
|
||||
nvlist_alloc(&props, NV_UNIQUE_NAME, KM_SLEEP);
|
||||
|
||||
dataset = zfs_get_name(zfs_hdl);
|
||||
nvlist_add_string(props, "dataset", dataset);
|
||||
|
||||
name = strrchr(dataset, '/') + 1;
|
||||
nvlist_add_string(props, "name", name);
|
||||
|
||||
mounted = zfs_is_mounted(zfs_hdl, &mountpoint);
|
||||
|
||||
if (mounted)
|
||||
nvlist_add_string(props, "mounted", mountpoint);
|
||||
|
||||
if (zfs_prop_get(zfs_hdl, ZFS_PROP_MOUNTPOINT, buf, 512,
|
||||
NULL, NULL, 0, 1) == 0)
|
||||
nvlist_add_string(props, "mountpoint", buf);
|
||||
|
||||
if (zfs_prop_get(zfs_hdl, ZFS_PROP_ORIGIN, buf, 512,
|
||||
NULL, NULL, 0, 1) == 0)
|
||||
nvlist_add_string(props, "origin", buf);
|
||||
|
||||
if (zfs_prop_get(zfs_hdl, ZFS_PROP_CREATION, buf, 512,
|
||||
NULL, NULL, 0, 1) == 0)
|
||||
nvlist_add_string(props, "creation", buf);
|
||||
|
||||
nvlist_add_boolean_value(props, "active",
|
||||
(strcmp(be_active_path(lbh), dataset) == 0));
|
||||
|
||||
if (zfs_prop_get(zfs_hdl, ZFS_PROP_USED, buf, 512,
|
||||
NULL, NULL, 0, 1) == 0)
|
||||
nvlist_add_string(props, "used", buf);
|
||||
|
||||
if (zfs_prop_get(zfs_hdl, ZFS_PROP_USEDDS, buf, 512,
|
||||
NULL, NULL, 0, 1) == 0)
|
||||
nvlist_add_string(props, "usedds", buf);
|
||||
|
||||
if (zfs_prop_get(zfs_hdl, ZFS_PROP_USEDSNAP, buf, 512,
|
||||
NULL, NULL, 0, 1) == 0)
|
||||
nvlist_add_string(props, "usedsnap", buf);
|
||||
|
||||
if (zfs_prop_get(zfs_hdl, ZFS_PROP_USEDREFRESERV, buf, 512,
|
||||
NULL, NULL, 0, 1) == 0)
|
||||
nvlist_add_string(props, "usedrefreserv", buf);
|
||||
|
||||
if (zfs_prop_get(zfs_hdl, ZFS_PROP_REFERENCED, buf, 512,
|
||||
NULL, NULL, 0, 1) == 0)
|
||||
nvlist_add_string(props, "referenced", buf);
|
||||
|
||||
nvlist_add_boolean_value(props, "nextboot",
|
||||
(strcmp(be_nextboot_path(lbh), dataset) == 0));
|
||||
|
||||
if (!data->single_object)
|
||||
nvlist_add_nvlist(data->list, name, props);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Updates the properties of each bootenv in the libbe handle
|
||||
* XXX TODO: ensure that this is always consistent (run after adds, deletes,
|
||||
* renames,etc
|
||||
*/
|
||||
int
|
||||
be_proplist_update(prop_data_t *data)
|
||||
{
|
||||
zfs_handle_t *root_hdl;
|
||||
|
||||
if ((root_hdl = zfs_open(data->lbh->lzh, data->lbh->root,
|
||||
ZFS_TYPE_FILESYSTEM)) == NULL)
|
||||
return (BE_ERR_ZFSOPEN);
|
||||
|
||||
/* XXX TODO: some error checking here */
|
||||
zfs_iter_filesystems(root_hdl, prop_list_builder_cb, data);
|
||||
|
||||
zfs_close(root_hdl);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
snapshot_proplist_update(zfs_handle_t *hdl, prop_data_t *data)
|
||||
{
|
||||
|
||||
return (zfs_iter_snapshots_sorted(hdl, prop_list_builder_cb, data));
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
be_prop_list_alloc(nvlist_t **be_list)
|
||||
{
|
||||
|
||||
return (nvlist_alloc(be_list, NV_UNIQUE_NAME, KM_SLEEP));
|
||||
}
|
||||
|
||||
/*
|
||||
* frees property list and its children
|
||||
*/
|
||||
void
|
||||
be_prop_list_free(nvlist_t *be_list)
|
||||
{
|
||||
nvlist_t *prop_list;
|
||||
nvpair_t *be_pair;
|
||||
|
||||
be_pair = nvlist_next_nvpair(be_list, NULL);
|
||||
if (nvpair_value_nvlist(be_pair, &prop_list) == 0)
|
||||
nvlist_free(prop_list);
|
||||
|
||||
while ((be_pair = nvlist_next_nvpair(be_list, be_pair)) != NULL) {
|
||||
if (nvpair_value_nvlist(be_pair, &prop_list) == 0)
|
||||
nvlist_free(prop_list);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Usage
|
||||
*/
|
||||
bool
|
||||
be_exists(libbe_handle_t *lbh, char *be)
|
||||
{
|
||||
char buf[BE_MAXPATHLEN];
|
||||
nvlist_t *dsprops;
|
||||
char *mntpoint;
|
||||
bool valid;
|
||||
|
||||
be_root_concat(lbh, be, buf);
|
||||
|
||||
if (!zfs_dataset_exists(lbh->lzh, buf, ZFS_TYPE_DATASET))
|
||||
return (false);
|
||||
|
||||
/* Also check if it's mounted at / */
|
||||
if (be_prop_list_alloc(&dsprops) != 0) {
|
||||
set_error(lbh, BE_ERR_UNKNOWN);
|
||||
return (false);
|
||||
}
|
||||
|
||||
if (be_get_dataset_props(lbh, buf, dsprops) != 0) {
|
||||
nvlist_free(dsprops);
|
||||
return (false);
|
||||
}
|
||||
|
||||
if (nvlist_lookup_string(dsprops, "mountpoint", &mntpoint) == 0) {
|
||||
valid = (strcmp(mntpoint, "/") == 0);
|
||||
nvlist_free(dsprops);
|
||||
return (valid);
|
||||
}
|
||||
|
||||
nvlist_free(dsprops);
|
||||
return (false);
|
||||
}
|
463
lib/libbe/libbe.3
Normal file
463
lib/libbe/libbe.3
Normal file
@ -0,0 +1,463 @@
|
||||
.\"
|
||||
.\" 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 August 10, 2018
|
||||
.Dt LIBBE 3
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm libbe
|
||||
.Nd library for creating, destroying and modifying ZFS boot environments
|
||||
.Sh LIBRARY
|
||||
.Lb libbe
|
||||
.Sh SYNOPSIS
|
||||
.In be.h
|
||||
.Ft "libbe_handle_t *hdl" Ns
|
||||
.Fn libbe_init void
|
||||
.Pp
|
||||
.Ft void
|
||||
.Fn libbe_close "libbe_handle_t *hdl"
|
||||
.Pp
|
||||
.Ft const char * Ns
|
||||
.Fn be_active_name "libbe_handle_t *hdl"
|
||||
.Pp
|
||||
.Ft const char * Ns
|
||||
.Fn be_active_path "libbe_handle_t *hdl"
|
||||
.Pp
|
||||
.Ft const char * Ns
|
||||
.Fn be_nextboot_name "libbe_handle_t *hdl"
|
||||
.Pp
|
||||
.Ft const char * Ns
|
||||
.Fn be_nextboot_path "libbe_handle_t *hdl"
|
||||
.Pp
|
||||
.Ft const char * Ns
|
||||
.Fn be_root_path "libbe_handle_t *hdl"
|
||||
.Pp
|
||||
.Ft int
|
||||
.Fn be_create "libbe_handle_t *hdl" "const char *be_name"
|
||||
.Pp
|
||||
.Ft int
|
||||
.Fn be_create_from_existing "libbe_handle_t *hdl" "const char *be_name" "const char *be_origin"
|
||||
.Pp
|
||||
.Ft int
|
||||
.Fn be_create_from_existing_snap "libbe_handle_t *hdl" "const char *be_name" "const char *snap"
|
||||
.Pp
|
||||
.Ft int
|
||||
.Fn be_rename "libbe_handle_t *hdl" "const char *be_old" "const char *be_new"
|
||||
.Pp
|
||||
.Ft int
|
||||
.Fn be_activate "libbe_handle_t *hdl" "const char *be_name" "bool temporary"
|
||||
.Ft int
|
||||
.Fn be_destroy "libbe_handle_t *hdl" "const char *be_name" "int options"
|
||||
.Pp
|
||||
.Ft void
|
||||
.Fn be_nicenum "uint64_t num" "char *buf" "size_t bufsz"
|
||||
.Pp
|
||||
.\" TODO: Write up of mount options
|
||||
.\" typedef enum {
|
||||
.\" BE_MNT_FORCE = 1 << 0,
|
||||
.\" BE_MNT_DEEP = 1 << 1,
|
||||
.\" } be_mount_opt_t
|
||||
.Ft int
|
||||
.Fn be_mount "libbe_handle_t *hdl" "char *be_name" "char *mntpoint" "int flags" "char *result"
|
||||
.Pp
|
||||
.Ft int
|
||||
.Fn be_mounted_at "libbe_handle_t *hdl" "const char *path" "nvlist_t *details"
|
||||
.Pp
|
||||
.Ft int
|
||||
.Fn be_unmount "libbe_handle_t *hdl" "char *be_name" "int flags"
|
||||
.Pp
|
||||
.Ft int
|
||||
.Fn libbe_errno "libbe_handle_t *hdl"
|
||||
.Pp
|
||||
.Ft const char * Ns
|
||||
.Fn libbe_error_description "libbe_handle_t *hdl"
|
||||
.Pp
|
||||
.Ft void
|
||||
.Fn libbe_print_on_error "libbe_handle_t *hdl" "bool doprint"
|
||||
.Pp
|
||||
.Ft int
|
||||
.Fn be_root_concat "libbe_handle_t *hdl" "const char *be_name" "char *result"
|
||||
.Pp
|
||||
.Ft int
|
||||
.Fn be_validate_name "libbe_handle_t *hdl" "const char *be_name"
|
||||
.Pp
|
||||
.Ft int
|
||||
.Fn be_validate_snap "libbe_handle_t *hdl" "const char *snap"
|
||||
.Pp
|
||||
.Ft bool
|
||||
.Fn be_exists "libbe_handle_t *hdl" "char *be_name"
|
||||
.Pp
|
||||
.Ft int
|
||||
.Fn be_export "libbe_handle_t *hdl" "const char *be_name" "int fd"
|
||||
.Pp
|
||||
.Ft int
|
||||
.Fn be_import "libbe_handle_t *hdl" "const char *be_name" "int fd"
|
||||
.Pp
|
||||
.Ft int
|
||||
.Fn be_prop_list_alloc "nvlist_t **prop_list"
|
||||
.Pp
|
||||
.Ft int
|
||||
.Fn be_get_bootenv_props "libbe_handle_t *hdl" "nvlist_t *be_list"
|
||||
.Pp
|
||||
.Ft int
|
||||
.Fn be_get_dataset_props "libbe_handle_t *hdl" "const char *ds_name" "nvlist_t *props"
|
||||
.Pp
|
||||
.Ft int
|
||||
.Fn be_get_dataset_snapshots "libbe_handle_t *hdl" "const char *ds_name" "nvlist_t *snap_list"
|
||||
.Pp
|
||||
.Ft void
|
||||
.Fn be_prop_list_free "nvlist_t *prop_list"
|
||||
.Sh DESCRIPTION
|
||||
.Nm
|
||||
interfaces with libzfs to provide a set of functions for various operations
|
||||
regarding ZFS boot environments including "deep" boot environments in which
|
||||
a boot environments has child datasets.
|
||||
.Pp
|
||||
A context structure is passed to each function, allowing for a small amount
|
||||
of state to be retained, such as errors from previous operations.
|
||||
.Nm
|
||||
may be configured to print the corresponding error message to
|
||||
.Dv stderr
|
||||
when an error is encountered with
|
||||
.Fn libbe_print_on_error .
|
||||
.Pp
|
||||
All functions returning an
|
||||
.Vt int
|
||||
return 0 on success, or a
|
||||
.Nm
|
||||
errno otherwise as described in
|
||||
.Sx DIAGNOSTICS .
|
||||
.Pp
|
||||
The
|
||||
.Fn libbe_init
|
||||
function initializes
|
||||
.Nm ,
|
||||
returning a
|
||||
.Vt "libbe_handle_t *"
|
||||
on success, or
|
||||
.Dv NULL
|
||||
on error.
|
||||
An error may occur if:
|
||||
.Bl -column
|
||||
.It /boot and / are not on the same filesystem and device,
|
||||
.It libzfs fails to initialize,
|
||||
.It The system has not been properly booted with a ZFS boot
|
||||
environment,
|
||||
.It Nm
|
||||
fails to open the zpool the active boot environment resides on, or
|
||||
.It Nm
|
||||
fails to locate the boot environment that is currently mounted.
|
||||
.El
|
||||
.Pp
|
||||
The
|
||||
.Fn libbe_close
|
||||
function frees all resources previously acquired in
|
||||
.Fn libbe_init ,
|
||||
invalidating the handle in the process.
|
||||
.Pp
|
||||
The
|
||||
.Fn be_active_name
|
||||
function returns the name of the currently booted boot environment,
|
||||
.Pp
|
||||
The
|
||||
.Fn be_active_path
|
||||
function returns the full path of the currently booted boot environment.
|
||||
.Pp
|
||||
The
|
||||
.Fn be_nextboot_name
|
||||
function returns the name of the boot environment that will be active on reboot.
|
||||
.Pp
|
||||
The
|
||||
.Fn be_nextboot_path
|
||||
function returns the full path of the boot environment that will be
|
||||
active on reboot.
|
||||
.Pp
|
||||
The
|
||||
.Fn be_root_path
|
||||
function returns the boot environment root path.
|
||||
.Pp
|
||||
The
|
||||
.Fn be_create
|
||||
function creates a boot environment with the given name.
|
||||
It will be created from a snapshot of the currently booted boot environment.
|
||||
.Pp
|
||||
The
|
||||
.Fn be_create_from_existing
|
||||
function creates a boot environment with the given name from the name of an
|
||||
existing boot environment.
|
||||
A snapshot will be made of the base boot environment, and the new boot
|
||||
environment will be created from that.
|
||||
.Pp
|
||||
The
|
||||
.Fn be_create_from_existing_snap
|
||||
function creates a boot environment with the given name from an existing
|
||||
snapshot.
|
||||
.Pp
|
||||
The
|
||||
.Fn be_rename
|
||||
function renames a boot environment.
|
||||
.Pp
|
||||
The
|
||||
.Fn be_activate
|
||||
function makes a boot environment active on the next boot.
|
||||
If the
|
||||
.Fa temporary
|
||||
flag is set, then it will be active for the next boot only, as done by
|
||||
.Xr zfsbootcfg 8 .
|
||||
Next boot functionality is currently only available when booting in x86 BIOS
|
||||
mode.
|
||||
.Pp
|
||||
The
|
||||
.Fn be_destroy
|
||||
function will recursively destroy the given boot environment.
|
||||
It will not destroy a mounted boot environment unless the
|
||||
.Dv BE_DESTROY_FORCE
|
||||
option is set in
|
||||
.Fa options .
|
||||
.Pp
|
||||
The
|
||||
.Fn be_nicenum
|
||||
function will format
|
||||
.Fa name
|
||||
in a traditional ZFS humanized format, similar to
|
||||
.Xr humanize_number 3 .
|
||||
This function effectively proxies
|
||||
.Fn zfs_nicenum
|
||||
from libzfs.
|
||||
.Pp
|
||||
The
|
||||
.Fn be_mount
|
||||
function will mount the given boot environment.
|
||||
If
|
||||
.Fa mountpoint
|
||||
is
|
||||
.Dv NULL ,
|
||||
a mount point will be generated in
|
||||
.Pa /tmp
|
||||
using
|
||||
.Xr mkdtemp 3 .
|
||||
If
|
||||
.Fa result
|
||||
is not
|
||||
.Dv NULL ,
|
||||
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.
|
||||
.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 / .
|
||||
.Pp
|
||||
The
|
||||
.Fn be_export
|
||||
function will export the given boot environment to the file specified by
|
||||
.Fa fd .
|
||||
A snapshot will be created of the boot environment prior to export.
|
||||
.Pp
|
||||
The
|
||||
.Fn be_import
|
||||
function will import the boot environment in the file specified by
|
||||
.Fa fd ,
|
||||
and give it the name
|
||||
.Fa be_name .
|
||||
.Pp
|
||||
The
|
||||
.Fn be_prop_list_alloc
|
||||
function allocates a property list suitable for passing to
|
||||
.Fn be_get_bootenv_props ,
|
||||
.Fn be_get_dataset_props ,
|
||||
or
|
||||
.Fn be_get_dataset_snapshots .
|
||||
It should be freed later by
|
||||
.Fa be_prop_list_free .
|
||||
.Pp
|
||||
The
|
||||
.Fn be_get_bootenv_props
|
||||
function will populate
|
||||
.Fa be_list
|
||||
with
|
||||
.Vt nvpair_t
|
||||
of boot environment names paired with an
|
||||
.Vt nvlist_t
|
||||
of their properties.
|
||||
The following properties are currently collected as appropriate:
|
||||
.Bl -column "Returned name"
|
||||
.It Sy Returned name Ta Sy Description
|
||||
.It dataset Ta -
|
||||
.It name Ta Boot environment name
|
||||
.It mounted Ta Current mount point
|
||||
.It mountpoint Ta Do mountpoint Dc property
|
||||
.It origin Ta Do origin Dc property
|
||||
.It creation Ta Do creation Dc property
|
||||
.It active Ta Currently booted environment
|
||||
.It used Ta Literal Do used Dc property
|
||||
.It usedds Ta Literal Do usedds Dc property
|
||||
.It usedsnap Ta Literal Do usedrefreserv Dc property
|
||||
.It referenced Ta Literal Do referenced Dc property
|
||||
.It nextboot Ta Active on next boot
|
||||
.El
|
||||
.Pp
|
||||
Only the
|
||||
.Dq dataset ,
|
||||
.Dq name ,
|
||||
.Dq active ,
|
||||
and
|
||||
.Dq nextboot
|
||||
returned values will always be present.
|
||||
All other properties may be omitted if not available.
|
||||
.Pp
|
||||
The
|
||||
.Fn be_get_dataset_props
|
||||
function will get properties of the specified dataset.
|
||||
.Fa props
|
||||
is populated directly with a list of the properties as returned by
|
||||
.Fn be_get_bootenv_props .
|
||||
.Pp
|
||||
The
|
||||
.Fn be_get_dataset_snapshots
|
||||
function will retrieve all snapshots of the given dataset.
|
||||
.Fa snap_list
|
||||
will be populated with a list of
|
||||
.Vt nvpair_t
|
||||
exactly as specified by
|
||||
.Fn be_get_bootenv_props .
|
||||
.Pp
|
||||
The
|
||||
.Fn be_prop_list_free
|
||||
function will free the property list.
|
||||
.Sh DIAGNOSTICS
|
||||
Upon error, one of the following values will be returned.
|
||||
.\" TODO: make each entry on its own line.
|
||||
.Bd -ragged -offset indent
|
||||
BE_ERR_SUCCESS,
|
||||
BE_ERR_INVALIDNAME,
|
||||
BE_ERR_EXISTS,
|
||||
BE_ERR_NOENT,
|
||||
BE_ERR_PERMS,
|
||||
BE_ERR_DESTROYACT,
|
||||
BE_ERR_DESTROYMNT,
|
||||
BE_ERR_BADPATH,
|
||||
BE_ERR_PATHBUSY,
|
||||
BE_ERR_PATHLEN,
|
||||
BE_ERR_INVORIGIN,
|
||||
BE_ERR_NOORIGIN,
|
||||
BE_ERR_MOUNTED,
|
||||
BE_ERR_NOMOUNT,
|
||||
BE_ERR_ZFSOPEN,
|
||||
BE_ERR_ZFSCLONE,
|
||||
BE_ERR_IO,
|
||||
BE_ERR_NOPOOL,
|
||||
BE_ERR_NOMEM,
|
||||
BE_ERR_UNKNOWN
|
||||
.Ed
|
||||
.Sh SEE ALSO
|
||||
.Xr be 1
|
||||
.Sh HISTORY
|
||||
.Nm
|
||||
and its corresponding command,
|
||||
.Xr bectl 8 ,
|
||||
were written as a 2017 Google Summer of Code project with Allan Jude serving
|
||||
as a mentor.
|
||||
Later work was done by
|
||||
.An Kyle Evans Aq Mt kevans@FreeBSD.org .
|
||||
.Sh BUGS
|
||||
The
|
||||
.Fn be_import
|
||||
function does not destroy the temporary boot environment it creates for import,
|
||||
because the snapshot created to do the import may not be deleted since it is the
|
||||
origin of the new boot environment.
|
@ -86,6 +86,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
|
||||
|
20
sbin/bectl/Makefile
Normal file
20
sbin/bectl/Makefile
Normal file
@ -0,0 +1,20 @@
|
||||
# $FreeBSD$
|
||||
|
||||
PROG= bectl
|
||||
MAN= bectl.8
|
||||
|
||||
SRCS= bectl.c bectl_jail.c bectl_list.c
|
||||
|
||||
LIBADD+= be
|
||||
LIBADD+= jail
|
||||
LIBADD+= nvpair
|
||||
LIBADD+= util
|
||||
|
||||
CFLAGS+= -I${SRCTOP}/cddl/contrib/opensolaris/lib/libzfs/common
|
||||
CFLAGS+= -I${SRCTOP}/sys/cddl/compat/opensolaris
|
||||
CFLAGS+= -I${SRCTOP}/sys/cddl/contrib/opensolaris/uts/common
|
||||
CFLAGS+= -I${SRCTOP}/cddl/contrib/opensolaris/lib/libnvpair
|
||||
|
||||
CFLAGS+= -DNEED_SOLARIS_BOOLEAN
|
||||
|
||||
.include <bsd.prog.mk>
|
279
sbin/bectl/bectl.8
Normal file
279
sbin/bectl/bectl.8
Normal file
@ -0,0 +1,279 @@
|
||||
.\"
|
||||
.\" 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 August 10, 2018
|
||||
.Dt BECTL 8
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm bectl
|
||||
.Nd Utility to manage Boot Environments on ZFS
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
activate
|
||||
.Op Fl t
|
||||
.Ao Ar beName Ac
|
||||
.Nm
|
||||
create
|
||||
.Op Fl r
|
||||
.Op Fl e Ar nonActiveBe | Fl e Ar beName@snapshot
|
||||
.Ao Ar beName Ac
|
||||
.Nm
|
||||
create
|
||||
.Op Fl r
|
||||
.Ao Ar beName@snapshot Ac
|
||||
.Nm
|
||||
destroy
|
||||
.Op Fl F
|
||||
.Ao Ar beName | beName@snapshot Ac
|
||||
.Nm
|
||||
export
|
||||
.Ao Ar sourceBe Ac
|
||||
.Nm
|
||||
import
|
||||
.Ao Ar targetBe Ac
|
||||
.Nm
|
||||
jail
|
||||
.Oo Fl o Ar key Ns = Ns Ar value | Fl u Ar key Oc Ns ...
|
||||
.Ao Ar jailID | jailName Ac
|
||||
.Ao Ar bootenv Ac
|
||||
.Nm
|
||||
list
|
||||
.Op Fl a
|
||||
.Op Fl D
|
||||
.Op Fl H
|
||||
.Op Fl s
|
||||
.Nm
|
||||
mount
|
||||
.Ao Ar beName Ac
|
||||
.Op mountpoint
|
||||
.Nm
|
||||
rename
|
||||
.Ao Ar origBeName Ac
|
||||
.Ao Ar newBeName Ac
|
||||
.Nm
|
||||
{ ujail | unjail }
|
||||
.Ao Ar jailID | jailName Ac
|
||||
.Ao Ar bootenv Ac
|
||||
.Nm
|
||||
{ umount | unmount }
|
||||
.Op Fl f
|
||||
.Ao Ar beName Ac
|
||||
.Sh DESCRIPTION
|
||||
The
|
||||
.Nm
|
||||
command is used to setup and interact with ZFS boot environments, which are bootable clones of datasets.
|
||||
.Pp
|
||||
.Em Boot Environments
|
||||
allows the system to be upgraded, while preserving the old system environment in a separate ZFS dataset.
|
||||
.Sh COMMANDS
|
||||
The following commands are supported by
|
||||
.Nm :
|
||||
.Bl -tag -width activate
|
||||
.It Ic activate
|
||||
.Op Fl t
|
||||
.Ar <beName>
|
||||
.Pp
|
||||
Activate the given
|
||||
.Ar beName
|
||||
as the default boot filesystem.
|
||||
If the
|
||||
.Op Fl t
|
||||
flag is given, this takes effect only for the next boot.
|
||||
.It Ic create
|
||||
.Op Fl r
|
||||
.Op Fl e Ar nonActiveBe | Fl e Ar beName@snapshot
|
||||
.Ao Ar beName Ac
|
||||
.Pp
|
||||
Creates a new boot environment named
|
||||
.Ar beName .
|
||||
If the -e param is specified, the new environment will be cloned from the given
|
||||
.Ar nonActiveBe | Ar beName@snapshot .
|
||||
If the
|
||||
.Op Fl r
|
||||
flag is given, a recursive boot environment will be made.
|
||||
.It Ic create
|
||||
.Op Fl r
|
||||
.Ao Ar beName@snapshot Ac
|
||||
.Pp
|
||||
Creates a snapshot of the existing boot environment named
|
||||
.Ar beName .
|
||||
If the
|
||||
.Op Fl r
|
||||
flag is given, a recursive boot environment will be made.
|
||||
.It Ic destroy
|
||||
.Op Fl F
|
||||
.Ao Ar beName | beName@snapshot Ac
|
||||
.Pp
|
||||
Destroys the given
|
||||
.Ar beName
|
||||
boot environment or
|
||||
.Ar beName@snapshot
|
||||
snapshot.
|
||||
Specifying
|
||||
.Fl F
|
||||
will automatically unmount without confirmation.
|
||||
.It Ic export
|
||||
.Ao Ar sourceBe Ac
|
||||
.Pp
|
||||
Export
|
||||
.Ar sourceBe
|
||||
to
|
||||
.Dv stdout .
|
||||
.Dv stdout
|
||||
must be piped or redirected to a file.
|
||||
.It Ic import
|
||||
.Ao Ar targetBe Ac
|
||||
.Pp
|
||||
Import
|
||||
.Ar targetBe
|
||||
from
|
||||
.Dv stdin .
|
||||
.It Ic jail
|
||||
.Oo Fl o Ar key Ns = Ns Ar value | Fl u Ar key Oc Ns ...
|
||||
.Ao Ar jailID | jailName Ac
|
||||
.Ao Ar bootenv Ac
|
||||
.Pp
|
||||
Creates a jail of the given boot environment.
|
||||
Multiple
|
||||
.Fl o
|
||||
and
|
||||
.Fl u
|
||||
arguments may be specified.
|
||||
.Fl o
|
||||
will set a jail parameter, and
|
||||
.Fl u
|
||||
will unset a jail parameter.
|
||||
.Pp
|
||||
The
|
||||
.Va name ,
|
||||
.Va host.hostname ,
|
||||
and
|
||||
.Va path
|
||||
may not actually be unset.
|
||||
Attempts to unset any of these will revert them to the default values specified
|
||||
below, if they have been overwritten by
|
||||
.Fl o .
|
||||
.Pp
|
||||
All
|
||||
.Ar key ,
|
||||
.Ar value
|
||||
pairs are interpreted as jail parameters as described in
|
||||
.Xr jail 8 .
|
||||
The following default parameters are provided:
|
||||
.Bl -tag -width -indent
|
||||
.It Va allow.mount Ns = Ns Ar true
|
||||
.It Va allow.mount.devfs Ns = Ns Ar true
|
||||
.It Va enforce_statfs Ns = Ns Ar 1
|
||||
.It Va name Ns = Ns Ar bootenv
|
||||
.It Va host.hostname Ns = Ns Ar bootenv
|
||||
.It Va path
|
||||
Set to a path in /tmp generated by
|
||||
.Xr libbe 3 .
|
||||
.El
|
||||
.Pp
|
||||
All default parameters may be overwritten.
|
||||
.It Ic list
|
||||
.Op Fl a
|
||||
.Op Fl D
|
||||
.Op Fl H
|
||||
.Op Fl s
|
||||
.Pp
|
||||
Displays all boot environments.
|
||||
The Active field indicates whether the boot environment is active now (N); active on reboot (R); or both (NR).
|
||||
.Pp
|
||||
If
|
||||
.Fl a
|
||||
is used, display all datasets.
|
||||
If
|
||||
.Fl D
|
||||
is used, display the full space usage for each boot environment, assuming all other boot environments were destroyed.
|
||||
The
|
||||
.Fl H
|
||||
option is used for scripting.
|
||||
It does not print headers and separate fields by a single tab instead of arbitrary white space.
|
||||
If
|
||||
.Fl s
|
||||
is used, display all snapshots as well.
|
||||
.It Ic mount
|
||||
.Ao Ar beName Ac
|
||||
.Op mountpoint
|
||||
.Pp
|
||||
Temporarily mount the boot environment.
|
||||
Mount at the specified
|
||||
.Ar mountpoint
|
||||
if provided.
|
||||
.It Ic rename Ao Ar origBeName Ac Ao Ar newBeName Ac
|
||||
.Pp
|
||||
Renames the given nonactive
|
||||
.Ar origBeName
|
||||
to the given
|
||||
.Ar newBeName
|
||||
.It Ic unjail Ao Ar jailID | jailName | beName Ac
|
||||
.Pp
|
||||
Destroys the jail created from the given boot environment.
|
||||
.It Ic unmount
|
||||
.Op Fl f
|
||||
.Ao Ar beName Ac
|
||||
.Pp
|
||||
Unmount the given boot environment, if it is mounted.
|
||||
Specifying
|
||||
.Fl f
|
||||
will force the unmount if busy.
|
||||
.El
|
||||
.Sh EXAMPLES
|
||||
.Bl -bullet
|
||||
.It
|
||||
To fill in with jail upgrade example when behavior is firm.
|
||||
.El
|
||||
.Sh SEE ALSO
|
||||
.Xr jail 8 ,
|
||||
.Xr zfs 8 ,
|
||||
.Xr zpool 8
|
||||
.Sh HISTORY
|
||||
.Nm
|
||||
is based on
|
||||
.Xr beadm 1
|
||||
and was implemented as a project for the 2017 Summer of Code, along with
|
||||
.Xr libbe 3 .
|
||||
.Sh AUTHORS
|
||||
.Bl -bullet
|
||||
.It
|
||||
.An Kyle Kneitinger (kneitinger) Aq Mt kyle@kneit.in
|
||||
.Pp
|
||||
Creator of
|
||||
.Nm .
|
||||
.It
|
||||
.An Slawomir Wojciech Wojtczak (vermaden) Aq Mt vermaden@interia.pl
|
||||
.Pp
|
||||
Creator and maintainer of
|
||||
.Xr beadm 1 .
|
||||
.It
|
||||
.An Bryan Drewery (bdrewery) Aq Mt bryan@shatow.net
|
||||
.Pp
|
||||
Wrote the original
|
||||
.Xr beadm 1
|
||||
manual page that this one is derived from.
|
||||
.El
|
||||
.Sh BUGS
|
||||
.Nm
|
||||
import
|
||||
does not destroy the temporary boot environment it creates for import, because
|
||||
the snapshot created to do the import may not be deleted since it is the
|
||||
origin of the new boot environment.
|
512
sbin/bectl/bectl.c
Normal file
512
sbin/bectl/bectl.c
Normal file
@ -0,0 +1,512 @@
|
||||
/*-
|
||||
* 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,
|
||||
"usage:\tbectl ( -h | -? | subcommand [args...] )\n"
|
||||
"\tbectl activate [-t] beName\n"
|
||||
"\tbectl create [-e nonActiveBe | -e beName@snapshot] beName\n"
|
||||
"\tbectl create beName@snapshot\n"
|
||||
"\tbectl destroy [-F] beName | beName@snapshot⟩\n"
|
||||
"\tbectl export sourceBe\n"
|
||||
"\tbectl import targetBe\n"
|
||||
#if SOON
|
||||
"\tbectl add (path)*\n"
|
||||
#endif
|
||||
"\tbectl jail [ -o key=value | -u key ]... bootenv\n"
|
||||
"\tbectl list [-a] [-D] [-H] [-s]\n"
|
||||
"\tbectl mount beName [mountpoint]\n"
|
||||
"\tbectl rename origBeName newBeName\n"
|
||||
"\tbectl { ujail | unjail } ⟨jailID | jailName | bootenv)\n"
|
||||
"\tbectl { umount | unmount } [-f] beName\n");
|
||||
|
||||
return (explicit ? 0 : EX_USAGE);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Represents a relationship between the command name and the parser action
|
||||
* that handles it.
|
||||
*/
|
||||
struct command_map_entry {
|
||||
const char *command;
|
||||
int (*fn)(int argc, char *argv[]);
|
||||
};
|
||||
|
||||
static struct command_map_entry command_map[] =
|
||||
{
|
||||
{ "activate", bectl_cmd_activate },
|
||||
{ "create", bectl_cmd_create },
|
||||
{ "destroy", bectl_cmd_destroy },
|
||||
{ "export", bectl_cmd_export },
|
||||
{ "import", bectl_cmd_import },
|
||||
#if SOON
|
||||
{ "add", bectl_cmd_add },
|
||||
#endif
|
||||
{ "jail", bectl_cmd_jail },
|
||||
{ "list", bectl_cmd_list },
|
||||
{ "mount", bectl_cmd_mount },
|
||||
{ "rename", bectl_cmd_rename },
|
||||
{ "unjail", bectl_cmd_unjail },
|
||||
{ "unmount", bectl_cmd_unmount },
|
||||
};
|
||||
|
||||
static int
|
||||
get_cmd_index(const char *cmd, int *index)
|
||||
{
|
||||
int map_size;
|
||||
|
||||
map_size = nitems(command_map);
|
||||
for (int i = 0; i < map_size; ++i) {
|
||||
if (strcmp(cmd, command_map[i].command) == 0) {
|
||||
*index = 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 *bootenv, *snapname, *source;
|
||||
int err, opt;
|
||||
|
||||
snapname = NULL;
|
||||
while ((opt = getopt(argc, argv, "e:")) != -1) {
|
||||
switch (opt) {
|
||||
case 'e':
|
||||
snapname = optarg;
|
||||
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 (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 (snapname == NULL)
|
||||
fprintf(stderr,
|
||||
"failed to create bootenv %s\n", bootenv);
|
||||
else
|
||||
fprintf(stderr,
|
||||
"failed to create bootenv %s from snapshot %s\n",
|
||||
bootenv, snapname);
|
||||
}
|
||||
|
||||
return (err);
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
bectl_cmd_export(int argc, char *argv[])
|
||||
{
|
||||
char *bootenv;
|
||||
|
||||
if (argc == 1) {
|
||||
fprintf(stderr, "bectl export: missing boot environment name\n");
|
||||
return (usage(false));
|
||||
}
|
||||
|
||||
if (argc > 2) {
|
||||
fprintf(stderr, "bectl export: extra arguments provided\n");
|
||||
return (usage(false));
|
||||
}
|
||||
|
||||
bootenv = argv[1];
|
||||
|
||||
if (isatty(STDOUT_FILENO)) {
|
||||
fprintf(stderr, "bectl export: must redirect output\n");
|
||||
return (EX_USAGE);
|
||||
}
|
||||
|
||||
be_export(be, bootenv, STDOUT_FILENO);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
bectl_cmd_import(int argc, char *argv[])
|
||||
{
|
||||
char *bootenv;
|
||||
int err;
|
||||
|
||||
if (argc == 1) {
|
||||
fprintf(stderr, "bectl import: missing boot environment name\n");
|
||||
return (usage(false));
|
||||
}
|
||||
|
||||
if (argc > 2) {
|
||||
fprintf(stderr, "bectl import: extra arguments provided\n");
|
||||
return (usage(false));
|
||||
}
|
||||
|
||||
bootenv = argv[1];
|
||||
|
||||
if (isatty(STDIN_FILENO)) {
|
||||
fprintf(stderr, "bectl import: input can not be from terminal\n");
|
||||
return (EX_USAGE);
|
||||
}
|
||||
|
||||
err = be_import(be, bootenv, STDIN_FILENO);
|
||||
|
||||
return (err);
|
||||
}
|
||||
|
||||
#if SOON
|
||||
static int
|
||||
bectl_cmd_add(int argc, char *argv[])
|
||||
{
|
||||
|
||||
if (argc < 2) {
|
||||
fprintf(stderr, "bectl add: must provide at least one path\n");
|
||||
return (usage(false));
|
||||
}
|
||||
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
printf("arg %d: %s\n", i, argv[i]);
|
||||
/* XXX TODO catch err */
|
||||
be_add_child(be, argv[i], true);
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
#endif
|
||||
|
||||
static int
|
||||
bectl_cmd_destroy(int argc, char *argv[])
|
||||
{
|
||||
char *target;
|
||||
int opt, err;
|
||||
bool force;
|
||||
|
||||
force = false;
|
||||
while ((opt = getopt(argc, argv, "F")) != -1) {
|
||||
switch (opt) {
|
||||
case 'F':
|
||||
force = true;
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "bectl destroy: unknown option '-%c'\n",
|
||||
optopt);
|
||||
return (usage(false));
|
||||
}
|
||||
}
|
||||
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
|
||||
if (argc != 1) {
|
||||
fprintf(stderr, "bectl destroy: wrong number of arguments\n");
|
||||
return (usage(false));
|
||||
}
|
||||
|
||||
target = argv[0];
|
||||
|
||||
err = be_destroy(be, target, force);
|
||||
|
||||
return (err);
|
||||
}
|
||||
|
||||
static int
|
||||
bectl_cmd_mount(int argc, char *argv[])
|
||||
{
|
||||
char result_loc[BE_MAXPATHLEN];
|
||||
char *bootenv, *mountpoint;
|
||||
int err;
|
||||
|
||||
if (argc < 2) {
|
||||
fprintf(stderr, "bectl mount: missing argument(s)\n");
|
||||
return (usage(false));
|
||||
}
|
||||
|
||||
if (argc > 3) {
|
||||
fprintf(stderr, "bectl mount: too many arguments\n");
|
||||
return (usage(false));
|
||||
}
|
||||
|
||||
bootenv = argv[1];
|
||||
mountpoint = ((argc == 3) ? argv[2] : NULL);
|
||||
|
||||
err = be_mount(be, bootenv, mountpoint, 0, result_loc);
|
||||
|
||||
switch (err) {
|
||||
case BE_ERR_SUCCESS:
|
||||
printf("successfully mounted %s at %s\n", bootenv, result_loc);
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr,
|
||||
(argc == 3) ? "failed to mount bootenv %s at %s\n" :
|
||||
"failed to mount bootenv %s at temporary path %s\n",
|
||||
bootenv, mountpoint);
|
||||
}
|
||||
|
||||
return (err);
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
bectl_cmd_rename(int argc, char *argv[])
|
||||
{
|
||||
char *dest, *src;
|
||||
int err;
|
||||
|
||||
if (argc < 3) {
|
||||
fprintf(stderr, "bectl rename: missing argument\n");
|
||||
return (usage(false));
|
||||
}
|
||||
|
||||
if (argc > 3) {
|
||||
fprintf(stderr, "bectl rename: too many arguments\n");
|
||||
return (usage(false));
|
||||
}
|
||||
|
||||
src = argv[1];
|
||||
dest = argv[2];
|
||||
|
||||
err = be_rename(be, src, dest);
|
||||
|
||||
switch (err) {
|
||||
case BE_ERR_SUCCESS:
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "failed to rename bootenv %s to %s\n",
|
||||
src, dest);
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
bectl_cmd_unmount(int argc, char *argv[])
|
||||
{
|
||||
char *bootenv, *cmd;
|
||||
int err, flags, opt;
|
||||
|
||||
/* Store alias used */
|
||||
cmd = argv[0];
|
||||
|
||||
flags = 0;
|
||||
while ((opt = getopt(argc, argv, "f")) != -1) {
|
||||
switch (opt) {
|
||||
case 'f':
|
||||
flags |= BE_MNT_FORCE;
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "bectl %s: unknown option '-%c'\n",
|
||||
cmd, optopt);
|
||||
return (usage(false));
|
||||
}
|
||||
}
|
||||
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
|
||||
if (argc != 1) {
|
||||
fprintf(stderr, "bectl %s: wrong number of arguments\n", cmd);
|
||||
return (usage(false));
|
||||
}
|
||||
|
||||
bootenv = argv[0];
|
||||
|
||||
err = be_unmount(be, bootenv, flags);
|
||||
|
||||
switch (err) {
|
||||
case BE_ERR_SUCCESS:
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "failed to unmount bootenv %s\n", bootenv);
|
||||
}
|
||||
|
||||
return (err);
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
const char *command;
|
||||
int command_index, rc;
|
||||
|
||||
if (argc < 2) {
|
||||
fprintf(stderr, "missing command\n");
|
||||
return (usage(false));
|
||||
}
|
||||
|
||||
command = argv[1];
|
||||
|
||||
/* Handle command aliases */
|
||||
if (strcmp(command, "umount") == 0)
|
||||
command = "unmount";
|
||||
|
||||
if (strcmp(command, "ujail") == 0)
|
||||
command = "unjail";
|
||||
|
||||
if ((strcmp(command, "-?") == 0) || (strcmp(command, "-h") == 0))
|
||||
return (usage(true));
|
||||
|
||||
if (get_cmd_index(command, &command_index)) {
|
||||
fprintf(stderr, "unknown command: %s\n", command);
|
||||
return (usage(false));
|
||||
}
|
||||
|
||||
|
||||
if ((be = libbe_init()) == NULL)
|
||||
return (-1);
|
||||
|
||||
libbe_print_on_error(be, true);
|
||||
|
||||
/* XXX TODO: can be simplified if offset by 2 instead of one */
|
||||
rc = command_map[command_index].fn(argc-1, argv+1);
|
||||
|
||||
libbe_close(be);
|
||||
return (rc);
|
||||
}
|
37
sbin/bectl/bectl.h
Normal file
37
sbin/bectl/bectl.h
Normal 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;
|
368
sbin/bectl/bectl_jail.c
Normal file
368
sbin/bectl/bectl_jail.c
Normal 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 ``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 <err.h>
|
||||
#include <jail.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <be.h>
|
||||
|
||||
#include "bectl.h"
|
||||
|
||||
static void jailparam_grow(void);
|
||||
static void jailparam_add(const char *name, const char *val);
|
||||
static void jailparam_del(const char *name);
|
||||
static bool jailparam_addarg(char *arg);
|
||||
static bool jailparam_delarg(char *arg);
|
||||
|
||||
static int bectl_search_jail_paths(const char *mnt);
|
||||
static int bectl_locate_jail(const char *ident);
|
||||
|
||||
/* We'll start with 8 parameters initially and grow as needed. */
|
||||
#define INIT_PARAMCOUNT 8
|
||||
|
||||
static struct jailparam *jp;
|
||||
static int jpcnt;
|
||||
static int jpused;
|
||||
static char mnt_loc[BE_MAXPATHLEN + 1];
|
||||
|
||||
static void
|
||||
jailparam_grow(void)
|
||||
{
|
||||
|
||||
jpcnt *= 2;
|
||||
jp = realloc(jp, jpcnt * sizeof(*jp));
|
||||
if (jp == NULL)
|
||||
err(2, "realloc");
|
||||
}
|
||||
|
||||
static void
|
||||
jailparam_add(const char *name, const char *val)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < jpused; ++i) {
|
||||
if (strcmp(name, jp[i].jp_name) == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
if (i < jpused)
|
||||
jailparam_free(&jp[i], 1);
|
||||
else if (jpused == jpcnt)
|
||||
/* The next slot isn't allocated yet */
|
||||
jailparam_grow();
|
||||
|
||||
if (jailparam_init(&jp[i], name) != 0)
|
||||
return;
|
||||
if (jailparam_import(&jp[i], val) != 0)
|
||||
return;
|
||||
++jpused;
|
||||
}
|
||||
|
||||
static void
|
||||
jailparam_del(const char *name)
|
||||
{
|
||||
int i;
|
||||
char *val;
|
||||
|
||||
for (i = 0; i < jpused; ++i) {
|
||||
if (strcmp(name, jp[i].jp_name) == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
/* Not found... technically successful */
|
||||
if (i == jpused)
|
||||
return;
|
||||
|
||||
for (; i < jpused - 1; ++i) {
|
||||
val = jailparam_export(&jp[i + 1]);
|
||||
|
||||
jailparam_free(&jp[i], 1);
|
||||
jailparam_init(&jp[i], jp[i + 1].jp_name);
|
||||
jailparam_import(&jp[i], val);
|
||||
free(val);
|
||||
}
|
||||
|
||||
jailparam_free(&jp[i], 1);
|
||||
--jpused;
|
||||
}
|
||||
|
||||
static bool
|
||||
jailparam_addarg(char *arg)
|
||||
{
|
||||
char *name, *val;
|
||||
|
||||
if (arg == NULL)
|
||||
return (false);
|
||||
name = arg;
|
||||
if ((val = strchr(arg, '=')) == NULL) {
|
||||
fprintf(stderr, "bectl jail: malformed jail option '%s'\n",
|
||||
arg);
|
||||
return (false);
|
||||
}
|
||||
|
||||
*val++ = '\0';
|
||||
if (strcmp(name, "path") == 0) {
|
||||
if (strlen(val) > BE_MAXPATHLEN) {
|
||||
fprintf(stderr,
|
||||
"bectl jail: skipping too long path assignment '%s' (max length = %d)\n",
|
||||
val, BE_MAXPATHLEN);
|
||||
return (false);
|
||||
}
|
||||
strcpy(mnt_loc, val);
|
||||
}
|
||||
jailparam_add(name, val);
|
||||
return (true);
|
||||
}
|
||||
|
||||
static bool
|
||||
jailparam_delarg(char *arg)
|
||||
{
|
||||
char *name, *val;
|
||||
|
||||
if (arg == NULL)
|
||||
return (false);
|
||||
name = arg;
|
||||
if ((val = strchr(name, '=')) != NULL)
|
||||
*val++ = '\0';
|
||||
|
||||
if (strcmp(name, "path") == 0)
|
||||
*mnt_loc = '\0';
|
||||
jailparam_del(name);
|
||||
return (true);
|
||||
}
|
||||
|
||||
int
|
||||
bectl_cmd_jail(int argc, char *argv[])
|
||||
{
|
||||
char *bootenv, *mountpoint;
|
||||
int jid, opt;
|
||||
bool default_hostname, default_name;
|
||||
|
||||
default_hostname = default_name = true;
|
||||
jpcnt = INIT_PARAMCOUNT;
|
||||
jp = malloc(jpcnt * sizeof(*jp));
|
||||
if (jp == NULL)
|
||||
err(2, "malloc");
|
||||
|
||||
jailparam_add("persist", "true");
|
||||
jailparam_add("allow.mount", "true");
|
||||
jailparam_add("allow.mount.devfs", "true");
|
||||
jailparam_add("enforce_statfs", "1");
|
||||
|
||||
while ((opt = getopt(argc, argv, "o:u:")) != -1) {
|
||||
switch (opt) {
|
||||
case 'o':
|
||||
if (jailparam_addarg(optarg)) {
|
||||
/*
|
||||
* optarg has been modified to null terminate
|
||||
* at the assignment operator.
|
||||
*/
|
||||
if (strcmp(optarg, "name") == 0)
|
||||
default_name = false;
|
||||
if (strcmp(optarg, "host.hostname") == 0)
|
||||
default_hostname = false;
|
||||
}
|
||||
break;
|
||||
case 'u':
|
||||
if (jailparam_delarg(optarg)) {
|
||||
if (strcmp(optarg, "name") == 0)
|
||||
default_name = true;
|
||||
if (strcmp(optarg, "host.hostname") == 0)
|
||||
default_hostname = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "bectl jail: unknown option '-%c'\n",
|
||||
optopt);
|
||||
return (usage(false));
|
||||
}
|
||||
}
|
||||
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
|
||||
/* struct jail be_jail = { 0 }; */
|
||||
if (argc < 1) {
|
||||
fprintf(stderr, "bectl jail: missing boot environment name\n");
|
||||
return (usage(false));
|
||||
}
|
||||
if (argc > 2) {
|
||||
fprintf(stderr, "bectl jail: too many arguments\n");
|
||||
return (usage(false));
|
||||
}
|
||||
|
||||
bootenv = argv[0];
|
||||
|
||||
/*
|
||||
* XXX TODO: if its already mounted, perhaps there should be a flag to
|
||||
* indicate its okay to proceed??
|
||||
*/
|
||||
if (*mnt_loc == '\0')
|
||||
mountpoint = NULL;
|
||||
else
|
||||
mountpoint = mnt_loc;
|
||||
if (be_mount(be, bootenv, mountpoint, 0, mnt_loc) != BE_ERR_SUCCESS) {
|
||||
fprintf(stderr, "could not mount bootenv\n");
|
||||
return (1);
|
||||
}
|
||||
|
||||
if (default_name)
|
||||
jailparam_add("name", bootenv);
|
||||
if (default_hostname)
|
||||
jailparam_add("host.hostname", bootenv);
|
||||
/*
|
||||
* This is our indicator that path was not set by the user, so we'll use
|
||||
* the path that libbe generated for us.
|
||||
*/
|
||||
if (mountpoint == NULL)
|
||||
jailparam_add("path", mnt_loc);
|
||||
jid = jailparam_set(jp, jpused, JAIL_CREATE | JAIL_ATTACH);
|
||||
if (jid == -1) {
|
||||
fprintf(stderr, "unable to create jail. error: %d\n", errno);
|
||||
return (1);
|
||||
}
|
||||
|
||||
jailparam_free(jp, jpused);
|
||||
free(jp);
|
||||
|
||||
/* We're attached within the jail... good bye! */
|
||||
chdir("/");
|
||||
execl("/bin/sh", "/bin/sh", NULL);
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
bectl_search_jail_paths(const char *mnt)
|
||||
{
|
||||
char jailpath[MAXPATHLEN + 1];
|
||||
int jid;
|
||||
|
||||
jid = 0;
|
||||
(void)mnt;
|
||||
while ((jid = jail_getv(0, "lastjid", &jid, "path", &jailpath,
|
||||
NULL)) != -1) {
|
||||
if (strcmp(jailpath, mnt) == 0)
|
||||
return (jid);
|
||||
}
|
||||
|
||||
return (-1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Locate a jail based on an arbitrary identifier. This may be either a name,
|
||||
* a jid, or a BE name. Returns the jid or -1 on failure.
|
||||
*/
|
||||
static int
|
||||
bectl_locate_jail(const char *ident)
|
||||
{
|
||||
nvlist_t *belist, *props;
|
||||
char *mnt;
|
||||
int jid;
|
||||
|
||||
/* Try the easy-match first */
|
||||
jid = jail_getid(ident);
|
||||
if (jid != -1)
|
||||
return (jid);
|
||||
|
||||
/* Attempt to try it as a BE name, first */
|
||||
if (be_prop_list_alloc(&belist) != 0)
|
||||
return (-1);
|
||||
|
||||
if (be_get_bootenv_props(be, belist) != 0)
|
||||
return (-1);
|
||||
|
||||
if (nvlist_lookup_nvlist(belist, ident, &props) == 0) {
|
||||
/* We'll attempt to resolve the jid by way of mountpoint */
|
||||
if (nvlist_lookup_string(props, "mountpoint", &mnt) == 0) {
|
||||
jid = bectl_search_jail_paths(mnt);
|
||||
be_prop_list_free(belist);
|
||||
return (jid);
|
||||
}
|
||||
|
||||
be_prop_list_free(belist);
|
||||
}
|
||||
|
||||
return (-1);
|
||||
}
|
||||
|
||||
int
|
||||
bectl_cmd_unjail(int argc, char *argv[])
|
||||
{
|
||||
char path[MAXPATHLEN + 1];
|
||||
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 + 1);
|
||||
name = jail_getname(jid);
|
||||
if (jail_getv(0, "name", name, "path", path, NULL) != jid) {
|
||||
free(name);
|
||||
fprintf(stderr,
|
||||
"bectl %s: failed to get path for jail requested by '%s'\n",
|
||||
cmd, target);
|
||||
return (1);
|
||||
}
|
||||
|
||||
free(name);
|
||||
|
||||
if (be_mounted_at(be, path, NULL) != 0) {
|
||||
fprintf(stderr, "bectl %s: jail requested by '%s' not a BE\n",
|
||||
cmd, target);
|
||||
return (1);
|
||||
}
|
||||
|
||||
jail_remove(jid);
|
||||
unmount(path, 0);
|
||||
|
||||
return (0);
|
||||
}
|
419
sbin/bectl/bectl_list.c
Normal file
419
sbin/bectl/bectl_list.c
Normal 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 (chosen_be_header != HEADER_BE)
|
||||
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);
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ LIBASN1?= ${LIBDESTDIR}${LIBDIR_BASE}/libasn1.a
|
||||
LIBATM?= ${LIBDESTDIR}${LIBDIR_BASE}/libatm.a
|
||||
LIBAUDITD?= ${LIBDESTDIR}${LIBDIR_BASE}/libauditd.a
|
||||
LIBAVL?= ${LIBDESTDIR}${LIBDIR_BASE}/libavl.a
|
||||
LIBBE?= ${LIBDESTDIR}${LIBDIR_BASE}/libbe.a
|
||||
LIBBEGEMOT?= ${LIBDESTDIR}${LIBDIR_BASE}/libbegemot.a
|
||||
LIBBLACKLIST?= ${LIBDESTDIR}${LIBDIR_BASE}/libblacklist.a
|
||||
LIBBLUETOOTH?= ${LIBDESTDIR}${LIBDIR_BASE}/libbluetooth.a
|
||||
|
@ -62,6 +62,7 @@ _LIBRARIES= \
|
||||
asn1 \
|
||||
auditd \
|
||||
avl \
|
||||
be \
|
||||
begemot \
|
||||
bluetooth \
|
||||
bsdxml \
|
||||
@ -335,6 +336,7 @@ _DP_zfs= md pthread umem util uutil m nvpair avl bsdxml geom nvpair z \
|
||||
zfs_core
|
||||
_DP_zfs_core= nvpair
|
||||
_DP_zpool= md pthread z nvpair avl umem
|
||||
_DP_be= zfs nvpair
|
||||
|
||||
# OFED support
|
||||
.if ${MK_OFED} != "no"
|
||||
@ -472,6 +474,8 @@ LIBBSNMPTOOLS?= ${LIBBSNMPTOOLSDIR}/libbsnmptools.a
|
||||
LIBAMUDIR= ${OBJTOP}/usr.sbin/amd/libamu
|
||||
LIBAMU?= ${LIBAMUDIR}/libamu.a
|
||||
|
||||
LIBBE?= ${LIBBEDIR}/libbe.a
|
||||
|
||||
LIBPMCSTATDIR= ${OBJTOP}/lib/libpmcstat
|
||||
LIBPMCSTAT?= ${LIBPMCSTATDIR}/libpmcstat.a
|
||||
|
||||
@ -482,6 +486,7 @@ LIBC_NOSSP_PIC?= ${LIBC_NOSSP_PICDIR}/libc_nossp_pic.a
|
||||
# not using a --sysroot or for meta mode bootstrapping when there is no
|
||||
# Makefile.depend. These are sorted by directory.
|
||||
LIBAVLDIR= ${OBJTOP}/cddl/lib/libavl
|
||||
LIBBEDIR= ${OBJTOP}/cddl/lib/libbe
|
||||
LIBCTFDIR= ${OBJTOP}/cddl/lib/libctf
|
||||
LIBDTRACEDIR= ${OBJTOP}/cddl/lib/libdtrace
|
||||
LIBNVPAIRDIR= ${OBJTOP}/cddl/lib/libnvpair
|
||||
|
Loading…
x
Reference in New Issue
Block a user