/*
 *  This file is part of ixemul.library for the Amiga.
 *  Copyright (C) 1991, 1992  Markus M. Wild
 *  Portions Copyright (C) 1996 by Jeff Shepherd
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This 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
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public
 *  License along with this library; if not, write to the Free
 *  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *  socket.c,v 1.1.1.1 1994/04/04 04:29:41 amiga Exp
 *
 *  socket.c,v
 * Revision 1.1.1.1  1994/04/04  04:29:41  amiga
 * Initial CVS check in.
 *
 * Revision 1.2  1993/11/05  22:02:33  mwild
 * inet.library code, plus NOT YET WORKING code for "own" sockets
 *
 */

#define KERNEL
#include "ixemul.h"
#include "kprintf.h"

#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/unix_socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <net/route.h>
#include <netinet/in.h>
#include "select.h"
#include <machine/param.h>
#include <string.h>

#define static
/* AS225 and AmiTCP already has AF_UNIX sockets (sort of) */
/* #define USER_SOCKETS */ /* include code for own AF_UNIX sockets */
static struct file *getsock (int fdes);
static int soo_read   (struct file *fp, char *buf, int len);
static int soo_write  (struct file *fp, char *buf, int len);
static int soo_ioctl  (struct file *fp, int cmd, int inout, int arglen, caddr_t data);
static int soo_select (struct file *fp, int select_cmd, int io_mode);
static int soo_close  (struct file *fp);
static int unix_bind  (struct unix_socket *so, struct sockaddr_in *name);
static int unix_read  (struct file *fp, char *buf, int len);
static int unix_write (struct file *fp, char *buf, int len);
static int unix_ioctl (struct file *fp, int cmd, int inout, int arglen, caddr_t data);
static int unix_select(struct file *fp, int select_cmd, int io_mode);
static int unix_close (struct file *fp);
static int unix_connect(struct unix_socket *so,struct sockaddr_in *name);
static struct unix_socket *unix_accept(struct unix_socket *so, struct sockaddr_in *name);
static int unix_listen(struct unix_socket *so, int backlog);
static int unix_create(struct unix_socket **so, int type, int protocol);
static int sock_connect(struct unix_socket *so1, struct unix_socket *so2);
static void sock_close(struct unix_socket *so);
void _set_socket_params(struct file *fp, int type);

int
socket (int domain, int type, int protocol) {
    extern int NET__socket(int, int, int);
    struct file *fp;
    struct user *p = &u;
    int fd, err, ostat, omask;

    if (domain == AF_INET && !p->u_ixnetbase)
    {
		errno = ENOSYS;
		KPRINTF (("&errno = %lx, errno = %ld\n", &errno, errno));
		return -1;
    }

    ostat = p->p_stat;
    p->p_stat = SWAIT;
    omask = syscall (SYS_sigsetmask, ~0);

    do {
		if ((err = falloc (&fp, &fd)))
	    break;

#ifdef USER_SOCKETS
	if (domain == AF_INET) {
#endif
            fp->f_so = NET__socket(domain,type,protocol);
				err = (fp->f_so == -1) ? errno : 0;

#ifdef USER_SOCKETS
	}
	else if (domain == AF_UNIX) {
	    struct unix_socket *so;
	    err = unix_create (&so, type, protocol);
	    if (!err)
		fp->f_sock = so;
	}
	else {
	    err = ENOSYS;
	}
#endif
	if (err) {
	    /* free the allocated fd */
	    p->u_ofile[fd] = 0;
	    fp->f_count = 0;
	    break;
	}

	_set_socket_params(fp,domain);
    } while (0);

    syscall (SYS_sigsetmask, omask);

    if (CURSIG (p))
		SetSignal (0, SIGBREAKF_CTRL_C);

    p->p_stat = ostat;

    if (err == EINTR)
		setrun (FindTask (0));

    errno = err;
    KPRINTF (("&errno = %lx, errno = %ld\n", &errno, errno));
    return err ? -1 : fd;
}


int
bind (int s, const struct sockaddr *name, int namelen) {
    extern int NET__bind(struct file *, const struct sockaddr *, int);
    register struct file *fp = getsock (s);
    struct user *p = &u;
    int ostat, error;

    if (! fp)
	return -1;

    ostat = p->p_stat;
    p->p_stat = SWAIT;
#ifdef USER_SOCKETS
    if (fp->f_type == DTYPE_SOCKET) {
#endif
	error = NET__bind(fp,name,namelen);
#ifdef USER_SOCKETS
    }
    else {
		error = unix_bind(fp->f_sock,(struct sockaddr_in *)name);
    }
#endif

    if (CURSIG (p))
		SetSignal (0, SIGBREAKF_CTRL_C);

    p->p_stat = ostat;

    if (errno == EINTR)
		setrun (FindTask (0));

    KPRINTF (("&errno = %lx, errno = %ld\n", &errno, errno));

    return error;
}

int
listen (int s, int backlog) {
    extern int NET__listen(struct file *, int);
    register struct file *fp = getsock (s);
    struct user *p = &u;
    int ostat, error;

    if (! fp)
			return -1;

    ostat = p->p_stat;
    p->p_stat = SWAIT;

#ifdef USER_SOCKETS
    if (fp->f_type == DTYPE_SOCKET) {
#endif
	error = NET__listen(fp,backlog);
#ifdef USER_SOCKETS
    }
    else {
		error = unix_listen (fp->f_sock, backlog);
    }
#endif

    if (CURSIG (p))
		SetSignal (0, SIGBREAKF_CTRL_C);

    p->p_stat = ostat;

    if (errno == EINTR)
		setrun (FindTask (0));

    KPRINTF (("&errno = %lx, errno = %ld\n", &errno, errno));
    return error;
}

int
accept (int s, struct sockaddr *name, int *namelen) {
    extern int NET__accept(struct file *, struct sockaddr *, int *);
    struct file *fp = getsock (s), *fp2;
    struct user *p = &u;
    int err, fd2, ostat;

    if (! fp)
		return -1;

    ostat = p->p_stat;
    p->p_stat = SWAIT;

  do {
	/* first try to get a new descriptor. If that fails, don't even
	   bother to call the library */
	if ((err = falloc (&fp2, &fd2)))
	    break;

#ifdef USER_SOCKETS
	if (fp->f_type == DTYPE_SOCKET) {
#endif
            fp2->f_so = NET__accept(fp,name,namelen);
	    err = (fp2->f_so == -1) ? errno : 0;
#ifdef USER_SOCKETS
	}
	else {
	    fp2->f_sock = unix_accept(fp->f_sock,(struct sockaddr_in *)name);
	    err = (fp2->f_sock) ? 0 : errno;
	}
#endif
	if (err) {
	    /*	the second file */
	    u.u_ofile[fd2] = 0;
	    fp2->f_count = 0;
	    break;
	}

	_set_socket_params(fp2,(fp->f_type == DTYPE_SOCKET) ? AF_INET : AF_UNIX);

    } while (0);

    if (CURSIG (p))
		SetSignal (0, SIGBREAKF_CTRL_C);

    p->p_stat = ostat;

    if (err == EINTR)
		setrun (FindTask (0));

    errno = err;
    KPRINTF (("&errno = %lx, errno = %ld\n", &errno, errno));
    return err ? -1 : fd2;
}


int
connect (int s, const struct sockaddr *name, int namelen) {
    extern int NET__connect(struct file *, const struct sockaddr *, int);
    register struct file *fp = getsock (s);
    struct user *p = &u;
    int ostat, error;

    if (! fp)
		return -1;

    ostat = p->p_stat;
    p->p_stat = SWAIT;

#ifdef USER_SOCKETS
    if (fp->f_type == DTYPE_SOCKET) {
#endif
	error = NET__connect(fp, name, namelen);
#ifdef USER_SOCKETS
    }
    else {
        error = unix_connect(fp->f_sock,name);
    }
#endif

    if (CURSIG (p))
		SetSignal (0, SIGBREAKF_CTRL_C);

    p->p_stat = ostat;

    if (errno == EINTR)
		setrun (FindTask (0));

    KPRINTF (("&errno = %lx, errno = %ld\n", &errno, errno));
    return error;
}

/* This function is not available with Commodore sockets. */

#ifdef USER_SOCKETS
int
socketpair (int domain, int type, int protocol, int sv[2]) {
    struct file *fp1, *fp2;
    struct unix_socket *so1, *so2;
    struct user *p = &u;
    int ostat, error;

    /* minimal test.. */
    if (!sv[0] || !sv[1]) {
		errno = EFAULT;
		KPRINTF (("&errno = %lx, errno = %ld\n", &errno, errno));
		return -1;
    }

    /* no go with Commo-sockets */
    if (domain == AF_INET) {
		errno = EPFNOSUPPORT;
		KPRINTF (("&errno = %lx, errno = %ld\n", &errno, errno));
		return -1;
    }

    ostat = p->p_stat;
    p->p_stat = SWAIT;

    do {
	/* first try to allocate two descriptors */
	if (error = falloc (&fp1, &sv[0]))
	    break;

	if (error = falloc (&fp2, &sv[1])) {
free_first:
	    /* free first descriptor */
	    u.u_ofile[sv[0]] = 0;
	    fp1->f_count = 0;
	    break;
	}

	if (error = unix_create(&so1, type, protocol)) {
free_second:
	    u.u_ofile[sv[1]] = 0;
	    fp2->f_count = 0;
	    goto free_first;
	}

	if (error = unix_create(&so2, type, protocol)) {
close_so1:
	    sock_close (so1);
	    goto free_second;
	}

	if (error = sock_connect(so1, so2)) {
close_so2:
	    sock_close (so2);
	    goto close_so1;
	}
#if 0
	if (type == SOCK_DGRAM) {
	    /*
	     * Datagram socket connection is asymmetric.
	     */
	    if (error = sock_connect(so2, so1))
	    goto close_so2;
	}
#endif
    } while (0);

    p->p_stat = ostat;

    if (error == EINTR)
		setrun (FindTask (0));

    errno = error;
    KPRINTF (("&errno = %lx, errno = %ld\n", &errno, errno));
    return error ? -1 : 0;
}
#else
int
socketpair (int domain, int type, int protocol, int sv[2]) {
    errno = EPFNOSUPPORT;
    KPRINTF (("&errno = %lx, errno = %ld\n", &errno, errno));
    return -1;
}
#endif

int
sendto (int s, const void *buf, int len, int flags, const struct sockaddr *to, int tolen) {
    extern NET__sendto(struct file *,const void *, int, int, const struct sockaddr *, int);
    register struct file *fp = getsock (s);
    struct user *p = &u;
    int ostat;
    int rc;

    if (! fp || fp->f_type == DTYPE_USOCKET)
		return -1;

    ostat = p->p_stat;
    p->p_stat = SWAIT;

    rc = NET__sendto(fp,buf,len,flags,to,tolen);

    if (CURSIG (p))
		SetSignal (0, SIGBREAKF_CTRL_C);

    p->p_stat = ostat;

    /* the library doesn't send this to us of course ;-) */
    if (errno == EPIPE)
		_psignal (FindTask (0), SIGPIPE);

    if (errno == EINTR)
		setrun (FindTask (0));

    KPRINTF (("&errno = %lx, errno = %ld\n", &errno, errno));
    return rc;
}


int
send (int s,  const void *buf, int len, int flags) {
    extern int NET__send(struct file *, const void *, int, int);
    register struct file *fp = getsock (s);
    struct user *p = &u;
    int ostat;
    int rc;

    if (! fp)
		return -1;

#ifdef USER_SOCKETS
    /* send is the same as write() for AF_UNIX sockets */
    if (fp->f_type == DTYPE_USOCKET) {
		return unix_write (fp, (char *)buf,len);
    }
#endif

    ostat = p->p_stat;
    p->p_stat = SWAIT;

    rc = NET__send(fp, buf, len, flags);

    if (CURSIG (p))
		SetSignal (0, SIGBREAKF_CTRL_C);

    p->p_stat = ostat;

    /* the library doesn't send this to us of course ;-) */
    if (errno == EPIPE)
		_psignal (FindTask (0), SIGPIPE);

    if (errno == EINTR)
		setrun (FindTask (0));

    KPRINTF (("&errno = %lx, errno = %ld\n", &errno, errno));
    return rc;
}


int
sendmsg (int s, const struct msghdr *msg, int flags) {
    extern int NET__sendmsg(struct file *, const struct msghdr *, int);
    register struct file *fp = getsock (s);
    struct user *p = &u;
    int ostat, rc;

    if (!fp || fp->f_type == DTYPE_USOCKET)
		return -1;

    ostat = p->p_stat;
    p->p_stat = SWAIT;

    rc = NET__sendmsg(fp,msg,flags);

    if (CURSIG (p))
		SetSignal (0, SIGBREAKF_CTRL_C);

    p->p_stat = ostat;

    /* the library doesn't send this to us of course ;-) */
    if (errno == EPIPE)
		_psignal (FindTask (0), SIGPIPE);

    if (errno == EINTR)
		setrun (FindTask (0));

    KPRINTF (("&errno = %lx, errno = %ld\n", &errno, errno));
    return rc;
}


int
recvfrom (int s, void *buf, int len, int flags, struct sockaddr *from, int *fromlen) {
    extern int NET__recvfrom(struct file *, void *, int, int, struct sockaddr *, int *);
    register struct file *fp = getsock (s);
    struct user *p = &u;
    int ostat, rc;

    if (!fp || fp->f_type == DTYPE_USOCKET)
		return -1;

    ostat = p->p_stat;
    p->p_stat = SWAIT;

    rc = NET__recvfrom(fp,buf,len,flags,from,fromlen);

    if (CURSIG (p))
		SetSignal (0, SIGBREAKF_CTRL_C);

    p->p_stat = ostat;

    if (errno == EINTR)
		setrun (FindTask (0));

    KPRINTF (("&errno = %lx, errno = %ld\n", &errno, errno));
    return rc;
}


int
recv (int s, void *buf, int len, int flags) {
    extern int NET__recv(struct file *, void *, int, int);
    register struct file *fp = getsock (s);
    struct user *p = &u;
    int ostat, rc;

    if (! fp)
		return -1;

#ifdef USER_SOCKETS
    /* recv is the same as read() for AF_UNIX sockets */
    if (fp->f_type == DTYPE_USOCKET) {
		return unix_read (fp, (char *)buf,len);
    }
#endif

    ostat = p->p_stat;
    p->p_stat = SWAIT;

    rc = NET__recv(fp,buf,len,flags);

    if (CURSIG (p))
		SetSignal (0, SIGBREAKF_CTRL_C);

    p->p_stat = ostat;

    if (errno == EINTR)
		setrun (FindTask (0));

    KPRINTF (("&errno = %lx, errno = %ld\n", &errno, errno));
    return rc;
}


int
recvmsg (int s, struct msghdr *msg, int flags) {
    extern int NET__recvmsg(struct file *, struct msghdr *, int);
    register struct file *fp = getsock (s);
    struct user *p = &u;
    int ostat, rc;

    if (!fp || fp->f_type == DTYPE_USOCKET)
		return -1;

    ostat = p->p_stat;
    p->p_stat = SWAIT;

    rc = NET__recvmsg(fp,msg,flags);

    if (CURSIG (p))
		SetSignal (0, SIGBREAKF_CTRL_C);

    p->p_stat = ostat;

    if (errno == EINTR)
		setrun (FindTask (0));

    KPRINTF (("&errno = %lx, errno = %ld\n", &errno, errno));
    return rc;
}


int
shutdown (int s, int how) {
    extern int NET__shutdown(struct file *, int);
    register struct file *fp = getsock (s);
    struct user *p = &u;
    int ostat, err;

    if (! fp)
		return -1;

    ostat = p->p_stat;
    p->p_stat = SWAIT;

#ifdef USER_SOCKETS
    if (fp->f_type == DTYPE_USOCKET) {
		struct unix_socket *so = fp->f_sock;
		switch (how) {
			case 0:
				so->ss_options &= ~SS_CANREAD;
			break;

			case 1:
				so->ss_options &= ~SS_CANWRITE;
			break;

			case 2:
				so->ss_options &= ~SS_CANREADWRITE;
			break;
		}
		err = 0;
    }
    else {
#endif
       err = NET__shutdown(fp,how);
#ifdef USER_SOCKETS
    }
#endif

    if (CURSIG (p))
		SetSignal (0, SIGBREAKF_CTRL_C);

    p->p_stat = ostat;

    if (errno == EINTR)
		setrun (FindTask (0));

    KPRINTF (("&errno = %lx, errno = %ld\n", &errno, errno));
    return err;
}


int
setsockopt (int s, int level, int name, const void *val, int valsize) {
    extern int NET__setsockopt(struct file *, int, int, const void *, int);
    register struct file *fp = getsock (s);
    struct user *p = &u;
    int ostat, err;

    if (! fp)
		return -1;

    ostat = p->p_stat;
    p->p_stat = SWAIT;

    err = NET__setsockopt(fp,level,name,val,valsize);

    if (CURSIG (p))
		SetSignal (0, SIGBREAKF_CTRL_C);

    p->p_stat = ostat;

    if (errno == EINTR)
		setrun (FindTask (0));

    KPRINTF (("&errno = %lx, errno = %ld\n", &errno, errno));
    return err;
}


int
getsockopt (int s, int level, int name, void *val, int *valsize) {
    extern int NET__getsockopt(struct file *, int, int, void *, int *);
    register struct file *fp = getsock (s);
    struct user *p = &u;
    int ostat, err;

    if (! fp)
		return -1;

    ostat = p->p_stat;
    p->p_stat = SWAIT;

    err = NET__getsockopt(fp,level,name,val,valsize);

    if (CURSIG (p))
		SetSignal (0, SIGBREAKF_CTRL_C);

    p->p_stat = ostat;

    if (errno == EINTR)
		setrun (FindTask (0));

    KPRINTF (("&errno = %lx, errno = %ld\n", &errno, errno));
    return err;
}


/*
 * Get socket name.
 */
int
getsockname (int fdes, struct sockaddr *asa, int *alen) {
    extern int NET__getsockname(struct file *, struct sockaddr *, int *);
    register struct file *fp = getsock (fdes);
    struct user *p = &u;
    int ostat, err;

    if (! fp)
		return -1;

    ostat = p->p_stat;
    p->p_stat = SWAIT;

    err = NET__getsockname(fp,asa,alen);

    if (CURSIG (p))
		SetSignal (0, SIGBREAKF_CTRL_C);

    p->p_stat = ostat;

    if (errno == EINTR)
		setrun (FindTask (0));

    KPRINTF (("&errno = %lx, errno = %ld\n", &errno, errno));
    return err;
}

/*
 * Get name of peer for connected socket.
 */
int
getpeername (int fdes, struct sockaddr *asa, int *alen) {
    extern int NET__getpeername(struct file *, struct sockaddr *, int *);
    register struct file *fp = getsock (fdes);
    struct user *p = &u;
    int ostat, err;

    if (! fp)
		return -1;

    ostat = p->p_stat;
    p->p_stat = SWAIT;

    err = NET__getpeername(fp,asa,alen);

    if (CURSIG (p))
		SetSignal (0, SIGBREAKF_CTRL_C);

    p->p_stat = ostat;

    if (errno == EINTR)
		setrun (FindTask (0));

    KPRINTF (("&errno = %lx, errno = %ld\n", &errno, errno));
    return err;
}

static struct file *
getsock (int fdes) {
    register struct file *fp;
    register struct user *p = &u;

    if ((unsigned) fdes >= NOFILE) {
		errno = EBADF;
		KPRINTF (("&errno = %lx, errno = %ld\n", &errno, errno));
		return 0;
    }

    fp = p->u_ofile[fdes];

    if (fp == NULL) {
		errno = EBADF;
		KPRINTF (("&errno = %lx, errno = %ld\n", &errno, errno));
		return (0);
    }

    if (fp->f_type != DTYPE_SOCKET && fp->f_type != DTYPE_USOCKET) {
		errno = ENOTSOCK;
		KPRINTF (("&errno = %lx, errno = %ld\n", &errno, errno));
		return (0);
    }

    if (fp->f_type == DTYPE_SOCKET && !p->u_ixnetbase) {
			errno = EPIPE; /* ????? */
			KPRINTF (("&errno = %lx, errno = %ld\n", &errno, errno));
			return 0;
    }

    return (fp);
}


static int
soo_read (struct file *fp, char *buf, int len) {
    extern int NET__tcp_read(struct file *, char *, int);
    return NET__tcp_read(fp,buf,len);
}


static int
soo_write (struct file *fp, char *buf, int len) {
    extern int NET__tcp_write(struct file *, char *, int);
    return NET__tcp_write(fp,buf,len);
}



static int
soo_ioctl (struct file *fp, int cmd, int inout, int arglen, caddr_t data) {
    extern int NET__tcp_ioctl(struct file *, int, int, int, caddr_t);
    return NET__tcp_ioctl(fp,cmd,inout,arglen,data);
}


/* ix_lock_base() is very fussy - so put most of the close() code here */
static int
soo_close (struct file *fp) {
#if 0
    return NET__tcp_close(fp);
#else
    extern int NET__tcp_close(struct file *);
    int err = 0;

    ix_lock_base ();
    fp->f_count--;

    if (fp->f_count == 0) {
		/* don't have the base locked for IN_close, this MAY block!! */
		ix_unlock_base ();

	err = NET__tcp_close(fp);
    }
    else
		ix_unlock_base ();

    KPRINTF (("&errno = %lx, errno = %ld\n", &errno, errno));
    return err;
#endif
}

static int
soo_select (struct file *fp, int select_cmd, int io_mode) {
    extern int NET__tcp_select(struct file *, int, int);
    return NET__tcp_select(fp,select_cmd,io_mode);
}

#ifdef USER_SOCKETS
/* create an AF_UNIX socket */
static int unix_create(struct unix_socket **sock, int type, int protocol) {
    struct unix_socket *so;

    /* only SOCK_STREAM is supported (for now) */
    if (type != SOCK_STREAM)
	return ENOSYS;

    so = *sock = (struct unix_socket *)syscall(SYS_malloc,sizeof(struct unix_socket));
    if (!so)
	return ENOMEM;

    so->recvport = CreateMsgPort();
    if (!so->recvport)
	goto so_error;

    so->readptr = so->readbuflen = 0;
    so->readbuf = NULL;
    so->sendport = so->listenport = so->connport = so->replyport = NULL;
    so->so_options = 0;
    so->ss_options = SS_CANREADWRITE;
    so->type = type;
    so->protocol = protocol;
    return 0;

so_error:

    if (so->recvport)
	DeleteMsgPort(so->recvport);

    syscall(SYS_free,so);
    *sock = NULL;
    return ENOMEM;
}

static int unix_bind(struct unix_socket *so, struct sockaddr_in *name) {

    so->connport = CreateMsgPort();
    if (!so->connport)
	return ENOMEM;

    /* assign a port if want any port */
    /* assume MAX_PORTS ports are not in use at once */
    if (name->sin_addr.s_addr == INADDR_ANY) {
	/* if we specify a port by hand, ix_next_free_port is not updated
	 * we need to check the port list using ix_next_free_port just to
	 * make sure
	 */
	while (1) {
	    char portname[16];
	    sprintf(portname,PORTNAME,ixemulbase->ix_next_free_port);

	    if (FindName((struct List *)&ixemulbase->ix_socket_list, portname)) {
		ixemulbase->ix_next_free_port = (ixemulbase->ix_next_free_port + 1) % (MAX_PORTS - 1);
	    }
	    else {
		name->sin_port = ixemulbase->ix_next_free_port;
		ixemulbase->ix_next_free_port = (ixemulbase->ix_next_free_port + 1) % (MAX_PORTS - 1);
		break;
	    }
	}
    }
    /* make sure port wanted is not in use */
    else {
	char portname[16];
	sprintf(portname,PORTNAME,name->sin_port);

	if (FindName((struct List *)&ixemulbase->ix_socket_list, portname))
	    return EADDRINUSE;
    }

    /* put the connect port into the global list, name is ixport + port # */
    so->connport->mp_Node.ln_Name = (char *)syscall(SYS_malloc,6 + 10 /* assume < 10 digits */);
    sprintf(so->connport->mp_Node.ln_Name,PORTNAME, name->sin_port);
    so->connport->mp_Node.ln_Pri = 0;
    so->connport->mp_Node.ln_Type = NT_MSGPORT;
    AddHead((struct List *)&ixemulbase->ix_socket_list,(struct Node *)so->connport);

    so->so_options |= SS_BOUND;

    return 0;
}

static int unix_listen (struct unix_socket *so, int backlog) {
    /* socket needs to be bound first */
    if (!(so->so_options & SS_BOUND))
	return EDESTADDRREQ;

    /* priority of the msgport holds the backlog */
    so->connport->mp_Node.ln_Pri = backlog;
    so->so_options |= SO_ACCEPTCONN;

    return 0;
}

/* get the next message off of the connport and establish connection by replying message */
static struct unix_socket *unix_accept(struct unix_socket *so, struct sockaddr_in *name) {
    struct ConnectMsg *msg;
    struct unix_socket *retval = NULL;
    int err = 0;
    ULONG sig;
    struct user *p = &u;
    int ostat;

    if (!(so->so_options & SO_ACCEPTCONN)) {
	errno = EINVAL;
	return NULL;
    }

    if ((so->ss_options & SS_NBIO) && IsListEmpty(&so->connport->mp_MsgList)) {
	errno =  EWOULDBLOCK;
	return NULL;
    }

    ostat = p->p_stat;
    p->p_stat = SWAIT;

    /* get the address of the other end's recvport */
#if 0
    sig = Wait((1L << so->connport->mp_SigBit) | SIGBREAKF_CTRL_C);
    if (sig & SIGBREAKF_CTRL_C) {
	err = EINTR;
#else
    WaitPort(so->connport);
    if (0) {
	;
#endif
    }
    else {
	msg = (struct ConnectMsg *)GetMsg(so->connport);

	/* maybe name->sin_addr not needed */
	err = unix_create(&retval,so->type,so->protocol);
	if (!err) {
	    retval->sendport = msg->dataport;
	    retval->listenport = so->connport;
	    name->sin_addr.s_addr = (u_long)msg->dataport;

	    /* reply the message with our recvport */
	    msg->dataport = retval->recvport;

	    /* allow one less connection */
	    so->connport->mp_Node.ln_Pri--;

	    /* start up connect() program */
	    ReplyMsg((struct Message *)msg);
	}
    }

    p->p_stat = ostat;

    if (err == EINTR)
	setrun (FindTask (0));

    errno = err;

    if (CURSIG (p))
	SetSignal (0, SIGBREAKF_CTRL_C);

    return retval;
}

static int unix_connect(struct unix_socket *so,struct sockaddr_in *name) {
    char portnum[10];
    struct MsgPort *conport;
    struct ConnectMsg *conmsg;
    int ostat;
    int retval = 0;
    ULONG sig;
    struct user *p = &u;

    sprintf(portnum,PORTNAME,name->sin_port);

    if (so->ss_options & SS_ISCONNECTED)
	return EISCONN;

    ostat = p->p_stat;
    p->p_stat = SWAIT;

    /* find the port in the list */
    conport = (struct MsgPort *)FindName((struct List *)&ixemulbase->ix_socket_list, portnum);
    if (!conport) {
	retval = EADDRNOTAVAIL;
	goto conn_end;
    }

    /* no more connections allowed */
    if (conport->mp_Node.ln_Pri == 0) {
	retval = ECONNREFUSED;
	goto conn_end;
    }

    /* put the message on the connection port telling about the recv port */
    conmsg = (struct ConnectMsg *)syscall(SYS_malloc,sizeof(struct ConnectMsg));
    if (!conmsg) {
	retval = ENOMEM;
	goto conn_end;
    }

    conmsg->dataport = so->recvport;

    /* get the reply about the other end's recv port in our recvport
     * (the only port created right now) */
    conmsg->msg.mn_ReplyPort = so->recvport;
    conmsg->msg.mn_Length = sizeof(struct ConnectMsg);
    PutMsg(conport,(struct Message *)conmsg);

    /* wait for a reply */
#if 0
    sig = Wait((1L << so->recvport->mp_SigBit) | SIGBREAKF_CTRL_C);
    if (sig & SIGBREAKF_CTRL_C) {
	retval = EINTR;
#else
    WaitPort(so->recvport);
    if (0) {
	;
#endif
    }
    else {
	/* pluck off the other end's recvport */
	conmsg = (struct ConnectMsg *)GetMsg(so->recvport);
	so->sendport = conmsg->dataport;
	syscall(SYS_free,conmsg);

	so->ss_options |= SS_ISCONNECTED;
	conport->mp_Node.ln_Pri--;
    }
conn_end:

    p->p_stat = ostat;

    if (retval)
	errno = retval;

    if (retval == EINTR)
	setrun (FindTask (0));

    return (retval) ? -1 : 0;

}

/* NEW */

static int
unix_read (struct file *fp, char *buf, int len) {
    int ostat;
    int retval = len;
    struct user *p = &u;
    struct unix_socket *so = fp->f_sock;

    if (!(so->ss_options & SS_CANREAD))
	return -1;

    /* make sure we can read all the data without blocking */
    /* only check this for NBIO */
    if (so->ss_options & SS_NBIO) {
	struct DataMsg *msg;
	int datalen = 0;

	/* count the # of bytes in the buffer (if any) */
	if (so->readbuf) {
	    datalen = so->readbuflen - so->readptr;
	}
	Forbid();
	for(msg = (struct DataMsg *)so->recvport->mp_MsgList.lh_Head;
	    msg && msg->msg.mn_Node.ln_Succ;
	    msg = (struct DataMsg *)msg->msg.mn_Node.ln_Succ) {
	    datalen += msg->length;
	}
	Permit();
	if (datalen < len) {
	    errno = EWOULDBLOCK;
	    retval = -1;
	    goto end_read;
	}
    }

    ostat = p->p_stat;
    p->p_stat = SWAIT;

    while (len) {
	int sig;
	/* no buffer = need to get data from msgport */
	if (!so->readbuf) {
	    struct DataMsg *dmsg;
#if 0
	    sig = Wait((1L << so->recvport->mp_SigBit) | SIGBREAKF_CTRL_C);
	    if (sig & SIGBREAKF_CTRL_C) {
		errno = EINTR;
		retval = -1;
		break;
	    }
#else
	    WaitPort(so->recvport);
#endif
	    dmsg = (struct DataMsg *)GetMsg(so->recvport);
	    so->readbuf = (char *)syscall(SYS_malloc,dmsg->length);
	    so->readbuflen = dmsg->length;
	    if (!so->readbuf) {
		retval -= len;
		goto end_read;
	    }
	    memcpy(so->readbuf,dmsg->data,so->readbuflen);
	    so->readptr = 0;
	    ReplyMsg((struct Message *)dmsg);
	}

	/* we need more data than we have */
	if (len >= (so->readbuflen - so->readptr)) {
	    memcpy(buf,&so->readbuf[so->readptr],so->readbuflen - so->readptr);
	    syscall(SYS_free,so->readbuf);
	    so->readbuf = NULL;
	    len -= (so->readbuflen - so->readptr);
	    buf += (so->readbuflen - so->readptr);
	}
	/* we have more data than we need */
	else {
	    memcpy(buf,&so->readbuf[so->readptr],len);
	    so->readptr += len;
	    len = 0;

	    if (so->readptr == so->readbuflen) {
		syscall(SYS_free,so->readbuf);
		so->readbuf = NULL;
	    }
	}
    }

end_read:

    p->p_stat = ostat;

    if (CURSIG (p))
	SetSignal (0, SIGBREAKF_CTRL_C);


    if (errno == EINTR)
	setrun (FindTask (0));

    KPRINTF (("&errno = %lx, errno = %ld\n", &errno, errno));

    return retval;
}


static int
unix_write (struct file *fp, char *buf, int len) {
    struct user *p = &u;
    struct DataMsg *dmsg = (struct DataMsg *)syscall(SYS_malloc,sizeof(struct DataMsg));
    struct unix_socket *so = fp->f_sock;
    int ostat;
    int retval = -1;

    if (!dmsg) {
	errno = ENOMEM;
	return -1;
    }

    if (!(so->ss_options & SS_CANWRITE))
	return -1;

    ostat = p->p_stat;
    p->p_stat = SWAIT;

    dmsg->data = (char *)syscall(SYS_malloc,len);
    if (!dmsg->data) {
	syscall(SYS_free,dmsg);
	errno = ENOMEM;
	goto done_write;
    }
    else {

	/* we do this allocation here since not all sockets do a write () */
	if (!so->replyport) {
	    so->replyport = CreateMsgPort();
	    if (!so->replyport) {
		retval = ENOMEM;
		goto done_write;
	    }
	}
	memcpy(dmsg->data,buf,len);
	dmsg->msg.mn_Length = sizeof(struct DataMsg);
	dmsg->length = len;
	dmsg->msg.mn_ReplyPort = so->replyport;
	PutMsg(so->sendport,(struct Message *)dmsg);

	/* only wait for a reply if we do asynchronous IO */
	if (! (so->ss_options & SS_ASYNC)) {
	    WaitPort(so->replyport);
	    while (dmsg = (struct DataMsg *)GetMsg(so->replyport)) {
		syscall(SYS_free,dmsg->data);
		syscall(SYS_free,dmsg);
	    }
	}
    }

    retval = 0;

done_write:
    if (CURSIG (p))
	SetSignal (0, SIGBREAKF_CTRL_C);

    p->p_stat = ostat;

    KPRINTF (("&errno = %lx, errno = %ld\n", &errno, errno));
    return (retval) ? -1 : len;
}



static int
unix_ioctl (struct file *fp, int cmd, int inout, int arglen, caddr_t data) {
    struct unix_socket *so = fp->f_sock;
    struct user *p = &u;

    switch (cmd) {
	case FIONBIO :
	    if ((int)data == 1) {
		so->ss_options |= SS_NBIO;
	    }
	    else {
		so->ss_options &= ~SS_NBIO;
	    }
	break;

	case FIOASYNC:
	    if ((int)data == 1) {
		so->ss_options |= SS_ASYNC;
	    }
	    else {
		so->ss_options &= ~SS_ASYNC;
	    }
	break;

	default:
	    /* we don't care about everything else */
	    errno = EINVAL;
	    KPRINTF (("&errno = %lx, errno = %ld\n", &errno, errno));
	    return -1;
    }

    if (CURSIG (p))
	SetSignal (0, SIGBREAKF_CTRL_C);

    return 0;
}


static int
unix_select (struct file *fp, int select_cmd, int io_mode) {
    struct user *p = &u;
    struct unix_socket *so = fp->f_sock;
    int retval = 0;
    int ostat;

    ostat = p->p_stat;
    p->p_stat = SWAIT;

    if (select_cmd == SELCMD_PREPARE) {
	switch (io_mode) {
	    case SELMODE_IN:
		/* wait if no data in buf and none in msgport */
		if (!so->readbuf && IsListEmpty(&so->recvport->mp_MsgList)) {
		    retval = (1L << so->recvport->mp_SigBit);
		}
	    break;

	    case SELMODE_OUT: /* make sure we don't wait so signal ourselves */
		Signal(FindTask(NULL), 1 << p->u_sleep_sig);
	    break;

	    case SELMODE_EXC:
	    break;
	}
    }
    else {
	switch (io_mode) {
	    case SELMODE_IN:
		/* check the signal bit only if readbuf is empty */
		if (!so->readbuf) {
		    if (!IsListEmpty(&so->recvport->mp_MsgList)) {
			retval = 1;
		    }
		}
		else {
		    retval = 1;
		}
	    break;

	    case SELMODE_OUT: /* always ready to write */
		retval = 1;
	    break;

	    case SELMODE_EXC:
	    break;
	}
    }

    if (CURSIG (p))
	SetSignal (0, SIGBREAKF_CTRL_C);

    p->p_stat = ostat;
    return retval;
}


static int
unix_close (struct file *fp) {
    struct user *p = &u;
    struct unix_socket *so = fp->f_sock;

    ix_lock_base ();
    fp->f_count--;

    if (fp->f_count == 0) {
	sock_close(so);
    }
    ix_unlock_base ();
    return 0;
}

static int sock_connect(struct unix_socket *so1, struct unix_socket *so2) {
    so1->sendport = so2->recvport;
    so2->sendport = so1->recvport;
    so2->ss_options |= SS_ISCONNECTED;
    so2->ss_options |= SS_ISCONNECTED;
    return 0;
}

static void sock_close(struct unix_socket *so) {
    struct ConnectMsg *cmsg;
    struct DataMsg *dmsg;

    /* remove connection port from global list */
    if (so->connport) {
	Remove((struct Node *)so->connport);
	while (cmsg = (struct ConnectMsg *)GetMsg(so->connport)) {
	    /* signal everybody waiting */
	    if (cmsg->dataport)
		Signal(cmsg->dataport->mp_SigTask, 1L << cmsg->dataport->mp_SigBit);
	}
	if (so->connport->mp_Node.ln_Name)
	    syscall(SYS_free,so->connport->mp_Node.ln_Name);
	DeleteMsgPort(so->connport);
    }

    /* increment the backlog in the connection port */
    if (so->listenport) {
	so->listenport->mp_Node.ln_Pri++;
    }

    if (so->recvport) {
#if 0 /* Too bad the sender is going to have to suffer since the port may have disappeared  */
      /* if this was removed great crashes follow */

	/* reply to the sender that the outstanding msgs has been received */
	while(dmsg = (struct DataMsg *)GetMsg(so->recvport))
	    ReplyMsg((struct Message *)dmsg);

#endif
	DeleteMsgPort(so->recvport);
    }

    /* delete any data that was written */
    if (so->replyport) {
	while (dmsg = (struct DataMsg *)GetMsg(so->replyport)) {
	    syscall(SYS_free,dmsg->data);
	    syscall(SYS_free,dmsg);
	}
    }

    if (so->readbuf) {
	syscall(SYS_free,so->readbuf);
    }
}
#endif

/* needed to set of the function pointers */
void _set_socket_params(struct file *fp, int type) {
	    fp->f_stb.st_mode = 0666 | S_IFSOCK; /* not always, but.. */
	    fp->f_stb.st_size = 128;	/* sizeof mbuf. */
	    fp->f_stb.st_blksize = 128;
    fp->f_type	 = type;
	    fp->f_flags  = FREAD|FWRITE;
    fp->f_type	 = ( (type == AF_INET) ? DTYPE_SOCKET : DTYPE_USOCKET);

    if (fp->f_type == DTYPE_SOCKET) {
									fp->f_read   = soo_read;
									fp->f_write  = soo_write;
									fp->f_ioctl  = soo_ioctl;
									fp->f_close  = soo_close;
	fp->f_select = soo_select;
		}
#ifdef USER_SOCKETS
    else {
	fp->f_read   = unix_read;
	fp->f_write  = unix_write;
	fp->f_ioctl  = unix_ioctl;
	fp->f_close  = unix_close;
	fp->f_select = unix_select;
	}
#endif
}

