#include "common.h"
#include <auth.h>
#include <ndb.h>

/*
 *  number of predefined fd's
 */
int nsysfile=3;

static char err[Errlen];

/*
 *  return the date
 */
extern char *
thedate(void)
{
	static char now[64];
	char *cp;

	strcpy(now, ctime(time(0)));
	cp = strchr(now, '\n');
	if(cp)
		*cp = 0;
	return now;
}

/*
 *  return the user id of the current user
 */
extern char *
getlog(void)
{
	static char user[64];
	int fd;
	int n;

	fd = open("/dev/user", 0);
	if(fd < 0)
		return nil;
	if((n=read(fd, user, sizeof(user)-1)) <= 0)
		return nil;
	close(fd);
	user[n] = 0;
	return user;
}

/*
 *  return the lock name (we use one lock per directory)
 */
static String *
lockname(char *path)
{
	String *lp;
	char *cp;

	/*
	 *  get the name of the lock file
	 */
	lp = s_new();
	cp = strrchr(path, '/');
	if(cp)
		s_nappend(lp, path, cp - path + 1);
	s_append(lp, "L.mbox");

	return lp;
}

int
syscreatelocked(char *path, int mode, int perm)
{
	return create(path, mode, DMEXCL|perm);
}

int
sysopenlocked(char *path, int mode)
{
/*	return open(path, OEXCL|mode);/**/
	return open(path, mode);		/* until system call is fixed */
}

int
sysunlockfile(int fd)
{
	return close(fd);
}

/*
 *  try opening a lock file.  If it doesn't exist try creating it.
 */
static int
openlockfile(Mlock *l)
{
	int fd;
	Dir *d;
	Dir nd;
	char *p;

	fd = open(s_to_c(l->name), OREAD);
	if(fd >= 0){
		l->fd = fd;
		return 0;
	}

	d = dirstat(s_to_c(l->name));
	if(d == nil){
		/* file doesn't exist */
		/* try creating it */
		fd = create(s_to_c(l->name), OREAD, DMEXCL|0666);
		if(fd >= 0){
			nulldir(&nd);
			nd.mode = DMEXCL|0666;
			if(dirfwstat(fd, &nd) < 0){
				/* if we can't chmod, don't bother */
				/* live without the lock but log it */
				syslog(0, "mail", "lock error: %s: %r", s_to_c(l->name));
				remove(s_to_c(l->name));
			}
			l->fd = fd;
			return 0;
		}

		/* couldn't create */
		/* do we have write access to the directory? */
		p = strrchr(s_to_c(l->name), '/');
		if(p != 0){
			*p = 0;
			fd = access(s_to_c(l->name), 2);
			*p = '/';
			if(fd < 0){
				/* live without the lock but log it */
				syslog(0, "mail", "lock error: %s: %r", s_to_c(l->name));
				return 0;
			}
		} else {
			fd = access(".", 2);
			if(fd < 0){
				/* live without the lock but log it */
				syslog(0, "mail", "lock error: %s: %r", s_to_c(l->name));
				return 0;
			}
		}
	} else
		free(d);

	return 1; /* try again later */
}

#define LSECS 5*60

/*
 *  Set a lock for a particular file.  The lock is a file in the same directory
 *  and has L. prepended to the name of the last element of the file name.
 */
extern Mlock *
syslock(char *path)
{
	Mlock *l;
	int tries;

	l = mallocz(sizeof(Mlock), 1);
	if(l == 0)
		return nil;

	l->name = lockname(path);

	/*
	 *  wait LSECS seconds for it to unlock
	 */
	for(tries = 0; tries < LSECS*2; tries++){
		switch(openlockfile(l)){
		case 0:
			return l;
		case 1:
			sleep(500);
			break;
		default:
			goto noway;
		}
	}

noway:
	s_free(l->name);
	free(l);
	return nil;
}

/*
 *  like lock except don't wait
 */
extern Mlock *
trylock(char *path)
{
	Mlock *l;
	char buf[1];
	int fd;

	l = malloc(sizeof(Mlock));
	if(l == 0)
		return 0;

	l->name = lockname(path);
	if(openlockfile(l) != 0){
		s_free(l->name);
		free(l);
		return 0;
	}
	
	/* fork process to keep lock alive */
	switch(l->pid = rfork(RFPROC)){
	default:
		break;
	case 0:
		fd = l->fd;
		for(;;){
			sleep(1000*60);
			if(pread(fd, buf, 1, 0) < 0)
				break;
		}
		_exits(0);
	}
	return l;
}

extern void
syslockrefresh(Mlock *l)
{
	char buf[1];

	pread(l->fd, buf, 1, 0);
}

extern void
sysunlock(Mlock *l)
{
	if(l == 0)
		return;
	if(l->name){
		s_free(l->name);
	}
	if(l->fd >= 0)
		close(l->fd);
	if(l->pid > 0)
		postnote(PNPROC, l->pid, "time to die");
	free(l);
}

/*
 *  Open a file.  The modes are:
 *
 *	l	- locked
 *	a	- set append permissions
 *	r	- readable
 *	w	- writable
 *	A	- append only (doesn't exist in Bio)
 */
extern Biobuf *
sysopen(char *path, char *mode, ulong perm)
{
	int sysperm;
	int sysmode;
	int fd;
	int docreate;
	int append;
	int truncate;
	Dir *d, nd;
	Biobuf *bp;

	/*
	 *  decode the request
	 */
	sysperm = 0;
	sysmode = -1;
	docreate = 0;
	append = 0;
	truncate = 0;
 	for(; mode && *mode; mode++)
		switch(*mode){
		case 'A':
			sysmode = OWRITE;
			append = 1;
			break;
		case 'c':
			docreate = 1;
			break;
		case 'l':
			sysperm |= DMEXCL;
			break;
		case 'a':
			sysperm |= DMAPPEND;
			break;
		case 'w':
			if(sysmode == -1)
				sysmode = OWRITE;
			else
				sysmode = ORDWR;
			break;
		case 'r':
			if(sysmode == -1)
				sysmode = OREAD;
			else
				sysmode = ORDWR;
			break;
		case 't':
			truncate = 1;
			break;
		default:
			break;
		}
	switch(sysmode){
	case OREAD:
	case OWRITE:
	case ORDWR:
		break;
	default:
		if(sysperm&DMAPPEND)
			sysmode = OWRITE;
		else
			sysmode = OREAD;
		break;
	}

	/*
	 *  create file if we need to
	 */
	if(truncate)
		sysmode |= OTRUNC;
	fd = open(path, sysmode);
	if(fd < 0){
		d = dirstat(path);
		if(d == nil){
			if(docreate == 0)
				return 0;

			fd = create(path, sysmode, sysperm|perm);
			if(fd < 0)
				return 0;
			nulldir(&nd);
			nd.mode = sysperm|perm;
			dirfwstat(fd, &nd);
		} else {
			free(d);
			return 0;
		}
	}

	bp = (Biobuf*)malloc(sizeof(Biobuf));
	if(bp == 0){
		close(fd);
		return 0;
	}
	memset(bp, 0, sizeof(Biobuf));
	Binit(bp, fd, sysmode&~OTRUNC);

	if(append)
		Bseek(bp, 0, 2);
	return bp;
}

/*
 *  close the file, etc.
 */
int
sysclose(Biobuf *bp)
{
	int rv;

	rv = Bterm(bp);
	close(Bfildes(bp));
	free(bp);
	return rv;
}

/*
 *  create a file
 */
int
syscreate(char *file, int mode, ulong perm)
{
	return create(file, mode, perm);
}

/*
 *  make a directory
 */
int
sysmkdir(char *file, ulong perm)
{
	int fd;

	if((fd = create(file, OREAD, DMDIR|perm)) < 0)
		return -1;
	close(fd);
	return 0;
}

/*
 *  change the group of a file
 */
int
syschgrp(char *file, char *group)
{
	Dir nd;

	if(group == 0)
		return -1;
	nulldir(&nd);
	nd.gid = group;
	return dirwstat(file, &nd);
}

extern int
sysdirreadall(int fd, Dir **d)
{
	return dirreadall(fd, d);
}

/*
 *  read in the system name
 */
extern char *
sysname_read(void)
{
	static char name[128];
	char *cp;

	cp = getenv("site");
	if(cp == 0 || *cp == 0)
		cp = alt_sysname_read();
	if(cp == 0 || *cp == 0)
		cp = "kremvax";
	strecpy(name, name+sizeof name, cp);
	return name;
}
extern char *
alt_sysname_read(void)
{
	static char name[128];
	int n, fd;

	fd = open("/dev/sysname", OREAD);
	if(fd < 0)
		return 0;
	n = read(fd, name, sizeof(name)-1);
	close(fd);
	if(n <= 0)
		return 0;
	name[n] = 0;
	return name;
}

/*
 *  get all names
 */
extern char**
sysnames_read(void)
{
	static char **namev;
	Ndbtuple *t, *nt;
	int n;
	char *cp;

	if(namev)
		return namev;

	free(csgetvalue(0, "sys", alt_sysname_read(), "dom", &t));

	n = 0;
	for(nt = t; nt; nt = nt->entry)
		if(strcmp(nt->attr, "dom") == 0)
			n++;

	namev = (char**)malloc(sizeof(char *)*(n+3));

	if(namev){
		n = 0;
		namev[n++] = strdup(sysname_read());
		cp = alt_sysname_read();
		if(cp)
			namev[n++] = strdup(cp);
		for(nt = t; nt; nt = nt->entry)
			if(strcmp(nt->attr, "dom") == 0)
				namev[n++] = strdup(nt->val);
		namev[n] = 0;
	}
	if(t)
		ndbfree(t);

	return namev;
}

/*
 *  read in the domain name
 */
extern char *
domainname_read(void)
{
	char **namev;

	for(namev = sysnames_read(); *namev; namev++)
		if(strchr(*namev, '.'))
			return *namev;
	return 0;
}

/*
 *  return true if the last error message meant file
 *  did not exist.
 */
extern int
e_nonexistent(void)
{
	rerrstr(err, sizeof(err));
	return strcmp(err, "file does not exist") == 0;
}

/*
 *  return true if the last error message meant file
 *  was locked.
 */
extern int
e_locked(void)
{
	rerrstr(err, sizeof(err));
	return strcmp(err, "open/create -- file is locked") == 0;
}

/*
 *  return the length of a file
 */
extern long
sysfilelen(Biobuf *fp)
{
	Dir *d;
	long rv;

	d = dirfstat(Bfildes(fp));
	if(d == nil)
		return -1;
	rv = d->length;
	free(d);
	return rv;
}

/*
 *  remove a file
 */
extern int
sysremove(char *path)
{
	return remove(path);
}

/*
 *  rename a file, fails unless both are in the same directory
 */
extern int
sysrename(char *old, char *new)
{
	Dir d;
	char *obase;
	char *nbase;

	obase = strrchr(old, '/');
	nbase = strrchr(new, '/');
	if(obase){
		if(nbase == 0)
			return -1;
		if(strncmp(old, new, obase-old) != 0)
			return -1;
		nbase++;
	} else {
		if(nbase)
			return -1;
		nbase = new;
	}
	nulldir(&d);
	d.name = nbase;
	return dirwstat(old, &d);
}

/*
 *  see if a file exists
 */
extern int
sysexist(char *file)
{
	Dir	*d;

	d = dirstat(file);
	if(d == nil)
		return 0;
	free(d);
	return 1;
}

/*
 *  return nonzero if file is a directory
 */
extern int
sysisdir(char *file)
{
	Dir	*d;
	int	rv;

	d = dirstat(file);
	if(d == nil)
		return 0;
	rv = d->mode & DMDIR;
	free(d);
	return rv;
}

/*
 * kill a process or process group
 */

static int
stomp(int pid, char *file)
{
	char name[64];
	int fd;

	snprint(name, sizeof(name), "/proc/%d/%s", pid, file);
	fd = open(name, 1);
	if(fd < 0)
		return -1;
	if(write(fd, "die: yankee pig dog\n", sizeof("die: yankee pig dog\n") - 1) <= 0){
		close(fd);
		return -1;
	}
	close(fd);
	return 0;
	
}

/*
 *  kill a process
 */
extern int
syskill(int pid)
{
	return stomp(pid, "note");
	
}

/*
 *  kill a process group
 */
extern int
syskillpg(int pid)
{
	return stomp(pid, "notepg");
}

extern int
sysdetach(void)
{
	if(rfork(RFENVG|RFNAMEG|RFNOTEG) < 0) {
		werrstr("rfork failed");
		return -1;
	}
	return 0;
}

/*
 *  catch a write on a closed pipe
 */
static int *closedflag;
static int
catchpipe(void *a, char *msg)
{
	static char *foo = "sys: write on closed pipe";

	USED(a);
	if(strncmp(msg, foo, strlen(foo)) == 0){
		if(closedflag)
			*closedflag = 1;
		return 1;
	}
	return 0;
}
void
pipesig(int *flagp)
{
	closedflag = flagp;
	atnotify(catchpipe, 1);
}
void
pipesigoff(void)
{
	atnotify(catchpipe, 0);
}

void
exit(int i)
{
	char buf[32];

	if(i == 0)
		exits(0);
	snprint(buf, sizeof(buf), "%d", i);
	exits(buf);
}

static int
islikeatty(int fd)
{
	char buf[64];

	if(fd2path(fd, buf, sizeof buf) != 0)
		return 0;

	/* might be /mnt/term/dev/cons */
	return strlen(buf) >= 9 && strcmp(buf+strlen(buf)-9, "/dev/cons") == 0;
}

extern int
holdon(void)
{
	int fd;

	if(!islikeatty(0))
		return -1;

	fd = open("/dev/consctl", OWRITE);
	write(fd, "holdon", 6);

	return fd;
}

extern int
sysopentty(void)
{
	return open("/dev/cons", ORDWR);
}

extern void
holdoff(int fd)
{
	write(fd, "holdoff", 7);
	close(fd);
}

extern int
sysfiles(void)
{
	return 128;
}

/*
 *  expand a path relative to the user's mailbox directory
 *
 *  if the path starts with / or ./, don't change it
 *
 */
extern String *
mboxpath(char *path, char *user, String *to, int dot)
{
	if (dot || *path=='/' || strncmp(path, "./", 2) == 0
			      || strncmp(path, "../", 3) == 0) {
		to = s_append(to, path);
	} else {
		to = s_append(to, MAILROOT);
		to = s_append(to, "/box/");
		to = s_append(to, user);
		to = s_append(to, "/");
		to = s_append(to, path);
	}
	return to;
}

extern String *
mboxname(char *user, String *to)
{
	return mboxpath("mbox", user, to, 0);
}

extern String *
deadletter(String *to)		/* pass in sender??? */
{
	char *cp;

	cp = getlog();
	if(cp == 0)
		return 0;
	return mboxpath("dead.letter", cp, to, 0);
}

char *
homedir(char *user)
{
	USED(user);
	return getenv("home");
}

String *
readlock(String *file)
{
	char *cp;

	cp = getlog();
	if(cp == 0)
		return 0;
	return mboxpath("reading", cp, file, 0);
}

String *
username(String *from)
{
	int n;
	Biobuf *bp;
	char *p, *q;
	String *s;

	bp = Bopen("/adm/keys.who", OREAD);
	if(bp == 0)
		bp = Bopen("/adm/netkeys.who", OREAD);
	if(bp == 0)
		return 0;

	s = 0;
	n = strlen(s_to_c(from));
	for(;;) {
		p = Brdline(bp, '\n');
		if(p == 0)
			break;
		p[Blinelen(bp)-1] = 0;
		if(strncmp(p, s_to_c(from), n))
			continue;
		p += n;
		if(*p != ' ' && *p != '\t')	/* must be full match */
			continue;
		while(*p && (*p == ' ' || *p == '\t'))
				p++;
		if(*p == 0)
			continue;
		for(q = p; *q; q++)
			if(('0' <= *q && *q <= '9') || *q == '<')
				break;
		while(q > p && q[-1] != ' ' && q[-1] != '\t')
			q--;
		while(q > p && (q[-1] == ' ' || q[-1] == '\t'))
			q--;
		*q = 0;
		s = s_new();
		s_append(s, "\"");
		s_append(s, p);
		s_append(s, "\"");
		break;
	}
	Bterm(bp);
	return s;
}

char *
remoteaddr(int fd, char *dir)
{
	char buf[128], *p;
	int n;

	if(dir == 0){
		if(fd2path(fd, buf, sizeof(buf)) != 0)
			return "";

		/* parse something of the form /net/tcp/nnnn/data */
		p = strrchr(buf, '/');
		if(p == 0)
			return "";
		strncpy(p+1, "remote", sizeof(buf)-(p-buf)-2);
	} else
		snprint(buf, sizeof buf, "%s/remote", dir);
	buf[sizeof(buf)-1] = 0;

	fd = open(buf, OREAD);
	if(fd < 0)
		return "";
	n = read(fd, buf, sizeof(buf)-1);
	close(fd);
	if(n > 0){
		buf[n] = 0;
		p = strchr(buf, '!');
		if(p)
			*p = 0;
		return strdup(buf);
	}
	return "";
}

//  create a file and 
//	1) ensure the modes we asked for
//	2) make gid == uid
static int
docreate(char *file, int perm)
{
	int fd;
	Dir ndir;
	Dir *d;

	//  create the mbox
	fd = create(file, OREAD, perm);
	if(fd < 0){
		fprint(2, "couldn't create %s\n", file);
		return -1;
	}
	d = dirfstat(fd);
	if(d == nil){
		fprint(2, "couldn't stat %s\n", file);
		return -1;
	}
	nulldir(&ndir);
	ndir.mode = perm;
	ndir.gid = d->uid;
	if(dirfwstat(fd, &ndir) < 0)
		fprint(2, "couldn't chmod %s: %r\n", file);
	close(fd);
	return 0;
}

//  create a mailbox
int
creatembox(char *user, char *folder)
{
	char *p;
	String *mailfile;
	char buf[512];
	Mlock *ml;

	mailfile = s_new();
	if(folder == 0)
		mboxname(user, mailfile);
	else {
		snprint(buf, sizeof(buf), "%s/mbox", folder);
		mboxpath(buf, user, mailfile, 0);
	}

	// don't destroy existing mailbox
	if(access(s_to_c(mailfile), 0) == 0){
		fprint(2, "mailbox already exists\n");
		return -1;
	}
	fprint(2, "creating new mbox: %s\n", s_to_c(mailfile));

	//  make sure preceding levels exist
	for(p = s_to_c(mailfile); p; p++) {
		if(*p == '/')	/* skip leading or consecutive slashes */
			continue;
		p = strchr(p, '/');
		if(p == 0)
			break;
		*p = 0;
		if(access(s_to_c(mailfile), 0) != 0){
			if(docreate(s_to_c(mailfile), DMDIR|0711) < 0)
				return -1;
		}
		*p = '/';
	}

	//  create the mbox
	if(docreate(s_to_c(mailfile), 0622|DMAPPEND|DMEXCL) < 0)
		return -1;

	/*
	 *  create the lock file if it doesn't exist
	 */
	ml = trylock(s_to_c(mailfile));
	if(ml != nil)
		sysunlock(ml);

	return 0;
}
