/** 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 DBI_RC_RETRY and DBI_RC_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;
    dbi_db_t *db = NULL;
    dbi_txn_t *db_txn = NULL;
    dbi_val_t data = {0};
    dbi_val_t key = {0};
    int len, rc;
    char temp_id[sizeof(ID)];
    struct backentry *encrypted_entry = NULL;
    char *entrydn = NULL;
    uint32_t esize;

    slapi_log_err(SLAPI_LOG_TRACE, "id2entry_add_ext", "=> ( %lu, \"%s\" )\n",
                  (u_long)e->ep_id, backentry_get_ndn(e));

    if ((rc = dblayer_get_id2entry(be, &db)) != 0) {
        slapi_log_err(SLAPI_LOG_ERR, "id2entry_add_ext",
                      "Could not open/create id2entry\n");
        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) {
            slapi_log_err(SLAPI_LOG_ERR, "id2entry_add_ext",
                          "attrcrypt_encrypt_entry failed\n");
            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));
        entrydn = slapi_entry_get_dn(entry_to_use);
        slapi_entry_attr_set_charptr(entry_to_use, SLAPI_ATTR_DS_ENTRYDN,
                entrydn);

        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). */
                        slapi_log_err(SLAPI_LOG_WARNING, "id2entry_add_ext", "Entry disappeared "
                                                                             "from cache (%s)\n",
                                      slapi_sdn_get_dn(oldbdn->dn_sdn));
                    }
                }
                CACHE_RETURN(&inst->inst_dncache, &oldbdn); /* to free oldbdn */
            }

            CACHE_RETURN(&inst->inst_dncache, &bdn);
            slapi_log_err(SLAPI_LOG_TRACE,
                          "id2entry_add_ext", "(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 */
    esize = (uint32_t)data.dsize;
    plugin_call_entrystore_plugins((char **)&data.dptr, &esize);
    data.dsize = esize;

    /* store it  */
    if (txn && txn->back_special_handling_fn) {
        rc = txn->back_special_handling_fn(be, BTXNACT_ID2ENTRY_ADD, db, &key, &data, txn);
    } else {
        rc = dblayer_db_op(be, db, db_txn, DBI_OP_PUT, &key, &data);
    }
    /* 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_strcasecmp(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);
    }

    slapi_log_err(SLAPI_LOG_TRACE, "id2entry_add_ext", "<= %d\n", rc);
    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 DBI_RC_RETRY and DBI_RC_RUNRECOVERY returned
 */
int
id2entry_delete(backend *be, struct backentry *e, back_txn *txn)
{
    dbi_db_t *db = NULL;
    dbi_txn_t *db_txn = NULL;
    dbi_val_t key = {0};
    int rc;
    char temp_id[sizeof(ID)];

    slapi_log_err(SLAPI_LOG_TRACE, "id2entry_delete", "=>( %lu, \"%s\" )\n",
                  (u_long)e->ep_id, backentry_get_ndn(e));

    if ((rc = dblayer_get_id2entry(be, &db)) != 0) {
        slapi_log_err(SLAPI_LOG_ERR, "id2entry_delete",
                      "Could not open/create id2entry\n");
        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_err(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 = dblayer_db_op(be, db, db_txn, DBI_OP_DEL, &key, 0);
    dblayer_release_id2entry(be, db);

    slapi_log_err(SLAPI_LOG_TRACE, "id2entry_delete", "<= %d\n", rc);
    return (rc);
}

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

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

    if ((e = cache_find_id(&inst->inst_cache, id)) != NULL) {
        slapi_log_err(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_err(SLAPI_LOG_ERR, ID2ENTRY,
                      "Could not open id2entry err %d\n", *err);
        return (NULL);
    }


    id_internal_to_stored(id, temp_id);

    dblayer_value_set_buffer(be, &key, temp_id,  sizeof(temp_id));
    dblayer_value_init(be, &data);

    if (NULL != txn) {
        db_txn = txn->back_txn_txn;
    }
    do {
        *err = dblayer_db_op(be, db, db_txn, DBI_OP_GET, &key, &data);
        if ((0 != *err) &&
            (DBI_RC_NOTFOUND != *err) && (DBI_RC_RETRY != *err)) {
            slapi_log_err(SLAPI_LOG_ERR, ID2ENTRY, "db error %d (%s)\n",
                          *err, dblayer_strerror(*err));
        }
    } while ((DBI_RC_RETRY == *err) && (txn == NULL));

    if ((0 != *err) && (DBI_RC_NOTFOUND != *err) && (DBI_RC_RETRY != *err)) {
        if ((DBI_RC_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_err(SLAPI_LOG_CRIT, 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_err(SLAPI_LOG_TRACE, ID2ENTRY,
                      "<= id2entry( %lu ) not found\n", (u_long)id);
        goto bail;
    }

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

    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_err(SLAPI_LOG_CACHE, ID2ENTRY,
                              "dncache_find_id returned: %s\n", normdn);
                CACHE_RETURN(&inst->inst_dncache, &bdn);
            } else {
                Slapi_DN *sdn = NULL;
                if (config_get_return_orig_dn() &&
                    !get_value_from_string((const char *)data.dptr, SLAPI_ATTR_DS_ENTRYDN, &normdn))
                {
                    srdn = slapi_rdn_new_all_dn(normdn);
                } else {
                    rc = entryrdn_lookup_dn(be, rdn, id, &normdn, &srdn, txn);
                    if (rc) {
                        slapi_log_err(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_err(SLAPI_LOG_ERR, 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_err(SLAPI_LOG_CACHE, ID2ENTRY,
                                  "%s is already in the dn cache\n", normdn);
                } else {
                    CACHE_RETURN(&inst->inst_dncache, &bdn);
                    slapi_log_err(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_err(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_err(SLAPI_LOG_ERR, 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_err(SLAPI_LOG_TRACE, ID2ENTRY,
                          "Failed to put entry (id %lu, dn %s) "
                          "into entry cache\n",
                          (u_long)id,
                          backentry_get_ndn(e));
        }
    } else {
        slapi_log_err(SLAPI_LOG_ERR, ID2ENTRY,
                      "str2entry returned NULL for id %lu, string=\"%s\"\n",
                      (u_long)id, (char *)data.data);
        e = NULL;
    }

bail:
    dblayer_value_free(be, &data);
    dblayer_release_id2entry(be, db);

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