/* 
 *  randomd
 * 
 *  Copyright (c) 2002 Anthony Gialluca
 * 
 *  This program 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.
 *
 *  This program 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 this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 
 *  USA.
 */

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <syslog.h>
#include <malloc.h>
#include <errno.h>
#include <limits.h>
#include <sys/signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <kstat.h>
#include <sys/unistd.h>
#include <sys/sysinfo.h>

#ifdef DMALLOC
#include <dmalloc.h>
#endif


/* DEFINES */
char VERSION[] = "$Id: randomd.c,v 1.1.1.1 2002/04/18 17:43:05 root Exp $";

#define DEFAULT_SLEEP 10

#if     !defined(TRUE) || ((TRUE) != 1)
#define TRUE    (1)
#endif

#if     !defined(FALSE) || ((FALSE) != 0)
#define FALSE   (0)
#endif

/* where is the pid file */
#define PID_FILE "/var/run/randomd.pid"

/* Period parameters */  
#define N 624
#define M 397
#define MATRIX_A 0x9908b0df   /* constant vector a */
#define UPPER_MASK 0x80000000 /* most significant w-r bits */
#define LOWER_MASK 0x7fffffff /* least significant r bits */

/* Tempering parameters */   
#define TEMPERING_MASK_B 0x9d2c5680
#define TEMPERING_MASK_C 0xefc60000
#define TEMPERING_SHIFT_U(y)  (y >> 11)
#define TEMPERING_SHIFT_S(y)  (y << 7)
#define TEMPERING_SHIFT_T(y)  (y << 15)
#define TEMPERING_SHIFT_L(y)  (y >> 18)

static unsigned long mt[N]; /* the array for the state vector  */
static int mti=N+1; /* mti==N+1 means mt[N] is not initialized */

/*
 * Globals
 */

int debug = 0;			/* Should we turn on debugging ? */

char pidfile[] = PID_FILE;	/* the pid file  */

static kstat_ctl_t *kc = NULL;	/* libkstat cookie */

int sleep_secs = DEFAULT_SLEEP;	/* How many seconds to sleep between updates */

/*
 *  Prototypes
 */

void cleanUp(int);
void usage(void);
int checkPID(void);
void Terminate(int);
void CPU_stats(void);
void safe_zalloc(void **, uint_t);
void randomd_write(uint_t);
void Pull_and_Write(void);
void Mersenne_Twist(void);
void sgenrand(unsigned long);
unsigned long genrand(void);


/*
 * The main function.
 */

int
main(int argc, char **argv) {
    int pid = 0, c, uid;

  while((c = getopt(argc,argv, "HhVds:")) != EOF)
    switch(c) {
    case 's':
      sleep_secs = atoi(optarg);
      break;
    case 'd':
      debug++;
      break;
    case 'V':
      fprintf(stdout,"Version: %s\n",VERSION);
      exit(0);
      break;
    case 'h':
    case 'H':
      usage();
      exit(0);
      break;
    default:
      usage();
    }

  uid = getuid();
  if(uid != 0) {
    fprintf(stderr,"\n\t!! ERROR: randomd: must be root to run !!\n\n");
    exit(-1);
  }
  

  if(debug) { 
    fprintf(stdout,
	    "\n\tDEBUG: Starting randomd, debug defined, not forking ...\n"); 
  }else{
    pid = fork();   
    if (pid)
      exit(0);
  }
   

  /* Initialize syslog facility */
  openlog("randomd", LOG_PID | LOG_NDELAY, LOG_DAEMON);
  syslog(LOG_INFO, "Starting up.");

  checkPID();

  signal(SIGHUP,cleanUp);
  signal(SIGINT,cleanUp);
  signal(SIGQUIT,cleanUp);
  signal(SIGABRT,cleanUp);
  signal(SIGKILL,cleanUp);
  signal(SIGTERM,cleanUp);


  /* if not in debug mode close stdout and stderr
   *  and re-direct it to /dev/null.
   */

  if(!debug) { 
    /* STDIN */
    if(close(0) != 0) {
      perror("Cannot redirect stdin !\n");
      Terminate(-2);
    }
    if(open("/dev/zero",O_RDONLY) < 0) {
      perror("Cannot redirect stdin !\n");
      Terminate(-2);
    }
    /* STDOUT */
    if(close(1) != 0) {
      perror("Cannot redirect stdout !\n");
      Terminate(-2);
    }
    if(open("/dev/null",O_WRONLY) < 0) {
      perror("Cannot redirect stdout !\n");
      Terminate(-2);
    }
    /* STDERR */
    if(close(2) != 0) {
      perror("Cannot redirect stderr !\n");
      Terminate(-2);
    }
    if(open("/dev/null",O_WRONLY) < 0) {
      perror("Cannot redirect stderr !\n");
      Terminate(-2);
    }
  }

  /* initialize the kstat control structure. */

  if(( kc = kstat_open()) == NULL) {
    perror("Cannot initialize the kstat control structure.");
    Terminate(-2);
  }

  while(1) {
    CPU_stats();
    Mersenne_Twist();
    Pull_and_Write();
    sleep(sleep_secs);
  }

  /* test_write(); */

  Terminate(0);

}



/*
 * prints out usage information
 */
void
usage(void) {
  fprintf(stdout,"\n\nrandomd: [-d] [-h|-H]\n");
  fprintf(stdout,"\twhere:\n");
  fprintf(stdout,"\t-d      -> Turn on debugging.\n");
  fprintf(stdout,"\t            Will not fork into backgroud\n");
  fprintf(stdout,"\t-h      -> Print usage and exit.\n");
  fprintf(stdout,"\t-H      -> Print usage and exit.\n");
  fprintf(stdout,"\t-s secs -> Sleep 'secs' between wakeups.\n");
  fprintf(stdout,"\t             Default is %d\n",DEFAULT_SLEEP);
  fprintf(stdout,"\n\n");
}


/* 
 *  CheckPID()
 */
int
checkPID()
{
  FILE *f;
  char buffer[128];
  if ((f = fopen(pidfile, "r")) != NULL) {
    if(fgets(buffer, sizeof(buffer), f) == NULL){
      syslog(LOG_INFO,"-- ERROR -- Cannot Read pidfile \"%s\"",pidfile);
      exit(-1);
    }
    syslog(LOG_INFO,"-- ERROR -- Already running pid \"%s\" ? (remove pidfile \"%s\").",buffer,pidfile);
    fprintf(stderr,"\n\t!! ERROR: Already running, pid \"%s\"\n",
	   buffer);
    fprintf(stderr,"\t   Note: Remove pidfile \"%s\" to override !\n\n",
	   pidfile);
    exit(-1);
  }
  fclose(f);
  f = fopen(pidfile, "w");
  if (f) {
    fprintf(f, "%d", getpid());
    fclose(f);
  } else {
    syslog(LOG_ERR,"Error creating pidfile %s: %s", pidfile,
		     strerror(errno));
  }
}





void
cleanUp(int sig)
{
  int i;

  if(debug)
    fprintf(stdout,"\tDEBUG: Caught signal %d, Terminating...\n\n",sig);

  syslog(LOG_INFO, "Caught Signal %d, cleaning up",sig);

  Terminate(0);
}



void
Terminate(int final)
{
  /* free all resources associated with kstat ctl structure */
  if(kc != NULL) kstat_close(kc);

  /* Remove PID file */
  unlink(pidfile);

  /* Inform syslog that we are terminating */
  syslog(LOG_INFO, "Terminating, exit status is %d.",final);

  if(debug)
    fprintf(stdout,"\tDEBUG: randomd terminating. exit status is %d.\n\n",final);

  /* Now exit */
  exit(final);
}


void
CPU_stats(void){
  kstat_t *ksp;
  static int nconf_cpus = 0;
  cpu_stat_t cs;
  static uint_t X=0;
  kid_t kcid = -1;

  if(debug)
    fprintf(stdout,"\tDEBUG: entering CPU_status()\n");

  /* 
   * Get Number of processors configured.
   * Dont cache this since it may change due to DR operations.
   */
  nconf_cpus = sysconf(_SC_NPROCESSORS_CONF);

  if(debug)
    fprintf(stdout,"\tDEBUG: NProcessors conf: %d\n",nconf_cpus);
  
  /*
   * Update the kstats chain, if necessary.
   */
  if((kcid = kstat_chain_update(kc)) == -1) {
    syslog(LOG_ERR,"kstat_chain_update() error:");
    fprintf(stderr,"\n\tError: kstat_chain_update() Error\n");
    Terminate(-2);
  }else if (debug && kcid == 0) {
    /* 
     * There's been no change to the kstat chain. No reason
     *  to continue this interval.
     */
    if(debug)
      fprintf(stdout,"\tDEBUG: kstat_chain_update(): %d\n",kcid);
  }else if(debug)
    fprintf(stdout,"\tDEBUG: kstat_chain_update(): %d, State Change.\n",kcid);

  /*
   * How many cpu kstats do we have ?
   */
  for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) {
    if(strncmp(ksp->ks_name, "cpu_stat", 8) == 0) {

      /* For debugging purposes */
/*       fprintf(stdout,"Name: %s, inst %d, mod %s\n", */
/* 	      ksp->ks_name,ksp->ks_instance,ksp->ks_module); */
/*       fprintf(stdout,"Records %d, data size %d, type %d\n", */
/* 	      ksp->ks_ndata,ksp->ks_data_size, ksp->ks_type); */

      if(kstat_read(kc, ksp, &cs) == -1) {
	fprintf(stderr,"\n\tERROR: kread() failed\n");
	Terminate(-1);
      };
 
      /* VMINFO */

     /* minor page faults via as_fault()	*/
      X ^= (cs.cpu_vminfo.as_fault << 16) ^ (cs.cpu_vminfo.as_fault >> 16);
      /* copy-on-write faults			*/
      X ^= (cs.cpu_vminfo.cow_fault << 16) ^ (cs.cpu_vminfo.cow_fault >> 16);
      /* executable pages paged in		*/
      X ^= (cs.cpu_vminfo.execpgin << 16) ^ (cs.cpu_vminfo.cow_fault >> 16);
      /* fs pages paged in			*/
      X ^= (cs.cpu_vminfo.fspgin << 16) ^ (cs.cpu_vminfo.cow_fault >> 16);	
      /* page reclaims (includes pageout)	*/
      X ^= (cs.cpu_vminfo.pgrec << 16) ^ (cs.cpu_vminfo.cow_fault >> 16);
      /* page reclaims from free list		*/
      X ^= (cs.cpu_vminfo.pgfrec << 16) ^ (cs.cpu_vminfo.cow_fault >> 16);	
      /* pageins				*/
      X ^= (cs.cpu_vminfo.pgin << 16) ^ (cs.cpu_vminfo.cow_fault >> 16);
      /* pages paged in			*/
      X ^= (cs.cpu_vminfo.pgpgin << 16) ^ (cs.cpu_vminfo.cow_fault >> 16);
      /* pageouts				*/
      X ^= (cs.cpu_vminfo.pgout << 16) ^ (cs.cpu_vminfo.cow_fault >> 16);
      /* pages paged out			*/
      X ^= (cs.cpu_vminfo.pgpgout << 16) ^ (cs.cpu_vminfo.cow_fault >> 16);
      
      if(debug)
	fprintf(stdout,"\tDEBUG: CPU Instance %d, VmInfo: %u\n",
		ksp->ks_instance,X);

      randomd_write(X);

      /* SYSINFO */
      
      /* physical block reads			*/
      X ^= (cs.cpu_sysinfo.bread << 16) ^ (cs.cpu_sysinfo.bread >> 16);		
      /* physical block writes (sync+async)	*/
      X ^= (cs.cpu_sysinfo.bwrite << 16) ^ (cs.cpu_sysinfo.bwrite >> 16);		
      /* logical block reads			*/
      X ^= (cs.cpu_sysinfo.lread << 16) ^ (cs.cpu_sysinfo.lread >> 16);		
      /* logical block writes			*/
      X ^= (cs.cpu_sysinfo.lwrite << 16) ^ (cs.cpu_sysinfo.lwrite >> 16);		
      /* context switches			*/
      X ^= (cs.cpu_sysinfo.pswitch << 16) ^ (cs.cpu_sysinfo.pswitch >> 16);	
      /* traps				*/
      X ^= (cs.cpu_sysinfo.trap << 16) ^ (cs.cpu_sysinfo.trap >> 16);		
      /* device interrupts			*/
      X ^= (cs.cpu_sysinfo.intr << 16) ^ (cs.cpu_sysinfo.intr >> 16);		
      /* system calls				*/
      X ^= (cs.cpu_sysinfo.syscall << 16) ^ (cs.cpu_sysinfo.syscall >> 16);	
      /* read() + readv() system calls	*/
      X ^= (cs.cpu_sysinfo.sysread << 16) ^ (cs.cpu_sysinfo.sysread >> 16);	
      /* write() + writev() system calls	*/
      X ^= (cs.cpu_sysinfo.syswrite << 16) ^ (cs.cpu_sysinfo.syswrite >> 16);	
      /* forks				*/
      X ^= (cs.cpu_sysinfo.sysfork << 16) ^ (cs.cpu_sysinfo.sysfork >> 16);	
      /* vforks				*/
      X ^= (cs.cpu_sysinfo.sysvfork << 16) ^ (cs.cpu_sysinfo.sysvfork >> 16);	
      /* execs				*/
      X ^= (cs.cpu_sysinfo.sysexec << 16) ^ (cs.cpu_sysinfo.sysexec >> 16);	
      /* bytes read by rdwr()			*/
      X ^= (cs.cpu_sysinfo.readch << 16) ^ (cs.cpu_sysinfo.readch >> 16);		
      /* bytes written by rdwr()		*/
      X ^= (cs.cpu_sysinfo.writech << 16) ^ (cs.cpu_sysinfo.writech >> 16);	
      /* interrupts as threads (below clock)	*/
      X ^= (cs.cpu_sysinfo.intrthread << 16) ^ (cs.cpu_sysinfo.intrthread >> 16);	
      /* intrs blkd/prempted/released (swtch)	*/
      X ^= (cs.cpu_sysinfo.intrblk << 16) ^ (cs.cpu_sysinfo.intrblk >> 16);	
      /* involuntary context switches		*/
      X ^= (cs.cpu_sysinfo.inv_swtch << 16) ^ (cs.cpu_sysinfo.inv_swtch >> 16);	
      
      if(debug)
	fprintf(stdout,"\tDEBUG: CPU Instance %d, SysInfo: %u\n",
		ksp->ks_instance,X);

      randomd_write(X);
    }
  }
  
  if(debug)
    fprintf(stdout,"\tDEBUG: exiting CPU_status()\n");
}


void
safe_zalloc(void **ptr, uint_t size) {
  if(*ptr != NULL) {
    free(*ptr);
  }
  if((*ptr = (void *)calloc(1,size)) == NULL) {
    syslog(LOG_ERR,"calloc() error:");
    perror("calloc() failure !");
    Terminate(-1);
  }
}



void
randomd_write(uint_t value) {
  FILE *fptr = NULL;
  int written = 0;

  if(debug)
    fprintf(stdout,"\tDEBUG: entering write(): %u\n",value);

  if((fptr = fopen("/dev/urandom","w")) == NULL) {
    syslog(LOG_ERR,"Error: randomd_write(): Cannot open \"/dev/urandom\" for writing");
    perror("Cannot open \"/dev/urandom\" for writing");
    Terminate(-2);
  }
  
  if((written = fwrite((void *)&value, sizeof(uint_t),1,fptr)) != 1) {
    syslog(LOG_ERR,"Error: randomd_write(): Did not write correct amount");
    fprintf(stderr,"\n\tError: Did not write correct amount: %d written, needed 1",
	   written);
    perror("fwrite() error:");
    fclose(fptr);
    Terminate(-2);
  }


  fclose(fptr);
  fptr = NULL;

  if(debug)
    fprintf(stdout,"\tDEBUG: exiting write()\n");
}


void
Pull_and_Write(void) {
  FILE *fptr = NULL;
  int written = 0;
  uint_t buf[16];

  if(debug)
    fprintf(stdout,"\tDEBUG: entering Pull_and_Write()\n");

  /* Pull 16 random integers */
  if((fptr = fopen("/dev/urandom","r")) == NULL) {
    syslog(LOG_ERR,"Error: Pull_and_Write(): Cannot open \"/dev/urandom\" for reading");
    perror("Cannot open \"/dev/urandom\" for reading");
    Terminate(-2);
  }

  if((written = fread((void *)buf, sizeof(uint_t),16,fptr)) != 16) {
    syslog(LOG_ERR,"Error: Pull_and_Write(): Did not read correct amount: %d written, needed 16",
	   written);
    fprintf(stderr,"\n\tError: Did not read correct amount: %d written, needed 16",
	   written);
    perror("fread() error:");
    fclose(fptr);
    Terminate(-2);
  }
  fclose(fptr);
  fptr = NULL;

  if((fptr = fopen("/dev/urandom","w")) == NULL) {
    syslog(LOG_ERR,"Error: Pull_and_Write(): Cannot open \"/dev/urandom\" for writing");
    perror("Cannot open \"/dev/urandom\" for writing");
    Terminate(-2);
  }
  
  if((written = fwrite((void *)buf, sizeof(uint_t),16,fptr)) != 16) {
    syslog(LOG_ERR,"Error: Pull_and_Write(): Did not write correct amount: %d written, needed 16",
	   written);
    fprintf(stderr,"\n\tError: Did not write correct amount: %d written, needed 16",
	   written);
    perror("fwrite() error:");
    fclose(fptr);
    Terminate(-2);
  }


  fclose(fptr);

  if(debug)
    fprintf(stdout,"\tDEBUG: exiting Pull_and_Write()\n");
}


/*
 * Lets initialize a Mersenne Twister RNG, Pull and discard
 *  a series of numbers then re-feed 16 of them back to /dev/urandom
 */
void
Mersenne_Twist(void)
{
  FILE *fptr = NULL;
  int written = 0,i;
  uint_t buf[16];

  if(debug)
    fprintf(stdout,"\tDEBUG: entering Mersenne_Twist()\n");

  /* Pull 16 random integers */
  if((fptr = fopen("/dev/urandom","r")) == NULL) {
    syslog(LOG_ERR,"Error: Mersenne_Twist(): Cannot open \"/dev/urandom\" for reading");
    perror("Cannot open \"/dev/urandom\" for reading");
    Terminate(-2);
  }

  if((written = fread((void *)buf, sizeof(uint_t),16,fptr)) != 16) {
    syslog(LOG_ERR,"Error: Mersenne_Twist(): Did not read correct amount: %d written, needed 16",
	   written);
    fprintf(stderr,"\n\tError: Did not read correct amount: %d written, needed 16",
	   written);
    perror("fread() error:");
    fclose(fptr);
    Terminate(-2);
  }
  fclose(fptr);
  fptr = NULL;

  /* Now feed two to sgenrand() */
  sgenrand(buf[3] ^ buf[5]);
  if(debug)
    fprintf(stdout,"\tDEBUG: Seeded with %u\n",buf[3] ^ buf[5]);


  /* Now pull and discard a series of ints before re-seeding buf 
   *   Re-use written
   *
   */
  written = 10000;
  for(i = 0; i < sizeof(buf)/sizeof(uint_t); i++) {
    while(written < 0) 
      genrand();
    buf[i] = genrand();
/*    if(debug) */
/*      fprintf(stdout,"\tDEBUG: buf[%d] with %ul\n",i,buf[i]); */
    written = 10000;
  }
  
  if((fptr = fopen("/dev/urandom","w")) == NULL) {
    syslog(LOG_ERR,"Error: Mersenne_Twist(): Cannot open \"/dev/urandom\" for writing");
    perror("Cannot open \"/dev/urandom\" for writing");
    Terminate(-2);
  }
  
  if((written = fwrite((void *)buf, sizeof(uint_t),16,fptr)) != 16) {
    syslog(LOG_ERR,"Error: Mersenne_Twist(): Did not write correct amount: %d written, needed 16",
	   written);
    fprintf(stderr,"\n\tError: Did not write correct amount: %d written, needed 16",
	   written);
    perror("fwrite() error:");
    fclose(fptr);
    Terminate(-2);
  }


  fclose(fptr);

  if(debug)
    fprintf(stdout,"\tDEBUG: exiting Mersenne_Twist()\n");

}

/* initializing the array with a NONZERO seed */
void
sgenrand(seed)
     unsigned long seed; 
{
  /* setting initial seeds to mt[N] using         */
  /* the generator Line 25 of Table 1 in          */
  /* [KNUTH 1981, The Art of Computer Programming */
  /*    Vol. 2 (2nd Ed.), pp102]                  */
  mt[0]= seed & 0xffffffff;
  for (mti=1; mti<N; mti++)
    mt[mti] = (69069 * mt[mti-1]) & 0xffffffff;
}

unsigned long 
genrand()
{
  unsigned long y;
  static unsigned long mag01[2]={0x0, MATRIX_A};
  /* mag01[x] = x * MATRIX_A  for x=0,1 */

  if (mti >= N) { /* generate N words at one time */
    int kk;

    if (mti == N+1)   /* if sgenrand() has not been called, */
      sgenrand(4357); /* a default initial seed is used   */

    for (kk=0;kk<N-M;kk++) {
      y = (mt[kk]&UPPER_MASK)|(mt[kk+1]&LOWER_MASK);
      mt[kk] = mt[kk+M] ^ (y >> 1) ^ mag01[y & 0x1];
    }
    for (;kk<N-1;kk++) {
      y = (mt[kk]&UPPER_MASK)|(mt[kk+1]&LOWER_MASK);
      mt[kk] = mt[kk+(M-N)] ^ (y >> 1) ^ mag01[y & 0x1];
    }
    y = (mt[N-1]&UPPER_MASK)|(mt[0]&LOWER_MASK);
    mt[N-1] = mt[M-1] ^ (y >> 1) ^ mag01[y & 0x1];

    mti = 0;
  }
  
  y = mt[mti++];
  y ^= TEMPERING_SHIFT_U(y);
  y ^= TEMPERING_SHIFT_S(y) & TEMPERING_MASK_B;
  y ^= TEMPERING_SHIFT_T(y) & TEMPERING_MASK_C;
  y ^= TEMPERING_SHIFT_L(y);

  return y; 
}

