;; -*- Mode: Lisp; Syntax: Common-Lisp -*-
;; $Id: sockets.lisp 64 2005-01-26 01:02:30Z blitz $

;; This file is based on SBCL's SB-BSD-SOCKET module and has been
;; heavily modified to work with ECL by Julian Stecklina.

;; You may do whatever you want with this file. (PUBLIC DOMAIN)

;; Trivial stuff is copied from SBCL's SB-BSD-SOCKETS, which is also
;; in the public domain.

(defpackage "BSD-SOCKETS"
  (:use "CL" "FFI" "SI")
  (:export "GET-HOST-BY-NAME" "GET-HOST-BY-ADDRESS"
	   "SOCKET-BIND" "SOCKET-ACCEPT" "SOCKET-CONNECT"
	   "SOCKET-PEERNAME" "SOCKET-NAME" "SOCKET-LISTEN"
	   "SOCKET-RECEIVE" "SOCKET-CLOSE" "SOCKET-MAKE-STREAM"
	   "GET-PROTOCOL-BY-NAME" "MAKE-INET-ADDRESS" "LOCAL-SOCKET"
	   "SOCKET" "INET-SOCKET" "SOCKET-FILE-DESCRIPTOR"
	   "SOCKET-FAMILY" "SOCKET-PROTOCOL" "SOCKET-TYPE"
	   "SOCKET-ERROR" "NAME-SERVICE-ERROR" "NON-BLOCKING-MODE"
	   "HOST-ENT-NAME" "HOST-ENT-ALIASES" "HOST-ENT-ADDRESS-TYPE"
	   "HOST-ENT-ADDRESSES" "HOST-ENT" "HOST-ENT-ADDRESS"))
(in-package "BSD-SOCKETS")

;; Obviously this requires the one or other form of BSD compatible
;; socket interface.

;; Include the neccessary headers
(clines
 "#include <sys/types.h>"
 "#include <sys/socket.h>"
 "#include <sys/un.h>"
 "#include <netdb.h>"
 "#include <strings.h>"
 "#include <netinet/in.h>"
 "#include <errno.h>"
 "#include <fcntl.h>"
 "#include <stdio.h>")

(eval-when (:compile-toplevel :load-toplevel)
  (defmacro define-c-constants (&rest args)
    `(progn
       ,@(loop
	    for (lisp-name c-name) on args by #'cddr
	    collect `(defconstant ,lisp-name (c-inline () () :int ,c-name :one-liner t)))))
  (defmacro c-constant (name)
    `(c-inline () () :int ,name :one-liner t)))

(define-c-constants
  +af-inet+ "AF_INET"
  +af-local+ "AF_LOCAL")

;; Foreign functions

(defentry ff-socket (:int :int :int) (:int "socket"))
(defentry ff-listen (:int :int) (:int "listen"))
(defentry ff-close (:int) (:int "close"))

;;; This courtesy of Pierre Mai in comp.lang.lisp 08 Jan 1999 00:51:44 +0100
;;; Message-ID: <87lnjebq0f.fsf@orion.dent.isdn.cs.tu-berlin.de>

(defun split (string &optional max (ws '(#\Space #\Tab)))
  "Split `string' along whitespace as defined by the sequence `ws'.
The whitespace is elided from the result.  The whole string will be
split, unless `max' is a non-negative integer, in which case the
string will be split into `max' tokens at most, the last one
containing the whole rest of the given `string', if any."
  (flet ((is-ws (char) (find char ws)))
    (loop for start = (position-if-not #'is-ws string)
          then (position-if-not #'is-ws string :start index)
          for index = (and start
                           (if (and max (= (1+ word-count) max))
                               nil
                             (position-if #'is-ws string :start start)))
          while start
          collect (subseq string start index)
          count 1 into word-count
          while index)))

;; DNS

(defclass host-ent ()
  ((name :initarg :name :accessor host-ent-name)
   (aliases :initarg :aliases :accessor host-ent-aliases)
   (address-type :initarg :type :accessor host-ent-address-type)
					; presently always AF_INET
   (addresses :initarg :addresses :accessor host-ent-addresses))
  (:documentation ""))

(defgeneric host-ent-address (host-ent)
  (:documentation ""))

(defmethod host-ent-address ((host-ent host-ent))  
  (car (host-ent-addresses host-ent)))

;; FIXME: Do we need to free the hostent structures?
(defun get-host-by-name (host-name)
  "Returns a HOST-ENT instance for HOST-NAME or throws some kind of condition.
HOST-NAME may also be an IP address in dotted quad notation or some other
weird stuff - see gethostbyname(3) for grisly details."
  (let ((host-ent (make-instance 'host-ent)))
    (if (c-inline (host-name host-ent
			     #'(setf host-ent-name)
			     #'(setf host-ent-aliases)
			     #'(setf host-ent-address-type)
			     #'(setf host-ent-addresses))
		  (:cstring t t t t t) t
		  "
{ 
	struct hostent *hostent = gethostbyname(#0);
	
	if (hostent != NULL) {
 	        char **aliases;
                char **addrs;
                cl_object aliases_list = Cnil;
                cl_object addr_list = Cnil;
                int length = hostent->h_length;

		funcall(3,#2,make_simple_string(hostent->h_name),#1);
                funcall(3,#4,make_integer(hostent->h_addrtype),#1);

                for (aliases = hostent->h_aliases; *aliases != NULL; aliases++) {
                        aliases_list = CONS(make_simple_string(*aliases),aliases_list);
                }
                funcall(3,#3,aliases_list,#1);

                for (addrs = hostent->h_addr_list; *addrs != NULL; addrs++) {
                        int pos;
                        cl_object vector = funcall(2,@make-array,MAKE_FIXNUM(length));
                        for (pos = 0; pos < length; pos++)
                                aset(vector, pos, MAKE_FIXNUM((unsigned char)((*addrs)[pos])));
                        addr_list = CONS(vector, addr_list);


                }
                funcall(3,#5,addr_list,#1);

                @(return) = #1;
	} else {
		@(return) = Cnil;
	}
}"
		  :side-effects t)
	host-ent
	(name-service-error "get-host-by-name"))))
               
(defun get-host-by-address (address)
  (assert (typep address 'vector)
	  (and (= (length address) 4)))
  (let ((host-ent (make-instance 'host-ent)))
    (if 
     (c-inline (address host-ent
			#'(setf host-ent-name)
			#'(setf host-ent-aliases)
			#'(setf host-ent-address-type)
			#'(setf host-ent-addresses))
	       (t t t t t t) t
	       "
{
        unsigned char vector[4] = { fixint(aref(#0,0)),
                                    fixint(aref(#0,1)),
                                    fixint(aref(#0,2)),
                                    fixint(aref(#0,3)) };
	struct hostent *hostent = gethostbyaddr(vector,4,AF_INET);

	if (hostent != NULL) {
 	        char **aliases;
                char **addrs;
                cl_object aliases_list = Cnil;
                cl_object addr_list = Cnil;
                int length = hostent->h_length;

		funcall(3,#2,make_simple_string(hostent->h_name),#1);
                funcall(3,#4,make_integer(hostent->h_addrtype),#1);

                for (aliases = hostent->h_aliases; *aliases != NULL; aliases++) {
                        aliases_list = CONS(make_simple_string(*aliases),aliases_list);
                }
                funcall(3,#3,aliases_list,#1);

                for (addrs = hostent->h_addr_list; *addrs != NULL; addrs++) {
                        int pos;
                        cl_object vector = funcall(2,@make-array,MAKE_FIXNUM(length));
                        for (pos = 0; pos < length; pos++)
                                aset(vector, pos, MAKE_FIXNUM((unsigned char)((*addrs)[pos])));
                        addr_list = CONS(vector, addr_list);


                }
                funcall(3,#5,addr_list,#1);

                @(return) = #1;
	} else {
		@(return) = Cnil;
	}
}"
	       :side-effects t)
     host-ent
     (name-service-error "get-host-by-address"))))

;; Common socket stuff

(defclass socket ()
  ((file-descriptor :initarg :descriptor
		    :reader socket-file-descriptor)
   (family :initform (error "No socket family")
	   :reader socket-family)
   (protocol :initarg :protocol
	     :reader socket-protocol
	     :documentation "Protocol used by the socket. If a
keyword, the symbol-name of the keyword will be passed to
GET-PROTOCOL-BY-NAME downcased, and the returned value used as
protocol. Other values are used as-is.")
   (type  :initarg :type
	  :reader socket-type
	  :initform :stream
	  :documentation "Type of the socket: :STREAM or :DATAGRAM.")
   (stream))
  (:documentation "Common base class of all sockets, not ment to be
directly instantiated."))


(defmethod print-object ((object socket) stream)
  (print-unreadable-object (object stream :type t :identity t)
                           (princ "descriptor " stream)
                           (princ (slot-value object 'file-descriptor) stream)))

(defmethod shared-initialize :after ((socket socket) slot-names
				     &key protocol type
				     &allow-other-keys)
  (let* ((proto-num
	  (cond ((and protocol (keywordp protocol))
		 (get-protocol-by-name (string-downcase (symbol-name protocol))))
		(protocol protocol)
		(t 0)))
	 (fd (or (and (slot-boundp socket 'file-descriptor)
		      (socket-file-descriptor socket))
		 (ff-socket (socket-family socket)
			    (ecase (or type
				       (socket-type socket))
			      ((:datagram) (c-constant "SOCK_DGRAM"))
			      ((:stream) (c-constant "SOCK_STREAM")))
			    proto-num))))
    (if (= fd -1) (socket-error "socket"))
    (setf (slot-value socket 'file-descriptor) fd
	  (slot-value socket 'protocol) proto-num
	  (slot-value socket 'type) type)
    #+ ignore
    (sb-ext:finalize socket (lambda () (sockint::close fd)))))

;; Generics

(defgeneric socket-bind (socket &rest address)
  (:documentation "Bind SOCKET to ADDRESS, which may vary according to
socket family.  For the INET family, pass ADDRESS and PORT as two
arguments; for FILE address family sockets, pass the filename string.
See also bind(2)"))

(defgeneric socket-accept (socket)
  (:documentation "Perform the accept(2) call, returning a
newly-created connected socket and the peer address as multiple
values"))

(defgeneric socket-connect (socket &rest address)
  (:documentation "Perform the connect(2) call to connect SOCKET to a
  remote PEER.  No useful return value."))

(defgeneric socket-peername (socket)
  (:documentation "Return the socket's peer; depending on the address
  family this may return multiple values"))

(defgeneric socket-name (socket)
  (:documentation "Return the address (as vector of bytes) and port
  that the socket is bound to, as multiple values."))

(defgeneric socket-listen (socket backlog)
  (:documentation "Mark SOCKET as willing to accept incoming connections.  BACKLOG
defines the maximum length that the queue of pending connections may
grow to before new connection attempts are refused.  See also listen(2)"))

(defgeneric socket-receive (socket buffer length
			    &key
			    oob peek waitall element-type)
  (:documentation "Read LENGTH octets from SOCKET into BUFFER (or a freshly-consed buffer if
NIL), using recvfrom(2).  If LENGTH is NIL, the length of BUFFER is
used, so at least one of these two arguments must be non-NIL.  If
BUFFER is supplied, it had better be of an element type one octet wide.
Returns the buffer, its length, and the address of the peer
that sent it, as multiple values.  On datagram sockets, sets MSG_TRUNC
so that the actual packet length is returned even if the buffer was too
small"))

(defgeneric socket-close (socket)
  (:documentation "Close SOCKET.  May throw any kind of error that write(2) would have
thrown.  If SOCKET-MAKE-STREAM has been called, calls CLOSE on that
stream instead"))

(defgeneric socket-make-stream (socket  &rest args)
    (:documentation "Find or create a STREAM that can be used for IO
on SOCKET (which must be connected).  ARGS are passed onto
SB-SYS:MAKE-FD-STREAM."))

(defgeneric non-blocking-mode (socket)
  (:documentation "Is SOCKET in non-blocking mode?"))

(defgeneric (setf non-blocking-mode) (non-blocking-p socket)
  (:documentation "Put SOCKET in non-blocking mode - or not, according to NON-BLOCKING-P"))

;; Methods

(defmethod socket-listen ((socket socket) backlog)
  (let ((r (ff-listen (socket-file-descriptor socket) backlog)))
    (if (= r -1)
        (socket-error "listen"))))

(defmethod socket-close ((socket socket))
  ;; the close(2) manual page has all kinds of warning about not
  ;; checking the return value of close, on the grounds that an
  ;; earlier write(2) might have returned successfully w/o actually
  ;; writing the stuff to disk.  It then goes on to define the only
  ;; possible error return as EBADF (fd isn't a valid open file
  ;; descriptor).  Presumably this is an oversight and we could also
  ;; get anything that write(2) would have given us.

  ;; note that if you have a socket _and_ a stream on the same fd, 
  ;; the socket will avoid doing anything to close the fd in case
  ;; the stream has done it already - if so, it may have been
  ;; reassigned to some other file, and closing it would be bad

  (let ((fd (socket-file-descriptor socket)))
    (cond ((eql fd -1) ; already closed
	   nil)
	  ((slot-boundp socket 'stream)
	   (close (slot-value socket 'stream)) ;; closes fd
	   (setf (slot-value socket 'file-descriptor) -1)
	   (slot-makunbound socket 'stream))
	  (t
	   (if (= (ff-close fd) -1)
		   (socket-error "close"))))))

;; FIXME: How bad is manipulating fillp directly?
(defmethod socket-receive ((socket socket) buffer length
			   &key oob peek waitall element-type)
  (unless (or buffer length) (error "You have to supply either buffer or length!"))
  (let ((buffer (or buffer (make-array length :element-type element-type)))
	(length (or length (length buffer)))
	(fd (socket-file-descriptor socket)))
    (assert (or (stringp buffer)
		(typep buffer 'vector)))
    (let ((len-recv
	   (c-inline (fd buffer length
		      oob peek waitall)
		     (:int :object :int :bool :bool :bool)
		     :long
		     "
{
        int flags = ( #3 ? MSG_OOB : 0 )  |
                    ( #4 ? MSG_PEEK : 0 ) |
                    ( #5 ? MSG_WAITALL : 0 );
        cl_type type = type_of(#1);

        ssize_t len = recvfrom(#0,( type == t_vector ? #1->vector.self.ch :
                                  ( type == t_string ? #1->string.self : NULL )),
                               #2, flags, NULL,NULL);
        if (len >= 0) {
               if (type == t_vector) { #1->vector.fillp = len; }
               else if (type == t_string) { #1->string.fillp = len; }
        }
        @(return) = len;
}
"
		     :one-liner nil)))
      (if (= len-recv -1)
	  (socket-error "receive")
	  (values buffer len-recv)))))

;; INET sockets

;; We could refactor a lot here, if we pass sockaddr_foo structs around in Lisp. But
;; I do not feel comfortable with that.

(defun get-protocol-by-name (string-or-symbol)
  "Calls getprotobyname"
  (let ((string (string string-or-symbol)))
    (c-inline (string) (:cstring) :int
	      "getprotobyname(#0)->p_proto"
	      :one-liner t)))

(defun make-inet-address (dotted-quads)
  "Return a vector of octets given a string DOTTED-QUADS in the format
\"127.0.0.1\""
  (map 'vector
       #'parse-integer
       (split dotted-quads nil '(#\.))))

(defclass inet-socket (socket)
  ((family :initform +af-inet+))
  (:documentation "Class representing TCP and UDP sockets.

Examples:

 (make-instance 'inet-socket :type :stream :protocol :tcp)

 (make-instance 'inet-socket :type :datagram :protocol :udp)
"))

(defun make-inet-socket (type protocol)
  "Make an INET socket.  Deprecated in favour of make-instance"
  (make-instance 'inet-socket :type type :protocol protocol))

(Clines
 "
void static fill_inet_sockaddr(struct sockaddr_in *sockaddr, int port, 
			       int a1, int a2, int a3, int a4)
{
	bzero(sockaddr,sizeof(struct sockaddr_in));
	sockaddr->sin_family = AF_INET;
	sockaddr->sin_port = htons(port);
	sockaddr->sin_addr.s_addr= htonl((uint32_t)a1<<24 | (uint32_t)a2<<16 | (uint32_t)a3<<8 | (uint32_t)a4) ;

}
")

(defmethod socket-bind ((socket inet-socket) &rest address)
  (assert (= 2 (length address)) (address) "Socket-bind needs three parameters for inet sockets.")
  (let ((ip (first address))
	(port (second address)))
    (if (= -1
	   (c-inline (port (aref ip 0) (aref ip 1) (aref ip 2) (aref ip 3)
			   (socket-file-descriptor socket))
		     (:int :int :int :int :int :int)
		     :int
		     "
{
	struct sockaddr_in sockaddr;

	fill_inet_sockaddr(&sockaddr, #0, #1, #2, #3, #4);
	@(return) = bind(#5,&sockaddr, sizeof(struct sockaddr_in));
}"
		     :side-effects t))
	(socket-error "bind"))))

(Clines
"
cl_object static do_accept_inet(cl_object cl_socket_fd)
{
        struct sockaddr_in sockaddr;
        int socket_fd = fixint(cl_socket_fd);
        int addr_len = sizeof(struct sockaddr_in);
        int new_fd = accept(socket_fd, &sockaddr, &addr_len);

        if (new_fd != -1) {
                uint32_t ip = ntohl(sockaddr.sin_addr.s_addr);
                uint16_t port = ntohs(sockaddr.sin_port);
                cl_object vector = cl_make_array(1,MAKE_FIXNUM(4));

                aset(vector,0, MAKE_FIXNUM( ip>>24 ));
		aset(vector,1, MAKE_FIXNUM( (ip>>16) & 0xFF));
		aset(vector,2, MAKE_FIXNUM( (ip>>8) & 0xFF));
                aset(vector,3, MAKE_FIXNUM( ip & 0xFF ));
		
		NVALUES = 2;
		VALUES(0) = make_integer(new_fd);
		VALUES(1) = vector;
		return VALUES(0);
	} else return MAKE_FIXNUM(-1);
}
")

(c-inline () () t "cl_def_c_function(@do-accept-inet,do_accept_inet,1)" :one-liner t)

(defmethod socket-accept ((socket inet-socket))
  (multiple-value-bind (fd vector)
      (do-accept-inet (socket-file-descriptor socket))
    (cond 
      ((= fd -1)
       (socket-error "accept"))
      (t
       (values
	(make-instance (class-of socket)
		       :type (socket-type socket)
		       :protocol (socket-protocol socket)
		       :descriptor fd)
	vector)))))

(defmethod socket-connect ((socket inet-socket) &rest address)
  (let ((ip (first address))
	(port (second address)))
    (if (= -1
	   (c-inline (port (aref ip 0) (aref ip 1) (aref ip 2) (aref ip 3)
			   (socket-file-descriptor socket))
		     (:int :int :int :int :int :int)
		     :int
		     "
{
	struct sockaddr_in sockaddr;

	fill_inet_sockaddr(&sockaddr, #0, #1, #2, #3, #4);
	@(return) = connect(#5,&sockaddr, sizeof(struct sockaddr_in));
}"))
	(socket-error "connect"))))

(defmethod socket-peername ((socket inet-socket))
  (let* ((vector (make-array 4))
	 (fd (socket-file-descriptor socket))
	 (port (c-inline (fd vector) (:int t) :int
"{
        struct sockaddr_in name;
        socklen_t len = sizeof(struct sockaddr_in);
        int ret = getpeername(#0,&name,&len);

        if (ret == 0) {
                uint32_t ip = ntohl(name.sin_addr.s_addr);
                uint16_t port = ntohs(name.sin_port);

                aset(#1,0, MAKE_FIXNUM( ip>>24 ));
		aset(#1,1, MAKE_FIXNUM( (ip>>16) & 0xFF));
		aset(#1,2, MAKE_FIXNUM( (ip>>8) & 0xFF));
                aset(#1,3, MAKE_FIXNUM( ip & 0xFF ));

                @(return) = port;
         } else {
                @(return) = Cnil;
         }
}")))
    (if port
	(values vector port)
	(socket-error "getpeername"))))

;; Local (UNIX) sockets

(defclass local-socket (socket)
  ((family :initform +af-local+))
  (:documentation "Class representing local domain (AF_LOCAL) sockets,
also known as unix-domain sockets."))


(defmethod socket-bind ((socket local-socket) &rest address)
  (assert (= 1 (length address)) (address) "Socket-bind needs two parameters for local sockets.")
  (let ((name (first address))
	(fd (socket-file-descriptor socket))
	(family (socket-family socket)))
    (if (= -1
	   (c-inline (fd name family) (:int :cstring :int) :int
		     "
{
        struct sockaddr_un sockaddr;

        sockaddr.sun_len = sizeof(struct sockaddr_un);
        sockaddr.sun_family = #2;
        strncpy(&sockaddr.sun_path,#1,104);

        @(return) = bind(#0,&sockaddr, sizeof(struct sockaddr_un));
}"))
	(socket-error "bind"))))

(Clines
 "
cl_object static do_accept_un(cl_object cl_socket_fd)
{
        struct sockaddr_un sockaddr;
        int socket_fd = fixint(cl_socket_fd);
        int addr_len = sizeof(struct sockaddr_un);
        int new_fd = accept(socket_fd, &sockaddr, &addr_len);

        if (new_fd != -1) {
                NVALUES = 2;
                VALUES(0) = make_integer(new_fd);
                VALUES(1) = make_string_copy(&sockaddr.sun_path);
                return VALUES(0);
        } else return MAKE_FIXNUM(-1);
}
")

(c-inline () () nil  "cl_def_c_function(@do-accept-un,do_accept_un,1)" :one-liner t)
                
(defmethod socket-accept ((socket local-socket))
  (multiple-value-bind (fd name)
      (do-accept-un (socket-file-descriptor socket))
    (cond
      ((= fd -1)
       (socket-error "accept"))
      (t
       (values
	(make-instance (class-of socket)
		       :type (socket-type socket)
		       :protocol (socket-protocol socket)
		       :descriptor fd)
	name)))))

(defmethod socket-connect ((socket local-socket) &rest address)
  (assert (= 1 (length address)) (address) "Socket-connect needs two parameters for local sockets.")
  (let ((path (first address))
	(fd (socket-file-descriptor socket))
	(family (socket-family socket)))
    (if (= -1
	   (c-inline (fd family path) (:int :int :cstring) :int
		     "
{
        struct sockaddr_un sockaddr;

        sockaddr.sun_len = sizeof(struct sockaddr_un);
        sockaddr.sun_family = #1;
        strncpy(&sockaddr.sun_path,#2,104);

        @(return) = connect(#0,&sockaddr, sizeof(struct sockaddr_un));
}"))
	(socket-error "connect"))))

(defmethod socket-peername ((socket local-socket))
  (let* ((fd (socket-file-descriptor socket))
	 (peer (c-inline (fd) (:int) t
			 "
{
        struct sockaddr_un name;
        socklen_t len = sizeof(struct sockaddr_un);
        int ret = getpeername(#0,&name,&len);

        if (ret == 0) {
                @(return) = make_string_copy(&name.sun_path);
        } else {
                @(return) = Cnil;
        }
}")))
    (if peer
	peer
	(socket-error "getpeername"))))

;; Non-blocking mode

(defmethod non-blocking-mode ((socket socket))
  (let ((fd (socket-file-descriptor socket)))
    (not (zerop (c-inline (fd) (:int) :int "fcntl(#0,F_GETFL,NULL)&O_NONBLOCK" :one-liner t)))))

(defmethod (setf non-blocking-mode) (non-blocking-p (socket socket))
  (let ((fd (socket-file-descriptor socket))
	(nblock (if non-blocking-p 1 0)))
    (if (= -1 (c-inline (fd nblock) (:int :int) :int
	      "
{
        int oldflags = fcntl(#0,F_GETFL,NULL);
        int newflags = (oldflags & ~O_NONBLOCK) |
                       (#1 ? O_NONBLOCK : 0);
        @(return) = fcntl(#0,F_SETFL,newflags);
}"))
	(socket-error "fcntl")
	non-blocking-p)))

;; Streams

(defun make-stream-from-fd (fd mode &optional (name "FD-STREAM"))
  (assert (stringp name) (name) "name must be a string.")
  (c-inline (name fd (ecase mode
		       (:input (c-constant "smm_input"))
		       (:output (c-constant "smm_output"))))
	    (t :int :int)
	    t
	    "ecl_make_stream_from_fd(#0,#1,#2)"
	    :one-liner t))

(defgeneric socket-make-stream (socket  &rest args)
    (:documentation "Find or create a STREAM that can be used for IO
on SOCKET (which must be connected).  ARGS are ignored."))

(defmethod socket-make-stream ((socket socket)  &rest args)
  (declare (ignore args))
  (let ((stream (and (slot-boundp socket 'stream)
		     (slot-value socket 'stream))))
    (unless stream
      (setf stream (let ((fd (socket-file-descriptor socket)))
		     (make-two-way-stream
		      (make-stream-from-fd fd :input)
		      (make-stream-from-fd fd :output))))
      (setf (slot-value socket 'stream) stream)
      #+ ignore
      (sb-ext:cancel-finalization socket))
    stream))

;;; Error handling

(define-condition socket-error (error)
  ((errno :initform nil
          :initarg :errno  
          :reader socket-error-errno) 
   (symbol :initform nil :initarg :symbol :reader socket-error-symbol)
   (syscall  :initform "outer space" :initarg :syscall :reader socket-error-syscall))
  (:report (lambda (c s)
             (let ((num (socket-error-errno c)))
               (format s "Socket error in \"~A\": ~A (~A)"
                       (socket-error-syscall c)
                       (or (socket-error-symbol c) (socket-error-errno c))
		       (c-inline (num) (:int) :cstring
				 "strerror(#0)" :one-liner t)))))
  (:documentation "Common base class of socket related conditions."))

(defmacro define-socket-condition (symbol name)
  `(progn
     (defconstant ,symbol (c-constant ,(symbol-name symbol)))
     (define-condition ,name (socket-error)
       ((symbol :reader socket-error-symbol :initform (quote ,symbol))))
     (export ',name)
     (push (cons ,symbol (quote ,name)) *conditions-for-errno*)))

(defparameter *conditions-for-errno* nil)
;;; this needs the rest of the list adding to it, really.  They also
;;; need symbols to be added to constants.ccon
;;; I haven't yet thought of a non-kludgey way of keeping all this in
;;; the same place
(define-socket-condition EADDRINUSE address-in-use-error)
(define-socket-condition EAGAIN interrupted-error)
(define-socket-condition EBADF bad-file-descriptor-error)
(define-socket-condition ECONNREFUSED connection-refused-error)
(define-socket-condition ETIMEDOUT operation-timeout-error)
(define-socket-condition EINTR interrupted-error)
(define-socket-condition EINVAL invalid-argument-error)
(define-socket-condition ENOBUFS no-buffers-error)
(define-socket-condition ENOMEM out-of-memory-error)
(define-socket-condition EOPNOTSUPP operation-not-supported-error)
(define-socket-condition EPERM operation-not-permitted-error)
(define-socket-condition EPROTONOSUPPORT protocol-not-supported-error)
(define-socket-condition ESOCKTNOSUPPORT socket-type-not-supported-error)
(define-socket-condition ENETUNREACH network-unreachable-error)


(defun condition-for-errno (err)
  (or (cdr (assoc err *conditions-for-errno* :test #'eql)) 'socket-error))

(defun socket-error (where)
  (let* ((errno  (c-constant "errno"))
         (condition (condition-for-errno errno)))
    (error condition :errno errno  :syscall where)))

;; DNS errors

(defvar *name-service-errno* 0
  "The value of h_errno, after it's been fetched from Unix-land by calling
GET-NAME-SERVICE-ERRNO")

(defun name-service-error (where)
  (get-name-service-errno)
  ;; Comment next to NETDB_INTERNAL in netdb.h says "See errno.".
  ;; This special case treatment hasn't actually been tested yet.
  (if (= *name-service-errno* (c-constant "NETDB_INTERNAL"))
      (socket-error where)
    (let ((condition
	   (condition-for-name-service-errno *name-service-errno*)))
      (error condition :errno *name-service-errno* :syscall where))))

(define-condition name-service-error (condition)
  ((errno :initform nil
	  :initarg :errno
	  :reader name-service-error-errno)
   (symbol :initform nil :initarg :symbol :reader name-service-error-symbol)
   (syscall :initform "an unknown location" :initarg :syscall :reader name-service-error-syscall))
  (:report (lambda (c s)
	     (let ((num (name-service-error-errno c)))
	       (format s "Name service error in \"~A\": ~A (~A)"
		       (name-service-error-syscall c)
		       (or (name-service-error-symbol c)
			   (name-service-error-errno c))
		       (get-name-service-error-message num))))))

(defmacro define-name-service-condition (symbol name)
  `(progn
     (defconstant ,symbol (c-constant ,(symbol-name symbol)))
     (define-condition ,name (name-service-error)
       ((symbol :reader name-service-error-symbol :initform (quote ,symbol))))
     (push (cons ,symbol (quote ,name)) *conditions-for-name-service-errno*)
     (export (quote ,symbol))))

(defparameter *conditions-for-name-service-errno* nil)

(define-name-service-condition NETDB_INTERNAL netdb-internal-error)
(define-name-service-condition NETDB_SUCCESS netdb-success-error)
(define-name-service-condition HOST_NOT_FOUND host-not-found-error)
(define-name-service-condition TRY_AGAIN try-again-error)
(define-name-service-condition NO_RECOVERY no-recovery-error)
;; this is the same as the next one
;;(define-name-service-condition NO_DATA no-data-error)
(define-name-service-condition NO_ADDRESS no-address-error)

(defun condition-for-name-service-errno (err)
  (or (cdr (assoc err *conditions-for-name-service-errno* :test #'eql))
      'name-service))

(defun get-name-service-errno ()
  (setf *name-service-errno* (c-constant "h_errno")))

(defun get-name-service-error-message (num)
  (c-inline (num) (:int) :cstring "hstrerror(#0)" :one-liner t))

;; Sockopt fun

(defun get-sockopt-int (fd const)
  (let ((ret (c-inline (fd const) (:int :int) t
"{
        int sockopt;
        socklen_t socklen = sizeof(int);
        int ret = getsockopt(#0,SOL_SOCKET,#1,&sockopt,&socklen);

        @(return) = (ret == 0) ? make_integer(sockopt) : Cnil;
}")))
    (if ret
	ret
	(error "Sockopt error: ~A" (c-inline () () :cstring "strerror(errno)" :one-liner t)))))


(defun get-sockopt-bool (fd const)
  (let ((ret (c-inline (fd const) (:int :int) t
"{
        int sockopt;
        socklen_t socklen = sizeof(int);
        int ret = getsockopt(#0,SOL_SOCKET,#1,&sockopt,&socklen);

        @(return) = (ret == 0) ? Ct : Cnil;
}")))
    (if ret
	ret
	(error "Sockopt error: ~A" (c-inline () () :cstring "strerror(errno)" :one-liner t)))))

(defun set-sockopt-int (fd const value)
  (let ((ret (c-inline (fd const value) (:int :int :int) t
"{
        int sockopt = #2;
        int ret = setsockopt(#0,SOL_SOCKET,#1,&sockopt,sizeof(int));
        @(return) = (ret == 0) ? Ct : Cnil;
}")))
    (if ret
	value
	(error "Sockopt error: ~A" (c-inline () () :cstring "strerror(errno)" :one-liner t)))))

(defun set-sockopt-bool (fd const value)
  (let ((ret (c-inline (fd const value) (:int :int :object) t
"{
        int sockopt = (#2 == Cnil) ? 0 : 1;
        int ret = setsockopt(#0,SOL_SOCKET,#1,&sockopt,sizeof(int));
        @(return) = (ret == 0) ? Ct : Cnil;
}")))
    (if ret
	ret
	(error "Sockopt error: ~A" (c-inline () () :cstring "strerror(errno)" :one-liner t)))))

(eval-when (:compile-toplevel :load-toplevel)
  (defmacro define-sockopt (name c-const type &optional (read-only nil))
    `(progn
       (export ',name)
       (defun ,name (socket)
	 (,(intern (format nil "GET-SOCKOPT-~A" type))
	   (socket-file-descriptor socket)
	   (c-constant ,c-const)))
       ,@(unless read-only
	   `((defun (setf ,name) (value socket)
	       (,(intern (format nil "SET-SOCKOPT-~A" type))
		 (socket-file-descriptor socket)
		 (c-constant ,c-const)
		 value)))))))

(define-sockopt sockopt-type "SO_TYPE" int t)
(define-sockopt sockopt-receive-buffer "SO_RCVBUF" int)
(define-sockopt sockopt-reuse-address "SO_REUSEADDR" bool)
(define-sockopt sockopt-keep-alive "SO_KEEPALIVE" bool)
(define-sockopt socket-dont-route "SO_DONTROUTE" bool)
(define-sockopt socket-linger "SO_LINGER" bool)

#- linux
(define-sockopt sockopt-reuse-port "SO_REUSEPORT" bool)

;; Add sockopts here as you need them...
