//===========================================================================
//  @(#) $Name: cflowd-2-1-b1 $
//  @(#) $Id: cfdcollect.cc,v 1.12 1999/08/18 06:56:12 dwm Exp $
//===========================================================================
//  CAIDA Copyright Notice
//
//  By accessing this software, cflowd, you are duly informed
//  of and agree to be bound by the conditions described below in this
//  notice:
//
//  This software product, cflowd, is developed by Daniel W. McRobb, and
//  copyrighted(C) 1998 by the University of California, San Diego
//  (UCSD), with all rights reserved.  UCSD administers the CAIDA grant,
//  NCR-9711092, under which part of this code was developed.
//
//  There is no charge for cflowd software. You can redistribute it
//  and/or modify it under the terms of the GNU General Public License,
//  v.  2 dated June 1991 which is incorporated by reference herein.
//  cflowd is distributed WITHOUT ANY WARRANTY, IMPLIED OR EXPRESS, OF
//  MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE or that the use
//  of it will not infringe on any third party's intellectual property
//  rights.
//
//  You should have received a copy of the GNU GPL along with cflowd.
//  Copies can also be obtained from:
//
//    http://www.gnu.org/copyleft/gpl.html
//
//  or by writing to:
//
//    University of California, San Diego
//
//    SDSC/CAIDA
//    9500 Gilman Dr., MS-0505
//    La Jolla, CA 92093 - 0505  USA
//
//  Or contact:
//
//    info@caida.org
//===========================================================================
extern "C" {
#include "aclocal.h"
#include <sys/types.h>
#if HAVE_SYS_FILIO_H
  #define BSD_COMP 1
#endif
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <time.h>
#include <assert.h>
#include <syslog.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#if HAVE_SYS_SELECT_H
  #include <sys/select.h>
#endif
  
}

#include <fstream.h>
#include <string>

#include "Arts.hh"
#include "ArtsPortTable.hh"
#include "ArtsProtocolTable.hh"
#include "ArtsAsMatrix.hh"
#include "ArtsNetMatrix.hh"
#include "CflowdCisco.hh"
#include "CflowdVersion.hh"
#include "CfdcollectConfig.hh"
#include "Signal.hh"

static const string rcsid = "@(#) $Name: cflowd-2-1-b1 $ $Id: cfdcollect.cc,v 1.12 1999/08/18 06:56:12 dwm Exp $";
static CflowdVersion g_cflowdVersion = CflowdVersion(rcsid);

static CfdcollectConfig     g_config;

extern int LoadCfdcollectConfigFile(const char *configFileName,
                                    CfdcollectConfig & config);

//-------------------------------------------------------------------------
//                         static void Daemonize()                         
//.........................................................................
//  Become a daemon process.
//-------------------------------------------------------------------------
static void Daemonize()
{
  int   rc;
  int   fd;

  rc = fork();
  if (rc > 0)
    exit(0);
  
  if (rc < 0) {
    syslog(LOG_ERR,"[E] fork() failed: %m {%s:%d}",__FILE__,__LINE__);
    syslog(LOG_ERR,"[E] exiting {%s:%d}",__FILE__,__LINE__);
    exit(1);
  }
    
  //   disassociate from process group
  setpgid(0,getpid());

  //  ignore terminal I/O signals
  #ifdef SIGTTOU
    signal(SIGTTOU, SIG_IGN);
  #endif

  //  disassociate from control terminal
  if ((fd = open("/dev/tty",O_RDWR)) >= 0)
  {
    ioctl(fd,TIOCNOTTY,(char *)0);
    close(fd);
  }
  return;
}

//-------------------------------------------------------------------------
//                static void HandleSigHup(int signalSumber)
//.........................................................................
//  SIGHUP handler.
//-------------------------------------------------------------------------
static void HandleSigHup(int signalSumber)
{
  syslog(LOG_INFO,"[I] SIGHUP received, reloading...");
  return;
}

//-------------------------------------------------------------------------
//               static void HandleSigAlrm(int signalSumber)               
//.........................................................................
//  SIGALRM handler.
//-------------------------------------------------------------------------
static void HandleSigAlrm(int signalSumber)
{
  syslog(LOG_INFO,"[I] awakened by alarm.");
  
  return;
}

//--------------------------------------------------------------------------
//                       static void RemovePidFile()
//..........................................................................
//  Removes the PID file (where we store our process ID).
//--------------------------------------------------------------------------
static void RemovePidFile()
{
  if (unlink(g_config.PidFile().c_str()) < 0) {
    syslog(LOG_ERR,"[E] unlink(\"%s\") failed: %m {%s:%d}",
           g_config.PidFile().c_str(),__FILE__,__LINE__);
  }

  return;
}

//--------------------------------------------------------------------------
//                        static int UpdatePidFile()                         
//..........................................................................
//  Puts process ID into file named by g_config.pidFileName.           
//  Returns 0.                                                               
//--------------------------------------------------------------------------
static int UpdatePidFile()
{
  pid_t      processId;
  
  ofstream   pidFileStream(g_config.PidFile().c_str());
  
  if (! pidFileStream) {
    syslog(LOG_ERR,"[E] unable to open pidfile '%s': %m {%s:%d}",
           g_config.PidFile().c_str(),__FILE__,__LINE__);
    return(-1);
  }
  
  processId = getpid();
  pidFileStream << processId << endl;
  pidFileStream.close();

  return(0);
}


//-------------------------------------------------------------------------
//               static void HandleSigTerm(int signalSumber)               
//.........................................................................
//  SIGTERM handler.
//-------------------------------------------------------------------------
static void HandleSigTerm(int signalSumber)
{
  syslog(LOG_INFO,"[I] received SIGTERM.  Exiting.");
  RemovePidFile();
  exit(1);
}

//----------------------------------------------------------------------------
//               static void HandleSigUsr1(int signalNumber) 
//............................................................................
//  SIGUSR1 handler.
//----------------------------------------------------------------------------
static void HandleSigUsr1(int signalNumber)
{
  syslog(LOG_INFO,"[I] received SIGUSR1.");

  return;
}

//-------------------------------------------------------------------------
//                      void Usage(const char *argv0)                      
//.........................................................................
//  
//-------------------------------------------------------------------------
void Usage(const char *argv0)
{
  cerr << "usage: " << argv0 << " configfile"
       << endl;
  return;
}

//-------------------------------------------------------------------------
//                     int main(int argc, char *argv[])                    
//.........................................................................
//  
//-------------------------------------------------------------------------
int main(int argc, char *argv[])
{
  time_t                          now;
  vector<CflowdServer>::iterator  cflowdIter;
  time_t                          sleepTime;
  bool                            canSleep;
  Signal                          sigHup(SIGHUP);
  Signal                          sigAlarm(SIGALRM);
  Signal                          sigTerm(SIGTERM);
  Signal                          sigUsr1(SIGUSR1);

  if (argc != 2) {
    Usage(argv[0]);
    exit(1);
  }

  if (LoadCfdcollectConfigFile(argv[1],g_config) < 0) {
    cerr << "failed to load config file '" << argv[1] << "': "
         << strerror(errno) << endl;
    Usage(argv[0]);
    exit(1);
  }
  
  Daemonize();
  openlog("cfdcollect",LOG_PID,g_config.LogFacility());
  syslog(LOG_INFO,
         "[I] cfdcollect (version %s) started with %d cflowd instances.",
         g_cflowdVersion.Name().c_str(),g_config.CflowdServers().size());
  UpdatePidFile();

  //  install signal handlers
  sigHup.InstallHandler(HandleSigHup);
  sigAlarm.InstallHandler(HandleSigAlrm);
  sigTerm.InstallHandler(HandleSigTerm);
    
  for (;;) {
    sigHup.Unblock();

    if (sigHup.Caught()) {
      sigHup.Catch();
      g_config.Clear();
      LoadCfdcollectConfigFile(argv[1],g_config);
      openlog("cfdcollect",LOG_PID,g_config.LogFacility());
      syslog(LOG_INFO,"[I] Reloaded, now have %d cflowd instances.",
             g_config.CflowdServers().size());
    }
    sigHup.Block();
    
    time(&now);
    sleepTime = 1000000;

    canSleep = true;
    
    for (cflowdIter = g_config.CflowdServers().begin();
         cflowdIter != g_config.CflowdServers().end(); cflowdIter++) {
      if (now >= (*cflowdIter).NextPollTime()) {
        canSleep = false;
        break;
      }
      if (((*cflowdIter).NextPollTime() - now) < sleepTime) {
        sleepTime = (*cflowdIter).NextPollTime() - now;
      }
    }

    struct itimerval  timerValue;

    if (canSleep) {
      sigset_t          sigSuspendMask;

      //  set a timer, then sigsuspend()
      
      sigfillset(&sigSuspendMask);
      sigdelset(&sigSuspendMask,SIGALRM);
      sigdelset(&sigSuspendMask,SIGTERM);
      sigdelset(&sigSuspendMask,SIGHUP);
      timerValue.it_interval.tv_sec = 0;
      timerValue.it_interval.tv_usec = 0;
      timerValue.it_value.tv_sec = sleepTime;
      timerValue.it_value.tv_usec = 0;
      
      syslog(LOG_INFO,"[I] sleeping for %d seconds.",sleepTime);
      sigHup.Unblock();
      setitimer(ITIMER_REAL,&timerValue,(struct itimerval *)NULL);
      sigsuspend(&sigSuspendMask);
      timerValue.it_interval.tv_sec = 0;
      timerValue.it_interval.tv_usec = 0;
      timerValue.it_value.tv_sec = 0;
      timerValue.it_value.tv_usec = 0;
      setitimer(ITIMER_REAL,&timerValue,(struct itimerval *)NULL);
      sigAlarm.Catch();
      sigHup.Block();
    }
    
    for (cflowdIter = g_config.CflowdServers().begin();
         cflowdIter != g_config.CflowdServers().end(); cflowdIter++) {
      if (now > (*cflowdIter).NextPollTime()) {
        //  Block SIGTERM.
        sigTerm.Block();

        //  set a timer so we don't block in read if a host doesn't
        //  respond.  We use a value of 600 seconds; this is just
        //  an off-the-cuff value and subject to change (but note
        //  the intent is to prevent a really long hang time in
        //  a blocked read()).
        timerValue.it_interval.tv_sec = 0;
        timerValue.it_interval.tv_usec = 0;
        timerValue.it_value.tv_sec = 600;
        timerValue.it_value.tv_usec = 0;
        setitimer(ITIMER_REAL,&timerValue,(struct itimerval *)NULL);

        //  get the data.
        (*cflowdIter).GetData(g_config.DataDir(),g_config.FilePrefix(),
                              sigAlarm);

        //  cancel the timer.
        timerValue.it_interval.tv_sec = 0;
        timerValue.it_interval.tv_usec = 0;
        timerValue.it_value.tv_sec = 0;
        timerValue.it_value.tv_usec = 0;
        setitimer(ITIMER_REAL,&timerValue,(struct itimerval *)NULL);

        //  Mark SIGALRM as caught.
        sigAlarm.Catch();

        //  unblock SIGTERM.
        sigTerm.Unblock();
      }
    }  // for (cflowdIter = g_config.CflowdServers().begin(); ...)
  }  // for (;;)

  exit(0);
}
