/*
 * 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.
 *
 *	$Id: pthread.h,v 1.20 2006/03/14 19:35:00 ca Exp $
 */

#ifndef SM_PTHREAD_H
#define SM_PTHREAD_H 1

#if HAVE_PTHREAD_H
# include <pthread.h>
#else
# error Missing <pthread.h>
#endif

#include "sm/error.h"
#include "sm/assert.h"

#ifndef SM_MUTEX_DEBUG
# define SM_MUTEX_DEBUG	0
#endif
#ifndef SM_LOCK_DEBUG
# define SM_LOCK_DEBUG	0
#endif

sm_ret_T thr_init(void);
sm_ret_T thr_stop(void);

/* default attribute for pthread_mutex_init() */
#define SM_PTHREAD_MUTEXATTR	NULL

#if SM_MUTEX_DEBUG

/*
**  Debugging versions of mutex_(un)lock()
**  These require that the macro SMFCT is set to the function name.
**  The functions simply print to smioerr, which is good enough for debugging.
*/

int	sm_thread_mutex_lock(pthread_mutex_t *mutex, const char *mname, const char *fct);
int	sm_thread_mutex_unlock(pthread_mutex_t *mutex, const char *mname, const char *fct);

#define smthread_mutex_lock(mutex)	sm_thread_mutex_lock(mutex, #mutex, SMFCT)
#define smthread_mutex_unlock(mutex)	sm_thread_mutex_unlock(mutex, #mutex, SMFCT)

#else /* SM_MUTEX_DEBUG */
#define smthread_mutex_lock(mutex)	pthread_mutex_lock(mutex)
#define smthread_mutex_unlock(mutex)	pthread_mutex_unlock(mutex)
#endif /* SM_MUTEX_DEBUG */

/*
**  Require that a pthread_mutex_lock() worked.
**  This is its own macro in case we don't want to abort on an error.
*/

#define SM_LOCK_OK(r) SM_ASSERT((r) == 0)

/* Require that a mutex is locked (useful for debugging) */
#if SM_LOCK_DEBUG
#define SM_IS_LOCKED(mutex)	\
	do			\
	{			\
		int r;		\
		r = pthread_mutex_trylock(mutex);	\
		SM_ASSERT(r == EBUSY);			\
	} while (0)
#else
#define SM_IS_LOCKED(mutex)	SM_NOOP
#endif


/*
**  Parameters for functions to lock/unlock their data structures:
**  nolocking, lock it, unlock it if no error, unlock on error.
**  This is better than having a boolean since now we can also return
**  a data structure which is still locked (to operate on it, and
**  unlock it later).
**
**  Behavior:
**  LOCK_IT: lock mutex
**  UNL_NO_ERR: unlock mutex if no error occurs
**  UNL_IF_ERR: unlock mutex on error return
**  UNLOCK_IT: always unlock mutex on return
**
**  To have a function:
**  perform no locking at all: THR_NO_LOCK
**  lock/unlock in all cases: THR_LOCK_UNLOCK (normal operation; set all flags)
**  lock but only unlock on error: THR_LOCK_UNLERR
**	this is useful to have a function lock a mutex and keep it locked
**	after return unless an error occurred.
**  just lock: THR_LOCK_IT
**  lock but only unlock if no error: THR_LOCK_IT|THR_UNL_NO_ERR
**  only unlock if no error: THR_UNL_NO_ERR
**  only unlock if error: THR_UNL_IF_ERR
**
**  A function looks like this:
**	if (thr_lock_it(locktype))
**		lock(mutex)
**	... do work ...
**	if (thr_unl_no_err(locktype))
**		lock(mutex)
**	return OK;
**    error:
**	if (thr_unl_if_err(locktype))
**		lock(mutex)
**	return ERROR;
**
**   if there is no error: label:
**	if ((!sm_is_err(ret) && thr_unl_no_err(locktype))
**	    || (sm_is_err(ret) && thr_unl_if_err(locktype)))
**
**   XXX Question: when is thr_unl_always() used?
*/

typedef uint	thr_lock_T;
#define THR_NO_LOCK	0x0000u	/* no locking */
#define THR_LOCK_IT	0x0001u	/* lock it */
#define THR_UNL_NO_ERR	0x0002u	/* unlock it if no error */
#define THR_UNL_IF_ERR	0x0004u	/* unlock it if error */

#define THR_UNLOCK_IT	(THR_UNL_NO_ERR|THR_UNL_IF_ERR)
#define THR_LOCK_UNLOCK	(THR_LOCK_IT|THR_UNLOCK_IT)
#define THR_LOCK_UNLERR	(THR_LOCK_IT|THR_UNL_IF_ERR)

#define thr_lock_it(lck)	(((lck) & THR_LOCK_IT) != 0)
#define thr_unl_if_err(lck)	(((lck) & THR_UNL_IF_ERR) != 0)
#define thr_unl_no_err(lck)	(((lck) & THR_UNL_NO_ERR) != 0)
#define thr_unl_always(lck)	(((lck) & THR_UNLOCK_IT) != 0)

/*
**  Locking for substructure
**  This can be used in the following situation:
**  a structure contains a (list/hash/queue/...) of substructures,
**  each of which can be locked individually.
**  A caller can request to lock the structure and its substructures.
**  Example:
**  struct s1 {mutex s1m; list_s2 s1l; }
**  struct s2 {mutex s2m; item s2i; }
**  function: find entry:
**  lock s1m, find matching entry s2, lock s2m, unlock s1m
**  return s2 (locked) to caller.
**  Note: this isn't used yet.
*/

#define THR_NO_LOCK_S1		0x0000u	/* no locking */
#define THR_LOCK_IT_S1		0x0010u	/* lock it */
#define THR_UNL_NO_ERR_S1	0x0020u	/* unlock it if no error */
#define THR_UNL_IF_ERR_S1	0x0040u	/* unlock it if error */

#define THR_UNLOCK_IT_S1	(THR_UNL_NO_ERR_S1|THR_UNL_IF_ERR_S1)
#define THR_LOCK_UNLOCK_S1	(THR_LOCK_IT_S1|THR_UNLOCK_IT_S1)
#define THR_LOCK_UNLERR_S1	(THR_LOCK_IT_S1|THR_UNL_IF_ERR_S1)

#define thr_lock_it_s1(lck)	(((lck) & THR_LOCK_IT_S1) != 0)
#define thr_unl_if_err_s1(lck)	(((lck) & THR_UNL_IF_ERR_S1) != 0)
#define thr_unl_no_err_s1(lck)	(((lck) & THR_UNL_NO_ERR_S1) != 0)
#define thr_unl_always_s1(lck)	(((lck) & THR_UNLOCK_IT_S1) != 0)

/*
**  Locking for second level function
**  This can be used in the following situation:
**  a function L1 calls another function L2 both of which need locking.
**  A caller can request to lock the structures in L1 and L2 independently.
**  Note: this isn't used yet.
*/

#define THR_NO_LOCK_L2		0x0000u	/* no locking */
#define THR_LOCK_IT_L2		0x0100u	/* lock it */
#define THR_UNL_NO_ERR_L2	0x0200u	/* unlock it if no error */
#define THR_UNL_IF_ERR_L2	0x0400u	/* unlock it if error */

/* convert level 2 locktype to "normal" locktype */
#define THR_L2_TO_L0(lck)	((lck) >> 8)

#define THR_UNLOCK_IT_L2	(THR_UNL_NO_ERR_L2|THR_UNL_IF_ERR_L2)
#define THR_LOCK_UNLOCK_L2	(THR_LOCK_IT_L2|THR_UNLOCK_IT_L2)
#define THR_LOCK_UNLERR_L2	(THR_LOCK_IT_L2|THR_UNL_IF_ERR_L2)

#define thr_lock_it_l2(lck)	(((lck) & THR_LOCK_IT_L2) != 0)
#define thr_unl_if_err_l2(lck)	(((lck) & THR_UNL_IF_ERR_L2) != 0)
#define thr_unl_no_err_l2(lck)	(((lck) & THR_UNL_NO_ERR_L2) != 0)
#define thr_unl_always_l2(lck)	(((lck) & THR_UNLOCK_IT_L2) != 0)


#if !HAVE_PTHREAD_RWLOCK_INIT

/*
**  XXX HACK ... provide only single threaded access for those OS
**  that do not have pthread_rwlock.
*/

#define pthread_rwlock_destroy(m)	pthread_mutex_destroy(m)
#define pthread_rwlock_init(m, a)	pthread_mutex_init((m), (a))
#define pthread_rwlock_rdlock(m)	pthread_mutex_lock(m)
#define pthread_rwlock_wrlock(m)	pthread_mutex_lock(m)
#define pthread_rwlock_unlock(m)	pthread_mutex_unlock(m)
#define pthread_rwlock_t	pthread_mutex_t

#endif /* !HAVE_PTHREAD_RWLOCK_INIT */

#endif /* SM_PTHREAD_H */


syntax highlighted by Code2HTML, v. 0.9.1