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_table.cc
24 *
25 * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
26 * Use is subject to license terms.
27 */
28
29 #include <stdio.h>
30 #include <malloc.h>
31 #include <string.h>
32 #include <stdlib.h> /* srand48() */
33 #include <lber.h>
34 #include <ldap.h>
35 #include "db_headers.h"
36 #include "db_table.h"
37 #include "db_pickle.h" /* for dump and load */
38 #include "db_entry.h"
39 #include "nisdb_mt.h"
40
41 #include "ldap_parse.h"
42 #include "ldap_util.h"
43 #include "ldap_map.h"
44 #include "ldap_xdr.h"
45 #include "nis_hashitem.h"
46 #include "nisdb_ldap.h"
47 #include "nis_parse_ldap_conf.h"
48
49 static time_t maxTimeT;
50
51 /*
52 * Find the largest (positive) value of time_t.
53 *
54 * If time_t is unsigned, the largest possible value is just ~0.
55 * However, if it's signed, then ~0 is negative. Since lint (for
56 * sure), and perhaps the compiler too, dislike comparing an
57 * unsigned quantity to see if it's less than zero, we compare
58 * to one instead. If negative, the largest possible value is
59 * th inverse of 2**(N-1), where N is the number of bits in a
60 * time_t.
61 */
62 extern "C" {
63 static void
__setMaxTimeT(void)64 __setMaxTimeT(void)
65 {
66 unsigned char b[sizeof (time_t)];
67 int i;
68
69 /* Compute ~0 for an unknown length integer */
70 for (i = 0; i < sizeof (time_t); i++) {
71 b[i] = 0xff;
72 }
73 /* Set maxTimeT to ~0 of appropriate length */
74 (void) memcpy(&maxTimeT, b, sizeof (time_t));
75
76 if (maxTimeT < 1)
77 maxTimeT = ~(1<<((8*sizeof (maxTimeT))-1));
78 }
79 #pragma init(__setMaxTimeT)
80 }
81
82 /* How much to grow table by */
83 #define DB_TABLE_GROWTH_INCREMENT 1024
84
85 /* 0'th not used; might be confusing. */
86 #define DB_TABLE_START 1
87
88 /* prevents wrap around numbers from being passed */
89 #define CALLOC_LIMIT 536870911
90
91 /* Initial table sizes to use before using 1K increments. */
92 /* This helps conserve memory usage when there are lots of small tables. */
93 static int tabsizes[] = {
94 16,
95 128,
96 512,
97 DB_TABLE_GROWTH_INCREMENT,
98 0
99 };
100
101 /* Returns the next size to use for table */
102 static long unsigned
get_new_table_size(long unsigned oldsize)103 get_new_table_size(long unsigned oldsize)
104 {
105 long unsigned newsize = 0, n;
106 if (oldsize == 0)
107 newsize = tabsizes[0];
108 else {
109 for (n = 0; newsize = tabsizes[n++]; )
110 if (oldsize == newsize) {
111 newsize = tabsizes[n]; /* get next size */
112 break;
113 }
114 if (newsize == 0)
115 newsize = oldsize + DB_TABLE_GROWTH_INCREMENT;
116 }
117 return (newsize);
118 }
119
120
121 /* destructor */
~db_free_list()122 db_free_list::~db_free_list()
123 {
124 WRITELOCKV(this, "w db_free_list::~db_free_list");
125 reset(); /* free list entries */
126 DESTROYRW(free_list);
127 }
128
129 void
reset()130 db_free_list::reset()
131 {
132 db_free_entry *current, *nextentry;
133
134 WRITELOCKV(this, "w db_free_list::reset");
135 for (current = head; current != NULL; ) {
136 nextentry = current->next;
137 delete current;
138 current = nextentry;
139 }
140 head = NULL;
141 count = 0;
142 WRITEUNLOCKV(this, "wu db_free_list::reset");
143 }
144
145 /* Returns the location of a free entry, or NULL, if there aren't any. */
146 entryp
pop()147 db_free_list::pop()
148 {
149 WRITELOCK(this, NULL, "w db_free_list::pop");
150 if (head == NULL) {
151 WRITEUNLOCK(this, NULL, "wu db_free_list::pop");
152 return (NULL);
153 }
154 db_free_entry* old_head = head;
155 entryp found = head->where;
156 head = head->next;
157 delete old_head;
158 --count;
159 WRITEUNLOCK(this, found, "wu db_free_list::pop");
160 return (found);
161 }
162
163 /*
164 * Adds given location to the free list.
165 * Returns TRUE if successful, FALSE otherwise (when out of memory).
166 */
167 bool_t
push(entryp tabloc)168 db_free_list::push(entryp tabloc)
169 {
170 db_free_entry * newentry = new db_free_entry;
171
172 WRITELOCK(this, FALSE, "w db_free_list::push");
173 if (newentry == NULL) {
174 WRITEUNLOCK(this, FALSE, "wu db_free_list::push");
175 FATAL3("db_free_list::push: cannot allocation space",
176 DB_MEMORY_LIMIT, FALSE);
177 }
178 newentry->where = tabloc;
179 newentry->next = head;
180 head = newentry;
181 ++count;
182 WRITEUNLOCK(this, TRUE, "wu db_free_list::push");
183 return (TRUE);
184 }
185
186 /*
187 * Returns in a vector the information in the free list.
188 * Vector returned is of form: [n free cells][n1][n2][loc1], ..[locn].
189 * Leave the first 'n' cells free.
190 * n1 is the number of entries that should be in the freelist.
191 * n2 is the number of entries actually found in the freelist.
192 * [loc1...locn] are the entries. n2 <= n1 because we never count beyond n1.
193 * It is up to the caller to free the returned vector when he is through.
194 */
195 long *
stats(int nslots)196 db_free_list::stats(int nslots)
197 {
198 long realcount = 0,
199 i,
200 liststart = nslots, // start of freelist
201 listend = nslots+count+2; // end of freelist
202 db_free_entry_p current = head;
203
204 READLOCK(this, NULL, "r db_free_list::stats");
205
206 long *answer = (long *)malloc((int)(listend)*sizeof (long));
207 if (answer == 0) {
208 READUNLOCK(this, NULL, "ru db_free_list::stats");
209 FATAL3("db_free_list::stats: cannot allocation space",
210 DB_MEMORY_LIMIT, NULL);
211 }
212
213 answer[liststart] = count; /* size of freelist */
214
215 for (i = liststart+2; i < listend && current != NULL; i++) {
216 answer[i] = current->where;
217 current = current->next;
218 ++realcount;
219 }
220
221 answer[liststart+1] = realcount;
222 READUNLOCK(this, answer, "ru db_free_list::stats");
223 return (answer);
224 }
225
226
227 /* Set default values for the mapping structure */
228 void
initMappingStruct(__nisdb_table_mapping_t * m)229 db_table::initMappingStruct(__nisdb_table_mapping_t *m) {
230 if (m == 0)
231 return;
232
233 m->initTtlLo = (ldapDBTableMapping.initTtlLo > 0) ?
234 ldapDBTableMapping.initTtlLo : (3600-1800);
235 m->initTtlHi = (ldapDBTableMapping.initTtlHi > 0) ?
236 ldapDBTableMapping.initTtlHi : (3600+1800);
237 m->ttl = (ldapDBTableMapping.ttl > 0) ?
238 ldapDBTableMapping.ttl : 3600;
239 m->enumExpire = 0;
240 m->fromLDAP = FALSE;
241 m->toLDAP = FALSE;
242 m->isMaster = FALSE;
243 m->retrieveError = ldapDBTableMapping.retrieveError;
244 m->retrieveErrorRetry.attempts =
245 ldapDBTableMapping.retrieveErrorRetry.attempts;
246 m->retrieveErrorRetry.timeout =
247 ldapDBTableMapping.retrieveErrorRetry.timeout;
248 m->storeError = ldapDBTableMapping.storeError;
249 m->storeErrorRetry.attempts =
250 ldapDBTableMapping.storeErrorRetry.attempts;
251 m->storeErrorRetry.timeout =
252 ldapDBTableMapping.storeErrorRetry.timeout;
253 m->storeErrorDisp = ldapDBTableMapping.storeErrorDisp;
254 m->refreshError = ldapDBTableMapping.refreshError;
255 m->refreshErrorRetry.attempts =
256 ldapDBTableMapping.refreshErrorRetry.attempts;
257 m->refreshErrorRetry.timeout =
258 ldapDBTableMapping.refreshErrorRetry.timeout;
259 m->matchFetch = ldapDBTableMapping.matchFetch;
260
261 if (mapping.expire != 0)
262 free(mapping.expire);
263 m->expire = 0;
264
265 if (m->tm != 0)
266 free(m->tm);
267 m->tm = 0;
268
269 /*
270 * The 'objType' field obviously indicates the type of object.
271 * However, we also use it to tell us if we've retrieved mapping
272 * data from LDAP or not; in the latter case, 'objType' is
273 * NIS_BOGUS_OBJ. For purposes of maintaining expiration times,
274 * we may need to know if the object is a table or a directory
275 * _before_ we've retrieved any mapping data. Hence the 'expireType'
276 * field, which starts as NIS_BOGUS_OBJ (meaning, don't know, assume
277 * directory for now), and later is set to NIS_DIRECTORY_OBJ
278 * (always keep expiration data, in case one of the dir entries
279 * is mapped) or NIS_TABLE_OBJ (only need expiration data if
280 * tha table is mapped).
281 */
282 m->objType = NIS_BOGUS_OBJ;
283 m->expireType = NIS_BOGUS_OBJ;
284 if (m->objName != 0)
285 free(m->objName);
286 m->objName = 0;
287 }
288
289 void
db_table_ldap_init(void)290 db_table::db_table_ldap_init(void) {
291
292 INITRW(table);
293
294 enumMode.flag = 0;
295 enumCount.flag = 0;
296 enumIndex.ptr = 0;
297 enumArray.ptr = 0;
298
299 mapping.expire = 0;
300 mapping.tm = 0;
301 mapping.objName = 0;
302 mapping.isDeferredTable = FALSE;
303 (void) mutex_init(&mapping.enumLock, 0, 0);
304 mapping.enumTid = 0;
305 mapping.enumStat = -1;
306 mapping.enumDeferred = 0;
307 mapping.enumEntries = 0;
308 mapping.enumTime = 0;
309 }
310
311 /* db_table constructor */
db_table()312 db_table::db_table() : freelist()
313 {
314 tab = NULL;
315 table_size = 0;
316 last_used = 0;
317 count = 0;
318
319 db_table_ldap_init();
320 initMappingStruct(&mapping);
321
322 /* grow(); */
323 }
324
325 /*
326 * db_table destructor:
327 * 1. Get rid of contents of freelist
328 * 2. delete all entries hanging off table
329 * 3. get rid of table itself
330 */
~db_table()331 db_table::~db_table()
332 {
333 WRITELOCKV(this, "w db_table::~db_table");
334 reset();
335 DESTROYRW(table);
336 }
337
338 /* reset size and pointers */
339 void
reset()340 db_table::reset()
341 {
342 int i, done = 0;
343
344 WRITELOCKV(this, "w db_table::reset");
345 freelist.reset();
346
347 /* Add sanity check in case of table corruption */
348 if (tab != NULL) {
349 for (i = 0;
350 i <= last_used && i < table_size && done < count;
351 i++) {
352 if (tab[i]) {
353 free_entry(tab[i]);
354 ++done;
355 }
356 }
357 }
358
359 delete tab;
360 table_size = last_used = count = 0;
361 tab = NULL;
362 sfree(mapping.expire);
363 mapping.expire = NULL;
364 mapping.objType = NIS_BOGUS_OBJ;
365 mapping.expireType = NIS_BOGUS_OBJ;
366 sfree(mapping.objName);
367 mapping.objName = 0;
368 /* Leave other values of the mapping structure unchanged */
369 enumMode.flag = 0;
370 enumCount.flag = 0;
371 sfree(enumIndex.ptr);
372 enumIndex.ptr = 0;
373 sfree(enumArray.ptr);
374 enumArray.ptr = 0;
375 WRITEUNLOCKV(this, "wu db_table::reset");
376 }
377
378 db_status
allocateExpire(long oldSize,long newSize)379 db_table::allocateExpire(long oldSize, long newSize) {
380 time_t *newExpire;
381
382 newExpire = (time_t *)realloc(mapping.expire,
383 newSize * sizeof (mapping.expire[0]));
384 if (newExpire != NULL) {
385 /* Initialize new portion */
386 (void) memset(&newExpire[oldSize], 0,
387 (newSize-oldSize) * sizeof (newExpire[0]));
388 mapping.expire = newExpire;
389 } else {
390 return (DB_MEMORY_LIMIT);
391 }
392
393 return (DB_SUCCESS);
394 }
395
396 db_status
allocateEnumArray(long oldSize,long newSize)397 db_table::allocateEnumArray(long oldSize, long newSize) {
398 entry_object **newEnumArray;
399 const char *myself = "db_table::allocateEnumArray";
400
401 if (enumCount.flag > 0) {
402 if (enumIndex.ptr == 0) {
403 enumIndex.ptr = (entryp *)am(myself, enumCount.flag *
404 sizeof (entryp));
405 if (enumIndex.ptr == 0)
406 return (DB_MEMORY_LIMIT);
407 }
408 oldSize = 0;
409 newSize = enumCount.flag;
410 }
411 newEnumArray = (entry_object **)realloc(enumArray.ptr,
412 newSize * sizeof (entry_object *));
413 if (newEnumArray != 0 && newSize > oldSize) {
414 (void) memcpy(&newEnumArray[oldSize], &tab[oldSize],
415 (newSize-oldSize) * sizeof (entry_object *));
416 enumArray.ptr = newEnumArray;
417 } else if (newEnumArray == 0) {
418 return (DB_MEMORY_LIMIT);
419 }
420
421 return (DB_SUCCESS);
422 }
423
424 /* Expand the table. Fatal error if insufficient memory. */
425 void
grow()426 db_table::grow()
427 {
428 WRITELOCKV(this, "w db_table::grow");
429 long oldsize = table_size;
430 entry_object_p *oldtab = tab;
431 long i;
432
433 table_size = get_new_table_size(oldsize);
434
435 #ifdef DEBUG
436 fprintf(stderr, "db_table GROWING to %d\n", table_size);
437 #endif
438
439 if (table_size > CALLOC_LIMIT) {
440 table_size = oldsize;
441 WRITEUNLOCKV(this, "wu db_table::grow");
442 FATAL("db_table::grow: table size exceeds calloc limit",
443 DB_MEMORY_LIMIT);
444 }
445
446 // if ((tab = new entry_object_p[table_size]) == NULL)
447 if ((tab = (entry_object_p*)
448 calloc((unsigned int) table_size,
449 sizeof (entry_object_p))) == NULL) {
450 tab = oldtab; // restore previous table info
451 table_size = oldsize;
452 WRITEUNLOCKV(this, "wu db_table::grow");
453 FATAL("db_table::grow: cannot allocate space", DB_MEMORY_LIMIT);
454 }
455
456 /*
457 * For directories, we may need the expire time array even if the
458 * directory itself isn't mapped. If the objType and expireType both
459 * are bogus, we don't know yet if this is a table or a directory,
460 * and must proceed accordingly.
461 */
462 if (mapping.objType == NIS_DIRECTORY_OBJ ||
463 mapping.expireType != NIS_TABLE_OBJ ||
464 mapping.fromLDAP) {
465 db_status stat = allocateExpire(oldsize, table_size);
466 if (stat != DB_SUCCESS) {
467 free(tab);
468 tab = oldtab;
469 table_size = oldsize;
470 WRITEUNLOCKV(this, "wu db_table::grow expire");
471 FATAL(
472 "db_table::grow: cannot allocate space for expire", stat);
473 }
474 }
475
476 if (oldtab != NULL) {
477 for (i = 0; i < oldsize; i++) { // transfer old to new
478 tab[i] = oldtab[i];
479 }
480 delete oldtab;
481 }
482
483 if (enumMode.flag) {
484 db_status stat = allocateEnumArray(oldsize, table_size);
485 if (stat != DB_SUCCESS) {
486 free(tab);
487 tab = oldtab;
488 table_size = oldsize;
489 WRITEUNLOCKV(this, "wu db_table::grow enumArray");
490 FATAL(
491 "db_table::grow: cannot allocate space for enumArray", stat);
492 }
493 }
494
495 WRITEUNLOCKV(this, "wu db_table::grow");
496 }
497
498 /*
499 * Return the first entry in table, also return its position in
500 * 'where'. Return NULL in both if no next entry is found.
501 */
502 entry_object*
first_entry(entryp * where)503 db_table::first_entry(entryp * where)
504 {
505 ASSERTRHELD(table);
506 if (count == 0 || tab == NULL) { /* empty table */
507 *where = NULL;
508 return (NULL);
509 } else {
510 entryp i;
511 for (i = DB_TABLE_START;
512 i < table_size && i <= last_used; i++) {
513 if (tab[i] != NULL) {
514 *where = i;
515 return (tab[i]);
516 }
517 }
518 }
519 *where = NULL;
520 return (NULL);
521 }
522
523 /*
524 * Return the next entry in table from 'prev', also return its position in
525 * 'newentry'. Return NULL in both if no next entry is found.
526 */
527 entry_object *
next_entry(entryp prev,entryp * newentry)528 db_table::next_entry(entryp prev, entryp* newentry)
529 {
530 long i;
531
532 ASSERTRHELD(table);
533 if (prev >= table_size || tab == NULL || tab[prev] == NULL)
534 return (NULL);
535 for (i = prev+1; i < table_size && i <= last_used; i++) {
536 if (tab[i] != NULL) {
537 *newentry = i;
538 return (tab[i]);
539 }
540 }
541 *newentry = NULL;
542 return (NULL);
543 }
544
545 /* Return entry at location 'where', NULL if location is invalid. */
546 entry_object *
get_entry(entryp where)547 db_table::get_entry(entryp where)
548 {
549 ASSERTRHELD(table);
550 if (where < table_size && tab != NULL && tab[where] != NULL)
551 return (tab[where]);
552 else
553 return (NULL);
554 }
555
556 void
setEntryExp(entryp where,entry_obj * obj,int initialLoad)557 db_table::setEntryExp(entryp where, entry_obj *obj, int initialLoad) {
558 nis_object *o;
559 const char *myself = "db_table::setEntryExp";
560
561 /*
562 * If we don't know what type of object this is yet, we
563 * can find out now. If it's a directory, the pseudo-object
564 * in column zero will have the type "IN_DIRECTORY";
565 * otherwise, it's a table object.
566 */
567 if (mapping.expireType == NIS_BOGUS_OBJ) {
568 if (obj != 0) {
569 if (obj->en_type != 0 &&
570 strcmp(obj->en_type, "IN_DIRECTORY") == 0) {
571 mapping.expireType = NIS_DIRECTORY_OBJ;
572 } else {
573 mapping.expireType = NIS_TABLE_OBJ;
574 if (!mapping.fromLDAP) {
575 free(mapping.expire);
576 mapping.expire = 0;
577 }
578 }
579 }
580 }
581
582 /* Set the entry TTL */
583 if (mapping.expire != NULL) {
584 struct timeval now;
585 time_t lo, hi, ttl;
586
587 (void) gettimeofday(&now, NULL);
588 if (mapping.expireType == NIS_TABLE_OBJ) {
589 lo = mapping.initTtlLo;
590 hi = mapping.initTtlHi;
591 ttl = mapping.ttl;
592 /* TTL == 0 means always expired */
593 if (ttl == 0)
594 ttl = -1;
595 } else {
596 __nis_table_mapping_t *t = 0;
597
598 o = unmakePseudoEntryObj(obj, 0);
599 if (o != 0) {
600 __nis_buffer_t b = {0, 0};
601
602 bp2buf(myself, &b, "%s.%s",
603 o->zo_name, o->zo_domain);
604 t = getObjMapping(b.buf, 0, 1, 0, 0);
605 sfree(b.buf);
606 nis_destroy_object(o);
607 }
608
609 if (t != 0) {
610 lo = t->initTtlLo;
611 hi = t->initTtlHi;
612 ttl = t->ttl;
613 /* TTL == 0 means always expired */
614 if (ttl == 0)
615 ttl = -1;
616 } else {
617 /*
618 * No expiration time initialization
619 * data. Cook up values that will
620 * result in mapping.expire[where]
621 * set to maxTimeT.
622 */
623 hi = lo = ttl = maxTimeT - now.tv_sec;
624 }
625 }
626
627 if (initialLoad) {
628 int interval = hi - lo + 1;
629 if (interval <= 1) {
630 mapping.expire[where] = now.tv_sec + lo;
631 } else {
632 srand48(now.tv_sec);
633 mapping.expire[where] = now.tv_sec +
634 (lrand48() % interval);
635 }
636 if (mapping.enumExpire == 0 ||
637 mapping.expire[where] <
638 mapping.enumExpire)
639 mapping.enumExpire = mapping.expire[where];
640 } else {
641 mapping.expire[where] = now.tv_sec + ttl;
642 }
643 }
644 }
645
646 /*
647 * Add given entry to table in first available slot (either look in freelist
648 * or add to end of table) and return the the position of where the record
649 * is placed. 'count' is incremented if entry is added. Table may grow
650 * as a side-effect of the addition. Copy is made of input.
651 */
652 entryp
add_entry(entry_object * obj,int initialLoad)653 db_table::add_entry(entry_object *obj, int initialLoad) {
654 /*
655 * We're returning an index of the table array, so the caller
656 * should hold a lock until done with the index. To save us
657 * the bother of upgrading to a write lock, it might as well
658 * be a write lock to begin with.
659 */
660 ASSERTWHELD(table);
661 entryp where = freelist.pop();
662 if (where == NULL) { /* empty freelist */
663 if (last_used >= (table_size-1)) /* full (> is for 0) */
664 grow();
665 where = ++last_used;
666 }
667 if (tab != NULL) {
668 ++count;
669 setEntryExp(where, obj, initialLoad);
670
671 if (enumMode.flag)
672 enumTouch(where);
673 tab[where] = new_entry(obj);
674 return (where);
675 } else {
676 return (NULL);
677 }
678 }
679
680 /*
681 * Replaces object at specified location by given entry.
682 * Returns TRUE if replacement successful; FALSE otherwise.
683 * There must something already at the specified location, otherwise,
684 * replacement fails. Copy is not made of the input.
685 * The pre-existing entry is freed.
686 */
687 bool_t
replace_entry(entryp where,entry_object * obj)688 db_table::replace_entry(entryp where, entry_object * obj)
689 {
690 ASSERTWHELD(table);
691 if (where < DB_TABLE_START || where >= table_size ||
692 tab == NULL || tab[where] == NULL)
693 return (FALSE);
694 /* (Re-)set the entry TTL */
695 setEntryExp(where, obj, 0);
696
697 if (enumMode.flag)
698 enumTouch(where);
699 free_entry(tab[where]);
700 tab[where] = obj;
701 return (TRUE);
702 }
703
704 /*
705 * Deletes entry at specified location. Returns TRUE if location is valid;
706 * FALSE if location is invalid, or the freed location cannot be added to
707 * the freelist. 'count' is decremented if the deletion occurs. The object
708 * at that location is freed.
709 */
710 bool_t
delete_entry(entryp where)711 db_table::delete_entry(entryp where)
712 {
713 bool_t ret = TRUE;
714
715 ASSERTWHELD(table);
716 if (where < DB_TABLE_START || where >= table_size ||
717 tab == NULL || tab[where] == NULL)
718 return (FALSE);
719 if (mapping.expire != NULL) {
720 mapping.expire[where] = 0;
721 }
722 if (enumMode.flag)
723 enumTouch(where);
724 free_entry(tab[where]);
725 tab[where] = NULL; /* very important to set it to null */
726 --count;
727 if (where == last_used) { /* simple case, deleting from end */
728 --last_used;
729 return (TRUE);
730 } else {
731 return (freelist.push(where));
732 }
733 return (ret);
734 }
735
736 /*
737 * Returns statistics of table.
738 * [vector_size][table_size][last_used][count][freelist].
739 * It is up to the caller to free the returned vector when his is through.
740 * The free list is included if 'fl' is TRUE.
741 */
742 long *
stats(bool_t include_freelist)743 db_table::stats(bool_t include_freelist)
744 {
745 long *answer;
746
747 READLOCK(this, NULL, "r db_table::stats");
748 if (include_freelist)
749 answer = freelist.stats(3);
750 else {
751 answer = (long *)malloc(3*sizeof (long));
752 }
753
754 if (answer) {
755 answer[0] = table_size;
756 answer[1] = last_used;
757 answer[2] = count;
758 }
759 READUNLOCK(this, answer, "ru db_table::stats");
760 return (answer);
761 }
762
763 bool_t
configure(char * tablePath)764 db_table::configure(char *tablePath) {
765 long i;
766 struct timeval now;
767 const char *myself = "db_table::configure";
768
769 (void) gettimeofday(&now, NULL);
770
771 WRITELOCK(this, FALSE, "db_table::configure w");
772
773 /* (Re-)initialize from global info */
774 initMappingStruct(&mapping);
775
776 /* Retrieve table mapping for this table */
777 mapping.tm = (__nis_table_mapping_t *)__nis_find_item_mt(
778 tablePath, &ldapMappingList, 0, 0);
779 if (mapping.tm != 0) {
780 __nis_object_dn_t *odn = mapping.tm->objectDN;
781
782 /*
783 * The mapping.fromLDAP and mapping.toLDAP fields serve as
784 * quick-references that tell us if mapping is enabled.
785 * Hence, initialize them appropriately from the table
786 * mapping objectDN.
787 */
788 while (odn != 0 && (!mapping.fromLDAP || !mapping.toLDAP)) {
789 if (odn->read.scope != LDAP_SCOPE_UNKNOWN)
790 mapping.fromLDAP = TRUE;
791 if (odn->write.scope != LDAP_SCOPE_UNKNOWN)
792 mapping.toLDAP = TRUE;
793 odn = (__nis_object_dn_t *)odn->next;
794 }
795
796 /* Set the timeout values */
797 mapping.initTtlLo = mapping.tm->initTtlLo;
798 mapping.initTtlHi = mapping.tm->initTtlHi;
799 mapping.ttl = mapping.tm->ttl;
800
801 mapping.objName = sdup(myself, T, mapping.tm->objName);
802 if (mapping.objName == 0 && mapping.tm->objName != 0) {
803 WRITEUNLOCK(this, FALSE,
804 "db_table::configure wu objName");
805 FATAL3("db_table::configure objName",
806 DB_MEMORY_LIMIT, FALSE);
807 }
808 }
809
810 /*
811 * In order to initialize the expiration times, we need to know
812 * if 'this' represents a table or a directory. To that end, we
813 * find an entry in the table, and invoke setEntryExp() on it.
814 * As a side effect, setEntryExp() will examine the pseudo-object
815 * in the entry, and set the expireType accordingly.
816 */
817 if (tab != 0) {
818 for (i = 0; i <= last_used; i++) {
819 if (tab[i] != NULL) {
820 setEntryExp(i, tab[i], 1);
821 break;
822 }
823 }
824 }
825
826 /*
827 * If mapping from an LDAP repository, make sure we have the
828 * expiration time array.
829 */
830 if ((mapping.expireType != NIS_TABLE_OBJ || mapping.fromLDAP) &&
831 mapping.expire == NULL && table_size > 0 && tab != 0) {
832 db_status stat = allocateExpire(0, table_size);
833 if (stat != DB_SUCCESS) {
834 WRITEUNLOCK(this, FALSE,
835 "db_table::configure wu expire");
836 FATAL3("db_table::configure expire",
837 stat, FALSE);
838 }
839 } else if (mapping.expireType == NIS_TABLE_OBJ && !mapping.fromLDAP &&
840 mapping.expire != NULL) {
841 /* Not using expiration times */
842 free(mapping.expire);
843 mapping.expire = NULL;
844 }
845
846 /*
847 * Set initial expire times for entries that don't already have one.
848 * Establish the enumeration expiration time to be the minimum of
849 * all expiration times in the table, though no larger than current
850 * time plus initTtlHi.
851 */
852 if (mapping.expire != NULL) {
853 int interval = mapping.initTtlHi - mapping.initTtlLo + 1;
854 time_t enumXp = now.tv_sec + mapping.initTtlHi;
855
856 if (interval > 1)
857 srand48(now.tv_sec);
858 for (i = 0; i <= last_used; i++) {
859 if (tab[i] != NULL && mapping.expire[i] == 0) {
860 if (mapping.expireType == NIS_TABLE_OBJ) {
861 if (interval > 1)
862 mapping.expire[i] =
863 now.tv_sec +
864 (lrand48() % interval);
865 else
866 mapping.expire[i] =
867 now.tv_sec +
868 mapping.initTtlLo;
869 } else {
870 setEntryExp(i, tab[i], 1);
871 }
872 }
873 if (enumXp > mapping.expire[i])
874 enumXp = mapping.expire[i];
875 }
876 mapping.enumExpire = enumXp;
877 }
878
879 WRITEUNLOCK(this, FALSE, "db_table::configure wu");
880
881 return (TRUE);
882 }
883
884 /* Return TRUE if the entry at 'loc' hasn't expired */
885 bool_t
cacheValid(entryp loc)886 db_table::cacheValid(entryp loc) {
887 bool_t ret;
888 struct timeval now;
889
890 (void) gettimeofday(&now, 0);
891
892 READLOCK(this, FALSE, "db_table::cacheValid r");
893
894 if (loc < 0 || loc >= table_size || tab == 0 || tab[loc] == 0)
895 ret = FALSE;
896 else if (mapping.expire == 0 || mapping.expire[loc] >= now.tv_sec)
897 ret = TRUE;
898 else
899 ret = FALSE;
900
901 READUNLOCK(this, ret, "db_table::cacheValid ru");
902
903 return (ret);
904 }
905
906 /*
907 * If the supplied object has the same content as the one at 'loc',
908 * update the expiration time for the latter, and return TRUE.
909 */
910 bool_t
dupEntry(entry_object * obj,entryp loc)911 db_table::dupEntry(entry_object *obj, entryp loc) {
912 if (obj == 0 || loc < 0 || loc >= table_size || tab == 0 ||
913 tab[loc] == 0)
914 return (FALSE);
915
916 if (sameEntry(obj, tab[loc])) {
917 setEntryExp(loc, tab[loc], 0);
918
919 if (enumMode.flag > 0)
920 enumTouch(loc);
921 return (TRUE);
922 }
923
924 return (FALSE);
925 }
926
927 /*
928 * If enumeration mode is enabled, we keep a shadow array that initially
929 * starts out the same as 'tab'. Any update activity (add, remove, replace,
930 * or update timestamp) for an entry in the table means we delete the shadow
931 * array pointer. When ending enumeration mode, we return the shadow array.
932 * Any non-NULL entries in the array have not been updated since the start
933 * of the enum mode.
934 *
935 * The indended use is for enumeration of an LDAP container, where we
936 * will update all entries that currently exist in LDAP. The entries we
937 * don't update are those that don't exist in LDAP, and thus should be
938 * removed.
939 *
940 * Note that any LDAP query strictly speaking can be a partial enumeration
941 * (i.e., return more than one match). Since the query might also have
942 * matched multiple local DB entries, we need to do the same work as for
943 * enumeration for any query. In order to avoid having to work on the
944 * whole 'tab' array for simple queries (which we expect usually will
945 * match just one or at most a few entries), we have a "reduced" enum mode,
946 * where the caller supplies a count of the number of DB entries (derived
947 * from db_mindex::satisfy_query() or similar), and then uses enumSetup()
948 * to specify which 'tab' entries we're interested in.
949 */
950 void
setEnumMode(long enumNum)951 db_table::setEnumMode(long enumNum) {
952 const char *myself = "setEnumMode";
953
954 enumMode.flag++;
955 if (enumMode.flag == 1) {
956 db_status stat;
957
958 if (enumNum < 0)
959 enumNum = 0;
960 else if (enumNum >= table_size)
961 enumNum = table_size;
962
963 enumCount.flag = enumNum;
964
965 stat = allocateEnumArray(0, table_size);
966
967 if (stat != DB_SUCCESS) {
968 enumMode.flag = 0;
969 enumCount.flag = 0;
970 logmsg(MSG_NOTIMECHECK, LOG_ERR,
971 "%s: No memory for enum check array; entry removal disabled",
972 myself);
973 }
974 }
975 }
976
977 void
clearEnumMode(void)978 db_table::clearEnumMode(void) {
979 if (enumMode.flag > 0) {
980 enumMode.flag--;
981 if (enumMode.flag == 0) {
982 sfree(enumArray.ptr);
983 enumArray.ptr = 0;
984 if (enumCount.flag > 0) {
985 sfree(enumIndex.ptr);
986 enumIndex.ptr = 0;
987 enumCount.flag = 0;
988 }
989 }
990 }
991 }
992
993 entry_object **
endEnumMode(long * numEa)994 db_table::endEnumMode(long *numEa) {
995 if (enumMode.flag > 0) {
996 enumMode.flag--;
997 if (enumMode.flag == 0) {
998 entry_obj **ea = (entry_object **)enumArray.ptr;
999 long nea;
1000
1001 enumArray.ptr = 0;
1002
1003 if (enumCount.flag > 0) {
1004 nea = enumCount.flag;
1005 enumCount.flag = 0;
1006 sfree(enumIndex.ptr);
1007 enumIndex.ptr = 0;
1008 } else {
1009 nea = table_size;
1010 }
1011
1012 if (numEa != 0)
1013 *numEa = nea;
1014
1015 return (ea);
1016 }
1017 }
1018
1019 if (numEa != 0)
1020 *numEa = 0;
1021
1022 return (0);
1023 }
1024
1025 /*
1026 * Set the appropriate entry in the enum array to NULL.
1027 */
1028 void
enumTouch(entryp loc)1029 db_table::enumTouch(entryp loc) {
1030 if (loc < 0 || loc >= table_size)
1031 return;
1032
1033 if (enumMode.flag > 0) {
1034 if (enumCount.flag < 1) {
1035 ((entry_object **)enumArray.ptr)[loc] = 0;
1036 } else {
1037 int i;
1038
1039 for (i = 0; i < enumCount.flag; i++) {
1040 if (loc == ((entryp *)enumIndex.ptr)[i]) {
1041 ((entry_object **)enumArray.ptr)[i] = 0;
1042 break;
1043 }
1044 }
1045 }
1046 }
1047 }
1048
1049 /*
1050 * Add the entry indicated by 'loc' to the enumIndex array, at 'index'.
1051 */
1052 void
enumSetup(entryp loc,long index)1053 db_table::enumSetup(entryp loc, long index) {
1054 if (enumMode.flag == 0 || loc < 0 || loc >= table_size ||
1055 index < 0 || index >= enumCount.flag)
1056 return;
1057
1058 ((entryp *)enumIndex.ptr)[index] = loc;
1059 ((entry_object **)enumArray.ptr)[index] = tab[loc];
1060 }
1061
1062 /*
1063 * Touch, i.e., update the expiration time for the entry. Also, if enum
1064 * mode is in effect, mark the entry used for enum purposes.
1065 */
1066 void
touchEntry(entryp loc)1067 db_table::touchEntry(entryp loc) {
1068 if (loc < 0 || loc >= table_size || tab == 0 || tab[loc] == 0)
1069 return;
1070
1071 setEntryExp(loc, tab[loc], 0);
1072
1073 enumTouch(loc);
1074 }
1075
1076 /* ************************* pickle_table ********************* */
1077 /* Does the actual writing to/from file specific for db_table structure. */
1078 /*
1079 * This was a static earlier with the func name being transfer_aux. The
1080 * backup and restore project needed this to copy files over.
1081 */
1082 bool_t
transfer_aux_table(XDR * x,pptr dp)1083 transfer_aux_table(XDR* x, pptr dp)
1084 {
1085 return (xdr_db_table(x, (db_table*) dp));
1086 }
1087
1088 class pickle_table: public pickle_file {
1089 public:
pickle_table(char * f,pickle_mode m)1090 pickle_table(char *f, pickle_mode m) : pickle_file(f, m) {}
1091
1092 /* Transfers db_table structure pointed to by dp to/from file. */
transfer(db_table * dp)1093 int transfer(db_table* dp)
1094 { return (pickle_file::transfer((pptr) dp, &transfer_aux_table)); }
1095 };
1096
1097 /*
1098 * Writes the contents of table, including the all the entries, into the
1099 * specified file in XDR format. May need to change this to use APPEND
1100 * mode instead.
1101 */
1102 int
dump(char * file)1103 db_table::dump(char *file)
1104 {
1105 int ret;
1106 READLOCK(this, -1, "r db_table::dump");
1107 pickle_table f(file, PICKLE_WRITE); /* may need to use APPEND mode */
1108 int status = f.transfer(this);
1109
1110 if (status == 1)
1111 ret = -1;
1112 else
1113 ret = status;
1114 READUNLOCK(this, ret, "ru db_table::dump");
1115 }
1116
1117 /* Constructor that loads in the table from the given file */
db_table(char * file)1118 db_table::db_table(char *file) : freelist()
1119 {
1120 pickle_table f(file, PICKLE_READ);
1121 tab = NULL;
1122 table_size = last_used = count = 0;
1123
1124 /* load table */
1125 if (f.transfer(this) < 0) {
1126 /* fell through, something went wrong, initialize to null */
1127 tab = NULL;
1128 table_size = last_used = count = 0;
1129 freelist.init();
1130 }
1131
1132 db_table_ldap_init();
1133 initMappingStruct(&mapping);
1134 }
1135
1136 /* Returns whether location is valid. */
entry_exists_p(entryp i)1137 bool_t db_table::entry_exists_p(entryp i) {
1138 bool_t ret = FALSE;
1139 READLOCK(this, FALSE, "r db_table::entry_exists_p");
1140 if (tab != NULL && i < table_size)
1141 ret = tab[i] != NULL;
1142 READUNLOCK(this, ret, "ru db_table::entry_exists_p");
1143 return (ret);
1144 }
1145
1146 /* Empty free list */
init()1147 void db_free_list::init() {
1148 WRITELOCKV(this, "w db_free_list::init");
1149 head = NULL;
1150 count = 0;
1151 WRITEUNLOCKV(this, "wu db_free_list::init");
1152 }
1153