/* Program B.4 */
/* uici.c  sockets implementation */

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <netdb.h>
#include <signal.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <errno.h>
#include "uici.h"

#define MAXBACKLOG 50

#define U_BAD_HOST_MSG "Cannot resolve host name"
#define U_BAD_CONNECTION_MSG "Connection request failed"
#define U_BAD_SOCKET_OPTION_MSG "Set socket option failed"
#define U_BAD_BIND_MSG "Bind failed"
#define U_BAD_BACKLOG_MSG "Set Backlog failed"
#define U_TIMEOUT_MSG "Timeout"
#define U_UNKNOWN_MSG "Unknown UICI error"


/* return 1 if error, 0 if OK */
static int u_ignore_sigpipe()
{
   struct sigaction act;

   if (sigaction(SIGPIPE, (struct sigaction *)NULL, &act) < 0)
      return 1;
   if (act.sa_handler == SIG_DFL) {
      act.sa_handler = SIG_IGN;
      if (sigaction(SIGPIPE, &act, (struct sigaction *)NULL) < 0)
         return 1;
   }   
   return 0;
}

/*
 *                           u_open
 * Return a file descriptor which is bound to the given port.
 *
 * parameter:
 *        s = number of port to bind to
 * returns:  file descriptor if successful
 *           -1 on error on system error that sets errno
 *           U_ESOCKOPT if setting socket option fails
 *           U_EBIND if bind fails
 *           U_EBACKLOG if setting backlog fails
 */
int u_open(u_port_t port)
{
   struct sockaddr_in server;
   int sock;
   int true = 1;

   if ( (u_ignore_sigpipe() != 0) ||
        ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) )
      return -1;

   if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&true,
                  sizeof(true)) < 0) {
      close(sock);
      return U_ESOCKOPT;
   }

   server.sin_family = AF_INET;
   server.sin_addr.s_addr = htonl(INADDR_ANY);
   server.sin_port = htons((short)port);
   if (bind(sock, (struct sockaddr *)&server, sizeof(server)) < 0) {
      close(sock);
      return U_EBIND;
   }

   if (listen(sock, MAXBACKLOG) < 0) {
      close(sock);
      return U_EBACKLOG;
   }
   return sock;
}

/*
 *                           u_accept
 * Wait for a from a host on a specified port.
 *
 * parameters:
 *      fd = file descriptor previously bound to listening port
 *      hostn = name of host to listen for
 *      hostnsize = size of hostn buffer
 * returns:  communication file descriptor or -1 on error
 *
 * comments: This function is used by the server to wait for a
 * communication.  It blocks until a remote request is received
 * from the port bound to the given file descriptor.
 * hostn is filled with an ASCII string containing the remote
 * host name.  It must point to a buffer of size at least hostnsize.
 * If the name does not fit, as much of the name as is possible is put
 * into the buffer.
 * If hostn is NULL or hostnsize <= 0, no hostname is copied.
 */
int u_accept(int fd, char *hostn, int hostnsize)
{
   struct hostent *hostptr;
   int len = sizeof(struct sockaddr);
   struct sockaddr_in net_client;
   int retval;

   while ( ((retval =
           accept(fd, (struct sockaddr *)(&net_client), &len)) == -1) &&
          (errno == EINTR) )
      ;  
   if ( (retval == -1) || (hostn == NULL) || (hostnsize <= 0) )
      return retval;
   hostptr = gethostbyaddr((char *)&(net_client.sin_addr), 4, AF_INET);
   if (hostptr == NULL)
      strncpy(hostn, inet_ntoa(net_client.sin_addr), hostnsize-1);
   else
      strncpy(hostn, (*hostptr).h_name, hostnsize-1);
   hostn[hostnsize-1] = 0;
   return retval;
}

/*
 *                           u_connect
 * Initiate communication with a remote server.
 *
 * parameters:
 *     port  = well-known port on remote server
 *     hostn = character string giving the Internet name of the
 *             remote machine
 * returns:  the file descriptor used for communication if successful
 *           -1 on error on system error that sets errno
 *           U_EHOST if hostn cannot be resolved
 *           U_ECONNECTION if connection fails
 */
int u_connect(u_port_t port, char *hostn)
{
   struct hostent *hp;
   int retval;
   struct sockaddr_in server;
   int sock;

   if (isdigit((int)(*hostn))) {
      server.sin_addr.s_addr = inet_addr(hostn);
   }
   else {
      hp = gethostbyname(hostn);
      if (hp == NULL) {
         return U_EHOST;
      }  
      memcpy((char *)&server.sin_addr, hp->h_addr_list[0], hp->h_length);
   }
   server.sin_port = htons((short)port);
   server.sin_family = AF_INET;

   if ( (u_ignore_sigpipe() != 0) ||
        ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) )
      return -1;

   while ( ((retval =
       connect(sock, (struct sockaddr *)&server, sizeof(server))) == -1)
        && (errno == EINTR) )
      ;  
   if (retval == -1) {
        close(sock);
        return U_ECONNECTION;
   }
   return sock;
}
 
/*
 *                           u_read
 *
 * Retrieve information from a file descriptor.
 * This is identical to read except that it restarts if interrupted by a signal.
 *
 * parameters:
 *       fd = file descriptor
 *       buf = buffer to be output
 *       nbyte = number of bytes to retrieve
 * returns:
 *      -1 if an error occurred
 *      otherwise the number of bytes read is returned
 */
 
ssize_t u_read(int fd, void *buf, size_t size)
{
   ssize_t retval;
 
   while (retval = read(fd, buf, size), retval == -1 && errno == EINTR)
     ;
   return retval;
}    
 
/*
 *                           u_write
 *
 * Send information on a file descriptor.
 * This is identical to write except that is restarts if interrupted
 * by a signal and will keep attempting to send until the entire
 * buffer is sent.  
 *
 * parameters:
 *       fd = file descriptor
 *       buf = buffer to be output
 *       nbyte = number of bytes to send
 * returns:
 *      -1 if an error occurred
 *      otherwise nbyte bytes have been written and nbye is returned
 */
 
ssize_t u_write(int fd, void *buf, size_t size)
{
   size_t total_bytes;
   ssize_t bytes_written;
   size_t bytes_to_write;
   char *bufp;

   for (bufp = buf, bytes_to_write = size, total_bytes = 0;
       bytes_to_write > 0;
       bufp += bytes_written, bytes_to_write -= bytes_written) {
      bytes_written = write(fd, bufp, bytes_to_write);
      if ((bytes_written) == -1 && (errno != EINTR))
         return -1;
      if (bytes_written == -1)
         bytes_written = 0;
      total_bytes += bytes_written;
   }
   return total_bytes;
}     

char *u_strerror(int errnum) {
   if (errnum == -1)
      return strerror(errnum);
   if (errnum == U_EHOST)
      return U_BAD_HOST_MSG;
   if (errnum == U_ECONNECTION)
      return U_BAD_CONNECTION_MSG;
   if (errnum == U_ESOCKOPT)
      return U_BAD_SOCKET_OPTION_MSG;
   if (errnum == U_EBACKLOG)
      return U_BAD_BACKLOG_MSG;
   if (errnum == U_EBIND)
      return U_BAD_BIND_MSG;
   if (errnum == U_ETIMEOUT)
      return U_TIMEOUT_MSG;
   else
      return U_UNKNOWN_MSG;
}



