/*
 * $Id: database.c,v 1.35 1999/11/12 16:44:39 labovit Exp $
 * originally Id: database.c,v 1.48 1998/07/30 20:48:29 labovit Exp 
 */

#include <stdio.h>
#include <string.h>
#include "mrt.h"
#include "trace.h"
/*#include "interface.h"*/
#include <time.h>
#include <signal.h>
#include "config_file.h"
#include <fcntl.h>
#include "irrd.h"
#include <pipeline_defs.h>

extern trace_t *default_trace;

void munge_buffer (char *buffer, irr_database_t *irr_database);


/* radix_flush
 * Delete a radix tree. Called by database_clear
 */
void radix_flush (radix_tree_t *radix_tree) {
  radix_node_t *node = NULL;
  radix_str_t tmp;

  LINKED_LIST *ll_nodes;

  ll_nodes = LL_Create (LL_Intrusive, True,
			LL_NextOffset, LL_Offset (&tmp, &tmp.next),
			LL_PrevOffset, LL_Offset (&tmp, &tmp.prev),
			LL_DestroyFunction, delete_radix_str,
			0);

  RADIX_WALK_ALL (radix_tree->head, node) {
    LL_Add (ll_nodes, new_radix_str (node));
  }
  RADIX_WALK_END;

  LL_Destroy (ll_nodes);
  Delete (radix_tree);
}



/* database_clear
 * delete all indicies associated with a database 
 */
void database_clear (irr_database_t *db) {
  int i;

  if (db == NULL) {
    trace (ERROR, default_trace, "*** NULL DATABASE!!! ****\n");
    return;
  }

  trace (NORM, default_trace, "Clearing out database %s\n", db->name);

  db->bytes = 0;

  for (i=0; i< IRR_MAX_KEYS; i++) 
    db->num_objects[i] = 0;
  
  if (db->hash != NULL) {
    trace (NORM, default_trace, "Clearing out db->gash\n");
    HASH_Clear (db->hash);
  }
  if (db->radix != NULL) {
    trace (NORM, default_trace, "Clearing out db->radix\n");
    radix_flush (db->radix);
  }
  if (db->hash_spec) {
    trace (NORM, default_trace, "Clearing out db->hash_spec\n");
    HASH_Clear (db->hash_spec);
  }

  trace (NORM, default_trace, "Creating new radix and closing db->fd\n");
  db->radix = New_Radix (128);
  fclose (db->fd);

  db->fd = NULL;
  db->db_syntax = EMPTY_DB;

  if (IRR.use_disk == 1) {
    trace (NORM, default_trace, "Closing gdb files\n");
#ifdef USE_GDBM
    trace (NORM, default_trace, "Closing IRRd GDBM files...\n");
    gdbm_close (db->dbm);
    gdbm_close (db->dbm_spec);
#endif /* USE_GDBM */
#ifdef USE_DB1
    trace (NORM, default_trace, "Closing IRRd DB1 files...\n");
    if (db->dbm->close(db->dbm))
      trace (ERROR, default_trace,
             "Could not close DB1 non-spec file: (%s)\n", strerror(errno));

    if (db->dbm_spec->close(db->dbm_spec))
      trace (ERROR, default_trace,
             "Could not close DB1 spec file: (%s)\n", strerror(errno));

#endif /* USE_DB1 */
  }
}


/* irr_reload_database !B... command, eg, !Bradb
 * Clear, and then reload each database in a comma seperated list
 */
int irr_reload_database(char *name, uii_connection_t *uii) {
  irr_database_t *database;

  database = find_database (name);
  if (database == NULL) {
    if (uii != NULL) uii_send_data(uii, "%s database not found\r\n", name);
    return(-1);
  }

  irr_lock(database);
  if (uii != NULL) uii_send_data(uii, "Clearing out old data...\r\n");
  database_clear(database);
  if (uii != NULL) uii_send_data(uii, "Reading database file %s/%s...\r\n", 
				 IRR.database_dir, database->name);
  scan_irr_file(database, NULL, 0, NULL);
  scan_irr_serial(database);

  irr_unlock(database);
  return(1);
}

/*JW  add a \n to the end of the file to seperate objects */
void append_blank_line (FILE *fd) {
  size_t num;
  int start_offset, tmp_pos;
  char buffer[MAXLINE];
  
  if ((fd == (void *) -11) || 
      (fd == (void *) -1)  ||
      (fd == NULL))
    return;

  start_offset = ftell (fd);
  if (fseek (fd, 0, SEEK_END) < 0) 
    trace (NORM, default_trace, 
	   "** ERROR ** fseek() failed in append_blank_line! [1]");
  
  tmp_pos = ftell (fd);

  if (tmp_pos > 1) {
    if (fseek (fd, (long) tmp_pos - 2, SEEK_SET) < 0) 
      trace (NORM, default_trace, 
	     "** ERROR ** fseek() failed in append_blank_line! [2]");
    if ((num = fread (buffer, 1, 2, fd)) != 2)
      return;

    buffer[2] = '\0';
    if (strcmp (buffer, "\n\n")) {
      buffer[0] = '\n';
      buffer[1] = '\n';
      if (fseek (fd, 0, SEEK_END) < 0) 
	trace (NORM, default_trace, 
	       "** ERROR ** fseek() failed in append_blank_line! [3]");
      fwrite (buffer, 1, 2, fd);
    }
    
    if (fseek (fd, start_offset, SEEK_SET) < 0) 
      trace (NORM, default_trace, 
	     "** ERROR ** fseek() failed in append_blank_line! [4]");
  }
}

/* irr_load_data
 * Load all of the databases and their serial numbers. Call from main on startup
 */
void irr_load_data () {
  irr_database_t *database;
  int false = 0;

  LL_Iterate (IRR.ll_database, database) {
    irr_lock (database);

    /* forced rebuild, or indicies do NOT already exist
     * we have to build them */ 
#ifdef USE_GDBM
    if ((IRR.rebuild_indicies == 1) ||  
	(IRR.use_disk == 0) || (irr_open_dbm_file (database) == 0)) {
#endif 
      if (scan_irr_file (database, NULL, 0, NULL) != NULL)
	/* exit (0); */ ;   /* No! we should be able to start with an empty database! */
      scan_irr_serial (database);

#ifdef USE_GDBM
      if (IRR.use_disk == 1) {
	if (database->dbm != NULL) {
	  /* turn off fast indexing. We now ask GDBM to commit everything to disk */
	  gdbm_setopt(database->dbm, GDBM_FASTMODE, &false, sizeof(int));
	  gdbm_setopt(database->dbm_spec, GDBM_FASTMODE, &false, sizeof(int));
	}
      }
      /* JW need to add support for db1 (not sure what setopt does)? */
      /* **** ANSWER **** by default, GDBM writes everything to disk (very, very, slow)
       * we don't need this type of transaction control on database
       * builds, so we cache in memory and then sync GDBM at the end
       * of index building - Craig
       */
    }
#endif

    append_blank_line (database->fd);

    irr_unlock (database);
  }
}



/* eventually seperate out dbm specific stuff and clean up code 
 * a bit
 */
int open_existing_database (irr_database_t *database) {


  return (1);
}


/* the database on disk contains "xx", or deleted objects. Our pointers
 * in memory point to new objects later. This routine create a new databse
 * and relodes memory with offsets of new database. The new database file
 * on disk is free of any "xx" objects.
 */
int irr_database_sync (irr_database_t *database) {
  char file[MAXLINE], buffer[MAXLINE], new[MAXLINE];
  extern key_label_t key_info[100][2];
  FILE *fd;
  u_long f;
  int line, blank_line;
  int i, skip = 0;

  line = blank_line = 0;

  /* database cleaning disabled */
  if (database->no_dbclean) {
    trace (TR_ERROR, default_trace, "Clean aborted -- configured for %s as disabled!\n",
	   database->name);
    return (0);
  }

  /* new database maybe? */
  if (database->fd == NULL) {
    trace (NORM, default_trace, "DBClean aborted -- NULL fd for %s\n", 
	   database->name);
    return (-1);
  }


  sprintf (file, "%s/.%s.db.clean", IRR.database_dir, database->name);


  irr_lock (database);

  /* read from beginning */
  if (fseek (database->fd, 0, SEEK_SET) < 0) 
    trace (NORM, default_trace, "** ERROR ** fseek() failed in irr_database_sync!");

  if ((fd = fopen (file, "w")) == NULL) {
    trace (TR_ERROR, default_trace, "Could not open temporary file %s:%s\n", 
	   file, strerror (errno));
    irr_unlock (database);
    return (-1);
  }

  trace (NORM, default_trace, "Starting clean of %s\n", database->name);

  while (fgets (buffer, sizeof (buffer)-1, database->fd) != NULL) {
    line++;

    if (strlen (buffer) < 2) {
	skip = 0;
	blank_line++;
    }
    else 
      blank_line = 0;

    if (!strncasecmp ("*xx", buffer, 3)) {
      skip = 1;
    }
    
    /* skip over comments */
    if ((!strncasecmp ("#", buffer, 1)) && (line > 10)) {
      skip = 1;
    }

    if ((skip == 0) && (database->obj_filter)) {
      for (i = 0; i < IRR_MAX_KEYS; i++) {  
	f = (int) key_info[i][RIPE181].filter_val; 
	if ((database->obj_filter & f)) { 
	  if (!strncmp (key_info[i][RIPE181].name, buffer, 
			key_info[i][RIPE181].len)) { 
	    skip = 1; 
	    break; 
	  } 
	}
      } 
    }

    if ((skip != 1)  && (blank_line < 3))
      fwrite (buffer, 1, strlen (buffer), fd);
    /*else
      trace (TR_TRACE, default_trace, "Skipping %s", buffer);*/ 
  }

  fclose (fd);
  trace (NORM, default_trace, "DBClean created %s temporary file\n", file);
  
  /* move .<database>.db.sync to <database>.db */
  sprintf (new, "%s/%s.db", IRR.database_dir, database->name);
  trace (NORM, default_trace, "Renaming tmp file %s -> %s\n", file, new);

  if (rename (file, new) < 0) {
    trace (TR_ERROR, default_trace, "Could not rename file to %s:%s\n", 
	   new, strerror (errno));
    irr_unlock (database);
    return (-1);
  }

  trace (NORM, default_trace, "DBClean clearing old memory database for %s\n", 
	 database->name);
  database_clear (database);

  scan_irr_file (database, NULL, 0, NULL);
  scan_irr_serial (database);      
  irr_unlock (database);

  return (1);

}

void irr_export_timer (mtimer_t *timer, irr_database_t *db) {
  irr_database_export (db);
}


void irr_sync_timer (mtimer_t *timer, irr_database_t *db) {
  irr_database_sync (db);
}

/* Given a DB as input determine it's syntax.
 * This function is designed to support reload's,
 * mirror's and !us...!ue updates.  It does not
 * syntax check the file but finds the first
 * attribute of the first object and determines
 * syntax.
 *
 * Return: (the DB syntax type)
 *   RPSL
 *   RIPE181
 *   EMPTY_DB
 *   UNRECOGNIZED
 */
enum DB_SYNTAX get_database_syntax (FILE *fp) {
  char buffer[MAXLINE], *cp;
  long fpos;
  u_long offset = 0, position = 0;
  int state = BLANK_LINE, db_syntax = EMPTY_DB;

  fpos = ftell (fp); /* save the current file pos */

  /* rewind */
  if (fseek (fp, 0L, SEEK_SET) < 0) 
    trace (NORM, default_trace, "** ERROR ** fseek failed in get_database_syntax");

  while (state != DB_EOF) {
    if ((cp = fgets (buffer, sizeof (buffer) - 1, fp)) != NULL) {
      position = offset;
      offset += strlen (buffer);
    }

    state = get_state (buffer, cp, state);
    /* dump comment lines and lines that exceed the buffer size */
    if (state == OVRFLW     || 
	state == OVRFLW_END ||
	state == COMMENT    ||
	state == BLANK_LINE)
      continue;
    
    if (state == START_F) {
      if (buffer[0] == '%'            ||
	  !strncmp (buffer, "ADD", 3) ||
	  !strncmp (buffer, "DEL", 3))
	continue;

      /* we have a ripe181 DB or a deleted
       * ripe181 or rpsl object
       */
      if (buffer[0] == '*') {
	if (strlen (buffer) > 3) {
	  if (buffer[3] == ':')
	    db_syntax = RIPE181;
	  else
	    db_syntax = RPSL;
	}
	else
	  db_syntax = UNRECOGNIZED;
      }
      else
	db_syntax = RPSL; /* later check for all legal rpsl fields */
      
      break;
    }
  }

  /* restore file pos before exit */
  if (fseek (fp, fpos, SEEK_SET) < 0) 
    trace (NORM, default_trace, "** ERROR ** fseek failed in get_database_syntax");

  return (db_syntax);
}








int irr_database_export (irr_database_t *database) {
  char file1[MAXLINE], file2[MAXLINE];
  char new[MAXLINE], command[MAXLINE];
  u_long serial;

  if (IRR.ftp_dir == NULL) {
    trace (TR_ERROR, default_trace, "Aborting export -- ftp directory not configured!\n");
    return (0);
  }

  /* new database maybe? */
  if (database->fd == NULL) {
    trace (NORM, default_trace, "Export aborted -- NULL fd for %s\n", 
	   database->name);
    return (-1);
  }

  sprintf (file1, "%s/%s.db", IRR.database_dir, database->name);

  if (IRR.tmp_dir != NULL) 
    sprintf (file2, "%s/.%s.db.export", IRR.tmp_dir, database->name);
  else
    sprintf (file2, "%s/.%s.db.export", IRR.ftp_dir, database->name);

  /* copy to export area (or maybe tmp directory) _atomically_ */
  irr_lock (database);
  serial = database->serial_number;
  if (irr_copy_file (file1, file2, 1) != 1) {
    irr_unlock (database);
    trace (TR_ERROR, default_trace, "Export failed! Aborting.\n");
    return (-1);
  }
  irr_unlock (database);
  /* -- done with atomic copy */


  /* compress -- we should really find the path in configure.in */
  sprintf (command, "%s -n -q -f %s", GZIP_CMD, file2); 
  trace (NORM, default_trace, "Running gzip -n -q -f %s\n", file2);
  if (system (command) < 0) {
    trace (NORM, default_trace, "Error occured during gzip!\n");
    return (-1);
  }

  /* if necessary, copy from the tmp directory */
  if (IRR.tmp_dir != NULL) {
    sprintf (file1, "%s/.%s.db.export.gz", IRR.tmp_dir, database->name);
    sprintf (file2, "%s/.%s.db.export.gz", IRR.ftp_dir, database->name);
    if (irr_copy_file (file1, file2, 0) != 1) {
      trace (TR_ERROR, default_trace, "Export failed! Aborting.\n");
      return (-1); 
    }
  }
  
  /* move .<database>.db.export.gz to <database>.db.gz */
  sprintf (file2, "%s/.%s.db.export.gz", IRR.ftp_dir, database->name);
  if (database->export_filename != NULL) 
    sprintf (new, "%s/%s.db.gz", IRR.ftp_dir, database->export_filename);
  else
    sprintf (new, "%s/%s.db.gz", IRR.ftp_dir, database->name);
  trace (NORM, default_trace, "Atomic move %s -> %s\n", 
	 file2, new);
  if (rename (file2, new) < 0) {
    trace (TR_ERROR, default_trace, "Could not rename file to %s:%s\n", 
	   new, strerror (errno));
    return (-1);
  }

  write_irr_serial_export (serial, database);  
  
  trace (NORM, default_trace, "Database %s copied to export directory\n",
	 database->name);

  return (1);
}


int irr_copy_file (char *infile, char *outfile, int add_eof_flag) {
  char buf[MIRROR_BUFFER];
  FILE *fd1, *fd2;
  int n1, n2;

  trace (NORM, default_trace, "Starting copy of %s to %s\n", infile, outfile);


  if ((fd1 = fopen (infile, "r")) == NULL) {
    trace (TR_ERROR, default_trace, "*ERROR* Could not open %s for reading\n", 
	   infile);
    return (-1);
  }

  if ((fd2 = fopen (outfile, "w")) == NULL) {
    trace (TR_ERROR, default_trace, "*ERROR* Could not open %s for writing\n", 
	   outfile);
    fclose (fd1);
    return (-1);
  }

  while ((n1 = fread (buf, 1, MIRROR_BUFFER, fd1))) {
    if (n1 < 0) {
      trace (TR_ERROR, default_trace, 
	     "Encountered error copying (read failed) %s->%s (%d, %d)\n", 
	     infile, outfile, n1, n2);
      fclose (fd1);
      fclose (fd2);
      return (-1);
    }

    if ((n2 = fwrite (buf, 1, n1, fd2)) != n1) {
      trace (TR_ERROR, default_trace, 
	     "Encountered error copying %s->%s (%d, %d)\n", 
	     infile, outfile, n1, n2);
      fclose (fd1);
      fclose (fd2);
      return (-1);
    }
  }

  /* check feof and make sure no errors */
  if (ferror (fd1)) {
    trace (TR_ERROR, default_trace, "Encountered error copying %s\n", 
	   infile);
    fclose (fd1);
    fclose (fd2);
    return (-1);
  }


  /* write #EOF tag for nice export */
  if (add_eof_flag) {
    char buffer[100];
    sprintf (buffer, "\n# EOF\n\n"); 
    fwrite (buffer, 1, strlen (buffer), fd2); 
  }

  fclose (fd1);
  fclose (fd2);
  return (1);
}
  
