/** 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 */
  
  LDAPDebug( LDAP_DEBUG_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 );
  LDAPDebug( LDAP_DEBUG_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 );
      fclose(fp);
      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;
  
  LDAPDebug( LDAP_DEBUG_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:
      LDAPDebug( LDAP_DEBUG_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:
      LDAPDebug( LDAP_DEBUG_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:
      LDAPDebug( LDAP_DEBUG_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++ ) {
      LDAPDebug( LDAP_DEBUG_ARGS, "   %s: %s\n",
		mods[j]->mod_type, mods[j]->mod_bvalues[i]->bv_val,
		0 );
    }
    LDAPDebug( LDAP_DEBUG_ARGS, "   -\n", 0, 0, 0 );
    
    if ( err != LDAP_SUCCESS ) {
      break;
    }
  }
  
  LDAPDebug( LDAP_DEBUG_TRACE, "<= apply_mods %d\n", err, 0, 0 );
  return( err );
}
