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