/*
 * sockio.c
 * File Revision 0
 * Socket I/O for http server.
 * (c) 2000 Jacob Lundberg, jacob@chaos2.org
 */


/*
 * 2000.11.10	Initial implementation
 */


#include "server.h"


/* Location of symbols for DBG_PRN. */
#define  WHENCE                 "sockio.c"


void setup_socket(void) {
/*
 * setup_socket()
 * Open a socket to listen on.
 */

   DBG_PRN("setup_socket\n", WHENCE);

   /* Open the socket for listening. */
   sv_sock_lstn = socket(AF_INET, SOCK_STREAM, getprotobyname("tcp")->p_proto);

   /* Done reading protocol information. */
   endprotoent();

   /* Make sure a socket was opened. */
   if(sv_sock_lstn < 0) {
      fprintf(stderr, "server: unable to establish tcp socket\n");
      do_exit(1);
   }

}


void setup_bind(unsigned short port) {
/*
 * setup_bind()
 * Bind an open socket.
 */

   /* Name our socket (and make sure port isn't swabbed). */
   struct sockaddr_in address = {AF_INET, htons(port), {INADDR_ANY}};

   DBG_PRN("setup_bind: sv_sock_lstn = %i, port = %i\n", WHENCE, sv_sock_lstn, port);

   /* Try to bind the socket. */
   if(bind(sv_sock_lstn, (struct sockaddr *)&address, sizeof(struct sockaddr_in))) {
      fprintf(stderr, "server: unable to bind tcp socket\n");
      do_exit(1);
   }

}


void setup_listen(int backlog) {
/*
 * setup_listen()
 * Start to listen on a bound socket.
 */

   DBG_PRN("setup_listen: sv_sock_lstn = %i, backlog = %i\n", WHENCE, sv_sock_lstn, backlog);

   if(listen(sv_sock_lstn, backlog)) {
      fprintf(stderr, "server: unable to listen on tcp socket\n");
      do_exit(1);
   }

}


void action_accept() {
/*
 * action_accept()
 * Wait on new connections.
 */

   struct sockaddr_in address;
   int sockaddr_size = sizeof(struct sockaddr_in);

   DBG_PRN("action_accept: sv_sock_lstn = %i\n", WHENCE, sv_sock_lstn);

   /*
    * Accept connections on our socket.
    * Different OSen want different struct types here.
    * The type I've used makes Linux happy but generates
    * a warning on SunOS.  There is no operational impact.
    */
   if((sv_sock_comm = accept(sv_sock_lstn, (struct sockaddr *)&address, &sockaddr_size)) < 0) {
      fprintf(stderr, "server: unable to accept on tcp socket\n");
      do_exit(1);
   }

}


char *action_read(char *buffer) {
/*
 * action_read()
 * Read in a request into a newly realloc'd buffer.
 */

   int resizes = 1;
   size_t size_total = 0, size_add = 0;

   DBG_PRN("action_read: buffer = %p\n", WHENCE, buffer);

   /* Size the buffer to the basic growth size. */
   buffer = (char *)realloc(buffer, SV_BUFFER_GROW_SIZE);

   /* Loop and get more data. */
   do {
      if(size_total > SV_BUFFER_GROW_SIZE * (resizes - 1)) {
         /* Buffer is within the last region, so increase its size. */
         buffer = (char *)realloc(buffer, SV_BUFFER_GROW_SIZE * ++resizes);
      }

      /* Try to read some more data. */
      size_add = read(sv_sock_comm, buffer + size_total, SV_BUFFER_GROW_SIZE);

      /* Detect end-of-input conditions. */
      if(size_add == 0) break;
      if(size_add > 0) size_total += size_add;
      if(buffer[size_total - 2] == '\n' && buffer[size_total - 1] == '\n') break;
      if(buffer[size_total - 4] == '\r' && buffer[size_total - 3] == '\n' &&
         buffer[size_total - 2] == '\r' && buffer[size_total - 1] == '\n') break;
   } while(resizes < SV_BUFFER_MAX_GROWTHS);

   /* Make sure the string is NULL-terminated. */
   if(size_total >= SV_BUFFER_MAX_GROWTHS * SV_BUFFER_GROW_SIZE)
      size_total = SV_BUFFER_MAX_GROWTHS * SV_BUFFER_GROW_SIZE - 1;
   if(size_total >= SV_BUFFER_GROW_SIZE * resizes)
      buffer = (char *)realloc(buffer, SV_BUFFER_GROW_SIZE * ++resizes);
   buffer[size_total] = '\0';

   return(buffer);

}


void action_write(char *buffer, int size) {
/*
 * action_write()
 * Write out a string of chars to the socket.
 */

   int written;

   if(buffer == NULL) {
      DBG_PRN("action_write: NULL buffer\n", WHENCE);
      return;
   } else
      DBG_PRN("action_write: size = %i\n", WHENCE, size);

   /* Write out some data.  Length 0 writes are no-ops for portability. */
   while(size > 0) {
      written = write(sv_sock_comm, buffer, size * sizeof(char));
      if(written < 0) {
         DBG_PRN("action_write: socket write error\n", WHENCE);
         return;
      } else {
         size -= written * sizeof(char);
         buffer += written;
      }
   }

}

