diff --git a/sbin/bectl/bectl.c b/sbin/bectl/bectl.c index 50229b4b96b7..d3de58ea2982 100644 --- a/sbin/bectl/bectl.c +++ b/sbin/bectl/bectl.c @@ -1,597 +1,604 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2017 Kyle J. Kneitinger * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bectl.h" static int bectl_cmd_activate(int argc, char *argv[]); static int bectl_cmd_check(int argc, char *argv[]); static int bectl_cmd_create(int argc, char *argv[]); static int bectl_cmd_destroy(int argc, char *argv[]); static int bectl_cmd_export(int argc, char *argv[]); static int bectl_cmd_import(int argc, char *argv[]); #if SOON static int bectl_cmd_add(int argc, char *argv[]); #endif static int bectl_cmd_mount(int argc, char *argv[]); static int bectl_cmd_rename(int argc, char *argv[]); static int bectl_cmd_unmount(int argc, char *argv[]); libbe_handle_t *be; int usage(bool explicit) { FILE *fp; fp = explicit ? stdout : stderr; fprintf(fp, "%s", "Usage:\tbectl {-h | -? | subcommand [args...]}\n" #if SOON "\tbectl add (path)*\n" #endif "\tbectl activate [-t] beName\n" "\tbectl activate [-T]\n" "\tbectl check\n" "\tbectl create [-r] [-e {nonActiveBe | beName@snapshot}] beName\n" "\tbectl create [-r] beName@snapshot\n" "\tbectl destroy [-Fo] {beName | beName@snapshot}\n" "\tbectl export sourceBe\n" "\tbectl import targetBe\n" "\tbectl jail [-bU] [{-o key=value | -u key}]... beName\n" "\t [utility [argument ...]]\n" "\tbectl list [-aDHs] [{-c property | -C property}]\n" "\tbectl mount beName [mountpoint]\n" "\tbectl rename origBeName newBeName\n" "\tbectl {ujail | unjail} {jailID | jailName | beName}\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[]); /* True if libbe_print_on_error should be disabled */ bool silent; }; static struct command_map_entry command_map[] = { { "activate", bectl_cmd_activate,false }, { "create", bectl_cmd_create, false }, { "destroy", bectl_cmd_destroy, false }, { "export", bectl_cmd_export, false }, { "import", bectl_cmd_import, false }, #if SOON { "add", bectl_cmd_add, false }, #endif { "jail", bectl_cmd_jail, false }, { "list", bectl_cmd_list, false }, { "mount", bectl_cmd_mount, false }, { "rename", bectl_cmd_rename, false }, { "unjail", bectl_cmd_unjail, false }, { "unmount", bectl_cmd_unmount, false }, { "check", bectl_cmd_check, true }, }; static struct command_map_entry * get_cmd_info(const char *cmd) { size_t i; for (i = 0; i < nitems(command_map); ++i) { if (strcmp(cmd, command_map[i].command) == 0) return (&command_map[i]); } return (NULL); } static int bectl_cmd_activate(int argc, char *argv[]) { int err, opt; bool temp, reset; temp = false; reset = false; while ((opt = getopt(argc, argv, "tT")) != -1) { switch (opt) { case 't': if (reset) return (usage(false)); temp = true; break; case 'T': if (temp) return (usage(false)); reset = true; break; default: fprintf(stderr, "bectl activate: unknown option '-%c'\n", optopt); return (usage(false)); } } argc -= optind; argv += optind; if (argc != 1 && (!reset || argc != 0)) { fprintf(stderr, "bectl activate: wrong number of arguments\n"); return (usage(false)); } if (reset) { if ((err = be_deactivate(be, NULL, reset)) == 0) printf("Temporary activation removed\n"); else printf("Failed to remove temporary activation\n"); return (err); } /* 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 snapshot[BE_MAXPATHLEN]; char *atpos, *bootenv, *snapname; int err, opt; bool recursive; snapname = NULL; recursive = false; while ((opt = getopt(argc, argv, "e:r")) != -1) { switch (opt) { case 'e': snapname = optarg; break; case 'r': recursive = true; break; default: fprintf(stderr, "bectl create: unknown option '-%c'\n", optopt); return (usage(false)); } } argc -= optind; argv += optind; if (argc != 1) { fprintf(stderr, "bectl create: wrong number of arguments\n"); return (usage(false)); } bootenv = *argv; err = BE_ERR_SUCCESS; - if ((atpos = strchr(bootenv, '@')) != NULL) { + if (strchr(bootenv, ' ') != NULL) + /* BE datasets with spaces are not bootable */ + err = BE_ERR_INVALIDNAME; + else if ((atpos = strchr(bootenv, '@')) != NULL) { /* * This is the "create a snapshot variant". No new boot * environment is to be created here. */ *atpos++ = '\0'; err = be_snapshot(be, bootenv, atpos, recursive, NULL); } else { if (snapname == NULL) /* Create from currently booted BE */ err = be_snapshot(be, be_active_path(be), NULL, recursive, snapshot); else if (strchr(snapname, '@') != NULL) /* Create from given snapshot */ strlcpy(snapshot, snapname, sizeof(snapshot)); else /* Create from given BE */ err = be_snapshot(be, snapname, NULL, recursive, snapshot); if (err == BE_ERR_SUCCESS) err = be_create_depth(be, bootenv, snapshot, recursive == true ? -1 : 0); } switch (err) { case BE_ERR_SUCCESS: break; + case BE_ERR_INVALIDNAME: + fprintf(stderr, + "bectl create: boot environment name must not contain spaces\n"); + break; default: if (atpos != NULL) fprintf(stderr, "Failed to create a snapshot '%s' of '%s'\n", atpos, bootenv); else if (snapname == NULL) fprintf(stderr, "Failed to create bootenv %s\n", bootenv); else fprintf(stderr, "Failed to create bootenv %s from snapshot %s\n", bootenv, snapname); } return (err); } static int bectl_cmd_export(int argc, char *argv[]) { char *bootenv; if (argc == 1) { fprintf(stderr, "bectl export: missing boot environment name\n"); return (usage(false)); } if (argc > 2) { fprintf(stderr, "bectl export: extra arguments provided\n"); return (usage(false)); } bootenv = argv[1]; if (isatty(STDOUT_FILENO)) { fprintf(stderr, "bectl export: must redirect output\n"); return (EX_USAGE); } be_export(be, bootenv, STDOUT_FILENO); return (0); } static int bectl_cmd_import(int argc, char *argv[]) { char *bootenv; int err; if (argc == 1) { fprintf(stderr, "bectl import: missing boot environment name\n"); return (usage(false)); } if (argc > 2) { fprintf(stderr, "bectl import: extra arguments provided\n"); return (usage(false)); } bootenv = argv[1]; if (isatty(STDIN_FILENO)) { fprintf(stderr, "bectl import: input can not be from terminal\n"); return (EX_USAGE); } err = be_import(be, bootenv, STDIN_FILENO); return (err); } #if SOON static int bectl_cmd_add(int argc, char *argv[]) { if (argc < 2) { fprintf(stderr, "bectl add: must provide at least one path\n"); return (usage(false)); } for (int i = 1; i < argc; ++i) { printf("arg %d: %s\n", i, argv[i]); /* XXX TODO catch err */ be_add_child(be, argv[i], true); } return (0); } #endif static int bectl_cmd_destroy(int argc, char *argv[]) { nvlist_t *props; char *origin, *target, targetds[BE_MAXPATHLEN]; int err, flags, opt; flags = 0; while ((opt = getopt(argc, argv, "Fo")) != -1) { switch (opt) { case 'F': flags |= BE_DESTROY_FORCE; break; case 'o': flags |= BE_DESTROY_ORIGIN; break; default: fprintf(stderr, "bectl destroy: unknown option '-%c'\n", optopt); return (usage(false)); } } argc -= optind; argv += optind; if (argc != 1) { fprintf(stderr, "bectl destroy: wrong number of arguments\n"); return (usage(false)); } target = argv[0]; /* We'll emit a notice if there's an origin to be cleaned up */ if ((flags & BE_DESTROY_ORIGIN) == 0 && strchr(target, '@') == NULL) { flags |= BE_DESTROY_AUTOORIGIN; if (be_root_concat(be, target, targetds) != 0) goto destroy; if (be_prop_list_alloc(&props) != 0) goto destroy; if (be_get_dataset_props(be, targetds, props) != 0) { be_prop_list_free(props); goto destroy; } if (nvlist_lookup_string(props, "origin", &origin) == 0 && !be_is_auto_snapshot_name(be, origin)) fprintf(stderr, "bectl destroy: leaving origin '%s' intact\n", origin); be_prop_list_free(props); } destroy: err = be_destroy(be, target, flags); return (err); } static int bectl_cmd_mount(int argc, char *argv[]) { char result_loc[BE_MAXPATHLEN]; char *bootenv, *mountpoint; int err, mntflags; /* XXX TODO: Allow shallow */ mntflags = BE_MNT_DEEP; if (argc < 2) { fprintf(stderr, "bectl mount: missing argument(s)\n"); return (usage(false)); } if (argc > 3) { fprintf(stderr, "bectl mount: too many arguments\n"); return (usage(false)); } bootenv = argv[1]; mountpoint = ((argc == 3) ? argv[2] : NULL); err = be_mount(be, bootenv, mountpoint, mntflags, result_loc); switch (err) { case BE_ERR_SUCCESS: printf("Successfully mounted %s at %s\n", bootenv, result_loc); break; default: fprintf(stderr, (argc == 3) ? "Failed to mount bootenv %s at %s\n" : "Failed to mount bootenv %s at temporary path %s\n", bootenv, mountpoint); } return (err); } static int bectl_cmd_rename(int argc, char *argv[]) { char *dest, *src; int err; if (argc < 3) { fprintf(stderr, "bectl rename: missing argument\n"); return (usage(false)); } if (argc > 3) { fprintf(stderr, "bectl rename: too many arguments\n"); return (usage(false)); } src = argv[1]; dest = argv[2]; err = be_rename(be, src, dest); switch (err) { case BE_ERR_SUCCESS: break; default: fprintf(stderr, "Failed to rename bootenv %s to %s\n", src, dest); } return (0); } static int bectl_cmd_unmount(int argc, char *argv[]) { char *bootenv, *cmd; int err, flags, opt; /* Store alias used */ cmd = argv[0]; flags = 0; while ((opt = getopt(argc, argv, "f")) != -1) { switch (opt) { case 'f': flags |= BE_MNT_FORCE; break; default: fprintf(stderr, "bectl %s: unknown option '-%c'\n", cmd, optopt); return (usage(false)); } } argc -= optind; argv += optind; if (argc != 1) { fprintf(stderr, "bectl %s: wrong number of arguments\n", cmd); return (usage(false)); } bootenv = argv[0]; err = be_unmount(be, bootenv, flags); switch (err) { case BE_ERR_SUCCESS: break; default: fprintf(stderr, "Failed to unmount bootenv %s\n", bootenv); } return (err); } static int bectl_cmd_check(int argc, char *argv[] __unused) { /* The command is left as argv[0] */ if (argc != 1) { fprintf(stderr, "bectl check: wrong number of arguments\n"); return (usage(false)); } return (0); } int main(int argc, char *argv[]) { struct command_map_entry *cmd; const char *command; char *root; int rc; cmd = NULL; root = NULL; if (argc < 2) return (usage(false)); if (strcmp(argv[1], "-r") == 0) { if (argc < 4) return (usage(false)); root = strdup(argv[2]); command = argv[3]; argc -= 3; argv += 3; } else { command = argv[1]; argc -= 1; argv += 1; } /* Handle command aliases */ if (strcmp(command, "umount") == 0) command = "unmount"; if (strcmp(command, "ujail") == 0) command = "unjail"; if ((strcmp(command, "-?") == 0) || (strcmp(command, "-h") == 0)) return (usage(true)); if ((cmd = get_cmd_info(command)) == NULL) { fprintf(stderr, "Unknown command: %s\n", command); return (usage(false)); } if ((be = libbe_init(root)) == NULL) { if (!cmd->silent) fprintf(stderr, "libbe_init(\"%s\") failed.\n", root != NULL ? root : ""); return (-1); } libbe_print_on_error(be, !cmd->silent); rc = cmd->fn(argc, argv); libbe_close(be); return (rc); } diff --git a/sbin/bectl/tests/bectl_test.sh b/sbin/bectl/tests/bectl_test.sh index 221fe21e29ed..b101591c3ee9 100755 --- a/sbin/bectl/tests/bectl_test.sh +++ b/sbin/bectl/tests/bectl_test.sh @@ -1,533 +1,537 @@ # # SPDX-License-Identifier: BSD-2-Clause-FreeBSD # # Copyright (c) 2018 Kyle Evans # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # # $FreeBSD$ ZPOOL_NAME_FILE=zpool_name get_zpool_name() { cat $ZPOOL_NAME_FILE } make_zpool_name() { mktemp -u bectl_test_XXXXXX > $ZPOOL_NAME_FILE get_zpool_name } # Establishes a bectl_create zpool that can be used for some light testing; contains # a 'default' BE and not much else. bectl_create_setup() { zpool=$1 disk=$2 mnt=$3 # Sanity check to make sure `make_zpool_name` succeeded atf_check test -n "$zpool" kldload -n -q zfs || atf_skip "ZFS module not loaded on the current system" if ! getconf MIN_HOLE_SIZE "$(pwd)"; then echo "getconf MIN_HOLE_SIZE $(pwd) failed; sparse files " \ "probably not supported by file system" mount atf_skip "Test's work directory does not support sparse files;" \ "try with a different TMPDIR?" fi atf_check mkdir -p ${mnt} atf_check truncate -s 1G ${disk} atf_check zpool create -R ${mnt} ${zpool} ${disk} atf_check zfs create -o mountpoint=none ${zpool}/ROOT atf_check zfs create -o mountpoint=/ -o canmount=noauto \ ${zpool}/ROOT/default } bectl_create_deep_setup() { zpool=$1 disk=$2 mnt=$3 # Sanity check to make sure `make_zpool_name` succeeded atf_check test -n "$zpool" bectl_create_setup ${zpool} ${disk} ${mnt} atf_check mkdir -p ${root} atf_check -o ignore bectl -r ${zpool}/ROOT mount default ${root} atf_check mkdir -p ${root}/usr atf_check zfs create -o mountpoint=/usr -o canmount=noauto \ ${zpool}/ROOT/default/usr atf_check -o ignore bectl -r ${zpool}/ROOT umount default } bectl_cleanup() { zpool=$1 if [ -z "$zpool" ]; then echo "Skipping cleanup; zpool not set up" elif zpool get health ${zpool} >/dev/null 2>&1; then zpool destroy -f ${zpool} fi } atf_test_case bectl_create cleanup bectl_create_head() { atf_set "descr" "Check the various forms of bectl create" atf_set "require.user" root } bectl_create_body() { if [ "$(atf_config_get ci false)" = "true" ] && \ [ "$(uname -p)" = "i386" ]; then atf_skip "https://bugs.freebsd.org/249055" fi if [ "$(atf_config_get ci false)" = "true" ] && \ [ "$(uname -p)" = "armv7" ]; then atf_skip "https://bugs.freebsd.org/249229" fi cwd=$(realpath .) zpool=$(make_zpool_name) disk=${cwd}/disk.img mount=${cwd}/mnt bectl_create_setup ${zpool} ${disk} ${mount} # Create a child dataset that will be used to test creation # of recursive and non-recursive boot environments. atf_check zfs create -o mountpoint=/usr -o canmount=noauto \ ${zpool}/ROOT/default/usr + # BE datasets with spaces are not bootable, PR 254441. + atf_check -e not-empty -s not-exit:0 \ + bectl -r ${zpool}/ROOT create "foo bar" + # Test standard creation, creation of a snapshot, and creation from a # snapshot. atf_check bectl -r ${zpool}/ROOT create -e default default2 atf_check bectl -r ${zpool}/ROOT create default2@test_snap atf_check bectl -r ${zpool}/ROOT create -e default2@test_snap default3 # Test standard creation, creation of a snapshot, and creation from a # snapshot for recursive boot environments. atf_check bectl -r ${zpool}/ROOT create -r -e default recursive atf_check bectl -r ${zpool}/ROOT create -r recursive@test_snap atf_check bectl -r ${zpool}/ROOT create -r -e recursive@test_snap recursive-snap # Test that non-recursive boot environments have no child datasets. atf_check -e not-empty -s not-exit:0 \ zfs list "${zpool}/ROOT/default2/usr" atf_check -e not-empty -s not-exit:0 \ zfs list "${zpool}/ROOT/default3/usr" # Test that recursive boot environments have child datasets. atf_check -o not-empty \ zfs list "${zpool}/ROOT/recursive/usr" atf_check -o not-empty \ zfs list "${zpool}/ROOT/recursive-snap/usr" } bectl_create_cleanup() { bectl_cleanup $(get_zpool_name) } atf_test_case bectl_destroy cleanup bectl_destroy_head() { atf_set "descr" "Check bectl destroy" atf_set "require.user" root } bectl_destroy_body() { if [ "$(atf_config_get ci false)" = "true" ] && \ [ "$(uname -p)" = "i386" ]; then atf_skip "https://bugs.freebsd.org/249055" fi if [ "$(atf_config_get ci false)" = "true" ] && \ [ "$(uname -p)" = "armv7" ]; then atf_skip "https://bugs.freebsd.org/249229" fi cwd=$(realpath .) zpool=$(make_zpool_name) disk=${cwd}/disk.img mount=${cwd}/mnt root=${mount}/root bectl_create_setup ${zpool} ${disk} ${mount} atf_check bectl -r ${zpool}/ROOT create -e default default2 atf_check -o not-empty zfs get mountpoint ${zpool}/ROOT/default2 atf_check -e ignore bectl -r ${zpool}/ROOT destroy default2 atf_check -e not-empty -s not-exit:0 zfs get mountpoint ${zpool}/ROOT/default2 # Test origin snapshot deletion when the snapshot to be destroyed # belongs to a mounted dataset, see PR 236043. atf_check mkdir -p ${root} atf_check -o not-empty bectl -r ${zpool}/ROOT mount default ${root} atf_check bectl -r ${zpool}/ROOT create -e default default3 atf_check bectl -r ${zpool}/ROOT destroy -o default3 atf_check bectl -r ${zpool}/ROOT unmount default # create two be from the same parent and destroy the parent atf_check bectl -r ${zpool}/ROOT create -e default default2 atf_check bectl -r ${zpool}/ROOT create -e default default3 atf_check bectl -r ${zpool}/ROOT destroy default atf_check bectl -r ${zpool}/ROOT destroy default2 atf_check bectl -r ${zpool}/ROOT rename default3 default # Create a BE, have it be the parent for another and repeat, then start # deleting environments. Arbitrarily chose default3 as the first. # Sleeps are required to prevent conflicting snapshots- libbe will # use the time with a serial at the end as needed to prevent collisions, # but as BEs get promoted the snapshot names will convert and conflict # anyways. libbe should perhaps consider adding something extra to the # default name to prevent collisions like this, but the default name # includes down to the second and creating BEs this rapidly is perhaps # uncommon enough. atf_check bectl -r ${zpool}/ROOT create -e default default2 sleep 1 atf_check bectl -r ${zpool}/ROOT create -e default2 default3 sleep 1 atf_check bectl -r ${zpool}/ROOT create -e default3 default4 atf_check bectl -r ${zpool}/ROOT destroy default3 atf_check bectl -r ${zpool}/ROOT destroy default2 atf_check bectl -r ${zpool}/ROOT destroy default4 # Create two BEs, then create an unrelated snapshot on the originating # BE and destroy it. We shouldn't have promoted the second BE, and it's # only possible to tell if we promoted it by making sure we didn't # demote the first BE at some point -- if we did, it's origin will no # longer be empty. atf_check bectl -r ${zpool}/ROOT create -e default default2 atf_check bectl -r ${zpool}/ROOT create default@test atf_check bectl -r ${zpool}/ROOT destroy default@test atf_check -o inline:"-\n" zfs get -Ho value origin ${zpool}/ROOT/default atf_check bectl -r ${zpool}/ROOT destroy default2 # As observed by beadm, if we explicitly try to destroy a snapshot that # leads to clones, we shouldn't have allowed it. atf_check bectl -r ${zpool}/ROOT create default@test atf_check bectl -r ${zpool}/ROOT create -e default@test default2 atf_check -e not-empty -s not-exit:0 bectl -r ${zpool}/ROOT destroy \ default@test } bectl_destroy_cleanup() { bectl_cleanup $(get_zpool_name) } atf_test_case bectl_export_import cleanup bectl_export_import_head() { atf_set "descr" "Check bectl export and import" atf_set "require.user" root } bectl_export_import_body() { if [ "$(atf_config_get ci false)" = "true" ] && \ [ "$(uname -p)" = "i386" ]; then atf_skip "https://bugs.freebsd.org/249055" fi if [ "$(atf_config_get ci false)" = "true" ] && \ [ "$(uname -p)" = "armv7" ]; then atf_skip "https://bugs.freebsd.org/249229" fi cwd=$(realpath .) zpool=$(make_zpool_name) disk=${cwd}/disk.img mount=${cwd}/mnt bectl_create_setup ${zpool} ${disk} ${mount} atf_check -o save:exported bectl -r ${zpool}/ROOT export default atf_check -x "bectl -r ${zpool}/ROOT import default2 < exported" atf_check -o not-empty zfs get mountpoint ${zpool}/ROOT/default2 atf_check -e ignore bectl -r ${zpool}/ROOT destroy default2 atf_check -e not-empty -s not-exit:0 zfs get mountpoint \ ${zpool}/ROOT/default2 } bectl_export_import_cleanup() { bectl_cleanup $(get_zpool_name) } atf_test_case bectl_list cleanup bectl_list_head() { atf_set "descr" "Check bectl list" atf_set "require.user" root } bectl_list_body() { if [ "$(atf_config_get ci false)" = "true" ] && \ [ "$(uname -p)" = "i386" ]; then atf_skip "https://bugs.freebsd.org/249055" fi if [ "$(atf_config_get ci false)" = "true" ] && \ [ "$(uname -p)" = "armv7" ]; then atf_skip "https://bugs.freebsd.org/249229" fi cwd=$(realpath .) zpool=$(make_zpool_name) disk=${cwd}/disk.img mount=${cwd}/mnt bectl_create_setup ${zpool} ${disk} ${mount} # Test the list functionality, including that BEs come and go away # as they're created and destroyed. Creation and destruction tests # use the 'zfs' utility to verify that they're actually created, so # these are just light tests that 'list' is picking them up. atf_check -o save:list.out bectl -r ${zpool}/ROOT list atf_check -o not-empty grep 'default' list.out atf_check bectl -r ${zpool}/ROOT create -e default default2 atf_check -o save:list.out bectl -r ${zpool}/ROOT list atf_check -o not-empty grep 'default2' list.out atf_check -e ignore bectl -r ${zpool}/ROOT destroy default2 atf_check -o save:list.out bectl -r ${zpool}/ROOT list atf_check -s not-exit:0 grep 'default2' list.out # XXX TODO: Formatting checks } bectl_list_cleanup() { bectl_cleanup $(get_zpool_name) } atf_test_case bectl_mount cleanup bectl_mount_head() { atf_set "descr" "Check bectl mount/unmount" atf_set "require.user" root } bectl_mount_body() { if [ "$(atf_config_get ci false)" = "true" ] && \ [ "$(uname -p)" = "i386" ]; then atf_skip "https://bugs.freebsd.org/249055" fi if [ "$(atf_config_get ci false)" = "true" ] && \ [ "$(uname -p)" = "armv7" ]; then atf_skip "https://bugs.freebsd.org/249229" fi cwd=$(realpath .) zpool=$(make_zpool_name) disk=${cwd}/disk.img mount=${cwd}/mnt root=${mount}/root bectl_create_deep_setup ${zpool} ${disk} ${mount} atf_check mkdir -p ${root} # Test unmount first... atf_check -o not-empty bectl -r ${zpool}/ROOT mount default ${root} atf_check -o not-empty -x "mount | grep '^${zpool}/ROOT/default'" atf_check bectl -r ${zpool}/ROOT unmount default atf_check -s not-exit:0 -x "mount | grep '^${zpool}/ROOT/default'" # Then umount! atf_check -o not-empty bectl -r ${zpool}/ROOT mount default ${root} atf_check -o not-empty -x "mount | grep '^${zpool}/ROOT/default'" atf_check bectl -r ${zpool}/ROOT umount default atf_check -s not-exit:0 -x "mount | grep '^${zpool}/ROOT/default'" } bectl_mount_cleanup() { bectl_cleanup $(get_zpool_name) } atf_test_case bectl_rename cleanup bectl_rename_head() { atf_set "descr" "Check bectl rename" atf_set "require.user" root } bectl_rename_body() { if [ "$(atf_config_get ci false)" = "true" ] && \ [ "$(uname -p)" = "i386" ]; then atf_skip "https://bugs.freebsd.org/249055" fi if [ "$(atf_config_get ci false)" = "true" ] && \ [ "$(uname -p)" = "armv7" ]; then atf_skip "https://bugs.freebsd.org/249229" fi cwd=$(realpath .) zpool=$(make_zpool_name) disk=${cwd}/disk.img mount=${cwd}/mnt bectl_create_setup ${zpool} ${disk} ${mount} atf_check bectl -r ${zpool}/ROOT rename default default2 atf_check -o not-empty zfs get mountpoint ${zpool}/ROOT/default2 atf_check -e not-empty -s not-exit:0 zfs get mountpoint \ ${zpool}/ROOT/default } bectl_rename_cleanup() { bectl_cleanup $(get_zpool_name) } atf_test_case bectl_jail cleanup bectl_jail_head() { atf_set "descr" "Check bectl rename" atf_set "require.user" root atf_set "require.progs" jail } bectl_jail_body() { if [ "$(atf_config_get ci false)" = "true" ] && \ [ "$(uname -p)" = "i386" ]; then atf_skip "https://bugs.freebsd.org/249055" fi if [ "$(atf_config_get ci false)" = "true" ] && \ [ "$(uname -p)" = "armv7" ]; then atf_skip "https://bugs.freebsd.org/249229" fi cwd=$(realpath .) zpool=$(make_zpool_name) disk=${cwd}/disk.img mount=${cwd}/mnt root=${mount}/root if [ ! -f /rescue/rescue ]; then atf_skip "This test requires a rescue binary" fi bectl_create_deep_setup ${zpool} ${disk} ${mount} # Prepare our minimal BE... plop a rescue binary into it atf_check mkdir -p ${root} atf_check -o ignore bectl -r ${zpool}/ROOT mount default ${root} atf_check mkdir -p ${root}/rescue atf_check cp /rescue/rescue ${root}/rescue/rescue atf_check bectl -r ${zpool}/ROOT umount default # Prepare some more boot environments atf_check -o empty -s exit:0 bectl -r ${zpool}/ROOT create -e default target atf_check -o empty -s exit:0 bectl -r ${zpool}/ROOT create -e default 1234 # Attempt to unjail a BE with numeric name; jail_getid at one point # did not validate that the input was a valid jid before returning the # jid. atf_check -o empty -s exit:0 bectl -r ${zpool}/ROOT jail -b 1234 atf_check -o empty -s exit:0 bectl -r ${zpool}/ROOT unjail 1234 # When a jail name is not explicit, it should match the jail id. atf_check -o empty -s exit:0 bectl -r ${zpool}/ROOT jail -b -o jid=233637 default atf_check -o inline:"233637\n" -s exit:0 -x "jls -j 233637 name" atf_check -o empty -s exit:0 bectl -r ${zpool}/ROOT unjail default # Basic command-mode tests, with and without jail cleanup atf_check -o inline:"rescue\nusr\n" bectl -r ${zpool}/ROOT \ jail default /rescue/rescue ls -1 atf_check -o inline:"rescue\nusr\n" bectl -r ${zpool}/ROOT \ jail -Uo path=${root} default /rescue/rescue ls -1 atf_check [ -f ${root}/rescue/rescue ] atf_check bectl -r ${zpool}/ROOT ujail default # Batch mode tests atf_check bectl -r ${zpool}/ROOT jail -bo path=${root} default atf_check -o not-empty -x "jls | grep -F \"${root}\"" atf_check bectl -r ${zpool}/ROOT ujail default atf_check -s not-exit:0 -x "jls | grep -F \"${root}\"" # 'unjail' naming atf_check bectl -r ${zpool}/ROOT jail -b default atf_check bectl -r ${zpool}/ROOT unjail default atf_check -s not-exit:0 -x "jls | grep -F \"${root}\"" # 'unjail' by BE name. Force bectl to lookup jail id by the BE name. atf_check -o empty -s exit:0 bectl -r ${zpool}/ROOT jail -b default atf_check -o empty -s exit:0 bectl -r ${zpool}/ROOT jail -b -o name=bectl_test target atf_check -o empty -s exit:0 bectl -r ${zpool}/ROOT unjail target atf_check -o empty -s exit:0 bectl -r ${zpool}/ROOT unjail default # cannot unjail an unjailed BE (by either command name) atf_check -e ignore -s not-exit:0 bectl -r ${zpool}/ROOT ujail default atf_check -e ignore -s not-exit:0 bectl -r ${zpool}/ROOT unjail default # set+unset atf_check bectl -r ${zpool}/ROOT jail -b -o path=${root} -u path default # Ensure that it didn't mount at ${root} atf_check -s not-exit:0 -x "mount | grep -F '${root}'" atf_check bectl -r ${zpool}/ROOT ujail default } # If a test has failed, it's possible that the boot environment hasn't # been 'unjail'ed. We want to remove the jail before 'bectl_cleanup' # attempts to destroy the zpool. bectl_jail_cleanup() { if [ "$(atf_config_get ci false)" = "true" ] && \ [ "$(uname -p)" = "i386" ]; then atf_skip "https://bugs.freebsd.org/249055" fi if [ "$(atf_config_get ci false)" = "true" ] && \ [ "$(uname -p)" = "armv7" ]; then atf_skip "https://bugs.freebsd.org/249229" fi zpool=$(get_zpool_name) for bootenv in "default" "target" "1234"; do # mountpoint of the boot environment mountpoint="$(bectl -r ${zpool}/ROOT list -H | grep ${bootenv} | awk '{print $3}')" # see if any jail paths match the boot environment mountpoint jailid="$(jls | grep ${mountpoint} | awk '{print $1}')" if [ -z "$jailid" ]; then continue; fi jail -r ${jailid} done; bectl_cleanup ${zpool} } atf_init_test_cases() { atf_add_test_case bectl_create atf_add_test_case bectl_destroy atf_add_test_case bectl_export_import atf_add_test_case bectl_list atf_add_test_case bectl_mount atf_add_test_case bectl_rename atf_add_test_case bectl_jail }