/*
* 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.
*/
#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 NULL, if there aren't any. */
entryp
db_free_list::pop()
{
WRITELOCK(this, NULL, "w db_free_list::pop");
if (head == NULL) {
WRITEUNLOCK(this, NULL, "wu db_free_list::pop");
return (NULL);
}
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 he 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 = NULL;
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 = NULL;
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 = NULL;
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 == NULL) { /* 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 (NULL);
}
}
/*
* 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 his 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");
}
/* 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");
}