/* -*-C-*-

$Id: os2sock.c,v 1.22 2007/01/05 21:19:25 cph Exp $

Copyright (C) 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994,
    1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
    2006, 2007 Massachusetts Institute of Technology

This file is part of MIT/GNU Scheme.

MIT/GNU Scheme is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at
your option) any later version.

MIT/GNU Scheme is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with MIT/GNU Scheme; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301,
USA.

*/

/* This conditional encompasses the entire file.  */
#ifndef DISABLE_SOCKET_SUPPORT

/* This definition is necessary for compilation with newer versions of
   the Developer's Toolkit for OS/2.  The newer version default to
   using TCP/IP 4.1, but this code was designed for TCP/IP 4.0.  */
#define TCPV40HDRS

#include "scheme.h"
#include "prims.h"
#include "osscheme.h"
#include "os2.h"
#include "uxsock.h"

#include <types.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/un.h>
#include <sys/socket.h>

#ifndef INADDR_LOOPBACK
#  define INADDR_LOOPBACK 0x7F000001
#endif

static Tchannel initialize_stream_socket (int, enum channel_type);
static msg_t * stream_socket_reader (LHANDLE, qid_t, msg_t *, int *);
static void stream_socket_operator
  (Tchannel, chop_t, choparg_t, choparg_t, choparg_t);

#define VOID_SOCKET_CALL(proc, args)					\
{									\
  while ((proc args) < 0)						\
    {									\
      if ((sock_errno ()) != SOCEINTR)					\
	OS2_error_system_call ((sock_errno ()), syscall_ ## proc);	\
      deliver_pending_interrupts ();					\
    }									\
}

#define VALUE_SOCKET_CALL(proc, args, var)				\
{									\
  while (1)								\
    {									\
      int rc = (proc args);						\
      if (rc >= 0)							\
	{								\
	  var = rc;							\
	  break;							\
	}								\
      if ((sock_errno ()) != SOCEINTR)					\
	OS2_error_system_call ((sock_errno ()), syscall_ ## proc);	\
      deliver_pending_interrupts ();					\
    }									\
}

static void
socket_close_on_abort_1 (void * sp)
{
  (void) soclose (* ((int *) sp));
}

static void
socket_close_on_abort (int s)
{
  int * sp = (dstack_alloc (sizeof (int)));
  (*sp) = s;
  transaction_record_action (tat_abort, socket_close_on_abort_1, sp);
}

Tchannel
OS_open_tcp_stream_socket (void * host, unsigned int port)
{
  int s;
  struct sockaddr_in address;

  transaction_begin ();
  VALUE_SOCKET_CALL (socket, (PF_INET, SOCK_STREAM, 0), s);
  socket_close_on_abort (s);
  memset ((&address), 0, (sizeof (address)));
  (address . sin_family) = AF_INET;
  memcpy ((& (address . sin_addr)), host, (sizeof (struct in_addr)));
  (address . sin_port) = port;
  VOID_SOCKET_CALL
    (connect, (s,
	       ((struct sockaddr *) (&address)),
	       (sizeof (address))));
  return (initialize_stream_socket (s, channel_type_tcp_stream_socket));
}

Tchannel
OS_open_unix_stream_socket (const char * filename)
{
  int s;
  struct sockaddr_un address;

  transaction_begin ();
  VALUE_SOCKET_CALL (socket, (PF_OS2, SOCK_STREAM, 0), s);
  socket_close_on_abort (s);
  memset ((&address), 0, (sizeof (address)));
  (address . sun_family) = AF_OS2;
  strncpy ((address . sun_path), filename, (sizeof (address . sun_path)));
  VOID_SOCKET_CALL
    (connect, (s,
	       ((struct sockaddr *) (&address)),
	       (sizeof (address))));
  return (initialize_stream_socket (s, channel_type_unix_stream_socket));
}

int
OS_get_service_by_name (const char * service_name, const char * protocol_name)
{
  struct servent * entry
    = (getservbyname (((char *) service_name),
		      ((char *) protocol_name)));
  return ((entry == 0) ? (-1) : (entry -> s_port));
}

unsigned long
OS_get_service_by_number (const unsigned long port_number)
{
  return ((unsigned long) (htons ((unsigned short) port_number)));
}

unsigned int
OS_host_address_length (void)
{
  return (sizeof (struct in_addr));
}

char **
OS_get_host_by_name (const char * host_name)
{
  struct hostent * entry = (gethostbyname ((char *) host_name));
  return ((entry == 0) ? 0 : (entry -> h_addr_list));
}

#define HOSTNAMESIZE 1024

const char *
OS_get_host_name (void)
{
  char host_name [HOSTNAMESIZE];
  if (gethostname (host_name, HOSTNAMESIZE))
    OS2_error_anonymous ();
  {
    char * result = (OS_malloc ((strlen (host_name)) + 1));
    strcpy (result, host_name);
    return (result);
  }
}

const char *
OS_canonical_host_name (const char * host_name)
{
  struct hostent * entry = (gethostbyname ((char *) host_name));
  if (entry == 0)
    return (0);
  {
    char * result = (OS_malloc ((strlen (entry -> h_name)) + 1));
    strcpy (result, (entry -> h_name));
    return (result);
  }
}

const char *
OS_get_host_by_address (const char * host_addr)
{
  struct hostent * entry
    = (gethostbyaddr (((char *) host_addr),
		      (OS_host_address_length ()),
		      AF_INET));
  if (entry == 0)
    return (0);
  {
    char * result = (OS_malloc ((strlen (entry -> h_name)) + 1));
    strcpy (result, (entry -> h_name));
    return (result);
  }
}

void
OS_host_address_any (void * addr)
{
  (((struct in_addr *) addr) -> s_addr) = (htonl (INADDR_ANY));
}

void
OS_host_address_loopback (void * addr)
{
  (((struct in_addr *) addr) -> s_addr) = (htonl (INADDR_LOOPBACK));
}

Tchannel
OS_create_tcp_server_socket (void)
{
  int s;
  VALUE_SOCKET_CALL (socket, (PF_INET, SOCK_STREAM, 0), s);
  return (initialize_stream_socket (s, channel_type_tcp_server_socket));
}

void
OS_bind_tcp_server_socket (Tchannel channel, void * host, unsigned int port)
{
  struct sockaddr_in address;
  memset ((&address), 0, (sizeof (address)));
  (address . sin_family) = AF_INET;
  memcpy ((& (address . sin_addr)), host, (sizeof (address . sin_addr)));
  (address . sin_port) = port;
  VOID_SOCKET_CALL
    (bind, (((int) (CHANNEL_HANDLE (channel))),
	    ((struct sockaddr *) (&address)),
	    (sizeof (struct sockaddr_in))));
}

#ifndef SOCKET_LISTEN_BACKLOG
#define SOCKET_LISTEN_BACKLOG 5
#endif

void
OS_listen_tcp_server_socket (Tchannel channel)
{
  VOID_SOCKET_CALL
    (listen, (((int) (CHANNEL_HANDLE (channel))), SOCKET_LISTEN_BACKLOG));
}

Tchannel
OS_server_connection_accept (Tchannel channel,
			     void * peer_host, unsigned int * peer_port)
{
  static struct sockaddr_in address;
  int s;

  transaction_begin ();
  while (1)
    {
      int address_length = (sizeof (struct sockaddr_in));
      s = (accept (((int) (CHANNEL_HANDLE (channel))),
		   ((struct sockaddr *) (&address)),
		   (&address_length)));
      if (s >= 0)
	break;
      if ((sock_errno ()) == SOCEWOULDBLOCK)
	return (NO_CHANNEL);
      if ((sock_errno ()) != SOCEINTR)
	OS2_error_system_call ((sock_errno ()), syscall_accept);
      deliver_pending_interrupts ();
    }
  socket_close_on_abort (s);
  if (peer_host != 0)
    memcpy (peer_host, (& (address . sin_addr)), (sizeof (struct in_addr)));
  if (peer_port != 0)
    (*peer_port) = (address . sin_port);
  return (initialize_stream_socket (s, channel_type_tcp_stream_socket));
}

static Tchannel
initialize_stream_socket (int s, enum channel_type type)
{
  Tchannel channel = (OS2_allocate_channel ());
  OS2_initialize_channel (channel, s, (CHANNEL_READ | CHANNEL_WRITE), type);
  OS2_start_channel_thread (channel,
			    stream_socket_reader,
			    stream_socket_operator);
  transaction_commit ();
  return (channel);
}

static msg_t *
stream_socket_reader (LHANDLE handle, qid_t qid, msg_t * message, int * eofp)
{
  int nread;
  do
    nread = (recv (((int) handle),
		   (SM_READAHEAD_DATA (message)),
		   (sizeof (SM_READAHEAD_DATA (message))),
		   0));
  while ((nread < 0) && ((sock_errno ()) == SOCEINTR));
  if (nread >= 0)
    {
      (SM_READAHEAD_SIZE (message)) = nread;
      (*eofp) = (nread == 0);
      return (message);
    }
  OS2_destroy_message (message);
  if ((sock_errno ()) == SOCENOTSOCK)
    /* Socket was closed on us -- no need to do anything else.  */
    return (0);
  (*eofp) = 0;
  return (OS2_make_syscall_error ((sock_errno ()), syscall_recv));
}

static void
stream_socket_operator (Tchannel channel, chop_t operation,
			choparg_t arg1, choparg_t arg2, choparg_t arg3)
{
  switch (operation)
    {
    case chop_read:
      OS2_channel_thread_read_op (channel, arg1, arg2, arg3);
      break;
    case chop_write:
      VALUE_SOCKET_CALL
	(send, (((int) (CHANNEL_HANDLE (channel))),
		((char *) arg1),
		((size_t) arg2),
		0),
	 (* ((long *) arg3)));
      break;
    case chop_close:
      OS2_channel_thread_close (channel);
      VOID_SOCKET_CALL (soclose, ((int) (CHANNEL_HANDLE (channel))));
      break;
    default:
      OS2_logic_error ("Unknown operation for stream socket.");
      break;
    }
}

#endif /* not DISABLE_SOCKET_SUPPORT */
