/* posix */
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>

/* bsd extensions */
#include <sys/uio.h>
#include <sys/socket.h>

#include "priv.h"

#include <lock.h>

static Lock _sock_lock;
static Rock *_sock_rock;

Rock*
_sock_findrock(int fd, struct stat *dp)
{
	Rock *r;
	struct stat d;

	if(dp == 0)
		dp = &d;
	if(fstat(fd, dp) < 0)
		return 0;
	lock(&_sock_lock);
	for(r = _sock_rock; r; r = r->next){
		if(r->inode == dp->st_ino
		&& r->dev == dp->st_dev)
			break;
	}
	unlock(&_sock_lock);
	return r;
}

Rock*
_sock_newrock(int fd)
{
	Rock *r;
	struct stat d;

	if(fstat(fd, &d) < 0)
		return 0;
	lock(&_sock_lock);
	for(r = _sock_rock; r; r = r->next){
		if(r->inode == d.st_ino
		&& r->dev == d.st_dev)
			break;
	}
	if(r == 0){
		r = malloc(sizeof(Rock));
		if(r == 0){
			unlock(&_sock_lock);
			return 0;
		}
		r->dev = d.st_dev;
		r->inode = d.st_ino;
		r->next = _sock_rock;
		_sock_rock = r;
	}
	unlock(&_sock_lock);
	memset(&r->raddr, 0, sizeof(r->raddr));
	memset(&r->addr, 0, sizeof(r->addr));
	r->reserved = 0;
	r->other = -1;
	return r;
}

int
_sock_data(int cfd, char *net, int domain, int stype, int protocol, Rock **rp)
{
	int n, fd;
	Rock *r;
	char name[Ctlsize];

	/* get the data file name */
	n = read(cfd, name, sizeof(name)-1);
	if(n < 0){
		close(cfd);
		errno = ENOBUFS;
		return -1;
	}
	name[n] = 0;
	n = strtoul(name, 0, 0);
	snprintf(name, sizeof name, "/net/%s/%d/data", net, n);

	/* open data file */
	fd = open(name, O_RDWR);
	close(cfd);
	if(fd < 0){
		errno = ENOBUFS;
		return -1;
	}

	/* hide stuff under the rock */
	snprintf(name, sizeof name, "/net/%s/%d/ctl", net, n);
	r = _sock_newrock(fd);
	if(r == 0){
		close(fd);
		errno = ENOBUFS;
		return -1;
	}
	r->domain = domain;
	r->stype = stype;
	r->protocol = protocol;
	strcpy(r->ctl, name);
	if(rp)
		*rp = r;
	return fd;
}

int
socket(int domain, int stype, int protocol)
{
	Rock *r;
	int cfd, fd, n;
	int pfd[2];
	char *net;

	switch(domain){
	case PF_INET:
	case PF_INET6:
		/* get a free network directory */
		switch(stype){
		case SOCK_DGRAM:
			net = "udp";
			cfd = open("/net/udp/clone", O_RDWR);
			break;
		case SOCK_STREAM:
			net = "tcp";
			cfd = open("/net/tcp/clone", O_RDWR);
			break;
		case SOCK_RDM:
			net = "il";
			cfd = open("/net/il/clone", O_RDWR);
			break;
		default:
			errno = EPROTONOSUPPORT;
			return -1;
		}
		if(cfd < 0)
			return -1;
		return _sock_data(cfd, net, domain, stype, protocol, 0);
	case PF_UNIX:
		if(pipe(pfd) < 0)
			return -1;
		r = _sock_newrock(pfd[0]);
		if(r == 0){
			close(pfd[0]);
			close(pfd[1]);
			errno = ENOBUFS;
			return -1;
		}
		r->domain = domain;
		r->stype = stype;
		r->protocol = protocol;
		r->other = pfd[1];
		return pfd[0];
	default:
		errno = EPROTONOSUPPORT;
		return -1;
	}
}

int
issocket(int fd)
{
	Rock *r;

	r = _sock_findrock(fd, 0);
	return (r != 0);
}

/*
 * probably should do better than this
 */
int getsockopt(int, int, int, void *, int *)
{
	return -1;
}

int setsockopt(int, int, int, void *, int)
{
	return 0;
}

