/*
 * $Id: procstatd.c,v 1.1 2001/03/15 22:16:13 jpormann Exp jpormann $
 *
 * procstatd - Copyright (c) 1999 by Robert G. Brown, rgb@phy.duke.edu
 *         GPL version 2b (b for beverage) granted.
 *
 * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 *
 * procstatd - A daemon to extract statistics from /proc/stat and publish them
 *         on demand via a socket connection or broadcast.
 */

#include "procstatd.h"

/* void fatal(const char *fmt, ...); */
void sigchld_handler(int);
char **outfields;
int sendline(int fd, char *buffer, int length);
int readline(int fd, char *buffer, int bufsize);

int verbose;
int daemonmode,port;
int server_fd, serverlen, client_fd, clientlen;
int input_fd,output_fd,error_fd;
int buflen;
char outbuf[BUFLEN];
char hostname[BUFLEN];
char hostip[BUFLEN];
char clientname[BUFLEN];
char statbuf[BUFLEN];
char **fields;	/* for parsing via parse() */
long int interval;
struct sockaddr_in serverINETaddress;
struct sockaddr_in clientINETaddress;
FILE *stat_fd[N_SOURCES];
FILE *sendstat_fd;
Stathash stats[N_STATS];
Kernel kernel;

int main(int argc, char **argv)
{

 struct sockaddr_in serverINETaddress;
 struct sockaddr_in clientINETaddress;
 struct sockaddr* serverSockAddrPtr;
 struct sockaddr* clientSockAddrPtr;
 struct linger linger;
 struct hostent* hostidentptr;
 struct in_addr* hostaddr;

 int on = 1;				/* To reuse socket */
 int pid;				/* for forking */
 fd_set fdset;				/* for the select statement */
 int ret;				/* for return value of select */
 int retries;				/* number of times to try to get the port */

 int n;
 char procname[BUFLEN];
 int pnamelen;

/*
 * START by parsing the command line and setting global options.
 */
 parsecl(argc,argv);

 fields = (char **) malloc((size_t) (MAXFIELDNUMBER*sizeof(char*)));
 fields[0] = (char *) malloc((size_t)(MAXFIELDNUMBER*BUFLEN*sizeof(char)));
 for(n = 1;n < MAXFIELDNUMBER;n++) fields[n] = fields[n-1] + BUFLEN;

/* 
 * These are needed in init/get/eval_indentity(); we set them very early
 * because they might be useful elsewhere.
 */
 gethostname(hostname,BUFLEN-2);
 hostidentptr = gethostbyname(hostname);
 hostaddr = (struct in_addr*) hostidentptr->h_addr_list[0];
 sprintf(hostip,"%s",inet_ntoa(*hostaddr));
 if(verbose) printf("procstatd running on host = %s, hostip = %s\n",hostname,hostip);

/*
 * We now have two broad directions in the code.  In one, procd is
 * an inetd daemon, which is very simple -- it reads from stdin (fed
 * by inetd, which actually handles the connection) and writes to
 * stdout.  In the other, procd is a "real" forking daemon, and
 * disconnects itself from any tty on the fork so that stdin/stdout/stderr
 * are all discarded.  I think that I want them to work identically
 * as line parsers, but probably not at first.
 */
 switch(daemonmode){
   case FORK:
     if(verbose) printf("I am a Forking Daemon!  You'd have to be crazy not to be scared!\n");
     break;
   case INETD:
     if(verbose) printf("I am an inetd Daemon?\n");
     input_fd = (int) stdin;
     output_fd = (int) stdout;
     error_fd = (int) stderr;
     handlemessages();
     break;
   case MCAST:
   default:
     if(verbose) printf("Huh?\n");
     fprintf(stderr,"Multicast (or other) operation not yet supported.  Try again.\n");
     exit(0);
     break;
 }

/* 
 * Ignore death of child signals to prevent zombies 
 */
 signal(SIGCHLD,SIG_IGN);

/* 
 * Create a INET socket, bidirectional, default protocol.  This is
 * all boringly standard code, a shame it is so hard to read.  It's
 * a bit simpler on the perl end!
 */
 server_fd = socket(AF_INET,SOCK_STREAM,0);
 if (server_fd < 0){
   fprintf(stderr,"socket: %.100s", strerror(errno));
   exit(1);
 }
 /* 
  * Set socket options.  We try to make the port reusable and have it
  * close as fast as possible without waiting in unnecessary wait states
  * on close. 
  */
 setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, (void *)&on, 
                 sizeof(on));
 linger.l_onoff = 1;	/* Linger for just a bit */
 linger.l_linger = 15;	/* 15 hundredths of a second, to be exact */
 setsockopt(server_fd, SOL_SOCKET, SO_LINGER, (void *)&linger, 
                 sizeof(linger));


 serverlen = sizeof(serverINETaddress);
 bzero( (char*) &serverINETaddress,serverlen);	/* clear structure */
 serverINETaddress.sin_family = AF_INET;	/* Internet domain */
 serverINETaddress.sin_addr.s_addr = htonl(INADDR_ANY);	/* Accept all */
 serverINETaddress.sin_port = htons(port);	/* Server port number */

 serverSockAddrPtr = (struct sockaddr*) &serverINETaddress;
 /* 
  * Bind the socket to the desired port.  Try up to six times (30sec) IF the
  * port is in use, as sometimes it will be if the old daemon was hung,
  * killed, and immediately restarted.
  */
 retries = 6;
 errno = 0;	/* To zero any possible garbage value */
 while(retries--){
   if(bind(server_fd,serverSockAddrPtr,serverlen) < 0) {
     if(errno != EADDRINUSE){
       close(server_fd);
       fprintf(stderr,"Bind: %.100s\n", strerror(errno));
       fprintf(stderr,"Bind to port %d failed: %d.\n", port,errno);
       exit(255);
     }
   } else break;
   /* printf("Got no port: %s\n",strerror(errno)); */
   sleep(5);
 }
 if(errno){
   if(errno == EADDRINUSE){
     fprintf(stderr,"Timeout (tried to bind six times five seconds apart)\n");
   } 
   close(server_fd);
   fprintf(stderr,"Bind to port %d failed: %.100s\n",port,strerror(errno));
   exit(255);
  }

 /* 
  * Queue up to five incoming connections or die.
  */
 if(listen(server_fd,5) < 0){
   fprintf(stderr,"listen: %.100s", strerror(errno));
   exit(255);
 }

 /* Arrange SIGCHLD to be caught. */
 signal(SIGCHLD, sigchld_handler);

 /*
  * Initialize client structures.
  */
 clientlen = sizeof(clientINETaddress);
 clientSockAddrPtr = (struct sockaddr*) &clientINETaddress;

 /*
  * Loop "forever", or until daemon crashes or is killed with a signal.
  */
 while(1){
   /* Accept a client connection */
   if(verbose) printf("Accepting Client connection...\n");
          
   /* 
    * Wait in select until there is a connection. Presumably this is
    * more efficient than just blocking on the accept
    */
   FD_ZERO(&fdset);
   FD_SET(server_fd, &fdset);
   ret = select(server_fd + 1, &fdset, NULL, NULL, NULL);
   if (ret < 0 || !FD_ISSET(server_fd, &fdset)) {
     if (errno == EINTR)
       continue;
     fprintf(stderr,"select: %.100s", strerror(errno));
     continue;
   }
          
   /*
    * A call is waiting.  Accept it.
    */
   client_fd = accept(server_fd,clientSockAddrPtr,&clientlen);
   if (client_fd < 0){
     if (errno == EINTR)
       continue;
     fprintf(stderr,"accept: %.100s", strerror(errno));
     continue;
   }
   if(verbose) printf("                           ...client connection made.\n");

   /*
    * IF I GET HERE... 
    * ...I'm a real daemon.  I therefore fork and have the child process
    * the connection.  The parent continues listening and can service
    * multiple connections in parallel.
    */

   /* 
    * CHILD.  Close the listening (server) socket, and start using the 
    * accepted (client) socket.  We break out of the (infinite) loop to 
    * handle the connection. 
    */
   if ((pid = fork()) == 0){
     close(server_fd);
     break;
   }

   /* 
    * PARENT.  Stay in the loop.  Close the client socket (it's the child's)
    * but leave the server socket open.
    */
   if (pid < 0)
     fprintf(stderr,"fork: %.100s", strerror(errno));
   else
     if(verbose) printf("Forked child %d to handle socket %d.", pid,client_fd);
   close(client_fd);
 }

 /* No need to wait for children -- I'm the child */
 signal(SIGCHLD, SIG_DFL);
 /* Dissociate from calling process group and control terminal */
 setsid();

 if(verbose) printf("I am the child %d forked to handle socket %d.", pid,client_fd);

 /* 
  * This routine needs to be written to work EITHER inside inetd OR
  * as a forking daemon.  For the moment, try encapsulating the handler
  * from the old forking daemon code.  We'll have to rename the streams
  * somehow to permit fprintf to be used to send the messages in both
  * cases...
  */
 input_fd = client_fd;
 output_fd = client_fd;
 error_fd = client_fd;
 handlemessages();

}  /* End of procd main */

/*
 * This is the main message handling loop.  It should remind one of the smtp
 * protocol -- a handful of commands, each of which causes
 * a specified action to be performed.  There is very little that we need
 * to tell the daemon to do.  We need to tell it to initialize its
 * variables (open the /proc/files, read them in, set base times from
 * which to calculate rates).  We need to tell it to send an update of
 * all the statistical variables it knows about in a single packet, if
 * possible.  We need to tell it to quit.  It may be (in a future revision)
 * that we want to be able to take a daemon and tell it to start or stop
 * broadcasting or multicasting its stats update at some time granularity.
 * There may even be more commands that make sense, but I doubt it.  We
 * therefore support the following commands:
 *
 *    init			initialize stats variables, open files.
 *    send			send all stats and rates.
 *    [broadcast on/off [time]]	turn on and off broadcasting
 *    jobs			send job info (like global ps) -- added by JBP
 *    quik                      send simple (& quick) info -- added by JBP
 *    quit			end connection and resume listen
 *
 * The broadcast command is NOT yet implemented and may never be.
 */

void handlemessages()
{

 int i,n,numfields,ifld;
 char inbuf[BUFLEN],commandname[BUFLEN],valuename[BUFLEN],*nextval;

 bzero(inbuf,sizeof(inbuf));

 sprintf(commandname,"ready");	/* To start the loop */
 if(verbose) printf("%s to read/write with socket.\n",commandname);
 while(strncmp(commandname,"quit",4) != 0){
   /*
    * read a LF-null-terminated line.
    */
   sprintf(commandname,"quit");	/* To end the loop on a dropped line */
   n = readline(client_fd,inbuf,BUFLEN);
   /* JBP: if socket is dropped, then n==0 and we should exit */
   if(n <= 0) break;
   if(verbose) printf("\n# Read in %s\n",inbuf);
   /* 
    * Get the command.  Leave the rest dangling for now.
    */
   numfields = parse(inbuf,fields,MAXFIELDNUMBER,BUFLEN);
   if(verbose){
     printf("# Parse returned:|");
     for(i=0;i<numfields;i++){
       printf(" %s |",fields[i]);
     }
     printf("\n# ==================================================\n");
   }
   strcpy(commandname, fields[0]);
   if(verbose) printf("# Command: %s\n",commandname);
   /* 
    * We should have the first field of a command line in commandname.  
    * Now we parse it.  This is straightforward.  Note the use of
    * strncmp, to prevent buffer overwrite attacks.
    */

   /* Command: init */
   if(strncmp(commandname,"init",4) == 0){
     /*
      * initialize the advanced statistics and efficient parse of
      * /proc files.
      */
     if(verbose) printf("# Running init\n");
     init_statlist();
     init_jobslist();
     init_quiklist();

     if(verbose) printf("# Kernel revision %d.%d.%d\n",kernel.major,kernel.minor,kernel.revision);
     fflush(stdout);

     get_statlist();
     eval_statlist();
     send_statlist();

     /*
      * Send a list of fields back (we're assuming eventual inetd, here.)
      * Probably need to fancy up print_statlist to use sendline.
      */
     if(verbose) print_statlist(stdout);
   }	/* This terminates "init" */
   else	/* So that we can ONLY execute known commands */
   /* Command: send */
   if(strncmp(commandname,"send",4) == 0){	/* This is send */
     if(verbose) printf("# Running send\n");
     get_statlist();
     eval_statlist();
     send_statlist();
     if(verbose) {
        print_statlist(stdout);
     }
   }	/* End of send command */
   else	/* So that we can ONLY execute known commands */
   /* Command: jobs */
   if(strncmp(commandname,"jobs",4) == 0){	/* This is jobs */
     if(verbose) printf("# Running jobs\n");
     get_and_send_jobslist();
   }	/* End of jobs command */
   else	/* So that we can ONLY execute known commands */
   /* Command: quik */
   if(strncmp(commandname,"quik",4) == 0){	/* This is quik */
     if(verbose) printf("# Running quik\n");
     get_and_send_quiklist();
   }	/* End of quik command */
   else	/* So that we can ONLY execute known commands */
   /* Command: quit */
   if(strncmp(commandname,"quit",4) == 0){
     /*
      * To quit, we flush and CLOSE the fd, then exit.  The primary daemon
      * remains to listen for new connections.
      */
     if(verbose) printf("# Closing connection on quit command.\n");
     close(client_fd);
     exit(0);
   }  /* End of quit command */
   else {
     /* Error:  Command not recognized */
     if(verbose) printf("Error: Command %s not recognized.\n",commandname);
     send_error("Error:  Commandname not recognized or not permitted.");
   }
 }	/* End of command parsing loop */

}	/* End of handlemessage() */

