xref: /openbsd-src/usr.sbin/unbound/util/mini_event.c (revision e21c60efe88ccf825a55e871fba3e7017d017ae8)
1933707f3Ssthen /*
2933707f3Ssthen  * mini_event.c - implementation of part of libevent api, portably.
3933707f3Ssthen  *
4933707f3Ssthen  * Copyright (c) 2007, NLnet Labs. All rights reserved.
5933707f3Ssthen  *
6933707f3Ssthen  * This software is open source.
7933707f3Ssthen  *
8933707f3Ssthen  * Redistribution and use in source and binary forms, with or without
9933707f3Ssthen  * modification, are permitted provided that the following conditions
10933707f3Ssthen  * are met:
11933707f3Ssthen  *
12933707f3Ssthen  * Redistributions of source code must retain the above copyright notice,
13933707f3Ssthen  * this list of conditions and the following disclaimer.
14933707f3Ssthen  *
15933707f3Ssthen  * Redistributions in binary form must reproduce the above copyright notice,
16933707f3Ssthen  * this list of conditions and the following disclaimer in the documentation
17933707f3Ssthen  * and/or other materials provided with the distribution.
18933707f3Ssthen  *
19933707f3Ssthen  * Neither the name of the NLNET LABS nor the names of its contributors may
20933707f3Ssthen  * be used to endorse or promote products derived from this software without
21933707f3Ssthen  * specific prior written permission.
22933707f3Ssthen  *
23933707f3Ssthen  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
245d76a658Ssthen  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
255d76a658Ssthen  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
265d76a658Ssthen  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
275d76a658Ssthen  * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
285d76a658Ssthen  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
295d76a658Ssthen  * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
305d76a658Ssthen  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
315d76a658Ssthen  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
325d76a658Ssthen  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
335d76a658Ssthen  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34933707f3Ssthen  *
35933707f3Ssthen  */
36933707f3Ssthen 
37933707f3Ssthen /**
38933707f3Ssthen  * \file
39933707f3Ssthen  * fake libevent implementation. Less broad in functionality, and only
40933707f3Ssthen  * supports select(2).
41933707f3Ssthen  */
42933707f3Ssthen 
43933707f3Ssthen #include "config.h"
44f6b99bafSsthen #include "util/mini_event.h"
45933707f3Ssthen #ifdef HAVE_TIME_H
46933707f3Ssthen #include <time.h>
47933707f3Ssthen #endif
48933707f3Ssthen #include <sys/time.h>
49933707f3Ssthen 
50933707f3Ssthen #if defined(USE_MINI_EVENT) && !defined(USE_WINSOCK)
51933707f3Ssthen #include <signal.h>
52933707f3Ssthen #include "util/fptr_wlist.h"
53933707f3Ssthen 
54933707f3Ssthen /** compare events in tree, based on timevalue, ptr for uniqueness */
mini_ev_cmp(const void * a,const void * b)55933707f3Ssthen int mini_ev_cmp(const void* a, const void* b)
56933707f3Ssthen {
57933707f3Ssthen 	const struct event *e = (const struct event*)a;
58933707f3Ssthen 	const struct event *f = (const struct event*)b;
59933707f3Ssthen 	if(e->ev_timeout.tv_sec < f->ev_timeout.tv_sec)
60933707f3Ssthen 		return -1;
61933707f3Ssthen 	if(e->ev_timeout.tv_sec > f->ev_timeout.tv_sec)
62933707f3Ssthen 		return 1;
63933707f3Ssthen 	if(e->ev_timeout.tv_usec < f->ev_timeout.tv_usec)
64933707f3Ssthen 		return -1;
65933707f3Ssthen 	if(e->ev_timeout.tv_usec > f->ev_timeout.tv_usec)
66933707f3Ssthen 		return 1;
67933707f3Ssthen 	if(e < f)
68933707f3Ssthen 		return -1;
69933707f3Ssthen 	if(e > f)
70933707f3Ssthen 		return 1;
71933707f3Ssthen 	return 0;
72933707f3Ssthen }
73933707f3Ssthen 
74933707f3Ssthen /** set time */
75933707f3Ssthen static int
settime(struct event_base * base)76933707f3Ssthen settime(struct event_base* base)
77933707f3Ssthen {
78933707f3Ssthen 	if(gettimeofday(base->time_tv, NULL) < 0) {
79933707f3Ssthen 		return -1;
80933707f3Ssthen 	}
81933707f3Ssthen #ifndef S_SPLINT_S
82229e174cSsthen 	*base->time_secs = (time_t)base->time_tv->tv_sec;
83933707f3Ssthen #endif
84933707f3Ssthen 	return 0;
85933707f3Ssthen }
86933707f3Ssthen 
87933707f3Ssthen /** create event base */
event_init(time_t * time_secs,struct timeval * time_tv)88229e174cSsthen void *event_init(time_t* time_secs, struct timeval* time_tv)
89933707f3Ssthen {
90933707f3Ssthen 	struct event_base* base = (struct event_base*)malloc(
91933707f3Ssthen 		sizeof(struct event_base));
92933707f3Ssthen 	if(!base)
93933707f3Ssthen 		return NULL;
94933707f3Ssthen 	memset(base, 0, sizeof(*base));
95933707f3Ssthen 	base->time_secs = time_secs;
96933707f3Ssthen 	base->time_tv = time_tv;
97933707f3Ssthen 	if(settime(base) < 0) {
98933707f3Ssthen 		event_base_free(base);
99933707f3Ssthen 		return NULL;
100933707f3Ssthen 	}
101933707f3Ssthen 	base->times = rbtree_create(mini_ev_cmp);
102933707f3Ssthen 	if(!base->times) {
103933707f3Ssthen 		event_base_free(base);
104933707f3Ssthen 		return NULL;
105933707f3Ssthen 	}
106933707f3Ssthen 	base->capfd = MAX_FDS;
107933707f3Ssthen #ifdef FD_SETSIZE
108933707f3Ssthen 	if((int)FD_SETSIZE < base->capfd)
109933707f3Ssthen 		base->capfd = (int)FD_SETSIZE;
110933707f3Ssthen #endif
111933707f3Ssthen 	base->fds = (struct event**)calloc((size_t)base->capfd,
112933707f3Ssthen 		sizeof(struct event*));
113933707f3Ssthen 	if(!base->fds) {
114933707f3Ssthen 		event_base_free(base);
115933707f3Ssthen 		return NULL;
116933707f3Ssthen 	}
117933707f3Ssthen 	base->signals = (struct event**)calloc(MAX_SIG, sizeof(struct event*));
118933707f3Ssthen 	if(!base->signals) {
119933707f3Ssthen 		event_base_free(base);
120933707f3Ssthen 		return NULL;
121933707f3Ssthen 	}
122933707f3Ssthen #ifndef S_SPLINT_S
123933707f3Ssthen 	FD_ZERO(&base->reads);
124933707f3Ssthen 	FD_ZERO(&base->writes);
125933707f3Ssthen #endif
126933707f3Ssthen 	return base;
127933707f3Ssthen }
128933707f3Ssthen 
129933707f3Ssthen /** get version */
event_get_version(void)130933707f3Ssthen const char *event_get_version(void)
131933707f3Ssthen {
132933707f3Ssthen 	return "mini-event-"PACKAGE_VERSION;
133933707f3Ssthen }
134933707f3Ssthen 
135933707f3Ssthen /** get polling method, select */
event_get_method(void)136933707f3Ssthen const char *event_get_method(void)
137933707f3Ssthen {
138933707f3Ssthen 	return "select";
139933707f3Ssthen }
140933707f3Ssthen 
141933707f3Ssthen /** call timeouts handlers, and return how long to wait for next one or -1 */
handle_timeouts(struct event_base * base,struct timeval * now,struct timeval * wait)142933707f3Ssthen static void handle_timeouts(struct event_base* base, struct timeval* now,
143933707f3Ssthen 	struct timeval* wait)
144933707f3Ssthen {
145933707f3Ssthen 	struct event* p;
146933707f3Ssthen #ifndef S_SPLINT_S
147933707f3Ssthen 	wait->tv_sec = (time_t)-1;
148933707f3Ssthen #endif
149933707f3Ssthen 
15077079be7Ssthen 	while((rbnode_type*)(p = (struct event*)rbtree_first(base->times))
151933707f3Ssthen 		!=RBTREE_NULL) {
152933707f3Ssthen #ifndef S_SPLINT_S
153933707f3Ssthen 		if(p->ev_timeout.tv_sec > now->tv_sec ||
154933707f3Ssthen 			(p->ev_timeout.tv_sec==now->tv_sec &&
155933707f3Ssthen 		 	p->ev_timeout.tv_usec > now->tv_usec)) {
156933707f3Ssthen 			/* there is a next larger timeout. wait for it */
157933707f3Ssthen 			wait->tv_sec = p->ev_timeout.tv_sec - now->tv_sec;
158933707f3Ssthen 			if(now->tv_usec > p->ev_timeout.tv_usec) {
159933707f3Ssthen 				wait->tv_sec--;
160933707f3Ssthen 				wait->tv_usec = 1000000 - (now->tv_usec -
161933707f3Ssthen 					p->ev_timeout.tv_usec);
162933707f3Ssthen 			} else {
163933707f3Ssthen 				wait->tv_usec = p->ev_timeout.tv_usec
164933707f3Ssthen 					- now->tv_usec;
165933707f3Ssthen 			}
166933707f3Ssthen 			return;
167933707f3Ssthen 		}
168933707f3Ssthen #endif
169933707f3Ssthen 		/* event times out, remove it */
170933707f3Ssthen 		(void)rbtree_delete(base->times, p);
171933707f3Ssthen 		p->ev_events &= ~EV_TIMEOUT;
172933707f3Ssthen 		fptr_ok(fptr_whitelist_event(p->ev_callback));
173933707f3Ssthen 		(*p->ev_callback)(p->ev_fd, EV_TIMEOUT, p->ev_arg);
174933707f3Ssthen 	}
175933707f3Ssthen }
176933707f3Ssthen 
177933707f3Ssthen /** call select and callbacks for that */
handle_select(struct event_base * base,struct timeval * wait)178933707f3Ssthen static int handle_select(struct event_base* base, struct timeval* wait)
179933707f3Ssthen {
180933707f3Ssthen 	fd_set r, w;
181933707f3Ssthen 	int ret, i;
182933707f3Ssthen 
183933707f3Ssthen #ifndef S_SPLINT_S
184933707f3Ssthen 	if(wait->tv_sec==(time_t)-1)
185933707f3Ssthen 		wait = NULL;
186933707f3Ssthen #endif
187933707f3Ssthen 	memmove(&r, &base->reads, sizeof(fd_set));
188933707f3Ssthen 	memmove(&w, &base->writes, sizeof(fd_set));
189933707f3Ssthen 	memmove(&base->ready, &base->content, sizeof(fd_set));
190933707f3Ssthen 
191933707f3Ssthen 	if((ret = select(base->maxfd+1, &r, &w, NULL, wait)) == -1) {
192933707f3Ssthen 		ret = errno;
193933707f3Ssthen 		if(settime(base) < 0)
194933707f3Ssthen 			return -1;
195933707f3Ssthen 		errno = ret;
196933707f3Ssthen 		if(ret == EAGAIN || ret == EINTR)
197933707f3Ssthen 			return 0;
198933707f3Ssthen 		return -1;
199933707f3Ssthen 	}
200933707f3Ssthen 	if(settime(base) < 0)
201933707f3Ssthen 		return -1;
202933707f3Ssthen 
203933707f3Ssthen 	for(i=0; i<base->maxfd+1; i++) {
204933707f3Ssthen 		short bits = 0;
205933707f3Ssthen 		if(!base->fds[i] || !(FD_ISSET(i, &base->ready))) {
206933707f3Ssthen 			continue;
207933707f3Ssthen 		}
208933707f3Ssthen 		if(FD_ISSET(i, &r)) {
209933707f3Ssthen 			bits |= EV_READ;
210933707f3Ssthen 			ret--;
211933707f3Ssthen 		}
212933707f3Ssthen 		if(FD_ISSET(i, &w)) {
213933707f3Ssthen 			bits |= EV_WRITE;
214933707f3Ssthen 			ret--;
215933707f3Ssthen 		}
216933707f3Ssthen 		bits &= base->fds[i]->ev_events;
217933707f3Ssthen 		if(bits) {
218933707f3Ssthen 			fptr_ok(fptr_whitelist_event(
219933707f3Ssthen 				base->fds[i]->ev_callback));
220933707f3Ssthen 			(*base->fds[i]->ev_callback)(base->fds[i]->ev_fd,
221933707f3Ssthen 				bits, base->fds[i]->ev_arg);
222933707f3Ssthen 			if(ret==0)
223933707f3Ssthen 				break;
224933707f3Ssthen 		}
225933707f3Ssthen 	}
226933707f3Ssthen 	return 0;
227933707f3Ssthen }
228933707f3Ssthen 
229933707f3Ssthen /** run select in a loop */
event_base_dispatch(struct event_base * base)230933707f3Ssthen int event_base_dispatch(struct event_base* base)
231933707f3Ssthen {
232933707f3Ssthen 	struct timeval wait;
233933707f3Ssthen 	if(settime(base) < 0)
234933707f3Ssthen 		return -1;
235933707f3Ssthen 	while(!base->need_to_exit)
236933707f3Ssthen 	{
237933707f3Ssthen 		/* see if timeouts need handling */
238933707f3Ssthen 		handle_timeouts(base, base->time_tv, &wait);
239933707f3Ssthen 		if(base->need_to_exit)
240933707f3Ssthen 			return 0;
241933707f3Ssthen 		/* do select */
242933707f3Ssthen 		if(handle_select(base, &wait) < 0) {
243933707f3Ssthen 			if(base->need_to_exit)
244933707f3Ssthen 				return 0;
245933707f3Ssthen 			return -1;
246933707f3Ssthen 		}
247933707f3Ssthen 	}
248933707f3Ssthen 	return 0;
249933707f3Ssthen }
250933707f3Ssthen 
251933707f3Ssthen /** exit that loop */
event_base_loopexit(struct event_base * base,struct timeval * ATTR_UNUSED (tv))252933707f3Ssthen int event_base_loopexit(struct event_base* base,
253933707f3Ssthen 	struct timeval* ATTR_UNUSED(tv))
254933707f3Ssthen {
255933707f3Ssthen 	base->need_to_exit = 1;
256933707f3Ssthen 	return 0;
257933707f3Ssthen }
258933707f3Ssthen 
259933707f3Ssthen /* free event base, free events yourself */
event_base_free(struct event_base * base)260933707f3Ssthen void event_base_free(struct event_base* base)
261933707f3Ssthen {
262933707f3Ssthen 	if(!base)
263933707f3Ssthen 		return;
264933707f3Ssthen 	free(base->times);
265933707f3Ssthen 	free(base->fds);
266933707f3Ssthen 	free(base->signals);
267933707f3Ssthen 	free(base);
268933707f3Ssthen }
269933707f3Ssthen 
270933707f3Ssthen /** set content of event */
event_set(struct event * ev,int fd,short bits,void (* cb)(int,short,void *),void * arg)271933707f3Ssthen void event_set(struct event* ev, int fd, short bits,
272933707f3Ssthen 	void (*cb)(int, short, void *), void* arg)
273933707f3Ssthen {
274933707f3Ssthen 	ev->node.key = ev;
275933707f3Ssthen 	ev->ev_fd = fd;
276933707f3Ssthen 	ev->ev_events = bits;
277933707f3Ssthen 	ev->ev_callback = cb;
278933707f3Ssthen 	fptr_ok(fptr_whitelist_event(ev->ev_callback));
279933707f3Ssthen 	ev->ev_arg = arg;
280933707f3Ssthen 	ev->added = 0;
281933707f3Ssthen }
282933707f3Ssthen 
283933707f3Ssthen /* add event to a base */
event_base_set(struct event_base * base,struct event * ev)284933707f3Ssthen int event_base_set(struct event_base* base, struct event* ev)
285933707f3Ssthen {
286933707f3Ssthen 	ev->ev_base = base;
287933707f3Ssthen 	ev->added = 0;
288933707f3Ssthen 	return 0;
289933707f3Ssthen }
290933707f3Ssthen 
291933707f3Ssthen /* add event to make it active, you may not change it with event_set anymore */
event_add(struct event * ev,struct timeval * tv)292933707f3Ssthen int event_add(struct event* ev, struct timeval* tv)
293933707f3Ssthen {
294933707f3Ssthen 	if(ev->added)
295933707f3Ssthen 		event_del(ev);
296933707f3Ssthen 	if(ev->ev_fd != -1 && ev->ev_fd >= ev->ev_base->capfd)
297933707f3Ssthen 		return -1;
298933707f3Ssthen 	if( (ev->ev_events&(EV_READ|EV_WRITE)) && ev->ev_fd != -1) {
299933707f3Ssthen 		ev->ev_base->fds[ev->ev_fd] = ev;
300933707f3Ssthen 		if(ev->ev_events&EV_READ) {
301933707f3Ssthen 			FD_SET(FD_SET_T ev->ev_fd, &ev->ev_base->reads);
302933707f3Ssthen 		}
303933707f3Ssthen 		if(ev->ev_events&EV_WRITE) {
304933707f3Ssthen 			FD_SET(FD_SET_T ev->ev_fd, &ev->ev_base->writes);
305933707f3Ssthen 		}
306933707f3Ssthen 		FD_SET(FD_SET_T ev->ev_fd, &ev->ev_base->content);
307933707f3Ssthen 		FD_CLR(FD_SET_T ev->ev_fd, &ev->ev_base->ready);
308933707f3Ssthen 		if(ev->ev_fd > ev->ev_base->maxfd)
309933707f3Ssthen 			ev->ev_base->maxfd = ev->ev_fd;
310933707f3Ssthen 	}
311933707f3Ssthen 	if(tv && (ev->ev_events&EV_TIMEOUT)) {
312933707f3Ssthen #ifndef S_SPLINT_S
313933707f3Ssthen 		struct timeval *now = ev->ev_base->time_tv;
314933707f3Ssthen 		ev->ev_timeout.tv_sec = tv->tv_sec + now->tv_sec;
315933707f3Ssthen 		ev->ev_timeout.tv_usec = tv->tv_usec + now->tv_usec;
3168240c1b9Ssthen 		while(ev->ev_timeout.tv_usec >= 1000000) {
317933707f3Ssthen 			ev->ev_timeout.tv_usec -= 1000000;
318933707f3Ssthen 			ev->ev_timeout.tv_sec++;
319933707f3Ssthen 		}
320933707f3Ssthen #endif
321933707f3Ssthen 		(void)rbtree_insert(ev->ev_base->times, &ev->node);
322933707f3Ssthen 	}
323933707f3Ssthen 	ev->added = 1;
324933707f3Ssthen 	return 0;
325933707f3Ssthen }
326933707f3Ssthen 
327933707f3Ssthen /* remove event, you may change it again */
event_del(struct event * ev)328933707f3Ssthen int event_del(struct event* ev)
329933707f3Ssthen {
330933707f3Ssthen 	if(ev->ev_fd != -1 && ev->ev_fd >= ev->ev_base->capfd)
331933707f3Ssthen 		return -1;
332933707f3Ssthen 	if((ev->ev_events&EV_TIMEOUT))
333933707f3Ssthen 		(void)rbtree_delete(ev->ev_base->times, &ev->node);
334933707f3Ssthen 	if((ev->ev_events&(EV_READ|EV_WRITE)) && ev->ev_fd != -1) {
335933707f3Ssthen 		ev->ev_base->fds[ev->ev_fd] = NULL;
336933707f3Ssthen 		FD_CLR(FD_SET_T ev->ev_fd, &ev->ev_base->reads);
337933707f3Ssthen 		FD_CLR(FD_SET_T ev->ev_fd, &ev->ev_base->writes);
338933707f3Ssthen 		FD_CLR(FD_SET_T ev->ev_fd, &ev->ev_base->ready);
339933707f3Ssthen 		FD_CLR(FD_SET_T ev->ev_fd, &ev->ev_base->content);
340*e21c60efSsthen 		if(ev->ev_fd == ev->ev_base->maxfd) {
341*e21c60efSsthen                         int i = ev->ev_base->maxfd - 1;
342*e21c60efSsthen                         for (; i > 3; i--) {
343*e21c60efSsthen                                 if (NULL != ev->ev_base->fds[i]) {
344*e21c60efSsthen                                         break;
345*e21c60efSsthen                                 }
346*e21c60efSsthen                         }
347*e21c60efSsthen                         ev->ev_base->maxfd = i;
348*e21c60efSsthen                 }
349933707f3Ssthen 	}
350933707f3Ssthen 	ev->added = 0;
351933707f3Ssthen 	return 0;
352933707f3Ssthen }
353933707f3Ssthen 
354933707f3Ssthen /** which base gets to handle signals */
355933707f3Ssthen static struct event_base* signal_base = NULL;
356933707f3Ssthen /** signal handler */
sigh(int sig)357933707f3Ssthen static RETSIGTYPE sigh(int sig)
358933707f3Ssthen {
359933707f3Ssthen 	struct event* ev;
360933707f3Ssthen 	if(!signal_base || sig < 0 || sig >= MAX_SIG)
361933707f3Ssthen 		return;
362933707f3Ssthen 	ev = signal_base->signals[sig];
363933707f3Ssthen 	if(!ev)
364933707f3Ssthen 		return;
365933707f3Ssthen 	fptr_ok(fptr_whitelist_event(ev->ev_callback));
366933707f3Ssthen 	(*ev->ev_callback)(sig, EV_SIGNAL, ev->ev_arg);
367933707f3Ssthen }
368933707f3Ssthen 
369933707f3Ssthen /** install signal handler */
signal_add(struct event * ev,struct timeval * ATTR_UNUSED (tv))370933707f3Ssthen int signal_add(struct event* ev, struct timeval* ATTR_UNUSED(tv))
371933707f3Ssthen {
372933707f3Ssthen 	if(ev->ev_fd == -1 || ev->ev_fd >= MAX_SIG)
373933707f3Ssthen 		return -1;
374933707f3Ssthen 	signal_base = ev->ev_base;
375933707f3Ssthen 	ev->ev_base->signals[ev->ev_fd] = ev;
376933707f3Ssthen 	ev->added = 1;
377933707f3Ssthen 	if(signal(ev->ev_fd, sigh) == SIG_ERR) {
378933707f3Ssthen 		return -1;
379933707f3Ssthen 	}
380933707f3Ssthen 	return 0;
381933707f3Ssthen }
382933707f3Ssthen 
383933707f3Ssthen /** remove signal handler */
signal_del(struct event * ev)384933707f3Ssthen int signal_del(struct event* ev)
385933707f3Ssthen {
386933707f3Ssthen 	if(ev->ev_fd == -1 || ev->ev_fd >= MAX_SIG)
387933707f3Ssthen 		return -1;
388933707f3Ssthen 	ev->ev_base->signals[ev->ev_fd] = NULL;
389933707f3Ssthen 	ev->added = 0;
390933707f3Ssthen 	return 0;
391933707f3Ssthen }
392933707f3Ssthen 
393933707f3Ssthen #else /* USE_MINI_EVENT */
394933707f3Ssthen #ifndef USE_WINSOCK
mini_ev_cmp(const void * ATTR_UNUSED (a),const void * ATTR_UNUSED (b))395933707f3Ssthen int mini_ev_cmp(const void* ATTR_UNUSED(a), const void* ATTR_UNUSED(b))
396933707f3Ssthen {
397933707f3Ssthen 	return 0;
398933707f3Ssthen }
399933707f3Ssthen #endif /* not USE_WINSOCK */
400933707f3Ssthen #endif /* USE_MINI_EVENT */
401