xref: /illumos-gate/usr/src/lib/libnisdb/db_table.cc (revision b3bec642)
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 
52 static 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  */
65 extern "C" {
66 static void
__setMaxTimeT(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. */
96 static 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 */
105 static long unsigned
get_new_table_size(long unsigned oldsize)106 get_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 */
~db_free_list()125 db_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 
132 void
reset()133 db_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. */
149 entryp
pop()150 db_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 */
170 bool_t
push(entryp tabloc)171 db_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 */
198 long *
stats(int nslots)199 db_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 */
231 void
initMappingStruct(__nisdb_table_mapping_t * m)232 db_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 
292 void
db_table_ldap_init(void)293 db_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 */
db_table()315 db_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 */
~db_table()334 db_table::~db_table()
335 {
336 	WRITELOCKV(this, "w db_table::~db_table");
337 	reset();
338 	DESTROYRW(table);
339 }
340 
341 /* reset size and pointers */
342 void
reset()343 db_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 
381 db_status
allocateExpire(long oldSize,long newSize)382 db_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 
399 db_status
allocateEnumArray(long oldSize,long newSize)400 db_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. */
428 void
grow()429 db_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  */
505 entry_object*
first_entry(entryp * where)506 db_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  */
530 entry_object *
next_entry(entryp prev,entryp * newentry)531 db_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. */
549 entry_object *
get_entry(entryp where)550 db_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 
559 void
setEntryExp(entryp where,entry_obj * obj,int initialLoad)560 db_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 */
655 entryp
add_entry(entry_object * obj,int initialLoad)656 db_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  */
690 bool_t
replace_entry(entryp where,entry_object * obj)691 db_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  */
713 bool_t
delete_entry(entryp where)714 db_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 */
745 long *
stats(bool_t include_freelist)746 db_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 
766 bool_t
configure(char * tablePath)767 db_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 */
888 bool_t
cacheValid(entryp loc)889 db_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  */
913 bool_t
dupEntry(entry_object * obj,entryp loc)914 db_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  */
953 void
setEnumMode(long enumNum)954 db_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 
980 void
clearEnumMode(void)981 db_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 
996 entry_object **
endEnumMode(long * numEa)997 db_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  */
1031 void
enumTouch(entryp loc)1032 db_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  */
1055 void
enumSetup(entryp loc,long index)1056 db_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  */
1069 void
touchEntry(entryp loc)1070 db_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  */
1085 bool_t
transfer_aux_table(XDR * x,pptr dp)1086 transfer_aux_table(XDR* x, pptr dp)
1087 {
1088 	return (xdr_db_table(x, (db_table*) dp));
1089 }
1090 
1091 class pickle_table: public pickle_file {
1092     public:
pickle_table(char * f,pickle_mode m)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. */
transfer(db_table * dp)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  */
1105 int
dump(char * file)1106 db_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 */
db_table(char * file)1122 db_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. */
entry_exists_p(entryp i)1141 bool_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 */
init()1151 void 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