/* Sockets that don't block in a (Toolbox) app

$Id: xsock,v 1.18 2001/04/01 11:21:22 joseph Exp $

*/

#include <stdio.h>
#include <string.h>
#include <time.h>

#include "sys/types.h"

#ifdef SOCKLIB
  #include "socklib.h"
  #include "unixlib.h"
#else
  #include "sys/filio.h"
  #include "sys/select.h"
#endif

#include "sys/errno.h"
#include "sys/ioctl.h"
#include "sys/socket.h"
#include "sys/time.h"

#include "swis.h"

#include "config.h"
#include "defines.h"
#include "errlist.h"
#include "log.h"
#include "msgs.h"
#include "treport.h"
#include "xsock.h"
#include "stopquit.h"
#include "leakcheck.h"
#include "sockwatch.h"

#include "popstar.h"

#define Resolver_GetHost 0x46001

#ifndef SOCKLIB
  /* Missing from NetLib's headers */
  extern void close(int);
#endif

static int xsock_nice = 20;
static int xsock_last_poll;
static int xsock_timeout = 300;

/* Calls event_poll and records time */
static _kernel_oserror *xsock_poll(int *code, WimpPollBlock *block, int nextidle)
{
  _kernel_oserror *result;

  if ( nextidle != -1 && socketwatch_pollword == 0 )
    result = popstar_event_poll(code, block, -1); /* no poll word for some reasn, take all null polls */
  else
    result = popstar_event_poll(code, block, nextidle);

  _swix(OS_ReadMonotonicTime, _OUT(0), &xsock_last_poll);

  return result;
}

/* Calls xsock_poll if it's been a while since last poll
   Returns true if result of poll makes it necessary to quit */
static bool xsock_check_nice()
{
  int now;

  _swix(OS_ReadMonotonicTime, _OUT(0), &now);
  now -= xsock_last_poll;
  if ( now < 0 || now > xsock_nice )
  {
    int ecode;
    WimpPollBlock event;

    xsock_poll(&ecode, &event, -1);
    if ( stopquit_pending )
      return true;

    xsock_poll(&ecode, &event, -1);
    if ( stopquit_pending )
      return true;
  }
  return false;
}

/* if the call is successful, dns will be filled in with a 
 * dns handle that the client must dispose of when it's finished
 * with the returned hostent structure
 */
struct hostent *xsock_gethostbyname(const char *name, dns_t **dns)
{
  struct hostent *addr;
  dns_status_t status;
  time_t timeout;
  int ecode;
  WimpPollBlock event;
  int finished;

  xsock_timeout = config_lookup_num("SocketTimeout:300");
  timeout = time(NULL) + xsock_timeout;

  *dns = dns_gethostbyname(name);

  finished = 0;
  do
  {
    status = dns_check(*dns);
    
    switch (status)
    {
      case dns_complete_success:
      case dns_complete_failure:
        finished = 1;
        break;
      
      default:
        /* not finished yet */
        break;
    }

    if (time(NULL) > timeout)
      finished = 1;
    
    if (!finished)
      xsock_poll(&ecode, &event, -1);
    
    /* abort loop when we get a quit event (eg. user pressing 'quit'
     * button) or we finish
     */
  }
  while (!finished && !stopquit_pending);
  
  addr = dns_getanswer(*dns);
  
  if (!addr)
  {
    xsyslog_logmessage(log_NAME,
      msgs_lookup2("NoHost", name, ""),
      log_SocketError);
    dns_dispose(*dns);
  }
  
  return addr;
}

xsock xsock_create(int af, int type, int protocol)
{
  xsock s;
  leak_check();
  s = socket(af, type, protocol);
  if (s != -1)
  {
    int on = 1;

    if (ioctl(s, FIONBIO, &on) == -1)
    {
      close(s);
      leak_check();
      return -1;
    }
    socketwatch_register( s );
  }
  leak_check();
  return s;
}

int xsock_connect(xsock s, const struct sockaddr *name, int namelen)
{
  int result;
  int val = 1;
  time_t timeout;
  
  xsock_timeout = config_lookup_num("SocketTimeout:300");
  timeout = time(NULL) + xsock_timeout;

  leak_check();

  xsock_nice = config_lookup_num( "Nice:20" );
  _swix( OS_ReadMonotonicTime, _OUT(0), &xsock_last_poll );
  for (;;)
  {
    if ((result = connect(s, (struct sockaddr *) name, namelen)) == -1)
    {
      int ecode;
      WimpPollBlock event;

      if (time(NULL) > timeout)
        errno = ETIMEDOUT;

      switch (errno)
      {
      case EISCONN:
        return 0;
      case EALREADY:
      case EWOULDBLOCK:
      case EINPROGRESS:
      {
        unsigned int now;
        _swix(OS_ReadMonotonicTime, _OUT(0), &now);
        xsock_poll(&ecode, &event, now + 500);
        if ( stopquit_pending )
          return -1;
        break;
      }
      default:
        return -1;
      }
    }
    else
    {
      if (ioctl(s, FIONBIO, &val) == -1)
      {
        close(s);
        leak_check();
        return -1;
      }
      return result;
    }
  }


  return 0;
}

void xsock_close(xsock s)
{
  leak_check();
  close(s);
  socketwatch_deregister( s );
  leak_check();
}

static bool xsock_write(xsock s, const char *data, int len)
{
  time_t timeout = time(NULL) + xsock_timeout;

  while (len)
  {
    int sent;

    if ( xsock_check_nice() )
      return false;

    if ((sent = send(s, (char *) data, len, 0)) <= 0)
    {
      int ecode;
      WimpPollBlock event;
      
      if (time(NULL) > timeout)
        errno = ETIMEDOUT;

      switch (errno)
      {
      case EALREADY:
      case EISCONN:
      case EWOULDBLOCK:
      case EINPROGRESS:
        xsock_poll(&ecode, &event, -1);
        if ( stopquit_pending )
          return false;
        break;
      default:
        return false;
      }
    }
    else
    {
      timeout = time(NULL) + xsock_timeout;
      len -= sent;
      data += sent;
    }
  }

  return true;
}

int xsock_read(xsock s, char *data, int len)
{
  int received;
  time_t timeout = time(NULL) + xsock_timeout;

  for (;;)
  {
    if ( xsock_check_nice() )
      return -1;

    if ((received = recv(s, data, len, 0)) <= 0)
    {
      int ecode;
      WimpPollBlock event;

      if (time(NULL) > timeout)
        errno = ETIMEDOUT;

      switch (errno)
      {
      case EALREADY:
      case EISCONN:
      case EWOULDBLOCK:
      case EINPROGRESS:
      {
        unsigned int now;
        _swix(OS_ReadMonotonicTime, _OUT(0), &now);
        xsock_poll(&ecode, &event, now + 500);
        if ( stopquit_pending )
          return -1;
        break;
      }
      default:
        return -1;
      }
    }
    else
    {
      leak_check();
      return received;
    }
  }
  leak_check();
}

bool xsock_xwrite(xsock sock, const char *command)
{
  char sendbuf[MAX_PATH_LEN];
  leak_check();

  snprintf(sendbuf, sizeof (sendbuf), "%s\r\n", command);
  if (!strncmp (command, "PASS ", 5))
    command = "PASS <password>";
  if (xsock_write(sock, sendbuf, strlen(sendbuf)) <= 0)
  {
    if (!stopquit_pending)
    {
      char str[32];
      sprintf(str, "%d", errno);
      treport(msgs_lookup2("SendErr", errlist_str(errno), str), log_SocketError);
    }
  }
  else
  {
    xsyslog_logmessage(log_NAME,
    	msgs_lookup1("SendOK", command), log_DebugInfo);
    leak_check();
    return true;
  }
  leak_check();
  return false;
}

bool xsock_ywrite(xsock sock, const char *text)
{
  leak_check();
  if (xsock_write(sock, text, strlen(text)) <= 0)
  {
    if (!stopquit_pending)
    {
      char str[32];
      sprintf(str, "%d", errno);
      treport(msgs_lookup2("SendErr", errlist_str(errno), str), log_SocketError);
    }
    leak_check();
    return false;
  }
  leak_check();
  return true;
}

bool xsock_ywritelen(xsock sock, const char *text, int len)
{
  if (xsock_write(sock, text, len) <= 0)
  {
    if (!stopquit_pending)
    {
      char str[32];
      sprintf(str, "%d", errno);
      treport(msgs_lookup2("SendErr", errlist_str(errno), str), log_SocketError);
    }
    leak_check();
    return false;
  }

  return true;
}

