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