/** BEGIN COPYRIGHT BLOCK
 * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
 * Copyright (C) 2005 Red Hat, Inc.
 * All rights reserved.
 *
 * License: GPL (version 3 or any later version).
 * See LICENSE for details. 
 * END COPYRIGHT BLOCK **/

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

/* id2entry.c - routines to deal with the id2entry index */

#include "back-ldbm.h"

#define ID2ENTRY "id2entry"

/* 
 * The caller MUST check for DB_LOCK_DEADLOCK and DB_RUNRECOVERY returned
 * If cache_res is not NULL, it stores the result of CACHE_ADD of the
 * entry cache.
 */
int
id2entry_add_ext(backend *be, struct backentry *e, back_txn *txn, 
                 int encrypt, int *cache_res)
{
    ldbm_instance *inst = (ldbm_instance *) be->be_instance_info;
    DB     *db = NULL;
    DB_TXN *db_txn = NULL;
    DBT    data;
    DBT    key;
    int    len, rc;
    char   temp_id[sizeof(ID)];
    struct backentry *encrypted_entry = NULL;
    char *entrydn = NULL;

    LDAPDebug( LDAP_DEBUG_TRACE, "=> id2entry_add( %lu, \"%s\" )\n",
                                 (u_long)e->ep_id, backentry_get_ndn(e), 0 );

    if ( (rc = dblayer_get_id2entry( be, &db )) != 0 ) {
        LDAPDebug( LDAP_DEBUG_ANY, "Could not open/create id2entry\n",
            0, 0, 0 );
        rc = -1;
        goto done;
    }

    id_internal_to_stored(e->ep_id,temp_id);

    memset(&key, 0, sizeof(key));
    key.dptr = temp_id;
    key.dsize = sizeof(temp_id);

    /* Encrypt attributes in this entry if necessary */
    if (encrypt) {
        rc = attrcrypt_encrypt_entry(be, e, &encrypted_entry);
        if (rc) {
            LDAPDebug( LDAP_DEBUG_ANY, "attrcrypt_encrypt_entry failed in id2entry_add\n",
                0, 0, 0 );
            rc = -1;
            goto done;
        }
    }

    {
        int options = SLAPI_DUMP_STATEINFO | SLAPI_DUMP_UNIQUEID;
        Slapi_Entry *entry_to_use = encrypted_entry ? encrypted_entry->ep_entry : e->ep_entry;
        memset(&data, 0, sizeof(data));
        if (entryrdn_get_switch())
        {
            struct backdn *oldbdn = NULL;
            Slapi_DN *sdn =
                         slapi_sdn_dup(slapi_entry_get_sdn_const(entry_to_use));
            struct backdn *bdn = backdn_init(sdn, e->ep_id, 0);
            options |= SLAPI_DUMP_RDN_ENTRY;

            /* If the ID already exists in the DN cache && the DNs do not match,
             * replace it. */
            if (CACHE_ADD( &inst->inst_dncache, bdn, &oldbdn ) == 1) {
                if (slapi_sdn_compare(sdn, oldbdn->dn_sdn)) {
                    if (cache_replace( &inst->inst_dncache, oldbdn, bdn ) != 0) {
                        /* The entry was not in the cache for some reason (this
                         * should not happen since CACHE_ADD said it existed above). */
                        LDAPDebug( LDAP_DEBUG_ANY, "id2entry_add_ext(): Entry disappeared "
                                   "from cache (%s)\n", oldbdn->dn_sdn, 0, 0 );
                    }
                }
                CACHE_RETURN(&inst->inst_dncache, &oldbdn); /* to free oldbdn */
            }

            CACHE_RETURN(&inst->inst_dncache, &bdn);
            LDAPDebug2Args( LDAP_DEBUG_TRACE,
                   "=> id2entry_add (dncache) ( %lu, \"%s\" )\n",
                   (u_long)e->ep_id, slapi_entry_get_dn_const(entry_to_use) );
        }
        data.dptr = slapi_entry2str_with_options(entry_to_use, &len, options);
        data.dsize = len + 1;
    }

    if (NULL != txn) {
        db_txn = txn->back_txn_txn;
    }

    /* call pre-entry-store plugin */
    plugin_call_entrystore_plugins( (char **) &data.dptr, &data.dsize );

    /* store it  */
    rc = db->put( db, db_txn, &key, &data, 0);
    /* DBDB looks like we're freeing memory allocated by another DLL, which is bad */
    slapi_ch_free( &(data.dptr) );

    dblayer_release_id2entry( be, db );

    if (0 == rc) {
        int cache_rc = 0;
        /* Putting the entry into the entry cache.  
         * We don't use the encrypted entry here. */
        if (entryrdn_get_switch()) {
            struct backentry *parententry = NULL;
            ID parentid = slapi_entry_attr_get_ulong(e->ep_entry, "parentid");
            const char *myrdn = slapi_entry_get_rdn_const(e->ep_entry);
            const char *parentdn = NULL;
            char *myparentdn = NULL;
            Slapi_Attr *eattr = NULL;
            /* If the parent is in the cache, check the parent's DN and 
             * adjust to it if they don't match. (bz628300) */
            if (parentid && myrdn) {
                parententry = cache_find_id(&inst->inst_cache, parentid);
                if (parententry) {
                    parentdn = slapi_entry_get_dn_const(parententry->ep_entry);
                    if (parentdn) {
                        int is_tombstone = slapi_entry_flag_is_set(e->ep_entry,
                                                    SLAPI_ENTRY_FLAG_TOMBSTONE);
                        myparentdn = slapi_dn_parent_ext(
                                          slapi_entry_get_dn_const(e->ep_entry),
                                          is_tombstone);
                        if (myparentdn && PL_strcmp(parentdn, myparentdn)) {
                            Slapi_DN *sdn = slapi_entry_get_sdn(e->ep_entry);
                            char *newdn = NULL;
                            CACHE_LOCK(&inst->inst_cache);
                            slapi_sdn_done(sdn);
                            newdn = slapi_ch_smprintf("%s,%s", myrdn, parentdn);
                            slapi_sdn_init_dn_passin(sdn, newdn);
                            slapi_sdn_get_ndn(sdn); /* to set ndn */
                            CACHE_UNLOCK(&inst->inst_cache);
                        }
                        slapi_ch_free_string(&myparentdn);
                    }
                    CACHE_RETURN(&inst->inst_cache, &parententry);
                }
            }
            /* 
             * Adding entrydn attribute value to the entry,
             * which should be done before adding the entry to the entry cache.
             * Note: since we removed entrydn from the entry before writing
             * it to the database, it is guaranteed not in the entry.
             */
            /* slapi_ch_strdup and slapi_dn_ignore_case never returns NULL */
            entrydn = slapi_ch_strdup(slapi_entry_get_dn_const(e->ep_entry));
            entrydn = slapi_dn_ignore_case(entrydn);
            slapi_entry_attr_set_charptr (e->ep_entry,
                                          LDBM_ENTRYDN_STR, entrydn);
            if (0 == slapi_entry_attr_find(e->ep_entry,
                                           LDBM_ENTRYDN_STR, &eattr)) {
                /* now entrydn should exist in the entry */
                /* Set it to operational attribute */
                eattr->a_flags = SLAPI_ATTR_FLAG_OPATTR;
            }
            slapi_ch_free_string(&entrydn);
        }
        /* 
         * For ldbm_back_add and ldbm_back_modify, this entry had been already
         * reserved as a tentative entry.  So, it should be safe.
         * For ldbm_back_modify, the original entry having the same dn/id
         * should be in the cache.  Thus, this entry e won't be put into the
         * entry cache.  It'll be added by cache_replace.
         */
        cache_rc = CACHE_ADD(&inst->inst_cache, e, NULL);
        if (cache_res) {
            *cache_res = cache_rc;
        }
    }

done:
    /* If we had an encrypted entry, we no longer need it.
     * Note: encrypted_entry is not in the entry cache. */
    if (encrypted_entry) {
        backentry_free(&encrypted_entry);
    }

    LDAPDebug( LDAP_DEBUG_TRACE, "<= id2entry_add %d\n", rc, 0, 0 );
    return( rc );
}

int
id2entry_add( backend *be, struct backentry *e, back_txn *txn  )
{
    return id2entry_add_ext(be, e, txn, 1, NULL);
}

/* 
 * The caller MUST check for DB_LOCK_DEADLOCK and DB_RUNRECOVERY returned
 */
int
id2entry_delete( backend *be, struct backentry *e, back_txn *txn )
{
    DB     *db = NULL;
    DB_TXN *db_txn = NULL;
    DBT    key = {0};
    int    rc;
    char temp_id[sizeof(ID)];

    LDAPDebug( LDAP_DEBUG_TRACE, "=> id2entry_delete( %lu, \"%s\" )\n",
        (u_long)e->ep_id, backentry_get_ndn(e), 0 );

    if ( (rc = dblayer_get_id2entry( be, &db )) != 0 ) {
        LDAPDebug( LDAP_DEBUG_ANY, "Could not open/create id2entry\n",
            0, 0, 0 );
        return( -1 );
    }

    id_internal_to_stored(e->ep_id,temp_id);

    key.dptr = temp_id;
    key.dsize = sizeof(temp_id);        

    if (NULL != txn) {
        db_txn = txn->back_txn_txn;
    }

    if (entryrdn_get_switch())
    {
        ldbm_instance *inst = (ldbm_instance *)be->be_instance_info;
        struct backdn *bdn = dncache_find_id(&inst->inst_dncache, e->ep_id);
        if (bdn) {
            slapi_log_error(SLAPI_LOG_CACHE, ID2ENTRY,
                            "dncache_find_id returned: %s\n", 
                            slapi_sdn_get_dn(bdn->dn_sdn));
            CACHE_REMOVE(&inst->inst_dncache, bdn);
            CACHE_RETURN(&inst->inst_dncache, &bdn);
        }
    }

    rc = db->del( db,db_txn,&key,0 );
    dblayer_release_id2entry( be, db );

    LDAPDebug( LDAP_DEBUG_TRACE, "<= id2entry_delete %d\n", rc, 0, 0 );
    return( rc );
}

struct backentry *
id2entry( backend *be, ID id, back_txn *txn, int *err  )
{
    ldbm_instance    *inst = (ldbm_instance *) be->be_instance_info;
    DB               *db = NULL;
    DB_TXN           *db_txn = NULL;
    DBT              key = {0};
    DBT              data = {0};
    struct backentry *e = NULL;
    Slapi_Entry      *ee;
    char             temp_id[sizeof(ID)];

    slapi_log_error(SLAPI_LOG_TRACE, ID2ENTRY,
                    "=> id2entry(%lu)\n", (u_long)id);

    if ( (e = cache_find_id( &inst->inst_cache, id )) != NULL ) {
        slapi_log_error(SLAPI_LOG_TRACE, ID2ENTRY, 
                        "<= id2entry %p, dn \"%s\" (cache)\n",
                        e, backentry_get_ndn(e));
        goto bail;
    }

    *err = dblayer_get_id2entry( be, &db );
    if ( (*err != 0) || (NULL == db) ) {
        slapi_log_error(SLAPI_LOG_FATAL, ID2ENTRY,
                        "Could not open id2entry err %d\n", *err);
        return( NULL );
    }


    id_internal_to_stored(id,temp_id);

    key.data = temp_id;
    key.size = sizeof(temp_id);

    /* DBDB need to improve this, we're mallocing, freeing, all over the place here */
    data.flags = DB_DBT_MALLOC;

    if (NULL != txn) {
        db_txn = txn->back_txn_txn;
    }
    do {
        *err = db->get( db, db_txn, &key, &data, 0 );
        if ( (0 != *err) && 
             (DB_NOTFOUND != *err) && (DB_LOCK_DEADLOCK != *err) )
        {
            slapi_log_error(SLAPI_LOG_FATAL, ID2ENTRY, "db error %d (%s)\n",
                            *err, dblayer_strerror( *err ));
        }
    }
    while ( (DB_LOCK_DEADLOCK == *err) && (txn == NULL) );

    if ( (0 != *err) && (DB_NOTFOUND != *err) && (DB_LOCK_DEADLOCK != *err) )
    {
        if ( (DB_BUFFER_SMALL == *err) && (data.dptr == NULL) )
        {
            /* 
             * Now we are setting slapi_ch_malloc and its friends to libdb
             * by ENV->set_alloc in dblayer.c.  As long as the functions are 
             * used by libdb, it won't reach here.
             */
            slapi_log_error(SLAPI_LOG_FATAL, ID2ENTRY, 
                            "malloc failed in libdb; "
                            "terminating the server; OS error %d (%s)\n",
                            *err, slapd_system_strerror( *err ));
            exit (1);
        }
        dblayer_release_id2entry( be, db );
        return( NULL );
    }

    if ( data.dptr == NULL ) {
        slapi_log_error(SLAPI_LOG_TRACE, ID2ENTRY, 
                        "<= id2entry( %lu ) not found\n", (u_long)id);
        goto bail;
    }

    /* call post-entry plugin */
    plugin_call_entryfetch_plugins( (char **) &data.dptr, &data.dsize );

    if (entryrdn_get_switch()) {
        char *rdn = NULL;
        int rc = 0;

        /* rdn is allocated in get_value_from_string */
        rc = get_value_from_string((const char *)data.dptr, "rdn", &rdn);
        if (rc) {
            /* data.dptr may not include rdn: ..., try "dn: ..." */
            ee = slapi_str2entry( data.dptr, SLAPI_STR2ENTRY_NO_ENTRYDN );
        } else {
            char *normdn = NULL;
            Slapi_RDN * srdn = NULL;
            struct backdn *bdn = dncache_find_id(&inst->inst_dncache, id);
            if (bdn) {
                normdn = slapi_ch_strdup(slapi_sdn_get_dn(bdn->dn_sdn));
                slapi_log_error(SLAPI_LOG_CACHE, ID2ENTRY,
                                "dncache_find_id returned: %s\n", normdn);
                CACHE_RETURN(&inst->inst_dncache, &bdn);
            } else {
                Slapi_DN *sdn = NULL;
                rc = entryrdn_lookup_dn(be, rdn, id, &normdn, &srdn, txn);
                if (rc) {
                    slapi_log_error(SLAPI_LOG_TRACE, ID2ENTRY,
                                    "id2entry: entryrdn look up failed "
                                    "(rdn=%s, ID=%d)\n", rdn, id);
                    /* Try rdn as dn. Could be RUV. */
                    normdn = slapi_ch_strdup(rdn);
                } else if (NULL == normdn) {
                    slapi_log_error(SLAPI_LOG_FATAL, ID2ENTRY, 
                        "<= id2entry( %lu ) entryrdn_lookup_dn returned NULL. "
                        "Index file may be deleted or corrupted.\n",
                        (u_long)id);
                    goto bail;
                }
                sdn = slapi_sdn_new_normdn_byval((const char *)normdn);
                bdn = backdn_init(sdn, id, 0);
                if (CACHE_ADD( &inst->inst_dncache, bdn, NULL )) {
                    backdn_free(&bdn);
                    slapi_log_error(SLAPI_LOG_CACHE, ID2ENTRY,
                                    "%s is already in the dn cache\n", normdn);
                } else {
                    CACHE_RETURN(&inst->inst_dncache, &bdn);
                    slapi_log_error(SLAPI_LOG_CACHE, ID2ENTRY,
                                    "entryrdn_lookup_dn returned: %s, "
                                    "and set to dn cache (id %d)\n", normdn, id);
                }
            }
            ee = slapi_str2entry_ext( (const char *)normdn, (const Slapi_RDN *)srdn, data.dptr, 
                                      SLAPI_STR2ENTRY_NO_ENTRYDN );
            slapi_ch_free_string(&rdn);
            slapi_ch_free_string(&normdn);
            slapi_rdn_free(&srdn);
        }
    } else {
        ee = slapi_str2entry( data.dptr, 0 );
    }

    if ( ee != NULL ) {
        int retval = 0;
        struct backentry *imposter = NULL;

        /* All entries should have uniqueids */
        PR_ASSERT(slapi_entry_get_uniqueid(ee) != NULL);
        /* ownership of the entry is passed into the backentry */
        e = backentry_init( ee );
        e->ep_id = id;
        slapi_log_error(SLAPI_LOG_TRACE, ID2ENTRY, 
                        "id2entry id: %d, dn \"%s\" -- adding it to cache\n",
                        id, backentry_get_ndn(e));

        /* Decrypt any encrypted attributes in this entry, 
         * before adding it to the cache */
        retval = attrcrypt_decrypt_entry(be, e);
        if (retval) {
            slapi_log_error(SLAPI_LOG_FATAL, ID2ENTRY,
                            "attrcrypt_decrypt_entry failed in id2entry\n");
        }
        
        /* 
         * If return entry exists AND entryrdn switch is on,
         * add the entrydn value.
         */
        if (entryrdn_get_switch()) {
            Slapi_Attr *eattr = NULL;
            /* Check if entrydn is in the entry or not */
            if (slapi_entry_attr_find(e->ep_entry, LDBM_ENTRYDN_STR, &eattr)) {
                /* entrydn does not exist in the entry */
                char *entrydn = NULL;
                /* slapi_ch_strdup and slapi_dn_ignore_case never returns NULL */
                entrydn = slapi_ch_strdup(slapi_entry_get_dn_const(e->ep_entry));
                entrydn = slapi_dn_ignore_case(entrydn);
                slapi_entry_attr_set_charptr (e->ep_entry,
                                              LDBM_ENTRYDN_STR, entrydn);
                if (0 == slapi_entry_attr_find(e->ep_entry,
                                               LDBM_ENTRYDN_STR, &eattr)) {
                    /* now entrydn should exist in the entry */
                    /* Set it to operational attribute */
                    eattr->a_flags = SLAPI_ATTR_FLAG_OPATTR;
                }
                slapi_ch_free_string(&entrydn);
            }
        }
        retval = CACHE_ADD( &inst->inst_cache, e, &imposter );
        if (1 == retval) {
            /* This means that someone else put the entry in the cache
            while we weren't looking ! So, we need to use the pointer
            returned and free the one we made earlier */
            if (imposter)
            {
                backentry_free(&e);
                e = imposter;
            }
        } else if (-1 == retval) {
            /* the entry is in idtable but not in dntable, i.e., the entry
             * could have been renamed */
            slapi_log_error(SLAPI_LOG_TRACE, ID2ENTRY,
                            "id2entry: failed to put entry (id %lu, dn %s) "
                            "into entry cache\n", (u_long)id,
                            backentry_get_ndn(e));
        }
    } else {
        slapi_log_error(SLAPI_LOG_FATAL, ID2ENTRY,
                        "str2entry returned NULL for id %lu, string=\"%s\"\n",
                        (u_long)id, (char*)data.data);
        e = NULL;
    }

bail:
    slapi_ch_free( &(data.data) );

    dblayer_release_id2entry( be, db );

    slapi_log_error(SLAPI_LOG_TRACE, ID2ENTRY,
                    "<= id2entry( %lu ) %p (disk)\n", (u_long)id, e);
    return( e );
}
