/* sockecho.c (emx+gcc) */

/* Test inheritance of socket handles across fork().

   This program is for testing fork().  If you want to implement a
   TCP/IP daemon under OS/2, better use _beginthread() instead of
   fork().

   Unfortunately, accept() of IBM TCP/IP returns EINVAL instead of
   EINTR if interupted by a signal.  Define ACCEPT_BROKEN_EINTR to
   work around the problem by not using signals. */

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

#if defined (__EMX__)
#include <io.h>                 /* setmode() */
#endif


static int opt_banner;
static int opt_incr;
static int verbose;
static int test_holes;
static int test_repeat;
static int connect_no;


static void show_wait (int pid, int t)
{
  if (verbose >= 2)
    {
      if (WIFEXITED (t))
        printf ("Process %d terminated normally, rc=%d\n",
                pid, WEXITSTATUS (t));
      else if (WIFSTOPPED (t))
        printf ("Process %d stopped by signal %d\n", pid, WSTOPSIG (t));
      else
        printf ("Process %d terminated by signal %d\n", pid, WTERMSIG (t));
    }
}


#if !defined (ACCEPT_BROKEN_EINTR)

/* Collect status codes of terminated child processes to avoid filling
   memory with uncollected status codes. */

static void sigchld_handler (int sig)
{
  int t, pid;

  if (verbose >= 2)
    printf ("SIGCHLD\n");
  pid = wait (&t);
  if (pid < 0)
    perror ("wait()");
  else
    show_wait (pid, t);
}

#endif


/* This function handles a connection. */

static void connection (int s)
{
  struct sockaddr_in in_client;
  char buf[4096], *p;
  int i, n, repeat;
  struct hostent *host;

  /* We could take the socket address from the structure pointed to by
     the second argument of accept(), but getpeername() also needs to
     be tested. */

  memset (&in_client, 0, sizeof (in_client));
  n = sizeof (in_client);
  if (getpeername (s, (struct sockaddr *)&in_client, &n) < 0)
    {
      perror ("getpeername()");
      exit (2);
    }
  host = gethostbyaddr ((char *)&in_client.sin_addr,
                        sizeof (in_client.sin_addr), AF_INET);
  if (verbose >= 1)
    {
      if (host != NULL)
        printf ("New connection %d from %s\n", connect_no, host->h_name);
      else
        printf ("New connection %d from %s\n", connect_no,
                inet_ntoa (in_client.sin_addr));
    }

#if defined (__EMX__)
  setmode (s, O_BINARY);
#endif

  if (opt_banner)
    {
      p = "Hello!  I'm sockecho.\r\n";
      if (write (s, p, strlen (p)) != strlen (p))
        {
          perror ("write()");
          exit (2);
        }
    }

  repeat = 0;
  for (;;)
    {
      n = read (s, buf, sizeof (buf));
      if (verbose >= 3)
        printf ("connection(%d): read -> %d\n", s, n);
      if (n < 0)
        perror ("read()");
      if (test_repeat)
        {
          if (n <= 0)
            ++repeat;
          else
            repeat = 0;
          if (n <= 0 && repeat < 8)
            continue;
        }
      if (n < 0)
        exit (2);
      if (n == 0)
        break;
      if (opt_incr)
        for (i = 0; i < n; ++i)
          if (buf[i] >= 0x20 && buf[i] < 0x7e)
            ++buf[i];
      if (write (s, buf, n) != n)
        {
          perror ("write()");
          exit (2);
        }
    }
  if (close (s) != 0)
    {
      perror ("close()");
      exit (2);
    }
  if (verbose >= 1)
    printf ("Connection closed\n");
}


static void usage (void)
{
  fputs ("Usage: sockecho [-p<port>] [-biqv]\n", stderr);
  exit (1);
}


int main (int argc, char *argv[])
{
  int i, port, s_server, s_client;
  struct sockaddr_in in_local;
#if !defined (ACCEPT_BROKEN_EINTR)
  struct sigaction sa;
#endif

  /* Make sure that stdout is unbuffered -- we are using fork()! */

  setvbuf (stdout, NULL, _IONBF, 0);

  port = 7; verbose = 1;

  while ((i = getopt (argc, argv, "bip:qvHR")) != -1)
    switch (i)
      {
      case 'b':
        opt_banner = 1;
        break;
      case 'i':
        opt_incr = 1;
        break;
      case 'p':
        port = atoi (optarg);
        break;
      case 'q':
        verbose = 0;
        break;
      case 'v':
        if (verbose < 2)
          verbose = 2;
        else
          verbose += 1;
        break;
      case 'H':
        test_holes = 1;
        break;
      case 'R':
        test_repeat = 1;
        break;
      default:
        usage ();
      }
  if (optind < argc)
    usage ();

  s_server = socket (AF_INET, SOCK_STREAM, 0);
  if (s_server < 0)
    {
      perror ("socket()");
      exit (2);
    }

  i = 1;
  if (setsockopt (s_server, SOL_SOCKET, SO_REUSEADDR, &i, sizeof (i)) != 0)
    {
      perror ("setsockopt()");
      exit (2);
    }

  memset (&in_local, 0, sizeof (in_local));
  in_local.sin_family = AF_INET;
  in_local.sin_addr.s_addr = INADDR_ANY;
  in_local.sin_port = htons (port);
  if (bind (s_server, (struct sockaddr *)&in_local, sizeof (in_local)) != 0)
    {
      perror ("bind()");
      exit (2);
    }

  if (listen (s_server, 5) != 0)
    {
      perror ("listen()");
      exit (2);
    }

#if !defined (ACCEPT_BROKEN_EINTR)
  sa.sa_handler = sigchld_handler;
  sa.sa_flags = 0;
  sigemptyset (&sa.sa_mask);
  if (sigaction (SIGCHLD, &sa, NULL) != 0)
    {
      perror ("sigaction()");
      exit (2);
    }
#endif

  if (test_holes)
    close (0);

  if (verbose >= 1)
    printf ("Waiting for connections...\n");

  for (;;)
    {
      do
        {
          s_client = accept (s_server, NULL, NULL);
        } while (s_client < 0 && errno == EINTR);
      if (s_client < 0)
        {
          perror ("accept");
          exit (2);
        }
      ++connect_no;
      if (fork () == 0)
        {
          if (close (s_server) != 0)
            {
              perror ("close()");
              exit (2);
            }
          connection (s_client);
          exit (0);
        }
      if (close (s_client) != 0)
        {
          perror ("close()");
          exit (2);
        }

#if defined (ACCEPT_BROKEN_EINTR)
      /* Collect status codes of terminated child processes to avoid
         filling memory with uncollected status codes. */

      {
        int pid, t;

        while ((pid = waitpid (-1, &t, WNOHANG)) > 0)
          show_wait (pid, t);
      }
#endif
    }

  return 0;
}
