xref: /freebsd/usr.sbin/ypserv/yp_dblookup.c (revision 5521ff5a4d1929056e7ffc982fac3341ca54df7c)
1 /*
2  * Copyright (c) 1995
3  *	Bill Paul <wpaul@ctr.columbia.edu>.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. All advertising materials mentioning features or use of this software
14  *    must display the following acknowledgement:
15  *	This product includes software developed by Bill Paul.
16  * 4. Neither the name of the author nor the names of any co-contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL Bill Paul OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  */
32 
33 #ifndef lint
34 static const char rcsid[] =
35   "$FreeBSD$";
36 #endif /* not lint */
37 
38 #include <db.h>
39 #include <errno.h>
40 #include <fcntl.h>
41 #include <limits.h>
42 #include <paths.h>
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <unistd.h>
47 #include <sys/stat.h>
48 #include <sys/param.h>
49 #include <rpcsvc/yp.h>
50 #include "yp_extern.h"
51 
52 int ypdb_debug = 0;
53 enum ypstat yp_errno = YP_TRUE;
54 
55 #define PERM_SECURE (S_IRUSR|S_IWUSR)
56 HASHINFO openinfo = {
57 	4096,		/* bsize */
58 	32,		/* ffactor */
59 	256,		/* nelem */
60 	2048 * 512, 	/* cachesize */
61 	NULL,		/* hash */
62 	0,		/* lorder */
63 };
64 
65 #ifdef DB_CACHE
66 #include <sys/queue.h>
67 
68 #ifndef MAXDBS
69 #define MAXDBS 20
70 #endif
71 
72 static int numdbs = 0;
73 
74 struct dbent {
75 	DB *dbp;
76 	char *name;
77 	char *key;
78 	int size;
79 	int flags;
80 };
81 
82 static TAILQ_HEAD(circlehead, circleq_entry) qhead;
83 
84 struct circleq_entry {
85 	struct dbent *dbptr;
86 	TAILQ_ENTRY(circleq_entry) links;
87 };
88 
89 /*
90  * Initialize the circular queue.
91  */
92 void yp_init_dbs()
93 {
94 	TAILQ_INIT(&qhead);
95 	return;
96 }
97 
98 /*
99  * Dynamically allocate an entry for the circular queue.
100  * Return a NULL pointer on failure.
101  */
102 static struct circleq_entry *yp_malloc_qent()
103 {
104 	register struct circleq_entry *q;
105 
106 	q = (struct circleq_entry *)malloc(sizeof(struct circleq_entry));
107 	if (q == NULL) {
108 		yp_error("failed to malloc() circleq entry");
109 		return(NULL);
110 	}
111 	bzero((char *)q, sizeof(struct circleq_entry));
112 	q->dbptr = (struct dbent *)malloc(sizeof(struct dbent));
113 	if (q->dbptr == NULL) {
114 		yp_error("failed to malloc() circleq entry");
115 		free(q);
116 		return(NULL);
117 	}
118 	bzero((char *)q->dbptr, sizeof(struct dbent));
119 
120 	return(q);
121 }
122 
123 /*
124  * Free a previously allocated circular queue
125  * entry.
126  */
127 static void yp_free_qent(q)
128 	struct circleq_entry *q;
129 {
130 	/*
131 	 * First, close the database. In theory, this is also
132 	 * supposed to free the resources allocated by the DB
133 	 * package, including the memory pointed to by q->dbptr->key.
134 	 * This means we don't have to free q->dbptr->key here.
135 	 */
136 	if (q->dbptr->dbp) {
137 		(void)(q->dbptr->dbp->close)(q->dbptr->dbp);
138 		q->dbptr->dbp = NULL;
139 	}
140 	/*
141 	 * Then free the database name, which was strdup()'ed.
142 	 */
143 	free(q->dbptr->name);
144 
145 	/*
146 	 * Free the rest of the dbent struct.
147 	 */
148 	free(q->dbptr);
149 	q->dbptr = NULL;
150 
151 	/*
152 	 * Free the circleq struct.
153 	 */
154 	free(q);
155 	q = NULL;
156 
157 	return;
158 }
159 
160 /*
161  * Zorch a single entry in the dbent queue and release
162  * all its resources. (This always removes the last entry
163  * in the queue.)
164  */
165 static void yp_flush()
166 {
167 	register struct circleq_entry *qptr;
168 
169 	qptr = TAILQ_LAST(&qhead, circlehead);
170 	TAILQ_REMOVE(&qhead, qptr, links);
171 	yp_free_qent(qptr);
172 	numdbs--;
173 
174 	return;
175 }
176 
177 /*
178  * Close all databases, erase all database names and empty the queue.
179  */
180 void yp_flush_all()
181 {
182 	register struct circleq_entry *qptr;
183 
184 	while(!TAILQ_EMPTY(&qhead)) {
185 		qptr = TAILQ_FIRST(&qhead); /* save this */
186 		TAILQ_REMOVE(&qhead, qptr, links);
187 		yp_free_qent(qptr);
188 	}
189 	numdbs = 0;
190 
191 	return;
192 }
193 
194 static char *inter_string = "YP_INTERDOMAIN";
195 static char *secure_string = "YP_SECURE";
196 static int inter_sz = sizeof("YP_INTERDOMAIN") - 1;
197 static int secure_sz = sizeof("YP_SECURE") - 1;
198 
199 static int yp_setflags(dbp)
200 	DB *dbp;
201 {
202 	DBT key = { NULL, 0 }, data = { NULL, 0 };
203 	int flags = 0;
204 
205 	key.data = inter_string;
206 	key.size = inter_sz;
207 
208 	if (!(dbp->get)(dbp, &key, &data, 0))
209 		flags |= YP_INTERDOMAIN;
210 
211 	key.data = secure_string;
212 	key.size = secure_sz;
213 
214 	if (!(dbp->get)(dbp, &key, &data, 0))
215 		flags |= YP_SECURE;
216 
217 	return(flags);
218 }
219 
220 int yp_testflag(map, domain, flag)
221 	char *map;
222 	char *domain;
223 	int flag;
224 {
225 	char buf[MAXPATHLEN + 2];
226 	register struct circleq_entry *qptr;
227 
228 	if (map == NULL || domain == NULL)
229 		return(0);
230 
231 	strcpy(buf, domain);
232 	strcat(buf, "/");
233 	strcat(buf, map);
234 
235 	TAILQ_FOREACH(qptr, &qhead, links) {
236 		if (!strcmp(qptr->dbptr->name, buf)) {
237 			if (qptr->dbptr->flags & flag)
238 				return(1);
239 			else
240 				return(0);
241 		}
242 	}
243 
244 	if (yp_open_db_cache(domain, map, NULL, 0) == NULL)
245 		return(0);
246 
247 	if (TAILQ_FIRST(&qhead)->dbptr->flags & flag)
248 		return(1);
249 
250 	return(0);
251 }
252 
253 /*
254  * Add a DB handle and database name to the cache. We only maintain
255  * fixed number of entries in the cache, so if we're asked to store
256  * a new entry when all our slots are already filled, we have to kick
257  * out the entry in the last slot to make room.
258  */
259 static int yp_cache_db(dbp, name, size)
260 	DB *dbp;
261 	char *name;
262 	int size;
263 {
264 	register struct circleq_entry *qptr;
265 
266 	if (numdbs == MAXDBS) {
267 		if (ypdb_debug)
268 			yp_error("queue overflow -- releasing last slot");
269 		yp_flush();
270 	}
271 
272 	/*
273 	 * Allocate a new queue entry.
274 	 */
275 
276 	if ((qptr = yp_malloc_qent()) == NULL) {
277 		yp_error("failed to allocate a new cache entry");
278 		return(1);
279 	}
280 
281 	qptr->dbptr->dbp = dbp;
282 	qptr->dbptr->name = strdup(name);
283 	qptr->dbptr->size = size;
284 	qptr->dbptr->key = NULL;
285 
286 	qptr->dbptr->flags = yp_setflags(dbp);
287 
288 	TAILQ_INSERT_HEAD(&qhead, qptr, links);
289 	numdbs++;
290 
291 	return(0);
292 }
293 
294 /*
295  * Search the list for a database matching 'name.' If we find it,
296  * move it to the head of the list and return its DB handle. If
297  * not, just fail: yp_open_db_cache() will subsequently try to open
298  * the database itself and call yp_cache_db() to add it to the
299  * list.
300  *
301  * The search works like this:
302  *
303  * - The caller specifies the name of a database to locate. We try to
304  *   find an entry in our queue with a matching name.
305  *
306  * - If the caller doesn't specify a key or size, we assume that the
307  *   first entry that we encounter with a matching name is returned.
308  *   This will result in matches regardless of the key/size values
309  *   stored in the queue entry.
310  *
311  * - If the caller also specifies a key and length, we check to see
312  *   if the key and length saved in the queue entry also matches.
313  *   This lets us return a DB handle that's already positioned at the
314  *   correct location within a database.
315  *
316  * - Once we have a match, it gets migrated to the top of the queue
317  *   so that it will be easier to find if another request for
318  *   the same database comes in later.
319  */
320 static DB *yp_find_db(name, key, size)
321 	char *name;
322 	char *key;
323 	int size;
324 {
325 	register struct circleq_entry *qptr;
326 
327 	TAILQ_FOREACH(qptr, &qhead, links) {
328 		if (!strcmp(qptr->dbptr->name, name)) {
329 			if (size) {
330 				if (size != qptr->dbptr->size ||
331 				   strncmp(qptr->dbptr->key, key, size))
332 					continue;
333 			} else {
334 				if (qptr->dbptr->size)
335 					continue;
336 			}
337 			if (qptr != TAILQ_FIRST(&qhead)) {
338 				TAILQ_REMOVE(&qhead, qptr, links);
339 				TAILQ_INSERT_HEAD(&qhead, qptr, links);
340 			}
341 			return(qptr->dbptr->dbp);
342 		}
343 	}
344 
345 	return(NULL);
346 }
347 
348 /*
349  * Open a DB database and cache the handle for later use. We first
350  * check the cache to see if the required database is already open.
351  * If so, we fetch the handle from the cache. If not, we try to open
352  * the database and save the handle in the cache for later use.
353  */
354 DB *yp_open_db_cache(domain, map, key, size)
355 	const char *domain;
356 	const char *map;
357 	const char *key;
358 	const int size;
359 {
360 	DB *dbp = NULL;
361 	char buf[MAXPATHLEN + 2];
362 /*
363 	snprintf(buf, sizeof(buf), "%s/%s", domain, map);
364 */
365 	yp_errno = YP_TRUE;
366 
367 	strcpy(buf, domain);
368 	strcat(buf, "/");
369 	strcat(buf, map);
370 
371 	if ((dbp = yp_find_db((char *)&buf, key, size)) != NULL) {
372 		return(dbp);
373 	} else {
374 		if ((dbp = yp_open_db(domain, map)) != NULL) {
375 			if (yp_cache_db(dbp, (char *)&buf, size)) {
376 				(void)(dbp->close)(dbp);
377 				yp_errno = YP_YPERR;
378 				return(NULL);
379 			}
380 		}
381 	}
382 
383 	return (dbp);
384 }
385 #endif
386 
387 /*
388  * Open a DB database.
389  */
390 DB *yp_open_db(domain, map)
391 	const char *domain;
392 	const char *map;
393 {
394 	DB *dbp = NULL;
395 	char buf[MAXPATHLEN + 2];
396 
397 	yp_errno = YP_TRUE;
398 
399 	if (map[0] == '.' || strchr(map, '/')) {
400 		yp_errno = YP_BADARGS;
401 		return (NULL);
402 	}
403 
404 #ifdef DB_CACHE
405 	if (yp_validdomain(domain)) {
406 		yp_errno = YP_NODOM;
407 		return(NULL);
408 	}
409 #endif
410 	snprintf(buf, sizeof(buf), "%s/%s/%s", yp_dir, domain, map);
411 
412 #ifdef DB_CACHE
413 again:
414 #endif
415 	dbp = dbopen(buf,O_RDONLY, PERM_SECURE, DB_HASH, NULL);
416 
417 	if (dbp == NULL) {
418 		switch(errno) {
419 #ifdef DB_CACHE
420 		case ENFILE:
421 			/*
422 			 * We ran out of file descriptors. Nuke an
423 			 * open one and try again.
424 			 */
425 			yp_error("ran out of file descriptors");
426 			yp_flush();
427 			goto again;
428 			break;
429 #endif
430 		case ENOENT:
431 			yp_errno = YP_NOMAP;
432 			break;
433 		case EFTYPE:
434 			yp_errno = YP_BADDB;
435 			break;
436 		default:
437 			yp_errno = YP_YPERR;
438 			break;
439 		}
440 	}
441 
442 	return (dbp);
443 }
444 
445 /*
446  * Database access routines.
447  *
448  * - yp_get_record(): retrieve an arbitrary key/data pair given one key
449  *                 to match against.
450  *
451  * - yp_first_record(): retrieve first key/data base in a database.
452  *
453  * - yp_next_record(): retrieve key/data pair that sequentially follows
454  *                   the supplied key value in the database.
455  */
456 
457 #ifdef DB_CACHE
458 int yp_get_record(dbp,key,data,allow)
459 	DB *dbp;
460 #else
461 int yp_get_record(domain,map,key,data,allow)
462 	const char *domain;
463 	const char *map;
464 #endif
465 	const DBT *key;
466 	DBT *data;
467 	int allow;
468 {
469 #ifndef DB_CACHE
470 	DB *dbp;
471 #endif
472 	int rval = 0;
473 #ifndef DB_CACHE
474 	static unsigned char buf[YPMAXRECORD];
475 #endif
476 
477 	if (ypdb_debug)
478 		yp_error("looking up key [%.*s]",
479 			  key->size, key->data);
480 
481 	/*
482 	 * Avoid passing back magic "YP_*" entries unless
483 	 * the caller specifically requested them by setting
484 	 * the 'allow' flag.
485 	 */
486 	if (!allow && !strncmp(key->data, "YP_", 3))
487 		return(YP_NOKEY);
488 
489 #ifndef DB_CACHE
490 	if ((dbp = yp_open_db(domain, map)) == NULL) {
491 		return(yp_errno);
492 	}
493 #endif
494 
495 	if ((rval = (dbp->get)(dbp, key, data, 0)) != 0) {
496 #ifdef DB_CACHE
497 		TAILQ_FIRST(&qhead)->dbptr->size = 0;
498 #else
499 		(void)(dbp->close)(dbp);
500 #endif
501 		if (rval == 1)
502 			return(YP_NOKEY);
503 		else
504 			return(YP_BADDB);
505 	}
506 
507 	if (ypdb_debug)
508 		yp_error("result of lookup: key: [%.*s] data: [%.*s]",
509 			 key->size, key->data, data->size, data->data);
510 
511 #ifdef DB_CACHE
512 	if (TAILQ_FIRST(&qhead)->dbptr->size) {
513 		TAILQ_FIRST(&qhead)->dbptr->key = "";
514 		TAILQ_FIRST(&qhead)->dbptr->size = 0;
515 	}
516 #else
517 	bcopy((char *)data->data, (char *)&buf, data->size);
518 	data->data = (void *)&buf;
519 	(void)(dbp->close)(dbp);
520 #endif
521 
522 	return(YP_TRUE);
523 }
524 
525 int yp_first_record(dbp,key,data,allow)
526 	const DB *dbp;
527 	DBT *key;
528 	DBT *data;
529 	int allow;
530 {
531 	int rval;
532 #ifndef DB_CACHE
533 	static unsigned char buf[YPMAXRECORD];
534 #endif
535 
536 	if (ypdb_debug)
537 		yp_error("retrieving first key in map");
538 
539 	if ((rval = (dbp->seq)(dbp,key,data,R_FIRST)) != 0) {
540 #ifdef DB_CACHE
541 		TAILQ_FIRST(&qhead)->dbptr->size = 0;
542 #endif
543 		if (rval == 1)
544 			return(YP_NOKEY);
545 		else
546 			return(YP_BADDB);
547 	}
548 
549 	/* Avoid passing back magic "YP_*" records. */
550 	while (!strncmp(key->data, "YP_", 3) && !allow) {
551 		if ((rval = (dbp->seq)(dbp,key,data,R_NEXT)) != 0) {
552 #ifdef DB_CACHE
553 			TAILQ_FIRST(&qhead)->dbptr->size = 0;
554 #endif
555 			if (rval == 1)
556 				return(YP_NOKEY);
557 			else
558 				return(YP_BADDB);
559 		}
560 	}
561 
562 	if (ypdb_debug)
563 		yp_error("result of lookup: key: [%.*s] data: [%.*s]",
564 			 key->size, key->data, data->size, data->data);
565 
566 #ifdef DB_CACHE
567 	if (TAILQ_FIRST(&qhead)->dbptr->size) {
568 		TAILQ_FIRST(&qhead)->dbptr->key = key->data;
569 		TAILQ_FIRST(&qhead)->dbptr->size = key->size;
570 	}
571 #else
572 	bcopy((char *)data->data, (char *)&buf, data->size);
573 	data->data = (void *)&buf;
574 #endif
575 
576 	return(YP_TRUE);
577 }
578 
579 int yp_next_record(dbp,key,data,all,allow)
580 	const DB *dbp;
581 	DBT *key;
582 	DBT *data;
583 	int all;
584 	int allow;
585 {
586 	static DBT lkey = { NULL, 0 };
587 	static DBT ldata = { NULL, 0 };
588 	int rval;
589 #ifndef DB_CACHE
590 	static unsigned char keybuf[YPMAXRECORD];
591 	static unsigned char datbuf[YPMAXRECORD];
592 #endif
593 
594 	if (key == NULL || !key->size || key->data == NULL) {
595 		rval = yp_first_record(dbp,key,data,allow);
596 		if (rval == YP_NOKEY)
597 			return(YP_NOMORE);
598 		else {
599 #ifdef DB_CACHE
600 			TAILQ_FIRST(&qhead)->dbptr->key = key->data;
601 			TAILQ_FIRST(&qhead)->dbptr->size = key->size;
602 #endif
603 			return(rval);
604 		}
605 	}
606 
607 	if (ypdb_debug)
608 		yp_error("retrieving next key, previous was: [%.*s]",
609 			  key->size, key->data);
610 
611 	if (!all) {
612 #ifdef DB_CACHE
613 		if (TAILQ_FIRST(&qhead)->dbptr->key == NULL) {
614 #endif
615 			(dbp->seq)(dbp,&lkey,&ldata,R_FIRST);
616 			while (key->size != lkey.size ||
617 			    strncmp((char *)key->data, lkey.data,
618 			    (int)key->size))
619 				if ((dbp->seq)(dbp,&lkey,&ldata,R_NEXT)) {
620 #ifdef DB_CACHE
621 					TAILQ_FIRST(&qhead)->dbptr->size = 0;
622 #endif
623 					return(YP_NOKEY);
624 				}
625 
626 #ifdef DB_CACHE
627 		}
628 #endif
629 	}
630 
631 	if ((dbp->seq)(dbp,key,data,R_NEXT)) {
632 #ifdef DB_CACHE
633 		TAILQ_FIRST(&qhead)->dbptr->size = 0;
634 #endif
635 		return(YP_NOMORE);
636 	}
637 
638 	/* Avoid passing back magic "YP_*" records. */
639 	while (!strncmp(key->data, "YP_", 3) && !allow)
640 		if ((dbp->seq)(dbp,key,data,R_NEXT)) {
641 #ifdef DB_CACHE
642 		TAILQ_FIRST(&qhead)->dbptr->size = 0;
643 #endif
644 			return(YP_NOMORE);
645 		}
646 
647 	if (ypdb_debug)
648 		yp_error("result of lookup: key: [%.*s] data: [%.*s]",
649 			 key->size, key->data, data->size, data->data);
650 
651 #ifdef DB_CACHE
652 	if (TAILQ_FIRST(&qhead)->dbptr->size) {
653 		TAILQ_FIRST(&qhead)->dbptr->key = key->data;
654 		TAILQ_FIRST(&qhead)->dbptr->size = key->size;
655 	}
656 #else
657 	bcopy((char *)key->data, (char *)&keybuf, key->size);
658 	lkey.data = (void *)&keybuf;
659 	lkey.size = key->size;
660 	bcopy((char *)data->data, (char *)&datbuf, data->size);
661 	data->data = (void *)&datbuf;
662 #endif
663 
664 	return(YP_TRUE);
665 }
666 
667 #ifdef DB_CACHE
668 /*
669  * Database glue functions.
670  */
671 
672 static DB *yp_currmap_db = NULL;
673 static int yp_allow_db = 0;
674 
675 ypstat yp_select_map(map, domain, key, allow)
676 	char *map;
677 	char *domain;
678 	keydat *key;
679 	int allow;
680 {
681 	if (key == NULL)
682 		yp_currmap_db = yp_open_db_cache(domain, map, NULL, 0);
683 	else
684 		yp_currmap_db = yp_open_db_cache(domain, map,
685 						 key->keydat_val,
686 						 key->keydat_len);
687 
688 	yp_allow_db = allow;
689 	return(yp_errno);
690 }
691 
692 ypstat yp_getbykey(key, val)
693 	keydat *key;
694 	valdat *val;
695 {
696 	DBT db_key = { NULL, 0 }, db_val = { NULL, 0 };
697 	ypstat rval;
698 
699 	db_key.data = key->keydat_val;
700 	db_key.size = key->keydat_len;
701 
702 	rval = yp_get_record(yp_currmap_db,
703 				&db_key, &db_val, yp_allow_db);
704 
705 	if (rval == YP_TRUE) {
706 		val->valdat_val = db_val.data;
707 		val->valdat_len = db_val.size;
708 	}
709 
710 	return(rval);
711 }
712 
713 ypstat yp_firstbykey(key, val)
714 	keydat *key;
715 	valdat *val;
716 {
717 	DBT db_key = { NULL, 0 }, db_val = { NULL, 0 };
718 	ypstat rval;
719 
720 	rval = yp_first_record(yp_currmap_db, &db_key, &db_val, yp_allow_db);
721 
722 	if (rval == YP_TRUE) {
723 		key->keydat_val = db_key.data;
724 		key->keydat_len = db_key.size;
725 		val->valdat_val = db_val.data;
726 		val->valdat_len = db_val.size;
727 	}
728 
729 	return(rval);
730 }
731 
732 ypstat yp_nextbykey(key, val)
733 	keydat *key;
734 	valdat *val;
735 {
736 	DBT db_key = { NULL, 0 }, db_val = { NULL, 0 };
737 	ypstat rval;
738 
739 	db_key.data = key->keydat_val;
740 	db_key.size = key->keydat_len;
741 
742 	rval = yp_next_record(yp_currmap_db, &db_key, &db_val, 0, yp_allow_db);
743 
744 	if (rval == YP_TRUE) {
745 		key->keydat_val = db_key.data;
746 		key->keydat_len = db_key.size;
747 		val->valdat_val = db_val.data;
748 		val->valdat_len = db_val.size;
749 	}
750 
751 	return(rval);
752 }
753 #endif
754