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