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 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 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 */ 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 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 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 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 * 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 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 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 */ 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 */ 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 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 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 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 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* 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 * 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 * 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 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 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 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 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 * 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 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 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 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 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 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 ** 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 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 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 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 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: 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. */ 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 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 */ 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. */ 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 */ 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