/*
 *	Implementation of the socket device driver and the generic part
 *	of the socket layer. Most of the work is done in the domain
 *	specific parts of the socket layer.
 *
 *	09/30/93, kay roemer.
 */

#include "config.h" 
#include "file.h"
#include "kerbind.h"
#include "mintsock.h"
#include "atarierr.h"
#include "sockerr.h"
#include "socket.h"
#include "sockios.h"
#include "net.h"
#include "iov.h"

/* special (internal use only) ioctl() to get a handle's fileptr */
#define FD2FP		(('S' << 8) | 101)

static long	sock_open	(FILEPTR *);
static long	sock_write	(FILEPTR *, char *, long);
static long	sock_read	(FILEPTR *, char *, long);
static long	sock_lseek	(FILEPTR *, long, short);
static long	sock_ioctl	(FILEPTR *, short, void *);
static long	sock_datime	(FILEPTR *, short *, short);
static long	sock_close	(FILEPTR *, short);
static long	sock_select	(FILEPTR *, long, short);
static void	sock_unselect	(FILEPTR *, long, short);

static long	socketcall	(FILEPTR *, struct generic_cmd *);

static long	sock_socket	(FILEPTR *, short, enum so_type, short);
static long	sock_socketpair	(FILEPTR *, short, enum so_type, short);
static long	sock_bind	(FILEPTR *, struct sockaddr *, short);
static long	sock_listen	(FILEPTR *, short);
static long	sock_accept	(FILEPTR *, struct sockaddr *, short *);
static long	sock_connect	(FILEPTR *, struct sockaddr *, short);
static long	sock_getsockname(FILEPTR *, struct sockaddr *, short *);
static long	sock_getpeername(FILEPTR *, struct sockaddr *, short *);
static long	sock_send	(FILEPTR *, char *, long, short);
static long	sock_sendto	(FILEPTR *, char *, long, short,
				struct sockaddr *, short);
static long	sock_recv	(FILEPTR *, char *, long, short);
static long	sock_recvfrom	(FILEPTR *, char *, long, short,
				struct sockaddr *, short *);
static long	sock_setsockopt	(FILEPTR *, short, short, void *, long);
static long	sock_getsockopt	(FILEPTR *, short, short, void *, long *);
static long	sock_shutdown	(FILEPTR *, short);
static long	sock_sendmsg	(FILEPTR *, struct msghdr *, short);
static long	sock_recvmsg	(FILEPTR *, struct msghdr *, short);

static void	so_drop		(FILEPTR *);
static long	so_getnewsock	(FILEPTR **);

struct devdrv sockdev = {
	sock_open, sock_write, sock_read, sock_lseek,
	sock_ioctl, sock_datime, sock_close,
	sock_select, sock_unselect,
	{ 0l, 0l, 0l }
};

struct dev_descr sockdev_descr = {
	&sockdev, 0, 0, (struct tty *)0,
	{ 0l, 0l, 0l, 0l }
};

/* stack of registered domains */
struct dom_ops	*alldomains = 0l;

/* This comes first, because of the `inline' */
static inline long
socketcall (fp, call)
	FILEPTR *fp;
	struct generic_cmd *call;
{
	switch (call->cmd) {
	case SOCKET_CMD: {
		struct socket_cmd *c = (struct socket_cmd *)call;
		return sock_socket (fp, c->domain, c->type, c->protocol);
	}
	case SOCKETPAIR_CMD: {
		struct socketpair_cmd *c = (struct socketpair_cmd *)call;
		return sock_socketpair (fp, c->domain, c->type, c->protocol);
	}
	case BIND_CMD: {
		struct bind_cmd *c = (struct bind_cmd *)call;
		return sock_bind (fp, c->addr, c->addrlen);
	}
	case LISTEN_CMD: {
		struct listen_cmd *c = (struct listen_cmd *)call;
		return sock_listen (fp, c->backlog);
	}
	case ACCEPT_CMD: {
		struct accept_cmd *c = (struct accept_cmd *)call;
		return sock_accept (fp, c->addr, c->addrlen);
	}
	case CONNECT_CMD: {
		struct connect_cmd *c = (struct connect_cmd *)call;
		return sock_connect (fp, c->addr, c->addrlen);
	} 
	case GETSOCKNAME_CMD: {
		struct getsockname_cmd *c = (struct getsockname_cmd *)call;
		return sock_getsockname (fp, c->addr, c->addrlen);
	}
	case GETPEERNAME_CMD: {
		struct getpeername_cmd *c = (struct getpeername_cmd *)call;
		return sock_getpeername (fp, c->addr, c->addrlen);
	}
	case SEND_CMD: {
		struct send_cmd *c = (struct send_cmd *)call;
		return sock_send (fp, c->buf, c->buflen, c->flags);
	}
	case SENDTO_CMD: {
		struct sendto_cmd *c = (struct sendto_cmd *)call;
		return sock_sendto (fp, c->buf, c->buflen, c->flags,
				   c->addr, c->addrlen);
	}
	case RECV_CMD: {
		struct recv_cmd *c = (struct recv_cmd *)call;
		return sock_recv (fp, c->buf, c->buflen, c->flags);
	} 
	case RECVFROM_CMD: {
		struct recvfrom_cmd *c = (struct recvfrom_cmd *)call;
		return sock_recvfrom (fp, c->buf, c->buflen, c->flags,
				     c->addr, c->addrlen);
	}
	case SETSOCKOPT_CMD: {
		struct setsockopt_cmd *c = (struct setsockopt_cmd *)call;
		return sock_setsockopt (fp, c->level, c->optname,
					c->optval, c->optlen);
	}
	case GETSOCKOPT_CMD: {
		struct getsockopt_cmd *c = (struct getsockopt_cmd *)call;
		return sock_getsockopt (fp, c->level, c->optname,
					c->optval, c->optlen);
	}
	case SHUTDOWN_CMD: {
		struct shutdown_cmd *c = (struct shutdown_cmd *)call;
		return sock_shutdown (fp, c->how);
	}
	case SENDMSG_CMD: {
		struct sendmsg_cmd *c = (struct sendmsg_cmd *)call;
		return sock_sendmsg (fp, c->msg, c->flags);
	}
	case RECVMSG_CMD: {
		struct recvmsg_cmd *c = (struct recvmsg_cmd *)call;
		return sock_recvmsg (fp, c->msg, c->flags);
	}
	default:
		return EINVFN;
	}
}

static long
sock_open (fp)
	FILEPTR *fp;
{
	struct socket *so;
	
	so = so_create ();
	if (!so) {
		DEBUG (("sockdev: sock_open: out of memory"));
		return ENSMEM;
	}
	fp->devinfo = (long)so;		
	return 0;	 
}

static long
sock_write (fp, buf, buflen)
	FILEPTR *fp;
	char *buf;
	long buflen;
{
	struct socket *so = (struct socket *)fp->devinfo;
	struct iovec iov[1] = {{ buf, buflen }};

	if (so->state == SS_VIRGIN)
		return EINVAL;
	return (*so->ops->send) (so, iov, 1, fp->flags & O_NDELAY, 0, 0, 0);
}

static long
sock_read (fp, buf, buflen)
	FILEPTR *fp;
	char *buf;
	long buflen;
{
	struct socket *so = (struct socket *)fp->devinfo;
	struct iovec iov[1] = {{ buf, buflen }};
	
	if (so->state == SS_VIRGIN)
		return EINVAL;
	return (*so->ops->recv) (so, iov, 1, fp->flags & O_NDELAY, 0, 0, 0);
}

static long
sock_lseek (fp, where, whence)
	FILEPTR *fp;
	long where;
	short whence;
{
	return EACCDN;
}

static long
sock_ioctl (fp, cmd, buf)
	FILEPTR *fp;
	short cmd;
	void *buf;
{
	struct socket *so = (struct socket *)fp->devinfo;

	switch (cmd) {
	case SOCKETCALL:
		return socketcall (fp, (struct generic_cmd *)buf);

	case FD2FP:
		*(FILEPTR **)buf = fp;
		return 0;

	case SIOCSPGRP:
		so->pgrp = (short)*(long *)buf;
		return 0;

	case SIOCGPGRP:
		*(long *)buf = (long)so->pgrp;
		return 0;

	case F_SETLK:
	case F_SETLKW: {
		struct flock *lk = (struct flock *)buf;

		switch (lk->l_type) {
		case F_UNLCK:
			if (so->lockpid != p_getpid ()) return ELOCKED;
			if (fp->flags & O_LOCK) {
				fp->flags &= ~O_LOCK;
				wake (IO_Q, (long)&so->lockpid);
			} else {
				DEBUG (("sock_ioctl (F_UNLCK): not locked"));
				return ENSLOCK;
			}
			return 0;

		case F_RDLCK:
		case F_WRLCK:
			while (fp->flags & O_LOCK) {
				if (so->lockpid == p_getpid ()) return 0;
				if (cmd == F_SETLK) return ELOCKED;
				if (isleep (IO_Q, (long)&so->lockpid)) {
					return EINTR;
				}
			}
			fp->flags |= O_LOCK;
			so->lockpid = p_getpid ();
			return 0;

		default:
			return EINVAL;
		}
	}
	case F_GETLK: {
		struct flock *lk = (struct flock *)buf;

		if (fp->flags & O_LOCK) {
			lk->l_start = 0;
			lk->l_len = 0;
			lk->l_pid = so->lockpid;
			lk->l_type = F_WRLCK;
		} else	lk->l_type = F_UNLCK;
		return 0;
	}
	default:
		if (so->state == SS_VIRGIN || so->state == SS_ISDISCONNECTED)
			return EINVAL;
		return (*so->ops->ioctl) (so, cmd, buf);
	}
}

static long
sock_datime (fp, timeptr, rwflag)
	FILEPTR *fp;
	short *timeptr;
	short rwflag;
{
	struct socket *so = (struct socket *)fp->devinfo;
	
	if (rwflag) {
		so->time = timeptr[0];
		so->date = timeptr[1];
	} else {
		timeptr[0] = so->time;
		timeptr[1] = so->date;
	}
	return 0;	
}

static long
sock_close (fp, pid)
	FILEPTR *fp;
	short pid;
{
	struct socket *so = (struct socket *)fp->devinfo;

/* Wake anyone waiting on the socket. */
	so_wakersel (so);
	so_wakewsel (so);
	so_wakexsel (so);
	wake (IO_Q, (long)so);

	if (fp->flags & O_LOCK && so->lockpid == pid) {
		fp->flags &= ~O_LOCK;
		wake (IO_Q, (long)&so->lockpid);
	}
	if (fp->links <= 0) {
		short oflags = so->flags;
		if (so_release (so) == 0) {
			if (oflags & SO_CLOSING && fp->links < 0) {
				/*
				 * This is hackish, but keeps Mints
				 * non reentrant close() from panic.
				 */
				fp->links = 0;
			}
			kfree (so);
		} else	FATAL (("sock_close: close() reentrancy problem!"));
	}
	return 0;
}

static long
sock_select (fp, proc, mode)
	FILEPTR *fp;
	long proc;
	short mode;
{
	struct socket *so = (struct socket *)fp->devinfo;

	if (so->state == SS_VIRGIN || so->state == SS_ISDISCONNECTED)
		return 1;
	return (*so->ops->select) (so, mode, proc);
}

static void
sock_unselect (fp, proc, mode)
	FILEPTR *fp;
	long proc;
	short mode;
{
	struct socket *so = (struct socket *)fp->devinfo;
	
	switch (mode) {
	case O_RDONLY:
		if (so->rsel == proc) so->rsel = 0;
		break;

	case O_WRONLY:
		if (so->wsel == proc) so->wsel = 0;
		break;

	case O_RDWR:
		if (so->xsel == proc) so->xsel = 0;
		break;
	}
}

static long
sock_socket (fp, domain, type, protocol)
	FILEPTR *fp;
	short domain;
	enum so_type type;
	short protocol;
{
	struct socket *so = (struct socket *)fp->devinfo;
	struct dom_ops *ops;
	long r;
	
	if (so->state != SS_VIRGIN)
		return EINVAL;

	for (ops = alldomains; ops; ops = ops->next)
		if (ops->domain == domain) break;
	if (!ops) {
		DEBUG (("sockdev: sock_socket: domain %d not supported",
			domain));
		return EAFNOSUPPORT;
	}

	switch (type) {
	case SOCK_DGRAM:
	case SOCK_STREAM:
	case SOCK_RAW:
	case SOCK_RDM:
	case SOCK_SEQPACKET:
		so->ops = ops;
		so->type = type;

		r = (*so->ops->attach) (so, protocol);
		if (r < 0) {
			DEBUG (("sockdev: sock_socket: failed to attach "
				"protocol data"));
			return r;
		}			
		so->state = SS_ISUNCONNECTED;
		return 0;

	default:
		DEBUG (("sockdev: sock_socket: no such socket type %d", type));
		return ESOCKTNOSUPPORT;
	}
}

static long
sock_socketpair (fp1, domain, type, protocol)
	FILEPTR *fp1;
	short domain;
	enum so_type type;
	short protocol;
{
	struct socket *so2, *so1 = (struct socket *)fp1->devinfo;
	FILEPTR *fp2;
	long fd2, r;

	r = sock_socket (fp1, domain, type, protocol);
	if (r < 0) {
		DEBUG (("sockdev: sock_socketpair: cannot create 1st socket"));
		return r;
	}
	fd2 = so_getnewsock (&fp2);
	if (fd2 < 0) {
		DEBUG (("sockdev: sock_socketpair: cannot alloc 2nd socket"));
		so_release (so1);
		return fd2;
	}
	r = sock_socket (fp2, domain, type, protocol);
	if (r < 0) {
		DEBUG (("sockdev: sock_socketpair: cannot create 2nd socket"));
		so_release (so1);
		f_close (fd2);
		return r;
	}	
	so2 = (struct socket *)fp2->devinfo;
	
	r = (*so1->ops->socketpair) (so1, so2);
	if (r < 0) {
		DEBUG (("sockdev: sock_socketpair: cannot connect the sockets"));
		so_release (so1);
		f_close (fd2);
		return r;
	}
	return fd2;	
}

static long
sock_bind (fp, addr, addrlen)
	FILEPTR *fp;
	struct sockaddr *addr;
	short addrlen;
{
	struct socket *so = (struct socket *)fp->devinfo;
	
	if (so->state == SS_VIRGIN || so->state == SS_ISDISCONNECTED)
		return EINVAL;
	return (*so->ops->bind) (so, addr, addrlen);
}

static long
sock_listen (fp, backlog)
	FILEPTR *fp;
	short backlog;
{
	struct socket *so = (struct socket *)fp->devinfo;
	long r;
	
	if (so->state != SS_ISUNCONNECTED)
		return EINVAL;

	if (backlog < 0) backlog = 0;
	r = (*so->ops->listen) (so, backlog);
	if (r < 0) {
		DEBUG (("sockdev: sock_listen: failing ..."));
		return r;
	}
	so->flags |= SO_ACCEPTCON;
	return 0;
}

static void
so_drop (fp)
	FILEPTR *fp;
{
	struct socket *newso, *so = (struct socket *)fp->devinfo;
	
	if (!(so->flags & SO_DROP))
		return;

	DEBUG (("so_drop: dropping incoming connection"));
	if (!(newso = so_create ()))
		return;
	newso->type = so->type;
	newso->ops = so->ops;
	if ((*so->ops->dup) (newso, so) < 0) {
		kfree (newso);
		return;
	}
	newso->state = SS_ISUNCONNECTED;
	(*so->ops->accept) (so, newso, fp->flags & O_NDELAY);
	so_release (newso);
	kfree (newso);
}

static long
sock_accept (fp, addr, addrlen)
	FILEPTR *fp;
	struct sockaddr *addr;
	short *addrlen;
{
	struct socket *newso, *so = (struct socket *)fp->devinfo;
	FILEPTR *newfp;
	long newfd, r;
	
	if (so->state != SS_ISUNCONNECTED)
		return EINVAL;

	if (!(so->flags & SO_ACCEPTCON)) {
		DEBUG (("sockdev: sock_accept: socket not listening"));
		return EINVAL;
	}
		
	newfd = so_getnewsock (&newfp);
	if (newfd < 0) {
		DEBUG (("sockdev: sock_accept: cannot alloc fresh socket"));
		so_drop (fp);
		return newfd;
	}
	newso = (struct socket *)newfp->devinfo;
	newso->type = so->type;
	newso->ops = so->ops;

	r = (*so->ops->dup) (newso, so);
	if (r < 0) {
		DEBUG (("sockdev: sock_accept: failed to dup protocol data"));
		f_close (newfd);
		so_drop (fp);
		return r;
	}
	newso->state = SS_ISUNCONNECTED;

	r = (*so->ops->accept) (so, newso, fp->flags & O_NDELAY);
	if (r < 0) {
		DEBUG (("sockdev: sock_accept: cannot accept a connection"));
		f_close (newfd);
		return r;
	}
	if (addr) {
		r = (*newso->ops->getname) (newso, addr, addrlen, PEER_ADDR);
		if (r < 0) {
			DEBUG (("sockdev: sock_accept: getname failed"));
			*addrlen = 0;
		}
	}
	return newfd;
}

static long
sock_connect (fp, addr, addrlen)
	FILEPTR *fp;
	struct sockaddr *addr;
	short addrlen;
{
	struct socket *so = (struct socket *)fp->devinfo;
	
	switch (so->state) {
	case SS_ISUNCONNECTED:
	case SS_ISCONNECTING:
		if (so->flags & SO_ACCEPTCON) {
			DEBUG (("sockdev: sock_connect: attempt to connect "
				"a listening socket"));
			return EINVAL;
		}
		return (*so->ops->connect) (so, addr, addrlen,
					   fp->flags & O_NDELAY);

	case SS_ISCONNECTED:
/* Connectionless sockets can be connected serveral times. So their state
   must always be SS_ISUNCONNECTED. */
		DEBUG (("sockdev: sock_connect: already connected"));
		return EISCONN;
   		
	case SS_ISDISCONNECTING:
	case SS_ISDISCONNECTED:
	case SS_VIRGIN:
		DEBUG (("sockdev: sock_connect: socket cannot connect"));
		return EINVAL;

	default:
		DEBUG (("socketdev: so_connect: invalid socket state %d",
			so->state));
		return EINTRN;
	}
}

static long
sock_getsockname (fp, addr, addrlen)
	FILEPTR *fp;
	struct sockaddr *addr;
	short *addrlen;
{
	struct socket *so = (struct socket *)fp->devinfo;

	if (so->state == SS_VIRGIN || so->state == SS_ISDISCONNECTED)
		return EINVAL;
	return (*so->ops->getname) (so, addr, addrlen, SOCK_ADDR);
}

static long
sock_getpeername (fp, addr, addrlen)
	FILEPTR *fp;
	struct sockaddr *addr;
	short *addrlen;
{
	struct socket *so = (struct socket *)fp->devinfo;

	if (so->state == SS_VIRGIN || so->state == SS_ISDISCONNECTED)
		return EINVAL;
	return (*so->ops->getname) (so, addr, addrlen, PEER_ADDR);
}

static long
sock_send (fp, buf, buflen, flags)
	FILEPTR *fp;
	char *buf;
	long buflen;
	short flags;
{
	struct socket *so = (struct socket *)fp->devinfo;
	struct iovec iov[1] = {{ buf, buflen }};
	
	if (so->state == SS_VIRGIN)
		return EINVAL;
	return (*so->ops->send) (so, iov, 1, fp->flags & O_NDELAY, flags, 0, 0);
}

static long
sock_sendto (fp, buf, buflen, flags, addr, addrlen)
	FILEPTR *fp;
	char *buf;
	long buflen;
	short flags;
	struct sockaddr *addr;
	short addrlen;
{
	struct socket *so = (struct socket *)fp->devinfo;
	struct iovec iov[1] = {{ buf, buflen }};
	
	if (so->state == SS_VIRGIN)
		return EINVAL;
	return (*so->ops->send) (so, iov, 1, fp->flags & O_NDELAY, flags,
				addr, addrlen);
}

static long
sock_recv (fp, buf, buflen, flags)
	FILEPTR *fp;
	char *buf;
	long buflen;
	short flags;
{
	struct socket *so = (struct socket *)fp->devinfo;
	struct iovec iov[1] = {{ buf, buflen }};
	
	if (so->state == SS_VIRGIN)
		return EINVAL;
	return (*so->ops->recv) (so, iov, 1, fp->flags & O_NDELAY, flags, 0, 0);
}

static long
sock_recvfrom (fp, buf, buflen, flags, addr, addrlen)
	FILEPTR *fp;
	char *buf;
	long buflen;
	short flags;
	struct sockaddr *addr;
	short *addrlen;
{
 	struct socket *so = (struct socket *)fp->devinfo;
	struct iovec iov[1] = {{ buf, buflen }};
	
	if (so->state == SS_VIRGIN)
		return EINVAL;
	return (*so->ops->recv) (so, iov, 1, fp->flags & O_NDELAY, flags,
				addr, addrlen);
}

static long
sock_setsockopt (fp, level, optname, optval, optlen)
	FILEPTR *fp;
	short level, optname;
	void *optval;
	long optlen;
{
	struct socket *so = (struct socket *)fp->devinfo;

	if (so->state == SS_VIRGIN || so->state == SS_ISDISCONNECTED)
		return EINVAL;
	if (level == SOL_SOCKET) switch (optname) {
	case SO_DROPCONN:
		if (!optval || optlen < sizeof (long))
			return EINVAL;
		if (*(long *)optval)
			so->flags |= SO_DROP;
		else	so->flags &= ~SO_DROP;
		return 0;

	default:
		break;
	}
	return (*so->ops->setsockopt) (so, level, optname, optval, optlen);
}

static long
sock_getsockopt (fp, level, optname, optval, optlen)
	FILEPTR *fp;
	short level, optname;
	void *optval;
	long *optlen;
{
	struct socket *so = (struct socket *)fp->devinfo;

	if (so->state == SS_VIRGIN || so->state == SS_ISDISCONNECTED) {
		DEBUG (("sockdev: sock_getsockopt: virgin state"));
		return EINVAL;
	}
	if (level == SOL_SOCKET) switch (optname) {
	case SO_DROPCONN:
		if (!optval || !optlen || *optlen < sizeof (long))
			return EINVAL;
		*(long *)optval = !!(so->flags & SO_DROP);
		*optlen = sizeof (long);
		return 0;

	default:
		break;
	}
	return (*so->ops->getsockopt) (so, level, optname, optval, optlen);
}

static long
sock_shutdown (fp, how)
	FILEPTR *fp;
	short how;
{
	struct socket *so = (struct socket *)fp->devinfo;
 
	if (so->state == SS_VIRGIN || so->state == SS_ISDISCONNECTED)
		return EINVAL;

	switch (how) {
	case 0:
		so->flags |= SO_CANTRCVMORE;
		break;
	case 1:
		so->flags |= SO_CANTSNDMORE;
		break;
	case 2:
		so->flags |= (SO_CANTRCVMORE|SO_CANTSNDMORE);
		break;
	}
	return (*so->ops->shutdown) (so, how);
}

static long
sock_sendmsg (fp, msg, flags)
	FILEPTR *fp;
	struct msghdr *msg;
	short flags;
{
	struct socket *so = (struct socket *)fp->devinfo;

	if (so->state == SS_VIRGIN)
		return EINVAL;

	if (msg->msg_accrights && msg->msg_accrightslen)
		return EINVAL;

	return (*so->ops->send) (so, msg->msg_iov, msg->msg_iovlen,
				fp->flags & O_NDELAY, flags,
				msg->msg_name, msg->msg_namelen);
}

static long
sock_recvmsg (fp, msg, flags)
	FILEPTR *fp;
	struct msghdr *msg;
	short flags;
{
	struct socket *so = (struct socket *)fp->devinfo;
	short namelen = msg->msg_namelen;
	long r;
	
	if (so->state == SS_VIRGIN)
		return EINVAL;

	if (msg->msg_accrights && msg->msg_accrightslen)
		msg->msg_accrightslen = 0;

	r = (*so->ops->recv) (so, msg->msg_iov, msg->msg_iovlen,
				fp->flags & O_NDELAY, flags,
				msg->msg_name, &namelen);
	msg->msg_namelen = namelen;
	return r;
}

/* socket utility functions */

/* Get a new filehandle with an associated fileptr and virgin socket.
 * Return the filehandle or an negative error code.
 * Return the fileptr in *fp.
 */
static long
so_getnewsock (fp)
	FILEPTR **fp;
{
	extern char sockdev_name[];
	long fd, r;

	fd = f_open (sockdev_name, O_RDWR);
	if (fd < 0) {
		DEBUG (("sockdev: so_getnewsock: cannot open %s", sockdev_name));
		return fd;
	}
	r = f_cntl (fd, (long)fp, FD2FP);
	if (r < 0) {
		DEBUG (("sockdev: so_getnewsock: f_cntl failed"));
		f_close (fd);
		return r;
	}
	return fd;
}
