root/usr/src/lib/libnisdb/db_table.cc
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 *      db_table.cc
 *
 * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 *
 * Copyright 2015 RackTop Systems.
 * Copyright (c) 2016 by Delphix. All rights reserved.
 */

#include <stdio.h>
#include <malloc.h>
#include <string.h>
#include <stdlib.h>             /* srand48() */
#include <lber.h>
#include <ldap.h>
#include "db_headers.h"
#include "db_table.h"
#include "db_pickle.h"    /* for dump and load */
#include "db_entry.h"
#include "nisdb_mt.h"

#include "ldap_parse.h"
#include "ldap_util.h"
#include "ldap_map.h"
#include "ldap_xdr.h"
#include "nis_hashitem.h"
#include "nisdb_ldap.h"
#include "nis_parse_ldap_conf.h"

static time_t   maxTimeT;

/*
 * Find the largest (positive) value of time_t.
 *
 * If time_t is unsigned, the largest possible value is just ~0.
 * However, if it's signed, then ~0 is negative. Since lint (for
 * sure), and perhaps the compiler too, dislike comparing an
 * unsigned quantity to see if it's less than zero, we compare
 * to one instead. If negative, the largest possible value is
 * th inverse of 2**(N-1), where N is the number of bits in a
 * time_t.
 */
extern "C" {
static void
__setMaxTimeT(void)
{
        unsigned char   b[sizeof (time_t)];
        int             i;

        /* Compute ~0 for an unknown length integer */
        for (i = 0; i < sizeof (time_t); i++) {
                b[i] = 0xff;
        }
        /* Set maxTimeT to ~0 of appropriate length */
        (void) memcpy(&maxTimeT, b, sizeof (time_t));

        if (maxTimeT < 1)
                maxTimeT = ~(1L<<((8*sizeof (maxTimeT))-1));
}
#pragma init(__setMaxTimeT)
}

/* How much to grow table by */
#define DB_TABLE_GROWTH_INCREMENT 1024

/* 0'th not used; might be confusing. */
#define DB_TABLE_START 1

/* prevents wrap around numbers from being passed */
#define CALLOC_LIMIT 536870911

/* Initial table sizes to use before using 1K increments. */
/* This helps conserve memory usage when there are lots of small tables. */
static int tabsizes[] = {
        16,
        128,
        512,
        DB_TABLE_GROWTH_INCREMENT,
        0
        };

/* Returns the next size to use for table */
static long unsigned
get_new_table_size(long unsigned oldsize)
{
        long unsigned newsize = 0, n;
        if (oldsize == 0)
                newsize = tabsizes[0];
        else {
                for (n = 0; newsize = tabsizes[n++]; )
                        if (oldsize == newsize) {
                                newsize = tabsizes[n];  /* get next size */
                                break;
                        }
                if (newsize == 0)
                        newsize = oldsize + DB_TABLE_GROWTH_INCREMENT;
        }
        return (newsize);
}


/* destructor */
db_free_list::~db_free_list()
{
        WRITELOCKV(this, "w db_free_list::~db_free_list");
        reset();   /* free list entries */
        DESTROYRW(free_list);
}

void
db_free_list::reset()
{
        db_free_entry *current, *nextentry;

        WRITELOCKV(this, "w db_free_list::reset");
        for (current = head; current != NULL; ) {
                nextentry = current->next;
                delete current;
                current = nextentry;
        }
        head = NULL;
        count = 0;
        WRITEUNLOCKV(this, "wu db_free_list::reset");
}

/* Returns the location of a free entry, or 0, if there aren't any. */
entryp
db_free_list::pop()
{
        WRITELOCK(this, 0, "w db_free_list::pop");
        if (head == NULL) {
                WRITEUNLOCK(this, 0, "wu db_free_list::pop");
                return (0);
        }
        db_free_entry* old_head = head;
        entryp found = head->where;
        head = head->next;
        delete old_head;
        --count;
        WRITEUNLOCK(this, found, "wu db_free_list::pop");
        return (found);
}

/*
 * Adds given location to the free list.
 * Returns TRUE if successful, FALSE otherwise (when out of memory).
*/
bool_t
db_free_list::push(entryp tabloc)
{
        db_free_entry * newentry = new db_free_entry;

        WRITELOCK(this, FALSE, "w db_free_list::push");
        if (newentry == NULL) {
                WRITEUNLOCK(this, FALSE, "wu db_free_list::push");
            FATAL3("db_free_list::push: cannot allocation space",
                    DB_MEMORY_LIMIT, FALSE);
        }
        newentry->where = tabloc;
        newentry->next = head;
        head = newentry;
        ++count;
        WRITEUNLOCK(this, TRUE, "wu db_free_list::push");
        return (TRUE);
}

/*
 * Returns in a vector the information in the free list.
 * Vector returned is of form: [n free cells][n1][n2][loc1], ..[locn].
 * Leave the first 'n' cells free.
 * n1 is the number of entries that should be in the freelist.
 * n2 is the number of entries actually found in the freelist.
 * [loc1...locn] are the entries.   n2 <= n1 because we never count beyond n1.
 * It is up to the caller to free the returned vector when it is through.
*/
long *
db_free_list::stats(int nslots)
{
        long    realcount = 0,
                i,
                liststart = nslots,             // start of freelist
                listend = nslots+count+2;       // end of freelist
        db_free_entry_p current = head;

        READLOCK(this, NULL, "r db_free_list::stats");

        long *answer = (long *)malloc((int)(listend)*sizeof (long));
        if (answer == 0) {
                READUNLOCK(this, NULL, "ru db_free_list::stats");
                FATAL3("db_free_list::stats:  cannot allocation space",
                    DB_MEMORY_LIMIT, NULL);
        }

        answer[liststart] = count;  /* size of freelist */

        for (i = liststart+2; i < listend && current != NULL; i++) {
                answer[i] = current->where;
                current = current->next;
                ++realcount;
        }

        answer[liststart+1] = realcount;
        READUNLOCK(this, answer, "ru db_free_list::stats");
        return (answer);
}


/* Set default values for the mapping structure */
void
db_table::initMappingStruct(__nisdb_table_mapping_t *m) {
        if (m == 0)
                return;

        m->initTtlLo = (ldapDBTableMapping.initTtlLo > 0) ?
                        ldapDBTableMapping.initTtlLo : (3600-1800);
        m->initTtlHi = (ldapDBTableMapping.initTtlHi > 0) ?
                        ldapDBTableMapping.initTtlHi : (3600+1800);
        m->ttl = (ldapDBTableMapping.ttl > 0) ?
                        ldapDBTableMapping.ttl : 3600;
        m->enumExpire = 0;
        m->fromLDAP = FALSE;
        m->toLDAP = FALSE;
        m->isMaster = FALSE;
        m->retrieveError = ldapDBTableMapping.retrieveError;
        m->retrieveErrorRetry.attempts =
                ldapDBTableMapping.retrieveErrorRetry.attempts;
        m->retrieveErrorRetry.timeout =
                ldapDBTableMapping.retrieveErrorRetry.timeout;
        m->storeError = ldapDBTableMapping.storeError;
        m->storeErrorRetry.attempts =
                ldapDBTableMapping.storeErrorRetry.attempts;
        m->storeErrorRetry.timeout =
                ldapDBTableMapping.storeErrorRetry.timeout;
        m->storeErrorDisp = ldapDBTableMapping.storeErrorDisp;
        m->refreshError = ldapDBTableMapping.refreshError;
        m->refreshErrorRetry.attempts =
                ldapDBTableMapping.refreshErrorRetry.attempts;
        m->refreshErrorRetry.timeout =
                ldapDBTableMapping.refreshErrorRetry.timeout;
        m->matchFetch = ldapDBTableMapping.matchFetch;

        if (mapping.expire != 0)
                free(mapping.expire);
        m->expire = 0;

        if (m->tm != 0)
                free(m->tm);
        m->tm = 0;

        /*
         * The 'objType' field obviously indicates the type of object.
         * However, we also use it to tell us if we've retrieved mapping
         * data from LDAP or not; in the latter case, 'objType' is
         * NIS_BOGUS_OBJ. For purposes of maintaining expiration times,
         * we may need to know if the object is a table or a directory
         * _before_ we've retrieved any mapping data. Hence the 'expireType'
         * field, which starts as NIS_BOGUS_OBJ (meaning, don't know, assume
         * directory for now), and later is set to NIS_DIRECTORY_OBJ
         * (always keep expiration data, in case one of the dir entries
         * is mapped) or NIS_TABLE_OBJ (only need expiration data if
         * tha table is mapped).
         */
        m->objType = NIS_BOGUS_OBJ;
        m->expireType = NIS_BOGUS_OBJ;
        if (m->objName != 0)
                free(m->objName);
        m->objName = 0;
}

void
db_table::db_table_ldap_init(void) {

        INITRW(table);

        enumMode.flag = 0;
        enumCount.flag = 0;
        enumIndex.ptr = 0;
        enumArray.ptr = 0;

        mapping.expire = 0;
        mapping.tm = 0;
        mapping.objName = 0;
        mapping.isDeferredTable = FALSE;
        (void) mutex_init(&mapping.enumLock, 0, 0);
        mapping.enumTid = 0;
        mapping.enumStat = -1;
        mapping.enumDeferred = 0;
        mapping.enumEntries = 0;
        mapping.enumTime = 0;
}

/* db_table constructor */
db_table::db_table() : freelist()
{
        tab = NULL;
        table_size = 0;
        last_used = 0;
        count = 0;

        db_table_ldap_init();
        initMappingStruct(&mapping);

/*  grow(); */
}

/*
 * db_table destructor:
 * 1.  Get rid of contents of freelist
 * 2.  delete all entries hanging off table
 * 3.  get rid of table itself
*/
db_table::~db_table()
{
        WRITELOCKV(this, "w db_table::~db_table");
        reset();
        DESTROYRW(table);
}

/* reset size and pointers */
void
db_table::reset()
{
        int i, done = 0;

        WRITELOCKV(this, "w db_table::reset");
        freelist.reset();

        /* Add sanity check in case of table corruption */
        if (tab != NULL) {
                for (i = 0;
                        i <= last_used && i < table_size && done < count;
                        i++) {
                        if (tab[i]) {
                                free_entry(tab[i]);
                                ++done;
                        }
                }
        }

        delete tab;
        table_size = last_used = count = 0;
        tab = NULL;
        sfree(mapping.expire);
        mapping.expire = NULL;
        mapping.objType = NIS_BOGUS_OBJ;
        mapping.expireType = NIS_BOGUS_OBJ;
        sfree(mapping.objName);
        mapping.objName = 0;
        /* Leave other values of the mapping structure unchanged */
        enumMode.flag = 0;
        enumCount.flag = 0;
        sfree(enumIndex.ptr);
        enumIndex.ptr = 0;
        sfree(enumArray.ptr);
        enumArray.ptr = 0;
        WRITEUNLOCKV(this, "wu db_table::reset");
}

db_status
db_table::allocateExpire(long oldSize, long newSize) {
        time_t                  *newExpire;

        newExpire = (time_t *)realloc(mapping.expire,
                                newSize * sizeof (mapping.expire[0]));
        if (newExpire != NULL) {
                /* Initialize new portion */
                (void) memset(&newExpire[oldSize], 0,
                                (newSize-oldSize) * sizeof (newExpire[0]));
                mapping.expire = newExpire;
        } else {
                return (DB_MEMORY_LIMIT);
        }

        return (DB_SUCCESS);
}

db_status
db_table::allocateEnumArray(long oldSize, long newSize) {
        entry_object    **newEnumArray;
        const char      *myself = "db_table::allocateEnumArray";

        if (enumCount.flag > 0) {
                if (enumIndex.ptr == 0) {
                        enumIndex.ptr = (entryp *)am(myself, enumCount.flag *
                                                sizeof (entryp));
                        if (enumIndex.ptr == 0)
                                return (DB_MEMORY_LIMIT);
                }
                oldSize = 0;
                newSize = enumCount.flag;
        }
        newEnumArray = (entry_object **)realloc(enumArray.ptr,
                        newSize * sizeof (entry_object *));
        if (newEnumArray != 0 && newSize > oldSize) {
                (void) memcpy(&newEnumArray[oldSize], &tab[oldSize],
                        (newSize-oldSize) * sizeof (entry_object *));
                enumArray.ptr = newEnumArray;
        } else if (newEnumArray == 0) {
                return (DB_MEMORY_LIMIT);
        }

        return (DB_SUCCESS);
}

/* Expand the table.  Fatal error if insufficient memory. */
void
db_table::grow()
{
        WRITELOCKV(this, "w db_table::grow");
        long oldsize = table_size;
        entry_object_p *oldtab = tab;
        long i;

        table_size = get_new_table_size(oldsize);

#ifdef DEBUG
        fprintf(stderr, "db_table GROWING to %d\n", table_size);
#endif

        if (table_size > CALLOC_LIMIT) {
                table_size = oldsize;
                WRITEUNLOCKV(this, "wu db_table::grow");
                FATAL("db_table::grow: table size exceeds calloc limit",
                        DB_MEMORY_LIMIT);
        }

//  if ((tab = new entry_object_p[table_size]) == NULL)
        if ((tab = (entry_object_p*)
                calloc((unsigned int) table_size,
                        sizeof (entry_object_p))) == NULL) {
                tab = oldtab;           // restore previous table info
                table_size = oldsize;
                WRITEUNLOCKV(this, "wu db_table::grow");
                FATAL("db_table::grow: cannot allocate space", DB_MEMORY_LIMIT);
        }

        /*
         * For directories, we may need the expire time array even if the
         * directory itself isn't mapped. If the objType and expireType both
         * are bogus, we don't  know yet if this is a table or a directory,
         * and must proceed accordingly.
         */
        if (mapping.objType == NIS_DIRECTORY_OBJ ||
                        mapping.expireType != NIS_TABLE_OBJ ||
                        mapping.fromLDAP) {
                db_status stat = allocateExpire(oldsize, table_size);
                if (stat != DB_SUCCESS) {
                        free(tab);
                        tab = oldtab;
                        table_size = oldsize;
                        WRITEUNLOCKV(this, "wu db_table::grow expire");
                        FATAL(
                "db_table::grow: cannot allocate space for expire", stat);
                }
        }

        if (oldtab != NULL) {
                for (i = 0; i < oldsize; i++) { // transfer old to new
                        tab[i] = oldtab[i];
                }
                delete oldtab;
        }

        if (enumMode.flag) {
                db_status stat = allocateEnumArray(oldsize, table_size);
                if (stat != DB_SUCCESS) {
                        free(tab);
                        tab = oldtab;
                        table_size = oldsize;
                        WRITEUNLOCKV(this, "wu db_table::grow enumArray");
                        FATAL(
                "db_table::grow: cannot allocate space for enumArray", stat);
                }
        }

        WRITEUNLOCKV(this, "wu db_table::grow");
}

/*
 * Return the first entry in table, also return its position in
 * 'where'.  Return NULL in both if no next entry is found.
 */
entry_object*
db_table::first_entry(entryp * where)
{
        ASSERTRHELD(table);
        if (count == 0 || tab == NULL) {  /* empty table */
                *where = 0;
                return (NULL);
        } else {
                entryp i;
                for (i = DB_TABLE_START;
                        i < table_size && i <= last_used; i++) {
                        if (tab[i] != NULL) {
                                *where = i;
                                return (tab[i]);
                        }
                }
        }
        *where = 0;
        return (NULL);
}

/*
 * Return the next entry in table from 'prev', also return its position in
 * 'newentry'.  Return NULL in both if no next entry is found.
 */
entry_object *
db_table::next_entry(entryp prev, entryp* newentry)
{
        long i;

        ASSERTRHELD(table);
        if (prev >= table_size || tab == NULL || tab[prev] == NULL)
                return (NULL);
        for (i = prev+1; i < table_size && i <= last_used; i++) {
                if (tab[i] != NULL) {
                        *newentry = i;
                        return (tab[i]);
                }
        }
        *newentry = 0;
        return (NULL);
}

/* Return entry at location 'where', NULL if location is invalid. */
entry_object *
db_table::get_entry(entryp where)
{
        ASSERTRHELD(table);
        if (where < table_size && tab != NULL && tab[where] != NULL)
                return (tab[where]);
        else
                return (NULL);
}

void
db_table::setEntryExp(entryp where, entry_obj *obj, int initialLoad) {
        nis_object              *o;
        const char              *myself = "db_table::setEntryExp";

        /*
         * If we don't know what type of object this is yet, we
         * can find out now. If it's a directory, the pseudo-object
         * in column zero will have the type "IN_DIRECTORY";
         * otherwise, it's a table object.
         */
        if (mapping.expireType == NIS_BOGUS_OBJ) {
                if (obj != 0) {
                        if (obj->en_type != 0 &&
                                strcmp(obj->en_type, "IN_DIRECTORY") == 0) {
                                mapping.expireType = NIS_DIRECTORY_OBJ;
                        } else {
                                mapping.expireType = NIS_TABLE_OBJ;
                                if (!mapping.fromLDAP) {
                                        free(mapping.expire);
                                        mapping.expire = 0;
                                }
                        }
                }
        }

        /* Set the entry TTL */
        if (mapping.expire != NULL) {
                struct timeval  now;
                time_t          lo, hi, ttl;

                (void) gettimeofday(&now, NULL);
                if (mapping.expireType == NIS_TABLE_OBJ) {
                        lo = mapping.initTtlLo;
                        hi = mapping.initTtlHi;
                        ttl = mapping.ttl;
                        /* TTL == 0 means always expired */
                        if (ttl == 0)
                                ttl = -1;
                } else {
                        __nis_table_mapping_t   *t = 0;

                        o = unmakePseudoEntryObj(obj, 0);
                        if (o != 0) {
                                __nis_buffer_t  b = {0, 0};

                                bp2buf(myself, &b, "%s.%s",
                                        o->zo_name, o->zo_domain);
                                t = getObjMapping(b.buf, 0, 1, 0, 0);
                                sfree(b.buf);
                                nis_destroy_object(o);
                        }

                        if (t != 0) {
                                lo = t->initTtlLo;
                                hi = t->initTtlHi;
                                ttl = t->ttl;
                                /* TTL == 0 means always expired */
                                if (ttl == 0)
                                        ttl = -1;
                        } else {
                                /*
                                 * No expiration time initialization
                                 * data. Cook up values that will
                                 * result in mapping.expire[where]
                                 * set to maxTimeT.
                                 */
                                hi = lo = ttl = maxTimeT - now.tv_sec;
                        }
                }

                if (initialLoad) {
                        int     interval = hi - lo + 1;
                        if (interval <= 1) {
                                mapping.expire[where] = now.tv_sec + lo;
                        } else {
                                srand48(now.tv_sec);
                                mapping.expire[where] = now.tv_sec +
                                                        (lrand48() % interval);
                        }
                        if (mapping.enumExpire == 0 ||
                                        mapping.expire[where] <
                                                        mapping.enumExpire)
                                mapping.enumExpire = mapping.expire[where];
                } else {
                        mapping.expire[where] = now.tv_sec + ttl;
                }
        }
}

/*
 * Add given entry to table in first available slot (either look in freelist
 * or add to end of table) and return the the position of where the record
 * is placed. 'count' is incremented if entry is added. Table may grow
 * as a side-effect of the addition. Copy is made of input.
*/
entryp
db_table::add_entry(entry_object *obj, int initialLoad) {
        /*
         * We're returning an index of the table array, so the caller
         * should hold a lock until done with the index. To save us
         * the bother of upgrading to a write lock, it might as well
         * be a write lock to begin with.
         */
        ASSERTWHELD(table);
        entryp where = freelist.pop();
        if (where == 0) {                               /* empty freelist */
                if (last_used >= (table_size-1))        /* full (> is for 0) */
                        grow();
                where = ++last_used;
        }
        if (tab != NULL) {
                ++count;
                setEntryExp(where, obj, initialLoad);

                if (enumMode.flag)
                        enumTouch(where);
                tab[where] = new_entry(obj);
                return (where);
        } else {
                return (0);
        }
}

/*
 * Replaces object at specified location by given entry.
 * Returns TRUE if replacement successful; FALSE otherwise.
 * There must something already at the specified location, otherwise,
 * replacement fails. Copy is not made of the input.
 * The pre-existing entry is freed.
 */
bool_t
db_table::replace_entry(entryp where, entry_object * obj)
{
        ASSERTWHELD(table);
        if (where < DB_TABLE_START || where >= table_size ||
            tab == NULL || tab[where] == NULL)
                return (FALSE);
        /* (Re-)set the entry TTL */
        setEntryExp(where, obj, 0);

        if (enumMode.flag)
                enumTouch(where);
        free_entry(tab[where]);
        tab[where] = obj;
        return (TRUE);
}

/*
 * Deletes entry at specified location.  Returns TRUE if location is valid;
 * FALSE if location is invalid, or the freed location cannot be added to
 * the freelist.  'count' is decremented if the deletion occurs.  The object
 * at that location is freed.
 */
bool_t
db_table::delete_entry(entryp where)
{
        bool_t  ret = TRUE;

        ASSERTWHELD(table);
        if (where < DB_TABLE_START || where >= table_size ||
            tab == NULL || tab[where] == NULL)
                return (FALSE);
        if (mapping.expire != NULL) {
                mapping.expire[where] = 0;
        }
        if (enumMode.flag)
                enumTouch(where);
        free_entry(tab[where]);
        tab[where] = NULL;    /* very important to set it to null */
        --count;
        if (where == last_used) { /* simple case, deleting from end */
                --last_used;
                return (TRUE);
        } else {
                return (freelist.push(where));
        }
        return (ret);
}

/*
 * Returns statistics of table.
 * [vector_size][table_size][last_used][count][freelist].
 * It is up to the caller to free the returned vector when it is through.
 * The free list is included if 'fl' is TRUE.
*/
long *
db_table::stats(bool_t include_freelist)
{
        long *answer;

        READLOCK(this, NULL, "r db_table::stats");
        if (include_freelist)
                answer = freelist.stats(3);
        else {
                answer = (long *)malloc(3*sizeof (long));
        }

        if (answer) {
                answer[0] = table_size;
                answer[1] = last_used;
                answer[2] = count;
        }
        READUNLOCK(this, answer, "ru db_table::stats");
        return (answer);
}

bool_t
db_table::configure(char *tablePath) {
        long            i;
        struct timeval  now;
        const char      *myself = "db_table::configure";

        (void) gettimeofday(&now, NULL);

        WRITELOCK(this, FALSE, "db_table::configure w");

        /* (Re-)initialize from global info */
        initMappingStruct(&mapping);

        /* Retrieve table mapping for this table */
        mapping.tm = (__nis_table_mapping_t *)__nis_find_item_mt(
                                        tablePath, &ldapMappingList, 0, 0);
        if (mapping.tm != 0) {
                __nis_object_dn_t       *odn = mapping.tm->objectDN;

                /*
                 * The mapping.fromLDAP and mapping.toLDAP fields serve as
                 * quick-references that tell us if mapping is enabled.
                 * Hence, initialize them appropriately from the table
                 * mapping objectDN.
                 */
                while (odn != 0 && (!mapping.fromLDAP || !mapping.toLDAP)) {
                        if (odn->read.scope != LDAP_SCOPE_UNKNOWN)
                                mapping.fromLDAP = TRUE;
                        if (odn->write.scope != LDAP_SCOPE_UNKNOWN)
                                mapping.toLDAP = TRUE;
                        odn = (__nis_object_dn_t *)odn->next;
                }

                /* Set the timeout values */
                mapping.initTtlLo = mapping.tm->initTtlLo;
                mapping.initTtlHi = mapping.tm->initTtlHi;
                mapping.ttl = mapping.tm->ttl;

                mapping.objName = sdup(myself, T, mapping.tm->objName);
                if (mapping.objName == 0 && mapping.tm->objName != 0) {
                        WRITEUNLOCK(this, FALSE,
                                "db_table::configure wu objName");
                        FATAL3("db_table::configure objName",
                                DB_MEMORY_LIMIT, FALSE);
                }
        }

        /*
         * In order to initialize the expiration times, we need to know
         * if 'this' represents a table or a directory. To that end, we
         * find an entry in the table, and invoke setEntryExp() on it.
         * As a side effect, setEntryExp() will examine the pseudo-object
         * in the entry, and set the expireType accordingly.
         */
        if (tab != 0) {
                for (i = 0; i <= last_used; i++) {
                        if (tab[i] != NULL) {
                                setEntryExp(i, tab[i], 1);
                                break;
                        }
                }
        }

        /*
         * If mapping from an LDAP repository, make sure we have the
         * expiration time array.
         */
        if ((mapping.expireType != NIS_TABLE_OBJ || mapping.fromLDAP) &&
                        mapping.expire == NULL && table_size > 0 && tab != 0) {
                db_status stat = allocateExpire(0, table_size);
                if (stat != DB_SUCCESS) {
                        WRITEUNLOCK(this, FALSE,
                                "db_table::configure wu expire");
                        FATAL3("db_table::configure expire",
                                stat, FALSE);
                }
        } else if (mapping.expireType == NIS_TABLE_OBJ && !mapping.fromLDAP &&
                        mapping.expire != NULL) {
                /* Not using expiration times */
                free(mapping.expire);
                mapping.expire = NULL;
        }

        /*
         * Set initial expire times for entries that don't already have one.
         * Establish the enumeration expiration time to be the minimum of
         * all expiration times in the table, though no larger than current
         * time plus initTtlHi.
         */
        if (mapping.expire != NULL) {
                int     interval = mapping.initTtlHi - mapping.initTtlLo + 1;
                time_t  enumXp = now.tv_sec + mapping.initTtlHi;

                if (interval > 1)
                        srand48(now.tv_sec);
                for (i = 0; i <= last_used; i++) {
                        if (tab[i] != NULL && mapping.expire[i] == 0) {
                                if (mapping.expireType == NIS_TABLE_OBJ) {
                                        if (interval > 1)
                                                mapping.expire[i] =
                                                        now.tv_sec +
                                                        (lrand48() % interval);
                                        else
                                                mapping.expire[i] =
                                                        now.tv_sec +
                                                        mapping.initTtlLo;
                                } else {
                                        setEntryExp(i, tab[i], 1);
                                }
                        }
                        if (enumXp > mapping.expire[i])
                                enumXp = mapping.expire[i];
                }
                mapping.enumExpire = enumXp;
        }

        WRITEUNLOCK(this, FALSE, "db_table::configure wu");

        return (TRUE);
}

/* Return TRUE if the entry at 'loc' hasn't expired */
bool_t
db_table::cacheValid(entryp loc) {
        bool_t          ret;
        struct timeval  now;

        (void) gettimeofday(&now, 0);

        READLOCK(this, FALSE, "db_table::cacheValid r");

        if (loc < 0 || loc >= table_size || tab == 0 || tab[loc] == 0)
                ret = FALSE;
        else if (mapping.expire == 0 || mapping.expire[loc] >= now.tv_sec)
                ret = TRUE;
        else
                ret = FALSE;

        READUNLOCK(this, ret, "db_table::cacheValid ru");

        return (ret);
}

/*
 * If the supplied object has the same content as the one at 'loc',
 * update the expiration time for the latter, and return TRUE.
 */
bool_t
db_table::dupEntry(entry_object *obj, entryp loc) {
        if (obj == 0 || loc < 0 || loc >= table_size || tab == 0 ||
                        tab[loc] == 0)
                return (FALSE);

        if (sameEntry(obj, tab[loc])) {
                setEntryExp(loc, tab[loc], 0);

                if (enumMode.flag > 0)
                        enumTouch(loc);
                return (TRUE);
        }

        return (FALSE);
}

/*
 * If enumeration mode is enabled, we keep a shadow array that initially
 * starts out the same as 'tab'. Any update activity (add, remove, replace,
 * or update timestamp) for an entry in the table means we delete the shadow
 * array pointer. When ending enumeration mode, we return the shadow array.
 * Any non-NULL entries in the array have not been updated since the start
 * of the enum mode.
 *
 * The indended use is for enumeration of an LDAP container, where we
 * will update all entries that currently exist in LDAP. The entries we
 * don't update are those that don't exist in LDAP, and thus should be
 * removed.
 *
 * Note that any LDAP query strictly speaking can be a partial enumeration
 * (i.e., return more than one match). Since the query might also have
 * matched multiple local DB entries, we need to do the same work as for
 * enumeration for any query. In order to avoid having to work on the
 * whole 'tab' array for simple queries (which we expect usually will
 * match just one or at most a few entries), we have a "reduced" enum mode,
 * where the caller supplies a count of the number of DB entries (derived
 * from db_mindex::satisfy_query() or similar), and then uses enumSetup()
 * to specify which 'tab' entries we're interested in.
 */
void
db_table::setEnumMode(long enumNum) {
        const char      *myself = "setEnumMode";

        enumMode.flag++;
        if (enumMode.flag == 1) {
                db_status       stat;

                if (enumNum < 0)
                        enumNum = 0;
                else if (enumNum >= table_size)
                        enumNum = table_size;

                enumCount.flag = enumNum;

                stat = allocateEnumArray(0, table_size);

                if (stat != DB_SUCCESS) {
                        enumMode.flag = 0;
                        enumCount.flag = 0;
                        logmsg(MSG_NOTIMECHECK, LOG_ERR,
                "%s: No memory for enum check array; entry removal disabled",
                                myself);
                }
        }
}

void
db_table::clearEnumMode(void) {
        if (enumMode.flag > 0) {
                enumMode.flag--;
                if (enumMode.flag == 0) {
                        sfree(enumArray.ptr);
                        enumArray.ptr = 0;
                        if (enumCount.flag > 0) {
                                sfree(enumIndex.ptr);
                                enumIndex.ptr = 0;
                                enumCount.flag = 0;
                        }
                }
        }
}

entry_object **
db_table::endEnumMode(long *numEa) {
        if (enumMode.flag > 0) {
                enumMode.flag--;
                if (enumMode.flag == 0) {
                        entry_obj       **ea = (entry_object **)enumArray.ptr;
                        long            nea;

                        enumArray.ptr = 0;

                        if (enumCount.flag > 0) {
                                nea = enumCount.flag;
                                enumCount.flag = 0;
                                sfree(enumIndex.ptr);
                                enumIndex.ptr = 0;
                        } else {
                                nea = table_size;
                        }

                        if (numEa != 0)
                                *numEa = nea;

                        return (ea);
                }
        }

        if (numEa != 0)
                *numEa = 0;

        return (0);
}

/*
 * Set the appropriate entry in the enum array to NULL.
 */
void
db_table::enumTouch(entryp loc) {
        if (loc < 0 || loc >= table_size)
                return;

        if (enumMode.flag > 0) {
                if (enumCount.flag < 1) {
                        ((entry_object **)enumArray.ptr)[loc] = 0;
                } else {
                        int     i;

                        for (i = 0; i < enumCount.flag; i++) {
                                if (loc == ((entryp *)enumIndex.ptr)[i]) {
                                        ((entry_object **)enumArray.ptr)[i] = 0;
                                        break;
                                }
                        }
                }
        }
}

/*
 * Add the entry indicated by 'loc' to the enumIndex array, at 'index'.
 */
void
db_table::enumSetup(entryp loc, long index) {
        if (enumMode.flag == 0 || loc < 0 || loc >= table_size ||
                        index < 0 || index >= enumCount.flag)
                return;

        ((entryp *)enumIndex.ptr)[index] = loc;
        ((entry_object **)enumArray.ptr)[index] = tab[loc];
}

/*
 * Touch, i.e., update the expiration time for the entry. Also, if enum
 * mode is in effect, mark the entry used for enum purposes.
 */
void
db_table::touchEntry(entryp loc) {
        if (loc < 0 || loc >= table_size || tab == 0 || tab[loc] == 0)
                return;

        setEntryExp(loc, tab[loc], 0);

        enumTouch(loc);
}

/* ************************* pickle_table ********************* */
/* Does the actual writing to/from file specific for db_table structure. */
/*
 * This was a static earlier with the func name being transfer_aux. The
 * backup and restore project needed this to copy files over.
 */
bool_t
transfer_aux_table(XDR* x, pptr dp)
{
        return (xdr_db_table(x, (db_table*) dp));
}

class pickle_table: public pickle_file {
    public:
        pickle_table(char *f, pickle_mode m) : pickle_file(f, m) {}

        /* Transfers db_table structure pointed to by dp to/from file. */
        int transfer(db_table* dp)
        { return (pickle_file::transfer((pptr) dp, &transfer_aux_table)); }
};

/*
 * Writes the contents of table, including the all the entries, into the
 * specified file in XDR format.  May need to change this to use APPEND
 * mode instead.
 */
int
db_table::dump(char *file)
{
        int     ret;
        READLOCK(this, -1, "r db_table::dump");
        pickle_table f(file, PICKLE_WRITE);   /* may need to use APPEND mode */
        int status = f.transfer(this);

        if (status == 1)
                ret = -1;
        else
                ret = status;
        READUNLOCK(this, ret, "ru db_table::dump");
        return (ret);
}

/* Constructor that loads in the table from the given file */
db_table::db_table(char *file)  : freelist()
{
        pickle_table f(file, PICKLE_READ);
        tab = NULL;
        table_size = last_used = count = 0;

        /* load  table */
        if (f.transfer(this) < 0) {
                /* fell through, something went wrong, initialize to null */
                tab = NULL;
                table_size = last_used = count = 0;
                freelist.init();
        }

        db_table_ldap_init();
        initMappingStruct(&mapping);
}

/* Returns whether location is valid. */
bool_t db_table::entry_exists_p(entryp i) {
        bool_t  ret = FALSE;
        READLOCK(this, FALSE, "r db_table::entry_exists_p");
        if (tab != NULL && i < table_size)
                ret = tab[i] != NULL;
        READUNLOCK(this, ret, "ru db_table::entry_exists_p");
        return (ret);
}

/* Empty free list */
void db_free_list::init() {
        WRITELOCKV(this, "w db_free_list::init");
        head = NULL;
        count = 0;
        WRITEUNLOCKV(this, "wu db_free_list::init");
}