root/usr/src/cmd/beadm/beadm.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
 * Copyright 2013 Nexenta Systems, Inc. All rights reserved.
 * Copyright 2015 Toomas Soome <tsoome@me.com>
 * Copyright 2015 Gary Mills
 * Copyright (c) 2015 by Delphix. All rights reserved.
 * Copyright 2017 Jason King
 * Copyright 2021 OmniOS Community Edition (OmniOSce) Association.
 * Copyright (c) 2018, Joyent, Inc.
 * Copyright 2023 Bill Sommerfeld <sommerfeld@alum.mit.edu>
 */

/*
 * System includes
 */

#include <assert.h>
#include <stdio.h>
#include <strings.h>
#include <libzfs.h>
#include <locale.h>
#include <langinfo.h>
#include <stdlib.h>
#include <wchar.h>
#include <sys/types.h>
#include <sys/debug.h>
#include <libcmdutils.h>

#include "libbe.h"

#ifndef lint
#define _(x) gettext(x)
#else
#define _(x) (x)
#endif

#ifndef TEXT_DOMAIN
#define TEXT_DOMAIN "SYS_TEST"
#endif

#define DT_BUF_LEN (128)
#define NUM_COLS (6)
CTASSERT(DT_BUF_LEN >= NN_NUMBUF_SZ);

static int be_do_activate(int argc, char **argv);
static int be_do_create(int argc, char **argv);
static int be_do_destroy(int argc, char **argv);
static int be_do_list(int argc, char **argv);
static int be_do_mount(int argc, char **argv);
static int be_do_unmount(int argc, char **argv);
static int be_do_rename(int argc, char **argv);
static int be_do_rollback(int argc, char **argv);
static void usage(void);

/*
 * single column name/width output format description
 */
struct col_info {
        const char *col_name;
        size_t width;
};

/*
 * all columns output format
 */
struct hdr_info {
        struct col_info cols[NUM_COLS];
};

/*
 * type of possible output formats
 */
enum be_fmt {
        BE_FMT_DEFAULT,
        BE_FMT_DATASET,
        BE_FMT_SNAPSHOT,
        BE_FMT_ALL
};

/*
 * command handler description
 */
typedef struct be_command {
        const char      *name;
        int             (*func)(int argc, char **argv);
} be_command_t;

/*
 * sorted list of be commands
 */
static const be_command_t be_command_tbl[] = {
        { "activate",           be_do_activate },
        { "create",             be_do_create },
        { "destroy",            be_do_destroy },
        { "list",               be_do_list },
        { "mount",              be_do_mount },
        { "unmount",            be_do_unmount },
        { "umount",             be_do_unmount }, /* unmount alias */
        { "rename",             be_do_rename },
        { "rollback",           be_do_rollback },
        { NULL,                 NULL },
};

static void
usage(void)
{
        (void) fprintf(stderr, _("usage:\n"
            "\tbeadm subcommand cmd_options\n"
            "\n"
            "\tsubcommands:\n"
            "\n"
            "\tbeadm activate [-v] [-t | -T] beName\n"
            "\tbeadm create [-a | -t] [-d BE_desc]\n"
            "\t\t[-o property=value] ... [-p zpool] \n"
            "\t\t[-e nonActiveBe | beName@snapshot] [-v] beName\n"
            "\tbeadm create [-d BE_desc]\n"
            "\t\t[-o property=value] ... [-p zpool] [-v] beName@snapshot\n"
            "\tbeadm destroy [-Ffsv] beName \n"
            "\tbeadm destroy [-Fv] beName@snapshot \n"
            "\tbeadm list [-a | -ds] [-H]\n"
            "\t\t[-k|-K date | name | space] [-v] [beName]\n"
            "\tbeadm mount [-s ro|rw] [-v] beName [mountpoint]\n"
            "\tbeadm unmount [-fv] beName | mountpoint\n"
            "\tbeadm umount [-fv] beName | mountpoint\n"
            "\tbeadm rename [-v] origBeName newBeName\n"
            "\tbeadm rollback [-v] beName snapshot\n"
            "\tbeadm rollback [-v] beName@snapshot\n"));
}

static int
run_be_cmd(const char *cmdname, int argc, char **argv)
{
        const be_command_t *command;

        for (command = &be_command_tbl[0]; command->name != NULL; command++)
                if (strcmp(command->name, cmdname) == 0)
                        return (command->func(argc, argv));

        (void) fprintf(stderr, _("Invalid command: %s\n"), cmdname);
        usage();
        return (1);
}

int
main(int argc, char **argv)
{
        const char *cmdname;

        (void) setlocale(LC_ALL, "");
        (void) textdomain(TEXT_DOMAIN);

        if (argc < 2)
                cmdname = "list";
        else
                cmdname = argv[1];

        /* Turn error printing off */
        libbe_print_errors(B_FALSE);

        return (run_be_cmd(cmdname, --argc, ++argv));
}

static void
print_hdr(struct hdr_info *hdr_info)
{
        boolean_t first = B_TRUE;
        size_t i;
        for (i = 0; i < NUM_COLS; i++) {
                struct col_info *col_info = &hdr_info->cols[i];
                const char *name = col_info->col_name;
                size_t width = col_info->width;
                if (name == NULL)
                        continue;

                if (first) {
                        (void) printf("%-*s", width, name);
                        first = B_FALSE;
                } else
                        (void) printf(" %-*s", width, name);
        }
        (void) putchar('\n');
}

static void
init_hdr_cols(enum be_fmt be_fmt, struct hdr_info *hdr)
{
        struct col_info *col = hdr->cols;
        size_t i;

        col[1].col_name = _("Active");
        col[2].col_name = _("Mountpoint");
        col[3].col_name = _("Space");
        col[4].col_name = _("Policy");
        col[5].col_name = _("Created");

        switch (be_fmt) {
        case BE_FMT_ALL:
                col[0].col_name = _("BE/Dataset/Snapshot");
                break;
        case BE_FMT_DATASET:
                col[0].col_name = _("BE/Dataset");
                break;
        case BE_FMT_SNAPSHOT:
                col[0].col_name = _("BE/Snapshot");
                col[1].col_name = NULL;
                col[2].col_name = NULL;
                break;
        case BE_FMT_DEFAULT:
        default:
                col[0].col_name = _("BE");
        }

        for (i = 0; i < NUM_COLS; i++) {
                const char *name = col[i].col_name;
                col[i].width = 0;

                if (name != NULL) {
                        wchar_t wname[128];
                        size_t sz = mbstowcs(wname, name, sizeof (wname) /
                            sizeof (wchar_t));
                        if (sz > 0) {
                                int wcsw = wcswidth(wname, sz);
                                if (wcsw > 0)
                                        col[i].width = wcsw;
                                else
                                        col[i].width = sz;
                        } else {
                                col[i].width = strlen(name);
                        }
                }
        }
}

static void
count_widths(enum be_fmt be_fmt, struct hdr_info *hdr, be_node_list_t *be_nodes)
{
        size_t len[NUM_COLS];
        char buf[DT_BUF_LEN];
        int i;
        be_node_list_t *cur_be;

        for (i = 0; i < NUM_COLS; i++)
                len[i] = hdr->cols[i].width;

        for (cur_be = be_nodes; cur_be != NULL; cur_be = cur_be->be_next_node) {
                char name[ZFS_MAX_DATASET_NAME_LEN];
                const char *be_name = cur_be->be_node_name;
                const char *root_ds = cur_be->be_root_ds;
                char *pos;
                size_t node_name_len = strlen(cur_be->be_node_name);
                size_t root_ds_len = strlen(cur_be->be_root_ds);
                size_t mntpt_len = 0;
                size_t policy_len = 0;
                size_t used_len;
                uint64_t used = cur_be->be_space_used;
                be_snapshot_list_t *snap = NULL;

                if (cur_be->be_mntpt != NULL)
                        mntpt_len = strlen(cur_be->be_mntpt);
                if (cur_be->be_policy_type != NULL)
                        policy_len = strlen(cur_be->be_policy_type);

                (void) strlcpy(name, root_ds, sizeof (name));
                pos = strstr(name, be_name);

                if (be_fmt == BE_FMT_DEFAULT) {
                        if (node_name_len > len[0])
                                len[0] = node_name_len;
                } else {
                        if (root_ds_len + 3 > len[0])
                                len[0] = root_ds_len + 3;
                }

                if (mntpt_len > len[2])
                        len[2] = mntpt_len;
                if (policy_len > len[4])
                        len[4] = policy_len;

                for (snap = cur_be->be_node_snapshots; snap != NULL;
                    snap = snap->be_next_snapshot) {
                        uint64_t snap_used = snap->be_snapshot_space_used;
                        const char *snap_name = snap->be_snapshot_name;
                        (void) strcpy(pos, snap_name);

                        if (be_fmt == BE_FMT_DEFAULT)
                                used += snap_used;
                        else if (be_fmt & BE_FMT_SNAPSHOT) {
                                int snap_len = strlen(name) + 3;
                                if (be_fmt == BE_FMT_SNAPSHOT)
                                        snap_len -= pos - name;
                                if (snap_len > len[0])
                                        len[0] = snap_len;
                                nicenum(snap_used, buf, sizeof (buf));
                                used_len = strlen(buf);
                                if (used_len > len[3])
                                        len[3] = used_len;
                        }
                }

                if (be_fmt == BE_FMT_DEFAULT) {
                        int used_len;
                        nicenum(used, buf, sizeof (buf));
                        used_len = strlen(buf);
                        if (used_len > len[3])
                                len[3] = used_len;
                }

                nicenum(used, buf, sizeof (buf));
        }

        for (i = 0; i < NUM_COLS; i++)
                hdr->cols[i].width = len[i];
}

static void
print_be_nodes(const char *be_name, boolean_t parsable, struct hdr_info *hdr,
    be_node_list_t *nodes)
{
        char buf[64];
        char datetime[DT_BUF_LEN];
        be_node_list_t  *cur_be;

        for (cur_be = nodes; cur_be != NULL; cur_be = cur_be->be_next_node) {
                char active[4] = "-\0\0";
                int ai = 0;
                const char *datetime_fmt = "%F %R";
                const char *name = cur_be->be_node_name;
                const char *mntpt = cur_be->be_mntpt;
                const char *uuid_str = cur_be->be_uuid_str;
                be_snapshot_list_t *snap = NULL;
                uint64_t used = cur_be->be_space_used;
                time_t creation = cur_be->be_node_creation;
                struct tm *tm;

                if (be_name != NULL && strcmp(be_name, name) != 0)
                        continue;

                if (parsable)
                        active[0] = '\0';

                tm = localtime(&creation);
                (void) strftime(datetime, DT_BUF_LEN, datetime_fmt, tm);

                for (snap = cur_be->be_node_snapshots; snap != NULL;
                    snap = snap->be_next_snapshot)
                        used += snap->be_snapshot_space_used;

                if (!cur_be->be_global_active)
                        active[ai++] = 'x';

                if (cur_be->be_active)
                        active[ai++] = 'N';
                if (cur_be->be_active_on_boot) {
                        if (!cur_be->be_global_active)
                                active[ai++] = 'b';
                        else
                                active[ai++] = 'R';
                }
                if (cur_be->be_active_next) {
                        active[ai] = 'T';
                }

                nicenum(used, buf, sizeof (buf));
                if (parsable)
                        (void) printf("%s;%s;%s;%s;%llu;%s;%ld\n",
                            name,
                            (uuid_str != NULL ? uuid_str: ""),
                            active,
                            (cur_be->be_mounted ? mntpt: ""),
                            used,
                            cur_be->be_policy_type,
                            creation);
                else
                        (void) printf("%-*s %-*s %-*s %-*s %-*s %-*s\n",
                            hdr->cols[0].width, name,
                            hdr->cols[1].width, active,
                            hdr->cols[2].width, (cur_be->be_mounted ? mntpt:
                            "-"),
                            hdr->cols[3].width, buf,
                            hdr->cols[4].width, cur_be->be_policy_type,
                            hdr->cols[5].width, datetime);
        }
}

static void
print_be_snapshots(be_node_list_t *be, struct hdr_info *hdr, boolean_t parsable)
{
        char buf[64];
        char datetime[DT_BUF_LEN];
        be_snapshot_list_t *snap = NULL;

        for (snap = be->be_node_snapshots; snap != NULL;
            snap = snap->be_next_snapshot) {
                char name[ZFS_MAX_DATASET_NAME_LEN];
                const char *datetime_fmt = "%F %R";
                const char *be_name = be->be_node_name;
                const char *root_ds = be->be_root_ds;
                const char *snap_name = snap->be_snapshot_name;
                char *pos;
                uint64_t used = snap->be_snapshot_space_used;
                time_t creation = snap->be_snapshot_creation;
                struct tm *tm = localtime(&creation);

                (void) strncpy(name, root_ds, sizeof (name));
                pos = strstr(name, be_name);
                (void) strcpy(pos, snap_name);

                (void) strftime(datetime, DT_BUF_LEN, datetime_fmt, tm);
                nicenum(used, buf, sizeof (buf));

                if (parsable)
                        if (hdr->cols[1].width != 0)
                                (void) printf("%s;%s;%s;%s;%llu;%s;%ld\n",
                                    be_name,
                                    snap_name,
                                    "",
                                    "",
                                    used,
                                    be->be_policy_type,
                                    creation);
                        else
                                (void) printf("%s;%s;%llu;%s;%ld\n",
                                    be_name,
                                    snap_name,
                                    used,
                                    be->be_policy_type,
                                    creation);
                else
                        if (hdr->cols[1].width != 0)
                                (void) printf("   %-*s %-*s %-*s %-*s %-*s "
                                    "%-*s\n",
                                    hdr->cols[0].width-3, name,
                                    hdr->cols[1].width, "-",
                                    hdr->cols[2].width, "-",
                                    hdr->cols[3].width, buf,
                                    hdr->cols[4].width, be->be_policy_type,
                                    hdr->cols[5].width, datetime);
                        else
                                (void) printf("   %-*s %-*s %-*s %-*s\n",
                                    hdr->cols[0].width-3, snap_name,
                                    hdr->cols[3].width, buf,
                                    hdr->cols[4].width, be->be_policy_type,
                                    hdr->cols[5].width, datetime);
        }
}

static void
print_fmt_nodes(const char *be_name, enum be_fmt be_fmt, boolean_t parsable,
    struct hdr_info *hdr, be_node_list_t *nodes)
{
        char buf[64];
        char datetime[DT_BUF_LEN];
        be_node_list_t  *cur_be;

        for (cur_be = nodes; cur_be != NULL; cur_be = cur_be->be_next_node) {
                char active[4] = "-\0\0";
                int ai = 0;
                const char *datetime_fmt = "%F %R";
                const char *name = cur_be->be_node_name;
                const char *mntpt = cur_be->be_mntpt;
                uint64_t used = cur_be->be_space_used;
                time_t creation = cur_be->be_node_creation;
                struct tm *tm;

                if (be_name != NULL && strcmp(be_name, name) != 0)
                        continue;

                if (!parsable)
                        (void) printf("%-s\n", name);
                else
                        active[0] = '\0';

                tm = localtime(&creation);
                (void) strftime(datetime, DT_BUF_LEN, datetime_fmt, tm);

                if (cur_be->be_active)
                        active[ai++] = 'N';
                if (cur_be->be_active_on_boot)
                        active[ai++] = 'R';
                if (cur_be->be_active_next)
                        active[ai++] = 'T';

                nicenum(used, buf, sizeof (buf));
                if (be_fmt & BE_FMT_DATASET)
                        if (parsable)
                                (void) printf("%s;%s;%s;%s;%llu;%s;%ld\n",
                                    cur_be->be_node_name,
                                    cur_be->be_root_ds,
                                    active,
                                    (cur_be->be_mounted ? mntpt: ""),
                                    used,
                                    cur_be->be_policy_type,
                                    creation);
                        else
                                (void) printf("   %-*s %-*s %-*s %-*s %-*s "
                                    "%-*s\n",
                                    hdr->cols[0].width-3, cur_be->be_root_ds,
                                    hdr->cols[1].width, active,
                                    hdr->cols[2].width, (cur_be->be_mounted ?
                                    mntpt: "-"),
                                    hdr->cols[3].width, buf,
                                    hdr->cols[4].width, cur_be->be_policy_type,
                                    hdr->cols[5].width, datetime);

                if (be_fmt & BE_FMT_SNAPSHOT)
                        print_be_snapshots(cur_be, hdr, parsable);
        }
}

static void
print_nodes(const char *be_name, boolean_t dsets, boolean_t snaps,
    boolean_t parsable, be_node_list_t *be_nodes)
{
        struct hdr_info hdr;
        enum be_fmt be_fmt  = BE_FMT_DEFAULT;

        if (dsets)
                be_fmt |= BE_FMT_DATASET;
        if (snaps)
                be_fmt |= BE_FMT_SNAPSHOT;

        if (!parsable) {
                init_hdr_cols(be_fmt, &hdr);
                count_widths(be_fmt, &hdr, be_nodes);
                print_hdr(&hdr);
        }

        if (be_fmt == BE_FMT_DEFAULT)
                print_be_nodes(be_name, parsable, &hdr, be_nodes);
        else
                print_fmt_nodes(be_name, be_fmt, parsable, &hdr, be_nodes);
}

static boolean_t
confirm_destroy(const char *name)
{
        boolean_t res = B_FALSE;
        const char *yesre = nl_langinfo(YESEXPR);
        const char *nore = nl_langinfo(NOEXPR);
        regex_t yes_re;
        regex_t no_re;
        char buf[128];
        char *answer;
        int cflags = REG_EXTENDED;

        if (regcomp(&yes_re, yesre, cflags) != 0) {
                /* should not happen */
                (void) fprintf(stderr, _("Failed to compile 'yes' regexp\n"));
                return (res);
        }
        if (regcomp(&no_re, nore, cflags) != 0) {
                /* should not happen */
                (void) fprintf(stderr, _("Failed to compile 'no' regexp\n"));
                regfree(&yes_re);
                return (res);
        }

        (void) printf(_("Are you sure you want to destroy %s?\n"
            "This action cannot be undone (y/[n]): "), name);

        answer = fgets(buf, sizeof (buf), stdin);
        if (answer == NULL || *answer == '\0' || *answer == 10)
                goto out;

        if (regexec(&yes_re, answer, 0, NULL, 0) == 0) {
                res = B_TRUE;
        } else if (regexec(&no_re, answer, 0, NULL, 0) != 0) {
                (void) fprintf(stderr, _("Invalid response. "
                    "Please enter 'y' or 'n'.\n"));
        }

out:
        regfree(&yes_re);
        regfree(&no_re);
        return (res);
}

static int
be_nvl_alloc(nvlist_t **nvlp)
{
        assert(nvlp != NULL);

        if (nvlist_alloc(nvlp, NV_UNIQUE_NAME, 0) != 0) {
                (void) perror(_("nvlist_alloc failed.\n"));
                return (1);
        }

        return (0);
}

static int
be_nvl_add_boolean(nvlist_t *nvl, const char *name, boolean_t val)
{
        assert(nvl != NULL);

        if (nvlist_add_boolean_value(nvl, name, val) != 0) {
                (void) fprintf(stderr, _("nvlist_add_boolean_value failed for "
                    "%s (%s).\n"), name, val ? "true" : "false");
                return (1);
        }

        return (0);
}

static int
be_nvl_add_string(nvlist_t *nvl, const char *name, const char *val)
{
        assert(nvl != NULL);

        if (nvlist_add_string(nvl, name, val) != 0) {
                (void) fprintf(stderr, _("nvlist_add_string failed for "
                    "%s (%s).\n"), name, val);
                return (1);
        }

        return (0);
}

static int
be_nvl_add_nvlist(nvlist_t *nvl, const char *name, nvlist_t *val)
{
        assert(nvl != NULL);

        if (nvlist_add_nvlist(nvl, name, val) != 0) {
                (void) fprintf(stderr, _("nvlist_add_nvlist failed for %s.\n"),
                    name);
                return (1);
        }

        return (0);
}

static int
be_nvl_add_uint16(nvlist_t *nvl, const char *name, uint16_t val)
{
        assert(nvl != NULL);

        if (nvlist_add_uint16(nvl, name, val) != 0) {
                (void) fprintf(stderr, _("nvlist_add_uint16 failed for "
                    "%s (%hu).\n"), name, val);
                return (1);
        }

        return (0);
}

static int
be_do_activate(int argc, char **argv)
{
        nvlist_t        *be_attrs;
        int             err = 1;
        int             c;
        char            *obe_name;
        boolean_t       nextboot = B_FALSE;
        boolean_t       do_nextboot = B_FALSE;

        while ((c = getopt(argc, argv, "vtT")) != -1) {
                switch (c) {
                case 'v':
                        libbe_print_errors(B_TRUE);
                        break;
                case 't':
                        if (do_nextboot == B_TRUE) {
                                usage();
                                return (1);
                        }
                        nextboot = B_TRUE;
                        do_nextboot = B_TRUE;
                        break;
                case 'T':
                        if (do_nextboot == B_TRUE) {
                                usage();
                                return (1);
                        }
                        nextboot = B_FALSE;
                        do_nextboot = B_TRUE;
                        break;
                default:
                        usage();
                        return (1);
                }
        }

        argc -= optind;
        argv += optind;

        if (argc != 1) {
                usage();
                return (1);
        }

        obe_name = argv[0];

        if (be_nvl_alloc(&be_attrs) != 0)
                return (1);

        if (be_nvl_add_string(be_attrs, BE_ATTR_ORIG_BE_NAME, obe_name) != 0)
                goto out;

        if (do_nextboot == B_TRUE) {
                if (be_nvl_add_boolean(be_attrs, BE_ATTR_ACTIVE_NEXTBOOT,
                    nextboot) != 0)
                        goto out;
        }

        err = be_activate(be_attrs);

        switch (err) {
        case BE_SUCCESS:
                if (do_nextboot && nextboot == B_FALSE)
                        (void) printf(_("Temporary activation removed\n"));
                else
                        (void) printf(_("Activated successfully\n"));
                break;
        case BE_ERR_BE_NOENT:
                (void) fprintf(stderr, _("%s does not exist or appear "
                    "to be a valid BE.\nPlease check that the name of "
                    "the BE provided is correct.\n"), obe_name);
                break;
        case BE_ERR_PERM:
        case BE_ERR_ACCESS:
                (void) fprintf(stderr, _("Unable to activate %s.\n"), obe_name);
                (void) fprintf(stderr, _("You have insufficient privileges to "
                    "execute this command.\n"));
                break;
        case BE_ERR_ACTIVATE_CURR:
        default:
                (void) fprintf(stderr, _("Unable to activate %s.\n"), obe_name);
                (void) fprintf(stderr, "%s\n", be_err_to_str(err));
        }

out:
        nvlist_free(be_attrs);
        return (err);
}

static int
be_do_create(int argc, char **argv)
{
        nvlist_t        *be_attrs;
        nvlist_t        *zfs_props = NULL;
        boolean_t       activate = B_FALSE;
        boolean_t       t_activate = B_FALSE;
        boolean_t       is_snap = B_FALSE;
        int             c;
        int             err = 1;
        char            *obe_name = NULL;
        char            *snap_name = NULL;
        char            *nbe_zpool = NULL;
        char            *nbe_name = NULL;
        char            *nbe_desc = NULL;
        char            *propname = NULL;
        char            *propval = NULL;
        char            *strval = NULL;

        while ((c = getopt(argc, argv, "ad:e:io:p:tv")) != -1) {
                switch (c) {
                case 'a':
                        activate = B_TRUE;
                        break;
                case 'd':
                        nbe_desc = optarg;
                        break;
                case 'e':
                        obe_name = optarg;
                        break;
                case 'o':
                        if (zfs_props == NULL && be_nvl_alloc(&zfs_props) != 0)
                                return (1);

                        propname = optarg;
                        if ((propval = strchr(propname, '=')) == NULL) {
                                (void) fprintf(stderr, _("missing "
                                    "'=' for -o option\n"));
                                goto out2;
                        }
                        *propval = '\0';
                        propval++;
                        if (nvlist_lookup_string(zfs_props, propname,
                            &strval) == 0) {
                                (void) fprintf(stderr, _("property '%s' "
                                    "specified multiple times\n"), propname);
                                goto out2;

                        }
                        if (be_nvl_add_string(zfs_props, propname, propval)
                            != 0)
                                goto out2;

                        break;
                case 'p':
                        nbe_zpool = optarg;
                        break;
                case 't':
                        t_activate = B_TRUE;
                        break;
                case 'v':
                        libbe_print_errors(B_TRUE);
                        break;
                default:
                        usage();
                        goto out2;
                }
        }

        if (activate && t_activate) {
                (void) fprintf(stderr,
                    _("create: -a and -t are mutually exclusive\n"));
                usage();
                goto out2;
        }

        argc -= optind;
        argv += optind;

        if (argc != 1) {
                usage();
                goto out2;
        }

        nbe_name = argv[0];

        if ((snap_name = strrchr(nbe_name, '@')) != NULL) {
                if (snap_name[1] == '\0') {
                        usage();
                        goto out2;
                }

                snap_name[0] = '\0';
                snap_name++;
                is_snap = B_TRUE;
        }

        if (obe_name) {
                if (is_snap) {
                        usage();
                        goto out2;
                }

                /*
                 * Check if obe_name is really a snapshot name.
                 * If so, split it out.
                 */
                if ((snap_name = strrchr(obe_name, '@')) != NULL) {
                        if (snap_name[1] == '\0') {
                                usage();
                                goto out2;
                        }

                        snap_name[0] = '\0';
                        snap_name++;
                }
        } else if (is_snap) {
                obe_name = nbe_name;
                nbe_name = NULL;
        }

        if (be_nvl_alloc(&be_attrs) != 0)
                goto out2;


        if (zfs_props != NULL && be_nvl_add_nvlist(be_attrs,
            BE_ATTR_ORIG_BE_NAME, zfs_props) != 0)
                goto out;

        if (obe_name != NULL && be_nvl_add_string(be_attrs,
            BE_ATTR_ORIG_BE_NAME, obe_name) != 0)
                goto out;

        if (snap_name != NULL && be_nvl_add_string(be_attrs,
            BE_ATTR_SNAP_NAME, snap_name) != 0)
                goto out;

        if (nbe_zpool != NULL && be_nvl_add_string(be_attrs,
            BE_ATTR_NEW_BE_POOL, nbe_zpool) != 0)
                goto out;

        if (nbe_name != NULL && be_nvl_add_string(be_attrs,
            BE_ATTR_NEW_BE_NAME, nbe_name) != 0)
                goto out;

        if (nbe_desc != NULL && be_nvl_add_string(be_attrs,
            BE_ATTR_NEW_BE_DESC, nbe_desc) != 0)
                goto out;

        if (is_snap)
                err = be_create_snapshot(be_attrs);
        else
                err = be_copy(be_attrs);

        switch (err) {
        case BE_SUCCESS:
                if (!is_snap && !nbe_name) {
                        /*
                         * We requested an auto named BE; find out the
                         * name of the BE that was created for us and
                         * the auto snapshot created from the original BE.
                         */
                        if (nvlist_lookup_string(be_attrs, BE_ATTR_NEW_BE_NAME,
                            &nbe_name) != 0) {
                                (void) fprintf(stderr, _("failed to get %s "
                                    "attribute\n"), BE_ATTR_NEW_BE_NAME);
                                break;
                        } else
                                (void) printf(_("Auto named BE: %s\n"),
                                    nbe_name);

                        if (nvlist_lookup_string(be_attrs, BE_ATTR_SNAP_NAME,
                            &snap_name) != 0) {
                                (void) fprintf(stderr, _("failed to get %s "
                                    "attribute\n"), BE_ATTR_SNAP_NAME);
                                break;
                        } else
                                (void) printf(_("Auto named snapshot: %s\n"),
                                    snap_name);
                }

                if (!is_snap && activate) {
                        char *args[] = { "activate", "", NULL };
                        args[1] = nbe_name;
                        optind = 1;

                        err = be_do_activate(2, args);
                        goto out;
                }
                if (!is_snap && t_activate) {
                        char *args[] = { "activate", "-t", "", NULL };
                        args[2] = nbe_name;
                        optind = 1;

                        err = be_do_activate(3, args);
                        goto out;
                }

                (void) printf(_("Created successfully\n"));
                break;
        case BE_ERR_BE_EXISTS:
                (void) fprintf(stderr, _("BE %s already exists\n."
                    "Please choose a different BE name.\n"), nbe_name);
                break;
        case BE_ERR_SS_EXISTS:
                (void) fprintf(stderr, _("BE %s snapshot %s already exists.\n"
                    "Please choose a different snapshot name.\n"), obe_name,
                    snap_name);
                break;
        case BE_ERR_PERM:
        case BE_ERR_ACCESS:
                if (is_snap)
                        (void) fprintf(stderr, _("Unable to create snapshot "
                            "%s.\n"), snap_name);
                else
                        (void) fprintf(stderr, _("Unable to create %s.\n"),
                            nbe_name);
                (void) fprintf(stderr, _("You have insufficient privileges to "
                    "execute this command.\n"));
                break;
        default:
                if (is_snap)
                        (void) fprintf(stderr, _("Unable to create snapshot "
                            "%s.\n"), snap_name);
                else
                        (void) fprintf(stderr, _("Unable to create %s.\n"),
                            nbe_name);
                (void) fprintf(stderr, "%s\n", be_err_to_str(err));
        }

out:
        nvlist_free(be_attrs);
out2:
        nvlist_free(zfs_props);

        return (err);
}

static int
be_do_destroy(int argc, char **argv)
{
        nvlist_t        *be_attrs;
        boolean_t       is_snap = B_FALSE;
        boolean_t       suppress_prompt = B_FALSE;
        int             err = 1;
        int             c;
        int             destroy_flags = 0;
        char            *snap_name;
        char            *be_name;

        while ((c = getopt(argc, argv, "fFsv")) != -1) {
                switch (c) {
                case 'f':
                        destroy_flags |= BE_DESTROY_FLAG_FORCE_UNMOUNT;
                        break;
                case 's':
                        destroy_flags |= BE_DESTROY_FLAG_SNAPSHOTS;
                        break;
                case 'v':
                        libbe_print_errors(B_TRUE);
                        break;
                case 'F':
                        suppress_prompt = B_TRUE;
                        break;
                default:
                        usage();
                        return (1);
                }
        }

        argc -= optind;
        argv += optind;

        if (argc != 1) {
                usage();
                return (1);
        }

        be_name = argv[0];
        if (!suppress_prompt && !confirm_destroy(be_name)) {
                (void) printf(_("%s has not been destroyed.\n"), be_name);
                return (0);
        }

        if ((snap_name = strrchr(be_name, '@')) != NULL) {
                if (snap_name[1] == '\0') {
                        usage();
                        return (1);
                }

                is_snap = B_TRUE;
                *snap_name = '\0';
                snap_name++;
        }

        if (be_nvl_alloc(&be_attrs) != 0)
                return (1);


        if (be_nvl_add_string(be_attrs, BE_ATTR_ORIG_BE_NAME, be_name) != 0)
                goto out;

        if (is_snap) {
                if (be_nvl_add_string(be_attrs, BE_ATTR_SNAP_NAME,
                    snap_name) != 0)
                        goto out;

                err = be_destroy_snapshot(be_attrs);
        } else {
                if (be_nvl_add_uint16(be_attrs, BE_ATTR_DESTROY_FLAGS,
                    destroy_flags) != 0)
                        goto out;

                err = be_destroy(be_attrs);
        }

        switch (err) {
        case BE_SUCCESS:
                (void) printf(_("Destroyed successfully\n"));
                break;
        case BE_ERR_MOUNTED:
                (void) fprintf(stderr, _("Unable to destroy %s.\n"), be_name);
                (void) fprintf(stderr, _("It is currently mounted and must be "
                    "unmounted before it can be destroyed.\n" "Use 'beadm "
                    "unmount %s' to unmount the BE before destroying\nit or "
                    "'beadm destroy -f %s'.\n"), be_name, be_name);
                break;
        case BE_ERR_DESTROY_CURR_BE:
                (void) fprintf(stderr, _("%s is the currently active BE and "
                    "cannot be destroyed.\nYou must boot from another BE in "
                    "order to destroy %s.\n"), be_name, be_name);
                break;
        case BE_ERR_ZONES_UNMOUNT:
                (void) fprintf(stderr, _("Unable to destroy one of " "%s's "
                    "zone BE's.\nUse 'beadm destroy -f %s' or "
                    "'zfs -f destroy <dataset>'.\n"), be_name, be_name);
                break;
        case BE_ERR_SS_NOENT:
                (void) fprintf(stderr, _("%s does not exist or appear "
                    "to be a valid snapshot.\nPlease check that the name of "
                    "the snapshot provided is correct.\n"), snap_name);
                break;
        case BE_ERR_PERM:
        case BE_ERR_ACCESS:
                (void) fprintf(stderr, _("Unable to destroy %s.\n"), be_name);
                (void) fprintf(stderr, _("You have insufficient privileges to "
                    "execute this command.\n"));
                break;
        case BE_ERR_SS_EXISTS:
                (void) fprintf(stderr, _("Unable to destroy %s: "
                    "BE has snapshots.\nUse 'beadm destroy -s %s' or "
                    "'zfs destroy -r <dataset>'.\n"), be_name, be_name);
                break;
        default:
                (void) fprintf(stderr, _("Unable to destroy %s.\n"), be_name);
                (void) fprintf(stderr, "%s\n", be_err_to_str(err));
        }

out:
        nvlist_free(be_attrs);
        return (err);
}

static int
be_do_list(int argc, char **argv)
{
        be_node_list_t  *be_nodes = NULL;
        boolean_t       all = B_FALSE;
        boolean_t       dsets = B_FALSE;
        boolean_t       snaps = B_FALSE;
        boolean_t       parsable = B_FALSE;
        int             err = 1;
        int             c = 0;
        char            *be_name = NULL;
        be_sort_t       order = BE_SORT_UNSPECIFIED;

        while ((c = getopt(argc, argv, "adk:svHK:")) != -1) {
                switch (c) {
                case 'a':
                        all = B_TRUE;
                        break;
                case 'd':
                        dsets = B_TRUE;
                        break;
                case 'k':
                case 'K':
                        if (order != BE_SORT_UNSPECIFIED) {
                                (void) fprintf(stderr, _("Sort key can be "
                                    "specified only once.\n"));
                                usage();
                                return (1);
                        }
                        if (strcmp(optarg, "date") == 0) {
                                if (c == 'k')
                                        order = BE_SORT_DATE;
                                else
                                        order = BE_SORT_DATE_REV;
                                break;
                        }
                        if (strcmp(optarg, "name") == 0) {
                                if (c == 'k')
                                        order = BE_SORT_NAME;
                                else
                                        order = BE_SORT_NAME_REV;
                                break;
                        }
                        if (strcmp(optarg, "space") == 0) {
                                if (c == 'k')
                                        order = BE_SORT_SPACE;
                                else
                                        order = BE_SORT_SPACE_REV;
                                break;
                        }
                        (void) fprintf(stderr, _("Unknown sort key: %s\n"),
                            optarg);
                        usage();
                        return (1);
                case 's':
                        snaps = B_TRUE;
                        break;
                case 'v':
                        libbe_print_errors(B_TRUE);
                        break;
                case 'H':
                        parsable = B_TRUE;
                        break;
                default:
                        usage();
                        return (1);
                }
        }

        if (all) {
                if (dsets) {
                        (void) fprintf(stderr, _("Invalid options: -a and %s "
                            "are mutually exclusive.\n"), "-d");
                        usage();
                        return (1);
                }
                if (snaps) {
                        (void) fprintf(stderr, _("Invalid options: -a and %s "
                            "are mutually exclusive.\n"), "-s");
                        usage();
                        return (1);
                }

                dsets = B_TRUE;
                snaps = B_TRUE;
        }

        argc -= optind;
        argv += optind;


        if (argc == 1)
                be_name = argv[0];

        err = be_list(be_name, &be_nodes,
            snaps ? BE_LIST_SNAPSHOTS : BE_LIST_DEFAULT);

        switch (err) {
        case BE_SUCCESS:
                /* the default sort is ascending date, no need to sort twice */
                if (order == BE_SORT_UNSPECIFIED)
                        order = BE_SORT_DATE;

                if (order != BE_SORT_DATE) {
                        err = be_sort(&be_nodes, order);
                        if (err != BE_SUCCESS) {
                                (void) fprintf(stderr, _("Unable to sort Boot "
                                    "Environment\n"));
                                (void) fprintf(stderr, "%s\n",
                                    be_err_to_str(err));
                                break;
                        }
                }

                print_nodes(be_name, dsets, snaps, parsable, be_nodes);
                break;
        case BE_ERR_BE_NOENT:
                if (be_name == NULL)
                        (void) fprintf(stderr, _("No boot environments found "
                            "on this system.\n"));
                else {
                        (void) fprintf(stderr, _("%s does not exist or appear "
                            "to be a valid BE.\nPlease check that the name of "
                            "the BE provided is correct.\n"), be_name);
                }
                break;
        default:
                (void) fprintf(stderr, _("Unable to display Boot "
                    "Environment\n"));
                (void) fprintf(stderr, "%s\n", be_err_to_str(err));
        }

        if (be_nodes != NULL)
                be_free_list(be_nodes);
        return (err);
}

static int
be_do_mount(int argc, char **argv)
{
        nvlist_t        *be_attrs;
        boolean_t       shared_fs = B_FALSE;
        int             err = 1;
        int             c;
        int             mount_flags = 0;
        char            *obe_name;
        char            *mountpoint;
        char            *tmp_mp = NULL;

        while ((c = getopt(argc, argv, "s:v")) != -1) {
                switch (c) {
                case 's':
                        shared_fs = B_TRUE;

                        mount_flags |= BE_MOUNT_FLAG_SHARED_FS;

                        if (strcmp(optarg, "rw") == 0) {
                                mount_flags |= BE_MOUNT_FLAG_SHARED_RW;
                        } else if (strcmp(optarg, "ro") != 0) {
                                (void) fprintf(stderr, _("The -s flag "
                                    "requires an argument [ rw | ro ]\n"));
                                usage();
                                return (1);
                        }

                        break;
                case 'v':
                        libbe_print_errors(B_TRUE);
                        break;
                default:
                        usage();
                        return (1);
                }
        }

        argc -= optind;
        argv += optind;

        if (argc < 1 || argc > 2) {
                usage();
                return (1);
        }

        obe_name = argv[0];

        if (argc == 2) {
                mountpoint = argv[1];
                if (mountpoint[0] != '/') {
                        (void) fprintf(stderr, _("Invalid mount point %s. "
                            "Mount point must start with a /.\n"), mountpoint);
                        return (1);
                }
        } else {
                const char *tmpdir = getenv("TMPDIR");
                const char *tmpname = "tmp.XXXXXX";
                int sz;

                if (tmpdir == NULL)
                        tmpdir = "/tmp";

                sz = asprintf(&tmp_mp, "%s/%s", tmpdir, tmpname);
                if (sz < 0) {
                        (void) fprintf(stderr, _("internal error: "
                            "out of memory\n"));
                        return (1);
                }

                mountpoint = mkdtemp(tmp_mp);
        }

        if (be_nvl_alloc(&be_attrs) != 0)
                return (1);

        if (be_nvl_add_string(be_attrs, BE_ATTR_ORIG_BE_NAME, obe_name) != 0)
                goto out;

        if (be_nvl_add_string(be_attrs, BE_ATTR_MOUNTPOINT, mountpoint) != 0)
                goto out;

        if (shared_fs && be_nvl_add_uint16(be_attrs, BE_ATTR_MOUNT_FLAGS,
            mount_flags) != 0)
                goto out;

        err = be_mount(be_attrs);

        switch (err) {
        case BE_SUCCESS:
                (void) printf(_("Mounted successfully on: '%s'\n"), mountpoint);
                break;
        case BE_ERR_BE_NOENT:
                (void) fprintf(stderr, _("%s does not exist or appear "
                    "to be a valid BE.\nPlease check that the name of "
                    "the BE provided is correct.\n"), obe_name);
                break;
        case BE_ERR_MOUNTED:
                (void) fprintf(stderr, _("%s is already mounted.\n"
                    "Please unmount the BE before mounting it again.\n"),
                    obe_name);
                break;
        case BE_ERR_PERM:
        case BE_ERR_ACCESS:
                (void) fprintf(stderr, _("Unable to mount %s.\n"), obe_name);
                (void) fprintf(stderr, _("You have insufficient privileges to "
                    "execute this command.\n"));
                break;
        case BE_ERR_NO_MOUNTED_ZONE:
                (void) fprintf(stderr, _("Mounted on '%s'.\nUnable to mount "
                    "one of %s's zone BE's.\n"), mountpoint, obe_name);
                break;
        default:
                (void) fprintf(stderr, _("Unable to mount %s.\n"), obe_name);
                (void) fprintf(stderr, "%s\n", be_err_to_str(err));
        }

out:
        if (tmp_mp != NULL)
                free(tmp_mp);
        nvlist_free(be_attrs);
        return (err);
}

static int
be_do_unmount(int argc, char **argv)
{
        nvlist_t        *be_attrs;
        char            *obe_name;
        int             err = 1;
        int             c;
        int             unmount_flags = 0;

        while ((c = getopt(argc, argv, "fv")) != -1) {
                switch (c) {
                case 'f':
                        unmount_flags |= BE_UNMOUNT_FLAG_FORCE;
                        break;
                case 'v':
                        libbe_print_errors(B_TRUE);
                        break;
                default:
                        usage();
                        return (1);
                }
        }

        argc -= optind;
        argv += optind;

        if (argc != 1) {
                usage();
                return (1);
        }

        obe_name = argv[0];

        if (be_nvl_alloc(&be_attrs) != 0)
                return (1);


        if (be_nvl_add_string(be_attrs, BE_ATTR_ORIG_BE_NAME, obe_name) != 0)
                goto out;

        if (be_nvl_add_uint16(be_attrs, BE_ATTR_UNMOUNT_FLAGS,
            unmount_flags) != 0)
                goto out;

        err = be_unmount(be_attrs);

        switch (err) {
        case BE_SUCCESS:
                (void) printf(_("Unmounted successfully\n"));
                break;
        case BE_ERR_BE_NOENT:
                (void) fprintf(stderr, _("%s does not exist or appear "
                    "to be a valid BE.\nPlease check that the name of "
                    "the BE provided is correct.\n"), obe_name);
                break;
        case BE_ERR_UMOUNT_CURR_BE:
                (void) fprintf(stderr, _("%s is the currently active BE.\n"
                    "It cannot be unmounted unless another BE is the "
                    "currently active BE.\n"), obe_name);
                break;
        case BE_ERR_UMOUNT_SHARED:
                (void) fprintf(stderr, _("%s is a shared file system and it "
                    "cannot be unmounted.\n"), obe_name);
                break;
        case BE_ERR_PERM:
        case BE_ERR_ACCESS:
                (void) fprintf(stderr, _("Unable to unmount %s.\n"), obe_name);
                (void) fprintf(stderr, _("You have insufficient privileges to "
                    "execute this command.\n"));
                break;
        default:
                (void) fprintf(stderr, _("Unable to unmount %s.\n"), obe_name);
                (void) fprintf(stderr, "%s\n", be_err_to_str(err));
        }

out:
        nvlist_free(be_attrs);
        return (err);
}

static int
be_do_rename(int argc, char **argv)
{
        nvlist_t        *be_attrs;
        char            *obe_name;
        char            *nbe_name;
        int err = 1;
        int c;

        while ((c = getopt(argc, argv, "v")) != -1) {
                switch (c) {
                case 'v':
                        libbe_print_errors(B_TRUE);
                        break;
                default:
                        usage();
                        return (1);
                }
        }

        argc -= optind;
        argv += optind;

        if (argc != 2) {
                usage();
                return (1);
        }

        obe_name = argv[0];
        nbe_name = argv[1];

        if (be_nvl_alloc(&be_attrs) != 0)
                return (1);

        if (be_nvl_add_string(be_attrs, BE_ATTR_ORIG_BE_NAME, obe_name) != 0)
                goto out;

        if (be_nvl_add_string(be_attrs, BE_ATTR_NEW_BE_NAME, nbe_name) != 0)
                goto out;

        err = be_rename(be_attrs);

        switch (err) {
        case BE_SUCCESS:
                (void) printf(_("Renamed successfully\n"));
                break;
        case BE_ERR_BE_NOENT:
                (void) fprintf(stderr, _("%s does not exist or appear "
                    "to be a valid BE.\nPlease check that the name of "
                    "the BE provided is correct.\n"), obe_name);
                break;
        case BE_ERR_PERM:
        case BE_ERR_ACCESS:
                (void) fprintf(stderr, _("Rename of BE %s failed.\n"),
                    obe_name);
                (void) fprintf(stderr, _("You have insufficient privileges to "
                    "execute this command.\n"));
                break;
        default:
                (void) fprintf(stderr, _("Rename of BE %s failed.\n"),
                    obe_name);
                (void) fprintf(stderr, "%s\n", be_err_to_str(err));
        }

out:
        nvlist_free(be_attrs);
        return (err);
}

static int
be_do_rollback(int argc, char **argv)
{
        nvlist_t        *be_attrs;
        char            *obe_name;
        char            *snap_name;
        int             err = 1;
        int             c;

        while ((c = getopt(argc, argv, "v")) != -1) {
                switch (c) {
                case 'v':
                        libbe_print_errors(B_TRUE);
                        break;
                default:
                        usage();
                        return (1);
                }
        }

        argc -= optind;
        argv += optind;

        if (argc < 1 || argc > 2) {
                usage();
                return (1);
        }

        obe_name = argv[0];
        if (argc == 2)
                snap_name = argv[1];
        else { /* argc == 1 */
                if ((snap_name = strrchr(obe_name, '@')) != NULL) {
                        if (snap_name[1] == '\0') {
                                usage();
                                return (1);
                        }

                        snap_name[0] = '\0';
                        snap_name++;
                } else {
                        usage();
                        return (1);
                }
        }

        if (be_nvl_alloc(&be_attrs) != 0)
                return (1);

        if (be_nvl_add_string(be_attrs, BE_ATTR_ORIG_BE_NAME, obe_name) != 0)
                goto out;

        if (be_nvl_add_string(be_attrs, BE_ATTR_SNAP_NAME, snap_name) != 0)
                goto out;

        err = be_rollback(be_attrs);

        switch (err) {
        case BE_SUCCESS:
                (void) printf(_("Rolled back successfully\n"));
                break;
        case BE_ERR_BE_NOENT:
                (void) fprintf(stderr, _("%s does not exist or appear "
                    "to be a valid BE.\nPlease check that the name of "
                    "the BE provided is correct.\n"), obe_name);
                break;
        case BE_ERR_SS_NOENT:
                (void) fprintf(stderr, _("%s does not exist or appear "
                    "to be a valid snapshot.\nPlease check that the name of "
                    "the snapshot provided is correct.\n"), snap_name);
                break;
        case BE_ERR_PERM:
        case BE_ERR_ACCESS:
                (void) fprintf(stderr, _("Rollback of BE %s snapshot %s "
                    "failed.\n"), obe_name, snap_name);
                (void) fprintf(stderr, _("You have insufficient privileges to "
                    "execute this command.\n"));
                break;
        default:
                (void) fprintf(stderr, _("Rollback of BE %s snapshot %s "
                    "failed.\n"), obe_name, snap_name);
                (void) fprintf(stderr, "%s\n", be_err_to_str(err));
        }

out:
        nvlist_free(be_attrs);
        return (err);
}