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