/* * 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 /* defines: FKIOCTL */ #include #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)); }