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