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 #pragma ident "%Z%%M% %I% %E% SMI" 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 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 = ~(1<<((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 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 */ 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 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 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 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 * 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 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 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 */ 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 */ 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 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 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 399 db_table::allocateEnumArray(long oldSize, long newSize) { 400 entry_object **newEnumArray; 401 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 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* 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 * 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 * 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 559 db_table::setEntryExp(entryp where, entry_obj *obj, int initialLoad) { 560 nis_object *o; 561 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 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 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 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 * 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 766 db_table::configure(char *tablePath) { 767 long i; 768 struct timeval now; 769 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 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 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 953 db_table::setEnumMode(long enumNum) { 954 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 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 ** 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 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 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 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 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: 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. */ 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 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 */ 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. */ 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 */ 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