/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (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_mindex.cc * * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include "db_headers.h" #include "db.h" #include "db_mindex.h" #include "db_pickle.h" #include "nisdb_mt.h" #include "nisdb_ldap.h" #include "ldap_nisdbquery.h" #include "ldap_map.h" #include "ldap_ruleval.h" #include "ldap_scheme.h" #include "ldap_parse.h" #include "nis_hashitem.h" /* * Constructor: Create new table using scheme defintion supplied. * (Make copy of scheme and keep it with table.) */ db_mindex::db_mindex(db_scheme *how, char *tablePath) : rversion() { noWriteThrough.flag = 0; noLDAPquery.flag = 0; initialLoad.flag = 0; objPath.ptr = NULL; init(how); if (tablePath != NULL) configure(tablePath); } /* Constructor: Create empty table (no scheme, no table or indices). */ db_mindex::db_mindex() : rversion() { scheme = NULL; table = NULL; indices.indices_len = 0; indices.indices_val = NULL; noWriteThrough.flag = 0; noLDAPquery.flag = 0; initialLoad.flag = 0; objPath.ptr = NULL; INITRW(mindex); } db_mindex::~db_mindex() { reset(); /* get rid of data structures first */ DESTROYRW(mindex); } /* * Initialize table using information given in scheme 'how'. * Record the scheme for later use (make copy of it); * create the required number of indices; and create table for storing * entries. */ void db_mindex::init(db_scheme * how) { scheme = new db_scheme(how); // make copy if (scheme == NULL) FATAL("db_mindex::init: could not allocate space for scheme", DB_MEMORY_LIMIT); if (scheme->numkeys() == 0) { WARNING("db_mindex::init: empty scheme encountered"); /* what action should we take here? */ } indices.indices_len = how->numkeys(); db_key_desc * keys = how->keyloc(); int i; /* homogeneous indices for now */ indices.indices_val = new db_index[indices.indices_len]; if (indices.indices_val == NULL) { delete scheme; indices.indices_len = 0; scheme = NULL; FATAL("db_mindex::init: could not allocate space for indices", DB_MEMORY_LIMIT); } for (i = 0; i < indices.indices_len; i++) { indices.indices_val[i].init(&(keys[i])); } table = new db_table(); if (table == NULL) { delete scheme; scheme = NULL; delete indices.indices_val; indices.indices_val = NULL; indices.indices_len = 0; FATAL("db_mindex::init: could not allocate space for table", DB_MEMORY_LIMIT); } rversion.zero(); INITRW(mindex); objPath.ptr = NULL; } /* empty associated tables associated */ void db_mindex::reset_tables() { int i; WRITELOCKV(this, "w db_mindex::reset_tables"); /* Add sanity check in case of table corruption */ if (indices.indices_val != NULL) { for (i = 0; i < indices.indices_len; i++) { indices.indices_val[i].reset(); } } if (table) table->reset(); WRITEUNLOCKV(this, "wu db_mindex::reset_tables"); } /* * Return a list of index_entries that satsify the given query 'q'. * Return the size of the list in 'count'. Return NULL if list is empty. * Return in 'valid' FALSE if query is not well formed. */ db_index_entry_p db_mindex::satisfy_query(db_query *q, long *count, bool_t *valid) { return (satisfy_query(q, count, valid, FALSE)); } db_index_entry_p db_mindex::satisfy_query(db_query *q, long *count, bool_t *valid, bool_t fromLDAP) { db_index_entry_p ret; bool_t validRequest; int queryRes; /* Make sure we have somewhere to store the "request valid" status */ if (valid == NULL) valid = &validRequest; /* Prepare for a failed lock */ *count = 0; *valid = FALSE; READLOCK(this, NULL, "r db_mindex::satisfy_query"); /* * Only get data from LDAP if the caller requested it, * and if we're mapping for this table. */ fromLDAP = (fromLDAP && !noLDAPquery.flag && (table->mapping.fromLDAP || table->mapping.objType != NIS_TABLE_OBJ)); /* * If we always fetch data from LDAP for query's, then do so now, * before invoking the "real" satisfy_query(). */ if (fromLDAP && table->mapping.matchFetch == mat_always) { int lockcode = 0; READLOCKNR(table, lockcode, "r db_mindex::satisfy_query table"); if (lockcode != 0) { READUNLOCK(this, NULL, "ru db_mindex::satisfy_query"); return (NULL); } queryRes = queryLDAP(q, 0, 1); READUNLOCKNR(table, lockcode, "ru db_mindex::satisfy_query table"); if (lockcode != 0) { READUNLOCK(this, NULL, "ru db_mindex::satisfy_query"); return (NULL); } if (queryRes != LDAP_SUCCESS) { /* queryLDAP() sets error codes etc. */ READUNLOCK(this, NULL, "ru db_mindex::satisfy_query"); return (NULL); } } ret = satisfy_query_dbonly(q, count, fromLDAP ? TRUE : FALSE, valid); /* If we found it, or if we're not mapping, return */ if (ret != NULL || !fromLDAP) { READUNLOCK(this, NULL, "ru db_mindex::satisfy_query"); return (ret); } else if (ret == NULL && !(*valid)) { /* No result, and the request wasn't valid */ READUNLOCK(this, NULL, "ru db_mindex::satisfy_query"); return (NULL); } /* Get data from LDAP */ if (table->mapping.matchFetch != mat_never) { queryRes = queryLDAP(q, 0, 1); } else { /* * We'll now go on to check for an un-expired entry again, * even though we're pretty sure that won't work (already * did that, and nothing's changed). However, we accept that * slight inefficiency in the interest of keeping the code * simple; we expect 'mat_never' to be used very rarely. */ queryRes = LDAP_SUCCESS; } if (queryRes == LDAP_SUCCESS) { /* * Check if we've got a match now. If not, try one * last time for an expired match. */ ret = satisfy_query_dbonly(q, count, TRUE, valid); if (ret == NULL) { ret = satisfy_query_dbonly(q, count, FALSE, valid); } } else { /* * Check if we have an expired entry; if so, return * it with an appropriate status. */ ret = satisfy_query_dbonly(q, count, FALSE, valid); } READUNLOCK(this, NULL, "ru db_mindex::satisfy_query"); return (ret); } db_index_entry_p db_mindex::satisfy_query_dbonly(db_query *q, long *count, bool_t checkExpire, bool_t *valid) { db_index_entry_p oldres = NULL, newres; int i, curr_ind; long num_new, num_old = 0; int limit = q->size(); db_qcomp * comps = q->queryloc(); if (valid) *valid = TRUE; /* True to begin with. */ /* Add sanity check in case table corrupted */ if (indices.indices_len != 0 && indices.indices_val == NULL) { WARNING("db_mindex::satisfy_query: table has no indices"); if (valid) *valid = FALSE; *count = 0; return (NULL); } for (i = 0; i < limit; i++) { if ((curr_ind = comps[i].which_index) < indices.indices_len) { newres = indices.indices_val[curr_ind].lookup( comps[i].index_value, &num_new, table, checkExpire); if (newres == NULL) { *count = 0; return (NULL); } if (oldres == NULL) { oldres = newres; num_old = num_new; } else { oldres = newres->join(num_new, num_old, oldres, &num_old); if (oldres == NULL) { *count = 0; return (NULL); } } } else { WARNING("db_mindex::satisfy_query: index out of range"); if (valid) *valid = FALSE; *count = 0; return (NULL); } } *count = num_old; return (oldres); } /* * Returns an array of size 'count' of 'entry_object_p's, pointing to * copies of entry_objects named by the result list of db_index_entries 'res'. * Sets db_status 'statp' if error encountered; otherwise, leaves it unchanged. */ entry_object_p * db_mindex::prepare_results(int count, db_index_entry_p res, db_status *statp) { READLOCK(this, NULL, "r db_mindex::prepare_results"); READLOCK2(table, NULL, "r table db_mindex::prepare_results", this); entry_object_p * entries = new entry_object_p[count]; int i; if (entries == NULL) { READUNLOCK2(this, table, NULL, NULL, "ru db_mindex::prepare_results: could not allocate space", "ru table db_mindex::prepare_results: could not allocate space"); FATAL3("db_mindex::prepare_results: could not allocate space", DB_MEMORY_LIMIT, NULL); } for (i = 0; i < count; i++) { if (res == NULL) { int j; for (j = 0; j < i; j++) // cleanup free_entry(entries[j]); syslog(LOG_ERR, "db_mindex::prepare_results: incorrect count"); *statp = DB_INTERNAL_ERROR; } else { entries[i] = new_entry(table->get_entry(res->getlocation())); res = res->getnextresult(); } } READUNLOCK2(this, table, entries, entries, "ru db_mindex::prepare_results", "ru db_mindex::prepare_results"); return (entries); } /* * Returns a newly created db_query structure containing the index values * as obtained from the record named by 'recnum'. The record itself, along * with information on the schema definition of this table, will determine * which values are extracted from the record and placed into the result. * Returns NULL if recnum is not a valid entry. * Note that space is allocated for the query and the index values * (i.e. do not share pointers with strings in 'obj'.) */ db_query * db_mindex::extract_index_values_from_record(entryp recnum) { db_query *ret; ret = extract_index_values_from_object(table->get_entry(recnum)); return (ret); } /* * Returns a newly created db_query containing the index values as * obtained from the given object. The object itself, * along with information on the scheme given, will determine * which values are extracted from the object and placed into the query. * Returns an empty query if 'obj' is not a valid entry. * Note that space is allocated for the query and the index values * (i.e. do not share pointers with strings in 'obj'.) */ db_query * db_mindex::extract_index_values_from_object(entry_object_p obj) { READLOCK(this, NULL, "r db_mindex::extract_index_values_from_object"); if (scheme->numkeys() != indices.indices_len) { // probably built wrong syslog(LOG_ERR, "number of keys (%d) does not equal number of indices (%d)", scheme->numkeys(), indices.indices_len); READUNLOCK(this, NULL, "ru db_mindex::extract_index_values_from_object"); return (new db_query()); // null query } else if (obj == NULL) { READUNLOCK(this, NULL, "ru db_mindex::extract_index_values_from_object"); return (NULL); } else { db_query* answer = new db_query(scheme, obj); if (answer) { /* * XXX If the unlock fails, and we return NULL, * we leak 'answer'. On the other hand, if we * return 'answer', the object may remain locked, * but the caller doesn't know that anything * went wrong. */ READUNLOCK(this, NULL, "ru db_mindex::extract_index_values_from_object"); return (answer); } else { FATAL3("db_mindex::extract: could not allocate space", DB_MEMORY_LIMIT, NULL); } } READUNLOCK(this, NULL, "ru db_mindex::extract_index_values_from_object"); return (NULL); } /* * Returns the first entry found in the table by setting 'answer' to * point to the a copy of entry_object. Returns DB_SUCCESS if found; * DB_NOTFOUND otherwise. */ db_status db_mindex::first(entryp *where, entry_object ** answer) { db_status ret = DB_SUCCESS; /* * table->first_entry() returns a pointer into the table, so * we must keep the table read locked until we've copied the * entry_object. In order to maintain lock integrity, we must * lock the db_mindex (this) before the db_table (table). */ READLOCK(this, DB_LOCK_ERROR, "r db_mindex::first"); READLOCK2(table, DB_LOCK_ERROR, "r table db_mindex::first", this); if (table->mapping.fromLDAP) { struct timeval now; (void) gettimeofday(&now, NULL); if (now.tv_sec >= table->mapping.enumExpire) { int queryRes = queryLDAP(0, 0, 1); if (queryRes == LDAP_SUCCESS) table->mapping.enumExpire = now.tv_sec + table->mapping.ttl; else { READUNLOCK2(this, table, DB_LOCK_ERROR, DB_LOCK_ERROR, "ru db_mindex::first LDAP", "ru table db_mindex::first LDAP"); return (DB_INTERNAL_ERROR); } } } entry_object_p ptr = table->first_entry(where); if (ptr == NULL) ret = DB_NOTFOUND; else *answer = new_entry(ptr); READUNLOCK2(this, table, ret, ret, "ru db_mindex::first", "ru table db_mindex::first"); return (ret); } /* * Returns the next entry in the table after 'previous' by setting 'answer' to * point to copy of the entry_object. Returns DB_SUCCESS if 'previous' is * valid and next entry is found; DB_NOTFOUND otherwise. Sets 'where' to * location of where entry is found for input as subsequent 'next' operation. */ db_status db_mindex::next(entryp previous, entryp *where, entry_object **answer) { db_status ret = DB_SUCCESS; READLOCK(this, DB_LOCK_ERROR, "r db_mindex::next"); READLOCK2(table, DB_LOCK_ERROR, "r db_mindex::next", this); if (!(table->entry_exists_p(previous))) ret = DB_NOTFOUND; else { entry_object * ptr = table->next_entry(previous, where); if (ptr == NULL) ret = DB_NOTFOUND; else *answer = new_entry(ptr); } READUNLOCK2(this, table, ret, ret, "ru db_mindex::next", "ru table db_mindex::next"); return (ret); } static void delete_result_list(db_next_index_desc* orig) { db_next_index_desc* curr, *save_next; for (curr = orig; curr != NULL; 0) { save_next = curr->next; delete curr; curr = save_next; } } static db_next_index_desc * copy_result_list(db_index_entry* orig) { db_next_index_desc *head = NULL, *curr; db_index_entry *current; for (current = orig; current != NULL; current = current->getnextresult()) { curr = new db_next_index_desc(current->getlocation(), head); if (curr == NULL) { FATAL3( "db_mindex::copy_result_list: could not allocate space", DB_MEMORY_LIMIT, NULL); } head = curr; // list is actually reversed } return (head); } /* * Delete the given list of results; used when no longer interested in * the results of the first/next query that returned this list. */ db_status db_mindex::reset_next(db_next_index_desc *orig) { if (orig == NULL) return (DB_NOTFOUND); delete_result_list(orig); return (DB_SUCCESS); } /* * Finds entry that satisfy the query 'q'. Returns the first answer by * setting the pointer 'answer' to point to a copy of it. 'where' is set * so that the other answers could be gotten by passing 'where' to 'next' * successively. Note that the answer is a pointer to a copy of the entry. * Returns DB_SUCCESS if search was successful; DB_NOTFOUND otherwise. */ db_status db_mindex::first(db_query *q, db_next_index_desc **where, entry_object ** answer) { READLOCK(this, DB_LOCK_ERROR, "r db_mindex::first"); READLOCK2(table, DB_LOCK_ERROR, "r table db_mindex::first", this); long count; bool_t valid_query; db_status ret = DB_SUCCESS; db_index_entry * rp = satisfy_query(q, &count, &valid_query, TRUE); if (valid_query != TRUE) ret = DB_BADQUERY; else if (rp == NULL) { *answer = NULL; ret = DB_NOTFOUND; } else { *where = copy_result_list(rp); entry_object_p ptr = table->get_entry((*where)->location); if (ptr == NULL) ret = DB_NOTFOUND; else *answer = new_entry(ptr); } READUNLOCK2(this, table, ret, ret, "ru db_mindex::first", "ru table db_mindex::first"); return (ret); } /* * Returns the next entry in the table after 'previous' by setting 'answer' to * point to copy of the entry_object. Next is next in chain of answers found * in previous first search with query. Returns DB_SUCCESS if 'previous' is * valid and next entry is found; DB_NOTFOUND otherwise. Sets 'where' to * location of where entry is found for input as subsequent 'next' operation. */ db_status db_mindex::next(db_next_index_desc *previous, db_next_index_desc **where, entry_object **answer) { READLOCK(this, DB_LOCK_ERROR, "r db_mindex::next"); READLOCK2(table, DB_LOCK_ERROR, "r table db_mindex::next", this); db_status ret = DB_SUCCESS; if (previous == NULL) ret = DB_NOTFOUND; else { // should further check validity of 'previous' pointer *where = previous->next; delete previous; // delete previous entry if (*where == NULL) ret = DB_NOTFOUND; else { entry_object * ptr = table->get_entry((*where)->location); if (ptr == NULL) ret = DB_NOTFOUND; else { *answer = new_entry(ptr); ret = DB_SUCCESS; } } } READUNLOCK2(this, table, ret, ret, "ru db_mindex::next", "ru table db_mindex::next"); return (ret); } /* * Finds entry that satisfy the query 'q'. Returns the answer by * setting the pointer 'rp' to point to the list of answers. * Note that the answers are pointers to the COPIES of entries. * Returns the number of answers find in 'count'. * Returns DB_SUCCESS if search found at least one answer; * returns DB_NOTFOUND if none is found. */ db_status db_mindex::lookup(db_query *q, long *count, entry_object_p **result) { bool_t valid_query; db_index_entry * rp = satisfy_query(q, count, &valid_query, TRUE); db_status stat = DB_SUCCESS; if (valid_query != TRUE) return (DB_BADQUERY); if (rp == NULL) { *result = NULL; return (DB_NOTFOUND); } *result = prepare_results((int)*count, rp, &stat); return (stat); } /* * Return all entries within table. Returns the answer by * setting the pointer 'rp' to point to the list of answers. * Note that the answers are pointers to copies of the entries. * Returns the number of answers find in 'count'. * Returns DB_SUCCESS if search found at least one answer; * returns DB_NOTFOUND if none is found. */ db_status db_mindex::all(long *count, entry_object_p **result) { entry_object *ptr; entryp where; long how_many, i; int lret = 0; if (table == NULL) { *result = NULL; return (DB_NOTFOUND); } READLOCK(this, DB_LOCK_ERROR, "r db_mindex::all"); /* Read lock 'table' while we're traversing it */ READLOCKNR(table, lret, "r table db_mindex::all"); if (lret != 0) { READUNLOCK(this, DB_LOCK_ERROR, "ru db_mindex::all"); return (DB_LOCK_ERROR); } if (table->mapping.fromLDAP) { struct timeval now; (void) gettimeofday(&now, NULL); if (now.tv_sec >= table->mapping.enumExpire) { int queryRes = queryLDAP(0, 0, 1); if (queryRes != LDAP_SUCCESS) { READUNLOCKNR(table, lret, "ru table db_mindex::all LDAP"); READUNLOCK(this, DB_LOCK_ERROR, "ru db_mindex::all LDAP"); return (DB_INTERNAL_ERROR); } } } if ((how_many = table->fullness()) <= 0) { /* * Set '*count' so that the caller avoids putting garbage * in an 'objects_len' field. */ *count = 0; *result = NULL; READUNLOCKNR(table, lret, "ru table db_mindex::all"); READUNLOCK(this, DB_NOTFOUND, "ru db_mindex::all"); return (DB_NOTFOUND); } entry_object_p * answer = new entry_object_p[how_many]; if (answer == NULL) { READUNLOCKNR(table, lret, "ru table db_mindex::all"); READUNLOCK(this, DB_MEMORY_LIMIT, "ru db_mindex::all"); FATAL3("db_mindex::all: could not allocate space", DB_MEMORY_LIMIT, DB_MEMORY_LIMIT); } *count = how_many; ptr = table->first_entry(&where); if (ptr != NULL) answer[0] = new_entry(ptr); else { WARNING("db_mindex::all: null first entry found in all"); answer[0] = NULL; } for (i = 1; i < how_many; i++) { ptr = table->next_entry(where, &where); if (ptr != NULL) answer[i] = new_entry(ptr); else { WARNING( "db_mindex::all: null internal entry found in all"); answer[i] = NULL; /* Answer gets null too. -CM */ } } READUNLOCKNR(table, lret, "ru table db_mindex::all"); *result = answer; READUNLOCK(this, DB_SUCCESS, "ru db_mindex::all"); return (DB_SUCCESS); } /* * Remove the entry identified by 'recloc' from: * 1. all indices, as obtained by extracting the index values from the entry * 2. table where entry is stored. */ db_status db_mindex::remove_aux(entryp recloc) { int i, curr_ind; db_status res = DB_SUCCESS; WRITELOCK(this, DB_LOCK_ERROR, "w db_mindex::remove_aux"); /* get index values of this record */ db_query * cq = extract_index_values_from_record(recloc); if (cq == NULL) { WRITEUNLOCK(this, DB_MEMORY_LIMIT, "wu db_mindex::remove_aux"); FATAL3("db_mindex::remove_aux: could not allocate space", DB_MEMORY_LIMIT, DB_MEMORY_LIMIT); } if (cq->size() != indices.indices_len) { /* something is wrong */ delete cq; // clean up syslog(LOG_ERR, "db_mindex::remove_aux: record contains wrong number of indices"); WRITEUNLOCK(this, DB_INTERNAL_ERROR, "wu db_mindex::remove_aux"); return (DB_INTERNAL_ERROR); } if (!noWriteThrough.flag) { nis_object *o = 0; entry_object *e = table->get_entry(recloc); int queryRes, doingModify; /* * If the removal is part of a modify operation, we * defer the LDAP update until the modified NIS+ object * is added back. */ if (saveOldObjForModify((entry_obj *)e, &doingModify) == 0) res = DB_INTERNAL_ERROR; if (res == DB_SUCCESS && !doingModify) { /* * If we're removing a directory entry, and the * entry is LDAP-mapped, but the directory isn't, * we need a copy of the entry object in order * to remove if from LDAP. */ if (e != 0 && e->en_type != 0 && strcmp(e->en_type, "IN_DIRECTORY") == 0) o = unmakePseudoEntryObj(e, 0); queryRes = removeLDAP(cq, o); if (queryRes != LDAP_SUCCESS) { if (table->mapping.storeErrorDisp == abandon) res = DB_INTERNAL_ERROR; } if (o != 0) nis_destroy_object(o); } } if (res == DB_SUCCESS) { db_qcomp * comps = cq->queryloc(); /* Add sanity check in case of corrupted table */ if (indices.indices_val != NULL) { /* update indices */ for (i = 0; i < indices.indices_len; i++) { /* unnec. if sorted */ curr_ind = comps[i].which_index; indices.indices_val[curr_ind].remove( comps[i].index_value, recloc); } } /* update table where record is stored */ table->delete_entry(recloc); } /* delete query */ delete cq; WRITEUNLOCK(this, DB_SUCCESS, "wu db_mindex::remove_aux"); return (res); } /* * Removes the entry in the table named by given query 'q'. * If a NULL query is supplied, all entries in table are removed. * Returns DB_NOTFOUND if no entry is found. * Returns DB_SUCCESS if one entry is found; this entry is removed from * its record storage, and it is also removed from all the indices of the * table. If more than one entry satisfying 'q' is found, all are removed. */ db_status db_mindex::remove(db_query *q) { long count = 0; db_index_entry *rp; db_status rstat; bool_t valid_query; WRITELOCK(this, DB_LOCK_ERROR, "w db_mindex::remove"); WRITELOCK2(table, DB_LOCK_ERROR, "w table db_mindex::remove", this); if (q == NULL) { /* remove all entries in table */ if (table->mapping.toLDAP && !noWriteThrough.flag) { int queryRes = removeLDAP(q, 0); #ifdef NISDB_LDAP_DEBUG if (queryRes != LDAP_SUCCESS) abort(); #endif /* NISDB_LDAP_DEBUG */ } if (table != NULL && table->getsize() > 0) { reset_tables(); WRITEUNLOCK2(table, this, DB_SUCCESS, DB_SUCCESS, "wu table db_mindex::remove", "wu db_mindex::remove"); return (DB_SUCCESS); } else { WRITEUNLOCK2(table, this, DB_NOTFOUND, DB_NOTFOUND, "wu table db_mindex::remove", "wu db_mindex::remove"); return (DB_NOTFOUND); } } rp = satisfy_query(q, &count, &valid_query, FALSE); if (valid_query != TRUE) { WRITEUNLOCK2(table, this, DB_BADQUERY, DB_BADQUERY, "wu table db_mindex::remove", "wu db_mindex::remove"); return (DB_BADQUERY); } if (count == 0) { /* not found */ WRITEUNLOCK2(table, this, DB_NOTFOUND, DB_NOTFOUND, "wu table db_mindex::remove", "wu db_mindex::remove"); return (DB_NOTFOUND); } else if (count == 1) { /* found, update indices */ db_status s; s = remove_aux(rp->getlocation()); WRITEUNLOCK2(table, this, s, s, "wu table db_mindex::remove", "wu db_mindex::remove"); return (s); } else { /* ambiguous, remove all entries */ int i; db_index_entry *next_entry; for (i = 0; i < count; i++) { if (rp == NULL) { syslog(LOG_ERR, "db_mindex::remove: incorrect number of indices"); WRITEUNLOCK2(table, this, DB_INTERNAL_ERROR, DB_INTERNAL_ERROR, "wu table db_mindex::remove", "wu db_mindex::remove"); return (DB_INTERNAL_ERROR); } next_entry = rp->getnextresult(); // save before removal rstat = remove_aux(rp->getlocation()); if (rstat != DB_SUCCESS) { WRITEUNLOCK2(table, this, rstat, rstat, "wu table db_mindex::remove", "wu db_mindex::remove"); return (rstat); } rp = next_entry; // go on to next } WRITEUNLOCK2(table, this, DB_SUCCESS, DB_SUCCESS, "wu table db_mindex::remove", "wu db_mindex::remove"); return (DB_SUCCESS); } } /* * Add copy of given entry to table. Entry is identified by query 'q'. * The entry (if any) satisfying the query is first deleted, then * added to the indices (using index values extracted form the given entry) * and the table. * Returns DB_NOTUNIQUE if more than one entry satisfies the query. * Returns DB_NOTFOUND if query is not well-formed. * Returns DB_SUCCESS if entry can be added. */ db_status db_mindex::add(db_query *q, entry_object * obj) { long count = 0; int i, curr_ind; bool_t valid; db_index_entry *rp = NULL; db_status rstat; const char *myself = "db_mindex::add"; /* * The argument q is only NULL when we know that there are * no objects in the database that match the object. */ WRITELOCK(this, DB_LOCK_ERROR, "w db_mindex::add"); WRITELOCK2(table, DB_LOCK_ERROR, "w table db_mindex::add", this); if (q) { rp = satisfy_query(q, &count, &valid, FALSE); if (!valid) { WRITEUNLOCK2(this, table, DB_LOCK_ERROR, DB_LOCK_ERROR, "wu db_mindex::add", "wu table db_mindex::add"); return (DB_BADQUERY); } } if (count == 1) { /* found, first delete */ rstat = remove_aux(rp->getlocation()); if (rstat != DB_SUCCESS) { WRITEUNLOCK2(this, table, rstat, rstat, "wu db_mindex::add", "wu table db_mindex::add"); return (rstat); } count = 0; /* fall through to add */ } if (count == 0) { /* not found, insert */ /* add object to table */ entryp recloc = table->add_entry(obj, initialLoad.flag); /* get index values of this object, might be same as 'q' */ db_query *cq = extract_index_values_from_object(obj); if (cq == NULL) { table->delete_entry(recloc); WRITEUNLOCK2(this, table, DB_MEMORY_LIMIT, DB_MEMORY_LIMIT, "wu db_mindex::add DB_MEMORY_LIMIT", "wu table db_mindex::add DB_MEMORY_LIMIT"); FATAL3("db_mindex::add: could not allocate space for", DB_MEMORY_LIMIT, DB_MEMORY_LIMIT); } if (cq ->size() != indices.indices_len) { /* something wrong */ table->delete_entry(recloc); delete cq; // clean up syslog(LOG_ERR, "db_mindex::add: record contains wrong number of indices"); WRITEUNLOCK2(this, table, DB_INTERNAL_ERROR, DB_INTERNAL_ERROR, "wu db_mindex::add DB_INTERNAL_ERROR", "wu table db_mindex::add DB_INTERNAL_ERROR"); return (DB_INTERNAL_ERROR); } db_qcomp * comps = cq->queryloc(); /* update indices */ if (indices.indices_val != NULL) { for (i = 0; i < indices.indices_len; i++) { curr_ind = comps[i].which_index; indices.indices_val[curr_ind].add( comps[i].index_value, recloc); } } delete cq; // clean up if (!noWriteThrough.flag) { int queryRes; entry_object *e = 0; if (retrieveOldObjForModify((entry_obj **)&e) == 0) { logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Error retrieving old object for LDAP update", myself); return (DB_INTERNAL_ERROR); } queryRes = storeLDAP(q, obj, 0, e, 0); if (queryRes != LDAP_SUCCESS) { if (table->mapping.storeErrorDisp == abandon) { WRITEUNLOCK2(this, table, DB_INTERNAL_ERROR, DB_INTERNAL_ERROR, "wu db_mindex::add LDAP", "wu table db_mindex::add LDAP"); return (DB_INTERNAL_ERROR); } else { logmsg(MSG_NOTIMECHECK, LOG_WARNING, "%s: LDAP store failed: %s", myself, ldap_err2string(queryRes)); } } } rstat = DB_SUCCESS; } else /* ambiguous */ rstat = DB_NOTUNIQUE; WRITEUNLOCK2(this, table, rstat, rstat, "wu db_mindex::add", "wu table db_mindex::add"); return (rstat); } /* ************************* pickle_mindex ********************* */ /* Does the actual writing to/from file specific for db_mindex structure. */ static bool_t transfer_aux(XDR* x, pptr rp) { return (xdr_db_mindex(x, (db_mindex*) rp)); } class pickle_mindex: public pickle_file { public: pickle_mindex(char *f, pickle_mode m) : pickle_file(f, m) {} /* Transfers db_mindex structure pointed to by dp to/from file. */ int transfer(db_mindex* dp) { int ret; WRITELOCK(dp, -1, "w pickle_mindex::transfer"); ret = pickle_file::transfer((pptr) dp, &transfer_aux); WRITEUNLOCK(dp, ret, "wu pickle_mindex::transfer"); return (ret); } }; /* Write this structure (table, indices, scheme) into the specified file. */ int db_mindex::dump(char *file) { pickle_mindex f(file, PICKLE_WRITE); int status = f.transfer(this); if (status == 1) return (-1); /* could not open for write */ else return (status); } /* * Reset the table by: deleting all the indices, table of entries, and its * scheme. */ void db_mindex::reset() { WRITELOCKV(this, "w db_mindex::reset"); reset_tables(); /* clear table contents first */ if (indices.indices_val) { delete [] indices.indices_val; indices.indices_val = NULL; } if (table) { delete table; table = NULL; } if (scheme) { delete scheme; scheme = NULL; } indices.indices_len = 0; rversion.zero(); if (objPath.ptr != 0) { free(objPath.ptr); objPath.ptr = 0; } WRITEUNLOCKV(this, "wu db_mindex::reset"); } /* * Initialize table using information from specified file. * The table is first 'reset', then the attempt to load from the file * is made. If the load failed, the table is again reset. * Therefore, the table will be modified regardless of the success of the * load. Returns 0 if successful, 1 if DB disk file couldn't be opened, * -1 for various other failures. */ int db_mindex::load(char *file) { pickle_mindex f(file, PICKLE_READ); int status; int init_table = (this->table == NULL); int init_scheme = (this->scheme == NULL); WRITELOCK(this, -1, "w db_mindex::load"); reset(); /* load new mindex */ if ((status = f.transfer(this)) != 0) { /* load failed. Reset. */ reset(); } /* Initialize the 'scheme' locking */ if (status == 0 && this->scheme != 0 && init_scheme) { /* * Since we've added fields to the db_scheme that aren't * read from disk, we need to re-allocate so that the * db_scheme instance is large enough. */ db_scheme *tmpscheme = new db_scheme(); if (tmpscheme != 0) { (void) memcpy(tmpscheme, this->scheme, this->scheme->oldstructsize()); free(this->scheme); this->scheme = tmpscheme; } else { status = -1; } } /* * If the 'table' field was NULL before the load, but not now, * initialize the table locking and mapping. */ if (status == 0 && this->table != 0 && init_table) { /* * As for the db_scheme, make sure the db_table is large * enough. */ db_table *tmptable = new db_table(); if (tmptable != 0) { (void) memcpy(tmptable, this->table, this->table->oldstructsize()); free(this->table); this->table = tmptable; (void) this->configure(file); } else { status = -1; } } if (status == 0 && this->indices.indices_val != NULL) { /* * Recreate the db_index instance so that it is * correctly initialized. */ db_index *tmp_indices; int n_index = this->indices.indices_len; tmp_indices = new db_index[n_index]; if (tmp_indices != NULL) { for (int i = 0; i < n_index; i++) { if (tmp_indices[i].move_xdr_db_index (&this->indices.indices_val[i]) != DB_SUCCESS) { status = -1; break; } } free(this->indices.indices_val); this->indices.indices_val = tmp_indices; this->indices.indices_len = n_index; } else { status = -1; } } WRITEUNLOCK(this, status, "wu db_mindex::load"); return (status); } /* * Prints statistics of the table. This includes the size of the table, * the number of entries, and the index sizes. */ void db_mindex::print_stats() { long size, count, i; long *stats = table->stats(TRUE); printf("table_size = %d\n", stats[0]); printf("last_used = %d\n", stats[1]); printf("count = %d\n", stats[2]); printf("free list size = %d\n", stats[3]); printf("free list count = %d\n", stats[4]); for (i = 5; i < 5+stats[4]; i++) { printf("%d, ", stats[i]); } printf("\n"); free((char *)stats); /* Add sanity check in case of corrupted table */ if (indices.indices_val == NULL) { printf("No indices to print\n"); return; } for (i = 0; i < indices.indices_len; i++) { printf("***** INDEX %d ******\n", i); indices.indices_val[i].stats(&size, &count); printf("index table size = %d\ncount = %d\n", size, count); } } /* Prints statistics about all indices of table. */ void db_mindex::print_all_indices() { int i; READLOCKV(this, "r db_mindex::print_all_indices"); /* Add sanity check in case of corrupted table */ if (indices.indices_val == NULL) { printf("No indices to print\n"); READUNLOCKV(this, "ru db_mindex::print_all_indices"); return; } for (i = 0; i < indices.indices_len; i++) { printf("***** INDEX %d ******\n", i); indices.indices_val[i].print(); } READUNLOCKV(this, "ru db_mindex::print_all_indices"); } /* Prints statistics about indices identified by 'n'. */ void db_mindex::print_index(int n) { READLOCKV(this, "r db_mindex::print_index"); if (n >= 0 && n < indices.indices_len) indices.indices_val[n].print(); READUNLOCKV(this, "ru db_mindex::print_index"); }