/*
 * Copyright (c) 2002-2005 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: qmgr_start.c,v 1.127 2007/06/18 04:42:31 ca Exp $")
#include "sm/error.h"
#include "sm/assert.h"
#include "sm/qmgr.h"
#include "sm/qmgr-int.h"
#include "sm/unixsock.h"
#include "sm/signal.h"
#include "sm/resource.h"
#include "sm/misc.h"
#include "qmgr.h"
#include "log.h"

/*
**  QM_LISTEN -- start one QMGR listener
**
**	Parameters:
**		sockname -- path for (Unix domain) socket
**			XXX Needs to be fixed for Windows!
**		pfd -- (pointer to) fd (output)
**
**	Returns:
**		usual sm_error code
*/

static sm_ret_T
qm_listen(const char *sockname, int *pfd)
{
	int lfd, r;
	sm_ret_T ret;

	SM_REQUIRE(sockname != NULL);
	SM_REQUIRE(pfd != NULL);
	ret = SM_SUCCESS;
	r = unlink(sockname);
	if (r < 0 && errno != ENOENT) {
		ret = sm_error_perm(SM_EM_Q_START, errno);
		sm_io_fprintf(smioerr,
			"sev=ERROR, func=qm_listen, socket=%s, unlink=%m\n",
			sockname, ret);
		goto error;
	}
	lfd = unix_server_listen(sockname, 10);
	if (!is_valid_socket(lfd)) {
		ret = sm_error_perm(SM_EM_Q_START, errno);
		sm_io_fprintf(smioerr,
			"sev=ERROR, func=qm_listen, socket=%s, unix_server_listen=%m\n",
			sockname, lfd);
		goto error;
	}
	r = chmod(sockname, 0660);
	if (r < 0) {
		ret = sm_error_perm(SM_EM_Q_START, errno);
		sm_io_fprintf(smioerr,
			"sev=ERROR, func=qm_listen, socket=%s, chmod=%m\n",
			sockname, ret);
		goto error;
	}
	*pfd = lfd;
	/* FALLTHROUGH */
  error:
	return ret;
}

/*
**  QM_STLI -- start QMGR listeners
**
**	Parameters:
**		qmgr_ctx -- QMGR context
**
**	Returns:
**		usual sm_error code
**
**	Question: should we store "task" in qmgr_ctx?
*/

static sm_ret_T
qm_stli(qmgr_ctx_P qmgr_ctx)
{
	int fd;
	uint u;
	sm_ret_T ret;
	sm_evthr_task_P task;

	SM_IS_QMGR_CTX(qmgr_ctx);

	/* start listen connections */
	/* these should be in qmgr_ctx after the configuration has been read */

	/* SMTPS */
	ret = qm_listen(qmgr_ctx->qmgr_cnf.q_cnf_smtpssock_abs, &fd);
	if (sm_is_err(ret))
		goto error;
	ret = evthr_task_new(qmgr_ctx->qmgr_ev_ctx, &task, EVTHR_EV_LI,
			fd, NULL, qm_gen_li, (void *) &qmgr_ctx->qmgr_ss_li);
	if (sm_is_err(ret))
		goto error;
	qmgr_ctx->qmgr_ss_li.qm_gli_lfd = fd;

	/* SMTPC (XXX just for now, later on QMGR requests SMTPC to start) */
	ret = qm_listen(qmgr_ctx->qmgr_cnf.q_cnf_smtpcsock_abs, &fd);
	if (sm_is_err(ret))
		goto error;
	ret = evthr_task_new(qmgr_ctx->qmgr_ev_ctx, &task, EVTHR_EV_LI,
			fd, NULL, qm_gen_li, (void *) &qmgr_ctx->qmgr_sc_li);
	if (sm_is_err(ret))
		goto error;
	qmgr_ctx->qmgr_sc_li.qm_gli_lfd = fd;

	/* control socket */
	if (qmgr_ctx->qmgr_cnf.q_cnf_ctlsock_abs != NULL &&
	    *qmgr_ctx->qmgr_cnf.q_cnf_ctlsock_abs != '\0')
	{
		ret = qm_listen(qmgr_ctx->qmgr_cnf.q_cnf_ctlsock_abs, &fd);
		if (sm_is_err(ret))
			goto error;
		ret = evthr_task_new(qmgr_ctx->qmgr_ev_ctx, &task, EVTHR_EV_LI,
			fd, NULL, qm_ctl_li, (void *) qmgr_ctx);
		if (sm_is_err(ret))
			goto error;
		qmgr_ctx->qmgr_ctllfd = fd;
	}

	fd = INVALID_SOCKET;

	/* simple loop: count seconds, sleep(1) is used */
	for (u = 0;
	     u < qmgr_ctx->qmgr_cnf.q_cnf_wait4srv && !is_valid_socket(fd);
	     u++)
	{
#if SMAR_TCP_NET
		ret = net_client_connect(smarip, smarport, &fd);
#else
		ret = unix_client_connect(qmgr_ctx->qmgr_cnf.q_cnf_smarsock_abs, &fd);
#endif
		if (!is_valid_socket(fd))
			sleep(1);
		else
			break;
	}
	if (!is_valid_socket(fd)) {
		sm_io_fprintf(smioerr,
			"sev=ERROR, func=qm_stli, socket=%s, unix_client_connect=%m\n",
			qmgr_ctx->qmgr_cnf.q_cnf_smarsock_abs, ret);
		goto error;
	}

	qmgr_ctx->qmgr_ar_fd = fd;

	ret = sm_rcb_open_rcv(qmgr_ctx->qmgr_ar_ctx->qar_com.rcbcom_rdrcb);
	if (sm_is_err(ret)) {
		sm_io_fprintf(smioerr,
			"sev=ERROR, func=qm_stli, sm_rcb_open_rcv=%m\n",
			ret);
		goto error;
	}

	ret = sm_fd_nonblock(fd, true);
	if (sm_is_err(ret))
		goto error;	/* XXX COMPLAIN */

	/* start a task for the new AR */
	ret = evthr_task_new(qmgr_ctx->qmgr_ev_ctx, &qmgr_ctx->qmgr_ar_tsk,
				EVTHR_EV_RD, fd, NULL, qmgr_ar, qmgr_ctx->qmgr_ar_ctx);
	if (sm_is_err(ret)) {
		sm_io_fprintf(smioerr,
			"sev=ERROR, func=qm_stli, evthr_task_new=%m\n",
			ret);
		goto error;
	}
	SM_IS_EVTHR_TSK(qmgr_ctx->qmgr_ar_tsk);
	qmgr_ctx->qmgr_ar_ctx->qar_com.rcbcom_tsk = qmgr_ctx->qmgr_ar_tsk;

	if (sm_is_err(ret))
		goto error;

	QM_LEV_DPRINTFC(QDC_START, 6, (QM_DEBFP, "sev=DBG, func=qm_stli, status=OK\n"));
	qmgr_ctx->qmgr_st_time = evthr_time(qmgr_ctx->qmgr_ev_ctx);
	return SM_SUCCESS;

  error:
	CLOSE_FD(qmgr_ctx->qmgr_ss_li.qm_gli_lfd);
	CLOSE_FD(qmgr_ctx->qmgr_sc_li.qm_gli_lfd);
	CLOSE_FD(qmgr_ctx->qmgr_ar_fd);
	return ret;
}

/*
**  QM_REOPEN_MAPS -- reopen QMGR maps
**
**	Parameters:
**		qmgr_ctx -- QMGR context
**
**	Returns:
**		usual sm_error code
*/

sm_ret_T
qm_reopen_maps(qmgr_ctx_P qmgr_ctx)
{
	sm_ret_T ret;

	SM_IS_QMGR_CTX(qmgr_ctx);

	/* reload map(s) */
	ret = SM_SUCCESS;
	if (qmgr_ctx->qmgr_conf_map != NULL) {
		ret = sm_map_reopen(qmgr_ctx->qmgr_conf_map, 0, SMPO_END);
		if (sm_is_err(ret)) {
			/* qmgr_ctx->qmgr_conf_map = NULL; */
			sm_log_write(qmgr_ctx->qmgr_lctx,
				QM_LCAT_CONTROL, QM_LMOD_SCHED,
				SM_LOG_ERR, 2,
				"sev=ERROR, func=qm_reopen_maps, sm_map_reopen=%m",
				ret);
		}
	}
	return SM_SUCCESS;
}

/*
**  QM_INFO -- print current usage and configuration
**
**	Parameters:
**		qmgr_ctx -- QMGR context
**		fp -- file pointer for output
**
**	Returns:
**		usual sm_error code
*/

sm_ret_T
qm_info(qmgr_ctx_P qmgr_ctx, sm_file_T *fp)
{
	int r;
	uint32_t j;
	uint u;
	struct rusage rusage;
#if SM_BHT_PERF
	char out[1024];
#endif

	SM_IS_QMGR_CTX(qmgr_ctx);

	if (qmgr_ctx->qmgr_ev_ctx != NULL) {
		time_T now;

		now = evthr_time(qmgr_ctx->qmgr_ev_ctx);
		sm_io_fprintf(fp, "[");
		(void) sm_timestamp(now, fp);
		sm_io_fprintf(fp, "] sev=INFO, where=qmgr_usr1\n");
	}
#if SM_HEAP_CHECK
	sm_io_fprintf(fp, "sev=INFO, func=qm_info\n");
	if (HEAP_CHECK)
		sm_heap_report(fp, 3);
#endif

	/* configuration */
	qmgr_prt_cnf(&qmgr_ctx->qmgr_cnf, fp, true);

	/* print only active entries? */
	for (j = 1, r = 0; r < QM_N_SS_GLI(qmgr_ctx); r++, j *= 2)
	{
		qss_ctx_P qss_ctx;

		if (0 == (qmgr_ctx->qmgr_ss_li.qm_gli_used & j) ||
		    (qss_ctx = qmgr_li_ss(qmgr_ctx, r)) == NULL)
			continue;
		sm_io_fprintf(fp,
			"qss_max_thrs     [%2d] =%7u\n"
			"qss_max_cur_thrds[%2d] =%7u\n"
			"qss_cur_session  [%2d] =%7u\n"
			, r, qss_ctx->qss_max_thrs
			, r, qss_ctx->qss_max_cur_thrs
			, r, qss_ctx->qss_cur_session
			);
	}

	for (r = 0; r < QM_N_SC_GLI(qmgr_ctx); r++)
	{
		qsc_ctx_P qsc_ctx;

		if ((qsc_ctx = qmgr_li_sc(qmgr_ctx, r)) == NULL ||
		    qsc_ctx->qsc_dadb_ctx == NULL)
			continue;
#if 0
		sm_io_fprintf(fp, "qsc_curactive   [%2d] =%7u\n",
			r, qmgr_ctx->qmgr_scctx[r]->qsc_curactive);
		sm_io_fprintf(fp, "qsc_maxthreads  [%2d] =%7u\n",
			r, qmgr_ctx->qmgr_scctx[r]->qsc_maxthreads);
#else /* 0 */
		sm_io_fprintf(fp, "dadb_entries_cur [%2d] =%7u\n", r,
			qsc_ctx->qsc_dadb_ctx->dadb_entries_cur);
		sm_io_fprintf(fp, "dadb_entries_idle[%2d] =%7u\n", r,
			qsc_ctx->qsc_dadb_ctx->dadb_entries_idle);
		sm_io_fprintf(fp, "dadb_entries_lim [%2d] =%7u\n", r,
			qsc_ctx->qsc_dadb_ctx->dadb_entries_lim);
		sm_io_fprintf(fp, "dadb_entries_max [%2d] =%7u\n", r,
			qsc_ctx->qsc_dadb_ctx->dadb_entries_max);
#if DADB_STATS
		sm_io_fprintf(fp, "dadb_entries_used[%2d] =%7u\n", r,
			qsc_ctx->qsc_dadb_ctx->dadb_entries_max_used);
#endif
#endif /* 0 */
	}

#if QMGR_STATS
	sm_io_fprintf(fp, "Transactions received=%7lu\n",
		qmgr_ctx->qmgr_tas_rcvd);
	sm_io_fprintf(fp, "Recipients received  =%7lu\n",
		qmgr_ctx->qmgr_rcpts_rcvd);
	sm_io_fprintf(fp, "Transactions sent    =%7lu\n",
		qmgr_ctx->qmgr_tas_sent);
	sm_io_fprintf(fp, "Recipients sent      =%7lu\n",
		qmgr_ctx->qmgr_rcpts_sent);
#endif /* QMGR_STATS */

	sm_io_fprintf(fp, "IQDB usage           =%7u %%\n",
		iqdb_usage(qmgr_ctx->qmgr_iqdb));
	sm_io_fprintf(fp, "IQDB current         =%7u\n",
		iqdb_entries(qmgr_ctx->qmgr_iqdb, &u));
	sm_io_fprintf(fp, "IQDB max_used        =%7u\n", u);

	sm_io_fprintf(fp, "AQ usage (all)       =%7u %%\n",
		aq_usage(qmgr_ctx->qmgr_aq, AQ_USAGE_ALL));
	sm_io_fprintf(fp, "AQ usage (defedb)    =%7u %%\n",
		aq_usage(qmgr_ctx->qmgr_aq, AQ_USAGE_DEFEDB));
	sm_io_fprintf(fp, "AQ current           =%7u\n",
		qmgr_ctx->qmgr_aq->aq_entries);
#if AQ_STATS
	sm_io_fprintf(fp, "AQ waitq current     =%7u\n",
		qmgr_ctx->qmgr_aq->aq_waitq_entries);
	sm_io_fprintf(fp, "AQ waitq max_used    =%7u\n",
		qmgr_ctx->qmgr_aq->aq_waitq_max);
#endif
	sm_io_fprintf(fp, "AQ rdq max_used      =%7u\n",
		qmgr_ctx->qmgr_aq->aq_rdq_used_max);
	sm_io_fprintf(fp, "EDBC current         =%7u\n",
		qmgr_ctx->qmgr_edbc->edbc_entries);

	sm_io_fprintf(fp, "IBDB kbfree          =%7lu\n",
		qmgr_ctx->qmgr_ibdb_kbfree);
	sm_io_fprintf(fp, "CDB kbfree           =%7lu\n",
		qmgr_ctx->qmgr_cdb_kbfree);
	sm_io_fprintf(fp, "EDB kbfree           =%7lu\n",
		qmgr_ctx->qmgr_edb_kbfree);

	sm_io_fprintf(fp, "qmgr_rflags          =%#08x\n",
		qmgr_ctx->qmgr_rflags);
	sm_io_fprintf(fp, "qmgr_sflags          =%#08x\n",
		qmgr_ctx->qmgr_sflags);
	sm_io_fprintf(fp, "qmgr_total_usage     =%3u %%\n",
		qmgr_ctx->qmgr_total_usage);

	for (u = 0; u <= QMGR_RFL_LAST_I; u++)
	{
		sm_io_fprintf(fp, "qmgr_usage[%d]    =%3u %%\n",
			u, qmgr_ctx->qmgr_usage[u]);
#if QMGR_STATS
		sm_io_fprintf(fp, "qmgr_max_use[%d]  =%3u %%\n",
			u, qmgr_ctx->qmgr_max_use[u]);
#endif
	}

	(void) ibdb_stats(qmgr_ctx->qmgr_ibdb, fp);
	(void) ibdbc_show_seq(qmgr_ctx->qmgr_ibdb, THR_LOCK_UNLOCK, fp);

	(void) edb_status(qmgr_ctx->qmgr_edb, fp);

#if 0 && SM_BHT_PERF
	/* which table(s)? */
	sm_bht_stats(table, out, sizeof(out));
#endif

	(void) occ_print(qmgr_ctx->qmgr_occ_ctx, fp, THR_LOCK_UNLOCK);

	r = getrusage(RUSAGE_SELF, &rusage);
	if (0 == r) {
		sm_io_fprintf(fp,
			"ru_utime=   %7ld.%07ld s\n"
			"ru_stime=   %7ld.%07ld s\n"
			"ru_maxrss=  %7ld\n"
			"ru_ixrss=   %7ld\n"
			"ru_idrss=   %7ld\n"
			"ru_isrss=   %7ld\n"
			"ru_minflt=  %7ld\n"
			"ru_majflt=  %7ld\n"
			"ru_nswap=   %7ld\n"
			"ru_inblock= %7ld\n"
			"ru_oublock= %7ld\n"
			"ru_msgsnd=  %7ld\n"
			"ru_msgrcv=  %7ld\n"
			"ru_nsignals=%7ld\n"
			"ru_nvcsw=   %7ld\n"
			"ru_nivcsw=  %7ld\n"
			, rusage.ru_utime.tv_sec
			, rusage.ru_utime.tv_usec
			, rusage.ru_stime.tv_sec
			, rusage.ru_stime.tv_usec
			, rusage.ru_maxrss
			, rusage.ru_ixrss
			, rusage.ru_idrss
			, rusage.ru_isrss
			, rusage.ru_minflt
			, rusage.ru_majflt
			, rusage.ru_nswap
			, rusage.ru_inblock
			, rusage.ru_oublock
			, rusage.ru_msgsnd
			, rusage.ru_msgrcv
			, rusage.ru_nsignals
			, rusage.ru_nvcsw
			, rusage.ru_nivcsw
			);
	}
	(void) sm_io_flush(fp);

	return SM_SUCCESS;
}

/*
**  QM_USR1 -- task handler for SIGUSR1
**	print current usage and configuration
**
**	Parameters:
**		tsk -- evthr task
**
**	Returns:
**		EVTHR_WAITQ
*/

static sm_ret_T
qm_usr1(sm_evthr_task_P tsk)
{
	qmgr_ctx_P qmgr_ctx;

	SM_IS_EVTHR_TSK(tsk);
	qmgr_ctx = (qmgr_ctx_P) tsk->evthr_t_actx;
	SM_IS_QMGR_CTX(qmgr_ctx);

	(void) qm_reopen_maps(qmgr_ctx);
	(void) qm_info(qmgr_ctx, smioout);
	return EVTHR_WAITQ;
}

/*
**  QM_USR2 -- task handler for SIGUSR2
**	reopen logfiles (useful for log rotation right now, should be
**	handled differently later, e.g., see isc log module)
**
**	Parameters:
**		tsk -- evthr task
**
**	Returns:
**		EVTHR_WAITQ
*/

static sm_ret_T
qm_usr2(sm_evthr_task_P tsk)
{
	qmgr_ctx_P qmgr_ctx;

	SM_IS_EVTHR_TSK(tsk);
	qmgr_ctx = (qmgr_ctx_P) tsk->evthr_t_actx;
	SM_IS_QMGR_CTX(qmgr_ctx);
	(void) sm_log_reopen(qmgr_ctx->qmgr_lctx);

	return EVTHR_WAITQ;
}

/*
**  QMGR_START -- start QMGR
**
**	Parameters:
**		qmgr_ctx -- QMGR context
**
**	Returns:
**		usual sm_error code
**
**	Last code review: 2003-10-24 17:29:23
*/

sm_ret_T
qmgr_start(qmgr_ctx_P qmgr_ctx)
{
	sm_ret_T ret;
	timeval_T now, sleept, delay;
	sm_evthr_task_P task;
	ibdb_rcpt_T ibdb_rcpt;
	sessta_id_T ta_id;

	SM_IS_QMGR_CTX(qmgr_ctx);

	sm_memzero(&ibdb_rcpt, sizeof(ibdb_rcpt));
	sm_memzero(&ta_id, sizeof(ta_id));
	ret = ssocc_open(&qmgr_ctx->qmgr_ssocc_ctx,
			qmgr_ctx->qmgr_cnf.q_cnf_iqdb_htsize,
			qmgr_ctx->qmgr_cnf.q_cnf_ss_cc_size);
	if (sm_is_err(ret)) {
		sm_log_write(qmgr_ctx->qmgr_lctx,
			QM_LCAT_INIT, QM_LMOD_START,
			SM_LOG_ERR, 2,
			"sev=ERROR, func=qmgr_start, ssocc_open=%m", ret);
		goto error;
	}

	ret = occ_open(&qmgr_ctx->qmgr_occ_ctx, qmgr_ctx->qmgr_cnf.q_cnf_occ_size);
	if (sm_is_err(ret)) {
		sm_log_write(qmgr_ctx->qmgr_lctx,
			QM_LCAT_INIT, QM_LMOD_START,
			SM_LOG_ERR, 2,
			"sev=ERROR, func=qmgr_start, occ_open=%m", ret);
		goto error;
	}

	ret = evthr_timeval(qmgr_ctx->qmgr_ev_ctx, &now);
	if (sm_is_err(ret)) {
		sm_log_write(qmgr_ctx->qmgr_lctx,
			QM_LCAT_INIT, QM_LMOD_START,
			SM_LOG_ERR, 2,
			"sev=ERROR, func=qmgr_start, evthr_timeval=%m", ret);
		goto error;
	}

	/* XXX is it ok to run this that early? */
	ret = qm_edb_scan(qmgr_ctx);
	if (sm_is_err(ret)) {
		sm_log_write(qmgr_ctx->qmgr_lctx,
			QM_LCAT_INIT, QM_LMOD_START,
			SM_LOG_ERR, 2,
			"sev=ERROR, func=qmgr_start, qm_edb_scan=%m", ret);
		goto error;
	}

	/*
	**  Add a dummy record to IBDB; must be added/removed otherwise IBDB
	**  will complain.
	**  The dummy record is there to keep track of the latest id counter
	**  for SMTPS in case QMGR is restarted without receiving a new
	**  transaction.
	**  Note: there's a race condition: IBDB is removed in qmgr_init()
	**  but a new entry is only written here, so the counter can get
	**  "lost" if the system stops between those two functions.
	**  However, the startup code checks also DEFEDB for existing
	**  entries, hence the counter should be at least bigger than
	**  any counter from a previous run unless there is no data
	**  in either EDB in which case it is possible that IDs are
	**  reused which may screw up logfile analyzers...
	**  The race condition could be closed if another entry is written
	**  before the old IBDB is removed.
	*/

	sm_snprintf(ta_id, sizeof(ta_id), SMTPS_STID_FORMAT, qmgr_ctx->qmgr_idc, 0);
	ibdb_rcpt.ibr_ta_id = ta_id;
	ibdb_rcpt.ibr_idx = 0;
	ibdb_rcpt.ibr_pa = qmgr_ctx->qmgr_pm_addr;
	ret = ibdb_rcpt_status(qmgr_ctx->qmgr_ibdb, &ibdb_rcpt, IBDB_RCPT_NEW,
			IBDB_FL_NONE, THR_LOCK_UNLOCK);
	if (sm_is_err(ret)) {
		sm_log_write(qmgr_ctx->qmgr_lctx,
			QM_LCAT_INIT, QM_LMOD_START,
			SM_LOG_ERR, 2,
			"sev=ERROR, func=qmgr_start, ibdb_rcpt_status1=%m",
			ret);
		goto error;
	}
	ret = ibdb_rcpt_status(qmgr_ctx->qmgr_ibdb, &ibdb_rcpt, IBDB_RCPT_DONE,
			IBDB_FL_NONE, THR_LOCK_UNLOCK);
	if (sm_is_err(ret)) {
		sm_log_write(qmgr_ctx->qmgr_lctx,
			QM_LCAT_INIT, QM_LMOD_START,
			SM_LOG_ERR, 2,
			"sev=ERROR, func=qmgr_start, ibdb_rcpt_status2=%m",
			ret);
		goto error;
	}
	ret = ibdb_commit(qmgr_ctx->qmgr_ibdb);
	if (sm_is_err(ret)) {
		sm_log_write(qmgr_ctx->qmgr_lctx,
			QM_LCAT_INIT, QM_LMOD_START,
			SM_LOG_ERR, 2,
			"sev=ERROR, func=qmgr_start, ibdb_commit=%m",
			ret);
		goto error;
	}

	NOTE(COMPETING_THREADS_NOW)
	ret = qm_stli(qmgr_ctx);
	if (sm_is_err(ret)) {
		sm_log_write(qmgr_ctx->qmgr_lctx,
			QM_LCAT_INIT, QM_LMOD_START,
			SM_LOG_ERR, 2,
			"sev=ERROR, func=qmgr_start, qm_stli=%m", ret);
		goto error;
	}

	/*
	**  XXX Notice: on a quiet system it is not a good idea to have the
	**  commit thread wake up every second just to figure out that
	**  it got nothing to do, see also qmgr_ibdb_commit.
	*/

	/* initial timeout: 1s */
	sleept = now;
	sleept.tv_sec++;
	ret = evthr_task_new(qmgr_ctx->qmgr_ev_ctx, &qmgr_ctx->qmgr_icommit,
		EVTHR_EV_SL, INVALID_FD, &sleept, qmgr_ibdb_commit, qmgr_ctx);
	if (sm_is_err(ret)) {
		sm_log_write(qmgr_ctx->qmgr_lctx,
			QM_LCAT_INIT, QM_LMOD_START,
			SM_LOG_ERR, 2,
			"sev=ERROR, func=qmgr_start, start_qmgr_ibdb_commit=%m", ret);
		goto error;
	}

	sleept = now;

	/* initial timeout: 10s */
	delay.tv_usec = 0;
	delay.tv_sec = 10;
	timeradd(&now, &delay, &(sleept));
	ret = evthr_task_new(qmgr_ctx->qmgr_ev_ctx, &qmgr_ctx->qmgr_sched,
		EVTHR_EV_SL, INVALID_FD, &sleept, qmgr_sched, qmgr_ctx);
	if (sm_is_err(ret)) {
		sm_log_write(qmgr_ctx->qmgr_lctx,
			QM_LCAT_INIT, QM_LMOD_START,
			SM_LOG_ERR, 2,
			"sev=ERROR, func=qmgr_start, start_qmgr_sched=%m", ret);
		goto error;
	}

	sleept = now;
	sleept.tv_sec += 60;
	ret = qcleanup_ctx_new(qmgr_ctx, &qmgr_ctx->qmgr_cleanup_ctx);
	if (sm_is_err(ret)) {
		sm_log_write(qmgr_ctx->qmgr_lctx,
			QM_LCAT_INIT, QM_LMOD_START,
			SM_LOG_ERR, 2,
			"sev=ERROR, func=qmgr_start, qcleanup_ctx_new=%m", ret);
		goto error;
	}

	ret = evthr_task_new(qmgr_ctx->qmgr_ev_ctx,
		&qmgr_ctx->qmgr_tsk_cleanup, EVTHR_EV_SL, INVALID_FD,
		&sleept, qmgr_cleanup, qmgr_ctx->qmgr_cleanup_ctx);
	if (sm_is_err(ret)) {
		sm_log_write(qmgr_ctx->qmgr_lctx,
			QM_LCAT_INIT, QM_LMOD_START,
			SM_LOG_ERR, 2,
			"sev=ERROR, func=qmgr_start, start_qmgr_cleanup=%m", ret);
		goto error;
	}

	ret = evthr_task_new(qmgr_ctx->qmgr_ev_ctx, &task, EVTHR_EV_SG,
			SIGUSR1, NULL, qm_usr1, (void *) qmgr_ctx);
	if (sm_is_err(ret)) {
		sm_log_write(qmgr_ctx->qmgr_lctx,
			QM_LCAT_INIT, QM_LMOD_START,
			SM_LOG_ERR, 2,
			"sev=ERROR, func=qmgr_start, start_sm_qm_usr1=%m", ret);
		goto error;
	}

	ret = evthr_task_new(qmgr_ctx->qmgr_ev_ctx, &task, EVTHR_EV_SG,
			SIGUSR2, NULL, qm_usr2, (void *) qmgr_ctx);
	if (sm_is_err(ret)) {
		sm_log_write(qmgr_ctx->qmgr_lctx,
			QM_LCAT_INIT, QM_LMOD_START,
			SM_LOG_ERR, 2,
			"sev=ERROR, func=qmgr_start, start_sm_qm_usr2=%m", ret);
		goto error;
	}

#if QMGR_TEST
	delay.tv_usec = 0;
	delay.tv_sec = 1;
	timeradd(&now, &delay, &sleept);
	(void) evthr_new_sl(qmgr_ctx->qmgr_sched, sleept, false);
#endif /* QMGR_TEST */


	qmgr_ctx->qmgr_status = QMGR_ST_START;
	return SM_SUCCESS;

  error:
	/*
	**  cleanup? tasks may have been started, but QMGR will terminate
	**  due to the errors.
	*/

	return ret;
}


syntax highlighted by Code2HTML, v. 0.9.1