/*
* Copyright (c) 2002-2006 Sendmail, Inc. and its suppliers.
* All rights reserved.
*
* By using this file, you agree to the terms and conditions set
* forth in the LICENSE file which can be found at the top level of
* the sendmail distribution.
*/
#include "sm/generic.h"
SM_RCSID("@(#)$Id: rsc.c,v 1.72 2006/10/05 04:27:37 ca Exp $")
#include "sm/assert.h"
#include "sm/error.h"
#include "sm/memops.h"
#include "sm/heap.h"
#include "sm/queue.h"
#include "sm/bhtable.h"
#define RSC_COMPILE 1
#if SM_RSC_TYPED
# include "sm/rsct.h"
#else
# include "sm/rsc.h"
#endif
#if MTA_USE_PTHREADS
# include "sm/pthread.h"
#endif
#ifndef SM_RSC_STATS
# define SM_RSC_STATS 1
#endif
/*
** Notice:
** if locking is active (MTA_USE_PTHREADS) then each function locks
** the entire RSC during its operation. That means none of these
** functions can call another one herein (if both use locking)!
**
** Possible enhancements:
** - change size of RSC? it should be easy to extend the RSC,
** there is a function for BHT to grow. However, there is no
** function for BHT to shrink (which shouldn't matter much,
** we can just restrict the size of the RSC; that won't
** reclaim the memory in the BHT, but a bit in the RSC).
*/
/* abstraction layer for linking, XXX entries in struct are not abstracted */
#define link_P rsc_entry_P
#define link_succ(r) CIRCLEQ_NEXT(r, rsce_link)
#define link_pred(r) CIRCLEQ_PREV(r, rsce_link)
#define LINK_INIT(rsc) CIRCLEQ_INIT(&((rsc)->rsc_link))
#define LINK_APPEND(rsc, entry) CIRCLEQ_INSERT_HEAD(&((rsc)->rsc_link), entry, rsce_link)
#define LINK_REMOVE(rsc, entry) CIRCLEQ_REMOVE(&((rsc)->rsc_link), (entry), rsce_link)
#define LINK_OLDEST(rsc) CIRCLEQ_LAST(&((rsc)->rsc_link))
#define LINK_NEWEST(rsc) CIRCLEQ_FIRST(&((rsc)->rsc_link))
#define LINK_END(rsc) CIRCLEQ_END(&((rsc)->rsc_link))
/* minimum size of an RSC */
#define RSC_MIN_SIZE 5
typedef struct rsc_entry_S rsc_entry_T, *rsc_entry_P;
/*
** RSC entries are kept in most-recently used order.
** A BHT is used to find RSC entries.
*/
struct rsc_entry_S
{
#if SM_RSCE_CHECK
sm_magic_T sm_magic;
#endif
CIRCLEQ_ENTRY(rsc_entry_S) rsce_link; /* MRU linkage */
#if SM_RSC_TYPED
uint rsce_type; /* type of entry */
#endif
const char *rsce_key; /* lookup key */
uint rsce_len; /* key len */
void *rsce_value; /* corresponding value */
};
#if SM_RSC_TYPED
# define RSC_DELETE(rsc, value, type) (rsc)->rsc_delete(value, (rsc)->rsc_ctx, (type))
# define RSCTYPE , type
#else
# define RSC_DELETE(rsc, value, type) (rsc)->rsc_delete(value, (rsc)->rsc_ctx)
# define RSCTYPE
#endif
struct sm_rsc_S
{
#if SM_RSC_CHECK
sm_magic_T sm_magic;
#endif
bht_P rsc_table; /* table with key, rsc_entry pairs */
uint rsc_limit; /* max # of entries */
uint rsc_used; /* current # of entries */
CIRCLEQ_HEAD(, rsc_entry_S) rsc_link; /* MRU linkage */
void *rsc_ctx; /* application context */
sm_rsc_create_F rsc_create; /* constructor */
sm_rsc_delete_F rsc_delete; /* destructor */
#if MTA_USE_PTHREADS
pthread_mutex_t rsc_mutex;
#endif
#if SM_RSC_STATS
uint rsc_max_used; /* max # of entries */
#endif
};
#if SM_RSC_CHECK
# define SM_IS_RSC(rsc) SM_REQUIRE_ISA((rsc), SM_RSC_MAGIC)
#else
# define SM_IS_RSC(rsc) SM_REQUIRE((rsc) != NULL)
#endif
/*
** SM_RSC_CREATE -- create an RSC
**
** Parameters:
** limit -- maximum number of entries in the RSC
** htsize -- size of BHT
** create -- function to create an entry
** delete -- function to delete an entry
** context -- context for create/delete functions
**
** Returns:
** Pointer to RSC (NULL on error)
** (fixme: should we return the usual error code??)
**
** Last code review: 2005-04-07 22:20:17
** Last code change: 2005-04-07 22:20:11
*/
sm_rsc_P
sm_rsc_new(uint limit, uint htsize,
sm_rsc_create_F create, sm_rsc_delete_F delete,
void *context)
{
sm_rsc_P rsc;
#if MTA_USE_PTHREADS
int r;
#endif
if (limit < 1)
return NULL;
if (htsize < limit) /* this include htsize = 0 */
htsize = limit;
rsc = (sm_rsc_P) sm_malloc(sizeof(*rsc));
if (rsc == NULL)
return NULL;
#if MTA_USE_PTHREADS
r = pthread_mutex_init(&rsc->rsc_mutex, SM_PTHREAD_MUTEXATTR);
if (r != 0)
return NULL; /* sm_error_perm(SM_EM_RSC, r); */
#endif
rsc->rsc_table = bht_new(htsize, limit);
if (rsc->rsc_table == NULL)
{
sm_free_size(rsc, sizeof(*rsc));
goto error;
}
rsc->rsc_limit = (limit < RSC_MIN_SIZE) ? RSC_MIN_SIZE : limit;
rsc->rsc_used = 0;
#if SM_RSC_STATS
rsc->rsc_max_used = 0;
#endif
rsc->rsc_create = create;
rsc->rsc_delete = delete;
LINK_INIT(rsc);
rsc->rsc_ctx = context;
#if SM_RSC_CHECK
rsc->sm_magic = SM_RSC_MAGIC;
#endif
return rsc;
error:
#if MTA_USE_PTHREADS
(void) pthread_mutex_destroy(&rsc->rsc_mutex);
#endif
return NULL;
}
/*
** SM_RSC_ADD -- add item to RSC or return old one
**
** Parameters:
** rsc -- RSC
** delok -- is it ok to delete a value?
** key -- key to lookup
** len -- length of key
** value -- value to add (passed to create function)
** pvalue -- pointer to value of entry (output)
** locktype -- kind of locking
**
** Returns:
** >=0: usage
** <0: usual sm_error code; ENOMEM, SM_E_FULL
**
** Side Effects: if RSC is full the function removes the oldest
** entry which is not "undone" on error.
**
** Locking: locks entire RSC during operation, returns unlocked
** (depending on locktype)
**
** Last code review: 2005-04-07 22:17:15; see comments
** Last code change:
*/
sm_ret_T
sm_rsc_add(sm_rsc_P rsc, bool delok,
#if SM_RSC_TYPED
uint type,
#endif
char *key, uint len, void *value, void **pvalue
#if MTA_USE_PTHREADS
, thr_lock_T locktype
#endif
)
{
rsc_entry_P entry;
sm_ret_T ret;
#if MTA_USE_PTHREADS
int r;
#endif
SM_IS_RSC(rsc);
#if MTA_USE_PTHREADS
if (thr_lock_it(locktype))
{
r = pthread_mutex_lock(&rsc->rsc_mutex);
SM_LOCK_OK(r);
if (r != 0)
{
/* LOG? */
return sm_error_perm(SM_EM_RSC, r);
}
}
#endif
entry = (rsc_entry_P) bht_find(rsc->rsc_table, key, len);
if (entry == NULL)
{
bool allocated;
bht_entry_P bht_entry;
allocated = false;
/* not in RSC: is limit reached? */
if (rsc->rsc_used >= rsc->rsc_limit)
{
if (!delok)
{
ret = sm_error_temp(SM_EM_RSC, SM_E_FULL);
goto error;
}
/* remove oldest entry (not "undone" on error!) */
entry = LINK_OLDEST(rsc);
if (rsc->rsc_delete != NULL)
{
ret = RSC_DELETE(rsc, entry->rsce_value,
entry->rsce_type);
if (sm_is_err(ret))
goto error;
}
LINK_REMOVE(rsc, entry);
bht_rm(rsc->rsc_table, entry->rsce_key,
entry->rsce_len, NULL, NULL);
}
else
{
entry = (rsc_entry_P) sm_zalloc(sizeof(*entry));
if (entry == NULL)
{
ret = sm_error_temp(SM_EM_RSC, ENOMEM);
goto error;
}
allocated = true;
rsc->rsc_used++;
#if SM_RSC_STATS
if (rsc->rsc_used > rsc->rsc_max_used)
rsc->rsc_max_used = rsc->rsc_used;
#endif
}
/* this must not fail... fixme: handle NULL return? */
entry->rsce_value = rsc->rsc_create(key, len, value,
rsc->rsc_ctx RSCTYPE);
ret = bht_add(rsc->rsc_table, key, len,
(char *) entry, &bht_entry);
if (sm_is_err(ret))
{
/* clean up! */
if (rsc->rsc_delete != NULL)
RSC_DELETE(rsc, entry->rsce_value,
entry->rsce_type);
if (allocated)
sm_free_size(entry, sizeof(*entry));
rsc->rsc_used--;
goto error;
}
entry->rsce_key = bht_entry->bhe_key;
entry->rsce_len = len;
#if SM_RSC_TYPED
entry->rsce_type = type;
#endif
/* insert entry right at (after) the head */
LINK_APPEND(rsc, entry);
}
else if (entry != LINK_NEWEST(rsc))
{
/* move the entry up front */
LINK_REMOVE(rsc, entry);
LINK_APPEND(rsc, entry);
}
if (pvalue != NULL)
*pvalue = entry->rsce_value;
#if MTA_USE_PTHREADS
if (thr_unl_no_err(locktype))
{
r = pthread_mutex_unlock(&rsc->rsc_mutex);
SM_ASSERT(r == 0);
/* r isn't checked further; will fail on next iteration */
}
#endif
return (int) ((rsc->rsc_used * 100) / rsc->rsc_limit);
error:
#if MTA_USE_PTHREADS
if (thr_unl_if_err(locktype))
{
r = pthread_mutex_unlock(&rsc->rsc_mutex);
SM_ASSERT(r == 0); /* MUST NOT happen */
if (r != 0 && sm_is_success(ret))
ret = sm_error_perm(SM_EM_RSC, r);
}
#endif
return ret;
}
/*
** SM_RSC_RM -- remove item from RSC.
**
** Parameters:
** rsc -- RSC
** key -- key to lookup
** len -- length of key
** locktype -- kind of locking
**
** Returns:
** usual sm_error code; SM_E_NOTFOUND
**
** Side Effects: no side effects on error.
**
** Locking: locks entire RSC during operation, returns unlocked
** (depending on locktype)
**
** Last code review: 2005-03-31 17:51:39
** Last code change:
*/
sm_ret_T
sm_rsc_rm(sm_rsc_P rsc, const char *key, uint len
#if MTA_USE_PTHREADS
, thr_lock_T locktype
#endif
)
{
sm_ret_T ret;
#if MTA_USE_PTHREADS
int r;
#endif
rsc_entry_P entry;
SM_IS_RSC(rsc);
#if MTA_USE_PTHREADS
if (thr_lock_it(locktype))
{
r = pthread_mutex_lock(&rsc->rsc_mutex);
SM_LOCK_OK(r);
if (r != 0)
{
/* LOG? */
return sm_error_perm(SM_EM_RSC, r);
}
}
#endif
ret = SM_SUCCESS;
entry = (rsc_entry_P) bht_find(rsc->rsc_table, key, len);
if (entry == NULL)
{
ret = sm_error_perm(SM_EM_RSC, SM_E_NOTFOUND);
goto error;
}
LINK_REMOVE(rsc, entry);
if (rsc->rsc_delete != NULL)
RSC_DELETE(rsc, entry->rsce_value, entry->rsce_type);
bht_rm(rsc->rsc_table, entry->rsce_key, entry->rsce_len, NULL, NULL);
SM_ASSERT(rsc->rsc_used > 0);
--rsc->rsc_used;
sm_free_size(entry, sizeof(*entry));
#if MTA_USE_PTHREADS
if (thr_unl_no_err(locktype))
{
r = pthread_mutex_unlock(&rsc->rsc_mutex);
SM_ASSERT(r == 0);
if (r != 0 && sm_is_success(ret))
ret = sm_error_perm(SM_EM_RSC, r);
}
#endif
return ret;
error:
#if MTA_USE_PTHREADS
if (thr_unl_if_err(locktype))
{
r = pthread_mutex_unlock(&rsc->rsc_mutex);
SM_ASSERT(r == 0);
if (r != 0 && sm_is_success(ret))
ret = sm_error_perm(SM_EM_RSC, r);
}
#endif
return ret;
}
/*
** SM_RSC_LOOKUP -- look up item in RSC
**
** Parameters:
** rsc -- RSC
** key -- key to lookup
** len -- length of key
** locktype -- kind of locking
**
** Returns:
** Pointer to value of entry (NULL if not found)
** (fixme: should we return the usual error code??)
**
** Locking: locks entire RSC during operation, returns unlocked
** (depending on locktype)
** Note: the item that is returned is not locked hence
** there's a possible race condition depending on the
** application (how it uses those items).
**
** #if SM_RSC_TYPED
** Restrict this to a certain type?
** #endif
**
** Last code review: 2005-03-31 17:47:15
** Last code change:
*/
const void *
sm_rsc_lookup(sm_rsc_P rsc, const char *key, uint len
#if MTA_USE_PTHREADS
, thr_lock_T locktype
#endif
)
{
rsc_entry_P entry;
#if MTA_USE_PTHREADS
int r;
#endif
SM_IS_RSC(rsc);
#if MTA_USE_PTHREADS
if (thr_lock_it(locktype))
{
r = pthread_mutex_lock(&rsc->rsc_mutex);
SM_LOCK_OK(r);
if (r != 0)
{
/* LOG? */
return NULL; /* sm_error_perm(SM_EM_RSC, r); */
}
}
#endif
entry = (rsc_entry_P) bht_find(rsc->rsc_table, key, len);
#if MTA_USE_PTHREADS
if (thr_unl_always(locktype))
{
r = pthread_mutex_unlock(&rsc->rsc_mutex);
SM_ASSERT(r == 0); /* MUST NOT happen */
/* r isn't checked further; will fail on next iteration */
}
#endif
if (entry == NULL)
return NULL;
return entry->rsce_value;
}
/*
** SM_RSC_FREE_CALLBACK -- callback function for rsc_free
**
** Parameters:
** ptr -- pointer to RSC element
** key -- key (unused)
** ctx -- context.
**
** Returns:
** Nothing.
**
** Last code review: 2005-04-07 22:19:13
** Last code change: 2005-04-07 22:18:49
*/
static void
sm_rsc_free_callback(void *ptr, void *key, void *ctx)
{
rsc_entry_P entry;
sm_rsc_P rsc;
(void) key;
SM_REQUIRE(ptr != NULL);
entry = (rsc_entry_P) ptr;
rsc = (sm_rsc_P) ctx;
SM_IS_RSC(rsc);
if (rsc->rsc_delete != NULL)
RSC_DELETE(rsc, entry->rsce_value, entry->rsce_type);
sm_free_size(entry, sizeof(*entry));
}
/*
** SM_RSC_FREE -- free rsc and its contents
**
** Parameters:
** rsc -- RSC
**
** Returns:
** Nothing.
**
** Last code review: 2005-04-07 22:22:52
** Last code change: 2005-04-07 22:22:48
*/
void
sm_rsc_free(sm_rsc_P rsc)
{
#if MTA_USE_PTHREADS
int r;
#endif
SM_IS_RSC(rsc);
bht_destroy(rsc->rsc_table, sm_rsc_free_callback, (void *) rsc);
#if MTA_USE_PTHREADS
r = pthread_mutex_destroy(&rsc->rsc_mutex);
if (r != 0)
{
/* LOG an error?? */
}
#endif
sm_free_size(rsc, sizeof(*rsc));
}
/*
** SM_RSC_WALK -- iterate over all rsc entries
**
** Parameters:
** rsc -- RSC
** action -- function to apply
** ctx -- context for action
** locktype -- kind of locking
**
** Returns:
** SM_SUCCESS except for (un)lock errors
**
** Locking: locks entire RSC during operation, returns unlocked
** (depending on locktype)
**
** Last code review: 2005-04-07 22:34:25
** Last code change:
*/
sm_ret_T
sm_rsc_walk(sm_rsc_P rsc, sm_rsc_walk_F *action, void *ctx
#if MTA_USE_PTHREADS
, thr_lock_T locktype
#endif
)
{
link_P entry, next;
#if MTA_USE_PTHREADS
int r;
#endif
SM_IS_RSC(rsc);
SM_REQUIRE(action != NULL);
#if MTA_USE_PTHREADS
if (thr_lock_it(locktype))
{
r = pthread_mutex_lock(&rsc->rsc_mutex);
SM_LOCK_OK(r);
if (r != 0)
{
/* LOG? */
return sm_error_perm(SM_EM_RSC, r);
}
}
#endif
for (entry = LINK_NEWEST(rsc); entry != LINK_END(rsc); entry = next)
{
next = link_succ(entry);
action(entry->rsce_key, entry->rsce_value, rsc->rsc_ctx, ctx
#if SM_RSC_TYPED
, entry->rsce_type
#endif
);
}
#if MTA_USE_PTHREADS
if (thr_unl_always(locktype))
{
r = pthread_mutex_unlock(&rsc->rsc_mutex);
SM_ASSERT(r == 0);
if (r != 0)
return sm_error_perm(SM_EM_RSC, r);
}
#endif
return SM_SUCCESS;
}
/*
** SM_RSC_USAGE -- return percentage of # of item in RSC (* 100)
**
** Parameters:
** rsc -- RSC
**
** Returns:
** fill level
**
** Locking: no locking, may return garbage.
**
** Last code review: 2005-04-07 22:34:33
** Last code change:
*/
uint
sm_rsc_usage(sm_rsc_P rsc)
{
SM_IS_RSC(rsc);
return (int) ((rsc->rsc_used * 100) / rsc->rsc_limit);
}
/*
** SM_RSC_ENTRIES -- return # of items in RSC
**
** Parameters:
** rsc -- RSC
** pmax_used -- (pointer to) max. number of entries (output)
**
** Returns:
** fill level
**
** Locking: no locking, may return garbage.
**
** Last code review: 2005-04-07 22:34:40
** Last code change: 2005-05-19 20:16:33
*/
uint
sm_rsc_entries(sm_rsc_P rsc, uint *pmax_used)
{
SM_IS_RSC(rsc);
if (pmax_used != NULL)
#if SM_RSC_STATS
*pmax_used = rsc->rsc_max_used;
#else
*pmax_used = 0;
#endif
return rsc->rsc_used;
}
/*
** SM_RSC_STATS -- return bht stats of RSC
**
** Parameters:
** rsc -- RSC
** out -- buffer to store stats in
** len -- length of buffer out
**
** Returns:
** none
**
** Locking: no locking, may return garbage.
**
** Last code review: 2005-04-07 22:34:46
** Last code change:
*/
void
sm_rsc_stats(sm_rsc_P rsc, char *out, uint len)
{
SM_IS_RSC(rsc);
bht_stats(rsc->rsc_table, out, len);
}
syntax highlighted by Code2HTML, v. 0.9.1