#include <assert.h>
#include <sys/avl.h>
#include <smbsrv/libsmb.h>
#define SMB_CACHE_RDLOCK 0
#define SMB_CACHE_WRLOCK 1
#define SMB_CACHE_STATE_NOCACHE 0
#define SMB_CACHE_STATE_READY 1
#define SMB_CACHE_STATE_REFRESHING 2
#define SMB_CACHE_STATE_DESTROYING 3
static int smb_cache_lock(smb_cache_t *, int);
static int smb_cache_rdlock(smb_cache_t *);
static int smb_cache_wrlock(smb_cache_t *);
static void smb_cache_unlock(smb_cache_t *);
static boolean_t smb_cache_wait(smb_cache_t *);
static void smb_cache_destroy_nodes(smb_cache_t *);
void
smb_cache_create(smb_cache_t *chandle, uint32_t waittime,
int (*cmpfn) (const void *, const void *),
void (*freefn)(void *),
void (*copyfn)(const void *, void *, size_t),
size_t datasz)
{
assert(chandle);
assert(copyfn);
(void) mutex_lock(&chandle->ch_mtx);
if (chandle->ch_state != SMB_CACHE_STATE_NOCACHE) {
(void) mutex_unlock(&chandle->ch_mtx);
return;
}
avl_create(&chandle->ch_cache, cmpfn, sizeof (smb_cache_node_t),
offsetof(smb_cache_node_t, cn_link));
chandle->ch_state = SMB_CACHE_STATE_READY;
chandle->ch_nops = 0;
chandle->ch_wait = waittime;
chandle->ch_sequence = random();
chandle->ch_datasz = datasz;
chandle->ch_free = freefn;
chandle->ch_copy = copyfn;
(void) mutex_unlock(&chandle->ch_mtx);
}
void
smb_cache_destroy(smb_cache_t *chandle)
{
(void) mutex_lock(&chandle->ch_mtx);
switch (chandle->ch_state) {
case SMB_CACHE_STATE_NOCACHE:
case SMB_CACHE_STATE_DESTROYING:
(void) mutex_unlock(&chandle->ch_mtx);
return;
default:
break;
}
chandle->ch_state = SMB_CACHE_STATE_DESTROYING;
while (chandle->ch_nops > 0)
(void) cond_wait(&chandle->ch_cv, &chandle->ch_mtx);
smb_cache_destroy_nodes(chandle);
avl_destroy(&chandle->ch_cache);
chandle->ch_state = SMB_CACHE_STATE_NOCACHE;
(void) mutex_unlock(&chandle->ch_mtx);
}
void
smb_cache_flush(smb_cache_t *chandle)
{
if (smb_cache_wrlock(chandle) == 0) {
smb_cache_destroy_nodes(chandle);
chandle->ch_sequence++;
smb_cache_unlock(chandle);
}
}
int
smb_cache_add(smb_cache_t *chandle, const void *data, int flags)
{
smb_cache_node_t *newnode;
smb_cache_node_t *node;
avl_index_t where;
int rc = 0;
assert(data);
if ((rc = smb_cache_wrlock(chandle)) != 0)
return (rc);
if ((newnode = malloc(sizeof (smb_cache_node_t))) == NULL) {
smb_cache_unlock(chandle);
return (ENOMEM);
}
newnode->cn_data = (void *)data;
node = avl_find(&chandle->ch_cache, newnode, &where);
if (node != NULL) {
if (flags & SMB_CACHE_REPLACE) {
avl_remove(&chandle->ch_cache, node);
if (chandle->ch_free)
chandle->ch_free(node->cn_data);
free(node);
} else {
free(newnode);
smb_cache_unlock(chandle);
return (EEXIST);
}
}
avl_insert(&chandle->ch_cache, newnode, where);
chandle->ch_sequence++;
smb_cache_unlock(chandle);
return (rc);
}
void
smb_cache_remove(smb_cache_t *chandle, const void *data)
{
smb_cache_node_t keynode;
smb_cache_node_t *node;
assert(data);
if (smb_cache_wrlock(chandle) != 0)
return;
keynode.cn_data = (void *)data;
node = avl_find(&chandle->ch_cache, &keynode, NULL);
if (node) {
chandle->ch_sequence++;
avl_remove(&chandle->ch_cache, node);
if (chandle->ch_free)
chandle->ch_free(node->cn_data);
free(node);
}
smb_cache_unlock(chandle);
}
void
smb_cache_iterinit(smb_cache_t *chandle, smb_cache_cursor_t *cursor)
{
cursor->cc_sequence = chandle->ch_sequence;
cursor->cc_next = NULL;
}
boolean_t
smb_cache_iterate(smb_cache_t *chandle, smb_cache_cursor_t *cursor, void *data)
{
smb_cache_node_t *node;
assert(data);
if (smb_cache_rdlock(chandle) != 0)
return (B_FALSE);
if (cursor->cc_sequence != chandle->ch_sequence) {
smb_cache_unlock(chandle);
return (B_FALSE);
}
if (cursor->cc_next == NULL)
node = avl_first(&chandle->ch_cache);
else
node = AVL_NEXT(&chandle->ch_cache, cursor->cc_next);
if (node != NULL)
chandle->ch_copy(node->cn_data, data, chandle->ch_datasz);
cursor->cc_next = node;
smb_cache_unlock(chandle);
return (node != NULL);
}
uint32_t
smb_cache_num(smb_cache_t *chandle)
{
uint32_t num = 0;
if (smb_cache_rdlock(chandle) == 0) {
num = (uint32_t)avl_numnodes(&chandle->ch_cache);
smb_cache_unlock(chandle);
}
return (num);
}
int
smb_cache_refreshing(smb_cache_t *chandle)
{
int rc = 0;
(void) mutex_lock(&chandle->ch_mtx);
switch (chandle->ch_state) {
case SMB_CACHE_STATE_READY:
chandle->ch_state = SMB_CACHE_STATE_REFRESHING;
rc = 0;
break;
case SMB_CACHE_STATE_REFRESHING:
while (chandle->ch_state == SMB_CACHE_STATE_REFRESHING)
(void) cond_wait(&chandle->ch_cv,
&chandle->ch_mtx);
if (chandle->ch_state == SMB_CACHE_STATE_READY) {
chandle->ch_state = SMB_CACHE_STATE_REFRESHING;
rc = 0;
} else {
rc = ENODATA;
}
break;
case SMB_CACHE_STATE_NOCACHE:
case SMB_CACHE_STATE_DESTROYING:
rc = ENODATA;
break;
default:
assert(0);
}
(void) mutex_unlock(&chandle->ch_mtx);
return (rc);
}
void
smb_cache_ready(smb_cache_t *chandle)
{
(void) mutex_lock(&chandle->ch_mtx);
switch (chandle->ch_state) {
case SMB_CACHE_STATE_REFRESHING:
chandle->ch_state = SMB_CACHE_STATE_READY;
(void) cond_broadcast(&chandle->ch_cv);
break;
case SMB_CACHE_STATE_NOCACHE:
case SMB_CACHE_STATE_DESTROYING:
break;
case SMB_CACHE_STATE_READY:
default:
assert(0);
}
(void) mutex_unlock(&chandle->ch_mtx);
}
static int
smb_cache_lock(smb_cache_t *chandle, int mode)
{
(void) mutex_lock(&chandle->ch_mtx);
switch (chandle->ch_state) {
case SMB_CACHE_STATE_NOCACHE:
case SMB_CACHE_STATE_DESTROYING:
(void) mutex_unlock(&chandle->ch_mtx);
return (ENODATA);
case SMB_CACHE_STATE_REFRESHING:
if (mode == SMB_CACHE_RDLOCK) {
if (!smb_cache_wait(chandle)) {
(void) mutex_unlock(&chandle->ch_mtx);
return (ETIME);
}
}
case SMB_CACHE_STATE_READY:
chandle->ch_nops++;
break;
default:
assert(0);
}
(void) mutex_unlock(&chandle->ch_mtx);
if (mode == SMB_CACHE_RDLOCK)
(void) rw_rdlock(&chandle->ch_cache_lck);
else
(void) rw_wrlock(&chandle->ch_cache_lck);
return (0);
}
static int
smb_cache_rdlock(smb_cache_t *chandle)
{
return (smb_cache_lock(chandle, SMB_CACHE_RDLOCK));
}
static int
smb_cache_wrlock(smb_cache_t *chandle)
{
return (smb_cache_lock(chandle, SMB_CACHE_WRLOCK));
}
static void
smb_cache_unlock(smb_cache_t *chandle)
{
(void) mutex_lock(&chandle->ch_mtx);
assert(chandle->ch_nops > 0);
chandle->ch_nops--;
(void) cond_broadcast(&chandle->ch_cv);
(void) mutex_unlock(&chandle->ch_mtx);
(void) rw_unlock(&chandle->ch_cache_lck);
}
static boolean_t
smb_cache_wait(smb_cache_t *chandle)
{
timestruc_t to;
int err;
if (chandle->ch_wait == 0)
return (B_TRUE);
to.tv_sec = chandle->ch_wait;
to.tv_nsec = 0;
while (chandle->ch_state == SMB_CACHE_STATE_REFRESHING) {
err = cond_reltimedwait(&chandle->ch_cv,
&chandle->ch_mtx, &to);
if (err == ETIME)
break;
}
return (chandle->ch_state == SMB_CACHE_STATE_READY);
}
static void
smb_cache_destroy_nodes(smb_cache_t *chandle)
{
void *cookie = NULL;
smb_cache_node_t *cnode;
avl_tree_t *cache;
cache = &chandle->ch_cache;
while ((cnode = avl_destroy_nodes(cache, &cookie)) != NULL) {
if (chandle->ch_free)
chandle->ch_free(cnode->cn_data);
free(cnode);
}
}