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
db_valid_version(u_int vers)80 db_valid_version(u_int vers)
81 {
82 return ((vers == DB_CURRENT_VERSION) || (vers == DB_ORIG_VERSION));
83 }
84
85 static char *
db_version_str(u_int vers)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
xdr_db_dict_version(XDR * xdrs,db_dict_version * objp)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
make_zero(vers * v)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
delete_table_desc(db_table_desc * current)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
create_table_desc(char * tab,table_obj * zdesc,db_table_desc ** answer)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
delete_bucket(db_table_desc * head)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
delete_dictionary(db_dict_desc * dict)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
relocate_bucket(db_table_desc * bucket,db_table_desc_p * new_tab,unsigned long hashsize)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
enumerate_bucket(db_table_desc * bucket,db_status (* func)(db_table_desc *))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
search_bucket(db_table_desc * bucket,unsigned long hval,char * target)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
remove_from_bucket(db_table_desc_p bucket,db_table_desc_p * head,unsigned long hval,char * target,bool_t free_storage)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
add_to_bucket(db_table_desc_p bucket,db_table_desc ** head,db_table_desc_p td)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
print_bucket(db_table_desc * head)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
print_table(db_table_desc * tbl)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
get_next_hashsize(long unsigned oldsize)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
grow_dictionary(db_dict_desc_p dd)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
get_hashval(char * value)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
enumerate_dictionary(db_dict_desc * dd,db_status (* func)(db_table_desc *))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 *
search_dictionary(db_dict_desc * dd,char * target)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
remove_from_dictionary(db_dict_desc * dd,char * target,bool_t remove_storage)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
add_to_dictionary(db_dict_desc_p dd,db_table_desc * td)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
transfer_aux(XDR * x,pptr tbl)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:
pickle_dict_desc(char * f,pickle_mode m)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. */
transfer(db_dict_desc_p * dp)605 int transfer(db_dict_desc_p * dp)
606 { return (pickle_file::transfer((pptr) dp, &transfer_aux)); }
607 };
608
609 /* ************************ dictionary methods *************************** */
610
db_dictionary()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
db_clone_bucket(db_table_desc * bucket,db_table_desc ** clone)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
change_table_name(db_table_desc * clone,char * tok,char * repl)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
inittemp(char * dictname,db_dictionary & curdict)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
massage_dict(char * newdictname,char * tok,char * repl)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
merge_dict(db_dictionary & tempdict,char * tok,char * repl)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
copyfile(char * infile,char * outfile)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
extract_entries(db_dictionary & tempdict,char ** fs,int fscnt)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
init(char * file)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
apply_log_entry(db_dictlog_entry * j,char * dictchar,int * count)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
incorporate_log(char * file_name)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
db_shutdown()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
dump()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
checkpoint()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
reset_log()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
close_log()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
open_log()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()) == NULL) {
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
db_standby(char * tab)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*
find_table_desc(char * tab)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 *
find_table_desc(char * tab,bool_t searchDeferred)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 *
find_table(char * tab,db_table_desc ** where)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 *
find_table(char * tab,db_table_desc ** where,bool_t searchDeferred)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 *
find_table(char * tab,db_table_desc ** where,bool_t searchDeferred,bool_t doLDAP,bool_t doLoad)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 *
find_table_noLDAP(char * tab,db_table_desc ** where,bool_t searchDeferred,bool_t doLoad)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
log_action(int action,char * tab,table_obj * tobj)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
delete_table_aux(char * tab,int mode)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
delete_table(char * tab)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
add_table_aux(char * tab,table_obj * tobj,int mode)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 errror 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
add_table(char * tab,table_obj * tobj)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*
translate_to_query(db_table_desc * tbl,int numattrs,nis_attr * attrlist)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
get_table_name(db_table_desc * tbl)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*
get_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 (>_answer);
1896 }
1897
1898 static db_status
db_checkpoint_aux(db_table_desc * current)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
db_checkpoint_aux_cont(db_table_desc * current)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
db_checkpoint(char * tab)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
close_standby_list()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
add_to_standby_list(db * database)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
remove_from_standby_list(db * database)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
db_release_dictionary(db_dict_desc_p d)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
db_copy_dictionary(void)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
defer(char * table)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
rollback(char * table)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
commit(char * table)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
setNoWriteThrough(void)2433 db_dictionary::setNoWriteThrough(void) {
2434 ASSERTWHELD(this->dict);
2435 noWriteThrough.flag++;
2436 }
2437
2438 void
clearNoWriteThrough(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