1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License, Version 1.0 only
   6  * (the "License").  You may not use this file except in compliance
   7  * with the License.
   8  *
   9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
  10  * or http://www.opensolaris.org/os/licensing.
  11  * See the License for the specific language governing permissions
  12  * and limitations under the License.
  13  *
  14  * When distributing Covered Code, include this CDDL HEADER in each
  15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  16  * If applicable, add the following below this CDDL HEADER, with the
  17  * fields enclosed by brackets "[]" replaced with your own identifying
  18  * information: Portions Copyright [yyyy] [name of copyright owner]
  19  *
  20  * CDDL HEADER END
  21  */
  22 /*
  23  *      db_table.cc
  24  *
  25  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
  26  * Use is subject to license terms.
  27  *
  28  * Copyright 2015 RackTop Systems.
  29  */
  30 
  31 #include <stdio.h>
  32 #include <malloc.h>
  33 #include <string.h>
  34 #include <stdlib.h>               /* srand48() */
  35 #include <lber.h>
  36 #include <ldap.h>
  37 #include "db_headers.h"
  38 #include "db_table.h"
  39 #include "db_pickle.h"    /* for dump and load */
  40 #include "db_entry.h"
  41 #include "nisdb_mt.h"
  42 
  43 #include "ldap_parse.h"
  44 #include "ldap_util.h"
  45 #include "ldap_map.h"
  46 #include "ldap_xdr.h"
  47 #include "nis_hashitem.h"
  48 #include "nisdb_ldap.h"
  49 #include "nis_parse_ldap_conf.h"
  50 
  51 static time_t   maxTimeT;
  52 
  53 /*
  54  * Find the largest (positive) value of time_t.
  55  *
  56  * If time_t is unsigned, the largest possible value is just ~0.
  57  * However, if it's signed, then ~0 is negative. Since lint (for
  58  * sure), and perhaps the compiler too, dislike comparing an
  59  * unsigned quantity to see if it's less than zero, we compare
  60  * to one instead. If negative, the largest possible value is
  61  * th inverse of 2**(N-1), where N is the number of bits in a
  62  * time_t.
  63  */
  64 extern "C" {
  65 static void
  66 __setMaxTimeT(void)
  67 {
  68         unsigned char   b[sizeof (time_t)];
  69         int             i;
  70 
  71         /* Compute ~0 for an unknown length integer */
  72         for (i = 0; i < sizeof (time_t); i++) {
  73                 b[i] = 0xff;
  74         }
  75         /* Set maxTimeT to ~0 of appropriate length */
  76         (void) memcpy(&maxTimeT, b, sizeof (time_t));
  77 
  78         if (maxTimeT < 1)
  79                 maxTimeT = ~(1L<<((8*sizeof (maxTimeT))-1));
  80 }
  81 #pragma init(__setMaxTimeT)
  82 }
  83 
  84 /* How much to grow table by */
  85 #define DB_TABLE_GROWTH_INCREMENT 1024
  86 
  87 /* 0'th not used; might be confusing. */
  88 #define DB_TABLE_START 1
  89 
  90 /* prevents wrap around numbers from being passed */
  91 #define CALLOC_LIMIT 536870911
  92 
  93 /* Initial table sizes to use before using 1K increments. */
  94 /* This helps conserve memory usage when there are lots of small tables. */
  95 static int tabsizes[] = {
  96         16,
  97         128,
  98         512,
  99         DB_TABLE_GROWTH_INCREMENT,
 100         0
 101         };
 102 
 103 /* Returns the next size to use for table */
 104 static long unsigned
 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         const char      *myself = "db_table::allocateEnumArray";
 402 
 403         if (enumCount.flag > 0) {
 404                 if (enumIndex.ptr == 0) {
 405                         enumIndex.ptr = (entryp *)am(myself, enumCount.flag *
 406                                                 sizeof (entryp));
 407                         if (enumIndex.ptr == 0)
 408                                 return (DB_MEMORY_LIMIT);
 409                 }
 410                 oldSize = 0;
 411                 newSize = enumCount.flag;
 412         }
 413         newEnumArray = (entry_object **)realloc(enumArray.ptr,
 414                         newSize * sizeof (entry_object *));
 415         if (newEnumArray != 0 && newSize > oldSize) {
 416                 (void) memcpy(&newEnumArray[oldSize], &tab[oldSize],
 417                         (newSize-oldSize) * sizeof (entry_object *));
 418                 enumArray.ptr = newEnumArray;
 419         } else if (newEnumArray == 0) {
 420                 return (DB_MEMORY_LIMIT);
 421         }
 422 
 423         return (DB_SUCCESS);
 424 }
 425 
 426 /* Expand the table.  Fatal error if insufficient memory. */
 427 void
 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         const char              *myself = "db_table::setEntryExp";
 562 
 563         /*
 564          * If we don't know what type of object this is yet, we
 565          * can find out now. If it's a directory, the pseudo-object
 566          * in column zero will have the type "IN_DIRECTORY";
 567          * otherwise, it's a table object.
 568          */
 569         if (mapping.expireType == NIS_BOGUS_OBJ) {
 570                 if (obj != 0) {
 571                         if (obj->en_type != 0 &&
 572                                 strcmp(obj->en_type, "IN_DIRECTORY") == 0) {
 573                                 mapping.expireType = NIS_DIRECTORY_OBJ;
 574                         } else {
 575                                 mapping.expireType = NIS_TABLE_OBJ;
 576                                 if (!mapping.fromLDAP) {
 577                                         free(mapping.expire);
 578                                         mapping.expire = 0;
 579                                 }
 580                         }
 581                 }
 582         }
 583 
 584         /* Set the entry TTL */
 585         if (mapping.expire != NULL) {
 586                 struct timeval  now;
 587                 time_t          lo, hi, ttl;
 588 
 589                 (void) gettimeofday(&now, NULL);
 590                 if (mapping.expireType == NIS_TABLE_OBJ) {
 591                         lo = mapping.initTtlLo;
 592                         hi = mapping.initTtlHi;
 593                         ttl = mapping.ttl;
 594                         /* TTL == 0 means always expired */
 595                         if (ttl == 0)
 596                                 ttl = -1;
 597                 } else {
 598                         __nis_table_mapping_t   *t = 0;
 599 
 600                         o = unmakePseudoEntryObj(obj, 0);
 601                         if (o != 0) {
 602                                 __nis_buffer_t  b = {0, 0};
 603 
 604                                 bp2buf(myself, &b, "%s.%s",
 605                                         o->zo_name, o->zo_domain);
 606                                 t = getObjMapping(b.buf, 0, 1, 0, 0);
 607                                 sfree(b.buf);
 608                                 nis_destroy_object(o);
 609                         }
 610 
 611                         if (t != 0) {
 612                                 lo = t->initTtlLo;
 613                                 hi = t->initTtlHi;
 614                                 ttl = t->ttl;
 615                                 /* TTL == 0 means always expired */
 616                                 if (ttl == 0)
 617                                         ttl = -1;
 618                         } else {
 619                                 /*
 620                                  * No expiration time initialization
 621                                  * data. Cook up values that will
 622                                  * result in mapping.expire[where]
 623                                  * set to maxTimeT.
 624                                  */
 625                                 hi = lo = ttl = maxTimeT - now.tv_sec;
 626                         }
 627                 }
 628 
 629                 if (initialLoad) {
 630                         int     interval = hi - lo + 1;
 631                         if (interval <= 1) {
 632                                 mapping.expire[where] = now.tv_sec + lo;
 633                         } else {
 634                                 srand48(now.tv_sec);
 635                                 mapping.expire[where] = now.tv_sec +
 636                                                         (lrand48() % interval);
 637                         }
 638                         if (mapping.enumExpire == 0 ||
 639                                         mapping.expire[where] <
 640                                                         mapping.enumExpire)
 641                                 mapping.enumExpire = mapping.expire[where];
 642                 } else {
 643                         mapping.expire[where] = now.tv_sec + ttl;
 644                 }
 645         }
 646 }
 647 
 648 /*
 649  * Add given entry to table in first available slot (either look in freelist
 650  * or add to end of table) and return the the position of where the record
 651  * is placed. 'count' is incremented if entry is added. Table may grow
 652  * as a side-effect of the addition. Copy is made of input.
 653 */
 654 entryp
 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         const char      *myself = "db_table::configure";
 770 
 771         (void) gettimeofday(&now, NULL);
 772 
 773         WRITELOCK(this, FALSE, "db_table::configure w");
 774 
 775         /* (Re-)initialize from global info */
 776         initMappingStruct(&mapping);
 777 
 778         /* Retrieve table mapping for this table */
 779         mapping.tm = (__nis_table_mapping_t *)__nis_find_item_mt(
 780                                         tablePath, &ldapMappingList, 0, 0);
 781         if (mapping.tm != 0) {
 782                 __nis_object_dn_t       *odn = mapping.tm->objectDN;
 783 
 784                 /*
 785                  * The mapping.fromLDAP and mapping.toLDAP fields serve as
 786                  * quick-references that tell us if mapping is enabled.
 787                  * Hence, initialize them appropriately from the table
 788                  * mapping objectDN.
 789                  */
 790                 while (odn != 0 && (!mapping.fromLDAP || !mapping.toLDAP)) {
 791                         if (odn->read.scope != LDAP_SCOPE_UNKNOWN)
 792                                 mapping.fromLDAP = TRUE;
 793                         if (odn->write.scope != LDAP_SCOPE_UNKNOWN)
 794                                 mapping.toLDAP = TRUE;
 795                         odn = (__nis_object_dn_t *)odn->next;
 796                 }
 797 
 798                 /* Set the timeout values */
 799                 mapping.initTtlLo = mapping.tm->initTtlLo;
 800                 mapping.initTtlHi = mapping.tm->initTtlHi;
 801                 mapping.ttl = mapping.tm->ttl;
 802 
 803                 mapping.objName = sdup(myself, T, mapping.tm->objName);
 804                 if (mapping.objName == 0 && mapping.tm->objName != 0) {
 805                         WRITEUNLOCK(this, FALSE,
 806                                 "db_table::configure wu objName");
 807                         FATAL3("db_table::configure objName",
 808                                 DB_MEMORY_LIMIT, FALSE);
 809                 }
 810         }
 811 
 812         /*
 813          * In order to initialize the expiration times, we need to know
 814          * if 'this' represents a table or a directory. To that end, we
 815          * find an entry in the table, and invoke setEntryExp() on it.
 816          * As a side effect, setEntryExp() will examine the pseudo-object
 817          * in the entry, and set the expireType accordingly.
 818          */
 819         if (tab != 0) {
 820                 for (i = 0; i <= last_used; i++) {
 821                         if (tab[i] != NULL) {
 822                                 setEntryExp(i, tab[i], 1);
 823                                 break;
 824                         }
 825                 }
 826         }
 827 
 828         /*
 829          * If mapping from an LDAP repository, make sure we have the
 830          * expiration time array.
 831          */
 832         if ((mapping.expireType != NIS_TABLE_OBJ || mapping.fromLDAP) &&
 833                         mapping.expire == NULL && table_size > 0 && tab != 0) {
 834                 db_status stat = allocateExpire(0, table_size);
 835                 if (stat != DB_SUCCESS) {
 836                         WRITEUNLOCK(this, FALSE,
 837                                 "db_table::configure wu expire");
 838                         FATAL3("db_table::configure expire",
 839                                 stat, FALSE);
 840                 }
 841         } else if (mapping.expireType == NIS_TABLE_OBJ && !mapping.fromLDAP &&
 842                         mapping.expire != NULL) {
 843                 /* Not using expiration times */
 844                 free(mapping.expire);
 845                 mapping.expire = NULL;
 846         }
 847 
 848         /*
 849          * Set initial expire times for entries that don't already have one.
 850          * Establish the enumeration expiration time to be the minimum of
 851          * all expiration times in the table, though no larger than current
 852          * time plus initTtlHi.
 853          */
 854         if (mapping.expire != NULL) {
 855                 int     interval = mapping.initTtlHi - mapping.initTtlLo + 1;
 856                 time_t  enumXp = now.tv_sec + mapping.initTtlHi;
 857 
 858                 if (interval > 1)
 859                         srand48(now.tv_sec);
 860                 for (i = 0; i <= last_used; i++) {
 861                         if (tab[i] != NULL && mapping.expire[i] == 0) {
 862                                 if (mapping.expireType == NIS_TABLE_OBJ) {
 863                                         if (interval > 1)
 864                                                 mapping.expire[i] =
 865                                                         now.tv_sec +
 866                                                         (lrand48() % interval);
 867                                         else
 868                                                 mapping.expire[i] =
 869                                                         now.tv_sec +
 870                                                         mapping.initTtlLo;
 871                                 } else {
 872                                         setEntryExp(i, tab[i], 1);
 873                                 }
 874                         }
 875                         if (enumXp > mapping.expire[i])
 876                                 enumXp = mapping.expire[i];
 877                 }
 878                 mapping.enumExpire = enumXp;
 879         }
 880 
 881         WRITEUNLOCK(this, FALSE, "db_table::configure wu");
 882 
 883         return (TRUE);
 884 }
 885 
 886 /* Return TRUE if the entry at 'loc' hasn't expired */
 887 bool_t
 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         const char      *myself = "setEnumMode";
 955 
 956         enumMode.flag++;
 957         if (enumMode.flag == 1) {
 958                 db_status       stat;
 959 
 960                 if (enumNum < 0)
 961                         enumNum = 0;
 962                 else if (enumNum >= table_size)
 963                         enumNum = table_size;
 964 
 965                 enumCount.flag = enumNum;
 966 
 967                 stat = allocateEnumArray(0, table_size);
 968 
 969                 if (stat != DB_SUCCESS) {
 970                         enumMode.flag = 0;
 971                         enumCount.flag = 0;
 972                         logmsg(MSG_NOTIMECHECK, LOG_ERR,
 973                 "%s: No memory for enum check array; entry removal disabled",
 974                                 myself);
 975                 }
 976         }
 977 }
 978 
 979 void
 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 }