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 }