/*
 * title:       capitalize_server.c
 *
 * authors:     Craig E. Ward
 *              Kholoud Khateeb
 *              CMSI 698, Homework 3, Problem 4
 *              Spring 2003
 *
 * environment: ANSI C (GCC 3.1/MacOS X:Darwin 6.4)
 *
 * description: This is a translation of a Java implementation of a server that
 *              accepts text from a client and returns the text with all
 *              letters capitalized. The purpose of this program is to
 *              compare and contrast socket and thread programming in a Java
 *              environment with a C environment.
 *
 * build:       cc -ansi -g -Wall -o capitalize_server capitalize_server.c
 *
 * usage:       capitalize_server
 *                  - or redirect stderr on UNIX (including to /dev/null) -
 *              capitalize_server 2>logfile
 */
#include <errno.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <time.h>
#include <signal.h>
#include <pthread.h>            /* POSIX Threads */
#include <sys/types.h>          /* Network Headers */
#include <sys/socket.h>         /* BSD sockets */
#include <netinet/in.h>         /* protocol domains */
#include <arpa/inet.h>          /* Internet Protocol */

/* definitions */
#define SERVICE_PORT    9898
static const short port_id = SERVICE_PORT;
#define MAX_BUF_SIZE    512

/*
 * Structure used to pass data to each thread. The pthreads library only allows
 * for one argument for each created thread.
 */
typedef struct client_data {
  int socket;
  int client_number;
} client_data_t;

/* static function prototypes */
static char *simple_basename(char *);
static void capitalize_service(int);
static void close_server_socket(void);
static void capitalizer(client_data_t *);
static void signal_handler(int);

/* global function prototypes */
void log(char *, ...);

/* initialized data */
static const  char *Greeting[]= {
  "You are talking to the Capitalize Server using POSIX Threads!\n",
  "Enter a line with only a period to quit.\n"
};
int sockfd = 0;

/* uninitialized data */
char *progname;

int main (int argc, char *argv[])
{
  struct sockaddr_in serv_addr;
  int serv_addr_size = sizeof(struct sockaddr_in);

  /* initialize some basics */
  progname = simple_basename(argv[0]);
  atexit(close_server_socket);
  if ((signal(SIGHUP, signal_handler)  == SIG_ERR) ||
      (signal(SIGINT, signal_handler)  == SIG_ERR) ||
      (signal(SIGQUIT, signal_handler) == SIG_ERR)) {
    log("%s: error setting up signal handler: %s", 
        progname, strerror(errno));
  }

  if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) > 0) {
    memset(&serv_addr, '\0', serv_addr_size);
    serv_addr.sin_len         = sizeof serv_addr;
    serv_addr.sin_family      = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port        = htons(port_id);
    if ((bind(sockfd,
              (struct sockaddr *)&serv_addr,
              serv_addr_size)) >= 0) {
      listen(sockfd,SOMAXCONN);
      printf("%s: The capititalization server is running as process %d.\n",
             progname, getpid());
      capitalize_service(sockfd); /* start capitalize server */
    } else {
      log("%s: bind call failed: %s", progname, strerror(errno));
      exit(EXIT_FAILURE);
    }
  } else {
    log("%s: socket or setsockopt call failed: %s", 
        progname, strerror(errno));
    exit(EXIT_FAILURE);
  }

  exit(EXIT_SUCCESS);
}

/*---------------------------------------------------------------------------
 * capitalize_service()
 *
 * Listen for service requests and start a new thread for each incoming
 * client.
 *---------------------------------------------------------------------------
 */
static void capitalize_service(int sock)
{
  log("%s.capitalize_service(%d)", __FILE__, sock);
  int status = 0;
  int client_count = 0;
  int cli_addr_length = sizeof(struct sockaddr_in);
  int next_sock = 0;
  struct sockaddr_in cli_addr;
  pthread_t *worker_thread_p;
  client_data_t *cdata_p;

  while (1) {
    memset(&cli_addr, '\0', cli_addr_length);
    next_sock = accept(sock,
                       (struct sockaddr*)&cli_addr,
                       &cli_addr_length);
    if (next_sock >= 0) {
      client_count++;
      /*
       * Allocate a pthread structure for this client and 
       * create the new thread
       */
      worker_thread_p = (pthread_t *)malloc(sizeof(pthread_t));
      cdata_p = (client_data_t *)malloc(sizeof(client_data_t));
      cdata_p->socket = next_sock;
      cdata_p->client_number = client_count;
      log("-->>creating thread for client %d on socket %d", 
          cdata_p->socket, cdata_p->client_number);

      status = pthread_create(worker_thread_p,
                              NULL,
                              (void *)capitalizer,
                              (void *)cdata_p);
      if (status != 0) {
        log("%s.capitalize_service: pthread_create failed: %s",
            __FILE__, strerror(status));
        break;
      }
      /* 
       * Mark this thread for deletion so the library will cleanup after it
       * when it exits without telling us and free what we allocated here.
       */
      pthread_detach(*worker_thread_p);
      free(worker_thread_p);
    } else {
      log("%s.capitalize_service: accept failed: %s"
          , __FILE__, strerror(errno));
    }
  }
  log("%s.capitalize_service return", __FILE__);
  return;
} /* capitalize_service() */

/*---------------------------------------------------------------------------
 * capitalizer()
 *
 * Service a client with the capitalization service.
 *---------------------------------------------------------------------------
 */
static void capitalizer(client_data_t *cdata_p)
{
  log("%s.capitalizer: client %d on socket %d", __FILE__, 
      cdata_p->client_number, cdata_p->socket);
  int i = 0;
  int bytes = 0;
  char inbuf[MAX_BUF_SIZE];
  char outbuf[MAX_BUF_SIZE];

  /* Send lines expected by the CapitalizeClient class */
  memset(outbuf, '\0', sizeof outbuf);
  sprintf(outbuf, "Greetings! Yor are client number %d!\n", cdata_p->client_number);
  bytes = (int)send(cdata_p->socket, outbuf, strlen(outbuf), 0);
  for (i = 0; i < (sizeof Greeting / sizeof(char *)); i++) {
    bytes = (int)send(cdata_p->socket, Greeting[i], strlen(Greeting[i]), 0);
  }

  /* 
   * Loop until the client wants to quit
   */
  while (1) {
    memset(inbuf, '\0', sizeof inbuf);
    memset(outbuf, '\0', sizeof outbuf);

    bytes = (int)recv(cdata_p->socket, inbuf, (sizeof inbuf) - 1, 0);
    if (bytes == -1) {
      log("%s.capitalizer: recv error: %s",
          __FILE__, strerror(errno));
      break;                    /* terminate loop here */
    }

    if (inbuf[0] == '.' || inbuf[0] == '\0') {  /* say bye! */
      log("%s.capitalizer: closing connection for client %d",
          __FILE__, cdata_p->client_number);
      break;
    }
    for (i = 0; i < bytes; i++) {
      outbuf[i] = (char)toupper((int)inbuf[i]);
    }

    bytes = (int)send(cdata_p->socket, outbuf, strlen(outbuf), 0);
    if (bytes == -1) {
      log("%s.capitalizer: send error: %s",
          __FILE__, strerror(errno));
      break;                    /* terminate loop here */
    }
  }
  /*
   * close this socket and return the allocated memory with the client data.
   */
  close(cdata_p->socket);
  free(cdata_p);
  log("%s.capitalizer return", __FILE__);  
  return;
} /* capitalizer() */

/*---------------------------------------------------------------------------
 * close_server_socket()
 *
 * Close the socket opened for the server. Called asynchronously through the
 * atexit(0 mechanism.
 *---------------------------------------------------------------------------
 */
static void close_server_socket(void)
{
  log("%s: shutting down socket %d for server", progname, sockfd);
  if (sockfd > 0) {
    (void)close(sockfd);
    sockfd = 0;
  }
  return;
} /* close_socket() */

/*---------------------------------------------------------------------------
 * simple_basename()
 *
 * Strip path from string is there is one.
 *---------------------------------------------------------------------------
 */
static char *simple_basename(char *path)
{
  char *name;
  name = strrchr(path, '/');
  if (name != NULL) 
    name++; 
  else
    name = path;
  return name;
} /* simple_basename() */

/*---------------------------------------------------------------------------
 * signal_handler()
 *
 * Trap some common signals used to shutdown the server. Just exit and get the
 * routines registered via atexit() to run.
 *---------------------------------------------------------------------------
 */
static void signal_handler(int sig)
{
  log("%s: caught signal %d, terminating", progname, sig);
  exit(EXIT_SUCCESS);
} /* signal_handler() */

/*---------------------------------------------------------------------------
 * log()
 *
 * A simple logger; writes to stderr.
 *---------------------------------------------------------------------------
 */
void log(char *format, ...)
{
  va_list ap;                   /* variable argument list pointer */
  time_t now = 0;               /* current time */
  char timestr[41];             /* formatting of time */
  
  /* put a timestamp on log text */
  memset(timestr, '\0', sizeof timestr);
  time(&now);
  (void)strftime(timestr, ((sizeof timestr) - 1), 
                 "%m%d %H:%M:%S", localtime(&now));
  fprintf(stderr, "[%s]: ", timestr);

  /* process the argument string */
  va_start(ap, format);
  vfprintf(stderr,format,ap);
  va_end(ap);

  fputc('\n',stderr);
  (void)fflush(stderr);

  return;
} /* log */
