view waaad/session.c @ 415:540ed390c04f

Added sess_destroy function
author Sebastien Decugis <sdecugis@nict.go.jp>
date Tue, 16 Jun 2009 13:37:46 +0900
parents 860f41038ea2
children
line wrap: on
line source

/*********************************************************************************************************
* Software License Agreement (BSD License)                                                               *
* Author: Sebastien Decugis <sdecugis@nict.go.jp>							 *
*													 *
* Copyright (c) 2009, WIDE Project and NICT								 *
* All rights reserved.											 *
* 													 *
* Redistribution and use of this software in source and binary forms, with or without modification, are  *
* permitted provided that the following conditions are met:						 *
* 													 *
* * Redistributions of source code must retain the above 						 *
*   copyright notice, this list of conditions and the 							 *
*   following disclaimer.										 *
*    													 *
* * Redistributions in binary form must reproduce the above 						 *
*   copyright notice, this list of conditions and the 							 *
*   following disclaimer in the documentation and/or other						 *
*   materials provided with the distribution.								 *
* 													 *
* * Neither the name of the WIDE Project or NICT nor the 						 *
*   names of its contributors may be used to endorse or 						 *
*   promote products derived from this software without 						 *
*   specific prior written permission of WIDE Project and 						 *
*   NICT.												 *
* 													 *
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 	 *
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 	 *
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF   *
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.								 *
*********************************************************************************************************/

/* Session module.
 * 
 * See session.h and session-api.h for more information on the functions and types involved.
 */

#include "waaad-internal.h"

/* More details about the session implementation:
 *
 *  The sess_id_t, representing a session id, are stored as _sess_id_t objects in a hash table.
 * These objects contains generic information about the session: sid string, creation time, ...
 * They keep a refcount. when the refcount becomes 0, the object is freed.
 *
 *  The sess_reg_t represent clients for the module.
 * The data is stored in a list which root is this _sess_reg_t object.
 * Each element of the list contains a pointer to a _sess_id_t, a pointer to user's data, and
 * a reference to a cleanup handler.
 */

/* Types and globals definitions */

/* The following two integers are used to generate values which are eternaly unique */
static uint32_t   	g_sid_h;	/* initialized to the current time at module initialization */
static uint32_t   	g_sid_l;	/* incremented each time a session id is created. */

static pthread_mutex_t 	g_sid_lock = PTHREAD_MUTEX_INITIALIZER;

/************ sess_id_t ***********/

/* Size of the hash table to hold the sess_id_t objects (pow of 2. ex: 6 => 2^6 = 64). must be between 0 and 31. */
#define SESS_HASH_SIZE	6

/* The sess_id_t internal definition */
typedef struct {
	uti_list_t 	 chain;	/* Chaining information. the "o" field is the pointer to the session id string, \0 terminated */
	uint32_t	 hash;	/* The value of the hash of the sid string */
	int		 eyec;	/* An eye catcher to ensure the object is valid */
	int		 rc;	/* The refcount */
	struct timespec	 ts;	/* Time of the object creation */
} _sess_id_t;

/* The eyec value */
#define _SI_EYEC 0x53551D

/* some useful cast macros */
#define _SI( _si ) ((_sess_id_t *)(_si))
#define _SID( _si ) ((char *)( _LIST( _si )->o))

/* Macro to check an element is valid */
#define VALIDATE_SI( _si ) ( ((_si) != NULL) && ( _SI(_si)->eyec == _SI_EYEC) )

/* The hash table */
static struct {
	uti_list_t	sentinel;	/* sentinel element for this sublist */
	pthread_mutex_t lock;		/* the mutex for this sublist */
	int		count;		/* Number of items in this list */
} sess_hash [ 1 << SESS_HASH_SIZE ] ;
#define H_MASK( __hash ) ((__hash) & (( 1 << SESS_HASH_SIZE ) - 1))
#define H_LIST( _hash ) (&(sess_hash[H_MASK(_hash)].sentinel))
#define H_LOCK( _hash ) (&(sess_hash[H_MASK(_hash)].lock    ))
#define H_COUNT(_hash ) (  sess_hash[H_MASK(_hash)].count    )


/************ sess_reg_t and data ***********/

/* A list element */
typedef struct {
	uti_list_t	  list;		 /* Elements are ordered in the same order as the hash table: by hash and string */
					 /* the "o" field is a pointer to the session element (_sess_id_t) this list element refers */
	void 		(*clncb)(void *);/* The cleanup callback to call on data when the element is destroyed */
	void		 *data;		 /* The data that is registered for this client and session */
} _sd_data_t;

#define _SD( _sd_ ) ( (_sd_data_t *)( _sd_) )
#define _SDI( _sd_ ) ( _SI( _LIST( _sd_ )->o ) )

/* Now the sess_reg_t internal definition */
typedef struct {
	uti_list_t	list;	 /* List of the registered clients with no particular order; only to ensure proper cleanup in the end. */
	int		eyec;	 /* An eye catcher to ensure the object is valid */
	pthread_mutex_t lock;	 /* A lock to protect the list of data */
	uti_list_t	sentinel;/* Sentinel for the list of _sd_data_t of this client */
} _sess_reg_t;

/* The sentinel for the list of registered handlers */
static uti_list_t 	_srt_sentinel;
static pthread_mutex_t 	_str_lock; /* lock to protect the list of registered clients */

/* The eyec value */
#define _SR_EYEC 0x5355DA1A

/* useful cast macro */
#define _SR( _sr ) ((_sess_reg_t *)(_sr))

/* Macro to check an element is valid */
#define VALIDATE_SR( _sr ) ( ((_sr) != NULL) && ( _SR(_sr)->eyec == _SR_EYEC) )

	
/********************************************************************************************************/

/* Function to compare a (hash, sid, len) with a sess_id_element. */
static __inline__ int cmp_si(uint32_t hash1, char * sid1, size_t sid1len,  _sess_id_t * si2)
{
	if (hash1 < si2->hash)
		return -1;
	if (hash1 > si2->hash)
		return 1;
	return strncmp(sid1, _SID(si2), sid1len); /* si2's sid is NULL terminated */
}

/* Function to find a location for a (hash, sid) in a _sess_id_t list.
 * return 0 if the element is found, -1 if the previous element has been returned (in *loc)
 * The sublist lock must be held */
static __inline__ int find_si(uint32_t hash, char * sid, size_t sidlen, uti_list_t * sentinel, uti_list_t ** loc )
{
	int cmp = -1;
	for ((*loc) = sentinel; (*loc)->next != sentinel; (*loc) = (*loc)->next) {
		cmp = cmp_si(hash, sid, sidlen, _SI((*loc)->next));
		if (!cmp)
			(*loc) = (*loc)->next; /* we found the sid in the list */
		if (cmp <= 0)	/* The next element in the list is bigger than our element, so stop here */
			break;
	}
	return cmp;
}
	
/* Function to find a location for a _sess_id_t in a _sd_data_t list (_sess_reg_t -> sentinel).
 * return 0 if the element is found, -1 if the previous element has been returned (in *loc)
 * The list lock must be held */
static __inline__ int find_sd(_sess_id_t * sid, uti_list_t * sentinel, uti_list_t ** loc )
{
	int cmp = -1;
	size_t len = strlen(_SID(sid));
	for ((*loc) = sentinel; (*loc)->next != sentinel; (*loc) = (*loc)->next) {
		cmp = cmp_si(sid->hash, _SID(sid), len, _SDI((*loc)->next));
		if (!cmp)
			(*loc) = (*loc)->next; /* we found the sid in the list */
		if (cmp <= 0)	/* The next element in the list is bigger than our element, so stop here */
			break;
	}
	return cmp;
}
	
	

/********************************************************************************************************/

/* Remove a piece of data linked from a _sess_reg_t. The _sess_reg_t lock must be held */
static int _destroy_sr_data(_sd_data_t * sd, void ** data)
{

	/* Call the destroy function for this element */
	if (data)
		*data = sd->data;
	else
		if (sd->clncb)
			(*sd->clncb)(sd->data);

	/* Unlink the session */
	CHECK_FCT( 	sess_unlink((sess_id_t **)&_LIST(sd)->o) 	);

	/* Unchain this element */
	uti_list_unlink( _LIST(sd) );

	/* And free it */
	free(sd);
	
	return 0;
}

/* Remove any data linked from a _sess_reg_t. The _sess_reg_t lock must be held */
static int _destroy_sr_alldata(uti_list_t * sentinel)
{
	while (!IS_LIST_EMPTY( sentinel )) {
		CHECK_FCT( _destroy_sr_data(_SD(sentinel->next), NULL)	);
	}
	return 0;
}

/* Remove a _sess_reg_t and any data linked from it. The _str_lock lock must be held */	
static int _destroy_sr(_sess_reg_t * item)
{
	int ret = 0;
	
	/* Lock the object */
	CHECK_POSIX(	pthread_mutex_lock( &item->lock )	);
	
	/* Invalidate */
	item->eyec = 0xdead;
	
	/* Destroy any data contained in this object */
	ret = _destroy_sr_alldata(&item->sentinel);
	if (ret != 0) {
		TRACE_DEBUG(INFO, "_destroy_sr_alldata failed: %s", strerror(ret));
		CHECK_POSIX_DO(	pthread_mutex_unlock( &item->lock ),	);
		return ret;
	}
	
	/* Unlink */
	uti_list_unlink(&item->list);
	
	/* Destroy the mutex */
	CHECK_POSIX(	pthread_mutex_unlock( &item->lock )	);
	CHECK_POSIX(	pthread_mutex_destroy( &item->lock )	);
	
	/* Free the object */
	free(item);
	
	return 0;
}
	

/********************************************************************************************************/

/* Initialize the module */
int sess_init ( void )
{
	int i;
	
	TRACE_ENTRY( "" );
	
	/* Initialize the hash table */
	for (i = 0; i < sizeof(sess_hash) / sizeof(sess_hash[0]); i++) {
		CHECK_POSIX(	pthread_mutex_init(&sess_hash[i].lock, NULL)	);
		uti_list_init( &sess_hash[i].sentinel, NULL );
		sess_hash[i].count = 0;
	}
	
	/* Initialize the sentinel for registered clients */
	uti_list_init( &_srt_sentinel, NULL );
	/* and its lock */
	CHECK_POSIX(	pthread_mutex_init(&_str_lock, NULL)	);
	
	/* Initialize the global counters */
	g_sid_h = (uint32_t) time(NULL);
	g_sid_l = 0;
	
	return 0;
}

/* Find a session object corresponding to a given session-id / or create it */
int sess_fromsid ( char * sid, size_t len, sess_id_t ** session, int * new )
{
	int         ret = 0;
	uti_list_t *li  = NULL;
	uint32_t    hash;
	
	TRACE_ENTRY( "%p %d %p", sid, len, session );
	
	/* Validate the parameters */
	CHECK_PARAMS( 	(sid != NULL) && (len != 0) && (session != NULL)	);
	
	/* Compute the hash value of this string */
	hash = uti_hash(sid, len);
	
	/* Lock the hash table line */
	CHECK_POSIX(	pthread_mutex_lock( H_LOCK(hash) )	);
	
	/* Search in the table */
	ret = find_si( hash, sid, len, H_LIST(hash), &li);
	if (ret == 0) {
		/* The element is found in the table */
		if (new)
			*new = 0;
		
		/* increment its refcount, as if sess_link had been called */
		_SI(li)->rc++;
		
		/* Save the value that must be returned */
		*session = (sess_id_t *)li; 
	} else {
		_sess_id_t * newsi;
		char * sidcpy;
		
		/* The element did not exist already, li points to the previous element */
		/* Create a new element -- how to do this outside the mutex section, and efficiently? */
		CHECK_MALLOC_DO( 	sidcpy = malloc(len + 1), 
			{ 
				ret = ENOMEM; 
				goto end; 
			} );
		
		CHECK_MALLOC_DO( 	newsi = malloc(sizeof(_sess_id_t)),
			{ 
				ret = ENOMEM;
				free(sidcpy);
				goto end; 
			} );
		
		/* Initialize the element content */
		memset(newsi, 0, sizeof(_sess_id_t));
		uti_list_init( &newsi->chain, sidcpy );
		newsi->hash = hash;
		newsi->eyec = _SI_EYEC;
		memcpy(sidcpy, sid, len);
		sidcpy[len] = '\0';
		newsi->rc = 1;
		CHECK_POSIX_DO(	ret = clock_gettime(CLOCK_REALTIME, &newsi->ts),
			{
				free(sidcpy);
				free(newsi);
				goto end;
			} );
			
		/* Store this element in the list */
		uti_list_insert_after(li, &newsi->chain);
		
		/* Increment the counter */
		H_COUNT(hash) += 1;
		
		/* Save the reference to it */
		*session = (sess_id_t *)newsi; 
		
		if (new)
			*new = 1;
	}
end:	
	/* Finish, unlock the line */
	CHECK_POSIX(	pthread_mutex_unlock( H_LOCK(hash) )	);
	
	return ret;
}

/* Create a new session (identifier and object). */
int sess_new ( sess_id_t ** session, sess_flags_t flags, char * opt )
{
	int ret = 0;
	int new = 0;
	char * str = NULL;
	size_t strsz = 0;
	int mustfree = 0;
	
	TRACE_ENTRY( "%p %d %p", session, flags, opt );
	
	/* Check the parameters */
	CHECK_PARAMS(  (session != NULL)  );
	
	/* Ok, first create the identifier following the flags */
	switch (flags) {
		case SESSION_NEW_DEFAULT: 	/* "<diameterId>;<high32>;<low32>[;opt]" */
			{
				int len;
				
				/* Compute the size of the new string */
				len = g_conf->diamid_len;
				len += 2 * ( 1 /* ';' */ + 10 /* uint32_t max = 4294967295 = 10 digits */ );
				if (opt)
					len += 1 + strlen(opt);
				
				len++; /* for the final '\0' in the worst case */
				
				/* Allocate the space for the buffer */
				CHECK_MALLOC(	str = malloc(len)	);
				mustfree = 1;
				memset(str, 0, len);
				
				/* Increment the value of the low32 */
				CHECK_POSIX_DO( ret = pthread_mutex_lock(&g_sid_lock), 	goto end );
				++g_sid_l;
				
				/* Now write the new value */
				if (opt)
					strsz = snprintf(str, len, "%s;%u;%u;%s", g_pconf->diameter_identity, g_sid_h, g_sid_l, opt);
				else
					strsz = snprintf(str, len, "%s;%u;%u", g_pconf->diameter_identity, g_sid_h, g_sid_l);
				
				CHECK_POSIX_DO( ret = pthread_mutex_unlock(&g_sid_lock), 	goto end );
			}
			
			break;
			
		case SESSION_NEW_SEQUENCE: 	/* "<diameterId>;opt" */
			CHECK_PARAMS(  (opt != NULL)  );
			{
				int len;
				
				/* Compute the size of the new string */
				len = g_conf->diamid_len + 1 + strlen(opt) + 1; /* +1 for the ';' and the final '\0' */
				
				/* Allocate the space for the buffer */
				CHECK_MALLOC(	str = malloc(len)	);
				mustfree = 1;
				memset(str, 0, len);
				
				strsz = snprintf(str, len, "%s;%s", g_pconf->diameter_identity, opt);
			}
			break;
			
		case SESSION_NEW_FULL:		/* "opt"  */
			CHECK_PARAMS(  (opt != NULL)  );
			str = opt;
			strsz = strlen(str);
			break;

		case SESSION_NEW_OTHER: 	/* "<otherId>;<high32>;<low32>[;opt]" */
			CHECK_PARAMS(  (opt != NULL)  );
			{
				int len;
				char * secopt = NULL;
				char * dupopt; /* so that we can modify the string in case it's a constant */
				
				CHECK_MALLOC( dupopt = strdup(opt) );
				
				/* Compute the size of the new string */
				len = strlen(dupopt);
				len += 2 * ( 1 /* ';' */ + 10 /* uint32_t max = 4294967295 = 10 digits */ );
				len++; /* for the final '\0' in the worst case */
				
				/* Allocate the space for the buffer */
				CHECK_MALLOC(	str = malloc(len)	);
				mustfree = 1;
				memset(str, 0, len);
				
				/* Increment the value of the low32 */
				CHECK_POSIX_DO( ret = pthread_mutex_lock(&g_sid_lock), 	goto end );
				++g_sid_l;
				
				/* Search a ';' in opt */
				secopt = strchr(dupopt, ';');
				
				/* Now write the new value */
				if (secopt) {
					*secopt = '\0';
					secopt++;
					strsz = snprintf(str, len, "%s;%u;%u;%s", dupopt, g_sid_h, g_sid_l, secopt);
				} else {
					strsz = snprintf(str, len, "%s;%u;%u", dupopt, g_sid_h, g_sid_l);
				}
				
				CHECK_POSIX_DO( ret = pthread_mutex_unlock(&g_sid_lock), 	goto end );
				
				free(dupopt);
			}
			
			break;
			
		default:
			CHECK_PARAMS(  (flags, 0)  );
	}
	
	/* Now str points to the new session string. Create the new session */
	TRACE_DEBUG(FULL, "Creating session object for sid: '%s'", str);
	
	CHECK_FCT_DO(	ret = sess_fromsid( str, strsz, session, &new ), 	goto end	);
	
	/* Check that the object did not already exist before */
	if (new == 0) {
		TRACE_DEBUG(INFO, "The session created already existed => The opt parameter must have been bad");
		
		/* The string we received was not really a new unique value */
		CHECK_FCT_DO(	sess_unlink(session),	goto end	);
		
		ret = EALREADY;
	}
	
end:
	if (mustfree)
		free(str);
	
	return ret;
}

/* Get the session-id string of a session */
int sess_getsid ( sess_id_t * session, char ** sid )
{
	TRACE_ENTRY( "%p %p", session, sid );
	
	/* Check the parameters */
	CHECK_PARAMS(   VALIDATE_SI(session) && (sid != NULL)  );
	
	/* Write the location and exit */
	*sid = _SID(session);
	
	return 0;
}

/* Increase refcount of a session */
int sess_link ( sess_id_t * session )
{
	TRACE_ENTRY( "%p", session );
	
	/* Check the parameter */
	CHECK_PARAMS(   VALIDATE_SI(session)  );
	
	/* Lock the list entry */
	CHECK_POSIX(	pthread_mutex_lock(H_LOCK(_SI(session)->hash))	);
	
	/* Increment the refcount */
	_SI(session)->rc++;
	
	/* Unlock the list entry */
	CHECK_POSIX(	pthread_mutex_unlock(H_LOCK(_SI(session)->hash))	);
	
	return 0;
}

/* Decrease refcount of a session, eventually free it. */
int sess_unlink ( sess_id_t ** session )
{
	uint32_t hash;
	
	TRACE_ENTRY( "%p", session );
	
	/* Check the parameter */
	CHECK_PARAMS(  session && VALIDATE_SI(*session)  );
	
	/* Copy the hash value */
	hash = _SI(*session)->hash;
	
	/* Lock the list entry */
	CHECK_POSIX(	pthread_mutex_lock(H_LOCK(hash))	);
	
	/* Decrement the refcount */
	_SI(*session)->rc--;
	
	/* If the count is now 0, we destroy the object */
	if (_SI(*session)->rc == 0) {
		_SI(*session)->eyec = 0xdead;
		
		/* Unlink the element from the chain */
		uti_list_unlink(&_SI(*session)->chain);
		
		/* Update the count */
		H_COUNT(hash) -= 1;
		
		/* Now free the element */
		if (_SID(*session))
			free(_SID(*session));
		free(*session);
	}
	
	/* Unlock the list entry */
	CHECK_POSIX(	pthread_mutex_unlock(H_LOCK(hash))	);
	
	/* Update the location for the calling function */
	*session = NULL;
	
	return 0;
}

/* Create a new client of the module. */
int sess_regext ( sess_reg_t ** client )
{
	int ret = 0;
	_sess_reg_t * new = NULL;
	
	TRACE_ENTRY( "%p", client );
	
	/* Check the parameter */
	CHECK_PARAMS(  client != NULL  );
	
	/* Create a new object */
	CHECK_MALLOC(	new = (_sess_reg_t *) malloc(sizeof(_sess_reg_t))	);
	
	/* Initialize the object */
	memset(new, 0, sizeof(_sess_reg_t));
	uti_list_init(&new->list, NULL);
	new->eyec = _SR_EYEC;
	CHECK_POSIX_DO( ret = pthread_mutex_init(&new->lock, NULL), { free(new); return ret; } );
	uti_list_init(&new->sentinel, NULL);
	
	/* Save the new object in the list */
	CHECK_POSIX_DO( ret = pthread_mutex_lock(&_str_lock), { free(new); return ret; } );
	
	uti_list_insert_after( &_srt_sentinel, &new->list);
	
	CHECK_POSIX(	pthread_mutex_unlock(&_str_lock)	);
	
	/* Copy the reference to the calling process */
	*client = (sess_reg_t *)new;
	
	return 0;
}

/* Destroy a client of the module. */
int sess_deregext ( sess_reg_t * client )
{
	int ret = 0;
	
	TRACE_ENTRY( "%p", client );
	
	CHECK_PARAMS(  VALIDATE_SR(client)  );
	
	/* Lock the list of clients */
	CHECK_POSIX(	pthread_mutex_lock( &_str_lock )	);
	
	/* Now destroy the client handler */
	CHECK_FCT_DO(	ret = _destroy_sr(_SR(client)),
		{ CHECK_POSIX(pthread_mutex_unlock( &_str_lock )); return ret; } 	);
	
	/* Unlock the list of clients */
	CHECK_POSIX(	pthread_mutex_unlock( &_str_lock )	);
	
	return 0;
}

/* Register data in a session. */
int sess_data_reg ( sess_id_t * session, sess_reg_t * client, void * data, void (*cleanup)(void *) )
{
	int ret = 0;
	_sd_data_t *new  = NULL;
	uti_list_t *prev = NULL;
	
	TRACE_ENTRY( "%p %p %p %p", session, client, data, cleanup );
	
	/* Check parameters */
	CHECK_PARAMS(  VALIDATE_SI(session) && VALIDATE_SR(client) && (data != NULL)  );
	
	/* Create a new object to hold the data */
	CHECK_MALLOC(	new = (_sd_data_t *) malloc(sizeof(_sd_data_t))	);
	memset(new, 0, sizeof(_sd_data_t));
	uti_list_init(&new->list, (void *)session);
	new->clncb = cleanup;
	new->data = data;
	
	/* Link the session */
	CHECK_FCT_DO(	ret = sess_link(_SDI(new)),	{ free(new); return ret; }	);
	
	/* Lock the client object */
	CHECK_POSIX_DO(	ret = pthread_mutex_lock( &_SR(client)->lock ),
		{
			CHECK_FCT_DO( sess_unlink((sess_id_t **)&_LIST(new)->o), /* no fallback */ );
			free(new);
			return ret;
		}  );
	
	/* Find the place in the list */
	ret = find_sd(_SDI(new), &_SR(client)->sentinel, &prev);
	if (ret == 0) {
		/* There is already data associated with this session and client */
		TRACE_DEBUG(INFO, "Data already associated with session/client.");
		CHECK_FCT_DO( sess_unlink((sess_id_t **)&_LIST(new)->o), /* no fallback */ );
		free(new);
		CHECK_POSIX(	pthread_mutex_unlock( &_SR(client)->lock )	);
		return EALREADY;
	}
	
	/* Now "prev" is the previous element where the new element must be put */
	uti_list_insert_after(prev, &new->list);
	
	/* Unlock, we're done */
	CHECK_POSIX(	pthread_mutex_unlock( &_SR(client)->lock )	);
	
	return 0;
}

/* Retrieve registered data. */
int sess_data_get ( sess_id_t * session, sess_reg_t * client, void ** data )
{
	int ret = 0;
	uti_list_t *item = NULL;
	 
	TRACE_ENTRY( "%p %p %p", session, client, data );
	
	/* Check parameters */
	CHECK_PARAMS(  VALIDATE_SI(session) && VALIDATE_SR(client) && (data != NULL)  );
	
	*data = NULL;
	
	/* Lock the client object */
	CHECK_POSIX(	pthread_mutex_lock( &_SR(client)->lock )	);
	
	/* Find the data */
	ret = find_sd(_SI(session), &_SR(client)->sentinel, &item);
	if (ret == 0) {
		*data = _SD(item)->data;
	}
	
	/* Unlock, we're done */
	CHECK_POSIX(	pthread_mutex_unlock( &_SR(client)->lock )	);
	
	return (*data == NULL) ? ENOENT : 0;
}

/* Remove registered data from a session. */
int sess_data_dereg ( sess_id_t * session, sess_reg_t * client, void ** data )
{
	int ret = 0;
	int noent = 0;
	uti_list_t *item = NULL;
	 
	TRACE_ENTRY( "%p %p %p", session, client, data );
	
	/* Check parameters */
	CHECK_PARAMS(  VALIDATE_SI(session) && VALIDATE_SR(client)  );
	
	if (data)
		*data = NULL;
	
	/* Lock the client object */
	CHECK_POSIX(	pthread_mutex_lock( &_SR(client)->lock )	);
	
	/* Find the data */
	ret = find_sd(_SI(session), &_SR(client)->sentinel, &item);
	if (ret == 0) {
		CHECK_FCT_DO( ret = _destroy_sr_data(_SD(item), data),
			{
				CHECK_POSIX(	pthread_mutex_unlock( &_SR(client)->lock )	);
				return ret;
			}  );
	} else {
		TRACE_DEBUG(INFO, "data not found.");
		noent = 1;
	}
	
	/* Unlock, we're done */
	CHECK_POSIX(	pthread_mutex_unlock( &_SR(client)->lock )	);
	
	return noent ? ENOENT : 0;
}

/* Destroy a session and any registered data */
int sess_destroy( sess_id_t ** session )
{
	int ret = 0;
	uti_list_t *li, *item = NULL;
	
	TRACE_ENTRY( "%p", session );
	
	/* Check the parameter */
	CHECK_PARAMS(  session && VALIDATE_SI(*session)  );
	
	/* Lock the global list of handlers */
	CHECK_POSIX( pthread_mutex_lock(&_str_lock) );
	
	for (li = _srt_sentinel.next; li != &_srt_sentinel; li = li->next) {
		/* Lock the sublist */
		CHECK_POSIX(	pthread_mutex_lock( &_SR(li)->lock )	);
		
		/* Search for data registered for this session in this handler */
		ret = find_sd(_SI(*session), &_SR(li)->sentinel, &item);
		
		/* If found, destroy it */
		if (ret == 0) {
			CHECK_FCT( _destroy_sr_data(_SD(item), NULL) )
		}
		
		CHECK_POSIX(	pthread_mutex_unlock( &_SR(li)->lock )	);
	}
	
	CHECK_POSIX( pthread_mutex_unlock(&_str_lock) );
	
	/* Now unlink the session. It will be freed unless someone had called "sess_link" explicitely. */
	return sess_unlink(session);
}

/* End of the module */
int sess_fini ( void )
{
	int ret = 0;
	int i;
	
	TRACE_ENTRY( "" );
	
	/* Lock the list of clients */
	CHECK_POSIX(	pthread_mutex_lock( &_str_lock )	);
	
	/* Destroy all the registered clients */
	while (!IS_LIST_EMPTY( &_srt_sentinel )) {
		TRACE_DEBUG( FULL, "Destroying module %p...", _srt_sentinel.next );
		CHECK_FCT_DO(	ret = _destroy_sr( _SR(_srt_sentinel.next) ),
			{
				CHECK_POSIX(	pthread_mutex_unlock( &_str_lock )	);
				return ret;
			}  );
	}
	
	/* Unlock the list of clients */
	CHECK_POSIX(	pthread_mutex_unlock( &_str_lock )	);
	
	/* Destroy the lock */
	CHECK_POSIX(	pthread_mutex_destroy( &_str_lock )	);
	
	/* Check that there is no more session registered */
	for (i = 0; i < sizeof(sess_hash) / sizeof(sess_hash[0]); i++) {
		if (!IS_LIST_EMPTY(&sess_hash[i].sentinel)) {
			TRACE_DEBUG(INFO, "List of session (%d) is not empty. Maybe messages are still refering sessions? EBUSY", i);
			return EBUSY;
		}
		CHECK_POSIX(	pthread_mutex_destroy( &sess_hash[i].lock )	);
	}
	
	/* Done! */
	return 0;
}

/********************************************************************************************************/
/* debug functions */

static void _dump_si ( _sess_id_t * si, int indent )
{
	TRACE_DEBUG(FULL, "%*ssession @%p: h:%8x rc:%d ts:%d sid:'%s'", indent, "", si, si->hash, si->rc, si->ts.tv_sec, _SID(si));
}

static void _dump_hash (void)
{
	int i;
	
	TRACE_DEBUG(FULL, " ======= Dumping the session hash table ======= ");
	for (i=0; i < sizeof(sess_hash) / sizeof(sess_hash[0]); i++) {
		uti_list_t * item;
		TRACE_DEBUG(FULL, " l:%02d cnt:%d", i, sess_hash[i].count);
		for (item = sess_hash[i].sentinel.next; item != &sess_hash[i].sentinel; item = item->next)
			_dump_si(_SI(item), 3);
	}
	TRACE_DEBUG(FULL, " =======/end of session hash table dump/======= ");
}	

static void _dump_sd ( _sd_data_t * sd, int indent )
{
	TRACE_DEBUG(FULL, "%*sdata @%p: data:%p clncb:%p sid:'%s'", indent, "", sd, sd->data, sd->clncb, _SID(_SDI(sd)));
}

static void _dump_regs (void)
{
	uti_list_t * client;
	uti_list_t * data;
	
	TRACE_DEBUG(FULL, " ======= Dumping the registered clients list ======= ");
	for (client = _srt_sentinel.next; client != &_srt_sentinel; client = client->next) {
		TRACE_DEBUG(FULL, "client @%p:", client);
		for (data = _SR(client)->sentinel.next; data != &_SR(client)->sentinel; data = data->next) {
			_dump_sd(_SD(data), 3);
		}
	}
	TRACE_DEBUG(FULL, " =======/end of registered clients list dump/======= ");
}	

void sess_dump(void) 
{
	_dump_hash();
	_dump_regs();
}

"Welcome to our mercurial repository"