diff options
Diffstat (limited to 'usr')
26 files changed, 3884 insertions, 439 deletions
diff --git a/usr/src/boot/lib/libstand/zfs/Makefile.inc b/usr/src/boot/lib/libstand/zfs/Makefile.inc index 3596dd3a26..ef840587c9 100644 --- a/usr/src/boot/lib/libstand/zfs/Makefile.inc +++ b/usr/src/boot/lib/libstand/zfs/Makefile.inc @@ -23,6 +23,7 @@ SRCS += $(SRC)/common/list/list.c OBJS += zfs.o OBJS += gzip.o OBJS += edonr.o +OBJS += nvlist.o OBJS += skein.o OBJS += skein_iv.o OBJS += skein_block.o @@ -30,6 +31,8 @@ OBJS += list.o zfs.o := CPPFLAGS += -I../../common zfs.o := CPPFLAGS += -I../../../cddl/boot/zfs -I$(LZ4) +zfs.o := CPPFLAGS += -I$(SRC)/uts/common/fs/zfs +nvlist.o := CPPFLAGS += -I../../common -I../../../cddl/boot/zfs # Do not unroll skein loops, reduce code size skein_block.o := CPPFLAGS += -DSKEIN_LOOP=111 diff --git a/usr/src/boot/lib/libstand/zfs/libzfs.h b/usr/src/boot/lib/libstand/zfs/libzfs.h index 7a6fcf57ea..ddf7f91975 100644 --- a/usr/src/boot/lib/libstand/zfs/libzfs.h +++ b/usr/src/boot/lib/libstand/zfs/libzfs.h @@ -27,24 +27,133 @@ #ifndef _BOOT_LIBZFS_H_ #define _BOOT_LIBZFS_H_ +#include <zfsimpl.h> + #define ZFS_MAXNAMELEN 256 /* * ZFS fully-qualified device descriptor. */ struct zfs_devdesc { - struct devdesc dd; /* Must be first. */ - uint64_t pool_guid; - uint64_t root_guid; + struct devdesc dd; /* Must be first. */ + uint64_t pool_guid; + uint64_t root_guid; }; +/* nvp implementation version */ +#define NV_VERSION 0 + +/* nvlist persistent unique name flags, stored in nvl_nvflags */ +#define NV_UNIQUE_NAME 0x1 +#define NV_UNIQUE_NAME_TYPE 0x2 + +#define NV_ALIGN4(x) (((x) + 3) & ~3) +#define NV_ALIGN(x) (((x) + 7) & ~7) + +/* + * nvlist header. + * nvlist has 4 bytes header followed by version and flags, then nvpairs + * and the list is terminated by double zero. + */ +typedef struct { + char nvh_encoding; + char nvh_endian; + char nvh_reserved1; + char nvh_reserved2; +} nvs_header_t; + +typedef struct { + nvs_header_t nv_header; + size_t nv_asize; + size_t nv_size; + uint8_t *nv_data; + uint8_t *nv_idx; +} nvlist_t; + +/* + * nvpair header. + * nvpair has encoded and decoded size + * name string (size and data) + * data type and number of elements + * data + */ +typedef struct { + unsigned encoded_size; + unsigned decoded_size; +} nvp_header_t; + +/* + * nvlist stream head. + */ +typedef struct { + unsigned nvl_version; + unsigned nvl_nvflag; + nvp_header_t nvl_pair; +} nvs_data_t; + +typedef struct { + unsigned nv_size; + uint8_t nv_data[]; /* NV_ALIGN4(string) */ +} nv_string_t; + +typedef struct { + unsigned nv_type; /* data_type_t */ + unsigned nv_nelem; /* number of elements */ + uint8_t nv_data[]; /* data stream */ +} nv_pair_data_t; + +nvlist_t *nvlist_create(int); +void nvlist_destroy(nvlist_t *); +nvlist_t *nvlist_import(const char *, size_t); +int nvlist_export(nvlist_t *); +int nvlist_remove(nvlist_t *, const char *, data_type_t); +int nvpair_type_from_name(const char *); +nvp_header_t *nvpair_find(nvlist_t *, const char *); +void nvpair_print(nvp_header_t *, unsigned int); +void nvlist_print(const nvlist_t *, unsigned int); +char *nvstring_get(nv_string_t *); +int nvlist_find(const nvlist_t *, const char *, data_type_t, + int *, void *, int *); +nvp_header_t *nvlist_next_nvpair(nvlist_t *, nvp_header_t *); + +int nvlist_add_boolean_value(nvlist_t *, const char *, boolean_t); +int nvlist_add_byte(nvlist_t *, const char *, uint8_t); +int nvlist_add_int8(nvlist_t *, const char *, int8_t); +int nvlist_add_uint8(nvlist_t *, const char *, uint8_t); +int nvlist_add_int16(nvlist_t *, const char *, int16_t); +int nvlist_add_uint16(nvlist_t *, const char *, uint16_t); +int nvlist_add_int32(nvlist_t *, const char *, int32_t); +int nvlist_add_uint32(nvlist_t *, const char *, uint32_t); +int nvlist_add_int64(nvlist_t *, const char *, int64_t); +int nvlist_add_uint64(nvlist_t *, const char *, uint64_t); +int nvlist_add_string(nvlist_t *, const char *, const char *); +int nvlist_add_boolean_array(nvlist_t *, const char *, boolean_t *, uint32_t); +int nvlist_add_byte_array(nvlist_t *, const char *, uint8_t *, uint32_t); +int nvlist_add_int8_array(nvlist_t *, const char *, int8_t *, uint32_t); +int nvlist_add_uint8_array(nvlist_t *, const char *, uint8_t *, uint32_t); +int nvlist_add_int16_array(nvlist_t *, const char *, int16_t *, uint32_t); +int nvlist_add_uint16_array(nvlist_t *, const char *, uint16_t *, uint32_t); +int nvlist_add_int32_array(nvlist_t *, const char *, int32_t *, uint32_t); +int nvlist_add_uint32_array(nvlist_t *, const char *, uint32_t *, uint32_t); +int nvlist_add_int64_array(nvlist_t *, const char *, int64_t *, uint32_t); +int nvlist_add_uint64_array(nvlist_t *, const char *, uint64_t *, uint32_t); +int nvlist_add_string_array(nvlist_t *, const char *, char * const *, uint32_t); +int nvlist_add_nvlist(nvlist_t *, const char *, nvlist_t *); +int nvlist_add_nvlist_array(nvlist_t *, const char *, nvlist_t **, uint32_t); + int zfs_parsedev(struct zfs_devdesc *, const char *, const char **); char *zfs_bootfs(void *); char *zfs_fmtdev(void *); int zfs_probe_dev(const char *, uint64_t *); int zfs_list(const char *); +int zfs_get_bootonce(void *, const char *, char *, size_t); +int zfs_get_bootenv(void *, nvlist_t **); +int zfs_set_bootenv(void *, nvlist_t *); +int zfs_attach_nvstore(void *); uint64_t ldi_get_size(void *); +nvlist_t *vdev_read_bootenv(vdev_t *); + extern struct devsw zfs_dev; extern struct fs_ops zfs_fsops; diff --git a/usr/src/boot/lib/libstand/zfs/nvlist.c b/usr/src/boot/lib/libstand/zfs/nvlist.c new file mode 100644 index 0000000000..ff5bc8e0da --- /dev/null +++ b/usr/src/boot/lib/libstand/zfs/nvlist.c @@ -0,0 +1,1568 @@ +/* + * 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/cdefs.h> + +#include <stand.h> +#include <stdbool.h> +#include <sys/endian.h> +#include <sys/stdint.h> +#include <sys/param.h> +#include <zfsimpl.h> +#include "libzfs.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 int (*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)); +} + +/* + * 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) +{ + int hi; + unsigned lo; + 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 */ + hi = *lp >> 32; + lo = *lp & UINT32_MAX; + xdr->xdr_idx += xdr->xdr_putint(xdr, hi); + xdr->xdr_idx += xdr->xdr_putint(xdr, lo); + rv = true; + break; + + case XDR_OP_DECODE: + /* Decode buf, return value to *ip */ + xdr->xdr_idx += xdr->xdr_getint(xdr, &hi); + xdr->xdr_idx += xdr->xdr_getuint(xdr, &lo); + *lp = (((int64_t)hi) << 32) | lo; + rv = true; + } + return (rv); +} + +static bool +xdr_uint64(xdr_t *xdr, uint64_t *lp) +{ + unsigned hi, lo; + 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 */ + hi = *lp >> 32; + lo = *lp & UINT32_MAX; + xdr->xdr_idx += xdr->xdr_putint(xdr, hi); + xdr->xdr_idx += xdr->xdr_putint(xdr, lo); + rv = true; + break; + + case XDR_OP_DECODE: + /* Decode buf, return value to *ip */ + xdr->xdr_idx += xdr->xdr_getuint(xdr, &hi); + xdr->xdr_idx += xdr->xdr_getuint(xdr, &lo); + *lp = (((uint64_t)hi) << 32) | lo; + 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; + + 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; + + 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); + *(unsigned *)nvl->nv_idx = namelen; + nvl->nv_idx += sizeof (unsigned); + strlcpy((char *)nvl->nv_idx, name, namelen + 1); + nvl->nv_idx += NV_ALIGN4(namelen); + *(unsigned *)nvl->nv_idx = type; + nvl->nv_idx += sizeof (unsigned); + *(unsigned *)nvl->nv_idx = nelem; + nvl->nv_idx += sizeof (unsigned); + + switch (type) { + case DATA_TYPE_BOOLEAN: + break; + case DATA_TYPE_BYTE_ARRAY: + *(unsigned *)nvl->nv_idx = encoded_size; + nvl->nv_idx += sizeof (unsigned); + bcopy(data, nvl->nv_idx, nelem); + nvl->nv_idx += encoded_size; + break; + case DATA_TYPE_STRING: + encoded_size = strlen(data); + *(unsigned *)nvl->nv_idx = encoded_size; + nvl->nv_idx += sizeof (unsigned); + strlcpy((char *)nvl->nv_idx, data, encoded_size + 1); + nvl->nv_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]); + *(unsigned *)nvl->nv_idx = encoded_size; + nvl->nv_idx += sizeof (unsigned); + strlcpy((char *)nvl->nv_idx, ((char **)data)[i], + encoded_size + 1); + nvl->nv_idx += NV_ALIGN4(encoded_size); + } + break; + case DATA_TYPE_BYTE: + case DATA_TYPE_INT8: + case DATA_TYPE_UINT8: + case DATA_TYPE_INT8_ARRAY: + case DATA_TYPE_UINT8_ARRAY: + for (uint32_t i = 0; i < nelem; i++) { + *(unsigned *)nvl->nv_idx = ((uint8_t *)data)[i]; + nvl->nv_idx += sizeof (unsigned); + } + break; + case DATA_TYPE_INT16: + case DATA_TYPE_UINT16: + case DATA_TYPE_INT16_ARRAY: + case DATA_TYPE_UINT16_ARRAY: + for (uint32_t i = 0; i < nelem; i++) { + *(unsigned *)nvl->nv_idx = ((uint16_t *)data)[i]; + nvl->nv_idx += sizeof (unsigned); + } + break; + case DATA_TYPE_NVLIST: + bcopy(((nvlist_t *)data)->nv_data, nvl->nv_idx, encoded_size); + break; + case DATA_TYPE_NVLIST_ARRAY: { + uint8_t *buf = nvl->nv_idx; + size_t size; + xdr_t xdr; + + 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 (EINVAL); + + bcopy(((nvlist_t **)data)[i]->nv_data, buf, size); + buf += size; + } + break; + } + default: + bcopy(data, nvl->nv_idx, encoded_size); + } + + nvl->nv_size += head.encoded_size; + + return (0); +} + +int +nvlist_add_boolean_value(nvlist_t *nvl, const char *name, boolean_t 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, + boolean_t *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; + xdr_t xdr; + unsigned i, j, u; + uint64_t u64; + + 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); + + switch (nvp_data->nv_type) { + case DATA_TYPE_BYTE: + case DATA_TYPE_INT8: + case DATA_TYPE_UINT8: + bcopy(nvp_data->nv_data, &u, sizeof (u)); + printf(" = 0x%x\n", (unsigned char)u); + break; + + case DATA_TYPE_INT16: + case DATA_TYPE_UINT16: + bcopy(nvp_data->nv_data, &u, sizeof (u)); + printf(" = 0x%hx\n", (unsigned short)u); + break; + + case DATA_TYPE_BOOLEAN_VALUE: + case DATA_TYPE_INT32: + case DATA_TYPE_UINT32: + bcopy(nvp_data->nv_data, &u, sizeof (u)); + printf(" = 0x%x\n", u); + break; + + case DATA_TYPE_INT64: + case DATA_TYPE_UINT64: + bcopy(nvp_data->nv_data, &u64, sizeof (u64)); + printf(" = 0x%jx\n", (uintmax_t)u64); + 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"); +} diff --git a/usr/src/boot/lib/libstand/zfs/zfs.c b/usr/src/boot/lib/libstand/zfs/zfs.c index 34c1b7c23e..0e0af60831 100644 --- a/usr/src/boot/lib/libstand/zfs/zfs.c +++ b/usr/src/boot/lib/libstand/zfs/zfs.c @@ -123,7 +123,7 @@ zfs_close(struct open_file *f) { struct file *fp = (struct file *)f->f_fsdata; - dnode_cache_obj = 0; + dnode_cache_obj = NULL; f->f_fsdata = NULL; free(fp); @@ -168,7 +168,6 @@ zfs_seek(struct open_file *f, off_t offset, int where) struct stat sb; int error; - switch (where) { case SEEK_SET: fp->f_seekp = offset; @@ -360,6 +359,7 @@ vdev_read(vdev_t *vdev __unused, void *priv, off_t offset, void *buf, if (ret != 0) return (ret); + /* BEGIN CSTYLED */ /* * Handling reads of arbitrary offset and size - multi-sector case * and single-sector case. @@ -389,6 +389,7 @@ vdev_read(vdev_t *vdev __unused, void *priv, off_t offset, void *buf, * +-------------------------------+ * start_sec */ + /* END CSTYLED */ start_sec = offset / secsz; head = offset % secsz; total_size = roundup2(head + bytes, secsz); @@ -461,6 +462,118 @@ error: } static int +vdev_write(vdev_t *vdev, off_t offset, void *buf, size_t bytes) +{ + int fd, ret; + size_t head, tail, total_size, full_sec_size; + unsigned secsz, do_tail_write; + off_t start_sec; + ssize_t res; + char *outbuf, *bouncebuf; + + fd = (uintptr_t)vdev->v_priv; + outbuf = (char *)buf; + bouncebuf = NULL; + + ret = ioctl(fd, DIOCGSECTORSIZE, &secsz); + if (ret != 0) + return (ret); + + start_sec = offset / secsz; + head = offset % secsz; + total_size = roundup2(head + bytes, secsz); + tail = total_size - (head + bytes); + do_tail_write = ((tail > 0) && (head + bytes > secsz)); + full_sec_size = total_size; + if (head > 0) + full_sec_size -= secsz; + if (do_tail_write) + full_sec_size -= secsz; + + /* Partial sector write requires a bounce buffer. */ + if ((head > 0) || do_tail_write || bytes < secsz) { + bouncebuf = malloc(secsz); + if (bouncebuf == NULL) { + printf("vdev_write: out of memory\n"); + return (ENOMEM); + } + } + + if (lseek(fd, start_sec * secsz, SEEK_SET) == -1) { + ret = errno; + goto error; + } + + /* Partial data for first sector */ + if (head > 0) { + res = read(fd, bouncebuf, secsz); + if ((unsigned)res != secsz) { + ret = EIO; + goto error; + } + memcpy(bouncebuf + head, outbuf, min(secsz - head, bytes)); + (void) lseek(fd, -secsz, SEEK_CUR); + res = write(fd, bouncebuf, secsz); + if ((unsigned)res != secsz) { + ret = EIO; + goto error; + } + outbuf += min(secsz - head, bytes); + } + + /* + * Full data write to sectors. + * Note, there is still corner case where we write + * to sector boundary, but less than sector size, e.g. write 512B + * to 4k sector. + */ + if (full_sec_size > 0) { + if (bytes < full_sec_size) { + res = read(fd, bouncebuf, secsz); + if ((unsigned)res != secsz) { + ret = EIO; + goto error; + } + memcpy(bouncebuf, outbuf, bytes); + (void) lseek(fd, -secsz, SEEK_CUR); + res = write(fd, bouncebuf, secsz); + if ((unsigned)res != secsz) { + ret = EIO; + goto error; + } + } else { + res = write(fd, outbuf, full_sec_size); + if ((unsigned)res != full_sec_size) { + ret = EIO; + goto error; + } + outbuf += full_sec_size; + } + } + + /* Partial data write to last sector */ + if (do_tail_write) { + res = read(fd, bouncebuf, secsz); + if ((unsigned)res != secsz) { + ret = EIO; + goto error; + } + memcpy(bouncebuf, outbuf, secsz - tail); + (void) lseek(fd, -secsz, SEEK_CUR); + res = write(fd, bouncebuf, secsz); + if ((unsigned)res != secsz) { + ret = EIO; + goto error; + } + } + + ret = 0; +error: + free(bouncebuf); + return (ret); +} + +static int zfs_dev_init(void) { spa_t *spa; @@ -512,7 +625,7 @@ zfs_probe(int fd, uint64_t *pool_guid) int ret; spa = NULL; - ret = vdev_probe(vdev_read, (void *)(uintptr_t)fd, &spa); + ret = vdev_probe(vdev_read, vdev_write, (void *)(uintptr_t)fd, &spa); if (ret == 0 && pool_guid != NULL) *pool_guid = spa->spa_guid; return (ret); @@ -539,8 +652,8 @@ zfs_probe_partition(void *arg, const char *partname, ppa = (struct zfs_probe_args *)arg; strncpy(devname, ppa->devname, strlen(ppa->devname) - 1); devname[strlen(ppa->devname) - 1] = '\0'; - sprintf(devname, "%s%s:", devname, partname); - pa.fd = open(devname, O_RDONLY); + snprintf(devname, sizeof (devname), "%s%s:", devname, partname); + pa.fd = open(devname, O_RDWR); if (pa.fd == -1) return (ret); ret = zfs_probe(pa.fd, ppa->pool_guid); @@ -564,6 +677,728 @@ zfs_probe_partition(void *arg, const char *partname, return (0); } +/* + * Return bootenv nvlist from pool label. + */ +int +zfs_get_bootenv(void *vdev, nvlist_t **benvp) +{ + struct zfs_devdesc *dev = (struct zfs_devdesc *)vdev; + nvlist_t *benv = NULL; + vdev_t *vd; + spa_t *spa; + + if (dev->dd.d_dev->dv_type != DEVT_ZFS) + return (ENOTSUP); + + if ((spa = spa_find_by_dev(dev)) == NULL) + return (ENXIO); + + if (spa->spa_bootenv == NULL) { + STAILQ_FOREACH(vd, &spa->spa_root_vdev->v_children, + v_childlink) { + benv = vdev_read_bootenv(vd); + + if (benv != NULL) + break; + } + spa->spa_bootenv = benv; + } else { + benv = spa->spa_bootenv; + } + + if (benv == NULL) + return (ENOENT); + + *benvp = benv; + return (0); +} + +/* + * Store nvlist to pool label bootenv area. Also updates cached pointer in spa. + */ +int +zfs_set_bootenv(void *vdev, nvlist_t *benv) +{ + struct zfs_devdesc *dev = (struct zfs_devdesc *)vdev; + spa_t *spa; + vdev_t *vd; + + if (dev->dd.d_dev->dv_type != DEVT_ZFS) + return (ENOTSUP); + + if ((spa = spa_find_by_dev(dev)) == NULL) + return (ENXIO); + + STAILQ_FOREACH(vd, &spa->spa_root_vdev->v_children, v_childlink) { + vdev_write_bootenv(vd, benv); + } + + spa->spa_bootenv = benv; + return (0); +} + +/* + * Get bootonce value by key. The bootonce <key, value> pair is removed + * from the bootenv nvlist and the remaining nvlist is committed back to disk. + */ +int +zfs_get_bootonce(void *vdev, const char *key, char *buf, size_t size) +{ + nvlist_t *benv; + char *result = NULL; + int result_size, rv; + + if ((rv = zfs_get_bootenv(vdev, &benv)) != 0) + return (rv); + + if ((rv = nvlist_find(benv, key, DATA_TYPE_STRING, NULL, + &result, &result_size)) == 0) { + if (result_size == 0) { + /* ignore empty string */ + rv = ENOENT; + } else { + size = MIN((size_t)result_size + 1, size); + strlcpy(buf, result, size); + } + (void) nvlist_remove(benv, key, DATA_TYPE_STRING); + (void) zfs_set_bootenv(vdev, benv); + } + + return (rv); +} + +/* + * nvstore backend. + */ + +static int zfs_nvstore_setter(void *, int, const char *, + const void *, size_t); +static int zfs_nvstore_setter_str(void *, const char *, const char *, + const char *); +static int zfs_nvstore_unset_impl(void *, const char *, bool); +static int zfs_nvstore_setenv(void *, void *); + +/* + * nvstore is only present for current rootfs pool. + */ +static int +zfs_nvstore_sethook(struct env_var *ev, int flags __unused, const void *value) +{ + struct zfs_devdesc *dev; + int rv; + + archsw.arch_getdev((void **)&dev, NULL, NULL); + if (dev == NULL) + return (ENXIO); + + rv = zfs_nvstore_setter_str(dev, NULL, ev->ev_name, value); + + free(dev); + return (rv); +} + +/* + * nvstore is only present for current rootfs pool. + */ +static int +zfs_nvstore_unsethook(struct env_var *ev) +{ + struct zfs_devdesc *dev; + int rv; + + archsw.arch_getdev((void **)&dev, NULL, NULL); + if (dev == NULL) + return (ENXIO); + + rv = zfs_nvstore_unset_impl(dev, ev->ev_name, false); + + free(dev); + return (rv); +} + +static int +zfs_nvstore_getter(void *vdev, const char *name, void **data) +{ + struct zfs_devdesc *dev = (struct zfs_devdesc *)vdev; + spa_t *spa; + nvlist_t *nv; + char *str, **ptr; + int size; + int rv; + + if (dev->dd.d_dev->dv_type != DEVT_ZFS) + return (ENOTSUP); + + if ((spa = spa_find_by_dev(dev)) == NULL) + return (ENXIO); + + if (spa->spa_bootenv == NULL) + return (ENXIO); + + if (nvlist_find(spa->spa_bootenv, OS_NVSTORE, DATA_TYPE_NVLIST, + NULL, &nv, NULL) != 0) + return (ENOENT); + + rv = nvlist_find(nv, name, DATA_TYPE_STRING, NULL, &str, &size); + if (rv == 0) { + ptr = (char **)data; + asprintf(ptr, "%.*s", size, str); + if (*data == NULL) + rv = ENOMEM; + } + nvlist_destroy(nv); + return (rv); +} + +static int +zfs_nvstore_setter(void *vdev, int type, const char *name, + const void *data, size_t size) +{ + struct zfs_devdesc *dev = (struct zfs_devdesc *)vdev; + spa_t *spa; + nvlist_t *nv; + int rv; + bool env_set = true; + + if (dev->dd.d_dev->dv_type != DEVT_ZFS) + return (ENOTSUP); + + if ((spa = spa_find_by_dev(dev)) == NULL) + return (ENXIO); + + if (spa->spa_bootenv == NULL) + return (ENXIO); + + if (nvlist_find(spa->spa_bootenv, OS_NVSTORE, DATA_TYPE_NVLIST, + NULL, &nv, NULL) != 0) { + nv = nvlist_create(NV_UNIQUE_NAME); + if (nv == NULL) + return (ENOMEM); + } + + rv = 0; + switch (type) { + case DATA_TYPE_INT8: + if (size != sizeof (int8_t)) { + rv = EINVAL; + break; + } + rv = nvlist_add_int8(nv, name, *(int8_t *)data); + break; + + case DATA_TYPE_INT16: + if (size != sizeof (int16_t)) { + rv = EINVAL; + break; + } + rv = nvlist_add_int16(nv, name, *(int16_t *)data); + break; + + case DATA_TYPE_INT32: + if (size != sizeof (int32_t)) { + rv = EINVAL; + break; + } + rv = nvlist_add_int32(nv, name, *(int32_t *)data); + break; + + case DATA_TYPE_INT64: + if (size != sizeof (int64_t)) { + rv = EINVAL; + break; + } + rv = nvlist_add_int64(nv, name, *(int64_t *)data); + break; + + case DATA_TYPE_BYTE: + if (size != sizeof (uint8_t)) { + rv = EINVAL; + break; + } + rv = nvlist_add_byte(nv, name, *(int8_t *)data); + break; + + case DATA_TYPE_UINT8: + if (size != sizeof (uint8_t)) { + rv = EINVAL; + break; + } + rv = nvlist_add_uint8(nv, name, *(int8_t *)data); + break; + case DATA_TYPE_UINT16: + if (size != sizeof (uint16_t)) { + rv = EINVAL; + break; + } + rv = nvlist_add_uint16(nv, name, *(uint16_t *)data); + break; + + case DATA_TYPE_UINT32: + if (size != sizeof (uint32_t)) { + rv = EINVAL; + break; + } + rv = nvlist_add_uint32(nv, name, *(uint32_t *)data); + break; + + case DATA_TYPE_UINT64: + if (size != sizeof (uint64_t)) { + rv = EINVAL; + break; + } + rv = nvlist_add_uint64(nv, name, *(uint64_t *)data); + break; + + case DATA_TYPE_STRING: + rv = nvlist_add_string(nv, name, data); + break; + + case DATA_TYPE_BOOLEAN_VALUE: + if (size != sizeof (boolean_t)) { + rv = EINVAL; + break; + } + rv = nvlist_add_boolean_value(nv, name, *(boolean_t *)data); + break; + + default: + rv = EINVAL; + break; + } + + if (rv == 0) { + rv = nvlist_add_nvlist(spa->spa_bootenv, OS_NVSTORE, nv); + if (rv == 0) { + rv = zfs_set_bootenv(vdev, spa->spa_bootenv); + } + if (rv == 0) { + if (env_set) { + rv = zfs_nvstore_setenv(vdev, + nvpair_find(nv, name)); + } else { + env_discard(env_getenv(name)); + rv = 0; + } + } + } + + nvlist_destroy(nv); + return (rv); +} + +static int +get_int64(const char *data, int64_t *ip) +{ + char *end; + int64_t val; + + errno = 0; + val = strtoll(data, &end, 0); + if (errno != 0 || *data == '\0' || *end != '\0') + return (EINVAL); + + *ip = val; + return (0); +} + +static int +get_uint64(const char *data, uint64_t *ip) +{ + char *end; + uint64_t val; + + errno = 0; + val = strtoull(data, &end, 0); + if (errno != 0 || *data == '\0' || *end != '\0') + return (EINVAL); + + *ip = val; + return (0); +} + +/* + * Translate textual data to data type. If type is not set, and we are + * creating new pair, use DATA_TYPE_STRING. + */ +static int +zfs_nvstore_setter_str(void *vdev, const char *type, const char *name, + const char *data) +{ + struct zfs_devdesc *dev = (struct zfs_devdesc *)vdev; + spa_t *spa; + nvlist_t *nv; + int rv; + data_type_t dt; + int64_t val; + uint64_t uval; + + if (dev->dd.d_dev->dv_type != DEVT_ZFS) + return (ENOTSUP); + + if ((spa = spa_find_by_dev(dev)) == NULL) + return (ENXIO); + + if (spa->spa_bootenv == NULL) + return (ENXIO); + + if (nvlist_find(spa->spa_bootenv, OS_NVSTORE, DATA_TYPE_NVLIST, + NULL, &nv, NULL) != 0) { + nv = NULL; + } + + if (type == NULL) { + nvp_header_t *nvh; + + /* + * if there is no existing pair, default to string. + * Otherwise, use type from existing pair. + */ + nvh = nvpair_find(nv, name); + if (nvh == NULL) { + dt = DATA_TYPE_STRING; + } else { + nv_string_t *nvp_name; + nv_pair_data_t *nvp_data; + + nvp_name = (nv_string_t *)(nvh + 1); + nvp_data = (nv_pair_data_t *)(&nvp_name->nv_data[0] + + NV_ALIGN4(nvp_name->nv_size)); + dt = nvp_data->nv_type; + } + } else { + dt = nvpair_type_from_name(type); + } + nvlist_destroy(nv); + + rv = 0; + switch (dt) { + case DATA_TYPE_INT8: + rv = get_int64(data, &val); + if (rv == 0) { + int8_t v = val; + + rv = zfs_nvstore_setter(vdev, dt, name, &v, sizeof (v)); + } + break; + case DATA_TYPE_INT16: + rv = get_int64(data, &val); + if (rv == 0) { + int16_t v = val; + + rv = zfs_nvstore_setter(vdev, dt, name, &v, sizeof (v)); + } + break; + case DATA_TYPE_INT32: + rv = get_int64(data, &val); + if (rv == 0) { + int32_t v = val; + + rv = zfs_nvstore_setter(vdev, dt, name, &v, sizeof (v)); + } + break; + case DATA_TYPE_INT64: + rv = get_int64(data, &val); + if (rv == 0) { + rv = zfs_nvstore_setter(vdev, dt, name, &val, + sizeof (val)); + } + break; + + case DATA_TYPE_BYTE: + rv = get_uint64(data, &uval); + if (rv == 0) { + uint8_t v = uval; + + rv = zfs_nvstore_setter(vdev, dt, name, &v, sizeof (v)); + } + break; + + case DATA_TYPE_UINT8: + rv = get_uint64(data, &uval); + if (rv == 0) { + uint8_t v = uval; + + rv = zfs_nvstore_setter(vdev, dt, name, &v, sizeof (v)); + } + break; + + case DATA_TYPE_UINT16: + rv = get_uint64(data, &uval); + if (rv == 0) { + uint16_t v = uval; + + rv = zfs_nvstore_setter(vdev, dt, name, &v, sizeof (v)); + } + break; + + case DATA_TYPE_UINT32: + rv = get_uint64(data, &uval); + if (rv == 0) { + uint32_t v = uval; + + rv = zfs_nvstore_setter(vdev, dt, name, &v, sizeof (v)); + } + break; + + case DATA_TYPE_UINT64: + rv = get_uint64(data, &uval); + if (rv == 0) { + rv = zfs_nvstore_setter(vdev, dt, name, &uval, + sizeof (uval)); + } + break; + + case DATA_TYPE_STRING: + rv = zfs_nvstore_setter(vdev, dt, name, data, strlen(data) + 1); + break; + + case DATA_TYPE_BOOLEAN_VALUE: + rv = get_int64(data, &val); + if (rv == 0) { + boolean_t v = val; + + rv = zfs_nvstore_setter(vdev, dt, name, &v, sizeof (v)); + } + break; + + default: + rv = EINVAL; + } + return (rv); +} + +static int +zfs_nvstore_unset_impl(void *vdev, const char *name, bool unset_env) +{ + struct zfs_devdesc *dev = (struct zfs_devdesc *)vdev; + spa_t *spa; + nvlist_t *nv; + int rv; + + if (dev->dd.d_dev->dv_type != DEVT_ZFS) + return (ENOTSUP); + + if ((spa = spa_find_by_dev(dev)) == NULL) + return (ENXIO); + + if (spa->spa_bootenv == NULL) + return (ENXIO); + + if (nvlist_find(spa->spa_bootenv, OS_NVSTORE, DATA_TYPE_NVLIST, + NULL, &nv, NULL) != 0) + return (ENOENT); + + rv = nvlist_remove(nv, name, DATA_TYPE_UNKNOWN); + if (rv == 0) { + if (nvlist_next_nvpair(nv, NULL) == NULL) { + rv = nvlist_remove(spa->spa_bootenv, OS_NVSTORE, + DATA_TYPE_NVLIST); + } else { + rv = nvlist_add_nvlist(spa->spa_bootenv, + OS_NVSTORE, nv); + } + if (rv == 0) + rv = zfs_set_bootenv(vdev, spa->spa_bootenv); + } + + if (unset_env) + env_discard(env_getenv(name)); + return (rv); +} + +static int +zfs_nvstore_unset(void *vdev, const char *name) +{ + return (zfs_nvstore_unset_impl(vdev, name, true)); +} + +static int +zfs_nvstore_print(void *vdev __unused, void *ptr) +{ + + nvpair_print(ptr, 0); + return (0); +} + +/* + * Create environment variable from nvpair. + * set hook will update nvstore with new value, unset hook will remove + * variable from nvstore. + */ +static int +zfs_nvstore_setenv(void *vdev __unused, void *ptr) +{ + nvp_header_t *nvh = ptr; + nv_string_t *nvp_name, *nvp_value; + nv_pair_data_t *nvp_data; + char *name, *value; + int rv = 0; + + if (nvh == NULL) + return (ENOENT); + + nvp_name = (nv_string_t *)(nvh + 1); + nvp_data = (nv_pair_data_t *)(&nvp_name->nv_data[0] + + NV_ALIGN4(nvp_name->nv_size)); + + if ((name = nvstring_get(nvp_name)) == NULL) + return (ENOMEM); + + value = NULL; + switch (nvp_data->nv_type) { + case DATA_TYPE_BYTE: + case DATA_TYPE_UINT8: + (void) asprintf(&value, "%uc", + *(unsigned *)&nvp_data->nv_data[0]); + if (value == NULL) + rv = ENOMEM; + break; + + case DATA_TYPE_INT8: + (void) asprintf(&value, "%c", *(int *)&nvp_data->nv_data[0]); + if (value == NULL) + rv = ENOMEM; + break; + + case DATA_TYPE_INT16: + (void) asprintf(&value, "%hd", *(short *)&nvp_data->nv_data[0]); + if (value == NULL) + rv = ENOMEM; + break; + + case DATA_TYPE_UINT16: + (void) asprintf(&value, "%hu", + *(unsigned short *)&nvp_data->nv_data[0]); + if (value == NULL) + rv = ENOMEM; + break; + + case DATA_TYPE_BOOLEAN_VALUE: + case DATA_TYPE_INT32: + (void) asprintf(&value, "%d", *(int *)&nvp_data->nv_data[0]); + if (value == NULL) + rv = ENOMEM; + break; + + case DATA_TYPE_UINT32: + (void) asprintf(&value, "%u", + *(unsigned *)&nvp_data->nv_data[0]); + if (value == NULL) + rv = ENOMEM; + break; + + case DATA_TYPE_INT64: + (void) asprintf(&value, "%jd", + (intmax_t)*(int64_t *)&nvp_data->nv_data[0]); + if (value == NULL) + rv = ENOMEM; + break; + + case DATA_TYPE_UINT64: + (void) asprintf(&value, "%ju", + (uintmax_t)*(uint64_t *)&nvp_data->nv_data[0]); + if (value == NULL) + rv = ENOMEM; + break; + + case DATA_TYPE_STRING: + nvp_value = (nv_string_t *)&nvp_data->nv_data[0]; + if ((value = nvstring_get(nvp_value)) == NULL) { + rv = ENOMEM; + break; + } + break; + + default: + rv = EINVAL; + break; + } + + if (value != NULL) { + rv = env_setenv(name, EV_VOLATILE | EV_NOHOOK, value, + zfs_nvstore_sethook, zfs_nvstore_unsethook); + free(value); + } + free(name); + return (rv); +} + +static int +zfs_nvstore_iterate(void *vdev, int (*cb)(void *, void *)) +{ + struct zfs_devdesc *dev = (struct zfs_devdesc *)vdev; + spa_t *spa; + nvlist_t *nv; + nvp_header_t *nvh; + int rv; + + if (dev->dd.d_dev->dv_type != DEVT_ZFS) + return (ENOTSUP); + + if ((spa = spa_find_by_dev(dev)) == NULL) + return (ENXIO); + + if (spa->spa_bootenv == NULL) + return (ENXIO); + + if (nvlist_find(spa->spa_bootenv, OS_NVSTORE, DATA_TYPE_NVLIST, + NULL, &nv, NULL) != 0) + return (ENOENT); + + rv = 0; + nvh = NULL; + while ((nvh = nvlist_next_nvpair(nv, nvh)) != NULL) { + rv = cb(vdev, nvh); + if (rv != 0) + break; + } + return (rv); +} + +nvs_callbacks_t nvstore_zfs_cb = { + .nvs_getter = zfs_nvstore_getter, + .nvs_setter = zfs_nvstore_setter, + .nvs_setter_str = zfs_nvstore_setter_str, + .nvs_unset = zfs_nvstore_unset, + .nvs_print = zfs_nvstore_print, + .nvs_iterate = zfs_nvstore_iterate +}; + +int +zfs_attach_nvstore(void *vdev) +{ + struct zfs_devdesc *dev = vdev; + spa_t *spa; + uint64_t version; + int rv; + + if (dev->dd.d_dev->dv_type != DEVT_ZFS) + return (ENOTSUP); + + if ((spa = spa_find_by_dev(dev)) == NULL) + return (ENXIO); + + rv = nvlist_find(spa->spa_bootenv, BOOTENV_VERSION, DATA_TYPE_UINT64, + NULL, &version, NULL); + + if (rv != 0 || version != VB_NVLIST) { + return (ENXIO); + } + + dev = malloc(sizeof (*dev)); + if (dev == NULL) + return (ENOMEM); + memcpy(dev, vdev, sizeof (*dev)); + + rv = nvstore_init(spa->spa_name, &nvstore_zfs_cb, dev); + if (rv != 0) + free(dev); + else + rv = zfs_nvstore_iterate(dev, zfs_nvstore_setenv); + return (rv); +} + int zfs_probe_dev(const char *devname, uint64_t *pool_guid) { @@ -575,7 +1410,7 @@ zfs_probe_dev(const char *devname, uint64_t *pool_guid) if (pool_guid) *pool_guid = 0; - pa.fd = open(devname, O_RDONLY); + pa.fd = open(devname, O_RDWR); if (pa.fd == -1) return (ENXIO); /* @@ -636,7 +1471,7 @@ zfs_dev_print(int verbose) return (spa_all_status()); } STAILQ_FOREACH(spa, &zfs_pools, spa_link) { - sprintf(line, " zfs:%s\n", spa->spa_name); + snprintf(line, sizeof (line), " zfs:%s\n", spa->spa_name); ret = pager_output(line); if (ret != 0) break; @@ -660,12 +1495,9 @@ zfs_dev_open(struct open_file *f, ...) dev = va_arg(args, struct zfs_devdesc *); va_end(args); - if (dev->pool_guid == 0) - spa = STAILQ_FIRST(&zfs_pools); - else - spa = spa_find_by_guid(dev->pool_guid); - if (!spa) + if ((spa = spa_find_by_dev(dev)) == NULL) return (ENXIO); + mount = malloc(sizeof (*mount)); if (mount == NULL) rv = ENOMEM; @@ -856,10 +1688,11 @@ zfs_fmtdev(void *vdev) } if (rootname[0] == '\0') - sprintf(buf, "%s:%s:", dev->dd.d_dev->dv_name, spa->spa_name); + snprintf(buf, sizeof (buf), "%s:%s:", dev->dd.d_dev->dv_name, + spa->spa_name); else - sprintf(buf, "%s:%s/%s:", dev->dd.d_dev->dv_name, spa->spa_name, - rootname); + snprintf(buf, sizeof (buf), "%s:%s/%s:", dev->dd.d_dev->dv_name, + spa->spa_name, rootname); return (buf); } diff --git a/usr/src/boot/lib/libstand/zfs/zfsimpl.c b/usr/src/boot/lib/libstand/zfs/zfsimpl.c index 56889431af..9ceb9b9794 100644 --- a/usr/src/boot/lib/libstand/zfs/zfsimpl.c +++ b/usr/src/boot/lib/libstand/zfs/zfsimpl.c @@ -30,10 +30,12 @@ * Stand-alone ZFS file reader. */ +#include <stdbool.h> #include <sys/endian.h> #include <sys/stat.h> #include <sys/stdint.h> #include <sys/list.h> +#include <sys/zfs_bootenv.h> #include <inttypes.h> #include "zfsimpl.h" @@ -165,284 +167,48 @@ zfs_init(void) } static int -xdr_int(const unsigned char **xdr, int *ip) +nvlist_check_features_for_read(nvlist_t *nvl) { - *ip = be32dec(*xdr); - (*xdr) += 4; - return (0); -} - -static int -xdr_u_int(const unsigned char **xdr, uint_t *ip) -{ - *ip = be32dec(*xdr); - (*xdr) += 4; - return (0); -} - -static int -xdr_uint64_t(const unsigned char **xdr, uint64_t *lp) -{ - uint_t hi, lo; - - xdr_u_int(xdr, &hi); - xdr_u_int(xdr, &lo); - *lp = (((uint64_t)hi) << 32) | lo; - return (0); -} - -static int -nvlist_find(const unsigned char *nvlist, const char *name, int type, - int *elementsp, void *valuep, int *sizep) -{ - const unsigned char *p, *pair; - int junk; - int encoded_size, decoded_size; - - p = nvlist; - xdr_int(&p, &junk); - xdr_int(&p, &junk); - - pair = p; - xdr_int(&p, &encoded_size); - xdr_int(&p, &decoded_size); - while (encoded_size && decoded_size) { - int namelen, pairtype, elements; - const char *pairname; - - xdr_int(&p, &namelen); - pairname = (const char *)p; - p += roundup(namelen, 4); - xdr_int(&p, &pairtype); - - if (memcmp(name, pairname, namelen) == 0 && type == pairtype) { - xdr_int(&p, &elements); - if (elementsp) - *elementsp = elements; - if (type == DATA_TYPE_UINT64) { - xdr_uint64_t(&p, (uint64_t *)valuep); - return (0); - } else if (type == DATA_TYPE_STRING) { - int len; - xdr_int(&p, &len); - if (sizep != NULL) - *sizep = len; - (*(const char **)valuep) = (const char *)p; - return (0); - } else if (type == DATA_TYPE_NVLIST || - type == DATA_TYPE_NVLIST_ARRAY) { - (*(const unsigned char **)valuep) = - (const unsigned char *)p; - return (0); - } else { - return (EIO); - } - } else { - /* - * Not the pair we are looking for, skip to the - * next one. - */ - p = pair + encoded_size; - } - - pair = p; - xdr_int(&p, &encoded_size); - xdr_int(&p, &decoded_size); - } - - return (EIO); -} - -static int -nvlist_check_features_for_read(const unsigned char *nvlist) -{ - const unsigned char *p, *pair; - int junk; - int encoded_size, decoded_size; + nvlist_t *features = NULL; + nvs_data_t *data; + nvp_header_t *nvp; + nv_string_t *nvp_name; int rc; - rc = 0; + rc = nvlist_find(nvl, ZPOOL_CONFIG_FEATURES_FOR_READ, + DATA_TYPE_NVLIST, NULL, &features, NULL); + if (rc != 0) + return (rc); - p = nvlist; - xdr_int(&p, &junk); - xdr_int(&p, &junk); + data = (nvs_data_t *)features->nv_data; + nvp = &data->nvl_pair; /* first pair in nvlist */ - pair = p; - xdr_int(&p, &encoded_size); - xdr_int(&p, &decoded_size); - while (encoded_size && decoded_size) { - int namelen, pairtype; - const char *pairname; + while (nvp->encoded_size != 0 && nvp->decoded_size != 0) { int i, found; + nvp_name = (nv_string_t *)((uintptr_t)nvp + sizeof (*nvp)); found = 0; - xdr_int(&p, &namelen); - pairname = (const char *)p; - p += roundup(namelen, 4); - xdr_int(&p, &pairtype); - for (i = 0; features_for_read[i] != NULL; i++) { - if (memcmp(pairname, features_for_read[i], - namelen) == 0) { + if (memcmp(nvp_name->nv_data, features_for_read[i], + nvp_name->nv_size) == 0) { found = 1; break; } } if (!found) { - printf("ZFS: unsupported feature: %s\n", pairname); + printf("ZFS: unsupported feature: %.*s\n", + nvp_name->nv_size, nvp_name->nv_data); rc = EIO; } - - p = pair + encoded_size; - - pair = p; - xdr_int(&p, &encoded_size); - xdr_int(&p, &decoded_size); + nvp = (nvp_header_t *)((uint8_t *)nvp + nvp->encoded_size); } + nvlist_destroy(features); return (rc); } -/* - * Return the next nvlist in an nvlist array. - */ -static const unsigned char * -nvlist_next(const unsigned char *nvlist) -{ - const unsigned char *p, *pair; - int junk; - int encoded_size, decoded_size; - - p = nvlist; - xdr_int(&p, &junk); - xdr_int(&p, &junk); - - pair = p; - xdr_int(&p, &encoded_size); - xdr_int(&p, &decoded_size); - while (encoded_size && decoded_size) { - p = pair + encoded_size; - - pair = p; - xdr_int(&p, &encoded_size); - xdr_int(&p, &decoded_size); - } - - return (p); -} - -#ifdef TEST - -static const unsigned char * -nvlist_print(const unsigned char *nvlist, unsigned int indent) -{ - 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" - }; - - unsigned int i, j; - const unsigned char *p, *pair; - int junk; - int encoded_size, decoded_size; - - p = nvlist; - xdr_int(&p, &junk); - xdr_int(&p, &junk); - - pair = p; - xdr_int(&p, &encoded_size); - xdr_int(&p, &decoded_size); - while (encoded_size && decoded_size) { - int namelen, pairtype, elements; - const char *pairname; - - xdr_int(&p, &namelen); - pairname = (const char *)p; - p += roundup(namelen, 4); - xdr_int(&p, &pairtype); - - for (i = 0; i < indent; i++) - printf(" "); - printf("%s %.*s", typenames[pairtype], namelen, pairname); - - xdr_int(&p, &elements); - switch (pairtype) { - case DATA_TYPE_UINT64: { - uint64_t val; - xdr_uint64_t(&p, &val); - printf(" = 0x%jx\n", (uintmax_t)val); - break; - } - - case DATA_TYPE_STRING: { - int len; - xdr_int(&p, &len); - printf(" = \"%.*s\"\n", len, p); - break; - } - - case DATA_TYPE_NVLIST: - printf("\n"); - nvlist_print(p, indent + 1); - break; - - case DATA_TYPE_NVLIST_ARRAY: - for (j = 0; j < elements; j++) { - printf("[%d]\n", j); - p = nvlist_print(p, indent + 1); - if (j != elements - 1) { - for (i = 0; i < indent; i++) - printf(" "); - printf("%s %.*s", typenames[pairtype], - namelen, pairname); - } - } - break; - - default: - printf("\n"); - } - - p = pair + encoded_size; - - pair = p; - xdr_int(&p, &encoded_size); - xdr_int(&p, &decoded_size); - } - - return (p); -} - -#endif - static int vdev_read_phys(vdev_t *vdev, const blkptr_t *bp, void *buf, off_t offset, size_t size) @@ -450,8 +216,8 @@ vdev_read_phys(vdev_t *vdev, const blkptr_t *bp, void *buf, size_t psize; int rc; - if (!vdev->v_phys_read) - return (EIO); + if (vdev->v_phys_read == NULL) + return (ENOTSUP); if (bp) { psize = BP_GET_PSIZE(bp); @@ -459,7 +225,7 @@ vdev_read_phys(vdev_t *vdev, const blkptr_t *bp, void *buf, psize = size; } - rc = vdev->v_phys_read(vdev, vdev->v_read_priv, offset, buf, psize); + rc = vdev->v_phys_read(vdev, vdev->v_priv, offset, buf, psize); if (rc == 0) { if (bp != NULL) rc = zio_checksum_verify(vdev->v_spa, bp, buf); @@ -468,6 +234,15 @@ vdev_read_phys(vdev_t *vdev, const blkptr_t *bp, void *buf, return (rc); } +static int +vdev_write_phys(vdev_t *vdev, void *buf, off_t offset, size_t size) +{ + if (vdev->v_phys_write == NULL) + return (ENOTSUP); + + return (vdev->v_phys_write(vdev, offset, buf, size)); +} + typedef struct remap_segment { vdev_t *rs_vd; uint64_t rs_offset; @@ -1084,7 +859,7 @@ vdev_create(uint64_t guid, vdev_read_t *vdev_read) } static void -vdev_set_initial_state(vdev_t *vdev, const unsigned char *nvlist) +vdev_set_initial_state(vdev_t *vdev, const nvlist_t *nvlist) { uint64_t is_offline, is_faulted, is_degraded, is_removed, isnt_present; uint64_t is_log; @@ -1119,7 +894,7 @@ vdev_set_initial_state(vdev_t *vdev, const unsigned char *nvlist) } static int -vdev_init(uint64_t guid, const unsigned char *nvlist, vdev_t **vdevp) +vdev_init(uint64_t guid, const nvlist_t *nvlist, vdev_t **vdevp) { uint64_t id, ashift, asize, nparity; const char *path; @@ -1333,10 +1108,10 @@ vdev_insert(vdev_t *top_vdev, vdev_t *vdev) } static int -vdev_from_nvlist(spa_t *spa, uint64_t top_guid, const unsigned char *nvlist) +vdev_from_nvlist(spa_t *spa, uint64_t top_guid, const nvlist_t *nvlist) { vdev_t *top_vdev, *vdev; - const unsigned char *kids; + nvlist_t **kids = NULL; int rc, nkids; /* Get top vdev. */ @@ -1357,19 +1132,18 @@ vdev_from_nvlist(spa_t *spa, uint64_t top_guid, const unsigned char *nvlist) for (int i = 0; i < nkids; i++) { uint64_t guid; - rc = nvlist_find(kids, ZPOOL_CONFIG_GUID, + rc = nvlist_find(kids[i], ZPOOL_CONFIG_GUID, DATA_TYPE_UINT64, NULL, &guid, NULL); if (rc != 0) - return (rc); - rc = vdev_init(guid, kids, &vdev); + goto done; + + rc = vdev_init(guid, kids[i], &vdev); if (rc != 0) - return (rc); + goto done; vdev->v_spa = spa; vdev->v_top = top_vdev; vdev_insert(top_vdev, vdev); - - kids = nvlist_next(kids); } } else { /* @@ -1378,15 +1152,22 @@ vdev_from_nvlist(spa_t *spa, uint64_t top_guid, const unsigned char *nvlist) */ rc = 0; } +done: + if (kids != NULL) { + for (int i = 0; i < nkids; i++) + nvlist_destroy(kids[i]); + free(kids); + } return (rc); } static int -vdev_init_from_label(spa_t *spa, const unsigned char *nvlist) +vdev_init_from_label(spa_t *spa, const nvlist_t *nvlist) { uint64_t pool_guid, top_guid; - const unsigned char *vdevs; + nvlist_t *vdevs; + int rc; if (nvlist_find(nvlist, ZPOOL_CONFIG_POOL_GUID, DATA_TYPE_UINT64, NULL, &pool_guid, NULL) || @@ -1398,7 +1179,9 @@ vdev_init_from_label(spa_t *spa, const unsigned char *nvlist) return (ENOENT); } - return (vdev_from_nvlist(spa, top_guid, vdevs)); + rc = vdev_from_nvlist(spa, top_guid, vdevs); + nvlist_destroy(vdevs); + return (rc); } static void @@ -1447,10 +1230,10 @@ vdev_set_state(vdev_t *vdev) } static int -vdev_update_from_nvlist(uint64_t top_guid, const unsigned char *nvlist) +vdev_update_from_nvlist(uint64_t top_guid, const nvlist_t *nvlist) { vdev_t *vdev; - const unsigned char *kids; + nvlist_t **kids = NULL; int rc, nkids; /* Update top vdev. */ @@ -1465,29 +1248,32 @@ vdev_update_from_nvlist(uint64_t top_guid, const unsigned char *nvlist) for (int i = 0; i < nkids; i++) { uint64_t guid; - rc = nvlist_find(kids, ZPOOL_CONFIG_GUID, + rc = nvlist_find(kids[i], ZPOOL_CONFIG_GUID, DATA_TYPE_UINT64, NULL, &guid, NULL); if (rc != 0) break; vdev = vdev_find(guid); if (vdev != NULL) - vdev_set_initial_state(vdev, kids); - - kids = nvlist_next(kids); + vdev_set_initial_state(vdev, kids[i]); } } else { rc = 0; } + if (kids != NULL) { + for (int i = 0; i < nkids; i++) + nvlist_destroy(kids[i]); + free(kids); + } return (rc); } static int -vdev_init_from_nvlist(spa_t *spa, const unsigned char *nvlist) +vdev_init_from_nvlist(spa_t *spa, const nvlist_t *nvlist) { uint64_t pool_guid, vdev_children; - const unsigned char *vdevs, *kids; + nvlist_t *vdevs = NULL, **kids = NULL; int rc, nkids; if (nvlist_find(nvlist, ZPOOL_CONFIG_POOL_GUID, DATA_TYPE_UINT64, @@ -1501,13 +1287,16 @@ vdev_init_from_nvlist(spa_t *spa, const unsigned char *nvlist) } /* Wrong guid?! */ - if (spa->spa_guid != pool_guid) + if (spa->spa_guid != pool_guid) { + nvlist_destroy(vdevs); return (EINVAL); + } spa->spa_root_vdev->v_nchildren = vdev_children; rc = nvlist_find(vdevs, ZPOOL_CONFIG_CHILDREN, DATA_TYPE_NVLIST_ARRAY, &nkids, &kids, NULL); + nvlist_destroy(vdevs); /* * MOS config has at least one child for root vdev. @@ -1519,7 +1308,7 @@ vdev_init_from_nvlist(spa_t *spa, const unsigned char *nvlist) uint64_t guid; vdev_t *vdev; - rc = nvlist_find(kids, ZPOOL_CONFIG_GUID, DATA_TYPE_UINT64, + rc = nvlist_find(kids[i], ZPOOL_CONFIG_GUID, DATA_TYPE_UINT64, NULL, &guid, NULL); if (rc != 0) break; @@ -1528,12 +1317,16 @@ vdev_init_from_nvlist(spa_t *spa, const unsigned char *nvlist) * Top level vdev is missing, create it. */ if (vdev == NULL) - rc = vdev_from_nvlist(spa, guid, kids); + rc = vdev_from_nvlist(spa, guid, kids[i]); else - rc = vdev_update_from_nvlist(guid, kids); + rc = vdev_update_from_nvlist(guid, kids[i]); if (rc != 0) break; - kids = nvlist_next(kids); + } + if (kids != NULL) { + for (int i = 0; i < nkids; i++) + nvlist_destroy(kids[i]); + free(kids); } /* @@ -1568,29 +1361,17 @@ spa_find_by_name(const char *name) return (NULL); } -spa_t * -spa_get_primary(void) +static spa_t * +spa_find_by_dev(struct zfs_devdesc *dev) { - return (STAILQ_FIRST(&zfs_pools)); -} -vdev_t * -spa_get_primary_vdev(const spa_t *spa) -{ - vdev_t *vdev; - vdev_t *kid; - - if (spa == NULL) - spa = spa_get_primary(); - if (spa == NULL) - return (NULL); - vdev = spa->spa_root_vdev; - if (vdev == NULL) + if (dev->dd.d_dev->dv_type != DEVT_ZFS) return (NULL); - for (kid = STAILQ_FIRST(&vdev->v_children); kid != NULL; - kid = STAILQ_FIRST(&vdev->v_children)) - vdev = kid; - return (vdev); + + if (dev->pool_guid == 0) + return (STAILQ_FIRST(&zfs_pools)); + + return (spa_find_by_guid(dev->pool_guid)); } static spa_t * @@ -1835,10 +1616,258 @@ vdev_label_read(vdev_t *vd, int l, void *buf, uint64_t offset, return (vdev_read_phys(vd, &bp, buf, off, size)); } +/* + * We do need to be sure we write to correct location. + * Our vdev label does consist of 4 fields: + * pad1 (8k), reserved. + * bootenv (8k), checksummed, previously reserved, may contain garbage. + * vdev_phys (112k), checksummed + * uberblock ring (128k), checksummed. + * + * Since bootenv area may contain garbage, we can not reliably read it, as + * we can get checksum errors. + * Next best thing is vdev_phys - it is just after bootenv. It still may + * be corrupted, but in such case we will miss this one write. + */ +static int +vdev_label_write_validate(vdev_t *vd, int l, uint64_t offset) +{ + uint64_t off, o_phys; + void *buf; + size_t size = VDEV_PHYS_SIZE; + int rc; + + o_phys = offsetof(vdev_label_t, vl_vdev_phys); + off = vdev_label_offset(vd->v_psize, l, o_phys); + + /* off should be 8K from bootenv */ + if (vdev_label_offset(vd->v_psize, l, offset) + VDEV_PAD_SIZE != off) + return (EINVAL); + + buf = malloc(size); + if (buf == NULL) + return (ENOMEM); + + /* Read vdev_phys */ + rc = vdev_label_read(vd, l, buf, o_phys, size); + free(buf); + return (rc); +} + +static int +vdev_label_write(vdev_t *vd, int l, vdev_boot_envblock_t *be, uint64_t offset) +{ + zio_checksum_info_t *ci; + zio_cksum_t cksum; + off_t off; + size_t size = VDEV_PAD_SIZE; + int rc; + + if (vd->v_phys_write == NULL) + return (ENOTSUP); + + off = vdev_label_offset(vd->v_psize, l, offset); + + rc = vdev_label_write_validate(vd, l, offset); + if (rc != 0) { + return (rc); + } + + ci = &zio_checksum_table[ZIO_CHECKSUM_LABEL]; + be->vbe_zbt.zec_magic = ZEC_MAGIC; + zio_checksum_label_verifier(&be->vbe_zbt.zec_cksum, off); + ci->ci_func[0](be, size, NULL, &cksum); + be->vbe_zbt.zec_cksum = cksum; + + return (vdev_write_phys(vd, be, off, size)); +} + +static int +vdev_write_bootenv_impl(vdev_t *vdev, vdev_boot_envblock_t *be) +{ + vdev_t *kid; + int rv = 0, rc; + + STAILQ_FOREACH(kid, &vdev->v_children, v_childlink) { + if (kid->v_state != VDEV_STATE_HEALTHY) + continue; + rc = vdev_write_bootenv_impl(kid, be); + if (rv == 0) + rv = rc; + } + + /* + * Non-leaf vdevs do not have v_phys_write. + */ + if (vdev->v_phys_write == NULL) + return (rv); + + for (int l = 0; l < VDEV_LABELS; l++) { + rc = vdev_label_write(vdev, l, be, + offsetof(vdev_label_t, vl_be)); + if (rc != 0) { + printf("failed to write bootenv to %s label %d: %d\n", + vdev->v_name ? vdev->v_name : "unknown", l, rc); + rv = rc; + } + } + return (rv); +} + +int +vdev_write_bootenv(vdev_t *vdev, nvlist_t *nvl) +{ + vdev_boot_envblock_t *be; + nvlist_t nv, *nvp; + uint64_t version; + int rv; + + if (nvl->nv_size > sizeof (be->vbe_bootenv)) + return (E2BIG); + + version = VB_RAW; + nvp = vdev_read_bootenv(vdev); + if (nvp != NULL) { + nvlist_find(nvp, BOOTENV_VERSION, DATA_TYPE_UINT64, NULL, + &version, NULL); + nvlist_destroy(nvp); + } + + be = calloc(1, sizeof (*be)); + if (be == NULL) + return (ENOMEM); + + be->vbe_version = version; + switch (version) { + case VB_RAW: + /* + * If there is no envmap, we will just wipe bootenv. + */ + nvlist_find(nvl, GRUB_ENVMAP, DATA_TYPE_STRING, NULL, + be->vbe_bootenv, NULL); + rv = 0; + break; + + case VB_NVLIST: + nv.nv_header = nvl->nv_header; + nv.nv_asize = nvl->nv_asize; + nv.nv_size = nvl->nv_size; + + bcopy(&nv.nv_header, be->vbe_bootenv, sizeof (nv.nv_header)); + nv.nv_data = (uint8_t *)be->vbe_bootenv + sizeof (nvs_header_t); + bcopy(nvl->nv_data, nv.nv_data, nv.nv_size); + rv = nvlist_export(&nv); + break; + + default: + rv = EINVAL; + break; + } + + if (rv == 0) { + be->vbe_version = htobe64(be->vbe_version); + rv = vdev_write_bootenv_impl(vdev, be); + } + free(be); + return (rv); +} + +/* + * Read the bootenv area from pool label, return the nvlist from it. + * We return from first successful read. + */ +nvlist_t * +vdev_read_bootenv(vdev_t *vdev) +{ + vdev_t *kid; + nvlist_t *benv; + vdev_boot_envblock_t *be; + char *command; + bool ok; + int rv; + + STAILQ_FOREACH(kid, &vdev->v_children, v_childlink) { + if (kid->v_state != VDEV_STATE_HEALTHY) + continue; + + benv = vdev_read_bootenv(kid); + if (benv != NULL) + return (benv); + } + + be = malloc(sizeof (*be)); + if (be == NULL) + return (NULL); + + rv = 0; + for (int l = 0; l < VDEV_LABELS; l++) { + rv = vdev_label_read(vdev, l, be, + offsetof(vdev_label_t, vl_be), + sizeof (*be)); + if (rv == 0) + break; + } + if (rv != 0) { + free(be); + return (NULL); + } + + be->vbe_version = be64toh(be->vbe_version); + switch (be->vbe_version) { + case VB_RAW: + /* + * if we have textual data in vbe_bootenv, create nvlist + * with key "envmap". + */ + benv = nvlist_create(NV_UNIQUE_NAME); + if (benv != NULL) { + if (*be->vbe_bootenv == '\0') { + nvlist_add_uint64(benv, BOOTENV_VERSION, + VB_NVLIST); + break; + } + nvlist_add_uint64(benv, BOOTENV_VERSION, VB_RAW); + be->vbe_bootenv[sizeof (be->vbe_bootenv) - 1] = '\0'; + nvlist_add_string(benv, GRUB_ENVMAP, be->vbe_bootenv); + } + break; + + case VB_NVLIST: + benv = nvlist_import(be->vbe_bootenv, sizeof (be->vbe_bootenv)); + break; + + default: + command = (char *)be; + ok = false; + + /* Check for legacy zfsbootcfg command string */ + for (int i = 0; command[i] != '\0'; i++) { + if (iscntrl(command[i])) { + ok = false; + break; + } else { + ok = true; + } + } + benv = nvlist_create(NV_UNIQUE_NAME); + if (benv != NULL) { + if (ok) + nvlist_add_string(benv, FREEBSD_BOOTONCE, + command); + else + nvlist_add_uint64(benv, BOOTENV_VERSION, + VB_NVLIST); + } + break; + } + free(be); + return (benv); +} + static uint64_t -vdev_get_label_asize(unsigned char *nvl) +vdev_get_label_asize(nvlist_t *nvl) { - unsigned char *vdevs; + nvlist_t *vdevs; uint64_t asize; const char *type; int len; @@ -1867,7 +1896,7 @@ vdev_get_label_asize(unsigned char *nvl) goto done; if (memcmp(type, VDEV_TYPE_RAIDZ, len) == 0) { - unsigned char *kids; + nvlist_t **kids; int nkids; if (nvlist_find(vdevs, ZPOOL_CONFIG_CHILDREN, @@ -1877,6 +1906,9 @@ vdev_get_label_asize(unsigned char *nvl) } asize /= nkids; + for (int i = 0; i < nkids; i++) + nvlist_destroy(kids[i]); + free(kids); } asize += VDEV_LABEL_START_SIZE + VDEV_LABEL_END_SIZE; @@ -1884,48 +1916,44 @@ done: return (asize); } -static unsigned char * +static nvlist_t * vdev_label_read_config(vdev_t *vd, uint64_t txg) { vdev_phys_t *label; uint64_t best_txg = 0; uint64_t label_txg = 0; uint64_t asize; - unsigned char *nvl; - size_t nvl_size; + nvlist_t *nvl = NULL, *tmp; int error; label = malloc(sizeof (vdev_phys_t)); if (label == NULL) return (NULL); - nvl_size = VDEV_PHYS_SIZE - sizeof (zio_eck_t) - 4; - nvl = malloc(nvl_size); - if (nvl == NULL) - goto done; - for (int l = 0; l < VDEV_LABELS; l++) { - const unsigned char *nvlist; - if (vdev_label_read(vd, l, label, offsetof(vdev_label_t, vl_vdev_phys), sizeof (vdev_phys_t))) continue; - if (label->vp_nvlist[0] != NV_ENCODE_XDR) + tmp = nvlist_import(label->vp_nvlist, + sizeof (label->vp_nvlist)); + if (tmp == NULL) continue; - nvlist = (const unsigned char *) label->vp_nvlist + 4; - error = nvlist_find(nvlist, ZPOOL_CONFIG_POOL_TXG, + error = nvlist_find(tmp, ZPOOL_CONFIG_POOL_TXG, DATA_TYPE_UINT64, NULL, &label_txg, NULL); if (error != 0 || label_txg == 0) { - memcpy(nvl, nvlist, nvl_size); + nvlist_destroy(nvl); + nvl = tmp; goto done; } if (label_txg <= txg && label_txg > best_txg) { best_txg = label_txg; - memcpy(nvl, nvlist, nvl_size); + nvlist_destroy(nvl); + nvl = tmp; + tmp = NULL; /* * Use asize from pool config. We need this @@ -1936,10 +1964,11 @@ vdev_label_read_config(vdev_t *vd, uint64_t txg) vd->v_psize = asize; } } + nvlist_destroy(tmp); } if (best_txg == 0) { - free(nvl); + nvlist_destroy(nvl); nvl = NULL; } done: @@ -1973,17 +2002,17 @@ vdev_uberblock_load(vdev_t *vd, uberblock_t *ub) } static int -vdev_probe(vdev_phys_read_t *phys_read, void *read_priv, spa_t **spap) +vdev_probe(vdev_phys_read_t *_read, vdev_phys_write_t *_write, void *priv, + spa_t **spap) { vdev_t vtmp; spa_t *spa; vdev_t *vdev; - unsigned char *nvlist; + nvlist_t *nvl; uint64_t val; uint64_t guid, vdev_children; uint64_t pool_txg, pool_guid; const char *pool_name; - const unsigned char *features; int rc, namelen; /* @@ -1991,63 +2020,63 @@ vdev_probe(vdev_phys_read_t *phys_read, void *read_priv, spa_t **spap) * uberblock is most current. */ memset(&vtmp, 0, sizeof (vtmp)); - vtmp.v_phys_read = phys_read; - vtmp.v_read_priv = read_priv; - vtmp.v_psize = P2ALIGN(ldi_get_size(read_priv), + vtmp.v_phys_read = _read; + vtmp.v_phys_write = _write; + vtmp.v_priv = priv; + vtmp.v_psize = P2ALIGN(ldi_get_size(priv), (uint64_t)sizeof (vdev_label_t)); /* Test for minimum device size. */ if (vtmp.v_psize < SPA_MINDEVSIZE) return (EIO); - nvlist = vdev_label_read_config(&vtmp, UINT64_MAX); - if (nvlist == NULL) + nvl = vdev_label_read_config(&vtmp, UINT64_MAX); + if (nvl == NULL) return (EIO); - if (nvlist_find(nvlist, ZPOOL_CONFIG_VERSION, DATA_TYPE_UINT64, + if (nvlist_find(nvl, ZPOOL_CONFIG_VERSION, DATA_TYPE_UINT64, NULL, &val, NULL) != 0) { - free(nvlist); + nvlist_destroy(nvl); return (EIO); } if (!SPA_VERSION_IS_SUPPORTED(val)) { printf("ZFS: unsupported ZFS version %u (should be %u)\n", (unsigned)val, (unsigned)SPA_VERSION); - free(nvlist); + nvlist_destroy(nvl); return (EIO); } /* Check ZFS features for read */ - if (nvlist_find(nvlist, ZPOOL_CONFIG_FEATURES_FOR_READ, - DATA_TYPE_NVLIST, NULL, &features, NULL) == 0 && - nvlist_check_features_for_read(features) != 0) { - free(nvlist); + rc = nvlist_check_features_for_read(nvl); + if (rc != 0) { + nvlist_destroy(nvl); return (EIO); } - if (nvlist_find(nvlist, ZPOOL_CONFIG_POOL_STATE, DATA_TYPE_UINT64, + if (nvlist_find(nvl, ZPOOL_CONFIG_POOL_STATE, DATA_TYPE_UINT64, NULL, &val, NULL) != 0) { - free(nvlist); + nvlist_destroy(nvl); return (EIO); } if (val == POOL_STATE_DESTROYED) { /* We don't boot only from destroyed pools. */ - free(nvlist); + nvlist_destroy(nvl); return (EIO); } - if (nvlist_find(nvlist, ZPOOL_CONFIG_POOL_TXG, DATA_TYPE_UINT64, + if (nvlist_find(nvl, ZPOOL_CONFIG_POOL_TXG, DATA_TYPE_UINT64, NULL, &pool_txg, NULL) != 0 || - nvlist_find(nvlist, ZPOOL_CONFIG_POOL_GUID, DATA_TYPE_UINT64, + nvlist_find(nvl, ZPOOL_CONFIG_POOL_GUID, DATA_TYPE_UINT64, NULL, &pool_guid, NULL) != 0 || - nvlist_find(nvlist, ZPOOL_CONFIG_POOL_NAME, DATA_TYPE_STRING, + nvlist_find(nvl, ZPOOL_CONFIG_POOL_NAME, DATA_TYPE_STRING, NULL, &pool_name, &namelen) != 0) { /* * Cache and spare devices end up here - just ignore * them. */ - free(nvlist); + nvlist_destroy(nvl); return (EIO); } @@ -2058,11 +2087,11 @@ vdev_probe(vdev_phys_read_t *phys_read, void *read_priv, spa_t **spap) if (spa == NULL) { char *name; - nvlist_find(nvlist, ZPOOL_CONFIG_VDEV_CHILDREN, + nvlist_find(nvl, ZPOOL_CONFIG_VDEV_CHILDREN, DATA_TYPE_UINT64, NULL, &vdev_children, NULL); name = malloc(namelen + 1); if (name == NULL) { - free(nvlist); + nvlist_destroy(nvl); return (ENOMEM); } bcopy(pool_name, name, namelen); @@ -2070,7 +2099,7 @@ vdev_probe(vdev_phys_read_t *phys_read, void *read_priv, spa_t **spap) spa = spa_create(pool_guid, name); free(name); if (spa == NULL) { - free(nvlist); + nvlist_destroy(nvl); return (ENOMEM); } spa->spa_root_vdev->v_nchildren = vdev_children; @@ -2084,20 +2113,20 @@ vdev_probe(vdev_phys_read_t *phys_read, void *read_priv, spa_t **spap) * be some kind of alias (overlapping slices, dangerously dedicated * disks etc). */ - if (nvlist_find(nvlist, ZPOOL_CONFIG_GUID, DATA_TYPE_UINT64, + if (nvlist_find(nvl, ZPOOL_CONFIG_GUID, DATA_TYPE_UINT64, NULL, &guid, NULL) != 0) { - free(nvlist); + nvlist_destroy(nvl); return (EIO); } vdev = vdev_find(guid); /* Has this vdev already been inited? */ if (vdev && vdev->v_phys_read) { - free(nvlist); + nvlist_destroy(nvl); return (EIO); } - rc = vdev_init_from_label(spa, nvlist); - free(nvlist); + rc = vdev_init_from_label(spa, nvl); + nvlist_destroy(nvl); if (rc != 0) return (rc); @@ -2107,8 +2136,9 @@ vdev_probe(vdev_phys_read_t *phys_read, void *read_priv, spa_t **spap) */ vdev = vdev_find(guid); if (vdev != NULL) { - vdev->v_phys_read = phys_read; - vdev->v_read_priv = read_priv; + vdev->v_phys_read = _read; + vdev->v_phys_write = _write; + vdev->v_priv = priv; vdev->v_psize = vtmp.v_psize; /* * If no other state is set, mark vdev healthy. @@ -3382,12 +3412,12 @@ check_mos_features(const spa_t *spa) } static int -load_nvlist(spa_t *spa, uint64_t obj, unsigned char **value) +load_nvlist(spa_t *spa, uint64_t obj, nvlist_t **value) { dnode_phys_t dir; size_t size; int rc; - unsigned char *nv; + char *nv; *value = NULL; if ((rc = objset_get_dnode(spa, &spa->spa_mos, obj, &dir)) != 0) @@ -3411,7 +3441,8 @@ load_nvlist(spa_t *spa, uint64_t obj, unsigned char **value) nv = NULL; return (rc); } - *value = nv; + *value = nvlist_import(nv, size); + free(nv); return (rc); } @@ -3420,7 +3451,7 @@ zfs_spa_init(spa_t *spa) { dnode_phys_t dir; uint64_t config_object; - unsigned char *nvlist; + nvlist_t *nvlist; int rc; if (zio_read(spa, &spa->spa_uberblock.ub_rootbp, &spa->spa_mos)) { @@ -3463,8 +3494,8 @@ zfs_spa_init(spa_t *spa) * Update vdevs from MOS config. Note, we do skip encoding bytes * here. See also vdev_label_read_config(). */ - rc = vdev_init_from_nvlist(spa, nvlist + 4); - free(nvlist); + rc = vdev_init_from_nvlist(spa, nvlist); + nvlist_destroy(nvlist); return (rc); } diff --git a/usr/src/boot/sys/boot/common/bootstrap.h b/usr/src/boot/sys/boot/common/bootstrap.h index 7cdeb6d1c4..1703045266 100644 --- a/usr/src/boot/sys/boot/common/bootstrap.h +++ b/usr/src/boot/sys/boot/common/bootstrap.h @@ -28,9 +28,9 @@ #define _BOOTSTRAP_H_ #include <sys/types.h> -#include <stdbool.h> #include <sys/queue.h> #include <sys/linker_set.h> +#include <stdbool.h> /* Commands and return values; nonzero return sets command_errmsg != NULL */ typedef int (bootblk_cmd_t)(int argc, char *argv[]); @@ -375,6 +375,37 @@ void delay(int delay); void dev_cleanup(void); +/* + * nvstore API. + */ +typedef int (nvstore_getter_cb_t)(void *, const char *, void **); +typedef int (nvstore_setter_cb_t)(void *, int, const char *, + const void *, size_t); +typedef int (nvstore_setter_str_cb_t)(void *, const char *, const char *, + const char *); +typedef int (nvstore_unset_cb_t)(void *, const char *); +typedef int (nvstore_print_cb_t)(void *, void *); +typedef int (nvstore_iterate_cb_t)(void *, int (*)(void *, void *)); + +typedef struct nvs_callbacks { + nvstore_getter_cb_t *nvs_getter; + nvstore_setter_cb_t *nvs_setter; + nvstore_setter_str_cb_t *nvs_setter_str; + nvstore_unset_cb_t *nvs_unset; + nvstore_print_cb_t *nvs_print; + nvstore_iterate_cb_t *nvs_iterate; +} nvs_callbacks_t; + +int nvstore_init(const char *, nvs_callbacks_t *, void *); +int nvstore_fini(const char *); +void *nvstore_get_store(const char *); +int nvstore_print(void *); +int nvstore_get_var(void *, const char *, void **); +int nvstore_set_var(void *, int, const char *, void *, size_t); +int nvstore_set_var_from_string(void *, const char *, const char *, + const char *); +int nvstore_unset_var(void *, const char *); + #ifndef CTASSERT /* Allow lint to override */ #define CTASSERT(x) _CTASSERT(x, __LINE__) #define _CTASSERT(x, y) __CTASSERT(x, y) diff --git a/usr/src/boot/sys/boot/common/nvstore.c b/usr/src/boot/sys/boot/common/nvstore.c new file mode 100644 index 0000000000..b3e6cdbeaa --- /dev/null +++ b/usr/src/boot/sys/boot/common/nvstore.c @@ -0,0 +1,309 @@ +/* + * 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. + */ + +/* + * Big Theory Statement. + * + * nvstore is abstraction layer to implement data read/write to different + * types of non-volatile storage. + * + * User interfaces: + * Provide mapping via environment: setenv/unsetenv/putenv. Access via + * environment functions/commands is available once nvstore has + * attached the backend and stored textual data is mapped to environment. + * + * Provide command "nvstore" to create new data instances. + * + * API: TBD. + * nvstore_init(): attach new backend and create the environment mapping. + * nvstore_fini: detach backend and unmap the related environment. + * + * The disk based storage, such as UFS file or ZFS bootenv label area, is + * only accessible after root file system is set. Root file system change + * will switch the back end storage. + */ + +#include <sys/cdefs.h> + +#include <stdbool.h> +#include <sys/queue.h> +#include <bootstrap.h> +#include "stand.h" + +typedef struct nvstore { + char *nvs_name; + void *nvs_data; + nvs_callbacks_t *nvs_cb; + STAILQ_ENTRY(nvstore) nvs_next; +} nvstore_t; + +typedef STAILQ_HEAD(store_list, nvstore) nvstore_list_t; + +nvstore_list_t stores = STAILQ_HEAD_INITIALIZER(stores); + +void * +nvstore_get_store(const char *name) +{ + nvstore_t *st; + + st = NULL; + + STAILQ_FOREACH(st, &stores, nvs_next) { + if (strcmp(name, st->nvs_name) == 0) + break; + } + + return (st); +} + +int +nvstore_init(const char *name, nvs_callbacks_t *cb, void *data) +{ + nvstore_t *st; + + st = nvstore_get_store(name); + if (st != NULL) + return (EEXIST); + + if ((st = malloc(sizeof (*st))) == NULL) + return (ENOMEM); + + if ((st->nvs_name = strdup(name)) == NULL) { + free(st); + return (ENOMEM); + } + + st->nvs_data = data; + st->nvs_cb = cb; + + STAILQ_INSERT_TAIL(&stores, st, nvs_next); + return (0); +} + +int +nvstore_fini(const char *name) +{ + nvstore_t *st; + + st = nvstore_get_store(name); + if (st == NULL) + return (ENOENT); + + STAILQ_REMOVE(&stores, st, nvstore, nvs_next); + + free(st->nvs_name); + free(st->nvs_data); + free(st); + return (0); +} + +int +nvstore_print(void *ptr) +{ + nvstore_t *st = ptr; + + return (st->nvs_cb->nvs_iterate(st->nvs_data, st->nvs_cb->nvs_print)); +} + +int +nvstore_get_var(void *ptr, const char *name, void **data) +{ + nvstore_t *st = ptr; + + return (st->nvs_cb->nvs_getter(st->nvs_data, name, data)); +} + +int +nvstore_set_var(void *ptr, int type, const char *name, + void *data, size_t size) +{ + nvstore_t *st = ptr; + + return (st->nvs_cb->nvs_setter(st->nvs_data, type, name, data, size)); +} + +int +nvstore_set_var_from_string(void *ptr, const char *type, const char *name, + const char *data) +{ + nvstore_t *st = ptr; + + return (st->nvs_cb->nvs_setter_str(st->nvs_data, type, name, data)); +} + +int +nvstore_unset_var(void *ptr, const char *name) +{ + nvstore_t *st = ptr; + + return (st->nvs_cb->nvs_unset(st->nvs_data, name)); +} + +COMMAND_SET(nvstore, "nvstore", "manage non-volatile data", command_nvstore); + +static void +nvstore_usage(const char *me) +{ + printf("Usage:\t%s -l\n", me); + printf("\t%s store -l\n", me); + printf("\t%s store [-t type] key value\n", me); + printf("\t%s store -g key\n", me); + printf("\t%s store -d key\n", me); +} + +/* + * Usage: nvstore -l # list stores + * nvstore store -l # list data in store + * nvstore store [-t type] key value + * nvstore store -g key # get value + * nvstore store -d key # delete key + */ +static int +command_nvstore(int argc, char *argv[]) +{ + int c; + bool list, get, delete; + nvstore_t *st; + char *me, *name, *type; + + me = argv[0]; + optind = 1; + optreset = 1; + + list = false; + while ((c = getopt(argc, argv, "l")) != -1) { + switch (c) { + case 'l': + list = true; + break; + case '?': + default: + return (CMD_ERROR); + } + } + + argc -= optind; + argv += optind; + + if (argc == 0) { + if (list) { + if (STAILQ_EMPTY(&stores)) { + printf("No configured nvstores\n"); + return (CMD_OK); + } + printf("List of configured nvstores:\n"); + STAILQ_FOREACH(st, &stores, nvs_next) { + printf("\t%s\n", st->nvs_name); + } + return (CMD_OK); + } + nvstore_usage(me); + return (CMD_ERROR); + } + + if (argc == 0 || (argc != 0 && list)) { + nvstore_usage(me); + return (CMD_ERROR); + } + + st = nvstore_get_store(argv[0]); + if (st == NULL) { + nvstore_usage(me); + return (CMD_ERROR); + } + + optind = 1; + optreset = 1; + name = NULL; + type = NULL; + get = delete = false; + + while ((c = getopt(argc, argv, "d:g:lt:")) != -1) { + switch (c) { + case 'd': + if (list || get) { + nvstore_usage(me); + return (CMD_ERROR); + } + name = optarg; + delete = true; + break; + case 'g': + if (delete || list) { + nvstore_usage(me); + return (CMD_ERROR); + } + name = optarg; + get = true; + break; + case 'l': + if (delete || get) { + nvstore_usage(me); + return (CMD_ERROR); + } + list = true; + break; + case 't': + type = optarg; + break; + case '?': + default: + return (CMD_ERROR); + } + } + + argc -= optind; + argv += optind; + + if (list) { + (void) nvstore_print(st); + return (CMD_OK); + } + + if (delete && name != NULL) { + (void) nvstore_unset_var(st, name); + return (CMD_OK); + } + + if (get && name != NULL) { + char *ptr = NULL; + + if (nvstore_get_var(st, name, (void **)&ptr) == 0) + printf("%s = %s\n", name, ptr); + return (CMD_OK); + } + + if (argc == 2) { + c = nvstore_set_var_from_string(st, type, argv[0], argv[1]); + if (c != 0) { + printf("error: %s\n", strerror(c)); + return (CMD_ERROR); + } + return (CMD_OK); + } + + nvstore_usage(me); + return (CMD_OK); +} diff --git a/usr/src/boot/sys/boot/efi/loader/Makefile.com b/usr/src/boot/sys/boot/efi/loader/Makefile.com index 1c8abe2a8d..58dbcfb6f2 100644 --- a/usr/src/boot/sys/boot/efi/loader/Makefile.com +++ b/usr/src/boot/sys/boot/efi/loader/Makefile.com @@ -34,6 +34,7 @@ SRCS= \ memmap.c \ mb_header.S \ multiboot2.c \ + nvstore.c \ self_reloc.c \ smbios.c \ tem.c \ @@ -53,6 +54,7 @@ OBJS= \ memmap.o \ mb_header.o \ multiboot2.o \ + nvstore.o \ self_reloc.o \ smbios.o \ tem.o \ @@ -60,6 +62,7 @@ OBJS= \ module.o := CPPFLAGS += -I$(BOOTSRC)/libcrypto tem.o := CPPFLAGS += $(DEFAULT_CONSOLE_COLOR) +main.o := CPPFLAGS += -I$(SRC)/uts/common/fs/zfs CPPFLAGS += -I../../../../../include -I../../..../ CPPFLAGS += -I../../../../../lib/libstand diff --git a/usr/src/boot/sys/boot/efi/loader/main.c b/usr/src/boot/sys/boot/efi/loader/main.c index b5b310b9ad..9d229be58b 100644 --- a/usr/src/boot/sys/boot/efi/loader/main.c +++ b/usr/src/boot/sys/boot/efi/loader/main.c @@ -32,6 +32,7 @@ #include <sys/reboot.h> #include <sys/boot.h> #include <sys/consplat.h> +#include <sys/zfs_bootenv.h> #include <stand.h> #include <inttypes.h> #include <string.h> @@ -230,6 +231,23 @@ out: } static void +set_currdev(const char *devname) +{ + + /* + * Don't execute hooks here; we may need to try setting these more than + * once here if we're probing for the ZFS pool we're supposed to boot. + * The currdev hook is intended to just validate user input anyways, + * while the loaddev hook makes it immutable once we've determined what + * the proper currdev is. + */ + env_setenv("currdev", EV_VOLATILE | EV_NOHOOK, devname, efi_setcurrdev, + env_nounset); + env_setenv("loaddev", EV_VOLATILE | EV_NOHOOK, devname, env_noset, + env_nounset); +} + +static void set_currdev_devdesc(struct devdesc *currdev) { char *devname; @@ -237,10 +255,7 @@ set_currdev_devdesc(struct devdesc *currdev) devname = efi_fmtdev(currdev); printf("Setting currdev to %s\n", devname); - - env_setenv("currdev", EV_VOLATILE, devname, efi_setcurrdev, - env_nounset); - env_setenv("loaddev", EV_VOLATILE, devname, env_noset, env_nounset); + set_currdev(devname); } static void @@ -294,6 +309,8 @@ static bool probe_zfs_currdev(uint64_t guid) { struct zfs_devdesc currdev; + char *bootonce; + bool rv; currdev.dd.d_dev = &zfs_dev; currdev.dd.d_unit = 0; @@ -301,7 +318,24 @@ probe_zfs_currdev(uint64_t guid) currdev.root_guid = 0; set_currdev_devdesc((struct devdesc *)&currdev); - return (sanity_check_currdev()); + rv = sanity_check_currdev(); + if (rv) { + bootonce = malloc(VDEV_PAD_SIZE); + if (bootonce != NULL) { + if (zfs_get_bootonce(&currdev, OS_BOOTONCE, bootonce, + VDEV_PAD_SIZE) == 0) { + printf("zfs bootonce: %s\n", bootonce); + set_currdev(bootonce); + setenv("zfs-bootonce", bootonce, 1); + } + free(bootonce); + (void) zfs_attach_nvstore(&currdev); + } else { + printf("Failed to process bootonce data: %s\n", + strerror(errno)); + } + } + return (rv); } static bool diff --git a/usr/src/boot/sys/boot/i386/gptzfsboot/Makefile b/usr/src/boot/sys/boot/i386/gptzfsboot/Makefile index fdae14f16b..c826966da5 100644 --- a/usr/src/boot/sys/boot/i386/gptzfsboot/Makefile +++ b/usr/src/boot/sys/boot/i386/gptzfsboot/Makefile @@ -50,9 +50,10 @@ CPPFLAGS += -DBOOTPROG=\"gptzfsboot\" \ LDSCRIPT= ../boot.ldscript LD_FLAGS= -static -N --gc-sections -LIBI386= ../libi386/libi386.a -LIBSTAND= ../../libstand/$(MACH)/libstand.a +LIBI386= -L ../libi386 -li386 +LIBSTAND= -L ../../libstand/$(MACH) -lstand LIBS= $(LIBI386) $(LIBSTAND) +DPADD= ../libi386/libi386.a ../../libstand/$(MACH)/libstand.a include ../Makefile.inc @@ -65,6 +66,8 @@ install: all $(ROOTBOOTPROG) OBJS = mb_header.o zfsboot.o sio.o cons.o devopen.o \ part.o disk.o bcache.o zfs_cmd.o +zfsboot.o := CPPFLAGS += -I$(SRC)/uts/common -I$(SRC)/uts/common/fs/zfs +zfs_cmd.o := CPPFLAGS += -I$(SRC)/uts/common part.o := CPPFLAGS += -I$(ZLIB) smbios.o := CPPFLAGS += -DSMBIOS_SERIAL_NUMBERS smbios.o := CPPFLAGS += -DSMBIOS_LITTLE_ENDIAN_UUID @@ -89,7 +92,7 @@ CLEANFILES += gptzfsboot.bin gptzfsboot.out gptzfsboot.bin: gptzfsboot.out $(OBJCOPY) -S -O binary gptzfsboot.out $@ -gptzfsboot.out: $(BTXCRT) $(OBJS) $(LIBI386) $(LIBSTAND) +gptzfsboot.out: $(BTXCRT) $(OBJS) $(DPADD) $(LD) $(LD_FLAGS) -T $(LDSCRIPT) -o $@ $(BTXCRT) $(OBJS) $(LIBS) machine: diff --git a/usr/src/boot/sys/boot/i386/gptzfsboot/zfsboot.c b/usr/src/boot/sys/boot/i386/gptzfsboot/zfsboot.c index 63086e3cd1..a8278eec04 100644 --- a/usr/src/boot/sys/boot/i386/gptzfsboot/zfsboot.c +++ b/usr/src/boot/sys/boot/i386/gptzfsboot/zfsboot.c @@ -24,6 +24,7 @@ #include <sys/reboot.h> #include <sys/queue.h> #include <sys/multiboot.h> +#include <sys/zfs_bootenv.h> #include <machine/bootinfo.h> #include <machine/elf.h> @@ -146,6 +147,7 @@ main(void) unsigned i; int fd; bool auto_boot; + bool nextboot = false; struct disk_devdesc devdesc; bios_getmem(); @@ -201,11 +203,31 @@ main(void) if (bdev != NULL && bdev->dd.d_dev->dv_type == DEVT_ZFS) { /* set up proper device name string for ZFS */ strncpy(boot_devname, zfs_fmtdev(bdev), sizeof (boot_devname)); + if (zfs_get_bootonce(bdev, OS_BOOTONCE, cmd, + sizeof (cmd)) == 0) { + nvlist_t *benv; + + nextboot = true; + memcpy(cmddup, cmd, sizeof (cmd)); + if (parse_cmd()) { + printf("failed to parse bootonce command\n"); + exit(0); + } + if (!OPT_CHECK(RBX_QUIET)) + printf("zfs bootonce: %s\n", cmddup); + + if (zfs_get_bootenv(bdev, &benv) == 0) { + nvlist_add_string(benv, OS_BOOTONCE_USED, + cmddup); + zfs_set_bootenv(bdev, benv); + } + /* Do not process this command twice */ + *cmd = 0; + } } /* now make sure we have bdev on all cases */ - if (bdev != NULL) - free(bdev); + free(bdev); i386_getdev((void **)&bdev, boot_devname, NULL); env_setenv("currdev", EV_VOLATILE, boot_devname, i386_setcurrdev, @@ -244,6 +266,10 @@ main(void) *cmd = 0; } + /* Do not risk waiting at the prompt forever. */ + if (nextboot && !auto_boot) + exit(0); + if (auto_boot && !*kname) { /* * Try to exec stage 3 boot loader. If interrupted by a diff --git a/usr/src/boot/sys/boot/i386/libi386/Makefile b/usr/src/boot/sys/boot/i386/libi386/Makefile index d28628c0dd..aa11fc2276 100644 --- a/usr/src/boot/sys/boot/i386/libi386/Makefile +++ b/usr/src/boot/sys/boot/i386/libi386/Makefile @@ -131,6 +131,11 @@ CFLAGS += -I$(SRC)/common/ficl -I../../libficl \ # the location of libstand CFLAGS += -I../../../../lib/libstand/ +multiboot.o := CPPFLAGS += -I../../../cddl/boot/zfs +multiboot2.o := CPPFLAGS += -I../../../cddl/boot/zfs +devicename.o := CPPFLAGS += -I../../../cddl/boot/zfs +devicename_stubs.o := CPPFLAGS += -I../../../cddl/boot/zfs + # Handle FreeBSD specific %b and %D printf format specifiers #FORMAT_EXTENSIONS=-D__printf__=__freebsd_kprintf__ #CFLAGS += ${FORMAT_EXTENSIONS} diff --git a/usr/src/boot/sys/boot/i386/loader/Makefile b/usr/src/boot/sys/boot/i386/loader/Makefile index e8d796b7fd..c8be1beb55 100644 --- a/usr/src/boot/sys/boot/i386/loader/Makefile +++ b/usr/src/boot/sys/boot/i386/loader/Makefile @@ -56,7 +56,7 @@ LIBFICL= ../../libficl/$(MACH)/libficl.a # Always add MI sources SRCS += boot.c commands.c console.c devopen.c interp.c SRCS += interp_backslash.c interp_parse.c ls.c misc.c -SRCS += module.c linenoise.c multiboot2.c +SRCS += module.c linenoise.c multiboot2.c nvstore.c SRCS += zfs_cmd.c SRCS += font.c $(FONT).c tem.c @@ -104,6 +104,11 @@ CPPFLAGS += -I../btx/lib include ../Makefile.inc +conf.o := CPPFLAGS += -I../../../cddl/boot/zfs +multiboot2.o := CPPFLAGS += -I../../../cddl/boot/zfs +main.o := CPPFLAGS += -I../../../cddl/boot/zfs -I$(SRC)/uts/common/fs/zfs +zfs_cmd.o := CPPFLAGS += -I../../../cddl/boot/zfs + # For multiboot2.h, must be last, to avoid conflicts CPPFLAGS += -I$(SRC)/uts/common diff --git a/usr/src/boot/sys/boot/i386/loader/main.c b/usr/src/boot/sys/boot/i386/loader/main.c index 169d0e1ce5..7c6fc2dcde 100644 --- a/usr/src/boot/sys/boot/i386/loader/main.c +++ b/usr/src/boot/sys/boot/i386/loader/main.c @@ -40,6 +40,7 @@ #include <sys/disk.h> #include <sys/param.h> #include <sys/reboot.h> +#include <sys/zfs_bootenv.h> #include <rbx.h> #include "bootstrap.h" @@ -208,6 +209,7 @@ extract_currdev(void) { struct i386_devdesc new_currdev; struct zfs_boot_args *zargs; + char *bootonce; int biosdev = -1; /* Assume we are booting from a BIOS disk by default */ @@ -247,6 +249,17 @@ extract_currdev(void) new_currdev.d_kind.zfs.root_guid = 0; } new_currdev.dd.d_dev = &zfs_dev; + if ((bootonce = malloc(VDEV_PAD_SIZE)) != NULL) { + if (zfs_get_bootonce(&new_currdev, OS_BOOTONCE_USED, + bootonce, VDEV_PAD_SIZE) == 0) { + setenv("zfs-bootonce", bootonce, 1); + } + free(bootonce); + (void) zfs_attach_nvstore(&new_currdev); + } else { + printf("Failed to process bootonce data: %s\n", + strerror(errno)); + } } else if ((initial_bootdev & B_MAGICMASK) != B_DEVMAGIC) { /* The passed-in boot device is bad */ new_currdev.d_kind.biosdisk.slice = -1; diff --git a/usr/src/boot/sys/cddl/boot/zfs/zfsimpl.h b/usr/src/boot/sys/cddl/boot/zfs/zfsimpl.h index 4993ca754c..43ec7e614e 100644 --- a/usr/src/boot/sys/cddl/boot/zfs/zfsimpl.h +++ b/usr/src/boot/sys/cddl/boot/zfs/zfsimpl.h @@ -59,8 +59,12 @@ * Copyright (c) 2013 by Delphix. All rights reserved. */ -#ifndef _ZFSIMPL_H -#define _ZFSIMPL_H +#ifndef _ZFSIMPL_H_ +#define _ZFSIMPL_H_ + +#include <sys/queue.h> +#include <sys/list.h> +#include <bootstrap.h> #define MAXNAMELEN 256 @@ -495,7 +499,7 @@ typedef struct zio_gbh { #define VDEV_RAIDZ_MAXPARITY 3 #define VDEV_PAD_SIZE (8 << 10) -/* 2 padding areas (vl_pad1 and vl_pad2) to skip */ +/* 2 padding areas (vl_pad1 and vl_be) to skip */ #define VDEV_SKIP_SIZE VDEV_PAD_SIZE * 2 #define VDEV_PHYS_SIZE (112 << 10) #define VDEV_UBERBLOCK_RING (128 << 10) @@ -521,9 +525,29 @@ typedef struct vdev_phys { zio_eck_t vp_zbt; } vdev_phys_t; +typedef enum vbe_vers { + /* The bootenv file is stored as ascii text in the envblock */ + VB_RAW = 0, + + /* + * The bootenv file is converted to an nvlist and then packed into the + * envblock. + */ + VB_NVLIST = 1 +} vbe_vers_t; + +typedef struct vdev_boot_envblock { + uint64_t vbe_version; + char vbe_bootenv[VDEV_PAD_SIZE - sizeof (uint64_t) - + sizeof (zio_eck_t)]; + zio_eck_t vbe_zbt; +} vdev_boot_envblock_t; + +CTASSERT(sizeof (vdev_boot_envblock_t) == VDEV_PAD_SIZE); + typedef struct vdev_label { char vl_pad1[VDEV_PAD_SIZE]; /* 8K */ - char vl_pad2[VDEV_PAD_SIZE]; /* 8K */ + vdev_boot_envblock_t vl_be; /* 8K */ vdev_phys_t vl_vdev_phys; /* 112K */ char vl_uberblock[VDEV_UBERBLOCK_RING]; /* 128K */ } vdev_label_t; /* 256K total */ @@ -899,6 +923,7 @@ typedef struct uberblock { */ uint64_t ub_mmp_delay; + /* BEGIN CSTYLED */ /* * The ub_mmp_config contains the multihost write interval, multihost * fail intervals, sequence number for sub-second granularity, and @@ -917,6 +942,7 @@ typedef struct uberblock { * - 0x04 - Fail Intervals * - 0xf8 - Reserved */ + /* END CSTYLED */ uint64_t ub_mmp_config; /* @@ -1020,6 +1046,7 @@ typedef struct dnode_phys { uint64_t dn_pad3[4]; + /* BEGIN CSTYLED */ /* * The tail region is 448 bytes for a 512 byte dnode, and * correspondingly larger for larger dnode sizes. The spill @@ -1036,6 +1063,7 @@ typedef struct dnode_phys { * | dn_blkptr[0] | dn_bonus[0..191] | dn_spill | * +---------------+-----------------------+---------------+ */ + /* END CSTYLED */ union { blkptr_t dn_blkptr[1+DN_OLD_MAX_BONUSLEN/sizeof (blkptr_t)]; struct { @@ -1641,10 +1669,9 @@ typedef struct znode_phys { */ struct vdev; struct spa; -typedef int vdev_phys_read_t(struct vdev *vdev, void *priv, - off_t offset, void *buf, size_t bytes); -typedef int vdev_read_t(struct vdev *vdev, const blkptr_t *bp, - void *buf, off_t offset, size_t bytes); +typedef int vdev_phys_read_t(struct vdev *, void *, off_t, void *, size_t); +typedef int vdev_phys_write_t(struct vdev *, off_t, void *, size_t); +typedef int vdev_read_t(struct vdev *, const blkptr_t *, void *, off_t, size_t); typedef STAILQ_HEAD(vdev_list, vdev) vdev_list_t; @@ -1774,8 +1801,9 @@ typedef struct vdev { size_t v_nchildren; /* # children */ vdev_state_t v_state; /* current state */ vdev_phys_read_t *v_phys_read; /* read from raw leaf vdev */ + vdev_phys_write_t *v_phys_write; /* write to raw leaf vdev */ vdev_read_t *v_read; /* read from vdev */ - void *v_read_priv; /* private data for read function */ + void *v_priv; /* data for read/write function */ boolean_t v_islog; struct spa *v_spa; /* link to spa */ /* @@ -1802,6 +1830,7 @@ typedef struct spa { void *spa_cksum_tmpls[ZIO_CHECKSUM_FUNCTIONS]; vdev_t *spa_boot_vdev; /* boot device for kernel */ boolean_t spa_with_log; /* this pool has log */ + void *spa_bootenv; /* bootenv from pool label */ } spa_t; /* IO related arguments. */ @@ -1819,6 +1848,6 @@ typedef struct zio { int io_error; } zio_t; -static void decode_embedded_bp_compressed(const blkptr_t *, void *); +extern void decode_embedded_bp_compressed(const blkptr_t *, void *); -#endif /* _ZFSIMPL_H */ +#endif /* _ZFSIMPL_H_ */ diff --git a/usr/src/cmd/beadm/beadm.c b/usr/src/cmd/beadm/beadm.c index 1dfc8afcd1..8a89c380b4 100644 --- a/usr/src/cmd/beadm/beadm.c +++ b/usr/src/cmd/beadm/beadm.c @@ -129,7 +129,7 @@ usage(void) "\n" "\tsubcommands:\n" "\n" - "\tbeadm activate [-v] beName\n" + "\tbeadm activate [-v] [-t | -T] beName\n" "\tbeadm create [-a] [-d BE_desc]\n" "\t\t[-o property=value] ... [-p zpool] \n" "\t\t[-e nonActiveBe | beName@snapshot] [-v] beName\n" @@ -343,7 +343,7 @@ print_be_nodes(const char *be_name, boolean_t parsable, struct hdr_info *hdr, be_node_list_t *cur_be; for (cur_be = nodes; cur_be != NULL; cur_be = cur_be->be_next_node) { - char active[3] = "-\0"; + char active[4] = "-\0\0"; int ai = 0; const char *datetime_fmt = "%F %R"; const char *name = cur_be->be_node_name; @@ -374,9 +374,12 @@ print_be_nodes(const char *be_name, boolean_t parsable, struct hdr_info *hdr, active[ai++] = 'N'; if (cur_be->be_active_on_boot) { if (!cur_be->be_global_active) - active[ai] = 'b'; + active[ai++] = 'b'; else - active[ai] = 'R'; + active[ai++] = 'R'; + } + if (cur_be->be_active_next) { + active[ai] = 'T'; } nicenum(used, buf, sizeof (buf)); @@ -472,7 +475,7 @@ print_fmt_nodes(const char *be_name, enum be_fmt be_fmt, boolean_t parsable, be_node_list_t *cur_be; for (cur_be = nodes; cur_be != NULL; cur_be = cur_be->be_next_node) { - char active[3] = "-\0"; + char active[4] = "-\0\0"; int ai = 0; const char *datetime_fmt = "%F %R"; const char *name = cur_be->be_node_name; @@ -495,7 +498,9 @@ print_fmt_nodes(const char *be_name, enum be_fmt be_fmt, boolean_t parsable, if (cur_be->be_active) active[ai++] = 'N'; if (cur_be->be_active_on_boot) - active[ai] = 'R'; + active[ai++] = 'R'; + if (cur_be->be_active_next) + active[ai++] = 'T'; nicenum(used, buf, sizeof (buf)); if (be_fmt & BE_FMT_DATASET) @@ -606,6 +611,20 @@ be_nvl_alloc(nvlist_t **nvlp) } static int +be_nvl_add_boolean(nvlist_t *nvl, const char *name, boolean_t val) +{ + assert(nvl != NULL); + + if (nvlist_add_boolean_value(nvl, name, val) != 0) { + (void) fprintf(stderr, _("nvlist_add_boolean_value failed for " + "%s (%s).\n"), name, val ? "true" : "false"); + return (1); + } + + return (0); +} + +static int be_nvl_add_string(nvlist_t *nvl, const char *name, const char *val) { assert(nvl != NULL); @@ -654,12 +673,30 @@ be_do_activate(int argc, char **argv) int err = 1; int c; char *obe_name; + boolean_t nextboot = B_FALSE; + boolean_t do_nextboot = B_FALSE; - while ((c = getopt(argc, argv, "v")) != -1) { + while ((c = getopt(argc, argv, "vtT")) != -1) { switch (c) { case 'v': libbe_print_errors(B_TRUE); break; + case 't': + if (do_nextboot == B_TRUE) { + usage(); + return (1); + } + nextboot = B_TRUE; + do_nextboot = B_TRUE; + break; + case 'T': + if (do_nextboot == B_TRUE) { + usage(); + return (1); + } + nextboot = B_FALSE; + do_nextboot = B_TRUE; + break; default: usage(); return (1); @@ -682,11 +719,20 @@ be_do_activate(int argc, char **argv) if (be_nvl_add_string(be_attrs, BE_ATTR_ORIG_BE_NAME, obe_name) != 0) goto out; + if (do_nextboot == B_TRUE) { + if (be_nvl_add_boolean(be_attrs, BE_ATTR_ACTIVE_NEXTBOOT, + nextboot) != 0) + goto out; + } + err = be_activate(be_attrs); switch (err) { case BE_SUCCESS: - (void) printf(_("Activated successfully\n")); + if (do_nextboot && nextboot == B_FALSE) + (void) printf(_("Temporary activation removed\n")); + else + (void) printf(_("Activated successfully\n")); break; case BE_ERR_BE_NOENT: (void) fprintf(stderr, _("%s does not exist or appear " diff --git a/usr/src/cmd/boot/bootadm/bootadm_loader.c b/usr/src/cmd/boot/bootadm/bootadm_loader.c index 15bf160745..e2d99b24b0 100644 --- a/usr/src/cmd/boot/bootadm/bootadm_loader.c +++ b/usr/src/cmd/boot/bootadm/bootadm_loader.c @@ -66,6 +66,7 @@ extern char *bam_root; typedef struct menu_entry { int me_idx; boolean_t me_active; + boolean_t me_active_next; char *me_title; char *me_type; char *me_bootfs; @@ -114,6 +115,10 @@ print_menu_cb(ofmt_arg_t *ofarg, char *buf, uint_t bufsize) (void) snprintf(buf, bufsize, "%s", entry->me_type); break; case 4: + if (entry->me_active_next == B_TRUE) { + (void) snprintf(buf, bufsize, " T"); + break; + } if (entry->me_active == B_TRUE) (void) snprintf(buf, bufsize, " *"); else @@ -266,6 +271,26 @@ menu_active_on_boot(be_node_list_t *be_nodes, const char *bootfs) return (rv); } +/* + * Get the be_active_next for bootfs. + */ +static boolean_t +menu_active_next(be_node_list_t *be_nodes, const char *bootfs) +{ + be_node_list_t *be_node; + boolean_t rv = B_FALSE; + + for (be_node = be_nodes; be_node != NULL; + be_node = be_node->be_next_node) { + if (strcmp(be_node->be_root_ds, bootfs) == 0) { + rv = be_node->be_active_next; + break; + } + } + + return (rv); +} + error_t menu_read(struct menu_lst *menu, char *menu_path) { @@ -335,6 +360,7 @@ menu_read(struct menu_lst *menu, char *menu_path) mp->me_type = type; mp->me_bootfs = bootfs; mp->me_active = menu_active_on_boot(be_nodes, bootfs); + mp->me_active_next = menu_active_next(be_nodes, bootfs); STAILQ_INSERT_TAIL(menu, mp, me_next); title = NULL; diff --git a/usr/src/cmd/eeprom/i386/Makefile b/usr/src/cmd/eeprom/i386/Makefile index d47fd832d1..baf63db774 100644 --- a/usr/src/cmd/eeprom/i386/Makefile +++ b/usr/src/cmd/eeprom/i386/Makefile @@ -28,6 +28,7 @@ SRCDIR = .. include $(SRCDIR)/Makefile.com OBJS += benv.o benv_kvm.o +LDLIBS += -lzfsbootenv .KEEP_STATE: diff --git a/usr/src/cmd/eeprom/i386/benv.c b/usr/src/cmd/eeprom/i386/benv.c index 0e3a89f0b7..697bcd9a19 100644 --- a/usr/src/cmd/eeprom/i386/benv.c +++ b/usr/src/cmd/eeprom/i386/benv.c @@ -27,10 +27,12 @@ #include "benv.h" #include <ctype.h> #include <stdarg.h> +#include <stdbool.h> #include <sys/mman.h> #include <unistd.h> #include <signal.h> #include <sys/wait.h> +#include <libzfsbootenv.h> /* * Usage: % eeprom [-v] [-f prom_dev] [-] @@ -671,33 +673,295 @@ get_line(void) return (NULL); } +static int +add_pair(const char *name, const char *nvlist, const char *key, + const char *type, const char *value) +{ + void *data, *nv; + size_t size; + int rv; + char *end; + + rv = lzbe_nvlist_get(name, nvlist, &nv); + if (rv != 0) + return (rv); + + data = NULL; + rv = EINVAL; + if (strcmp(type, "DATA_TYPE_STRING") == 0) { + data = (void *)(uintptr_t)value; + size = strlen(data) + 1; + rv = lzbe_add_pair(nv, key, type, data, size); + } else if (strcmp(type, "DATA_TYPE_UINT64") == 0) { + uint64_t v; + + v = strtoull(value, &end, 0); + if (errno != 0 || *end != '\0') + goto done; + size = sizeof (v); + rv = lzbe_add_pair(nv, key, type, &v, size); + } else if (strcmp(type, "DATA_TYPE_INT64") == 0) { + int64_t v; + + v = strtoll(value, &end, 0); + if (errno != 0 || *end != '\0') + goto done; + size = sizeof (v); + rv = lzbe_add_pair(nv, key, type, &v, size); + } else if (strcmp(type, "DATA_TYPE_UINT32") == 0) { + u_longlong_t lv; + uint32_t v; + + lv = strtoull(value, &end, 0); + if (errno != 0 || *end != '\0') + goto done; + if (lv > UINT32_MAX) + goto done; + v = lv; + size = sizeof (v); + rv = lzbe_add_pair(nv, key, type, &v, size); + } else if (strcmp(type, "DATA_TYPE_INT32") == 0) { + longlong_t lv; + int32_t v; + + lv = strtoll(value, &end, 0); + if (errno != 0 || *end != '\0') + goto done; + if (lv < INT32_MIN || lv > INT32_MAX) + goto done; + v = lv; + size = sizeof (v); + rv = lzbe_add_pair(nv, key, type, &v, size); + } else if (strcmp(type, "DATA_TYPE_UINT16") == 0) { + uint32_t lv; + uint16_t v; + + lv = strtoul(value, &end, 0); + if (errno != 0 || *end != '\0') + goto done; + if (lv > UINT16_MAX) + goto done; + v = lv; + size = sizeof (v); + rv = lzbe_add_pair(nv, key, type, &v, size); + } else if (strcmp(type, "DATA_TYPE_INT16") == 0) { + int32_t lv; + int16_t v; + + v = strtol(value, &end, 0); + if (errno != 0 || *end != '\0') + goto done; + if (lv < INT16_MIN || lv > INT16_MAX) + goto done; + v = lv; + size = sizeof (v); + rv = lzbe_add_pair(nv, key, type, &v, size); + } else if (strcmp(type, "DATA_TYPE_UINT8") == 0) { + uint32_t lv; + uint8_t v; + + lv = strtoul(value, &end, 0); + if (errno != 0 || *end != '\0') + goto done; + if (lv > UINT8_MAX) + goto done; + v = lv; + size = sizeof (v); + rv = lzbe_add_pair(nv, key, type, &v, size); + } else if (strcmp(type, "DATA_TYPE_INT8") == 0) { + int32_t lv; + int8_t v; + + lv = strtol(value, &end, 0); + if (errno != 0 || *end != '\0') + goto done; + if (lv < INT8_MIN || lv > INT8_MAX) + goto done; + v = lv; + size = sizeof (v); + rv = lzbe_add_pair(nv, key, type, &v, size); + } else if (strcmp(type, "DATA_TYPE_BYTE") == 0) { + uint32_t lv; + uint8_t v; + + lv = strtoul(value, &end, 0); + if (errno != 0 || *end != '\0') + goto done; + if (lv > UINT8_MAX) + goto done; + v = lv; + size = sizeof (v); + rv = lzbe_add_pair(nv, key, type, &v, size); + } else if (strcmp(type, "DATA_TYPE_BOOLEAN_VALUE") == 0) { + int32_t v; + + v = strtol(value, &end, 0); + if (errno != 0 || *end != '\0') { + if (strcasecmp(value, "YES") == 0) + v = 1; + else if (strcasecmp(value, "NO") == 0) + v = 0; + else if (strcasecmp(value, "true") == 0) + v = 1; + else if (strcasecmp(value, "false") == 0) + v = 0; + else goto done; + } + size = sizeof (v); + rv = lzbe_add_pair(nv, key, type, &v, size); + } + + if (rv == 0) + rv = lzbe_nvlist_set(name, nvlist, nv); + +done: + lzbe_nvlist_free(nv); + return (rv); +} + +static int +delete_pair(const char *name, const char *nvlist, const char *key) +{ + void *nv; + int rv; + + rv = lzbe_nvlist_get(name, nvlist, &nv); + if (rv == 0) + rv = lzbe_remove_pair(nv, key); + + if (rv == 0) + rv = lzbe_nvlist_set(name, nvlist, nv); + + lzbe_nvlist_free(nv); + return (rv); +} + +static int +usage(char *name) +{ + char *usage = "Usage: %s [-v] [-f prom-device]" + " [variable[=value] ...]\n" + "%s [-z pool] [-d key] [-k key -t type -v value] [-p]\n" + "%s [-z pool] -n nvlist [-d key] [-k key -t type -v value] [-p]\n"; + + return (_error(NO_PERROR, usage, name, name, name)); +} + int main(int argc, char **argv) { int c; int updates = 0; - char *usage = "Usage: %s [-v] [-f prom-device]" - " [variable[=value] ...]"; eplist_t *elist; benv_des_t *bd; char *file = NULL; + bool bootenv, bootenv_print, bootenv_delete; + char *name, *key, *type, *nvlist, *value; + lzbe_flags_t flag = lzbe_add; + + nvlist = NULL; + name = "rpool"; + key = NULL; + type = NULL; + value = NULL; + bootenv = false; + bootenv_print = false; + bootenv_delete = false; setpname(argv[0]); - while ((c = getopt(argc, argv, "f:Itv")) != -1) + while ((c = getopt(argc, argv, "bd:f:k:n:prt:v:z:")) != -1) switch (c) { - case 'v': - verbose++; + case 'b': + bootenv = true; + break; + case 'd': + if (bootenv) { + bootenv_delete = true; + key = optarg; + } else { + exit(usage(argv[0])); + } break; case 'f': file = optarg; break; + case 'k': + if (bootenv) + key = optarg; + else + exit(usage(argv[0])); + break; + case 'n': + if (bootenv) + nvlist = optarg; + else + exit(usage(argv[0])); + break; + case 'p': + if (bootenv) + bootenv_print = true; + else + exit(usage(argv[0])); + break; + case 'r': + if (bootenv) + flag = lzbe_replace; + else + exit(usage(argv[0])); + break; case 't': - test++; + if (bootenv) + type = optarg; + else + test++; + break; + case 'v': + if (bootenv) + value = optarg; + else + verbose++; + break; + case 'z': + if (bootenv) + name = optarg; + else + exit(usage(argv[0])); break; default: - exit(_error(NO_PERROR, usage, argv[0])); + exit(usage(argv[0])); + } + + argc -= optind; + argv += optind; + + if (bootenv) { + int rv = 0; + + if (argc == 1) + value = argv[0]; + + if (bootenv_print) + return (lzbe_bootenv_print(name, nvlist, stdout)); + + if (key != NULL || value != NULL) { + if (type == NULL) + type = "DATA_TYPE_STRING"; + + if (bootenv_delete) + rv = delete_pair(name, nvlist, key); + else if (key == NULL) + rv = lzbe_set_boot_device(name, flag, value); + else + rv = add_pair(name, nvlist, key, type, value); + + if (rv == 0) + printf("zfs bootenv is successfully written\n"); + else + printf("error: %s\n", strerror(rv)); } + return (rv); + } (void) uname(&uts_buf); bd = new_bd(); diff --git a/usr/src/lib/Makefile b/usr/src/lib/Makefile index 55230ca831..e3b3889305 100644 --- a/usr/src/lib/Makefile +++ b/usr/src/lib/Makefile @@ -603,7 +603,8 @@ krb5: gss_mechs/mech_krb5 libtecla libldap5 libads: libnsl libadt_jni: libbsm libadutils: libldap5 libresolv2 -libbe: libzfs libinstzones libuuid libgen libdevinfo libefi libficl +libbe: libzfs libinstzones libuuid libgen libdevinfo libefi libficl \ + libzfsbootenv libbsm: libinetutil libscf libsecdb libtsol libcfgadm: libdevinfo libcontract: libnvpair diff --git a/usr/src/lib/libbe/Makefile.com b/usr/src/lib/libbe/Makefile.com index 969de425f3..ede66a0afc 100644 --- a/usr/src/lib/libbe/Makefile.com +++ b/usr/src/lib/libbe/Makefile.com @@ -52,7 +52,7 @@ INCS += -I$(SRCDIR) -I$(SRC)/cmd/boot/common -I$(SRC)/common/ficl CSTD= $(CSTD_GNU99) LDLIBS += -lficl-sys -lzfs -linstzones -luuid -lnvpair -lc -lgen -LDLIBS += -ldevinfo -lefi +LDLIBS += -ldevinfo -lefi -lzfsbootenv CPPFLAGS += $(INCS) CLOBBERFILES += $(LIBRARY) diff --git a/usr/src/lib/libbe/common/be_activate.c b/usr/src/lib/libbe/common/be_activate.c index 1c2454991d..6133f2ca59 100644 --- a/usr/src/lib/libbe/common/be_activate.c +++ b/usr/src/lib/libbe/common/be_activate.c @@ -47,6 +47,7 @@ #include <libbe.h> #include <libbe_priv.h> +#include <libzfsbootenv.h> char *mnttab = MNTTAB; @@ -92,6 +93,8 @@ be_activate(nvlist_t *be_attrs) { int ret = BE_SUCCESS; char *be_name = NULL; + be_nextboot_state_t nextboot; + boolean_t next_boot; /* Initialize libzfs handle */ if (!be_zfs_init()) @@ -114,7 +117,17 @@ be_activate(nvlist_t *be_attrs) return (BE_ERR_INVAL); } - ret = _be_activate(be_name); + if (nvlist_lookup_boolean_value(be_attrs, BE_ATTR_ACTIVE_NEXTBOOT, + &next_boot) == 0) { + if (next_boot) + nextboot = BE_NEXTBOOT_SET; + else + nextboot = BE_NEXTBOOT_UNSET; + } else { + nextboot = BE_NEXTBOOT_IGNORE; + } + + ret = _be_activate(be_name, nextboot); be_zfs_fini(); @@ -206,6 +219,7 @@ be_installboot(nvlist_t *be_attrs) * Description: This does the actual work described in be_activate. * Parameters: * be_name - pointer to the name of BE to activate. + * nextboot - flag to ignore, set or unset nextboot * * Return: * BE_SUCCESS - Success @@ -214,7 +228,7 @@ be_installboot(nvlist_t *be_attrs) * Public */ int -_be_activate(char *be_name) +_be_activate(char *be_name, be_nextboot_state_t nextboot) { be_transaction_data_t cb = { 0 }; zfs_handle_t *zhp = NULL; @@ -233,6 +247,9 @@ _be_activate(char *be_name) if (be_name == NULL) return (BE_ERR_INVAL); + if (nextboot == BE_NEXTBOOT_SET && getzoneid() != GLOBAL_ZONEID) + return (BE_ERR_INVAL); + /* Set obe_name to be_name in the cb structure */ cb.obe_name = be_name; @@ -288,11 +305,31 @@ _be_activate(char *be_name) } if (getzoneid() == GLOBAL_ZONEID) { - if ((ret = set_bootfs(be_nodes->be_rpool, - root_ds)) != BE_SUCCESS) { - be_print_err(gettext("be_activate: failed to set " - "bootfs pool property for %s\n"), root_ds); - goto done; + switch (nextboot) { + case BE_NEXTBOOT_SET: + if ((ret = lzbe_set_boot_device(be_nodes->be_rpool, + lzbe_add, root_ds)) != 0) { + be_print_err(gettext("be_activate: failed to " + "set nextboot for %s\n"), root_ds); + goto done; + } + break; + case BE_NEXTBOOT_UNSET: + if ((ret = lzbe_set_boot_device(be_nodes->be_rpool, + lzbe_add, "")) != 0) { + be_print_err(gettext("be_activate: failed to " + "clear nextboot for %s\n"), root_ds); + goto done; + } + break; + default: + if ((ret = set_bootfs(be_nodes->be_rpool, + root_ds)) != BE_SUCCESS) { + be_print_err(gettext("be_activate: failed to " + "set bootfs pool property for %s\n"), + root_ds); + goto done; + } } } @@ -415,7 +452,8 @@ be_activate_current_be(void) return (ret); } - if ((ret = _be_activate(bt.obe_name)) != BE_SUCCESS) { + ret = _be_activate(bt.obe_name, BE_NEXTBOOT_IGNORE); + if (ret != BE_SUCCESS) { be_print_err(gettext("be_activate_current_be: failed to " "activate %s\n"), bt.obe_name); return (ret); diff --git a/usr/src/lib/libbe/common/be_list.c b/usr/src/lib/libbe/common/be_list.c index e2a5bf901c..fbccd111aa 100644 --- a/usr/src/lib/libbe/common/be_list.c +++ b/usr/src/lib/libbe/common/be_list.c @@ -46,6 +46,7 @@ #include <libbe.h> #include <libbe_priv.h> +#include <libzfsbootenv.h> /* * Callback data used for zfs_iter calls. @@ -985,13 +986,8 @@ be_qsort_compare_datasets(const void *x, const void *y) * Private */ static int -be_get_node_data( - zfs_handle_t *zhp, - be_node_list_t *be_node, - char *be_name, - const char *rpool, - char *current_be, - char *be_ds) +be_get_node_data(zfs_handle_t *zhp, be_node_list_t *be_node, char *be_name, + const char *rpool, char *current_be, char *be_ds) { char prop_buf[MAXPATHLEN]; nvlist_t *userprops = NULL; @@ -1040,6 +1036,8 @@ be_get_node_data( be_node->be_space_used = zfs_prop_get_int(zhp, ZFS_PROP_USED); if (getzoneid() == GLOBAL_ZONEID) { + char *nextboot; + if ((zphp = zpool_open(g_zfs, rpool)) == NULL) { be_print_err(gettext("be_get_node_data: failed to open " "pool (%s): %s\n"), rpool, @@ -1047,6 +1045,16 @@ be_get_node_data( return (zfs_err_to_be_err(g_zfs)); } + /* Set nextboot info */ + be_node->be_active_next = B_FALSE; + if (lzbe_get_boot_device(rpool, &nextboot) == 0) { + if (nextboot != NULL) { + if (strcmp(nextboot, be_ds) == 0) + be_node->be_active_next = B_TRUE; + free(nextboot); + } + } + (void) zpool_get_prop(zphp, ZPOOL_PROP_BOOTFS, prop_buf, ZFS_MAXPROPLEN, NULL, B_FALSE); if (be_has_grub() && (be_default_grub_bootfs(rpool, diff --git a/usr/src/lib/libbe/common/libbe.h b/usr/src/lib/libbe/common/libbe.h index b670d86ce5..774fe1ef9f 100644 --- a/usr/src/lib/libbe/common/libbe.h +++ b/usr/src/lib/libbe/common/libbe.h @@ -67,6 +67,7 @@ extern "C" { #define BE_ATTR_ACTIVE "active" #define BE_ATTR_ACTIVE_ON_BOOT "active_boot" +#define BE_ATTR_ACTIVE_NEXTBOOT "active_nextboot" #define BE_ATTR_GLOBAL_ACTIVE "global_active" #define BE_ATTR_SPACE "space_used" #define BE_ATTR_DATASET "dataset" @@ -174,6 +175,7 @@ typedef struct be_snapshot_list { typedef struct be_node_list { boolean_t be_mounted; /* is BE currently mounted */ + boolean_t be_active_next; /* is this BE active on next boot */ boolean_t be_active_on_boot; /* is this BE active on boot */ boolean_t be_active; /* is this BE active currently */ boolean_t be_global_active; /* is zone's BE associated with */ diff --git a/usr/src/lib/libbe/common/libbe_priv.h b/usr/src/lib/libbe/common/libbe_priv.h index f2960c4f17..ace201577f 100644 --- a/usr/src/lib/libbe/common/libbe_priv.h +++ b/usr/src/lib/libbe/common/libbe_priv.h @@ -142,6 +142,12 @@ struct be_defaults { char be_deflt_bename_starts_with[ZFS_MAX_DATASET_NAME_LEN]; }; +typedef enum be_nextboot_state { + BE_NEXTBOOT_IGNORE = -1, + BE_NEXTBOOT_SET, + BE_NEXTBOOT_UNSET +} be_nextboot_state_t; + /* Library globals */ extern libzfs_handle_t *g_zfs; extern boolean_t do_print; @@ -201,7 +207,7 @@ int zfs_err_to_be_err(libzfs_handle_t *); int errno_to_be_err(int); /* be_activate.c */ -int _be_activate(char *); +int _be_activate(char *, be_nextboot_state_t); int be_activate_current_be(void); boolean_t be_is_active_on_boot(char *); diff --git a/usr/src/man/man1m/beadm.1m b/usr/src/man/man1m/beadm.1m index 29d470a392..250f4b4400 100644 --- a/usr/src/man/man1m/beadm.1m +++ b/usr/src/man/man1m/beadm.1m @@ -1,7 +1,7 @@ '\" te .\" Copyright 2013 Nexenta Systems, Inc. All rights reserved. .\" Copyright 2016 Toomas Soome <tsoome@me.com> -.TH BEADM 1M "Feb 21, 2016" +.TH BEADM 1M "May 2, 2020" .SH NAME beadm \- utility for managing zfs boot environments .SH SYNOPSIS @@ -45,7 +45,7 @@ beadm \- utility for managing zfs boot environments .LP .nf -\fBbeadm\fR \fBactivate\fR [\fB-v\fR] \fIbeName\fR +\fBbeadm\fR \fBactivate\fR [\fB-v\fR] [\fB-t\fR | \fB-T\fR] \fIbeName\fR .fi .LP @@ -338,7 +338,9 @@ The 'Active' field indicates whether the boot environment is active now, represented by 'N'; active on reboot, represented by 'R'; or both, represented -by 'NR'. In non-global zone the 'Active' field also indicates whether the +by 'NR'. If temporary next boot configuration is used, the boot environment +is marked with 'T'. +In non-global zone the 'Active' field also indicates whether the boot environment has a non-active parent BE, represented by 'x'; is active on boot in a non-active parent BE, represented by 'b'. Activate, rollback and snapshot operations for boot environments from non-active global parent @@ -539,7 +541,7 @@ Verbose mode. Displays verbose error messages from \fBbeadm\fR. .sp .ne 2 .na -\fBbeadm\fR \fBactivate\fR [\fB-v\fR] \fIbeName\fR +\fBbeadm\fR \fBactivate\fR [\fB-v\fR] [\fB-t\fR | \fB-T\fR] \fIbeName\fR .ad .sp .6 .RS 4n @@ -547,6 +549,29 @@ Makes beName the active BE on next reboot. .sp .ne 2 .na +\fB-t\fR +.ad +.sp .6 +.RS 4n +Sets temporary, one time activation. For next boot, the \fIbeName\fR +is used for boot, and the temporary activation is removed. +When temporary activation is removed, the next boot will use zfs dataset +specified in boot pool \fIbootfs\fR property. +.RE + +.sp +.ne 2 +.na +\fB-T\fR +.ad +.sp .6 +.RS 4n +Removes temporary next boot configuration of \fIbeName\fR. +.RE + +.sp +.ne 2 +.na \fB-v\fR .ad .sp .6 @@ -793,6 +818,32 @@ BE5;215b8387-4968-627c-d2d0-f4a011414bab;NR;/;7786206208;static;1221004384 .fi .in -2 .sp +.LP +\fBExample 19\fR: Create and list temporary activation. + +.sp +.in +2 +.nf +\fB# beadm list\fR +BE Active Mountpoint Space Policy Created +-- ------ ---------- ----- ------ ------- +BE2 - - 4,85M static 2016-10-30 14:29 +BE3 - - 4,84M static 2016-10-30 18:53 +BE4 - - 5,54M static 2016-10-30 23:53 +BE5 NR / 12,6G static 2016-11-06 01:46 +\fB# beadm create BE6\fR +\fB# beadm activate -t BE6\fR +\fB# beadm list\fR +BE Active Mountpoint Space Policy Created +-- ------ ---------- ----- ------ ------- +BE2 - - 4,85M static 2016-10-30 14:29 +BE3 - - 4,84M static 2016-10-30 18:53 +BE4 - - 5,54M static 2016-10-30 23:53 +BE5 NR / 60,0K static 2016-11-06 01:46 +BE6 T - 12,6G static 2016-11-06 12:56 +.fi +.in -2 +.sp .SH EXIT STATUS The following exit values are returned: |