1: /*************************************************************************
2: * (C) 2011 AITNET ltd - Sofia/Bulgaria - <misho@aitbg.com>
3: * by Michael Pounov <misho@openbsd-bg.org>
4: *
5: * $Author: misho $
6: * $Id: hooks.c,v 1.7 2012/05/30 08:52:45 misho Exp $
7: *
8: **************************************************************************
9: The ELWIX and AITNET software is distributed under the following
10: terms:
11:
12: All of the documentation and software included in the ELWIX and AITNET
13: Releases is copyrighted by ELWIX - Sofia/Bulgaria <info@elwix.org>
14:
15: Copyright 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012
16: by Michael Pounov <misho@elwix.org>. All rights reserved.
17:
18: Redistribution and use in source and binary forms, with or without
19: modification, are permitted provided that the following conditions
20: are met:
21: 1. Redistributions of source code must retain the above copyright
22: notice, this list of conditions and the following disclaimer.
23: 2. Redistributions in binary form must reproduce the above copyright
24: notice, this list of conditions and the following disclaimer in the
25: documentation and/or other materials provided with the distribution.
26: 3. All advertising materials mentioning features or use of this software
27: must display the following acknowledgement:
28: This product includes software developed by Michael Pounov <misho@elwix.org>
29: ELWIX - Embedded LightWeight unIX and its contributors.
30: 4. Neither the name of AITNET nor the names of its contributors
31: may be used to endorse or promote products derived from this software
32: without specific prior written permission.
33:
34: THIS SOFTWARE IS PROVIDED BY AITNET AND CONTRIBUTORS ``AS IS'' AND
35: ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
36: IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
37: ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
38: FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
39: DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
40: OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
41: HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
42: LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
43: OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
44: SUCH DAMAGE.
45: */
46: #include "global.h"
47: #include "hooks.h"
48:
49:
50: /*
51: * sched_hook_init() - Default INIT hook
52: *
53: * @root = root task
54: * @arg = unused
55: * return: <0 errors and 0 ok
56: */
57: void *
58: sched_hook_init(void *root, void *arg __unused)
59: {
60: sched_root_task_t *r = root;
61:
62: if (!r)
63: return (void*) -1;
64:
65: r->root_kq = kqueue();
66: if (r->root_kq == -1) {
67: LOGERR;
68: return (void*) -1;
69: }
70:
71: return NULL;
72: }
73:
74: /*
75: * sched_hook_fini() - Default FINI hook
76: *
77: * @root = root task
78: * @arg = unused
79: * return: <0 errors and 0 ok
80: */
81: void *
82: sched_hook_fini(void *root, void *arg __unused)
83: {
84: sched_root_task_t *r = root;
85:
86: if (!r)
87: return (void*) -1;
88:
89: if (r->root_kq > 2) {
90: close(r->root_kq);
91: r->root_kq = 0;
92: }
93:
94: return NULL;
95: }
96:
97: /*
98: * sched_hook_cancel() - Default CANCEL hook
99: *
100: * @task = current task
101: * @arg = unused
102: * return: <0 errors and 0 ok
103: */
104: void *
105: sched_hook_cancel(void *task, void *arg __unused)
106: {
107: sched_task_t *t = task;
108: struct kevent chg[1];
109: struct timespec timeout = { 0, 0 };
110:
111: if (!t || !TASK_ROOT(t))
112: return (void*) -1;
113:
114: switch (TASK_TYPE(t)) {
115: case taskREAD:
116: #ifdef __NetBSD__
117: EV_SET(&chg[0], TASK_FD(t), EVFILT_READ, EV_DELETE, 0, 0, (intptr_t) TASK_FD(t));
118: #else
119: EV_SET(&chg[0], TASK_FD(t), EVFILT_READ, EV_DELETE, 0, 0, (void*) TASK_FD(t));
120: #endif
121: kevent(TASK_ROOT(t)->root_kq, chg, 1, NULL, 0, &timeout);
122: break;
123: case taskWRITE:
124: #ifdef __NetBSD__
125: EV_SET(&chg[0], TASK_FD(t), EVFILT_WRITE, EV_DELETE, 0, 0, (intptr_t) TASK_FD(t));
126: #else
127: EV_SET(&chg[0], TASK_FD(t), EVFILT_WRITE, EV_DELETE, 0, 0, (void*) TASK_FD(t));
128: #endif
129: kevent(TASK_ROOT(t)->root_kq, chg, 1, NULL, 0, &timeout);
130: break;
131: case taskALARM:
132: #ifdef __NetBSD__
133: EV_SET(&chg[0], (uintptr_t) TASK_DATA(t), EVFILT_TIMER, EV_DELETE,
134: 0, 0, (intptr_t) TASK_DATA(t));
135: #else
136: EV_SET(&chg[0], (uintptr_t) TASK_DATA(t), EVFILT_TIMER, EV_DELETE,
137: 0, 0, (void*) TASK_DATA(t));
138: #endif
139: kevent(TASK_ROOT(t)->root_kq, chg, 1, NULL, 0, &timeout);
140: break;
141: default:
142: break;
143: }
144:
145: return NULL;
146: }
147:
148: /*
149: * sched_hook_read() - Default READ hook
150: *
151: * @task = current task
152: * @arg = unused
153: * return: <0 errors and 0 ok
154: */
155: void *
156: sched_hook_read(void *task, void *arg __unused)
157: {
158: sched_task_t *t = task;
159: struct kevent chg[1];
160: struct timespec timeout = { 0, 0 };
161:
162: if (!t || !TASK_ROOT(t))
163: return (void*) -1;
164:
165: #ifdef __NetBSD__
166: EV_SET(&chg[0], TASK_FD(t), EVFILT_READ, EV_ADD, 0, 0, (intptr_t) TASK_FD(t));
167: #else
168: EV_SET(&chg[0], TASK_FD(t), EVFILT_READ, EV_ADD, 0, 0, (void*) TASK_FD(t));
169: #endif
170: if (kevent(TASK_ROOT(t)->root_kq, chg, 1, NULL, 0, &timeout) == -1) {
171: if (TASK_ROOT(t)->root_hooks.hook_exec.exception)
172: TASK_ROOT(t)->root_hooks.hook_exec.exception(TASK_ROOT(t), NULL);
173: else
174: LOGERR;
175: return (void*) -1;
176: }
177:
178: return NULL;
179: }
180:
181: /*
182: * sched_hook_write() - Default WRITE hook
183: *
184: * @task = current task
185: * @arg = unused
186: * return: <0 errors and 0 ok
187: */
188: void *
189: sched_hook_write(void *task, void *arg __unused)
190: {
191: sched_task_t *t = task;
192: struct kevent chg[1];
193: struct timespec timeout = { 0, 0 };
194:
195: if (!t || !TASK_ROOT(t))
196: return (void*) -1;
197:
198: #ifdef __NetBSD__
199: EV_SET(&chg[0], TASK_FD(t), EVFILT_WRITE, EV_ADD, 0, 0, (intptr_t) TASK_FD(t));
200: #else
201: EV_SET(&chg[0], TASK_FD(t), EVFILT_WRITE, EV_ADD, 0, 0, (void*) TASK_FD(t));
202: #endif
203: if (kevent(TASK_ROOT(t)->root_kq, chg, 1, NULL, 0, &timeout) == -1) {
204: if (TASK_ROOT(t)->root_hooks.hook_exec.exception)
205: TASK_ROOT(t)->root_hooks.hook_exec.exception(TASK_ROOT(t), NULL);
206: else
207: LOGERR;
208: return (void*) -1;
209: }
210:
211: return NULL;
212: }
213:
214: /*
215: * sched_hook_alarm() - Default ALARM hook
216: *
217: * @task = current task
218: * @arg = unused
219: * return: <0 errors and 0 ok
220: */
221: void *
222: sched_hook_alarm(void *task, void *arg __unused)
223: {
224: sched_task_t *t = task;
225: struct kevent chg[1];
226: struct timespec timeout = { 0, 0 };
227:
228: if (!t || !TASK_ROOT(t))
229: return (void*) -1;
230:
231: #ifdef __NetBSD__
232: EV_SET(&chg[0], (uintptr_t) TASK_DATA(t), EVFILT_TIMER, EV_ADD | EV_ONESHOT, 0,
233: t->task_val.ts.tv_sec * 1000 + t->task_val.ts.tv_nsec / 1000000,
234: (intptr_t) TASK_DATA(t));
235: #else
236: EV_SET(&chg[0], (uintptr_t) TASK_DATA(t), EVFILT_TIMER, EV_ADD | EV_ONESHOT, 0,
237: t->task_val.ts.tv_sec * 1000 + t->task_val.ts.tv_nsec / 1000000,
238: (void*) TASK_DATA(t));
239: #endif
240: if (kevent(TASK_ROOT(t)->root_kq, chg, 1, NULL, 0, &timeout) == -1) {
241: if (TASK_ROOT(t)->root_hooks.hook_exec.exception)
242: TASK_ROOT(t)->root_hooks.hook_exec.exception(TASK_ROOT(t), NULL);
243: else
244: LOGERR;
245: return (void*) -1;
246: }
247:
248: return NULL;
249: }
250:
251: /*
252: * sched_hook_fetch() - Default FETCH hook
253: *
254: * @root = root task
255: * @arg = unused
256: * return: NULL error or !=NULL fetched task
257: */
258: void *
259: sched_hook_fetch(void *root, void *arg __unused)
260: {
261: sched_root_task_t *r = root;
262: sched_task_t *task, *tmp;
263: struct timespec now, m, mtmp;
264: struct timespec *timeout;
265: struct kevent evt[1], res[KQ_EVENTS];
266: register int i;
267: int en;
268:
269: if (!r)
270: return NULL;
271:
272: /* get new task by queue priority */
273: while ((task = TAILQ_FIRST(&r->root_event))) {
274: #ifdef HAVE_LIBPTHREAD
275: pthread_mutex_lock(&r->root_mtx[taskEVENT]);
276: #endif
277: TAILQ_REMOVE(&r->root_event, task, task_node);
278: #ifdef HAVE_LIBPTHREAD
279: pthread_mutex_unlock(&r->root_mtx[taskEVENT]);
280: #endif
281: task->task_type = taskUNUSE;
282: #ifdef HAVE_LIBPTHREAD
283: pthread_mutex_lock(&r->root_mtx[taskUNUSE]);
284: #endif
285: TAILQ_INSERT_TAIL(&r->root_unuse, task, task_node);
286: #ifdef HAVE_LIBPTHREAD
287: pthread_mutex_unlock(&r->root_mtx[taskUNUSE]);
288: #endif
289: return task;
290: }
291: while ((task = TAILQ_FIRST(&r->root_ready))) {
292: #ifdef HAVE_LIBPTHREAD
293: pthread_mutex_lock(&r->root_mtx[taskREADY]);
294: #endif
295: TAILQ_REMOVE(&r->root_ready, task, task_node);
296: #ifdef HAVE_LIBPTHREAD
297: pthread_mutex_unlock(&r->root_mtx[taskREADY]);
298: #endif
299: task->task_type = taskUNUSE;
300: #ifdef HAVE_LIBPTHREAD
301: pthread_mutex_lock(&r->root_mtx[taskUNUSE]);
302: #endif
303: TAILQ_INSERT_TAIL(&r->root_unuse, task, task_node);
304: #ifdef HAVE_LIBPTHREAD
305: pthread_mutex_unlock(&r->root_mtx[taskUNUSE]);
306: #endif
307: return task;
308: }
309:
310: #ifdef TIMER_WITHOUT_SORT
311: clock_gettime(CLOCK_MONOTONIC, &now);
312:
313: sched_timespecclear(&r->root_wait);
314: TAILQ_FOREACH(task, &r->root_timer, task_node) {
315: if (!sched_timespecisset(&r->root_wait))
316: r->root_wait = TASK_TS(task);
317: else if (sched_timespeccmp(&TASK_TS(task), &r->root_wait, -) < 0)
318: r->root_wait = TASK_TS(task);
319: }
320:
321: if (TAILQ_FIRST(&r->root_timer)) {
322: m = r->root_wait;
323: sched_timespecsub(&m, &now, &mtmp);
324: r->root_wait = mtmp;
325: } else {
326: /* set wait INFTIM */
327: sched_timespecinf(&r->root_wait);
328: }
329: #else
330: if (!TAILQ_FIRST(&r->root_eventlo) && (task = TAILQ_FIRST(&r->root_timer))) {
331: clock_gettime(CLOCK_MONOTONIC, &now);
332:
333: m = TASK_TS(task);
334: sched_timespecsub(&m, &now, &mtmp);
335: r->root_wait = mtmp;
336: } else {
337: /* set wait INFTIM */
338: sched_timespecinf(&r->root_wait);
339: }
340: #endif
341: /* if present member of eventLo, set NOWAIT */
342: if (TAILQ_FIRST(&r->root_eventlo))
343: sched_timespecclear(&r->root_wait);
344:
345: if (r->root_wait.tv_sec != -1 && r->root_wait.tv_nsec != -1)
346: timeout = &r->root_wait;
347: else if (sched_timespecisinf(&r->root_poll))
348: timeout = NULL;
349: else
350: timeout = &r->root_poll;
351: if ((en = kevent(r->root_kq, NULL, 0, res, KQ_EVENTS, timeout)) == -1) {
352: if (r->root_hooks.hook_exec.exception) {
353: if (r->root_hooks.hook_exec.exception(r, NULL))
354: return NULL;
355: } else if (errno != EINTR)
356: LOGERR;
357: return NULL;
358: }
359:
360: now.tv_sec = now.tv_nsec = 0;
361: /* Go and catch the cat into pipes ... */
362: for (i = 0; i < en; i++) {
363: memcpy(evt, &res[i], sizeof evt);
364: evt->flags = EV_DELETE;
365: /* Put read/write task to ready queue */
366: switch (res[i].filter) {
367: case EVFILT_READ:
368: TAILQ_FOREACH_SAFE(task, &r->root_read, task_node, tmp) {
369: if (TASK_FD(task) != ((intptr_t) res[i].udata))
370: continue;
371: /* remove read handle */
372: #ifdef HAVE_LIBPTHREAD
373: pthread_mutex_lock(&r->root_mtx[taskREAD]);
374: #endif
375: TAILQ_REMOVE(&r->root_read, task, task_node);
376: #ifdef HAVE_LIBPTHREAD
377: pthread_mutex_unlock(&r->root_mtx[taskREAD]);
378: #endif
379: if (r->root_hooks.hook_exec.exception && res[i].flags & EV_EOF) {
380: if (r->root_hooks.hook_exec.exception(r, (void*) EV_EOF)) {
381: task->task_type = taskUNUSE;
382: #ifdef HAVE_LIBPTHREAD
383: pthread_mutex_lock(&r->root_mtx[taskUNUSE]);
384: #endif
385: TAILQ_INSERT_TAIL(&r->root_unuse, task, task_node);
386: #ifdef HAVE_LIBPTHREAD
387: pthread_mutex_unlock(&r->root_mtx[taskUNUSE]);
388: #endif
389: } else {
390: task->task_type = taskREADY;
391: #ifdef HAVE_LIBPTHREAD
392: pthread_mutex_lock(&r->root_mtx[taskREADY]);
393: #endif
394: TAILQ_INSERT_TAIL(&r->root_ready, task, task_node);
395: #ifdef HAVE_LIBPTHREAD
396: pthread_mutex_unlock(&r->root_mtx[taskREADY]);
397: #endif
398: }
399: } else {
400: task->task_type = taskREADY;
401: #ifdef HAVE_LIBPTHREAD
402: pthread_mutex_lock(&r->root_mtx[taskREADY]);
403: #endif
404: TAILQ_INSERT_TAIL(&r->root_ready, task, task_node);
405: #ifdef HAVE_LIBPTHREAD
406: pthread_mutex_unlock(&r->root_mtx[taskREADY]);
407: #endif
408: }
409: break;
410: }
411: break;
412: case EVFILT_WRITE:
413: TAILQ_FOREACH_SAFE(task, &r->root_write, task_node, tmp) {
414: if (TASK_FD(task) != ((intptr_t) res[i].udata))
415: continue;
416: /* remove write handle */
417: #ifdef HAVE_LIBPTHREAD
418: pthread_mutex_lock(&r->root_mtx[taskWRITE]);
419: #endif
420: TAILQ_REMOVE(&r->root_write, task, task_node);
421: #ifdef HAVE_LIBPTHREAD
422: pthread_mutex_unlock(&r->root_mtx[taskWRITE]);
423: #endif
424: if (r->root_hooks.hook_exec.exception && res[i].flags & EV_EOF) {
425: if (r->root_hooks.hook_exec.exception(r, (void*) EV_EOF)) {
426: task->task_type = taskUNUSE;
427: #ifdef HAVE_LIBPTHREAD
428: pthread_mutex_lock(&r->root_mtx[taskUNUSE]);
429: #endif
430: TAILQ_INSERT_TAIL(&r->root_unuse, task, task_node);
431: #ifdef HAVE_LIBPTHREAD
432: pthread_mutex_unlock(&r->root_mtx[taskUNUSE]);
433: #endif
434: } else {
435: task->task_type = taskREADY;
436: #ifdef HAVE_LIBPTHREAD
437: pthread_mutex_lock(&r->root_mtx[taskREADY]);
438: #endif
439: TAILQ_INSERT_TAIL(&r->root_ready, task, task_node);
440: #ifdef HAVE_LIBPTHREAD
441: pthread_mutex_unlock(&r->root_mtx[taskREADY]);
442: #endif
443: }
444: } else {
445: task->task_type = taskREADY;
446: #ifdef HAVE_LIBPTHREAD
447: pthread_mutex_lock(&r->root_mtx[taskREADY]);
448: #endif
449: TAILQ_INSERT_TAIL(&r->root_ready, task, task_node);
450: #ifdef HAVE_LIBPTHREAD
451: pthread_mutex_unlock(&r->root_mtx[taskREADY]);
452: #endif
453: }
454: break;
455: }
456: break;
457: case EVFILT_TIMER:
458: TAILQ_FOREACH_SAFE(task, &r->root_alarm, task_node, tmp) {
459: if ((uintptr_t) TASK_DATA(task) != ((uintptr_t) res[i].udata))
460: continue;
461: /* remove alarm handle */
462: #ifdef HAVE_LIBPTHREAD
463: pthread_mutex_lock(&r->root_mtx[taskALARM]);
464: #endif
465: TAILQ_REMOVE(&r->root_alarm, task, task_node);
466: #ifdef HAVE_LIBPTHREAD
467: pthread_mutex_unlock(&r->root_mtx[taskALARM]);
468: #endif
469: task->task_type = taskREADY;
470: #ifdef HAVE_LIBPTHREAD
471: pthread_mutex_lock(&r->root_mtx[taskREADY]);
472: #endif
473: TAILQ_INSERT_TAIL(&r->root_ready, task, task_node);
474: #ifdef HAVE_LIBPTHREAD
475: pthread_mutex_unlock(&r->root_mtx[taskREADY]);
476: #endif
477: break;
478: }
479: break;
480: }
481: if (kevent(r->root_kq, evt, 1, NULL, 0, &now) == -1) {
482: if (r->root_hooks.hook_exec.exception) {
483: if (r->root_hooks.hook_exec.exception(r, NULL))
484: return NULL;
485: } else
486: LOGERR;
487: }
488: }
489:
490: /* timer update & put in ready queue */
491: clock_gettime(CLOCK_MONOTONIC, &now);
492:
493: TAILQ_FOREACH_SAFE(task, &r->root_timer, task_node, tmp)
494: if (sched_timespeccmp(&now, &TASK_TS(task), -) >= 0) {
495: #ifdef HAVE_LIBPTHREAD
496: pthread_mutex_lock(&r->root_mtx[taskTIMER]);
497: #endif
498: TAILQ_REMOVE(&r->root_timer, task, task_node);
499: #ifdef HAVE_LIBPTHREAD
500: pthread_mutex_unlock(&r->root_mtx[taskTIMER]);
501: #endif
502: task->task_type = taskREADY;
503: #ifdef HAVE_LIBPTHREAD
504: pthread_mutex_lock(&r->root_mtx[taskREADY]);
505: #endif
506: TAILQ_INSERT_TAIL(&r->root_ready, task, task_node);
507: #ifdef HAVE_LIBPTHREAD
508: pthread_mutex_unlock(&r->root_mtx[taskREADY]);
509: #endif
510: }
511:
512: /* put eventlo priority task to ready queue, if there is no ready task or
513: reach max missed fetch-rotate */
514: if ((task = TAILQ_FIRST(&r->root_eventlo))) {
515: if (!TAILQ_FIRST(&r->root_ready) || r->root_eventlo_miss > MAX_EVENTLO_MISS) {
516: r->root_eventlo_miss = 0;
517:
518: #ifdef HAVE_LIBPTHREAD
519: pthread_mutex_lock(&r->root_mtx[taskEVENTLO]);
520: #endif
521: TAILQ_REMOVE(&r->root_eventlo, task, task_node);
522: #ifdef HAVE_LIBPTHREAD
523: pthread_mutex_unlock(&r->root_mtx[taskEVENTLO]);
524: #endif
525: task->task_type = taskREADY;
526: #ifdef HAVE_LIBPTHREAD
527: pthread_mutex_lock(&r->root_mtx[taskREADY]);
528: #endif
529: TAILQ_INSERT_TAIL(&r->root_ready, task, task_node);
530: #ifdef HAVE_LIBPTHREAD
531: pthread_mutex_unlock(&r->root_mtx[taskREADY]);
532: #endif
533: } else
534: r->root_eventlo_miss++;
535: } else
536: r->root_eventlo_miss = 0;
537:
538: /* OK, lets get ready task !!! */
539: task = TAILQ_FIRST(&r->root_ready);
540: if (!(task))
541: return NULL;
542:
543: #ifdef HAVE_LIBPTHREAD
544: pthread_mutex_lock(&r->root_mtx[taskREADY]);
545: #endif
546: TAILQ_REMOVE(&r->root_ready, task, task_node);
547: #ifdef HAVE_LIBPTHREAD
548: pthread_mutex_unlock(&r->root_mtx[taskREADY]);
549: #endif
550: task->task_type = taskUNUSE;
551: #ifdef HAVE_LIBPTHREAD
552: pthread_mutex_lock(&r->root_mtx[taskUNUSE]);
553: #endif
554: TAILQ_INSERT_TAIL(&r->root_unuse, task, task_node);
555: #ifdef HAVE_LIBPTHREAD
556: pthread_mutex_unlock(&r->root_mtx[taskUNUSE]);
557: #endif
558: return task;
559: }
560:
561: /*
562: * sched_hook_exception() - Default EXCEPTION hook
563: *
564: * @root = root task
565: * @arg = custom handling: if arg == EV_EOF or other value; default: arg == NULL log errno
566: * return: <0 errors and 0 ok
567: */
568: void *
569: sched_hook_exception(void *root, void *arg)
570: {
571: sched_root_task_t *r = root;
572:
573: if (!r)
574: return NULL;
575:
576: /* custom exception handling ... */
577: if (arg) {
578: if (arg == (void*) EV_EOF)
579: return NULL;
580: return (void*) -1; /* raise scheduler error!!! */
581: }
582:
583: /* if error hook exists */
584: if (r->root_hooks.hook_root.error)
585: return (r->root_hooks.hook_root.error(root, (void*) ((intptr_t) errno)));
586:
587: /* default case! */
588: LOGERR;
589: return NULL;
590: }
591:
592: /*
593: * sched_hook_condition() - Default CONDITION hook
594: *
595: * @root = root task
596: * @arg = killState from schedRun()
597: * return: NULL kill scheduler loop or !=NULL ok
598: */
599: void *
600: sched_hook_condition(void *root, void *arg)
601: {
602: sched_root_task_t *r = root;
603:
604: if (!r)
605: return NULL;
606:
607: return (void*) (r->root_cond - *(intptr_t*) arg);
608: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>