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

#include "iscsi.h"
#include "nvfile.h"
#include <sys/file.h>       /* defines: FKIOCTL */
#include <sys/kobj.h>

#define NVF_GETF        16
static kmutex_t         nvf_getf_lock;
static file_t           *nvf_fd[NVF_GETF];
extern int modrootloaded;

/*
 * file names
 */
#define NVF_FILENAME            "/etc/iscsi/iscsi"
#define NVF_CURR_FILE_SUFFIX    "dbc"
#define NVF_PREV_FILE_SUFFIX    "dbp"
#define NVF_TMP_FILENAME        "/etc/iscsi/iscsi.dbt"
#define NVF_MAX_FILENAME_LEN    40

/*
 * file header definitions
 */
#define NVF_HDR_MAGIC           0x15C510DB              /* iSCSI DB */
#define NVF_HDR_VERSION         1
#define NVF_HDR_SIZE            128


/*
 * file flag definitions
 *
 * These flags describe the current state of reading or writing
 * the NVFILE/NVPAIR or the backing file.
 *
 * These flags are derived from a like NVPAIR/NVFILE implementation
 * in usr/src/uts/common/sys/devctl_impl.h
 */
#define NVF_ACTIVE      0x01    /* nvlist/nvpair file is active */
#define NVF_DIRTY       0x02    /* needs to be flushed */
#define NVF_SCHED       0x04    /* flush thread is currently scheduled */
#define NVF_FLUSHING    0x08    /* in process of being flushed */
#define NVF_ERROR       0x10    /* most recent flush failed */

#define NVF_IS_ACTIVE(flag)     (flag & NVF_ACTIVE)
#define NVF_MARK_ACTIVE(flag)   (flag |= NVF_ACTIVE)
#define NVF_CLEAR_ACTIVE(flag)  (flag &= ~NVF_ACTIVE)

#define NVF_IS_DIRTY(flag)      (flag & NVF_DIRTY)
#define NVF_MARK_DIRTY(flag)    (flag |= NVF_DIRTY)
#define NVF_CLEAR_DIRTY(flag)   (flag &= ~NVF_DIRTY)

#define NVF_IS_SCHED(flag)      (flag & NVF_SCHED)
#define NVF_MARK_SCHED(flag)    (flag |= NVF_SCHED)
#define NVF_CLEAR_SCHED(flag)   (flag &= ~NVF_SCHED)

/*
 * file flush time constants
 */
#define NVF_FLUSH_DELAY         10      /* number of ticks before flush */
#define NVF_RESCHED_MIN_TICKS   5       /* min # of ticks to resched thread */
#define NVF_FLUSH_BACKOFF_DELAY (SEC_TO_TICK(300))   /* re-try flush in 5 min */

/*
 * file access operations
 */
static file_t           *nvf_getf(int fdes);
static void             nvf_releasef(int fdes);
static int              nvf_setf(file_t *fp);

static int              nvf_open(char *path, int flags, int mode);
static int              nvf_close(int fdes);
static int              nvf_remove(char *filename);
static int              nvf_rename(char *oldname, char *newname);
static ssize_t          nvf_read(int fdes, void *cbuf, ssize_t count);
static ssize_t          nvf_write(int fdes, void *cbuf, ssize_t count);

int                     nvf_errno;

/*
 * file header data structure definition
 *
 * This data structure definition was derived from a like data structure
 * (nvpf_hdr_t) in usr/src/uts/common/sys/devctl_impl.h
 *
 * This header is larger than need in order to support extensability in the
 * future
 *
 */
typedef struct nvf_hdr {
        union {
                struct hdr {
                        uint32_t        h_magic;
                        int32_t         h_ver;
                        int64_t         h_size;
                        uint16_t        h_hdrsum;
                        uint16_t        h_datasum;
                } h_info;
                uchar_t h_pad[NVF_HDR_SIZE];
        } h_u;
} nvf_hdr_t;

#define nvfh_magic      h_u.h_info.h_magic
#define nvfh_ver        h_u.h_info.h_ver
#define nvfh_size       h_u.h_info.h_size
#define nvfh_hdrsum     h_u.h_info.h_hdrsum
#define nvfh_datasum    h_u.h_info.h_datasum


/*
 *  Local Global Variables
 */
static nvlist_t         *nvf_list;              /* pointer to root nvlist */
static uint32_t         nvf_flags;              /* nvfile state flags */
static kmutex_t         nvf_lock;               /* lock for file */
static krwlock_t        nvf_list_lock;          /* lock for nvlist access */
static timeout_id_t     nvf_thread_id;          /* thread identifier */
static clock_t          nvf_thread_ticks;       /* timeout tick value */
static char             nvf_curr_filename[NVF_MAX_FILENAME_LEN];
static char             nvf_prev_filename[NVF_MAX_FILENAME_LEN];
static boolean_t        nvf_written_once;       /* File has been written once */
/*
 *  Local Function Prototypes
 */
static uint16_t         nvf_chksum(char *buf, int64_t buflen);
static void             nvf_thread(void *arg);
static boolean_t        nvf_flush(void);
static boolean_t        nvf_parse(char *filename);

/*
 *  NVLIST/NVPAIR FILE External Interfaces
 */

void
nvf_init(void)
{
        mutex_init(&nvf_getf_lock, NULL, MUTEX_DRIVER, NULL);
        nvf_list = NULL;
        nvf_flags = 0;
        NVF_MARK_ACTIVE(nvf_flags);
        nvf_thread_id = 0;
        nvf_thread_ticks = 0;
        nvf_written_once = B_FALSE;
        (void) snprintf(nvf_curr_filename, NVF_MAX_FILENAME_LEN, "%s_v%d.%s",
            NVF_FILENAME, NVF_HDR_VERSION, NVF_CURR_FILE_SUFFIX);
        (void) snprintf(nvf_prev_filename, NVF_MAX_FILENAME_LEN, "%s_v%d.%s",
            NVF_FILENAME, NVF_HDR_VERSION, NVF_PREV_FILE_SUFFIX);
        mutex_init(&nvf_lock, NULL, MUTEX_DRIVER, NULL);
        rw_init(&nvf_list_lock, NULL, RW_DRIVER, NULL);
}

void
nvf_fini(void)
{
        mutex_enter(&nvf_lock);
        NVF_CLEAR_ACTIVE(nvf_flags);
        if (NVF_IS_SCHED(nvf_flags)) {
                nvf_thread_ticks = 0;
                mutex_exit(&nvf_lock);
                (void) untimeout(nvf_thread_id);
                mutex_enter(&nvf_lock);
        }

        rw_enter(&nvf_list_lock, RW_WRITER);
        if (nvf_list) {
                nvlist_free(nvf_list);
        }
        nvf_list = NULL;
        rw_exit(&nvf_list_lock);
        mutex_exit(&nvf_lock);

        rw_destroy(&nvf_list_lock);
        mutex_destroy(&nvf_lock);
}

/*
 * nvf_load - load contents of NVLIST/NVPAIR file into memory.
 */
boolean_t
nvf_load(void)
{
        char            corrupt_filename[NVF_MAX_FILENAME_LEN];
        boolean_t       rval;

        mutex_enter(&nvf_lock);

        /*
         * try to load current file
         */
        if (!modrootloaded) {
                mutex_exit(&nvf_lock);
                return (B_TRUE);
        } else {
                rval = nvf_parse(nvf_curr_filename);
        }
        if (rval == B_TRUE) {
                mutex_exit(&nvf_lock);
                return (rval);
        } else {
                /*
                 * Rename current file to add corrupted suffix
                 */
                (void) snprintf(corrupt_filename, NVF_MAX_FILENAME_LEN,
                    "%s.corrupt", nvf_curr_filename);
                (void) nvf_rename(nvf_curr_filename, corrupt_filename);
        }

        /*
         * try to load previous file
         */
        if (!modrootloaded) {
                mutex_exit(&nvf_lock);
                return (B_TRUE);
        } else {
                rval = nvf_parse(nvf_prev_filename);
        }

        if (rval == B_TRUE) {
                mutex_exit(&nvf_lock);
                return (rval);
        } else {
                /*
                 * Rename previous file to add corrupted suffix
                 */
                (void) snprintf(corrupt_filename, NVF_MAX_FILENAME_LEN,
                    "%s.corrupt", nvf_prev_filename);
                (void) nvf_rename(nvf_prev_filename, corrupt_filename);
        }

        /*
         * non-existent or corrupted files are OK.  We just create
         * an empty root nvlist and then write to file when
         * something added.  However, ensure that any current root nvlist
         * is deallocated before allocating a new one.
         */
        rw_enter(&nvf_list_lock, RW_WRITER);
        if (nvf_list != NULL) {
                nvlist_free(nvf_list);
                nvf_list = NULL;
        }
        rw_exit(&nvf_list_lock);

        rval = nvlist_alloc(&nvf_list, NV_UNIQUE_NAME, KM_SLEEP);
        if (rval != 0) {
                cmn_err(CE_NOTE, "!iscsi persistent store failed to "
                    "allocate root list (%d)", rval);
        }

        mutex_exit(&nvf_lock);
        return (rval == 0 ? B_TRUE : B_FALSE);
}

/*
 * nvf_update - start process of updating the NVPAIR/NVLIST file.
 *
 * This function is derived from a like NVPAIR/NVFILE implementation
 * in usr/src/uts/common/os/devctl.c
 *
 */
void
nvf_update(void)
{
        mutex_enter(&nvf_lock);

        /*
         * set dirty flag to indicate data flush is needed
         */
        NVF_MARK_DIRTY(nvf_flags);

        /*
         * If thread is already started just update number of
         * ticks before flush, otherwise start thread and set
         * number of ticks.  The thread will is responsible
         * for starting the actual store to file.
         *
         * If update error occured previously, reschedule flush to
         * occur almost immediately.  If error still exists, the
         * update thread will be backed off again
         */
        if (!NVF_IS_SCHED(nvf_flags)) {
                NVF_MARK_SCHED(nvf_flags);
                mutex_exit(&nvf_lock);
                nvf_thread_id = timeout(nvf_thread, NULL, NVF_FLUSH_DELAY);
        } else {
                nvf_thread_ticks = ddi_get_lbolt() + NVF_FLUSH_DELAY;
                /*
                 * If update error occured previously, reschedule flush
                 * attempt to occur quickly.  If an error still exists
                 * after a flush attempt, the update thread will be backed
                 * off again
                 */
                if (nvf_flags & NVF_ERROR) {
                        mutex_exit(&nvf_lock);
                        (void) untimeout(nvf_thread_id);
                        nvf_thread_id = timeout(nvf_thread, NULL,
                            NVF_FLUSH_DELAY);
                } else {
                        mutex_exit(&nvf_lock);
                }
        }
}

/*
 * nvf_data_check -- check if specified list exists
 */
boolean_t
nvf_list_check(char *id)
{
        nvlist_t        *list = NULL;
        int             rval;

        /*
         * find the specified list
         */
        rw_enter(&nvf_list_lock, RW_READER);
        rval = nvlist_lookup_nvlist(nvf_list, id, &list);
        rw_exit(&nvf_list_lock);

        return (rval == 0 ? B_TRUE : B_FALSE);
}

/*
 * nvf_node_value_set - store value associated with node
 */
boolean_t
nvf_node_value_set(char *id, uint32_t value)
{
        int     rval;

        ASSERT(id != NULL);

        rw_enter(&nvf_list_lock, RW_WRITER);

        if (nvf_list == NULL) {
                rw_exit(&nvf_list_lock);
                return (B_FALSE);
        }

        /*
         * update value.  If value already exists, it will be replaced
         * by this update.
         */
        rval =  nvlist_add_uint32(nvf_list, id, value);
        if (rval == 0) {
                /*
                 * value was set, so update associated file
                 */
                nvf_update();
        } else {
                cmn_err(CE_NOTE, "!iscsi persistent store failed to "
                    "store %s value (%d)", id, rval);
        }

        rw_exit(&nvf_list_lock);
        return (rval == 0 ? B_TRUE : B_FALSE);
}

/*
 * nvf_node_value_get - obtain value associated with node
 */
boolean_t
nvf_node_value_get(char *id, uint32_t *value)
{
        boolean_t       rval;

        ASSERT(id != NULL);
        ASSERT(value != NULL);

        rw_enter(&nvf_list_lock, RW_READER);

        if (nvf_list == NULL) {
                rw_exit(&nvf_list_lock);
                return (B_FALSE);
        }

        rval = nvlist_lookup_uint32(nvf_list, id, value);

        rw_exit(&nvf_list_lock);
        return (rval == 0 ? B_TRUE : B_FALSE);
}

/*
 * nvf_node_name_set - store a specific type of name
 */
boolean_t
nvf_node_name_set(char *id, char *name)
{
        boolean_t       rval = B_TRUE;

        rw_enter(&nvf_list_lock, RW_WRITER);

        if (nvf_list == NULL) {
                rw_exit(&nvf_list_lock);
                return (B_FALSE);
        }

        rval =  nvlist_add_string(nvf_list, id, name);
        if (rval == 0) {
                nvf_update();
        } else {
                cmn_err(CE_NOTE, "!iscsi persistent store failed to "
                    "store %s name (%d)", id, rval);
        }

        rw_exit(&nvf_list_lock);
        return (rval == 0 ? B_TRUE : B_FALSE);
}

/*
 * nvf_node_name_get - return a specific type of name
 */
boolean_t
nvf_node_name_get(char *id, char *name, uint_t nsize)
{
        boolean_t       rval = B_FALSE;
        char            *tmpname;

        rw_enter(&nvf_list_lock, RW_READER);

        if (nvf_list == NULL) {
                rw_exit(&nvf_list_lock);
                return (B_FALSE);
        }

        rval = nvlist_lookup_string(nvf_list, id, &tmpname);
        if (rval == 0) {
                /*
                 *  ensure name is able to fit into given buffer
                 */
                if (strlen(tmpname) < nsize) {
                        (void) strcpy(name, tmpname);
                        rval = B_TRUE;
                } else {
                        cmn_err(CE_NOTE, "!iscsi persistent store "
                            "unable to fit %s node name into buffer %d %s",
                            tmpname, nsize, id);
                        rval = B_FALSE;
                }
        } else {
                rval = B_FALSE;
        }

        rw_exit(&nvf_list_lock);
        return (rval);
}

/*
 * nvf_node_data_set -- store data element associated with node
 */
boolean_t
nvf_node_data_set(char *name, void *data, uint_t dsize)
{
        int             rval;

        ASSERT(name != NULL);
        ASSERT(data != NULL);

        rw_enter(&nvf_list_lock, RW_WRITER);

        if (nvf_list == NULL) {
                rw_exit(&nvf_list_lock);
                return (B_FALSE);
        }

        /*
         * update the address configuration element in the specific
         * list.  If this element already exists, it will be
         * replaced by this update.
         */
        rval = nvlist_add_byte_array(nvf_list, name, (uchar_t *)data, dsize);
        if (rval == 0) {
                /*
                 * data was set, so update associated file
                 */
                nvf_update();
        } else {
                cmn_err(CE_NOTE, "!iscsi persistent store failed "
                    "to store %s name (%d)", name, rval);
        }

        rw_exit(&nvf_list_lock);
        return (rval == 0 ? B_TRUE : B_FALSE);
}

/*
 * nvf_node_data_get -- obtain a data element associated with node
 */
iscsi_nvfile_status_t
nvf_node_data_get(char *name, void *data, uint_t dsize)
{
        uchar_t                 *value = NULL;
        uint_t                  vsize;
        int                     rval = 0;
        iscsi_nvfile_status_t   status = ISCSI_NVFILE_SUCCESS;

        ASSERT(name != NULL);
        ASSERT(data != NULL);

        rw_enter(&nvf_list_lock, RW_READER);

        if (nvf_list == NULL) {
                rw_exit(&nvf_list_lock);
                return (ISCSI_NVFILE_NVF_LIST_NOT_FOUND);
        }

        rval = nvlist_lookup_byte_array(nvf_list, name, &value, &vsize);
        if (rval == 0) {
                /*
                 *  ensure data is able to fit into given buffer
                 */
                if (vsize <= dsize) {
                        bcopy(value, data, vsize);
                } else {
                        bcopy(value, data, dsize);
                }
                status = ISCSI_NVFILE_SUCCESS;
        } else if (rval == ENOENT) {
                status = ISCSI_NVFILE_NAMEVAL_NOT_FOUND;
        } else {
                status = ISCSI_NVFILE_FAILURE;
        }

        rw_exit(&nvf_list_lock);
        return (status);
}

/*
 * nvf_node_data_clear -- remove a data element associated with node
 */
boolean_t
nvf_node_data_clear(char *name)
{
        int     rval;

        ASSERT(name != NULL);

        rw_enter(&nvf_list_lock, RW_WRITER);

        if (nvf_list == NULL) {
                rw_exit(&nvf_list_lock);
                return (B_FALSE);
        }

        /*
         * remove the specified data element
         */
        rval = nvlist_remove(nvf_list, name, DATA_TYPE_BYTE_ARRAY);
        if (rval == 0) {
                /*
                 * data was set, so update associated file
                 */
                nvf_update();
        }

        rw_exit(&nvf_list_lock);
        return (rval == 0 ? B_TRUE : B_FALSE);
}

/*
 * nvf_data_set -- store a data element in the specified list
 */
boolean_t
nvf_data_set(char *id, char *name, void *data, uint_t dsize)
{
        nvlist_t        *list = NULL;
        int             rval;
        boolean_t       list_alloc = B_FALSE;

        ASSERT(id != NULL);
        ASSERT(name != NULL);
        ASSERT(data != NULL);

        rw_enter(&nvf_list_lock, RW_WRITER);

        if (nvf_list == NULL) {
                rw_exit(&nvf_list_lock);
                return (B_FALSE);
        }

        /*
         * find the specified list
         */
        rval = nvlist_lookup_nvlist(nvf_list, id, &list);
        if (rval != 0) {
                rval = nvlist_alloc(&list, NV_UNIQUE_NAME, KM_SLEEP);
                if (rval != 0) {
                        cmn_err(CE_NOTE, "!iscsi persistent store failed to "
                            "allocate %s list (%d)", id, rval);
                        rw_exit(&nvf_list_lock);
                        return (B_FALSE);
                }
                list_alloc = B_TRUE;
        }

        /*
         * update the data element in the specified list.  If this element
         * already exists, it will be replaced by this update.
         */
        rval = nvlist_add_byte_array(list, name, (uchar_t *)data, dsize);
        if (rval == 0) {
                rval = nvlist_add_nvlist(nvf_list, id, list);
                if (rval != 0) {
                        cmn_err(CE_NOTE, "!iscsi persistent store failed "
                            "to add %s list to root (%d)", id, rval);
                        rw_exit(&nvf_list_lock);
                        return (B_FALSE);
                }
                /*
                 * data was set, so update file
                 */
                nvf_update();
        } else {
                cmn_err(CE_NOTE, "!iscsi persistent store failed to "
                    "store %s in %s list (%d)", name, id, rval);
        }

        if (list_alloc) {
                nvlist_free(list);
        }

        rw_exit(&nvf_list_lock);
        return (rval == 0 ? B_TRUE : B_FALSE);
}

/*
 * nvf_data_get -- get a data element from the specified list
 */
boolean_t
nvf_data_get(char *id, char *name, void *data, uint_t dsize)
{
        nvlist_t        *list = NULL;
        uchar_t         *value = NULL;
        uint_t          vsize;
        int             rval;

        ASSERT(id != NULL);
        ASSERT(name != NULL);
        ASSERT(data != NULL);

        rw_enter(&nvf_list_lock, RW_READER);

        if (nvf_list == NULL) {
                rw_exit(&nvf_list_lock);
                return (B_FALSE);
        }

        /*
         * find the specified list
         */
        rval = nvlist_lookup_nvlist(nvf_list, id, &list);
        if (rval != 0) {
                rw_exit(&nvf_list_lock);
                return (B_FALSE);
        }

        /* obtain data element from list */
        ASSERT(list != NULL);
        rval = nvlist_lookup_byte_array(list, name, &value, &vsize);
        if (rval == 0) {
                /*
                 *  ensure data is able to fit into given buffer
                 */
                if (vsize <= dsize) {
                        bcopy(value, data, vsize);
                } else {
                        bcopy(value, data, dsize);
                }
        }

        rw_exit(&nvf_list_lock);
        return (rval == 0 ? B_TRUE : B_FALSE);
}


/*
 * nvf_data_next -- get the next data element in the specified list
 */
boolean_t
nvf_data_next(char *id, void **v, char *name, void *data, uint_t dsize)
{
        nvlist_t        *list = NULL;
        nvpair_t        *pair = NULL;
        uchar_t         *value = NULL;
        uint_t          vsize;
        int             rval;

        ASSERT(id != NULL);
        ASSERT(v != NULL);
        ASSERT(name != NULL);
        ASSERT(data != NULL);

        rw_enter(&nvf_list_lock, RW_READER);
        if (nvf_list == NULL) {
                rw_exit(&nvf_list_lock);
                return (B_FALSE);
        }

        /*
         * find the specified list
         */
        rval = nvlist_lookup_nvlist(nvf_list, id, &list);
        if (rval != 0) {
                rw_exit(&nvf_list_lock);
                return (B_FALSE);
        }

        /*
         * get the next nvpair data item in the list
         */
        pair = nvlist_next_nvpair(list, (nvpair_t *)*v);
        *v = (void *)pair;
        if (pair == NULL) {
                rw_exit(&nvf_list_lock);
                return (B_FALSE);
        }

        /*
         * get the data bytes
         */
        rval = nvpair_value_byte_array(pair, &value, &vsize);
        if (rval != 0) {
                rw_exit(&nvf_list_lock);
                return (B_FALSE);
        }

        /*
         *  ensure data is able to fit into given buffer
         */
        (void) strcpy(name, nvpair_name(pair));
        if (vsize <= dsize) {
                bcopy(value, data, vsize);
        } else {
                bcopy(value, data, dsize);
        }

        rw_exit(&nvf_list_lock);
        return (B_TRUE);
}

/*
 * nvf_data_clear -- remove a data element from the specified list
 */
boolean_t
nvf_data_clear(char *id, char *name)
{
        nvlist_t        *list = NULL;
        int             rval = B_FALSE;

        ASSERT(id != NULL);
        ASSERT(name != NULL);

        rw_enter(&nvf_list_lock, RW_WRITER);

        if (nvf_list == NULL) {
                rw_exit(&nvf_list_lock);
                return (B_FALSE);
        }

        /*
         * find the specified list
         */
        rval = nvlist_lookup_nvlist(nvf_list, id, &list);
        if (rval != 0) {
                rw_exit(&nvf_list_lock);
                return (B_FALSE);
        }

        /*
         * remove the specified data element
         */
        rval = nvlist_remove(list, name, DATA_TYPE_BYTE_ARRAY);
        if (rval == 0) {
                /*
                 * data was set, so update associated file
                 */
                nvf_update();
        }

        rw_exit(&nvf_list_lock);
        return (rval == 0 ? B_TRUE : B_FALSE);
}

/*
 * +--------------------------------------------------------------------+
 * | Internal Helper Functions                                          |
 * +--------------------------------------------------------------------+
 */

/*
 * nvf_cksum - calculate checksum of given buffer.
 *
 * This function was derived from like function (nvp_cksum) in
 * usr/src/uts/common/os/devctl.c
 */
static uint16_t
nvf_chksum(char *buf, int64_t buflen)
{
        uint16_t cksum = 0;
        uint16_t *p = (uint16_t *)buf;
        int64_t n;

        if ((buflen & 0x01) != 0) {
                buflen--;
                cksum = buf[buflen];
        }
        n = buflen / 2;
        while (n-- > 0)
                cksum ^= *p++;
        return (cksum);
}


/*
 * nvf_thread - determines when writing of NVLIST/NVPAIR data to a file
 * should occur.
 */
/* ARGSUSED */
static void
nvf_thread(void *arg)
{
        clock_t         nticks;
        boolean_t       rval;

        mutex_enter(&nvf_lock);
        nticks = nvf_thread_ticks - ddi_get_lbolt();

        /*
         * check whether its time to write to file.  If not, reschedule self
         */
        if ((nticks > NVF_RESCHED_MIN_TICKS) || !modrootloaded) {
                if (NVF_IS_ACTIVE(nvf_flags)) {
                        mutex_exit(&nvf_lock);
                        nvf_thread_id = timeout(nvf_thread, NULL, nticks);
                        mutex_enter(&nvf_lock);
                }
                mutex_exit(&nvf_lock);
                return;
        }

        /*
         * flush NVLIST/NVPAIR data to file
         */
        NVF_CLEAR_DIRTY(nvf_flags);
        nvf_flags |= NVF_FLUSHING;
        mutex_exit(&nvf_lock);

        rval = nvf_flush();

        mutex_enter(&nvf_lock);
        nvf_flags &= ~NVF_FLUSHING;
        if (rval == B_FALSE) {
                NVF_MARK_DIRTY(nvf_flags);
                if ((nvf_flags & NVF_ERROR) == 0) {
                        if (nvf_written_once) {
                                cmn_err(CE_NOTE,
                                    "!iscsi persistent store update "
                                    "failed file:%s", nvf_curr_filename);
                        }
                        nvf_flags |= NVF_ERROR;
                }
                nvf_thread_ticks = NVF_FLUSH_BACKOFF_DELAY + ddi_get_lbolt();
        } else if (nvf_flags & NVF_ERROR) {
                cmn_err(CE_NOTE, "!iscsi persistent store update ok now "
                    "filename:%s", nvf_curr_filename);
                nvf_flags &= ~NVF_ERROR;
        }

        /*
         * re-check whether data is dirty and reschedule if necessary
         */
        if (NVF_IS_ACTIVE(nvf_flags) && NVF_IS_DIRTY(nvf_flags)) {
                nticks = nvf_thread_ticks - ddi_get_lbolt();
                mutex_exit(&nvf_lock);
                if (nticks > NVF_FLUSH_DELAY) {
                        nvf_thread_id = timeout(nvf_thread, NULL, nticks);
                } else {
                        nvf_thread_id = timeout(nvf_thread, NULL,
                            NVF_FLUSH_DELAY);
                }
        } else {
                NVF_CLEAR_SCHED(nvf_flags);
                mutex_exit(&nvf_lock);
        }
}

/*
 * nvf_flush - write contents of NVLIST/NVPAIR to a backing file.
 *
 * This function is derived from a like NVPAIR/NVFILE implementation
 * in usr/src/uts/common/os/devctl.c
 */
static boolean_t
nvf_flush(void)
{
        int             rval;
        nvlist_t        *tmpnvl;
        char            *nvfbuf;
        char            *nvlbuf;
        size_t          nvllen;
        size_t          nvflen;
        int             file;
        int             bytes_written;

        /*
         * duplicate data so access isn't blocked while writing to disk
         */
        rw_enter(&nvf_list_lock, RW_READER);
        rval = nvlist_dup(nvf_list, &tmpnvl, KM_SLEEP);
        if (rval != 0) {
                cmn_err(CE_NOTE, "!iscsi persistent store failed to "
                    "duplicate nvf_list (%d)", rval);
                rw_exit(&nvf_list_lock);
                return (B_FALSE);
        }
        rw_exit(&nvf_list_lock);

        /*
         * pack duplicated list to get ready for file write
         */
        nvlbuf = NULL;
        rval = nvlist_pack(tmpnvl, &nvlbuf, &nvllen, NV_ENCODE_NATIVE, 0);
        if (rval != 0) {
                cmn_err(CE_NOTE, "!iscsi persistent store failed to pack "
                    "nvf_list (%d)", rval);
                nvlist_free(tmpnvl);
                return (B_FALSE);
        }

        /*
         * allocate buffer to store both the header and the data.
         */
        nvflen = nvllen + sizeof (nvf_hdr_t);
        nvfbuf = kmem_zalloc(nvflen, KM_SLEEP);

        /*
         * fill buffer with contents of file header
         */
        ((nvf_hdr_t *)nvfbuf)->nvfh_magic = NVF_HDR_MAGIC;
        ((nvf_hdr_t *)nvfbuf)->nvfh_ver = NVF_HDR_VERSION;
        ((nvf_hdr_t *)nvfbuf)->nvfh_size = nvllen;
        ((nvf_hdr_t *)nvfbuf)->nvfh_datasum = nvf_chksum((char *)nvlbuf,
            nvllen);
        ((nvf_hdr_t *)nvfbuf)->nvfh_hdrsum = nvf_chksum((char *)nvfbuf,
            sizeof (nvf_hdr_t));

        /*
         * copy packed nvlist into buffer
         */
        bcopy(nvlbuf, nvfbuf + sizeof (nvf_hdr_t), nvllen);

        /*
         * free memory used for packed nvlist
         */
        nvlist_free(tmpnvl);
        kmem_free(nvlbuf, nvllen);

        /*
         *  To make it unlikely we suffer data loss, write
         * data to the new temporary file.  Once successful
         * complete the transaction by renaming the new file
         * to replace the previous.
         */

        /*
         * remove temporary file to ensure data content is written correctly
         */
        rval = nvf_remove(NVF_TMP_FILENAME);
        if (rval == -1) {
                kmem_free(nvfbuf, nvflen);
                return (B_FALSE);
        }

        /*
         * create tempororary file
         */
        file = nvf_open(NVF_TMP_FILENAME, O_RDWR | O_CREAT, 0600);
        if (file == -1) {
                mutex_enter(&nvf_lock);
                if (nvf_written_once) {
                        cmn_err(CE_NOTE,
                            "!iscsi persistent store failed to create "
                            "%s (errno:%d)", NVF_TMP_FILENAME, nvf_errno);
                }
                mutex_exit(&nvf_lock);
                kmem_free(nvfbuf, nvflen);
                return (B_FALSE);
        }

        /*
         * write data to tempororary file
         */
        bytes_written = nvf_write(file, nvfbuf, nvflen);
        kmem_free(nvfbuf, nvflen);
        if (bytes_written == -1) {
                cmn_err(CE_NOTE, "!iscsi persistent store failed to write "
                    "%s (errno:%d)", NVF_TMP_FILENAME, nvf_errno);
                return (B_FALSE);
        }

        if (bytes_written != nvflen) {
                cmn_err(CE_NOTE, "!iscsi persistent store failed to write "
                    "%s (errno:%d)\n\tpartial write %d of %ld bytes\n",
                    NVF_TMP_FILENAME, nvf_errno, bytes_written, nvflen);
                return (B_FALSE);
        }

        /*
         * close tempororary file
         */
        rval = nvf_close(file);
        if (rval == -1) {
                return (B_FALSE);
        }

        mutex_enter(&nvf_lock);
        /*
         * File has been written.  Set flag to allow the create and update
         * messages to be displayed in case of create or update failures.
         */
        nvf_written_once = B_TRUE;

        /*
         * rename current original file to previous original file
         */
        rval = nvf_rename(nvf_curr_filename, nvf_prev_filename);
        if (rval == -1) {
                cmn_err(CE_NOTE, "!iscsi persistent store failed to "
                    "rename %s (errno:%d)", nvf_curr_filename, nvf_errno);
                mutex_exit(&nvf_lock);
                return (B_FALSE);
        }

        /*
         * rename temporary file to current original file
         */
        rval = nvf_rename(NVF_TMP_FILENAME, nvf_curr_filename);
        if (rval == -1) {
                cmn_err(CE_NOTE, "!iscsi persistent store failed to "
                    "rename %s (errno:%d)", NVF_TMP_FILENAME, nvf_errno);
                mutex_exit(&nvf_lock);
                return (B_FALSE);
        }

        NVF_CLEAR_DIRTY(nvf_flags);

        mutex_exit(&nvf_lock);
        return (B_TRUE);
}

/*
 * nvf_parse - read contents of NVLIST/NVPAIR file.
 *
 * This function is derived from a like NVPAIR/NVFILE implementation
 * in usr/src/uts/common/os/devctl.c
 */
static boolean_t
nvf_parse(char *filename)
{
        int             file;
        nvf_hdr_t       hdr;
        int             bytes_read;
        int             rval;
        uint16_t        chksum;
        uint16_t        hdrsum;
        char            *buf;
        char            overfill;
        nvlist_t        *nvl;
        nvlist_t        *old_nvl;


        /*
         * open current file
         */
        file = nvf_open(filename, O_RDONLY, 0600);
        if (file == -1) {
                return (B_FALSE);
        }

        /*
         * read file header
         */
        bytes_read = nvf_read(file, (char *)&hdr, sizeof (hdr));
        if (bytes_read != sizeof (hdr)) {
                (void) nvf_close(file);
                return (B_FALSE);
        }

        /*
         * calculate checksum over file header bytes
         */
        chksum = hdr.nvfh_hdrsum;
        hdr.nvfh_hdrsum = 0;
        hdrsum = nvf_chksum((char *)&hdr, sizeof (hdr));

        /*
         * validate file header is as expected
         */
        if ((hdr.nvfh_magic != NVF_HDR_MAGIC) ||
            (hdr.nvfh_ver != NVF_HDR_VERSION) ||
            (hdrsum != chksum)) {
                (void) nvf_close(file);
                if (hdrsum != chksum) {
                        cmn_err(CE_NOTE, "!iscsi persistent store "
                            "checksum error %s actual:0x%x expected:0x%x",
                            filename, hdrsum, chksum);
                }
                cmn_err(CE_NOTE, "!iscsi persistent store %s has an "
                    "incorrect header", filename);
                return (B_FALSE);
        }

        ASSERT(hdr.nvfh_size >= 0);

        /*
         * read expected remaining content of file
         */
        buf = kmem_alloc(hdr.nvfh_size, KM_SLEEP);
        bytes_read = nvf_read(file, buf, hdr.nvfh_size);
        if (bytes_read != hdr.nvfh_size) {
                kmem_free(buf, hdr.nvfh_size);
                (void) nvf_close(file);
                if (bytes_read < 0) {
                        cmn_err(CE_NOTE, "!iscsi persistent store failed "
                            "to read %s bytes:%d", filename, bytes_read);
                } else {
                        cmn_err(CE_NOTE, "!iscsi persistent store incomplete "
                            "read %s bytes:%d/%lld", filename,
                            bytes_read, (longlong_t)hdr.nvfh_size);
                }
                return (B_FALSE);
        }

        /*
         * check whether file has anymore data.  If so this is an error
         */
        bytes_read = nvf_read(file, &overfill, 1);
        (void) nvf_close(file);
        if (bytes_read > 0) {
                kmem_free(buf, hdr.nvfh_size);
                cmn_err(CE_NOTE, "!iscsi persistent store file is larger "
                    "than expected %s bytes:%lld",
                    filename, (longlong_t)hdr.nvfh_size);
                return (B_FALSE);
        }

        DTRACE_PROBE1(hdr, nvf_hdr_t *, &hdr);

        /*
         * validate file data is as expected
         */
        chksum = nvf_chksum(buf, hdr.nvfh_size);
        if (hdr.nvfh_datasum != chksum) {
                kmem_free(buf, hdr.nvfh_size);
                cmn_err(CE_NOTE, "!iscsi persistent store checksum error %s "
                    "actual:0x%x expected:0x%x", filename,
                    hdr.nvfh_datasum, chksum);
                return (B_FALSE);
        }

        nvl = NULL;
        rval = nvlist_unpack(buf, hdr.nvfh_size, &nvl, 0);
        if (rval != 0) {
                kmem_free(buf, hdr.nvfh_size);
                cmn_err(CE_NOTE, "!iscsi persistent store failed unpacking "
                    "nvlist %s (%d)", filename, rval);
                return (B_FALSE);
        }

        kmem_free(buf, hdr.nvfh_size);

        /*
         * activate nvlist
         */
        rw_enter(&nvf_list_lock, RW_WRITER);
        old_nvl = nvf_list;
        nvf_list = nvl;
        rw_exit(&nvf_list_lock);

        /*
         * free up old nvlist
         */
        if (old_nvl) {
                nvlist_free(old_nvl);
        }

        return (B_TRUE);
}

/*
 * iscsid_getf -- given a file descriptor returns a file pointer
 */
static file_t *
nvf_getf(int fdes)
{
        file_t  *fp = NULL;

        mutex_enter(&nvf_getf_lock);
        if ((fdes >= 0) && (fdes < NVF_GETF)) {
                fp = nvf_fd[fdes];
                if (fp != NULL)
                        mutex_enter(&fp->f_tlock);
        }
        mutex_exit(&nvf_getf_lock);

        return (fp);
}

/*
 * nvf_releasef -- release lock on file pointer
 */
static void
nvf_releasef(int fdes)
{
        file_t  *fp;

        mutex_enter(&nvf_getf_lock);
        if ((fdes >= 0) && (fdes < NVF_GETF)) {
                fp = nvf_fd[fdes];
                mutex_exit(&fp->f_tlock);
        }
        mutex_exit(&nvf_getf_lock);
}

/*
 * nvf_setf -- stores the file pointer in an empty slot returning index
 */
static int
nvf_setf(file_t *fp)
{
        int     i = -1;

        mutex_enter(&nvf_getf_lock);
        for (i = 0; i < NVF_GETF; i++) {
                if (nvf_fd[i] == 0) {
                        nvf_fd[i] = fp;
                        break;
                }
        }
        mutex_exit(&nvf_getf_lock);
        return (i);
}

/*
 * nvf_freef -- gets the file pointer based on index and releases memory.
 */
static void
nvf_freef(int fdes)
{
        file_t *fp;

        mutex_enter(&nvf_getf_lock);
        if ((fdes >= 0) && (fdes < NVF_GETF)) {
                fp = nvf_fd[fdes];
                unfalloc(fp);
                nvf_fd[fdes] = NULL;
        }
        mutex_exit(&nvf_getf_lock);
}

/*
 * nvf_open -- acts like syscall open, but works for kernel
 *
 * Note: This works for regular files only. No umask is provided to
 * vn_open which means whatever mode is passed in will be used to
 * create a file.
 */
static int
nvf_open(char *path, int flags, int mode)
{
        file_t          *fp     = NULL;
        vnode_t         *vp     = NULL;
        int             fdes    = -1;
        int             fflags;

        /*
         * Need to convert from user mode flags to file system flags.
         * It's unfortunate that the kernel doesn't define a mask for
         * the read/write bits which would make this conversion easier.
         * Only O_RDONLY/O_WRONLY/O_RDWR are different than their FXXXXX
         * counterparts. If one was provided something like
         *      fflags = ((flags & mask) + 1) | (flags & ~mask)
         * would work. But, that would only be true if the relationship
         * be O_XXX and FXXX was defined and it's not. So we have the
         * following.
         */
        if (flags & O_WRONLY)
                fflags = FWRITE;
        else if (flags & O_RDWR)
                fflags = FWRITE | FREAD;
        else
                fflags = FREAD;

        /*
         * Now that fflags has been initialized with the read/write bits
         * look at the other flags and OR them in.
         */
        if (flags & O_CREAT)
                fflags |= FCREAT;
        if (flags & O_TRUNC)
                fflags |= FTRUNC;

        if (nvf_errno = vn_open(path, UIO_SYSSPACE, fflags,
            mode & MODEMASK, &vp, CRCREAT, 0)) {
                return (-1);
        }

        if (falloc(vp, fflags, &fp, NULL) != 0) {
                VN_RELE(vp);
                return (-1);
        }
        /* ---- falloc returns with f_tlock held on success ---- */
        mutex_exit(&fp->f_tlock);

        if ((fdes = nvf_setf(fp)) == -1) {
                VN_RELE(vp);
        }
        return (fdes);
}

/*
 * nvf_close -- closes down the file by releasing locks and memory.
 */
static int
nvf_close(int fdes)
{
        file_t  *fp;
        vnode_t *vp;

        if ((fp = nvf_getf(fdes)) == NULL)
                return (-1);
        vp = fp->f_vnode;

        (void) VOP_CLOSE(vp, fp->f_flag, 1, 0, kcred, NULL);
        VN_RELE(vp);
        /*
         * unfalloc which is called from here will do a mutex_exit
         * on t_lock in the fp. So don't call nvf_releasef() here.
         */
        nvf_freef(fdes);

        return (0);
}

/*
 * nvf_remove -- remove file from filesystem
 */
static int
nvf_remove(char *filename)
{
        return (vn_remove(filename, UIO_SYSSPACE, RMFILE));
}

/*
 * nvf_rename -- rename file from one name to another
 */
static int
nvf_rename(char *oldname, char *newname)
{
        return (vn_rename(oldname, newname, UIO_SYSSPACE));
}

/*
 * nvf_rw -- common read/write code. Very simplistic.
 */
static ssize_t
nvf_rw(int fdes, void *cbuf, ssize_t count, enum uio_rw rw)
{
        file_t  *fp;
        vnode_t *vp;
        ssize_t resid   = 0;

        if ((fp  = nvf_getf(fdes)) == NULL)
                return (-1);
        vp = fp->f_vnode;

        if (nvf_errno = vn_rdwr(rw, vp, (caddr_t)cbuf, count, fp->f_offset,
            UIO_SYSSPACE, 0, RLIM64_INFINITY, kcred, &resid)) {
                nvf_releasef(fdes);
                return (-1);
        }

        if ((count - resid) > 0)
                fp->f_offset += count;

        nvf_releasef(fdes);
        return (count - resid);
}

/*
 * nvf_write -- kernel write function
 */
static ssize_t
nvf_write(int fdes, void *cbuf, ssize_t count)
{
        return (nvf_rw(fdes, cbuf, count, UIO_WRITE));
}

/*
 * nvf_read -- kernel read function
 */
static ssize_t
nvf_read(int fdes, void *cbuf, ssize_t count)
{
        return (nvf_rw(fdes, cbuf, count, UIO_READ));
}