#include	<sys/types.h>
#include	<sys/stat.h>
#include	<dirent.h>
#include	<fcntl.h>
#include	<errno.h>
#include	<stdio.h> /* for remove, rename */
#include	<limits.h>

#ifndef NAME_MAX
#	define NAME_MAX 256
#endif
#include	"lib9.h"
#include	"sys.h"
#include	"error.h"


typedef	struct Ufsinfo	Ufsinfo;

enum
{
	NUID	= 256,
	NGID	= 256,
	MAXPATH	= 1024,
	MAXCOMP	= 128
};

struct Ufsinfo
{
	int	uid;
	int	gid;
	int	mode;
	int	fd;
	DIR*	dir;
	ulong	offset;
	Qlock	oq;
};

char	*base = "/";

static	Qid	fsqid(char*, struct stat *);
static	void	fspath(Path*, char*, char*);
static	void	fsperm(Chan*, int);
static	ulong	fsdirread(Chan*, uchar*, int, ulong);
static	int	fsomode(int);

void
fsinit(void)
{
}

Chan*
fsattach(void *spec)
{
	Chan *c;
	struct stat stbuf;
	static int devno;
	Ufsinfo *uif;

	if(stat(base, &stbuf) < 0)
		error(strerror(errno));

	c = devattach('U', spec);

	uif = mallocz(sizeof(Ufsinfo));
	uif->gid = stbuf.st_gid;
	uif->uid = stbuf.st_uid;
	uif->mode = stbuf.st_mode;

	c->u.aux = uif;
	c->dev = devno++;

	return c;
}

Chan*
fsclone(Chan *c, Chan *nc)
{
	Ufsinfo *uif;

	nc = devclone(c, nc);
	uif = mallocz(sizeof(Ufsinfo));
	*uif = *(Ufsinfo*)c->u.aux;
	nc->u.aux = uif;

	return nc;
}

int
fswalk(Chan *c, char *name)
{
	Path *op;
	struct stat stbuf;
	char path[MAXPATH];
	Ufsinfo *uif;

	fspath(c->path, name, path);

/*	print("** fs walk '%s' -> %s\n", path, name); */

	if(stat(path, &stbuf) < 0)
		return 0;

	uif = c->u.aux;

	uif->gid = stbuf.st_gid;
	uif->uid = stbuf.st_uid;
	uif->mode = stbuf.st_mode;

	c->qid = fsqid(path, &stbuf);

	op = c->path;
	c->path = ptenter(&syspt, op, name);
	refdec(&op->r);

	return 1;
}

void
fsstat(Chan *c, char *buf)
{
	Dir d;
	struct stat stbuf;
	char path[MAXPATH];

	fspath(c->path, 0, path);
	if(stat(path, &stbuf) < 0)
		error(strerror(errno));

	strncpy(d.name, c->path->elem, NAMELEN);
	strncpy(d.uid, "unknown", NAMELEN);
	strncpy(d.gid, "unknown", NAMELEN);
	d.qid = c->qid;
	d.mode = (c->qid.path&CHDIR)|(stbuf.st_mode&0777);
	d.atime = stbuf.st_atime;
	d.mtime = stbuf.st_mtime;
	d.length = stbuf.st_size;
	d.type = 'U';
	d.dev = c->dev;
	convD2M(&d, buf);
}

Chan*
fsopen(Chan *c, int mode)
{
	char path[MAXPATH];
	int m, isdir;
	Ufsinfo *uif;

	m = mode & (OTRUNC|3);
	switch(m) {
	case 0:
		fsperm(c, 4);
		break;
	case 1:
	case 1|16:
		fsperm(c, 2);
		break;
	case 2:	
	case 0|16:
	case 2|16:
		fsperm(c, 4);
		fsperm(c, 2);
		break;
	case 3:
		fsperm(c, 1);
		break;
	default:
		error(Ebadarg);
	}

	isdir = c->qid.path & CHDIR;

	if(isdir && mode != OREAD)
		error(Eperm);

	m = fsomode(m & 3);
	c->mode = openmode(mode);

	uif = c->u.aux;

	fspath(c->path, 0, path);
	if(isdir) {
		uif->dir = opendir(path);
		if(uif->dir == 0)
			error(strerror(errno));
	}	
	else {
		if(mode & OTRUNC)
			m |= O_TRUNC;
		uif->fd = open(path, m, 0666);

		if(uif->fd < 0)
			error(strerror(errno));
	}
	uif->offset = 0;

	c->offset = 0;
	c->flag |= COPEN;
	return c;
}

void
fscreate(Chan *c, char *name, int mode, ulong perm)
{
	int fd, m;
	char path[MAXPATH];
	struct stat stbuf;
	Path *op;
	Ufsinfo *uif;

	fsperm(c, 2);

	m = fsomode(mode&3);

	fspath(c->path, name, path);

	uif = c->u.aux;

	if(perm & CHDIR) {
		if(m)
			error(Eperm);

		if(mkdir(path, perm & 0777) < 0)
			error(strerror(errno));

		fd = open(path, 0);
		if(fd >= 0) {
			chmod(path, perm & 0777);
			chown(path, uif->uid, uif->uid);
		}
		close(fd);

		uif->dir = opendir(path);
		if(uif->dir == 0)
			error(strerror(errno));
	}
	else {
		fd = open(path, O_WRONLY|O_CREAT|O_TRUNC, 0666);
		if(fd >= 0) {
			if(m != 1) {
				close(fd);
				fd = open(path, m);
			}
			chmod(path, perm & 0777);
			chown(path, uif->uid, uif->gid);
		}
		if(fd < 0)
			error(strerror(errno));
		uif->fd = fd;
	}

	if(stat(path, &stbuf) < 0)
		error(strerror(errno));
	c->qid = fsqid(path, &stbuf);
	c->offset = 0;
	c->flag |= COPEN;
	c->mode = openmode(mode);

	op = c->path;
	c->path = ptenter(&syspt, op, name);
	refdec(&op->r);
}

void
fsclose(Chan *c)
{
	Ufsinfo *uif;

	uif = c->u.aux;

	if(c->flag & COPEN) {
		if(c->qid.path & CHDIR)
			closedir(uif->dir);
		else
			close(uif->fd);
	}

	free(uif);
}

long
fsread(Chan *c, void *va, long n, ulong offset)
{
	int fd, r;
	Ufsinfo *uif;

	if(c->qid.path & CHDIR)
		return fsdirread(c, va, n, offset);

	uif = c->u.aux;
	qlock(&uif->oq);
	if(waserror()) {
		qunlock(&uif->oq);
		nexterror();
	}
	fd = uif->fd;
	if(uif->offset != offset) {
		r = lseek(fd, offset, 0);
		if(r < 0)
			error(strerror(errno));
		uif->offset = offset;
	}

	n = read(fd, va, n);
	if(n < 0)
		error(strerror(errno));

	uif->offset += n;
	qunlock(&uif->oq);
	poperror();

	return n;
}

long
fswrite(Chan *c, void *va, long n, ulong offset)
{
	int fd, r;
	Ufsinfo *uif;

	uif = c->u.aux;

	qlock(&uif->oq);
	if(waserror()) {
		qunlock(&uif->oq);
		nexterror();
	}
	fd = uif->fd;
	if(uif->offset != offset) {
		r = lseek(fd, offset, 0);
		if(r < 0)
			error(strerror(errno));
		uif->offset = offset;
	}

	n = write(fd, va, n);
	if(n < 0)
		error(strerror(errno));

	uif->offset += n;
	qunlock(&uif->oq);
	poperror();

	return n;
}

void
fsremove(Chan *c)
{
	int n;
	char path[MAXPATH];

	fspath(c->path, 0, path);
	if(c->qid.path & CHDIR)
		n = rmdir(path);
	else
		n = remove(path);
	if(n < 0)
		error(strerror(errno));
}

void
fswchk(char *path)
{
	struct stat stbuf;

	if(stat(path, &stbuf) < 0)
		error(strerror(errno));

	if(stbuf.st_uid == up->uid)
		stbuf.st_mode >>= 6;
	else
	if(stbuf.st_gid == up->gid)
		stbuf.st_mode >>= 3;

	if(stbuf.st_mode & S_IWRITE)
		return;

	error(Eperm);
}

void
fswstat(Chan *c, char *buf)
{
	Dir d;
	Path *ph;
	struct stat stbuf;
	char old[MAXPATH], new[MAXPATH], dir[MAXPATH];
	Ufsinfo *uif;

	convM2D(buf, &d);
	
	fspath(c->path, 0, old);
	if(stat(old, &stbuf) < 0)
		error(strerror(errno));

	uif = c->u.aux;

	if(uif->uid != stbuf.st_uid)
		error(Eowner);

	if(strcmp(d.name, c->path->elem) != 0) {
		fspath(c->path->parent, 0, dir);
		fswchk(dir);		
		fspath(c->path, 0, old);
		ph = ptenter(&syspt, c->path->parent, d.name);
		fspath(ph, 0, new);
		if(rename(old, new) < 0)
			error(strerror(errno));

		refdec(&c->path->r);
		c->path = ph;
	}

	fspath(c->path, 0, old);
	if((int)(d.mode&0777) != (int)(stbuf.st_mode&0777)) {
		if(chmod(old, d.mode&0777) < 0)
			error(strerror(errno));
		uif->mode &= ~0777;
		uif->mode |= d.mode&0777;
	}
/*
	p = name2pass(gid, d.gid);
	if(p == 0)
		error(Eunknown);

	if(p->id != stbuf.st_gid) {
		if(chown(old, stbuf.st_uid, p->id) < 0)
			error(strerror(errno));

		uif->gid = p->id;
	}
*/
}

static Qid
fsqid(char *p, struct stat *st)
{
	Qid q;
	int dev;
	ulong h;
	static int nqdev;
	static uchar *qdev;

	if(qdev == 0)
		qdev = mallocz(65536U);

	q.path = 0;
	if((st->st_mode&S_IFMT) ==  S_IFDIR)
		q.path = CHDIR;

	dev = st->st_dev & 0xFFFFUL;
	if(qdev[dev] == 0) {
		if(++nqdev >= 128)
			error("too many devices");
		qdev[dev] = nqdev;
	}
	h = 0;
	while(*p != '\0')
		h += *p++ * 13;
	
	q.path |= qdev[dev]<<24;
	q.path |= h & 0x00FFFFFFUL;
	q.vers = st->st_mtime;

	return q;
}

static void
fspath(Path *p, char *ext, char *path)
{
	int i, n;
	char *comp[MAXCOMP];

	strcpy(path, base);
	i = strlen(base);

	if(p == 0) {
		if(ext) {
			path[i++] = '/';
			strcpy(path+i, ext);
		}
		return;
	}

	n = 0;
	if(ext)
		comp[n++] = ext;
	while(p->parent) {
		comp[n++] = p->elem;
		p = p->parent;
	}

	while(n) {
		path[i++] = '/';
		strcpy(path+i, comp[--n]);
		i += strlen(comp[n]);
	}
	path[i] = '\0';
}

static void
fsperm(Chan *c, int mask)
{
	int m;
	Ufsinfo *uif;

	uif = c->u.aux;

	m = uif->mode;
/*
	print("fsperm: %o %o uuid %d ugid %d cuid %d cgid %d\n",
		m, mask, up->uid, up->gid, uif->uid, uif->gid);
*/
	if(uif->uid == up->uid)
		m >>= 6;
	else
	if(uif->gid == up->gid)
		m >>= 3;

	m &= mask;
	if(m == 0)
		error(Eperm);
}

static int
isdots(char *name)
{
	if(name[0] != '.')
		return 0;
	if(name[1] == '\0')
		return 1;
	if(name[1] != '.')
		return 0;
	if(name[2] == '\0')
		return 1;
	return 0;
}

static int
p9readdir(char *name, DIR *d)
{
	struct dirent *de;
	
	de = readdir(d);
	if(de == NULL)
		return 0;
		
	strcpy(name, de->d_name);
	return 1;
}

static ulong
fsdirread(Chan *c, uchar *va, int count, ulong offset)
{
	int i;
	Dir d;
	long n;
	char de[NAME_MAX];
	struct stat stbuf;
	char path[MAXPATH], dirpath[MAXPATH];
	Ufsinfo *uif;

	count = (count/DIRLEN)*DIRLEN;

	i = 0;
	uif = c->u.aux;

	errno = 0;
	if(uif->offset != offset) {
		uif->offset = offset;  /* sync offset */
		rewinddir(uif->dir);
		for(n=0; n<(int)offset; ) {
			if(!p9readdir(de, uif->dir))
				break;
			if(de[0]==0 || isdots(de))
				continue;
			n += DIRLEN;
		}
	}

	fspath(c->path, 0, dirpath);

	while(i < count) {
		if(!p9readdir(de, uif->dir))
			break;

		if(de[0]==0 || isdots(de))
			continue;

		strncpy(d.name, de, NAMELEN-1);
		d.name[NAMELEN-1] = 0;
		sprint(path, "%s/%s", dirpath, de);
		memset(&stbuf, 0, sizeof stbuf);

		if(stat(path, &stbuf) < 0) {
			fprint(2, "dir: bad path %s\n", path);
			/* but continue... probably a bad symlink */
		}

		strncpy(d.uid, "unknown", NAMELEN);
		strncpy(d.gid, "unknown", NAMELEN);
		d.qid = fsqid(path, &stbuf);
		d.mode = (d.qid.path&CHDIR)|(stbuf.st_mode&0777);
		d.atime = stbuf.st_atime;
		d.mtime = stbuf.st_mtime;
		d.length = stbuf.st_size;
		d.type = 'U';
		d.dev = c->dev;
		convD2M(&d, (char*)va+i);
		i += DIRLEN;
	}
	return i;
}

static int
fsomode(int m)
{
	switch(m) {
	case 0:			/* OREAD */
	case 3:			/* OEXEC */
		return 0;
	case 1:			/* OWRITE */
		return 1;
	case 2:			/* ORDWR */
		return 2;
	}
	error(Ebadarg);
	return 0;
}

