1/*
2 *  Copyright (c) 2006 Proofpoint, Inc. and its suppliers.
3 *	All rights reserved.
4 *
5 * By using this file, you agree to the terms and conditions set
6 * forth in the LICENSE file which can be found at the top level of
7 * the sendmail distribution.
8 *
9 */
10
11#include <sm/gen.h>
12SM_RCSID("@(#)$Id: monitor.c,v 8.8 2013-11-22 20:51:36 ca Exp $")
13#include "libmilter.h"
14
15#if _FFR_THREAD_MONITOR
16
17/*
18**  Thread Monitoring
19**  Todo: more error checking (return code from function calls)
20**  add comments.
21*/
22
23bool Monitor = false; /* use monitoring? */
24static unsigned int Mon_exec_time = 0;
25
26/* mutex protects Mon_cur_ctx, Mon_ctx_head, and ctx_start */
27static smutex_t Mon_mutex;
28static scond_t Mon_cv;
29
30/*
31**  Current ctx to monitor.
32**  Invariant:
33**  Mon_cur_ctx == NULL || Mon_cur_ctx is thread which was started the longest
34**	time ago.
35**
36**  Basically the entries in the list are ordered by time because new
37**	entries are appended at the end. However, due to the concurrent
38**	execution (multi-threaded) and no guaranteed order of wakeups
39**	after a mutex_lock() attempt, the order might not be strict,
40**	i.e., if the list contains e1 and e2 (in that order) then
41**	the the start time of e2 can be (slightly) smaller than that of e1.
42**	However, this slight inaccuracy should not matter for the proper
43**	working of this algorithm.
44*/
45
46static SMFICTX_PTR Mon_cur_ctx = NULL;
47static smfi_hd_T Mon_ctx_head; /* head of the linked list of active contexts */
48
49/*
50**  SMFI_SET_MAX_EXEC_TIME -- set maximum execution time for a thread
51**
52**	Parameters:
53**		tm -- maximum execution time for a thread
54**
55**	Returns:
56**		MI_SUCCESS
57*/
58
59int
60smfi_set_max_exec_time(tm)
61	unsigned int tm;
62{
63	Mon_exec_time = tm;
64	return MI_SUCCESS;
65}
66
67/*
68**  MI_MONITOR_THREAD -- monitoring thread
69**
70**	Parameters:
71**		arg -- ignored (required by pthread_create())
72**
73**	Returns:
74**		NULL on termination.
75*/
76
77static void *
78mi_monitor_thread(arg)
79	void *arg;
80{
81	sthread_t tid;
82	int r;
83	time_t now, end;
84
85	SM_ASSERT(Monitor);
86	SM_ASSERT(Mon_exec_time > 0);
87	tid = (sthread_t) sthread_get_id();
88	if (pthread_detach(tid) != 0)
89	{
90		/* log an error */
91		return (void *)1;
92	}
93
94/*
95**  NOTE: this is "flow through" code,
96**  do NOT use do { } while ("break" is used here!)
97*/
98
99#define MON_CHK_STOP							\
100	now = time(NULL);						\
101	end = Mon_cur_ctx->ctx_start + Mon_exec_time;			\
102	if (now > end)							\
103	{								\
104		smi_log(SMI_LOG_ERR,					\
105			"WARNING: monitor timeout triggered, now=%ld, end=%ld, tid=%ld, state=0x%x",\
106			(long) now, (long) end,				\
107			(long) Mon_cur_ctx->ctx_id, Mon_cur_ctx->ctx_state);\
108		mi_stop_milters(MILTER_STOP);				\
109		break;							\
110	}
111
112	(void) smutex_lock(&Mon_mutex);
113	while (mi_stop() == MILTER_CONT)
114	{
115		if (Mon_cur_ctx != NULL && Mon_cur_ctx->ctx_start > 0)
116		{
117			struct timespec abstime;
118
119			MON_CHK_STOP;
120			abstime.tv_sec = end;
121			abstime.tv_nsec = 0;
122			r = pthread_cond_timedwait(&Mon_cv, &Mon_mutex,
123					&abstime);
124		}
125		else
126			r = pthread_cond_wait(&Mon_cv, &Mon_mutex);
127		if (mi_stop() != MILTER_CONT)
128			break;
129		if (Mon_cur_ctx != NULL && Mon_cur_ctx->ctx_start > 0)
130		{
131			MON_CHK_STOP;
132		}
133	}
134	(void) smutex_unlock(&Mon_mutex);
135
136	return NULL;
137}
138
139/*
140**  MI_MONITOR_INIT -- initialize monitoring thread
141**
142**	Parameters: none
143**
144**	Returns:
145**		MI_SUCCESS/MI_FAILURE
146*/
147
148int
149mi_monitor_init()
150{
151	int r;
152	sthread_t tid;
153
154	SM_ASSERT(!Monitor);
155	if (Mon_exec_time <= 0)
156		return MI_SUCCESS;
157	Monitor = true;
158	if (!smutex_init(&Mon_mutex))
159		return MI_FAILURE;
160	if (scond_init(&Mon_cv) != 0)
161		return MI_FAILURE;
162	SM_TAILQ_INIT(&Mon_ctx_head);
163
164	r = thread_create(&tid, mi_monitor_thread, (void *)NULL);
165	if (r != 0)
166		return r;
167	return MI_SUCCESS;
168}
169
170/*
171**  MI_MONITOR_WORK_BEGIN -- record start of thread execution
172**
173**	Parameters:
174**		ctx -- session context
175**		cmd -- milter command char
176**
177**	Returns:
178**		0
179*/
180
181int
182mi_monitor_work_begin(ctx, cmd)
183	SMFICTX_PTR ctx;
184	int cmd;
185{
186	(void) smutex_lock(&Mon_mutex);
187	if (NULL == Mon_cur_ctx)
188	{
189		Mon_cur_ctx = ctx;
190		(void) scond_signal(&Mon_cv);
191	}
192	ctx->ctx_start = time(NULL);
193	SM_TAILQ_INSERT_TAIL(&Mon_ctx_head, ctx, ctx_mon_link);
194	(void) smutex_unlock(&Mon_mutex);
195	return 0;
196}
197
198/*
199**  MI_MONITOR_WORK_END -- record end of thread execution
200**
201**	Parameters:
202**		ctx -- session context
203**		cmd -- milter command char
204**
205**	Returns:
206**		0
207*/
208
209int
210mi_monitor_work_end(ctx, cmd)
211	SMFICTX_PTR ctx;
212	int cmd;
213{
214	(void) smutex_lock(&Mon_mutex);
215	ctx->ctx_start = 0;
216	SM_TAILQ_REMOVE(&Mon_ctx_head, ctx, ctx_mon_link);
217	if (Mon_cur_ctx == ctx)
218	{
219		if (SM_TAILQ_EMPTY(&Mon_ctx_head))
220			Mon_cur_ctx = NULL;
221		else
222			Mon_cur_ctx = SM_TAILQ_FIRST(&Mon_ctx_head);
223	}
224	(void) smutex_unlock(&Mon_mutex);
225	return 0;
226}
227#endif /* _FFR_THREAD_MONITOR */
228