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 (>_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