/* multiserver.c - Simple TCP server that accepts connections
 *            and forks up to MAXCHILD children to handle them. Each child
 *            redirects the connection to standard input and output and
 *            execs an executable image. If a connection cannot be
 *            accepted, since there are already enough children, 
 *            the connection is dropped. 
 * Syntax: multiserver [port [filename]]
 *            port:protocol port number to use
 *            filename: image to execute in child
 * Note: The port argument is optional. If no protocol port is
 *       specified, the server uses the default given by PROTOPORT(5194).
 *       and the same for filename (default is "child").
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/wait.h>

extern int errno;

/* Code executed by the child process: after redirecting the socket sd to
   standard input and output, executes the filename image.
*/
void child (int sd, const char *filename )
{
  /* Redirect standard input and standard output to this socket 
     and close the socket */
  if ((dup2(sd, STDIN_FILENO) != STDIN_FILENO) ||
      (dup2(sd, STDOUT_FILENO) != STDOUT_FILENO)) {
    perror("Cannot dup2 to standard files");
    exit(0);
  }
  close(sd);

  /* At last we execute the image */
  execv(filename, NULL);
  /* We should never get here */
  perror("Failed in signal handler");
  exit(1);
}


#define PROTOPORT 5194 /* default protocol port number*/
#define MAXCHILD 5     /* maximum number of concurrent children */

/* Handler for SIGCHLD signal */
static int children_count = 0; /* Number of children currently running */
void sig_child (int n) 
{
  int pid, status;
  
  /* The child has terminated. Collect status information */
  /* When there is the SIGCHD signal the parent process collects information
     about all currently deceased children. WNOHANG makes waitpid non-blocking */
  while ( (pid = waitpid(-1, &status, WNOHANG)) > 0)
    children_count--;
}

int
main(int argc, char *argv[])
{
  struct sockaddr_in sad;/* structure to hold server's address*/
  struct sockaddr_in cad;/* structure to hold client's address*/
  int    sd;             /* listening socket descriptor       */
  int    sd2;            /* connected socket's descriptor     */
  int    port;           /* protocol port number              */
  int    alen;           /* length of address                 */
  pid_t  pid;            /* process id of child process       */
  int option_value;      /* needed for setsockopt             */
  static char *filename; /* name of file executed by child process*/
 
  /* Check command-line arguments. Use defaults if necessary. */
  filename = (argc > 2)?argv[2]:"child";
  port = (argc > 1)?atoi(argv[1]):PROTOPORT;

  /* Create listening socket */
  if ( (sd = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
    perror("socket creation failed");
    exit(1);
  }

  /* clear and set server's sockaddr structure */
  memset((char *)&sad, 0, sizeof(sad)); 
  sad.sin_family = AF_INET;  /* set family to Internet */
  sad.sin_addr.s_addr = INADDR_ANY; /* set the local IP address */
  sad.sin_port = htons((u_short)port); /* Set port */

  /* Make listening socket's port reusable - must appear before bind */
  option_value = 1;
  if (setsockopt(sd,SOL_SOCKET, SO_REUSEADDR, (char *)&option_value,
		 sizeof(option_value)) < 0)
    {
      perror("setsockopt");
      exit(0);
    }

  /* Bind a local address to the listening socket */
  if (bind(sd, (struct sockaddr *)&sad, sizeof(sad)) < 0) {
    perror("bind failed");
    exit(1);
  }
  
  /* Specify size of request queue of the listening socket */
  if (listen(sd, 6) < 0) {
    perror("listen failed");
    exit(1);
  }

  /* Establish handling of SIGCHLD signal */
  if (signal(SIGCHLD, sig_child) == SIG_ERR) {
    perror("Unable to set up signal handler for SIGCHLD");
    exit(1);
  }
  
  /* Main server loop - accept and handle requests */
  for (;;) {
    while (children_count >= MAXCHILD) {pause();} /* Latency would be substantially reduced if we accepted  */
                                                  /* and closed the connection instead of pausing. But that */
                                                  /* would mean more failures. */
    alen = sizeof(cad);
    if ((sd2 = accept(sd, (struct sockaddr *)&cad, &alen)) < 0){
       if (errno == EINTR) 
          continue;
       perror("accept failed");
       exit(1);
    }
    /* Create child to handle connection */
    children_count++;
    if ((pid = fork()) < 0) {
	perror("Cannot fork a child");
	exit(1);
    } else if (pid == 0)  // in child
	child(sd2, filename); // The child will not return

    /* Close the connected socket: it is being handled by child */
    close(sd2);
  }
}






