/* ------------------------------------------------------------------------

   File:	server.c
   Synopsis:	A process to server web clients up a little Soar
   Created By:	Christopher R. Waterson <waterson@eecs.umich.edu>
   Created On:	05 Sep 1996
   Modified By:	Christopher R. Waterson <waterson@eecs.umich.edu>
   Modified:	05 Sep 1996

   This file contains the complete code for the "Soar Server", a
   process that creates a server socket and accepts connections. When
   a new connection is made, it forks a child process, re-mapping the
   child's stdin, stdout, and stderr to the socket, then executing
   the Soar7 interpreter via the runsoar script.

   By default, it serves on the port specified by DEFAULT_PORT (1212),
   but this can be changed using the "-port" command-line
   parameter. It runs the command specified by SOAR_COMMAND, but this
   can be changed using the "-command" command-line parameter.

------------------------------------------------------------------------ */

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

/*
 * The command used to start Soar7
 */

#define SOAR_COMMAND "runsoar"


/*
 * The default port on which we'll server
 */

#define DEFAULT_PORT 1212


/*
 * A buffer used for sending and receiving messages
 */

char buffer[256];



/*
 * Create the server socket on which we'll accept connections
 *
 * port - The port on which to create the server process.
 */

int createServerSocket(int port)
{
  struct sockaddr_in serverSockAddr;
  int fd = socket(AF_INET, SOCK_STREAM, 0);

  if (fd == -1) {
    fprintf(stderr, "Unable to create socket: %s\n", strerror(errno));
    exit(1);
  }

  /* So we can re-use addresses */
  if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, 0, 0) == -1) {
    fprintf(stderr, "Unable to set SO_REUSEADDR: %s\n", strerror(errno));
  }


  /* Bind it to the specified port */
  serverSockAddr.sin_family      = AF_INET;
  serverSockAddr.sin_addr.s_addr = htonl(INADDR_ANY);
  serverSockAddr.sin_port        = htons(port);

  if (bind(fd, (struct sockaddr *) &serverSockAddr,
	   sizeof(serverSockAddr)) == -1) {
    fprintf(stderr, "Unable to bind socket: %s\n", strerror(errno));
    exit(1);
  }

  return fd;
}



/*
 * Execute the Soar7 process after re-mapping the stdin, stdout
 * and stderr file descriptors to the specified socket file descriptor.
 *
 * fd - The file descriptor to which we should remap all I/O.
 */

void run(char* command, int fd)
{
  char * argv[2];

  /* Remap all the standard I/O file descriptors to the specified
     socket file descriptor. */

  if (dup2(fd, STDIN_FILENO) == -1) {
    fprintf(stderr, "Unable to dup stdin: %s\n", strerror(errno));
    exit(1);
  }


  if (dup2(fd, STDOUT_FILENO) == -1) {
    fprintf(stderr, "Unable to dup stdout: %s\n", strerror(errno));
    exit(1);
  }


  if (dup2(fd, STDERR_FILENO) == -1) {
    fprintf(stderr, "Unable to dup stderr: %s\n", strerror(errno));
    exit(1);
  }

  /* Execute Soar */
  argv[0] = command;
  argv[1] = 0;

  if (execvp(command, argv) == -1) {
    fprintf(stderr, "Unable to exec \"%s\": %s\n", command, strerror(errno));
    exit(1);
  }
}




/*
 * Create the server-side Soar process by forking a new process
 *
 * fd - The file-descriptor to use for communication with the client.
 */

void createServerProcess(char* command, int fd)
{
  int childPid = fork();
      
  if (childPid < 0) {
    fprintf(stderr, "Unable to fork child process: %s\n", strerror(errno));
    exit(1);
  }

  if (childPid == 0) {
    /* We're in the child process */
    /* printf("Accepted connection.\n"); */

    /* This won't return... */
    run(command, fd);
    exit(0);
  }
}



/*
 * Accept connections on the specified server socket.
 *
 * serverFd - The file descriptor of the server socket on which to
 * accept connections
 */

void acceptConnections(int serverFd, char* command)
{
  if (listen(serverFd, 4) == -1) {
    fprintf(stderr, "Unable to listen: %s\n", strerror(errno));
    exit(1);
  }

  for ( ; ; ) {
    struct sockaddr_in clientSockAddr;
    int clientSockAddrSize = sizeof(clientSockAddr);

    int clientFd = accept(serverFd, (struct sockaddr *) &clientSockAddr,
			&clientSockAddrSize);

    if (clientFd == -1) {
      fprintf(stderr, "Unable to accept connection: %s\n", strerror(errno));
      exit(1);
    }

    createServerProcess(command, clientFd);
    close(clientFd);

    /* Call wait() to clean up any straggling connection */
    waitpid(-1, 0, WNOHANG);
  }
}




/*
 * The main program body. Creates the server socket and accepts
 * connections.
 */

int main(int argc, char* argv[]) {
  char* command = SOAR_COMMAND;
  int port = DEFAULT_PORT;
  int fd;
  int i;

  for (i = 1; i < argc; ++i) {
    /* They specified an alternate port */
    if (strcmp(argv[i], "-port") == 0) {
      port = atoi(argv[++i]);
    }

    /* They specified an alternate command */
    if (strcmp(argv[i], "-command") == 0) {
      command = argv[++i];
    }

    /* They want to know how to use it */
    if (strcmp(argv[i], "-help") == 0
	|| strcmp(argv[i], "?") == 0) {
      fprintf(stderr, "Usage: %s [-port <port>] [-command <command>]\n",
	      argv[0]);

      return 0;
    }
  }


  fd = createServerSocket(port);
  acceptConnections(fd, command);

  return 0;
}



