root/usr/src/lib/libzfsbootenv/common/lzbe_device.c
/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */
/*
 * Copyright 2020 Toomas Soome <tsoome@me.com>
 */

#include <sys/types.h>
#include <string.h>
#include <libzfs.h>
#include <libzfsbootenv.h>
#include <sys/zfs_bootenv.h>
#include <sys/vdev_impl.h>

/*
 * Store device name to zpool label bootenv area.
 * This call will set bootenv version to VB_NVLIST, if bootenv currently
 * does contain other version, then old data will be replaced.
 */
int
lzbe_set_boot_device(const char *pool, lzbe_flags_t flag, const char *device)
{
        libzfs_handle_t *hdl;
        zpool_handle_t *zphdl;
        nvlist_t *nv;
        char *descriptor;
        uint64_t version;
        int rv = -1;

        if (pool == NULL || *pool == '\0')
                return (rv);

        if ((hdl = libzfs_init()) == NULL)
                return (rv);

        zphdl = zpool_open(hdl, pool);
        if (zphdl == NULL) {
                libzfs_fini(hdl);
                return (rv);
        }

        switch (flag) {
        case lzbe_add:
                rv = zpool_get_bootenv(zphdl, &nv);
                if (rv == 0) {
                        /*
                         * We got the nvlist, check for version.
                         * if version is missing or is not VB_NVLIST,
                         * create new list.
                         */
                        rv = nvlist_lookup_uint64(nv, BOOTENV_VERSION,
                            &version);
                        if (rv == 0 && version == VB_NVLIST)
                                break;

                        /* Drop this nvlist */
                        fnvlist_free(nv);
                }
                /* FALLTHROUGH */
        case lzbe_replace:
                nv = fnvlist_alloc();
                break;
        default:
                return (rv);
        }

        /* version is mandatory */
        fnvlist_add_uint64(nv, BOOTENV_VERSION, VB_NVLIST);

        /*
         * If device name is empty, remove boot device configuration.
         */
        if ((device == NULL || *device == '\0')) {
                if (nvlist_exists(nv, OS_BOOTONCE))
                        fnvlist_remove(nv, OS_BOOTONCE);
        } else {
                /*
                 * Use device name directly if it does start with
                 * prefix "zfs:". Otherwise, add prefix and sufix.
                 */
                if (strncmp(device, "zfs:", 4) == 0) {
                        fnvlist_add_string(nv, OS_BOOTONCE, device);
                } else {
                        descriptor = NULL;
                        if (asprintf(&descriptor, "zfs:%s:", device) > 0)
                                fnvlist_add_string(nv, OS_BOOTONCE, descriptor);
                        else
                                rv = ENOMEM;
                        free(descriptor);
                }
        }

        rv = zpool_set_bootenv(zphdl, nv);
        if (rv != 0)
                fprintf(stderr, "%s\n", libzfs_error_description(hdl));

        fnvlist_free(nv);
        zpool_close(zphdl);
        libzfs_fini(hdl);
        return (rv);
}

/*
 * Return boot device name from bootenv, if set.
 */
int
lzbe_get_boot_device(const char *pool, char **device)
{
        libzfs_handle_t *hdl;
        zpool_handle_t *zphdl;
        nvlist_t *nv;
        char *val;
        int rv = -1;

        if (pool == NULL || *pool == '\0' || device == NULL)
                return (rv);

        if ((hdl = libzfs_init()) == NULL)
                return (rv);

        zphdl = zpool_open(hdl, pool);
        if (zphdl == NULL) {
                libzfs_fini(hdl);
                return (rv);
        }

        rv = zpool_get_bootenv(zphdl, &nv);
        if (rv == 0) {
                rv = nvlist_lookup_string(nv, OS_BOOTONCE, &val);
                if (rv == 0) {
                        /*
                         * zfs device descriptor is in form of "zfs:dataset:",
                         * we only do need dataset name.
                         */
                        if (strncmp(val, "zfs:", 4) == 0) {
                                val += 4;
                                val = strdup(val);
                                if (val != NULL) {
                                        size_t len = strlen(val);

                                        if (val[len - 1] == ':')
                                                val[len - 1] = '\0';
                                        *device = val;
                                } else {
                                        rv = ENOMEM;
                                }
                        } else {
                                rv = EINVAL;
                        }
                }
                nvlist_free(nv);
        }

        zpool_close(zphdl);
        libzfs_fini(hdl);
        return (rv);
}