root/usr/src/cmd/nvmeadm/nvmeadm.c
/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright 2017 Joyent, Inc.
 * Copyright 2026 Oxide Computer Company
 * Copyright 2022 Tintri by DDN, Inc. All rights reserved.
 */

/*
 * nvmeadm -- NVMe administration utility
 *
 * nvmeadm [-v] [-d] [-h] <command> [<ctl>[/<ns>][,...]] [args]
 * commands:    list
 *              identify
 *              list-logpages [logpage name],...
 *              get-logpage <logpage name>
 *              get-features <feature>[,...]
 *              format ...
 *              secure-erase ...
 *              detach ...
 *              attach ...
 *              list-firmware ...
 *              load-firmware ...
 *              commit-firmware ...
 *              activate-firmware ...
 */

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <unistd.h>
#include <fcntl.h>
#include <strings.h>
#include <ctype.h>
#include <err.h>
#include <sys/sunddi.h>
#include <libdevinfo.h>
#include <sys/sysmacros.h>
#include <sys/stat.h>
#include <sys/mman.h>

#include <sys/nvme.h>

#include "nvmeadm.h"

/*
 * Assertions to make sure that we've properly captured various aspects of the
 * packed structures and haven't broken them during updates.
 */
CTASSERT(sizeof (nvme_identify_ctrl_t) == NVME_IDENTIFY_BUFSIZE);
CTASSERT(offsetof(nvme_identify_ctrl_t, id_oacs) == 256);
CTASSERT(offsetof(nvme_identify_ctrl_t, id_sqes) == 512);
CTASSERT(offsetof(nvme_identify_ctrl_t, id_oncs) == 520);
CTASSERT(offsetof(nvme_identify_ctrl_t, id_subnqn) == 768);
CTASSERT(offsetof(nvme_identify_ctrl_t, id_nvmof) == 1792);
CTASSERT(offsetof(nvme_identify_ctrl_t, id_psd) == 2048);
CTASSERT(offsetof(nvme_identify_ctrl_t, id_vs) == 3072);

CTASSERT(sizeof (nvme_identify_nsid_t) == NVME_IDENTIFY_BUFSIZE);
CTASSERT(offsetof(nvme_identify_nsid_t, id_fpi) == 32);
CTASSERT(offsetof(nvme_identify_nsid_t, id_anagrpid) == 92);
CTASSERT(offsetof(nvme_identify_nsid_t, id_nguid) == 104);
CTASSERT(offsetof(nvme_identify_nsid_t, id_lbaf) == 128);
CTASSERT(offsetof(nvme_identify_nsid_t, id_vs) == 384);

CTASSERT(sizeof (nvme_identify_nsid_list_t) == NVME_IDENTIFY_BUFSIZE);
CTASSERT(sizeof (nvme_identify_ctrl_list_t) == NVME_IDENTIFY_BUFSIZE);

CTASSERT(sizeof (nvme_identify_primary_caps_t) == NVME_IDENTIFY_BUFSIZE);
CTASSERT(offsetof(nvme_identify_primary_caps_t, nipc_vqfrt) == 32);
CTASSERT(offsetof(nvme_identify_primary_caps_t, nipc_vifrt) == 64);

CTASSERT(sizeof (nvme_nschange_list_t) == 4096);

static void usage(const nvmeadm_cmd_t *);
static bool nvmeadm_ctrl_disc_cb(nvme_t *, const nvme_ctrl_disc_t *, void *);

static int do_list(const nvme_process_arg_t *);
static int do_identify(const nvme_process_arg_t *);
static int do_identify_ctrl(const nvme_process_arg_t *);
static int do_identify_ns(const nvme_process_arg_t *);
static int do_list_logs(const nvme_process_arg_t *);
static int do_get_logpage_fwslot(const nvme_process_arg_t *);
static int do_get_logpage(const nvme_process_arg_t *);
static int do_print_logpage(const nvme_process_arg_t *);
static int do_list_features(const nvme_process_arg_t *);
static boolean_t do_get_feat_intr_vect(const nvme_process_arg_t *,
    const nvme_feat_disc_t *, const nvmeadm_feature_t *);
static boolean_t do_get_feat_temp_thresh(const nvme_process_arg_t *,
    const nvme_feat_disc_t *, const nvmeadm_feature_t *);
static int do_get_features(const nvme_process_arg_t *);
static int do_format(const nvme_process_arg_t *);
static int do_secure_erase(const nvme_process_arg_t *);
static int do_attach_bd(const nvme_process_arg_t *);
static int do_detach_bd(const nvme_process_arg_t *);
static int do_firmware_load(const nvme_process_arg_t *);
static int do_firmware_commit(const nvme_process_arg_t *);
static int do_firmware_activate(const nvme_process_arg_t *);

static void optparse_list(nvme_process_arg_t *);
static void optparse_identify(nvme_process_arg_t *);
static void optparse_identify_ctrl(nvme_process_arg_t *);
static void optparse_identify_ns(nvme_process_arg_t *);
static void optparse_list_logs(nvme_process_arg_t *);
static void optparse_get_logpage(nvme_process_arg_t *);
static void optparse_print_logpage(nvme_process_arg_t *);
static void optparse_list_features(nvme_process_arg_t *);
static void optparse_secure_erase(nvme_process_arg_t *);

static void usage_list(const char *);
static void usage_identify(const char *);
static void usage_identify_ctrl(const char *);
static void usage_identify_ns(const char *);
static void usage_list_logs(const char *);
static void usage_get_logpage(const char *);
static void usage_print_logpage(const char *);
static void usage_list_features(const char *);
static void usage_get_features(const char *);
static void usage_format(const char *);
static void usage_secure_erase(const char *);
static void usage_attach_detach_bd(const char *);
static void usage_firmware_list(const char *);
static void usage_firmware_load(const char *);
static void usage_firmware_commit(const char *);
static void usage_firmware_activate(const char *);

int verbose;
int debug;

/*
 * nvmeadm Secure-erase specific options
 */
#define NVMEADM_O_SE_CRYPTO     0x00000004

/*
 * nvmeadm identify specific options
 */
#define NVMEADM_O_ID_NSID_LIST  0x00000008
#define NVMEADM_O_ID_COMMON_NS  0x00000010
#define NVMEADM_O_ID_CTRL_LIST  0x00000020
#define NVMEADM_O_ID_DESC_LIST  0x00000040
#define NVMEADM_O_ID_ALLOC_NS   0x00000080

/*
 * nvmeadm List specific options
 */
#define NVMEADM_O_LS_CTRL       0x00000100

static int exitcode;

/*
 * Nvmeadm subcommand definitons.
 *
 * When adding a new subcommand, please check that the commands still
 * line up in the usage() message, and adjust the format string in
 * usage() below if necessary.
 */
static const nvmeadm_cmd_t nvmeadm_cmds[] = {
        {
                "list",
                "list controllers and namespaces",
                "  -c\t\tlist only controllers\n"
                "  -p\t\tprint parsable output\n"
                "  -o field\tselect a field for parsable output\n",
                "  model\t\tthe controller's model name\n"
                "  serial\tthe controller's serial number\n"
                "  fwrev\t\tthe controller's current firmware revision\n"
                "  version\tthe controller's NVMe specification version\n"
                "  capacity\tthe controller or namespace's capacity in bytes\n"
                "  instance\tthe device driver instance (e.g. nvme3)\n"
                "  ctrlpath\tthe controller's /devices path\n"
                "  unallocated\tthe amount of unallocated NVM in bytes\n"
                "  size\t\tthe namespace's logical size in bytes\n"
                "  used\t\tthe namespace's bytes used\n"
                "  disk\t\tthe name of the namespace's disk device\n"
                "  namespace\tthe namespace's numerical value\n"
                "  ns-state\tthe namespace's current state\n"
                "  format\ta description of the namespace's format\n"
                "  fmtid\t\tthe numerical id of the namespace's format\n"
                "  fmtds\t\tthe data size of the namespace's format\n"
                "  fmtms\t\tthe metadata size of the namespace's format\n",
                do_list, usage_list, optparse_list,
                NVMEADM_C_MULTI
        },
        {
                "identify",
                "identify controllers and/or namespaces",
                "  -C\t\tget Common Namespace Identification\n"
                "  -a\t\tget only allocated namespace information\n"
                "  -c\t\tget controller identifier list\n"
                "  -d\t\tget namespace identification descriptors list\n"
                "  -n\t\tget namespaces identifier list",
                NULL,
                do_identify, usage_identify, optparse_identify,
                NVMEADM_C_MULTI
        },
        {
                "identify-controller",
                "identify controllers",
                "  -C\t\tget Common Namespace Identification\n"
                "  -a\t\tget only allocated namespace information\n"
                "  -c\t\tget controller identifier list\n"
                "  -n\t\tget namespaces identifier list",
                NULL,
                do_identify_ctrl, usage_identify_ctrl, optparse_identify_ctrl,
                NVMEADM_C_MULTI
        },
        {
                "identify-namespace",
                "identify namespaces",
                "  -c\t\tget attached controller identifier list\n"
                "  -d\t\tget namespace identification descriptors list",
                NULL,
                do_identify_ns, usage_identify_ns, optparse_identify_ns,
                NVMEADM_C_MULTI
        },
        {
                "list-logpages",
                "list a device's supported log pages",
                "  -a\t\tprint all log pages, including unimplemented ones\n"
                "  -H\t\tomit column headers\n"
                "  -o field\tselect a field for parsable output\n"
                "  -p\t\tprint parsable output\n"
                "  -s scope\tprint logs that match the specified scopes "
                "(default is based on\n\t\tdevice)\n",
                "  device\tthe name of the controller or namespace\n"
                "  name\t\tthe name of the log page\n"
                "  desc\t\ta description of the loage page\n"
                "  scope\t\tthe valid device scopes for the log page\n"
                "  fields\tthe list of fields in the get log request that may "
                "be set or required\n\t\t(e.g. lsi, lsp, rae, etc.)\n"
                "  csi\t\tthe command set interface the log page belongs to\n"
                "  lid\t\tthe log page's numeric ID\n"
                "  impl\t\tindicates whether the device implements the log "
                "page\n"
                "  size\t\tthe size of the log page for fixed size logs\n"
                "  minsize\tthe minimum size required to determine the full "
                "log page size\n\t\tfor variable-length pages\n"
                "  sources\twhere information for this log page came from\n"
                "  kind\t\tindicates the kind of log page e.g. standard, "
                "vendor-specific,\n\t\tetc.",
                do_list_logs, usage_list_logs, optparse_list_logs,
                NVMEADM_C_MULTI
        },
        {
                .c_name = "get-logpage",
                .c_desc = "get a log page from controllers and/or namespaces",
                .c_flagdesc = "  -H\t\tomit column headers\n"
                    "  -o field\tselect a field for parsable output\n"
                    "  -O file\toutput log raw binary data to a file\n"
                    "  -p\t\tprint parsable output\n"
                    "  -x\t\tforce hex dump output\n",
                .c_fielddesc = "  short\t\tfield's short name\n"
                    "  desc\t\tdescription of the field\n"
                    "  value\t\tthe raw field value\n"
                    "  human\t\tthe human printable form of the value\n"
                    "  length\tthe length of the field in bytes\n"
                    "  bitlen\tthe number of additional bits long the field "
                    "is\n"
                    "  offset\tthe offset in bytes to the start of the field\n"
                    "  bitoff\tthe additional number of bits to the start of "
                    "the field\n",
                .c_func = do_get_logpage,
                .c_usage = usage_get_logpage,
                .c_optparse = optparse_get_logpage,
                .c_flags = NVMEADM_C_MULTI
        },
        {
                .c_name = "print-logpage",
                .c_desc = "print and filter a log page from disk",
                .c_flagdesc = "  -f file\tfile that contains log data\n"
                    "  -H\t\tomit column headers\n"
                    "  -o field\tselect a field for parsable output\n"
                    "  -p\t\tprint parsable output\n"
                    "  -x\t\tforce hex dump output\n",
                .c_fielddesc = "  short\t\tfield's short name\n"
                    "  desc\t\tdescription of the field\n"
                    "  value\t\tthe raw field value\n"
                    "  human\t\tthe human printable form of the value\n"
                    "  offset\tthe length of the field in bytes\n"
                    "  bitlen\tthe number of additional bits long the field "
                    "is\n"
                    "  offset\tthe offset in bytes to the start of the field\n"
                    "  bitoff\tthe additional number of bits to the start of "
                    "the field\n",
                .c_func = do_print_logpage,
                .c_usage = usage_print_logpage,
                .c_optparse = optparse_print_logpage,
                .c_flags = NVMEADM_C_NOCTRL
        },
        {
                "list-features",
                "list a device's supported features",
                "  -a\t\tprint all features, including unsupported\n"
                "  -H\t\tomit column headers\n"
                "  -o field\tselect a field for parsable output\n"
                "  -p\t\tprint parsable output",
                "  device\tthe name of the controller or namespace\n"
                "  short\t\tthe short name of the feature\n"
                "  spec\t\tthe longer feature description from the NVMe spec\n"
                "  fid\t\tthe numeric feature ID\n"
                "  scope\t\tthe valid device scopes for the feature\n"
                "  kind\t\tindicates the kind of feature e.g. standard, "
                "vendor-specific,\n\t\tetc.\n"
                "  csi\t\tindicates the features command set interface\n"
                "  flags\t\tindicates additional properties of the feature\n"
                "  get-in\tindicates the fields that are required to get the "
                "feature\n"
                "  set-in\tindicates the fields that are required to set the "
                "feature\n"
                "  get-out\tindicates the fields the feature outputs\n"
                "  set-out\tindicates the fields the feature outputs when "
                "setting the feature\n"
                "  datalen\tindicates the length of the feature's data "
                "payload\n"
                "  impl\t\tindicates whether the device implements the "
                "feature",
                do_list_features, usage_list_features, optparse_list_features,
                NVMEADM_C_MULTI
        },
        {
                "get-features",
                "get features from controllers and/or namespaces",
                NULL,
                NULL,
                do_get_features, usage_get_features, NULL,
                NVMEADM_C_MULTI
        },
        {
                "format",
                "format namespace(s) of a controller",
                NULL,
                NULL,
                do_format, usage_format, NULL,
                NVMEADM_C_EXCL
        },
        {
                "secure-erase",
                "secure erase namespace(s) of a controller",
                "  -c  Do a cryptographic erase.",
                NULL,
                do_secure_erase, usage_secure_erase, optparse_secure_erase,
                NVMEADM_C_EXCL
        },
        {
                "create-namespace",
                "create a new namespace",
                "  -b block-size\tNamespace format chosen to match the "
                "requested block-size\n"
                "  -c cap\tSpecifies the namespace capacity in bytes, defaults "
                "to the\n\t\tnamespace's size. When the size is greater than "
                "the\n\t\tcapacity, the namespace is thin provisioned.\n"
                "  -f flbas\tformatted LBA block size index\n"
                "  -n nmic\tmulti-path I/O and namespace sharing capabilities, "
                "valid values:\n"
                "\t\tnone\tno namespace sharing\n"
                "\t\tshared\tthe namespace may be attached by two or more "
                "controllers\n"
                "  -t csi\tspecifies the namespace's command set interface, "
                "defaults to\n\t\tnvm\n",
                NULL,
                do_create_ns, usage_create_ns, optparse_create_ns,
                NVMEADM_C_EXCL
        },
        {
                "delete-namespace",
                "delete a namespace",
                NULL, NULL,
                do_delete_ns, usage_delete_ns, NULL,
                NVMEADM_C_EXCL
        },
        {
                "attach-namespace",
                "attach a namespace to a controller",
                NULL, NULL,
                do_attach_ns, usage_attach_ns, NULL,
                NVMEADM_C_EXCL
        },
        {
                "detach-namespace",
                "detach a namespace from a controller",
                NULL, NULL,
                do_detach_ns, usage_detach_ns, NULL,
                NVMEADM_C_EXCL
        },

        {
                "detach",
                "detach blkdev(4D) from namespace(s) of a controller",
                NULL,
                NULL,
                do_detach_bd, usage_attach_detach_bd, NULL,
                NVMEADM_C_EXCL
        },
        {
                "attach",
                "attach blkdev(4D) to namespace(s) of a controller",
                NULL,
                NULL,
                do_attach_bd, usage_attach_detach_bd, NULL,
                NVMEADM_C_EXCL
        },
        {
                "list-firmware",
                "list firmware on a controller",
                NULL,
                NULL,
                do_get_logpage_fwslot, usage_firmware_list, NULL,
                0
        },
        {
                "load-firmware",
                "load firmware to a controller",
                NULL,
                NULL,
                do_firmware_load, usage_firmware_load, NULL,
                NVMEADM_C_EXCL
        },
        {
                "commit-firmware",
                "commit downloaded firmware to a slot of a controller",
                NULL,
                NULL,
                do_firmware_commit, usage_firmware_commit, NULL,
                NVMEADM_C_EXCL
        },
        {
                "activate-firmware",
                "activate a firmware slot of a controller",
                NULL,
                NULL,
                do_firmware_activate, usage_firmware_activate, NULL,
                NVMEADM_C_EXCL
        },
        {
                .c_name = "measure-phyeye",
                .c_desc = "measure the physical interface eye",
                .c_flagdesc = "  -o output\tthe data output file\n"
                    "  -Q quality\tthe quality of the eye measurement (good, "
                    "better, or best)\n",
                .c_func = do_measure_phyeye_cmd,
                .c_usage = usage_measure_phyeye_cmd,
                .c_optparse = optparse_measure_phyeye_cmd,
                .c_flags = NVMEADM_C_EXCL
        },
        {
                .c_name = "report-phyeye",
                .c_desc = "report information about the measured eye",
                .c_flagdesc = "  -e eye\toptional eye index to report\n"
                    "  -l lane\tprint information only about the specific "
                    "lane\n"
                    "  -m mode\tdata report mode, either 'print-eye' or "
                    "'eye-data'\n",
                .c_func = do_report_phyeye_cmd,
                .c_usage = usage_report_phyeye_cmd,
                .c_optparse = optparse_report_phyeye_cmd,
                .c_flags = NVMEADM_C_NOCTRL
        },
        {
                .c_name = "vendor-cmd",
                .c_desc = "run an arbitrary vendor-specific command",
                .c_flagdesc = "  -O opcode\tcommand opcode\n"
                    "  -n nsid\tcommand namespace identifier\n"
                    "  --cdw12 cdw12\tcdw12 32-bit value\n"
                    "  --cdw13 cdw13\tcdw13 32-bit value\n"
                    "  --cdw14 cdw14\tcdw14 32-bit value\n"
                    "  --cdw15 cdw15\tcdw15 32-bit value\n"
                    "  -l length\tthe command data size in bytes\n"
                    "  -i input\tthe command input data file\n"
                    "  -o output\tthe command output data file\n"
                    "  -L lock\trequest a controller or namespace lock\n"
                    "  -I impact\trequest impact on the system\n"
                    "  -t timeout\trequest timeout in seconds\n",
                .c_func = do_vendor_cmd,
                .c_usage = usage_vendor_cmd,
                .c_optparse = optparse_vendor_cmd
        },
        {
                .c_name = "sandisk/hwrev",
                .c_desc = "obtain device hardware revision",
                .c_func = do_sandisk_hwrev,
                .c_usage = usage_sandisk_hwrev,
                .c_flags = 0
        },
        {
                .c_name = "sandisk/pci-eye",
                .c_desc = "get PCIe receiver eye diagram",
                .c_flagdesc = "  -l lane\t\tspecify the PCIe lane (0-3)\n"
                    "  -o output\tspecify output file destination\n",
                .c_func = do_sandisk_pcieye,
                .c_usage = usage_sandisk_pcieye,
                .c_optparse = optparse_sandisk_pcieye
        },
        {
                "wdc/e6dump",
                "dump WDC e6 diagnostic log",
                "  -o output\tspecify output file destination\n",
                NULL,
                do_wdc_e6dump, usage_wdc_e6dump, optparse_wdc_e6dump,
                0
        },
        {
                "wdc/resize",
                "change a WDC device's capacity",
                "  -g\t\tquery the device's current resized capacity\n"
                "  -s size\tset the size of a device to the specified in gb",
                NULL,
                do_wdc_resize, usage_wdc_resize, optparse_wdc_resize,
                /*
                 * We do not set NVMEADM_C_EXCL here as that is handled by the
                 * vendor unique command logic and operates based on the
                 * information we get from vuc discovery.
                 */
                0
        },
        {
                "wdc/clear-assert",
                "clear internal device assertion",
                NULL,
                NULL,
                do_wdc_clear_assert, usage_wdc_clear_assert, NULL
        },
        {
                "wdc/inject-assert",
                "inject internal device assertion",
                NULL,
                NULL,
                do_wdc_inject_assert, usage_wdc_inject_assert, NULL
        },
        {
                NULL, NULL, NULL,
                NULL, NULL, NULL, 0
        }
};

static const nvmeadm_feature_t features[] = {
        {
                .f_feature = NVME_FEAT_ARBITRATION,
                .f_print = nvme_print_feat_arbitration
        }, {
                .f_feature = NVME_FEAT_POWER_MGMT,
                .f_print = nvme_print_feat_power_mgmt
        }, {
                .f_feature = NVME_FEAT_LBA_RANGE,
                .f_print = nvme_print_feat_lba_range
        }, {
                .f_feature = NVME_FEAT_TEMPERATURE,
                .f_get = do_get_feat_temp_thresh,
                .f_print = nvme_print_feat_temperature
        }, {
                .f_feature = NVME_FEAT_ERROR,
                .f_print = nvme_print_feat_error
        }, {
                .f_feature = NVME_FEAT_WRITE_CACHE,
                .f_print = nvme_print_feat_write_cache
        }, {
                .f_feature = NVME_FEAT_NQUEUES,
                .f_print = nvme_print_feat_nqueues
        }, {
                .f_feature = NVME_FEAT_INTR_COAL,
                .f_print = nvme_print_feat_intr_coal
        }, {
                .f_feature = NVME_FEAT_INTR_VECT,
                .f_get = do_get_feat_intr_vect,
                .f_print = nvme_print_feat_intr_vect
        }, {
                .f_feature = NVME_FEAT_WRITE_ATOM,
                .f_print = nvme_print_feat_write_atom
        }, {
                .f_feature = NVME_FEAT_ASYNC_EVENT,
                .f_print = nvme_print_feat_async_event
        }, {
                .f_feature = NVME_FEAT_AUTO_PST,
                .f_print = nvme_print_feat_auto_pst
        }, {
                .f_feature = NVME_FEAT_PROGRESS,
                .f_print = nvme_print_feat_progress
        }, {
                .f_feature = NVME_FEAT_HOST_BEHAVE,
                .f_print = nvme_print_feat_host_behavior
        }
};

static void
nvmeadm_ctrl_vwarn(const nvme_process_arg_t *npa, const char *fmt, va_list ap)
{
        nvme_ctrl_t *ctrl = npa->npa_ctrl;

        (void) fprintf(stderr, "nvmeadm: ");
        (void) vfprintf(stderr, fmt, ap);
        (void) fprintf(stderr, ": %s: %s (libnvme: 0x%x, sys: %d)\n",
            nvme_ctrl_errmsg(ctrl), nvme_ctrl_errtostr(npa->npa_ctrl,
            nvme_ctrl_err(ctrl)), nvme_ctrl_err(ctrl), nvme_ctrl_syserr(ctrl));
}

static void
nvmeadm_hdl_vwarn(const nvme_process_arg_t *npa, const char *fmt, va_list ap)
{
        nvme_t *nvme = npa->npa_nvme;

        (void) fprintf(stderr, "nvmeadm: ");
        (void) vfprintf(stderr, fmt, ap);
        (void) fprintf(stderr, ": %s: %s (libnvme: 0x%x, sys: %d)\n",
            nvme_errmsg(nvme), nvme_errtostr(nvme, nvme_err(nvme)),
            nvme_err(nvme), nvme_syserr(nvme));
}

static void
nvmeadm_ctrl_info_vwarn(const nvme_process_arg_t *npa, const char *fmt,
    va_list ap)
{
        nvme_ctrl_info_t *info = npa->npa_ctrl_info;

        (void) fprintf(stderr, "nvmeadm: ");
        (void) vfprintf(stderr, fmt, ap);
        (void) fprintf(stderr, ": %s: %s (libnvme info: 0x%x, sys: %d)\n",
            nvme_ctrl_info_errmsg(info), nvme_ctrl_info_errtostr(info,
            nvme_ctrl_info_err(info)), nvme_ctrl_info_err(info),
            nvme_ctrl_info_syserr(info));
}

void
nvmeadm_warn(const nvme_process_arg_t *npa, const char *fmt, ...)
{
        va_list ap;

        va_start(ap, fmt);
        nvmeadm_ctrl_vwarn(npa, fmt, ap);
        va_end(ap);
}

void __NORETURN
nvmeadm_fatal(const nvme_process_arg_t *npa, const char *fmt, ...)
{
        va_list ap;

        va_start(ap, fmt);
        nvmeadm_ctrl_vwarn(npa, fmt, ap);
        va_end(ap);

        exit(-1);
}

void
nvmeadm_hdl_warn(const nvme_process_arg_t *npa, const char *fmt, ...)
{
        va_list ap;

        va_start(ap, fmt);
        nvmeadm_hdl_vwarn(npa, fmt, ap);
        va_end(ap);
}

void __NORETURN
nvmeadm_hdl_fatal(const nvme_process_arg_t *npa, const char *fmt, ...)
{
        va_list ap;

        va_start(ap, fmt);
        nvmeadm_hdl_vwarn(npa, fmt, ap);
        va_end(ap);

        exit(-1);
}

static void
nvmeadm_ctrl_info_warn(const nvme_process_arg_t *npa, const char *fmt, ...)
{
        va_list ap;

        va_start(ap, fmt);
        nvmeadm_ctrl_info_vwarn(npa, fmt, ap);
        va_end(ap);
}

static void
nvmeadm_ctrl_info_fatal(const nvme_process_arg_t *npa, const char *fmt, ...)
{
        va_list ap;

        va_start(ap, fmt);
        nvmeadm_ctrl_info_vwarn(npa, fmt, ap);
        va_end(ap);

        exit(-1);
}

boolean_t
nvme_version_check(const nvme_process_arg_t *npa, const nvme_version_t *vers)
{
        return (nvme_vers_atleast(npa->npa_version, vers) ? B_TRUE : B_FALSE);
}

/*
 * Because nvmeadm operates on a series of NVMe devices for several commands,
 * here we need to clean up everything that we allocated for this device so we
 * can prepare for the next.
 */
static void
nvmeadm_cleanup_npa(nvme_process_arg_t *npa)
{
        npa->npa_idctl = NULL;
        npa->npa_version = NULL;

        if (npa->npa_excl) {
                if (npa->npa_ns != NULL) {
                        nvme_ns_unlock(npa->npa_ns);
                } else if (npa->npa_ctrl != NULL) {
                        nvme_ctrl_unlock(npa->npa_ctrl);
                }
        }

        if (npa->npa_ns_info != NULL) {
                nvme_ns_info_free(npa->npa_ns_info);
                npa->npa_ns_info = NULL;
        }

        if (npa->npa_ctrl_info != NULL) {
                nvme_ctrl_info_free(npa->npa_ctrl_info);
                npa->npa_ctrl_info = NULL;
        }

        if (npa->npa_ns != NULL) {
                nvme_ns_fini(npa->npa_ns);
                npa->npa_ns = NULL;
        }

        if (npa->npa_ctrl != NULL) {
                nvme_ctrl_fini(npa->npa_ctrl);
                npa->npa_ctrl = NULL;
        }
}

/*
 * Determine if a command requires a controller or namespace write lock. If so
 * we first attempt to grab it non-blocking and then if that fails, we'll warn
 * that we may be blocking for the lock so that way the user has a chance to do
 * something and can cancel it.
 */
void
nvmeadm_excl(const nvme_process_arg_t *npa, nvme_lock_level_t level)
{
        bool ret;
        nvme_lock_flags_t flags = NVME_LOCK_F_DONT_BLOCK;

        if (npa->npa_ns != NULL) {
                ret = nvme_ns_lock(npa->npa_ns, level, flags);
        } else {
                ret = nvme_ctrl_lock(npa->npa_ctrl, level, flags);
        }

        if (ret) {
                return;
        }

        if (nvme_ctrl_err(npa->npa_ctrl) != NVME_ERR_LOCK_WOULD_BLOCK) {
                nvmeadm_fatal(npa, "failed to acquire lock on %s",
                    npa->npa_name);
        }

        (void) fprintf(stderr, "Waiting on contended %s lock on %s...",
            npa->npa_ns != NULL ? "namespace": "controller", npa->npa_name);
        (void) fflush(stderr);

        flags &= ~NVME_LOCK_F_DONT_BLOCK;
        if (npa->npa_ns != NULL) {
                ret = nvme_ns_lock(npa->npa_ns, level, flags);
        } else {
                ret = nvme_ctrl_lock(npa->npa_ctrl, level, flags);
        }

        if (!ret) {
                nvmeadm_fatal(npa, "failed to acquire lock on %s",
                    npa->npa_name);
        }

        (void) fprintf(stderr, " acquired\n");
}

/*
 * Most of nvmeadm was written before the existence of libnvme and always had
 * things like the identify controller or namespace information sitting around.
 * As such we try to grab all this in one place for it. Note, regardless if this
 * succeeds or fails, our callers will still call nvmeadm_cleanup_npa() so we
 * don't need to clean up the various libnvme objects.
 */
static boolean_t
nvmeadm_open_dev(nvme_process_arg_t *npa)
{
        if (!nvme_ctrl_ns_init(npa->npa_nvme, npa->npa_name, &npa->npa_ctrl,
            &npa->npa_ns)) {
                nvmeadm_hdl_warn(npa, "failed to open '%s'", npa->npa_name);
                exitcode = -1;
                return (B_FALSE);
        }

        /*
         * Several commands expect to be able to access the controller's
         * information snapshot. Grab that now for it and the namespace if it
         * exists.
         */
        if (!nvme_ctrl_info_snap(npa->npa_ctrl, &npa->npa_ctrl_info)) {
                nvmeadm_warn(npa, "failed to get controller info for %s",
                    npa->npa_ctrl_name);
                exitcode = -1;
                return (B_FALSE);
        }

        if (npa->npa_ns != NULL && !nvme_ns_info_snap(npa->npa_ns,
            &npa->npa_ns_info)) {
                nvmeadm_warn(npa, "failed to get namespace info for %s",
                    npa->npa_name);
                exitcode = -1;
                return (B_FALSE);
        }

        /*
         * Snapshot data the rest of the command has fairly ingrained.
         */
        npa->npa_version = nvme_ctrl_info_version(npa->npa_ctrl_info);
        npa->npa_idctl = nvme_ctrl_info_identify(npa->npa_ctrl_info);

        /*
         * If this command has requested exclusive access, proceed to grab that
         * before we continue.
         */
        if (npa->npa_excl) {
                nvmeadm_excl(npa, NVME_LOCK_L_WRITE);
        }

        return (B_TRUE);
}

static bool
nvmeadm_ctrl_disc_cb(nvme_t *nvme, const nvme_ctrl_disc_t *disc, void *arg)
{
        nvme_process_arg_t *npa = arg;
        di_node_t di = nvme_ctrl_disc_devi(disc);
        char name[128];

        (void) snprintf(name, sizeof (name), "%s%d", di_driver_name(di),
            di_instance(di));
        npa->npa_name = name;
        npa->npa_ctrl_name = name;

        if (nvmeadm_open_dev(npa)) {
                if (npa->npa_cmd->c_func(npa) != 0) {
                        exitcode = -1;
                }
        }

        nvmeadm_cleanup_npa(npa);
        return (true);
}

int
main(int argc, char **argv)
{
        int c;
        const nvmeadm_cmd_t *cmd;
        nvme_process_arg_t npa = { 0 };
        int help = 0;
        char *ctrl = NULL;

        while ((c = getopt(argc, argv, "dhv")) != -1) {
                switch (c) {
                case 'd':
                        debug++;
                        break;

                case 'v':
                        verbose++;
                        break;

                case 'h':
                        help++;
                        break;

                case '?':
                        usage(NULL);
                        exit(-1);
                }
        }

        if (optind == argc) {
                usage(NULL);
                if (help)
                        exit(0);
                else
                        exit(-1);
        }

        /* Look up the specified command in the command table. */
        for (cmd = &nvmeadm_cmds[0]; cmd->c_name != NULL; cmd++)
                if (strcmp(cmd->c_name, argv[optind]) == 0)
                        break;

        if (cmd->c_name == NULL) {
                usage(NULL);
                exit(-1);
        }

        if (help) {
                usage(cmd);
                exit(0);
        }

        npa.npa_nvme = nvme_init();
        if (npa.npa_nvme == NULL) {
                err(-1, "failed to initialize libnvme");
        }
        npa.npa_cmd = cmd;
        npa.npa_excl = ((npa.npa_cmd->c_flags & NVMEADM_C_EXCL) != 0);

        optind++;

        /*
         * Store the remaining arguments for use by the command. Give the
         * command a chance to process the options across the board before going
         * into each controller.
         */
        npa.npa_argc = argc - optind;
        npa.npa_argv = &argv[optind];

        if (npa.npa_cmd->c_optparse != NULL) {
                optind = 0;
                npa.npa_cmd->c_optparse(&npa);
                npa.npa_argc -= optind;
                npa.npa_argv += optind;
        }

        /*
         * If this command indicates that it does not require a controller, then
         * don't bother with the rest and just call it immediately.
         */
        if ((npa.npa_cmd->c_flags & NVMEADM_C_NOCTRL) != 0) {
                if (npa.npa_cmd->c_func(&npa) != 0) {
                        exitcode = -1;
                }
                exit(exitcode);
        }

        /*
         * All commands but "list" require a ctl/ns argument. However, this
         * should not be passed through to the command in its subsequent
         * arguments.
         */
        if (npa.npa_argc == 0 && cmd->c_func != do_list) {
                warnx("missing controller/namespace name");
                usage(cmd);
                exit(-1);
        }

        if (npa.npa_argc > 0) {
                ctrl = npa.npa_argv[0];
                npa.npa_argv++;
                npa.npa_argc--;
        } else {
                if (!nvme_ctrl_discover(npa.npa_nvme, nvmeadm_ctrl_disc_cb,
                    &npa)) {
                        nvmeadm_hdl_fatal(&npa, "failed to walk controllers");
                }
                exit(exitcode);
        }

        /*
         * Make sure we're not running commands on multiple controllers that
         * aren't allowed to do that.
         */
        if (ctrl != NULL && strchr(ctrl, ',') != NULL &&
            (cmd->c_flags & NVMEADM_C_MULTI) == 0) {
                warnx("%s not allowed on multiple controllers",
                    cmd->c_name);
                usage(cmd);
                exit(-1);
        }

        /*
         * Get controller/namespace arguments and run command.
         */
        while ((npa.npa_name = strsep(&ctrl, ",")) != NULL) {
                char *ctrl_name, *slash;

                /*
                 * We may be given just a controller as an argument or a
                 * controller and a namespace as an argument. Parts of the
                 * commands want to know what controller they're referring to
                 * even if the overall argument was for a namespace. So we
                 * always dup the argument and try to make the controller out of
                 * it.
                 */
                ctrl_name = strdup(npa.npa_name);
                if (ctrl_name == NULL) {
                        err(-1, "failed to duplicate NVMe controller/namespace "
                            "name");
                }
                if ((slash = strchr(ctrl_name, '/')) != NULL)
                        *slash = '\0';
                npa.npa_ctrl_name = ctrl_name;

                if (nvmeadm_open_dev(&npa)) {
                        if (npa.npa_cmd->c_func(&npa) != 0) {
                                exitcode = -1;
                        }
                }

                nvmeadm_cleanup_npa(&npa);
                free(ctrl_name);
        }

        exit(exitcode);
}

static void
nvme_oferr(const char *fmt, ...)
{
        va_list ap;

        va_start(ap, fmt);
        verrx(-1, fmt, ap);
}

static void
usage(const nvmeadm_cmd_t *cmd)
{
        const char *progname = getprogname();

        (void) fprintf(stderr, "usage:\n");
        (void) fprintf(stderr, "  %s -h %s\n", progname,
            cmd != NULL ? cmd->c_name : "[<command>]");
        (void) fprintf(stderr, "  %s [-dv] ", progname);

        if (cmd != NULL) {
                cmd->c_usage(cmd->c_name);
        } else {
                (void) fprintf(stderr,
                    "<command> <ctl>[/<ns>][,...] [<args>]\n");
                (void) fprintf(stderr,
                    "\n  Manage NVMe controllers and namespaces.\n");
                (void) fprintf(stderr, "\ncommands:\n");

                for (cmd = &nvmeadm_cmds[0]; cmd->c_name != NULL; cmd++) {
                        /*
                         * The longest nvmeadm subcommand is 19 characters long.
                         * The format string needs to be updated every time a
                         * longer subcommand is added.
                         */
                        (void) fprintf(stderr, "  %-19s - %s\n",
                            cmd->c_name, cmd->c_desc);
                }
        }
        (void) fprintf(stderr, "\n%s flags:\n"
            "  -h\t\tprint usage information\n"
            "  -d\t\tprint information useful for debugging %s\n"
            "  -v\t\tprint verbose information\n",
            progname, progname);

        if (cmd != NULL && cmd->c_flagdesc != NULL) {
                (void) fprintf(stderr, "\n%s %s flags:\n",
                    progname, cmd->c_name);
                (void) fprintf(stderr, "%s\n", cmd->c_flagdesc);
        }

        if (cmd != NULL && cmd->c_fielddesc != NULL) {
                (void) fprintf(stderr, "\n%s %s valid fields:\n",
                    progname, cmd->c_name);
                (void) fprintf(stderr, "%s\n", cmd->c_fielddesc);
        }
}

char *
nvme_dskname(di_node_t ctrl, const char *bd_addr)
{
        di_dim_t dim;
        char *diskname = NULL;

        dim = di_dim_init();
        if (dim == NULL) {
                err(-1, "failed to initialize devinfo minor translation");
        }

        for (di_node_t child = di_child_node(ctrl); child != DI_NODE_NIL;
            child = di_sibling_node(child)) {
                char *disk_ctd, *path = NULL;
                const char *addr = di_bus_addr(child);
                if (addr == NULL)
                        continue;

                if (strcmp(addr, bd_addr) != 0)
                        continue;

                path = di_dim_path_dev(dim, di_driver_name(child),
                    di_instance(child), "c");

                /*
                 * Error out if we didn't get a path, or if it's too short for
                 * the following operations to be safe.
                 */
                if (path == NULL || strlen(path) < 2) {
                        errx(-1, "failed to get a valid minor path");
                }

                /* Chop off 's0' and get everything past the last '/' */
                path[strlen(path) - 2] = '\0';
                disk_ctd = strrchr(path, '/');
                if (disk_ctd == NULL) {
                        errx(-1, "encountered malformed minor path: %s", path);
                }

                diskname = strdup(++disk_ctd);
                if (diskname == NULL) {
                        err(-1, "failed to duplicate disk path");
                }

                free(path);
                break;
        }

        di_dim_fini(dim);
        return (diskname);
}

static void
usage_list(const char *c_name)
{
        (void) fprintf(stderr, "%s "
            "[-c] [-p -o field[,...]] [<ctl>[/<ns>][,...]\n\n"
            "  List NVMe controllers and their namespaces. If no "
            "controllers and/or name-\n  spaces are specified, all "
            "controllers and namespaces in the system will be\n  "
            "listed.\n", c_name);
}

static void
optparse_list(nvme_process_arg_t *npa)
{
        int c;
        uint_t oflags = 0;
        boolean_t parse = B_FALSE;
        const char *fields = NULL;
        const ofmt_field_t *ofmt = nvmeadm_list_nsid_ofmt;

        while ((c = getopt(npa->npa_argc, npa->npa_argv, ":co:p")) != -1) {
                switch (c) {
                case 'c':
                        npa->npa_cmdflags |= NVMEADM_O_LS_CTRL;
                        ofmt = nvmeadm_list_ctrl_ofmt;
                        break;
                case 'o':
                        fields = optarg;
                        break;

                case 'p':
                        parse = B_TRUE;
                        oflags |= OFMT_PARSABLE;
                        break;

                case '?':
                        errx(-1, "unknown option: -%c", optopt);

                case ':':
                        errx(-1, "option -%c requires an argument", optopt);
                }
        }

        if (fields != NULL && !parse) {
                errx(-1, "-o can only be used when in parsable mode (-p)");
        }

        if (parse && fields == NULL) {
                errx(-1, "parsable mode (-p) requires one to specify output "
                    "fields with -o");
        }

        if (parse) {
                ofmt_status_t oferr;

                oferr = ofmt_open(fields, ofmt, oflags, 0,
                    &npa->npa_ofmt);
                ofmt_check(oferr, B_TRUE, npa->npa_ofmt, nvme_oferr, warnx);
        }
}

static void
do_list_nsid(const nvme_process_arg_t *npa, nvme_ctrl_info_t *ctrl,
    nvme_ns_info_t *ns)
{
        const char *bd_addr, *disk = NULL, *state = NULL;
        char *disk_path = NULL;
        di_node_t ctrl_devi;

        switch (nvme_ns_info_level(ns)) {
        case NVME_NS_DISC_F_ALL:
                disk = "unallocated";
                state = "unallocated";
                break;
        case NVME_NS_DISC_F_ALLOCATED:
                disk = "inactive";
                state = "allocated";
                break;
        case NVME_NS_DISC_F_ACTIVE:
                disk = "ignored";
                state = "active";
                break;
        case NVME_NS_DISC_F_NOT_IGNORED:
                disk = "unattached";
                state = "active-usable";
                break;
        case NVME_NS_DISC_F_BLKDEV:
                disk = "unknown";
                state = "blkdev";
                if (nvme_ns_info_bd_addr(ns, &bd_addr) &&
                    nvme_ctrl_devi(npa->npa_ctrl, &ctrl_devi)) {
                        disk_path = nvme_dskname(ctrl_devi, bd_addr);
                        disk = disk_path;
                }
                break;
        }

        if (npa->npa_ofmt != NULL) {
                nvmeadm_list_ofmt_arg_t oarg = { 0 };

                oarg.nloa_name = npa->npa_ctrl_name;
                oarg.nloa_ctrl = ctrl;
                oarg.nloa_ns = ns;
                oarg.nloa_disk = disk_path;
                oarg.nloa_state = state;
                if (!nvme_ctrl_devi(npa->npa_ctrl, &oarg.nloa_dip))
                        oarg.nloa_dip = DI_NODE_NIL;

                ofmt_print(npa->npa_ofmt, &oarg);
        } else {
                (void) printf("  %s/%u (%s)", npa->npa_ctrl_name,
                    nvme_ns_info_nsid(ns), disk);
                if (nvme_ns_info_level(ns) >= NVME_NS_DISC_F_ACTIVE) {
                        (void) printf(": ");
                        nvme_print_nsid_summary(ns);
                } else {
                        (void) printf("\n");
                }
        }

        free(disk_path);
}

static int
do_list(const nvme_process_arg_t *npa)
{
        nvme_ctrl_info_t *info = NULL;
        nvme_ns_iter_t *iter = NULL;
        nvme_iter_t ret;
        const nvme_ns_disc_t *disc;
        nvme_ns_disc_level_t level;
        int rv = -1;

        if (npa->npa_argc > 0) {
                errx(-1, "%s passed extraneous arguments starting with %s",
                    npa->npa_cmd->c_name, npa->npa_argv[0]);
        }

        if (!nvme_ctrl_info_snap(npa->npa_ctrl, &info)) {
                nvmeadm_warn(npa, "failed to get controller information for %s",
                    npa->npa_ctrl_name);
                return (-1);
        }

        if (npa->npa_ofmt == NULL) {
                (void) printf("%s: ", npa->npa_ctrl_name);
                nvme_print_ctrl_summary(info);
        } else if ((npa->npa_cmdflags & NVMEADM_O_LS_CTRL) != 0) {
                nvmeadm_list_ofmt_arg_t oarg = { 0 };
                oarg.nloa_name = npa->npa_ctrl_name;
                oarg.nloa_ctrl = info;
                if (!nvme_ctrl_devi(npa->npa_ctrl, &oarg.nloa_dip))
                        oarg.nloa_dip = DI_NODE_NIL;

                ofmt_print(npa->npa_ofmt, &oarg);
        }

        if ((npa->npa_cmdflags & NVMEADM_O_LS_CTRL) != 0) {
                rv = 0;
                goto out;
        }

        /*
         * Check if we were given an explicit namespace as an argument. If so,
         * we always list it and don't need to do discovery.
         */
        if (npa->npa_ns != NULL) {
                nvme_ns_info_t *ns_info;

                if (!nvme_ns_info_snap(npa->npa_ns, &ns_info)) {
                        nvmeadm_warn(npa, "failed to get namespace "
                            "information for %s", npa->npa_name);
                        goto out;
                }

                do_list_nsid(npa, info, ns_info);
                nvme_ns_info_free(ns_info);
                rv = 0;
                goto out;
        }

        if (verbose) {
                level = NVME_NS_DISC_F_ALL;
        } else {
                level = NVME_NS_DISC_F_NOT_IGNORED;
        }

        if (!nvme_ns_discover_init(npa->npa_ctrl, level, &iter)) {
                nvmeadm_warn(npa, "failed to iterate namespaces on %s",
                    npa->npa_ctrl_name);
                goto out;
        }

        while ((ret = nvme_ns_discover_step(iter, &disc)) == NVME_ITER_VALID) {
                nvme_ns_info_t *ns_info;
                uint32_t nsid = nvme_ns_disc_nsid(disc);

                if (!nvme_ctrl_ns_info_snap(npa->npa_ctrl, nsid, &ns_info)) {
                        nvmeadm_warn(npa, "failed to get namespace "
                            "information for %s/%u", npa->npa_ctrl_name, nsid);
                        exitcode = -1;
                        continue;
                }

                do_list_nsid(npa, info, ns_info);
                nvme_ns_info_free(ns_info);
        }

        nvme_ns_discover_fini(iter);
        if (ret == NVME_ITER_ERROR) {
                nvmeadm_warn(npa, "failed to iterate all namespaces on %s",
                    npa->npa_ctrl_name);
        } else {
                rv = 0;
        }

out:
        nvme_ctrl_info_free(info);
        return (rv);
}

static void
optparse_identify_ctrl(nvme_process_arg_t *npa)
{
        int c;

        while ((c = getopt(npa->npa_argc, npa->npa_argv, ":Cacn")) != -1) {
                switch (c) {
                case 'C':
                        npa->npa_cmdflags |= NVMEADM_O_ID_COMMON_NS;
                        break;

                case 'a':
                        npa->npa_cmdflags |= NVMEADM_O_ID_ALLOC_NS;
                        break;

                case 'c':
                        npa->npa_cmdflags |= NVMEADM_O_ID_CTRL_LIST;
                        break;

                case 'n':
                        npa->npa_cmdflags |= NVMEADM_O_ID_NSID_LIST;
                        break;

                case '?':
                        errx(-1, "unknown option: -%c", optopt);

                case ':':
                        errx(-1, "option -%c requires an argument", optopt);
                }
        }
}

static void
usage_identify_ctrl(const char *c_name)
{
        (void) fprintf(stderr, "%s [-C | -c | [-a] -n] <ctl>[,...]\n\n"
            "  Print detailed information about the specified NVMe "
            "controllers.\n", c_name);
}

static int
do_identify_ctrl(const nvme_process_arg_t *npa)
{
        boolean_t alloc = B_FALSE;

        if (npa->npa_ns != NULL)
                errx(-1, "identify-controller cannot be used on namespaces");

        if (npa->npa_argc > 0) {
                errx(-1, "%s passed extraneous arguments starting with %s",
                    npa->npa_cmd->c_name, npa->npa_argv[0]);
        }

        if ((npa->npa_cmdflags & NVMEADM_O_ID_COMMON_NS) != 0 &&
            npa->npa_cmdflags != NVMEADM_O_ID_COMMON_NS) {
                errx(-1, "-C cannot be combined with other flags");
        }

        if ((npa->npa_cmdflags & NVMEADM_O_ID_CTRL_LIST) != 0 &&
            npa->npa_cmdflags != NVMEADM_O_ID_CTRL_LIST) {
                errx(-1, "-c cannot be combined with other flags");
        }

        if ((npa->npa_cmdflags & NVMEADM_O_ID_ALLOC_NS) != 0 &&
            npa->npa_cmdflags !=
            (NVMEADM_O_ID_ALLOC_NS | NVMEADM_O_ID_NSID_LIST)) {
                errx(-1, "-a can only be used together with -n");
        }

        if ((npa->npa_cmdflags & NVMEADM_O_ID_ALLOC_NS) != 0) {
                alloc = B_TRUE;
        }

        if ((npa->npa_cmdflags & NVMEADM_O_ID_COMMON_NS) != 0) {
                const nvme_identify_nsid_t *idns;

                if (!nvme_ctrl_info_common_ns(npa->npa_ctrl_info, &idns)) {
                        nvmeadm_ctrl_info_warn(npa, "failed to get common "
                            "namespace information for %s", npa->npa_name);
                        return (-1);
                }

                (void) printf("%s: ", npa->npa_name);
                nvme_print_identify_nsid(idns, npa->npa_version);
        } else if ((npa->npa_cmdflags & NVMEADM_O_ID_NSID_LIST) != 0) {
                const char *caption;
                uint32_t cns;
                nvme_identify_nsid_list_t *idnslist;
                nvme_id_req_t *req;

                if (alloc) {
                        caption = "Identify Allocated Namespace List";
                        cns = NVME_IDENTIFY_NSID_ALLOC_LIST;
                } else {
                        caption = "Identify Active Namespace List";
                        cns = NVME_IDENTIFY_NSID_LIST;
                }

                if ((idnslist = malloc(NVME_IDENTIFY_BUFSIZE)) == NULL) {
                        err(-1, "failed to allocate identify buffer size");
                }

                if (!nvme_id_req_init_by_cns(npa->npa_ctrl, NVME_CSI_NVM, cns,
                    &req)) {
                        nvmeadm_fatal(npa, "failed to initialize %s request",
                            caption);
                }

                /*
                 * Always set the NSID for these requests to NSID 0 so that way
                 * we can start the list at the beginning. When we encounter
                 * devices with more than 1024 NSIDs then we'll need to issue
                 * additional requests.
                 */
                if (!nvme_id_req_set_nsid(req, 0) ||
                    !nvme_id_req_set_output(req, idnslist,
                    NVME_IDENTIFY_BUFSIZE)) {
                        nvmeadm_fatal(npa, "failed to set required fields for "
                            "identify request");
                }

                if (!nvme_id_req_exec(req)) {
                        nvmeadm_fatal(npa, "failed to execute identify "
                            "request");
                }
                nvme_id_req_fini(req);

                (void) printf("%s: ", npa->npa_name);

                nvme_print_identify_nsid_list(caption, idnslist);
                free(idnslist);
        } else if ((npa->npa_cmdflags & NVMEADM_O_ID_CTRL_LIST) != 0) {
                nvme_identify_ctrl_list_t *ctlist;
                nvme_id_req_t *req;

                if ((ctlist = malloc(NVME_IDENTIFY_BUFSIZE)) == NULL) {
                        err(-1, "failed to allocate identify buffer size");
                }

                if (!nvme_id_req_init_by_cns(npa->npa_ctrl, NVME_CSI_NVM,
                    NVME_IDENTIFY_CTRL_LIST, &req)) {
                        nvmeadm_fatal(npa, "failed to initialize identify "
                            "request");
                }

                if (!nvme_id_req_set_ctrlid(req, 0) ||
                    !nvme_id_req_set_output(req, ctlist,
                    NVME_IDENTIFY_BUFSIZE)) {
                        nvmeadm_fatal(npa, "failed to set required fields for "
                            "identify request");
                }
                if (!nvme_id_req_exec(req)) {
                        nvmeadm_fatal(npa, "failed to execute identify "
                            "request");
                }
                nvme_id_req_fini(req);

                (void) printf("%s: ", npa->npa_name);
                nvme_print_identify_ctrl_list("Identify Controller List",
                    ctlist);
                free(ctlist);
        } else {
                uint32_t mpsmin;

                if (!nvme_ctrl_info_pci_mps_min(npa->npa_ctrl_info,
                    &mpsmin)) {
                        nvmeadm_ctrl_info_fatal(npa, "failed to get minimum "
                            "memory page size");
                }

                (void) printf("%s: ", npa->npa_name);
                nvme_print_identify_ctrl(npa->npa_idctl, mpsmin,
                    npa->npa_version);
        }

        return (0);
}

static void
optparse_identify_ns(nvme_process_arg_t *npa)
{
        int c;

        while ((c = getopt(npa->npa_argc, npa->npa_argv, ":cd")) != -1) {
                switch (c) {
                case 'c':
                        npa->npa_cmdflags |= NVMEADM_O_ID_CTRL_LIST;
                        break;

                case 'd':
                        npa->npa_cmdflags |= NVMEADM_O_ID_DESC_LIST;
                        break;

                case '?':
                        errx(-1, "unknown option: -%c", optopt);

                case ':':
                        errx(-1, "option -%c requires an argument", optopt);
                }
        }
}

static void
usage_identify_ns(const char *c_name)
{
        (void) fprintf(stderr, "%s [-c | -d ] <ctl>/<ns>[,...]\n\n"
            "  Print detailed information about the specified NVMe "
            "namespaces.\n", c_name);
}

static int
do_identify_ns(const nvme_process_arg_t *npa)
{
        uint32_t nsid;

        if (npa->npa_ns == NULL)
                errx(-1, "identify-namespace cannot be used on controllers");

        if (npa->npa_argc > 0) {
                errx(-1, "%s passed extraneous arguments starting with %s",
                    npa->npa_cmd->c_name, npa->npa_argv[0]);
        }

        if ((npa->npa_cmdflags & NVMEADM_O_ID_CTRL_LIST) != 0 &&
            npa->npa_cmdflags != NVMEADM_O_ID_CTRL_LIST) {
                errx(-1, "-c cannot be combined with other flags");
        }

        if ((npa->npa_cmdflags & NVMEADM_O_ID_DESC_LIST) != 0 &&
            npa->npa_cmdflags != NVMEADM_O_ID_DESC_LIST) {
                errx(-1, "-d cannot be combined with other flags");
        }

        if ((npa->npa_cmdflags & NVMEADM_O_ID_ALLOC_NS) != 0) {
                errx(-1, "-a cannot be used on namespaces");
        }

        nsid = nvme_ns_info_nsid(npa->npa_ns_info);

        if ((npa->npa_cmdflags & NVMEADM_O_ID_CTRL_LIST) != 0) {
                nvme_identify_ctrl_list_t *ctlist;
                nvme_id_req_t *req;

                if ((ctlist = malloc(NVME_IDENTIFY_BUFSIZE)) == NULL) {
                        err(-1, "failed to allocate identify buffer size");
                }

                if (!nvme_id_req_init_by_cns(npa->npa_ctrl, NVME_CSI_NVM,
                    NVME_IDENTIFY_NSID_CTRL_LIST, &req)) {
                        nvmeadm_fatal(npa, "failed to initialize identify "
                            "request");
                }

                if (!nvme_id_req_set_nsid(req, nsid) ||
                    !nvme_id_req_set_ctrlid(req, 0) ||
                    !nvme_id_req_set_output(req, ctlist,
                    NVME_IDENTIFY_BUFSIZE)) {
                        nvmeadm_fatal(npa, "failed to set required fields for "
                            "identify request");
                }

                if (!nvme_id_req_exec(req)) {
                        nvmeadm_fatal(npa, "failed to execute identify "
                            "request");
                }
                nvme_id_req_fini(req);

                (void) printf("%s: ", npa->npa_name);
                nvme_print_identify_ctrl_list(
                    "Identify Attached Controller List", ctlist);
                free(ctlist);
        } else if ((npa->npa_cmdflags & NVMEADM_O_ID_DESC_LIST) != 0) {
                nvme_identify_nsid_desc_t *nsdesc;
                nvme_id_req_t *req;

                if ((nsdesc = malloc(NVME_IDENTIFY_BUFSIZE)) == NULL) {
                        err(-1, "failed to allocate identify buffer size");
                }

                if (!nvme_id_req_init_by_cns(npa->npa_ctrl, NVME_CSI_NVM,
                    NVME_IDENTIFY_NSID_DESC, &req)) {
                        nvmeadm_fatal(npa, "failed to initialize identify "
                            "request");
                }

                if (!nvme_id_req_set_nsid(req, nsid) ||
                    !nvme_id_req_set_output(req, nsdesc,
                    NVME_IDENTIFY_BUFSIZE)) {
                        nvmeadm_fatal(npa, "failed to set required fields for "
                            "identify request");
                }

                if (!nvme_id_req_exec(req)) {
                        nvmeadm_fatal(npa, "failed to execute identify "
                            "request");
                }
                nvme_id_req_fini(req);

                (void) printf("%s: ", npa->npa_name);
                nvme_print_identify_nsid_desc(nsdesc);
                free(nsdesc);
        } else {
                const nvme_identify_nsid_t *idns;

                (void) printf("%s: ", npa->npa_name);
                idns = nvme_ns_info_identify(npa->npa_ns_info);
                nvme_print_identify_nsid(idns, npa->npa_version);
        }

        return (0);
}

static void
optparse_identify(nvme_process_arg_t *npa)
{
        int c;

        while ((c = getopt(npa->npa_argc, npa->npa_argv, ":Cacdn")) != -1) {
                switch (c) {
                case 'C':
                        npa->npa_cmdflags |= NVMEADM_O_ID_COMMON_NS;
                        break;

                case 'a':
                        npa->npa_cmdflags |= NVMEADM_O_ID_ALLOC_NS;
                        break;

                case 'c':
                        npa->npa_cmdflags |= NVMEADM_O_ID_CTRL_LIST;
                        break;

                case 'd':
                        npa->npa_cmdflags |= NVMEADM_O_ID_DESC_LIST;
                        break;

                case 'n':
                        npa->npa_cmdflags |= NVMEADM_O_ID_NSID_LIST;
                        break;

                case '?':
                        errx(-1, "unknown option: -%c", optopt);

                case ':':
                        errx(-1, "option -%c requires an argument", optopt);

                }
        }

        if ((npa->npa_cmdflags & NVMEADM_O_ID_ALLOC_NS) != 0 &&
            (npa->npa_cmdflags &
            ~(NVMEADM_O_ID_ALLOC_NS | NVMEADM_O_ID_NSID_LIST)) != 0) {
                errx(-1, "-a can only be used alone or together with -n");
        }

        if ((npa->npa_cmdflags & NVMEADM_O_ID_COMMON_NS) != 0 &&
            npa->npa_cmdflags != NVMEADM_O_ID_COMMON_NS) {
                errx(-1, "-C cannot be combined with other flags");

        }

        if ((npa->npa_cmdflags & NVMEADM_O_ID_CTRL_LIST) != 0 &&
            npa->npa_cmdflags != NVMEADM_O_ID_CTRL_LIST) {
                errx(-1, "-c cannot be combined with other flags");
        }

        if ((npa->npa_cmdflags & NVMEADM_O_ID_DESC_LIST) != 0 &&
            npa->npa_cmdflags != NVMEADM_O_ID_DESC_LIST) {
                errx(-1, "-d cannot be combined with other flags");
        }
}

static void
usage_identify(const char *c_name)
{
        (void) fprintf(stderr,
            "%s [ -C | -c | -d | [-a] -n ] <ctl>[/<ns>][,...]\n\n"
            "  Print detailed information about the specified NVMe "
            "controllers and/or name-\n  spaces.\n", c_name);
}

static int
do_identify(const nvme_process_arg_t *npa)
{
        if (npa->npa_argc > 0) {
                errx(-1, "%s passed extraneous arguments starting with %s",
                    npa->npa_cmd->c_name, npa->npa_argv[0]);
        }

        if (npa->npa_ns != NULL) {
                if ((npa->npa_cmdflags & NVMEADM_O_ID_COMMON_NS) != 0)
                        errx(-1, "-C cannot be used on namespaces");

                if ((npa->npa_cmdflags & NVMEADM_O_ID_ALLOC_NS) != 0)
                        errx(-1, "-a cannot be used on namespaces");

                if ((npa->npa_cmdflags & NVMEADM_O_ID_NSID_LIST) != 0)
                        errx(-1, "-n cannot be used on namespaces");

                return (do_identify_ns(npa));
        } else {
                if ((npa->npa_cmdflags & NVMEADM_O_ID_DESC_LIST) != 0)
                        errx(-1, "-d cannot be used on controllers");

                return (do_identify_ctrl(npa));
        }
}

static void
optparse_list_logs(nvme_process_arg_t *npa)
{
        int c;
        uint_t oflags = 0;
        boolean_t parse = B_FALSE;
        const char *fields = NULL;
        char *scope = NULL;
        ofmt_status_t oferr;
        nvmeadm_list_logs_t *nll;

        if ((nll = calloc(1, sizeof (nvmeadm_list_logs_t))) == NULL) {
                err(-1, "failed to allocate memory to track log information");
        }

        npa->npa_cmd_arg = nll;

        while ((c = getopt(npa->npa_argc, npa->npa_argv, ":aHo:ps:")) != -1) {
                switch (c) {
                case 'a':
                        nll->nll_unimpl = B_TRUE;
                        break;
                case 'H':
                        oflags |= OFMT_NOHEADER;
                        break;
                case 'o':
                        fields = optarg;
                        break;
                case 'p':
                        parse = B_TRUE;
                        oflags |= OFMT_PARSABLE;
                        break;
                case 's':
                        scope = optarg;
                        break;
                case '?':
                        errx(-1, "unknown option: -%c", optopt);
                case ':':
                        errx(-1, "option -%c requires an argument", optopt);
                }
        }

        if (!parse) {
                oflags |= OFMT_WRAP;
        }

        if (parse && fields == NULL) {
                errx(-1, "parsable mode (-p) requires fields specified with "
                    "-o");
        }

        if (fields == NULL) {
                if (nll->nll_unimpl) {
                        fields = nvmeadm_list_logs_fields_impl;
                } else {
                        fields = nvmeadm_list_logs_fields;
                }
        }

        if (scope != NULL) {
                const char *str;

                while ((str = strsep(&scope, ",")) != NULL) {
                        if (strcasecmp(str, "nvm") == 0) {
                                nll->nll_scope |= NVME_LOG_SCOPE_NVM;
                        } else if (strcasecmp(str, "ns") == 0 ||
                            strcasecmp(str, "namespace") == 0) {
                                nll->nll_scope |= NVME_LOG_SCOPE_NS;
                        } else if (strcasecmp(str, "ctrl") == 0 ||
                            strcasecmp(str, "controller") == 0) {
                                nll->nll_scope |= NVME_LOG_SCOPE_CTRL;
                        } else {
                                errx(-1, "unknown scope string: '%s'; valid "
                                    "values are 'nvm', 'namespace', and "
                                    "'controller'", str);
                        }
                }
        }

        oferr = ofmt_open(fields, nvmeadm_list_logs_ofmt, oflags, 0,
            &npa->npa_ofmt);
        ofmt_check(oferr, B_TRUE, npa->npa_ofmt, nvme_oferr, warnx);

        if (npa->npa_argc - optind > 1) {
                nll->nll_nfilts = npa->npa_argc - optind - 1;
                nll->nll_filts = npa->npa_argv + optind + 1;
                nll->nll_used = calloc(nll->nll_nfilts, sizeof (boolean_t));
                if (nll->nll_used == NULL) {
                        err(-1, "failed to allocate memory for tracking log "
                            "page filters");
                }
        }
}

static void
usage_list_logs(const char *c_name)
{
        (void) fprintf(stderr, "%s [-H] [-o field,[...] [-p]] [-s scope,[...]] "
            "[-a]\n\t  <ctl>[/<ns>][,...] [logpage...]\n\n"
            "  List log pages supported by controllers or namespaces.\n",
            c_name);
}

static boolean_t
do_list_logs_match(const nvme_log_disc_t *disc, nvmeadm_list_logs_t *nll)
{
        if (!nll->nll_unimpl && !nvme_log_disc_impl(disc)) {
                return (B_FALSE);
        }

        if (nll->nll_nfilts <= 0) {
                return (B_TRUE);
        }

        /*
         * Check all filters. If we have multiple specified that would match we
         * want that to count.
         */
        boolean_t match = B_FALSE;
        for (int i = 0; i < nll->nll_nfilts; i++) {
                if (strcmp(nvme_log_disc_name(disc), nll->nll_filts[i]) == 0) {
                        nll->nll_used[i] = B_TRUE;
                        match = B_TRUE;
                        continue;
                }

                size_t naliases = nvme_log_disc_naliases(disc);
                const char *const *aliases = nvme_log_disc_aliases(disc);
                for (size_t a = 0; a < naliases; a++) {
                        if (strcmp(aliases[a], nll->nll_filts[i]) == 0) {
                                nll->nll_used[i] = B_TRUE;
                                match = B_TRUE;
                                break;
                        }
                }
        }

        return (match);
}

static int
do_list_logs(const nvme_process_arg_t *npa)
{
        nvme_log_disc_scope_t scope;
        nvme_log_iter_t *iter;
        nvme_iter_t ret;
        const nvme_log_disc_t *disc;
        nvmeadm_list_logs_t *nll = npa->npa_cmd_arg;

        if (nll->nll_scope != 0) {
                scope = nll->nll_scope;
        } else if (npa->npa_ns != NULL) {
                scope = NVME_LOG_SCOPE_NS;
        } else {
                scope = NVME_LOG_SCOPE_CTRL | NVME_LOG_SCOPE_NVM;
        }

        if (!nvme_log_discover_init(npa->npa_ctrl, scope, 0, &iter)) {
                nvmeadm_warn(npa, "failed to iterate logs on %s",
                    npa->npa_ctrl_name);
                return (-1);
        }

        while ((ret = nvme_log_discover_step(iter, &disc)) == NVME_ITER_VALID) {
                if (do_list_logs_match(disc, nll)) {
                        nvmeadm_list_logs_ofmt_arg_t print;

                        print.nlloa_name = npa->npa_name;
                        print.nlloa_disc = disc;
                        ofmt_print(npa->npa_ofmt, &print);
                        nll->nll_nprint++;
                }
        }

        nvme_log_discover_fini(iter);
        if (ret == NVME_ITER_ERROR) {
                nvmeadm_warn(npa, "failed to iterate logs on %s",
                    npa->npa_ctrl_name);
                return (-1);
        }

        for (int i = 0; i < nll->nll_nfilts; i++) {
                if (!nll->nll_used[i]) {
                        warnx("log page filter '%s' did match any log pages",
                            nll->nll_filts[i]);
                        exitcode = -1;
                }
        }

        if (nll->nll_nprint == 0) {
                if (nll->nll_nfilts == 0) {
                        warnx("no log pages found for %s", npa->npa_name);
                }
                exitcode = -1;
        }

        return (exitcode);
}

static void
usage_get_logpage(const char *c_name)
{
        (void) fprintf(stderr, "%s [-O file | -x | -p -o field,[...] [-H]]\n"
            "\t  <ctl>[/<ns>][,...] <logpage> [filter...]\n\n"
            "  Print the specified log page of the specified NVMe "
            "controllers and/or name-\n  spaces. Run \"nvmeadm list-logpages\" "
            "for supported log pages. All devices\n  support error, health, "
            "and firmware.\n", c_name);
}

static void
usage_firmware_list(const char *c_name)
{
        (void) fprintf(stderr, "%s <ctl>\n\n"
            "  Print the log page that contains the list of firmware "
            "images installed on the specified NVMe controller.\n", c_name);
}

static uint64_t
do_get_logpage_size(const nvme_process_arg_t *npa, nvme_log_disc_t *disc,
    nvme_log_req_t *req)
{
        uint64_t len, ret;
        void *buf;
        nvme_log_size_kind_t kind;

        kind = nvme_log_disc_size(disc, &len);
        if (kind != NVME_LOG_SIZE_K_VAR) {
                return (len);
        }

        /*
         * We have a log with a variable length size. To determine the actual
         * size we must actually determine the full length of this.
         */
        if ((buf = malloc(len)) == NULL) {
                errx(-1, "failed to allocate %zu byte buffer to get log "
                    "page size", len);
        }

        if (!nvme_log_req_set_output(req, buf, len)) {
                nvmeadm_fatal(npa, "failed to set output parameters to "
                    "determine log length");
        }

        if (!nvme_log_req_exec(req)) {
                nvmeadm_fatal(npa, "failed to execute log request %s to "
                    "determine log length", npa->npa_argv[0]);
        }

        if (!nvme_log_disc_calc_size(disc, &ret, buf, len)) {
                errx(-1, "failed to determine full %s log length",
                    npa->npa_argv[0]);
        }

        free(buf);
        return (ret);
}

static void
do_get_logpage_dump(const void *buf, size_t len, const char *file)
{
        size_t off = 0;
        int fd = open(file, O_WRONLY | O_TRUNC | O_CREAT, 0644);

        if (fd < 0) {
                err(-1, "failed to create output file %s", file);
        }

        while (len > 0) {
                ssize_t ret = write(fd, buf + off, len - off);
                if (ret < 0) {
                        err(EXIT_FAILURE, "failed to write log data to file %s "
                            "at offset %zu", file, off);
                }

                off += (size_t)ret;
                len -= (size_t)ret;
        }

        (void) close(fd);
}

/*
 * Here we need to explicitly attempt to release any context that has previously
 * existed for the persistent event log. It is fine if none exists as the
 * controller is required not to error. However, if we don't do this and attempt
 * to establish a new context, then it will generate an error.
 *
 * We'll use our existing request, which doesn't ask for data yet and issue the
 * get log page request with the LSP in question. After it is completed, we'll
 * reset the LSP to establish a context.
 */
static void
do_get_logpage_pev_relctx(const nvme_process_arg_t *npa, nvme_log_req_t *req)
{
        uint32_t buf;

        if (!nvme_log_req_set_lsp(req, NVME_PEV_LSP_REL_CTX)) {
                nvmeadm_fatal(npa, "failed to set lsp to release the "
                    "persistent event log context");
        }

        /*
         * In NVMe 2.0 the spec made it explicit that the controller was
         * supposed to ignore the length and offset for a request to release the
         * context; however, that wasn't present in NVMe 1.4. The number of
         * dwords part of the get log page command is a zeros based value,
         * meaning there is no explicit way to request zero bytes. Rather than
         * trust that all controllers get this right (especially when it wasn't
         * exactly specified in NVMe 1.4), we just toss a throwaway buffer here.
         */
        if (!nvme_log_req_set_output(req, &buf, sizeof (buf))) {
                nvmeadm_fatal(npa, "failed to set zero log length for "
                    "persistent event log release context");
        }

        if (!nvme_log_req_exec(req)) {
                nvmeadm_fatal(npa, "failed to execute log request %s to "
                    "release the event log context", npa->npa_argv[0]);
        }

        if (!nvme_log_req_set_lsp(req, NVME_PEV_LSP_EST_CTX_READ)) {
                nvmeadm_fatal(npa, "failed to set lsp to establish the "
                    "persistent event log context");
        }

        /*
         * Make sure that our stack buffer is no longer part of the log request.
         */
        if (!nvme_log_req_clear_output(req)) {
                nvmeadm_fatal(npa, "failed to clear output from persistent "
                    "event log release context");
        }
}

/*
 * Fill in the baseline context for a phyeye request. In this case we always
 * perform a normal read command and set the quality to "good" (the
 * lowest quality). Configurable use of this is driven through the phyeye
 * commands in nvmeadm_phyeye.c. In addition, we must always set the controller
 * ID in this request to our own.
 */
static void
do_get_logpage_phyeye_ctx(const nvme_process_arg_t *npa, nvme_log_req_t *req)
{
        nvme_eom_lsp_t lsp;

        (void) memset(&lsp, 0, sizeof (lsp));
        lsp.nel_mqual = NVME_EOM_LSP_MQUAL_GOOD;
        lsp.nel_act = NVME_EOM_LSP_READ;

        if (!nvme_log_req_set_lsp(req, lsp.r)) {
                nvmeadm_fatal(npa, "failed to set lsp for host phyeye");
        }

        if (!nvme_log_req_set_lsi(req, npa->npa_idctl->id_cntlid)) {
                nvmeadm_fatal(npa, "failed to set lsi for host phyeye");
        }
}

static int
do_get_logpage_common(const nvme_process_arg_t *npa, const char *page,
    nvmeadm_field_filt_t *filts, size_t nfilts)
{
        int ret = 0;
        nvme_log_disc_t *disc;
        nvme_log_req_t *req;
        nvme_log_disc_scope_t scope;
        void *buf;
        size_t toalloc;
        nvmeadm_get_logpage_t *log = npa->npa_cmd_arg;

        /*
         * If we have enough information to identify a log-page via libnvme (or
         * in the future take enough options to allow us to actually do this
         * manually), then we will fetch it. If we don't know how to print it,
         * then we'll just hex dump it for now.
         */
        if (!nvme_log_req_init_by_name(npa->npa_ctrl, page, 0, &disc, &req)) {
                nvmeadm_fatal(npa, "could not initialize log request for %s",
                    page);
        }

        if (npa->npa_ns != NULL) {
                scope = NVME_LOG_SCOPE_NS;
        } else {
                scope = NVME_LOG_SCOPE_CTRL | NVME_LOG_SCOPE_NVM;
        }

        if ((scope & nvme_log_disc_scopes(disc)) == 0) {
                errx(-1, "log page %s does not support operating on %s", page,
                    npa->npa_ns != NULL ? "namespaces" : "controllers");
        }

        /*
         * In the future we should add options to allow one to specify and set
         * the fields for the lsp, lsi, etc. and set them here. Some log pages
         * need a specific lsp set and special handling related to contexts. Do
         * that now.
         */
        switch (nvme_log_disc_lid(disc)) {
        case NVME_LOGPAGE_PEV:
                do_get_logpage_pev_relctx(npa, req);
                break;
        case NVME_LOGPAGE_TELMHOST:
                return (do_get_logpage_telemetry(npa, disc, req));
        case NVME_LOGPAGE_PHYEYE:
                do_get_logpage_phyeye_ctx(npa, req);
                break;
        default:
                break;
        }

        if (npa->npa_ns != NULL) {
                uint32_t nsid = nvme_ns_info_nsid(npa->npa_ns_info);

                if (!nvme_log_req_set_nsid(req, nsid)) {
                        nvmeadm_fatal(npa, "failed to set log request "
                            "namespace ID to 0x%x", nsid);
                }
        }

        /*
         * The output size should be the last thing that we determine as we may
         * need to issue a log request to figure out how much data we should
         * actually be reading.
         */
        toalloc = do_get_logpage_size(npa, disc, req);
        buf = malloc(toalloc);
        if (buf == NULL) {
                err(-1, "failed to allocate %zu bytes for log "
                    "request %s", toalloc, page);
        }

        if (!nvme_log_req_set_output(req, buf, toalloc)) {
                nvmeadm_fatal(npa, "failed to set output parameters");
        }

        /*
         * Again, we need to potentially adjust specific LSP values here for the
         * various contexts that exist. Note that we are reusing the existing
         * request so if the prior values for the initial request are fine, then
         * we can just leave it there.
         */
        switch (nvme_log_disc_lid(disc)) {
        case NVME_LOGPAGE_PEV:
                if (!nvme_log_req_set_lsp(req, NVME_PEV_LSP_READ)) {
                        nvmeadm_fatal(npa, "failed to set lsp to read the "
                            "persistent event log");
                }
                break;
        default:
                break;
        }

        if (!nvme_log_req_exec(req)) {
                nvmeadm_fatal(npa, "failed to execute log request %s",
                    npa->npa_argv[0]);
        }

        if (log != NULL && log->ngl_output != NULL) {
                do_get_logpage_dump(buf, toalloc, log->ngl_output);
                goto done;
        } else if (log != NULL && log->ngl_hex) {
                nvmeadm_dump_hex(buf, toalloc);
                goto done;
        }

        if (npa->npa_ofmt == NULL) {
                (void) printf("%s: ", npa->npa_name);
        }
        if (strcmp(page, "error") == 0) {
                size_t nlog = toalloc / sizeof (nvme_error_log_entry_t);
                if (nfilts > 0) {
                        warnx("log filters are not currently supported with "
                            "the %s log page", page);
                        ret = -1;
                }

                nvme_print_error_log(nlog, buf, npa->npa_version);
        } else if (strcmp(page, "health") == 0) {
                if (nfilts > 0) {
                        warnx("log filters are not currently supported with "
                            "the %s log page", page);
                        ret = -1;
                }

                nvme_print_health_log(buf, npa->npa_idctl, npa->npa_version);
        } else if (strcmp(page, "firmware") == 0) {
                if (nfilts > 0) {
                        warnx("log filters are not currently supported with "
                            "the %s log page", page);
                        ret = -1;
                }

                nvme_print_fwslot_log(buf, npa->npa_idctl);
        } else {
                if (npa->npa_ofmt == NULL) {
                        (void) printf("%s (%s)\n", nvme_log_disc_desc(disc),
                            page);
                }
                if (!nvmeadm_log_page_fields(npa, page, buf, toalloc, filts,
                    nfilts, 0)) {
                        ret = -1;
                }
        }

done:
        free(buf);
        nvme_log_disc_free(disc);
        nvme_log_req_fini(req);

        return (ret);
}

static int
do_get_logpage_fwslot(const nvme_process_arg_t *npa)
{
        if (npa->npa_argc >= 1) {
                warnx("no additional arguments may be specified to %s",
                    npa->npa_cmd->c_name);
                usage(npa->npa_cmd);
                exit(-1);
        }

        return (do_get_logpage_common(npa, "firmware", NULL, 0));
}

static void
optparse_get_logpage(nvme_process_arg_t *npa)
{
        int c;
        const char *fields = NULL;
        bool parse = false;
        uint_t oflags = 0;
        nvmeadm_get_logpage_t *log;
        uint_t count = 0;

        if ((log = calloc(1, sizeof (nvmeadm_get_logpage_t))) == NULL) {
                err(-1, "failed to allocate memory to track log page "
                    "information");
        }

        while ((c = getopt(npa->npa_argc, npa->npa_argv, ":Ho:O:px")) != -1) {
                switch (c) {
                case 'H':
                        oflags |= OFMT_NOHEADER;
                        break;
                case 'o':
                        fields = optarg;
                        break;
                case 'O':
                        log->ngl_output = optarg;
                        count++;
                        break;
                case 'p':
                        parse = true;
                        oflags |= OFMT_PARSABLE;
                        count++;
                        break;
                case 'x':
                        log->ngl_hex = true;
                        count++;
                        break;
                case '?':
                        errx(-1, "unknown option: -%c", optopt);
                case ':':
                        errx(-1, "option -%c requires an argument", optopt);
                }
        }

        if (parse && fields == NULL) {
                errx(-1, "parsable mode (-p) requires fields specified with "
                    "-o");
        }

        if (!parse && fields != NULL) {
                errx(-1, "output field selection (-o) only usable in "
                    "parsable mode (-p)");
        }

        if (!parse && (oflags & OFMT_NOHEADER) != 0) {
                errx(-1, "omitting headers (-H) only usable in parsable mode "
                    "(-p)");
        }

        if (count > 1) {
                errx(-1, "only one of parsable output (-p), outputing to a "
                    "file (-O), and hexadecimal output (-x) may be requested");
        }

        if (parse) {
                ofmt_status_t oferr = ofmt_open(fields, nvmeadm_field_ofmt,
                    oflags, 0, &npa->npa_ofmt);
                ofmt_check(oferr, B_TRUE, npa->npa_ofmt, nvme_oferr, warnx);
        }

        npa->npa_cmd_arg = log;
}

static void
do_logpage_filts(const nvme_process_arg_t *npa, nvmeadm_field_filt_t **filtsp,
    size_t *nfiltsp)
{
        size_t nfilts;
        nvmeadm_field_filt_t *filts;

        *filtsp = NULL;
        *nfiltsp = 0;

        if (npa->npa_argc <= 1)
                return;

        nfilts = (size_t)npa->npa_argc - 1;
        filts = calloc(nfilts, sizeof (nvmeadm_field_filt_t));
        if (filts == NULL) {
                err(-1, "failed to allocate memory for filter "
                    "tracking");
        }

        for (size_t i = 0; i < nfilts; i++) {
                filts[i].nff_len = strlen(npa->npa_argv[i + 1]);
                filts[i].nff_str = npa->npa_argv[i + 1];
        }

        *filtsp = filts;
        *nfiltsp = nfilts;
}

static int
do_get_logpage(const nvme_process_arg_t *npa)
{
        size_t nfilts = 0;
        nvmeadm_field_filt_t *filts = NULL;
        nvmeadm_get_logpage_t *log = npa->npa_cmd_arg;

        if (npa->npa_argc < 1) {
                warnx("missing log page name");
                usage(npa->npa_cmd);
                exit(-1);
        }

        do_logpage_filts(npa, &filts, &nfilts);

        if (log != NULL && log->ngl_hex && nfilts > 0) {
                errx(-1, "hexadecimal output (-x) cannot be used with "
                    "log filter operands");
        }

        return (do_get_logpage_common(npa, npa->npa_argv[0], filts, nfilts));
}

static void
usage_print_logpage(const char *c_name)
{
        (void) fprintf(stderr, "%s -f file [-x | -p -o field,[...] [-H]] "
            "<logpage>\n\t  [filter...]\n\n"
            "  Print the specified log page from a file. Optional filters "
            "may be used to\n  restrict the log fields printed. See nvmeadm(8) "
            "for a list of all log\n  pages. Run \"nvmeadm list-logpages\" to "
            "see log pages a specific controller\n  supports.\n", c_name);
}

typedef struct {
        const char *pl_input;
        bool pl_hex;
} print_logpages_t;

static void
optparse_print_logpage(nvme_process_arg_t *npa)
{
        int c;
        const char *fields = NULL;
        bool parse = false;
        uint_t oflags = 0;
        print_logpages_t *pl;

        if ((pl = calloc(1, sizeof (print_logpages_t))) == NULL) {
                err(-1, "failed to allocate memory for option tracking");
        }

        while ((c = getopt(npa->npa_argc, npa->npa_argv, ":f:Ho:px")) != -1) {
                switch (c) {
                case 'f':
                        pl->pl_input = optarg;
                        break;
                case 'H':
                        oflags |= OFMT_NOHEADER;
                        break;
                case 'o':
                        fields = optarg;
                        break;
                case 'p':
                        parse = true;
                        oflags |= OFMT_PARSABLE;
                        break;
                case 'x':
                        pl->pl_hex = true;
                        break;
                case '?':
                        errx(-1, "unknown option: -%c", optopt);
                case ':':
                        errx(-1, "option -%c requires an argument", optopt);
                }
        }

        if (pl->pl_input == NULL) {
                errx(-1, "missing required input file to process (-f)");
        }

        if (parse && fields == NULL) {
                errx(-1, "parsable mode (-p) requires fields specified with "
                    "-o");
        }

        if (!parse && fields != NULL) {
                errx(-1, "output field selection (-o) only usable in "
                    "parsable mode (-p)");
        }

        if (!parse && (oflags & OFMT_NOHEADER) != 0) {
                errx(-1, "omitting headers (-H) only usable in parsable mode "
                    "(-p)");
        }

        if (pl->pl_hex && parse) {
                errx(-1, "only one of parsable output (-p) and heaxdecimal "
                    "output (-x) may be requested");
        }

        if (parse) {
                ofmt_status_t oferr = ofmt_open(fields, nvmeadm_field_ofmt,
                    oflags, 0, &npa->npa_ofmt);
                ofmt_check(oferr, B_TRUE, npa->npa_ofmt, nvme_oferr, warnx);
        }

        npa->npa_cmd_arg = pl;
}

static int
do_print_logpage(const nvme_process_arg_t *npa)
{
        int fd = -1, ret = 0;
        struct stat st;
        void *data;
        const char *logpage = NULL;
        size_t nfilts = 0;
        nvmeadm_field_filt_t *filts = NULL;
        print_logpages_t *pl = npa->npa_cmd_arg;
        nvmeadm_log_field_flag_t flag = 0;

        if (pl->pl_hex) {
                if (npa->npa_argc != 0) {
                        errx(-1, "log page and filters not supported with -x");
                }
        } else {
                if (npa->npa_argc == 0) {
                        errx(-1, "missing required log page");
                }

                logpage = npa->npa_argv[0];
                do_logpage_filts(npa, &filts, &nfilts);
                flag |= NVMEADM_LFF_CHECK_NAME;
        }

        if ((fd = open(pl->pl_input, O_RDONLY)) < 0) {
                err(-1, "failed to open input file %s", pl->pl_input);
        }

        if (fstat(fd, &st) != 0) {
                err(-1, "failed to stat %s", pl->pl_input);
        }

        if (st.st_size > NVMEADM_MAX_MMAP) {
                errx(-1, "%s file size of 0x%lx exceeds maximum allowed size "
                    "of 0x%llx", pl->pl_input, st.st_size, NVMEADM_MAX_MMAP);
        }

        data = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
        if (data == MAP_FAILED) {
                errx(-1, "failed to mmap %s", pl->pl_input);
        }

        if (!nvmeadm_log_page_fields(npa, logpage, data, st.st_size,
            filts, nfilts, flag)) {
                ret = -1;
        }

        VERIFY0(munmap(data, st.st_size));
        VERIFY0(close(fd));
        free(filts);

        return (ret);
}

static void
optparse_list_features(nvme_process_arg_t *npa)
{
        int c;
        uint_t oflags = 0;
        boolean_t parse = B_FALSE;
        const char *fields = NULL;
        nvmeadm_features_t *feat;
        ofmt_status_t oferr;

        if ((feat = calloc(1, sizeof (nvmeadm_features_t))) == NULL) {
                err(-1, "failed to allocate memory to track feature "
                    "information");
        }

        npa->npa_cmd_arg = feat;

        while ((c = getopt(npa->npa_argc, npa->npa_argv, ":aHo:p")) != -1) {
                switch (c) {
                case 'a':
                        feat->nf_unimpl = B_TRUE;
                        break;
                case 'H':
                        oflags |= OFMT_NOHEADER;
                        break;
                case 'o':
                        fields = optarg;
                        break;
                case 'p':
                        parse = B_TRUE;
                        oflags |= OFMT_PARSABLE;
                        break;
                case '?':
                        errx(-1, "unknown option: -%c", optopt);
                case ':':
                        errx(-1, "option -%c requires an argument", optopt);
                }
        }

        if (!parse) {
                oflags |= OFMT_WRAP;
        }

        if (parse && fields == NULL) {
                errx(-1, "parsable mode (-p) requires fields specified with "
                    "-o");
        }

        if (fields == NULL) {
                fields = nvmeadm_list_features_fields;
        }

        oferr = ofmt_open(fields, nvmeadm_list_features_ofmt, oflags, 0,
            &npa->npa_ofmt);
        ofmt_check(oferr, B_TRUE, npa->npa_ofmt, nvme_oferr, warnx);

        if (npa->npa_argc - optind > 1) {
                feat->nf_nfilts = (uint32_t)(npa->npa_argc - optind - 1);
                feat->nf_filts = npa->npa_argv + optind + 1;
                feat->nf_used = calloc(feat->nf_nfilts, sizeof (boolean_t));
                if (feat->nf_used == NULL) {
                        err(-1, "failed to allocate memory for tracking "
                            "feature filters");
                }
        }
}

static void
usage_list_features(const char *c_name)
{
        (void) fprintf(stderr, "%s [-a] [-H] [-o field,[...] [-p]] "
            "<ctl>[/<ns>][,...]\n\t  [feature...]\n\n"
            "  List features supported by controllers or namespaces.\n",
            c_name);
}

static boolean_t
do_features_match(const nvme_feat_disc_t *disc, nvmeadm_features_t *nf)
{
        if (nf->nf_nfilts == 0) {
                return (B_TRUE);
        }

        for (uint32_t i = 0; i < nf->nf_nfilts; i++) {
                const char *match = nf->nf_filts[i];
                long long fid;
                const char *err;

                if (strcmp(nvme_feat_disc_short(disc), match) == 0 ||
                    strcasecmp(nvme_feat_disc_spec(disc), match) == 0) {
                        nf->nf_used[i] = B_TRUE;
                        return (B_TRUE);
                }

                fid = strtonumx(match, 0, UINT32_MAX, &err, 0);
                if (err == NULL && fid == nvme_feat_disc_fid(disc)) {
                        nf->nf_used[i] = B_TRUE;
                        return (B_TRUE);
                }
        }

        return (B_FALSE);
}


/*
 * This is a common entry point for both list-features and get-features, which
 * iterate over all features and take action for each one.
 */
typedef void (*do_features_cb_f)(const nvme_process_arg_t *,
    const nvme_feat_disc_t *);
static int
do_features(const nvme_process_arg_t *npa, nvmeadm_features_t *nf,
    do_features_cb_f func)
{
        nvme_feat_scope_t scope;
        nvme_feat_iter_t *iter;
        nvme_iter_t ret;
        const nvme_feat_disc_t *disc;

        if (npa->npa_ns != NULL) {
                scope = NVME_FEAT_SCOPE_NS;
        } else {
                scope = NVME_FEAT_SCOPE_CTRL;
        }

        if (!nvme_feat_discover_init(npa->npa_ctrl, scope, 0, &iter)) {
                nvmeadm_warn(npa, "failed to iterate features on %s",
                    npa->npa_ctrl_name);
                return (-1);
        }

        while ((ret = nvme_feat_discover_step(iter, &disc)) ==
            NVME_ITER_VALID) {
                if (do_features_match(disc, nf)) {
                        if (!nf->nf_unimpl && nvme_feat_disc_impl(disc) ==
                            NVME_FEAT_IMPL_UNSUPPORTED) {
                                continue;
                        }

                        func(npa, disc);
                        nf->nf_nprint++;
                }
        }

        nvme_feat_discover_fini(iter);
        if (ret == NVME_ITER_ERROR) {
                nvmeadm_warn(npa, "failed to iterate features on %s",
                    npa->npa_ctrl_name);
                return (-1);
        }

        for (uint32_t i = 0; i < nf->nf_nfilts; i++) {
                if (!nf->nf_used[i]) {
                        warnx("feature filter '%s' did match any features",
                            nf->nf_filts[i]);
                        exitcode = -1;
                }
        }

        if (nf->nf_nprint == 0) {
                if (nf->nf_nfilts == 0) {
                        warnx("no features found for %s", npa->npa_name);
                }
                exitcode = -1;
        }

        return (exitcode);
}

static void
do_list_features_cb(const nvme_process_arg_t *npa, const nvme_feat_disc_t *disc)
{
        nvmeadm_list_features_ofmt_arg_t print;

        print.nlfoa_name = npa->npa_name;
        print.nlfoa_feat = disc;
        ofmt_print(npa->npa_ofmt, &print);
}

static int
do_list_features(const nvme_process_arg_t *npa)
{
        nvmeadm_features_t *nf = npa->npa_cmd_arg;

        return (do_features(npa, nf, do_list_features_cb));
}

static void
usage_get_features(const char *c_name)
{
        (void) fprintf(stderr, "%s <ctl>[/<ns>][,...] [<feature>[,...]]\n\n"
            "  Print the specified features of the specified NVMe controllers "
            "and/or\n  namespaces. Feature support varies on the controller.\n"
            "Run \"nvmeadm list-features <ctl>\" to see supported features.\n",
            c_name);
}

/*
 * The nvmeadm(8) get-features output has traditionally swallowed certain errors
 * for features that it considers unimplemented in tandem with the kernel. With
 * the introduction of libnvme and ioctl interface changes, the kernel no longer
 * caches information about features that are unimplemented.
 *
 * There are two cases that we currently swallow errors on and the following
 * must all be true:
 *
 * 1) We have a controller error.
 * 2) The system doesn't know whether the feature is implemented or not.
 * 3) The controller error indicates that we have an invalid field.
 *
 * There is one additional wrinkle that we are currently papering over due to
 * the history of nvmeadm swallowing errors. The error recovery feature was made
 * explicitly namespace-specific in NVMe 1.4. However, various NVMe 1.3 devices
 * will error if we ask for it without specifying a namespace. Conversely, older
 * devices will be upset if you do ask for a namespace. This case can be removed
 * once we better survey devices and come up with a heuristic for how to handle
 * this across older generations.
 *
 * If we add a single feature endpoint that gives flexibility over how the
 * feature are listed, then we should not swallow errors.
 */
static boolean_t
swallow_get_feat_err(const nvme_process_arg_t *npa,
    const nvme_feat_disc_t *disc)
{
        uint32_t sct, sc;

        if (nvme_ctrl_err(npa->npa_ctrl) != NVME_ERR_CONTROLLER) {
                return (B_FALSE);
        }

        nvme_ctrl_deverr(npa->npa_ctrl, &sct, &sc);
        if (nvme_feat_disc_impl(disc) == NVME_FEAT_IMPL_UNKNOWN &&
            sct == NVME_CQE_SCT_GENERIC && sc == NVME_CQE_SC_GEN_INV_FLD) {
                return (B_TRUE);
        }

        if (nvme_feat_disc_fid(disc) == NVME_FEAT_ERROR &&
            sct == NVME_CQE_SCT_GENERIC && (sc == NVME_CQE_SC_GEN_INV_FLD ||
            sc == NVME_CQE_SC_GEN_INV_NS)) {
                return (B_TRUE);
        }

        return (B_FALSE);
}

static boolean_t
do_get_feat_common(const nvme_process_arg_t *npa, const nvme_feat_disc_t *disc,
    uint32_t cdw11, uint32_t *cdw0, void **datap, size_t *lenp)
{
        nvme_get_feat_req_t *req = NULL;
        void *data = NULL;
        uint64_t datalen = 0;
        nvme_get_feat_fields_t fields = nvme_feat_disc_fields_get(disc);

        if (!nvme_get_feat_req_init_by_disc(npa->npa_ctrl, disc, &req)) {
                nvmeadm_warn(npa, "failed to initialize get feature request "
                    "for feature %s", nvme_feat_disc_short(disc));
                exitcode = -1;
                goto err;
        }

        if ((fields & NVME_GET_FEAT_F_CDW11) != 0 &&
            !nvme_get_feat_req_set_cdw11(req, cdw11)) {
                nvmeadm_warn(npa, "failed to set cdw11 to 0x%x for feature %s",
                    cdw11, nvme_feat_disc_short(disc));
                exitcode = -1;
                goto err;
        }

        if ((fields & NVME_GET_FEAT_F_DATA) != 0) {
                datalen = nvme_feat_disc_data_size(disc);
                VERIFY3U(datalen, !=, 0);
                data = malloc(datalen);
                if (data == NULL) {
                        err(-1, "failed to allocate %zu bytes for feature %s "
                            "data buffer", datalen, nvme_feat_disc_short(disc));
                }

                if (!nvme_get_feat_req_set_output(req, data, datalen)) {
                        nvmeadm_warn(npa, "failed to set output data for "
                            "feature %s", nvme_feat_disc_short(disc));
                        exitcode = -1;
                        goto err;
                }
        }

        if ((fields & NVME_GET_FEAT_F_NSID) != 0) {
                uint32_t nsid = nvme_ns_info_nsid(npa->npa_ns_info);

                if (!nvme_get_feat_req_set_nsid(req, nsid)) {
                        nvmeadm_warn(npa, "failed to set nsid to 0x%x for "
                            "feature %s", nsid, nvme_feat_disc_spec(disc));
                        exitcode = -1;
                        goto err;
                }
        }

        if (!nvme_get_feat_req_exec(req)) {
                if (!swallow_get_feat_err(npa, disc)) {
                        nvmeadm_warn(npa, "failed to get feature %s",
                            nvme_feat_disc_spec(disc));
                        exitcode = -1;
                }

                goto err;
        }

        if (!nvme_get_feat_req_get_cdw0(req, cdw0)) {
                nvmeadm_warn(npa, "failed to get cdw0 result data for %s",
                    nvme_feat_disc_spec(disc));
                goto err;
        }

        *datap = data;
        *lenp = datalen;
        nvme_get_feat_req_fini(req);
        return (B_TRUE);

err:
        free(data);
        nvme_get_feat_req_fini(req);
        return (B_FALSE);
}

static void
do_get_feat_temp_thresh_one(const nvme_process_arg_t *npa,
    const nvme_feat_disc_t *disc, const nvmeadm_feature_t *feat,
    const char *label, uint16_t tmpsel, uint16_t thsel)
{
        uint32_t cdw0;
        void *buf = NULL;
        size_t buflen;
        nvme_temp_threshold_t tt;

        tt.r = 0;
        tt.b.tt_tmpsel = tmpsel;
        tt.b.tt_thsel = thsel;

        /*
         * The printing function treats the buffer argument as the label to
         * print for this threshold.
         */
        if (!do_get_feat_common(npa, disc, tt.r, &cdw0, &buf, &buflen)) {
                return;
        }

        feat->f_print(cdw0, (void *)label, 0, npa->npa_idctl,
            npa->npa_version);
        free(buf);
}

/*
 * In NVMe 1.2, the specification allowed for up to 8 sensors to be on the
 * device and changed the main device to have a composite temperature sensor. As
 * a result, there is a set of thresholds for each sensor. In addition, they
 * added both an over-temperature and under-temperature threshold. Since most
 * devices don't actually implement all the sensors, we get the health page and
 * see which sensors have a non-zero value to determine how to proceed.
 */
static boolean_t
do_get_feat_temp_thresh(const nvme_process_arg_t *npa,
    const nvme_feat_disc_t *disc, const nvmeadm_feature_t *feat)
{
        nvme_log_req_t *req = NULL;
        nvme_log_disc_t *log_disc = NULL;
        size_t toalloc;
        void *buf = NULL;
        boolean_t ret = B_FALSE;
        const nvme_health_log_t *hlog;

        nvme_print(2, nvme_feat_disc_spec(disc), -1, NULL);
        do_get_feat_temp_thresh_one(npa, disc, feat,
            "Composite Over Temp. Threshold", 0, NVME_TEMP_THRESH_OVER);

        if (!nvme_version_check(npa, &nvme_vers_1v2)) {
                return (B_TRUE);
        }

        if (!nvme_log_req_init_by_name(npa->npa_ctrl, "health", 0, &log_disc,
            &req)) {
                nvmeadm_warn(npa, "failed to initialize health log page "
                    "request");
                return (B_FALSE);
        }

        toalloc = do_get_logpage_size(npa, log_disc, req);
        buf = malloc(toalloc);
        if (buf == NULL) {
                err(-1, "failed to allocate %zu bytes for health log page",
                    toalloc);
        }

        if (!nvme_log_req_set_output(req, buf, toalloc)) {
                nvmeadm_warn(npa, "failed to set output parameters for health "
                    "log page");
                goto out;
        }

        if (!nvme_log_req_exec(req)) {
                nvmeadm_warn(npa, "failed to retrieve the health log page");
                goto out;
        }

        /* cast required to prove our intentionality to smatch */
        hlog = (const nvme_health_log_t *)buf;

        do_get_feat_temp_thresh_one(npa, disc, feat,
            "Composite Under Temp. Threshold", 0, NVME_TEMP_THRESH_UNDER);
        if (hlog->hl_temp_sensor_1 != 0) {
                do_get_feat_temp_thresh_one(npa, disc, feat,
                    "Temp. Sensor 1 Over Temp. Threshold", 1,
                    NVME_TEMP_THRESH_OVER);
                do_get_feat_temp_thresh_one(npa, disc, feat,
                    "Temp. Sensor 1 Under Temp. Threshold", 1,
                    NVME_TEMP_THRESH_UNDER);
        }

        if (hlog->hl_temp_sensor_2 != 0) {
                do_get_feat_temp_thresh_one(npa, disc, feat,
                    "Temp. Sensor 2 Over Temp. Threshold", 2,
                    NVME_TEMP_THRESH_OVER);
                do_get_feat_temp_thresh_one(npa, disc, feat,
                    "Temp. Sensor 2 Under Temp. Threshold", 2,
                    NVME_TEMP_THRESH_UNDER);
        }

        if (hlog->hl_temp_sensor_3 != 0) {
                do_get_feat_temp_thresh_one(npa, disc, feat,
                    "Temp. Sensor 3 Over Temp. Threshold", 3,
                    NVME_TEMP_THRESH_OVER);
                do_get_feat_temp_thresh_one(npa, disc, feat,
                    "Temp. Sensor 3 Under Temp. Threshold", 3,
                    NVME_TEMP_THRESH_UNDER);
        }

        if (hlog->hl_temp_sensor_4 != 0) {
                do_get_feat_temp_thresh_one(npa, disc, feat,
                    "Temp. Sensor 4 Over Temp. Threshold", 4,
                    NVME_TEMP_THRESH_OVER);
                do_get_feat_temp_thresh_one(npa, disc, feat,
                    "Temp. Sensor 4 Under Temp. Threshold", 4,
                    NVME_TEMP_THRESH_UNDER);
        }

        if (hlog->hl_temp_sensor_5 != 0) {
                do_get_feat_temp_thresh_one(npa, disc, feat,
                    "Temp. Sensor 5 Over Temp. Threshold", 5,
                    NVME_TEMP_THRESH_OVER);
                do_get_feat_temp_thresh_one(npa, disc, feat,
                    "Temp. Sensor 5 Under Temp. Threshold", 5,
                    NVME_TEMP_THRESH_UNDER);
        }

        if (hlog->hl_temp_sensor_6 != 0) {
                do_get_feat_temp_thresh_one(npa, disc, feat,
                    "Temp. Sensor 6 Over Temp. Threshold", 6,
                    NVME_TEMP_THRESH_OVER);
                do_get_feat_temp_thresh_one(npa, disc, feat,
                    "Temp. Sensor 6 Under Temp. Threshold", 6,
                    NVME_TEMP_THRESH_UNDER);
        }

        if (hlog->hl_temp_sensor_7 != 0) {
                do_get_feat_temp_thresh_one(npa, disc, feat,
                    "Temp. Sensor 7 Over Temp. Threshold", 7,
                    NVME_TEMP_THRESH_OVER);
                do_get_feat_temp_thresh_one(npa, disc, feat,
                    "Temp. Sensor 7 Under Temp. Threshold", 7,
                    NVME_TEMP_THRESH_UNDER);
        }

        if (hlog->hl_temp_sensor_8 != 0) {
                do_get_feat_temp_thresh_one(npa, disc, feat,
                    "Temp. Sensor 8 Over Temp. Threshold", 8,
                    NVME_TEMP_THRESH_OVER);
                do_get_feat_temp_thresh_one(npa, disc, feat,
                    "Temp. Sensor 8 Under Temp. Threshold", 8,
                    NVME_TEMP_THRESH_UNDER);
        }

        ret = B_TRUE;
out:
        nvme_log_req_fini(req);
        free(buf);
        return (ret);
}

static boolean_t
do_get_feat_intr_vect(const nvme_process_arg_t *npa,
    const nvme_feat_disc_t *disc, const nvmeadm_feature_t *feat)
{
        uint32_t nintrs;
        boolean_t ret = B_TRUE;

        if (!nvme_ctrl_info_pci_nintrs(npa->npa_ctrl_info, &nintrs)) {
                nvmeadm_ctrl_info_warn(npa, "failed to get interrupt count "
                    "from controller %s information snapshot", npa->npa_name);
                return (B_FALSE);
        }

        nvme_print(2, nvme_feat_disc_spec(disc), -1, NULL);
        for (uint32_t i = 0; i < nintrs; i++) {
                uint32_t cdw0;
                void *buf;
                size_t buflen;
                nvme_intr_vect_t vect;

                vect.r = 0;
                vect.b.iv_iv = i;

                if (!do_get_feat_common(npa, disc, vect.r, &cdw0, &buf,
                    &buflen)) {
                        ret = B_FALSE;
                        continue;
                }

                feat->f_print(cdw0, buf, buflen, npa->npa_idctl,
                    npa->npa_version);
                free(buf);
        }

        return (ret);
}

/*
 * We've been asked to print the following feature that the controller probably
 * supports. Find our internal feature information for this to see if we know
 * how to deal with it.
 */
static void
do_get_features_cb(const nvme_process_arg_t *npa, const nvme_feat_disc_t *disc)
{
        const nvmeadm_feature_t *feat = NULL;
        uint32_t fid = nvme_feat_disc_fid(disc);
        nvme_get_feat_fields_t fields;
        void *data = NULL;
        size_t datalen = 0;
        uint32_t cdw0;

        for (size_t i = 0; i < ARRAY_SIZE(features); i++) {
                if (features[i].f_feature == fid) {
                        feat = &features[i];
                        break;
                }
        }

        /*
         * Determine if we have enough logic in here to get and print the
         * feature. The vast majority of NVMe features only output a single
         * uint32_t in cdw0 and potentially a data buffer. As long as no input
         * arguments are required, then we can go ahead and get this and print
         * the data. If there is, then we will refuse unless we have a
         * particular function. If we have a specific get function, we expect it
         * to do all the printing.
         */
        if (feat != NULL && feat->f_get != NULL) {
                if (!feat->f_get(npa, disc, feat)) {
                        exitcode = -1;
                }
                return;
        }

        fields = nvme_feat_disc_fields_get(disc);
        if ((fields & NVME_GET_FEAT_F_CDW11) != 0) {
                warnx("unable to get feature %s due to missing nvmeadm(8) "
                    "implementation logic", nvme_feat_disc_spec(disc));
                exitcode = -1;
                return;
        }

        /*
         * We do not set exitcode on failure here so that way we can swallow
         * errors from unimplemented features.
         */
        if (!do_get_feat_common(npa, disc, 0, &cdw0, &data, &datalen)) {
                return;
        }

        nvme_print(2, nvme_feat_disc_spec(disc), -1, NULL);
        if (feat != NULL && feat->f_print != NULL) {
                feat->f_print(cdw0, data, datalen, npa->npa_idctl,
                    npa->npa_version);
        } else {
                nvme_feat_output_t output = nvme_feat_disc_output_get(disc);
                nvme_print_feat_unknown(output, cdw0, data, datalen);
        }

        free(data);
}

/*
 * This is an entry point which prints every feature that we know about. We
 * often go to lengths to discover all the variable inputs that can be used for
 * a given feature that requires an argument in cdw11. Due to the semantics of
 * filtering being used for features and the need to print each feature, this is
 * not the place to add general field filtering or a means to request a specific
 * cdw11 argument or similar. Instead, a new get-feature which requires someone
 * to specify the short name for a feature and then allows particular fields to
 * be grabbed and arguments should be created instead.
 *
 * This uses the same general feature logic that underpins do_list_features()
 * and therefore we transform filter arguments into the same style used there.
 */
static int
do_get_features(const nvme_process_arg_t *npa)
{
        char *fstr = NULL;
        char **filts = NULL;
        boolean_t *used = NULL;
        nvmeadm_features_t nf;
        int ret;

        if (npa->npa_argc > 1)
                errx(-1, "unexpected arguments");

        if (npa->npa_ns != NULL && nvme_ns_info_level(npa->npa_ns_info) <
            NVME_NS_DISC_F_ACTIVE) {
                errx(-1, "cannot get feature: namespace is inactive");
        }

        /*
         * We always leave nf_unimpl set to false as we don't want to bother
         * trying to print a feature that we know the device doesn't support.
         */
        (void) memset(&nf, 0, sizeof (nvmeadm_features_t));

        /*
         * If we've been given a series of features to print, treat those as
         * filters on the features as we're walking them to determine which to
         * print or not.
         */
        if (npa->npa_argc == 1) {
                char *f;
                uint32_t i;

                nf.nf_nfilts = 1;
                fstr = strdup(npa->npa_argv[0]);

                if (fstr == NULL) {
                        err(-1, "failed to allocate memory to duplicate "
                            "feature string");
                }

                for (const char *c = strchr(fstr, ','); c != NULL;
                    c = strchr(c + 1, ',')) {
                        nf.nf_nfilts++;
                }

                filts = calloc(nf.nf_nfilts, sizeof (char *));
                if (filts == NULL) {
                        err(-1, "failed to allocate memory for filter list");
                }

                i = 0;
                while ((f = strsep(&fstr, ",")) != NULL) {
                        filts[i] = f;
                        i++;
                }
                VERIFY3U(i, ==, nf.nf_nfilts);
                nf.nf_filts = filts;

                used = calloc(nf.nf_nfilts, sizeof (boolean_t));
                if (used == NULL) {
                        err(-1, "failed to allocate memory for filter use "
                            "tracking");
                }
                nf.nf_used = used;
        }

        (void) printf("%s: Get Features\n", npa->npa_name);
        ret = do_features(npa, &nf, do_get_features_cb);

        free(fstr);
        free(filts);
        free(used);
        return (ret);
}

static int
do_format_common(const nvme_process_arg_t *npa, uint32_t lbaf,
    uint32_t ses)
{
        int ret = 0;
        nvme_format_req_t *req;

        if (npa->npa_ns != NULL && nvme_ns_info_level(npa->npa_ns_info) <
            NVME_NS_DISC_F_ACTIVE) {
                errx(-1, "cannot %s: namespace is inactive",
                    npa->npa_cmd->c_name);
        }

        if (!nvme_format_req_init(npa->npa_ctrl, &req)) {
                nvmeadm_fatal(npa, "failed to initialize format request for "
                    "%s", npa->npa_name);
        }

        if (npa->npa_ns != NULL) {
                uint32_t nsid = nvme_ns_info_nsid(npa->npa_ns_info);

                if (!nvme_format_req_set_nsid(req, nsid)) {
                        nvmeadm_fatal(npa, "failed to set format request "
                            "namespace ID to 0x%x", nsid);
                }
        }

        if (!nvme_format_req_set_lbaf(req, lbaf) ||
            !nvme_format_req_set_ses(req, ses)) {
                nvmeadm_fatal(npa, "failed to set format request fields for %s",
                    npa->npa_name);
        }

        if (do_detach_bd(npa) != 0) {
                errx(-1, "cannot %s %s due to namespace detach failure",
                    npa->npa_cmd->c_name, npa->npa_name);
        }

        if (!nvme_format_req_exec(req)) {
                nvmeadm_warn(npa, "failed to %s %s", npa->npa_cmd->c_name,
                    npa->npa_name);
                ret = -1;
        }

        if (do_attach_bd(npa) != 0)
                ret = -1;

        return (ret);
}

static void
usage_format(const char *c_name)
{
        (void) fprintf(stderr, "%s <ctl>[/<ns>] [<lba-format>]\n\n"
            "  Format one or all namespaces of the specified NVMe "
            "controller. Supported LBA\n  formats can be queried with "
            "the \"%s identify\" command on the namespace\n  to be "
            "formatted.\n", c_name, getprogname());
}

static uint32_t
do_format_determine_lbaf(const nvme_process_arg_t *npa)
{
        const nvme_nvm_lba_fmt_t *fmt;
        nvme_ns_info_t *ns_info = NULL;
        uint32_t lbaf;

        if (npa->npa_argc > 0) {
                unsigned long lba;
                uint32_t nlbaf = nvme_ctrl_info_nformats(npa->npa_ctrl_info);

                errno = 0;
                lba = strtoul(npa->npa_argv[0], NULL, 10);
                if (errno != 0 || lba >= nlbaf)
                        errx(-1, "invalid LBA format %s", npa->npa_argv[0]);

                if (!nvme_ctrl_info_format(npa->npa_ctrl_info, (uint32_t)lba,
                    &fmt)) {
                        nvmeadm_fatal(npa, "failed to get LBA format %lu "
                            "information", lba);
                }
        } else {
                /*
                 * If we have a namespace then we use the current namespace's
                 * LBA format. If we don't have a namespace, then we promised
                 * we'd look at namespace 1 in the manual page.
                 */
                if (npa->npa_ns_info == NULL) {
                        if (!nvme_ctrl_ns_info_snap(npa->npa_ctrl, 1,
                            &ns_info)) {
                                nvmeadm_fatal(npa, "failed to get namespace 1 "
                                    "information, please explicitly specify an "
                                    "LBA format");
                        }

                        if (!nvme_ns_info_curformat(ns_info, &fmt)) {
                                nvmeadm_fatal(npa, "failed to retrieve current "
                                    "namespace format from namespace 1");
                        }
                } else {
                        if (!nvme_ns_info_curformat(npa->npa_ns_info, &fmt)) {
                                nvmeadm_fatal(npa, "failed to get the current "
                                    "format information from %s",
                                    npa->npa_name);
                        }
                }
        }

        if (nvme_nvm_lba_fmt_meta_size(fmt) != 0) {
                errx(-1, "LBA formats with metadata are not supported");
        }

        lbaf = nvme_nvm_lba_fmt_id(fmt);
        nvme_ns_info_free(ns_info);
        return (lbaf);
}

static int
do_format(const nvme_process_arg_t *npa)
{
        uint32_t lbaf;

        if (npa->npa_argc > 1) {
                errx(-1, "%s passed extraneous arguments starting with %s",
                    npa->npa_cmd->c_name, npa->npa_argv[1]);
        }

        lbaf = do_format_determine_lbaf(npa);
        return (do_format_common(npa, lbaf, 0));
}

static void
usage_secure_erase(const char *c_name)
{
        (void) fprintf(stderr, "%s [-c] <ctl>[/<ns>]\n\n"
            "  Secure-Erase one or all namespaces of the specified "
            "NVMe controller.\n", c_name);
}

static void
optparse_secure_erase(nvme_process_arg_t *npa)
{
        int c;

        while ((c = getopt(npa->npa_argc, npa->npa_argv, ":c")) != -1) {
                switch (c) {
                case 'c':
                        npa->npa_cmdflags |= NVMEADM_O_SE_CRYPTO;
                        break;

                case '?':
                        errx(-1, "unknown option: -%c", optopt);

                case ':':
                        errx(-1, "option -%c requires an argument", optopt);

                }
        }
}

static int
do_secure_erase(const nvme_process_arg_t *npa)
{
        unsigned long lbaf;
        uint8_t ses = NVME_FRMT_SES_USER;

        if (npa->npa_argc > 0) {
                errx(-1, "%s passed extraneous arguments starting with %s",
                    npa->npa_cmd->c_name, npa->npa_argv[0]);
        }

        if ((npa->npa_cmdflags & NVMEADM_O_SE_CRYPTO) != 0)
                ses = NVME_FRMT_SES_CRYPTO;

        lbaf = do_format_determine_lbaf(npa);
        return (do_format_common(npa, lbaf, ses));
}

static void
usage_attach_detach_bd(const char *c_name)
{
        (void) fprintf(stderr, "%s <ctl>[/<ns>]\n\n"
            "  %c%s blkdev(4D) %s one or all namespaces of the "
            "specified NVMe controller.\n",
            c_name, toupper(c_name[0]), &c_name[1],
            c_name[0] == 'd' ? "from" : "to");
}

/*
 * nvmeadm does not generate an error when trying to attach blkdev to something
 * that already has it attached. Swallow that here.
 */
static boolean_t
swallow_attach_bd_err(const nvme_process_arg_t *npa)
{
        return (nvme_ctrl_err(npa->npa_ctrl) == NVME_ERR_NS_BLKDEV_ATTACH);
}

static int
do_attach_bd(const nvme_process_arg_t *npa)
{
        int rv;
        nvme_ns_iter_t *iter = NULL;
        nvme_iter_t ret;
        const nvme_ns_disc_t *disc;

        if (npa->npa_ns != NULL) {
                if (!nvme_ns_bd_attach(npa->npa_ns) &&
                    !swallow_attach_bd_err(npa)) {
                        nvmeadm_warn(npa, "faild to attach %s", npa->npa_name);
                        return (-1);
                }
                return (0);
        }

        if (!nvme_ns_discover_init(npa->npa_ctrl, NVME_NS_DISC_F_NOT_IGNORED,
            &iter))  {
                nvmeadm_fatal(npa, "failed to initialize namespace discovery "
                    "on %s", npa->npa_name);
        }

        rv = 0;
        while ((ret = nvme_ns_discover_step(iter, &disc)) == NVME_ITER_VALID) {
                nvme_ns_t *ns;
                uint32_t nsid;

                if (nvme_ns_disc_level(disc) == NVME_NS_DISC_F_BLKDEV)
                        continue;

                nsid = nvme_ns_disc_nsid(disc);
                if (!nvme_ns_init(npa->npa_ctrl, nsid, &ns)) {
                        nvmeadm_warn(npa, "failed to open namespace %s/%u "
                            "handle", npa->npa_name, nsid);
                        rv = -1;
                        continue;
                }

                /*
                 * nvmeadm has historically swallowed the case where you ask to
                 * attach an already attached namespace.
                 */
                if (!nvme_ns_bd_attach(ns) && !swallow_attach_bd_err(npa)) {
                        nvmeadm_warn(npa, "failed to attach namespace "
                            "%s/%u", npa->npa_name, nsid);
                        rv = -1;
                }
                nvme_ns_fini(ns);
        }

        nvme_ns_discover_fini(iter);
        if (ret == NVME_ITER_ERROR) {
                nvmeadm_warn(npa, "failed to iterate namespaces on %s",
                    npa->npa_name);
                rv = -1;
        }

        return (rv);
}

/*
 * nvmeadm does not generate an error when trying to attach blkdev to something
 * that already has it attached. Swallow that here.
 */
static boolean_t
swallow_detach_bd_err(const nvme_process_arg_t *npa)
{
        switch (nvme_ctrl_err(npa->npa_ctrl)) {
        case NVME_ERR_NS_UNALLOC:
        case NVME_ERR_NS_CTRL_NOT_ATTACHED:
        case NVME_ERR_NS_CTRL_ATTACHED:
                return (B_TRUE);
        default:
                return (B_FALSE);
        }
}

static int
do_detach_bd(const nvme_process_arg_t *npa)
{
        int rv;
        nvme_ns_iter_t *iter = NULL;
        nvme_iter_t ret;
        const nvme_ns_disc_t *disc;

        if (npa->npa_ns != NULL) {
                if (!nvme_ns_bd_detach(npa->npa_ns) &&
                    !swallow_detach_bd_err(npa)) {
                        nvmeadm_warn(npa, "failed to detach %s", npa->npa_name);
                        return (-1);
                }
                return (0);
        }

        if (!nvme_ns_discover_init(npa->npa_ctrl, NVME_NS_DISC_F_BLKDEV,
            &iter))  {
                nvmeadm_fatal(npa, "failed to initialize namespace discovery "
                    "on %s", npa->npa_name);
        }

        rv = 0;
        while ((ret = nvme_ns_discover_step(iter, &disc)) == NVME_ITER_VALID) {
                nvme_ns_t *ns;
                uint32_t nsid = nvme_ns_disc_nsid(disc);

                if (!nvme_ns_init(npa->npa_ctrl, nsid, &ns)) {
                        nvmeadm_warn(npa, "failed to open namespace %s/%u "
                            "handle", npa->npa_name, nsid);
                        rv = -1;
                        continue;
                }

                if (!nvme_ns_bd_detach(ns) && !swallow_detach_bd_err(npa)) {
                        nvmeadm_warn(npa, "failed to detach namespace %s/%u",
                            npa->npa_name, nsid);
                        rv = -1;
                }
                nvme_ns_fini(ns);
        }

        nvme_ns_discover_fini(iter);
        if (ret == NVME_ITER_ERROR) {
                nvmeadm_warn(npa, "failed to iterate namespaces on %s",
                    npa->npa_name);
                rv = -1;
        }

        return (rv);
}

static void
usage_firmware_load(const char *c_name)
{
        (void) fprintf(stderr, "%s <ctl> <image file> [<offset>]\n\n"
            "  Load firmware <image file> to offset <offset>.\n"
            "  The firmware needs to be committed to a slot using "
            "\"nvmeadm commit-firmware\"\n  command.\n", c_name);
}

/*
 * Read exactly len bytes, or until eof.
 */
static size_t
read_block(const nvme_process_arg_t *npa, int fd, char *buf, size_t len)
{
        size_t remain;

        remain = len;
        while (remain > 0) {
                ssize_t bytes = read(fd, buf, remain);
                if (bytes == 0)
                        break;

                if (bytes < 0) {
                        if (errno == EINTR)
                                continue;

                        err(-1, "Error reading \"%s\"", npa->npa_argv[0]);
                }

                buf += (size_t)bytes;
                remain -= (size_t)bytes;
        }

        return (len - remain);
}

/*
 * Convert a string to a valid firmware upload offset (in bytes).
 */
static uint64_t
get_fw_offsetb(char *str)
{
        longlong_t offsetb;
        char *valend;

        errno = 0;
        offsetb = strtoll(str, &valend, 0);
        if (errno != 0 || *valend != '\0' || offsetb < 0 ||
            offsetb > NVME_FW_OFFSETB_MAX)
                errx(-1, "Offset must be numeric and in the range of 0 to %llu",
                    NVME_FW_OFFSETB_MAX);

        if ((offsetb & NVME_DWORD_MASK) != 0)
                errx(-1, "Offset must be multiple of %d", NVME_DWORD_SIZE);

        return ((uint64_t)offsetb);
}

#define FIRMWARE_READ_BLKSIZE   (64 * 1024)             /* 64K */

static int
do_firmware_load(const nvme_process_arg_t *npa)
{
        int fw_fd;
        uint64_t offset = 0;
        size_t size, len;
        char buf[FIRMWARE_READ_BLKSIZE];

        if (npa->npa_argc > 2)
                errx(-1, "%s passed extraneous arguments starting with %s",
                    npa->npa_cmd->c_name, npa->npa_argv[2]);

        if (npa->npa_argc == 0)
                errx(-1, "Requires firmware file name, and an "
                    "optional offset");

        if (npa->npa_ns != NULL)
                errx(-1, "Firmware loading not available on a per-namespace "
                    "basis");

        if (npa->npa_argc == 2)
                offset = get_fw_offsetb(npa->npa_argv[1]);

        fw_fd = open(npa->npa_argv[0], O_RDONLY);
        if (fw_fd < 0)
                errx(-1, "Failed to open \"%s\": %s", npa->npa_argv[0],
                    strerror(errno));

        size = 0;
        do {
                len = read_block(npa, fw_fd, buf, sizeof (buf));

                if (len == 0)
                        break;

                if (!nvme_fw_load(npa->npa_ctrl, buf, len, offset)) {
                        nvmeadm_fatal(npa, "failed to load firmware image "
                            "\"%s\" at offset %" PRIu64, npa->npa_argv[0],
                            offset);
                }

                offset += len;
                size += len;
        } while (len == sizeof (buf));

        (void) close(fw_fd);

        if (verbose)
                (void) printf("%zu bytes downloaded.\n", size);

        return (0);
}

/*
 * Common firmware commit for nvmeadm commit-firmware and activate-firmware.
 */
static void
nvmeadm_firmware_commit(const nvme_process_arg_t *npa, uint32_t slot,
    uint32_t act)
{
        nvme_fw_commit_req_t *req;

        if (!nvme_fw_commit_req_init(npa->npa_ctrl, &req)) {
                nvmeadm_fatal(npa, "failed to initialize firmware commit "
                    "request for %s", npa->npa_name);
        }

        if (!nvme_fw_commit_req_set_slot(req, slot) ||
            !nvme_fw_commit_req_set_action(req, act)) {
                nvmeadm_fatal(npa, "failed to set firmware commit fields for "
                    "%s", npa->npa_name);
        }

        if (!nvme_fw_commit_req_exec(req)) {
                /*
                 * A number of command specific status values are informational
                 * and indicate that the operation was successful but that
                 * something else, such as a device reset, is still required
                 * before the new firmware is active.
                 * We distinguish those here and report them as a note rather
                 * than a fatal error.
                 */
                if (nvme_ctrl_err(npa->npa_ctrl) == NVME_ERR_CONTROLLER) {
                        uint32_t sct, sc;

                        nvme_ctrl_deverr(npa->npa_ctrl, &sct, &sc);
                        if (sct == NVME_CQE_SCT_SPECIFIC && (
                            sc == NVME_CQE_SC_SPC_FW_RESET ||
                            sc == NVME_CQE_SC_SPC_FW_NSSR ||
                            sc == NVME_CQE_SC_SPC_FW_NEXT_RESET)) {
                                fprintf(stderr,
                                    "nvmeadm: commit successful but %s\n",
                                    nvme_sctostr(npa->npa_ctrl, NVME_CSI_NVM,
                                    sct, sc));
                        } else {
                                nvmeadm_fatal(npa, "failed to %s on %s",
                                    npa->npa_cmd->c_name, npa->npa_name);
                        }
                } else {
                        nvmeadm_fatal(npa, "failed to %s on %s",
                            npa->npa_cmd->c_name, npa->npa_name);
                }
        }

        nvme_fw_commit_req_fini(req);
}

/*
 * Convert str to a valid firmware slot number.
 */
static uint32_t
get_slot_number(char *str)
{
        longlong_t slot;
        char *valend;

        errno = 0;
        slot = strtoll(str, &valend, 0);
        if (errno != 0 || *valend != '\0' ||
            slot < NVME_FW_SLOT_MIN || slot > NVME_FW_SLOT_MAX)
                errx(-1, "Slot must be numeric and in the range of %u to %u",
                    NVME_FW_SLOT_MIN, NVME_FW_SLOT_MAX);

        return ((uint32_t)slot);
}

static void
usage_firmware_commit(const char *c_name)
{
        (void) fprintf(stderr, "%s <ctl> <slot>\n\n"
            "  Commit previously downloaded firmware to slot <slot>.\n"
            "  The firmware is only activated after a "
            "\"nvmeadm activate-firmware\" command.\n", c_name);
}

static int
do_firmware_commit(const nvme_process_arg_t *npa)
{
        uint32_t slot;

        if (npa->npa_argc > 1)
                errx(-1, "%s passed extraneous arguments starting with %s",
                    npa->npa_cmd->c_name, npa->npa_argv[1]);

        if (npa->npa_argc == 0)
                errx(-1, "Firmware slot number is required");

        if (npa->npa_ns != NULL)
                errx(-1, "Firmware committing not available on a per-namespace "
                    "basis");

        slot = get_slot_number(npa->npa_argv[0]);

        if (slot == 1 && npa->npa_idctl->id_frmw.fw_readonly)
                errx(-1, "Cannot commit firmware to slot 1: slot is read-only");

        nvmeadm_firmware_commit(npa, slot, NVME_FWC_SAVE);

        if (verbose)
                (void) printf("Firmware committed to slot %u.\n", slot);

        return (0);
}

static void
usage_firmware_activate(const char *c_name)
{
        (void) fprintf(stderr, "%s <ctl> <slot>\n\n"
            "  Activate firmware in slot <slot>.\n"
            "  The firmware will be in use after the next system reset.\n",
            c_name);
}

static int
do_firmware_activate(const nvme_process_arg_t *npa)
{
        uint32_t slot;

        if (npa->npa_argc > 1)
                errx(-1, "%s passed extraneous arguments starting with %s",
                    npa->npa_cmd->c_name, npa->npa_argv[1]);

        if (npa->npa_argc == 0)
                errx(-1, "Firmware slot number is required");

        if (npa->npa_ns != NULL)
                errx(-1, "Firmware activation not available on a per-namespace "
                    "basis");

        slot = get_slot_number(npa->npa_argv[0]);

        nvmeadm_firmware_commit(npa, slot, NVME_FWC_ACTIVATE);

        if (verbose)
                (void) printf("Slot %u successfully activated.\n", slot);

        return (0);
}