xref: /illumos-gate/usr/src/lib/libnisdb/db_table.cc (revision 24da5b34f49324ed742a340010ed5bd3d4e06625)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  *	db_table.cc
24  *
25  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
26  * Use is subject to license terms.
27  */
28 
29 #pragma ident	"%Z%%M%	%I%	%E% SMI"
30 
31 #include <stdio.h>
32 #include <malloc.h>
33 #include <string.h>
34 #include <stdlib.h>		/* srand48() */
35 #include <lber.h>
36 #include <ldap.h>
37 #include "db_headers.h"
38 #include "db_table.h"
39 #include "db_pickle.h"    /* for dump and load */
40 #include "db_entry.h"
41 #include "nisdb_mt.h"
42 
43 #include "ldap_parse.h"
44 #include "ldap_util.h"
45 #include "ldap_map.h"
46 #include "ldap_xdr.h"
47 #include "nis_hashitem.h"
48 #include "nisdb_ldap.h"
49 #include "nis_parse_ldap_conf.h"
50 
51 static time_t	maxTimeT;
52 
53 /*
54  * Find the largest (positive) value of time_t.
55  *
56  * If time_t is unsigned, the largest possible value is just ~0.
57  * However, if it's signed, then ~0 is negative. Since lint (for
58  * sure), and perhaps the compiler too, dislike comparing an
59  * unsigned quantity to see if it's less than zero, we compare
60  * to one instead. If negative, the largest possible value is
61  * th inverse of 2**(N-1), where N is the number of bits in a
62  * time_t.
63  */
64 extern "C" {
65 static void
66 __setMaxTimeT(void)
67 {
68 	unsigned char	b[sizeof (time_t)];
69 	int		i;
70 
71 	/* Compute ~0 for an unknown length integer */
72 	for (i = 0; i < sizeof (time_t); i++) {
73 		b[i] = 0xff;
74 	}
75 	/* Set maxTimeT to ~0 of appropriate length */
76 	(void) memcpy(&maxTimeT, b, sizeof (time_t));
77 
78 	if (maxTimeT < 1)
79 		maxTimeT = ~(1<<((8*sizeof (maxTimeT))-1));
80 }
81 #pragma init(__setMaxTimeT)
82 }
83 
84 /* How much to grow table by */
85 #define	DB_TABLE_GROWTH_INCREMENT 1024
86 
87 /* 0'th not used; might be confusing. */
88 #define	DB_TABLE_START 1
89 
90 /* prevents wrap around numbers from being passed */
91 #define	CALLOC_LIMIT 536870911
92 
93 /* Initial table sizes to use before using 1K increments. */
94 /* This helps conserve memory usage when there are lots of small tables. */
95 static int tabsizes[] = {
96 	16,
97 	128,
98 	512,
99 	DB_TABLE_GROWTH_INCREMENT,
100 	0
101 	};
102 
103 /* Returns the next size to use for table */
104 static long unsigned
105 get_new_table_size(long unsigned oldsize)
106 {
107 	long unsigned newsize = 0, n;
108 	if (oldsize == 0)
109 		newsize = tabsizes[0];
110 	else {
111 		for (n = 0; newsize = tabsizes[n++]; )
112 			if (oldsize == newsize) {
113 				newsize = tabsizes[n];	/* get next size */
114 				break;
115 			}
116 		if (newsize == 0)
117 			newsize = oldsize + DB_TABLE_GROWTH_INCREMENT;
118 	}
119 	return (newsize);
120 }
121 
122 
123 /* destructor */
124 db_free_list::~db_free_list()
125 {
126 	WRITELOCKV(this, "w db_free_list::~db_free_list");
127 	reset();   /* free list entries */
128 	DESTROYRW(free_list);
129 }
130 
131 void
132 db_free_list::reset()
133 {
134 	db_free_entry *current, *nextentry;
135 
136 	WRITELOCKV(this, "w db_free_list::reset");
137 	for (current = head; current != NULL; ) {
138 		nextentry = current->next;
139 		delete current;
140 		current = nextentry;
141 	}
142 	head = NULL;
143 	count = 0;
144 	WRITEUNLOCKV(this, "wu db_free_list::reset");
145 }
146 
147 /* Returns the location of a free entry, or NULL, if there aren't any. */
148 entryp
149 db_free_list::pop()
150 {
151 	WRITELOCK(this, NULL, "w db_free_list::pop");
152 	if (head == NULL) {
153 		WRITEUNLOCK(this, NULL, "wu db_free_list::pop");
154 		return (NULL);
155 	}
156 	db_free_entry* old_head = head;
157 	entryp found = head->where;
158 	head = head->next;
159 	delete old_head;
160 	--count;
161 	WRITEUNLOCK(this, found, "wu db_free_list::pop");
162 	return (found);
163 }
164 
165 /*
166  * Adds given location to the free list.
167  * Returns TRUE if successful, FALSE otherwise (when out of memory).
168 */
169 bool_t
170 db_free_list::push(entryp tabloc)
171 {
172 	db_free_entry * newentry = new db_free_entry;
173 
174 	WRITELOCK(this, FALSE, "w db_free_list::push");
175 	if (newentry == NULL) {
176 		WRITEUNLOCK(this, FALSE, "wu db_free_list::push");
177 	    FATAL3("db_free_list::push: cannot allocation space",
178 		    DB_MEMORY_LIMIT, FALSE);
179 	}
180 	newentry->where = tabloc;
181 	newentry->next = head;
182 	head = newentry;
183 	++count;
184 	WRITEUNLOCK(this, TRUE, "wu db_free_list::push");
185 	return (TRUE);
186 }
187 
188 /*
189  * Returns in a vector the information in the free list.
190  * Vector returned is of form: [n free cells][n1][n2][loc1], ..[locn].
191  * Leave the first 'n' cells free.
192  * n1 is the number of entries that should be in the freelist.
193  * n2 is the number of entries actually found in the freelist.
194  * [loc1...locn] are the entries.   n2 <= n1 because we never count beyond n1.
195  * It is up to the caller to free the returned vector when he is through.
196 */
197 long *
198 db_free_list::stats(int nslots)
199 {
200 	long	realcount = 0,
201 		i,
202 		liststart = nslots,		// start of freelist
203 		listend = nslots+count+2;	// end of freelist
204 	db_free_entry_p current = head;
205 
206 	READLOCK(this, NULL, "r db_free_list::stats");
207 
208 	long *answer = (long *)malloc((int)(listend)*sizeof (long));
209 	if (answer == 0) {
210 		READUNLOCK(this, NULL, "ru db_free_list::stats");
211 		FATAL3("db_free_list::stats:  cannot allocation space",
212 		    DB_MEMORY_LIMIT, NULL);
213 	}
214 
215 	answer[liststart] = count;  /* size of freelist */
216 
217 	for (i = liststart+2; i < listend && current != NULL; i++) {
218 		answer[i] = current->where;
219 		current = current->next;
220 		++realcount;
221 	}
222 
223 	answer[liststart+1] = realcount;
224 	READUNLOCK(this, answer, "ru db_free_list::stats");
225 	return (answer);
226 }
227 
228 
229 /* Set default values for the mapping structure */
230 void
231 db_table::initMappingStruct(__nisdb_table_mapping_t *m) {
232 	if (m == 0)
233 		return;
234 
235 	m->initTtlLo = (ldapDBTableMapping.initTtlLo > 0) ?
236 			ldapDBTableMapping.initTtlLo : (3600-1800);
237 	m->initTtlHi = (ldapDBTableMapping.initTtlHi > 0) ?
238 			ldapDBTableMapping.initTtlHi : (3600+1800);
239 	m->ttl = (ldapDBTableMapping.ttl > 0) ?
240 			ldapDBTableMapping.ttl : 3600;
241 	m->enumExpire = 0;
242 	m->fromLDAP = FALSE;
243 	m->toLDAP = FALSE;
244 	m->isMaster = FALSE;
245 	m->retrieveError = ldapDBTableMapping.retrieveError;
246 	m->retrieveErrorRetry.attempts =
247 		ldapDBTableMapping.retrieveErrorRetry.attempts;
248 	m->retrieveErrorRetry.timeout =
249 		ldapDBTableMapping.retrieveErrorRetry.timeout;
250 	m->storeError = ldapDBTableMapping.storeError;
251 	m->storeErrorRetry.attempts =
252 		ldapDBTableMapping.storeErrorRetry.attempts;
253 	m->storeErrorRetry.timeout =
254 		ldapDBTableMapping.storeErrorRetry.timeout;
255 	m->storeErrorDisp = ldapDBTableMapping.storeErrorDisp;
256 	m->refreshError = ldapDBTableMapping.refreshError;
257 	m->refreshErrorRetry.attempts =
258 		ldapDBTableMapping.refreshErrorRetry.attempts;
259 	m->refreshErrorRetry.timeout =
260 		ldapDBTableMapping.refreshErrorRetry.timeout;
261 	m->matchFetch = ldapDBTableMapping.matchFetch;
262 
263 	if (mapping.expire != 0)
264 		free(mapping.expire);
265 	m->expire = 0;
266 
267 	if (m->tm != 0)
268 		free(m->tm);
269 	m->tm = 0;
270 
271 	/*
272 	 * The 'objType' field obviously indicates the type of object.
273 	 * However, we also use it to tell us if we've retrieved mapping
274 	 * data from LDAP or not; in the latter case, 'objType' is
275 	 * NIS_BOGUS_OBJ. For purposes of maintaining expiration times,
276 	 * we may need to know if the object is a table or a directory
277 	 * _before_ we've retrieved any mapping data. Hence the 'expireType'
278 	 * field, which starts as NIS_BOGUS_OBJ (meaning, don't know, assume
279 	 * directory for now), and later is set to NIS_DIRECTORY_OBJ
280 	 * (always keep expiration data, in case one of the dir entries
281 	 * is mapped) or NIS_TABLE_OBJ (only need expiration data if
282 	 * tha table is mapped).
283 	 */
284 	m->objType = NIS_BOGUS_OBJ;
285 	m->expireType = NIS_BOGUS_OBJ;
286 	if (m->objName != 0)
287 		free(m->objName);
288 	m->objName = 0;
289 }
290 
291 void
292 db_table::db_table_ldap_init(void) {
293 
294 	INITRW(table);
295 
296 	enumMode.flag = 0;
297 	enumCount.flag = 0;
298 	enumIndex.ptr = 0;
299 	enumArray.ptr = 0;
300 
301 	mapping.expire = 0;
302 	mapping.tm = 0;
303 	mapping.objName = 0;
304 	mapping.isDeferredTable = FALSE;
305 	(void) mutex_init(&mapping.enumLock, 0, 0);
306 	mapping.enumTid = 0;
307 	mapping.enumStat = -1;
308 	mapping.enumDeferred = 0;
309 	mapping.enumEntries = 0;
310 	mapping.enumTime = 0;
311 }
312 
313 /* db_table constructor */
314 db_table::db_table() : freelist()
315 {
316 	tab = NULL;
317 	table_size = 0;
318 	last_used = 0;
319 	count = 0;
320 
321 	db_table_ldap_init();
322 	initMappingStruct(&mapping);
323 
324 /*  grow(); */
325 }
326 
327 /*
328  * db_table destructor:
329  * 1.  Get rid of contents of freelist
330  * 2.  delete all entries hanging off table
331  * 3.  get rid of table itself
332 */
333 db_table::~db_table()
334 {
335 	WRITELOCKV(this, "w db_table::~db_table");
336 	reset();
337 	DESTROYRW(table);
338 }
339 
340 /* reset size and pointers */
341 void
342 db_table::reset()
343 {
344 	int i, done = 0;
345 
346 	WRITELOCKV(this, "w db_table::reset");
347 	freelist.reset();
348 
349 	/* Add sanity check in case of table corruption */
350 	if (tab != NULL) {
351 		for (i = 0;
352 			i <= last_used && i < table_size && done < count;
353 			i++) {
354 			if (tab[i]) {
355 				free_entry(tab[i]);
356 				++done;
357 			}
358 		}
359 	}
360 
361 	delete tab;
362 	table_size = last_used = count = 0;
363 	tab = NULL;
364 	sfree(mapping.expire);
365 	mapping.expire = NULL;
366 	mapping.objType = NIS_BOGUS_OBJ;
367 	mapping.expireType = NIS_BOGUS_OBJ;
368 	sfree(mapping.objName);
369 	mapping.objName = 0;
370 	/* Leave other values of the mapping structure unchanged */
371 	enumMode.flag = 0;
372 	enumCount.flag = 0;
373 	sfree(enumIndex.ptr);
374 	enumIndex.ptr = 0;
375 	sfree(enumArray.ptr);
376 	enumArray.ptr = 0;
377 	WRITEUNLOCKV(this, "wu db_table::reset");
378 }
379 
380 db_status
381 db_table::allocateExpire(long oldSize, long newSize) {
382 	time_t			*newExpire;
383 
384 	newExpire = (time_t *)realloc(mapping.expire,
385 				newSize * sizeof (mapping.expire[0]));
386 	if (newExpire != NULL) {
387 		/* Initialize new portion */
388 		(void) memset(&newExpire[oldSize], 0,
389 				(newSize-oldSize) * sizeof (newExpire[0]));
390 		mapping.expire = newExpire;
391 	} else {
392 		return (DB_MEMORY_LIMIT);
393 	}
394 
395 	return (DB_SUCCESS);
396 }
397 
398 db_status
399 db_table::allocateEnumArray(long oldSize, long newSize) {
400 	entry_object	**newEnumArray;
401 	char		*myself = "db_table::allocateEnumArray";
402 
403 	if (enumCount.flag > 0) {
404 		if (enumIndex.ptr == 0) {
405 			enumIndex.ptr = (entryp *)am(myself, enumCount.flag *
406 						sizeof (entryp));
407 			if (enumIndex.ptr == 0)
408 				return (DB_MEMORY_LIMIT);
409 		}
410 		oldSize = 0;
411 		newSize = enumCount.flag;
412 	}
413 	newEnumArray = (entry_object **)realloc(enumArray.ptr,
414 			newSize * sizeof (entry_object *));
415 	if (newEnumArray != 0 && newSize > oldSize) {
416 		(void) memcpy(&newEnumArray[oldSize], &tab[oldSize],
417 			(newSize-oldSize) * sizeof (entry_object *));
418 		enumArray.ptr = newEnumArray;
419 	} else if (newEnumArray == 0) {
420 		return (DB_MEMORY_LIMIT);
421 	}
422 
423 	return (DB_SUCCESS);
424 }
425 
426 /* Expand the table.  Fatal error if insufficient memory. */
427 void
428 db_table::grow()
429 {
430 	WRITELOCKV(this, "w db_table::grow");
431 	long oldsize = table_size;
432 	entry_object_p *oldtab = tab;
433 	long i;
434 
435 	table_size = get_new_table_size(oldsize);
436 
437 #ifdef DEBUG
438 	fprintf(stderr, "db_table GROWING to %d\n", table_size);
439 #endif
440 
441 	if (table_size > CALLOC_LIMIT) {
442 		table_size = oldsize;
443 		WRITEUNLOCKV(this, "wu db_table::grow");
444 		FATAL("db_table::grow: table size exceeds calloc limit",
445 			DB_MEMORY_LIMIT);
446 	}
447 
448 //  if ((tab = new entry_object_p[table_size]) == NULL)
449 	if ((tab = (entry_object_p*)
450 		calloc((unsigned int) table_size,
451 			sizeof (entry_object_p))) == NULL) {
452 		tab = oldtab;		// restore previous table info
453 		table_size = oldsize;
454 		WRITEUNLOCKV(this, "wu db_table::grow");
455 		FATAL("db_table::grow: cannot allocate space", DB_MEMORY_LIMIT);
456 	}
457 
458 	/*
459 	 * For directories, we may need the expire time array even if the
460 	 * directory itself isn't mapped. If the objType and expireType both
461 	 * are bogus, we don't  know yet if this is a table or a directory,
462 	 * and must proceed accordingly.
463 	 */
464 	if (mapping.objType == NIS_DIRECTORY_OBJ ||
465 			mapping.expireType != NIS_TABLE_OBJ ||
466 			mapping.fromLDAP) {
467 		db_status stat = allocateExpire(oldsize, table_size);
468 		if (stat != DB_SUCCESS) {
469 			free(tab);
470 			tab = oldtab;
471 			table_size = oldsize;
472 			WRITEUNLOCKV(this, "wu db_table::grow expire");
473 			FATAL(
474 		"db_table::grow: cannot allocate space for expire", stat);
475 		}
476 	}
477 
478 	if (oldtab != NULL) {
479 		for (i = 0; i < oldsize; i++) { // transfer old to new
480 			tab[i] = oldtab[i];
481 		}
482 		delete oldtab;
483 	}
484 
485 	if (enumMode.flag) {
486 		db_status stat = allocateEnumArray(oldsize, table_size);
487 		if (stat != DB_SUCCESS) {
488 			free(tab);
489 			tab = oldtab;
490 			table_size = oldsize;
491 			WRITEUNLOCKV(this, "wu db_table::grow enumArray");
492 			FATAL(
493 		"db_table::grow: cannot allocate space for enumArray", stat);
494 		}
495 	}
496 
497 	WRITEUNLOCKV(this, "wu db_table::grow");
498 }
499 
500 /*
501  * Return the first entry in table, also return its position in
502  * 'where'.  Return NULL in both if no next entry is found.
503  */
504 entry_object*
505 db_table::first_entry(entryp * where)
506 {
507 	ASSERTRHELD(table);
508 	if (count == 0 || tab == NULL) {  /* empty table */
509 		*where = NULL;
510 		return (NULL);
511 	} else {
512 		entryp i;
513 		for (i = DB_TABLE_START;
514 			i < table_size && i <= last_used; i++) {
515 			if (tab[i] != NULL) {
516 				*where = i;
517 				return (tab[i]);
518 			}
519 		}
520 	}
521 	*where = NULL;
522 	return (NULL);
523 }
524 
525 /*
526  * Return the next entry in table from 'prev', also return its position in
527  * 'newentry'.  Return NULL in both if no next entry is found.
528  */
529 entry_object *
530 db_table::next_entry(entryp prev, entryp* newentry)
531 {
532 	long i;
533 
534 	ASSERTRHELD(table);
535 	if (prev >= table_size || tab == NULL || tab[prev] == NULL)
536 		return (NULL);
537 	for (i = prev+1; i < table_size && i <= last_used; i++) {
538 		if (tab[i] != NULL) {
539 			*newentry = i;
540 			return (tab[i]);
541 		}
542 	}
543 	*newentry = NULL;
544 	return (NULL);
545 }
546 
547 /* Return entry at location 'where', NULL if location is invalid. */
548 entry_object *
549 db_table::get_entry(entryp where)
550 {
551 	ASSERTRHELD(table);
552 	if (where < table_size && tab != NULL && tab[where] != NULL)
553 		return (tab[where]);
554 	else
555 		return (NULL);
556 }
557 
558 void
559 db_table::setEntryExp(entryp where, entry_obj *obj, int initialLoad) {
560 	nis_object		*o;
561 	char			*myself = "db_table::setEntryExp";
562 
563 	/*
564 	 * If we don't know what type of object this is yet, we
565 	 * can find out now. If it's a directory, the pseudo-object
566 	 * in column zero will have the type "IN_DIRECTORY";
567 	 * otherwise, it's a table object.
568 	 */
569 	if (mapping.expireType == NIS_BOGUS_OBJ) {
570 		if (obj != 0) {
571 			if (obj->en_type != 0 &&
572 				strcmp(obj->en_type, "IN_DIRECTORY") == 0) {
573 				mapping.expireType = NIS_DIRECTORY_OBJ;
574 			} else {
575 				mapping.expireType = NIS_TABLE_OBJ;
576 				if (!mapping.fromLDAP) {
577 					free(mapping.expire);
578 					mapping.expire = 0;
579 				}
580 			}
581 		}
582 	}
583 
584 	/* Set the entry TTL */
585 	if (mapping.expire != NULL) {
586 		struct timeval	now;
587 		time_t		lo, hi, ttl;
588 
589 		(void) gettimeofday(&now, NULL);
590 		if (mapping.expireType == NIS_TABLE_OBJ) {
591 			lo = mapping.initTtlLo;
592 			hi = mapping.initTtlHi;
593 			ttl = mapping.ttl;
594 			/* TTL == 0 means always expired */
595 			if (ttl == 0)
596 				ttl = -1;
597 		} else {
598 			__nis_table_mapping_t	*t = 0;
599 
600 			o = unmakePseudoEntryObj(obj, 0);
601 			if (o != 0) {
602 				__nis_buffer_t	b = {0, 0};
603 
604 				bp2buf(myself, &b, "%s.%s",
605 					o->zo_name, o->zo_domain);
606 				t = getObjMapping(b.buf, 0, 1, 0, 0);
607 				sfree(b.buf);
608 				nis_destroy_object(o);
609 			}
610 
611 			if (t != 0) {
612 				lo = t->initTtlLo;
613 				hi = t->initTtlHi;
614 				ttl = t->ttl;
615 				/* TTL == 0 means always expired */
616 				if (ttl == 0)
617 					ttl = -1;
618 			} else {
619 				/*
620 				 * No expiration time initialization
621 				 * data. Cook up values that will
622 				 * result in mapping.expire[where]
623 				 * set to maxTimeT.
624 				 */
625 				hi = lo = ttl = maxTimeT - now.tv_sec;
626 			}
627 		}
628 
629 		if (initialLoad) {
630 			int	interval = hi - lo + 1;
631 			if (interval <= 1) {
632 				mapping.expire[where] = now.tv_sec + lo;
633 			} else {
634 				srand48(now.tv_sec);
635 				mapping.expire[where] = now.tv_sec +
636 							(lrand48() % interval);
637 			}
638 			if (mapping.enumExpire == 0 ||
639 					mapping.expire[where] <
640 							mapping.enumExpire)
641 				mapping.enumExpire = mapping.expire[where];
642 		} else {
643 			mapping.expire[where] = now.tv_sec + ttl;
644 		}
645 	}
646 }
647 
648 /*
649  * Add given entry to table in first available slot (either look in freelist
650  * or add to end of table) and return the the position of where the record
651  * is placed. 'count' is incremented if entry is added. Table may grow
652  * as a side-effect of the addition. Copy is made of input.
653 */
654 entryp
655 db_table::add_entry(entry_object *obj, int initialLoad) {
656 	/*
657 	 * We're returning an index of the table array, so the caller
658 	 * should hold a lock until done with the index. To save us
659 	 * the bother of upgrading to a write lock, it might as well
660 	 * be a write lock to begin with.
661 	 */
662 	ASSERTWHELD(table);
663 	entryp where = freelist.pop();
664 	if (where == NULL) {				/* empty freelist */
665 		if (last_used >= (table_size-1))	/* full (> is for 0) */
666 			grow();
667 		where = ++last_used;
668 	}
669 	if (tab != NULL) {
670 		++count;
671 		setEntryExp(where, obj, initialLoad);
672 
673 		if (enumMode.flag)
674 			enumTouch(where);
675 		tab[where] = new_entry(obj);
676 		return (where);
677 	} else {
678 		return (NULL);
679 	}
680 }
681 
682 /*
683  * Replaces object at specified location by given entry.
684  * Returns TRUE if replacement successful; FALSE otherwise.
685  * There must something already at the specified location, otherwise,
686  * replacement fails. Copy is not made of the input.
687  * The pre-existing entry is freed.
688  */
689 bool_t
690 db_table::replace_entry(entryp where, entry_object * obj)
691 {
692 	ASSERTWHELD(table);
693 	if (where < DB_TABLE_START || where >= table_size ||
694 	    tab == NULL || tab[where] == NULL)
695 		return (FALSE);
696 	/* (Re-)set the entry TTL */
697 	setEntryExp(where, obj, 0);
698 
699 	if (enumMode.flag)
700 		enumTouch(where);
701 	free_entry(tab[where]);
702 	tab[where] = obj;
703 	return (TRUE);
704 }
705 
706 /*
707  * Deletes entry at specified location.  Returns TRUE if location is valid;
708  * FALSE if location is invalid, or the freed location cannot be added to
709  * the freelist.  'count' is decremented if the deletion occurs.  The object
710  * at that location is freed.
711  */
712 bool_t
713 db_table::delete_entry(entryp where)
714 {
715 	bool_t	ret = TRUE;
716 
717 	ASSERTWHELD(table);
718 	if (where < DB_TABLE_START || where >= table_size ||
719 	    tab == NULL || tab[where] == NULL)
720 		return (FALSE);
721 	if (mapping.expire != NULL) {
722 		mapping.expire[where] = 0;
723 	}
724 	if (enumMode.flag)
725 		enumTouch(where);
726 	free_entry(tab[where]);
727 	tab[where] = NULL;    /* very important to set it to null */
728 	--count;
729 	if (where == last_used) { /* simple case, deleting from end */
730 		--last_used;
731 		return (TRUE);
732 	} else {
733 		return (freelist.push(where));
734 	}
735 	return (ret);
736 }
737 
738 /*
739  * Returns statistics of table.
740  * [vector_size][table_size][last_used][count][freelist].
741  * It is up to the caller to free the returned vector when his is through.
742  * The free list is included if 'fl' is TRUE.
743 */
744 long *
745 db_table::stats(bool_t include_freelist)
746 {
747 	long *answer;
748 
749 	READLOCK(this, NULL, "r db_table::stats");
750 	if (include_freelist)
751 		answer = freelist.stats(3);
752 	else {
753 		answer = (long *)malloc(3*sizeof (long));
754 	}
755 
756 	if (answer) {
757 		answer[0] = table_size;
758 		answer[1] = last_used;
759 		answer[2] = count;
760 	}
761 	READUNLOCK(this, answer, "ru db_table::stats");
762 	return (answer);
763 }
764 
765 bool_t
766 db_table::configure(char *tablePath) {
767 	long		i;
768 	struct timeval	now;
769 	char		*myself = "db_table::configure";
770 
771 	(void) gettimeofday(&now, NULL);
772 
773 	WRITELOCK(this, FALSE, "db_table::configure w");
774 
775 	/* (Re-)initialize from global info */
776 	initMappingStruct(&mapping);
777 
778 	/* Retrieve table mapping for this table */
779 	mapping.tm = (__nis_table_mapping_t *)__nis_find_item_mt(
780 					tablePath, &ldapMappingList, 0, 0);
781 	if (mapping.tm != 0) {
782 		__nis_object_dn_t	*odn = mapping.tm->objectDN;
783 
784 		/*
785 		 * The mapping.fromLDAP and mapping.toLDAP fields serve as
786 		 * quick-references that tell us if mapping is enabled.
787 		 * Hence, initialize them appropriately from the table
788 		 * mapping objectDN.
789 		 */
790 		while (odn != 0 && (!mapping.fromLDAP || !mapping.toLDAP)) {
791 			if (odn->read.scope != LDAP_SCOPE_UNKNOWN)
792 				mapping.fromLDAP = TRUE;
793 			if (odn->write.scope != LDAP_SCOPE_UNKNOWN)
794 				mapping.toLDAP = TRUE;
795 			odn = (__nis_object_dn_t *)odn->next;
796 		}
797 
798 		/* Set the timeout values */
799 		mapping.initTtlLo = mapping.tm->initTtlLo;
800 		mapping.initTtlHi = mapping.tm->initTtlHi;
801 		mapping.ttl = mapping.tm->ttl;
802 
803 		mapping.objName = sdup(myself, T, mapping.tm->objName);
804 		if (mapping.objName == 0 && mapping.tm->objName != 0) {
805 			WRITEUNLOCK(this, FALSE,
806 				"db_table::configure wu objName");
807 			FATAL3("db_table::configure objName",
808 				DB_MEMORY_LIMIT, FALSE);
809 		}
810 	}
811 
812 	/*
813 	 * In order to initialize the expiration times, we need to know
814 	 * if 'this' represents a table or a directory. To that end, we
815 	 * find an entry in the table, and invoke setEntryExp() on it.
816 	 * As a side effect, setEntryExp() will examine the pseudo-object
817 	 * in the entry, and set the expireType accordingly.
818 	 */
819 	if (tab != 0) {
820 		for (i = 0; i <= last_used; i++) {
821 			if (tab[i] != NULL) {
822 				setEntryExp(i, tab[i], 1);
823 				break;
824 			}
825 		}
826 	}
827 
828 	/*
829 	 * If mapping from an LDAP repository, make sure we have the
830 	 * expiration time array.
831 	 */
832 	if ((mapping.expireType != NIS_TABLE_OBJ || mapping.fromLDAP) &&
833 			mapping.expire == NULL && table_size > 0 && tab != 0) {
834 		db_status stat = allocateExpire(0, table_size);
835 		if (stat != DB_SUCCESS) {
836 			WRITEUNLOCK(this, FALSE,
837 				"db_table::configure wu expire");
838 			FATAL3("db_table::configure expire",
839 				stat, FALSE);
840 		}
841 	} else if (mapping.expireType == NIS_TABLE_OBJ && !mapping.fromLDAP &&
842 			mapping.expire != NULL) {
843 		/* Not using expiration times */
844 		free(mapping.expire);
845 		mapping.expire = NULL;
846 	}
847 
848 	/*
849 	 * Set initial expire times for entries that don't already have one.
850 	 * Establish the enumeration expiration time to be the minimum of
851 	 * all expiration times in the table, though no larger than current
852 	 * time plus initTtlHi.
853 	 */
854 	if (mapping.expire != NULL) {
855 		int	interval = mapping.initTtlHi - mapping.initTtlLo + 1;
856 		time_t	enumXp = now.tv_sec + mapping.initTtlHi;
857 
858 		if (interval > 1)
859 			srand48(now.tv_sec);
860 		for (i = 0; i <= last_used; i++) {
861 			if (tab[i] != NULL && mapping.expire[i] == 0) {
862 				if (mapping.expireType == NIS_TABLE_OBJ) {
863 					if (interval > 1)
864 						mapping.expire[i] =
865 							now.tv_sec +
866 							(lrand48() % interval);
867 					else
868 						mapping.expire[i] =
869 							now.tv_sec +
870 							mapping.initTtlLo;
871 				} else {
872 					setEntryExp(i, tab[i], 1);
873 				}
874 			}
875 			if (enumXp > mapping.expire[i])
876 				enumXp = mapping.expire[i];
877 		}
878 		mapping.enumExpire = enumXp;
879 	}
880 
881 	WRITEUNLOCK(this, FALSE, "db_table::configure wu");
882 
883 	return (TRUE);
884 }
885 
886 /* Return TRUE if the entry at 'loc' hasn't expired */
887 bool_t
888 db_table::cacheValid(entryp loc) {
889 	bool_t		ret;
890 	struct timeval	now;
891 
892 	(void) gettimeofday(&now, 0);
893 
894 	READLOCK(this, FALSE, "db_table::cacheValid r");
895 
896 	if (loc < 0 || loc >= table_size || tab == 0 || tab[loc] == 0)
897 		ret = FALSE;
898 	else if (mapping.expire == 0 || mapping.expire[loc] >= now.tv_sec)
899 		ret = TRUE;
900 	else
901 		ret = FALSE;
902 
903 	READUNLOCK(this, ret, "db_table::cacheValid ru");
904 
905 	return (ret);
906 }
907 
908 /*
909  * If the supplied object has the same content as the one at 'loc',
910  * update the expiration time for the latter, and return TRUE.
911  */
912 bool_t
913 db_table::dupEntry(entry_object *obj, entryp loc) {
914 	if (obj == 0 || loc < 0 || loc >= table_size || tab == 0 ||
915 			tab[loc] == 0)
916 		return (FALSE);
917 
918 	if (sameEntry(obj, tab[loc])) {
919 		setEntryExp(loc, tab[loc], 0);
920 
921 		if (enumMode.flag > 0)
922 			enumTouch(loc);
923 		return (TRUE);
924 	}
925 
926 	return (FALSE);
927 }
928 
929 /*
930  * If enumeration mode is enabled, we keep a shadow array that initially
931  * starts out the same as 'tab'. Any update activity (add, remove, replace,
932  * or update timestamp) for an entry in the table means we delete the shadow
933  * array pointer. When ending enumeration mode, we return the shadow array.
934  * Any non-NULL entries in the array have not been updated since the start
935  * of the enum mode.
936  *
937  * The indended use is for enumeration of an LDAP container, where we
938  * will update all entries that currently exist in LDAP. The entries we
939  * don't update are those that don't exist in LDAP, and thus should be
940  * removed.
941  *
942  * Note that any LDAP query strictly speaking can be a partial enumeration
943  * (i.e., return more than one match). Since the query might also have
944  * matched multiple local DB entries, we need to do the same work as for
945  * enumeration for any query. In order to avoid having to work on the
946  * whole 'tab' array for simple queries (which we expect usually will
947  * match just one or at most a few entries), we have a "reduced" enum mode,
948  * where the caller supplies a count of the number of DB entries (derived
949  * from db_mindex::satisfy_query() or similar), and then uses enumSetup()
950  * to specify which 'tab' entries we're interested in.
951  */
952 void
953 db_table::setEnumMode(long enumNum) {
954 	char	*myself = "setEnumMode";
955 
956 	enumMode.flag++;
957 	if (enumMode.flag == 1) {
958 		db_status	stat;
959 
960 		if (enumNum < 0)
961 			enumNum = 0;
962 		else if (enumNum >= table_size)
963 			enumNum = table_size;
964 
965 		enumCount.flag = enumNum;
966 
967 		stat = allocateEnumArray(0, table_size);
968 
969 		if (stat != DB_SUCCESS) {
970 			enumMode.flag = 0;
971 			enumCount.flag = 0;
972 			logmsg(MSG_NOTIMECHECK, LOG_ERR,
973 		"%s: No memory for enum check array; entry removal disabled",
974 				myself);
975 		}
976 	}
977 }
978 
979 void
980 db_table::clearEnumMode(void) {
981 	if (enumMode.flag > 0) {
982 		enumMode.flag--;
983 		if (enumMode.flag == 0) {
984 			sfree(enumArray.ptr);
985 			enumArray.ptr = 0;
986 			if (enumCount.flag > 0) {
987 				sfree(enumIndex.ptr);
988 				enumIndex.ptr = 0;
989 				enumCount.flag = 0;
990 			}
991 		}
992 	}
993 }
994 
995 entry_object **
996 db_table::endEnumMode(long *numEa) {
997 	if (enumMode.flag > 0) {
998 		enumMode.flag--;
999 		if (enumMode.flag == 0) {
1000 			entry_obj	**ea = (entry_object **)enumArray.ptr;
1001 			long		nea;
1002 
1003 			enumArray.ptr = 0;
1004 
1005 			if (enumCount.flag > 0) {
1006 				nea = enumCount.flag;
1007 				enumCount.flag = 0;
1008 				sfree(enumIndex.ptr);
1009 				enumIndex.ptr = 0;
1010 			} else {
1011 				nea = table_size;
1012 			}
1013 
1014 			if (numEa != 0)
1015 				*numEa = nea;
1016 
1017 			return (ea);
1018 		}
1019 	}
1020 
1021 	if (numEa != 0)
1022 		*numEa = 0;
1023 
1024 	return (0);
1025 }
1026 
1027 /*
1028  * Set the appropriate entry in the enum array to NULL.
1029  */
1030 void
1031 db_table::enumTouch(entryp loc) {
1032 	if (loc < 0 || loc >= table_size)
1033 		return;
1034 
1035 	if (enumMode.flag > 0) {
1036 		if (enumCount.flag < 1) {
1037 			((entry_object **)enumArray.ptr)[loc] = 0;
1038 		} else {
1039 			int	i;
1040 
1041 			for (i = 0; i < enumCount.flag; i++) {
1042 				if (loc == ((entryp *)enumIndex.ptr)[i]) {
1043 					((entry_object **)enumArray.ptr)[i] = 0;
1044 					break;
1045 				}
1046 			}
1047 		}
1048 	}
1049 }
1050 
1051 /*
1052  * Add the entry indicated by 'loc' to the enumIndex array, at 'index'.
1053  */
1054 void
1055 db_table::enumSetup(entryp loc, long index) {
1056 	if (enumMode.flag == 0 || loc < 0 || loc >= table_size ||
1057 			index < 0 || index >= enumCount.flag)
1058 		return;
1059 
1060 	((entryp *)enumIndex.ptr)[index] = loc;
1061 	((entry_object **)enumArray.ptr)[index] = tab[loc];
1062 }
1063 
1064 /*
1065  * Touch, i.e., update the expiration time for the entry. Also, if enum
1066  * mode is in effect, mark the entry used for enum purposes.
1067  */
1068 void
1069 db_table::touchEntry(entryp loc) {
1070 	if (loc < 0 || loc >= table_size || tab == 0 || tab[loc] == 0)
1071 		return;
1072 
1073 	setEntryExp(loc, tab[loc], 0);
1074 
1075 	enumTouch(loc);
1076 }
1077 
1078 /* ************************* pickle_table ********************* */
1079 /* Does the actual writing to/from file specific for db_table structure. */
1080 /*
1081  * This was a static earlier with the func name being transfer_aux. The
1082  * backup and restore project needed this to copy files over.
1083  */
1084 bool_t
1085 transfer_aux_table(XDR* x, pptr dp)
1086 {
1087 	return (xdr_db_table(x, (db_table*) dp));
1088 }
1089 
1090 class pickle_table: public pickle_file {
1091     public:
1092 	pickle_table(char *f, pickle_mode m) : pickle_file(f, m) {}
1093 
1094 	/* Transfers db_table structure pointed to by dp to/from file. */
1095 	int transfer(db_table* dp)
1096 	{ return (pickle_file::transfer((pptr) dp, &transfer_aux_table)); }
1097 };
1098 
1099 /*
1100  * Writes the contents of table, including the all the entries, into the
1101  * specified file in XDR format.  May need to change this to use APPEND
1102  * mode instead.
1103  */
1104 int
1105 db_table::dump(char *file)
1106 {
1107 	int	ret;
1108 	READLOCK(this, -1, "r db_table::dump");
1109 	pickle_table f(file, PICKLE_WRITE);   /* may need to use APPEND mode */
1110 	int status = f.transfer(this);
1111 
1112 	if (status == 1)
1113 		ret = -1;
1114 	else
1115 		ret = status;
1116 	READUNLOCK(this, ret, "ru db_table::dump");
1117 }
1118 
1119 /* Constructor that loads in the table from the given file */
1120 db_table::db_table(char *file)  : freelist()
1121 {
1122 	pickle_table f(file, PICKLE_READ);
1123 	tab = NULL;
1124 	table_size = last_used = count = 0;
1125 
1126 	/* load  table */
1127 	if (f.transfer(this) < 0) {
1128 		/* fell through, something went wrong, initialize to null */
1129 		tab = NULL;
1130 		table_size = last_used = count = 0;
1131 		freelist.init();
1132 	}
1133 
1134 	db_table_ldap_init();
1135 	initMappingStruct(&mapping);
1136 }
1137 
1138 /* Returns whether location is valid. */
1139 bool_t db_table::entry_exists_p(entryp i) {
1140 	bool_t	ret = FALSE;
1141 	READLOCK(this, FALSE, "r db_table::entry_exists_p");
1142 	if (tab != NULL && i < table_size)
1143 		ret = tab[i] != NULL;
1144 	READUNLOCK(this, ret, "ru db_table::entry_exists_p");
1145 	return (ret);
1146 }
1147 
1148 /* Empty free list */
1149 void db_free_list::init() {
1150 	WRITELOCKV(this, "w db_free_list::init");
1151 	head = NULL;
1152 	count = 0;
1153 	WRITEUNLOCKV(this, "wu db_free_list::init");
1154 }
1155