/* -*- mode: C; mode: fold; -*- */
/*
 Copyright (C) 2006-2021,2022 John E. Davis
 *
 This file is part of the S-Lang Library.
 *
 The S-Lang Library 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.
 *
 The S-Lang Library 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 this library; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
 USA.
 */

/* This file was derived from the code in SLtcp.c distributed with slsh */

/*{{{ Include Files */

#define _BSD_SOURCE 1		       /* to get struct ip_mreq */
#define _DEFAULT_SOURCE 1
#include "config.h"
#include <stdio.h>
#include <string.h>

#include <stdlib.h>
#include <unistd.h>

#include <errno.h>
#include <ctype.h>
#include <stdarg.h>

#include <setjmp.h>
#include <signal.h>

#include <sys/types.h>

#include <time.h>
#ifdef HAVE_SYS_TIME_H
# include <sys/time.h>
#endif

#ifdef HAVE_SOCKET_H
# include <socket.h>
#endif

#ifdef HAVE_SYS_SOCKET_H
# include <sys/socket.h>
#endif

#if defined(__NT__)
# include <winsock.h>
# define USE_WINSOCK_SLTCP	1
#else
# if defined(__MINGW32__)
#  define Win32_Winsock
#  include <windows.h>
#  define USE_WINSOCK_SLTCP	1
# endif
#endif

#ifdef USE_WINSOCK_SLTCP
# define USE_WINSOCK_SLTCP	1
#else
# include <netdb.h>
#endif

#ifdef HAVE_SYS_UN_H
# include <sys/un.h>		       /* for AF_UNIX sockets */
#endif

#ifdef HAVE_NETINET_IN_H
# include <netinet/in.h>
#endif

#ifdef HAVE_ARPA_INET_H
# include <arpa/inet.h>
#endif

#include <slang.h>
SLANG_MODULE(socket);

#ifndef h_errno
extern int h_errno;
#endif

/*}}}*/

static int SocketError = -1;
static int SocketHerrnoError = -1;
static int Socket_Type_Id = -1;

typedef struct Socket_Type Socket_Type;
typedef struct
{
   int domain;
   int (*connect)(Socket_Type *, int);
   int (*bind)(Socket_Type *, int);
#define MAX_ACCEPT_REF_ARGS 4
   Socket_Type *(*accept)(Socket_Type *, unsigned int, SLang_Ref_Type **);
   void (*free_socket_data)(Socket_Type *);
}
Domain_Methods_Type;

struct Socket_Type
{
   int fd;			       /* socket descriptor */
   Domain_Methods_Type *methods;
   VOID_STAR socket_data;
   int domain;
   int type;
   int protocol;
};

static Socket_Type *create_socket (int, int, int, int);
static void free_socket (Socket_Type *);
static int close_socket (int);

/*{{{ Generic Routines */

static int Module_H_Errno = 0;

static SLFUTURE_CONST char *herror_to_string (int h)
{
#ifdef HOST_NOT_FOUND
   if (h == HOST_NOT_FOUND)
     return "The specified host is unknown";
#endif

#ifdef NO_ADDRESS
   if (h == NO_ADDRESS)
     return "The requested name is valid but does not have an IP address";
#endif

#ifdef NO_DATA
   if (h == NO_DATA)
     return "The requested name is valid but does not have an IP address";
#endif

#ifdef NO_RECOVERY
   if (h == NO_RECOVERY)
     return "A non-recoverable name server error occurred";
#endif

#ifdef TRY_AGAIN
   if (h == TRY_AGAIN)
     return "A temporary error occurred on an authoritative name server.  Try again later";
#endif

   return "Unknown h_error";
}

static void throw_herror (SLFUTURE_CONST char *what, int h)
{
   Module_H_Errno = h;
   SLang_verror (SocketHerrnoError, "%s: %s", what, herror_to_string(h));
}

static void throw_errno_error (SLFUTURE_CONST char *what, int e)
{
   SLerrno_set_errno (e);
   SLang_verror (SocketError, "%s: %s", what, SLerrno_strerror (e));
}

static int perform_connect (int fd, struct sockaddr *addr, unsigned int len, int throw_err)
{
   while (-1 == connect (fd, addr, len))
     {
#ifdef EINTR
	if (errno == EINTR)
	  {
	     if (-1 == SLang_handle_interrupt ())
	       return -1;
	     continue;
	  }
#endif
	/* The manpage indicates EAGAIN will be returned if no free ports exist.
	 * So allow the caller to handle that.
	 */
	if (throw_err)
	  throw_errno_error ("connect", errno);
	return -1;
     }
   return 0;
}

static int perform_bind (int fd, struct sockaddr *addr, unsigned int len)
{
   while (-1 == bind (fd, addr, len))
     {
#ifdef EINTR
	if (errno == EINTR)
	  {
	     if (-1 == SLang_handle_interrupt ())
	       return -1;
	     continue;
	  }
#endif
	/* The manpage indicates EAGAIN will be returned if no free ports exist.
	 * So allow the caller to handle that.
	 */
	throw_errno_error ("bind", errno);
	return -1;
     }
   return 0;
}

static Socket_Type *perform_accept (Socket_Type *s, struct sockaddr *addr, unsigned int *lenp)
{
   socklen_t addr_len;
   Socket_Type *s1;
   int fd1;

   addr_len = *lenp;
   while (-1 == (fd1 = accept (s->fd, addr, &addr_len)))
     {
#ifdef EINTR
	if (errno == EINTR)
	  {
	     if (-1 == SLang_handle_interrupt ())
	       return NULL;
	     continue;
	  }
#endif
	throw_errno_error ("accept", errno);
	return NULL;
     }
   *lenp = (unsigned int) addr_len;
   if (NULL == (s1 = create_socket (fd1, s->domain, s->type, s->protocol)))
     (void) close_socket (fd1);

   return s1;
}

/*}}}*/

/* Domain Methods */
#if defined(PF_UNIX) && defined(AF_UNIX) /*{{{ */

static void free_af_unix (Socket_Type *s)
{
   char *file = (char *) s->socket_data;
   if (file == NULL)
     return;

   (void) unlink (file);
   SLang_free_slstring (file);
   s->socket_data = NULL;
}

static int connect_af_unix (Socket_Type *s, int nargs)
{
   struct sockaddr_un addr;
   char *file;

   if (nargs != 1)
     {
	SLang_verror (SL_NumArgs_Error, "This socket expects a filename");
	return -1;
     }
   if (-1 == SLang_pop_slstring (&file))
     return -1;

   if (strlen (file) >= sizeof(addr.sun_path))
     {
	SLang_verror (SL_InvalidParm_Error, "filename too long for PF_UNIX socket");
	SLang_free_slstring (file);
	return -1;
     }

   memset ((char *)&addr, 0, sizeof (struct sockaddr_un));
   addr.sun_family = AF_UNIX;
   strcpy (addr.sun_path, file);       /* \0 terminated */

   SLang_free_slstring (file);
   return perform_connect (s->fd, (struct sockaddr *)&addr, sizeof (addr), 1);
}

static int bind_af_unix (Socket_Type *s, int nargs)
{
   struct sockaddr_un addr;
   char *file;

   if (nargs != 1)
     {
	SLang_verror (SL_NumArgs_Error, "This socket expects a filename");
	return -1;
     }
   if (-1 == SLang_pop_slstring (&file))
     return -1;

   if (strlen (file) >= sizeof(addr.sun_path))
     {
	SLang_verror (SL_InvalidParm_Error, "filename too long for PF_UNIX socket");
	SLang_free_slstring (file);
	return -1;
     }

   memset ((char *)&addr, 0, sizeof (struct sockaddr_un));
   addr.sun_family = AF_UNIX;
   strcpy (addr.sun_path, file);       /* \0 terminated */

   (void) unlink (file);
   s->socket_data = (VOID_STAR) file;
   return perform_bind (s->fd, (struct sockaddr *)&addr, sizeof (addr));
}

static Socket_Type *accept_af_unix (Socket_Type *s, unsigned int nrefs, SLang_Ref_Type **refs)
{
   struct sockaddr_un addr;
   Socket_Type *s1;
   unsigned int addr_len;

   (void) refs;
   if (nrefs != 0)
     {
	SLang_verror (SL_NotImplemented_Error, "accept: reference args not supported for PF_UNIX sockets");
	return NULL;
     }
   addr_len = sizeof (struct sockaddr_un);
   s1 = perform_accept (s, (struct sockaddr *)&addr, &addr_len);
   return s1;
}

#endif
/*}}}*/

#if defined(PF_INET) && defined(AF_INET) /*{{{*/
static int pop_host_port (SLFUTURE_CONST char *what, int nargs, char **hostp, int *portp)
{
   char *host;
   int port;

   if (nargs != 2)
     {
	SLang_verror (SL_NumArgs_Error, "%s on an PF_INET socket requires a hostname and portnumber", what);
	return -1;
     }

   *hostp = NULL;
   if ((-1 == SLang_pop_int (&port))
       || (-1 == SLang_pop_slstring (&host)))
     return -1;

   *hostp = host;
   *portp = port;
   return 0;
}

typedef struct
{
   int h_addrtype;		       /* AF_INET or AF_INET6 */
   int h_length;		       /* length of address */
   unsigned int num;		       /* num elements of h_addr_list */
   char **h_addr_list;		       /* Array of num of these */
}
Host_Addr_Info_Type;

static void free_host_addr_info (Host_Addr_Info_Type *hinfo)
{
   if (hinfo == NULL)
     return;
   if (hinfo->h_addr_list != NULL)
     SLfree ((char *)hinfo->h_addr_list);
   SLfree ((char *) hinfo);
}

static Host_Addr_Info_Type *alloc_host_addr_info (unsigned int num, int h_length)
{
   Host_Addr_Info_Type *hinfo;
   size_t nbytes;
   char *buf;
   unsigned int i;

   hinfo = (Host_Addr_Info_Type *) SLcalloc (1, sizeof (Host_Addr_Info_Type));
   if (hinfo == NULL)
     return NULL;

   /* We need memory to hold num (char *) addresses + num*h_length bytes */
   nbytes = num * sizeof(char *) + num * h_length;
   if (NULL == (buf = (char *)SLmalloc (nbytes)))
     {
	SLfree ((char *)hinfo);
	return NULL;
     }
   hinfo->h_addr_list = (char **)buf;
   buf += num*sizeof(char *);
   for (i = 0; i < num; i++)
     {
	hinfo->h_addr_list[i] = buf;
	buf += h_length;
     }
   hinfo->num = num;
   hinfo->h_length = h_length;

   return hinfo;
}

/* glibc removed the h_addr compat macro, which messes up the logic below. */
# ifndef h_addr
#  ifdef __GNUC_PREREQ
#   if __GNUC_PREREQ(2,8)
#    define h_addr "unused"	       /* define it, but do not use it */
#   endif
#  endif
# endif

static Host_Addr_Info_Type *get_host_addr_info (char *host)
{
   in_addr_t addr;
   Host_Addr_Info_Type *hinfo;
   struct hostent *hp;
   unsigned int max_retries;
# ifndef h_addr
   char *fake_h_addr_list[2];
# endif
   char **h_addr_list;
   unsigned int i, num;

# ifndef INADDR_NONE
#  define INADDR_NONE ((in_addr_t)(-1))
# endif
   if ((isdigit ((unsigned char)*host))
       && (INADDR_NONE != (addr = inet_addr (host))))
     {
	/* Numerical address */
	if (NULL == (hinfo = alloc_host_addr_info (1, sizeof(in_addr_t))))
	  return NULL;
	hinfo->h_addrtype = AF_INET;
	memcpy (hinfo->h_addr_list[0], (char *)&addr, sizeof(in_addr_t));
	return hinfo;
     }

   max_retries = 3;
   while (NULL == (hp = gethostbyname (host)))
     {
# ifdef TRY_AGAIN
	max_retries--;
	if (max_retries && (h_errno == TRY_AGAIN))
	  {
	     sleep (1);
	     continue;
	  }
# endif
	throw_herror ("gethostbyname", h_errno);
	return NULL;
     }
# ifndef h_addr
   /* Older interface.  There is only one address, so fake a list */
   h_addr_list = fake_h_addr_list;
   h_addr_list [0] = hp->h_addr;
   h_addr_list [1] = NULL;
# else
   h_addr_list = hp->h_addr_list;
# endif

   /* Now count the number of addresses */
   num = 0;
   while (h_addr_list[num] != NULL)
     num++;

   if (num == 0)
     {
# ifdef NO_DATA
	throw_herror ("gethostbyname", NO_DATA);
# else
	throw_herror ("gethostbyname", NO_ADDRESS);
# endif
	return NULL;
     }

   if (NULL == (hinfo = alloc_host_addr_info (num, hp->h_length)))
     return NULL;

   hinfo->h_addrtype = hp->h_addrtype;

   for (i = 0; i < num; i++)
     memcpy (hinfo->h_addr_list[i], h_addr_list[i], hp->h_length);

   return hinfo;
}

static int connect_af_inet (Socket_Type *s, int nargs)
{
   struct sockaddr_in s_in;
   int port;
   char *host;
   Host_Addr_Info_Type *hinfo;
   unsigned int i;

   if (-1 == pop_host_port ("connect", nargs, &host, &port))
     return -1;

   if (NULL == (hinfo = get_host_addr_info (host)))
     {
	SLang_free_slstring (host);
	return -1;
     }

   if (hinfo->h_addrtype != AF_INET)
     {
# ifdef AF_INET6
	if (hinfo->h_addrtype == AF_INET6)
	  SLang_verror (SL_NOT_IMPLEMENTED, "AF_INET6 not implemented");
	else
# endif
	  SLang_verror (SocketError, "Unknown socket family for host %s", host);
	SLang_free_slstring (host);
	free_host_addr_info (hinfo);
	return -1;
     }

   memset ((char *) &s_in, 0, sizeof(s_in));
   s_in.sin_family = hinfo->h_addrtype;
   s_in.sin_port = htons((unsigned short) port);

   for (i = 0; i < hinfo->num; i++)
     {
	memcpy ((char *) &s_in.sin_addr, hinfo->h_addr_list[i], hinfo->h_length);
	if (-1 == perform_connect (s->fd, (struct sockaddr *)&s_in, sizeof (s_in), 0))
	  continue;

	free_host_addr_info (hinfo);
	SLang_free_slstring (host);
	return 0;
     }
   throw_errno_error ("connect", errno);
   free_host_addr_info (hinfo);
   SLang_free_slstring (host);
   return -1;
}

static int bind_af_inet (Socket_Type *s, int nargs)
{
   struct sockaddr_in s_in;
   char *host;
   int port;
   int status;
   Host_Addr_Info_Type *hinfo;

   if (-1 == pop_host_port ("bind", nargs, &host, &port))
     return -1;

   if (NULL == (hinfo = get_host_addr_info (host)))
     {
	SLang_free_slstring (host);
	return -1;
     }

   if (hinfo->h_addrtype != AF_INET)
     {
# ifdef AF_INET6
	if (hinfo->h_addrtype == AF_INET6)
	  SLang_verror (SL_NOT_IMPLEMENTED, "AF_INET6 not implemented");
	else
# endif
	  SLang_verror (SocketError, "Unknown socket family for host %s", host);
	SLang_free_slstring (host);
	free_host_addr_info (hinfo);
	return -1;
     }

   memset ((char *) &s_in, 0, sizeof(s_in));
   s_in.sin_family = hinfo->h_addrtype;
   s_in.sin_port = htons((unsigned short) port);

   memcpy ((char *) &s_in.sin_addr, hinfo->h_addr_list[0], hinfo->h_length);

   status = perform_bind (s->fd, (struct sockaddr *)&s_in, sizeof (s_in));

   free_host_addr_info (hinfo);
   SLang_free_slstring (host);
   return status;
}

/* Usage: s1 = accept (s [,&host,&port]); */
static Socket_Type *accept_af_inet (Socket_Type *s, unsigned int nrefs, SLang_Ref_Type **refs)
{
   struct sockaddr_in s_in;
   Socket_Type *s1;
   unsigned int addr_len;

   if ((nrefs != 0) && (nrefs != 2))
     {
	SLang_verror (SL_NumArgs_Error, "accept (sock [,&host,&port])");
	return NULL;
     }

   addr_len = sizeof (struct sockaddr_in);
   s1 = perform_accept (s, (struct sockaddr *)&s_in, &addr_len);

   if ((s1 == NULL) || (nrefs == 0))
     return s1;

   if (nrefs == 2)
     {
	char *host;
	char host_ip[32];  /* aaa.bbb.ccc.ddd */
	unsigned char *bytes = (unsigned char *)&s_in.sin_addr;
	int port = ntohs (s_in.sin_port);
	sprintf (host_ip, "%d.%d.%d.%d",
		 (int)bytes[0],(int)bytes[1],(int)bytes[2],(int)bytes[3]);

	if (NULL == (host = SLang_create_slstring (host_ip)))
	  {
	     free_socket (s1);
	     return NULL;
	  }
	if (-1 == SLang_assign_to_ref (refs[0], SLANG_STRING_TYPE, (VOID_STAR)&host))
	  {
	     SLang_free_slstring (host);
	     free_socket (s1);
	     return NULL;
	  }
	SLang_free_slstring (host);
	if (-1 == SLang_assign_to_ref (refs[1], SLANG_INT_TYPE, &port))
	  {
	     free_socket (s1);
	     return NULL;
	  }
     }
   return s1;
}

#endif

/*}}}*/

static Domain_Methods_Type Domain_Methods_Table [] =
{
#if defined(PF_UNIX) && defined(AF_UNIX)
   {PF_UNIX, connect_af_unix, bind_af_unix, accept_af_unix, free_af_unix},
#endif
#if defined(PF_INET) && defined(AF_INET)
   {PF_INET, connect_af_inet, bind_af_inet, accept_af_inet, NULL},
#endif
   {0, NULL, NULL, NULL, NULL}
};

static Domain_Methods_Type *lookup_domain_methods (int domain)
{
   Domain_Methods_Type *a = Domain_Methods_Table;
   unsigned int i, n;

   n = sizeof (Domain_Methods_Table)/sizeof(Domain_Methods_Type);
   for (i = 0; i < n; i++)
     {
	if (a->domain == domain)
	  return a;
	a++;
     }

   SLang_verror (SocketError, "Unsupported socket domain: %d", domain);
   return NULL;
}

static int close_socket (int fd)
{
   /* Do not call close again to avoid undefined behavior */
   if (-1 == close (fd))
     {
#ifdef EINTR
	if (errno == EINTR)
	  {
	     if (-1 == SLang_handle_interrupt ())
	       return -1;
	  }
#endif
	return -1;
     }
   return 0;
}

static void free_socket (Socket_Type *s)
{
   if (s == NULL)
     return;

   if ((s->methods != NULL) && (s->methods->free_socket_data != NULL))
     (*s->methods->free_socket_data)(s);

   if (s->fd != -1)
     close_socket (s->fd);

   SLfree ((char *) s);
}

static Socket_Type *create_socket (int fd, int domain, int type, int protocol)
{
   Socket_Type *s;
   Domain_Methods_Type *methods;

   if (NULL == (methods = lookup_domain_methods (domain)))
     return NULL;

   s = (Socket_Type *)SLmalloc (sizeof (Socket_Type));
   if (s == NULL)
     return s;
   memset ((char *)s, 0, sizeof (Socket_Type));

   s->fd = fd;
   s->domain = domain;
   s->protocol = protocol;
   s->type = type;
   s->methods = methods;

   return s;
}

static int close_socket_callback (VOID_STAR cd)
{
   Socket_Type *s;

   s = (Socket_Type *) cd;
   if (s->fd == -1)
     {
#ifdef EBADF
	errno = EBADF;
#endif
	return -1;
     }
   if (-1 == close (s->fd))
     return -1;

   s->fd = -1;
   return 0;
}

static void free_socket_callback (VOID_STAR cd)
{
   free_socket ((Socket_Type *)cd);
}

static SLFile_FD_Type *socket_to_fd (Socket_Type *s)
{
   SLFile_FD_Type *f;
   if (NULL == (f = SLfile_create_fd ("*socket*", s->fd)))
     return NULL;

   (void) SLfile_set_clientdata (f, free_socket_callback, (VOID_STAR)s, Socket_Type_Id);
   (void) SLfile_set_close_method (f, close_socket_callback);
   return f;
}

static Socket_Type *socket_from_fd (SLFile_FD_Type *f)
{
   Socket_Type *s;
   if (-1 == SLfile_get_clientdata (f, Socket_Type_Id, (VOID_STAR *)&s))
     {
	SLang_verror (SL_TypeMismatch_Error, "File descriptor does not represent a socket");
	return NULL;
     }
   return s;
}

/* This function frees the socket before returning */
static int push_socket (Socket_Type *s)
{
   SLFile_FD_Type *f;
   int status;

   if (s == NULL)
     return SLang_push_null ();

   if (NULL == (f = socket_to_fd (s)))
     {
	free_socket (s);
	return -1;
     }

   status = SLfile_push_fd (f);
   SLfile_free_fd (f);
   return status;
}

static Socket_Type *pop_socket (SLFile_FD_Type **fp)
{
   SLFile_FD_Type *f;
   Socket_Type *s;

   if (-1 == SLfile_pop_fd (&f))
     {
	*fp = NULL;
	return NULL;
     }
   if (NULL == (s = socket_from_fd (f)))
     {
	SLfile_free_fd (f);
	return NULL;
     }
   *fp = f;
   return s;
}

static void socket_intrin (int *domain, int *type, int *protocol)
{
   Socket_Type *s;
   int fd;

   if (NULL == lookup_domain_methods (*domain))
     return;

   fd = socket (*domain, *type, *protocol);
   if (fd == -1)
     {
	throw_errno_error ("socket", errno);
	return;
     }

   if (NULL == (s = create_socket (fd, *domain, *type, *protocol)))
     {
	close_socket (fd);
	return;
     }

   (void) push_socket (s);	       /* frees it upon error */
   return;
}

#ifdef HAVE_SOCKETPAIR
static void socketpair_intrin (int *domain, int *type, int *protocol)
{
   Socket_Type *s;
   int fds[2];

   if (NULL == lookup_domain_methods (*domain))
     return;

   if (-1 == socketpair (*domain, *type, *protocol, fds))
     {
	throw_errno_error ("socketpair", errno);
	return;
     }

   if (NULL == (s = create_socket (fds[0], *domain, *type, *protocol)))
     {
	close_socket (fds[0]);
	close_socket (fds[1]);
	return;
     }
   if (-1 == push_socket (s))	       /* frees upon error */
     {
	close_socket (fds[1]);
	return;
     }
   if (NULL == (s = create_socket (fds[1], *domain, *type, *protocol)))
     {
	close_socket (fds[1]);
	return;
     }
   (void) push_socket (s);	       /* frees it upon error */
   return;
}
#endif

static void connect_intrin (void)
{
   Socket_Type *s;
   SLFile_FD_Type *f;
   int nargs = SLang_Num_Function_Args;
   Domain_Methods_Type *methods;

   if (-1 == SLroll_stack (-nargs))
     return;

   if (NULL == (s = pop_socket (&f)))
     return;
   nargs--;

   methods = s->methods;
   (void) (*methods->connect)(s, nargs);
   SLfile_free_fd (f);
}

static void bind_intrin (void)
{
   Socket_Type *s;
   SLFile_FD_Type *f;
   int nargs = SLang_Num_Function_Args;
   Domain_Methods_Type *methods;

   if (-1 == SLroll_stack (-nargs))
     return;

   if (NULL == (s = pop_socket (&f)))
     return;
   nargs--;

   methods = s->methods;
   (void)(*methods->bind)(s, nargs);
   SLfile_free_fd (f);
}

static void listen_intrin (SLFile_FD_Type *f, int *np)
{
   Socket_Type *s;

   if (NULL == (s = socket_from_fd (f)))
     return;

   if (0 == listen (s->fd, *np))
     return;

   throw_errno_error ("listen", errno);
}

static void accept_intrin (void)
{
   SLFile_FD_Type *f;
   Socket_Type *s, *s1;
   Domain_Methods_Type *methods;
   int nargs = SLang_Num_Function_Args;
   SLang_Ref_Type *refs[MAX_ACCEPT_REF_ARGS];
   int i;

   if (nargs <= 0)
     {
	SLang_verror (SL_Usage_Error, "s1 = accept (s [,&v...])");
	return;
     }

   if (-1 == SLroll_stack (-nargs))
     return;

   if (NULL == (s = pop_socket (&f)))
     return;
   nargs--;

   if (nargs > MAX_ACCEPT_REF_ARGS)
     {
	SLang_verror (SL_NumArgs_Error, "accept: too many reference args");
	SLfile_free_fd (f);
     }
   memset ((char *)refs, 0, sizeof (refs));

   i = nargs;
   while (i != 0)
     {
	i--;
	if (-1 == SLang_pop_ref (refs+i))
	  goto free_return;
     }

   methods = s->methods;
   if (NULL != (s1 = (*methods->accept)(s, nargs, refs)))
     (void) push_socket (s1);	       /* frees it upon error */

   /* drop */

free_return:
   for (i = 0; i < nargs; i++)
     {
	if (refs[i] != NULL)
	  SLang_free_ref (refs[i]);
     }
   SLfile_free_fd (f);
}

typedef struct
{
   int optname;
   int (*setopt)(Socket_Type *, int, int);
   int (*getopt)(Socket_Type *, int, int);
}
SockOpt_Type;

static int do_setsockopt (int fd, int level, int optname, void *val, socklen_t len)
{
   if (-1 == setsockopt (fd, level, optname, val, len))
     {
	throw_errno_error ("setsockopt", errno);
	return -1;
     }
   return 0;
}

static int do_getsockopt (int fd, int level, int optname, void *val, socklen_t *lenp)
{
   if (-1 == getsockopt (fd, level, optname, val, lenp))
     {
	throw_errno_error ("getsockopt", errno);
	return -1;
     }
   return 0;
}

static int set_int_sockopt (Socket_Type *s, int level, int optname)
{
   int val;

   if (-1 == SLang_pop_int (&val))
     return -1;

   return do_setsockopt (s->fd, level, optname, (void *)&val, sizeof(int));
}

static int get_int_sockopt (Socket_Type *s, int level, int optname)
{
   int val;
   socklen_t len;

   len = sizeof (int);
   if (-1 == do_getsockopt (s->fd, level, optname, (void *)&val, &len))
     return -1;

   return SLang_push_int (val);
}

static int set_str_sockopt (Socket_Type *s, int level, int optname)
{
   char *val;
   socklen_t len;
   int ret;

   if (-1 == SLang_pop_slstring (&val))
     return -1;
   len = strlen (val); len++;
   ret = do_setsockopt (s->fd, level, optname, (void *)val, len);
   SLang_free_slstring (val);
   return ret;
}
static int get_str_sockopt (Socket_Type *s, int level, int optname)
{
   char buf[1024];
   socklen_t len = sizeof (buf)-1;

   if (-1 == do_getsockopt (s->fd, level, optname, (void *)buf, &len))
     return -1;

   buf[len] = 0;
   return SLang_push_string (buf);
}

static int set_struct_sockopt (Socket_Type *s, int level, int optname,
			       SLang_CStruct_Field_Type *cs, VOID_STAR v,
			       socklen_t len)
{
   int ret;

   if (-1 == SLang_pop_cstruct (v, cs))
     return -1;

   ret = do_setsockopt (s->fd, level, optname, v, len);
   SLang_free_cstruct (v, cs);
   return ret;
}

static int get_struct_sockopt (Socket_Type *s, int level, int optname,
			       SLang_CStruct_Field_Type *cs, VOID_STAR v,
			       socklen_t len)
{
   if (-1 == do_getsockopt (s->fd, level, optname, v, &len))
     return -1;

   return SLang_push_cstruct (v, cs);
}

static SLang_CStruct_Field_Type TV_Struct [] =
{
   MAKE_CSTRUCT_INT_FIELD(struct timeval, tv_sec, "tv_sec", 0),
   MAKE_CSTRUCT_INT_FIELD(struct timeval, tv_sec, "tv_usec", 0),
   SLANG_END_CSTRUCT_TABLE
};

static int set_timeval_sockopt (Socket_Type *s, int level, int optname)
{
   struct timeval tv;
   return set_struct_sockopt (s, level, optname, TV_Struct, (VOID_STAR)&tv, sizeof(struct timeval));
}

static int get_timeval_sockopt (Socket_Type *s, int level, int optname)
{
   struct timeval tv;
   return get_struct_sockopt (s, level, optname, TV_Struct, (VOID_STAR)&tv, sizeof(struct timeval));
}

#if defined(SO_LINGER)
static SLang_CStruct_Field_Type Linger_Struct [] =
{
   MAKE_CSTRUCT_INT_FIELD(struct linger, l_onoff, "l_onoff", 0),
   MAKE_CSTRUCT_INT_FIELD(struct linger, l_linger, "l_linger", 0),
   SLANG_END_CSTRUCT_TABLE
};

static int set_linger_sockopt (Socket_Type *s, int level, int optname)
{
   struct linger lg;
   return set_struct_sockopt (s, level, optname, Linger_Struct, (VOID_STAR)&lg, sizeof(struct linger));
}

static int get_linger_sockopt (Socket_Type *s, int level, int optname)
{
   struct linger lg;
   return get_struct_sockopt (s, level, optname, Linger_Struct, (VOID_STAR)&lg, sizeof(struct linger));
}
#endif

#ifdef SOL_SOCKET
static SockOpt_Type SO_Option_Table[] =
{
# ifdef SO_KEEPALIVE
   {SO_KEEPALIVE, set_int_sockopt, get_int_sockopt},
# endif
# ifdef SO_OOBINLINE
   {SO_OOBINLINE, set_int_sockopt, get_int_sockopt},
# endif
# ifdef SO_RCVLOWAT
   {SO_RCVLOWAT, set_int_sockopt, get_int_sockopt},
# endif
# ifdef SO_SNDLOWAT
   {SO_SNDLOWAT, set_int_sockopt, get_int_sockopt},
# endif
# ifdef SO_BSDCOMPAT
   {SO_BSDCOMPAT, set_int_sockopt, get_int_sockopt},
# endif
# ifdef SO_PASSCRED
   {SO_PASSCRED, set_int_sockopt, get_int_sockopt},
# endif
# ifdef SO_BINDTODEVICE
   {SO_BINDTODEVICE, set_str_sockopt, get_str_sockopt},
# endif
# ifdef SO_DEBUG
   {SO_DEBUG, set_int_sockopt, get_int_sockopt},
# endif
# ifdef SO_REUSEADDR
   {SO_REUSEADDR, set_int_sockopt, get_int_sockopt},
# endif
# ifdef SO_TYPE
   {SO_TYPE, set_int_sockopt, get_int_sockopt},
# endif
# ifdef SO_ACCEPTCONN
   {SO_ACCEPTCONN, set_int_sockopt, get_int_sockopt},
# endif
# ifdef SO_DONTROUTE
   {SO_DONTROUTE, set_int_sockopt, get_int_sockopt},
# endif
# ifdef SO_BROADCAST
   {SO_BROADCAST, set_int_sockopt, get_int_sockopt},
# endif
# ifdef SO_SNDBUF
   {SO_SNDBUF, set_int_sockopt, get_int_sockopt},
# endif
# ifdef SO_RCVBUF
   {SO_RCVBUF, set_int_sockopt, get_int_sockopt},
# endif
# ifdef SO_PRIORITY
   {SO_PRIORITY, set_int_sockopt, get_int_sockopt},
# endif
# ifdef SO_ERROR
   {SO_ERROR, NULL, get_int_sockopt},
# endif
# ifdef SO_PEERCRED
   /* {SO_PEERCRED, NULL, get_peercred_sockopt}, */
# endif
# ifdef SO_RCVTIMEO
   {SO_RCVTIMEO, set_timeval_sockopt, get_timeval_sockopt},
# endif
# ifdef SO_SNDTIMEO
   {SO_SNDTIMEO, set_timeval_sockopt, get_timeval_sockopt},
# endif
# ifdef SO_LINGER
   {SO_LINGER, set_linger_sockopt, get_linger_sockopt},
# endif

   {-1, NULL, NULL}
};
#endif				       /* SOL_SOCKET */

#if defined(IP_ADD_MEMBERSHIP) /* either add or drop same args */
static int set_multicast_sockopt (Socket_Type *s, int level, int option)
{
   struct ip_mreq group;
   char *multi;
   char *local = NULL;
   Host_Addr_Info_Type *multi_info = NULL;
   Host_Addr_Info_Type *local_info = NULL;
   int ret = -1;

   if (-1 == SLang_pop_slstring(&multi))
     return -1;

   if (5 == SLang_Num_Function_Args)
     {
	if (-1 == SLang_pop_slstring(&local))
	  {
	     SLang_free_slstring (multi);
	     return -1;
	  }
     }

   if (NULL == (multi_info = get_host_addr_info (multi)))
     goto free_and_return;

   if (local != NULL)
     {
	if (NULL == (local_info = get_host_addr_info (local)))
	  goto free_and_return;

	memcpy ((char *) &group.imr_interface.s_addr, local_info->h_addr_list[0], local_info->h_length);
     }
   else
     {
	group.imr_interface.s_addr = INADDR_ANY;
     }
   memcpy ((char *) &group.imr_multiaddr.s_addr, multi_info->h_addr_list[0], multi_info->h_length);

   ret = do_setsockopt (s->fd, level, option, (void *)&group, sizeof(group));

free_and_return:

   SLang_free_slstring(multi);
   if (NULL != local)
     SLang_free_slstring(local);
   free_host_addr_info (multi_info);
   if (NULL != local_info)
     free_host_addr_info (local_info);

   return ret;
}
#endif

#if defined(IP_MULTICAST_IF)
static int set_multicast_if_sockopt (Socket_Type *s, int level, int option)
{
   struct in_addr iface;
   char *local;
   Host_Addr_Info_Type *local_info;

   if (-1 == SLang_pop_slstring(&local))
     return -1;

   if (NULL == (local_info = get_host_addr_info (local)))
     {
	SLang_free_slstring (local);
	return -1;
     }
   memcpy ((char *) &iface.s_addr, local_info->h_addr_list[0], local_info->h_length);

   SLang_free_slstring(local);
   free_host_addr_info (local_info);

   return do_setsockopt (s->fd, level, option, (void *)&iface, sizeof(iface));
}
#endif

#ifdef SOL_IP
static SockOpt_Type IP_Option_Table[] =
{
# ifdef IP_OPTIONS
   /* {IP_OPTIONS, NULL, NULL}, */
# endif
# ifdef IP_PKTINFO
   /* {IP_PKTINFO, NULL, NULL}, */
# endif
# ifdef IP_RECVTOS
   /* {IP_RECVTOS, NULL, NULL}, */
# endif
# ifdef IP_RECVTTL
   /* {IP_RECVTTL, NULL, NULL}, */
# endif
# ifdef IP_RECVOPTS
   /* {IP_RECVOPTS, NULL, NULL}, */
# endif
# ifdef IP_TOS
   /* {IP_TOS, set_int_sockopt, get_int_sockopt}, */
# endif
# ifdef IP_TTL
   {IP_TTL, set_int_sockopt, get_int_sockopt},
# endif
# ifdef IP_HDRINCL
   {IP_HDRINCL, set_int_sockopt, get_int_sockopt},
# endif
# ifdef IP_RECVERR
   /* {IP_RECVERR, set_int_sockopt, get_int_sockopt}, */
# endif
# ifdef IP_MTU_DISCOVER
   /* {IP_MTU_DISCOVER, set_int_sockopt, get_int_sockopt}, */
# endif
# ifdef IP_MTU
   {IP_MTU_DISCOVER, NULL, get_int_sockopt},
# endif
# ifdef IP_ROUTER_ALERT
   {IP_ROUTER_ALERT, set_int_sockopt, get_int_sockopt},
# endif
# ifdef IP_MULTICAST_TTL
   {IP_MULTICAST_TTL, set_int_sockopt, get_int_sockopt},
# endif
# ifdef IP_MULTICAST_LOOP
   {IP_MULTICAST_LOOP, set_int_sockopt, get_int_sockopt},
# endif
# ifdef IP_ADD_MEMBERSHIP
   {IP_ADD_MEMBERSHIP, set_multicast_sockopt, NULL},
# endif
# ifdef IP_DROP_MEMBERSHIP
   {IP_DROP_MEMBERSHIP, set_multicast_sockopt, NULL},
# endif
# ifdef IP_MULTICAST_IF
   {IP_MULTICAST_IF, set_multicast_if_sockopt, NULL},
# endif

   {-1, NULL, NULL}
};
#endif				       /* SOL_IP */
/* Usage: get/setsockopt (socket, level, optname, value) */
static void getset_sockopt (int set)
{
   Socket_Type *s;
   SLFile_FD_Type *f;
   int level, optname;
   SockOpt_Type *table;

   if (-1 == SLreverse_stack (SLang_Num_Function_Args))
     return;

   if (NULL == (s = pop_socket (&f)))
     return;

   if ((-1 == SLang_pop_int (&level))
       || (-1 == SLang_pop_int (&optname)))
     {
	SLfile_free_fd (f);
	return;
     }

   switch (level)
     {
#ifdef SOL_SOCKET
      case SOL_SOCKET: table = SO_Option_Table; break;
#endif
#ifdef SOL_IP
      case SOL_IP: table = IP_Option_Table; break;
#endif
      default:
	SLang_verror (SL_NotImplemented_Error, "get/setsockopt level %d is not supported", level);
	goto free_return;
     }

   while (1)
     {
	if (table->optname == optname)
	  {
	     int (*func)(Socket_Type *, int, int);
	     if (set)
	       func = table->setopt;
	     else
	       func = table->getopt;
	     if (func == NULL)
	       goto not_implemented_error;

	     (void)(*func)(s, level, optname);
	     break;
	  }
	if (table->optname == -1)
	  goto free_return;

	table++;
     }

   /* drop */
free_return:
   SLfile_free_fd (f);
   return;

not_implemented_error:
   SLang_verror (SL_NotImplemented_Error, "get/setsockopt option %d is not supported at level %d", optname, level);
   SLfile_free_fd (f);
}

static void setsockopt_intrin (void)
{
   getset_sockopt (1);
}
static void getsockopt_intrin (void)
{
   getset_sockopt (0);
}

#define I SLANG_INT_TYPE
#define V SLANG_VOID_TYPE
#define F SLANG_FILE_FD_TYPE
static SLang_Intrin_Fun_Type Module_Intrinsics [] =
{
   MAKE_INTRINSIC_3("socket", socket_intrin, V, I, I, I),
#ifdef HAVE_SOCKETPAIR
   MAKE_INTRINSIC_3("socketpair", socketpair_intrin, V, I, I, I),
#endif
   MAKE_INTRINSIC_0("connect", connect_intrin, V),
   MAKE_INTRINSIC_0("bind", bind_intrin, V),
   MAKE_INTRINSIC_2("listen", listen_intrin, V, F, I),
   MAKE_INTRINSIC_0("accept", accept_intrin, V),
   MAKE_INTRINSIC_0("getsockopt", getsockopt_intrin, V),
   MAKE_INTRINSIC_0("setsockopt", setsockopt_intrin, V),
   SLANG_END_INTRIN_FUN_TABLE
};
#undef F
#undef V
#undef I

static SLang_IConstant_Type Module_IConstants [] =
{
#ifdef SOCK_STREAM
   MAKE_ICONSTANT("SOCK_STREAM", SOCK_STREAM),
#endif
#ifdef SOCK_DGRAM
   MAKE_ICONSTANT("SOCK_DGRAM", SOCK_DGRAM),
#endif
#ifdef SOCK_RAW
   MAKE_ICONSTANT("SOCK_RAW", SOCK_RAW),
#endif
#ifdef SOCK_RDM
   MAKE_ICONSTANT("SOCK_RDM", SOCK_RDM),
#endif
#ifdef SOCK_SEQPACKET
   MAKE_ICONSTANT("SOCK_SEQPACKET", SOCK_SEQPACKET),
#endif
#ifdef SOCK_PACKET
   MAKE_ICONSTANT("SOCK_PACKET", SOCK_PACKET),
#endif

   /* Domains  */
#ifdef PF_UNIX
   MAKE_ICONSTANT("PF_UNIX", PF_UNIX),
#endif
#ifdef PF_INET
   MAKE_ICONSTANT("PF_INET", PF_INET),
#endif
#ifdef AF_UNIX
   MAKE_ICONSTANT("AF_UNIX", AF_UNIX),
#endif
#ifdef AF_INET
   MAKE_ICONSTANT("AF_INET", AF_INET),
#endif

#ifdef SOL_SOCKET
   MAKE_ICONSTANT("SOL_SOCKET", SOL_SOCKET),
# ifdef SO_KEEPALIVE
   MAKE_ICONSTANT("SO_KEEPALIVE", SO_KEEPALIVE),
# endif
# ifdef SO_OOBINLINE
   MAKE_ICONSTANT("SO_OOBINLINE", SO_OOBINLINE),
# endif
# ifdef SO_RCVLOWAT
   MAKE_ICONSTANT("SO_RCVLOWAT", SO_RCVLOWAT),
# endif
# ifdef SO_SNDLOWAT
   MAKE_ICONSTANT("SO_SNDLOWAT", SO_SNDLOWAT),
# endif
# ifdef SO_BSDCOMPAT
   MAKE_ICONSTANT("SO_BSDCOMPAT", SO_BSDCOMPAT),
# endif
# ifdef SO_PASSCRED
   MAKE_ICONSTANT("SO_PASSCRED", SO_PASSCRED),
# endif
# ifdef SO_BINDTODEVICE
   MAKE_ICONSTANT("SO_BINDTODEVICE", SO_BINDTODEVICE),
# endif
# ifdef SO_DEBUG
   MAKE_ICONSTANT("SO_DEBUG", SO_DEBUG),
# endif
# ifdef SO_REUSEADDR
   MAKE_ICONSTANT("SO_REUSEADDR", SO_REUSEADDR),
# endif
# ifdef SO_TYPE
   MAKE_ICONSTANT("SO_TYPE", SO_TYPE),
# endif
# ifdef SO_ACCEPTCONN
   MAKE_ICONSTANT("SO_ACCEPTCONN", SO_ACCEPTCONN),
# endif
# ifdef SO_DONTROUTE
   MAKE_ICONSTANT("SO_DONTROUTE", SO_DONTROUTE),
# endif
# ifdef SO_BROADCAST
   MAKE_ICONSTANT("SO_BROADCAST", SO_BROADCAST),
# endif
# ifdef SO_SNDBUF
   MAKE_ICONSTANT("SO_SNDBUF", SO_SNDBUF),
# endif
# ifdef SO_RCVBUF
   MAKE_ICONSTANT("SO_RCVBUF", SO_RCVBUF),
# endif
# ifdef SO_PRIORITY
   MAKE_ICONSTANT("SO_PRIORITY", SO_PRIORITY),
# endif
# ifdef SO_ERROR
   MAKE_ICONSTANT("SO_ERROR", SO_ERROR),
# endif
# ifdef SO_PEERCRED
   MAKE_ICONSTANT("SO_PEERCRED", SO_PEERCRED),
# endif
# ifdef SO_RCVTIMEO
   MAKE_ICONSTANT("SO_RCVTIMEO", SO_RCVTIMEO),
# endif
# ifdef SO_SNDTIMEO
   MAKE_ICONSTANT("SO_SNDTIMEO", SO_SNDTIMEO),
# endif
# ifdef SO_LINGER
   MAKE_ICONSTANT("SO_LINGER", SO_LINGER),
# endif
#endif   			       /* SOL_SOCKET */

#ifdef SOL_IP
   MAKE_ICONSTANT("SOL_IP", SOL_IP),
# ifdef IP_RECVTTL
   MAKE_ICONSTANT("IP_RECVTTL", IP_RECVTTL),
# endif
# ifdef IP_RECVOPTS
   MAKE_ICONSTANT("IP_RECVOPTS", IP_RECVOPTS),
# endif
# ifdef IP_RECVOPTS
   MAKE_ICONSTANT("IP_RECVOPTS", IP_RECVOPTS),
# endif
# ifdef IP_TOS
   MAKE_ICONSTANT("IP_TOS", IP_TOS),
# endif
# ifdef IP_TTL
   MAKE_ICONSTANT("IP_TTL", IP_TTL),
# endif
# ifdef IP_HDRINCL
   MAKE_ICONSTANT("IP_HDRINCL", IP_HDRINCL),
# endif
# ifdef IP_RECVERR
   MAKE_ICONSTANT("IP_RECVERR", IP_RECVERR),
# endif
# ifdef IP_MTU_DISCOVER
   MAKE_ICONSTANT("IP_MTU_DISCOVER", IP_MTU_DISCOVER),
# endif
# ifdef IP_ROUTER_ALERT
   MAKE_ICONSTANT("IP_ROUTER_ALERT", IP_ROUTER_ALERT),
# endif
# ifdef IP_MULTICAST_TTL
   MAKE_ICONSTANT("IP_MULTICAST_TTL", IP_MULTICAST_TTL),
# endif
# ifdef IP_MULTICAST_LOOP
   MAKE_ICONSTANT("IP_MULTICAST_LOOP", IP_MULTICAST_LOOP),
# endif
# ifdef IP_ADD_MEMBERSHIP
   MAKE_ICONSTANT("IP_ADD_MEMBERSHIP", IP_ADD_MEMBERSHIP),
# endif
# ifdef IP_DROP_MEMBERSHIP
   MAKE_ICONSTANT("IP_DROP_MEMBERSHIP", IP_DROP_MEMBERSHIP),
# endif
# ifdef IP_MULTICAST_IF
   MAKE_ICONSTANT("IP_MULTICAST_IF", IP_MULTICAST_IF),
# endif
# ifdef IP_OPTIONS
   MAKE_ICONSTANT("IP_OPTIONS", IP_OPTIONS),
# endif
# ifdef IP_PKTINFO
   MAKE_ICONSTANT("IP_PKTINFO", IP_PKTINFO),
# endif
# ifdef IP_RECVTOS
   MAKE_ICONSTANT("IP_RECVTOS", IP_RECVTOS),
# endif
# ifdef IPPROTO_IP
   MAKE_ICONSTANT("IPPROTO_IP", IPPROTO_IP),
# endif
#endif				       /* SOL_IP */

   SLANG_END_ICONST_TABLE
};

int init_socket_module_ns (char *ns_name)
{
   SLang_NameSpace_Type *ns;

   if (SocketError == -1)
     {
	if (-1 == (SocketError = SLerr_new_exception (SL_RunTime_Error, "SocketError", "Socket Error")))
	  return -1;
	if (-1 == (SocketHerrnoError = SLerr_new_exception (SocketError, "SocketHError", "Socket h_errno Error")))
	  return -1;
     }
   if (Socket_Type_Id == -1)
     {
	(void) SLfile_create_clientdata_id (&Socket_Type_Id);
     }

   if (NULL == (ns = SLns_create_namespace (ns_name)))
     return -1;

   if ((-1 == SLns_add_intrin_fun_table (ns, Module_Intrinsics, NULL))
       || (-1 == SLns_add_iconstant_table (ns, Module_IConstants, NULL)))
     return -1;

   if (-1 == SLns_add_intrinsic_variable(ns, "h_errno", (VOID_STAR)&Module_H_Errno, SLANG_INT_TYPE, 1))
     return -1;

   return 0;
}

void deinit_socket_module (void)
{
}
