/* multiserver1.c - Simple TCP server that forks MAXCHILD children.
 * Then it waits for a child to terminate accidentaly, in which case a new
 * child is forked to take its place. 
 *
 * All children block on an accept statement on the shared
 * listening socket. When a child successfully accepts a connection, it
 * redirects standard input and output to the connection.
 * When a request arrives a child will read the request, treat it as 
 * a nonsense string, and reply by writing to the client the content 
 * of the file index.html. Then it will  close the connection and go back 
 * to the accept statement.
 *
 * WARNING: in some other Unix systems (I am using Digital Unix) apparently
 * when there is a connection all children complete the accept statement, 
 * but only one with success. All others have to go back to the accept statement. 
 *
 * The main program of the server is created with the command
 *	% gcc -o multiserver1 multiserver1.c 
 * and is invoked with the command
 *	% multiserver1 [port] 
 * where
 * 	port:protocol port number to use
 * The port argument is optional. If no protocol port is
 * specified, the server uses the default given by PROTOPORT (5195).
 * 
 * You may see the connections that exist at a particular time on the server
 * with the command
 *      % netstat | grep 5195
 * where 5195, or whatever, is the port used by the server.
 *
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/wait.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 <fcntl.h>

extern int errno;

char *buffer;			/* buffer where the file is read into */
int bufsize;                    /* size of buffer = file size + 1 */

/* Executed by each child - Read request, and write the buffer */
void child_main(void)
{
  enum {SEGSIZE = 8192};
  int n;
  char readbuf[SEGSIZE];
  int writeseg = (bufsize < SEGSIZE)?bufsize:SEGSIZE;
  char *cursor;
  
  for ( ; ; ) {
    n = read(0, readbuf, SEGSIZE);
    if (n <= 0) {
      /* We check to make sure that read did not fail because
	 of an intervening signal */
      if (errno == EINTR) {
	fprintf(stderr, "Signal while the child was reading\n");
	continue;
      }
      break;
    }
  }
  close(0); /* Close read end */
  for (cursor = buffer; cursor < buffer + writeseg; ) {
    n = write(1, cursor, writeseg);
    if (n <= 0) {
      if (errno == EINTR) {
	fprintf(stderr, "Signal while writing to the child\n");
	continue;
      }
    } else
	cursor += n;
  }
  close(1); /* Close write end */
  return;
}

/* Code executed by the child process: it uses the listening 
   socket sd to accept a client's request on a connected socket.
   After redirecting the socket to standard input and output, 
   calls the child_main routine, and `then is ready for a new connection.
*/
void child (int sd)
{
  int    sd2;                      /* connected socket's descriptor     */  
  struct sockaddr_in cad;          /* structure to hold client's address*/
  int    alen;                     /* length of address                 */
  
  for ( ; ; ) {
    alen = sizeof(cad);
    if ((sd2 = accept(sd, (struct sockaddr *)&cad, &alen)) < 0)
      continue;

    /* Redirect standard input and standard output to this socket 
       and close the socket */
    if ((dup2(sd2, STDIN_FILENO) != STDIN_FILENO) ||
	(dup2(sd2, STDOUT_FILENO) != STDOUT_FILENO)) 
      exit(0);
    close(sd2);
    
    child_main();
  }
}

void filetobuffer(const char *filename)
{
  int fid;                /* descriptor for file to be sent to client */
  struct stat filestat;   /* stats for fid */

  if ((fid = open(filename, O_RDONLY)) == -1) {
    fprintf(stderr, "Unable to open %s\n", filename);
    exit(1);
  }
  if (fstat(fid, &filestat) == -1) {
    fprintf(stderr, "Unable to stat %s\n", filename);
    exit(1);
  }
  bufsize = filestat.st_size;
  buffer = (char *)malloc((size_t)(1+bufsize));
  if (buffer == 0) {
    perror("Out of memory");
    exit(1);
  }
  if (read(fid, buffer, bufsize) != bufsize) {
    fprintf(stderr, "Cannot read all of %s\n", filename);
    exit(1);
  }
  printf ("bufsize = %d\n", bufsize);
}

/* simple routine that creates a child and passes to it the socket sd*/
pid_t create_process(int sd) 
{
  pid_t pid;

  if ((pid = fork()) < 0) {
    perror("Cannot fork a child");
    exit(1);
  } else if (pid == 0) /* in child */
    child(sd); /* The child will not return */
  return pid;
}

/*****************************************************************/

#define PROTOPORT 5195 /* default protocol port number*/
#define MAXCHILD 10     /* maximum number of concurrent children */

int
main(int argc, char *argv[])
{
  struct sockaddr_in sad;/* structure to hold server's address*/
  int    sd;             /* listening socket descriptor       */
  int    port;           /* protocol port number              */
  pid_t  pid;            /* process id of child process       */
  int children_count;    /* number of children being forked */
  int option_value;      /* needed for setsockopt             */
 
  /* Check command-line argument. Use default if necessary. */
  port = (argc > 1)?atoi(argv[1]):PROTOPORT;

  /* Read file to buffer */
  filetobuffer("index.html");

  /* Create listening socket */
  if ( (sd = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
    perror("socket creation failed");
    exit(1);
  }
  
  /* 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);
    }

  /* 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 */
  
  /* 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);
  }
  
  /* create the children processes */
  for (children_count = 0; children_count < MAXCHILD; ++children_count)
    pid = create_process(sd);
  
  /* If a child terminates, create a new child */
  for (;;) {
    pid = wait(0);
    printf("Process %d terminated\n", pid);
    pid = create_process(sd);
  }
}



