/******************************************************************************
*                                                                             *
*   Copyright 2005 University of Cambridge Computer Laboratory.               *
*                                                                             *
*   This file is part of Nprobe.                                              *
*                                                                             *
*   Nprobe is free software; you can redistribute it and/or modify            *
*   it under the terms of the GNU General Public License as published by      *
*   the Free Software Foundation; either version 2 of the License, or         *
*   (at your option) any later version.                                       *
*                                                                             *
*   Nprobe is distributed in the hope that it will be useful,                 *
*   but WITHOUT ANY WARRANTY; without even the implied warranty of            *
*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the             *
*   GNU General Public License for more details.                              *
*                                                                             *
*   You should have received a copy of the GNU General Public License         *
*   along with Nprobe; if not, write to the Free Software                     *
*   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA *
*                                                                             *
******************************************************************************/


#include <string.h>
#include <fcntl.h>
#include <sys/time.h>
#include <limits.h>
#include <unistd.h>
#include <sys/resource.h>
#include <sys/mman.h>
#include <sys/param.h>


#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <ctype.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/time.h>
#ifdef __alpha__
#include <sys/mbuf.h>
#endif
#include <net/route.h>
#include <net/if.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#ifdef __alpha__
#include <netinet/ip_var.h>
#endif
#include <netinet/tcp.h>
#include <netinet/if_ether.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sched.h>

#include "list.h"
#include "pkt.h"
#include "seq.h"
#include "flows.h"
#include "service.h"
#include "http.h"
#include "tcp.h"
#include "udp.h"
#include "udp_ns.h"
#include "print_util.h"
#include "report.h"
#include "output.h"
#include "tcpdump_patch.h"
#include "counters.h"
#include "writer.h"

#include "cprintf.h"

int writer_pid = 0;
int parent_pid;
int output_finished = 0;

/* used to harvest writer rusage */
struct rusage *wru = NULL;	/* where to dump - also serves as parent lock*/
struct timeval *wru_ts;		/* when got */
int writer_rusage_lock = 0;     /* RO for writer - in use by parent */

static char repbuf[(N_REPBLKS+N_REP_ORUNBLKS)*WRITE_BLKSZ];
static char dumpbuf[(N_DUMPBLKS+N_DUMP_ORUNBLKS)*WRITE_BLKSZ];

blk_map_t rep_blk_map[N_REPBLKS+N_REP_ORUNBLKS+1];
blk_map_t dump_blk_map[N_DUMPBLKS+N_DUMP_ORUNBLKS+1];

uint repblk_consumed;		/* used this write */
uint repblk_used = 0;		/* made visible to writer */
uint repblk_written = 0;		/* done */
uint dumpblk_used = 0;
uint dumpblk_written = 0;


/*
 * for de-bugging under gdb 
 */
int 
kill_writer(void)
{
  //if (kill(writer_pid, SIGKILL) != 0)
    //error("kill_writer()", "");


  return kill(writer_pid, SIGKILL);
}
void 
writer_error(char *msg, char *pmsg)
{
  int kill_ret;

  cfprintf(stderr, F_RED, "WRITER FATAL ERROR %s: %s %s\n", 
	  msg, pmsg, strerror(errno));

  /* kill off parent */
  if ((kill_ret = kill(parent_pid, SIGUSR1)) != 0)
    cfprintf(stderr, F_RED, "writer_error() - kill_parent fail (returned %d)\n", 
	    kill_ret);
  else
    cfprintf(stderr, F_BLUE, "writer_error() - parent sent SIGUSR1\n");

  exit(1);
}
/* Actual fd's and file names are global */

void 
open_rep_file(FILE *f)
{
  static int cycle = -1;
  char tmp[PATH_MAX+1];
  char cycle_str[CYCLE_STR_LEN];  
  
  cycle++;

  sprintf(cycle_str, ".%04d", cycle);
  
  sprintf(tmp, "%s%s", repfnm, cycle_str);
  
  fprintf(f, "%sWriting report file to %s\n", 
	  f == stderr ? "\t" : "", tmp);

  if ((rep_fd = open(tmp, O_RDWR | O_CREAT | O_TRUNC | O_NONBLOCK, 0660)) == -1)
    writer_error("open_rep_file() fail", tmp);
  
  return;
  
}

void 
open_dump_file(FILE *f)
{
  static int cycle = -1;
  char tmp[PATH_MAX+1];
  char cycle_str[CYCLE_STR_LEN];  
  
  cycle++;

  sprintf(cycle_str, ".%04d", cycle);
  
  sprintf(tmp, "%s%s", dumpfnm, cycle_str);
  
  fprintf(f, "%sWriting dump file to %s\n", 
	  f == stderr ? "\t" : "", tmp);

  if ((dump_fd = open(tmp, O_RDWR | O_CREAT | O_TRUNC | O_NONBLOCK, 0660)) == -1)
    writer_error("open_dump_file() fail", tmp);
  
  return;
  
}


void 
close_rep_file(void) 
{
 
  if (close(rep_fd) == -1)
    writer_error("close_rep_file", "");
  
  return;
}


void 
close_dump_file(void) 
{
  
  if (close(dump_fd) == -1)
    writer_error("close_dump_file", "");
  
  return;
}

void 
rep_file_cycle(void)
{
  close_rep_file();
  open_rep_file(stdout);

  return;
}

void 
dump_file_cycle(void)
{
  close_dump_file();
  open_dump_file(stdout);

  return;
}

#ifndef OWAN
int 
writer_init(void) 
{
  void *stack;
  int writer_pid;


  /* set up stack and spawn writer thread */

  if ((stack = malloc(WRITER_STACK_SZ)) == NULL)
    error("writer_init", "writer stack malloc");
//return 0;

  stack += WRITER_STACK_SZ;

  if ((writer_pid = __clone(writer, stack, SIGCHLD | CLONE_VM | CLONE_FS | CLONE_FILES,
			    NULL)) == -1)
    error("writer_init", "__clone");

  return writer_pid;
}
#endif

void 
obuffs_init(void) 
{
  int i;
  char *cp;

  outp = repbuf;
  dumpp = dumpbuf;

  for (i = 0, cp = repbuf; i < N_REPBLKS+N_REP_ORUNBLKS+1; i++, cp += WRITE_BLKSZ)
    {
      rep_blk_map[i].blk_start = cp;
      rep_blk_map[i].file_end = NULL;
    }

  for (i = 0, cp = dumpbuf; i < N_DUMPBLKS+N_DUMP_ORUNBLKS+1; i++, cp += WRITE_BLKSZ)
    {
      dump_blk_map[i].blk_start = cp;
      dump_blk_map[i].file_end = NULL;
    }

  fprintf(stderr, "\tOutput buffers initialised rep:%d dump:%d\n", 
	  (N_REPBLKS+N_REP_ORUNBLKS)*WRITE_BLKSZ,
	  (N_DUMPBLKS+N_DUMP_ORUNBLKS)*WRITE_BLKSZ);

  return;
}

void 
writer_loop(void)
{
  int written;
  int repblks_to_write, dumpblks_to_write;
  struct timeval timeo;
  
  for (;;)
    {
      
      /* check to see if parent has requested rusage */
      if (wru != NULL && !writer_rusage_lock)
	{
	  /* want one, parent not accessing - if it is just let it go */
	  if (getrusage(RUSAGE_SELF, wru) != 0)
	    writer_error("writer", "getrusage");
	  GETTIMEOFDAY("writer - wru_ts", wru_ts, (struct timezone *)0);
	  wru = NULL;
	}

      written = 0;

      repblks_to_write = repblk_used - repblk_written;
      WRITER_TRACE(F_GREEN, "Writer %d rep blocks to write\n", repblks_to_write);
      while (repblks_to_write)
	{
	  static int cycle = 0;
	  int repblkmap_written_indx = repblk_written % N_REPBLKS;
	  int nblks = 0;
	  int indx = repblkmap_written_indx;
	  int bytes;

	  while (repblks_to_write 
		 && indx < N_REPBLKS 
		 && rep_blk_map[indx].file_end == NULL)
	    {
	      indx++;
	      nblks++;
	      repblks_to_write--;
	    }

	  written+=nblks;
	  
	  bytes = nblks*WRITE_BLKSZ;
	  WRITER_TRACE(F_GREEN, "Writer writing %d rep bytes - %d blocks\n", bytes, nblks);
	  
	  if (write(rep_fd, rep_blk_map[repblkmap_written_indx].blk_start, 
		    bytes) != bytes)
	    writer_error("writer", " rep write1");
	  repblk_written += nblks;
	  counters.rep_blks_dumped += nblks;

	  /* check for file cycle */
	  if (rep_blk_map[indx].file_end != NULL)
	    {
	      cycle++;
	      bytes = rep_blk_map[indx].file_end - rep_blk_map[indx].blk_start;
	      WRITER_TRACE(F_GREEN, "Writer writing %d rep bytes to complete\n", bytes);
	      if (write(rep_fd, rep_blk_map[indx].blk_start, 
			bytes) != bytes)
		writer_error("writer", " rep write2");

	      /* reset */
	      rep_blk_map[indx].file_end = NULL;

	      //printf("Writer - rep file cycle\n");
	      repblk_written++;
	      repblks_to_write--;

	      close_rep_file();
	      if (repblks_to_write || !output_finished)
		open_rep_file(stdout);
	    }
	}

      dumpblks_to_write = dumpblk_used - dumpblk_written;
      WRITER_TRACE(F_GREEN, "Writer %d dump blocks to write (%d-%d)\n", dumpblks_to_write, dumpblk_used, dumpblk_written);
      while (dumpblks_to_write)
	{
	  int dumpblkmap_written_indx = dumpblk_written % N_DUMPBLKS;
	  int nblks = 0;
	  int indx = dumpblkmap_written_indx;
	  int bytes;

	  while (dumpblks_to_write > 0
		 && indx < N_DUMPBLKS 
		 && dump_blk_map[indx].file_end == NULL)
	    {
	      indx++;
	      nblks++;
	      dumpblks_to_write--;
	    }

	  written+=nblks;
	  
	  bytes = nblks*WRITE_BLKSZ;
	  WRITER_TRACE(F_GREEN, "Writer writing %d dump bytes - %d blocks\n", bytes, nblks);
	  if (write(dump_fd, dump_blk_map[dumpblkmap_written_indx].blk_start, 
		    bytes) != bytes)
	    writer_error("writer", " dump write1");
	  dumpblk_written += nblks;
	  counters.dump_blks_dumped += nblks;

	  /* check for file cycle */
	  if (dump_blk_map[indx].file_end != NULL)
	    {
	      bytes = dump_blk_map[indx].file_end - dump_blk_map[indx].blk_start;
	      WRITER_TRACE(F_GREEN, "Writer writing %d dump bytes to complete\n", bytes);
	      if (write(dump_fd, dump_blk_map[indx].blk_start, 
			bytes) != bytes)
		writer_error("writer", " dump write2");

	      /* reset */
	      dump_blk_map[indx].file_end = NULL;

	      dumpblk_written++;
	      dumpblks_to_write--;
	      printf("Writer - dump file cycle\n");

	      close_dump_file();
	      if (dumpblks_to_write || !output_finished)
		open_dump_file(stdout);
	    }
	}
     
      if (!written)
	{
	  WRITER_TRACE(F_GREEN, "Writer - nothing to write\n");
#ifndef OWAN
	  timeo.tv_sec = 0;
	  timeo.tv_usec = 250000;
	  if (select(0, NULL, NULL, NULL, &timeo) < 0)
	    writer_error("writer", "select");
#else
	  break;

#endif
	}

      if (output_finished 
	  && dumpblk_used == dumpblk_written 
	  && repblk_used == repblk_written)
	break;
      
    }

  return;
}


#ifndef OWAN
int 
writer(void *arg)
{

  fprintf(stderr, "\tWriter running\n");

  /* ignore handling of SIGUSR1 and SIGINT */
  if (signal(SIGUSR1, SIG_IGN) == SIG_ERR)
    writer_error("writer", "signal SIGUSR1");
  if (signal(SIGINT, SIG_IGN) == SIG_ERR)
    writer_error("writer", "signal SIGINT");

  /* open first files */
  open_rep_file(stderr);
  open_dump_file(stderr);

  writer_loop();
  
  printf("Writer finished\n");
  
  return 0;
}
#endif
      

  
