/* Distributed Checksum Clearinghouse
 *
 * threaded version of client locking
 *
 * --C-LICENSE--
 * $Revision: 1.83 $
 */

#include "cmn_defs.h"


/* many POSIX thread implementations have unexpected side effects on
 * ordinary system calls, so don't use the threaded version unless
 * necessary */

u_char clnt_threaded = 1;


/* protect the links among contexts and the miscellaneous global
 * variables in the DCC client library */
static pthread_mutex_t ctxts_mutex;
#ifdef DCC_DEBUG_LOCKS
static pthread_t ctxts_owner;
#endif

/* make syslog() thread-safe */
static pthread_mutex_t syslog_mutex;
static u_char syslog_threaded;

#ifdef DCC_DEBUG_HEAP
static pthread_mutex_t malloc_mutex;
static u_char malloc_threaded;
#endif

/* make gethostbyname() thread-safe */
static pthread_mutex_t host_mutex;

static pthread_t clnt_resolve_tid;
static pthread_cond_t clnt_resolve_cond;
static u_char clnt_resolve_stopping;

/* The threaded DNS blacklist support uses fork() to create helper processes
 * to wait for the typical single-threaded DNS resolver library. */
static pthread_mutex_t helper_mutex;
static pthread_t helper_owner;

/* create user logs in a burst while holding a lock
 *	this reduces the total number of file descriptors needed
 *	at a cost of stopping everything while copying from the main
 *	log file to the per-user log files */
pthread_mutex_t user_log_mutex;
pthread_t user_log_owner;


/* this is used only in the threaded DCC clients */
void
clnt_sigs_off(sigset_t *sigsold)
{
	sigset_t sigsnew;
	int error;

	sigemptyset(&sigsnew);
	sigaddset(&sigsnew, SIGHUP);
	sigaddset(&sigsnew, SIGINT);
	sigaddset(&sigsnew, SIGTERM);
	sigaddset(&sigsnew, SIGALRM);
	error = pthread_sigmask(SIG_BLOCK, &sigsnew, sigsold);
	if (error)
		dcc_logbad(EX_SOFTWARE, "pthread_sigmask(): %s",
			   ERROR_STR1(error));
}



void
dcc_ctxts_lock(void)
{
	int error;

#ifdef DCC_DEBUG_LOCKS
	if (ctxts_owner == pthread_self())
		dcc_logbad(EX_SOFTWARE, "already have ctxts lock");
#endif

	error = pthread_mutex_lock(&ctxts_mutex);
	if (error)
		dcc_logbad(EX_SOFTWARE, "pthread_mutex_lock(ctxts): %s",
			   ERROR_STR1(error));
#ifdef DCC_DEBUG_LOCKS
	ctxts_owner = pthread_self();
#endif
}



void
dcc_ctxts_unlock(void)
{
	int error;

#ifdef DCC_DEBUG_LOCKS
	ctxts_owner = 0;
#endif
	error = pthread_mutex_unlock(&ctxts_mutex);
	if (error)
		dcc_logbad(EX_SOFTWARE, "pthread_mutex_unlock(ctxts): %s",
			   ERROR_STR1(error));
}



#ifdef DCC_DEBUG_LOCKS
void
assert_ctxts_locked(void)
{
	if (ctxts_owner != pthread_self())
		dcc_logbad(EX_SOFTWARE, "don't have ctxts lock");
}



void
assert_ctxts_unlocked(void)
{
	if (ctxts_owner == pthread_self())
		dcc_logbad(EX_SOFTWARE, "have ctxts lock");
}
#endif



void
dcc_syslog_lock(void)
{
	int error;

	if (!clnt_threaded || !syslog_threaded)
		return;
	error = pthread_mutex_lock(&syslog_mutex);
	if (error)
		dcc_logbad(EX_SOFTWARE, "pthread_mutex_lock(syslog): %s",
			   ERROR_STR1(error));
}



void
dcc_syslog_unlock(void)
{
	int error;

	if (!clnt_threaded || !syslog_threaded)
		return;
	error = pthread_mutex_unlock(&syslog_mutex);
	if (error)
		dcc_logbad(EX_SOFTWARE, "pthread_mutex_unlock(syslog): %s",
			   ERROR_STR1(error));
}



/* gethostbyname() etc. are usually not reentrant */
u_char dcc_host_locked = 1;

/* do not worry about locking gethostbyname() until the locks have
 * been initialized */
static u_char dcc_host_threaded = 0;

/* This function is mentioned in dccifd/dccif-test/dccif-test.c
 *	and so cannot change lightly. */
void
dcc_host_lock(void)
{
	int error;

	if (!dcc_host_threaded)
		return;

	error = pthread_mutex_lock(&host_mutex);
	if (error)
		dcc_logbad(EX_SOFTWARE, "pthread_mutex_lock(host): %s",
			   ERROR_STR1(error));
	dcc_host_locked = 1;
}



/* This function is mentioned in dccifd/dccif-test/dccif-test.c
 *	and so cannot change lightly. */
void
dcc_host_unlock(void)
{
	int error;

	if (!dcc_host_threaded)
		return;

	dcc_host_locked = 0;
	error = pthread_mutex_unlock(&host_mutex);
	if (error)
		dcc_logbad(EX_SOFTWARE, "pthread_mutex_unlock(host): %s",
			   ERROR_STR1(error));
}



#ifdef DCC_DEBUG_HEAP
void
dcc_malloc_lock(void)
{
	int error;

	if (!malloc_threaded)		/* no locking until locks created */
		return;
	error = pthread_mutex_lock(&malloc_mutex);
	if (error)
		dcc_logbad(EX_SOFTWARE, "pthread_mutex_lock(malloc): %s",
			   ERROR_STR1(error));
}


void
dcc_malloc_unlock(void)
{
	int error;

	if (!malloc_threaded)		/* no locking until locks created */
		return;
	error = pthread_mutex_unlock(&malloc_mutex);
	if (error)
		dcc_logbad(EX_SOFTWARE, "pthread_mutex_unlock(malloc): %s",
			   ERROR_STR1(error));
}
#endif /* DCC_DEBUG_HEAP */



#ifndef HAVE_LOCALTIME_R
/* make localtime() thread safe */
static pthread_mutex_t localtime_mutex;
static u_char localtime_threaded;

void
dcc_localtime_lock(void)
{
	int error;

	if (!localtime_threaded)
		return;
	error = pthread_mutex_lock(&localtime_mutex);
	if (error)
		dcc_logbad(EX_SOFTWARE, "pthread_mutex_lock(localtime): %s",
			   ERROR_STR1(error));
}



void
dcc_localtime_unlock(void)
{
	int error;

	if (!localtime_threaded)
		return;
	error = pthread_mutex_unlock(&localtime_mutex);
	if (error)
		dcc_logbad(EX_SOFTWARE, "pthread_mutex_unlock(localtime): %s",
			   ERROR_STR1(error));
}
#endif /* HAVE_LOCALTIME_R */



const char *main_white_nm;
const char *mapfile_nm = DCC_MAP_NM_DEF;

/* resolve things */
static DCC_CLNT_CTXT *
resolve_sub(DCC_CLNT_CTXT *ctxt,	/* 0=allocate and initialize */
	    WF *wf, WF *tmp_wf)
{
	DCC_EMSG emsg;

	if (!ctxt) {
		wf_init(wf, 0);
		if (main_white_nm)
			dcc_new_white_nm(&emsg, wf, main_white_nm);

		emsg.c[0] = '\0';
		if (!dcc_map_info(&emsg, mapfile_nm, -1))
			dcc_logbad(EX_USAGE, "%s", emsg.c);

		ctxt = dcc_alloc_ctxt();
	}

	if (wf->ascii_nm.c[0] != '\0') {
		if (clnt_resolve_stopping)
			return ctxt;
		dcc_ctxts_unlock();
		switch (wf_rdy(&emsg, wf, tmp_wf)) {
		case WHITE_OK:
		case WHITE_NOFILE:
		case WHITE_SILENT:
			break;
		case WHITE_CONTINUE:
		case WHITE_COMPLAIN:
			dcc_error_msg("%s", emsg.c);
			break;
		}
		dcc_ctxts_lock();

		/* Tell the other threads that the whitelist hash table
		 * in the disk file has changed.
		 * This kludge lets this thread use its own
		 * wf structure without hogging the lock on cmn_wf. */
		if (wf->closed) {
			wf->closed = 0;
			cmn_wf.need_reopen = 1;
		}
	}

	return ctxt;
}



static void * DCC_NORET
clnt_resolve_thread(void *arg DCC_UNUSED)
{
	WF wf, tmp_wf;
	DCC_CLNT_CTXT *ctxt;
#ifndef DCC_WIN32
	struct stat sb;
#endif
	DCC_EMSG emsg;
	int error;

	/* let the thread in charge of signals deal with them */
	clnt_sigs_off(0);

	ctxt = 0;
	dcc_ctxts_lock();
	for (;;) {
		if (clnt_resolve_stopping) {
			if (ctxt) {
				dcc_rel_ctxt(ctxt);
				ctxt = 0;
			}
			dcc_ctxts_unlock();
			pthread_exit(0);
		}

#ifndef DCC_WIN32
		/* Notice if the file has been unlinked and try to reopen it.
		 * Open files cannot be unlinked in WIN32, which lets us not
		 * worry about whether WIN32 files have device and i-numbers */
		if (ctxt && !(dcc_clnt_info->fgs & DCC_INFO_FG_TMP)) {
			if (!dcc_ck_private(0, &sb, dcc_info_nm.c, -1)
			    || sb.st_dev != dcc_info_sb.st_dev
			    || sb.st_ino != dcc_info_sb.st_ino) {
				dcc_error_msg("%s disappeared",
					      dcc_info_nm.c);
				if (ctxt) {
					dcc_rel_ctxt(ctxt);
					ctxt = 0;
				}
				dcc_unmap_close_info(0);
			}
		}
#endif

		ctxt = resolve_sub(ctxt, &wf, &tmp_wf);

		if (clnt_resolve_stopping) {
			if (ctxt) {
				dcc_rel_ctxt(ctxt);
				ctxt = 0;
			}
			dcc_ctxts_unlock();
			pthread_exit(0);
		}
		emsg.c[0] = '\0';
		if (!dcc_clnt_rdy(&emsg, ctxt, DCC_CLNT_FG_NO_FAIL))
			dcc_error_msg("%s", emsg.c);
		else if (!dcc_info_unlock(&emsg))
			dcc_logbad(emsg_ex_code(&emsg), "%s", emsg.c);

		if (grey_on) {
			if (clnt_resolve_stopping) {
				if (ctxt) {
					dcc_rel_ctxt(ctxt);
					ctxt = 0;
				}
				dcc_ctxts_unlock();
				pthread_exit(0);
			}
			emsg.c[0] = '\0';
			if (!dcc_clnt_rdy(&emsg, ctxt, (DCC_CLNT_FG_GREY
						       | DCC_CLNT_FG_NO_FAIL)))
				dcc_error_msg("%s", emsg.c);
			else if (!dcc_info_unlock(&emsg))
				dcc_logbad(emsg_ex_code(&emsg), "%s", emsg.c);
		}

#ifdef DCC_DEBUG_LOCKS
		ctxts_owner = 0;
#endif
		error = pthread_cond_wait(&clnt_resolve_cond, &ctxts_mutex);
		if (error != 0)
			dcc_logbad(EX_SOFTWARE,
				   "pthread_cond_wait(resolve): %s",
				   ERROR_STR1(error));
#ifdef DCC_DEBUG_LOCKS
		ctxts_owner = pthread_self();
#endif
	}
}



u_char					/* 1=awoke the resolver thread */
dcc_clnt_wake_resolve(void)
{
	int error;

	/* we cannot awaken ourself or awaken the thread before it starts */
	if (clnt_resolve_tid == 0
	    || pthread_equal(pthread_self(), clnt_resolve_tid))
		return 0;

	error = pthread_cond_signal(&clnt_resolve_cond);
	if (error != 0)
		dcc_logbad(EX_SOFTWARE, "pthread_cond_signal(resolve): %s",
			   ERROR_STR1(error));
	return 1;
}



void
dcc_clnt_stop_resolve(void)
{
	if (clnt_resolve_stopping)
		return;
	clnt_resolve_stopping = 1;
	if (pthread_equal(pthread_self(), clnt_resolve_tid))
		return;
	pthread_cond_signal(&clnt_resolve_cond);
}



/* some pthreads implementations (e.g. AIX) don't like static
 * initializations */
static void
dcc_mutex_init(void *mutex, const char *nm)
{
	int error = pthread_mutex_init(mutex, 0);
	if (error)
		dcc_logbad(EX_SOFTWARE, "pthread_mutex_init(%s): %s",
			   nm, ERROR_STR1(error));
}



static pthread_mutex_t work_mutex;
static pthread_t cwf_owner;

void
lock_work(void)
{
	int result = pthread_mutex_lock(&work_mutex);
	if (result)
		dcc_logbad(EX_SOFTWARE, "pthread_mutex_lock(work_free): %s",
			   ERROR_STR1(result));
}



void
unlock_work(void)
{
	int result = pthread_mutex_unlock(&work_mutex);
	if (result)
		dcc_logbad(EX_SOFTWARE, "pthread_mutex_unlock(work_free): %s",
			   ERROR_STR1(result));
}



/* lock all CWF structures as well as the cmn_wf structure */
static pthread_mutex_t cwf_mutex;

void
lock_wf(void)
{
	int error = pthread_mutex_lock(&cwf_mutex);
	if (error)
		dcc_logbad(EX_SOFTWARE, "pthread_mutex_lock(cwf): %s",
			   ERROR_STR1(error));
	cwf_owner = pthread_self();
}



void
unlock_wf(void)
{
	int error;
	cwf_owner = 0;
	error = pthread_mutex_unlock(&cwf_mutex);
	if (error)
		dcc_logbad(EX_SOFTWARE, "pthread_mutex_unlock(cwf): %s",
			   ERROR_STR1(error));
}



#ifdef DCC_DEBUG_LOCKS
void
assert_cwf_locked(void)
{
	if (cwf_owner != pthread_self())
		dcc_logbad(EX_SOFTWARE, "don't have cwf lock");
}
#endif



void
dcc_clnt_thread_init(void)
{
	DCC_CLNT_CTXT *ctxt;
	int error;

	/* Some pthreads implementations (e.g. AIX) don't like static
	 * POSIX thread initializations */

	dcc_mutex_init(&ctxts_mutex, "ctxt");
	dcc_mutex_init(&syslog_mutex, "syslog");
	syslog_threaded = 1;
#ifdef DCC_DEBUG_HEAP
	dcc_mutex_init(&malloc_mutex, "heap");
	malloc_threaded = 1;
#endif
#ifndef HAVE_LOCALTIME_R
	dcc_mutex_init(&localtime_mutex, "localtime");
	localtime_threaded = 1;
#endif
	dcc_mutex_init(&host_mutex, "host");
	dcc_host_threaded = 1;
	dcc_host_locked = 0;

	dcc_mutex_init(&user_log_mutex, "user_log");

	dcc_mutex_init(&work_mutex, "work_mutex");
	dcc_mutex_init(&cwf_mutex, "cwf_mutex");

	/* prevent race between resolver thread and other threads to
	 * initialize things by doing it before starting the thread */
	lock_wf();
	dcc_ctxts_lock();
	ctxt = resolve_sub(0, &cmn_wf, &cmn_tmp_wf);
	dcc_rel_ctxt(ctxt);
	ctxt = 0;
	dcc_ctxts_unlock();
	unlock_wf();

	error = pthread_cond_init(&clnt_resolve_cond, 0);
	if (error)
		dcc_logbad(EX_SOFTWARE, "phtread_cond_init(resolve): %s",
			   ERROR_STR1(error));
	error = pthread_create(&clnt_resolve_tid, 0, clnt_resolve_thread, 0);
	if (error)
		dcc_logbad(EX_SOFTWARE, "pthread_create(resolve): %s",
			   ERROR_STR1(error));
	error = pthread_detach(clnt_resolve_tid);
	if (error)
		dcc_logbad(EX_SOFTWARE, "pthread_detach(resolve): %s",
			   ERROR_STR1(error));
}



/* protect DNS blacklist helper channels */
u_char
helper_lock_init(void)
{
	dcc_mutex_init(&helper_mutex, "helper");
	return 1;
}



void
helper_lock(void)
{
	int error;

	assert_helper_unlocked();
	error =  pthread_mutex_lock(&helper_mutex);
	if (error)
		dcc_logbad(EX_SOFTWARE, "pthread_mutex_lock(helper): %s",
			   ERROR_STR1(error));
	helper_owner = pthread_self();
}



void
helper_unlock(void)
{
	int error;

	assert_helper_locked();
	helper_owner = 0;
	error = pthread_mutex_unlock(&helper_mutex);
	if (error)
		dcc_logbad(EX_SOFTWARE, "pthread_mutex_unlock(helper): %s",
			   ERROR_STR1(error));
}



#ifdef DCC_DEBUG_LOCKS
void
assert_helper_locked(void)
{
	if (helper_owner != pthread_self())
		dcc_logbad(EX_SOFTWARE, "don't have helper lock");
}



void
assert_helper_unlocked(void)
{
	if (helper_owner == pthread_self())
		dcc_logbad(EX_SOFTWARE, "have helper lock");
}
#endif
