#include <u.h>
#include <libc.h>
#include <bio.h>
#include <auth.h>
#include <authsrv.h>
#include "authlocal.h"

enum
{
	NARG	= 15,		/* max number of arguments */
	MAXARG	= 10*ANAMELEN,	/* max length of an argument */
};

static int	setenv(char*, char*);
static char	*expandarg(char*, char*);
static int	splitargs(char*, char*[], char*, int);
static int	nsfile(char*, Biobuf *, AuthRpc *, int);
static int	nsop(char*, int, char*[], AuthRpc*, int);
static int	catch(void*, char*);

int newnsdebug;

static int
freecloserpc(AuthRpc *rpc)
{
	if(rpc){
		close(rpc->afd);
		auth_freerpc(rpc);
	}
	return -1;
}

static int
buildns(int newns, char *user, char *file)
{
	Biobuf *b;
	char home[4*ANAMELEN];
	int afd, cdroot, dfd;
	char *path;
	AuthRpc *rpc;

	rpc = nil;
	/* try for factotum now because later is impossible */
	afd = open("/mnt/factotum/rpc", ORDWR|OCEXEC);
	if(afd < 0 && newnsdebug)
		fprint(2, "open /mnt/factotum/rpc: %r\n");
	if(afd >= 0){
		rpc = auth_allocrpc(afd);
		if(rpc == nil)
			close(afd);
	}
	/* rpc != nil iff afd >= 0 */

	dfd = open("#c/drivers", OWRITE|OCEXEC);
	if(dfd < 0 && newnsdebug)
		fprint(2, "open #c/drivers: %r\n");

	if(file == nil){
		if(!newns){
			close(dfd);
			werrstr("no namespace file specified");
			return freecloserpc(rpc);
		}
		file = "/lib/namespace";
	}
	b = Bopen(file, OREAD|OCEXEC);
	if(b == nil){
		close(dfd);
		werrstr("can't open %s: %r", file);
		return freecloserpc(rpc);
	}
	if(newns){
		rfork(RFENVG|RFCNAMEG);
		setenv("user", user);
		snprint(home, sizeof home, "/usr/%s", user);
		setenv("home", home);
	}

	cdroot = nsfile(newns ? "newns" : "addns", b, rpc, dfd);
	close(dfd);
	Bterm(b);
	freecloserpc(rpc);

	/* make sure we managed to cd into the new name space */
	if(newns && !cdroot){
		path = malloc(1024);
		if(path == nil || getwd(path, 1024) == 0 || chdir(path) < 0)
			chdir("/");
		if(path != nil)
			free(path);
	}

	return 0;
}

static int
nsfile(char *fn, Biobuf *b, AuthRpc *rpc, int dfd)
{
	int argc;
	char *cmd, *argv[NARG+1], argbuf[MAXARG*NARG];
	int cdroot;

	cdroot = 0;
	atnotify(catch, 1);
	while(cmd = Brdline(b, '\n')){
		cmd[Blinelen(b)-1] = '\0';
		while(*cmd==' ' || *cmd=='\t')
			cmd++;
		if(*cmd == '#')
			continue;
		argc = splitargs(cmd, argv, argbuf, NARG);
		if(argc)
			cdroot |= nsop(fn, argc, argv, rpc, dfd);
	}
	atnotify(catch, 0);
	return cdroot;
}

int
newns(char *user, char *file)
{
	return buildns(1, user, file);
}

int
addns(char *user, char *file)
{
	return buildns(0, user, file);
}

static int
famount(int fd, AuthRpc *rpc, char *mntpt, int flags, char *aname)
{
	int afd;
	AuthInfo *ai;
	int ret;

	afd = fauth(fd, aname);
	if(afd >= 0){
		ai = fauth_proxy(afd, rpc, amount_getkey, "proto=p9any role=client");
		if(ai != nil)
			auth_freeAI(ai);
	}
	ret = mount(fd, afd, mntpt, flags, aname);
	if(ret == -1)
		close(fd);
	if(afd >= 0)
		close(afd);
	return ret;
}

static int
nsop(char *fn, int argc, char *argv[], AuthRpc *rpc, int dfd)
{
	char *argv0;
	ulong flags;
	char *devop;
	int fd, i;
	Biobuf *b;
	int cdroot;

	cdroot = 0;
	flags = 0;
	devop = "&";
	argv0 = nil;
	if(newnsdebug){
		for (i = 0; i < argc; i++)
			fprint(2, "%s ", argv[i]);
		fprint(2, "\n");
	}
	ARGBEGIN{
	case 'a':
		flags |= MAFTER;
		break;
	case 'b':
		flags |= MBEFORE;
		break;
	case 'c':
		flags |= MCREATE;
		break;
	case 'C':
		flags |= MCACHE;
		break;
	case 'r':
		devop = "&~";
		break;
	case 'n':
		devop = "~";
		break;
	}ARGEND

	if(!(flags & (MAFTER|MBEFORE)))
		flags |= MREPL;

	if(strcmp(argv0, ".") == 0 && argc == 1){
		b = Bopen(argv[0], OREAD|OCEXEC);
		if(b == nil)
			return 0;
		cdroot |= nsfile(fn, b, rpc, dfd);
		Bterm(b);
	}else if(strcmp(argv0, "clear") == 0 && argc == 0){
		rfork(RFCNAMEG);
	}else if(strcmp(argv0, "bind") == 0 && argc == 2){
		if(bind(argv[0], argv[1], flags) == -1 && newnsdebug)
			fprint(2, "%s: bind: %s %s: %r\n", fn, argv[0], argv[1]);
	}else if(strcmp(argv0, "unmount") == 0){
		if(argc == 1)
			unmount(nil, argv[0]);
		else if(argc == 2)
			unmount(argv[0], argv[1]);
	}else if(strcmp(argv0, "mount") == 0){
		fd = open(argv[0], ORDWR);
		if(fd < 0){
			if(newnsdebug)
				fprint(2, "%s: mount: %s: %r\n", fn, argv[0]);
			return 0;
		}
		if(argc == 2){
			if(famount(fd, rpc, argv[1], flags, "") == -1 && newnsdebug)
				fprint(2, "%s: mount: %s %s: %r\n", fn, argv[0], argv[1]);
		}else if(argc == 3){
			if(famount(fd, rpc, argv[1], flags, argv[2]) == -1 && newnsdebug)
				fprint(2, "%s: mount: %s %s %s: %r\n", fn, argv[0], argv[1], argv[2]);
		} else {
			close(fd);
		}
	}else if(strcmp(argv0, "cd") == 0 && argc == 1){
		if(chdir(argv[0]) == 0 && *argv[0] == '/')
			cdroot = 1;
	}else if(strcmp(argv0, "chdev") == 0){
		//We should not silently fail if we can not honor a chdev
		//due to the parent namespace missing #c/drivers.
		if(dfd <= 0)
			sysfatal("chdev requested, but could not open #c/drivers");
		if(argc == 0 && devop[0] == '~'){
			if(fprint(dfd, "chdev ~") < 0 && newnsdebug)
				fprint(2, "%s: chdev ~: %r\n", fn);
		}else if(argc == 1){
			if(fprint(dfd, "chdev %s %s", devop, argv[0]) < 0 && newnsdebug)
				fprint(2, "%s: chdev %s %s: %r\n", fn, devop, argv[0]);
		}
	}
	return cdroot;
}

static char *wocp = "sys: write on closed pipe";

static int
catch(void *x, char *m)
{
	USED(x);
	return strncmp(m, wocp, strlen(wocp)) == 0;
}

static char*
unquote(char *s)
{
	char *r, *w;
	int inquote;
	
	inquote = 0;
	for(r=w=s; *r; r++){
		if(*r != '\''){
			*w++ = *r;
			continue;
		}
		if(inquote){
			if(*(r+1) == '\''){
				*w++ = '\'';
				r++;
			}else
				inquote = 0;
		}else
			inquote = 1;
	}
	*w = 0;
	return s;
}

static int
splitargs(char *p, char *argv[], char *argbuf, int nargv)
{
	char *q;
	int i, n;

	n = gettokens(p, argv, nargv, " \t\r");
	if(n == nargv)
		return 0;
	for(i = 0; i < n; i++){
		q = argv[i];
		argv[i] = argbuf;
		argbuf = expandarg(q, argbuf);
		if(argbuf == nil)
			return 0;
		unquote(argv[i]);
	}
	return n;
}

static char*
nextdollar(char *arg)
{
	char *p;
	int inquote;
	
	inquote = 0;
	for(p=arg; *p; p++){
		if(*p == '\'')
			inquote = !inquote;
		if(*p == '$' && !inquote)
			return p;
	}
	return nil;
}

/*
 * copy the arg into the buffer,
 * expanding any environment variables.
 * environment variables are assumed to be
 * names (ie. < ANAMELEN long)
 * the entire argument is expanded to be at
 * most MAXARG long and null terminated
 * the address of the byte after the terminating null is returned
 * any problems cause a 0 return;
 */
static char *
expandarg(char *arg, char *buf)
{
	char env[3+ANAMELEN], *p, *x;
	int fd, n, len;

	n = 0;
	while(p = nextdollar(arg)){
		len = p - arg;
		if(n + len + ANAMELEN >= MAXARG-1)
			return 0;
		memmove(&buf[n], arg, len);
		n += len;
		p++;
		arg = strpbrk(p, "/.!'$");
		if(arg == nil)
			arg = p+strlen(p);
		len = arg - p;
		if(len == 0 || len >= ANAMELEN)
			continue;
		strcpy(env, "#e/");
		strncpy(env+3, p, len);
		env[3+len] = '\0';
		fd = open(env, OREAD|OCEXEC);
		if(fd >= 0){
			len = read(fd, &buf[n], ANAMELEN - 1);
			/* some singleton environment variables have trailing NULs */
			/* lists separate entries with NULs; we arbitrarily take the first element */
			if(len > 0){
				x = memchr(&buf[n], 0, len);
				if(x != nil)
					len = x - &buf[n];
				n += len;
			}
			close(fd);
		}
	}
	len = strlen(arg);
	if(n + len >= MAXARG - 1)
		return 0;
	strcpy(&buf[n], arg);
	return &buf[n+len+1];
}

static int
setenv(char *name, char *val)
{
	int f;
	char ename[ANAMELEN+6];
	long s;

	sprint(ename, "#e/%s", name);
	f = create(ename, OWRITE|OCEXEC, 0664);
	if(f < 0)
		return -1;
	s = strlen(val);
	if(write(f, val, s) != s){
		close(f);
		return -1;
	}
	close(f);
	return 0;
}
