root/stand/libsa/zfs/nvlist.c
/*-
 * Copyright 2020 Toomas Soome <tsoome@me.com>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/param.h>
#include <sys/endian.h>
#include <sys/stdint.h>
#ifdef _STANDALONE
#include <stand.h>
#else
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#endif

#include "nvlist.h"

enum xdr_op {
        XDR_OP_ENCODE = 1,
        XDR_OP_DECODE = 2
};

typedef struct xdr {
        enum xdr_op xdr_op;
        int (*xdr_getint)(struct xdr *, int *);
        int (*xdr_putint)(struct xdr *, int);
        int (*xdr_getuint)(struct xdr *, unsigned *);
        int (*xdr_putuint)(struct xdr *, unsigned);
        const uint8_t *xdr_buf;
        uint8_t *xdr_idx;
        size_t xdr_buf_size;
} xdr_t;

static int nvlist_xdr_nvlist(xdr_t *, nvlist_t *);
static bool nvlist_size_xdr(xdr_t *, size_t *);
static bool nvlist_size_native(xdr_t *, size_t *);
static bool xdr_int(xdr_t *, int *);
static bool xdr_u_int(xdr_t *, unsigned *);

typedef bool (*xdrproc_t)(xdr_t *, void *);

/* Basic primitives for XDR translation operations, getint and putint. */
static int
_getint(struct xdr *xdr, int *ip)
{
        *ip = be32dec(xdr->xdr_idx);
        return (sizeof(int));
}

static int
_putint(struct xdr *xdr, int i)
{
        int *ip = (int *)xdr->xdr_idx;

        *ip = htobe32(i);
        return (sizeof(int));
}

static int
_getuint(struct xdr *xdr, unsigned *ip)
{
        *ip = be32dec(xdr->xdr_idx);
        return (sizeof(unsigned));
}

static int
_putuint(struct xdr *xdr, unsigned i)
{
        unsigned *up = (unsigned *)xdr->xdr_idx;

        *up = htobe32(i);
        return (sizeof(int));
}

static int
_getint_mem(struct xdr *xdr, int *ip)
{
        *ip = *(int *)xdr->xdr_idx;
        return (sizeof(int));
}

static int
_putint_mem(struct xdr *xdr, int i)
{
        int *ip = (int *)xdr->xdr_idx;

        *ip = i;
        return (sizeof(int));
}

static int
_getuint_mem(struct xdr *xdr, unsigned *ip)
{
        *ip = *(unsigned *)xdr->xdr_idx;
        return (sizeof(unsigned));
}

static int
_putuint_mem(struct xdr *xdr, unsigned i)
{
        unsigned *up = (unsigned *)xdr->xdr_idx;

        *up = i;
        return (sizeof(int));
}

/*
 * XDR data translations.
 */
static bool
xdr_short(xdr_t *xdr, short *ip)
{
        int i;
        bool rv;

        i = *ip;
        if ((rv = xdr_int(xdr, &i))) {
                if (xdr->xdr_op == XDR_OP_DECODE)
                        *ip = i;
        }
        return (rv);
}

static bool
xdr_u_short(xdr_t *xdr, unsigned short *ip)
{
        unsigned u;
        bool rv;

        u = *ip;
        if ((rv = xdr_u_int(xdr, &u))) {
                if (xdr->xdr_op == XDR_OP_DECODE)
                        *ip = u;
        }
        return (rv);
}

/*
 * translate xdr->xdr_idx, increment it by size of int.
 */
static bool
xdr_int(xdr_t *xdr, int *ip)
{
        bool rv = false;
        int *i = (int *)xdr->xdr_idx;

        if (xdr->xdr_idx + sizeof(int) > xdr->xdr_buf + xdr->xdr_buf_size)
                return (rv);

        switch (xdr->xdr_op) {
        case XDR_OP_ENCODE:
                /* Encode value *ip, store to buf */
                xdr->xdr_idx += xdr->xdr_putint(xdr, *ip);
                rv = true;
                break;

        case XDR_OP_DECODE:
                /* Decode buf, return value to *ip */
                xdr->xdr_idx += xdr->xdr_getint(xdr, i);
                *ip = *i;
                rv = true;
                break;
        }
        return (rv);
}

/*
 * translate xdr->xdr_idx, increment it by size of unsigned int.
 */
static bool
xdr_u_int(xdr_t *xdr, unsigned *ip)
{
        bool rv = false;
        unsigned *u = (unsigned *)xdr->xdr_idx;

        if (xdr->xdr_idx + sizeof(unsigned) > xdr->xdr_buf + xdr->xdr_buf_size)
                return (rv);

        switch (xdr->xdr_op) {
        case XDR_OP_ENCODE:
                /* Encode value *ip, store to buf */
                xdr->xdr_idx += xdr->xdr_putuint(xdr, *ip);
                rv = true;
                break;

        case XDR_OP_DECODE:
                /* Decode buf, return value to *ip */
                xdr->xdr_idx += xdr->xdr_getuint(xdr, u);
                *ip = *u;
                rv = true;
                break;
        }
        return (rv);
}

static bool
xdr_int64(xdr_t *xdr, int64_t *lp)
{
        bool rv = false;

        if (xdr->xdr_idx + sizeof(int64_t) > xdr->xdr_buf + xdr->xdr_buf_size)
                return (rv);

        switch (xdr->xdr_op) {
        case XDR_OP_ENCODE:
                /* Encode value *lp, store to buf */
                if (xdr->xdr_putint == _putint)
                        *(int64_t *)xdr->xdr_idx = htobe64(*lp);
                else
                        *(int64_t *)xdr->xdr_idx = *lp;
                xdr->xdr_idx += sizeof(int64_t);
                rv = true;
                break;

        case XDR_OP_DECODE:
                /* Decode buf, return value to *ip */
                if (xdr->xdr_getint == _getint)
                        *lp = be64toh(*(int64_t *)xdr->xdr_idx);
                else
                        *lp = *(int64_t *)xdr->xdr_idx;
                xdr->xdr_idx += sizeof(int64_t);
                rv = true;
        }
        return (rv);
}

static bool
xdr_uint64(xdr_t *xdr, uint64_t *lp)
{
        bool rv = false;

        if (xdr->xdr_idx + sizeof(uint64_t) > xdr->xdr_buf + xdr->xdr_buf_size)
                return (rv);

        switch (xdr->xdr_op) {
        case XDR_OP_ENCODE:
                /* Encode value *ip, store to buf */
                if (xdr->xdr_putint == _putint)
                        *(uint64_t *)xdr->xdr_idx = htobe64(*lp);
                else
                        *(uint64_t *)xdr->xdr_idx = *lp;
                xdr->xdr_idx += sizeof(uint64_t);
                rv = true;
                break;

        case XDR_OP_DECODE:
                /* Decode buf, return value to *ip */
                if (xdr->xdr_getuint == _getuint)
                        *lp = be64toh(*(uint64_t *)xdr->xdr_idx);
                else
                        *lp = *(uint64_t *)xdr->xdr_idx;
                xdr->xdr_idx += sizeof(uint64_t);
                rv = true;
        }
        return (rv);
}

static bool
xdr_char(xdr_t *xdr, char *cp)
{
        int i;
        bool rv = false;

        i = *cp;
        if ((rv = xdr_int(xdr, &i))) {
                if (xdr->xdr_op == XDR_OP_DECODE)
                        *cp = i;
        }
        return (rv);
}

static bool
xdr_string(xdr_t *xdr, nv_string_t *s)
{
        int size = 0;
        bool rv = false;

        switch (xdr->xdr_op) {
        case XDR_OP_ENCODE:
                size = s->nv_size;
                if (xdr->xdr_idx + sizeof(unsigned) + NV_ALIGN4(size) >
                    xdr->xdr_buf + xdr->xdr_buf_size)
                        break;
                xdr->xdr_idx += xdr->xdr_putuint(xdr, s->nv_size);
                xdr->xdr_idx += NV_ALIGN4(size);
                rv = true;
                break;

        case XDR_OP_DECODE:
                if (xdr->xdr_idx + sizeof(unsigned) >
                    xdr->xdr_buf + xdr->xdr_buf_size)
                        break;
                size = xdr->xdr_getuint(xdr, &s->nv_size);
                size = NV_ALIGN4(size + s->nv_size);
                if (xdr->xdr_idx + size > xdr->xdr_buf + xdr->xdr_buf_size)
                        break;
                xdr->xdr_idx += size;
                rv = true;
                break;
        }
        return (rv);
}

static bool
xdr_array(xdr_t *xdr, const unsigned nelem, const xdrproc_t elproc)
{
        bool rv = true;
        unsigned c = nelem;

        if (!xdr_u_int(xdr, &c))
                return (false);

        for (unsigned i = 0; i < nelem; i++) {
                if (!elproc(xdr, xdr->xdr_idx))
                        return (false);
        }
        return (rv);
}

/*
 * nvlist management functions.
 */
void
nvlist_destroy(nvlist_t *nvl)
{
        if (nvl != NULL) {
                /* Free data if it was allocated by us. */
                if (nvl->nv_asize > 0)
                        free(nvl->nv_data);
        }
        free(nvl);
}

char *
nvstring_get(nv_string_t *nvs)
{
        char *s;

        s = malloc(nvs->nv_size + 1);
        if (s != NULL) {
                bcopy(nvs->nv_data, s, nvs->nv_size);
                s[nvs->nv_size] = '\0';
        }
        return (s);
}

/*
 * Create empty nvlist.
 * The nvlist is terminated by 2x zeros (8 bytes).
 */
nvlist_t *
nvlist_create(int flag)
{
        nvlist_t *nvl;
        nvs_data_t *nvs;

        nvl = calloc(1, sizeof(*nvl));
        if (nvl == NULL)
                return (nvl);

        nvl->nv_header.nvh_encoding = NV_ENCODE_XDR;
        nvl->nv_header.nvh_endian = _BYTE_ORDER == _LITTLE_ENDIAN;

        nvl->nv_asize = nvl->nv_size = sizeof(*nvs);
        nvs = calloc(1, nvl->nv_asize);
        if (nvs == NULL) {
                free(nvl);
                return (NULL);
        }
        /* data in nvlist is byte stream */
        nvl->nv_data = (uint8_t *)nvs;

        nvs->nvl_version = NV_VERSION;
        nvs->nvl_nvflag = flag;
        return (nvl);
}

static bool
nvlist_xdr_nvp(xdr_t *xdr, nvlist_t *nvl)
{
        nv_string_t *nv_string;
        nv_pair_data_t *nvp_data;
        nvlist_t nvlist;
        unsigned type, nelem;
        xdr_t nv_xdr;

        nv_string = (nv_string_t *)xdr->xdr_idx;
        if (!xdr_string(xdr, nv_string)) {
                return (false);
        }
        nvp_data = (nv_pair_data_t *)xdr->xdr_idx;

        type = nvp_data->nv_type;
        nelem = nvp_data->nv_nelem;
        if (!xdr_u_int(xdr, &type) || !xdr_u_int(xdr, &nelem))
                return (false);

        switch (type) {
        case DATA_TYPE_NVLIST:
        case DATA_TYPE_NVLIST_ARRAY:
                bzero(&nvlist, sizeof(nvlist));
                nvlist.nv_data = xdr->xdr_idx;
                nvlist.nv_idx = nvlist.nv_data;

                /* Set up xdr for this nvlist. */
                nv_xdr = *xdr;
                nv_xdr.xdr_buf = nvlist.nv_data;
                nv_xdr.xdr_idx = nvlist.nv_data;
                nv_xdr.xdr_buf_size =
                    nvl->nv_data + nvl->nv_size - nvlist.nv_data;

                for (unsigned i = 0; i < nelem; i++) {
                        if (xdr->xdr_op == XDR_OP_ENCODE) {
                                if (!nvlist_size_native(&nv_xdr,
                                    &nvlist.nv_size))
                                        return (false);
                        } else {
                                if (!nvlist_size_xdr(&nv_xdr,
                                    &nvlist.nv_size))
                                        return (false);
                        }
                        if (nvlist_xdr_nvlist(xdr, &nvlist) != 0)
                                return (false);

                        nvlist.nv_data = nv_xdr.xdr_idx;
                        nvlist.nv_idx = nv_xdr.xdr_idx;

                        nv_xdr.xdr_buf = nv_xdr.xdr_idx;
                        nv_xdr.xdr_buf_size =
                            nvl->nv_data + nvl->nv_size - nvlist.nv_data;
                }
                break;

        case DATA_TYPE_BOOLEAN:
                /* BOOLEAN does not take value space */
                break;
        case DATA_TYPE_BYTE:
        case DATA_TYPE_INT8:
        case DATA_TYPE_UINT8:
                if (!xdr_char(xdr, (char *)&nvp_data->nv_data[0]))
                        return (false);
                break;

        case DATA_TYPE_INT16:
                if (!xdr_short(xdr, (short *)&nvp_data->nv_data[0]))
                        return (false);
                break;

        case DATA_TYPE_UINT16:
                if (!xdr_u_short(xdr, (unsigned short *)&nvp_data->nv_data[0]))
                        return (false);
                break;

        case DATA_TYPE_BOOLEAN_VALUE:
        case DATA_TYPE_INT32:
                if (!xdr_int(xdr, (int *)&nvp_data->nv_data[0]))
                        return (false);
                break;

        case DATA_TYPE_UINT32:
                if (!xdr_u_int(xdr, (unsigned *)&nvp_data->nv_data[0]))
                        return (false);
                break;

        case DATA_TYPE_HRTIME:
        case DATA_TYPE_INT64:
                if (!xdr_int64(xdr, (int64_t *)&nvp_data->nv_data[0]))
                        return (false);
                break;

        case DATA_TYPE_UINT64:
                if (!xdr_uint64(xdr, (uint64_t *)&nvp_data->nv_data[0]))
                        return (false);
                break;

        case DATA_TYPE_BYTE_ARRAY:
        case DATA_TYPE_STRING:
                nv_string = (nv_string_t *)&nvp_data->nv_data[0];
                if (!xdr_string(xdr, nv_string))
                        return (false);
                break;

        case DATA_TYPE_STRING_ARRAY:
                nv_string = (nv_string_t *)&nvp_data->nv_data[0];
                for (unsigned i = 0; i < nelem; i++) {
                        if (!xdr_string(xdr, nv_string))
                                return (false);
                        nv_string = (nv_string_t *)xdr->xdr_idx;
                }
                break;

        case DATA_TYPE_INT8_ARRAY:
        case DATA_TYPE_UINT8_ARRAY:
        case DATA_TYPE_INT16_ARRAY:
        case DATA_TYPE_UINT16_ARRAY:
        case DATA_TYPE_BOOLEAN_ARRAY:
        case DATA_TYPE_INT32_ARRAY:
        case DATA_TYPE_UINT32_ARRAY:
                if (!xdr_array(xdr, nelem, (xdrproc_t)xdr_u_int))
                        return (false);
                break;

        case DATA_TYPE_INT64_ARRAY:
        case DATA_TYPE_UINT64_ARRAY:
                if (!xdr_array(xdr, nelem, (xdrproc_t)xdr_uint64))
                        return (false);
                break;
        }
        return (true);
}

static int
nvlist_xdr_nvlist(xdr_t *xdr, nvlist_t *nvl)
{
        nvp_header_t *nvph;
        nvs_data_t *nvs;
        unsigned encoded_size, decoded_size;
        int rv;

        nvs = (nvs_data_t *)xdr->xdr_idx;
        nvph = &nvs->nvl_pair;

        if (!xdr_u_int(xdr, &nvs->nvl_version))
                return (EINVAL);
        if (!xdr_u_int(xdr, &nvs->nvl_nvflag))
                return (EINVAL);

        encoded_size = nvph->encoded_size;
        decoded_size = nvph->decoded_size;

        if (xdr->xdr_op == XDR_OP_ENCODE) {
                if (!xdr_u_int(xdr, &nvph->encoded_size))
                        return (EINVAL);
                if (!xdr_u_int(xdr, &nvph->decoded_size))
                        return (EINVAL);
        } else {
                xdr->xdr_idx += 2 * sizeof(unsigned);
        }

        rv = 0;
        while (encoded_size && decoded_size) {
                if (!nvlist_xdr_nvp(xdr, nvl))
                        return (EINVAL);

                nvph = (nvp_header_t *)(xdr->xdr_idx);
                encoded_size = nvph->encoded_size;
                decoded_size = nvph->decoded_size;
                if (xdr->xdr_op == XDR_OP_ENCODE) {
                        if (!xdr_u_int(xdr, &nvph->encoded_size))
                                return (EINVAL);
                        if (!xdr_u_int(xdr, &nvph->decoded_size))
                                return (EINVAL);
                } else {
                        xdr->xdr_idx += 2 * sizeof(unsigned);
                }
        }
        return (rv);
}

/*
 * Calculate nvlist size, translating encoded_size and decoded_size.
 */
static bool
nvlist_size_xdr(xdr_t *xdr, size_t *size)
{
        uint8_t *pair;
        unsigned encoded_size, decoded_size;

        xdr->xdr_idx += 2 * sizeof(unsigned);

        pair = xdr->xdr_idx;
        if (!xdr_u_int(xdr, &encoded_size) || !xdr_u_int(xdr, &decoded_size))
                return (false);

        while (encoded_size && decoded_size) {
                xdr->xdr_idx = pair + encoded_size;
                pair = xdr->xdr_idx;
                if (!xdr_u_int(xdr, &encoded_size) ||
                    !xdr_u_int(xdr, &decoded_size))
                        return (false);
        }
        *size = xdr->xdr_idx - xdr->xdr_buf;

        return (true);
}

nvp_header_t *
nvlist_next_nvpair(nvlist_t *nvl, nvp_header_t *nvh)
{
        uint8_t *pair;
        unsigned encoded_size, decoded_size;
        xdr_t xdr;

        if (nvl == NULL)
                return (NULL);

        xdr.xdr_buf = nvl->nv_data;
        xdr.xdr_idx = nvl->nv_data;
        xdr.xdr_buf_size = nvl->nv_size;

        xdr.xdr_idx += 2 * sizeof(unsigned);

        /* Skip tp current pair */
        if (nvh != NULL) {
                xdr.xdr_idx = (uint8_t *)nvh;
        }

        pair = xdr.xdr_idx;
        if (xdr.xdr_idx > xdr.xdr_buf + xdr.xdr_buf_size)
                return (NULL);

        encoded_size = *(unsigned *)xdr.xdr_idx;
        xdr.xdr_idx += sizeof(unsigned);
        if (xdr.xdr_idx > xdr.xdr_buf + xdr.xdr_buf_size)
                return (NULL);

        decoded_size = *(unsigned *)xdr.xdr_idx;
        xdr.xdr_idx += sizeof(unsigned);
        if (xdr.xdr_idx > xdr.xdr_buf + xdr.xdr_buf_size)
                return (NULL);

        while (encoded_size && decoded_size) {
                if (nvh == NULL)
                        return ((nvp_header_t *)pair);

                xdr.xdr_idx = pair + encoded_size;
                nvh = (nvp_header_t *)xdr.xdr_idx;

                if (xdr.xdr_idx > xdr.xdr_buf + xdr.xdr_buf_size)
                        return (NULL);

                encoded_size = *(unsigned *)xdr.xdr_idx;
                xdr.xdr_idx += sizeof(unsigned);
                if (xdr.xdr_idx > xdr.xdr_buf + xdr.xdr_buf_size)
                        return (NULL);
                decoded_size = *(unsigned *)xdr.xdr_idx;
                xdr.xdr_idx += sizeof(unsigned);
                if (xdr.xdr_idx > xdr.xdr_buf + xdr.xdr_buf_size)
                        return (NULL);

                if (encoded_size != 0 && decoded_size != 0) {
                        return (nvh);
                }
        }
        return (NULL);
}

/*
 * Calculate nvlist size by walking in memory data.
 */
static bool
nvlist_size_native(xdr_t *xdr, size_t *size)
{
        uint8_t *pair;
        unsigned encoded_size, decoded_size;

        xdr->xdr_idx += 2 * sizeof(unsigned);

        pair = xdr->xdr_idx;
        if (xdr->xdr_idx > xdr->xdr_buf + xdr->xdr_buf_size)
                return (false);

        encoded_size = *(unsigned *)xdr->xdr_idx;
        xdr->xdr_idx += sizeof(unsigned);
        if (xdr->xdr_idx > xdr->xdr_buf + xdr->xdr_buf_size)
                return (false);
        decoded_size = *(unsigned *)xdr->xdr_idx;
        xdr->xdr_idx += sizeof(unsigned);
        while (encoded_size && decoded_size) {
                xdr->xdr_idx = pair + encoded_size;
                pair = xdr->xdr_idx;
                if (xdr->xdr_idx > xdr->xdr_buf + xdr->xdr_buf_size)
                        return (false);
                encoded_size = *(unsigned *)xdr->xdr_idx;
                xdr->xdr_idx += sizeof(unsigned);
                if (xdr->xdr_idx > xdr->xdr_buf + xdr->xdr_buf_size)
                        return (false);
                decoded_size = *(unsigned *)xdr->xdr_idx;
                xdr->xdr_idx += sizeof(unsigned);
        }
        *size = xdr->xdr_idx - xdr->xdr_buf;

        return (true);
}

/*
 * Export nvlist to byte stream format.
 */
int
nvlist_export(nvlist_t *nvl)
{
        int rv;
        xdr_t xdr = {
                .xdr_op = XDR_OP_ENCODE,
                .xdr_putint = _putint,
                .xdr_putuint = _putuint,
                .xdr_buf = nvl->nv_data,
                .xdr_idx = nvl->nv_data,
                .xdr_buf_size = nvl->nv_size
        };

        if (nvl->nv_header.nvh_encoding != NV_ENCODE_XDR)
                return (ENOTSUP);

        nvl->nv_idx = nvl->nv_data;
        rv = nvlist_xdr_nvlist(&xdr, nvl);

        return (rv);
}

/*
 * Import nvlist from byte stream.
 * Determine the stream size and allocate private copy.
 * Then translate the data.
 */
nvlist_t *
nvlist_import(const char *stream, size_t size)
{
        nvlist_t *nvl;
        xdr_t xdr = {
                .xdr_op = XDR_OP_DECODE,
                .xdr_getint = _getint,
                .xdr_getuint = _getuint
        };

        /* Check the nvlist head. */
        if (stream[0] != NV_ENCODE_XDR ||
            (stream[1] != '\0' && stream[1] != '\1') ||
            stream[2] != '\0' || stream[3] != '\0' ||
            be32toh(*(uint32_t *)(stream + 4)) != NV_VERSION ||
            be32toh(*(uint32_t *)(stream + 8)) != NV_UNIQUE_NAME)
                return (NULL);

        nvl = malloc(sizeof(*nvl));
        if (nvl == NULL)
                return (nvl);

        nvl->nv_header.nvh_encoding = stream[0];
        nvl->nv_header.nvh_endian = stream[1];
        nvl->nv_header.nvh_reserved1 = stream[2];
        nvl->nv_header.nvh_reserved2 = stream[3];

        xdr.xdr_buf = xdr.xdr_idx = (uint8_t *)stream + 4;
        xdr.xdr_buf_size = size - 4;

        if (!nvlist_size_xdr(&xdr, &nvl->nv_asize)) {
                free(nvl);
                return (NULL);
        }
        nvl->nv_size = nvl->nv_asize;
        nvl->nv_data = malloc(nvl->nv_asize);
        if (nvl->nv_data == NULL) {
                free(nvl);
                return (NULL);
        }
        nvl->nv_idx = nvl->nv_data;
        bcopy(stream + 4, nvl->nv_data, nvl->nv_asize);

        xdr.xdr_buf = xdr.xdr_idx = nvl->nv_data;
        xdr.xdr_buf_size = nvl->nv_asize;

        if (nvlist_xdr_nvlist(&xdr, nvl) != 0) {
                free(nvl->nv_data);
                free(nvl);
                nvl = NULL;
        }

        return (nvl);
}

/*
 * remove pair from this nvlist.
 */
int
nvlist_remove(nvlist_t *nvl, const char *name, data_type_t type)
{
        uint8_t *head, *tail;
        nvs_data_t *data;
        nvp_header_t *nvp;
        nv_string_t *nvp_name;
        nv_pair_data_t *nvp_data;
        size_t size;
        xdr_t xdr;

        if (nvl == NULL || nvl->nv_data == NULL || name == NULL)
                return (EINVAL);

        /* Make sure the nvlist size is set correct */
        xdr.xdr_idx = nvl->nv_data;
        xdr.xdr_buf = xdr.xdr_idx;
        xdr.xdr_buf_size = nvl->nv_size;
        if (!nvlist_size_native(&xdr, &nvl->nv_size))
                return (EINVAL);

        data = (nvs_data_t *)nvl->nv_data;
        nvp = &data->nvl_pair;  /* first pair in nvlist */
        head = (uint8_t *)nvp;

        while (nvp->encoded_size != 0 && nvp->decoded_size != 0) {
                nvp_name = (nv_string_t *)(nvp + 1);

                nvp_data = (nv_pair_data_t *)(&nvp_name->nv_data[0] +
                    NV_ALIGN4(nvp_name->nv_size));

                if (strlen(name) == nvp_name->nv_size &&
                    memcmp(nvp_name->nv_data, name, nvp_name->nv_size) == 0 &&
                    (nvp_data->nv_type == type || type == DATA_TYPE_UNKNOWN)) {
                        /*
                         * set tail to point to next nvpair and size
                         * is the length of the tail.
                         */
                        tail = head + nvp->encoded_size;
                        size = nvl->nv_size - (tail - nvl->nv_data);

                        /* adjust the size of the nvlist. */
                        nvl->nv_size -= nvp->encoded_size;
                        bcopy(tail, head, size);
                        return (0);
                }
                /* Not our pair, skip to next. */
                head = head + nvp->encoded_size;
                nvp = (nvp_header_t *)head;
        }
        return (ENOENT);
}

static int
clone_nvlist(const nvlist_t *nvl, const uint8_t *ptr, unsigned size,
    nvlist_t **nvlist)
{
        nvlist_t *nv;

        nv = calloc(1, sizeof(*nv));
        if (nv == NULL)
                return (ENOMEM);

        nv->nv_header = nvl->nv_header;
        nv->nv_asize = size;
        nv->nv_size = size;
        nv->nv_data = malloc(nv->nv_asize);
        if (nv->nv_data == NULL) {
                free(nv);
                return (ENOMEM);
        }

        bcopy(ptr, nv->nv_data, nv->nv_asize);
        *nvlist = nv;
        return (0);
}

/*
 * Return the next nvlist in an nvlist array.
 */
static uint8_t *
nvlist_next(const uint8_t *ptr)
{
        nvs_data_t *data;
        nvp_header_t *nvp;

        data = (nvs_data_t *)ptr;
        nvp = &data->nvl_pair;  /* first pair in nvlist */

        while (nvp->encoded_size != 0 && nvp->decoded_size != 0) {
                nvp = (nvp_header_t *)((uint8_t *)nvp + nvp->encoded_size);
        }
        return ((uint8_t *)nvp + sizeof(*nvp));
}

/*
 * Note: nvlist and nvlist array must be freed by caller.
 */
int
nvlist_find(const nvlist_t *nvl, const char *name, data_type_t type,
    int *elementsp, void *valuep, int *sizep)
{
        nvs_data_t *data;
        nvp_header_t *nvp;
        nv_string_t *nvp_name;
        nv_pair_data_t *nvp_data;
        nvlist_t **nvlist, *nv;
        uint8_t *ptr;
        int rv;

        if (nvl == NULL || nvl->nv_data == NULL || name == NULL)
                return (EINVAL);

        data = (nvs_data_t *)nvl->nv_data;
        nvp = &data->nvl_pair;  /* first pair in nvlist */

        while (nvp->encoded_size != 0 && nvp->decoded_size != 0) {
                nvp_name = (nv_string_t *)((uint8_t *)nvp + sizeof(*nvp));
                if (nvl->nv_data + nvl->nv_size <
                    nvp_name->nv_data + nvp_name->nv_size)
                        return (EIO);

                nvp_data = (nv_pair_data_t *)
                    NV_ALIGN4((uintptr_t)&nvp_name->nv_data[0] +
                    nvp_name->nv_size);

                if (strlen(name) == nvp_name->nv_size &&
                    memcmp(nvp_name->nv_data, name, nvp_name->nv_size) == 0 &&
                    (nvp_data->nv_type == type || type == DATA_TYPE_UNKNOWN)) {
                        if (elementsp != NULL)
                                *elementsp = nvp_data->nv_nelem;
                        switch (nvp_data->nv_type) {
                        case DATA_TYPE_UINT64:
                                bcopy(nvp_data->nv_data, valuep,
                                    sizeof(uint64_t));
                                return (0);
                        case DATA_TYPE_STRING:
                                nvp_name = (nv_string_t *)nvp_data->nv_data;
                                if (sizep != NULL) {
                                        *sizep = nvp_name->nv_size;
                                }
                                *(const uint8_t **)valuep =
                                    &nvp_name->nv_data[0];
                                return (0);
                        case DATA_TYPE_NVLIST:
                                ptr = &nvp_data->nv_data[0];
                                rv = clone_nvlist(nvl, ptr,
                                    nvlist_next(ptr) - ptr, &nv);
                                if (rv == 0) {
                                        *(nvlist_t **)valuep = nv;
                                }
                                return (rv);

                        case DATA_TYPE_NVLIST_ARRAY:
                                nvlist = calloc(nvp_data->nv_nelem,
                                    sizeof(nvlist_t *));
                                if (nvlist == NULL)
                                        return (ENOMEM);
                                ptr = &nvp_data->nv_data[0];
                                rv = 0;
                                for (unsigned i = 0; i < nvp_data->nv_nelem;
                                    i++) {
                                        rv = clone_nvlist(nvl, ptr,
                                            nvlist_next(ptr) - ptr, &nvlist[i]);
                                        if (rv != 0)
                                                goto error;
                                        ptr = nvlist_next(ptr);
                                }
                                *(nvlist_t ***)valuep = nvlist;
                                return (rv);
                        }
                        return (EIO);
                }
                /* Not our pair, skip to next. */
                nvp = (nvp_header_t *)((uint8_t *)nvp + nvp->encoded_size);
                if (nvl->nv_data + nvl->nv_size < (uint8_t *)nvp)
                        return (EIO);
        }
        return (ENOENT);
error:
        for (unsigned i = 0; i < nvp_data->nv_nelem; i++) {
                free(nvlist[i]->nv_data);
                free(nvlist[i]);
        }
        free(nvlist);
        return (rv);
}

static int
get_value_size(data_type_t type, const void *data, uint32_t nelem)
{
        uint64_t value_sz = 0;

        switch (type) {
        case DATA_TYPE_BOOLEAN:
                value_sz = 0;
                break;
        case DATA_TYPE_BOOLEAN_VALUE:
        case DATA_TYPE_BYTE:
        case DATA_TYPE_INT8:
        case DATA_TYPE_UINT8:
        case DATA_TYPE_INT16:
        case DATA_TYPE_UINT16:
        case DATA_TYPE_INT32:
        case DATA_TYPE_UINT32:
                /* Our smallest data unit is 32-bit */
                value_sz = sizeof(uint32_t);
                break;
        case DATA_TYPE_HRTIME:
        case DATA_TYPE_INT64:
                value_sz = sizeof(int64_t);
                break;
        case DATA_TYPE_UINT64:
                value_sz = sizeof(uint64_t);
                break;
        case DATA_TYPE_STRING:
                if (data == NULL)
                        value_sz = 0;
                else
                        value_sz = strlen(data) + 1;
                break;
        case DATA_TYPE_BYTE_ARRAY:
                value_sz = nelem * sizeof(uint8_t);
                break;
        case DATA_TYPE_BOOLEAN_ARRAY:
        case DATA_TYPE_INT8_ARRAY:
        case DATA_TYPE_UINT8_ARRAY:
        case DATA_TYPE_INT16_ARRAY:
        case DATA_TYPE_UINT16_ARRAY:
        case DATA_TYPE_INT32_ARRAY:
        case DATA_TYPE_UINT32_ARRAY:
                value_sz = (uint64_t)nelem * sizeof(uint32_t);
                break;
        case DATA_TYPE_INT64_ARRAY:
                value_sz = (uint64_t)nelem * sizeof(int64_t);
                break;
        case DATA_TYPE_UINT64_ARRAY:
                value_sz = (uint64_t)nelem * sizeof(uint64_t);
                break;
        case DATA_TYPE_STRING_ARRAY:
                value_sz = (uint64_t)nelem * sizeof(uint64_t);

                if (data != NULL) {
                        char *const *strs = data;
                        uint32_t i;

                        for (i = 0; i < nelem; i++) {
                                if (strs[i] == NULL)
                                        return (-1);
                                value_sz += strlen(strs[i]) + 1;
                        }
                }
                break;
        case DATA_TYPE_NVLIST:
                /*
                 * The decoded size of nvlist is constant.
                 */
                value_sz = NV_ALIGN(6 * 4); /* sizeof nvlist_t */
                break;
        case DATA_TYPE_NVLIST_ARRAY:
                value_sz = (uint64_t)nelem * sizeof(uint64_t) +
                    (uint64_t)nelem * NV_ALIGN(6 * 4); /* sizeof nvlist_t */
                break;
        default:
                return (-1);
        }

        return (value_sz > INT32_MAX ? -1 : (int)value_sz);
}

static int
get_nvp_data_size(data_type_t type, const void *data, uint32_t nelem)
{
        uint64_t value_sz = 0;
        xdr_t xdr;
        size_t size;

        switch (type) {
        case DATA_TYPE_BOOLEAN:
                value_sz = 0;
                break;
        case DATA_TYPE_BOOLEAN_VALUE:
        case DATA_TYPE_BYTE:
        case DATA_TYPE_INT8:
        case DATA_TYPE_UINT8:
        case DATA_TYPE_INT16:
        case DATA_TYPE_UINT16:
        case DATA_TYPE_INT32:
        case DATA_TYPE_UINT32:
                /* Our smallest data unit is 32-bit */
                value_sz = sizeof(uint32_t);
                break;
        case DATA_TYPE_HRTIME:
        case DATA_TYPE_INT64:
        case DATA_TYPE_UINT64:
                value_sz = sizeof(uint64_t);
                break;
        case DATA_TYPE_STRING:
                value_sz = 4 + NV_ALIGN4(strlen(data));
                break;
        case DATA_TYPE_BYTE_ARRAY:
                value_sz = NV_ALIGN4(nelem);
                break;
        case DATA_TYPE_BOOLEAN_ARRAY:
        case DATA_TYPE_INT8_ARRAY:
        case DATA_TYPE_UINT8_ARRAY:
        case DATA_TYPE_INT16_ARRAY:
        case DATA_TYPE_UINT16_ARRAY:
        case DATA_TYPE_INT32_ARRAY:
        case DATA_TYPE_UINT32_ARRAY:
                value_sz = 4 + (uint64_t)nelem * sizeof(uint32_t);
                break;
        case DATA_TYPE_INT64_ARRAY:
        case DATA_TYPE_UINT64_ARRAY:
                value_sz = 4 + (uint64_t)nelem * sizeof(uint64_t);
                break;
        case DATA_TYPE_STRING_ARRAY:
                if (data != NULL) {
                        char *const *strs = data;
                        uint32_t i;

                        for (i = 0; i < nelem; i++) {
                                value_sz += 4 + NV_ALIGN4(strlen(strs[i]));
                        }
                }
                break;
        case DATA_TYPE_NVLIST:
                xdr.xdr_idx = ((nvlist_t *)data)->nv_data;
                xdr.xdr_buf = xdr.xdr_idx;
                xdr.xdr_buf_size = ((nvlist_t *)data)->nv_size;

                if (!nvlist_size_native(&xdr, &size))
                        return (-1);

                value_sz = size;
                break;
        case DATA_TYPE_NVLIST_ARRAY:
                value_sz = 0;
                for (uint32_t i = 0; i < nelem; i++) {
                        xdr.xdr_idx = ((nvlist_t **)data)[i]->nv_data;
                        xdr.xdr_buf = xdr.xdr_idx;
                        xdr.xdr_buf_size = ((nvlist_t **)data)[i]->nv_size;

                        if (!nvlist_size_native(&xdr, &size))
                                return (-1);
                        value_sz += size;
                }
                break;
        default:
                return (-1);
        }

        return (value_sz > INT32_MAX ? -1 : (int)value_sz);
}

#define NVPE_SIZE(name_len, data_len) \
        (4 + 4 + 4 + NV_ALIGN4(name_len) + 4 + 4 + data_len)
#define NVP_SIZE(name_len, data_len) \
        (NV_ALIGN((4 * 4) + (name_len)) + NV_ALIGN(data_len))

static int
nvlist_add_common(nvlist_t *nvl, const char *name, data_type_t type,
    uint32_t nelem, const void *data)
{
        nvs_data_t *nvs;
        nvp_header_t head, *hp;
        uint8_t *ptr;
        size_t namelen;
        int decoded_size, encoded_size;
        xdr_t xdr = {
                .xdr_op = XDR_OP_ENCODE,
                .xdr_putint = _putint_mem,
                .xdr_putuint = _putuint_mem,
                .xdr_buf = nvl->nv_data,
                .xdr_idx = nvl->nv_data,
                .xdr_buf_size = nvl->nv_size
        };

        nvs = (nvs_data_t *)nvl->nv_data;
        if (nvs->nvl_nvflag & NV_UNIQUE_NAME)
                (void) nvlist_remove(nvl, name, type);

        xdr.xdr_buf = nvl->nv_data;
        xdr.xdr_idx = nvl->nv_data;
        xdr.xdr_buf_size = nvl->nv_size;
        if (!nvlist_size_native(&xdr, &nvl->nv_size))
                return (EINVAL);

        namelen = strlen(name);
        if ((decoded_size = get_value_size(type, data, nelem)) < 0)
                return (EINVAL);
        if ((encoded_size = get_nvp_data_size(type, data, nelem)) < 0)
                return (EINVAL);

        /*
         * The encoded size is calculated as:
         * encode_size (4) + decode_size (4) +
         * name string size  (4 + NV_ALIGN4(namelen) +
         * data type (4) + nelem size (4) + datalen
         *
         * The decoded size is calculated as:
         * Note: namelen is with terminating 0.
         * NV_ALIGN(sizeof(nvpair_t) (4 * 4) + namelen + 1) +
         * NV_ALIGN(data_len)
         */

        head.encoded_size = NVPE_SIZE(namelen, encoded_size);
        head.decoded_size = NVP_SIZE(namelen + 1, decoded_size);

        if (nvl->nv_asize - nvl->nv_size < head.encoded_size + 8) {
                ptr = realloc(nvl->nv_data, nvl->nv_asize + head.encoded_size);
                if (ptr == NULL)
                        return (ENOMEM);
                nvl->nv_data = ptr;
                nvl->nv_asize += head.encoded_size;
        }
        nvl->nv_idx = nvl->nv_data + nvl->nv_size - sizeof(*hp);
        bzero(nvl->nv_idx, head.encoded_size + 8);
        hp = (nvp_header_t *)nvl->nv_idx;
        *hp = head;
        nvl->nv_idx += sizeof(*hp);

        xdr.xdr_buf = nvl->nv_data;
        xdr.xdr_buf_size = nvl->nv_asize;
        xdr.xdr_idx = nvl->nv_idx;

        xdr.xdr_idx += xdr.xdr_putuint(&xdr, namelen);
        strlcpy((char *)xdr.xdr_idx, name, namelen + 1);
        xdr.xdr_idx += NV_ALIGN4(namelen);
        xdr.xdr_idx += xdr.xdr_putuint(&xdr, type);
        xdr.xdr_idx += xdr.xdr_putuint(&xdr, nelem);

        switch (type) {
        case DATA_TYPE_BOOLEAN:
                break;

        case DATA_TYPE_BYTE_ARRAY:
                xdr.xdr_idx += xdr.xdr_putuint(&xdr, encoded_size);
                bcopy(data, xdr.xdr_idx, nelem);
                xdr.xdr_idx += NV_ALIGN4(encoded_size);
                break;

        case DATA_TYPE_STRING:
                encoded_size = strlen(data);
                xdr.xdr_idx += xdr.xdr_putuint(&xdr, encoded_size);
                strlcpy((char *)xdr.xdr_idx, data, encoded_size + 1);
                xdr.xdr_idx += NV_ALIGN4(encoded_size);
                break;

        case DATA_TYPE_STRING_ARRAY:
                for (uint32_t i = 0; i < nelem; i++) {
                        encoded_size = strlen(((char **)data)[i]);
                        xdr.xdr_idx += xdr.xdr_putuint(&xdr, encoded_size);
                        strlcpy((char *)xdr.xdr_idx, ((char **)data)[i],
                            encoded_size + 1);
                        xdr.xdr_idx += NV_ALIGN4(encoded_size);
                }
                break;

        case DATA_TYPE_BYTE:
        case DATA_TYPE_INT8:
        case DATA_TYPE_UINT8:
                xdr_char(&xdr, (char *)data);
                break;

        case DATA_TYPE_INT8_ARRAY:
        case DATA_TYPE_UINT8_ARRAY:
                xdr_array(&xdr, nelem, (xdrproc_t)xdr_char);
                break;

        case DATA_TYPE_INT16:
                xdr_short(&xdr, (short *)data);
                break;

        case DATA_TYPE_UINT16:
                xdr_u_short(&xdr, (unsigned short *)data);
                break;

        case DATA_TYPE_INT16_ARRAY:
                xdr_array(&xdr, nelem, (xdrproc_t)xdr_short);
                break;

        case DATA_TYPE_UINT16_ARRAY:
                xdr_array(&xdr, nelem, (xdrproc_t)xdr_u_short);
                break;

        case DATA_TYPE_BOOLEAN_VALUE:
        case DATA_TYPE_INT32:
                xdr_int(&xdr, (int *)data);
                break;

        case DATA_TYPE_UINT32:
                xdr_u_int(&xdr, (unsigned int *)data);
                break;

        case DATA_TYPE_BOOLEAN_ARRAY:
        case DATA_TYPE_INT32_ARRAY:
                xdr_array(&xdr, nelem, (xdrproc_t)xdr_int);
                break;

        case DATA_TYPE_UINT32_ARRAY:
                xdr_array(&xdr, nelem, (xdrproc_t)xdr_u_int);
                break;

        case DATA_TYPE_INT64:
                xdr_int64(&xdr, (int64_t *)data);
                break;

        case DATA_TYPE_UINT64:
                xdr_uint64(&xdr, (uint64_t *)data);
                break;

        case DATA_TYPE_INT64_ARRAY:
                xdr_array(&xdr, nelem, (xdrproc_t)xdr_int64);
                break;

        case DATA_TYPE_UINT64_ARRAY:
                xdr_array(&xdr, nelem, (xdrproc_t)xdr_uint64);
                break;

        case DATA_TYPE_NVLIST:
                bcopy(((nvlist_t *)data)->nv_data, xdr.xdr_idx, encoded_size);
                break;

        case DATA_TYPE_NVLIST_ARRAY: {
                size_t size;
                xdr_t xdr_nv;

                for (uint32_t i = 0; i < nelem; i++) {
                        xdr_nv.xdr_idx = ((nvlist_t **)data)[i]->nv_data;
                        xdr_nv.xdr_buf = xdr_nv.xdr_idx;
                        xdr_nv.xdr_buf_size = ((nvlist_t **)data)[i]->nv_size;

                        if (!nvlist_size_native(&xdr_nv, &size))
                                return (EINVAL);

                        bcopy(((nvlist_t **)data)[i]->nv_data, xdr.xdr_idx,
                            size);
                        xdr.xdr_idx += size;
                }
                break;
        }
        default:
                bcopy(data, xdr.xdr_idx, encoded_size);
        }

        nvl->nv_size += head.encoded_size;

        return (0);
}

int
nvlist_add_boolean_value(nvlist_t *nvl, const char *name, int value)
{
        return (nvlist_add_common(nvl, name, DATA_TYPE_BOOLEAN_VALUE, 1,
            &value));
}

int
nvlist_add_byte(nvlist_t *nvl, const char *name, uint8_t value)
{
        return (nvlist_add_common(nvl, name, DATA_TYPE_BYTE, 1, &value));
}

int
nvlist_add_int8(nvlist_t *nvl, const char *name, int8_t value)
{
        return (nvlist_add_common(nvl, name, DATA_TYPE_INT8, 1, &value));
}

int
nvlist_add_uint8(nvlist_t *nvl, const char *name, uint8_t value)
{
        return (nvlist_add_common(nvl, name, DATA_TYPE_UINT8, 1, &value));
}

int
nvlist_add_int16(nvlist_t *nvl, const char *name, int16_t value)
{
        return (nvlist_add_common(nvl, name, DATA_TYPE_INT16, 1, &value));
}

int
nvlist_add_uint16(nvlist_t *nvl, const char *name, uint16_t value)
{
        return (nvlist_add_common(nvl, name, DATA_TYPE_UINT16, 1, &value));
}

int
nvlist_add_int32(nvlist_t *nvl, const char *name, int32_t value)
{
        return (nvlist_add_common(nvl, name, DATA_TYPE_INT32, 1, &value));
}

int
nvlist_add_uint32(nvlist_t *nvl, const char *name, uint32_t value)
{
        return (nvlist_add_common(nvl, name, DATA_TYPE_UINT32, 1, &value));
}

int
nvlist_add_int64(nvlist_t *nvl, const char *name, int64_t value)
{
        return (nvlist_add_common(nvl, name, DATA_TYPE_INT64, 1, &value));
}

int
nvlist_add_uint64(nvlist_t *nvl, const char *name, uint64_t value)
{
        return (nvlist_add_common(nvl, name, DATA_TYPE_UINT64, 1, &value));
}

int
nvlist_add_string(nvlist_t *nvl, const char *name, const char *value)
{
        return (nvlist_add_common(nvl, name, DATA_TYPE_STRING, 1, value));
}

int
nvlist_add_boolean_array(nvlist_t *nvl, const char *name, int *a, uint32_t n)
{
        return (nvlist_add_common(nvl, name, DATA_TYPE_BOOLEAN_ARRAY, n, a));
}

int
nvlist_add_byte_array(nvlist_t *nvl, const char *name, uint8_t *a, uint32_t n)
{
        return (nvlist_add_common(nvl, name, DATA_TYPE_BYTE_ARRAY, n, a));
}

int
nvlist_add_int8_array(nvlist_t *nvl, const char *name, int8_t *a, uint32_t n)
{
        return (nvlist_add_common(nvl, name, DATA_TYPE_INT8_ARRAY, n, a));
}

int
nvlist_add_uint8_array(nvlist_t *nvl, const char *name, uint8_t *a, uint32_t n)
{
        return (nvlist_add_common(nvl, name, DATA_TYPE_UINT8_ARRAY, n, a));
}

int
nvlist_add_int16_array(nvlist_t *nvl, const char *name, int16_t *a, uint32_t n)
{
        return (nvlist_add_common(nvl, name, DATA_TYPE_INT16_ARRAY, n, a));
}

int
nvlist_add_uint16_array(nvlist_t *nvl, const char *name, uint16_t *a,
    uint32_t n)
{
        return (nvlist_add_common(nvl, name, DATA_TYPE_UINT16_ARRAY, n, a));
}

int
nvlist_add_int32_array(nvlist_t *nvl, const char *name, int32_t *a, uint32_t n)
{
        return (nvlist_add_common(nvl, name, DATA_TYPE_INT32_ARRAY, n, a));
}

int
nvlist_add_uint32_array(nvlist_t *nvl, const char *name, uint32_t *a,
    uint32_t n)
{
        return (nvlist_add_common(nvl, name, DATA_TYPE_UINT32_ARRAY, n, a));
}

int
nvlist_add_int64_array(nvlist_t *nvl, const char *name, int64_t *a, uint32_t n)
{
        return (nvlist_add_common(nvl, name, DATA_TYPE_INT64_ARRAY, n, a));
}

int
nvlist_add_uint64_array(nvlist_t *nvl, const char *name, uint64_t *a,
    uint32_t n)
{
        return (nvlist_add_common(nvl, name, DATA_TYPE_UINT64_ARRAY, n, a));
}

int
nvlist_add_string_array(nvlist_t *nvl, const char *name,
    char * const *a, uint32_t n)
{
        return (nvlist_add_common(nvl, name, DATA_TYPE_STRING_ARRAY, n, a));
}

int
nvlist_add_nvlist(nvlist_t *nvl, const char *name, nvlist_t *val)
{
        return (nvlist_add_common(nvl, name, DATA_TYPE_NVLIST, 1, val));
}

int
nvlist_add_nvlist_array(nvlist_t *nvl, const char *name, nvlist_t **a,
    uint32_t n)
{
        return (nvlist_add_common(nvl, name, DATA_TYPE_NVLIST_ARRAY, n, a));
}

static const char *typenames[] = {
        "DATA_TYPE_UNKNOWN",
        "DATA_TYPE_BOOLEAN",
        "DATA_TYPE_BYTE",
        "DATA_TYPE_INT16",
        "DATA_TYPE_UINT16",
        "DATA_TYPE_INT32",
        "DATA_TYPE_UINT32",
        "DATA_TYPE_INT64",
        "DATA_TYPE_UINT64",
        "DATA_TYPE_STRING",
        "DATA_TYPE_BYTE_ARRAY",
        "DATA_TYPE_INT16_ARRAY",
        "DATA_TYPE_UINT16_ARRAY",
        "DATA_TYPE_INT32_ARRAY",
        "DATA_TYPE_UINT32_ARRAY",
        "DATA_TYPE_INT64_ARRAY",
        "DATA_TYPE_UINT64_ARRAY",
        "DATA_TYPE_STRING_ARRAY",
        "DATA_TYPE_HRTIME",
        "DATA_TYPE_NVLIST",
        "DATA_TYPE_NVLIST_ARRAY",
        "DATA_TYPE_BOOLEAN_VALUE",
        "DATA_TYPE_INT8",
        "DATA_TYPE_UINT8",
        "DATA_TYPE_BOOLEAN_ARRAY",
        "DATA_TYPE_INT8_ARRAY",
        "DATA_TYPE_UINT8_ARRAY"
};

int
nvpair_type_from_name(const char *name)
{
        unsigned i;

        for (i = 0; i < nitems(typenames); i++) {
                if (strcmp(name, typenames[i]) == 0)
                        return (i);
        }
        return (0);
}

nvp_header_t *
nvpair_find(nvlist_t *nv, const char *name)
{
        nvp_header_t *nvh;

        nvh = NULL;
        while ((nvh = nvlist_next_nvpair(nv, nvh)) != NULL) {
                nv_string_t *nvp_name;

                nvp_name = (nv_string_t *)(nvh + 1);
                if (nvp_name->nv_size == strlen(name) &&
                    memcmp(nvp_name->nv_data, name, nvp_name->nv_size) == 0)
                        break;
        }
        return (nvh);
}

void
nvpair_print(nvp_header_t *nvp, unsigned int indent)
{
        nv_string_t *nvp_name;
        nv_pair_data_t *nvp_data;
        nvlist_t nvlist;
        unsigned i, j;
        xdr_t xdr = {
                .xdr_op = XDR_OP_DECODE,
                .xdr_getint = _getint_mem,
                .xdr_getuint = _getuint_mem,
                .xdr_buf = (const uint8_t *)nvp,
                .xdr_idx = NULL,
                .xdr_buf_size = nvp->encoded_size
        };

        nvp_name = (nv_string_t *)((uintptr_t)nvp + sizeof(*nvp));
        nvp_data = (nv_pair_data_t *)
            NV_ALIGN4((uintptr_t)&nvp_name->nv_data[0] + nvp_name->nv_size);

        for (i = 0; i < indent; i++)
                printf(" ");

        printf("%s [%d] %.*s", typenames[nvp_data->nv_type],
            nvp_data->nv_nelem, nvp_name->nv_size, nvp_name->nv_data);

        xdr.xdr_idx = nvp_data->nv_data;
        switch (nvp_data->nv_type) {
        case DATA_TYPE_BYTE:
        case DATA_TYPE_INT8:
        case DATA_TYPE_UINT8: {
                char c;

                if (xdr_char(&xdr, &c))
                        printf(" = 0x%x\n", c);
                break;
        }

        case DATA_TYPE_INT16:
        case DATA_TYPE_UINT16: {
                unsigned short u;

                if (xdr_u_short(&xdr, &u))
                        printf(" = 0x%hx\n", u);
                break;
        }

        case DATA_TYPE_BOOLEAN_VALUE:
        case DATA_TYPE_INT32:
        case DATA_TYPE_UINT32: {
                unsigned u;

                if (xdr_u_int(&xdr, &u))
                        printf(" = 0x%x\n", u);
                break;
        }

        case DATA_TYPE_INT64:
        case DATA_TYPE_UINT64: {
                uint64_t u;

                if (xdr_uint64(&xdr, &u))
                        printf(" = 0x%jx\n", (uintmax_t)u);
                break;
        }

        case DATA_TYPE_INT64_ARRAY:
        case DATA_TYPE_UINT64_ARRAY: {
                uint64_t *u;

                if (xdr_array(&xdr, nvp_data->nv_nelem,
                    (xdrproc_t)xdr_uint64)) {
                        u = (uint64_t *)(nvp_data->nv_data + sizeof(unsigned));
                        for (i = 0; i < nvp_data->nv_nelem; i++)
                                printf(" [%u] = 0x%jx", i, (uintmax_t)u[i]);
                        printf("\n");
                }

                break;
        }

        case DATA_TYPE_STRING:
        case DATA_TYPE_STRING_ARRAY:
                nvp_name = (nv_string_t *)&nvp_data->nv_data[0];
                for (i = 0; i < nvp_data->nv_nelem; i++) {
                        printf(" = \"%.*s\"\n", nvp_name->nv_size,
                            nvp_name->nv_data);
                }
                break;

        case DATA_TYPE_NVLIST:
                printf("\n");
                nvlist.nv_data = &nvp_data->nv_data[0];
                nvlist_print(&nvlist, indent + 2);
                break;

        case DATA_TYPE_NVLIST_ARRAY:
                nvlist.nv_data = &nvp_data->nv_data[0];
                for (j = 0; j < nvp_data->nv_nelem; j++) {
                        size_t size;

                        printf("[%d]\n", j);
                        nvlist_print(&nvlist, indent + 2);
                        if (j != nvp_data->nv_nelem - 1) {
                                for (i = 0; i < indent; i++)
                                        printf(" ");
                                printf("%s %.*s",
                                    typenames[nvp_data->nv_type],
                                    nvp_name->nv_size,
                                    nvp_name->nv_data);
                        }
                        xdr.xdr_idx = nvlist.nv_data;
                        xdr.xdr_buf = xdr.xdr_idx;
                        xdr.xdr_buf_size = nvp->encoded_size -
                            (xdr.xdr_idx - (uint8_t *)nvp);

                        if (!nvlist_size_native(&xdr, &size))
                                return;

                        nvlist.nv_data += size;
                }
                break;

        default:
                printf("\n");
        }
}

void
nvlist_print(const nvlist_t *nvl, unsigned int indent)
{
        nvs_data_t *data;
        nvp_header_t *nvp;

        data = (nvs_data_t *)nvl->nv_data;
        nvp = &data->nvl_pair;  /* first pair in nvlist */
        while (nvp->encoded_size != 0 && nvp->decoded_size != 0) {
                nvpair_print(nvp, indent);
                nvp = (nvp_header_t *)((uint8_t *)nvp + nvp->encoded_size);
        }
        printf("%*s\n", indent + 13, "End of nvlist");
}