#include <u.h>
#include <sys/types.h>
#include <pwd.h>
#include <netdb.h>
#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)
{
	return getuser();
}

/*
 *  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;
			sysmode |= OLOCK;
			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
 */
static char *unix_hostname_read(void);
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)
{
	char *cp;
	static char name[128];

	cp = getenv("sysname");
	if(cp == 0 || *cp == 0)
		cp = unix_hostname_read();
	if(cp == 0 || *cp == 0)
		return 0;
	strecpy(name, name+sizeof name, cp);
	return name;
}
static char *
unix_hostname_read(void)
{
	static char hostname[256];

	if(gethostname(hostname, sizeof hostname) < 0)
		return nil;
	return hostname;
}

/*
 *  get all names
 */
extern char**
sysnames_read(void)
{
	static char **namev;
	struct hostent *h;
	char **p, **a;

	if(namev)
		return namev;

	h = gethostbyname(alt_sysname_read());
	if(h == nil)
		return 0;

	for(p=h->h_aliases; *p; p++)
		;
	
	namev = malloc((2+p-h->h_aliases)*sizeof namev[0]);
	if(namev == 0)
		return 0;

	a = namev;
	*a++ = strdup(h->h_name);
	for(p=h->h_aliases; *p; p++)
		*a++ = strdup(*p);
	*a = 0;

	return namev;
}

/*
 *  read in the domain name.
 *  chop off beginning pieces until we find one with an mx record.
 */
extern char *
domainname_read(void)
{
	char **namev, *p;
	Ndbtuple *t;

	for(namev = sysnames_read(); namev && *namev; namev++){
		if(strchr(*namev, '.')){
			for(p=*namev-1; p && *++p; p=strchr(p, '.')){
				if((t = dnsquery(nil, p, "mx")) != nil){
					ndbfree(t);
					return p;
				}
			}
		}
	}
	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
 */
extern int
syskill(int pid)
{
	return postnote(PNPROC, pid, "kill");
}

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

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);
}

extern int
holdon(void)
{
	/* XXX talk to 9term? */
	return -1;
}

extern int
sysopentty(void)
{
	return open("/dev/tty", 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)
{
	char *dir;
	String *s;
	
	if (dot || *path=='/' || strncmp(path, "./", 2) == 0
			      || strncmp(path, "../", 3) == 0) {
		to = s_append(to, path);
	} else {
		if ((dir = homedir(user)) != nil) {
			s = s_copy(dir);
			s_append(s, "/mail/");
			if(access(s_to_c(s), AEXIST) >= 0){
				to = s_append(to, s_to_c(s));
				s_free(s);
				to = s_append(to, path);
				return to;
			}
			s_free(s);
		}
		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);
}

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

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

String *
username(String *from)
{
	String* s;
	struct passwd* pw;

	setpwent();
	while((pw = getpwent()) != nil){
		if(strcmp(s_to_c(from), pw->pw_name) == 0){
			s = s_new();
			s_append(s, "\"");
			s_append(s, pw->pw_gecos);
			s_append(s, "\"");
			return s;
		}
	}
	return nil;
}

char *
homedir(char *user)
{
	static char buf[1024];
	struct passwd* pw;

	setpwent();
	while((pw = getpwent()) != nil)
		if(strcmp(user, pw->pw_name) == 0){
			strecpy(buf, buf+sizeof buf, pw->pw_dir);
			return buf;
		}
	return nil;
}

char *
remoteaddr(int fd, char *dir)
{
	char *raddr;
	NetConnInfo *nci;
	
	if((nci = getnetconninfo(dir, fd)) == nil)
		return nil;
	raddr = strdup(nci->raddr);
	freenetconninfo(nci);
	return raddr;
}

/*  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;
}
