#include <u.h>
#include <libc.h>
#include <auth.h>
#include <fcall.h>
#include <thread.h>
#include <9p.h>
#include <pool.h>

char Ebadoff[] = "bad file offset or count";
char Eexist[] = "file already exists";
char Enomem[] = "no memory";
char Eperm[] = "permission denied";
char Enotowner[] = "not owner";
char Elocked[] = "file is locked";

enum {
	Tdat	= 0xbabababa,	
	Tind	= 0xdadadada,

	ESIZE	= 64*1024,
};

#define MAXFSIZE ((0x7fffffffll/sizeof(Ram*))*ESIZE)

typedef struct Ram Ram;
struct Ram
{
	int	type;
	int 	size;
	Ram	**link;
	Ram	*ent[];
};

int private;

void*
ramalloc(ulong size)
{
	void *v;

	v = sbrk(size);
	if(v == (void*)-1)
		return nil;
	return v;
}

void
rammoved(void*, void *to)
{
	Ram **link, **elink, *x = to;

	*x->link = x;
	if(x->type != Tind)
		return;

	link = x->ent;
	for(elink = link + (x->size / sizeof(Ram*)); link < elink; link++)
		if((x = *link) != nil)
			x->link = link;
}

void
ramnolock(Pool*)
{
}

Pool rampool = {
        .name=          "ram",
        .maxsize=       800*1024*1024,
        .minarena=      4*1024,
        .quantum=       32,
        .alloc=         ramalloc,
	.move=		rammoved,
	.lock=		ramnolock,
	.unlock=	ramnolock,
        .flags=         0,
};

void
accessfile(File *f, int a)
{
	f->atime = time(0);
	if(a & AWRITE){
		f->mtime = f->atime;
		f->qid.vers++;
	}
}

void
fsread(Req *r)
{
	int o, n, i, count;
	vlong top, off;
	File *f;
	Ram *x;
	char *p;

	f = r->fid->file;
	off = r->ifcall.offset;
	count = r->ifcall.count;

	if(count == 0 || off >= f->length || f->aux == nil){
		r->ofcall.count = 0;
		respond(r, nil);
		return;
	}

	top = off + count;
	if(top > MAXFSIZE){
		respond(r, Ebadoff);
		return;
	}
		
	if(top > f->length){
		top = f->length;
		count = top - off;
	}
	p = (char*)r->ofcall.data;
	while(count > 0){
		i = off / ESIZE;
		o = off % ESIZE;

		x = (Ram*)f->aux;
		if(i < (x->size / sizeof(Ram*)))
			x = x->ent[i];
		else
			x = nil;
		if(x != nil && o < x->size){
			n = x->size - o;
			if(n > count)
				n = count;
			memmove(p, (char*)&x[1] + o, n);
		} else {
			n = ESIZE - o;
			if(n > count)
				n = count;
			memset(p, 0, n);
		}
		p += n;
		off += n;
		count -= n;
	}
	accessfile(f, AREAD);

	r->ofcall.count = p - (char*)r->ofcall.data;
	respond(r, nil);
}

void
fswrite(Req *r)
{
	int o, n, i, count;
	Ram *x, **link;
	vlong top, off;
	File *f;
	char *p;

	f = r->fid->file;
	off = r->ifcall.offset;
	count = r->ifcall.count;

	if(f->mode & DMAPPEND)
		off = f->length;

	if(count == 0){
		r->ofcall.count = 0;
		respond(r, nil);
		return;
	}

	top = off + count;
	if(top > MAXFSIZE){
		respond(r, Ebadoff);
		return;
	}

	n = ((top + ESIZE-1)/ESIZE) * sizeof(Ram*);
	x = (Ram*)f->aux;
	if(x == nil || x->size < n){
		x = poolrealloc(&rampool, x, sizeof(Ram) + n);		
		if(x == nil){
			respond(r, Enomem);
			return;
		}
		link = (Ram**)&f->aux;
		if(*link == nil){
			memset(x, 0, sizeof(Ram));
			x->type = Tind;
			x->link = link;
			*link = x;
		} else if(x != *link)
			rammoved(*link, x);
		memset((char*)&x[1] + x->size, 0, n - x->size);
		x->size = n;
	}

	p = (char*)r->ifcall.data;
	while(count > 0){
		i = off / ESIZE;
		o = off % ESIZE;

		n = ESIZE - o;
		if(n > count)
			n = count;

		x = ((Ram*)f->aux)->ent[i];
		if(x == nil || x->size < o+n){
			x = poolrealloc(&rampool, x, sizeof(Ram) + o+n);
			if(x == nil){
				respond(r, Enomem);
				return;
			}
			link = &((Ram*)f->aux)->ent[i];
			if(*link == nil){
				memset(x, 0, sizeof(Ram));
				x->type = Tdat;
			}
			if(o > x->size)
				memset((char*)&x[1] + x->size, 0, o - x->size);
			x->size = o + n;
			x->link = link;
			*link = x;
		}

		memmove((char*)&x[1] + o, p, n);
		p += n;
		off += n;
		count -= n;
	}

	if(top > f->length)
		f->length = top;
	accessfile(f, AWRITE);

	r->ofcall.count = p - (char*)r->ifcall.data;
	respond(r, nil);
}

void
truncfile(File *f, vlong l)
{
	int i, o, n;
	Ram *x;

	x = (Ram*)f->aux;
	if(x != nil){
		n = x->size / sizeof(Ram*);
		i = l / ESIZE;
		if(i < n){
			o = l % ESIZE;
			if(o != 0 && x->ent[i] != nil){
				if(o < x->ent[i]->size)
					x->ent[i]->size = o;
				i++;
			}
			while(i < n){
				if(x->ent[i] != nil){
					poolfree(&rampool, x->ent[i]);
					x->ent[i] = nil;
				}
				i++;
			}
		}
		if(l == 0){
			poolfree(&rampool, (Ram*)f->aux);
			f->aux = nil;
		}
	}
	f->length = l;
}

void
fswstat(Req *r)
{
	File *f, *w;
	char *u;

	f = r->fid->file;
	u = r->fid->uid;

	/*
	 * To change length, must have write permission on file.
	 */
	if(r->d.length != ~0 && r->d.length != f->length){
		if(r->d.length > MAXFSIZE){
			respond(r, Ebadoff);
			return;
		}
	 	if(!hasperm(f, u, AWRITE) || (f->mode & DMDIR) != 0)
			goto Perm;
	}

	/*
	 * To change name, must have write permission in parent.
	 */
	if(r->d.name[0] != '\0' && strcmp(r->d.name, f->name) != 0){
		if((w = f->parent) == nil)
			goto Perm;
		incref(w);
	 	if(!hasperm(w, u, AWRITE)){
			closefile(w);
			goto Perm;
		}
		if((w = walkfile(w, r->d.name)) != nil){
			closefile(w);
			respond(r, Eexist);
			return;
		}
	}

	/*
	 * To change mode, must be owner or group leader.
	 * Because of lack of users file, leader=>group itself.
	 */
	if(r->d.mode != ~0 && f->mode != r->d.mode){
		if(strcmp(u, f->uid) != 0)
		if(strcmp(u, f->gid) != 0){
			respond(r, Enotowner);
			return;
		}
	}

	/*
	 * To change group, must be owner and member of new group,
	 * or leader of current group and leader of new group.
	 * Second case cannot happen, but we check anyway.
	 */
	while(r->d.gid[0] != '\0' && strcmp(f->gid, r->d.gid) != 0){
		if(strcmp(u, f->uid) == 0)
			break;
		if(strcmp(u, f->gid) == 0)
		if(strcmp(u, r->d.gid) == 0)
			break;
		respond(r, Enotowner);
		return;
	}

	if(r->d.mode != ~0){
		f->mode = r->d.mode;
		f->qid.type = f->mode >> 24;
	}
	if(r->d.name[0] != '\0'){
		free(f->name);
		f->name = estrdup9p(r->d.name);
	}
	if(r->d.length != ~0 && r->d.length != f->length)
		truncfile(f, r->d.length);

	accessfile(f, AWRITE);
	if(r->d.mtime != ~0){
		f->mtime = r->d.mtime;
	}

	respond(r, nil);
	return;

Perm:
	respond(r, Eperm);
}

void
fscreate(Req *r)
{
	File *f;
	int p;

	f = r->fid->file;
	p = r->ifcall.perm;
	if((p & DMDIR) != 0)
		p = (p & ~0777) | ((p & f->mode) & 0777);
	else
		p = (p & ~0666) | ((p & f->mode) & 0666);
	if((f = createfile(f, r->ifcall.name, r->fid->uid, p, nil)) == nil){
		responderror(r);
		return;
	}
	f->atime = f->mtime = time(0);
	f->aux = nil;
	r->fid->file = f;
	r->ofcall.qid = f->qid;
	respond(r, nil);
}

void
fsopen(Req *r)
{
	File *f;

	f = r->fid->file;
	if((f->mode & DMEXCL) != 0){
		if(f->ref > 2 && (long)((ulong)time(0)-(ulong)f->atime) < 300){
			respond(r, Elocked);
			return;
		}
	}
	if((f->mode & DMAPPEND) == 0 && (r->ifcall.mode & OTRUNC) != 0){
		truncfile(f, 0);
		accessfile(f, AWRITE);
	}
	respond(r, nil);
}

void
fsdestroyfid(Fid *fid)
{
	File *f;

	f = fid->file;
	if(fid->omode != -1 && (fid->omode & ORCLOSE) != 0 && f != nil && f->parent != nil)
		removefile(f);
}

void
fsdestroyfile(File *f)
{
	truncfile(f, 0);
}

void
fsstart(Srv *)
{
	char buf[40];
	int ctl;

	if(private){
		snprint(buf, sizeof buf, "/proc/%d/ctl", getpid());
		if((ctl = open(buf, OWRITE)) < 0)
			sysfatal("can't protect memory: %r");
		fprint(ctl, "noswap\n");
		fprint(ctl, "private\n");
		close(ctl);
	}
}

Srv fs = {
	.open=		fsopen,
	.read=		fsread,
	.write=		fswrite,
	.wstat=		fswstat,
	.create=	fscreate,
	.destroyfid=	fsdestroyfid,

	.start=		fsstart,
};

void
usage(void)
{
	fprint(2, "usage: %s [-Dipsubac] [-m mountpoint] [-S srvname]\n", argv0);
	exits("usage");
}

void
main(int argc, char **argv)
{
	char *srvname = nil;
	char *mtpt = "/tmp";
	int mountflags, stdio;

	fs.tree = alloctree(nil, nil, DMDIR|0777, fsdestroyfile);

	mountflags = stdio = 0;
	ARGBEGIN{
	case 'D':
		chatty9p++;
		break;
	case 's':
		srvname = "ramfs";
		mtpt = nil;
		break;
	case 'S':
		srvname = EARGF(usage());
		mtpt = nil;
		break;
	case 'm':
		mtpt = EARGF(usage());
		break;
	case 'i':
		stdio = 1;
		break;
	case 'p':
		private = 1;
		break;
	case 'u':
		rampool.maxsize = (uintptr)~0;
		break;
	case 'b':
		mountflags |= MBEFORE;
		break;
	case 'c':
		mountflags |= MCREATE;
		break;
	case 'a':
		mountflags |= MAFTER;
		break;
	default:
		usage();
	}ARGEND;
	if(argc > 0)
		usage();

	if(stdio){
		fs.infd = 0;
		fs.outfd = 1;
		srv(&fs);
		exits(0);
	}

	if(srvname == nil && mtpt == nil)
		sysfatal("must specify -S, or -m option");

	if(mountflags == 0)
		mountflags = MREPL | MCREATE;
	postmountsrv(&fs, srvname, mtpt, mountflags);
	exits(0);
}
