/* 
 * $Id: scan.c,v 1.26 2002/10/17 20:02:31 ljb Exp $
 * originally Id: scan.c,v 1.87 1998/07/29 21:15:17 gerald Exp 
 */

/* The routines in this file scan/parse the datase.db files */

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "mrt.h"
#include "trace.h"
#include <time.h>
#include <signal.h>
#include "config_file.h"
#include <fcntl.h>
#include "irrd.h"

extern trace_t *default_trace;

/* local functions */
static int populate_keyhash (irr_database_t *database);
static void pick_off_secondary_fields (char *buffer, int curr_f, 
				       irr_object_t *irr_object,
				       enum DB_SYNTAX db_syntax);
void mark_deleted_irr_object (irr_database_t *database, u_long offset);
static void add_field_items (char *buf,
			     enum DB_SYNTAX db_syntax, LINKED_LIST **ll);
static char *build_indexes (FILE *fp, irr_database_t *db, irr_object_t *object, 
			    u_long fp_pos, int update_flag, char *first_attr);
int find_blank_line (FILE *fp, char *buf, int buf_size,
                  enum STATES state, enum STATES *p_save_state,
		  u_long *position, u_long *offset, enum DB_SYNTAX db_syntax);
int dump_object_check (irr_object_t *object, enum STATES state, u_long mode, 
		       int update_flag, irr_database_t *db, FILE *fp);

/* Note: it is not necessary to define every field, only
 * those fields which irrd needs to recognize.
 * col 1 is RIPE181 and col 2 is RPSL 
 *
 * Also note that the data struct 'enum IRR_OBJECTS' defined in
 * scan.h is the first dimension index into this array.  get_curr_f ()
 * returns the current field which acts as the index into this data struct.
 * Any changes to key_info [] or to the 'enum IRR_OBJECTS' data struct
 * need to be coordinated.  m_info [] and obj_template [] also depend
 * on 'enum IRR_OBJECTS'.  See scan.h for more information. */
key_label_t key_info [] [2] = {
  { { "*an:", 4, NAME_F, AUTNUM_F},   { "aut-num:",      8, NAME_F, AUTNUM_F} },
  { { "*rt:", 4, NAME_F, ROUTE_F},    { "route:",        6, NAME_F, ROUTE_F} },
  { { "*am:", 4, NAME_F, ASMACRO_F},  { "",              1, NAME_F, ASMACRO_F} },
  { { "*cm:", 4, NAME_F, COMMUNITY_F},{ "",              1, NAME_F, COMMUNITY_F} },
  { { "*mt:", 4, NAME_F, MNTNER_F},   { "mntner:",       7, NAME_F, MNTNER_F} },
  { { "*ir:", 4, NAME_F, INET_RTR_F}, { "inet-rtr:",     9, NAME_F, INET_RTR_F} },
  { { "*pn:", 4, NAME_F, PERSON_F},   { "person:",       7, NAME_F, PERSON_F} },
  { { "*ro:", 4, NAME_F, ROLE_F},     { "role:",         5, NAME_F, ROLE_F} },
  { { "*is:", 4, NAME_F, IPV6_SITE_F},{ "ipv6-site:",   10, NAME_F, IPV6_SITE_F} },
  { { "*al:", 4, NON_NAME_F, XXX_F},  { "",              1, NON_NAME_F, XXX_F} },
  { { "*in:", 4, NAME_F, INETNUM_F},  { "inetnum:",      8, NAME_F,  INETNUM_F} },
  { { "*or:", 4, NON_NAME_F, XXX_F},  { "origin:",       7, NON_NAME_F, XXX_F} },
  { { "*cl:", 4, NON_NAME_F, XXX_F}, { "",              1, NON_NAME_F, XXX_F } },
  { { "*wd:", 4, NON_NAME_F, XXX_F}, { "withdrawn:",   10, NON_NAME_F, XXX_F } },
  { { "*mb:", 4, NON_NAME_F, XXX_F}, { "mnt-by:",       7, NON_NAME_F, XXX_F  } },
  { { "*ac:", 4, NON_NAME_F, XXX_F}, { "admin-c:",      8, NON_NAME_F, XXX_F } },
  { { "*tc:", 4, NON_NAME_F, XXX_F}, { "tech-c:",       7, NON_NAME_F, XXX_F } },
  { { "*nh:", 4, NON_NAME_F, XXX_F}, { "nic-hdl:",      8, NON_NAME_F, XXX_F} },
  { { "*dn:", 4, NAME_F, DOMAIN_F},  { "domain:",       7, NAME_F, DOMAIN_F } },
  { { "*ue:", 4, NON_NAME_F, XXX_F}, { "*error*:",      8, NON_NAME_F, XXX_F } },
  { { "*uw:", 4, NON_NAME_F, XXX_F}, { "warning:",      8, NON_NAME_F , XXX_F} },
  /* rpsl specific fields */
  { { "",     1, NON_NAME_F, XXX_F}, { "mbrs-by-ref:", 12, NON_NAME_F, XXX_F } },
  { { "",     1, NON_NAME_F, XXX_F}, { "member-of:",   10, NON_NAME_F, XXX_F } },
  { { "",     1, NON_NAME_F, XXX_F}, { "members:",      8, NON_NAME_F, XXX_F } },
  { { "",     1, NON_NAME_F, XXX_F}, { "prefix:",       7, NON_NAME_F, XXX_F } },
  { { "",     1, NON_NAME_F, XXX_F}, { "as-set:",       7, NAME_F, AS_SET_F } },
  { { "",     1, NON_NAME_F , XXX_F},{ "route-set:",   10, NAME_F, ROUTE_SET_F } },
  { { "",     1, NON_NAME_F, XXX_F}, { "contact:",      8, NON_NAME_F, XXX_F } },
  { { "",     1, NON_NAME_F , XXX_F},{ "filter-set:",   11, NAME_F, FILTER_SET_F } },
  { { "",     1, NON_NAME_F , XXX_F},{ "rtr-set:",       8, NAME_F, RTR_SET_F } },
  { { "",     1, NON_NAME_F , XXX_F},{ "peering-set:",  12, NAME_F, PEERING_SET_F } },
  { { "",     1, NON_NAME_F , XXX_F},{ "key-cert:",      9, NAME_F, KEY_CERT_F } },
  { { "",     1, NON_NAME_F , XXX_F},{ "dictionary:",   11, NAME_F, DICTIONARY_F } },
  { { "",     1, NON_NAME_F , XXX_F},{ "repository:",   11, NAME_F, REPOSITORY_F } },
  { { "",     1, NON_NAME_F , XXX_F},{ "inet6num:",      9, NAME_F, INET6NUM_F } },
  { { "",     1, NON_NAME_F , XXX_F},{ "dom-prefix:",   11, NAME_F, DOMAIN_PREFIX_F } },
  /* this should not change, add others before (ie, NO_FIELD row) */
  { { "",1, NON_NAME_F, XXX_F }, { "",      1, NON_NAME_F, XXX_F } },
};

static char *str_syntax[] = {
	"RIPE181",
	"RPSL"
};

#define MAX_RPSLNAME_LEN 32

/* scan_irr_serial
 * read the <DB>.serial file and store the number for later use 
 */
/* JMH - The serial number file is important only for mirrors
 *       and for authoritative databases.  If its not there, starting
 *	 from zero is fine.  This function could happily be void,
 *	 except that we can use it as a existance test for the file
 *	 and that it contains a valid u_long
 */
int scan_irr_serial (irr_database_t *database) {
  char tmp[BUFSIZE], file[BUFSIZE];
  FILE *fp;
  u_long serial = 0;
  int ret_code = 0;

  strcpy (tmp, database->name);
  convert_toupper (tmp);

  sprintf (file, "%s/%s.CURRENTSERIAL", IRR.database_dir, tmp);
  fp = fopen (file, "r");

  if (fp != NULL) {
    memset (tmp, 0, sizeof (tmp));
    if (fgets (tmp, sizeof (tmp), fp) != NULL) {
      if (convert_to_lu (tmp, &serial) == 1) {
	database->serial_number = serial;
	ret_code = 1;
      }
      else
        database->serial_number = 0;
    }
    fclose (fp);
  }

  return ret_code;
}

/* Write to disk the current serial value for DB (db->name).
 *
 * Function was rewritten to return a value to indicate if
 * there were any errors in writing the serial value and also
 * to avoid buffering issues.  This is part of our support for 
 * atomic transactions.
 *
 * Note: Later we should write the current serial value as the
 * first line in the DB in a fixed field.  The original choice to
 * have a seperate current serial file was (IMHO) a poor choice.
 *
 * Input:
 *  -pointer to the DB struct so we know the name of the DB 
 *   and the current serial value to write to disk (db)
 *
 * Return:
 *  -1 if the current serial value was written to disk without error.
 *  -0 otherwise.
 */
int write_irr_serial (irr_database_t *db) {
  int ret_code = 0;
  char dbname[BUFSIZE], file[BUFSIZE], serial[20];
  int fd;

  /* make the current serial file name */
  strcpy (dbname, db->name);
  convert_toupper (dbname);
  sprintf (file, "%s/%s.CURRENTSERIAL", IRR.database_dir, dbname);

  /* now write the current serial to file */
  if ((fd = open (file, O_WRONLY|O_TRUNC|O_CREAT, 0644)) >= 0) {
    sprintf (serial, "%ld", db->serial_number);
    if (write (fd, serial, strlen (serial)) > 0)  {
      SetStatusString (IRR.statusfile, dbname, "currentserial", serial);
      ret_code = 1;
    } else
      trace (ERROR, default_trace, "write_irr_serial (): file write error: (%s)\n",
	     strerror (errno));

    close (fd);
  }
  else
    trace (ERROR, default_trace, "write_irr_serial (): file open error: (%s)\n",
	   strerror (errno));

  return ret_code;
}

void write_irr_serial_export (u_long serial, irr_database_t *database) {
  char db[BUFSIZE], file[BUFSIZE], serial_out[20];
  FILE *fp;

  if (database->export_filename != NULL) 
    strcpy (db, database->export_filename);
  else
    strcpy (db, database->name);
  convert_toupper(db);

  sprintf (file, "%s/%s.CURRENTSERIAL", IRR.ftp_dir, db);
  if ((fp = fopen (file, "w")) != NULL) {
    sprintf (serial_out, "%ld", serial);
    fwrite (serial_out, 1, strlen (serial_out), fp);
    fclose (fp);
  }
  SetStatusString (IRR.statusfile, db, "lastexport", serial_out);
}

/* scan_irr_file
 * Open the db, update, or mirror log file for parsing
 * The update_flag (if == 1 || 2) indicates this is a update/mirror file
 * Determine the syntax (RIPE or RPSL)
 */
char *scan_irr_file (irr_database_t *database, char *extension, 
		     int update_flag, FILE *update_fp) {
  enum DB_SYNTAX syntax;
  FILE *fp = update_fp;
  char file[BUFSIZE], *p;

  /* either an update or reading for the first time (a normal .db file) */
  if (update_flag)
    sprintf (file, "%s/.%s.%s", IRR.database_dir, database->name, extension);
  else {
    sprintf (file, "%s/%s.db", IRR.database_dir, database->name);
    fp = database->db_fp;
  }
  
  /* CL: open file */
  if (fp == NULL) {
     if ((fp = fopen (file, "r+")) == NULL) {
       /* try creating it for the first time */
       trace (NORM, default_trace, "scan.c (%s) does not exist, creating it\n", file);
        fp = fopen (file, "w+");
      
       if (fp == NULL) {
 	trace (ERROR, default_trace, "scan_irr_file () "
	       "Could not open %s (%s)!\n", 
	       file, strerror (errno));
	return "IRRd error: Disk error in scan_irr_file ().  Could not open DB";
      }
    }
    /* the following setvbuf consumes alot of memory, performance does not seem
       to be particularly improved by it.  comment out for now -LJB */
    /* setvbuf (fp, NULL, _IOFBF, IRRD_FAST_BUF_SIZE+2);*/ /* big buffer */
  }

  if (!update_flag)
    database->db_fp = fp;

  if ((syntax = get_database_syntax (fp)) == UNRECOGNIZED) {
      trace (ERROR, default_trace, "Unrecognized DB syntax, reload aborted!\n");
      trace (ERROR, default_trace, "**** DB-(%s)\n", database->name);
      return "Unrecognized DB syntax!";
  }
  else if (syntax == EMPTY_DB) {
    trace (NORM, default_trace, "WARNING: No DB objects found, "
	   "scan complete DB-(%s)!\n", database->name);
    return "No DB objects found!";
  }
  
  if (database->db_syntax != syntax) {
    if (database->db_syntax == EMPTY_DB)
      database->db_syntax = syntax;
    else {
      trace (ERROR, default_trace, "DB syntax mismatch, reload aborted!\n");
      trace (NORM, default_trace, "**** (%s) is type (%s), update file type is"
	     " (%s).\n", database->name, str_syntax[database->db_syntax],
	     str_syntax[syntax]);
      return "DB syntax mismatch!";
    }
  }      

  if (IRR.database_syntax == EMPTY_DB)
    IRR.database_syntax = syntax;
  else if (IRR.database_syntax != MIXED &&
	   IRR.database_syntax != syntax)
    IRR.database_syntax = MIXED;

  if (!update_flag) {
    /* rewind */
    if (fseek (database->db_fp, 0L, SEEK_SET) < 0) {
      trace (ERROR, default_trace, "scan_irr_file () rewind DB (%s) "
	     "error: %s\n", database->name, strerror (errno));
      return "scan_irr_file () rewind DB error.  Abort reload!";
    }
    database->time_loaded = time (NULL);
  }

  trace (NORM, default_trace, "Begin loading %s\n", file);

  /* open transaction journal */
  if (database->journal_fd < 0) {
    char jfile[BUFSIZE];

    make_journal_name (database->name, JOURNAL_NEW, jfile);
    
    if ((database->journal_fd = open (jfile, O_RDWR | O_APPEND| O_CREAT, 0664)) < 0)
      trace (ERROR, default_trace, "scan_irr_file () Could not open "
	     "journal file %s: (%s)!\n", jfile, strerror (errno));
  }
  
  /* if updating, log the serial number in the Journal file */
  if (update_flag)
    journal_maybe_rollover (database);

  if (IRR.key_string_hash == NULL)
    populate_keyhash (database);

  p = (char *) scan_irr_file_main (fp, database, update_flag, SCAN_FILE);

  fflush (database->db_fp);
  trace (NORM, default_trace, "Finished loading %s\n", file);
  return p;
}

/* scan_irr_file_main
 * Parse file looking for objects. 
 * update_flag tell's us if we are parsing a mirror file (2)
 * or !us...!ue update file (1), in contrast to a reload on bootstrap
 * This tells us to look for mirror file format errors
 * and save's us 'check for mirror header' cycles on reloads.
 */
void *scan_irr_file_main (FILE *fp, irr_database_t *database, 
                          int update_flag, enum SCAN_T scan_scope) {
  char buffer[4096], *cp, *p = NULL;
  char first_attr[128];
  u_long save_offset, offset, position, mode;
  irr_object_t *irr_object;
  enum IRR_OBJECTS curr_f;
  enum STATES prev_state, save_state, state;
  hash_spec_t hash_spec_item;
  long lineno = 0;

  /* init everything */
  if (update_flag)
    position = save_offset = offset = (u_long) ftell (fp);
  else
    position = save_offset = offset = 0;

  mode       = IRR_NOMODE;
  state      = BLANK_LINE; 
  curr_f     = NO_FIELD;
  irr_object = NULL;

  if (scan_scope == SCAN_FILE) {
    int hashsize;

    if (update_flag)
      hashsize = SMALL_HASH_SIZE; /* only need a smallish hash for updates */
    else
      hashsize = DEF_HASH_SIZE;
    
    database->hash_spec_tmp = 
      HASH_Create (hashsize, HASH_KeyOffset, 
		   HASH_Offset (&hash_spec_item, &hash_spec_item.key), 
		   HASH_DestroyFunction, Delete_hash_spec, 0);
  }

  /* okay, here we go scanning the file */
  while (state != DB_EOF) { /* scan to end of file */
    if ((cp = fgets (buffer, sizeof (buffer), fp)) != NULL) {
      lineno++;
      position = offset;
      offset += strlen (buffer);
    }

    prev_state = state;
    state = get_state (buffer, cp, state, &save_state);
    
    /*JW This doesn'tallow #'s, +'s at the end of an object
    if (state == COMMENT &&
        prev_state != COMMENT)
      save_offset = position;
    JW*/
    /* dump comment lines and lines that exceed the buffer size */
    if (state & (OVRFLW | OVRFLW_END | COMMENT ))
      continue;

    if (update_flag      &&
	state == START_F && 
	irr_object == NULL) {
      if (!strncmp ("%END", buffer, 4))
        break; /* normal exit from successful mirror */
      else {
	state = pick_off_mirror_hdr (fp, buffer, sizeof(buffer), state, &save_state,
	                             &mode, &position, &offset, database);
	/* something wrong with update, abort scan */
	if (state == DB_EOF) {
	  p = "IRRd error: scan_irr_file_main (): Missing 'ADD' or 'DEL' "
	      "and/or malformed update!";
	  break;
	}
      }
    }

    if (state & (DB_EOF | OVRFLW | OVRFLW_END | BLANK_LINE))
      curr_f = NO_FIELD;
    else if (state != LINE_CONT)
      curr_f = get_curr_f (buffer);  

    /* we have entered into a new object -- create the initial structure */
    if (irr_object == NULL && state == START_F) {
      irr_object = New_IRR_Object (buffer, position, mode);
      /* save a copy of the first attribute for logging purposes */
      strncpy(first_attr, buffer, sizeof(first_attr));
      first_attr[sizeof(first_attr) - 1] = '\0';
    }

    /* Ignore these fields if they come at the end of the object.
     * The trick is to treat the fields like a comment at the end of the
     * object.
     * dump_object_check() will set state = DB_EOF if other error's found 
     */
    if (curr_f == SYNTAX_ERR || curr_f == WARNING) {

      /* this junk should not be in a well-formed transaction */
      if (atomic_trans) {
	p = "IRRd error: scan_irr_file_main (): 'WARNING' or 'ERROR' "
	    "attribute found in update.  Abort transaction!";
	if (irr_object != NULL)
	  Delete_IRR_Object (irr_object);
	break;
      }

      trace (ERROR, default_trace,"In (db,serial)=(%s,%lu) found 'WARNING' "
	     "or '*ERROR*' line, attempting to remove extraneous lines starting with:\n%s", database->name, database->serial_number + 1, buffer);

      /* mark end of object, read past err's and warn's */ 
      save_offset = position; 

      state = find_blank_line (fp, buffer, sizeof(buffer), state, &save_state,
                               &position, &offset, database->db_syntax);
      state = dump_object_check (irr_object, state, mode, update_flag, 
                                 database, fp);
      if (state == DB_EOF) { /* something went wrong, dump object and abort */
          trace (ERROR, default_trace,"Attempt to remove RIPE server extraneous line failed!  Abort!\n");
        Delete_IRR_Object (irr_object);
        irr_object = NULL;
        mode = IRR_NOMODE;
        continue;
      } else
        trace (NORM, default_trace, "Attempt to remove extraneous line succeeded!\n");
    }

    if (curr_f != NO_FIELD && (state & (START_F | LINE_CONT)) ) {

      /* if continuation line, attribute value starts at beginning + 1 */
      if (state == LINE_CONT)
        cp = buffer + 1; /* Ignore initial whitespace or '+' */
      else /* skip over attribute name label */
        cp = buffer + key_info[curr_f][database->db_syntax].len;

      /* NAME_F indicates object class name attribute */
      if (key_info[curr_f][database->db_syntax].f_type == NAME_F) {
        whitespace_remove(cp);
	if (*cp != '\0') { /* class name value may be on a continuation line */
          if (irr_object->name != NULL) {
 	    /* Shouldn't have more than one class name attribute */
            trace (NORM, default_trace, "Warning! Multiple class name attributes: Previous - %s %s,  New - %s %s\n", key_info[irr_object->type][database->db_syntax].name, irr_object->name, key_info[curr_f][database->db_syntax].name, cp );
	  } else {
	    irr_object->name = strdup (cp);
	    irr_object->type = curr_f;
	    irr_object->filter_val = key_info[curr_f][database->db_syntax].filter_val;
	  }
	}
      } else
        /* add secondary keys, and store things like origin and mnt-by */
        pick_off_secondary_fields (cp, curr_f, irr_object, database->db_syntax);

      continue;
    }

    /* Process OBJECT  (we just read a '\n' on a line by itself or eof) */
    if ( (state & (BLANK_LINE | DB_EOF)) && irr_object != NULL) {
      if (scan_scope == SCAN_OBJECT)
	return (void *) irr_object;

      if (curr_f == SYNTAX_ERR || curr_f == WARNING)
	position = save_offset;
      else if (state == DB_EOF)
	position = offset;

      if ((p = build_indexes (fp, database, irr_object, position, update_flag, first_attr)) != NULL) {
        if (!update_flag || (update_flag == 1 && atomic_trans))
	  state = DB_EOF; /* abort scan, something wrong found in input file */
	else
	  p = NULL;	/* ignore errors if mirroring or non-atomic update */
      }

      Delete_IRR_Object (irr_object);
      irr_object = NULL;
      mode = IRR_NOMODE;
    }
  } /* while (state != DB_EOF) */

  /* only do on reload's and mirror updates */
  if (scan_scope == SCAN_FILE) {
    if (p == NULL) 
      commit_spec_hash (database); /* commit if no critical errors */
    HASH_Destroy (database->hash_spec_tmp);
  }
  return (void *) p;	/* return error string (if any) */
}

/* pick_off_secondary_fields
 * store some information like as_origin, communities,
 * and secondary indicie keys
 */
void pick_off_secondary_fields (char *buffer, int curr_f, 
				irr_object_t *irr_object,
				enum DB_SYNTAX db_syntax) {
  char *cp = buffer;
  
  /* rt object origin */
  if (curr_f == ORIGIN) {
    whitespace_newline_remove(cp);
    cp += 2;
    irr_object->origin = (u_short) atoi (cp);
  } else if (curr_f == NIC_HDL) {
    whitespace_newline_remove(cp);
    irr_object->nic_hdl = strdup (cp);
  } else if (curr_f == WITHDRAWN) { /* withdrawn */
    irr_object->withdrawn = 1;
  } else if (db_syntax == RPSL && curr_f == PREFIX) { /* prefix -- IPV6 Site object */
    whitespace_newline_remove(cp);
    LL_Add (irr_object->ll_prefix, ascii2prefix (AF_INET6, cp));
  }
  /* ROUTE and AUT-NUM mnt-by: scanning */
  else if (db_syntax == RPSL && curr_f == MNT_BY)
    add_field_items (cp, db_syntax, &irr_object->ll_mnt_by);

  /* ROUTE and AUT-NUM member-of: scanning */
  else if (db_syntax == RPSL && curr_f == MEMBER_OF)
    add_field_items (cp, db_syntax, &irr_object->ll_mbr_of);

  /* AS-SET, RS-SET members: scanning */
  else if (db_syntax == RPSL && curr_f == MEMBERS)
    add_field_items (cp, db_syntax, &irr_object->ll_as);

  /* AS-SET, RS-SET mbrs-by-ref: scanning */
  else if (db_syntax == RPSL && curr_f == MBRS_BY_REF) 
    add_field_items (cp, db_syntax, &irr_object->ll_mbr_by_ref);

  /* ASMACRO *al: scanning, RIPE181 only */
  else if (db_syntax == RIPE181 && curr_f == AS_LIST)
    add_field_items (cp, db_syntax, &irr_object->ll_as);
}
  
void add_field_items (char *buf, enum DB_SYNTAX db_syntax, LINKED_LIST **ll) {
  char *cp = buf, *last;

  whitespace_newline_remove (cp);
  if (db_syntax == RPSL)
    strtok_r (cp, ",", &last); 
  else

    strtok_r (cp, " ", &last); 
  while (cp != NULL && *cp != '\0') {
    whitespace_remove (cp);

    if (*ll == NULL) /* this way we know if *ll != NULL list is non-empty */
      *ll = LL_Create (LL_DestroyFunction, free, 0);

    /* sanity check */
    if (strlen (cp) > 0) 
      LL_Add ((*ll), strdup (cp));
    
    if (db_syntax == RPSL)
      cp = strtok_r (NULL, ",", &last);
    else
      cp = strtok_r (NULL, " ", &last); 
  }
}

/* JW: note state OVRFLW needs to be fixed so that if it
 * occurs the object is dumped completely.  This is
 * consistent with transaction processing which will
 * come next.  The final step will be to roll-back
 * any objects that were added before the buffer
 * overflow and to report 'transaction abort' as
 * the return code.
 */

/* Given the previous state and the current line to be
 * processed return the next state which corresponds
 * to the current input line.  This state machine is
 * needed by the scan routine to know when it is processing
 * a new attribute so that it can build indexes, where the
 * end of the object is and to detect line continuation.
 * Several other routines also use this function for various
 * sub-tasks.  See scan.h for an explanation of the states.
 *
 * Return:
 *   state associated with the current input line
 * 
 */
int get_state (char *buf, char *cp, 
               enum STATES state, enum STATES *p_save_state) {
  char *p;

  if (cp == NULL) return DB_EOF;

  if (buf[strlen (buf) - 1] != '\n') return OVRFLW;

  /*JW put in later to support buffer overflow processing
  if (state == OVRFLW) {
    if (buf[0] == '\n')     return BLANK_LINE;

    if (buf[0] == '\r' && 
	buf[1] == '\n')     return BLANK_LINE;

    return OVRFLW;
    }*/

  /* can have comments embedded in fields, ignore input after '#' */
  if ((p = strchr (buf, '#')) != NULL) {
    if (p == buf) {         /* line starts with '#', we have a comment line */
      if (state != COMMENT)
	*p_save_state = state; /* save state for when we come out of comment */
      return COMMENT;
    }
    *p = '\0';              /* effectively ignore input at '#' and beyond */
  }

  if (state == COMMENT)
    state = *p_save_state;     /* comment is over, restore old state */

  if (state & (BLANK_LINE | OVRFLW_END)) {
    if (buf[0] == '\n')     return BLANK_LINE;

    if (buf[0] == '\r' && 
	buf[1] == '\n')     return BLANK_LINE;

    /* JMH - these are not really valid in the BLANK_LINE state 
       Will the parser ever pass this in? */

    if (buf[0] == ' '  ||
        buf[0] == '\t' ||
	buf[0] == '+')      return OVRFLW_END;

    return START_F;
  }

  if (state & (START_F | LINE_CONT)) {
    if (buf[0] == '\n')     return BLANK_LINE;

    if (buf[0] == '\r' && 
	buf[1] == '\n')     return BLANK_LINE;

    if (buf[0] == ' '  ||
        buf[0] == '\t' ||
	buf[0] == '+')      return LINE_CONT;

    return START_F;
  }

  return OVRFLW_END;
}

/* get the current field and return it's index (eg, 'mnt-by:' or 'source:') */
int get_curr_f (char *buf) {
  keystring_hash_t *keystring_item;
  char *src, *dst;
  char tmp[MAX_RPSLNAME_LEN + 1];
  int i;

  src = buf;
  dst = tmp;
  i = 0;

  while (i < MAX_RPSLNAME_LEN && *src != '\0') {
   if ( (*dst++ = tolower(*src++)) == ':') /* convert to lowercase and check for colon */
      break;
   i++;
  } 
  *dst = '\0';  /* write a null after the colon */

  keystring_item = HASH_Lookup (IRR.key_string_hash, tmp);

  if (keystring_item == NULL) {
    /* trace (NORM, default_trace,"Warning! Unrecognized attr: %s", buf); */
     return (NO_FIELD);
  }
  return (keystring_item->num);
}

/* read past an object to the first blank line */
int find_blank_line (FILE *fp, char *buf, int buf_size, 
                enum STATES state, enum STATES *p_save_state,
		u_long *position, u_long *offset, enum DB_SYNTAX db_syntax) {
  char *cp;
  
  do {
    if ((cp = fgets (buf, buf_size, fp)) != NULL) {
      *position = *offset;
      *offset += strlen (buf);
    }
    /* } BOGUS for % in vi */

    state = get_state (buf, cp, state, p_save_state);

    /* only dump lines if the ERROR or WARN line come 
     * at the end of the object.
     */
    if (state == START_F) {
      if (strncasecmp (key_info[WARNING][db_syntax].name, buf, 
                  key_info[WARNING][db_syntax].len) &&
          strncasecmp (key_info[SYNTAX_ERR][db_syntax].name, buf, 
                  key_info[SYNTAX_ERR][db_syntax].len)) {
        trace (ERROR, default_trace,"find_blank_line(): Encountered ERROR or WARN line embedded within an object.  Abort scan at following line: %s", buf); 
        return (DB_EOF);
      }
    } 

  } while (state != BLANK_LINE && state != DB_EOF);

  return (state);
}

/* Checks for exactly one blank line past "ADD" or "DEL"
 * and then start of object.  Else something wrong, abort update, eg,
 * ADD
 * 
 * route: 198.108.60.0/24
 *
 */
int read_blank_line_input (FILE *fp, char *buf, int buf_size,
                           enum STATES state, enum STATES *p_save_state,
			   u_long *position, u_long *offset,
			   irr_database_t *database) {
  char *cp = NULL;
  int lineno = 0;
  
  do {
    if ((cp = fgets (buf, buf_size, fp)) != NULL) {
      *position = *offset;
      *offset += strlen (buf);
    }
    /* } BOGUS to make vi % happy */
    lineno++;
  } while ((state = get_state (buf, cp, state, p_save_state)) == BLANK_LINE && lineno < 2);

  if (state == START_F && lineno == 2)
    return (state);
  else
    return (DB_EOF); /* something wrong with input, abort scan */
}

/* Check for an 'ADD' or 'DEL' at the begining of the
 * mirror or update.  Also calls read_blank_line_input ()
 * to check for exactly one blank line and then start
 * of object.
 *
 * Return:
 *   START_F if no errors.
 *   DB_EOF otherwise.
 */
int pick_off_mirror_hdr (FILE *fp, char *buf, int buf_size,
                         enum STATES state, enum STATES *p_save_state,
			 u_long *mode, u_long *position,
			 u_long *offset, irr_database_t *db) {

  if (!strncasecmp ("ADD", buf, 3))  {
    *mode = IRR_UPDATE;
    state = read_blank_line_input (fp, buf, buf_size, state, p_save_state, position, offset, db);
  }
  else if (!strncasecmp ("DEL", buf, 3))  {
    *mode = IRR_DELETE;
    state = read_blank_line_input (fp, buf, buf_size, state, p_save_state, position, offset, db);
  }
  else
    state = DB_EOF; /* no "ADD" or "DEL" so abort scan */

  if (state == DB_EOF) {
    trace (ERROR, default_trace,"scan.c: pick_off_mirror_hdr(): abort scan\n");
    trace (ERROR, default_trace,"line (%s)\n", buf);
  }

  return (state);
}

char *build_indexes (FILE *fp, irr_database_t *db, irr_object_t *object, 
                     u_long fp_pos, int update_flag, char *first_attr) {
  u_long db_offset = 0;
  int del_obj = -1;
  int skip_obj = 0;
  char *p = NULL;

  /* good to have these checks so our scanner
     can deal with junk objects */

  if (update_flag && object->mode == IRR_NOMODE) {
    trace (ERROR, default_trace, "got object with no "
	   "ADD or DEL during udpate, obj key-(%s), db-(%s)\n", 
	   object->name, db->name); 
    return "Corrupted update file.  Missing 'ADD' or 'DEL'";   
  }

  if ( object->name == NULL ||     /* can get *XX: objects on db loads, */
       object->type == NO_FIELD) { /* these obj's have NULL names */
    if (update_flag) {       /* log unrecognized object classes on updates  */ 
      trace (NORM, default_trace, "Unknown object class - %s", first_attr); 
      p = "Unrecognized object class";
    }
    skip_obj = 1;
  }

  if (db->obj_filter & object->filter_val) {
     skip_obj = 1;	/* skip object if it should be filtered */
     /*  trace (NORM, default_trace, "Filtered object class - %s", first_attr);  */
  }

  object->len = fp_pos - object->offset;
  object->fp = fp;

  if (!skip_obj) {
    switch (object->mode) {
    case IRR_NOMODE:     /* reading .db file for first time */
      add_irr_object (db, object);
      break;

    case IRR_DELETE:
      if (!update_flag) { /* db file has a "DEL" in it, abort scan */
	trace (ERROR, default_trace, "DEL found in DB file, obj key-(%s)\n", object->name);
	return "DB file has a 'DEL'";
      }

      if ((del_obj = delete_irr_object (db, object, &db_offset)) > 0) {
	db->num_objects_deleted[object->type]++;
	db->num_changes++;
      }
      else if (update_flag == 1) { /* !us...!ue udpate = 1, mirror update = 2 */
	p = "Disk or indexing error! IRR_DELETE";
	trace (ERROR, default_trace, 
	       "DELETE disk or indexing error: key (%s)\n", object->name);
	return (p);
      }
      break;

    case IRR_UPDATE: /* implicit replace from mirror or update */
      if (!update_flag) { /* db file has an "ADD" in it, abort scan */
	trace (ERROR, default_trace,"ADD found in DB file, obj key-(%s)\n", object->name);
	return "DB file has a 'ADD'.  Abort!";
      }

      del_obj = delete_irr_object (db, object, &db_offset);
      if (add_irr_object (db, object) > 0) {
	db->num_objects_changed[object->type]++;
	db->num_changes++;
      }
      else {
	p = "Disk or indexing error! IRR_UPDATE: add_irr_object ()";
	trace (ERROR, default_trace, 
	       "UPDATE disk or indexing error: key (%s)\n", object->name);
      }
      break;

    default:
      trace (ERROR, default_trace,
	     "unrecognized mode (%d) obj key-(%s)\n", 
	     object->mode, object->name);
      return "Something wrong with file, no mode specified!";
    }
  }

  if (update_flag) {
    if (del_obj > 0)
      mark_deleted_irr_object (db, db_offset);
    if (!atomic_trans || update_flag != 1)
      journal_irr_update (db, object, object->mode, skip_obj);
  }

  return p;
}

/* An *ERROR* or WARNING line has been found, now see if the scan should
 * continue.  Also journal the update if scan continues.  We dump objects
 * with said fields and continue scanning if all these checks pass.
 */
int dump_object_check (irr_object_t *object, enum STATES state, u_long mode,
                       int update_flag, irr_database_t *database, FILE *fp) {
  
  if (mode == IRR_NOMODE) {
    if (update_flag)
      trace (ERROR, default_trace,"Missing 'ADD' or 'DEL' in mirror file "
	     "(%s), exit from dump object!\n", database->name);
    else
      trace (ERROR, default_trace,"'WARNING' or '*ERROR*' found in DB file "
	     "(%s), exit from dump object!\n", database->name);
    state = DB_EOF;
  }
  else if (!update_flag) {
    trace (ERROR, default_trace,"Serial update and 'WARNING' or '*ERROR*' "
	   "found in file (%s), exit from dump object!\n", database->name);
    state = DB_EOF;
  }
  else if (state == DB_EOF) { /* causes us to skip over bad obj, next mirror
                               * will be able to run */
    object->fp = fp;
    journal_irr_update (database, object, mode, 1);
  }

  return (state);
}

static int populate_keyhash (irr_database_t *database) {
  keystring_hash_t keystring_item;
  int i;

  IRR.key_string_hash =
    HASH_Create (100, HASH_KeyOffset, HASH_Offset (&keystring_item, &keystring_item.key), 0);

  /* populate */
    for (i = 0; i < IRR_MAX_KEYS; i++) {
      keystring_hash_t *keystring_item;

      if (strlen (key_info[i][database->db_syntax].name) <= 0) continue;
      keystring_item = New (keystring_hash_t);
      keystring_item->key = strdup (key_info[i][database->db_syntax].name);
      keystring_item->num = i;
      HASH_Insert (IRR.key_string_hash, keystring_item);
    }
    return (1);
}


syntax highlighted by Code2HTML, v. 0.9.1