1
0
mirror of https://git.FreeBSD.org/src.git synced 2026-06-02 11:24:32 +00:00

bectl: Add -E flag to create an empty boot environment

Signed-off-by: Pat Maddox <pat@patmaddox.com>
Reviewed by:	kevans
MFC after:	1 week
Closes:		https://github.com/freebsd/freebsd-src/pull/1975
This commit is contained in:
Pat Maddox
2026-02-04 21:54:09 -06:00
committed by Kyle Evans
parent 4f7336a93c
commit 2e020c84cb
9 changed files with 111 additions and 23 deletions
+50 -14
View File
@@ -891,6 +891,54 @@ be_create_from_existing(libbe_handle_t *lbh, const char *bename, const char *old
return (set_error(lbh, err)); return (set_error(lbh, err));
} }
/*
* Create a zfs dataset and map the return to libbe error.
*/
static int
be_zfs_create(libzfs_handle_t *lzh, const char *buf, zfs_type_t t,
nvlist_t *props)
{
int err;
if ((err = zfs_create(lzh, buf, t, props)) != 0) {
switch (err) {
case EZFS_EXISTS:
return (BE_ERR_EXISTS);
case EZFS_NOENT:
return (BE_ERR_NOENT);
case EZFS_BADTYPE:
case EZFS_BADVERSION:
return (BE_ERR_NOPOOL);
case EZFS_BADPROP:
default:
/* We set something up wrong, probably... */
return (BE_ERR_UNKNOWN);
}
}
return (BE_ERR_SUCCESS);
}
/*
* Create an empty boot environment.
*/
int
be_create_empty(libbe_handle_t *lbh, const char *bename)
{
char buf[BE_MAXPATHLEN];
int err;
if ((err = be_validate_name(lbh, bename)) != 0)
return (set_error(lbh, err));
if ((err = be_root_concat(lbh, bename, buf)) != 0)
return (set_error(lbh, err));
err = be_zfs_create(lbh->lzh, buf, ZFS_TYPE_FILESYSTEM, NULL);
return (set_error(lbh, err));
}
/* /*
* Verifies that a snapshot has a valid name, exists, and has a mountpoint of * Verifies that a snapshot has a valid name, exists, and has a mountpoint of
@@ -1118,21 +1166,9 @@ be_create_child_noent(libbe_handle_t *lbh, const char *active,
nvlist_add_string(props, "mountpoint", child_path); nvlist_add_string(props, "mountpoint", child_path);
/* Create */ /* Create */
if ((err = zfs_create(lbh->lzh, active, ZFS_TYPE_DATASET, if ((err = be_zfs_create(lbh->lzh, active, ZFS_TYPE_DATASET,
props)) != 0) { props)) != 0) {
switch (err) { return (set_error(lbh, 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); nvlist_free(props);
+1
View File
@@ -65,6 +65,7 @@ bool be_is_auto_snapshot_name(libbe_handle_t *, const char *);
/* Bootenv creation functions */ /* Bootenv creation functions */
int be_create(libbe_handle_t *, const char *); int be_create(libbe_handle_t *, const char *);
int be_create_depth(libbe_handle_t *, const char *, const char *, int); int be_create_depth(libbe_handle_t *, const char *, const char *, int);
int be_create_empty(libbe_handle_t *, const char *);
int be_create_from_existing(libbe_handle_t *, const char *, 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_create_from_existing_snap(libbe_handle_t *, const char *, const char *);
int be_snapshot(libbe_handle_t *, const char *, const char *, bool, char *); int be_snapshot(libbe_handle_t *, const char *, const char *, bool, char *);
+8 -1
View File
@@ -25,7 +25,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE. .\" SUCH DAMAGE.
.\" .\"
.Dd December 11, 2025 .Dd February 4, 2026
.Dt LIBBE 3 .Dt LIBBE 3
.Os .Os
.Sh NAME .Sh NAME
@@ -69,6 +69,9 @@
.Fn be_create_depth "libbe_handle_t *hdl" "const char *be_name" "const char *snap" "int depth" .Fn be_create_depth "libbe_handle_t *hdl" "const char *be_name" "const char *snap" "int depth"
.Pp .Pp
.Ft int .Ft int
.Fn be_create_empty "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" .Fn be_create_from_existing "libbe_handle_t *hdl" "const char *be_name" "const char *be_origin"
.Pp .Pp
.Ft int .Ft int
@@ -282,6 +285,10 @@ A depth of '0' is no recursion and '-1' is unlimited (i.e., a recursive boot
environment). environment).
.Pp .Pp
The The
.Fn be_create_empty
function creates an empty boot environment with the given name.
.Pp
The
.Fn be_create_from_existing .Fn be_create_from_existing
function creates a boot environment with the given name from the name of an function creates a boot environment with the given name from the name of an
existing boot environment. existing boot environment.
+1 -1
View File
@@ -20,6 +20,6 @@ CFLAGS+= -I${ZFSTOP}/lib/libspl/include/os/freebsd
CFLAGS+= -I${SRCTOP}/sys CFLAGS+= -I${SRCTOP}/sys
CFLAGS+= -I${SRCTOP}/cddl/compat/opensolaris/include CFLAGS+= -I${SRCTOP}/cddl/compat/opensolaris/include
CFLAGS+= -include ${ZFSTOP}/include/os/freebsd/spl/sys/ccompile.h CFLAGS+= -include ${ZFSTOP}/include/os/freebsd/spl/sys/ccompile.h
CFLAGS+= -DHAVE_ISSETUGID CFLAGS+= -DHAVE_ISSETUGID -DHAVE_STRLCAT -DHAVE_STRLCPY
.include <bsd.test.mk> .include <bsd.test.mk>
+9
View File
@@ -174,6 +174,15 @@ libbe_create_body()
# the child dataset should exist # the child dataset should exist
atf_check -o not-empty \ atf_check -o not-empty \
zfs list "${zpool}/ROOT/relative-snap/usr" zfs list "${zpool}/ROOT/relative-snap/usr"
# test empty BE creation.
atf_check $prog "${zpool}/ROOT" \
empty \
ignored \
0
# the dataset should exist
atf_check -o not-empty \
zfs list "${zpool}/ROOT/empty"
} }
libbe_create_cleanup() libbe_create_cleanup()
+7 -4
View File
@@ -27,6 +27,7 @@
#include <sys/cdefs.h> #include <sys/cdefs.h>
#include <be.h> #include <be.h>
#include <string.h>
/* /*
* argv[1] = root boot environment (e.g. zroot/ROOT), * argv[1] = root boot environment (e.g. zroot/ROOT),
@@ -36,15 +37,17 @@
*/ */
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
libbe_handle_t *lbh; libbe_handle_t *lbh;
if (argc != 5) if (argc != 5)
return -1; return (-1);
if ((lbh = libbe_init(argv[1])) == NULL) if ((lbh = libbe_init(argv[1])) == NULL)
return -1; return (-1);
libbe_print_on_error(lbh, true); libbe_print_on_error(lbh, true);
if (strcmp(argv[2], "empty") == 0)
return (be_create_empty(lbh, argv[2]));
return (be_create_depth(lbh, argv[2], argv[3], atoi(argv[4]))); return (be_create_depth(lbh, argv[2], argv[3], atoi(argv[4])));
} }
+13 -1
View File
@@ -3,7 +3,7 @@
.\" .\"
.\" SPDX-License-Identifier: BSD-2-Clause .\" SPDX-License-Identifier: BSD-2-Clause
.\" .\"
.Dd June 13, 2025 .Dd February 4, 2026
.Dt BECTL 8 .Dt BECTL 8
.Os .Os
.Sh NAME .Sh NAME
@@ -33,6 +33,11 @@
.Ar beName@snapshot .Ar beName@snapshot
.Nm .Nm
.Op Fl r Ar beroot .Op Fl r Ar beroot
.Cm create
.Fl E
.Ar newBeName
.Nm
.Op Fl r Ar beroot
.Cm destroy .Cm destroy
.Op Fl \&Fo .Op Fl \&Fo
.Ar beName Ns Op Cm @ Ns Ar snapshot .Ar beName Ns Op Cm @ Ns Ar snapshot
@@ -181,6 +186,13 @@ for a discussion on different layouts.
.Pp .Pp
No new boot environment is created with this subcommand. No new boot environment is created with this subcommand.
.It Xo .It Xo
.Cm create
.Fl E
.Ar newBeName
.Xc
Create a new empty boot environment named
.Ar newBeName .
.It Xo
.Cm destroy .Cm destroy
.Op Fl \&Fo .Op Fl \&Fo
.Ar beName Ns Op Cm @ Ns Ar snapshot .Ar beName Ns Op Cm @ Ns Ar snapshot
+14 -2
View File
@@ -53,6 +53,7 @@ usage(bool explicit)
"\tbectl [-r beroot] check\n" "\tbectl [-r beroot] check\n"
"\tbectl [-r beroot] create [-r] [-e {nonActiveBe | beName@snapshot}] beName\n" "\tbectl [-r beroot] create [-r] [-e {nonActiveBe | beName@snapshot}] beName\n"
"\tbectl [-r beroot] create [-r] beName@snapshot\n" "\tbectl [-r beroot] create [-r] beName@snapshot\n"
"\tbectl [-r beroot] create -E beName\n"
"\tbectl [-r beroot] destroy [-Fo] {beName | beName@snapshot}\n" "\tbectl [-r beroot] destroy [-Fo] {beName | beName@snapshot}\n"
"\tbectl [-r beroot] export sourceBe\n" "\tbectl [-r beroot] export sourceBe\n"
"\tbectl [-r beroot] import targetBe\n" "\tbectl [-r beroot] import targetBe\n"
@@ -184,15 +185,19 @@ bectl_cmd_create(int argc, char *argv[])
char snapshot[BE_MAXPATHLEN]; char snapshot[BE_MAXPATHLEN];
char *atpos, *bootenv, *snapname; char *atpos, *bootenv, *snapname;
int err, opt; int err, opt;
bool recursive; bool empty, recursive;
snapname = NULL; snapname = NULL;
empty = false;
recursive = false; recursive = false;
while ((opt = getopt(argc, argv, "e:r")) != -1) { while ((opt = getopt(argc, argv, "e:Er")) != -1) {
switch (opt) { switch (opt) {
case 'e': case 'e':
snapname = optarg; snapname = optarg;
break; break;
case 'E':
empty = true;
break;
case 'r': case 'r':
recursive = true; recursive = true;
break; break;
@@ -221,6 +226,13 @@ bectl_cmd_create(int argc, char *argv[])
*/ */
*atpos++ = '\0'; *atpos++ = '\0';
err = be_snapshot(be, bootenv, atpos, recursive, NULL); err = be_snapshot(be, bootenv, atpos, recursive, NULL);
} else if (empty) {
if (snapname || recursive) {
fprintf(stderr,
"bectl create: -E cannot be combined with -e or -r\n");
return (usage(false));
}
err = be_create_empty(be, bootenv);
} else { } else {
if (snapname == NULL) if (snapname == NULL)
/* Create from currently booted BE */ /* Create from currently booted BE */
+8
View File
@@ -126,6 +126,14 @@ bectl_create_body()
zfs list "${zpool}/ROOT/recursive/usr" zfs list "${zpool}/ROOT/recursive/usr"
atf_check -o not-empty \ atf_check -o not-empty \
zfs list "${zpool}/ROOT/recursive-snap/usr" zfs list "${zpool}/ROOT/recursive-snap/usr"
# Test creation of an empty BE.
atf_check bectl -r ${zpool}/ROOT create -E empty
atf_check -o inline:"-\n" zfs get -H -o value origin ${zpool}/ROOT/empty
atf_check -e match:"cannot be combined" -s not-exit:0 \
bectl -r ${zpool}/ROOT create -E -e not-allowed empty-existing
atf_check -e match:"cannot be combined" -s not-exit:0 \
bectl -r ${zpool}/ROOT create -E -r empty-recursive
} }
bectl_create_cleanup() bectl_create_cleanup()
{ {