root/usr/src/lib/libnisdb/nis_db.cc
/*
 * 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
 */
/*
 *      nis_db.cc
 *
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 *
 * Copyright 2015 RackTop Systems.
 */


#include <sys/param.h>
#include <strings.h>
#include <syslog.h>
#include "nisdb_mt.h"
#include "db_headers.h"
#include "db_entry.h"
#include "db.h"
#include "db_dictionary.h"
#include "db_pickle.h"
#include "nis_db.h"
#include "nis_ldap.h"
#include "ldap_util.h"
#include "ldap_parse.h"
#include "ldap_glob.h"
#include "ldap_xdr.h"
#include "ldap_glob.h"

db_dictionary   curdict;
db_dictionary   tempdict; /* a temporary one */

db_dictionary *InUseDictionary = &curdict;
db_dictionary *FreeDictionary = &tempdict;

extern "C" {
static db_result        *db_add_entry_x(char *tab, int numattrs,
                                        nis_attr *attrname, entry_obj * newobj,
                                        int skiplog, int nosync);
db_status               db_table_exists(char *table_name);

/*
 * (Imported from rpc.nisd/nis_xx_proc.c)
 *
 * 'tbl_prototype' is used to create a table that holds a directory.
 */
static table_col cols[2] = {
        {(char *)"object", TA_BINARY+TA_XDR, 0},
        {(char *)"name", TA_CASE+TA_SEARCHABLE, 0}
};

table_obj tbl_prototype = { (char *)"DIRECTORY", 2, ' ', {2, &cols[0]}, NULL };
}

/*
 * Free resources associated with a db_result structure
 */
void
db_free_result(db_result *dr)
{
        int     i;

        if (dr == 0)
                return;

        /* Can't have valid objects */
        if (dr->status != DB_SUCCESS) {
                free(dr);
                return;
        }

        for (i = 0; i < dr->objects.objects_len; i++)
                free_entry(dr->objects.objects_val[i]);
        free(dr->objects.objects_val);
        free(dr);
}


/* Return an empty db_result structure with its status field set to 's'. */
db_result*
empty_result(db_status s)
{
        db_result * res = new db_result;
        if (res != NULL)  {
                res->status = s;
                res->nextinfo.db_next_desc_len = 0;
                res->nextinfo.db_next_desc_val = NULL;
                res->objects.objects_len = 0;
                res->objects.objects_val = NULL;
        } else {
                WARNING("nis_db::empty_result: cannot allocate space");
        }
        return (res);
}

static db_result*
set_result(db_result* res, db_status s)
{
        if (res != NULL)  {
                res->status = s;
        }
        return (res);
}

/*
 * Given a FQ object name for a table or directory, return the (db *)
 * corresponding to the object.
 */
db *
tableDB(char *tableName) {
        db_table_desc   *tbl = 0;
        char            *intName;
        db              *dbase;

        intName = internalTableName(tableName);
        if (intName == 0)
                return (0);

        dbase = InUseDictionary->find_table(intName, &tbl);

        sfree(intName);

        return (dbase);
}

extern "C" {

bool_t
db_in_dict_file(char *name)
{
        return (InUseDictionary->find_table_desc(name) != NULL);

}

const char
*db_perror(db_status dbstat)
{
        const char *str = NULL;

        switch (dbstat) {
                case DB_SUCCESS:
                        str = "Success";
                        break;
                case DB_NOTFOUND:
                        str = "Not Found";
                        break;
                case DB_BADTABLE:
                        str = "Bad Table";
                        break;
                case DB_BADQUERY:
                        str = "Bad Query";
                        break;
                case DB_BADOBJECT:
                        str = "Bad Object";
                        break;
                case DB_MEMORY_LIMIT:
                        str = "Memory limit exceeded";
                        break;
                case DB_STORAGE_LIMIT:
                        str = "Database storage limit exceeded";
                        break;
                case DB_INTERNAL_ERROR:
                        str = "Database internal error";
                        break;
                case DB_SYNC_FAILED:
                        str = "Sync of log file failed";
                        break;
                default:
                        str = "Unknown Error";
                        break;
        }
        return (str);
}

bool_t
db_extract_dict_entries(char *newdict, char **fs, int fscnt)
{
        /*
         * Use the "FreeDictionary" ptr for the backup
         * dictionary.
         */
        if (!FreeDictionary->inittemp(newdict, *InUseDictionary))
                return (FALSE);
        return (InUseDictionary->extract_entries (*FreeDictionary,
                fs, fscnt));
}

bool_t
db_copy_file(char *infile, char *outfile)
{
        return (InUseDictionary->copyfile(infile, outfile));

}


/*
 * The tok and repl parameters will allow us to merge two dictionaries
 * that reference tables from different domains (master/replica in live
 * in different domains). If set to NULL, then the dictionary merge is
 * done as normal (no name changing).
 */
db_status
db_begin_merge_dict(char *newdict, char *tok, char *repl)
{
        db_status dbstat;

        /*
         * It is assumed that InUseDictionary has already been initialized.
         */
        dbstat = InUseDictionary->checkpoint();
        if (dbstat != DB_SUCCESS)
                return (dbstat);

        /*
         * Use the "FreeDictionary" ptr for the backup
         * dictionary.
         */
        if (!FreeDictionary->init(newdict))
                return (DB_INTERNAL_ERROR);

        return (InUseDictionary->merge_dict(*FreeDictionary,
                tok, repl));
}


db_status
db_end_merge_dict()
{
        db_status       dbstat;

        dbstat = InUseDictionary->checkpoint();
        if (dbstat != DB_SUCCESS) {
                return (dbstat);
        }
        dbstat = InUseDictionary->db_shutdown();
        if (dbstat != DB_SUCCESS) {
                return (dbstat);
        }
        dbstat = FreeDictionary->db_shutdown();
        if (dbstat != DB_SUCCESS) {
                return (dbstat);
        }
        return (dbstat);
}



db_status
db_abort_merge_dict()
{
        db_status       dbstat;

        dbstat = InUseDictionary->db_shutdown();
        if (dbstat != DB_SUCCESS)
                return (dbstat);
        dbstat = FreeDictionary->db_shutdown();
        return (dbstat);
}


/*
 * Initialize system (dictionary) using file 'filename'.  If system cannot
 * be read from file, it is initialized to be empty. Returns TRUE if
 * initialization succeeds, FALSE otherwise.
 * This function must be called before any other.
*/
bool_t
db_initialize(char * filename)
{
        return (InUseDictionary->init(filename));
}


/*
 * Massage the dictionary file by replacing the specified token with the
 * the replacement string. This function is needed to provide backwards
 * compatibility for providing a transportable dictionary file. The idea
 * is that rpc.nisd will call this function when it wants to change the
 * /var/nis/<hostname> strings with something like /var/nis/data.
 *
 */
db_status
db_massage_dict(char *newdictname, char *tok, char *repl)
{
        return (InUseDictionary->massage_dict(newdictname, tok, repl));
}



/*
 * Create new table using given table name and table descriptor.
 * Returns DB_SUCCESS if successful; appropriate error code otherwise.
*/
db_status
db_create_table(char * table_name, table_obj * table_desc)
{
        return (InUseDictionary->add_table(table_name, table_desc));
}

/*
 * Destroys table named by 'table_name.'  Returns DB_SUCCESS if successful,
 * error code otherwise.  Note that currently, the removed table is no
 * longer accessible from this interface and all files associated with it
 * are removed from stable storage.
*/
db_status
db_destroy_table(char * table_name)
{
        return (InUseDictionary->delete_table(table_name));
}


/*
* Return a copy of the first entry in the specified table, that satisfies
* the given attributes.  The returned structure 'db_result' contains the status,
* the  copy of the object, and a 'db_next_desc' to be used for the 'next'
* operation.
 */
db_result *
db_first_entry(char * table_name, int numattrs, nis_attr * attrname)
{
        db_result * safety = empty_result(DB_SUCCESS);
        db_table_desc * tbl = NULL;
        db * dbase = InUseDictionary->find_table(table_name, &tbl);

        if (tbl == NULL || dbase == NULL)
                return (set_result(safety, DB_BADTABLE));
        else {
                db_result * res = NULL;
                db_query *query = NULL;

                if (numattrs != 0) {
                        query = InUseDictionary->translate_to_query(tbl,
                                        numattrs, attrname);
                        if (query == NULL)
                                return (set_result(safety,
                                                DB_BADQUERY));
                }
                res = dbase->execute(DB_FIRST, query, NULL, NULL);
                if (query) delete query;
                if (safety) delete safety;
                return (res);
        }
}

/*
 * Return a copy of the next entry in the specified table as specified by
 * the 'next_desc'.  The returned structure 'db_result' contains the status,
 * a copy of the object, and a db_next_desc to be used for a subsequent
 * 'next' operation.
*/
db_result *
db_next_entry(char * table_name, db_next_desc * next_desc)
{
        db_result * safety = empty_result(DB_SUCCESS);
        db * dbase = InUseDictionary->find_table(table_name);

        if (dbase != NULL) {
                if (safety) delete safety;
                return (dbase->execute(DB_NEXT, NULL, NULL, next_desc));
        } else
                return (set_result(safety, DB_BADTABLE));
}

/*
 * Indicate to the system that you are no longer interested in the rest of the
 * results identified by [next_desc].  After executing this operation, the
 * [next_desc] is no longer valid (cannot  be used as an argument for next).
*/

db_result *
db_reset_next_entry(char * table_name, db_next_desc * next_desc)
{
        db_result * safety = empty_result(DB_SUCCESS);
        db * dbase = InUseDictionary->find_table(table_name);

        if (dbase != NULL) {
                if (safety) delete safety;
                return (dbase->execute(DB_RESET_NEXT,
                                        NULL, NULL, next_desc));
        } else
                return (set_result(safety, DB_BADTABLE));
}

/*
 * Returns copies of entries that satisfy the given attributes from table.
 * Returns the status and entries in a db_result structure.
 * If no attributes are specified, DB_BADQUERY is returned.
*/
db_result *
__db_list_entries(char * table_name, int numattrs, nis_attr * attrname,
                        bool_t useDeferred)
{
        db_result * safety = empty_result(DB_SUCCESS);
        db_table_desc * tbl = NULL;
        db * dbase = InUseDictionary->find_table(table_name, &tbl,
                                                        useDeferred);

        if (tbl == NULL || dbase == NULL)
                return (set_result(safety, DB_BADTABLE));
        else {
                db_result * res = NULL;
                if (numattrs != 0) {
                        db_query *query;
                        query = InUseDictionary->translate_to_query(tbl,
                                                    numattrs, attrname);
                        if (query == NULL)
                                return (set_result(safety,
                                                        DB_BADQUERY));
                        res = dbase->execute(DB_LOOKUP, query,
                                                        NULL, NULL);
                        delete query;
                } else {
                        res = dbase->execute(DB_ALL, NULL, NULL, NULL);
                }
                if (safety) delete safety;
                return (res);
        }
}

db_result *
db_list_entries(char *table_name, int numattrs, nis_attr *attrname) {
        return (__db_list_entries(table_name, numattrs, attrname, TRUE));
}

/*
 * Input:       A fully qualified object name (example: "x.y.z").
 * Output:      Returns the first level of the object name ("x").
 *              If 'tableP' is non-NULL, '*tableP' will contain
 *              the internal table name for "y.z".
 *
 * Both the return value and '*tableP' must be freed by the caller.
 */
char *
entryName(const char *msg, char *objName, char **tableP) {
        char            *name, *table, *dir;
        const char      *myself = "entryName";

        if (msg == 0)
                msg = myself;

        name = sdup(msg, T, objName);
        if (name == 0)
                return (0);

        dir = strchr(name, '.');
        if (dir == 0) {
                sfree(name);
                return (0);
        }
        *(dir++) = '\0';

        if (tableP == 0)
                return (name);

        table = internalTableName(dir);
        if (table == 0) {
                sfree(name);
                return (0);
        }

        *tableP = table;

        return (name);
}

#define RETSTAT(obj, status) \
        { \
                if (statP != 0) \
                        *statP = status; \
                return (obj); \
        }

/*
 * Given a fully qualified object name, retrive a copy of the object,
 * using the NIS+ DB only (i.e., no LDAP). Avoids using nis_leaf_of()
 * etc., since they aren't re-entrant.
 */
nis_object *
dbFindObject(char *objName, db_status *statP) {
        char            buf[MAXPATHLEN+NIS_MAXNAMELEN+1];
        char            *name, *table = 0;
        nis_attr        attr;
        db              *dbase;
        db_result       *res;
        db_table_desc   *tbl = 0;
        db_query        *query;
        db_mindex       *mindex;
        nis_object      *o;
        int             lstat;
        const char      *myself = "dbFindObject";

        if (objName == 0)
                RETSTAT(0, DB_BADQUERY);

        /* The root dir is treated specially */
        table = internalTableName(objName);
        if (table == 0)
                RETSTAT(0, DB_BADQUERY);
        if (strcmp(ROOTDIRFILE, table) == 0) {
                sfree(table);

                o = get_root_object();
                if (o == 0)
                        RETSTAT(0, DB_NOTFOUND);

                RETSTAT(o, DB_SUCCESS);
        }

        /* If not the root dir, find the directory where the entry lives */

        sfree(table);
        name = entryName(myself, objName, &table);
        if (name == 0 || table == 0) {
                sfree(name);
                RETSTAT(0, DB_MEMORY_LIMIT);
        }

        dbase = InUseDictionary->find_table_noLDAP(table, &tbl, TRUE, TRUE);
        sfree(table);
        if (dbase != 0)
                mindex = dbase->mindex();
        if (dbase == 0 || tbl == 0 || mindex == 0) {
                sfree(name);
                RETSTAT(0, DB_BADTABLE);
        }

        WRITELOCKNR(mindex, lstat, "mindex w dbFindObject");
        if (lstat != 0) {
                sfree(name);
                RETSTAT(0, DB_LOCK_ERROR);
        }

        attr.zattr_ndx = (char *)"name";
        attr.zattr_val.zattr_val_val = name;
        attr.zattr_val.zattr_val_len = slen(name) + 1;

        query = InUseDictionary->translate_to_query(tbl, 1, &attr);
        if (query == 0) {
                sfree(name);
                WRITEUNLOCKNR(mindex, lstat, "mindex wu dbFindObject");
                RETSTAT(0, DB_BADQUERY);
        }

        /* Only want to look in the local DB */
        mindex->setNoLDAPquery();

        res = dbase->execute(DB_LOOKUP, query, 0, 0);

        mindex->clearNoLDAPquery();

        delete query;

        sfree(name);

        WRITEUNLOCKNR(mindex, lstat, "mindex wu dbFindObject");
        if (lstat != 0) {
                db_free_result(res);
                RETSTAT(0, DB_LOCK_ERROR);
        }

        if (res == 0)
                RETSTAT(0, DB_MEMORY_LIMIT);

        if (res->status != DB_SUCCESS) {
                db_status       st = res->status;

                db_free_result(res);
                RETSTAT(0, st);
        }

        if (res->objects.objects_len != 1 || res->objects.objects_val == 0 ||
                        res->objects.objects_val[0] == 0) {
                db_free_result(res);
                RETSTAT(0, DB_BADOBJECT);
        }

        o = unmakePseudoEntryObj(res->objects.objects_val[0], 0);

        db_free_result(res);

        if (o == 0) {
                RETSTAT(0, DB_BADOBJECT);
        }

        RETSTAT(o, DB_SUCCESS);
}

/*
 * Return the object specified by 't' or 'objName' from LDAP. Set
 * the LDAP status in '*statP'.
 */
nis_object *
ldapFindObj(__nis_table_mapping_t *t, char *objName, int *statP) {
        nis_object      *o;
        int             stat;
        const char      *myself = "ldapFindObj";

        if (t == 0) {
                char    *table, tbuf[MAXPATHLEN + NIS_MAXNAMELEN + 1];

                if (objName == 0) {
                        if (statP != 0)
                                *statP = LDAP_PARAM_ERROR;
                        return (0);
                }

                /* Look for mapping */
                table = internal_table_name(objName, tbuf);
                if (table == 0) {
                        if (statP != 0)
                                *statP = LDAP_PARAM_ERROR;
                        return (0);
                }

                t = (__nis_table_mapping_t *)__nis_find_item_mt(table,
                                                &ldapMappingList, 0, 0);
                if (t == 0) {
                        /* Not really an error; just not mapped */
                        *statP = LDAP_SUCCESS;
                        return (0);
                }
        }

        o = 0;
        stat = objFromLDAP(t, &o, 0, 0);

        if (statP != 0)
                *statP = stat;

        return (o);
}

/*
 * Look for the specified object, first locally, then in LDAP.
 */
nis_object *
findObj(char *name, db_status *statP, int *lstatP) {
        nis_object      *o;
        db_status       stat = DB_SUCCESS;
        int             lstat = LDAP_SUCCESS;
        const char      *myself = "findObj";

        o = dbFindObject(name, &stat);

        if (o == 0) {
                if (stat != DB_NOTFOUND)
                        logmsg(MSG_NOTIMECHECK, LOG_INFO,
                                "%s: DB error %d looking for \"%s\"",
                                myself, stat, NIL(name));

                o = ldapFindObj(0, name, &lstat);
                if (o == 0) {
                        if (lstat != LDAP_SUCCESS &&
                                        lstat != LDAP_NO_SUCH_OBJECT)
                                logmsg(MSG_NOTIMECHECK, LOG_INFO,
                                "%s: LDAP error looking for \"%s\": %s",
                                        myself, NIL(name),
                                        ldap_err2string(lstat));
                }
        }

        if (statP != 0)
                *statP = stat;
        if (lstatP != 0)
                *lstatP = lstat;

        return (o);
}

/*
 * Delete the specified object from the local DB.
 */
db_status
dbDeleteObj(char *objName) {
        nisdb_tsd_t     *tsd = __nisdb_get_tsd();
        nis_object      *o;
        db_status       stat;
        nisdb_obj_del_t *nod, *tmp;
        int             xid;
        const char      *myself = "dbDeleteObj";

        if (objName == 0)
                return (DB_SUCCESS);

        /*
         * Since in-structure locks can't completely protect
         * during structure deletion, we just note that the
         * object should be deleted, and leave that for a
         * (slightly) later time in rpc.nisd, where we can
         * use the rpc.nisd's table/directory locks for
         * protection.
         */

        if (tsd == 0)
                return (DB_INTERNAL_ERROR);

        o = dbFindObject(objName, &stat);
        if (o == 0) {
                if (stat == DB_NOTFOUND)
                        return (DB_SUCCESS);
                else
                        return (stat);
        }

        /*
         * In order to prevent a chicken-and-egg problem (if the
         * object doesn't exist in LDAP, is that because we just
         * haven't written it to LDAP yet, or because it's been
         * removed), we only allow object deletion if we're the
         * master for it.
         */

        nod = (nisdb_obj_del_t *)am(myself, sizeof (*nod));
        if (nod == 0) {
                nis_destroy_object(o);
                return (DB_MEMORY_LIMIT);
        }

        nod->objType = o->zo_data.zo_type;
        nis_destroy_object(o);

        nod->objName = sdup(myself, T, objName);
        if (nod->objName == 0) {
                sfree(nod);
                return (DB_MEMORY_LIMIT);
        }

        /* Check for a dup */
        for (tmp = tsd->objDelList; tmp != 0;
                        tmp = (nisdb_obj_del_t *)tmp->next) {
                if (strcmp(nod->objName, tmp->objName) == 0) {
                        sfree(nod->objName);
                        sfree(nod);
                        return (DB_SUCCESS);
                }
        }

        /* Insert at start of list */
        nod->next = tsd->objDelList;
        tsd->objDelList = nod;

        return (DB_SUCCESS);
}

/*
 * Touch (i.e., update the expiration time for) the specified object.
 */
db_status
dbTouchObj(char *objName) {
        char            *ent, *table;
        db              *dbase;
        db_table_desc   *tbl = 0;
        db_mindex       *mindex;
        nis_attr        attr;
        db_query        *query;
        db_status       stat;
        const char      *myself = "dbTouchObj";

        table = internalTableName(objName);
        if (table == 0)
                return (DB_BADQUERY);

        if (strcmp(ROOTDIRFILE, table) == 0) {
                sfree(table);

                if (touchRootDir() == 0)
                        return (DB_SUCCESS);
                else
                        return (DB_INTERNAL_ERROR);
        }

        sfree(table);
        table = 0;
        ent = entryName(myself, objName, &table);
        if (ent == 0 || table == 0) {
                sfree(ent);
                return (DB_MEMORY_LIMIT);
        }

        dbase = InUseDictionary->find_table(table, &tbl, TRUE);
        if (dbase != 0)
                mindex = dbase->mindex();
        if (dbase == 0 || tbl == 0 || mindex == 0) {
                sfree(ent);
                sfree(table);
                return (DB_BADTABLE);
        }

        attr.zattr_ndx = (char *)"name";
        attr.zattr_val.zattr_val_val = ent;
        attr.zattr_val.zattr_val_len = slen(ent) + 1;

        query = InUseDictionary->translate_to_query(tbl, 1, &attr);
        if (query == 0) {
                sfree(ent);
                sfree(table);
                return (DB_BADQUERY);
        }

        mindex->touchEntry(query);

        sfree(ent);
        sfree(table);
        delete query;

        return (DB_SUCCESS);
}

/*
 * Create a NIS_TABLE_OBJ.
 * Borrows heavily from rpc.nisd/nis_db.c:__create_table().
 */
db_status
dbCreateTable(char *intName, nis_object *obj) {
        table_col       tc[NIS_MAXCOLUMNS+1];
        table_obj       tobj, *t;
        int             i;
        const char      *myself = "dbCreateTable";

        if (intName == 0 || obj == 0)
                return (DB_BADTABLE);

        t = &(obj->TA_data);

        /* Make sure there are searchable columns */
        for (i = 0; i < t->ta_cols.ta_cols_len; i++) {
                if (t->ta_cols.ta_cols_val[i].tc_flags & TA_SEARCHABLE)
                        break;
        }
        if (i >= t->ta_cols.ta_cols_len) {
                logmsg(MSG_NOTIMECHECK, LOG_INFO,
                        "%s: No searchable columns in \"%s\" (\"%s\")",
                        myself, NIL(obj->zo_name), NIL(intName));
                return (DB_BADTABLE);
        }

        tobj = *t;
        /* Shift columns one step right */
        for (i = 0; i < tobj.ta_cols.ta_cols_len; i++) {
                tc[i+1] = tobj.ta_cols.ta_cols_val[i];
        }
        tc[0].tc_name = 0;
        tc[0].tc_flags = TA_XDR | TA_BINARY;
        tc[0].tc_rights = 0;
        tobj.ta_cols.ta_cols_len += 1;
        tobj.ta_cols.ta_cols_val = tc;

        return (db_create_table(intName, &tobj));
}

#define TABLE_COL(o, n) o->TA_data.ta_cols.ta_cols_val[n]

/*
 * Refresh (if necessary, create), the specified object in the local DB.
 */
db_status
dbRefreshObj(char *name, nis_object *o) {
        char            *objName;
        __nis_buffer_t  b = {0, 0};
        nis_object      *curObj;
        db_status       stat;
        char            *ent, *table, *objTable;
        int             rstat, isDir = 0, isTable = 0;
        const char      *myself = "refreshObj";

        if (o == 0)
                /* Delete it */
                return (dbDeleteObj(name));

        /* We don't work on entry objects */
        if (o->zo_data.zo_type == NIS_ENTRY_OBJ)
                return (DB_BADOBJECT);

        if (name != 0)
                objName = name;
        else {
                bp2buf(myself, &b, "%s.%s", NIL(o->zo_name), NIL(o->zo_domain));
                objName = b.buf;
        }

        curObj = dbFindObject(objName, &stat);
        if (curObj == 0 && stat != DB_NOTFOUND) {
                sfree(b.buf);
                return (stat);
        }

        /*
         * If the object doesn't change, just touch it to update the
         * expiration time.
         */
        if (curObj != 0) {
                if (sameNisPlusObj(o, curObj)) {
                        sfree(b.buf);
                        nis_destroy_object(curObj);
                        return (dbTouchObj(objName));
                }

                /* Otherwise, check that the name and type is the same */
                if (o->zo_data.zo_type != curObj->zo_data.zo_type ||
                        o->zo_name == 0 || curObj->zo_name == 0 ||
                        o->zo_domain == 0 || curObj->zo_domain == 0 ||
                        strcmp(o->zo_name, curObj->zo_name) != 0 ||
                        strcmp(o->zo_domain, curObj->zo_domain) != 0) {
                        sfree(b.buf);
                        nis_destroy_object(curObj);
                        return (DB_BADOBJECT);
                }

                /*
                 * If the object is a table, we can't allow the scheme
                 * to change.
                 */
                if (o->zo_data.zo_type == NIS_TABLE_OBJ) {
                        int     i;

                        if (o->TA_data.ta_maxcol !=
                                        curObj->TA_data.ta_maxcol) {
                                sfree(b.buf);
                                nis_destroy_object(curObj);
                                return (DB_BADOBJECT);
                        }

                        for (i = 0; i < o->TA_data.ta_maxcol; i++) {
                                if ((TABLE_COL(o, i).tc_flags &
                                                TA_SEARCHABLE) !=
                                        (TABLE_COL(curObj, i).tc_flags &
                                                TA_SEARCHABLE)) {
                                        sfree(b.buf);
                                        nis_destroy_object(curObj);
                                        return (DB_BADOBJECT);
                                }
                        }
                }
        } else {
                /*
                 * If we're creating a directory object, make a note
                 * so that we can add it to the serving list and create
                 * the disk file. Similarly, if creating a table, we
                 * also need to create the disk file.
                 */
                if (o->zo_data.zo_type == NIS_DIRECTORY_OBJ)
                        isDir = 1;
                else if (o->zo_data.zo_type == NIS_TABLE_OBJ)
                        isTable = 1;
        }

        objTable = internalTableName(objName);
        if (objTable == 0) {
                sfree(b.buf);
                if (curObj != 0)
                        nis_destroy_object(curObj);
                return (DB_BADQUERY);
        }

        if (strcmp(ROOTDIRFILE, objTable) == 0) {
                sfree(objTable);

                rstat = update_root_object((nis_name)ROOTOBJFILE, o);
                if (rstat == 1)
                        stat = DB_SUCCESS;
                else
                        stat = DB_INTERNAL_ERROR;
        } else {
                nis_attr        attr;
                entry_object    *e, eo;
                entry_col       ec[2];
                db              *dbase;
                db_table_desc   *tbl = 0;
                db_mindex       *mindex;
                db_result       *dbres;
                int             lstat;

                /* Find parent */
                ent = entryName(myself, objName, &table);
                if (ent == 0 || table == 0) {
                        sfree(b.buf);
                        sfree(objTable);
                        sfree(ent);
                        if (curObj != 0)
                                nis_destroy_object(curObj);
                        return (DB_MEMORY_LIMIT);
                }

                /*
                 * Calling vanilla find_table() here (which might go to
                 * LDAP and recurse back to ourselves) so that it should
                 * work to create a hierarchy of directories.
                 */
                dbase = InUseDictionary->find_table(table, &tbl, TRUE);
                if (dbase != 0)
                        mindex = dbase->mindex();
                if (dbase == 0 || tbl == 0 || mindex == 0) {
                        sfree(b.buf);
                        sfree(objTable);
                        sfree(ent);
                        sfree(table);
                        if (curObj != 0)
                                nis_destroy_object(curObj);
                        return (DB_BADTABLE);
                }

                /* Construct suitable nis_attr and entry_object */
                attr.zattr_ndx = (char *)"name";
                attr.zattr_val.zattr_val_val = ent;
                attr.zattr_val.zattr_val_len = slen(ent) + 1;

                ec[1].ec_flags = 0;
                ec[1].ec_value.ec_value_val = ent;
                ec[1].ec_value.ec_value_len = attr.zattr_val.zattr_val_len;

                eo.en_type = (char *)"IN_DIRECTORY";
                eo.en_cols.en_cols_val = ec;
                eo.en_cols.en_cols_len = 2;

                e = makePseudoEntryObj(o, &eo, 0);
                if (e == 0) {
                        sfree(objTable);
                        sfree(table);
                        sfree(ent);
                        if (curObj != 0)
                                nis_destroy_object(curObj);
                        return (DB_INTERNAL_ERROR);
                }

                /* Only want to update the local DB */

                WRITELOCKNR(mindex, lstat, "mindex w dbRefreshObj");
                if (lstat != 0) {
                        sfree(objTable);
                        sfree(table);
                        sfree(ent);
                        if (curObj != 0)
                                nis_destroy_object(curObj);
                        return (DB_LOCK_ERROR);
                }
                mindex->setNoWriteThrough();
                mindex->setNoLDAPquery();

                dbres = db_add_entry_x(table, 1, &attr, e, 0, 0);

                mindex->clearNoLDAPquery();
                mindex->clearNoWriteThrough();
                WRITEUNLOCKNR(mindex, lstat, "mindex wu dbRefreshObj");
                if (lstat != 0) {
                        sfree(objTable);
                        sfree(table);
                        sfree(ent);
                        if (curObj != 0)
                                nis_destroy_object(curObj);
                        db_free_result(dbres);
                        return (DB_LOCK_ERROR);
                }

                sfree(ent);
                sfree(table);

                if (dbres == 0)
                        stat = DB_MEMORY_LIMIT;
                else
                        stat = dbres->status;

                db_free_result(dbres);

                /*
                 * If successful so far, add the transaction.
                 */
                if (stat == DB_SUCCESS) {
                        int             xid, st;
                        db_status       ds;
                        nis_object      *dirObj;

                        /* Find the directory where this is added */
                        dirObj = dbFindObject(o->zo_domain, &ds);
                        if (dirObj == 0) {
                                sfree(objTable);
                                if (curObj != 0)
                                        nis_destroy_object(curObj);
                                return (ds);
                        }

                        xid = beginTransaction();
                        if (xid == 0) {
                                sfree(objTable);
                                if (curObj != 0)
                                        nis_destroy_object(curObj);
                                nis_destroy_object(dirObj);
                                return (DB_INTERNAL_ERROR);
                        }

                        st = addUpdate((curObj == 0) ? ADD_NAME : MOD_NAME_NEW,
                                        objName, 0, 0, o, curObj, 0);
                        if (st != 0) {
                                (void) abort_transaction(xid);
                                sfree(objTable);
                                if (curObj != 0)
                                        nis_destroy_object(curObj);
                                nis_destroy_object(dirObj);
                                return (DB_INTERNAL_ERROR);
                        }

                        st = endTransaction(xid, dirObj);
                        if (st != 0)
                                stat = DB_INTERNAL_ERROR;

                        if (curObj != 0)
                                nis_destroy_object(curObj);
                        nis_destroy_object(dirObj);
                }

                /*
                 * If it's a table or directory, create the DB file.
                 * If a directory, also add it to the serving list.
                 */
                if (stat == DB_SUCCESS &&(isDir || isTable)) {
                        if (isDir) {
                                stat = db_create_table(objTable,
                                                        &tbl_prototype);
                        } else {
                                stat = dbCreateTable(objTable, o);
                        }
                }
                sfree(objTable);
        }

        sfree(b.buf);

        return (stat);
}

/*
 * Replace the object stored with the mapping 't'. Return TRUE if
 * at least one object was replaced, FALSE otherwise.
 */
bool_t
replaceMappingObj(__nis_table_mapping_t *t, nis_object *n) {
        __nis_table_mapping_t   *x;
        nis_object              *old = 0;
        int                     assigned = 0;

        /*
         * The alternate mappings are usually mostly copies
         * of the original, so we try to make sure that we
         * don't free the same nis_object twice.
         */
        for (x = t; x != 0; x = (__nis_table_mapping_t *)x->next) {
                if (old == 0) {
                        old = x->obj;
                        if (x->obj != 0)
                                nis_destroy_object(x->obj);
                } else {
                        if (x->obj != old && x->obj != 0)
                                nis_destroy_object(x->obj);
                }
                x->obj = n;
                assigned++;
        }

        return (assigned > 0);
}

/*
 * Set object type, column info, and obj for the specified
 * mapping 't' from the object 'o'. Returns zero if 'o' was unused,
 * and should be freed by the caller, larger than zero otherwise.
 */
int
setMappingObjTypeEtc(__nis_table_mapping_t *t, nis_object *o) {
        __nis_table_mapping_t   *x;
        int                     ls, ret;
        int                     i;

        if (t == 0 || o == 0)
                return (0);

        t->objType = o->zo_data.zo_type;
        for (x = t; x != 0; x = (__nis_table_mapping_t *)x->next) {
                if (x != t) {
                        x->objType = t->objType;
                }
                if (x->objType == NIS_TABLE_OBJ) {
                        /*
                         * If we have rules, this mapping is for table entries,
                         * and we need the column names. Otherwise, remove the
                         * column names (if any).
                         */

                        for (i = 0; i < x->numColumns; i++)
                        sfree(x->column[i]);
                        sfree(x->column);
                        x->column = 0;
                        x->numColumns = 0;
                }
        }
        ret = replaceMappingObj(t, o);

        return (ret);
}

/*
 * Retrieve the specified object (internal DB name) from LDAP, and
 * refresh/create as appropriate.
 */
db_status
dbCreateFromLDAP(char *intName, int *ldapStat) {
        __nis_table_mapping_t   *t;
        int                     lstat, doDestroy;
        nis_object              *obj = 0;
        db_status               dstat;
        const char              *myself = "dbCreateFromLDAP";

        if (!useLDAPrespository) {
                if (ldapStat != 0)
                        *ldapStat = LDAP_SUCCESS;
                return (DB_SUCCESS);
        }

        t = (__nis_table_mapping_t *)__nis_find_item_mt(intName,
                                                        &ldapMappingList,
                                                        0, 0);

        /* No mapping isn't a failure */
        if (t == 0) {
                if (ldapStat != 0)
                        *ldapStat = LDAP_SUCCESS;
                return (DB_NOTFOUND);
        }

        lstat = objFromLDAP(t, &obj, 0, 0);
        if (ldapStat != 0)
                *ldapStat = lstat;
        if (lstat != LDAP_SUCCESS)
                return (DB_NOTFOUND);

        /*
         * If the LDAP operation was successful, but 'obj' is NULL,
         * there's no mapping for this object, and we're done.
         */
        if (obj == 0)
                return (DB_SUCCESS);

        /* Update the mapping with object info */
        doDestroy = setMappingObjTypeEtc(t, obj) == 0;

        dstat = dbRefreshObj(t->objName, obj);

        if (doDestroy)
                nis_destroy_object(obj);

        return (dstat);
}

/*
 * Up- (fromLDAP==0) or down- (fromLDAP==1) load all LDAP mapped data.
 * Returns an LDAP error status.
 */
int
loadAllLDAP(int fromLDAP, void *cookie, db_status *dstatP) {
        __nis_table_mapping_t   *t, *start;
        int                     stat = LDAP_SUCCESS;
        db_status               dstat = DB_SUCCESS;
        db                      *dbase;
        db_table_desc           *tbl = 0;
        db_mindex               *mindex;
        const char              *myself = "loadAllLDAP";

        /*
         * If the 'cookie' and '*cookie' are non-NULL, start scanning
         * the mappings from '*cookie'. When we return with an error,
         * we set '*cookie' to point to the mapping being processed.
         * This enables our caller to react appropriately, and retry
         * if desired.
         *
         * The cookie is opaque to our caller, who's only allowed to
         * initialize *cookie to NULL.
         */
        if (cookie != 0) {
                start = *((__nis_table_mapping_t **)cookie);
                if (start == 0)
                        start = ldapMappingSeq;
        } else {
                start = ldapMappingSeq;
        }

        for (t = start; t != 0; t = (__nis_table_mapping_t *)t->seqNext) {
                __nis_table_mapping_t   **tp;
                int                     nm;

                if (fromLDAP) {
                        /* Are there any mappings for the object proper ? */
                        tp = selectTableMapping(t, 0, 0, 1, t->dbId, &nm);
                        if (tp != 0 && nm > 0) {
                                dstat = dbCreateFromLDAP(t->objPath, &stat);
                                if (dstat != DB_SUCCESS) {
                                        logmsg(MSG_NOTIMECHECK, LOG_ERR,
                                "%s: DB error %d creating \"%s\": %s",
                                                myself, dstat, NIL(t->objName),
                                                ldap_err2string(stat));
                                        if (cookie != 0)
                                                *((__nis_table_mapping_t **)
                                                        cookie) = t;
                                        if (dstatP != 0)
                                                *dstatP = dstat;
                                        else if (stat == LDAP_SUCCESS)
                                                stat = LDAP_OPERATIONS_ERROR;
                                        sfree(tp);
                                        return (stat);
                                }
                        }
                        sfree(tp);

                        /* Any mappings for table entries ? */
                        tp = selectTableMapping(t, 0, 0, 0, t->dbId, &nm);
                        if (tp == 0 || nm <= 0) {
                                sfree(tp);
                                continue;
                        }
                        sfree(tp);

                        /*
                         * The object itself must exist in the local
                         * DB by now. Get the db_mindex and let
                         * db_mindex::queryLDAP() do the work; if
                         * the object isn't a table, queryLDAP()
                         * will do nothing and return success.
                         */
                        dbase = InUseDictionary->find_table(t->objPath,
                                                        &tbl, TRUE);
                        if (dbase != 0)
                                mindex = dbase->mindex();
                        if (dbase == 0 || tbl == 0 || mindex == 0) {
                                logmsg(MSG_NOTIMECHECK, LOG_ERR,
                                "%s: No local DB entry for \"%s\" (%s:%s)",
                                        myself, NIL(t->objPath),
                                        NIL(t->dbId), NIL(t->objName));
                                if (cookie != 0)
                                        *((__nis_table_mapping_t **)cookie) =
                                                t;
                                if (dstatP != 0)
                                        *dstatP = DB_BADTABLE;
                                return ((dstatP != 0) ?
                                        LDAP_SUCCESS : LDAP_OPERATIONS_ERROR);
                        }
                        mindex->setInitialLoad();
                        stat = mindex->queryLDAP(0, t->dbId, 0);
                        mindex->clearInitialLoad();
                        if (stat != LDAP_SUCCESS) {
                                logmsg(MSG_NOTIMECHECK, LOG_ERR,
                        "%s: LDAP error retrieving entries for %s:%s: %s",
                                        myself, NIL(t->dbId), NIL(t->objName),
                                        ldap_err2string(stat));
                                if (cookie != 0)
                                        *((__nis_table_mapping_t **)cookie) =
                                                t;
                                if (dstatP != 0)
                                        *dstatP = DB_SUCCESS;
                                return (stat);
                        }
                } else {
                        nis_object      *obj;
                        char            *ent, *objPath;
                        int             freeObjPath = 0;

                        /*
                         * Up-loading to LDAP, so the object must
                         * already exist in the local DB.
                         */
                        obj = dbFindObject(t->objName, &dstat);
                        if (obj == 0) {
                                if (dstat == DB_NOTFOUND)
                                        logmsg(MSG_NOTIMECHECK, LOG_WARNING,
                "%s: No local DB object for \"%s\" (%s:%s); skipping up-load",
                                                myself, NIL(t->objPath),
                                                NIL(t->dbId),
                                                NIL(t->objName));
                                else
                                        logmsg(MSG_NOTIMECHECK, LOG_WARNING,
                        "%s: DB error %d for \"%s\" (%s:%s); skipping up-load",
                                                myself, dstat,
                                                NIL(t->objPath),
                                                NIL(t->dbId),
                                                NIL(t->objName));
                                continue;
                        }

                        /*
                         * If it's a table or directory, there will be
                         * a dictionary entry for the object itself.
                         * Otherwise, we need the dictionary entry for
                         * the parent directory.
                         *
                         * For a table, we need the db_mindex for both the
                         * table object itself, as well as for the parent
                         * directory (in order to store table entries).
                         * We start with the latter.
                         */
                        if (obj->zo_data.zo_type == NIS_DIRECTORY_OBJ) {
                                objPath = t->objPath;
                                ent = 0;
                        } else {
                                objPath = 0;
                                ent = entryName(myself, t->objName,
                                    &objPath);
                                if (ent == 0 || objPath == 0) {
                                        logmsg(MSG_NOTIMECHECK, LOG_ERR,
        "%s: Error deriving entry/DB-table names for %s:%s; skipping up-load",
                                                myself, NIL(t->dbId),
                                                NIL(t->objName));
                                        sfree(ent);
                                        sfree(objPath);
                                        nis_destroy_object(obj);
                                        obj = 0;
                                        continue;
                                }
                                freeObjPath = 1;
                        }

                        dbase = InUseDictionary->find_table(objPath,
                                                        &tbl, TRUE);
                        if (dbase != 0)
                                mindex = dbase->mindex();
                        if (dbase == 0 || tbl == 0 || mindex == 0) {
                                logmsg(MSG_NOTIMECHECK, LOG_WARNING,
                "%s: No local DB entry for \"%s\" (%s:%s); skipping up-load",
                                        myself, objPath,
                                        NIL(t->dbId), NIL(t->objName));
                                sfree(ent);
                                if (freeObjPath)
                                        sfree(objPath);
                                nis_destroy_object(obj);
                                obj = 0;
                                continue;
                        }

                        /*
                         * Our next action(s) depend on the object type:
                         *
                         *      directory       Store dir object
                         *
                         *      table           Store table obj, as well
                         *                      as any entries in the
                         *                      table
                         *
                         *      other           Store object; we need to
                         *                      build a db_query specifying
                         *                      the first-level name of the
                         *                      object.
                         *
                         * storeLDAP() will just do nothing and return
                         * success if we try to, say, store a table object
                         * when only the table entries are mapped. Hence,
                         * we don't have to worry about those distinctions
                         * here.
                         */
                        if (obj->zo_data.zo_type == NIS_DIRECTORY_OBJ) {
                                stat = mindex->storeLDAP(0, 0, obj, 0, t->dbId);
                        } else {
                                nis_attr        attr;
                                db_query        *q;

                                attr.zattr_ndx = (char *)"name";
                                attr.zattr_val.zattr_val_val = ent;
                                attr.zattr_val.zattr_val_len = slen(ent) + 1;

                                q = new db_query(mindex->getScheme(), 1, &attr);
                                if (q == 0) {
                                        logmsg(MSG_NOTIMECHECK, LOG_ERR,
        "%s: error creating db_query for \"%s\" in \"%s\"; skipping up-load",
                                                myself, ent, objPath);
                                        sfree(ent);
                                        if (freeObjPath)
                                                sfree(objPath);
                                        nis_destroy_object(obj);
                                        obj = 0;
                                        continue;
                                }

                                stat = mindex->storeLDAP(q, 0, obj, 0, t->dbId);

                                delete q;

                        }

                        sfree(ent);
                        if (freeObjPath)
                                sfree(objPath);

                        if (stat != LDAP_SUCCESS) {
                                logmsg(MSG_NOTIMECHECK, LOG_ERR,
                        "%s: Error storing %s:%s to LDAP: %s",
                                        myself, NIL(t->dbId), NIL(t->objName),
                                        ldap_err2string(stat));
                                nis_destroy_object(obj);
                                obj = 0;
                                if (cookie != 0)
                                        *((__nis_table_mapping_t **)
                                                cookie) = t;
                                if (dstatP != 0)
                                        *dstatP = DB_SUCCESS;
                                return (stat);
                        }

                        /* Any mappings for table entries ? */
                        tp = selectTableMapping(t, 0, 0, 0, t->dbId, &nm);
                        if (tp == 0 || nm <= 0) {
                                sfree(tp);
                                nis_destroy_object(obj);
                                obj = 0;
                                continue;
                        }
                        sfree(tp);

                        /*
                         * If it's a table, we also need to store the table
                         * entries.
                         */
                        if (obj->zo_data.zo_type == NIS_TABLE_OBJ) {
                                tbl = 0;
                                dbase = InUseDictionary->find_table(t->objPath,
                                                                &tbl, TRUE);
                                if (dbase != 0)
                                mindex = dbase->mindex();
                                if (dbase == 0 || tbl == 0 || mindex == 0) {
                                        logmsg(MSG_NOTIMECHECK, LOG_WARNING,
        "%s: No local DB entry for \"%s\" (%s:%s); skipping entry up-load",
                                                myself, NIL(t->objPath),
                                                NIL(t->dbId), NIL(t->objName));
                                        nis_destroy_object(obj);
                                        obj = 0;
                                        continue;
                                }

                                stat = mindex->storeLDAP(0, 0, obj, 0, t->dbId);

                                if (stat != LDAP_SUCCESS) {
                                        logmsg(MSG_NOTIMECHECK, LOG_ERR,
                                "%s: Error storing %s:%s entries to LDAP: %s",
                                                myself, NIL(t->dbId),
                                                NIL(t->objName),
                                                ldap_err2string(stat));
                                        nis_destroy_object(obj);
                                        obj = 0;
                                        if (cookie != 0)
                                                *((__nis_table_mapping_t **)
                                                        cookie) = t;
                                        if (dstatP != 0)
                                                *dstatP = DB_SUCCESS;
                                        return (stat);
                                }
                        }
                        nis_destroy_object(obj);
                        obj = 0;
                }
        }

        if (dstatP != 0)
                *dstatP = dstat;
        return (stat);
}

/*
 * Object identified by given attribute name is added to specified table.
 * If object already exists, it is replaced.  If more than one object
 * matches the given attribute name, DB_NOTUNIQUE is returned.
 */
static
db_result *
db_add_entry_x(char * tab, int numattrs, nis_attr * attrname,
                entry_obj * newobj, int skiplog, int nosync)
{
        db_result * safety = empty_result(DB_SUCCESS);
        db_table_desc * tbl = NULL;
        db * dbase = InUseDictionary->find_table(tab, &tbl, FALSE);

        if (tbl == NULL || dbase == NULL) {
                return (set_result(safety, DB_BADTABLE));
        } else if (skiplog) {
                db_result * res;
                res = dbase->execute(DB_ADD_NOLOG, NULL,
                            (entry_object *) newobj, NULL);
                if (safety) delete safety;
                return (res);
        } else {
                db_result *res;
                db_query *
                query = InUseDictionary->translate_to_query(tbl,
                                                numattrs, attrname);
                if (query == NULL)
                        return (set_result(safety, DB_BADQUERY));
                if (nosync)
                        res = dbase->execute(DB_ADD_NOSYNC,
                                query, (entry_object *) newobj, NULL);
                else
                        res = dbase->execute(DB_ADD, query,
                                (entry_object *) newobj, NULL);
                delete query;
                if (safety) delete safety;
                return (res);
        }
}

db_result *
db_add_entry(char * tab, int numattrs, nis_attr * attrname,
                entry_obj * newobj)
{
        return (db_add_entry_x(tab, numattrs, attrname, newobj, 0, 0));
}

db_result *
__db_add_entry_nolog(char * tab, int numattrs, nis_attr * attrname,
                entry_obj * newobj)
{
        return (db_add_entry_x(tab, numattrs, attrname, newobj, 1, 0));
}

db_result *
__db_add_entry_nosync(char * tab, int numattrs, nis_attr * attrname,
                        entry_obj * newobj)
{
        return (db_add_entry_x(tab, numattrs, attrname, newobj, 0, 1));
}

/*
 * Remove object identified by given attributes from specified table.
 * If no attribute is supplied, all entries in table are removed.
 * If attributes identify more than one object, all objects are removed.
*/

db_result *
db_remove_entry_x(char * table_name, int num_attrs, nis_attr * attrname,
                        int nosync)
{
        db_result * safety = empty_result(DB_SUCCESS);
        db_table_desc * tbl = NULL;
        db * dbase = InUseDictionary->find_table(table_name, &tbl, FALSE);
        db_result * res;

        if (tbl == NULL || dbase == NULL)
                return (set_result(safety, DB_BADTABLE));
        else {
                if (num_attrs != 0) {
                        db_query *query;
                        query = InUseDictionary->translate_to_query(tbl,
                                        num_attrs, attrname);
                        if (query == NULL)
                                return (set_result(safety,
                                                DB_BADQUERY));
                        if (nosync)
                                res = dbase->execute(DB_REMOVE_NOSYNC,
                                                query, NULL, NULL);
                        else
                                res = dbase->execute(DB_REMOVE, query,
                                                NULL, NULL);
                        delete query;
                } else {
                        if (nosync)
                                res = dbase->execute(DB_REMOVE_NOSYNC,
                                        NULL, NULL, NULL);
                        else
                                res = dbase->execute(DB_REMOVE,
                                        NULL, NULL, NULL);
                }
                if (safety) delete safety;
                return (res);
        }
}

db_result *
db_remove_entry(char * table_name, int num_attrs, nis_attr * attrname)
{
        return (db_remove_entry_x(table_name, num_attrs, attrname, 0));
}

db_result *
__db_remove_entry_nosync(char * table_name, int num_attrs, nis_attr * attrname)
{
        return (db_remove_entry_x(table_name, num_attrs, attrname, 1));
}

/* Return a copy of the version of specified table. */
vers *
db_version(char * table_name)
{
        db * dbase = InUseDictionary->find_table(table_name);

        if (dbase == NULL)
                return (NULL);
        vers* v = new vers(dbase->get_version());
        if (v == NULL)
                WARNING("nis_db::db_version: cannot allocate space");
        return (v);
}

/* Return log entries since (later than) given version 'v' of table. */
db_log_list *
db_log_entries_since(char * table_name, vers * v)
{
        db * dbase = InUseDictionary->find_table(table_name);

        if (dbase == NULL)
                return (NULL);
        return (dbase->get_log_entries_since(v));
}

db_status
db_sync_log(char *table_name) {

        db * dbase = InUseDictionary->find_table(table_name);

        if (dbase == NULL)
                return (DB_BADTABLE);
        return (dbase->sync_log());
}

/*
 * Apply the given update specified in 'entry' to the specified table.
 * Returns DB_SUCCESS if update was executed.
 * Returns DB_NOTFOUND if update occurs too early to be applied.
*/
db_status
db_apply_log_entry(char * table_name, db_log_entry * entry)
{
        db * dbase = InUseDictionary->find_table(table_name, NULL, FALSE);

        if (dbase == NULL)
                return (DB_BADTABLE);
        if (dbase->execute_log_entry(entry))
                return (DB_SUCCESS);   /* got executed */
        else
                return (DB_NOTFOUND);  /* not executed */
}

/*
 * Checkpoint specified table (i.e. incorporate logged updates to main
 * database file).  If table_name is NULL, checkpoint all tables that
 * needs it.
*/
db_status
db_checkpoint(char * table_name)
{
        return (InUseDictionary->db_checkpoint(table_name));
}

/* Print names of tables in system. */
void
db_print_table_names()
{
        int i;
        db_table_names * answer = InUseDictionary->get_table_names();

        if (answer != NULL) {
                for (i = 0; i < answer->db_table_names_len; i++) {
                        printf("%s\n", answer->db_table_names_val[i]);
                        delete answer->db_table_names_val[i];
                }
                delete answer->db_table_names_val;
                delete answer;
        }
}

/* Print statistics of specified table to stdout. */
db_status
db_stats(char * table_name)
{
        db_table_desc * tbl = NULL;
        db *dbase = InUseDictionary->find_table(table_name, &tbl);

        if (tbl == NULL || dbase == NULL || tbl->scheme == NULL)
                return (DB_BADTABLE);

        dbase->print();
        tbl->scheme->print();
        return (DB_SUCCESS);
}


/* Print statistics of indices of specified table to stdout. */
db_status
db_print_all_indices(char * table_name)
{
        db * dbase = InUseDictionary->find_table(table_name);

        if (dbase == NULL)
                return (DB_BADTABLE);
        dbase->print_all_indices();
        return (DB_SUCCESS);
}

/* Print specified index of table to stdout. */
db_status
db_print_index(char * table_name, int which)
{
        db * dbase = InUseDictionary->find_table(table_name);

        if (dbase == NULL)
                return (DB_BADTABLE);
        dbase->print_index(which);
        return (DB_SUCCESS);
}

/* close open files */
db_status
db_standby(char * table_name)
{
        return (InUseDictionary->db_standby(table_name));
}

/* Returns DB_SUCCESS if table exists; DB_BADTABLE if table does not exist. */
db_status
db_table_exists(char * table_name)
{
        db_table_desc *dbtab = InUseDictionary->find_table_desc(table_name);

        if (dbtab == NULL)
                return (DB_BADTABLE);
        return (DB_SUCCESS);
}

/*
 * Returns DB_SUCCESS if table exists; DB_BADTABLE if table does not exist.
 *  If table already loaded, unload it.
*/
db_status
db_unload_table(char * table_name)
{
        db_table_desc *
        dbtab = InUseDictionary->find_table_desc(table_name);
        if (dbtab == NULL)
                return (DB_BADTABLE);
        // unload
        if (dbtab->database != NULL) {
                delete dbtab->database;
                dbtab->database = NULL;
        }
        return (DB_SUCCESS);
}

/*
 * Put the specified table in deferred mode, which means that updates go
 * to the original table, but reads are satisfied out of a copy (which we
 * make here). Thus, "defer" refers to the table as seen by read requests,
 * since for them, changes are deferred.
 */
db_status
__db_defer(char *table_name) {
        db_status       stat;

        stat = InUseDictionary->defer(table_name);
        return (stat);
}

/*
 * Commit deferred changes for the specified table. I.e., make visible
 * any updates made since the table was deferred.
 */
db_status
__db_commit(char *table_name) {
        db_status       stat;

        stat = InUseDictionary->commit(table_name);
        return (stat);
}

/*
 * Rollback, i.e., return to the state before we entered deferred mode.
 */
db_status
__db_rollback(char *table_name) {
        db_status       stat;

        stat = InUseDictionary->rollback(table_name);
        return (stat);
}

db_status
__db_configure(char *table_name) {
        db_status       stat;
        char            tablePath[MAXPATHLEN + NIS_MAXNAMELEN + 1];
        db              *dbase = InUseDictionary->find_table(table_name, NULL);

        if (dbase == NULL || table_name == 0)
                return (DB_BADTABLE);

        if (strlen(table_name) >= sizeof (tablePath))
                return (DB_BADQUERY);

        if (internal_table_name(table_name, tablePath) == 0)
                return (DB_STORAGE_LIMIT);

        if (dbase->configure(tablePath))
                stat = DB_SUCCESS;
        else
                stat = DB_INTERNAL_ERROR;

        return (stat);
}

/*
 * During some rpc.nisd operations (such as when recovering the trans.log),
 * we don't want to use the LDAP repository, so we provide a main switch.
 * Note that we expect this to be used only when rpc.nisd is single-threaded,
 * so there is no need for synchronization when reading or modifying the
 * value of the main switch.
 */
int     useLDAPrespository = 1;

void
__db_disallowLDAP(void) {
        useLDAPrespository = 0;
}

void
__db_allowLDAP(void) {
        useLDAPrespository = 1;
}

}  /* extern "C" */