#include	"u.h"
#include	<stdio.h> /* for remove, rename */
#include	<pwd.h>
#include	<grp.h>	/* going to regret this - getgrgid is a stack smasher */
#include	<sys/socket.h>
#include	<sys/un.h>

#if defined(__FreeBSD__)
#include	<sys/disk.h>
#include	<sys/disklabel.h>
#include	<sys/ioctl.h>
#endif

#if defined(__APPLE__)
#include	<sys/disk.h>
#endif

#if defined(__linux__)
#include	<linux/hdreg.h>
#include	<linux/fs.h>
#include	<sys/ioctl.h>
#endif

#include	"lib.h"
#include	"mem.h"
#include	"dat.h"
#include	"fns.h"
#include	"error.h"

enum
{
	Trace = 0,
	FsChar = 'Z',
};

extern char *canopen;
extern Path *addelem(Path*, char*, Chan*);
static char *uidtoname(int);
static char *gidtoname(int);
static int nametouid(char*);
static int nametogid(char*);

static vlong disksize(int, struct stat*);

typedef struct UnixFd UnixFd;
struct UnixFd
{
	int	fd;
	int	issocket;
	DIR*	dir;
	vlong	diroffset;
	QLock	dirlock;
	struct dirent *nextde;
	Path	*path;
};

void
oserrstr(void)
{
	if (errno == EINTR)
		kstrcpy(up->errstr, Eintr, ERRMAX);
	else
		kstrcpy(up->errstr, strerror(errno), ERRMAX);
}

void
oserror(void)
{
	if (errno == EINTR)
		error(Eintr);
	else
		error(strerror(errno));
}

static Qid
fsqid(struct stat *st)
{
	Qid q;
	int dev;
	static int nqdev;
	static uchar *qdev;

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

	q.type = 0;
	if((st->st_mode&S_IFMT) ==  S_IFDIR)
		q.type = QTDIR;

	dev = st->st_dev & 0xFFFFUL;
	if(qdev[dev] == 0)
		qdev[dev] = ++nqdev;
	
	q.path = (vlong)qdev[dev]<<48;
	q.path ^= st->st_ino;
	q.vers = st->st_mtime;
	
	return q;
}

static Chan*
fsattach(char *spec)
{
	struct stat st;
	Chan *c;
	UnixFd *ufd;
	int dev;
	
	dev = 1;
	if(spec && spec[0]){
		snprint(up->genbuf, sizeof up->genbuf, "no file system #%C%s", FsChar, spec);
		error(up->genbuf);
	}

	if(stat("/", &st) < 0)
		oserror();

	c = devattach(FsChar, 0);
	ufd = mallocz(sizeof(UnixFd), 1);
	ufd->path = newpath("/");
	ufd->fd = -1;

	c->aux = ufd;
	c->dev = dev;
	c->qid = fsqid(&st);
	
	if(Trace)
		print("fsattach /\n");

	return c;
}

static Chan*
fsclone(Chan *c, Chan *nc)
{
	UnixFd *ufd;
	
	ufd = mallocz(sizeof(UnixFd), 1);
	*ufd = *(UnixFd*)c->aux;
	if(ufd->path)
		incref(&ufd->path->ref);
	ufd->fd = -1;
	nc->aux = ufd;
	return nc;
}

static char*
lastelem(char *s)
{
	char *t;

	if(s[0] == '/' && s[1] == 0)
		return s;
	t = strrchr(s, '/');
	if(t == nil)
		return s;
	return t+1;
}

static char*
fspath(Chan *c, char *suffix)
{
	char *s, *t;
	int len;
	UnixFd *ufd;
	
	ufd = c->aux;
	s = ufd->path->s;
	len = strlen(s)+1;
	if(suffix)
		len += 1+strlen(suffix);
	t = smalloc(len);
	strcpy(t, s);
	if(suffix){
		if(s[strlen(s)-1] != '/')
			strcat(t, "/");
		strcat(t, suffix);
	}
	return t;
}

static int
fswalk1(Chan *c, char *name)
{
	struct stat st;
	char *path;
	UnixFd *ufd;
	
	ufd = c->aux;
	if(strcmp(name, "..") == 0 && strcmp(ufd->path->s, "/") == 0)
		return 0;
	
	path = fspath(c, name);
	if(stat(path, &st) < 0){
		if(Trace)
			print("fswalk1 %s (%s)\n", path, strerror(errno));	
		free(path);
		return -1;
	}
	if(Trace)
		print("fswalk1 %s\n", path);	
	free(path);
	
	c->qid = fsqid(&st);
	return 0;
}

static void
replacepath(Chan *c, Path *p)
{
	UnixFd *ufd;
	
	ufd = c->aux;
	incref(&p->ref);
	pathclose(ufd->path);
	ufd->path = p;
}

static Walkqid*
fswalk(Chan *c, Chan *nc, char **name, int nname)
{
	int i;
	Path *path;
	Walkqid *wq;
	UnixFd *ufd;

	if(nc != nil)
		panic("fswalk: nc != nil");
	wq = smalloc(sizeof(Walkqid)+(nname-1)*sizeof(Qid));
	nc = devclone(c);
	fsclone(c, nc);
	ufd = c->aux;
	path = ufd->path;
	incref(&path->ref);

	wq->clone = nc;
	for(i=0; i<nname; i++){
		ufd = nc->aux;
		replacepath(nc, path);
		if(fswalk1(nc, name[i]) < 0){
			if(i == 0){
				pathclose(path);
				cclose(nc);
				free(wq);
				error(Enonexist);
			}
			break;
		}
		path = addelem(path, name[i], nil);
		wq->qid[i] = nc->qid;
	}
	replacepath(nc, path);
	pathclose(path);
	if(i != nname){
		cclose(nc);
		wq->clone = nil;
	}
	wq->nqid = i;
	return wq;
}

static int
fsdirstat(char *path, int dev, Dir *d)
{
	int fd;
	struct stat st;
	
	if(stat(path, &st) < 0 && lstat(path, &st) < 0)
		return -1;

	d->name = lastelem(path);
	d->uid = uidtoname(st.st_uid);
	d->gid = gidtoname(st.st_gid);
	d->muid = "";
	d->qid = fsqid(&st);
	d->mode = (d->qid.type<<24) | (st.st_mode&0777);
	d->atime = st.st_atime;
	d->mtime = st.st_mtime;
	d->length = st.st_size;
	if(S_ISBLK(st.st_mode) && (fd = open(path, O_RDONLY)) >= 0){
		d->length = disksize(fd, &st);
		close(fd);
	}
	
	// devmnt leaves 1-9 unused so that we can steal them.
	// it is easier for all involved if #Z shows M as the file type instead of Z.
	// dev is c->dev, either 1 (#Z) or 2 (#Zplan9).
	d->type = 'M';
	d->dev = dev;
	return 0;
}

static int
fsstat(Chan *c, uchar *buf, int n)
{
	Dir d;
	char *path;
	UnixFd *ufd;

	ufd = c->aux;
	if(Trace)
		print("fsstat %s\n", ufd->path->s);

	if(n < BIT16SZ)
		error(Eshortstat);

	path = fspath(c, nil);
	if(fsdirstat(path, c->dev, &d) < 0){
		free(path);
		oserror();
	}
	if(strcmp(ufd->path->s, "/") == 0)
		d.name = "/";
	n = convD2M(&d, buf, n);
	free(path);
	return n;
}

static int
opensocket(UnixFd *ufd, char *path)
{
	int fd;
	struct stat st;
	struct sockaddr_un su;
	
	if(stat(path, &st) < 0)
		return -1;
	if(!S_ISSOCK(st.st_mode))
		return -1;
	memset(&su, 0, sizeof su);
	su.sun_family = AF_UNIX;
	if(strlen(path)+1 > sizeof su.sun_path){
		errno = ENAMETOOLONG;
		return -1;
	}
	strcpy(su.sun_path, path);
	if((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
		return -1;
	if(connect(fd, (struct sockaddr*)&su, sizeof su) < 0){
		close(fd);
		return -1;
	}
	ufd->fd = fd;
	ufd->issocket = 1;
	return 0;
}		

static Chan*
fsopen(Chan *c, int mode)
{
	char *path;
	int m;
	UnixFd *ufd;
	
	ufd = c->aux;
	if(Trace)
		print("fsopen %s %#x\n", ufd->path->s, mode);

	/* protect files whose path does not begin with allowed */
	if(strncmp(ufd->path->s, canopen, strlen(canopen)) != 0)
		error(Eperm);

	if(mode & ~(OTRUNC|ORCLOSE|3))
		error(Ebadarg);

	if((c->qid.type & QTDIR) && mode != OREAD)
		error(Eperm);
	
	if((c->qid.type&QTDIR) && mode != OREAD)
		error(Eperm);

	c->mode = openmode(mode);
	path = fspath(c, nil);
	if(c->qid.type & QTDIR){
		ufd->dir = opendir(path);
		if(ufd->dir == nil){
			free(path);
			oserror();
		}
		ufd->diroffset = 0;
		ufd->nextde = nil;
	}else{
		m = mode & 3;
		if(m == OEXEC)
			m = OREAD;
		if(mode & OTRUNC)
			m |= O_TRUNC;
		if((ufd->fd = open(path, m)) < 0 && opensocket(ufd, path) < 0){
			free(path);
			oserror();
		}
	}
	free(path);
	c->flag |= COPEN;
	return c;
}

static void
fscreate(Chan *c, char *name, int mode, ulong perm)
{
	char *path, *path0;
	int fd, mm;
	UnixFd *ufd;
	struct stat st;

	ufd = c->aux;
	if(Trace)
		print("fscreate %s %#x %#o\n", ufd->path->s, mode, perm);

	if(!(c->qid.type & QTDIR))
		error(Enotdir);

	if(mode & ~(OTRUNC|ORCLOSE|3))
		error(Ebadarg);

	if(perm & ~(DMDIR|0777))
		error(Ebadarg);

	path0 = fspath(c, nil);
	path = fspath(c, name);
	if(waserror()){
		free(path);
		free(path0);
		nexterror();
	}
	
	if(stat(path0, &st) < 0)
		oserror();

	if(perm & DMDIR){
		if(mode != OREAD)
			error(Eperm);
		/* have to do the minimum 0400 so we can open it */
		if(mkdir(path, (0400 | perm) & 0777) < 0)
			oserror();
		if((fd = open(path, 0)) < 0)
			oserror();
		fchown(fd, -1, st.st_gid);
		if(fstat(fd, &st) < 0){
			close(fd);
			oserror();
		}
		if((ufd->dir = opendir(path)) == nil) {
			/* arguably we should set the mode here too 
 			 * but it's hard to see that this case 
 			 * will ever happen 
 			 */
			close(fd);
			oserror();
		}
		// Be like Plan 9 file servers: inherit mode bits 
		// and group from parent.
		fchmod(fd, perm & st.st_mode & 0777);
		close(fd);
		ufd->diroffset = 0;
		ufd->nextde = nil;
	}else{
		mm = mode & 3;
		if(mode & OTRUNC)
			mm |= O_TRUNC;
		if((fd = open(path, mm|O_CREAT|O_EXCL, 0666)) < 0)
			oserror();
		// Be like Plan 9 file servers: inherit mode bits 
		// and group from parent.
		fchmod(fd, perm & st.st_mode & 0777);
		fchown(fd, -1, st.st_gid);
		if(fstat(fd, &st) < 0){
			close(fd);
			oserror();
		}
		ufd->fd = fd;
	}
	free(path);
	free(path0);
	poperror();
	
	ufd->path = addelem(ufd->path, name, nil);
	c->qid = fsqid(&st);
	c->offset = 0;
	c->flag |= COPEN;
	c->mode = openmode(mode);
}

static void
fsclose(Chan *c)
{
	UnixFd *ufd;
	char *path;
	
	ufd = c->aux;
	if(Trace)
		print("fsclose %s\n", ufd->path->s);

	if(c->flag & COPEN) {
		if(c->flag & CRCLOSE) {
			path = fspath(c, nil);
			unlink(path);
			free(path);
		}
		if(c->qid.type & QTDIR)
			closedir(ufd->dir);
		else
			close(ufd->fd);
	}
	if(ufd->path)
		pathclose(ufd->path);
	free(ufd);
}

static long fsdirread(Chan*, uchar*, long, vlong);

static long
fsread(Chan *c, void *va, long n, vlong offset)
{
	int r;
	UnixFd *ufd;

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

	ufd = c->aux;
	if(ufd->issocket)
		r = read(ufd->fd, va, n);
	else
		r = pread(ufd->fd, va, n, offset);
	if(r < 0)
		oserror();
	return r;
}

static long
fswrite(Chan *c, void *va, long n, vlong offset)
{
	int r;
	UnixFd *ufd;

	ufd = c->aux;
	if(ufd->issocket)
		r = write(ufd->fd, va, n);
	else
		r = pwrite(ufd->fd, va, n, offset);
	if(r < 0)
		oserror();
	return r;
}

static int
fswstat(Chan *c, uchar *buf, int n)
{
	char *elem, *path, *npath, *strs, *t;
	int nn;
	Dir d;
	UnixFd *ufd;

	if(n < 2)
		error(Ebadstat);

	nn = GBIT16((uchar*)buf);
	strs = smalloc(nn);
	if(convM2D(buf, n, &d, strs) != n){
		free(strs);
		error(Ebadstat);
	}
	
	path = fspath(c, nil);
	if(waserror()){
		free(path);
		free(strs);
		nexterror();
	}
	
	if(d.muid[0])
		error("cannot change muid");

	if(d.uid[0] || d.gid[0]){
		int uid, gid;
		
		uid = -1;
		gid = -1;
		if(d.uid[0] && (uid = nametouid(d.uid)) < 0)
			error("unknown uid");
		if(d.gid[0] && (gid = nametogid(d.gid)) < 0)
			error("unknown gid");
		if(chown(path, uid, gid) < 0)
			oserror();
	}
	
	ufd = c->aux;
	elem = lastelem(path);
	if(d.name[0] && strcmp(d.name, elem) != 0){
		if(strchr(d.name, '/'))
			error(Ebadarg);
		npath = smalloc(strlen(path)+strlen(d.name)+1);
		strcpy(npath, path);
		t = strrchr(npath, '/');
		strcpy(t+1, d.name);
		if(rename(path, npath) < 0){
			free(npath);
			oserror();
		}
		free(npath);
	}
	
	if(~d.mode != 0 && chmod(path, d.mode&0777) < 0)
		oserror();

	// TODO: Code to change uid, gid.
	
	poperror();
	return n;
}

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 long
fsdirread(Chan *c, uchar *va, long count, vlong offset)
{
	char *path;
	int n, total;
	struct dirent *de;
	UnixFd *ufd;
	Dir d;

	ufd = c->aux;
	qlock(&ufd->dirlock);
	if(waserror()){
		qunlock(&ufd->dirlock);
		nexterror();
	}

	if(ufd->diroffset != offset){
		if(offset != 0)
			error(Ebadarg);
		ufd->diroffset = 0;
		ufd->nextde = nil;
		rewinddir(ufd->dir);
	}

	total = 0;
	while(total+BIT16SZ < count) {
		if(ufd->nextde){
			de = ufd->nextde;
			ufd->nextde = nil;
		}
		else if((de = readdir(ufd->dir)) == nil)
			break;
		if(isdots(de->d_name))
			continue;
		path = fspath(c, de->d_name);
		if(fsdirstat(path, c->dev, &d) < 0){
			free(path);
			continue;
		}
		n = convD2M(&d, (uchar*)va+total, count-total);
		free(path);
		if(n == BIT16SZ){
			ufd->nextde = de;
			break;
		}
		total += n;
	}
	ufd->diroffset += total;
	qunlock(&ufd->dirlock);
	poperror();
	return total;
}

static void
fsremove(Chan *c)
{
	char *path;
	UnixFd *ufd;

	ufd = c->aux;
	if(Trace)
		print("fsremove %s\n", ufd->path->s);

	path = fspath(c, nil);
	if(waserror()){
		free(path);
		nexterror();
	}
	if(c->qid.type & QTDIR){
		if(rmdir(path) < 0)
			oserror();
	}else{
		if(remove(path) < 0)
			oserror();
	}
	free(path);
	poperror();
}

Dev fsdevtab = {
	FsChar,
	"fs",

	devreset,
	devinit,
	devshutdown,
	fsattach,
	fswalk,
	fsstat,
	fsopen,
	fscreate,
	fsclose,
	fsread,
	devbread,
	fswrite,
	devbwrite,
	fsremove,
	fswstat,
};


/* Uid management code adapted from u9fs */

/*
 * we keep a table by numeric id.  by name lookups happen infrequently
 * while by-number lookups happen once for every directory entry read
 * and every stat request.
 */
typedef struct User User;
struct User {
	int id;
	gid_t defaultgid;
	char *name;
	User *next;
};


static User *utab[64];
static User *gtab[64];

static User*
adduser(struct passwd *p)
{
	User *u;

	u = smalloc(sizeof(*u));
	u->id = p->pw_uid;
	kstrdup(&u->name, p->pw_name);
	u->next = utab[p->pw_uid%nelem(utab)];
	u->defaultgid = p->pw_gid;
	utab[p->pw_uid%nelem(utab)] = u;
	return u;
}

static User*
addgroup(struct group *g)
{
	User *u;

	u = smalloc(sizeof(*u));
	u->id = g->gr_gid;
	kstrdup(&u->name, g->gr_name);
	u->next = gtab[g->gr_gid%nelem(gtab)];
	gtab[g->gr_gid%nelem(gtab)] = u;
	return u;
}

static User*
uname2user(char *name)
{
	int i;
	User *u;
	struct passwd pwd, *result;
	char buf[512];

	for(i=0; i<nelem(utab); i++)
		for(u=utab[i]; u; u=u->next)
			if(strcmp(u->name, name) == 0)
				return u;

	getpwnam_r(name, &pwd, buf, sizeof(buf), &result);
	if(result == NULL)
		return nil;
	return adduser(result);
}

static User*
uid2user(int id)
{
	User *u;
	struct passwd pwd, *result;
	char buf[512];

	for(u=utab[id%nelem(utab)]; u; u=u->next)
		if(u->id == id)
			return u;

	getpwuid_r(id, &pwd, buf, sizeof(buf), &result);
	if(result == NULL)
		return nil;
	return adduser(result);
}

static User*
gname2user(char *name)
{
	int i;
	User *u;
	struct group grp, *result;
	char buf[512];

	for(i=0; i<nelem(gtab); i++)
		for(u=gtab[i]; u; u=u->next)
			if(strcmp(u->name, name) == 0)
				return u;

	getgrnam_r(name, &grp, buf, sizeof(buf), &result);
	if(result == NULL)
		return nil;
	return addgroup(result);
}

static User*
gid2user(int id)
{
	User *u;
	struct group grp, *result;
	char buf[512];

	for(u=gtab[id%nelem(gtab)]; u; u=u->next)
		if(u->id == id)
			return u;

	getgrgid_r(id, &grp, buf, sizeof(buf), &result);
	if(result == NULL)
		return nil;
	return addgroup(result);
}

static char*
uidtoname(int uid)
{
	User *u;
	
	u = uid2user(uid);
	if(u == nil)
		return "?";
	return u->name;
}

static char*
gidtoname(int gid)
{
	User *u;
	
	u = gid2user(gid);
	if(u == nil)
		return "?";
	return u->name;
}

static int
nametouid(char *name)
{
	User *u;
	
	u = uname2user(name);
	if(u == nil)
		return -1;
	return u->id;
}

static int
nametogid(char *name)
{
	User *u;
	
	u = gname2user(name);
	if(u == nil)
		return -1;
	return u->id;
}

#if defined(__linux__)

static vlong
disksize(int fd, struct stat *st)
{
	uvlong u64;
	long l;
	struct hd_geometry geo;
	
	memset(&geo, 0, sizeof geo);
	l = 0;
	u64 = 0;
#ifdef BLKGETSIZE64
	if(ioctl(fd, BLKGETSIZE64, &u64) >= 0)
		return u64;
#endif
	if(ioctl(fd, BLKGETSIZE, &l) >= 0)
		return l*512;
	if(ioctl(fd, HDIO_GETGEO, &geo) >= 0)
		return (vlong)geo.heads*geo.sectors*geo.cylinders*512;
	return 0;
}

#elif defined(__FreeBSD__) && defined(DIOCGMEDIASIZE)

static vlong
disksize(int fd, struct stat *st)
{
	off_t mediasize;
	
	if(ioctl(fd, DIOCGMEDIASIZE, &mediasize) >= 0)
		return mediasize;
	return 0;
}

#elif defined(__APPLE__)

static vlong
disksize(int fd, struct stat *st)
{
	uvlong bc;
	unsigned int bs;

	bs = 0;
	bc = 0;
	ioctl(fd, DKIOCGETBLOCKSIZE, &bs);
	ioctl(fd, DKIOCGETBLOCKCOUNT, &bc);
	if(bs >0 && bc > 0)
		return bc*bs;
	return 0;
}

#else

static vlong
disksize(int fd, struct stat *st)
{
	return 0;
}

#endif
