1/*	$NetBSD: recvbuff.c,v 1.9 2022/10/09 21:41:03 christos Exp $	*/
2
3#ifdef HAVE_CONFIG_H
4# include <config.h>
5#endif
6
7#include <stdio.h>
8
9#include "ntp_assert.h"
10#include "ntp_syslog.h"
11#include "ntp_stdlib.h"
12#include "ntp_lists.h"
13#include "recvbuff.h"
14#include "iosignal.h"
15
16#if (RECV_INC & (RECV_INC-1))
17# error RECV_INC not a power of 2!
18#endif
19#if (RECV_BATCH & (RECV_BATCH - 1))
20#error RECV_BATCH not a power of 2!
21#endif
22#if (RECV_BATCH < RECV_INC)
23#error RECV_BATCH must be >= RECV_INC!
24#endif
25
26/*
27 * Memory allocation
28 */
29static u_long volatile full_recvbufs;	/* recvbufs on full_recv_fifo */
30static u_long volatile free_recvbufs;	/* recvbufs on free_recv_list */
31static u_long volatile total_recvbufs;	/* total recvbufs currently in use */
32static u_long volatile lowater_adds;	/* number of times we have added memory */
33static u_long volatile buffer_shortfall;/* number of missed free receive buffers
34					   between replenishments */
35static u_long limit_recvbufs;		/* maximum total of receive buffers */
36static u_long emerg_recvbufs;		/* emergency/urgent buffers to keep */
37
38static DECL_FIFO_ANCHOR(recvbuf_t) full_recv_fifo;
39static recvbuf_t *		   free_recv_list;
40
41#if defined(SYS_WINNT)
42
43/*
44 * For Windows we need to set up a lock to manipulate the
45 * recv buffers to prevent corruption. We keep it lock for as
46 * short a time as possible
47 */
48static CRITICAL_SECTION RecvLock;
49static CRITICAL_SECTION FreeLock;
50# define LOCK_R()	EnterCriticalSection(&RecvLock)
51# define UNLOCK_R()	LeaveCriticalSection(&RecvLock)
52# define LOCK_F()	EnterCriticalSection(&FreeLock)
53# define UNLOCK_F()	LeaveCriticalSection(&FreeLock)
54#else
55# define LOCK_R()	do {} while (FALSE)
56# define UNLOCK_R()	do {} while (FALSE)
57# define LOCK_F()	do {} while (FALSE)
58# define UNLOCK_F()	do {} while (FALSE)
59#endif
60
61#ifdef DEBUG
62static void uninit_recvbuff(void);
63#endif
64
65
66u_long
67free_recvbuffs (void)
68{
69	return free_recvbufs;
70}
71
72u_long
73full_recvbuffs (void)
74{
75	return full_recvbufs;
76}
77
78u_long
79total_recvbuffs (void)
80{
81	return total_recvbufs;
82}
83
84u_long
85lowater_additions(void)
86{
87	return lowater_adds;
88}
89
90static inline void
91initialise_buffer(recvbuf_t *buff)
92{
93	ZERO(*buff);
94}
95
96static void
97create_buffers(
98	size_t		nbufs)
99{
100#   ifndef DEBUG
101	static const u_int chunk = RECV_INC;
102#   else
103	/* Allocate each buffer individually so they can be free()d
104	 * during ntpd shutdown on DEBUG builds to keep them out of heap
105	 * leak reports.
106	 */
107	static const u_int chunk = 1;
108#   endif
109
110	register recvbuf_t *bufp;
111	u_int i;
112	size_t abuf;
113
114	if (limit_recvbufs <= total_recvbufs)
115		return;
116
117	abuf = nbufs + buffer_shortfall;
118	buffer_shortfall = 0;
119
120	if (abuf < nbufs || abuf > RECV_BATCH)
121		abuf = RECV_BATCH;	/* clamp on overflow */
122	else
123		abuf += (~abuf + 1) & (RECV_INC - 1);	/* round up */
124
125	if (abuf > (limit_recvbufs - total_recvbufs))
126		abuf = limit_recvbufs - total_recvbufs;
127	abuf += (~abuf + 1) & (chunk - 1);		/* round up */
128
129	while (abuf) {
130		bufp = calloc(chunk, sizeof(*bufp));
131		if (!bufp) {
132			limit_recvbufs = total_recvbufs;
133			break;
134		}
135		for (i = chunk; i; --i,++bufp) {
136			LINK_SLIST(free_recv_list, bufp, link);
137		}
138		free_recvbufs += chunk;
139		total_recvbufs += chunk;
140		abuf -= chunk;
141	}
142	++lowater_adds;
143}
144
145void
146init_recvbuff(int nbufs)
147{
148
149	/*
150	 * Init buffer free list and stat counters
151	 */
152	free_recvbufs = total_recvbufs = 0;
153	full_recvbufs = lowater_adds = 0;
154
155	limit_recvbufs = RECV_TOOMANY;
156	emerg_recvbufs = RECV_CLOCK;
157
158	create_buffers(nbufs);
159
160#   if defined(SYS_WINNT)
161	InitializeCriticalSection(&RecvLock);
162	InitializeCriticalSection(&FreeLock);
163#   endif
164
165#   ifdef DEBUG
166	atexit(&uninit_recvbuff);
167#   endif
168}
169
170
171#ifdef DEBUG
172static void
173uninit_recvbuff(void)
174{
175	recvbuf_t *rbunlinked;
176
177	for (;;) {
178		UNLINK_FIFO(rbunlinked, full_recv_fifo, link);
179		if (rbunlinked == NULL)
180			break;
181		free(rbunlinked);
182	}
183
184	for (;;) {
185		UNLINK_HEAD_SLIST(rbunlinked, free_recv_list, link);
186		if (rbunlinked == NULL)
187			break;
188		free(rbunlinked);
189	}
190#   if defined(SYS_WINNT)
191	DeleteCriticalSection(&FreeLock);
192	DeleteCriticalSection(&RecvLock);
193#   endif
194}
195#endif	/* DEBUG */
196
197
198/*
199 * freerecvbuf - make a single recvbuf available for reuse
200 */
201void
202freerecvbuf(recvbuf_t *rb)
203{
204	if (rb) {
205		if (--rb->used != 0) {
206			msyslog(LOG_ERR, "******** freerecvbuff non-zero usage: %d *******", rb->used);
207			rb->used = 0;
208		}
209		LOCK_F();
210		LINK_SLIST(free_recv_list, rb, link);
211		++free_recvbufs;
212		UNLOCK_F();
213	}
214}
215
216
217void
218add_full_recv_buffer(recvbuf_t *rb)
219{
220	if (rb == NULL) {
221		msyslog(LOG_ERR, "add_full_recv_buffer received NULL buffer");
222		return;
223	}
224	LOCK_R();
225	LINK_FIFO(full_recv_fifo, rb, link);
226	++full_recvbufs;
227	UNLOCK_R();
228}
229
230
231recvbuf_t *
232get_free_recv_buffer(
233    int /*BOOL*/ urgent
234    )
235{
236	recvbuf_t *buffer = NULL;
237
238	LOCK_F();
239	if (free_recvbufs > (urgent ? emerg_recvbufs : 0)) {
240		UNLINK_HEAD_SLIST(buffer, free_recv_list, link);
241	}
242
243	if (buffer != NULL) {
244		if (free_recvbufs)
245			--free_recvbufs;
246		initialise_buffer(buffer);
247		++buffer->used;
248	} else {
249		++buffer_shortfall;
250	}
251	UNLOCK_F();
252
253	return buffer;
254}
255
256
257#ifdef HAVE_IO_COMPLETION_PORT
258recvbuf_t *
259get_free_recv_buffer_alloc(
260    int /*BOOL*/ urgent
261    )
262{
263	LOCK_F();
264	if (free_recvbufs <= emerg_recvbufs || buffer_shortfall > 0)
265		create_buffers(RECV_INC);
266	UNLOCK_F();
267	return get_free_recv_buffer(urgent);
268}
269#endif
270
271
272recvbuf_t *
273get_full_recv_buffer(void)
274{
275	recvbuf_t *	rbuf;
276
277	/*
278	 * make sure there are free buffers when we wander off to do
279	 * lengthy packet processing with any buffer we grab from the
280	 * full list.
281	 *
282	 * fixes malloc() interrupted by SIGIO risk (Bug 889)
283	 */
284	LOCK_F();
285	if (free_recvbufs <= emerg_recvbufs || buffer_shortfall > 0)
286		create_buffers(RECV_INC);
287	UNLOCK_F();
288
289	/*
290	 * try to grab a full buffer
291	 */
292	LOCK_R();
293	UNLINK_FIFO(rbuf, full_recv_fifo, link);
294	if (rbuf != NULL && full_recvbufs)
295		--full_recvbufs;
296	UNLOCK_R();
297
298	return rbuf;
299}
300
301
302/*
303 * purge_recv_buffers_for_fd() - purges any previously-received input
304 *				 from a given file descriptor.
305 */
306void
307purge_recv_buffers_for_fd(
308	int	fd
309	)
310{
311	recvbuf_t *rbufp;
312	recvbuf_t *next;
313	recvbuf_t *punlinked;
314	recvbuf_t *freelist = NULL;
315
316	/* We want to hold only one lock at a time. So we do a scan on
317	 * the full buffer queue, collecting items as we go, and when
318	 * done we spool the the collected items to 'freerecvbuf()'.
319	 */
320	LOCK_R();
321
322	for (rbufp = HEAD_FIFO(full_recv_fifo);
323	     rbufp != NULL;
324	     rbufp = next)
325	{
326		next = rbufp->link;
327#	    ifdef HAVE_IO_COMPLETION_PORT
328		if (rbufp->dstadr == NULL && rbufp->fd == fd)
329#	    else
330		if (rbufp->fd == fd)
331#	    endif
332		{
333			UNLINK_MID_FIFO(punlinked, full_recv_fifo,
334					rbufp, link, recvbuf_t);
335			INSIST(punlinked == rbufp);
336			if (full_recvbufs)
337				--full_recvbufs;
338			rbufp->link = freelist;
339			freelist = rbufp;
340		}
341	}
342
343	UNLOCK_R();
344
345	while (freelist) {
346		next = freelist->link;
347		freerecvbuf(freelist);
348		freelist = next;
349	}
350}
351
352
353/*
354 * Checks to see if there are buffers to process
355 */
356isc_boolean_t has_full_recv_buffer(void)
357{
358	if (HEAD_FIFO(full_recv_fifo) != NULL)
359		return (ISC_TRUE);
360	else
361		return (ISC_FALSE);
362}
363
364
365#ifdef NTP_DEBUG_LISTS_H
366void
367check_gen_fifo_consistency(void *fifo)
368{
369	gen_fifo *pf;
370	gen_node *pthis;
371	gen_node **pptail;
372
373	pf = fifo;
374	REQUIRE((NULL == pf->phead && NULL == pf->pptail) ||
375		(NULL != pf->phead && NULL != pf->pptail));
376
377	pptail = &pf->phead;
378	for (pthis = pf->phead;
379	     pthis != NULL;
380	     pthis = pthis->link)
381		if (NULL != pthis->link)
382			pptail = &pthis->link;
383
384	REQUIRE(NULL == pf->pptail || pptail == pf->pptail);
385}
386#endif	/* NTP_DEBUG_LISTS_H */
387