/*
 * 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 (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
 * Copyright 2018 Nexenta Systems, Inc. All rights reserved.
 * Copyright 2022-2023 RackTop Systems, Inc.
 * Copyright 2023 Oxide Computer Company
 */

#include <sys/param.h>
#include <sys/types.h>
#include <sys/tzfile.h>
#include <sys/atomic.h>
#include <sys/time.h>
#include <sys/spl.h>
#include <sys/random.h>
#include <smbsrv/smb_kproto.h>
#include <smbsrv/smb_fsops.h>
#include <smbsrv/smbinfo.h>
#include <smbsrv/smb_xdr.h>
#include <smbsrv/smb_vops.h>
#include <smbsrv/smb_idmap.h>

#include <sys/sid.h>
#include <sys/priv_names.h>
#include <sys/bitmap.h>

static kmem_cache_t	*smb_dtor_cache = NULL;

static boolean_t smb_avl_hold(smb_avl_t *);
static void smb_avl_rele(smb_avl_t *);

time_t tzh_leapcnt = 0;

struct tm
*smb_gmtime_r(time_t *clock, struct tm *result);

time_t
smb_timegm(struct tm *tm);

struct	tm {
	int	tm_sec;
	int	tm_min;
	int	tm_hour;
	int	tm_mday;
	int	tm_mon;
	int	tm_year;
	int	tm_wday;
	int	tm_yday;
	int	tm_isdst;
};

static const int days_in_month[] = {
	31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};

/*
 * Given a UTF-8 string (our internal form everywhere)
 * return either the Unicode (UTF-16) length in bytes,
 * or the OEM length in bytes.  Which we return is
 * determined by whether the client supports Unicode.
 * This length does NOT include the null.
 */
int
smb_ascii_or_unicode_strlen(struct smb_request *sr, char *str)
{
	if (sr->session->dialect >= SMB_VERS_2_BASE ||
	    (sr->smb_flg2 & SMB_FLAGS2_UNICODE) != 0)
		return (smb_wcequiv_strlen(str));
	return (smb_sbequiv_strlen(str));
}

/*
 * Given a UTF-8 string (our internal form everywhere)
 * return either the Unicode (UTF-16) length in bytes,
 * or the OEM length in bytes.  Which we return is
 * determined by whether the client supports Unicode.
 * This length DOES include the null.
 */
int
smb_ascii_or_unicode_strlen_null(struct smb_request *sr, char *str)
{
	if (sr->session->dialect >= SMB_VERS_2_BASE ||
	    (sr->smb_flg2 & SMB_FLAGS2_UNICODE) != 0)
		return (smb_wcequiv_strlen(str) + 2);
	return (smb_sbequiv_strlen(str) + 1);
}

int
smb_ascii_or_unicode_null_len(struct smb_request *sr)
{
	if (sr->session->dialect >= SMB_VERS_2_BASE ||
	    (sr->smb_flg2 & SMB_FLAGS2_UNICODE) != 0)
		return (2);
	return (1);
}

/*
 *
 * Convert old-style (DOS, LanMan) wildcard strings to NT style.
 * This should ONLY happen to patterns that come from old clients,
 * meaning dialect LANMAN2_1 etc. (dialect < NT_LM_0_12).
 *
 *	? is converted to >
 *	* is converted to < if it is followed by .
 *	. is converted to " if it is followed by ? or * or end of pattern
 *
 * Note: modifies pattern in place.
 */
void
smb_convert_wildcards(char *pattern)
{
	char	*p;

	for (p = pattern; *p != '\0'; p++) {
		switch (*p) {
		case '?':
			*p = '>';
			break;
		case '*':
			if (p[1] == '.')
				*p = '<';
			break;
		case '.':
			if (p[1] == '?' || p[1] == '*' || p[1] == '\0')
				*p = '\"';
			break;
		}
	}
}

/*
 * smb_sattr_check
 *
 * Check file attributes against a search attribute (sattr) mask.
 *
 * Normal files, which includes READONLY and ARCHIVE, always pass
 * this check.  If the DIRECTORY, HIDDEN or SYSTEM special attributes
 * are set then they must appear in the search mask.  The special
 * attributes are inclusive, i.e. all special attributes that appear
 * in sattr must also appear in the file attributes for the check to
 * pass.
 *
 * The following examples show how this works:
 *
 *		fileA:	READONLY
 *		fileB:	0 (no attributes = normal file)
 *		fileC:	READONLY, ARCHIVE
 *		fileD:	HIDDEN
 *		fileE:	READONLY, HIDDEN, SYSTEM
 *		dirA:	DIRECTORY
 *
 * search attribute: 0
 *		Returns: fileA, fileB and fileC.
 * search attribute: HIDDEN
 *		Returns: fileA, fileB, fileC and fileD.
 * search attribute: SYSTEM
 *		Returns: fileA, fileB and fileC.
 * search attribute: DIRECTORY
 *		Returns: fileA, fileB, fileC and dirA.
 * search attribute: HIDDEN and SYSTEM
 *		Returns: fileA, fileB, fileC, fileD and fileE.
 *
 * Returns true if the file and sattr match; otherwise, returns false.
 */
boolean_t
smb_sattr_check(uint16_t dosattr, uint16_t sattr)
{
	if ((dosattr & FILE_ATTRIBUTE_DIRECTORY) &&
	    !(sattr & FILE_ATTRIBUTE_DIRECTORY))
		return (B_FALSE);

	if ((dosattr & FILE_ATTRIBUTE_HIDDEN) &&
	    !(sattr & FILE_ATTRIBUTE_HIDDEN))
		return (B_FALSE);

	if ((dosattr & FILE_ATTRIBUTE_SYSTEM) &&
	    !(sattr & FILE_ATTRIBUTE_SYSTEM))
		return (B_FALSE);

	return (B_TRUE);
}

time_t
smb_get_boottime(void)
{
	return (curzone->zone_boot_time);
}

/*
 * smb_idpool_increment
 *
 * This function increments the ID pool by doubling the current size. This
 * function assumes the caller entered the mutex of the pool.
 */
static int
smb_idpool_increment(
    smb_idpool_t	*pool)
{
	uint8_t		*new_pool;
	uint32_t	new_size;

	ASSERT(pool->id_magic == SMB_IDPOOL_MAGIC);

	new_size = pool->id_size * 2;
	if (new_size <= SMB_IDPOOL_MAX_SIZE) {
		new_pool = kmem_alloc(new_size / 8, KM_NOSLEEP);
		if (new_pool) {
			bzero(new_pool, new_size / 8);
			bcopy(pool->id_pool, new_pool, pool->id_size / 8);
			kmem_free(pool->id_pool, pool->id_size / 8);
			pool->id_pool = new_pool;
			pool->id_free_counter += new_size - pool->id_size;
			pool->id_max_free_counter += new_size - pool->id_size;
			pool->id_size = new_size;
			pool->id_idx_msk = (new_size / 8) - 1;
			if (new_size >= SMB_IDPOOL_MAX_SIZE) {
				/* id -1 made unavailable */
				pool->id_pool[pool->id_idx_msk] = 0x80;
				pool->id_free_counter--;
				pool->id_max_free_counter--;
			}
			return (0);
		}
	}
	return (-1);
}

/*
 * smb_idpool_constructor
 *
 * This function initializes the pool structure provided.
 */
int
smb_idpool_constructor(
    smb_idpool_t	*pool)
{

	ASSERT(pool->id_magic != SMB_IDPOOL_MAGIC);

	pool->id_size = SMB_IDPOOL_MIN_SIZE;
	pool->id_idx_msk = (SMB_IDPOOL_MIN_SIZE / 8) - 1;
	pool->id_free_counter = SMB_IDPOOL_MIN_SIZE - 1;
	pool->id_max_free_counter = SMB_IDPOOL_MIN_SIZE - 1;
	pool->id_bit = 0x02;
	pool->id_bit_idx = 1;
	pool->id_idx = 0;
	pool->id_pool = (uint8_t *)kmem_alloc((SMB_IDPOOL_MIN_SIZE / 8),
	    KM_SLEEP);
	bzero(pool->id_pool, (SMB_IDPOOL_MIN_SIZE / 8));
	/* -1 id made unavailable */
	pool->id_pool[0] = 0x01;		/* id 0 made unavailable */
	mutex_init(&pool->id_mutex, NULL, MUTEX_DEFAULT, NULL);
	pool->id_magic = SMB_IDPOOL_MAGIC;
	return (0);
}

/*
 * smb_idpool_destructor
 *
 * This function tears down and frees the resources associated with the
 * pool provided.
 */
void
smb_idpool_destructor(
    smb_idpool_t	*pool)
{
	ASSERT(pool->id_magic == SMB_IDPOOL_MAGIC);
	ASSERT(pool->id_free_counter == pool->id_max_free_counter);
	pool->id_magic = (uint32_t)~SMB_IDPOOL_MAGIC;
	mutex_destroy(&pool->id_mutex);
	kmem_free(pool->id_pool, (size_t)(pool->id_size / 8));
}

/*
 * smb_idpool_alloc
 *
 * This function allocates an ID from the pool provided.
 */
int
smb_idpool_alloc(
    smb_idpool_t	*pool,
    uint16_t		*id)
{
	uint32_t	i;
	uint8_t		bit;
	uint8_t		bit_idx;
	uint8_t		byte;

	ASSERT(pool->id_magic == SMB_IDPOOL_MAGIC);

	mutex_enter(&pool->id_mutex);
	if ((pool->id_free_counter == 0) && smb_idpool_increment(pool)) {
		mutex_exit(&pool->id_mutex);
		return (-1);
	}

	i = pool->id_size;
	while (i) {
		bit = pool->id_bit;
		bit_idx = pool->id_bit_idx;
		byte = pool->id_pool[pool->id_idx];
		while (bit) {
			if (byte & bit) {
				bit = bit << 1;
				bit_idx++;
				continue;
			}
			pool->id_pool[pool->id_idx] |= bit;
			*id = (uint16_t)(pool->id_idx * 8 + (uint32_t)bit_idx);
			pool->id_free_counter--;
			/*
			 * Leave position at next bit to allocate,
			 * so we don't keep re-using the last in an
			 * alloc/free/alloc/free sequence.  Doing
			 * that can confuse some SMB clients.
			 */
			if (bit & 0x80) {
				pool->id_bit = 1;
				pool->id_bit_idx = 0;
				pool->id_idx++;
				pool->id_idx &= pool->id_idx_msk;
			} else {
				pool->id_bit = (bit << 1);
				pool->id_bit_idx = bit_idx + 1;
				/* keep id_idx */
			}
			mutex_exit(&pool->id_mutex);
			return (0);
		}
		pool->id_bit = 1;
		pool->id_bit_idx = 0;
		pool->id_idx++;
		pool->id_idx &= pool->id_idx_msk;
		--i;
	}
	/*
	 * This section of code shouldn't be reached. If there are IDs
	 * available and none could be found there's a problem.
	 */
	ASSERT(0);
	mutex_exit(&pool->id_mutex);
	return (-1);
}

/*
 * smb_idpool_free
 *
 * This function frees the ID provided.
 */
void
smb_idpool_free(
    smb_idpool_t	*pool,
    uint16_t		id)
{
	ASSERT(pool->id_magic == SMB_IDPOOL_MAGIC);
	ASSERT(id != 0);
	ASSERT(id != 0xFFFF);

	mutex_enter(&pool->id_mutex);
	if (pool->id_pool[id >> 3] & (1 << (id & 7))) {
		pool->id_pool[id >> 3] &= ~(1 << (id & 7));
		pool->id_free_counter++;
		ASSERT(pool->id_free_counter <= pool->id_max_free_counter);
		mutex_exit(&pool->id_mutex);
		return;
	}
	/* Freeing a free ID. */
	ASSERT(0);
	mutex_exit(&pool->id_mutex);
}

/*
 * smb_lavl_constructor
 *
 * This function initializes a locked avl.
 */
void
smb_lavl_constructor(
    smb_lavl_t	*la,
    int (*compar) (const void *, const void *),
    size_t	size,
    size_t	offset)
{
	rw_init(&la->la_lock, NULL, RW_DEFAULT, NULL);
	mutex_init(&la->la_mutex, NULL, MUTEX_DEFAULT, NULL);
	avl_create(&la->la_tree, compar, size, offset);
	list_create(&la->la_deleteq, sizeof (smb_dtor_t),
	    offsetof(smb_dtor_t, dt_lnd));
	la->la_wrop = 0;
	la->la_deleteq_count = 0;
	la->la_flushing = B_FALSE;
}

/*
 * Flush the delete queue and destroy a locked avl.
 */
void
smb_lavl_destructor(
    smb_lavl_t	*la)
{
	smb_lavl_flush(la);

	ASSERT(la->la_deleteq_count == 0);
	ASSERT0(avl_numnodes(&la->la_tree));

	rw_destroy(&la->la_lock);
	avl_destroy(&la->la_tree);
	list_destroy(&la->la_deleteq);
	mutex_destroy(&la->la_mutex);
}

/*
 * smb_lavl_enter
 * Not a macro so dtrace smbsrv:* can see it.
 */
void
smb_lavl_enter(smb_lavl_t *la, krw_t mode)
{
	rw_enter(&la->la_lock, mode);
}

/*
 * Post an object to the delete queue.  The delete queue will be processed
 * during smb_lavl_exit or lavl destruction.  Objects are often posted for
 * deletion during avl iteration (while the lavl is locked) but that is
 * not required, and an object can be posted at any time.
 */
void
smb_lavl_post(smb_lavl_t *la, void *object, smb_dtorproc_t dtorproc)
{
	smb_dtor_t	*dtor;

	ASSERT((object != NULL) && (dtorproc != NULL));

	dtor = kmem_cache_alloc(smb_dtor_cache, KM_SLEEP);
	bzero(dtor, sizeof (smb_dtor_t));
	dtor->dt_magic = SMB_DTOR_MAGIC;
	dtor->dt_object = object;
	dtor->dt_proc = dtorproc;

	mutex_enter(&la->la_mutex);
	list_insert_tail(&la->la_deleteq, dtor);
	++la->la_deleteq_count;
	mutex_exit(&la->la_mutex);
}

/*
 * Exit the lavl lock and process the delete queue.
 */
void
smb_lavl_exit(smb_lavl_t *la)
{
	rw_exit(&la->la_lock);
	smb_lavl_flush(la);
}

/*
 * Flush the lavl delete queue.  The mutex is dropped across the destructor
 * call in case this leads to additional objects being posted to the delete
 * queue.
 */
void
smb_lavl_flush(smb_lavl_t *la)
{
	smb_dtor_t    *dtor;

	mutex_enter(&la->la_mutex);
	if (la->la_flushing) {
		mutex_exit(&la->la_mutex);
		return;
	}
	la->la_flushing = B_TRUE;

	dtor = list_head(&la->la_deleteq);
	while (dtor != NULL) {
		SMB_DTOR_VALID(dtor);
		ASSERT((dtor->dt_object != NULL) && (dtor->dt_proc != NULL));
		list_remove(&la->la_deleteq, dtor);
		--la->la_deleteq_count;
		mutex_exit(&la->la_mutex);

		dtor->dt_proc(dtor->dt_object);

		dtor->dt_magic = (uint32_t)~SMB_DTOR_MAGIC;
		kmem_cache_free(smb_dtor_cache, dtor);
		mutex_enter(&la->la_mutex);
		dtor = list_head(&la->la_deleteq);
	}
	la->la_flushing = B_FALSE;

	mutex_exit(&la->la_mutex);
}

/*
 * smb_lavl_upgrade
 *
 * This function tries to upgrade the lock of the locked avl. It assumes the
 * locked has already been entered in RW_READER mode. It first tries using the
 * Solaris function rw_tryupgrade(). If that call fails the lock is released
 * and reentered in RW_WRITER mode. In that last case a window is opened during
 * which the contents of the avl may have changed. The return code indicates
 * whether or not the avl was modified when the lock was exited.
 */
int smb_lavl_upgrade(
    smb_lavl_t *la)
{
	uint64_t	wrop;

	if (rw_tryupgrade(&la->la_lock) != 0) {
		return (0);
	}
	wrop = la->la_wrop;
	rw_exit(&la->la_lock);
	rw_enter(&la->la_lock, RW_WRITER);
	return (wrop != la->la_wrop);
}

/*
 * smb_lavl_insert
 *
 * This function inserts the object passed into the tree
 * at the position determined by the AVL comparator.
 */
void
smb_lavl_insert(
    smb_lavl_t	*la,
    void	*obj)
{
	avl_add(&la->la_tree, obj);
	++la->la_wrop;
}

/*
 * smb_lavl_remove
 *
 * This function removes the object passed from the lavl. This function
 * assumes the lock of the lavl has already been entered.
 */
void
smb_lavl_remove(
    smb_lavl_t	*la,
    void	*obj)
{
	avl_remove(&la->la_tree, obj);
	++la->la_wrop;
}

/*
 * smb_lavl_get_count
 *
 * This function returns the number of elements in the specified avl.
 */
uint32_t
smb_lavl_get_count(
    smb_lavl_t *la)
{
	return ((uint32_t)avl_numnodes(&la->la_tree));
}

/*
 * Initialize the llist delete queue object cache.
 */
void
smb_llist_init(void)
{
	if (smb_dtor_cache != NULL)
		return;

	smb_dtor_cache = kmem_cache_create("smb_dtor_cache",
	    sizeof (smb_dtor_t), 8, NULL, NULL, NULL, NULL, NULL, 0);
}

/*
 * Destroy the llist delete queue object cache.
 */
void
smb_llist_fini(void)
{
	if (smb_dtor_cache != NULL) {
		kmem_cache_destroy(smb_dtor_cache);
		smb_dtor_cache = NULL;
	}
}

/*
 * smb_llist_constructor
 *
 * This function initializes a locked list.
 */
void
smb_llist_constructor(
    smb_llist_t	*ll,
    size_t	size,
    size_t	offset)
{
	rw_init(&ll->ll_lock, NULL, RW_DEFAULT, NULL);
	mutex_init(&ll->ll_mutex, NULL, MUTEX_DEFAULT, NULL);
	list_create(&ll->ll_list, size, offset);
	list_create(&ll->ll_deleteq, sizeof (smb_dtor_t),
	    offsetof(smb_dtor_t, dt_lnd));
	ll->ll_count = 0;
	ll->ll_wrop = 0;
	ll->ll_deleteq_count = 0;
	ll->ll_flushing = B_FALSE;
}

/*
 * Flush the delete queue and destroy a locked list.
 */
void
smb_llist_destructor(
    smb_llist_t	*ll)
{
	smb_llist_flush(ll);

	ASSERT(ll->ll_count == 0);
	ASSERT(ll->ll_deleteq_count == 0);

	rw_destroy(&ll->ll_lock);
	list_destroy(&ll->ll_list);
	list_destroy(&ll->ll_deleteq);
	mutex_destroy(&ll->ll_mutex);
}

/*
 * smb_llist_enter
 * Not a macro so dtrace smbsrv:* can see it.
 */
void
smb_llist_enter(smb_llist_t *ll, krw_t mode)
{
	rw_enter(&ll->ll_lock, mode);
}

/*
 * Post an object to the delete queue.  The delete queue will be processed
 * during list exit or list destruction.  Objects are often posted for
 * deletion during list iteration (while the list is locked) but that is
 * not required, and an object can be posted at any time.
 */
void
smb_llist_post(smb_llist_t *ll, void *object, smb_dtorproc_t dtorproc)
{
	smb_dtor_t	*dtor;

	ASSERT((object != NULL) && (dtorproc != NULL));

	dtor = kmem_cache_alloc(smb_dtor_cache, KM_SLEEP);
	bzero(dtor, sizeof (smb_dtor_t));
	dtor->dt_magic = SMB_DTOR_MAGIC;
	dtor->dt_object = object;
	dtor->dt_proc = dtorproc;

	mutex_enter(&ll->ll_mutex);
	list_insert_tail(&ll->ll_deleteq, dtor);
	++ll->ll_deleteq_count;
	mutex_exit(&ll->ll_mutex);
}

/*
 * Exit the list lock and process the delete queue.
 */
void
smb_llist_exit(smb_llist_t *ll)
{
	rw_exit(&ll->ll_lock);
	smb_llist_flush(ll);
}

/*
 * Flush the list delete queue.  The mutex is dropped across the destructor
 * call in case this leads to additional objects being posted to the delete
 * queue.
 */
void
smb_llist_flush(smb_llist_t *ll)
{
	smb_dtor_t    *dtor;

	mutex_enter(&ll->ll_mutex);
	if (ll->ll_flushing) {
		mutex_exit(&ll->ll_mutex);
		return;
	}
	ll->ll_flushing = B_TRUE;

	dtor = list_head(&ll->ll_deleteq);
	while (dtor != NULL) {
		SMB_DTOR_VALID(dtor);
		ASSERT((dtor->dt_object != NULL) && (dtor->dt_proc != NULL));
		list_remove(&ll->ll_deleteq, dtor);
		--ll->ll_deleteq_count;
		mutex_exit(&ll->ll_mutex);

		dtor->dt_proc(dtor->dt_object);

		dtor->dt_magic = (uint32_t)~SMB_DTOR_MAGIC;
		kmem_cache_free(smb_dtor_cache, dtor);
		mutex_enter(&ll->ll_mutex);
		dtor = list_head(&ll->ll_deleteq);
	}
	ll->ll_flushing = B_FALSE;

	mutex_exit(&ll->ll_mutex);
}

/*
 * smb_llist_upgrade
 *
 * This function tries to upgrade the lock of the locked list. It assumes the
 * locked has already been entered in RW_READER mode. It first tries using the
 * Solaris function rw_tryupgrade(). If that call fails the lock is released
 * and reentered in RW_WRITER mode. In that last case a window is opened during
 * which the contents of the list may have changed. The return code indicates
 * whether or not the list was modified when the lock was exited.
 */
int smb_llist_upgrade(
    smb_llist_t *ll)
{
	uint64_t	wrop;

	if (rw_tryupgrade(&ll->ll_lock) != 0) {
		return (0);
	}
	wrop = ll->ll_wrop;
	rw_exit(&ll->ll_lock);
	rw_enter(&ll->ll_lock, RW_WRITER);
	return (wrop != ll->ll_wrop);
}

/*
 * smb_llist_insert_head
 *
 * This function inserts the object passed a the beginning of the list. This
 * function assumes the lock of the list has already been entered.
 */
void
smb_llist_insert_head(
    smb_llist_t	*ll,
    void	*obj)
{
	list_insert_head(&ll->ll_list, obj);
	++ll->ll_wrop;
	++ll->ll_count;
}

/*
 * smb_llist_insert_tail
 *
 * This function appends to the object passed to the list. This function assumes
 * the lock of the list has already been entered.
 *
 */
void
smb_llist_insert_tail(
    smb_llist_t	*ll,
    void	*obj)
{
	list_insert_tail(&ll->ll_list, obj);
	++ll->ll_wrop;
	++ll->ll_count;
}

/*
 * smb_llist_remove
 *
 * This function removes the object passed from the list. This function assumes
 * the lock of the list has already been entered.
 */
void
smb_llist_remove(
    smb_llist_t	*ll,
    void	*obj)
{
	list_remove(&ll->ll_list, obj);
	++ll->ll_wrop;
	--ll->ll_count;
}

/*
 * smb_llist_get_count
 *
 * This function returns the number of elements in the specified list.
 */
uint32_t
smb_llist_get_count(
    smb_llist_t *ll)
{
	return (ll->ll_count);
}

/*
 * smb_slist_constructor
 *
 * Synchronized list constructor.
 */
void
smb_slist_constructor(
    smb_slist_t	*sl,
    size_t	size,
    size_t	offset)
{
	mutex_init(&sl->sl_mutex, NULL, MUTEX_DEFAULT, NULL);
	cv_init(&sl->sl_cv, NULL, CV_DEFAULT, NULL);
	list_create(&sl->sl_list, size, offset);
	sl->sl_count = 0;
	sl->sl_waiting = B_FALSE;
}

/*
 * smb_slist_destructor
 *
 * Synchronized list destructor.
 */
void
smb_slist_destructor(
    smb_slist_t	*sl)
{
	VERIFY(sl->sl_count == 0);

	mutex_destroy(&sl->sl_mutex);
	cv_destroy(&sl->sl_cv);
	list_destroy(&sl->sl_list);
}

/*
 * smb_slist_enter
 * Not a macro so dtrace smbsrv:* can see it.
 */
void
smb_slist_enter(smb_slist_t *sl)
{
	mutex_enter(&(sl)->sl_mutex);
}

/*
 * smb_slist_insert_head
 *
 * This function inserts the object passed a the beginning of the list.
 */
void
smb_slist_insert_head(
    smb_slist_t	*sl,
    void	*obj)
{
	mutex_enter(&sl->sl_mutex);
	list_insert_head(&sl->sl_list, obj);
	++sl->sl_count;
	mutex_exit(&sl->sl_mutex);
}

/*
 * smb_slist_insert_tail
 *
 * This function appends the object passed to the list.
 */
void
smb_slist_insert_tail(
    smb_slist_t	*sl,
    void	*obj)
{
	mutex_enter(&sl->sl_mutex);
	list_insert_tail(&sl->sl_list, obj);
	++sl->sl_count;
	mutex_exit(&sl->sl_mutex);
}

/*
 * smb_llist_remove
 *
 * This function removes the object passed by the caller from the list.
 */
void
smb_slist_remove(
    smb_slist_t	*sl,
    void	*obj)
{
	mutex_enter(&sl->sl_mutex);
	list_remove(&sl->sl_list, obj);
	if ((--sl->sl_count == 0) && (sl->sl_waiting)) {
		sl->sl_waiting = B_FALSE;
		cv_broadcast(&sl->sl_cv);
	}
	mutex_exit(&sl->sl_mutex);
}

/*
 * smb_slist_move_tail
 *
 * This function transfers all the contents of the synchronized list to the
 * list_t provided. It returns the number of objects transferred.
 */
uint32_t
smb_slist_move_tail(
    list_t	*lst,
    smb_slist_t	*sl)
{
	uint32_t	rv;

	mutex_enter(&sl->sl_mutex);
	rv = sl->sl_count;
	if (sl->sl_count) {
		list_move_tail(lst, &sl->sl_list);
		sl->sl_count = 0;
		if (sl->sl_waiting) {
			sl->sl_waiting = B_FALSE;
			cv_broadcast(&sl->sl_cv);
		}
	}
	mutex_exit(&sl->sl_mutex);
	return (rv);
}

/*
 * smb_slist_obj_move
 *
 * This function moves an object from one list to the end of the other list. It
 * assumes the mutex of each list has been entered.
 */
void
smb_slist_obj_move(
    smb_slist_t	*dst,
    smb_slist_t	*src,
    void	*obj)
{
	ASSERT(dst->sl_list.list_offset == src->sl_list.list_offset);
	ASSERT(dst->sl_list.list_size == src->sl_list.list_size);

	list_remove(&src->sl_list, obj);
	list_insert_tail(&dst->sl_list, obj);
	dst->sl_count++;
	src->sl_count--;
	if ((src->sl_count == 0) && (src->sl_waiting)) {
		src->sl_waiting = B_FALSE;
		cv_broadcast(&src->sl_cv);
	}
}

/*
 * smb_slist_wait_for_empty
 *
 * This function waits for a list to be emptied.
 */
void
smb_slist_wait_for_empty(
    smb_slist_t	*sl)
{
	mutex_enter(&sl->sl_mutex);
	while (sl->sl_count) {
		sl->sl_waiting = B_TRUE;
		cv_wait(&sl->sl_cv, &sl->sl_mutex);
	}
	mutex_exit(&sl->sl_mutex);
}

/*
 * smb_slist_exit
 *
 * This function exits the muetx of the list and signal the condition variable
 * if the list is empty.
 */
void
smb_slist_exit(smb_slist_t *sl)
{
	if ((sl->sl_count == 0) && (sl->sl_waiting)) {
		sl->sl_waiting = B_FALSE;
		cv_broadcast(&sl->sl_cv);
	}
	mutex_exit(&sl->sl_mutex);
}

/* smb_thread_... moved to smb_thread.c */

/*
 * smb_rwx_init
 */
void
smb_rwx_init(
    smb_rwx_t	*rwx)
{
	bzero(rwx, sizeof (smb_rwx_t));
	cv_init(&rwx->rwx_cv, NULL, CV_DEFAULT, NULL);
	mutex_init(&rwx->rwx_mutex, NULL, MUTEX_DEFAULT, NULL);
	rw_init(&rwx->rwx_lock, NULL, RW_DEFAULT, NULL);
}

/*
 * smb_rwx_destroy
 */
void
smb_rwx_destroy(
    smb_rwx_t	*rwx)
{
	mutex_destroy(&rwx->rwx_mutex);
	cv_destroy(&rwx->rwx_cv);
	rw_destroy(&rwx->rwx_lock);
}

/*
 * smb_rwx_rwenter
 */
void
smb_rwx_rwenter(smb_rwx_t *rwx, krw_t mode)
{
	rw_enter(&rwx->rwx_lock, mode);
}

/*
 * smb_rwx_rwexit
 */
void
smb_rwx_rwexit(
    smb_rwx_t	*rwx)
{
	rw_exit(&rwx->rwx_lock);
}


/*
 * smb_rwx_cvwait
 *
 * Wait on rwx->rw_cv, dropping the rw lock and retake after wakeup.
 * Assumes the smb_rwx lock was entered in RW_READER or RW_WRITER
 * mode. It will:
 *
 *	1) release the lock and save its current mode.
 *	2) wait until the condition variable is signaled.
 *	3) re-acquire the lock in the mode saved in (1).
 *
 * Lock order: rwlock, mutex
 */
int
smb_rwx_cvwait(
    smb_rwx_t	*rwx,
    clock_t	timeout)
{
	krw_t	mode;
	int	rc = 1;

	if (rw_write_held(&rwx->rwx_lock)) {
		ASSERT(rw_owner(&rwx->rwx_lock) == curthread);
		mode = RW_WRITER;
	} else {
		ASSERT(rw_read_held(&rwx->rwx_lock));
		mode = RW_READER;
	}

	mutex_enter(&rwx->rwx_mutex);
	rw_exit(&rwx->rwx_lock);

	rwx->rwx_waiting = B_TRUE;
	if (timeout == -1) {
		cv_wait(&rwx->rwx_cv, &rwx->rwx_mutex);
	} else {
		rc = cv_reltimedwait(&rwx->rwx_cv, &rwx->rwx_mutex,
		    timeout, TR_CLOCK_TICK);
	}
	mutex_exit(&rwx->rwx_mutex);

	rw_enter(&rwx->rwx_lock, mode);
	return (rc);
}

/*
 * smb_rwx_cvbcast
 *
 * Wake up threads waiting on rx_cv
 * The rw lock may or may not be held.
 * The mutex MUST NOT be held.
 */
void
smb_rwx_cvbcast(smb_rwx_t *rwx)
{
	mutex_enter(&rwx->rwx_mutex);
	if (rwx->rwx_waiting) {
		rwx->rwx_waiting = B_FALSE;
		cv_broadcast(&rwx->rwx_cv);
	}
	mutex_exit(&rwx->rwx_mutex);
}

/* smb_idmap_... moved to smb_idmap.c */

uint64_t
smb_time_unix_to_nt(timestruc_t *unix_time)
{
	uint64_t nt_time;

	if ((unix_time->tv_sec == 0) && (unix_time->tv_nsec == 0))
		return (0);

	nt_time = unix_time->tv_sec;
	nt_time *= 10000000;  /* seconds to 100ns */
	nt_time += unix_time->tv_nsec / 100;
	return (nt_time + NT_TIME_BIAS);
}

const timestruc_t smb_nttime_m1 = { -1, -1 }; /* minus 1 */
const timestruc_t smb_nttime_m2 = { -1, -2 }; /* minus 2 */

void
smb_time_nt_to_unix(uint64_t nt_time, timestruc_t *unix_time)
{
	static const timestruc_t tzero = { 0, 0 };
	uint32_t seconds;

	ASSERT(unix_time);

	/*
	 * NT time values (0, -1, -2) get special treatment in SMB.
	 * See notes above smb_node_setattr() for details.
	 */
	if (nt_time == 0) {
		*unix_time = tzero;
		return;
	}
	if ((int64_t)nt_time == -1) {
		*unix_time = smb_nttime_m1;
		return;
	}
	if ((int64_t)nt_time == -2) {
		*unix_time = smb_nttime_m2;
		return;
	}

	/*
	 * Can't represent times less than or equal NT_TIME_BIAS,
	 * so convert them to the oldest date we can store.
	 * Note that time zero is "special" being converted
	 * both directions as 0:0 (unix-to-nt, nt-to-unix).
	 */
	if (nt_time <= NT_TIME_BIAS) {
		unix_time->tv_sec = 0;
		unix_time->tv_nsec = 100;
		return;
	}

	nt_time -= NT_TIME_BIAS;
	seconds = nt_time / 10000000;
	unix_time->tv_sec = seconds;
	unix_time->tv_nsec = (nt_time  % 10000000) * 100;
}

/*
 * smb_time_gmt_to_local, smb_time_local_to_gmt
 *
 * Apply the gmt offset to convert between local time and gmt
 */
int32_t
smb_time_gmt_to_local(smb_request_t *sr, int32_t gmt)
{
	if ((gmt == 0) || (gmt == -1))
		return (0);

	return (gmt - sr->sr_gmtoff);
}

int32_t
smb_time_local_to_gmt(smb_request_t *sr, int32_t local)
{
	if ((local == 0) || (local == -1))
		return (0);

	return (local + sr->sr_gmtoff);
}


/*
 * smb_time_dos_to_unix
 *
 * Convert SMB_DATE & SMB_TIME values to a unix timestamp.
 *
 * A date/time field of 0 means that that server file system
 * assigned value need not be changed. The behaviour when the
 * date/time field is set to -1 is not documented but is
 * generally treated like 0.
 * If date or time is 0 or -1 the unix time is returned as 0
 * so that the caller can identify and handle this special case.
 */
int32_t
smb_time_dos_to_unix(int16_t date, int16_t time)
{
	struct tm	atm;

	if (((date == 0) || (time == 0)) ||
	    ((date == -1) || (time == -1))) {
		return (0);
	}

	atm.tm_year = ((date >>  9) & 0x3F) + 80;
	atm.tm_mon  = ((date >>  5) & 0x0F) - 1;
	atm.tm_mday = ((date >>  0) & 0x1F);
	atm.tm_hour = ((time >> 11) & 0x1F);
	atm.tm_min  = ((time >>  5) & 0x3F);
	atm.tm_sec  = ((time >>  0) & 0x1F) << 1;

	return (smb_timegm(&atm));
}

void
smb_time_unix_to_dos(int32_t ux_time, int16_t *date_p, int16_t *time_p)
{
	struct tm	atm;
	int		i;
	time_t		tmp_time;

	if (ux_time == 0) {
		*date_p = 0;
		*time_p = 0;
		return;
	}

	tmp_time = (time_t)ux_time;
	(void) smb_gmtime_r(&tmp_time, &atm);

	if (date_p) {
		i = 0;
		i += atm.tm_year - 80;
		i <<= 4;
		i += atm.tm_mon + 1;
		i <<= 5;
		i += atm.tm_mday;

		*date_p = (short)i;
	}
	if (time_p) {
		i = 0;
		i += atm.tm_hour;
		i <<= 6;
		i += atm.tm_min;
		i <<= 5;
		i += atm.tm_sec >> 1;

		*time_p = (short)i;
	}
}


/*
 * smb_gmtime_r
 *
 * Thread-safe version of smb_gmtime. Returns a null pointer if either
 * input parameter is a null pointer. Otherwise returns a pointer
 * to result.
 *
 * Day of the week calculation: the Epoch was a thursday.
 *
 * There are no timezone corrections so tm_isdst and tm_gmtoff are
 * always zero, and the zone is always WET.
 */
struct tm *
smb_gmtime_r(time_t *clock, struct tm *result)
{
	time_t tsec;
	int year;
	int month;
	int sec_per_month;

	if (clock == 0 || result == 0)
		return (0);

	bzero(result, sizeof (struct tm));
	tsec = *clock;
	tsec -= tzh_leapcnt;

	result->tm_wday = tsec / SECSPERDAY;
	result->tm_wday = (result->tm_wday + TM_THURSDAY) % DAYSPERWEEK;

	year = EPOCH_YEAR;
	while (tsec >= (isleap(year) ? (SECSPERDAY * DAYSPERLYEAR) :
	    (SECSPERDAY * DAYSPERNYEAR))) {
		if (isleap(year))
			tsec -= SECSPERDAY * DAYSPERLYEAR;
		else
			tsec -= SECSPERDAY * DAYSPERNYEAR;

		++year;
	}

	result->tm_year = year - TM_YEAR_BASE;
	result->tm_yday = tsec / SECSPERDAY;

	for (month = TM_JANUARY; month <= TM_DECEMBER; ++month) {
		sec_per_month = days_in_month[month] * SECSPERDAY;

		if (month == TM_FEBRUARY && isleap(year))
			sec_per_month += SECSPERDAY;

		if (tsec < sec_per_month)
			break;

		tsec -= sec_per_month;
	}

	result->tm_mon = month;
	result->tm_mday = (tsec / SECSPERDAY) + 1;
	tsec %= SECSPERDAY;
	result->tm_sec = tsec % 60;
	tsec /= 60;
	result->tm_min = tsec % 60;
	tsec /= 60;
	result->tm_hour = (int)tsec;

	return (result);
}


/*
 * smb_timegm
 *
 * Converts the broken-down time in tm to a time value, i.e. the number
 * of seconds since the Epoch (00:00:00 UTC, January 1, 1970). This is
 * not a POSIX or ANSI function. Per the man page, the input values of
 * tm_wday and tm_yday are ignored and, as the input data is assumed to
 * represent GMT, we force tm_isdst and tm_gmtoff to 0.
 *
 * Before returning the clock time, we use smb_gmtime_r to set up tm_wday
 * and tm_yday, and bring the other fields within normal range. I don't
 * think this is really how it should be done but it's convenient for
 * now.
 */
time_t
smb_timegm(struct tm *tm)
{
	time_t tsec;
	int dd;
	int mm;
	int yy;
	int year;

	if (tm == 0)
		return (-1);

	year = tm->tm_year + TM_YEAR_BASE;
	tsec = tzh_leapcnt;

	for (yy = EPOCH_YEAR; yy < year; ++yy) {
		if (isleap(yy))
			tsec += SECSPERDAY * DAYSPERLYEAR;
		else
			tsec += SECSPERDAY * DAYSPERNYEAR;
	}

	for (mm = TM_JANUARY; mm < tm->tm_mon; ++mm) {
		dd = days_in_month[mm] * SECSPERDAY;

		if (mm == TM_FEBRUARY && isleap(year))
			dd += SECSPERDAY;

		tsec += dd;
	}

	tsec += (tm->tm_mday - 1) * SECSPERDAY;
	tsec += tm->tm_sec;
	tsec += tm->tm_min * SECSPERMIN;
	tsec += tm->tm_hour * SECSPERHOUR;

	tm->tm_isdst = 0;
	(void) smb_gmtime_r(&tsec, tm);
	return (tsec);
}

/*
 * smb_pad_align
 *
 * Returns the number of bytes required to pad an offset to the
 * specified alignment.
 */
uint32_t
smb_pad_align(uint32_t offset, uint32_t align)
{
	uint32_t pad = offset % align;

	if (pad != 0)
		pad = align - pad;

	return (pad);
}

/*
 * smb_panic
 *
 * Logs the file name, function name and line number passed in and panics the
 * system.
 */
void
smb_panic(char *file, const char *func, int line)
{
	cmn_err(CE_PANIC, "%s:%s:%d\n", file, func, line);
}

/*
 * Creates an AVL tree and initializes the given smb_avl_t
 * structure using the passed args
 */
void
smb_avl_create(smb_avl_t *avl, size_t size, size_t offset,
    const smb_avl_nops_t *ops)
{
	ASSERT(avl);
	ASSERT(ops);

	rw_init(&avl->avl_lock, NULL, RW_DEFAULT, NULL);
	mutex_init(&avl->avl_mutex, NULL, MUTEX_DEFAULT, NULL);

	avl->avl_nops = ops;
	avl->avl_state = SMB_AVL_STATE_READY;
	avl->avl_refcnt = 0;
	(void) random_get_pseudo_bytes((uint8_t *)&avl->avl_sequence,
	    sizeof (uint32_t));

	avl_create(&avl->avl_tree, ops->avln_cmp, size, offset);
}

/*
 * Destroys the specified AVL tree.
 * It waits for all the in-flight operations to finish
 * before destroying the AVL.
 */
void
smb_avl_destroy(smb_avl_t *avl)
{
	void *cookie = NULL;
	void *node;

	ASSERT(avl);

	mutex_enter(&avl->avl_mutex);
	if (avl->avl_state != SMB_AVL_STATE_READY) {
		mutex_exit(&avl->avl_mutex);
		return;
	}

	avl->avl_state = SMB_AVL_STATE_DESTROYING;

	while (avl->avl_refcnt > 0)
		(void) cv_wait(&avl->avl_cv, &avl->avl_mutex);
	mutex_exit(&avl->avl_mutex);

	rw_enter(&avl->avl_lock, RW_WRITER);
	while ((node = avl_destroy_nodes(&avl->avl_tree, &cookie)) != NULL)
		avl->avl_nops->avln_destroy(node);

	avl_destroy(&avl->avl_tree);
	rw_exit(&avl->avl_lock);

	rw_destroy(&avl->avl_lock);

	mutex_destroy(&avl->avl_mutex);
	bzero(avl, sizeof (smb_avl_t));
}

/*
 * Adds the given item to the AVL if it's
 * not already there.
 *
 * Returns:
 *
 *	ENOTACTIVE	AVL is not in READY state
 *	EEXIST		The item is already in AVL
 */
int
smb_avl_add(smb_avl_t *avl, void *item)
{
	avl_index_t where;

	ASSERT(avl);
	ASSERT(item);

	if (!smb_avl_hold(avl))
		return (ENOTACTIVE);

	rw_enter(&avl->avl_lock, RW_WRITER);
	if (avl_find(&avl->avl_tree, item, &where) != NULL) {
		rw_exit(&avl->avl_lock);
		smb_avl_rele(avl);
		return (EEXIST);
	}

	avl_insert(&avl->avl_tree, item, where);
	avl->avl_sequence++;
	rw_exit(&avl->avl_lock);

	smb_avl_rele(avl);
	return (0);
}

/*
 * Removes the given item from the AVL.
 * If no reference is left on the item
 * it will also be destroyed by calling the
 * registered destroy operation.
 */
void
smb_avl_remove(smb_avl_t *avl, void *item)
{
	avl_index_t where;
	void *rm_item;

	ASSERT(avl);
	ASSERT(item);

	if (!smb_avl_hold(avl))
		return;

	rw_enter(&avl->avl_lock, RW_WRITER);
	if ((rm_item = avl_find(&avl->avl_tree, item, &where)) == NULL) {
		rw_exit(&avl->avl_lock);
		smb_avl_rele(avl);
		return;
	}

	avl_remove(&avl->avl_tree, rm_item);
	if (avl->avl_nops->avln_rele(rm_item))
		avl->avl_nops->avln_destroy(rm_item);
	avl->avl_sequence++;
	rw_exit(&avl->avl_lock);

	smb_avl_rele(avl);
}

/*
 * Looks up the AVL for the given item.
 * If the item is found a hold on the object
 * is taken before the pointer to it is
 * returned to the caller. The caller MUST
 * always call smb_avl_release() after it's done
 * using the returned object to release the hold
 * taken on the object.
 */
void *
smb_avl_lookup(smb_avl_t *avl, void *item)
{
	void *node = NULL;

	ASSERT(avl);
	ASSERT(item);

	if (!smb_avl_hold(avl))
		return (NULL);

	rw_enter(&avl->avl_lock, RW_READER);
	node = avl_find(&avl->avl_tree, item, NULL);
	if (node != NULL)
		avl->avl_nops->avln_hold(node);
	rw_exit(&avl->avl_lock);

	if (node == NULL)
		smb_avl_rele(avl);

	return (node);
}

/*
 * The hold on the given object is released.
 * This function MUST always be called after
 * smb_avl_lookup() and smb_avl_iterate() for
 * the returned object.
 *
 * If AVL is in DESTROYING state, the destroying
 * thread will be notified.
 */
void
smb_avl_release(smb_avl_t *avl, void *item)
{
	ASSERT(avl);
	ASSERT(item);

	if (avl->avl_nops->avln_rele(item))
		avl->avl_nops->avln_destroy(item);

	smb_avl_rele(avl);
}

/*
 * Initializes the given cursor for the AVL.
 * The cursor will be used to iterate through the AVL
 */
void
smb_avl_iterinit(smb_avl_t *avl, smb_avl_cursor_t *cursor)
{
	ASSERT(avl);
	ASSERT(cursor);

	cursor->avlc_next = NULL;
	cursor->avlc_sequence = avl->avl_sequence;
}

/*
 * Iterates through the AVL using the given cursor.
 * It always starts at the beginning and then returns
 * a pointer to the next object on each subsequent call.
 *
 * If a new object is added to or removed from the AVL
 * between two calls to this function, the iteration
 * will terminate prematurely.
 *
 * The caller MUST always call smb_avl_release() after it's
 * done using the returned object to release the hold taken
 * on the object.
 */
void *
smb_avl_iterate(smb_avl_t *avl, smb_avl_cursor_t *cursor)
{
	void *node;

	ASSERT(avl);
	ASSERT(cursor);

	if (!smb_avl_hold(avl))
		return (NULL);

	rw_enter(&avl->avl_lock, RW_READER);
	if (cursor->avlc_sequence != avl->avl_sequence) {
		rw_exit(&avl->avl_lock);
		smb_avl_rele(avl);
		return (NULL);
	}

	if (cursor->avlc_next == NULL)
		node = avl_first(&avl->avl_tree);
	else
		node = AVL_NEXT(&avl->avl_tree, cursor->avlc_next);

	if (node != NULL)
		avl->avl_nops->avln_hold(node);

	cursor->avlc_next = node;
	rw_exit(&avl->avl_lock);

	if (node == NULL)
		smb_avl_rele(avl);

	return (node);
}

/*
 * Increments the AVL reference count in order to
 * prevent the avl from being destroyed while it's
 * being accessed.
 */
static boolean_t
smb_avl_hold(smb_avl_t *avl)
{
	mutex_enter(&avl->avl_mutex);
	if (avl->avl_state != SMB_AVL_STATE_READY) {
		mutex_exit(&avl->avl_mutex);
		return (B_FALSE);
	}
	avl->avl_refcnt++;
	mutex_exit(&avl->avl_mutex);

	return (B_TRUE);
}

/*
 * Decrements the AVL reference count to release the
 * hold. If another thread is trying to destroy the
 * AVL and is waiting for the reference count to become
 * 0, it is signaled to wake up.
 */
static void
smb_avl_rele(smb_avl_t *avl)
{
	mutex_enter(&avl->avl_mutex);
	ASSERT(avl->avl_refcnt > 0);
	avl->avl_refcnt--;
	if (avl->avl_state == SMB_AVL_STATE_DESTROYING)
		cv_broadcast(&avl->avl_cv);
	mutex_exit(&avl->avl_mutex);
}

/*
 * smb_latency_init
 */
void
smb_latency_init(smb_latency_t *lat)
{
	bzero(lat, sizeof (*lat));
	mutex_init(&lat->ly_mutex, NULL, MUTEX_SPIN, (void *)ipltospl(SPL7));
}

/*
 * smb_latency_destroy
 */
void
smb_latency_destroy(smb_latency_t *lat)
{
	mutex_destroy(&lat->ly_mutex);
}

/*
 * smb_latency_add_sample
 *
 * Uses the new sample to calculate the new mean and standard deviation. The
 * sample must be a scaled value.
 */
void
smb_latency_add_sample(smb_latency_t *lat, hrtime_t sample)
{
	hrtime_t	a_mean;
	hrtime_t	d_mean;

	mutex_enter(&lat->ly_mutex);
	lat->ly_a_nreq++;
	lat->ly_a_sum += sample;
	if (lat->ly_a_nreq != 0) {
		a_mean = lat->ly_a_sum / lat->ly_a_nreq;
		lat->ly_a_stddev =
		    (sample - a_mean) * (sample - lat->ly_a_mean);
		lat->ly_a_mean = a_mean;
	}
	lat->ly_d_nreq++;
	lat->ly_d_sum += sample;
	if (lat->ly_d_nreq != 0) {
		d_mean = lat->ly_d_sum / lat->ly_d_nreq;
		lat->ly_d_stddev =
		    (sample - d_mean) * (sample - lat->ly_d_mean);
		lat->ly_d_mean = d_mean;
	}
	mutex_exit(&lat->ly_mutex);
}

/*
 * smb_srqueue_init
 */
void
smb_srqueue_init(smb_srqueue_t *srq)
{
	bzero(srq, sizeof (*srq));
	mutex_init(&srq->srq_mutex, NULL, MUTEX_SPIN, (void *)ipltospl(SPL7));
	srq->srq_wlastupdate = srq->srq_rlastupdate = gethrtime_unscaled();
}

/*
 * smb_srqueue_destroy
 */
void
smb_srqueue_destroy(smb_srqueue_t *srq)
{
	mutex_destroy(&srq->srq_mutex);
}

/*
 * smb_srqueue_waitq_enter
 */
void
smb_srqueue_waitq_enter(smb_srqueue_t *srq)
{
	hrtime_t	new;
	hrtime_t	delta;
	uint32_t	wcnt;

	mutex_enter(&srq->srq_mutex);
	new = gethrtime_unscaled();
	delta = new - srq->srq_wlastupdate;
	srq->srq_wlastupdate = new;
	wcnt = srq->srq_wcnt++;
	if (wcnt != 0) {
		srq->srq_wlentime += delta * wcnt;
		srq->srq_wtime += delta;
	}
	mutex_exit(&srq->srq_mutex);
}

/*
 * smb_srqueue_runq_exit
 */
void
smb_srqueue_runq_exit(smb_srqueue_t *srq)
{
	hrtime_t	new;
	hrtime_t	delta;
	uint32_t	rcnt;

	mutex_enter(&srq->srq_mutex);
	new = gethrtime_unscaled();
	delta = new - srq->srq_rlastupdate;
	srq->srq_rlastupdate = new;
	rcnt = srq->srq_rcnt--;
	ASSERT(rcnt > 0);
	srq->srq_rlentime += delta * rcnt;
	srq->srq_rtime += delta;
	mutex_exit(&srq->srq_mutex);
}

/*
 * smb_srqueue_waitq_to_runq
 */
void
smb_srqueue_waitq_to_runq(smb_srqueue_t *srq)
{
	hrtime_t	new;
	hrtime_t	delta;
	uint32_t	wcnt;
	uint32_t	rcnt;

	mutex_enter(&srq->srq_mutex);
	new = gethrtime_unscaled();
	delta = new - srq->srq_wlastupdate;
	srq->srq_wlastupdate = new;
	wcnt = srq->srq_wcnt--;
	ASSERT(wcnt > 0);
	srq->srq_wlentime += delta * wcnt;
	srq->srq_wtime += delta;
	delta = new - srq->srq_rlastupdate;
	srq->srq_rlastupdate = new;
	rcnt = srq->srq_rcnt++;
	if (rcnt != 0) {
		srq->srq_rlentime += delta * rcnt;
		srq->srq_rtime += delta;
	}
	mutex_exit(&srq->srq_mutex);
}

/*
 * smb_srqueue_update
 *
 * Takes a snapshot of the smb_sr_stat_t structure passed in.
 */
void
smb_srqueue_update(smb_srqueue_t *srq, smb_kstat_utilization_t *kd)
{
	hrtime_t	delta;
	hrtime_t	snaptime;

	mutex_enter(&srq->srq_mutex);
	snaptime = gethrtime_unscaled();
	delta = snaptime - srq->srq_wlastupdate;
	srq->srq_wlastupdate = snaptime;
	if (srq->srq_wcnt != 0) {
		srq->srq_wlentime += delta * srq->srq_wcnt;
		srq->srq_wtime += delta;
	}
	delta = snaptime - srq->srq_rlastupdate;
	srq->srq_rlastupdate = snaptime;
	if (srq->srq_rcnt != 0) {
		srq->srq_rlentime += delta * srq->srq_rcnt;
		srq->srq_rtime += delta;
	}
	kd->ku_rlentime = srq->srq_rlentime;
	kd->ku_rtime = srq->srq_rtime;
	kd->ku_wlentime = srq->srq_wlentime;
	kd->ku_wtime = srq->srq_wtime;
	mutex_exit(&srq->srq_mutex);
	scalehrtime(&kd->ku_rlentime);
	scalehrtime(&kd->ku_rtime);
	scalehrtime(&kd->ku_wlentime);
	scalehrtime(&kd->ku_wtime);
}

void
smb_threshold_init(smb_cmd_threshold_t *ct, char *cmd,
    uint_t threshold, uint_t timeout)
{
	bzero(ct, sizeof (smb_cmd_threshold_t));
	mutex_init(&ct->ct_mutex, NULL, MUTEX_DEFAULT, NULL);
	cv_init(&ct->ct_cond, NULL, CV_DEFAULT, NULL);

	ct->ct_cmd = cmd;
	ct->ct_threshold = threshold;
	ct->ct_timeout = timeout;
}

void
smb_threshold_fini(smb_cmd_threshold_t *ct)
{
	cv_destroy(&ct->ct_cond);
	mutex_destroy(&ct->ct_mutex);
}

/*
 * This threshold mechanism is used to limit the number of simultaneous
 * named pipe connections, concurrent authentication conversations, etc.
 * Requests that would take us over the threshold wait until either the
 * resources are available (return zero) or timeout (return error).
 */
int
smb_threshold_enter(smb_cmd_threshold_t *ct)
{
	clock_t	time, rem;

	time = MSEC_TO_TICK(ct->ct_timeout) + ddi_get_lbolt();
	mutex_enter(&ct->ct_mutex);

	while (ct->ct_threshold != 0 &&
	    ct->ct_threshold <= ct->ct_active_cnt) {
		ct->ct_blocked_cnt++;
		rem = cv_timedwait(&ct->ct_cond, &ct->ct_mutex, time);
		ct->ct_blocked_cnt--;
		if (rem < 0) {
			mutex_exit(&ct->ct_mutex);
			return (ETIME);
		}
	}
	if (ct->ct_threshold == 0) {
		mutex_exit(&ct->ct_mutex);
		return (ECANCELED);
	}

	ASSERT3U(ct->ct_active_cnt, <, ct->ct_threshold);
	ct->ct_active_cnt++;

	mutex_exit(&ct->ct_mutex);
	return (0);
}

void
smb_threshold_exit(smb_cmd_threshold_t *ct)
{
	mutex_enter(&ct->ct_mutex);
	ASSERT3U(ct->ct_active_cnt, >, 0);
	ct->ct_active_cnt--;
	if (ct->ct_blocked_cnt)
		cv_signal(&ct->ct_cond);
	mutex_exit(&ct->ct_mutex);
}

void
smb_threshold_wake_all(smb_cmd_threshold_t *ct)
{
	mutex_enter(&ct->ct_mutex);
	ct->ct_threshold = 0;
	cv_broadcast(&ct->ct_cond);
	mutex_exit(&ct->ct_mutex);
}

/* taken from mod_hash_byptr */
uint_t
smb_hash_uint64(smb_hash_t *hash, uint64_t val)
{
	uint64_t k = val >> hash->rshift;
	uint_t idx = ((uint_t)k) & (hash->num_buckets - 1);

	return (idx);
}

boolean_t
smb_is_pow2(size_t n)
{
	return ((n & (n - 1)) == 0);
}

smb_hash_t *
smb_hash_create(size_t elemsz, size_t link_offset,
    uint32_t num_buckets)
{
	smb_hash_t *hash = kmem_alloc(sizeof (*hash), KM_SLEEP);
	int i;

	if (!smb_is_pow2(num_buckets))
		num_buckets = 1 << highbit(num_buckets);

	hash->rshift = highbit(elemsz);
	hash->num_buckets = num_buckets;
	hash->buckets = kmem_zalloc(num_buckets * sizeof (smb_bucket_t),
	    KM_SLEEP);
	for (i = 0; i < num_buckets; i++)
		smb_llist_constructor(&hash->buckets[i].b_list, elemsz,
		    link_offset);
	return (hash);
}

void
smb_hash_destroy(smb_hash_t *hash)
{
	int i;

	for (i = 0; i < hash->num_buckets; i++)
		smb_llist_destructor(&hash->buckets[i].b_list);

	kmem_free(hash->buckets, hash->num_buckets * sizeof (smb_bucket_t));
	kmem_free(hash, sizeof (*hash));
}
