#include <u.h>
#include <libc.h>
#define NODEFINE
#include <9pm/u.h>
#include <9pm/libc.h>
#include <9pm/fcall.h>
#include <9pm/ns.h>

typedef struct Uchan	Uchan;
struct Uchan
{
	int	sysfd;
	Cname*	syspath;
};

/*
 * Bug: should perturb the qids to include dev, type.
 */
static PmQid
fsqid(Dir *d)
{
	return *(PmQid*)&d->qid;
}

static char*
fspath(Chan *c, char *elem)
{
	char *s;
	Uchan *uc;

	uc = c->aux;
	s = pm_smalloc(strlen(uc->syspath->s)+1+strlen(elem)+10);
	strcpy(s, uc->syspath->s);
	if(s[strlen(s)-1] != '/')
		strcat(s, "/");
	strcat(s, elem);
	return s;
}

static Chan*
pm_sysfd2chan(int sysfd, int mode, PmQid qid, char *buf)
{
	Chan *c;
	Uchan *uc;

	c = pm_newchan();
	c->name = pm_newcname(buf);
	c->mode = mode;
	c->flag |= COPEN;
	c->type = pm_devno('U', 1);
	c->qid = qid;
	uc = pm_smalloc(sizeof *uc);
	uc->syspath = pm_newcname(buf);
	uc->sysfd = sysfd;
	c->aux = uc;
	return c;
}

static Chan*
syspath2chan(PmQid qid, char *buf)
{
	Chan *c;
	Uchan *uc;

	c = pm_newchan();
	c->name = pm_newcname(buf);
	c->type = pm_devno('U', 1);
	c->qid = qid;
	uc = pm_smalloc(sizeof *uc);
	uc->syspath = pm_newcname(buf);
	c->aux = uc;
	return c;
}

static void
fsreset(void)
{
	char buf[128];
	int fd;
	Chan *c;
	Dir *d;
	Proc *p;
	PmQid q;

	p = pm_getproc();
	for(fd=0; fd<3; fd++){
		if(fd2path(fd, buf, sizeof buf) < 0)
			continue;
		if((d = dirfstat(fd)) == nil)
			memset(&q, 0, sizeof q);
		else{
			q = *(PmQid*)&d->qid;
			free(d);
		}
		c = pm_sysfd2chan(fd, fd==0?OREAD:OWRITE, q, buf);
		if(pm_newfdx(c, fd) < 0){
			fprint(2, "pm_newfdx %d: %s\n", fd, p->err);
			pm_panic("pm_fdinit");
		}
	}

	fd = open(".", OREAD);
	if(fd < 0)
		strcpy(buf, ".");
	else
		fd2path(fd, buf, sizeof buf);
	if((d = dirfstat(fd)) == nil)
		memset(&q, 0, sizeof q);
	else{
		q = *(PmQid*)&d->qid;
		free(d);
	}
	if(fd >= 0)
		close(fd);

	p->dot = syspath2chan(q, buf);
}

static Chan*
fsattach(char *spec)
{
	Chan *c;
	Dir *d;
	PmQid q;
	Uchan *uc;
	static Lock lk;
	static int devno;

	if(spec && spec[0])
		pm_error(PmEbadspec);

	if((d = dirstat(pm_conf.rootdir)) == nil){
		pm_oserror();
		pm_nexterror();
	}
	q = fsqid(d);
	free(d);
	c = pm_devattach('U', "");
	c->qid = q;
	uc = pm_smalloc(sizeof *uc);
	uc->sysfd = -1;
	uc->syspath = pm_newcname(pm_conf.rootdir);
	c->aux = uc;
	lock(&lk);
	c->dev = devno++;
	unlock(&lk);
	return c;
}

Walkqid*
fswalk(Chan *c, Chan *nc, char **name, int nname)
{
	char *path;
	int i;
	int volatile alloc;
	Cname *p;
	Dir *d;
	Uchan *uc;
	Walkqid *wq;

	if(nname > MAXWELEM)
		pm_error("devfs: too many name elements");
	alloc = 0;
	wq = pm_smalloc(sizeof(Walkqid)+(nname-1)*sizeof(Qid));
	if(pm_waserror()){
		if(alloc && wq->clone!=nil)
			pm_cclose(wq->clone);
		pm_free(wq);
		return nil;
	}

	if(nc == nil){
		nc = pm_devclone(c);
		uc = pm_smalloc(sizeof *uc);
		*uc = *(Uchan*)c->aux;
		nc->aux = uc;
		pm_incref(&uc->syspath->ref);
		alloc = 1;
	}
	wq->clone = nc;
	uc = nc->aux;

	for(i = 0; i < nname; i++){
		path = fspath(nc, name[i]);
		if((d = dirstat(path)) == nil){
			pm_free(path);
			if(alloc)
				pm_cclose(nc);
			wq->clone = nil;
			if(i == 0) {
				pm_free(wq);
				pm_poperror();
				return nil;
			}
			break;
		}
		wq->qid[i] = fsqid(d);
		free(d);
		pm_free(path);

		p = pm_addelem(uc->syspath, name[i]);
		if(strcmp(name[i], "..") == 0)
			pm_cleancname(p);
		uc->syspath = p;
	}
	pm_poperror();

	if(i > 0 && wq->clone != nil)
		wq->clone->qid = wq->qid[i-1];
	wq->nqid = i;
	return wq;
}

static Chan*
fsopen(Chan *c, int mode)
{
	Dir *d;
	Uchan *uc;

	uc = c->aux;
	if((uc->sysfd = open(uc->syspath->s, mode)) < 0){
		pm_oserror();
		pm_nexterror();
	}
	if((d = dirfstat(uc->sysfd)) == nil){
		pm_oserror();
		close(uc->sysfd);
		pm_nexterror();
		uc->sysfd = -1;
	}
	c->mode = pm_openmode(mode);
	c->offset = 0;
	c->qid = fsqid(d);
	c->flag |= COPEN;
	free(d);
	return c;
}

static void
fscreate(Chan *c, char *name, int mode, ulong perm)
{
	char *path;
	Dir *d;
	Uchan *uc;

	uc = c->aux;
	path = fspath(c, name);
	if((uc->sysfd = create(path, mode, perm)) < 0){
		pm_free(path);
		pm_oserror();
		pm_nexterror();
	}
	pm_free(path);
	if((d = dirfstat(uc->sysfd)) == nil){
		pm_oserror();
		close(uc->sysfd);
		uc->sysfd = -1;
		pm_nexterror();
	}
	c->mode = pm_openmode(mode);
	c->offset = 0;
	c->qid = fsqid(d);
	c->flag |= COPEN;
	free(d);
}

static void fsremove(Chan*);

static void
fsclose(Chan *c)
{
	Uchan *uc;

	uc = c->aux;
	if(c->flag & COPEN){
		if(c->flag & CRCLOSE){
			fsremove(c);
			return;
		}
		close(uc->sysfd);
		uc->sysfd = -1;
	}
	pm_cnameclose(uc->syspath);
	pm_free(uc);
}

static long
fsread(Chan *c, void *a, long n, vlong o)
{
	Uchan *uc;

	uc = c->aux;
	n = pread(uc->sysfd, a, n, o);
	if(n < 0){
		pm_oserror();
		pm_nexterror();
	}
	if(c->qid.type&PM_QTDIR)
		pm_mntdirreadfix(a, n, c);
	return n;
}

static long
fswrite(Chan *c, void *a, long n, vlong o)
{
	Uchan *uc;

	uc = c->aux;
	n = pwrite(uc->sysfd, a, n, o);
	if(n < 0){
		pm_oserror();
		pm_nexterror();
	}
	if(c->qid.type&PM_QTDIR)
		pm_mntdirreadfix(a, n, c);
	return n;
}

static void
fsremove(Chan *c)
{
	int err;
	Uchan *uc;

	uc = c->aux;
	err = 0;
	if(remove(uc->syspath->s) < 0){
		pm_oserror();
		err = 1;
	}
	if(c->flag & COPEN){
		close(uc->sysfd);
		uc->sysfd = -1;
	}
	pm_cnameclose(uc->syspath);
	pm_free(uc);
	if(err)
		pm_nexterror();
	return;
}

static int
fsstat(Chan *c, uchar *buf, int n)
{
	Uchan *uc;

	uc = c->aux;
	if(uc->sysfd >= 0)
		n = fstat(uc->sysfd, buf, n);
	else
		n = stat(uc->syspath->s, buf, n);
	if(n < 0){
		pm_oserror();
		pm_nexterror();
	}
	return n;
}

static int
fswstat(Chan *c, uchar *edir, int nedir)
{
	Uchan *uc;

	uc = c->aux;
	if((nedir = fwstat(uc->sysfd, edir, nedir)) < 0){
		pm_oserror();
		pm_nexterror();
	}
	return nedir;
}

static void
fschdir(Chan *c)
{
	Uchan *uc;

	uc = c->aux;
	if(chdir(uc->syspath->s) < 0){
		pm_oserror();
		pm_nexterror();
	}
}

Dev devfs = 
{
	(Rune)'U',
	"fs",

	fsreset,
	fsattach,
	fswalk,
	fsstat,
	fsopen,
	fscreate,
	fsclose,
	fsread,
	pm_devbread,
	fswrite,
	pm_devbwrite,
	fsremove,
	fswstat,

	fschdir,
};
