/* echosigserver.c - Simple TCP server that accepts connections
 *            and echoes back everything it receives.
 *            If in DEBUG mode (either define DEBUG, say
 *              #define DEBUG
 *            or compile the program with the qualifier -DDEBUG
 *               gcc -DDEBUG ..
 *            ) it also writes what it receives to the screen.
 *            It listens on port PROTOPORT set to 5194
 *            or to the port you specify on command line.
 *            Compile as
 *              % gcc -Wall -g [-DDEBUG] -o echosigserver echosigserver.c
 *            Run as
 *              % echosigserver [port] &
 *            You can terminate gracefully the program with the SIGHUP
 *            signal [if waiting at accept, terminate, otherwise
 *            wait for current connection to terminate.].
 *            Note that we did not use setsockopt with option SO_REUSEADDR
 *	      hence we cannot launch echosigserver immediately after
 *	      terminating it.
 *	      WARNING: Running the program on linux 2.6.9-1.667smp
 *            I get unexpected behavior:
 *	      (1) when SIGHUP is sent to echoserver, the handler is executed,
 *                but apparently SA_RESTART is enabled for the accept call, so
 *                the signal does not take the process out of that call, i.e
 *                the process does not terminate.
 *            (2) when I kill the program I can relaunch it immediately on the same
 *		  socket even though I did not set the option SO_REUSEADDR  
 */

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


/* If we are waiting reading from a pipe and
   the interlocutor dies abruptly (say because
   of ^C or kill -9), then we receive a SIGPIPE
   signal. We may also have a SIGPIPE signal if we 
   write twice to socket that has been closed by the 
   partner. Here we handle SIGPIPE.
 */
void sig_pipe(int n) 
{
   fprintf(stderr, "Broken pipe signal\n");
}

volatile int terminate = 0; /* It will become 1 when we have SIGHUP signal */


/* Handler for the SIGHUP signal */
void sig_hup(int n)
{
  printf("SIGHUP signal: graceful termination started\n");
  terminate = 1;
}

extern int errno;

/* Print an error message and exit (Comer+Stevens). See "man stdarg".
   Use it as a regular printf statement with format and perhaps parameters.
 */
int errexit(const char *format, ...)
{
        va_list args;

        va_start(args, format);
        vfprintf(stderr, format, args);
        va_end(args);
        exit(1);
	return 0; /* For silly compilers */
}


/*In an infinite loop, it reads characters from sockfd and writes them
  to the same socket (and to the screen if DEBUG is defined).
*/
void
chr_echo(int sockfd)
{
  for ( ; ; ) {
    ssize_t n;
    char c;
  
    n = read(sockfd, &c, 1);
    if (n > 0) {
       write(sockfd, &c, 1);
#ifdef DEBUG
       putchar(c);
#endif
    } else if ( n < 0) { 
       fprintf(stderr, "Negative return from Read\n");
       if (errno == EINTR) /* IO was interrupted by a signal */
           continue;
       return;
    } else  /* connection closed by other end */
      return;
  }
}


#define PROTOPORT 5194 /* default protocol port number*/
#define QLEN 6

/* Create a server listening socket waiting on port 'port'
   with queue size 'qz'*/
int listensocket(int port, int qz)
{
  int ls;
  int option_value;   /* needed for setsockopt             */ 
  struct sockaddr_in serv;
  
  ls = socket(AF_INET, SOCK_STREAM, 0);
  
  bzero((char *)&serv, sizeof(serv)); /* clears out buffer of this size*/
  serv.sin_family = AF_INET;
  serv.sin_port = htons((u_short)port); /* 80 */
  serv.sin_addr.s_addr = htonl(INADDR_ANY); /* any interface ????*/
  
  /* Make listening socket's port reusable - must appear before bind */
  option_value = 1;
  if (setsockopt(ls, SOL_SOCKET, SO_REUSEADDR, (char *)&option_value, 
		 sizeof(option_value)) < 0)
    errexit("setsockopt failure\n");
  
  /* Bind a local address to the socket */
  if (bind(ls, (struct sockaddr *)&serv, sizeof(serv)) < 0) 
    errexit("bind failed on port %d\n", port);
  
  /* Specify size of request queue */
  if (listen(ls, qz) < 0)
    errexit("listen failed on port %d\n", port);
  
    return ls;
}


/****************************************************************
 * Program: echoserver
 * Purpose: In a loop, accept a connection, echo back all you receive.
 * Syntax: echoserver [port]
 *            port:protocol port number to use
 * Note: The port argument is optional. If no protocol port is
 *       specified, the server uses the default given by PROTOPORT.
 ****************************************************************/
int
main(int argc, char *argv[])
{
  int    sd;             /* listening socket descriptor       */
  int    port;           /* protocol port number              */
 
  /* Check command-line argument for protocol port and extract
   * port number if one is specified. Otherwise, use the default
   * port value given by constant PROTOPORT
   */
  if (argc > 1) {         /* If protocol port is specified */
    port = atoi(argv[1]); /* Convert to binary */
  } else {
    port = PROTOPORT;    /* use default port number */
  }
    
  sd = listensocket(port, 15);
 
  /* Establish handling of the SIGPIPE and SIGHUP signals */
  if (signal(SIGPIPE, sig_pipe) == SIG_ERR ||
      signal(SIGHUP, sig_hup) == SIG_ERR) {
     fprintf(stderr, "Unable to set up signal handlers\n");
  }
  
  /* Main server loop - accept and handle requests */
  for ( ; ; ) {
    struct sockaddr_in cad;/* structure to hold client's address*/
    unsigned int    alen;           /* length of address                 */
    int    sd2;            /* connected socket descriptor */

    alen = sizeof(cad);
    if ((sd2 = accept(sd, (struct sockaddr *)&cad, &alen)) < 0){
      if (errno == EINTR) {
	 fprintf(stderr,"EINTR signal\n");
	 close(sd2);
	 if (terminate) {
	   printf("End of graceful termination\n");
	   break;
	 }
	 continue;
      }
      fprintf(stderr, "accept failed\n");
      exit(1);
    }
    if (terminate) {
	printf("End of graceful termination\n");
	break;
    }
    chr_echo(sd2);
    close(sd2);
    printf("*******************************  END *******************************\n");
  }
  close (sd);
  exit(0);
}


