/*
 This software is copyrighted by the University of Pennsylvania.
 Read COPYRIGHT for details.
 */

#include <stdio.h>
#include <sys/types.h>
#include <string.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <sys/resource.h>
#include <signal.h>
#include <sys/dir.h>
#include <sys/stat.h>

#include "pdb.h"
#include "node.h"
#include "messages.h"
#include "network.h"

#include "gophernodes.h"

extern char     *transfields[];
extern int      num_transfields;
extern char     trans_type;
extern char     *msglist[]; /* the list of error messages */
extern char     RESVNODES_FILE[];
extern char     GW_NODES_FILE[];
extern char     GW_NODES_DIR[];
extern char     GW_NODES_BAK_FILE[];
extern unsigned long _allocated;
extern int      strlower(char *str);
extern void     catch_child();
extern int      debug;
extern short    todaysdate;
extern int      next_id;

#define PRIME1   5003
#define PRIME2   1009

static struct s1      *a1[PRIME1];   /* gateway info table */
static struct s2      *a2[PRIME2];   /* nodeid table */
static struct resvnode reservednodes[MAX_RESV_NODES];
static int            numresvnodes = 0;
static int            numgophnodes = 0;
/* static int            time_lastchecked = 0; */
static int            day_lastrmnodes = 0;

int                   tables_changed = 0;
int                   lastused_changed = 0;
int                   addednewnodes = 0;
int                   savedatastruct_pid = 0;

struct resvnode *get_resvnode(long node)
{
 int idx, foundr;

 for (idx = 0, foundr = 0; !foundr && idx < numresvnodes; ) {
   if (reservednodes[idx].nodeid == node)
     foundr = 1;
   else
     idx++;
 }

 if (foundr)
   return ( &(reservednodes[idx]) );
 else
   return ((struct resvnode *) NULL);
}




extern struct s1 *getnode_bynodeid (long nodeid)
{
 struct s2 *nlist;
 int found;
 int h;

 h =  nodeid % PRIME2;
 /* NOT assuming that the lists in a2 are sorted; though it'd be faster */
 for (found=0, nlist = a2[h]; !found && nlist != NULL;) {
   if (nodeid == nlist->nodeid)
     found = 1;
   else
     nlist = nlist->nextnode;
 }

 if (nlist == (struct s2 *) NULL) {
   return (struct s1 *)NULL;
 }
 else {
   return (nlist->node);
 }
}




static int
 ghash(char *gophName, char *gophHost, char *gophPort, char *gophPath)
{
 int sum, i, j;

 strlower (gophHost);

 sum = 0;
 for ( i=0; gophHost[i] ; i++)
   sum += gophHost[i];

 for ( i=0; gophPath[i]; i++)
   sum += gophPath[i];

 for ( i=0; gophName[i]; i++)
   sum += gophName[i];

 return (((sum + atoi(gophPort)) * 15511) % PRIME1);
}



extern struct s1 *getnode_bygopherinfo (char **gopherfields)
{
 int h;
 struct s1 *temp;
 int found;
 char gophtype;
 char *gophpath, *gophserver, *gophport, *gophtitle;

 gophtype = *(gopherfields[0]);
 gophtitle = gopherfields[0] + 1;
 gophpath = gopherfields[1];
 gophserver = gopherfields[2];
 gophport = gopherfields[3];

 found = 0;
 h = ghash(gophtitle, gophserver, gophport, gophpath);

 /*
   For now, assuming that the lists in a1 are NOT sorted.
   To be more efficient, we should make the list be sorted.
 */

 for (temp = a1[h]; !found && temp != NULL; )
   {
     if (strcmp(temp->gopherport, gophport) == 0 &&
         strcmp(temp->gopherserver,gophserver) == 0 &&
         strcmp(temp->gopherpath, gophpath) == 0 &&
         strcmp(temp->gophertitle, gophtitle) == 0 &&
         temp->gophertype == gophtype) {
       found = 1;
     }
     else
       temp = temp->nextnode;
   }

 if (found)
   return (temp);
 else
   return (struct s1 *)NULL;
}


void reservednodes_unload (void)
{
 int n;

 if (debug)
   fprintf (stderr, "Freeing %d reserved nodes\n", numresvnodes);

 for (n = 0; n < numresvnodes; n++) {
   do_free (reservednodes[n].title);
   do_free (reservednodes[n].file);

   if (reservednodes[n].children)
     free (reservednodes[n].children);
   reservednodes[n].numchildren = 0;
 }
 numresvnodes = 0;
}


/*
 MODIFIED make_link_block code from MIT (it had a bug) --lam
 */

static long * make_link_block (char links[], short *linkcnt, int chknodeids)
{
 char      *cp;
 int       numlink;
 long      *nid_block;
 int       chadded;
 int       c, c2;
 long      nid;
 short     repeat;
 short     nodexists;
 struct    s1 *tempnode;

 numlink = *links ? strcnt(links, ',') + 1 : 0;
 nid_block = (long *) domalloc((unsigned int) numlink * sizeof(long));

 cp = links;
 chadded = 0;
 for (c = 0; c < numlink; c++) {
   nid = atol(cp);

   /* if nodeid is not a reserved node and is also not in a1[] table,
      then don't add it */

   if (is_reserved_node (nid))
     nodexists = 1;
   else {
     if (getnode_bynodeid(nid) != (struct s1 *) NULL)
       nodexists = 1;
     else
       nodexists = 0;
   }

   /* no repeats allowed */
   for (repeat = 0, c2 = 0; nodexists && c2 < c && !repeat; c2++)
     if (nid_block[c2] == nid)
       repeat++;

   if (!repeat && nodexists) {
     nid_block[chadded] = nid;
     chadded++;
   }
   /* MIT's BUG: if (repeat) continue -- but it doesn't increment cp */
   cp = index(cp, ',') + 1;
 }
 *linkcnt = chadded;
 /* chadded is better than numlink because of repeats --lam */
 return nid_block;
}




int reservednodes_load(int unloadfirst)
{
 char *resvfields[NUMFIELDS_RESVNODES];
 FILE *resvfile;
 char line[NLINE_MAXLEN];
 int numlines = 0;
 int numf;

 resvfile = fopen (RESVNODES_FILE, "r");

 if (resvfile == NULL) {
   perror (RESVNODES_FILE);
   return 0;
 }

 if (unloadfirst)
   reservednodes_unload();

 fprintf (stderr, "Reading reserved nodes from %s...", RESVNODES_FILE);

 numresvnodes = 0;
 while (fgets (line, NLINE_MAXLEN, resvfile) != NULL) {
   if (*line == '#')
     continue;
   numlines++;

   parsefields (line, resvfields, &numf, DLM, NUMFIELDS_RESVNODES);
   if (numf >= NUMFIELDS_RESVNODES) {
     reservednodes[numresvnodes].nodeid = atoi(resvfields[0]);

     reservednodes[numresvnodes].title =
       (char *) domalloc (strlen(resvfields[1]) + 1);
     strcpy (reservednodes[numresvnodes].title, resvfields[1]);

     reservednodes[numresvnodes].file =
       (char *) domalloc (strlen(resvfields[2]) + 1);
     strcpy (reservednodes[numresvnodes].file, resvfields[2]);

     reservednodes[numresvnodes].children = make_link_block
       (resvfields[3], &(reservednodes[numresvnodes].numchildren), 1);
   }
   else
     fprintf (stderr, "Not enough fields in line %d\n",numlines);
   numresvnodes++;
 }

 fclose (resvfile); /* close file */
 fprintf (stderr, "Done.\nRead %d lines, %d reserved nodes. _allocated=%ld bytes\n", numlines,numresvnodes, _allocated);

 return numresvnodes;
}



/* add node to array 1.  */
static void a1_add (struct s1 *nd)
{
 int h;

 h = ghash (nd->gophertitle, nd->gopherserver, nd->gopherport, nd->gopherpath);
 nd->nextnode = a1[h];
 /* add nd to the beginning of the list */
 a1[h] = nd;
 return;
}




static void a2_add (struct s1 *nd)
{
 struct s2 *nd2;
 int h;

 /* add new node to the beginning of the list */

 nd2 = (struct s2 *) domalloc (sizeof (struct s2));
 nd2->node = nd;
 nd2->nodeid  = nd->nodeid;

 h = nd->nodeid % PRIME2;  /* which list does it belong on? */
 nd2->nextnode = a2[h];
 a2[h] = nd2;

 return;
}




/* allocate memory for node and put initial values in it */
/* If checkexists && the nodeid or gopherinfo exists already,
  NULL is returned */

struct s1 *
init_gopher_node (char **gofields, long nodeid, short lastused, int checkexists)
{
 struct s1 *nd;
 int found;

 if (checkexists) { /*serv,port,path */ /*type-title path server port */
   nd = getnode_bygopherinfo(gofields);

   if (nd != NULL) {
     fprintf (stderr, "Gopher info exists (nodeid %d)\n", nodeid);
     return NULL;
   }
   nd = getnode_bynodeid (nodeid);

   if (nd != NULL) {
     return NULL;
   }
 }

 nd = (struct s1 *) domalloc (sizeof(struct s1));
 nd->nodeid         = nodeid;
 nd->lastused       = lastused;
 nd->gophertype     = *(gofields[0]);
 nd->gophertitle    = (char *)domalloc(strlen(gofields[0])+1);
 nd->gopherpath     = (char *)domalloc(strlen(gofields[1])+1);
 nd->gopherserver   = (char *)domalloc(strlen(gofields[2])+1);
 nd->gopherport     = (char *)domalloc(strlen(gofields[3])+1);

 strcpy (nd->gophertitle,    gofields[0]+1);
 strcpy (nd->gopherpath,     gofields[1]);
 strcpy (nd->gopherserver,   gofields[2]);
 strcpy (nd->gopherport,     gofields[3]);

 a1_add(nd);
 a2_add(nd);

 return nd;
}





struct s1 *load_gopher_node (char **gophernodestr)
{
 long nodeid;
 short lastused;
 char *gofields[NUMFIELDS_GOPHER];
 int numf;

 nodeid = atoi(gophernodestr[0]);
 lastused = atoi(gophernodestr[1]);

 if (is_reserved_node(nodeid)) {
   fprintf (stderr, "load_gopher_node: %d is reserved nodeid\n", nodeid);
   return (struct s1 *) NULL;
 }

 else if (GW_NODE_UNUSED(lastused)) {
   fprintf (stderr, "load_gopher_node: Nodeid %d last used %d, too old.\n",
            nodeid, lastused);
   return (struct s1 *) NULL;
 }

 else {
   parsefields (gophernodestr[2],gofields,&numf,GOPHER_DLM,NUMFIELDS_GOPHER);
   if (numf < NUMFIELDS_GOPHER) { /* gopher info not parseable */
     fprintf (stderr, "Only %d Gopher fields in %s\n",
              numf, gophernodestr[2]);
     return (struct s1 *) NULL;
   }
   else
     return(init_gopher_node(gofields, nodeid, lastused, 1));
 }
}



void gophernodes_unload ()
{
 int i, numunloaded;
 struct s1 *temp1, *temp2;
 struct s2 *temp3, *temp4;

 for (i = 0, numunloaded = 0; i < PRIME1; i++) {
   for (temp1 = a1[i]; temp1 != NULL; temp1 = temp2 ) {
     temp2 = temp1->nextnode;

     /* free the char data as well... */
     do_free(temp1->gophertitle);
     do_free(temp1->gopherserver);
     do_free(temp1->gopherpath);
     do_free(temp1->gopherport);

     free (temp1);
     numunloaded++;
   }
 }

 if (debug)
   fprintf (stderr, "Freed %d gopher nodes\n", numunloaded);

 for (i = 0; i < PRIME2; i++) {
   for (temp3 = a2[i]; temp3 != NULL; ) {
     temp4 = temp3->nextnode;
     free (temp3); /* there's no other data to worry about here */
     temp3 = temp4;
   }
 }

 numgophnodes = 0;
}



int gophernodes_load(int unloadfirst)
{
#define ADAY 86400

 int numf;
 char *gonodefields[NUMFIELDS_GOPHNODES];
 int numlines = 0;
 FILE *gophernodesfile;
 char line[NLINE_MAXLEN];

 gophernodesfile = fopen(GW_NODES_FILE, "r");
 if (gophernodesfile == NULL) {
   perror (GW_NODES_FILE);
   return 0;
 }

 if (unloadfirst)
   gophernodes_unload ();
 init_arrays();

 fprintf (stderr, "Reading gopher nodes from %s...", GW_NODES_FILE);
 addednewnodes = 0;
 tables_changed = 0;
 lastused_changed = 0;

 todaysdate = time(0) / ADAY;
 numgophnodes = 0;
 while (fgets(line, NLINE_MAXLEN, gophernodesfile) != NULL) {
   if (*line == '#')
     continue;
   numlines++;

   parsefields (line, gonodefields, &numf, DLM, NUMFIELDS_GOPHNODES);
   if (numf >= NUMFIELDS_GOPHNODES) {
     if (load_gopher_node (gonodefields))
       numgophnodes++;
   }
   else
     fprintf (stderr, "Not enough fields in line %d\n", numlines);
 }

 fclose (gophernodesfile);
 fprintf (stderr, "Done.\nRead %d lines, %d gopher nodes.  _allocated=%ld\n", numlines, numgophnodes, _allocated);

 return numgophnodes;
}





/* Two arrays of pointers to nodes.  Make them all null to start with */
init_arrays ()
{
 long i;

 for (i = 0; i < PRIME1; i++)
   a1[i] = (struct s1 *) NULL;

 for (i = 0; i < PRIME2; i++)
   a2[i] = (struct s2 *) NULL;
}


/* Load reserved nodes table, gopher nodes tables */
/* maybe add option to load one or the other & not both from disk */
int datastruct_load (int unloadfirst)
{
 int okay;

 okay = gophernodes_load(unloadfirst);
 /*
   get gopher nodes loaded first so that when reserved nodes are loaded,
   no unknown gopher nodes will be added to the reserved table
   */

 if (okay)
   okay = reservednodes_load(unloadfirst);

 get_nextid();
 fprintf (stderr, "Nextid = %d\n", next_id);
 return okay;
}

void cleancache(void)  /* any files whose mtime is older than cache timeout
                         should be removed */
{
 DIR *cachedir;
 struct direct *nextfile;
 struct stat stbuf;
 char filename[MAXPATHLEN];
 long now;
 int numrm;

/*  fprintf (stderr, "Removing old files from %s...", CACHEDIR); */
 cachedir = opendir(CACHEDIR);

 if (cachedir == NULL)  /* cache directory is not being used */
   return;

 now = time (0);
 numrm = 0;
 while ( (nextfile = readdir(cachedir)) != NULL ) {
   if (strcmp (".", nextfile->d_name) != 0 &&
       strcmp ("..", nextfile->d_name) != 0) {
     sprintf (filename, "%s%s", CACHEDIR, nextfile->d_name);
     if (stat(filename, &stbuf) >= 0
         && (now - stbuf.st_mtime) > CACHETIMEOUT) {
       unlink (filename);
       numrm++;
     }
   }
 }
 (int) closedir(cachedir);
 if (numrm)
   fprintf (stderr, "T=%d. Removed %d files from the cache\n", time(0),numrm);
}


/* Safekeeping:

 If an unreserved node _is_ a child of a reserved
 node, then it is safe, even if it hasn't been used
 for a while.

 */

static void get_safekeeping (long **safe, int *numsafe)
{
 int r, c;
 int maxsafe;

 /* first, we figure out the max # of nodes that could be safe */
 for (r = 0, maxsafe = 0; r < numresvnodes; r++)
   maxsafe += reservednodes[r].numchildren;

 *safe = (long *) malloc ( (unsigned int)maxsafe * sizeof(long));
 *numsafe = 0;

 for (r = 0, maxsafe = 0; r < numresvnodes; r++) {
   for (c = 0; c < reservednodes[r].numchildren; c++) {
     if ( ! is_reserved_node(reservednodes[r].children[c] ) ) {
       (*safe)[*numsafe] = reservednodes[r].children[c];
       (*numsafe)++;
     }
   }
 }
}



/* a2 is the nodeid table.
  Using the given nodeid, find the corresponding struct s2
  in the nodeid table and remove it from the table
  */
static void  removenode_nodeidtab (long nodeid)
{
 int h, done;
 struct s2 *nd, *b4, *tmp;

 h = nodeid % PRIME2;

 for (done = 0, b4 = NULL, nd = a2[h]; !done && nd != NULL; ) {
   if (nodeid == nd->nodeid) {
     done = 1;
     if (b4 == NULL)
       a2[h] = nd->nextnode; /* nd was the first on the list */
     else
       b4->nextnode = nd->nextnode;

     tmp = nd;
     nd = nd->nextnode;
     free (tmp);              /* get rid of nodeid node */
   }
   else {
     b4 = nd;
     nd = nd->nextnode;
   }
 }
}


/* remove unused should be improved so that it checks to see if
  todaysdate has changed since the last time nodes were removed
  from the tables because the granularity is a day; it's more
  efficient to avoid checking thousands of nodes unnecessarily */

static void remove_unused(void)
{
 int n;
 struct s1 *node, *b4, *temp;
 long *safelist;
 int numsafe;
 int s, is_safe;
 sigset_t prev_sigmask;
 sigset_t new_sigmask;
 sigset_t temp_sigmask;
 int numremoved = 0;

 if (todaysdate == day_lastrmnodes) {
   return;
 }
 else
   fprintf (stderr, "checking remove_unused (%d != %d)\n", todaysdate, day_lastrmnodes);

 day_lastrmnodes = todaysdate;

 /* block Children from interrupting this delicate stuff */
 sigprocmask (SIG_SETMASK, (sigset_t *)NULL, &prev_sigmask);
 new_sigmask = prev_sigmask | SIGCHLD;
 sigprocmask (SIG_SETMASK, &new_sigmask, NULL);
 /*  sigprocmask (SIG_SETMASK, NULL, &temp_sigmask);
     fprintf (stderr, "Signal mask has been set to %d.\n", temp_sigmask); */

 get_safekeeping(&safelist, &numsafe);
 for (n = 0; n < PRIME1; n++) {
   for (node = a1[n], b4=NULL; node != (struct s1 *) NULL; )
     {
       if ( ! GW_NODE_UNUSED(node->lastused) ) {
         b4 = node;
         node = node->nextnode;
       }
       else {
         for (s = 0, is_safe = 0; s < numsafe && !is_safe; s++)
           if (safelist[s] == node->nodeid) is_safe = 1;

         if ( is_safe ) {
           b4 = node;
           node = node->nextnode;
         }
         else {            /* node should be removed from the tables  */
           fprintf (stderr, "rm %d (%d)\n",
                    node->nodeid, node->lastused);
           numremoved++;
           removenode_nodeidtab (node->nodeid);
           if (b4 == NULL)  /* node was the first on the list */
             a1[n] = node->nextnode;
           else
             b4->nextnode = node->nextnode;
           /* leave b4 as it is... */
           temp = node;
           node = node->nextnode;
           free(temp);       /* Get rid of old node */
         }
       }
     }
 }

 if (safelist)
   free (safelist);

 tables_changed += numremoved;

 /* okay, set the signal mask back to whatever it was */
 sigprocmask (SIG_SETMASK, &prev_sigmask, NULL);
 if (numremoved)
   fprintf (stderr, "Removed %d nodes from tables\n", numremoved);
}


static void save_gwnodes (char *tempfilename)
{
 FILE *tempfile;
 int n;
 int g = 0;
 struct s1 *node;

 tempfile = fopen (tempfilename, "w");
 if (tempfile == NULL) { /* we're in trouble... */
   fprintf (stderr, "Unable to write gopher nodes temp file\n");
   perror (tempfilename);
 }
 else {
   fprintf (stderr, "%d: Saving gateway nodes to tempfile %s\n",
            getpid(), tempfilename);

   for (n = 0; n < PRIME1; n++) {
     for (node = a1[n]; node != (struct s1 *) NULL; node=node->nextnode)
       {/* go down the list writing out the data to temp file */

         /*<nodeid>:<lastu>:<cnt>:<inicnt>:<gtype><gtitle>TAB<gpath>TAB<gserver>TAB<gport>*/
         fprintf(tempfile, "%ld:%d:%c%s\t%s\t%s\t%s\n",
                 node->nodeid, node->lastused,
                 node->gophertype, node->gophertitle, node->gopherpath,
                 node->gopherserver, node->gopherport);
         g++;
       }
     fflush(tempfile);
   }
   fclose (tempfile);

   /* rename() requires the two files to be on the same file system */
   rename(GW_NODES_FILE, GW_NODES_BAK_FILE);
   rename(tempfilename, GW_NODES_FILE);
   chmod(GW_NODES_FILE,0644);
   fprintf(stderr, "%d: wrote %d gopher nodes to disk\n", getpid(), g);
 }
}



/* write gophernodes arrays out to disk */
void save_datastruct ()
{
 char *tempfilename;
 int forkstat;
 union wait status;
 struct rusage rusage;

 (void) cleancache();     /* parent cleans the cache */

 remove_unused();  /* get rid of useless nodeids in the tables */

 if (! tables_changed && ! addednewnodes && ! lastused_changed) {
   return;
 }

 get_nextid();
 fprintf (stderr,
          "T=%d. save_datastruct: added %d newnodes, %d lastused changes, %d table changes, nextid = %d.\n",
          time(0), addednewnodes, lastused_changed, tables_changed, next_id);

 /* Because these variables need to be known by the parent,
    the child cannot update them */
 tables_changed = 0;
 addednewnodes = 0;
 lastused_changed = 0;

 /* generate temporary file name */
 tempfilename = tempnam(GW_NODES_DIR, "gwnodes");
 forkstat = fork();
 if (forkstat != 0)
   signal(SIGCHLD, catch_child);

 if (forkstat <= 0) { /* child or error */
   save_gwnodes (tempfilename);
   if (forkstat == 0) {  /* child */
     exit (0);
   }
 }
 else { /* parent */
   savedatastruct_pid = forkstat;
   fprintf (stderr, "Save_datastruct child is %d.\n", forkstat);
/*    forkstat = wait3(&status, WNOHANG, &rusage); */
   do_free(tempfilename);  /* tempnam called malloc() */
 }
}





void catch_hup(void)
{
 /* print nodes tables */
 int i, j;
 long numnodes, numlists, maxlistlen, listlen;
 struct s1 *nd1;
 struct s2 *nd2;

 maxlistlen = 0;
 numnodes = 0;
 numlists = 0;
 for (i = 0; i < PRIME1; i++) {
   if (a1[i]) {
     fprintf (stderr, "a1[%d]\n", i);
     numlists++;
     listlen = 0;
     for (j=1, nd1 = a1[i];  nd1 != NULL;  j++, nd1 = nd1->nextnode) {
       fprintf (stderr, "%d) %d %ld %ld pa=%s, s=%s, po=%s t=%s\n",
                j, nd1->lastused, nd1->nodeid, nd1,
                nd1->gopherpath, nd1->gopherserver, nd1->gopherport,
                nd1->gophertitle);
       listlen++;
     }
     if (listlen > maxlistlen)
       maxlistlen = listlen;
   }
 }

 fprintf (stderr, "\nNumlists = %d, maxlistlen = %d\n\n", numlists,maxlistlen);

 for (i = 0; i < PRIME2; i++) {
   if (a2[i]) {
     fprintf (stderr, "a2[%d].  ", i);
     for (j=1, nd2 = a2[i];  nd2 != NULL;  j++, nd2 = nd2->nextnode) {
       fprintf (stderr, "%d) %ld %ld %ld\n",
                j, nd2->nodeid, nd2, nd2->node);
     }
   }
 }
}