/* $Header$ */

/*
 * Copyright © 1988-2004 Keith Packard and Bart Massey.
 * All Rights Reserved.  See the file COPYING in this directory
 * for licensing information.
 */

/*
 *	sockets.c
 *
 *	provide builtin functions for the Socket namespace
 */

#include	<unistd.h>
#include	<fcntl.h>
#include	<sys/types.h>
#include	<sys/socket.h>
#include        <netinet/in.h>
#include	<netdb.h>
#include	<string.h>
#include	"builtin.h"
#include	<errno.h>

#define perror(s) FilePrintf(FileStderr, s ": %s\n", FileGetErrorMessage(errno))
#ifdef HAVE_HSTRERROR
#define herror(s) FilePrintf(FileStderr, s ": %s\n", hstrerror(h_errno))
#else
#define herror(s) FilePrintf(FileStderr, s ": network error %d\n", h_errno);
#endif

NamespacePtr SocketNamespace;
Type	     *typeSockaddr;

Value do_Socket_create (Value type);
Value do_Socket_connect (Value s, Value host, Value port);
Value do_Socket_bind (Value s, Value host, Value port);
Value do_Socket_listen (Value s, Value backlog);
Value do_Socket_accept (Value s);
Value do_Socket_shutdown (Value s, Value how);
Value do_Socket_gethostname (void);
Value do_Socket_getsockname (Value s);

void
import_Socket_namespace()
{
    ENTER ();

    static const struct fbuiltin_0 funcs_0[] = {
	{ do_Socket_gethostname, "gethostname", "s", "", "\n"
	    " string gethostname ()\n"
	    "\n"
	    " Get the current hostname.\n" },
	{ 0 },
    };

    static const struct fbuiltin_1 funcs_1[] = {
        { do_Socket_create, "create", "f", "i", "\n"
	    " file create (int type)\n"
	    "\n"
	    " Create a socket where 'type' is one of:\n"
	    "   SOCK_STREAM:	a stream socket.\n"
	    "   SOCK_DGRAM:	a datagram socket.\n" },
        { do_Socket_accept, "accept", "f", "f", "\n"
	    " file accept (file listen)\n"
	    "\n"
	    " Return a socket for the next connection on 'listen'.\n" },
	{ do_Socket_getsockname, "getsockname", "a", "f", "\n"
	    " sockaddr getsockname (file socket)\n"
	    "\n"
	    " Returns the address and port of 'socket'.\n" },
        { 0 }
    };

    static const struct fbuiltin_2 funcs_2[] = {
        { do_Socket_listen, "listen", "v", "fi", "\n"
	    " void listen (file socket, int length)\n"
	    "\n"
	    " Establish a listen queue on 'f' of length 'i' (max 5).\n"	},
        { do_Socket_shutdown, "shutdown", "v", "fi", "\n"
	    " void shutdown (file socket, int dir)\n"
	    "\n"
	    " Shut communication in 'dir' direction:\n"
	    "   SHUT_RD:    shut down reading.\n"
	    "   SHUT_WR:    shut down writing.\n"
	    "   SHUT_RDWR:  shut down reading and writing.\n" },
        { 0 }
    };

    static const struct fbuiltin_3 funcs_3[] = {
        { do_Socket_connect, "connect", "v", "fss", "\n"
	    " void connect (file socket, string host, string port)\n"
	    "\n"
	    " Connect 'socket' to 'host', 'port'.\n" },
        { do_Socket_bind, "bind", "v", "fss", "\n"
	    " void bind (file socket, string host, string port)\n"
	    "\n"
	    " Bind 'socket' to 'host', 'port'.\n" },
        { 0 }
    };

    static const struct ibuiltin ivars[] = {
	{ SOCK_STREAM, "SOCK_STREAM", &SocketNamespace },
	{ SOCK_DGRAM, "SOCK_DGRAM", &SocketNamespace },
	{ SHUT_RD, "SHUT_RD", &SocketNamespace },
	{ SHUT_WR, "SHUT_WR", &SocketNamespace },
	{ SHUT_RDWR, "SHUT_RDWR", &SocketNamespace },
	{ 0 }
    };

    static const struct sbuiltin svars[] = {
	{ "0.0.0.0",		"INADDR_ANY", &SocketNamespace },
	{ "127.0.0.1",		"INADDR_LOOPBACK", &SocketNamespace },
	{ "255.255.255.255",	"INADDR_BROADCAST", &SocketNamespace },
	{ 0 }
    }; 
    SymbolPtr	sym;
    Type	*type;

    SocketNamespace = BuiltinNamespace (/*parent*/ 0, "Socket")->namespace.namespace;

    type = BuildStructType (2, 
			    typePrim[rep_integer], "addr",
			    typePrim[rep_integer], "port");
    
    sym = NewSymbolType (AtomId ("sockaddr"), type);

    typeSockaddr = NewTypeName (NewExprAtom (AtomId ("sockaddr"), 0, False),
				sym);
    
    NamespaceAddName (SocketNamespace, sym, publish_public);

    BuiltinFuncs0 (&SocketNamespace, funcs_0);
    BuiltinFuncs1 (&SocketNamespace, funcs_1);
    BuiltinFuncs2 (&SocketNamespace, funcs_2);
    BuiltinFuncs3 (&SocketNamespace, funcs_3);

    BuiltinIntegers (ivars);
    BuiltinStrings (svars);

    EXIT ();
}

/* File::file do_Socket_create ({SOCK_STREAM,SOCK_DGRAM} type); */
Value
do_Socket_create (Value type)
{
    ENTER ();
    int itype, s;
    itype = IntPart (type, "Illegal socket type");
    if (aborting)
	RETURN (Void);
    s = socket (PF_INET, itype, 0);
    if (s == -1)
	RETURN (Void);
    RETURN (FileCreate (s, FileReadable|FileWritable));
}

static Bool address_lookup (Value hostname, Value portname,
			    struct sockaddr_in *addr)
{
    struct hostent *host;
    struct servent *port;
    char *hostchars;
    char *portchars;
    char *endptr = 0;
    long int portnum;

    hostchars = StrzPart (hostname, "invalid hostname");
    if (!hostchars)
	return False;
    portchars = StrzPart (portname, "invalid portname");
    if (!portchars)
	return False;
    
    if (*hostchars == '\0' || *portchars == '\0')
	return False; /* FIXME: more here? */

    addr->sin_family = AF_INET;

    /* host lookup */
    host = gethostbyname (hostchars);
    if (host == 0)
    {
	herror ("address_lookup");
	return False; /* FIXME: more here? */
    }
    memcpy (&addr->sin_addr.s_addr, host->h_addr_list[0], sizeof addr->sin_addr.s_addr);

    /* port lookup */
    portnum = strtol (portchars, &endptr, /* base */ 10);
    if (*endptr != '\0') /* non-numeric port specification */
    {
	/* FIXME: this should not always be "tcp"! */
	port = getservbyname (portchars, "tcp");
	if (port == 0)
	    return False; /* FIXME: more here? */
	addr->sin_port = port->s_port;
    }
    else
    {
	if (portnum <= 0 || portnum >= (1 << 16))
	    return False; /* FIXME: more here? */
	addr->sin_port = htons (portnum);
    }

    return True;
}

/* void do_Socket_connect (File::file s, String host, String port); */
Value
do_Socket_connect (Value s, Value host, Value port)
{
    ENTER ();
    struct sockaddr_in addr;

    if (!address_lookup (host, port, &addr))
	RETURN (Void);

    if (!running->thread.partial)
    {
	int flags = fcntl (s->file.fd, F_GETFL);
	int n, err;
	flags |= O_NONBLOCK;
	fcntl (s->file.fd, F_SETFL, flags);
#ifdef SO_BROADCAST
	{
	    int one = 1;
	    setsockopt (s->file.fd, SOL_SOCKET, SO_BROADCAST,
			(char *) &one, sizeof (int));
	}
#endif
	n = connect (s->file.fd, (struct sockaddr *) &addr, sizeof addr);
	flags &= ~O_NONBLOCK;
	fcntl (s->file.fd, F_SETFL, flags);
	err = errno;
	if (n == -1)
	{
	    if (err == EWOULDBLOCK || err == EINPROGRESS)
	    {
		FileSetBlocked (s, FileOutputBlocked);
		running->thread.partial = 1;
	    }
	    else
	    {
		RaiseStandardException (exception_io_error,
					FileGetErrorMessage (err),
					2, FileGetError (err),
					s);
		RETURN (Void);
	    }
	}
    }
    if (s->file.flags & FileOutputBlocked)
    {
	ThreadSleep (running, s, PriorityIo);
	RETURN (Void);
    }
    
    complete = True;
    RETURN (Void);
}

/* void do_Socket_bind (File::file s, String host, String port); */
Value
do_Socket_bind (Value s, Value host, Value port)
{
    ENTER ();
    struct sockaddr_in addr;

    if (!address_lookup (host, port, &addr))
	RETURN (Void);

#ifdef SO_REUSEADDR
    {
	int one = 1;
	setsockopt (s->file.fd, SOL_SOCKET, SO_REUSEADDR, (char *) &one, sizeof (int));
    }
#endif
    if (bind (s->file.fd, (struct sockaddr *) &addr, sizeof addr) == -1)
    {
	RaiseStandardException (exception_io_error,
				FileGetErrorMessage (errno),
				2, FileGetError (errno),
				s);
	RETURN (Void);
    }

    RETURN (Void);
}

/* void do_Socket_listen (File::file s, int backlog); */
Value
do_Socket_listen (Value s, Value backlog)
{
    ENTER ();
    int ibacklog;

    ibacklog = IntPart (backlog, "Illegal backlog length");
    if (aborting)
	RETURN (Void);

    if (listen (s->file.fd, ibacklog) == -1)
    {
	RETURN (Void); /* FIXME: more here? */
    }

    RETURN (Void);
}

/* File::file do_Socket_accept (File::file s); */
Value
do_Socket_accept (Value s)
{
    ENTER ();
    int f, err;
    int flags = fcntl (s->file.fd, F_GETFL);
    flags |= O_NONBLOCK;
    fcntl (s->file.fd, F_SETFL, flags);
    f = accept (s->file.fd, 0, 0);
    flags &= ~O_NONBLOCK;
    fcntl (s->file.fd, F_SETFL, flags);
    err = errno;
    if (f == -1)
    {
        if (err == EWOULDBLOCK || err == EAGAIN)
        {
	    FileSetBlocked (s, FileInputBlocked);
	    running->thread.partial = 1;
	}
	else
	{
	    RaiseStandardException (exception_io_error,
				    FileGetErrorMessage (err),
				    2, FileGetError (err),
				    s);
	    RETURN (Void);
	}
    }
    if (s->file.flags & FileInputBlocked)
    {
    	ThreadSleep (running, s, PriorityIo);
	RETURN (Void);
    }
	
    complete = True;
    RETURN (FileCreate (f, FileReadable|FileWritable));
}

/* void do_Socket_shutdown (File::file s, {SHUT_RD,SHUT_WR,SHUT_RDWR} how); */
Value
do_Socket_shutdown (Value s, Value how)
{
    ENTER ();
    int ihow;

    ihow = IntPart (how, "Illegal socket shutdown request");
    if (aborting)
	RETURN (Void);

    if (shutdown (s->file.fd, ihow) == -1)
    {
	RETURN (Void); /* FIXME: more here? */
    }

    RETURN (Void);
}

Value
do_Socket_gethostname (void)
{
    ENTER ();
#ifndef HOST_NAME_MAX
#define HOST_NAME_MAX	255
#endif
    char    hostname[HOST_NAME_MAX+1];

    if (gethostname (hostname, sizeof (hostname)) == -1)
    {
	RaiseStandardException (exception_io_error,
				FileGetErrorMessage (errno),
				2, FileGetError (errno),
				Void);
	RETURN (Void);
    }
    /* null termination is not promised */
    hostname[HOST_NAME_MAX] = '\0';
    RETURN (NewStrString (hostname));
}

Value
do_Socket_getsockname (Value s)
{
    ENTER ();
    struct sockaddr_in	addr;
    socklen_t		len = sizeof (addr);
    Value		ret;
    BoxPtr		box;

    if (getsockname (s->file.fd, (struct sockaddr *) &addr, &len) == -1)
    {
	RaiseStandardException (exception_io_error,
				FileGetErrorMessage (errno),
				2, FileGetError (errno),
				s);
	RETURN (Void);
    }
    ret = NewStruct (TypeCanon (typeSockaddr)->structs.structs, False);
    box = ret->structs.values;
    BoxValueSet (box, 0, NewInteger (Positive, 
				     NewNatural (ntohl (addr.sin_addr.s_addr))));
    BoxValueSet (box, 1, NewInt (ntohs (addr.sin_port)));
    RETURN (ret);
}


syntax highlighted by Code2HTML, v. 0.9.1