xref: /freebsd/contrib/libbegemot/rpoll.c (revision 1e413cf93298b5b97441a21d9a50fdcd0ee9945e)
1 /*
2  * Copyright (c)1996-2002 by Hartmut Brandt
3  *	All rights reserved.
4  *
5  * Author: Hartmut Brandt
6  *
7  * Redistribution of this software and documentation and use in source and
8  * binary forms, with or without modification, are permitted provided that
9  * the following conditions are met:
10  *
11  * 1. Redistributions of source code or documentation must retain the above
12  *   copyright notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *   notice, this list of conditions and the following disclaimer in the
15  *   documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE AUTHOR
18  * AND ITS CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
19  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
20  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
21  * THE AUTHOR OR ITS CONTRIBUTORS  BE LIABLE FOR ANY DIRECT, INDIRECT,
22  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
24  * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
27  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29 /*
30  * These functions try to hide the poll/select/setitimer interface from the
31  * user. You associate callback functions with file descriptors and timers.
32  *
33  * $Begemot: libbegemot/rpoll.c,v 1.14 2004/09/21 15:59:00 brandt Exp $
34  */
35 # include <stdio.h>
36 # include <stdlib.h>
37 # include <stddef.h>
38 # include <stdarg.h>
39 # include <signal.h>
40 # include <string.h>
41 # include <errno.h>
42 # include <time.h>
43 # include <assert.h>
44 # include <unistd.h>
45 # include <sys/time.h>
46 
47 /*
48  * There happens to be linuxes which read siginfo.h when including
49  * signal.h, which, for no appearent reason, defines these symbols.
50  */
51 # ifdef POLL_IN
52 #  undef POLL_IN
53 # endif
54 # ifdef POLL_OUT
55 #  undef POLL_OUT
56 # endif
57 
58 # include "rpoll.h"
59 
60 /*
61 # define DEBUG
62 */
63 
64 # ifdef USE_POLL
65 #  ifdef NEED_POLL_XOPEN_TWIDDLE
66 #   define __USE_XOPEN
67 #  endif
68 #  include <poll.h>
69 #  ifdef NEED_POLL_XOPEN_TWIDDLE
70 #   undef __USE_XOPEN
71 #  endif
72 #  include <stropts.h>
73 # endif
74 
75 /*
76  * the second define is for Linux, which sometimes fails to
77  * declare INFTIM.
78  */
79 # if defined(USE_SELECT) || !defined(INFTIM)
80 #  define INFTIM (-1)
81 # endif
82 
83 # if defined(SIGPOLL)
84 #  define SIGNAL	SIGPOLL
85 # else
86 #  if defined(SIGIO)
87 #   define SIGNAL	SIGIO
88 #  endif
89 # endif
90 
91 # ifdef USE_POLL
92 #  define poll_in	(POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI)
93 #  define poll_out	(POLLOUT | POLLWRNORM | POLLWRBAND)
94 #  define poll_except	(POLLERR | POLLHUP)
95 # endif
96 
97 # ifdef BROKEN_SELECT_PROTO
98 #  define SELECT_CAST(P)	(int *)P
99 # else
100 #  define SELECT_CAST(P)	P
101 # endif
102 
103 
104 typedef int64_t tval_t;
105 
106 static inline tval_t GETUSECS(void);
107 
108 static inline tval_t
109 GETUSECS(void) {
110 	struct timeval tval;
111 
112 	(void)gettimeofday(&tval, NULL);
113 	return (tval_t)tval.tv_sec * 1000000 + tval.tv_usec;
114 }
115 
116 /*
117  * Simple fatal exit.
118  */
119 static void
120 _panic(const char *fmt, ...)
121 {
122 	va_list ap;
123 
124 	va_start(ap, fmt);
125 	fprintf(stderr, "panic: ");
126 	vfprintf(stderr, fmt, ap);
127 	fprintf(stderr, "\n");
128 	va_end(ap);
129 
130 	exit(1);
131 }
132 
133 static void *
134 _xrealloc(void *p, size_t s)
135 {
136 	void *ptr;
137 
138 	if(p == NULL) {
139 		if((ptr=malloc(s)) == NULL && (s!=0 || (ptr=malloc(1)) == NULL))
140 			_panic("out of memory: xrealloc(%lx, %lu)",
141 				(unsigned long)p, (unsigned long)s);
142 	} else if(s == 0) {
143 		free(p);
144 		if((ptr=malloc(s)) == NULL && (ptr=malloc(1)) == NULL)
145 			_panic("out of memory: xrealloc(%lx, %lu)",
146 				(unsigned long)p, (unsigned long)s);
147 	} else {
148 		if((ptr = realloc(p, s)) == NULL)
149 			_panic("out of memory: xrealloc(%lx, %lu)",
150 				(unsigned long)p, (unsigned long)s);
151 	}
152 
153 	return ptr;
154 }
155 
156 /*
157  * This structure holds one registration record for files
158  */
159 typedef struct {
160 	int	fd;		/* file descriptor (-1 if struct unused) */
161 	int	mask;		/* event flags */
162 	void *	arg;		/* client arg */
163 	poll_f	func;		/* handler */
164 # ifdef USE_POLL
165 	struct pollfd *pfd;	/* pointer to corresponding poll() structure */
166 # endif
167 } PollReg_t;
168 
169 /*
170  * Now for timers
171  */
172 typedef struct {
173 	uint64_t usecs;		/* microsecond value of the timer */
174 	int	repeat;		/* one shot or repeat? */
175 	void	*arg;		/* client arg */
176 	timer_f	func;		/* handler, 0 means disfunct */
177 	tval_t	when;		/* next time to trigger in usecs! */
178 } PollTim_t;
179 
180 /* how many records should our table grow at once? */
181 # define POLL_REG_GROW	100
182 
183 # ifdef USE_POLL
184 static struct pollfd *	pfd;		/* fd list for poll() */
185 # endif
186 
187 # ifdef USE_SELECT
188 static fd_set rset, wset, xset;		/* file descriptor sets for select() */
189 static int maxfd;			/* maximum fd number */
190 # endif
191 
192 static int		in_dispatch;
193 
194 static PollReg_t *	regs;		/* registration records */
195 static u_int		regs_alloc;	/* how many are allocated */
196 static u_int		regs_used;	/* upper used limit */
197 static sigset_t		bset;		/* blocked signals */
198 static int 		rebuild;	/* rebuild table on next dispatch() */
199 
200 static int *		tfd;		/* sorted entries */
201 static u_int		tfd_alloc;	/* number of entries allocated */
202 static u_int		tfd_used;	/* number of entries used */
203 static PollTim_t *	tims;		/* timer registration records */
204 static u_int		tims_alloc;	/* how many are allocated */
205 static u_int		tims_used;	/* how many are used */
206 static int		resort;		/* resort on next dispatch */
207 
208 int	rpoll_trace;
209 int	rpoll_policy;	/* if 0 start sched callbacks from 0 else try round robin */
210 
211 static void poll_build(void);
212 static void poll_blocksig(void);
213 static void poll_unblocksig(void);
214 static void sort_timers(void);
215 
216 
217 /*
218  * Private function to block SIGPOLL or SIGIO for a short time.
219  * Don't forget to call poll_unblock before return from the calling function.
220  * Don't change the mask between this calls (your changes will be lost).
221  */
222 static void
223 poll_blocksig(void)
224 {
225 	sigset_t set;
226 
227 	sigemptyset(&set);
228 	sigaddset(&set, SIGNAL);
229 
230 	if(sigprocmask(SIG_BLOCK, &set, &bset))
231 		_panic("sigprocmask(SIG_BLOCK): %s", strerror(errno));
232 }
233 
234 /*
235  * unblock the previously blocked signal
236  */
237 static void
238 poll_unblocksig(void)
239 {
240 	if(sigprocmask(SIG_SETMASK, &bset, NULL))
241 		_panic("sigprocmask(SIG_SETMASK): %s", strerror(errno));
242 }
243 
244 /*
245  * Register the file descriptor fd. If the event corresponding to
246  * mask arrives func is called with arg.
247  * If fd is already registered with that func and arg, only the mask
248  * is changed.
249  * We block the IO-signal, so the dispatch function can be called from
250  * within the signal handler.
251  */
252 int
253 poll_register(int fd, poll_f func, void *arg, int mask)
254 {
255 	PollReg_t * p;
256 
257 	poll_blocksig();
258 
259 	/* already registered? */
260 	for(p = regs; p < &regs[regs_alloc]; p++)
261 		if(p->fd == fd && p->func == func && p->arg == arg) {
262 			p->mask = mask;
263 			break;
264 		}
265 
266 	if(p == &regs[regs_alloc]) {
267 		/* no - register */
268 
269 		/* find a free slot */
270 		for(p = regs; p < &regs[regs_alloc]; p++)
271 			if(p->fd == -1)
272 				break;
273 
274 		if(p == &regs[regs_alloc]) {
275 			size_t newsize = regs_alloc + POLL_REG_GROW;
276 			regs = _xrealloc(regs, sizeof(regs[0]) * newsize);
277 			for(p = &regs[regs_alloc]; p < &regs[newsize]; p++) {
278 				p->fd = -1;
279 # ifdef USE_POLL
280 				p->pfd = NULL;
281 # endif
282 			}
283 			p = &regs[regs_alloc];
284 			regs_alloc = newsize;
285 		}
286 
287 		p->fd = fd;
288 		p->arg = arg;
289 		p->mask = mask;
290 		p->func = func;
291 
292 		regs_used++;
293 		rebuild = 1;
294 	}
295 
296 	poll_unblocksig();
297 
298 	if(rpoll_trace)
299 		fprintf(stderr, "poll_register(%d, %p, %p, %#x)->%tu",
300 			fd, (void *)func, (void *)arg, mask, p - regs);
301 	return p - regs;
302 }
303 
304 /*
305  * remove registration
306  */
307 void
308 poll_unregister(int handle)
309 {
310 	if(rpoll_trace)
311 		fprintf(stderr, "poll_unregister(%d)", handle);
312 
313 	poll_blocksig();
314 
315 	regs[handle].fd = -1;
316 # ifdef USE_POLL
317 	regs[handle].pfd = NULL;
318 # endif
319 	rebuild = 1;
320 	regs_used--;
321 
322 	poll_unblocksig();
323 }
324 
325 /*
326  * Build the structures used by poll() or select()
327  */
328 static void
329 poll_build(void)
330 {
331 	PollReg_t * p;
332 
333 # ifdef USE_POLL
334 	struct pollfd * f;
335 
336 	f = pfd = _xrealloc(pfd, sizeof(pfd[0]) * regs_used);
337 
338 	for(p = regs; p < &regs[regs_alloc]; p++)
339 		if(p->fd >= 0) {
340 			f->fd = p->fd;
341 			f->events = 0;
342 			if(p->mask & POLL_IN)
343 				f->events |= poll_in;
344 			if(p->mask & POLL_OUT)
345 				f->events |= poll_out;
346 			if(p->mask & POLL_EXCEPT)
347 				f->events |= poll_except;
348 			f->revents = 0;
349 			p->pfd = f++;
350 		}
351 	assert(f == &pfd[regs_used]);
352 # endif
353 
354 # ifdef USE_SELECT
355 	FD_ZERO(&rset);
356 	FD_ZERO(&wset);
357 	FD_ZERO(&xset);
358 	maxfd = -1;
359 	for(p = regs; p < &regs[regs_alloc]; p++)
360 		if(p->fd >= 0) {
361 			if(p->fd > maxfd)
362 				maxfd = p->fd;
363 			if(p->mask & POLL_IN)
364 				FD_SET(p->fd, &rset);
365 			if(p->mask & POLL_OUT)
366 				FD_SET(p->fd, &wset);
367 			if(p->mask & POLL_EXCEPT)
368 				FD_SET(p->fd, &xset);
369 		}
370 # endif
371 }
372 
373 int
374 poll_start_timer(u_int msecs, int repeat, timer_f func, void *arg)
375 {
376 	return (poll_start_utimer((unsigned long long)msecs * 1000,
377 	    repeat, func, arg));
378 }
379 
380 int
381 poll_start_utimer(unsigned long long usecs, int repeat, timer_f func, void *arg)
382 {
383 	PollTim_t *p;
384 
385 	/* find unused entry */
386 	for(p = tims; p < &tims[tims_alloc]; p++)
387 		if(p->func == NULL)
388 			break;
389 
390 	if(p == &tims[tims_alloc]) {
391 		if(tims_alloc == tims_used) {
392 			size_t newsize = tims_alloc + POLL_REG_GROW;
393 			tims = _xrealloc(tims, sizeof(tims[0]) * newsize);
394 			for(p = &tims[tims_alloc]; p < &tims[newsize]; p++)
395 				p->func = NULL;
396 			p = &tims[tims_alloc];
397 			tims_alloc = newsize;
398 		}
399 	}
400 
401 	/* create entry */
402 	p->usecs = usecs;
403 	p->repeat = repeat;
404 	p->arg = arg;
405 	p->func = func;
406 	p->when = GETUSECS() + usecs;
407 
408 	tims_used++;
409 
410 	resort = 1;
411 
412 	if(rpoll_trace)
413 		fprintf(stderr, "poll_start_utimer(%llu, %d, %p, %p)->%tu",
414 			usecs, repeat, (void *)func, (void *)arg, p - tims);
415 
416 	return p - tims;
417 }
418 
419 /*
420  * Here we have to look into the sorted table, whether any entry there points
421  * into the registration table for the deleted entry. This is needed,
422  * because a unregistration can occure while we are scanning through the
423  * table in dispatch(). Do this only, if we are really there - resorting
424  * will sort out things if we are called from outside the loop.
425  */
426 void
427 poll_stop_timer(int handle)
428 {
429 	u_int i;
430 
431 	if(rpoll_trace)
432 		fprintf(stderr, "poll_stop_timer(%d)", handle);
433 
434 	tims[handle].func = NULL;
435 	tims_used--;
436 
437 	resort = 1;
438 
439 	if(!in_dispatch)
440 		return;
441 
442 	for(i = 0; i < tfd_used; i++)
443 		if(tfd[i] == handle) {
444 			tfd[i] = -1;
445 			break;
446 		}
447 }
448 
449 /*
450  * Squeeze and sort timer table.
451  * Should perhaps use a custom sort.
452  */
453 static int
454 tim_cmp(const void *p1, const void *p2)
455 {
456 	int t1 = *(const int *)p1;
457 	int t2 = *(const int *)p2;
458 
459 	return tims[t1].when < tims[t2].when ? -1
460 	     : tims[t1].when > tims[t2].when ? +1
461 	     :                        		  0;
462 }
463 
464 /*
465  * Reconstruct the tfd-array. This will be an sorted array of indexes
466  * to the used entries in tims. The next timer to expire will be infront
467  * of the array. tfd_used is the number of used entries. The array is
468  * re-allocated if needed.
469  */
470 static void
471 sort_timers(void)
472 {
473 	int *pp;
474 	u_int i;
475 
476 	if(tims_used > tfd_alloc) {
477 		tfd_alloc = tims_used;
478 		tfd  = _xrealloc(tfd, sizeof(int *) * tfd_alloc);
479 	}
480 
481 	pp = tfd;
482 
483 	for(i = 0; i < tims_alloc; i++)
484 		if(tims[i].func)
485 			*pp++ = i;
486 	assert(pp - tfd == (ptrdiff_t)tims_used);
487 
488 	tfd_used = tims_used;
489 	if(tfd_used > 1)
490 		qsort(tfd, tfd_used, sizeof(int), tim_cmp);
491 }
492 
493 /*
494  * Poll the file descriptors and dispatch to the right function
495  * If wait is true the poll blocks until somewhat happens.
496  * Don't use a pointer here, because the called function may cause
497  * a reallocation! The check for pfd != NULL is required, because
498  * a sequence of unregister/register could make the wrong callback
499  * to be called. So we clear pfd in unregister and check here.
500  */
501 void
502 poll_dispatch(int wait)
503 {
504 	u_int i, idx;
505 	int ret;
506 	tval_t now;
507 	tval_t tout;
508 	static u_int last_index;
509 
510 # ifdef USE_SELECT
511 	fd_set nrset, nwset, nxset;
512 	struct timeval tv;
513 # endif
514 
515 	in_dispatch = 1;
516 
517 	if(rebuild) {
518 		rebuild = 0;
519 		poll_build();
520 	}
521 	if(resort) {
522 		resort = 0;
523 		sort_timers();
524 	}
525 
526 	/* in wait mode - compute the timeout */
527 	if(wait) {
528 		if(tfd_used) {
529 			now = GETUSECS();
530 # ifdef DEBUG
531 			{
532 				fprintf(stderr, "now=%llu", now);
533 				for(i = 0; i < tims_used; i++)
534 					fprintf(stderr, "timers[%2d] = %lld",
535 					    i, tfd[i]->when - now);
536 			}
537 # endif
538 			if((tout = tims[tfd[0]].when - now) < 0)
539 				tout = 0;
540 		} else
541 			tout = INFTIM;
542 	} else
543 		tout = 0;
544 
545 # ifdef DEBUG
546 	fprintf(stderr, "rpoll -- selecting with tout=%u", tout);
547 # endif
548 
549 # ifdef USE_POLL
550 	ret = poll(pfd, regs_used, tout == INFTIM ? INFTIM : (tout / 1000));
551 # endif
552 
553 # ifdef USE_SELECT
554 	nrset = rset;
555 	nwset = wset;
556 	nxset = xset;
557 	if(tout != INFTIM) {
558 		tv.tv_sec = tout / 1000000;
559 		tv.tv_usec = tout % 1000000;
560 	}
561 	ret = select(maxfd+1,
562 		SELECT_CAST(&nrset),
563 		SELECT_CAST(&nwset),
564 		SELECT_CAST(&nxset), (tout==INFTIM) ? NULL : &tv);
565 # endif
566 
567 	if(ret == -1) {
568 		if(errno == EINTR)
569 			return;
570 		_panic("poll/select: %s", strerror(errno));
571 	}
572 
573 	/* dispatch files */
574 	if(ret > 0) {
575 		for(i = 0; i < regs_alloc; i++) {
576 			idx = rpoll_policy ? ((last_index+i) % regs_alloc) : i;
577 
578 			assert(idx < regs_alloc);
579 
580 			if(regs[idx].fd >= 0) {
581 				int mask = 0;
582 
583 # ifdef USE_POLL
584 				if(regs[idx].pfd) {
585 					if ((regs[idx].mask & POLL_IN) &&
586 					    (regs[idx].pfd->revents & poll_in))
587 						mask |= POLL_IN;
588 					if ((regs[idx].mask & POLL_OUT) &&
589 					    (regs[idx].pfd->revents & poll_out))
590 						mask |= POLL_OUT;
591 					if((regs[idx].mask & POLL_EXCEPT) &&
592 					    (regs[idx].pfd->revents & poll_except))
593 						mask |= POLL_EXCEPT;
594 				}
595 # endif
596 # ifdef USE_SELECT
597 				if ((regs[idx].mask & POLL_IN) &&
598 				    FD_ISSET(regs[idx].fd, &nrset))
599 					mask |= POLL_IN;
600 				if ((regs[idx].mask & POLL_OUT) &&
601 				    FD_ISSET(regs[idx].fd, &nwset))
602 					mask |= POLL_OUT;
603 				if ((regs[idx].mask & POLL_EXCEPT) &&
604 				    FD_ISSET(regs[idx].fd, &nxset))
605 					mask |= POLL_EXCEPT;
606 # endif
607 				assert(idx < regs_alloc);
608 
609 				if(mask) {
610 					if(rpoll_trace)
611 						fprintf(stderr, "poll_dispatch() -- "
612 						    "file %d/%d %x",
613 						    regs[idx].fd, idx, mask);
614 					(*regs[idx].func)(regs[idx].fd, mask, regs[idx].arg);
615 				}
616 			}
617 
618 		}
619 		last_index++;
620 	}
621 
622 	/* dispatch timeouts */
623 	if(tfd_used) {
624 		now = GETUSECS();
625 		for(i = 0; i < tfd_used; i++) {
626 			if(tfd[i] < 0)
627 				continue;
628 			if(tims[tfd[i]].when > now)
629 				break;
630 			if(rpoll_trace)
631 				fprintf(stderr, "rpoll_dispatch() -- timeout %d",tfd[i]);
632 			(*tims[tfd[i]].func)(tfd[i], tims[tfd[i]].arg);
633 			if(tfd[i] < 0)
634 				continue;
635 			if(tims[tfd[i]].repeat)
636 				tims[tfd[i]].when = now + tims[tfd[i]].usecs;
637 			else {
638 				tims[tfd[i]].func = NULL;
639 				tims_used--;
640 				tfd[i] = -1;
641 			}
642 			resort = 1;
643 		}
644 	}
645 	in_dispatch = 0;
646 }
647 
648 
649 # ifdef TESTME
650 struct timeval start, now;
651 int t0, t1;
652 
653 double elaps(void);
654 void infunc(int fd, int mask, void *arg);
655 
656 double
657 elaps(void)
658 {
659 	gettimeofday(&now, NULL);
660 
661 	return (double)(10 * now.tv_sec + now.tv_usec / 100000 -
662 	    10 * start.tv_sec - start.tv_usec / 100000) / 10;
663 }
664 
665 void
666 infunc(int fd, int mask, void *arg)
667 {
668 	char buf[1024];
669 	int ret;
670 
671 	mask = mask;
672 	arg = arg;
673 	if((ret = read(fd, buf, sizeof(buf))) < 0)
674 		_panic("read: %s", strerror(errno));
675 	write(1, "stdin:", 6);
676 	write(1, buf, ret);
677 }
678 
679 void tfunc0(int tid, void *arg);
680 void tfunc1(int tid, void *arg);
681 
682 void
683 tfunc0(int tid, void *arg)
684 {
685 	printf("%4.1f -- %d: %s\n", elaps(), tid, (char *)arg);
686 }
687 void
688 tfunc1(int tid, void *arg)
689 {
690 	printf("%4.1f -- %d: %s\n", elaps(), tid, (char *)arg);
691 }
692 void
693 tfunc2(int tid, void *arg)
694 {
695 	static u_int count = 0;
696 
697 	if (++count % 10000 == 0)
698 		printf("%4.1f -- %d\n", elaps(), tid);
699 }
700 
701 void first(int tid, void *arg);
702 void second(int tid, void *arg);
703 
704 void
705 second(int tid, void *arg)
706 {
707 	printf("%4.1f -- %d: %s\n", elaps(), tid, (char *)arg);
708 	poll_start_utimer(5500000, 0, first, "first");
709 	poll_stop_timer(t1);
710 	t0 = poll_start_timer(1000, 1, tfunc0, "1 second");
711 }
712 void
713 first(int tid, void *arg)
714 {
715 	printf("%4.1f -- %d: %s\n", elaps(), tid, (char *)arg);
716 	poll_start_timer(3700, 0, second, "second");
717 	poll_stop_timer(t0);
718 	t1 = poll_start_timer(250, 1, tfunc1, "1/4 second");
719 }
720 
721 int
722 main(int argc, char *argv[])
723 {
724 	argv = argv;
725 	gettimeofday(&start, NULL);
726 	poll_register(0, infunc, NULL, POLL_IN);
727 
728 	if (argc < 2) {
729 		t0 = poll_start_timer(1000, 1, tfunc0, "1 second");
730 		poll_start_timer(2500, 0, first, "first");
731 	} else {
732 		t0 = poll_start_utimer(300, 1, tfunc2, NULL);
733 	}
734 
735 	while(1)
736 		poll_dispatch(1);
737 
738 	return 0;
739 }
740 # endif
741