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