/*
 * $Id: irrd_util.c,v 1.12 2002/10/17 20:02:30 ljb Exp $
 * originally Id: util.c,v 1.51 1998/08/07 19:48:58 gerald Exp 
 */

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

static int find_token (char **, char **);
extern key_label_t key_info[][2];
extern trace_t *default_trace;
extern m_command_t m_info [];

irr_database_t *new_database (char *name) {
  irr_database_t *database;
  hash_item_t hash_item;

  database = New (irr_database_t);
  memset(database, 0, sizeof(irr_database_t));

  database->radix = New_Radix (128);
  database->hash =
    HASH_Create (DEF_HASH_SIZE,
                 HASH_KeyOffset, HASH_Offset (&hash_item, &hash_item.key),
                 HASH_DestroyFunction, irr_hash_destroy,
                 0);
  database->hash_spec =
    HASH_Create (DEF_HASH_SIZE,
                 HASH_KeyOffset, HASH_Offset (&hash_item, &hash_item.key),
                 HASH_DestroyFunction, irr_hash_destroy, 0);

  database->name = strdup (name);
  database->mirror_fd  = -1;
  database->journal_fd = -1;
  database->max_journal_bytes = IRR_MAX_JOURNAL_SIZE;
  database->db_fp         = NULL;
  database->db_syntax  = EMPTY_DB;
  database->obj_filter = 0; /* Any object bit-fields that are 1 will be filtered out
                               * of the DB (including mirroring, updates and reloads).
                               */
  pthread_mutex_init (&database->mutex_lock, NULL);
  pthread_mutex_init (&database->mutex_clean_lock, NULL);
  /*rwl_init (&database->rwlock);*/
  return(database);
}

/* find_database
 * Given the name of a database (e.g. "mae-east") return a pointer to
 * the irr_database_t sructure, or NULL if it does not exist
 */
irr_database_t *find_database (char *name) {
  irr_database_t *db;
  int len;
  
  LL_Iterate (IRR.ll_database, db) {
    len = strlen (db->name);
    if (strlen (name) > len)
      len = strlen (name);

    if (!strncasecmp (db->name, name, len))
      return (db);
  }
  return (NULL);
}

/* irr_lock_all
 * Lock down all IRR database used by this IRR connection
 */
void irr_lock_all (irr_connection_t *irr) {
  irr_database_t *database;

  /* Avoid deadlock, only 1 routine can get all locks at one time */
  trace (TRACE, default_trace, "About to lock  --lock_all_mutex_lock--\n");
  if (pthread_mutex_lock (&IRR.lock_all_mutex_lock) != 0)
    trace (ERROR, default_trace, "Error locking --lock_all_mutex_lock--: %s\n", 
	   strerror (errno));
  else
    trace (TRACE, default_trace, "Locked --lock_all_mutex_lock--\n");

  LL_ContIterate (irr->ll_database, database) {
    irr_lock (database);
  }

  if (pthread_mutex_unlock (&IRR.lock_all_mutex_lock) != 0)
    trace (ERROR, default_trace, "Error unlocking --lock_all_mutex_lock--: %s\n",
           strerror (errno));
  else
    trace (TRACE, default_trace, "Unlocked --lock_all_mutex_lock--\n");

  trace (TRACE, default_trace, "Done with lock_all\n");
}

/* irr_unlock_all
 * Unlock down all IRR database used by this IRR connection
 */
void irr_unlock_all (irr_connection_t *irr) {
  irr_database_t *database;

  LL_ContIterate (irr->ll_database, database) {
    irr_unlock (database);
  }
}

void irr_update_lock (irr_database_t *database) {
  irr_clean_lock(database);
  irr_lock(database);
}

void irr_update_unlock (irr_database_t *database) {
  irr_unlock(database);
  irr_clean_unlock(database);
}

void irr_clean_lock (irr_database_t *database) {
  if (pthread_mutex_lock (&database->mutex_clean_lock) != 0)
    trace (ERROR, default_trace, "Error locking clean database %s : %s\n", 
	   database->name, strerror (errno));
}

void irr_clean_unlock (irr_database_t *database) {
  if (pthread_mutex_unlock (&database->mutex_clean_lock) != 0)
    trace (ERROR, default_trace, "Error unlocking clean database %s : %s\n", 
	   database->name, strerror (errno));
}

void irr_lock (irr_database_t *database) {
  /*if (rwl_writelock (&database->rwlock) != 0)*/
  if (pthread_mutex_lock (&database->mutex_lock) != 0)
    trace (ERROR, default_trace, "Error locking database %s : %s\n", 
	   database->name, strerror (errno));
}

void irr_unlock (irr_database_t *database) {
  if (pthread_mutex_unlock (&database->mutex_lock) != 0)
  /*if (rwl_writeunlock (&database->rwlock) != 0) */
    trace (ERROR, default_trace, "Error unlocking database %s : %s\n", 
	   database->name, strerror (errno));
}

void Delete_RT_Object (irr_route_object_t *attr) {
  Delete (attr);
}

int irr_comp (char *s1, char *s2) {
  return (strcmp (s1, s2));
}

int get_prefix_from_disk (FILE *fp, u_long offset, char *buffer) {
  char *p, temp[BUFSIZE], *last; 

  if (fseek (fp, offset, SEEK_SET) < 0) 
    trace (NORM, default_trace, "** Error ** fseek failed in get_prefix");
    
  if (fgets (temp, sizeof (temp) - 1, fp) != NULL) {
    if (strtok_r (temp, " ", &last) != NULL && 
	(p = strtok_r (NULL, " ", &last)) != NULL) {
      if (*(p + strlen (p) - 1) ==  '\n')
        *(p + strlen (p) - 1) = '\0';
      strcat (buffer + strlen (buffer), p);
      return 1;
    }
    else 
      trace (NORM, default_trace, 
	     "ERROR -- get_prefix_from_disk(): bad route field (%s)\n", temp);
  }
  else
    trace (NORM, default_trace, 
	   "ERROR -- get_prefix_from_disk(): offset is bad %lu\n", offset);

  return -1;
}

/* copy_irr_object
 * Copy an object from one <DB>.db file to another
 * this is used in updates and in resyching database
 */
long copy_irr_object (FILE *src_fp, long offset, irr_database_t *database,
                     u_long obj_length) {
  char buffer[BUFSIZE];
  long start_offset = 0;
  int bytes_read   = 0;
  int i;

  if ( database->db_fp == NULL )
    return -1L;
  
  if ((fseek (src_fp, offset, SEEK_SET) < 0) ||
      (fseek (database->db_fp, 0, SEEK_END) < 0)) 
    trace (NORM, default_trace, "** Error ** fseek failed in copy_irr_object");
  start_offset = ftell (database->db_fp);

  bytes_read = 0;
  while (fgets (buffer, sizeof (buffer)-1, src_fp) != NULL) {
    if ((i = strlen (buffer)) < 2) break;

    if (bytes_read < obj_length) {
      fwrite (buffer, 1, (size_t) i, database->db_fp);
      database->bytes += i;
    }
     bytes_read += i;
  }
  sprintf (buffer, "\n");
  fwrite (buffer, 1, strlen (buffer), database->db_fp);
  database->bytes += strlen (buffer);

  return start_offset;
}

/* Delete_IRR_Object
 * Delete generic object used to hold data during scanning
 */
void Delete_IRR_Object (irr_object_t *object) {
/* ll_as and ll_mbr_by_ref are saved as data for the hash */
  if (object->name) 
    Delete (object->name);
  if (object->nic_hdl)
    Delete (object->nic_hdl);
  if (object->ll_mnt_by) 
    LL_Destroy (object->ll_mnt_by);
  if (object->ll_as) 
    LL_Destroy (object->ll_as);
  if (object->ll_mbr_by_ref) 
    LL_Destroy (object->ll_mbr_by_ref);
  if (object->ll_mbr_of) 
    LL_Destroy (object->ll_mbr_of);
  if (object->ll_prefix) 
    LL_Destroy (object->ll_prefix);
  Delete (object);
}

/* New_IRR_Object
 * Create generic object used to hold data during scanning
 */
irr_object_t *New_IRR_Object (char *buffer, u_long position, u_long mode) {
  irr_object_t *irr_object;

  irr_object = New (irr_object_t);
  irr_object->offset = position;
  irr_object->mode = mode;
  irr_object->type = NO_FIELD;
  irr_object->ll_prefix = LL_Create (LL_DestroyFunction, free, 0);
  irr_object->filter_val = XXX_F;

  return (irr_object);
}

void Delete_Ref_keys (reference_key_t *ref_item) {
  Delete (ref_item->key);
  Delete (ref_item);
}

void foldin_key_list (LINKED_LIST **ll, char *key, int type) {
  int found;
  reference_key_t *ref_item;

  if (key == NULL)
    return;

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

  found = 0;
  LL_ContIterate ((*ll), ref_item) {
    if (!strcmp (ref_item->key, key)) {
      found = 1;
      break;
    }
  }

  if (!found) {
    ref_item = New (reference_key_t);
    ref_item->key = strdup (key);
    ref_item->type = type;
    LL_Add ((*ll), ref_item);
  }
}

void pick_off_indirect_references (irr_answer_t *irr_answer, LINKED_LIST **ll) {
  char *cp, buf[BUFSIZE];
  enum STATES state  = BLANK_LINE, save_state;
  int curr_f = NO_FIELD;
    
  if (irr_answer->type == ROUTE || irr_answer->type == PERSON)
    return;

  if (irr_answer->len == 0 ||
      fseek (irr_answer->fp, irr_answer->offset, SEEK_SET) < 0)
    return;

  do {
    cp = fgets (buf, BUFSIZE - 1, irr_answer->fp);

    if ((state = get_state (buf, cp, state, &save_state)) == START_F) {
      curr_f = get_curr_f (buf);

      /* all fields here must be *single valued*, else code won't work */
      if ((irr_answer->type == IPV6_SITE && 
	   (curr_f == PREFIX || 
	    curr_f == CONTACT)) ||
	  (curr_f == ADMIN_C || curr_f == TECH_C)) {
	cp = buf + key_info[curr_f][irr_answer->db_syntax].len;
	whitespace_newline_remove (cp);
	foldin_key_list (ll, cp, curr_f);
      }
    }
  } while (state != BLANK_LINE && state != DB_EOF);

  return;
}

void lookup_object_references (irr_connection_t *irr) {
  irr_answer_t *irr_answer;
  LINKED_LIST *ll = NULL;
  reference_key_t *ref;

  LL_ContIterate (irr->ll_answer, irr_answer)
    pick_off_indirect_references (irr_answer, &ll);
  
  if (ll) {
    LL_ContIterate (ll, ref) {
      /*
printf("JW: indirect key lookup-(%s,%d)\n",ref->key,ref->type);
*/
      irr_database_find_matches (irr, ref->key, SECONDARY, 
				 RAWHOISD_MODE, ref->type, NULL, NULL);
    }
    LL_Destroy (ll);
  }
}

void lookup_route_exact (irr_connection_t *irr, char *key) {
  irr_database_t *database;
  u_long offset, len = 0;
  char *last, *prefix, *str_orig;
  u_short origin;

  while (*key != '\0' && isspace ((int) *key)) key++;
  if (*key == '\0')
    return;

  if (strchr (key, '-') != NULL)
    prefix = strtok_r (key, "-", &last);
  else 
    prefix = strtok_r (key, " ", &last);

  if (prefix == NULL)
    return;

  if ((str_orig = strtok_r (NULL, " ", &last)) == NULL)
    return;

  while (*str_orig != '\0' && !isdigit ((int) *str_orig)) str_orig++;
  if (*str_orig == '\0')
    return;

  origin = (u_short) atoi (str_orig);
  LL_Iterate (irr->ll_database, database) {
    if (seek_route_object (database, prefix, origin, &offset, 
			   &len, irr->withdrawn) > 0)
      break;
  }

  if (len > 0)
    irr_build_answer (irr, database->db_fp, ROUTE, offset, len, NULL, database->db_syntax);
}

/* convert a string to an unsigned long
 * return 1 if no errors found
 * otherwise return -1
 */
int convert_to_lu (char *strval, u_long *uval) {
  char *p;
  u_long d;
  
  /* see if strval points to anything */
  if (strval == NULL)
    return (-1);

  /* make sure the value is in range and has non-zero digits */
  d = strtoul (strval, &p, 10);  
  if (d == ULONG_MAX || p == strval)
    return (-1);

  *uval = d;
  return (1);
}

irr_hash_string_t *new_irr_hash_string (char *str) {
  irr_hash_string_t *tmp;
  tmp = New (irr_hash_string_t);
  tmp->string = strdup (str);
  return (tmp);
}

void delete_irr_hash_string (irr_hash_string_t *str) {
  Delete (str->string);
  Delete (str);
}

/* This routine finds a token in the string.  *x will
 * point to the first character in the string and *y will
 * point to the first character after the token.  A token
 * is a printable character string.  A '\n' is not considered
 * part of a legal token.  
 * Invoke this routine by setting 'x' and 'y' to the beginning
 * of the target string like this: 'x = y = target_string;
 * if (find_token (&x, &y) > 0) ...
 * each successive call to find_token () will move the pointer
 * along.
 *
 * Return:
 *   length if a token is found (*x points to token, *y first token after)
 *  -1 if no token is found (*x and *y are to be ignored)
 */
int find_token (char **x, char **y) {

  /* It's possible the target string is NULL 
   * or we are in a rpsl comment 
   */
  if (*x == NULL)
    return -1;

  /* find the first non-blank character */
  *x = *y;
  while (**x != '\0' && (**x == ' ' || **x == '\t' || **x == '\n')) (*x)++;

  if (**x == '\0')
    return -1;

  /* find the first space at the end of the token */
  *y = *x + 1;
  while (**y != '\0' && isgraph ((int) **y)) (*y)++;

  return (*y - *x);  
}

/*
 * -i performs inverse lookups on the given attribute name.
 * Return 1 if attribute name valid/supported, else return -1.
 */
int ripe_inverse_attr (irr_connection_t *irr, char **cp) {
  int attrname_len;
  char *p, *q;

  p = q = *cp;
  if ( (attrname_len = find_token (&p, &q)) < 0)
    return attrname_len;
  *cp = q;

  /* We only support the mnt-by attribute for inverse lookups
     for now, so lets just do a quick dirty check */
  if ( attrname_len == 6 && !strncasecmp (p, "mnt-by", 6)) {
    irr->inverse_type = MNT_BY;
    return 1;
  } 

  return -1;
}

/*
 * -s flag has been found, now return the token after the '-s'.
 * Return 1 if something is found after the -s, else return -1.
 */
int ripe_set_source (irr_connection_t *irr, char **cp) {
  int sources_len;
  char *p, *q;

  p = q = *cp;
  if ( (sources_len = find_token (&p, &q)) < 0)
    return sources_len;
  *cp = q;

  if (sources_len >= RIPE_SOURCES_SZ)
    return -1;

  strncpy (irr->ripe_sources, p, sources_len);
  irr->ripe_sources[sources_len] = '\0';

  return 1;
}

/*
 * convert a "<object type>" in a '-t' flag from string to enum IRR_OJBECT.
 * return 1 if a valid object type is found, else return -1;
 */
int ripe_obj_type (irr_connection_t *irr, char **cp) {
  int i, j, slen, ret_code = -1;
  char *p, *q;

  p = q = *cp;
  if ((slen = find_token (&p, &q)) < 0)
    return slen;
  *cp = q;

  for (i = 0; i < IRR_MAX_MCMDS; i++) {
    j = strlen (m_info[i].command) - 1;
    if (slen == j &&
        !strncasecmp (p, m_info[i].command, j)) {
      irr->ripe_type = m_info[i].type;
      ret_code = 1;
      break;
    }
  }

  return ret_code;
}

void ignore_flag_param (irr_connection_t *irr, char **cp) {

  while (**cp != '\0' && isgraph ((int) **cp)) (*cp)++;
}

/*
 * Parse the ripe whois command line and set the corresponding flag
 * (and flag value if necessary).
 * This routine moves the cursor pointer used by the ripe command
 * processing routine.
 *
 * RETURN values:
 *
 *   return 1 if no flags encountered or supplied flags were
 * parsed without error.
 *
 *   return -1 if an error is encountered parsing
 * a flag. 
 */
int parse_ripe_flags (irr_connection_t *irr, char **cp) {
  int non_flag_token;
  char buf[BUFSIZE];
  char *p = *cp; /* save pointer to input line for trace output */

  /* buffer for error messages */
  buf[0] = '\0';

  for (non_flag_token = 0; **cp != '\0'; (*cp)++) {
    if (**cp == '-') {
      (*cp)++;
      switch (**cp) {
      case 'F': irr->ripe_flags |= FAST_OUT;    break;
      case 'r': irr->ripe_flags |= RECURS_OFF;  break;
      case 'l': irr->ripe_flags |= LESS_ONE;    break;
      case 'L': irr->ripe_flags |= LESS_ALL;    break;
      case 'm': irr->ripe_flags |= MORE_ONE;    break;
      case 'M': irr->ripe_flags |= MORE_ALL;    break;
      case 'a': irr->ripe_flags |= SOURCES_ALL; break;
      case 'i': (*cp)++;
                if (ripe_inverse_attr (irr, cp) > 0)
                  irr->ripe_flags |= INVERSE_ATTR;  
                else {
                  strcpy (buf, "%% Attribute name after \"-i\""
			  " is invalid or unsupported.\n");
                  non_flag_token = 1;
                }
                break;
      case 's': (*cp)++;
                if (ripe_set_source (irr, cp) > 0)
                  irr->ripe_flags |= SET_SOURCE;  
                else {
                  strcpy (buf, "%% DB source after \"-s\""
			  " flag is too long or invalid.\n");
                  non_flag_token = 1;
                }
                break;
      case 't': (*cp)++;
                if (ripe_obj_type (irr, cp) > 0)
                  irr->ripe_flags |= TEMPLATE;
                else
		  strcpy (buf, "%%  Required object type not specified after \"-t\" flag or unrecognized type.\n");
		non_flag_token = 1;
                break;
      case 'T': (*cp)++;
                if (ripe_obj_type (irr, cp) > 0)
                  irr->ripe_flags |= OBJ_TYPE;
                else {
		  strcpy (buf, "%%  Required object type not specified after \"-T\" flag or unrecognized type.\n");
		  non_flag_token = 1;
		}
		break;
      case 'V': ignore_flag_param (irr, cp);
                break;
      default : non_flag_token = 1;             
                sprintf (buf, "%%  Unrecognized flag \"-%c\".\n", **cp);
	        trace (NORM, default_trace, 
	        "ERROR:parse_ripe_flags(): unrecognized flag '%c'\n", **cp);
	        trace (NORM, default_trace, 
	        "ERROR:parse_ripe_flags(): rest of input line (%s)", p);
		(*cp)--;
		break;
      }
    }
    else if (**cp == ' ' ||
	     **cp == '\t')
      continue;
    else 
      non_flag_token = 1;

    if (non_flag_token)
      break;
  }

  if (buf[0] != '\0') { /* Got an error, tell the user */
    irr_write (irr, buf, strlen (buf));
    irr_write_buffer_flush (irr);
    return -1;
  }

  return 1;
}

void IRRD_Delete_Node (radix_node_t *node) {
  LINKED_LIST *ll_attr;

  ll_attr = (LINKED_LIST *) node->data;
  if (ll_attr) LL_Destroy (ll_attr);

  if (node->prefix)
    Delete_Prefix (node->prefix);
  Delete (node);
}

radix_str_t *new_radix_str (radix_node_t *node) {
  radix_str_t *tmp;
  tmp = New (radix_str_t);
  tmp->ptr = node;
  return (tmp);
}

void delete_radix_str (radix_str_t *str) {
  IRRD_Delete_Node (str->ptr);
  Delete (str);
}

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

  if (radix_tree == NULL)
    return;

  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);
}

void convert_toupper(char *_z) {
  while (*_z != '\0') {
    *_z = toupper((int) *_z);
    _z++;
  }
}

/* convert time_t to something more readable */
void nice_time (long seconds, char *buf) {

  if (seconds <= 0) {
    sprintf (buf, "--:--:--");
    return;
  }

  if (seconds < 200) {
    sprintf (buf, "%ld seconds", seconds);
    return;
  }

  if (seconds / 3600 > 99)
    sprintf (buf, "%02lddy%02ldhr", 
	     seconds / (3600 * 24), (seconds % (3600 * 24)) / 3600);
  else
    sprintf (buf, "%02ld:%02ld:%02ld", 
	     seconds / 3600, (seconds / 60) % 60, seconds % 60);

  return;
}

/* irr_sort_database  
 * A real hack to alphabetically sort databases after reading the config file
 * We need to convert linked_list types as the sorting code for intrusive
 * is broken. Easier to do this than fix the sorting code...
 *
 */

static int compare_db (irr_database_t *db1, irr_database_t *db2) {
   return (strcmp (db1->name, db2->name));
 }

void irr_sort_database () {
  LINKED_LIST *ll_tmp;
  irr_database_t *db = NULL;

  ll_tmp = LL_Create (LL_CompareFunction, compare_db, NULL);
  LL_Iterate (IRR.ll_database, db) {
    LL_Add (ll_tmp, db);
  }
  LL_Sort (ll_tmp);
  /*LL_Clear (IRR.ll_database_alphabetized);*/
  db = NULL;
  LL_Iterate (ll_tmp, db) {
    LL_Add (IRR.ll_database_alphabetized, db);
  }

  LL_Destroy (ll_tmp);
}

/* Send a message to the user and get a yes or no response.
 * Terminate execution if user replies 'y' or 'Y' to the message.
 *
 * Anything other than 'n' or 'N' is considered a 'yes'
 * response.
 *
 * Input:
 *  -a message to send to the user (msg)
 *
 * Return:
 *  -void
 */
void interactive_io (char *msg) {
  char buf[BUFSIZE+1];
  
  /* interactive input from the user */
  trace (NORM, default_trace, "%s [y/n]\n", msg);
  printf ("%s [y/n] ", msg);
  buf[0] = '\0';
  fgets (buf, BUFSIZE, stdin);

  if (buf[0] == 'n' || buf[0] == 'N') {
    trace (NORM, default_trace, "Terminating execution at user's request.\n");
    printf ("Terminating execution at user's request.\n");
    exit (0);
  }

  trace (NORM, default_trace, "Continuing execution.\n");
  printf ("Continuing execution.\n");
}


/* Perform basic dir tests and return a textual
 * description of anything we find wrong such as
 * insufficient permissions.
 *
 * Results will tell if (dir) exists and if we have 
 * read/write permissions.  Function will try to
 * create (dir) if it doesn't exist and the
 * (creat_dir) flag is set.
 *
 * Input:
 *  -name of the dir to check (dir)
 *  -flag to indicate if (dir) should be created if it 
 *   doesn't exist
 *
 * Return:
 *  -a text string describing some problem
 *  -NULL otherwise
 */
char *dir_chks (char *dir, int creat_dir) {
  char file[BUFSIZE+1];
  FILE *fp;

  /* Sanity checks */
  if (dir == NULL)
    return strdup ("No database directory specified!");

  if (strlen (dir) > BUFSIZE)
    return strdup ("Database directory name is too long! MAX chars (BUFSIZE)");
  
  /* see if we can create a temp file in the directory */
  snprintf (file, BUFSIZE, "%s/cache-test.%d", dir, (int) getpid ());
  if ((fp = fopen (file, "w+")) == NULL) {

    /* dir does not exist, try to make it */
    if (creat_dir &&
	errno == ENOENT) {
      if (!mkdir (dir, 00755))
	return NULL;
    }

    /* we have permission problems or ... */
    snprintf (file, BUFSIZE, 
	      "Could not create the directory: %s", strerror (errno));
    return strdup (file);
  }
  else {
    fclose (fp);
    remove (file);
  }

  return NULL;
}


syntax highlighted by Code2HTML, v. 0.9.1