xref: /titanic_50/usr/src/lib/libnisdb/db_dictionary.cc (revision 84f7a9b9dca4f23b5f50edef0e59d7eb44301114)
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_dictionary.cc
24  *
25  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
26  * Use is subject to license terms.
27  */
28 
29 #pragma ident	"%Z%%M%	%I%	%E% SMI"
30 
31 #include "db_headers.h"
32 #include "db_entry.h"
33 #include "db_dictionary.h"
34 #include "db_dictlog.h"
35 #include "db_vers.h"
36 #include "nisdb_mt.h"
37 #include "nisdb_rw.h"
38 #include "ldap_parse.h"
39 #include "ldap_map.h"
40 #include "nis_hashitem.h"
41 #include "ldap_util.h"
42 #include "nis_db.h"
43 #include <rpcsvc/nis.h>
44 
45 #include <stdio.h>
46 #include <string.h>
47 #include <malloc.h>
48 #ifdef TDRPC
49 #include <sysent.h>
50 #endif
51 #include <unistd.h>
52 #include <syslog.h>
53 #include <rpc/rpc.h>
54 
55 typedef bool_t	(*db_func)(XDR *, db_table_desc *);
56 
57 extern db_dictionary *InUseDictionary;
58 extern db_dictionary *FreeDictionary;
59 
60 /* *************** dictionary version ****************** */
61 
62 #define	DB_MAGIC 0x12340000
63 #define	DB_MAJOR 0
64 #define	DB_MINOR 10
65 #define	DB_VERSION_0_9	(DB_MAGIC|(DB_MAJOR<<8)|9)
66 #define	DB_ORIG_VERSION	DB_VERSION_0_9
67 #define	DB_CURRENT_VERSION (DB_MAGIC|DB_MAJOR<<8|DB_MINOR)
68 
69 vers db_update_version;   /* Note 'global' for all dbs. */
70 
71 #define	INMEMORY_ONLY 1
72 
73 /*
74  * Checks for valid version.  For now, there are two:
75  * DB_VERSION_ORIG was the ORIGINAL one with major = 0, minor = 9
76  * DB_CURRENT_VERSION is the latest one with changes in the database format
77  *	for entry objects and the change in the dictionary format.
78  *
79  * Our current implementation can support both versions.
80  */
81 static inline bool_t
82 db_valid_version(u_int vers)
83 {
84 	return ((vers == DB_CURRENT_VERSION) || (vers == DB_ORIG_VERSION));
85 }
86 
87 static char *
88 db_version_str(u_int vers)
89 {
90 	static char vstr[128];
91 	u_int d_major =  (vers&0x0000ff00)>>8;
92 	u_int d_minor =  (vers&0x000000ff);
93 
94 	sprintf(vstr, "SunSoft, SSM, Version %d.%d", d_major, d_minor);
95 	return (vstr);
96 }
97 
98 /*
99  * Special XDR version that checks for a valid version number.
100  * If we don't find an acceptable version, exit immediately instead
101  * of continuing to xdr rest of dictionary, which might lead to
102  * a core dump if the formats between versions are incompatible.
103  * In the future, there might be a switch to determine versions
104  * and their corresponding XDR routines for the rest of the dictionary.
105  */
106 extern "C" {
107 bool_t
108 xdr_db_dict_version(XDR *xdrs, db_dict_version *objp)
109 {
110 	if (xdrs->x_op == XDR_DECODE) {
111 		if (!xdr_u_int(xdrs, (u_int*) objp) ||
112 		    !db_valid_version(((u_int) *objp))) {
113 			syslog(LOG_ERR,
114 	"db_dictionary: invalid dictionary format! Expecting %s",
115 				db_version_str(DB_CURRENT_VERSION));
116 			fprintf(stderr,
117 	"db_dictionary: invalid dictionary format! Expecting %s\n",
118 				db_version_str(DB_CURRENT_VERSION));
119 			exit(1);
120 		}
121 	} else if (!xdr_u_int(xdrs, (u_int*) objp))
122 		return (FALSE);
123 	return (TRUE);
124 }
125 
126 void
127 make_zero(vers* v)
128 {
129 	v->zero();
130 }
131 
132 
133 };
134 
135 
136 /* ******************* dictionary data structures *************** */
137 
138 /* Delete contents of single db_table_desc pointed to by 'current.' */
139 static void
140 delete_table_desc(db_table_desc *current)
141 {
142 	if (current->table_name != NULL) delete current->table_name;
143 	if (current->scheme != NULL) delete current->scheme;
144 	if (current->database != NULL) delete current->database;
145 	delete current;
146 }
147 
148 /* Create new table descriptor using given table name and table_object. */
149 db_status
150 db_dictionary::create_table_desc(char *tab, table_obj* zdesc,
151 				db_table_desc** answer)
152 {
153 	db_table_desc *newtab;
154 	if ((newtab = new db_table_desc) == NULL) {
155 		FATAL3(
156 	    "db_dictionary::add_table: could not allocate space for new table",
157 		DB_MEMORY_LIMIT, DB_MEMORY_LIMIT);
158 	}
159 
160 	newtab->database = NULL;
161 	newtab->table_name = NULL;
162 	newtab->next = NULL;
163 
164 	if ((newtab->scheme = new db_scheme(zdesc)) == NULL) {
165 		delete_table_desc(newtab);
166 		FATAL3(
167 	"db_dictionary::add_table: could not allocate space for scheme",
168 		DB_MEMORY_LIMIT, DB_MEMORY_LIMIT);
169 	}
170 
171 	if (newtab->scheme->numkeys() == 0) {
172 		WARNING(
173 	"db_dictionary::add_table: could not translate table_obj to scheme");
174 		delete_table_desc(newtab);
175 		return (DB_BADOBJECT);
176 	}
177 
178 	if ((newtab->table_name = strdup(tab)) == NULL) {
179 		delete_table_desc(newtab);
180 		FATAL3(
181 	    "db_dictionary::add_table: could not allocate space for table name",
182 		DB_MEMORY_LIMIT, DB_MEMORY_LIMIT);
183 	}
184 
185 	if (answer)
186 		*answer = newtab;
187 	return (DB_SUCCESS);
188 }
189 
190 
191 /* Delete list of db_table_desc pointed to by 'head.' */
192 static void
193 delete_bucket(db_table_desc *head)
194 {
195 	db_table_desc * nextone, *current;
196 
197 	for (current = head; current != NULL; current = nextone) {
198 		nextone = current->next;	// remember next
199 		delete_table_desc(current);
200 	}
201 }
202 
203 static void
204 delete_dictionary(db_dict_desc *dict)
205 {
206 	db_table_desc* bucket;
207 	int i;
208 	if (dict) {
209 		if (dict->tables.tables_val) {
210 			/* delete each bucket */
211 			for (i = 0; i < dict->tables.tables_len; i++)
212 				bucket = dict->tables.tables_val[i];
213 				if (bucket)
214 					delete_bucket(bucket);
215 			/* delete table */
216 			delete dict->tables.tables_val;
217 		}
218 		/* delete dictionary */
219 		delete dict;
220 	}
221 }
222 
223 /* Relocate bucket starting with this entry to new hashtable 'new_tab'. */
224 static void
225 relocate_bucket(db_table_desc* bucket,
226 		db_table_desc_p *new_tab, unsigned long hashsize)
227 {
228 	db_table_desc_p np, next_np, *hp;
229 
230 	for (np = bucket; np != NULL; np = next_np) {
231 		next_np = np->next;
232 		hp = &new_tab[np->hashval % hashsize];
233 		np->next = *hp;
234 		*hp = np;
235 	}
236 }
237 
238 /*
239  * Return pointer to entry with same hash value and table_name
240  * as those supplied.  Returns NULL if not found.
241  */
242 static db_status
243 enumerate_bucket(db_table_desc* bucket, db_status(*func)(db_table_desc *))
244 {
245 	db_table_desc_p np;
246 	db_status status;
247 
248 	for (np = bucket; np != NULL; np = np->next) {
249 		status = (func)(np);
250 		if (status != DB_SUCCESS)
251 			return (status);
252 	}
253 	return (DB_SUCCESS);
254 }
255 
256 
257 /*
258  * Return pointer to entry with same hash value and table_name
259  * as those supplied.  Returns NULL if not found.
260  */
261 static db_table_desc_p
262 search_bucket(db_table_desc* bucket, unsigned long hval, char *target)
263 {
264 	db_table_desc_p np;
265 
266 	for (np = bucket; np != NULL; np = np->next) {
267 		if (np->hashval == hval &&
268 		    strcmp(np->table_name, target) == 0) {
269 			break;
270 		}
271 	}
272 	return (np);
273 }
274 
275 
276 /*
277  * Remove entry with the specified hashvalue and target table name.
278  * Returns 'TRUE' if successful, FALSE otherwise.
279  * If the entry being removed is at the head of the list, then
280  * the head is updated to reflect the removal. The storage for the
281  * entry is freed if desired.
282  */
283 static bool_t
284 remove_from_bucket(db_table_desc_p bucket,
285 		db_table_desc_p *head, unsigned long hval, char *target,
286 		bool_t free_storage)
287 {
288 	db_table_desc_p np, dp;
289 
290 	/* Search for it in the bucket */
291 	for (dp = np = bucket; np != NULL; np = np->next) {
292 		if (np->hashval == hval &&
293 		    strcmp(np->table_name, target) == 0) {
294 			break;
295 		} else {
296 			dp = np;
297 		}
298 	}
299 
300 	if (np == NULL)
301 		return (FALSE);	// cannot delete if it is not there
302 
303 	if (dp == np) {
304 		*head = np->next;	// deleting head of bucket
305 	} else {
306 		dp->next = np->next;	// deleting interior link
307 	}
308 	if (free_storage)
309 		delete_table_desc(np);
310 
311 	return (TRUE);
312 }
313 
314 
315 /*
316  * Add given entry to the bucket pointed to by 'bucket'.
317  * If an entry with the same table_name is found, no addition
318  * is done.  The entry is added to the head of the bucket.
319  */
320 static bool_t
321 add_to_bucket(db_table_desc_p bucket, db_table_desc **head, db_table_desc_p td)
322 {
323 	db_table_desc_p curr, prev;
324 	register char *target_name;
325 	unsigned long target_hval;
326 	target_name = td->table_name;
327 	target_hval = td->hashval;
328 
329 	/* Search for it in the bucket */
330 	for (prev = curr = bucket; curr != NULL; curr = curr->next) {
331 		if (curr->hashval == target_hval &&
332 		    strcmp(curr->table_name, target_name) == 0) {
333 			break;
334 		} else {
335 			prev = curr;
336 		}
337 	}
338 
339 	if (curr != NULL)
340 		return (FALSE);  /* duplicates not allowed */
341 
342 	curr = *head;
343 	*head = td;
344 	td->next = curr;
345 	return (TRUE);
346 }
347 
348 
349 /* Print bucket starting with this entry. */
350 static void
351 print_bucket(db_table_desc *head)
352 {
353 	db_table_desc *np;
354 	for (np = head; np != NULL; np = np->next) {
355 		printf("%s: %d\n", np->table_name, np->hashval);
356 	}
357 }
358 
359 static db_status
360 print_table(db_table_desc *tbl)
361 {
362 	if (tbl == NULL)
363 		return (DB_BADTABLE);
364 	printf("%s: %d\n", tbl->table_name, tbl->hashval);
365 	return (DB_SUCCESS);
366 }
367 
368 
369 static int hashsizes[] = {		/* hashtable sizes */
370 	11,
371 	53,
372 	113,
373 	337,
374 	977,
375 	2053,
376 	4073,
377 	8011,
378 	16001,
379 	0
380 };
381 
382 // prevents wrap around numbers from being passed
383 #define	CALLOC_LIMIT 536870911
384 
385 /* Returns the next size to use for the hash table */
386 static unsigned int
387 get_next_hashsize(long unsigned oldsize)
388 {
389 	long unsigned newsize, n;
390 	if (oldsize == 0)
391 		newsize = hashsizes[0];
392 	else {
393 		for (n = 0; newsize = hashsizes[n++]; )
394 			if (oldsize == newsize) {
395 				newsize = hashsizes[n];	/* get next size */
396 				break;
397 			}
398 		if (newsize == 0)
399 			newsize = oldsize * 2 + 1;	/* just double */
400 	}
401 	return (newsize);
402 }
403 
404 /*
405  * Grow the current hashtable upto the next size.
406  * The contents of the existing hashtable is copied to the new one and
407  * relocated according to its hashvalue relative to the new size.
408  * Old table is deleted after the relocation.
409  */
410 static void
411 grow_dictionary(db_dict_desc_p dd)
412 {
413 	unsigned int oldsize, i, new_size;
414 	db_table_desc_p * oldtab, *newtab;
415 
416 	oldsize = dd->tables.tables_len;
417 	oldtab = dd->tables.tables_val;
418 
419 	new_size = get_next_hashsize(oldsize);
420 
421 	if (new_size > CALLOC_LIMIT) {
422 		FATAL("db_dictionary::grow: table size exceeds calloc limit",
423 			DB_MEMORY_LIMIT);
424 	}
425 
426 	if ((newtab = (db_table_desc_p*)
427 		calloc((unsigned int) new_size,
428 			sizeof (db_table_desc_p))) == NULL) {
429 		FATAL("db_dictionary::grow: cannot allocate space",
430 			DB_MEMORY_LIMIT);
431 	}
432 
433 	if (oldtab != NULL) {		// must transfer contents of old to new
434 		for (i = 0; i < oldsize; i++) {
435 			relocate_bucket(oldtab[i], newtab, new_size);
436 		}
437 		delete oldtab;		// delete old hashtable
438 	}
439 
440 	dd->tables.tables_val = newtab;
441 	dd->tables.tables_len = new_size;
442 }
443 
444 #define	HASHSHIFT	3
445 #define	HASHMASK	0x1f
446 
447 static u_int
448 get_hashval(char *value)
449 {
450 	int i, len;
451 	u_int hval = 0;
452 
453 	len = strlen(value);
454 	for (i = 0; i < len; i++) {
455 		hval = ((hval<<HASHSHIFT)^hval);
456 		hval += (value[i] & HASHMASK);
457 	}
458 
459 	return (hval);
460 }
461 
462 static db_status
463 enumerate_dictionary(db_dict_desc *dd, db_status (*func) (db_table_desc*))
464 {
465 	int i;
466 	db_table_desc *bucket;
467 	db_status status;
468 
469 	if (dd == NULL)
470 		return (DB_SUCCESS);
471 
472 	for (i = 0; i < dd->tables.tables_len; i++) {
473 		bucket = dd->tables.tables_val[i];
474 		if (bucket) {
475 			status = enumerate_bucket(bucket, func);
476 			if (status != DB_SUCCESS)
477 				return (status);
478 		}
479 	}
480 
481 	return (DB_SUCCESS);
482 }
483 
484 
485 /*
486  * Look up target table_name in hashtable and return its db_table_desc.
487  * Return NULL if not found.
488  */
489 static db_table_desc *
490 search_dictionary(db_dict_desc *dd, char *target)
491 {
492 	register unsigned long hval;
493 	unsigned long bucket;
494 
495 	if (target == NULL || dd == NULL || dd->tables.tables_len == 0)
496 		return (NULL);
497 
498 	hval = get_hashval(target);
499 	bucket = hval % dd->tables.tables_len;
500 
501 	db_table_desc_p fst = dd->tables.tables_val[bucket];
502 
503 	if (fst != NULL)
504 		return (search_bucket(fst, hval, target));
505 	else
506 		return (NULL);
507 }
508 
509 /*
510  * Remove the entry with the target table_name from the dictionary.
511  * If successful, return DB_SUCCESS; otherwise DB_NOTUNIQUE if target
512  * is null; DB_NOTFOUND if entry is not found.
513  * If successful, decrement count of number of entries in hash table.
514  */
515 static db_status
516 remove_from_dictionary(db_dict_desc *dd, char *target, bool_t remove_storage)
517 {
518 	register unsigned long hval;
519 	unsigned long bucket;
520 	register db_table_desc *fst;
521 
522 	if (target == NULL)
523 		return (DB_NOTUNIQUE);
524 	if (dd == NULL || dd->tables.tables_len == 0)
525 		return (DB_NOTFOUND);
526 	hval = get_hashval(target);
527 	bucket = hval % dd->tables.tables_len;
528 	fst = dd->tables.tables_val[bucket];
529 	if (fst == NULL)
530 		return (DB_NOTFOUND);
531 	if (remove_from_bucket(fst, &dd->tables.tables_val[bucket],
532 			hval, target, remove_storage)) {
533 		--(dd->count);
534 		return (DB_SUCCESS);
535 	} else
536 		return (DB_NOTFOUND);
537 }
538 
539 /*
540  * Add a new db_table_desc to the dictionary.
541  * Return DB_NOTUNIQUE, if entry with identical table_name
542  * already exists.  If entry is added, return DB_SUCCESS.
543  * Increment count of number of entries in index table and grow table
544  * if number of entries equals size of table.
545  *
546  * Inputs: db_dict_desc_p dd	pointer to dictionary to add to.
547  *	   db_table_desc *td	pointer to table entry to be added. The
548  * 				db_table_desc.next field will be altered
549  *				without regard to it's current setting.
550  *				This means that if next points to a list of
551  *				table entries, they may be either linked into
552  *				the dictionary unexpectly or cut off (leaked).
553  */
554 static db_status
555 add_to_dictionary(db_dict_desc_p dd, db_table_desc *td)
556 {
557 	register unsigned long hval;
558 	char *target;
559 
560 	if (dd == NULL)
561 		return (DB_NOTFOUND);
562 
563 	if (td == NULL)
564 		return (DB_NOTFOUND);
565 	target = td->table_name;
566 	if (target == NULL)
567 		return (DB_NOTUNIQUE);
568 
569 	hval = get_hashval(target);
570 
571 	if (dd->tables.tables_val == NULL)
572 		grow_dictionary(dd);
573 
574 	db_table_desc_p fst;
575 	unsigned long bucket;
576 	bucket = hval % dd->tables.tables_len;
577 	fst = dd->tables.tables_val[bucket];
578 	td->hashval = hval;
579 	if (fst == NULL)  { /* Empty bucket */
580 		dd->tables.tables_val[bucket] = td;
581 	} else if (!add_to_bucket(fst, &dd->tables.tables_val[bucket], td)) {
582 			return (DB_NOTUNIQUE);
583 		}
584 
585 	/* increase hash table size if number of entries equals table size */
586 	if (++(dd->count) > dd->tables.tables_len)
587 		grow_dictionary(dd);
588 
589 	return (DB_SUCCESS);
590 }
591 
592 /* ******************* pickling routines for dictionary ******************* */
593 
594 
595 /* Does the actual writing to/from file specific for db_dict_desc structure. */
596 static bool_t
597 transfer_aux(XDR* x, pptr tbl)
598 {
599 	return (xdr_db_dict_desc_p(x, (db_dict_desc_p *) tbl));
600 }
601 
602 class pickle_dict_desc: public pickle_file {
603     public:
604 	pickle_dict_desc(char *f, pickle_mode m) : pickle_file(f, m) {}
605 
606 	/* Transfers db_dict_desc structure pointed to by dp to/from file. */
607 	int transfer(db_dict_desc_p * dp)
608 		{ return (pickle_file::transfer((pptr) dp, &transfer_aux)); }
609 };
610 
611 /* ************************ dictionary methods *************************** */
612 
613 db_dictionary::db_dictionary()
614 {
615 	dictionary = NULL;
616 	initialized = FALSE;
617 	filename = NULL;
618 	tmpfilename = NULL;
619 	logfilename = NULL;
620 	logfile = NULL;
621 	logfile_opened = FALSE;
622 	changed = FALSE;
623 	INITRW(dict);
624 	READLOCKOK(dict);
625 }
626 
627 /*
628  * This routine clones an entire hash bucket chain. If you clone a
629  * data dictionary entry with the ->next pointer set, you will get a
630  * clone of that entry, as well as the entire linked list. This can cause
631  * pain if you then pass the cloned bucket to routines such as
632  * add_to_dictionary(), which do not expect nor handle dictionary hash
633  * entries with the ->next pointer set. You might either get duplicate
634  * entires or lose entries. If you wish to clone the entire bucket chain
635  * and add it to a new dictionary, loop through the db_table_desc->next list
636  * and call add_to_dictionary() for each item.
637  */
638 int
639 db_dictionary::db_clone_bucket(db_table_desc *bucket, db_table_desc **clone)
640 {
641 	u_long		size;
642 	XDR		xdrs;
643 	char		*bufin = NULL;
644 
645 	READLOCK(this, DB_LOCK_ERROR, "r db_dictionary::db_clone_bucket");
646 	db_func use_this = xdr_db_table_desc;
647 	size = xdr_sizeof((xdrproc_t) use_this, (void *) bucket);
648 	bufin = (char *) calloc(1, (size_t) size * sizeof (char));
649 	if (!bufin) {
650 		READUNLOCK(this, DB_MEMORY_LIMIT,
651 			"db_dictionary::insert_modified_table: out of memory");
652 		FATAL3("db_dictionary::insert_modified_table: out of memory",
653 			DB_MEMORY_LIMIT, 0);
654 	}
655 	xdrmem_create(&xdrs, bufin, (size_t) size, XDR_ENCODE);
656 	if (!xdr_db_table_desc(&xdrs, bucket)) {
657 		free(bufin);
658 		xdr_destroy(&xdrs);
659 		READUNLOCK(this, DB_MEMORY_LIMIT,
660 		"db_dictionary::insert_modified_table: xdr encode failed");
661 		FATAL3(
662 		"db_dictionary::insert_modified_table: xdr encode failed.",
663 		DB_MEMORY_LIMIT, 0);
664 	}
665 	*clone = (db_table_desc *) calloc(1, (size_t) size * sizeof (char));
666 	if (!*clone) {
667 		xdr_destroy(&xdrs);
668 		free(bufin);
669 		READUNLOCK(this, DB_MEMORY_LIMIT,
670 			"db_dictionary::insert_modified_table: out of memory");
671 		FATAL3("db_dictionary::insert_modified_table: out of memory",
672 			DB_MEMORY_LIMIT, 0);
673 	}
674 
675 	xdrmem_create(&xdrs, bufin, (size_t) size, XDR_DECODE);
676 	if (!xdr_db_table_desc(&xdrs, *clone)) {
677 		free(bufin);
678 		free(*clone);
679 		xdr_destroy(&xdrs);
680 		READUNLOCK(this, DB_MEMORY_LIMIT,
681 		"db_dictionary::insert_modified_table: xdr encode failed");
682 		FATAL3(
683 		"db_dictionary::insert_modified_table: xdr encode failed.",
684 		DB_MEMORY_LIMIT, 0);
685 	}
686 	free(bufin);
687 	xdr_destroy(&xdrs);
688 	READUNLOCK(this, DB_LOCK_ERROR, "ru db_dictionary::db_clone_bucket");
689 	return (1);
690 }
691 
692 
693 int
694 db_dictionary::change_table_name(db_table_desc *clone, char *tok, char *repl)
695 {
696 	char 	*newname;
697 	char	*loc_end, *loc_beg;
698 
699 	WRITELOCK(this, DB_LOCK_ERROR, "w db_dictionary::change_table_name");
700 	while (clone) {
701 		/*
702 		 * Special case for a tok="". This is used for the
703 		 * nisrestore(1M), when restoring a replica in another
704 		 * domain. This routine is used to change the datafile
705 		 * names in the data.dict (see bugid #4031273). This will not
706 		 * effect massage_dict(), since it never generates an empty
707 		 * string for tok.
708 		 */
709 		if (strlen(tok) == 0) {
710 			strcat(clone->table_name, repl);
711 			clone = clone->next;
712 			continue;
713 		}
714 		newname = (char *) calloc(1, sizeof (char) *
715 				strlen(clone->table_name) +
716 				strlen(repl) - strlen(tok) + 1);
717 		if (!newname) {
718 			WRITEUNLOCK(this, DB_MEMORY_LIMIT,
719 			"db_dictionary::change_table_name: out of memory");
720 		    FATAL3("db_dictionary::change_table_name: out of memory.",
721 				DB_MEMORY_LIMIT, 0);
722 		}
723 		if (loc_beg = strstr(clone->table_name, tok)) {
724 			loc_end = loc_beg + strlen(tok);
725 			int s = loc_beg - clone->table_name;
726 			memcpy(newname, clone->table_name, s);
727 			strcat(newname + s, repl);
728 			strcat(newname, loc_end);
729 			free(clone->table_name);
730 			clone->table_name = newname;
731 		} else {
732 			free(newname);
733 		}
734 		clone = clone->next;
735 	}
736 	WRITEUNLOCK(this, DB_LOCK_ERROR,
737 			"wu db_dictionary::change_table_name");
738 	return (1);
739 }
740 
741 
742 #ifdef	curdict
743 #undef	curdict
744 #endif
745 /*
746  * A function to initialize the temporary dictionary from the real
747  * dictionary.
748  */
749 bool_t
750 db_dictionary::inittemp(char *dictname, db_dictionary& curdict)
751 {
752 	int status;
753 	db_table_desc_p	*newtab;
754 
755 	db_shutdown();
756 
757 	WRITELOCK(this, FALSE, "w db_dictionary::inittemp");
758 	if (initialized) {
759 		/* Someone else got in between db_shutdown() and lock() */
760 		WRITEUNLOCK(this, FALSE, "wu db_dictionary::inittemp");
761 		return (TRUE);
762 	}
763 
764 	pickle_dict_desc f(dictname, PICKLE_READ);
765 	filename = strdup(dictname);
766 	if (filename == NULL) {
767 		WRITEUNLOCK(this, FALSE,
768 			"db_dictionary::inittemp: could not allocate space");
769 		FATAL3("db_dictionary::inittemp: could not allocate space",
770 			DB_MEMORY_LIMIT, FALSE);
771 	}
772 	int len = strlen(filename);
773 	tmpfilename = new char[len+5];
774 	if (tmpfilename == NULL) {
775 		delete filename;
776 		WRITEUNLOCK(this, FALSE,
777 			"db_dictionary::inittemp: could not allocate space");
778 		FATAL3("db_dictionary::inittemp: could not allocate space",
779 			DB_MEMORY_LIMIT, FALSE);
780 	}
781 	logfilename = new char[len+5];
782 	if (logfilename == NULL) {
783 		delete filename;
784 		delete tmpfilename;
785 		WRITEUNLOCK(this, FALSE,
786 			"db_dictionary::inittemp: cannot allocate space");
787 		FATAL3("db_dictionary::inittemp: cannot allocate space",
788 			DB_MEMORY_LIMIT, FALSE);
789 	}
790 
791 	sprintf(tmpfilename, "%s.tmp", filename);
792 	sprintf(logfilename, "%s.log", filename);
793 	unlink(tmpfilename);  /* get rid of partial checkpoints */
794 	dictionary = NULL;
795 
796 	if ((status = f.transfer(&dictionary)) < 0) {
797 		initialized = FALSE;
798 	} else if (status == 1) { /* no dictionary exists, create one */
799 		dictionary = new db_dict_desc;
800 		if (dictionary == NULL) {
801 			WRITEUNLOCK(this, FALSE,
802 			"db_dictionary::inittemp: could not allocate space");
803 			FATAL3(
804 			"db_dictionary::inittemp: could not allocate space",
805 			DB_MEMORY_LIMIT, FALSE);
806 		}
807 		dictionary->tables.tables_len =
808 				curdict.dictionary->tables.tables_len;
809 		if ((newtab = (db_table_desc_p *) calloc(
810 			(unsigned int) dictionary->tables.tables_len,
811 			sizeof (db_table_desc_p))) == NULL) {
812 			WRITEUNLOCK(this, FALSE,
813 			"db_dictionary::inittemp: cannot allocate space");
814 			FATAL3(
815 			"db_dictionary::inittemp: cannot allocate space",
816 			DB_MEMORY_LIMIT, 0);
817 		}
818 		dictionary->tables.tables_val = newtab;
819 		dictionary->count = 0;
820 		dictionary->impl_vers = curdict.dictionary->impl_vers;
821 		initialized = TRUE;
822 	} else	/* dictionary loaded successfully */
823 		initialized = TRUE;
824 
825 	if (initialized == TRUE) {
826 		changed = FALSE;
827 		reset_log();
828 	}
829 
830 	WRITEUNLOCK(this, FALSE, "wu db_dictionary::inittemp");
831 	return (initialized);
832 }
833 
834 
835 /*
836  * This method replaces the token string specified with the replacment
837  * string specified. It assumes that at least one and only one instance of
838  * the token exists. It is the responsibility of the caller to ensure that
839  * the above assumption stays valid.
840  */
841 db_status
842 db_dictionary::massage_dict(char *newdictname, char *tok, char *repl)
843 {
844 	int		retval;
845 	u_int		i, tbl_count;
846 	db_status	status;
847 	db_table_desc 	*bucket, *np, *clone, *next_np;
848 	char		tail[NIS_MAXNAMELEN];
849 	db_dictionary	*tmpptr;
850 
851 	WRITELOCK(this, DB_LOCK_ERROR, "w db_dictionary::massage_dict");
852 	if (dictionary == NULL) {
853 		WRITEUNLOCK(this, DB_INTERNAL_ERROR,
854 		"db_dictionary::massage_dict: uninitialized dictionary file");
855 		FATAL3(
856 		"db_dictionary::massage_dict: uninitialized dictionary file.",
857 		DB_INTERNAL_ERROR, DB_INTERNAL_ERROR);
858 	}
859 
860 	if ((tbl_count = dictionary->count) == 0) {
861 		WRITEUNLOCK(this, DB_SUCCESS,
862 				"wu db_dictionary::massage_dict");
863 		return (DB_SUCCESS);
864 	}
865 
866 	/* First checkpoint */
867 	if ((status = checkpoint()) != DB_SUCCESS) {
868 		WRITEUNLOCK(this, status, "wu db_dictionary::massage_dict");
869 		return (status);
870 	}
871 
872 #ifdef DEBUG
873 	enumerate_dictionary(dictionary, &print_table);
874 #endif
875 
876 	/* Initialize the free dictionary so that we can start populating it */
877 	FreeDictionary->inittemp(newdictname, *this);
878 
879 	for (i = 0; i < dictionary->tables.tables_len; i++) {
880 		bucket = dictionary->tables.tables_val[i];
881 		if (bucket) {
882 			np = bucket;
883 			while (np != NULL) {
884 				next_np = np->next;
885 				retval = db_clone_bucket(np, &clone);
886 				if (retval != 1) {
887 					WRITEUNLOCK(this, DB_INTERNAL_ERROR,
888 					"wu db_dictionary::massage_dict");
889 					return (DB_INTERNAL_ERROR);
890 				}
891 				if (change_table_name(clone, tok, repl) == -1) {
892 					delete_table_desc(clone);
893 					WRITEUNLOCK(this, DB_INTERNAL_ERROR,
894 					"wu db_dictionary::massage_dict");
895 					return (DB_INTERNAL_ERROR);
896 				}
897 				/*
898 				 * We know we don't have a log file, so we will
899 				 * just add to the in-memory database and dump
900 				 * all of it once we are done.
901 				 */
902 				status = add_to_dictionary
903 						(FreeDictionary->dictionary,
904 						clone);
905 				if (status != DB_SUCCESS) {
906 					delete_table_desc(clone);
907 					WRITEUNLOCK(this, DB_INTERNAL_ERROR,
908 					"wu db_dictionary::massage_dict");
909 					return (DB_INTERNAL_ERROR);
910 				}
911 				status = remove_from_dictionary(dictionary,
912 							np->table_name, TRUE);
913 				if (status != DB_SUCCESS) {
914 					delete_table_desc(clone);
915 					WRITEUNLOCK(this, DB_INTERNAL_ERROR,
916 					"wu db_dictionary::massage_dict");
917 					return (DB_INTERNAL_ERROR);
918 				}
919 				np = next_np;
920 			}
921 		}
922 	}
923 
924 	if (FreeDictionary->dump() != DB_SUCCESS) {
925 		WRITEUNLOCK(this, DB_INTERNAL_ERROR,
926 				"wu db_dictionary::massage_dict");
927 		FATAL3(
928 		"db_dictionary::massage_dict: Unable to dump new dictionary.",
929 		DB_INTERNAL_ERROR, DB_INTERNAL_ERROR);
930 	}
931 
932 	/*
933 	 * Now, shutdown the inuse dictionary and update the FreeDictionary
934 	 * and InUseDictionary pointers as well. Also, delete the old dictionary
935 	 * file.
936 	 */
937 	unlink(filename); /* There shouldn't be a tmpfile or logfile */
938 	db_shutdown();
939 	tmpptr = InUseDictionary;
940 	InUseDictionary = FreeDictionary;
941 	FreeDictionary = tmpptr;
942 	WRITEUNLOCK(this, DB_LOCK_ERROR, "wu db_dictionary::massage_dict");
943 	return (DB_SUCCESS);
944 }
945 
946 
947 db_status
948 db_dictionary::merge_dict(db_dictionary& tempdict, char *tok, char *repl)
949 {
950 
951 	db_status	dbstat = DB_SUCCESS;
952 
953 	db_table_desc	*tbl = NULL, *clone = NULL, *next_td = NULL;
954 	int		retval, i;
955 
956 	WRITELOCK(this, DB_LOCK_ERROR, "w db_dictionary::merge_dict");
957 
958 	for (i = 0; i < tempdict.dictionary->tables.tables_len; ++i) {
959 		tbl = tempdict.dictionary->tables.tables_val[i];
960 		if (!tbl)
961 			continue;
962 		retval = db_clone_bucket(tbl, &clone);
963 		if (retval != 1) {
964 			WRITEUNLOCK(this, DB_INTERNAL_ERROR,
965 					"wu db_dictionary::merge_dict");
966 			return (DB_INTERNAL_ERROR);
967 		}
968 		while (clone) {
969 			next_td = clone->next;
970 			clone->next = NULL;
971 			if ((tok) &&
972 				(change_table_name(clone, tok, repl) == -1)) {
973 				delete_table_desc(clone);
974 				if (next_td)
975 					delete_table_desc(next_td);
976 				WRITEUNLOCK(this, DB_INTERNAL_ERROR,
977 					"wu db_dictionary::merge_dict");
978 				return (DB_INTERNAL_ERROR);
979 			}
980 
981 			dbstat = add_to_dictionary(dictionary, clone);
982 			if (dbstat == DB_NOTUNIQUE) {
983 				/* Overide */
984 				dbstat = remove_from_dictionary(dictionary,
985 						clone->table_name, TRUE);
986 				if (dbstat != DB_SUCCESS) {
987 					WRITEUNLOCK(this, dbstat,
988 					"wu db_dictionary::merge_dict");
989 					return (dbstat);
990 				}
991 				dbstat = add_to_dictionary(dictionary,
992 								clone);
993 			} else {
994 				if (dbstat != DB_SUCCESS) {
995 					WRITEUNLOCK(this, dbstat,
996 					"wu db_dictionary::merge_dict");
997 					return (dbstat);
998 				}
999 			}
1000 			clone = next_td;
1001 		}
1002 	}
1003 /*
1004  * If we were successful in merging the dictionaries, then mark the
1005  * dictionary changed, so that it will be properly checkpointed and
1006  * dumped to disk.
1007  */
1008 	if (dbstat == DB_SUCCESS)
1009 		changed = TRUE;
1010 	WRITEUNLOCK(this, DB_LOCK_ERROR, "wu db_dictionary::merge_dict");
1011 	return (dbstat);
1012 }
1013 
1014 int
1015 db_dictionary::copyfile(char *infile, char *outfile)
1016 {
1017 	db_table_desc	*tbl = NULL;
1018 	db	*dbase;
1019 	int	ret;
1020 
1021 	READLOCK(this, DB_LOCK_ERROR, "r db_dictionary::copyfile");
1022 	/*
1023 	 * We need to hold the read-lock until the dump() is done.
1024 	 * However, we must avoid the lock migration (read -> write)
1025 	 * that would happen in find_table() if the db must be loaded.
1026 	 * Hence, look first look for an already loaded db.
1027 	 */
1028 	dbase  = find_table(infile, &tbl, TRUE, TRUE, FALSE);
1029 	if (dbase == NULL) {
1030 		/* Release the read-lock, and try again, allowing load */
1031 		READUNLOCK(this, DB_LOCK_ERROR, "ru db_dictionary::copyfile");
1032 		dbase  = find_table(infile, &tbl, TRUE, TRUE, TRUE);
1033 		if (dbase == NULL)
1034 			return (DB_NOTFOUND);
1035 		/*
1036 		 * Read-lock again, and get a 'tbl' we can use since we're
1037 		 * still holding the lock.
1038 		 */
1039 		READLOCK(this, DB_LOCK_ERROR, "r db_dictionary::copyfile");
1040 		dbase  = find_table(infile, &tbl, TRUE, TRUE, FALSE);
1041 		if (dbase == NULL) {
1042 			READUNLOCK(this, DB_NOTFOUND,
1043 					"ru db_dictionary::copyfile");
1044 			return (DB_NOTFOUND);
1045 		}
1046 	}
1047 	ret = tbl->database->dump(outfile) ? DB_SUCCESS : DB_INTERNAL_ERROR;
1048 	READUNLOCK(this, ret, "ru db_dictionary::copyfile");
1049 	return (ret);
1050 }
1051 
1052 
1053 bool_t
1054 db_dictionary::extract_entries(db_dictionary& tempdict, char **fs, int fscnt)
1055 {
1056 	int		i, retval;
1057 	db_table_desc	*tbl, *clone;
1058 	db_table_desc	tbl_ent;
1059 	db_status	dbstat;
1060 
1061 	READLOCK(this, FALSE, "r db_dictionary::extract_entries");
1062 	for (i = 0; i < fscnt; ++i) {
1063 		tbl = find_table_desc(fs[i]);
1064 		if (!tbl) {
1065 			syslog(LOG_DEBUG,
1066 				"extract_entries: no dictionary entry for %s",
1067 				fs[i]);
1068 			READUNLOCK(this, FALSE,
1069 					"ru db_dictionary::extract_entries");
1070 			return (FALSE);
1071 		} else {
1072 			tbl_ent.table_name = tbl->table_name;
1073 			tbl_ent.hashval = tbl->hashval;
1074 			tbl_ent.scheme = tbl->scheme;
1075 			tbl_ent.database = tbl->database;
1076 			tbl_ent.next = NULL;
1077 		}
1078 		retval = db_clone_bucket(&tbl_ent, &clone);
1079 		if (retval != 1) {
1080 			syslog(LOG_DEBUG,
1081 			"extract_entries: unable to clone entry for %s",
1082 			fs[i]);
1083 			READUNLOCK(this, FALSE,
1084 					"ru db_dictionary::extract_entries");
1085 			return (FALSE);
1086 		}
1087 		dbstat = add_to_dictionary(tempdict.dictionary, clone);
1088 		if (dbstat != DB_SUCCESS) {
1089 			delete_table_desc(clone);
1090 			READUNLOCK(this, FALSE,
1091 					"ru db_dictionary::extract_entries");
1092 			return (FALSE);
1093 		}
1094 	}
1095 	if (tempdict.dump() != DB_SUCCESS) {
1096 		READUNLOCK(this, FALSE,
1097 				"ru db_dictionary::extract_entries");
1098 		return (FALSE);
1099 	}
1100 	READUNLOCK(this, FALSE,
1101 			"ru db_dictionary::extract_entries");
1102 	return (TRUE);
1103 }
1104 
1105 
1106 /*
1107  * Initialize dictionary from contents in 'file'.
1108  * If there is already information in this dictionary, it is removed.
1109  * Therefore, regardless of whether the load from the file succeeds,
1110  * the contents of this dictionary will be altered.  Returns
1111  * whether table has been initialized successfully.
1112  */
1113 bool_t
1114 db_dictionary::init(char *file)
1115 {
1116 	int status;
1117 
1118 	WRITELOCK(this, FALSE, "w db_dictionary::init");
1119 	db_shutdown();
1120 
1121 	pickle_dict_desc f(file, PICKLE_READ);
1122 	filename = strdup(file);
1123 	if (filename == NULL) {
1124 		WRITEUNLOCK(this, FALSE,
1125 			"db_dictionary::init: could not allocate space");
1126 		FATAL3("db_dictionary::init: could not allocate space",
1127 			DB_MEMORY_LIMIT, FALSE);
1128 	}
1129 	int len = strlen(filename);
1130 	tmpfilename = new char[len+5];
1131 	if (tmpfilename == NULL) {
1132 		delete filename;
1133 		WRITEUNLOCK(this, FALSE,
1134 			"db_dictionary::init: could not allocate space");
1135 		FATAL3("db_dictionary::init: could not allocate space",
1136 			DB_MEMORY_LIMIT, FALSE);
1137 	}
1138 	logfilename = new char[len+5];
1139 	if (logfilename == NULL) {
1140 		delete filename;
1141 		delete tmpfilename;
1142 		WRITEUNLOCK(this, FALSE,
1143 				"db_dictionary::init: cannot allocate space");
1144 		FATAL3("db_dictionary::init: cannot allocate space",
1145 			DB_MEMORY_LIMIT, FALSE);
1146 	}
1147 
1148 	sprintf(tmpfilename, "%s.tmp", filename);
1149 	sprintf(logfilename, "%s.log", filename);
1150 	unlink(tmpfilename);  /* get rid of partial checkpoints */
1151 	dictionary = NULL;
1152 
1153 	/* load dictionary */
1154 	if ((status = f.transfer(&dictionary)) < 0) {
1155 	    initialized = FALSE;
1156 	} else if (status == 1) {  /* no dictionary exists, create one */
1157 	    dictionary = new db_dict_desc;
1158 	    if (dictionary == NULL) {
1159 		WRITEUNLOCK(this, FALSE,
1160 			"db_dictionary::init: could not allocate space");
1161 		FATAL3("db_dictionary::init: could not allocate space",
1162 			DB_MEMORY_LIMIT, FALSE);
1163 	    }
1164 	    dictionary->tables.tables_len = 0;
1165 	    dictionary->tables.tables_val = NULL;
1166 	    dictionary->count = 0;
1167 	    dictionary->impl_vers = DB_CURRENT_VERSION;
1168 	    initialized = TRUE;
1169 	} else  /* dictionary loaded successfully */
1170 	    initialized = TRUE;
1171 
1172 	if (initialized == TRUE) {
1173 	    int num_changes = 0;
1174 	    changed = FALSE;
1175 	    reset_log();
1176 	    if ((num_changes = incorporate_log(logfilename)) < 0)
1177 		syslog(LOG_ERR,
1178 			"incorporation of dictionary logfile '%s' failed",
1179 			logfilename);
1180 	    changed = (num_changes > 0);
1181 	}
1182 
1183 	WRITEUNLOCK(this, initialized, "wu db_dictionary::init");
1184 	return (initialized);
1185 }
1186 
1187 /*
1188  * Execute log entry 'j' on the dictionary identified by 'dict' if the
1189  * version of j is later than that of the dictionary.  If 'j' is executed,
1190  * 'count' is incremented and the dictionary's verison is updated to
1191  * that of 'j'.
1192  * Returns TRUE always for valid log entries; FALSE otherwise.
1193  */
1194 static bool_t
1195 apply_log_entry(db_dictlog_entry *j, char *dictchar, int *count)
1196 {
1197 	db_dictionary *dict = (db_dictionary*) dictchar;
1198 
1199 	WRITELOCK(dict, FALSE, "w apply_log_entry");
1200 	if (db_update_version.earlier_than(j->get_version())) {
1201 		++ *count;
1202 #ifdef DEBUG
1203 		j->print();
1204 #endif /* DEBUG */
1205 		switch (j->get_action()) {
1206 		case DB_ADD_TABLE:
1207 			dict->add_table_aux(j->get_table_name(),
1208 				j->get_table_object(), INMEMORY_ONLY);
1209 			// ignore status
1210 			break;
1211 
1212 		case DB_REMOVE_TABLE:
1213 			dict->delete_table_aux(j->get_table_name(),
1214 							INMEMORY_ONLY);
1215 			// ignore status
1216 			break;
1217 
1218 		default:
1219 			WARNING("db::apply_log_entry: unknown action_type");
1220 			WRITEUNLOCK(dict, FALSE, "wu apply_log_entry");
1221 			return (FALSE);
1222 		}
1223 		db_update_version.assign(j->get_version());
1224 	}
1225 
1226 	WRITEUNLOCK(dict, TRUE, "wu apply_log_entry");
1227 	return (TRUE);
1228 }
1229 
1230 int
1231 db_dictionary::incorporate_log(char *file_name)
1232 {
1233 	db_dictlog f(file_name, PICKLE_READ);
1234 	int	ret;
1235 
1236 	WRITELOCK(this, -1, "w db_dictionary::incorporate_log");
1237 	setNoWriteThrough();
1238 	ret = f.execute_on_log(&(apply_log_entry), (char *) this);
1239 	clearNoWriteThrough();
1240 	WRITEUNLOCK(this, -1, "wu db_dictionary::incorporate_log");
1241 	return (ret);
1242 }
1243 
1244 
1245 /* Frees memory of filename and tables.  Has no effect on disk storage. */
1246 db_status
1247 db_dictionary::db_shutdown()
1248 {
1249 	WRITELOCK(this, DB_LOCK_ERROR, "w db_dictionary::db_shutdown");
1250 	if (!initialized) {
1251 		WRITEUNLOCK(this, DB_LOCK_ERROR,
1252 				"wu db_dictionary::db_shutdown");
1253 		return (DB_SUCCESS); /* DB_NOTFOUND? */
1254 	}
1255 
1256 	if (filename) {
1257 		delete filename;
1258 		filename = NULL;
1259 	}
1260 	if (tmpfilename) {
1261 		delete tmpfilename;
1262 		tmpfilename = NULL;
1263 	}
1264 	if (logfilename) {
1265 		delete logfilename;
1266 		logfilename = NULL;
1267 	}
1268 	if (dictionary) {
1269 		delete_dictionary(dictionary);
1270 		dictionary = NULL;
1271 	}
1272 	initialized = FALSE;
1273 	changed = FALSE;
1274 	reset_log();
1275 
1276 	WRITEUNLOCK(this, DB_LOCK_ERROR, "wu db_dictionary::db_shutdown");
1277 	return (DB_SUCCESS);
1278 }
1279 
1280 /*
1281  * Dump contents of this dictionary (minus the database representations)
1282  * to its file. Returns 0 if operation succeeds, -1 otherwise.
1283  */
1284 int
1285 db_dictionary::dump()
1286 {
1287 	int status;
1288 
1289 	READLOCK(this, -1, "r db_dictionary::dump");
1290 	if (!initialized) {
1291 		READUNLOCK(this, -1, "ru db_dictionary::dump");
1292 		return (-1);
1293 	}
1294 
1295 	unlink(tmpfilename);  /* get rid of partial dumps */
1296 	pickle_dict_desc f(tmpfilename, PICKLE_WRITE);
1297 
1298 	status = f.transfer(&dictionary); 	/* dump table descs */
1299 	if (status != 0) {
1300 		WARNING("db_dictionary::dump: could not write out dictionary");
1301 	} else if (rename(tmpfilename, filename) < 0) {
1302 		WARNING_M("db_dictionary::dump: could not rename temp file: ");
1303 		status = -1;
1304 	}
1305 
1306 	READUNLOCK(this, -1, "ru db_dictionary::dump");
1307 	return (status);
1308 }
1309 
1310 /*
1311  * Write out in-memory copy of dictionary to file.
1312  * 1.  Update major version.
1313  * 2.  Dump contents to temporary file.
1314  * 3.  Rename temporary file to real dictionary file.
1315  * 4.  Remove log file.
1316  * A checkpoint is done only if it has changed since the previous checkpoint.
1317  * Returns DB_SUCCESS if checkpoint was successful; error code otherwise
1318  */
1319 db_status
1320 db_dictionary::checkpoint()
1321 {
1322 	WRITELOCK(this, DB_LOCK_ERROR, "w db_dictionary::checkpoint");
1323 
1324 	if (changed == FALSE) {
1325 		WRITEUNLOCK(this, DB_LOCK_ERROR,
1326 				"wu db_dictionary::checkpoint");
1327 		return (DB_SUCCESS);
1328 	}
1329 
1330 	vers *oldv = new vers(db_update_version);	// copy
1331 	vers * newv = db_update_version.nextmajor();	// get next version
1332 	db_update_version.assign(newv);			// update version
1333 	delete newv;
1334 
1335 	if (dump() != 0) {
1336 		WARNING_M(
1337 		    "db_dictionary::checkpoint: could not dump dictionary: ");
1338 		db_update_version.assign(oldv);  // rollback
1339 		delete oldv;
1340 		WRITEUNLOCK(this, DB_INTERNAL_ERROR,
1341 			"wu db_dictionary::checkpoint");
1342 		return (DB_INTERNAL_ERROR);
1343 	}
1344 	unlink(logfilename);	/* should do atomic rename and log delete */
1345 	reset_log();		/* should check for what? */
1346 	delete oldv;
1347 	changed = FALSE;
1348 	WRITEUNLOCK(this, DB_LOCK_ERROR, "wu db_dictionary::checkpoint");
1349 	return (DB_SUCCESS);
1350 }
1351 
1352 /* close existing logfile and delete its structure */
1353 int
1354 db_dictionary::reset_log()
1355 {
1356 	WRITELOCK(this, -1, "w db_dictionary::reset_log");
1357 	/* try to close old log file */
1358 	/* doesnot matter since we do synchronous writes only */
1359 	if (logfile != NULL) {
1360 		if (logfile_opened == TRUE) {
1361 			if (logfile->close() < 0) {
1362 				WARNING_M(
1363 			"db_dictionary::reset_log: could not close log file: ");
1364 			}
1365 		}
1366 		delete logfile;
1367 		logfile = NULL;
1368 	}
1369 	logfile_opened = FALSE;
1370 	WRITEUNLOCK(this, -1, "wu db_dictionary::reset_log");
1371 	return (0);
1372 }
1373 
1374 /* close existing logfile, but leave its structure if exists */
1375 int
1376 db_dictionary::close_log()
1377 {
1378 	WRITELOCK(this, -1, "w db_dictionary::close_log");
1379 	if (logfile != NULL && logfile_opened == TRUE) {
1380 		logfile->close();
1381 	}
1382 	logfile_opened = FALSE;
1383 	WRITEUNLOCK(this, -1, "wu db_dictionary::close_log");
1384 	return (0);
1385 }
1386 
1387 /* open logfile, creating its structure if it does not exist */
1388 int
1389 db_dictionary::open_log()
1390 {
1391 	WRITELOCK(this, -1, "w db_dictionary::open_log");
1392 	if (logfile == NULL) {
1393 		if ((logfile = new db_dictlog(logfilename, PICKLE_APPEND)) ==
1394 				NULL) {
1395 			WRITEUNLOCK(this, -1, "wu db_dictionary::open_log");
1396 			FATAL3(
1397 			"db_dictionary::reset_log: cannot allocate space",
1398 				DB_MEMORY_LIMIT, -1);
1399 		}
1400 	}
1401 
1402 	if (logfile_opened == TRUE) {
1403 		WRITEUNLOCK(this, -1, "wu db_dictionary::open_log");
1404 		return (0);
1405 	}
1406 
1407 	if ((logfile->open()) == NULL) {
1408 		WARNING_M("db_dictionary::open_log: could not open log file: ");
1409 		delete logfile;
1410 		logfile = NULL;
1411 		WRITEUNLOCK(this, -1, "wu db_dictionary::open_log");
1412 		return (-1);
1413 	}
1414 
1415 	logfile_opened = TRUE;
1416 	WRITEUNLOCK(this, -1, "wu db_dictionary::open_log");
1417 	return (0);
1418 }
1419 
1420 /*
1421  * closes any open log files for all tables in dictionary or 'tab'.
1422  * "tab" is an optional argument.
1423  */
1424 static int close_standby_list();
1425 
1426 db_status
1427 db_dictionary::db_standby(char *tab)
1428 {
1429 	db_table_desc *tbl;
1430 
1431 	WRITELOCK(this, DB_LOCK_ERROR, "w db_dictionary::db_standby");
1432 	if (!initialized) {
1433 		WRITEUNLOCK(this, DB_BADDICTIONARY,
1434 				"wu db_dictionary::db_standby");
1435 		return (DB_BADDICTIONARY);
1436 	}
1437 
1438 	if (tab == NULL) {
1439 	    close_log();  // close dictionary log
1440 	    close_standby_list();
1441 	    WRITEUNLOCK(this, DB_LOCK_ERROR, "wu db_dictionary::db_standby");
1442 	    return (DB_SUCCESS);
1443 	}
1444 
1445 	if ((tbl = find_table_desc(tab)) == NULL) {
1446 	    WRITEUNLOCK(this, DB_LOCK_ERROR, "wu db_dictionary::db_standby");
1447 	    return (DB_BADTABLE);
1448 	}
1449 
1450 	if (tbl->database != NULL)
1451 	    tbl->database->close_log();
1452 	WRITEUNLOCK(this, DB_LOCK_ERROR, "wu db_dictionary::db_standby");
1453 	return (DB_SUCCESS);
1454 }
1455 
1456 /*
1457  * Returns db_table_desc of table name 'tab'.  'prev', if supplied,
1458  * is set to the entry located ahead of 'tab's entry in the dictionary.
1459  */
1460 db_table_desc*
1461 db_dictionary::find_table_desc(char *tab)
1462 {
1463 	db_table_desc	*ret;
1464 
1465 	READLOCK(this, NULL, "r db_dictionary::find_table_desc");
1466 	if (initialized)
1467 		ret = search_dictionary(dictionary, tab);
1468 	else
1469 		ret = NULL;
1470 
1471 	READUNLOCK(this, ret, "r db_dictionary::find_table_desc");
1472 	return (ret);
1473 }
1474 
1475 db_table_desc *
1476 db_dictionary::find_table_desc(char *tab, bool_t searchDeferred) {
1477 	db_table_desc	*ret = NULL;
1478 
1479 	READLOCK(this, NULL, "r db_dictionary::find_table_desc_d");
1480 
1481 	/* If desired, look in the deferred dictionary first */
1482 	if (initialized && searchDeferred && deferred.dictionary != NULL)
1483 		ret = search_dictionary(deferred.dictionary, tab);
1484 
1485 	/* No result yet => search the "normal" dictionary */
1486 	if (ret == NULL)
1487 		ret = find_table_desc(tab);
1488 
1489 	READUNLOCK(this, ret, "r db_dictionary::find_table_desc_d");
1490 	return (ret);
1491 }
1492 
1493 db *
1494 db_dictionary::find_table(char *tab, db_table_desc **where) {
1495 	/* Most operations should use the deferred dictionary if it exists */
1496 	return (find_table(tab, where, TRUE, TRUE, TRUE));
1497 }
1498 
1499 db *
1500 db_dictionary::find_table(char *tab, db_table_desc **where,
1501 				bool_t searchDeferred) {
1502 	return (find_table(tab, where, searchDeferred, TRUE, TRUE));
1503 }
1504 
1505 db *
1506 db_dictionary::find_table(char *tab, db_table_desc **where,
1507 				bool_t searchDeferred, bool_t doLDAP,
1508 				bool_t doLoad) {
1509 	db			*res;
1510 	int			lstat;
1511 	db_status		dstat;
1512 	char			*myself = "db_dictionary::find_table";
1513 
1514 	res = find_table_noLDAP(tab, where, searchDeferred, doLoad);
1515 	/* If found, or shouldn't try LDAP, we're done */
1516 	if (res != 0 || !doLDAP)
1517 		return (res);
1518 
1519 	/* See if we can retrieve the object from LDAP */
1520 	dstat = dbCreateFromLDAP(tab, &lstat);
1521 	if (dstat != DB_SUCCESS) {
1522 		if (dstat == DB_NOTFOUND) {
1523 			if (lstat != LDAP_SUCCESS) {
1524 				logmsg(MSG_NOTIMECHECK, LOG_INFO,
1525 					"%s: LDAP error for \"%s\": %s",
1526 					myself, NIL(tab),
1527 					ldap_err2string(lstat));
1528 			}
1529 		} else {
1530 			logmsg(MSG_NOTIMECHECK, LOG_INFO,
1531 				"%s: DB error %d for \"%s\"",
1532 				myself, dstat, NIL(tab));
1533 		}
1534 		return (0);
1535 	}
1536 
1537 	/* Try the dictionary again */
1538 	res = find_table_noLDAP(tab, where, searchDeferred, doLoad);
1539 
1540 	return (res);
1541 }
1542 
1543 /*
1544  * Return database structure of table named by 'tab'.
1545  * If 'where' is set, set it to the table_desc of 'tab.'
1546  * If the database is loaded in from stable store if it has not been loaded.
1547  * If it cannot be loaded, it is initialized using the scheme stored in
1548  * the table_desc.  NULL is returned if the initialization fails.
1549  */
1550 db *
1551 db_dictionary::find_table_noLDAP(char *tab, db_table_desc **where,
1552 				bool_t searchDeferred, bool_t doLoad)
1553 {
1554 	if (!initialized)
1555 		return (NULL);
1556 
1557 	db_table_desc* tbl;
1558 	db *dbase = NULL;
1559 	int		lret;
1560 
1561 	READLOCK(this, NULL, "r db_dictionary::find_table");
1562 	tbl = find_table_desc(tab, searchDeferred);
1563 	if (tbl == NULL) {
1564 		READUNLOCK(this, NULL, "ru db_dictionary::find_table");
1565 		return (NULL);		// not found
1566 	}
1567 
1568 	if (tbl->database != NULL || !doLoad) {
1569 		if (tbl->database && where) *where = tbl;
1570 		READUNLOCK(this, NULL, "ru db_dictionary::find_table");
1571 		return (tbl->database);  // return handle
1572 	}
1573 
1574 	READUNLOCK(this, NULL, "ru db_dictionary::find_table");
1575 	WRITELOCK(this, NULL, "w db_dictionary::find_table");
1576 	/* Re-check; some other thread might have loaded the db */
1577 	if (tbl->database != NULL) {
1578 		if (where) *where = tbl;
1579 		WRITEUNLOCK(this, NULL, "wu db_dictionary::find_table");
1580 		return (tbl->database);  // return handle
1581 	}
1582 
1583 	// need to load in/init database
1584 	dbase = new db(tab);
1585 
1586 	if (dbase == NULL) {
1587 		WRITEUNLOCK(this, NULL,
1588 			"db_dictionary::find_table: could not allocate space");
1589 		FATAL3("db_dictionary::find_table: could not allocate space",
1590 			DB_MEMORY_LIMIT, NULL);
1591 	}
1592 
1593 	/*
1594 	 * Lock the newly created 'dbase', so we can release the general
1595 	 * db_dictionary lock.
1596 	 */
1597 	WRITELOCKNR(dbase, lret, "w dbase db_dictionary::find_table");
1598 	if (lret != 0) {
1599 		WRITEUNLOCK(this, NULL,
1600 			"db_dictionary::find_table: could not lock dbase");
1601 		FATAL3("db_dictionary::find_table: could not lock dbase",
1602 			DB_LOCK_ERROR, NULL);
1603 	}
1604 	/* Assign tbl->database, and then release the 'this' lock */
1605 	tbl->database = dbase;
1606 	WRITEUNLOCK(this, NULL, "wu db_dictionary::find_table");
1607 
1608 	if (dbase->load()) {			// try to load in database
1609 		if (where) *where = tbl;
1610 		WRITEUNLOCK(dbase, dbase, "wu dbase db_dictionary::find_table");
1611 		return (dbase);
1612 	}
1613 
1614 	delete dbase;
1615 	tbl->database = NULL;
1616 	WARNING("db_dictionary::find_table: could not load database");
1617 	return (NULL);
1618 }
1619 
1620 /* Log action to be taken on the  dictionary and update db_update_version. */
1621 
1622 db_status
1623 db_dictionary::log_action(int action, char *tab, table_obj *tobj)
1624 {
1625 	WRITELOCK(this, DB_LOCK_ERROR, "w db_dictionary::log_action");
1626 
1627 	vers *newv = db_update_version.nextminor();
1628 	db_dictlog_entry le(action, newv, tab, tobj);
1629 
1630 	if (open_log() < 0) {
1631 		delete newv;
1632 		WRITEUNLOCK(this, DB_STORAGE_LIMIT,
1633 				"wu db_dictionary::log_action");
1634 		return (DB_STORAGE_LIMIT);
1635 	}
1636 
1637 	if (logfile->append(&le) < 0) {
1638 		WARNING_M("db::log_action: could not add log entry: ");
1639 		close_log();
1640 		delete newv;
1641 		WRITEUNLOCK(this, DB_STORAGE_LIMIT,
1642 				"wu db_dictionary::log_action");
1643 		return (DB_STORAGE_LIMIT);
1644 	}
1645 
1646 	db_update_version.assign(newv);
1647 	delete newv;
1648 	changed = TRUE;
1649 
1650 	WRITEUNLOCK(this, DB_LOCK_ERROR, "wu db_dictionary::log_action");
1651 	return (DB_SUCCESS);
1652 }
1653 
1654 // For a complete 'delete' operation, we want the following behaviour:
1655 // 1. If there is an entry in the log, the physical table exists and is
1656 //    stable.
1657 // 2. If there is no entry in the log, the physical table may or may not
1658 //    exist.
1659 
1660 db_status
1661 db_dictionary::delete_table_aux(char *tab, int mode)
1662 {
1663 	db_status	ret;
1664 
1665 	WRITELOCK(this, DB_LOCK_ERROR, "w db_dictionary::delete_table_aux");
1666 	if (!initialized) {
1667 		WRITEUNLOCK(this, DB_LOCK_ERROR,
1668 				"wu db_dictionary::delete_table_aux");
1669 		return (DB_BADDICTIONARY);
1670 	}
1671 
1672 	db_table_desc *tbl;
1673 	if ((tbl = find_table_desc(tab)) == NULL) { // table not found
1674 		WRITEUNLOCK(this, DB_LOCK_ERROR,
1675 				"wu db_dictionary::delete_table_aux");
1676 		return (DB_NOTFOUND);
1677 	}
1678 
1679 	if (mode != INMEMORY_ONLY) {
1680 		int need_free = 0;
1681 
1682 		// Update log.
1683 		db_status status = log_action(DB_REMOVE_TABLE, tab);
1684 		if (status != DB_SUCCESS) {
1685 			WRITEUNLOCK(this, status,
1686 				"wu db_dictionary::delete_table_aux");
1687 			return (status);
1688 		}
1689 
1690 		// Remove physical structures
1691 		db *dbase = tbl->database;
1692 		if (dbase == NULL) {	// need to get desc to access files
1693 			dbase = new db(tab);
1694 			need_free = 1;
1695 		}
1696 		if (dbase == NULL) {
1697 			WARNING(
1698 		"db_dictionary::delete_table: could not create db structure");
1699 			WRITEUNLOCK(this, DB_MEMORY_LIMIT,
1700 					"wu db_dictionary::delete_table_aux");
1701 			return (DB_MEMORY_LIMIT);
1702 		}
1703 		dbase->remove_files();	// remove physical files
1704 		if (need_free)
1705 			delete dbase;
1706 	}
1707 
1708 	// Remove in-memory structures
1709 	ret = remove_from_dictionary(dictionary, tab, TRUE);
1710 	WRITEUNLOCK(this, ret, "wu db_dictionary::delete_table_aux");
1711 	return (ret);
1712 }
1713 
1714 /*
1715  * Delete table with given name 'tab' from dictionary.
1716  * Returns error code if table does not exist or if dictionary has not been
1717  * initialized.   Dictionary is updated to stable store if deletion is
1718  * successful.  Fatal error occurs if dictionary cannot be saved.
1719  * Returns DB_SUCCESS if dictionary has been updated successfully.
1720  * Note that the files associated with the table are also removed.
1721  */
1722 db_status
1723 db_dictionary::delete_table(char *tab)
1724 {
1725 	return (delete_table_aux(tab, !INMEMORY_ONLY));
1726 }
1727 
1728 // For a complete 'add' operation, we want the following behaviour:
1729 // 1. If there is an entry in the log, then the physical table exists and
1730 //    has been initialized properly.
1731 // 2. If there is no entry in the log, the physical table may or may not
1732 //    exist.  In this case, we don't really care because we cannot get at
1733 //    it.  The next time we add a table with the same name to the dictionary,
1734 //    it will be initialized properly.
1735 // This mode is used when the table is first created.
1736 //
1737 // For an INMEMORY_ONLY operation, only the internal structure is created and
1738 // updated.  This mode is used when the database gets loaded and the internal
1739 // dictionary gets updated from the log entries.
1740 
1741 db_status
1742 db_dictionary::add_table_aux(char *tab, table_obj* tobj, int mode)
1743 {
1744 	db_status	ret;
1745 
1746 	WRITELOCK(this, DB_LOCK_ERROR, "w db_dictionary::add_table_aux");
1747 	if (!initialized) {
1748 		WRITEUNLOCK(this, DB_LOCK_ERROR,
1749 				"wu db_dictionary::add_table_aux");
1750 		return (DB_BADDICTIONARY);
1751 	}
1752 
1753 	if (find_table_desc(tab) != NULL) {
1754 		WRITEUNLOCK(this, DB_LOCK_ERROR,
1755 				"wu db_dictionary::add_table_aux");
1756 		return (DB_NOTUNIQUE);		// table already exists
1757 	}
1758 
1759 	// create data structures for table
1760 	db_table_desc *new_table = 0;
1761 	db_status status = create_table_desc(tab, tobj, &new_table);
1762 
1763 	if (status != DB_SUCCESS) {
1764 		WRITEUNLOCK(this, DB_LOCK_ERROR,
1765 				"wu db_dictionary::add_table_aux");
1766 		return (status);
1767 	}
1768 
1769 	if (mode != INMEMORY_ONLY) {
1770 		// create physical structures for table
1771 		new_table->database = new db(tab);
1772 		if (new_table->database == NULL) {
1773 			delete_table_desc(new_table);
1774 			WRITEUNLOCK(this, DB_MEMORY_LIMIT,
1775 		"db_dictionary::add_table: could not allocate space for db");
1776 			FATAL3(
1777 		    "db_dictionary::add_table: could not allocate space for db",
1778 			DB_MEMORY_LIMIT, DB_MEMORY_LIMIT);
1779 		}
1780 		if (new_table->database->init(new_table->scheme) == 0) {
1781 			WARNING(
1782 	"db_dictionary::add_table: could not initialize database from scheme");
1783 			new_table->database->remove_files();
1784 			delete_table_desc(new_table);
1785 			WRITEUNLOCK(this, DB_STORAGE_LIMIT,
1786 				"wu db_dictionary::add_table_aux");
1787 			return (DB_STORAGE_LIMIT);
1788 		}
1789 
1790 		// update 'external' copy of dictionary
1791 		status = log_action(DB_ADD_TABLE, tab, tobj);
1792 
1793 		if (status != DB_SUCCESS) {
1794 			new_table->database->remove_files();
1795 			delete_table_desc(new_table);
1796 			WRITEUNLOCK(this, status,
1797 					"wu db_dictionary::add_table_aux");
1798 			return (status);
1799 		}
1800 	}
1801 
1802 	// finally, update in-memory copy of dictionary
1803 	ret = add_to_dictionary(dictionary, new_table);
1804 	WRITEUNLOCK(this, ret, "wu db_dictionary::add_table_aux");
1805 	return (ret);
1806 }
1807 
1808 /*
1809  * Add table with given name 'tab' and description 'zdesc' to dictionary.
1810  * Returns errror code if table already exists, or if no memory can be found
1811  * to store the descriptor, or if dictionary has not been intialized.
1812  * Dictionary is updated to stable store if addition is successful.
1813  * Fatal error occurs if dictionary cannot be saved.
1814  * Returns DB_SUCCESS if dictionary has been updated successfully.
1815 */
1816 db_status
1817 db_dictionary::add_table(char *tab, table_obj* tobj)
1818 {
1819 	return (add_table_aux(tab, tobj, !INMEMORY_ONLY));
1820 }
1821 
1822 /*
1823  * Translate given NIS attribute list to a db_query structure.
1824  * Return FALSE if dictionary has not been initialized, or
1825  * table does not have a scheme (which should be a fatal error?).
1826  */
1827 db_query*
1828 db_dictionary::translate_to_query(db_table_desc* tbl, int numattrs,
1829 				nis_attr* attrlist)
1830 {
1831 	READLOCK(this, NULL, "r db_dictionary::translate_to_query");
1832 	if (!initialized ||
1833 		tbl->scheme == NULL || numattrs == 0 || attrlist == NULL) {
1834 		READUNLOCK(this, NULL, "ru db_dictionary::translate_to_query");
1835 		return (NULL);
1836 	}
1837 
1838 	db_query *q = new db_query(tbl->scheme, numattrs, attrlist);
1839 	if (q == NULL) {
1840 		READUNLOCK(this, NULL,
1841 			"db_dictionary::translate: could not allocate space");
1842 		FATAL3("db_dictionary::translate: could not allocate space",
1843 			DB_MEMORY_LIMIT, NULL);
1844 	}
1845 
1846 	if (q->size() == 0) {
1847 		delete q;
1848 		READUNLOCK(this, NULL, "ru db_dictionary::translate_to_query");
1849 		return (NULL);
1850 	}
1851 	READUNLOCK(this, NULL, "ru db_dictionary::translate_to_query");
1852 	return (q);
1853 }
1854 
1855 static db_table_names gt_answer;
1856 static int gt_posn;
1857 
1858 static db_status
1859 get_table_name(db_table_desc* tbl)
1860 {
1861 	if (tbl)
1862 		return (DB_BADTABLE);
1863 
1864 	if (gt_posn < gt_answer.db_table_names_len)
1865 		gt_answer.db_table_names_val[gt_posn++] =
1866 			strdup(tbl->table_name);
1867 	else
1868 		return (DB_BADTABLE);
1869 
1870 	return (DB_SUCCESS);
1871 }
1872 
1873 
1874 /*
1875  * Return the names of tables in this dictionary.
1876  * XXX This routine is used only for testing only;
1877  *	if to be used for real, need to free memory sensibly, or
1878  *	caller of get_table_names should have freed them.
1879  */
1880 db_table_names*
1881 db_dictionary::get_table_names()
1882 {
1883 	READLOCK(this, NULL, "r db_dictionary::get_table_names");
1884 	gt_answer.db_table_names_len = dictionary->count;
1885 	gt_answer.db_table_names_val = new db_table_namep[dictionary->count];
1886 	gt_posn = 0;
1887 	if ((gt_answer.db_table_names_val) == NULL) {
1888 		READUNLOCK(this, NULL,
1889 	"db_dictionary::get_table_names: could not allocate space for names");
1890 		FATAL3(
1891 	"db_dictionary::get_table_names: could not allocate space for names",
1892 		DB_MEMORY_LIMIT, NULL);
1893 	}
1894 
1895 	enumerate_dictionary(dictionary, &get_table_name);
1896 	READUNLOCK(this, NULL, "ru db_dictionary::get_table_names");
1897 	return (&gt_answer);
1898 }
1899 
1900 static db_status
1901 db_checkpoint_aux(db_table_desc *current)
1902 {
1903 	db *dbase;
1904 	int status;
1905 
1906 	if (current == NULL)
1907 		return (DB_BADTABLE);
1908 
1909 	if (current->database == NULL) {  /* need to load it in */
1910 		dbase = new db(current->table_name);
1911 		if (dbase == NULL) {
1912 			FATAL3(
1913 		    "db_dictionary::db_checkpoint: could not allocate space",
1914 			DB_MEMORY_LIMIT, DB_MEMORY_LIMIT);
1915 		}
1916 		if (dbase->load() == 0) {
1917 			syslog(LOG_ERR,
1918 			"db_dictionary::db_checkpoint: could not load table %s",
1919 							current->table_name);
1920 			delete dbase;
1921 			return (DB_BADTABLE);
1922 		}
1923 		status = dbase->checkpoint();
1924 		delete dbase;  // unload
1925 	} else
1926 	    status = current->database->checkpoint();
1927 
1928 	if (status == 0)
1929 		return (DB_STORAGE_LIMIT);
1930 	return (DB_SUCCESS);
1931 }
1932 
1933 /* Like db_checkpoint_aux except only stops on LIMIT errors */
1934 static db_status
1935 db_checkpoint_aux_cont(db_table_desc *current)
1936 {
1937 	db_status status = db_checkpoint_aux(current);
1938 
1939 	if (status == DB_STORAGE_LIMIT || status == DB_MEMORY_LIMIT)
1940 		return (status);
1941 	else
1942 		return (DB_SUCCESS);
1943 }
1944 
1945 db_status
1946 db_dictionary::db_checkpoint(char *tab)
1947 {
1948 	db_table_desc *tbl;
1949 	db_status	ret;
1950 	bool_t		init;
1951 
1952 	READLOCK(this, DB_LOCK_ERROR, "r db_dictionary::db_checkpoint");
1953 	init = initialized;
1954 	READUNLOCK(this, DB_LOCK_ERROR, "ru db_dictionary::db_checkpoint");
1955 	if (!init)
1956 		return (DB_BADDICTIONARY);
1957 
1958 	checkpoint();	// checkpoint dictionary first
1959 
1960 	READLOCK(this, DB_LOCK_ERROR, "r db_dictionary::db_checkpoint");
1961 
1962 	if (tab == NULL) {
1963 	    ret = enumerate_dictionary(dictionary, &db_checkpoint_aux_cont);
1964 	    READUNLOCK(this, ret, "ru db_dictionary::db_checkpoint");
1965 	    return (ret);
1966 	}
1967 
1968 	if ((tbl = find_table_desc(tab)) == NULL) {
1969 		READUNLOCK(this, DB_LOCK_ERROR,
1970 				"ru db_dictionary::db_checkpoint");
1971 	    return (DB_BADTABLE);
1972 	}
1973 
1974 	ret = db_checkpoint_aux(tbl);
1975 	READUNLOCK(this, ret, "ru db_dictionary::db_checkpoint");
1976 	return (ret);
1977 }
1978 
1979 /* *********************** db_standby **************************** */
1980 /* Deal with list of tables that need to be 'closed' */
1981 
1982 #define	OPENED_DBS_CHUNK	12
1983 static db	**db_standby_list;
1984 static uint_t	db_standby_size = 0;
1985 static uint_t	db_standby_count = 0;
1986 DECLMUTEXLOCK(db_standby_list);
1987 
1988 /*
1989  * Returns 1 if all databases on the list could be closed, 0
1990  * otherwise.
1991  */
1992 static int
1993 close_standby_list()
1994 {
1995 	db	*database;
1996 	int	i, ret;
1997 	char	*myself = "close_standby_list";
1998 
1999 	MUTEXLOCK(db_standby_list, "close_standby_list");
2000 
2001 	if (db_standby_count == 0) {
2002 		MUTEXUNLOCK(db_standby_list, "close_standby_list");
2003 		return (1);
2004 	}
2005 
2006 	for (i = 0, ret = 0; i < db_standby_size; i++) {
2007 		if ((database = db_standby_list[i])) {
2008 			/*
2009 			 * In order to avoid a potential dead-lock, we
2010 			 * check to see if close_log() would be able to
2011 			 * lock the db; if not, just skip the db.
2012 			 */
2013 			int	lockok;
2014 
2015 			TRYWRITELOCK(database, lockok,
2016 				"try w db_dictionary::close_standby_list");
2017 
2018 			if (lockok == 0) {
2019 				database->close_log(1);
2020 				db_standby_list[i] = (db*)NULL;
2021 				--db_standby_count;
2022 				WRITEUNLOCK(database, db_standby_count == 0,
2023 					"db_dictionary::close_standby_list");
2024 				if (db_standby_count == 0) {
2025 					ret = 1;
2026 					break;
2027 				}
2028 			} else if (lockok != EBUSY) {
2029 				logmsg(MSG_NOTIMECHECK, LOG_INFO,
2030 					"%s: try-lock error %d",
2031 					myself, lockok);
2032 			} /* else it's EBUSY; skip to the next one */
2033 		}
2034 	}
2035 
2036 	MUTEXUNLOCK(db_standby_list, "close_standby_list");
2037 
2038 	return (ret);
2039 }
2040 
2041 /*
2042  * Add given database to list of databases that have been opened for updates.
2043  * If size of list exceeds maximum, close opened databases first.
2044  */
2045 
2046 int
2047 add_to_standby_list(db* database)
2048 {
2049 	int	i;
2050 	char	*myself = "add_to_standby_list";
2051 
2052 	MUTEXLOCK(db_standby_list, "add_to_standby_list");
2053 
2054 	if (database == 0) {
2055 		MUTEXUNLOCK(db_standby_list, "add_to_standby_list");
2056 		return (1);
2057 	}
2058 
2059 	/* Try to keep the list below OPENED_DBS_CHUNK */
2060 	if (db_standby_count >= OPENED_DBS_CHUNK) {
2061 		MUTEXUNLOCK(db_standby_list, "add_to_standby_list");
2062 		close_standby_list();
2063 		MUTEXLOCK(db_standby_list, "add_to_standby_list");
2064 	}
2065 
2066 	if (db_standby_count >= db_standby_size) {
2067 		db	**ndsl = (db **)realloc(db_standby_list,
2068 					(db_standby_size+OPENED_DBS_CHUNK) *
2069 						sizeof (ndsl[0]));
2070 
2071 		if (ndsl == 0) {
2072 			MUTEXUNLOCK(db_standby_list, "add_to_standby_list");
2073 			logmsg(MSG_NOMEM, LOG_ERR,
2074 				"%s: realloc(%d) => NULL",
2075 				myself, (db_standby_size+OPENED_DBS_CHUNK) *
2076 				sizeof (ndsl[0]));
2077 			return (0);
2078 		}
2079 
2080 		db_standby_list = ndsl;
2081 
2082 		for (i = db_standby_size; i < db_standby_size+OPENED_DBS_CHUNK;
2083 				i++)
2084 			db_standby_list[i] = 0;
2085 
2086 		db_standby_size += OPENED_DBS_CHUNK;
2087 	}
2088 
2089 	for (i = 0; i < db_standby_size; i++) {
2090 		if (db_standby_list[i] == (db*)NULL) {
2091 			db_standby_list[i] = database;
2092 			++db_standby_count;
2093 			MUTEXUNLOCK(db_standby_list, "add_to_standby_list");
2094 			return (1);
2095 		}
2096 	}
2097 
2098 	MUTEXUNLOCK(db_standby_list, "add_to_standby_list");
2099 
2100 	return (0);
2101 }
2102 
2103 int
2104 remove_from_standby_list(db* database)
2105 {
2106 	int i;
2107 
2108 	MUTEXLOCK(db_standby_list, "remove_from_standby_list");
2109 
2110 	if (database == 0) {
2111 		MUTEXUNLOCK(db_standby_list, "remove_from_standby_list");
2112 		return (1);
2113 	}
2114 
2115 	for (i = 0; i < db_standby_size; i++) {
2116 		if ((database == db_standby_list[i])) {
2117 			db_standby_list[i] = (db*)NULL;
2118 			--db_standby_count;
2119 			MUTEXUNLOCK(db_standby_list,
2120 					"remove_from_standby_list");
2121 			return (1);
2122 		}
2123 	}
2124 
2125 	MUTEXUNLOCK(db_standby_list, "remove_from_standby_list");
2126 
2127 	return (0);
2128 }
2129 
2130 /* Release space for copied dictionary */
2131 static void
2132 db_release_dictionary(db_dict_desc_p d) {
2133 
2134 	int	i;
2135 
2136 	if (d != NULL) {
2137 		for (i = 0; i < d->tables.tables_len; i++) {
2138 			db_table_desc_p	n, t = d->tables.tables_val[i];
2139 			while (t != NULL) {
2140 				n = t->next;
2141 				delete_table_desc(t);
2142 				t = n;
2143 			}
2144 		}
2145 		delete d;
2146 	}
2147 
2148 	return;
2149 }
2150 
2151 /*
2152  * Make a copy of the dictionary
2153  */
2154 db_dict_desc_p
2155 db_dictionary::db_copy_dictionary(void) {
2156 
2157 	db_dict_desc_p	tmp;
2158 	int		i, ok = 1, count = 0;
2159 
2160 	WRITELOCK(this, NULL, "db_dictionary::db_copy_dictionary w");
2161 
2162 	if (dictionary == NULL) {
2163 		WRITEUNLOCK(this, NULL,
2164 			"db_dictionary::db_copy_dictionary wu");
2165 		return (NULL);
2166 	}
2167 
2168 	tmp = new db_dict_desc;
2169 	if (tmp == NULL) {
2170 		WRITEUNLOCK(this, NULL,
2171 			"db_dictionary::db_copy_dictionary wu: no memory");
2172 		return (NULL);
2173 	}
2174 
2175 	tmp->tables.tables_val = (db_table_desc_p *)calloc(
2176 						tmp->tables.tables_len,
2177 					sizeof (tmp->tables.tables_val[0]));
2178 	if (tmp->tables.tables_val == NULL) {
2179 		delete tmp;
2180 		WRITEUNLOCK(this, NULL,
2181 			"db_dictionary::db_copy_dictionary wu: no memory");
2182 		return (NULL);
2183 	}
2184 
2185 	tmp->impl_vers = dictionary->impl_vers;
2186 	tmp->tables.tables_len = 0;
2187 	tmp->count = 0;
2188 
2189 	/* For each table ... */
2190 	for (i = 0; ok && i < dictionary->tables.tables_len; i++) {
2191 		db_table_desc_p	tbl = NULL,
2192 				t = dictionary->tables.tables_val[i];
2193 		/* ... and each bucket in the chain ... */
2194 		while (ok && t != NULL) {
2195 			db_table_desc_p		n, savenext = t->next;
2196 			t->next = NULL;
2197 			if (db_clone_bucket(t, &n)) {
2198 				if (tbl != NULL) {
2199 					tbl->next = n;
2200 				} else {
2201 					tmp->tables.tables_val[i] = n;
2202 				}
2203 				tbl = n;
2204 				tmp->count++;
2205 			} else {
2206 				ok = 0;
2207 			}
2208 			t->next = savenext;
2209 		}
2210 		tmp->tables.tables_len++;
2211 	}
2212 
2213 	if (ok) {
2214 #ifdef	NISDB_LDAP_DEBUG
2215 		if ((tmp->tables.tables_len !=
2216 				dictionary->tables.tables_len) ||
2217 			(tmp->count != dictionary->count))
2218 			abort();
2219 #endif	/* NISDB_LDAP_DEBUG */
2220 	} else {
2221 		db_release_dictionary(tmp);
2222 		tmp = NULL;
2223 	}
2224 
2225 	return (tmp);
2226 }
2227 
2228 /*
2229  * Set deferred commit mode. To do this, we make a copy of the table
2230  * (structures and data), and put that on the deferred dictionary list.
2231  * This list is used for lookups during a resync, so clients continue
2232  * to see the pre-resync data. Meanwhile, any changes (including table
2233  * deletes) are done to the (temporarily hidden to clients) table in
2234  * the normal dictionary.
2235  */
2236 db_status
2237 db_dictionary::defer(char *table) {
2238 	db_status	ret = DB_SUCCESS;
2239 	WRITELOCK(this, DB_LOCK_ERROR, "w db_dictionary::defer");
2240 	db_table_desc	*tbl = find_table_desc(table);
2241 	int		res;
2242 	char		*myself = "db_dictionary::defer";
2243 
2244 	if (tbl != NULL) {
2245 		db_table_desc	*clone, *savenext = tbl->next;
2246 		/*
2247 		 * Only want to clone one db_table_desc, so temporarily
2248 		 * unlink the tail.
2249 		 */
2250 		tbl->next = NULL;
2251 		res = db_clone_bucket(tbl, &clone);
2252 		/* Restore link to tail */
2253 		tbl->next = savenext;
2254 		if (res == 1) {
2255 			db_status	stat;
2256 			if (deferred.dictionary == NULL) {
2257 				deferred.dictionary = new db_dict_desc;
2258 				if (deferred.dictionary == NULL) {
2259 					WRITEUNLOCK(this, DB_MEMORY_LIMIT,
2260 						"wu db_dictionary::defer");
2261 					return (DB_MEMORY_LIMIT);
2262 				}
2263 				deferred.dictionary->tables.tables_len = 0;
2264 				deferred.dictionary->tables.tables_val = NULL;
2265 				deferred.dictionary->count = 0;
2266 				deferred.dictionary->impl_vers =
2267 							DB_CURRENT_VERSION;
2268 			}
2269 			ret = DB_SUCCESS;
2270 			/* Initialize and load the database for the clone */
2271 			if (clone->database == 0) {
2272 				clone->database = new db(table);
2273 				if (clone->database != 0) {
2274 					if (clone->database->load()) {
2275 						logmsg(MSG_NOTIMECHECK,
2276 #ifdef	NISDB_LDAP_DEBUG
2277 							LOG_WARNING,
2278 #else
2279 							LOG_INFO,
2280 #endif	/* NISDB_LDAP_DEBUG */
2281 					"%s: Clone DB for \"%s\" loaded",
2282 							myself, NIL(table));
2283 					} else {
2284 						logmsg(MSG_NOTIMECHECK, LOG_ERR,
2285 					"%s: Error loading clone DB for \"%s\"",
2286 							myself, NIL(table));
2287 						delete clone->database;
2288 						clone->database = 0;
2289 						ret = DB_INTERNAL_ERROR;
2290 					}
2291 				} else {
2292 					logmsg(MSG_NOTIMECHECK, LOG_ERR,
2293 					"%s: Unable to clone DB for \"%s\"",
2294 						myself, NIL(table));
2295 					ret = DB_MEMORY_LIMIT;
2296 				}
2297 			}
2298 			if (clone->database != 0) {
2299 				clone->database->markDeferred();
2300 				stat = add_to_dictionary(deferred.dictionary,
2301 							clone);
2302 				ret = stat;
2303 				if (stat != DB_SUCCESS) {
2304 					delete clone->database;
2305 					clone->database = 0;
2306 					delete clone;
2307 					if (stat == DB_NOTUNIQUE) {
2308 						/* Already deferred */
2309 						ret = DB_SUCCESS;
2310 					}
2311 				}
2312 			} else {
2313 				delete clone;
2314 				/* Return value already set above */
2315 			}
2316 		} else {
2317 			ret = DB_INTERNAL_ERROR;
2318 		}
2319 	} else {
2320 		ret = DB_NOTFOUND;
2321 	}
2322 	WRITEUNLOCK(this, ret, "wu db_dictionary::defer");
2323 	return (ret);
2324 }
2325 
2326 /*
2327  * Unset deferred commit mode and roll back changes; doesn't recover the
2328  * disk data, but that's OK, since we only want to be able to continue
2329  * serving the table until we can try a full dump again.
2330  *
2331  * The rollback is done by removing (and deleting) the updated table from
2332  * the dictionary, and then moving the saved table from the deferred
2333  * dictionary list to the actual one.
2334  */
2335 db_status
2336 db_dictionary::rollback(char *table) {
2337 	db_status	ret = DB_SUCCESS;
2338 	WRITELOCK(this, DB_LOCK_ERROR, "w db_dictionary::rollback");
2339 	db_table_desc	*old = search_dictionary(deferred.dictionary, table);
2340 	db_table_desc	*upd = search_dictionary(dictionary, table);
2341 
2342 	if (old == NULL) {
2343 		WRITEUNLOCK(this, DB_NOTFOUND, "wu db_dictionary::rollback");
2344 		return (DB_NOTFOUND);
2345 	}
2346 
2347 	/*
2348 	 * Remove old incarnation from deferred dictionary. We already hold
2349 	 * a pointer ('old') to it, so don't delete.
2350 	 */
2351 	ret = remove_from_dictionary(deferred.dictionary, table, FALSE);
2352 	if (ret != DB_SUCCESS) {
2353 #ifdef	NISDB_LDAP_DEBUG
2354 		abort();
2355 #endif	/* NISDB_LDAP_DEBUG */
2356 		WRITEUNLOCK(this, ret, "wu db_dictionary::rollback");
2357 		return (ret);
2358 	}
2359 
2360 	if (old->database != 0)
2361 		old->database->unmarkDeferred();
2362 
2363 	/*
2364 	 * Remove updated incarnation from dictionary. If 'upd' is NULL,
2365 	 * the table has been removed while we were in deferred mode, and
2366 	 * that's OK; we just need to retain the old incarnation.
2367 	 */
2368 	if (upd != NULL) {
2369 		ret = remove_from_dictionary(dictionary, table, FALSE);
2370 		if (ret != DB_SUCCESS) {
2371 #ifdef	NISDB_LDAP_DEBUG
2372 			abort();
2373 #endif	/* NISDB_LDAP_DEBUG */
2374 			/*
2375 			 * Cut our losses; delete old incarnation, and leave
2376 			 * updated one in place.
2377 			 */
2378 			delete_table_desc(old);
2379 			WRITEUNLOCK(this, ret, "wu db_dictionary::rollback");
2380 			return (ret);
2381 		}
2382 		/* Throw away updates */
2383 		delete_table_desc(upd);
2384 	}
2385 
2386 	/* (Re-)insert old incarnation in the dictionary */
2387 	ret = add_to_dictionary(dictionary, old);
2388 	if (ret != DB_SUCCESS) {
2389 #ifdef	NISDB_LDAP_DEBUG
2390 		abort();
2391 #endif	/* NISDB_LDAP_DEBUG */
2392 		/* At least avoid memory leak */
2393 		delete_table_desc(old);
2394 		syslog(LOG_ERR,
2395 	"db_dictionary::rollback: rollback error %d for \"%s\"", ret, table);
2396 	}
2397 
2398 	WRITEUNLOCK(this, ret, "wu db_dictionary::rollback");
2399 	return (ret);
2400 }
2401 
2402 /*
2403  * Commit changes. Done by simply removing and deleting the pre-resync
2404  * data from the deferred dictionary.
2405  */
2406 db_status
2407 db_dictionary::commit(char *table) {
2408 	db_status	ret = DB_SUCCESS;
2409 	WRITELOCK(this, DB_LOCK_ERROR, "w db_dictionary::commit");
2410 	db_table_desc	*old = search_dictionary(deferred.dictionary, table);
2411 
2412 	if (old == NULL) {
2413 		/* Fine (we hope); nothing to do */
2414 		WRITEUNLOCK(this, ret, "wu db_dictionary::commit");
2415 		return (DB_SUCCESS);
2416 	}
2417 
2418 	ret = remove_from_dictionary(deferred.dictionary, table, FALSE);
2419 	if (ret == DB_SUCCESS)
2420 		delete_table_desc(old);
2421 #ifdef	NISDB_LDAP_DEBUG
2422 	else
2423 		abort();
2424 #endif	/* NISDB_LDAP_DEBUG */
2425 
2426 	WRITEUNLOCK(this, ret, "wu db_dictionary::commit");
2427 	return (ret);
2428 }
2429 
2430 /*
2431  * The noWriteThrough flag is used to prevent modifies/updates to LDAP
2432  * while we're incorporating log data into the in-memory tables.
2433  */
2434 void
2435 db_dictionary::setNoWriteThrough(void) {
2436 	ASSERTWHELD(this->dict);
2437 	noWriteThrough.flag++;
2438 }
2439 
2440 void
2441 db_dictionary::clearNoWriteThrough(void) {
2442 	ASSERTWHELD(this->dict);
2443 	if (noWriteThrough.flag > 0)
2444 		noWriteThrough.flag--;
2445 #ifdef	NISDB_LDAP_DEBUG
2446 	else
2447 		abort();
2448 #endif	/* NISDB_LDAP_DEBUG */
2449 }
2450