/** 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

/*
 *  File: modify.c
 *
 *  Functions:
 *
 *      ldif_back_modify() - ldif backend modify function
 *      update_db() - updates memory and disk db to reflect changes
 *      db2disk() - writes out ldif database to disk
 *      ldifentry_free() - frees an ldif_Entry
 *      ldifentry_dup() - copies an ldif_Entry
 *      ldif_find_entry() - searches an ldif DB for a particular dn
 *      apply_mods() - applies the modifications to an Entry
 *
 */

#include "back-ldif.h"

/*Prototypes*/
void ldifentry_free(ldif_Entry *);
ldif_Entry *ldifentry_dup(ldif_Entry *);
int apply_mods(Slapi_Entry *, LDAPMod **);
ldif_Entry *ldif_find_entry(Slapi_PBlock *, LDIF *, char *, ldif_Entry **);
int db2disk(Slapi_PBlock *, LDIF *);
int update_db(Slapi_PBlock *, LDIF *, ldif_Entry *, ldif_Entry *, int);

/*
 *  Function: ldif_back_modify
 *
 *  Returns: returns 0 if good, -1 else.
 *
 *  Description: For changetype: modify, this makes the changes
 */
int
ldif_back_modify(Slapi_PBlock *pb)
{
    LDIF *db;                          /*The ldif file is stored here*/
    ldif_Entry *entry, *entry2, *prev; /*For db manipulation*/
    int err;                           /*House keeping stuff*/
    LDAPMod **mods;                    /*Used to apply the modifications*/
    char *dn;                          /*Storage for the dn*/
    char *errbuf = NULL;               /* To get error back */

    slapi_log_err(SLAPI_LOG_TRACE, "=> ldif_back_modify\n", 0, 0, 0);
    prev = NULL;

    /*Get the database, the dn and the mods*/
    if (slapi_pblock_get(pb, SLAPI_PLUGIN_PRIVATE, &db) < 0 ||
        slapi_pblock_get(pb, SLAPI_MODIFY_TARGET, &dn) < 0 ||
        slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods) < 0) {
        slapi_send_ldap_result(pb, LDAP_OPERATIONS_ERROR, NULL, NULL, 0, NULL);
        return (-1);
    }
    /*Lock the database*/
    PR_Lock(db->ldif_lock);

    /*
   * Find the entry we are about to modify.
   * prev will point to the previous element in the list,
   * NULL if there is no previous element.
   */
    if ((entry = (ldif_Entry *)ldif_find_entry(pb, db, dn, &prev)) == NULL) {
        slapi_send_ldap_result(pb, LDAP_NO_SUCH_OBJECT, NULL, NULL, 0, NULL);
        PR_Unlock(db->ldif_lock);
        return (-1);
    }

    /*Check acl, note that entry is not an Entry, but a ldif_Entry*/
    if ((err = slapi_acl_check_mods(pb, entry->lde_e, mods, &errbuf)) != LDAP_SUCCESS) {
        slapi_send_ldap_result(pb, err, NULL, errbuf, 0, NULL);
        if (errbuf)
            free(errbuf);
        PR_Unlock(db->ldif_lock);
        goto error_return;
    }

    /* Create a copy of the entry and apply the changes to it */
    if ((entry2 = (ldif_Entry *)ldifentry_dup(entry)) == NULL) {
        slapi_send_ldap_result(pb, LDAP_OPERATIONS_ERROR, NULL, NULL, 0, NULL);
        PR_Unlock(db->ldif_lock);
        goto error_return;
    }

    /*Actually apply the modifications*/
    if ((err = apply_mods(entry2->lde_e, mods)) != 0) {
        slapi_send_ldap_result(pb, err, NULL, NULL, 0, NULL);
        PR_Unlock(db->ldif_lock);
        goto error_return;
    }

    /* Check for abandon */
    if (slapi_op_abandoned(pb)) {
        PR_Unlock(db->ldif_lock);
        goto error_return;
    }

    /* Check that the entry still obeys the schema */
    if (slapi_entry_schema_check(pb, entry2->lde_e) != 0) {
        slapi_send_ldap_result(pb, LDAP_OBJECT_CLASS_VIOLATION, NULL, NULL, 0, NULL);
        PR_Unlock(db->ldif_lock);
        goto error_return;
    }

    /* Check if the attribute values in the mods obey the syntaxes */
    if (slapi_mods_syntax_check(pb, mods, 0) != 0) {
        slapi_send_ldap_result(pb, LDAP_INVALID_SYNTAX, NULL, NULL, 0, NULL);
        PR_Unlock(db->ldif_lock);
        goto error_return;
    }

    /* Check for abandon again */
    if (slapi_op_abandoned(pb)) {
        PR_Unlock(db->ldif_lock);
        goto error_return;
    }

    /* Change the entry itself both on disk and in the cache */
    if (update_db(pb, db, entry2, prev, LDIF_DB_REPLACE) != 0) {
        slapi_send_ldap_result(pb, LDAP_OPERATIONS_ERROR, NULL, NULL, 0, NULL);
        PR_Unlock(db->ldif_lock);
        goto error_return;
    }

    slapi_send_ldap_result(pb, LDAP_SUCCESS, NULL, NULL, 0, NULL);
    PR_Unlock(db->ldif_lock);
    slapi_log_err(SLAPI_LOG_TRACE, "<= ldif_back_modify\n", 0, 0, 0);
    return (0);

error_return:;
    if (entry2 != NULL) {
        ldifentry_free(entry2);
    }

    return (-1);
}

/*
 *  Function: update_db
 *
 *  Returns: returns 0 if good, -1 else.
 *
 *  Description: Will update the database in memory, and on disk
 *               if prev == NULL, then the element to be deleted/replaced
 *               is the first in the list.
 *               mode = LDIF_DB_ADD | LDIF_DB_REPLACE | LDIF_DB_DELETE
 *               The database should be locked when this function is called.
 *               Note that on replaces and deletes, the old ldif_Entry's
 *               are freed.
 */
int
update_db(Slapi_PBlock *pb, LDIF *db, ldif_Entry *new, ldif_Entry *prev, int mode)
{
    ldif_Entry *tmp;    /*Used to free the removed/replaced entries*/
    char *buf;          /*Used to convert entries to strings for output to file*/
    FILE *fp;           /*File ptr to the ldif file*/
    int len;            /*Used by slapi_entry2str*/
    int db_updated = 0; /*Flag to designate if db in memory has been updated*/

    /*Make sure that the database is not null. Everything else can be, though*/
    if (db == NULL) {
        slapi_send_ldap_result(pb, LDAP_OPERATIONS_ERROR, NULL, NULL, 0, NULL);
        return (-1);
    }

    /*
   * If we are adding an entry, then prev should be pointing
   * to the last element in the list, or null if the list is empty,
   * and new should not be null.
   */
    if (mode == LDIF_DB_ADD) {

        /*Make sure there is something to add*/
        if (new == NULL) {
            slapi_send_ldap_result(pb, LDAP_OPERATIONS_ERROR, NULL, NULL, 0, NULL);
            return (-1);
        }

        /*If prev is null, then there had better be no entries in the list*/
        if (prev == NULL) {
            if (db->ldif_entries != NULL) {
                slapi_send_ldap_result(pb, LDAP_OPERATIONS_ERROR, NULL, NULL, 0, NULL);
                return (-1);
            }
            /*There are no elements, so let's add the new one*/
            db->ldif_entries = new;
            db->ldif_n++;

            /*Set a flag*/
            db_updated = 1;
        }
        /*
     * Last error case to test for is if prev is not null, and prev->next
     * points to something. This means that we are not at the end of the list
     */
        if (prev != NULL) {
            if (prev->next != NULL) {
                slapi_send_ldap_result(pb, LDAP_OPERATIONS_ERROR, NULL, NULL, 0, NULL);
                return (-1);
            }

            /*We're at the end of the list, so tack the new entry onto the end*/
            prev->next = new;
            db->ldif_n++;

            db_updated = 1;
        }

        /*If the database has been updated in memory, update the disk*/
        if (db_updated && db->ldif_file != NULL) {

            /*Update the disk by appending to the ldif file*/
            fp = fopen(db->ldif_file, "a");
            if (fp == NULL) {
                slapi_send_ldap_result(pb, LDAP_OPERATIONS_ERROR, NULL, NULL, 0, NULL);
                /*This is s pretty serious problem, so we exit*/
                exit(-1);
            }

            /*Convert the entry to ldif format*/
            buf = slapi_entry2str(new->lde_e, &len);
            fprintf(fp, "%s\n", buf);
            free((void *)buf);
            fclose(fp);
            return (0);
        } else {
            slapi_send_ldap_result(pb, LDAP_OPERATIONS_ERROR, NULL, NULL, 0, NULL);
            return (-1);
        }

    } else if (mode == LDIF_DB_DELETE) {

        /*We're not deleting the first entry in the list*/
        if (prev != NULL) {
            /*Make sure there is something to delete*/
            if (prev->next == NULL) {
                slapi_send_ldap_result(pb, LDAP_OPERATIONS_ERROR, NULL, NULL, 0, NULL);
                return (-1);
            }
            tmp = prev->next;
            prev->next = tmp->next;
            db->ldif_n--;
            ldifentry_free(tmp);

            db_updated = 1;

        } else { /*We are deleting the first entry in the list*/

            /*Make sure there is something to delete*/
            if (db->ldif_entries == NULL) {
                slapi_send_ldap_result(pb, LDAP_OPERATIONS_ERROR, NULL, NULL, 0, NULL);
                return (-1);
            }

            tmp = db->ldif_entries;
            db->ldif_entries = tmp->next;
            db->ldif_n--;

            /*Free the entry, and set the flag*/
            ldifentry_free(tmp);
            db_updated = 1;
        }

        /*
     * Update the disk by rewriting entire ldif file
     * I know, I know, but simplicity is the key here.
     */
        if (db_updated) {
            return (db2disk(pb, db));
        } else {
            slapi_send_ldap_result(pb, LDAP_OPERATIONS_ERROR, NULL, NULL, 0, NULL);
            return (-1);
        }

    } else if (mode == LDIF_DB_REPLACE) {

        /*Make sure there is something to replace with*/
        if (new == NULL) {
            slapi_send_ldap_result(pb, LDAP_OPERATIONS_ERROR, NULL, NULL, 0, NULL);
            return (-1);
        }


        /*We're not replacing the first element in the list*/
        if (prev != NULL) {

            /*Make sure there is something to replace*/
            if (prev->next == NULL) {
                slapi_send_ldap_result(pb, LDAP_OPERATIONS_ERROR, NULL, NULL, 0, NULL);
                return (-1);
            }

            /*Splice out the old entry, and put in the new*/
            tmp = prev->next;
            prev->next = new;
            new->next = tmp->next;

            /*Free it*/
            ldifentry_free(tmp);
            db_updated = 1;
        } else { /*We are replacing the first entry in the list*/

            /*Make sure there is something to replace*/
            if (db->ldif_entries == NULL) {
                slapi_send_ldap_result(pb, LDAP_OPERATIONS_ERROR, NULL, NULL, 0, NULL);
                return (-1);
            }

            /*Splice out the old entry, and put in the new*/
            tmp = db->ldif_entries;
            db->ldif_entries = new;
            new->next = tmp->next;

            /*Free it*/
            ldifentry_free(tmp);
            db_updated = 1;
        }

        /*Update the disk by rewriting entire ldif file*/
        if (db_updated) {
            return (db2disk(pb, db));
        } else {
            slapi_send_ldap_result(pb, LDAP_OPERATIONS_ERROR, NULL, NULL, 0, NULL);
            return (-1);
        }
    }
}

/*
 *  Function: db2disk
 *
 *  Returns: returns 0 if good, exits else
 *
 *  Description: Takes an ldif database, db, and writes it out to disk
 *               if it can't open the file, there's trouble, so we exit
 *               because this function is usually called after the db
 *               in memory has been updated.
 *
 */
int
db2disk(Slapi_PBlock *pb, LDIF *db)
{
    ldif_Entry *cur; /*Used for walking down the list*/
    char *buf;       /*temp storage for Entry->ldif converter*/
    FILE *fp;        /*File pointer to ldif target file*/
    int len;         /*length returned by slapi_entry2str*/

    /*Open the file*/
    fp = fopen(db->ldif_file, "w");
    if (fp == NULL) {
        slapi_send_ldap_result(pb, LDAP_OPERATIONS_ERROR, NULL, NULL, 0, NULL);
        /*This is s pretty serious problem, so we exit*/
        exit(-1);
    }

    /*
   * Walk down the list, converting each entry to a string,
   * writing the string out to fp
   */
    for (cur = db->ldif_entries; cur != NULL; cur = cur->next) {
        buf = slapi_entry2str(cur->lde_e, &len);
        fprintf(fp, "%s\n", buf);
        free((void *)buf);
    }

    fclose(fp);
    return (0);
}


/*
 *  Function: ldifentry_free
 *
 *  Returns: void
 *
 *  Description: Frees an ldif_Entry
 */
void
ldifentry_free(ldif_Entry *e)
{

    /*Make sure that there is actually something to free*/
    if (e == NULL) {
        return;
    }

    /*Free the entry*/
    slapi_entry_free(e->lde_e);

    /*Free the entire thing*/
    free((void *)e);
}

/*
 *  Function: ldifentry_dup
 *
 *  Returns: a pointer to the new ldif_entry, or NULL
 *
 *  Description: Copies and returns a pointer to a new
 *               ldif_Entry whose contents are a copy of e's contents
 *               Note: uses malloc
 */
ldif_Entry *
ldifentry_dup(ldif_Entry *e)
{
    ldif_Entry *new;

    /*Let's make sure that e is not null*/
    if (e == NULL) {
        return (NULL);
    }

    /*Allocate a new ldif_entry, and return it if it is null*/
    new = (ldif_Entry *)malloc((sizeof(ldif_Entry)));
    if (new == NULL) {
        return (new);
    }

    /*Copy the Entry in e*/
    new->lde_e = slapi_entry_dup(e->lde_e);
    new->next = NULL;

    return (new);
}

/*
 *  Function: ldif_find_entry
 *
 *  Returns: A pointer to the matched ldif_Entry, or Null
 *
 *  Description: Goes down the list of entries in db to find the entry
 *               matching dn. Returns a pointer to the entry,
 *               and sets prev to point to the entry before the match.
 *               If there is no match, prev points to the last
 *               entry in the list, and null is returned.
 *               If the first element matches, prev points to NULL
 */
ldif_Entry *
ldif_find_entry(Slapi_PBlock *pb, LDIF *db, char *dn, ldif_Entry **prev)
{
    ldif_Entry *cur;         /*Used for walking down the list*/
    char *finddn, *targetdn; /*Copies of dns for searching */
    int found_it = 0;        /*A flag to denote a successful search*/

    /*Set cur to the start of the list*/
    cur = db->ldif_entries;

    /*Increase the number of accesses*/
    db->ldif_tries++;

    /*Make a copy of the target dn, and normalize it*/
    targetdn = strdup(dn);
    (void)slapi_dn_normalize(targetdn);


    /*Go down the list until we find the entry*/
    while (cur != NULL) {
        finddn = strdup(slapi_entry_get_dn(cur->lde_e));
        (void)slapi_dn_normalize(finddn);


        /*Test to see if we got the entry matching the dn*/
        if (strcasecmp(targetdn, finddn) == 0) {
            found_it = 1;
            free((void *)finddn);
            db->ldif_hits++;
            break;
        }

        /*Udpate the pointers*/
        *prev = cur;
        cur = cur->next;
        free((void *)finddn);
    }

    free((void *)targetdn);


    /*
   * If we didn't find a matching entry, we should
   * return, and let the caller handle this (possible) error
   */
    if (!found_it) {
        return (NULL);
    }

    /*
   * If the first entry matches, we have to set prev to null,
   * so the caller knows.
   */
    if (*prev == cur) {
        *prev = NULL;
    }

    return (cur);
}

/*
 *  Function: apply_mods
 *
 *  Returns: LDAP_SUCCESS if success
 *
 *  Description: Applies the modifications specified in mods to e.
 */
int
apply_mods(Slapi_Entry *e, LDAPMod **mods)
{
    int err, i, j;

    slapi_log_err(SLAPI_LOG_TRACE, "=> apply_mods\n", 0, 0, 0);

    err = LDAP_SUCCESS;
    for (j = 0; (mods != NULL) && (mods[j] != NULL); j++) {
        switch (mods[j]->mod_op & ~LDAP_MOD_BVALUES) {
        case LDAP_MOD_ADD:
            slapi_log_err(SLAPI_LOG_ARGS, "   add: %s\n",
                          mods[j]->mod_type, 0, 0);
            err = slapi_entry_add_values(e, mods[j]->mod_type,
                                         mods[j]->mod_bvalues);
            break;

        case LDAP_MOD_DELETE:
            slapi_log_err(SLAPI_LOG_ARGS, "   delete: %s\n",
                          mods[j]->mod_type, 0, 0);
            err = slapi_entry_delete_values(e, mods[j]->mod_type,
                                            mods[j]->mod_bvalues);
            break;

        case LDAP_MOD_REPLACE:
            slapi_log_err(SLAPI_LOG_ARGS, "   replace: %s\n",
                          mods[j]->mod_type, 0, 0);
            err = entry_replace_values(e, mods[j]->mod_type,
                                       mods[j]->mod_bvalues);
            break;
        }
        for (i = 0; mods[j]->mod_bvalues != NULL &&
                    mods[j]->mod_bvalues[i] != NULL;
             i++) {
            slapi_log_err(SLAPI_LOG_ARGS, "   %s: %s\n",
                          mods[j]->mod_type, mods[j]->mod_bvalues[i]->bv_val,
                          0);
        }
        slapi_log_err(SLAPI_LOG_ARGS, "   -\n", 0, 0, 0);

        if (err != LDAP_SUCCESS) {
            break;
        }
    }

    slapi_log_err(SLAPI_LOG_TRACE, "<= apply_mods %d\n", err, 0, 0);
    return (err);
}
