1: /* Gforth support functions
2:
3: Copyright (C) 1995,1996,1997,1998,2000,2003,2004,2006 Free Software Foundation, Inc.
4:
5: This file is part of Gforth.
6:
7: Gforth is free software; you can redistribute it and/or
8: modify it under the terms of the GNU General Public License
9: as published by the Free Software Foundation; either version 2
10: of the License, or (at your option) any later version.
11:
12: This program is distributed in the hope that it will be useful,
13: but WITHOUT ANY WARRANTY; without even the implied warranty of
14: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15: GNU General Public License for more details.
16:
17: You should have received a copy of the GNU General Public License
18: along with this program; if not, write to the Free Software
19: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
20: */
21:
22: #include "config.h"
23: #include "forth.h"
24: #include "io.h"
25: #include <stdlib.h>
26: #include <string.h>
27: #include <sys/time.h>
28: #include <unistd.h>
29: #include <pwd.h>
30: #include <assert.h>
31: #ifndef STANDALONE
32: #include <dirent.h>
33: #include <math.h>
34: #include <ctype.h>
35: #include <errno.h>
36: #endif
37:
38: #ifdef HAS_FILE
39: char *cstr(Char *from, UCell size, int clear)
40: /* return a C-string corresponding to the Forth string ( FROM SIZE ).
41: the C-string lives until the next call of cstr with CLEAR being true */
42: {
43: static struct cstr_buffer {
44: char *buffer;
45: size_t size;
46: } *buffers=NULL;
47: static int nbuffers=0;
48: static int used=0;
49: struct cstr_buffer *b;
50:
51: if (buffers==NULL)
52: buffers=malloc(0);
53: if (clear)
54: used=0;
55: if (used>=nbuffers) {
56: buffers=realloc(buffers,sizeof(struct cstr_buffer)*(used+1));
57: buffers[used]=(struct cstr_buffer){malloc(0),0};
58: nbuffers=used+1;
59: }
60: b=&buffers[used];
61: if (size+1 > b->size) {
62: b->buffer = realloc(b->buffer,size+1);
63: b->size = size+1;
64: }
65: memcpy(b->buffer,from,size);
66: b->buffer[size]='\0';
67: used++;
68: return b->buffer;
69: }
70:
71: char *tilde_cstr(Char *from, UCell size, int clear)
72: /* like cstr(), but perform tilde expansion on the string */
73: {
74: char *s1,*s2;
75: int s1_len, s2_len;
76: struct passwd *getpwnam (), *user_entry;
77:
78: if (size<1 || from[0]!='~')
79: return cstr(from, size, clear);
80: if (size<2 || from[1]=='/') {
81: s1 = (char *)getenv ("HOME");
82: if(s1 == NULL)
83: #if defined(_WIN32) || defined (MSDOS)
84: s1 = (char *)getenv ("TEMP");
85: if(s1 == NULL)
86: s1 = (char *)getenv ("TMP");
87: if(s1 == NULL)
88: #endif
89: s1 = "";
90: s2 = (char *)from+1;
91: s2_len = size-1;
92: } else {
93: UCell i;
94: for (i=1; i<size && from[i]!='/'; i++)
95: ;
96: if (i==2 && from[1]=='+') /* deal with "~+", i.e., the wd */
97: return cstr(from+3, size<3?0:size-3,clear);
98: {
99: char user[i];
100: memcpy(user,from+1,i-1);
101: user[i-1]='\0';
102: user_entry=getpwnam(user);
103: }
104: if (user_entry==NULL)
105: return cstr(from, size, clear);
106: s1 = user_entry->pw_dir;
107: s2 = (char *)from+i;
108: s2_len = size-i;
109: }
110: s1_len = strlen(s1);
111: if (s1_len>1 && s1[s1_len-1]=='/')
112: s1_len--;
113: {
114: char path[s1_len+s2_len];
115: memcpy(path,s1,s1_len);
116: memcpy(path+s1_len,s2,s2_len);
117: return cstr((Char *)path,s1_len+s2_len,clear);
118: }
119: }
120: #endif
121:
122: DCell timeval2us(struct timeval *tvp)
123: {
124: #ifndef BUGGY_LONG_LONG
125: return (tvp->tv_sec*(DCell)1000000)+tvp->tv_usec;
126: #else
127: DCell d2;
128: DCell d1=mmul(tvp->tv_sec,1000000);
129: d2.lo = d1.lo+tvp->tv_usec;
130: d2.hi = d1.hi + (d2.lo<d1.lo);
131: return d2;
132: #endif
133: }
134:
135: DCell double2ll(Float r)
136: {
137: #ifndef BUGGY_LONG_LONG
138: return (DCell)(r);
139: #else
140: double ldexp(double x, int exp);
141: DCell d;
142: if (r<0) {
143: d.hi = ldexp(-r,-(int)(CELL_BITS));
144: d.lo = (-r)-ldexp((Float)d.hi,CELL_BITS);
145: return dnegate(d);
146: }
147: d.hi = ldexp(r,-(int)(CELL_BITS));
148: d.lo = r-ldexp((Float)d.hi,CELL_BITS);
149: return d;
150: #endif
151: }
152:
153: void cmove(Char *c_from, Char *c_to, UCell u)
154: {
155: while (u-- > 0)
156: *c_to++ = *c_from++;
157: }
158:
159: void cmove_up(Char *c_from, Char *c_to, UCell u)
160: {
161: while (u-- > 0)
162: c_to[u] = c_from[u];
163: }
164:
165: Cell compare(Char *c_addr1, UCell u1, Char *c_addr2, UCell u2)
166: {
167: Cell n;
168:
169: n = memcmp(c_addr1, c_addr2, u1<u2 ? u1 : u2);
170: if (n==0)
171: n = u1-u2;
172: if (n<0)
173: n = -1;
174: else if (n>0)
175: n = 1;
176: return n;
177: }
178:
179: Cell memcasecmp(const Char *s1, const Char *s2, Cell n)
180: {
181: Cell i;
182:
183: for (i=0; i<n; i++) {
184: Char c1=toupper(s1[i]);
185: Char c2=toupper(s2[i]);
186: if (c1 != c2) {
187: if (c1 < c2)
188: return -1;
189: else
190: return 1;
191: }
192: }
193: return 0;
194: }
195:
196: Cell capscompare(Char *c_addr1, UCell u1, Char *c_addr2, UCell u2)
197: {
198: Cell n;
199:
200: n = memcasecmp(c_addr1, c_addr2, u1<u2 ? u1 : u2);
201: if (n==0)
202: n = u1-u2;
203: if (n<0)
204: n = -1;
205: else if (n>0)
206: n = 1;
207: return n;
208: }
209:
210: struct Longname *listlfind(Char *c_addr, UCell u, struct Longname *longname1)
211: {
212: for (; longname1 != NULL; longname1 = (struct Longname *)(longname1->next))
213: if ((UCell)LONGNAME_COUNT(longname1)==u &&
214: memcasecmp(c_addr, (Char *)(longname1->name), u)== 0 /* or inline? */)
215: break;
216: return longname1;
217: }
218:
219: struct Longname *hashlfind(Char *c_addr, UCell u, Cell *a_addr)
220: {
221: struct Longname *longname1;
222:
223: while(a_addr != NULL) {
224: longname1=(struct Longname *)(a_addr[1]);
225: a_addr=(Cell *)(a_addr[0]);
226: if ((UCell)LONGNAME_COUNT(longname1)==u &&
227: memcasecmp(c_addr, (Char *)(longname1->name), u)== 0 /* or inline? */) {
228: return longname1;
229: }
230: }
231: return NULL;
232: }
233:
234: struct Longname *tablelfind(Char *c_addr, UCell u, Cell *a_addr)
235: {
236: struct Longname *longname1;
237: while(a_addr != NULL) {
238: longname1=(struct Longname *)(a_addr[1]);
239: a_addr=(Cell *)(a_addr[0]);
240: if ((UCell)LONGNAME_COUNT(longname1)==u &&
241: memcmp(c_addr, longname1->name, u)== 0 /* or inline? */) {
242: return longname1;
243: }
244: }
245: return NULL;
246: }
247:
248: UCell hashkey1(Char *c_addr, UCell u, UCell ubits)
249: /* this hash function rotates the key at every step by rot bits within
250: ubits bits and xors it with the character. This function does ok in
251: the chi-sqare-test. Rot should be <=7 (preferably <=5) for
252: ASCII strings (larger if ubits is large), and should share no
253: divisors with ubits.
254: */
255: {
256: static char rot_values[] = {5,0,1,2,3,4,5,5,5,5,3,5,5,5,5,7,5,5,5,5,7,5,5,5,5,6,5,5,5,5,7,5,5};
257: unsigned rot = rot_values[ubits];
258: Char *cp = c_addr;
259: UCell ukey;
260:
261: for (ukey=0; cp<c_addr+u; cp++)
262: ukey = ((((ukey<<rot) | (ukey>>(ubits-rot)))
263: ^ toupper(*cp))
264: & ((1<<ubits)-1));
265: return ukey;
266: }
267:
268: struct Cellpair parse_white(Char *c_addr1, UCell u1)
269: {
270: /* use !isgraph instead of isspace? */
271: struct Cellpair result;
272: Char *c_addr2;
273: Char *endp = c_addr1+u1;
274: while (c_addr1<endp && isspace(*c_addr1))
275: c_addr1++;
276: if (c_addr1<endp) {
277: for (c_addr2 = c_addr1; c_addr1<endp && !isspace(*c_addr1); c_addr1++)
278: ;
279: result.n1 = (Cell)c_addr2;
280: result.n2 = c_addr1-c_addr2;
281: } else {
282: result.n1 = (Cell)c_addr1;
283: result.n2 = 0;
284: }
285: return result;
286: }
287:
288: #ifdef HAS_FILE
289: Cell rename_file(Char *c_addr1, UCell u1, Char *c_addr2, UCell u2)
290: {
291: char *s1=tilde_cstr(c_addr2, u2, 1);
292: return IOR(rename(tilde_cstr(c_addr1, u1, 0), s1)==-1);
293: }
294:
295: struct Cellquad read_line(Char *c_addr, UCell u1, Cell wfileid)
296: {
297: UCell u2, u3;
298: Cell flag, wior;
299: Cell c;
300: struct Cellquad r;
301:
302: flag=-1;
303: u3=0;
304: for(u2=0; u2<u1; u2++) {
305: c = getc((FILE *)wfileid);
306: u3++;
307: if (c=='\n') break;
308: if (c=='\r') {
309: if ((c = getc((FILE *)wfileid))!='\n')
310: ungetc(c,(FILE *)wfileid);
311: else
312: u3++;
313: break;
314: }
315: if (c==EOF) {
316: flag=FLAG(u2!=0);
317: break;
318: }
319: c_addr[u2] = (Char)c;
320: }
321: wior=FILEIO(ferror((FILE *)wfileid));
322: r.n1 = u2;
323: r.n2 = flag;
324: r.n3 = u3;
325: r.n4 = wior;
326: return r;
327: }
328:
329: struct Cellpair file_status(Char *c_addr, UCell u)
330: {
331: struct Cellpair r;
332: Cell wfam;
333: Cell wior;
334: char *filename=tilde_cstr(c_addr, u, 1);
335:
336: if (access (filename, F_OK) != 0) {
337: wfam=0;
338: wior=IOR(1);
339: }
340: else if (access (filename, R_OK | W_OK) == 0) {
341: wfam=2; /* r/w */
342: wior=0;
343: }
344: else if (access (filename, R_OK) == 0) {
345: wfam=0; /* r/o */
346: wior=0;
347: }
348: else if (access (filename, W_OK) == 0) {
349: wfam=4; /* w/o */
350: wior=0;
351: }
352: else {
353: wfam=1; /* well, we cannot access the file, but better deliver a
354: legal access mode (r/o bin), so we get a decent error
355: later upon open. */
356: wior=0;
357: }
358: r.n1 = wfam;
359: r.n2 = wior;
360: return r;
361: }
362:
363: Cell to_float(Char *c_addr, UCell u, Float *rp)
364: {
365: /* convertible string := <significand>[<exponent>]
366: <significand> := [<sign>]{<digits>[.<digits0>] | .<digits> }
367: <exponent> := <marker><digits0>
368: <marker> := {<e-form> | <sign-form>}
369: <e-form> := <e-char>[<sign-form>]
370: <sign-form> := { + | - }
371: <e-char> := { D | d | E | e }
372: */
373: Char *s = c_addr;
374: Char c;
375: Char *send = c_addr+u;
376: UCell ndigits = 0;
377: UCell ndots = 0;
378: UCell edigits = 0;
379: char cnum[u+3]; /* append at most "e0\0" */
380: char *t=cnum;
381: char *endconv;
382: Float r;
383:
384: if (s >= send) /* treat empty string as 0e */
385: goto return0;
386: switch ((c=*s)) {
387: case ' ':
388: /* "A string of blanks should be treated as a special case
389: representing zero."*/
390: for (s++; s<send; )
391: if (*s++ != ' ')
392: goto error;
393: goto return0;
394: case '-':
395: case '+': *t++ = c; s++; goto aftersign;
396: }
397: aftersign:
398: if (s >= send)
399: goto exponent;
400: switch (c=*s) {
401: case '0' ... '9': *t++ = c; ndigits++; s++; goto aftersign;
402: case '.': *t++ = c; ndots++; s++; goto aftersign;
403: default: goto exponent;
404: }
405: exponent:
406: if (ndigits < 1 || ndots > 1)
407: goto error;
408: *t++ = 'E';
409: if (s >= send)
410: goto done;
411: switch (c=*s) {
412: case 'D':
413: case 'd':
414: case 'E':
415: case 'e': s++; break;
416: }
417: if (s >= send)
418: goto done;
419: switch (c=*s) {
420: case '+':
421: case '-': *t++ = c; s++; break;
422: }
423: edigits0:
424: if (s >= send)
425: goto done;
426: switch (c=*s) {
427: case '0' ... '9': *t++ = c; s++; edigits++; goto edigits0;
428: default: goto error;
429: }
430: done:
431: if (edigits == 0)
432: *t++ = '0';
433: *t++ = '\0';
434: assert(t-cnum <= u+3);
435: r = strtod(cnum, &endconv);
436: assert(*endconv == '\0');
437: *rp = r;
438: return -1;
439: return0:
440: *rp = 0.0;
441: return -1;
442: error:
443: *rp = 0.0;
444: return 0;
445: }
446: #endif
447:
448: #ifdef HAS_FLOATING
449: Float v_star(Float *f_addr1, Cell nstride1, Float *f_addr2, Cell nstride2, UCell ucount)
450: {
451: Float r;
452:
453: for (r=0.; ucount>0; ucount--) {
454: r += *f_addr1 * *f_addr2;
455: f_addr1 = (Float *)(((Address)f_addr1)+nstride1);
456: f_addr2 = (Float *)(((Address)f_addr2)+nstride2);
457: }
458: return r;
459: }
460:
461: void faxpy(Float ra, Float *f_x, Cell nstridex, Float *f_y, Cell nstridey, UCell ucount)
462: {
463: for (; ucount>0; ucount--) {
464: *f_y += ra * *f_x;
465: f_x = (Float *)(((Address)f_x)+nstridex);
466: f_y = (Float *)(((Address)f_y)+nstridey);
467: }
468: }
469: #endif
470:
471: UCell lshift(UCell u1, UCell n)
472: {
473: return u1 << n;
474: }
475:
476: UCell rshift(UCell u1, UCell n)
477: {
478: return u1 >> n;
479: }
480:
481: #ifndef STANDALONE
482: int gforth_system(Char *c_addr, UCell u)
483: {
484: int retval;
485: char *prefix = getenv("GFORTHSYSTEMPREFIX") ? : DEFAULTSYSTEMPREFIX;
486: size_t prefixlen = strlen(prefix);
487: char buffer[prefixlen+u+1];
488: #ifndef MSDOS
489: int old_tp=terminal_prepped;
490: deprep_terminal();
491: #endif
492: memcpy(buffer,prefix,prefixlen);
493: memcpy(buffer+prefixlen,c_addr,u);
494: buffer[prefixlen+u]='\0';
495: retval=system(buffer); /* ~ expansion on first part of string? */
496: #ifndef MSDOS
497: if (old_tp)
498: prep_terminal();
499: #endif
500: return retval;
501: }
502:
503: void gforth_ms(UCell u)
504: {
505: #ifdef HAVE_NANOSLEEP
506: struct timespec time_req;
507: time_req.tv_sec=u/1000;
508: time_req.tv_nsec=1000000*(u%1000);
509: while(nanosleep(&time_req, &time_req));
510: #else /* !defined(HAVE_NANOSLEEP) */
511: struct timeval timeout;
512: timeout.tv_sec=u/1000;
513: timeout.tv_usec=1000*(u%1000);
514: (void)select(0,0,0,0,&timeout);
515: #endif /* !defined(HAVE_NANOSLEEP) */
516: }
517: #endif /* !defined(STANDALONE) */
518:
519:
520: /* mixed division support; should usually be faster than gcc's
521: double-by-double division (and gcc typically does not generate
522: double-by-single division because of exception handling issues. If
523: the architecture has double-by-single division, you should define
524: ASM_SM_SLASH_REM and ASM_UM_SLASH_MOD appropriately. */
525:
526: /* Type definitions for longlong.h (according to the comments at the start):
527: declarations taken from libgcc2.h */
528:
529: typedef unsigned int UQItype __attribute__ ((mode (QI)));
530: typedef int SItype __attribute__ ((mode (SI)));
531: typedef unsigned int USItype __attribute__ ((mode (SI)));
532: typedef int DItype __attribute__ ((mode (DI)));
533: typedef unsigned int UDItype __attribute__ ((mode (DI)));
534: typedef UCell UWtype;
535: #if (SIZEOF_CHAR_P == 4)
536: typedef unsigned int UHWtype __attribute__ ((mode (HI)));
537: #endif
538: #if (SIZEOF_CHAR_P == 8)
539: typedef USItype UHWtype;
540: #endif
541: #ifndef BUGGY_LONG_LONG
542: typedef UDCell UDWtype;
543: #endif
544: #define W_TYPE_SIZE (SIZEOF_CHAR_P * 8)
545:
546: #include "longlong.h"
547:
548: #if defined(udiv_qrnnd) && !defined(__alpha) && UDIV_NEEDS_NORMALIZATION
549: static Cell MAYBE_UNUSED nlz(UCell x)
550: /* number of leading zeros, adapted from "Hacker's Delight" */
551: {
552: Cell n;
553:
554: #if !defined(COUNT_LEADING_ZEROS_0)
555: if (x == 0) return(CELL_BITS);
556: #endif
557: #if defined(count_leading_zeros)
558: count_leading_zeros(n,x);
559: #else
560: n = 0;
561: #if (SIZEOF_CHAR_P > 4)
562: if (x <= 0xffffffff)
563: n+=32;
564: else
565: x >>= 32;
566: #endif
567: if (x <= 0x0000FFFF) {n = n +16; x = x <<16;}
568: if (x <= 0x00FFFFFF) {n = n + 8; x = x << 8;}
569: if (x <= 0x0FFFFFFF) {n = n + 4; x = x << 4;}
570: if (x <= 0x3FFFFFFF) {n = n + 2; x = x << 2;}
571: if (x <= 0x7FFFFFFF) {n = n + 1;}
572: #endif
573: return n;
574: }
575: #endif /*defined(udiv_qrnnd) && !defined(__alpha) && UDIV_NEEDS_NORMALIZATION*/
576:
577: #if !defined(ASM_UM_SLASH_MOD)
578: UDCell umdiv (UDCell u, UCell v)
579: /* Divide unsigned double by single precision using shifts and subtracts.
580: Return quotient in lo, remainder in hi. */
581: {
582: UDCell res;
583: #if defined(udiv_qrnnd) && !defined(__alpha)
584: #if 0
585: This code is slower on an Alpha (timings with gcc-3.3.5):
586: other this
587: */ 5205 ms 5741 ms
588: */mod 5167 ms 5717 ms
589: fm/mod 5467 ms 5312 ms
590: sm/rem 4734 ms 5278 ms
591: um/mod 4490 ms 5020 ms
592: m*/ 15557 ms 17151 ms
593: #endif /* 0 */
594: UCell q,r,u0,u1;
595: UCell MAYBE_UNUSED lz;
596:
597: vm_ud2twoCell(u,u0,u1);
598: if (v==0)
599: throw(BALL_DIVZERO);
600: if (u1>=v)
601: throw(BALL_RESULTRANGE);
602: #if UDIV_NEEDS_NORMALIZATION
603: lz = nlz(v);
604: v <<= lz;
605: u = UDLSHIFT(u,lz);
606: vm_ud2twoCell(u,u0,u1);
607: #endif
608: udiv_qrnnd(q,r,u1,u0,v);
609: #if UDIV_NEEDS_NORMALIZATION
610: r >>= lz;
611: #endif
612: vm_twoCell2ud(q,r,res);
613: #else /* !(defined(udiv_qrnnd) && !defined(__alpha)) */
614: /* simple restoring subtract-and-shift algorithm, might be faster on Alpha */
615: int i = CELL_BITS, c = 0;
616: UCell q = 0;
617: UCell h, l;
618:
619: vm_ud2twoCell(u,l,h);
620: if (v==0)
621: throw(BALL_DIVZERO);
622: if (h>=v)
623: throw(BALL_RESULTRANGE);
624: for (;;)
625: {
626: if (c || h >= v)
627: {
628: q++;
629: h -= v;
630: }
631: if (--i < 0)
632: break;
633: c = HIGHBIT (h);
634: h <<= 1;
635: h += HIGHBIT (l);
636: l <<= 1;
637: q <<= 1;
638: }
639: vm_twoCell2ud(q,h,res);
640: #endif /* !(defined(udiv_qrnnd) && !defined(__alpha)) */
641: return res;
642: }
643: #endif
644:
645: #if !defined(ASM_SM_SLASH_REM)
646: #if defined(ASM_UM_SLASH_MOD)
647: /* define it if it is not defined above */
648: static UDCell MAYBE_UNUSED umdiv (UDCell u, UCell v)
649: {
650: UDCell res;
651: UCell u0,u1;
652: vm_ud2twoCell(u,u0,u1);
653: ASM_UM_SLASH_MOD(u0,u1,v,r,q);
654: vm_twoCell2ud(q,r,res);
655: return res;
656: }
657: #endif /* defined(ASM_UM_SLASH_MOD) */
658:
659: #ifndef BUGGY_LONG_LONG
660: #define dnegate(x) (-(x))
661: #endif
662:
663: DCell smdiv (DCell num, Cell denom)
664: /* symmetric divide procedure, mixed prec */
665: {
666: DCell res;
667: #if defined(sdiv_qrnnd)
668: #warning "using sdiv_qrnnd"
669: Cell u1,q,r
670: UCell u0;
671: UCell MAYBE_UNUSED lz;
672:
673: vm_d2twoCell(u,u0,u1);
674: if (v==0)
675: throw(BALL_DIVZERO);
676: if (u1>=v)
677: throw(BALL_RESULTRANGE);
678: sdiv_qrnnd(q,r,u1,u0,v);
679: vm_twoCell2d(q,r,res);
680: #else
681: UDCell ures;
682: UCell l, q, r;
683: Cell h;
684: Cell denomsign=denom;
685:
686: vm_d2twoCell(num,l,h);
687: if (h < 0)
688: num = dnegate (num);
689: if (denomsign < 0)
690: denom = -denom;
691: ures = umdiv(D2UD(num), denom);
692: vm_ud2twoCell(ures,q,r);
693: if ((h^denomsign)<0) {
694: q = -q;
695: if (((Cell)q) > 0) /* note: == 0 is possible */
696: throw(BALL_RESULTRANGE);
697: } else {
698: if (((Cell)q) < 0)
699: throw(BALL_RESULTRANGE);
700: }
701: if (h<0)
702: r = -r;
703: vm_twoCell2d(q,r,res);
704: #endif
705: return res;
706: }
707:
708: DCell fmdiv (DCell num, Cell denom)
709: /* floored divide procedure, mixed prec */
710: {
711: /* I have this technique from Andrew Haley */
712: DCell res;
713: UDCell ures;
714: Cell denomsign=denom;
715: Cell numsign;
716: UCell q,r;
717:
718: if (denom < 0) {
719: denom = -denom;
720: num = dnegate(num);
721: }
722: numsign = DHI(num);
723: if (numsign < 0)
724: DHI_IS(num,DHI(num)+denom);
725: ures = umdiv(D2UD(num),denom);
726: vm_ud2twoCell(ures,q,r);
727: if ((numsign^((Cell)q)) < 0)
728: throw(BALL_RESULTRANGE);
729: if (denomsign<0)
730: r = -r;
731: vm_twoCell2d(q,r,res);
732: return res;
733: }
734: #endif
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>