#include <sys/param.h>
#include <signal.h>
#include <sysent.h>
#include <libc.h>
#include <fio.h>
#include <ipc.h>
#include <string.h>
#include <errno.h>
#include <utsname.h>
#include <pwd.h>
#include "dbtypes.h"

/*
 *  system calls not in sysent.h
 */
extern "C" int select(int, fd_set*, fd_set*, int);
#define WNOHANG	1	/* don't hang in wait */

/*
 *  imported
 */
extern "C" int detach(char *);
extern int optind;
extern char *optarg;
extern "C" int getopt(int, char *[], char *);
extern "C" long time(long *);
extern "C" int uname(utsname *);

/*
 *  predeclared
 */
int announce(int, char *);
int doset(int, char *, int *);
int dovalue(int, char *, char *, int *);
void dohelp(int);
void initclient();
void newclient(int);
void dropclient(int);
int clientreq(int);
Db *getdb(char *, int *);
void basefiles(char *, char *, char *, char **, char **);

/*
 *  global
 */
char *av0;
int debug;
char *locsysname = 0;

/*
 *  return codes from clientreq
 */
#define OK 0
#define DOPARSE 1
#define DROPCLIENT 2

/*
 *  client data
 */
fd_set cvec;
struct client {
	int fd;
	char *bp;
	char *machine;		/* Client's machine from ipcinfo (0 if local) */
	char *user;		/* Client's login from ipcinfo */
	int dbhint;		/* Hint for finding the needed user db */
} client[NOFILE];
int lastclient = -1;

main(int ac, char *av[])
{
	int i, n;
	fd_set vec;
	char errbuf[sizeof(Fbuffer)];
	int doannounce, doparse;
	char *mtpt = "ns";
	int afd = -1;
	long lastcheck = 0;
	char *dbloadfile;
	char *sysdbdir;
	char *sysdbfile;
	int  dummyhint;
	utsname utsn;
	Db *sysdb;
	int noparse = 0;

	av0 = av[0];
	chdir("/cs");
	dbloadfile = "/usr/ipc/lib/ns.db";

	/*
	 *  parse arguments
	 */
	while((i = getopt(ac, av, "dm:f:")) != -1)
		switch(i){
		case 'd':
			debug = 1;
			break;
		case 'm':
			mtpt = optarg;
			break;
		case 'f':
			dbloadfile = optarg;
			break;
		case 'n':
			noparse = 1;
			break;
		}

	/*
	 * Don't die if pipe breaks
	 */

	signal(SIGPIPE, SIG_IGN);

	/*
	 *  let go of the console
	 */
	if(!debug)
		detach(mtpt);

	/*
	 * Find out who we are and make the system name global
	 */
	if (!uname(&utsn))
		locsysname = utsn.sysname;

	Finit(2, errbuf);
	initclient();

	/*
	 * Initialize system database and remember root system database
	 * file in sysdb structure
	 */

	basefiles("/usr/ipc/lib", "ns.db", dbloadfile, &sysdbdir, &sysdbfile);
	sysdb = newdb((int *) 0, sysdbdir, sysdbfile, locsysname, &dummyhint);

	/*
	 *  loop forever, announcing and listening for requests.  We
	 *  reannounce whenever a problem occurs with the announcement
	 *  fd.
	 */
	for(doannounce=doparse=1;;){
		if(doannounce){
			afd = announce(afd, mtpt);
			doannounce = 0;
		}
		if(doparse || time((long *)0) - lastcheck > 8*60){
			if (checkfiles(sysdb)) 
				parsefiles(sysdb);
			doparse = 0;
			lastcheck = time((long *)0);
		}
		for(; !doannounce && !doparse;){
			/*
			 *  wait for a request
			 */
			vec = cvec;
			FD_SET(afd, vec);
			switch(n=select(NOFILE, &vec, 0, 2*60*1000)){
			case 0:
				/*
				 *  check to reparse if the system is
				 *  quiescent
				 */
				if(noparse == 0)
					doparse = 1;
				continue;
			case -1:
				doannounce = 1;	/* all fd's bad */
				continue;
			}

			/*
			 *  new client?  A <0 return means that the 
			 *  announcement has gone sour.
			 */
			if(FD_ISSET(afd, vec)){
				n--;
				newclient(afd);
			}

			/*
			 *  client request?  A <0 return means the client
			 *  has gone sour.
			 */
			for(i=0; n>0 && i<NOFILE && client[i].fd>=0; i++){
				if(FD_ISSET(client[i].fd, vec)){
					n--;
					switch(clientreq(i)){
					case DROPCLIENT:
						dropclient(i);
						break;
					case DOPARSE:
						doparse = 1;
						break;
					}
				}
			}
		}
	}
}

/*
 *  announce a service
 */
announce(int afd, char *mtpt)
{
	if(afd>=0)
		close(afd);
	sync();
	for(afd=-1; afd<0; ){
		afd = ipccreat(mtpt, "");
		if(afd<0){
			logconsole("%s: can't announce (%s)\n",av0,errstr);
			sleep(10);
		}
	}
	chmod(mtpt, 0666);
	logconsole("%s: announced as %s\n", av0, mtpt);
	return afd;
}

/*
 * Break infile into base directory and file in base directory.  Use
 * default directory for relative infile.  Use default directory and
 * default file if there's no infile.
 */
void
basefiles(char *defdir, char *deffile, char *infile, char **basedir, char **file)
{
	char	*fileptr;

	if (fileptr= strrchr(infile,'/')) {
		*fileptr = 0;
		*file = strdup(fileptr + 1);
		if (fileptr == infile)
			*basedir = (char *) 0;
		else if (*infile == '/')
			*basedir = strdup(infile);
		else
			*basedir = strdup(path(infile,defdir));
		*fileptr = '/';
	} else if (infile && *infile) {
		*file = strdup(infile);
		*basedir = strdup(defdir);
	} else {
		*file = strdup(deffile);
		*basedir = strdup(defdir);
	}
}

/*
 *  set all clients to off
 */
void
initclient()
{
	int i;

	for(i=0; i<NOFILE; i++)
		client[i].fd = -1;
}

void
ding(int x)
{
	signal(SIGALRM, ding);
	alarm(20);
}

/*
 *  get a request for a new client
 */
void
newclient(int fd)
{
	int cfd;
	int i;
	ipcinfo *ip;

	/*
	 *  since we're single stream, don't let listening for
	 *  a call take forever.  The alarm will abort any reads
	 *  in ipclisten and ipcaccept.
	 */
	signal(SIGALRM, ding);
	alarm(20);
	ip = ipclisten(fd);
	if(ip==0)
		return;
	cfd = ipcaccept(ip);
	alarm(0);
	if(cfd<0)
		return;
	for(i=0; i<NOFILE; i++)
		if(client[i].fd<0){
			client[i].fd = cfd;
			FD_SET(cfd, cvec);
			if(i>lastclient)
				lastclient = i;
			if(!client[i].bp)
				client[i].bp = malloc(sizeof(Fbuffer));
			Finit(cfd, client[i].bp);
			client[i].machine = 0;
			if (ip->machine && *ip->machine)
				client[i].machine = strdup(ip->machine);
			client[i].user = strdup(ip->user);
			client[i].dbhint = -1;		/* no hint yet */
			return;
		}
	close(cfd);
}

/*
 *  drop a client
 */
void
dropclient(int c)
{
	/*
	 *  close off this client
	 */
	FD_CLR(client[c].fd, cvec);
	close(client[c].fd);
	client[c].fd = -1;
	if (client[c].machine)
		free(client[c].machine);
	if (client[c].user)
		free(client[c].user);

	/*
	 *  move last client to this spot, lastclient may equal c
	 */
	client[c] = client[lastclient];
	client[lastclient].fd = -1;
	lastclient--;
}

/*
 *  service a client request
 */
int
clientreq(int c)
{
	char buf[512];
	int n;
	char *fields[3];
	int fd = client[c].fd;

	/*
	 *  read a line and split command and arguments
	 */
	if((n=read(fd, buf, sizeof(buf)-1))<=0)
		return DROPCLIENT;
	buf[n] = '\0';
	setfields(" \t\n");
	n = getmfields(buf, fields, 2);
	if(n<1) {
		fprint(fd, "ILL null command\n");
		return OK;
	}

	/*
	 *  act on command
	 */
	if(fstrcmp("set", fields[0])==0){
		if(n<2)
			fprint(fd, "ILL no search key\n");
		else
			return doset(fd, fields[1], &client[c].dbhint);
	} else if(fstrcmp("value", fields[0])==0) {
		if(n<2) {
			fprint(fd, "ILL no value types, no search key\n");
		} else {
			n = getmfields(fields[1], fields, 2);
			if(n<2)
				fprint(fd, "ILL no search key\n");
			else
				dovalue(fd,fields[0],fields[1],
					&client[c].dbhint);
		}
	} else if(fstrcmp("help", fields[0])==0) {
		dohelp(fd);
	} else if(fstrcmp("quit", fields[0])==0) {
		return DROPCLIENT;
	} else if(fstrcmp("reset", fields[0])==0) {
		Fprint(fd, "OK\n");
		Fprint(fd, "DONE\n");
		Fflush(fd);
		logevent("reset by %s!%s\n",
			(client[c].machine? client[c].machine:locsysname),
			client[c].user);
		releasedbs();
		return DOPARSE;
	} else
		fprint(fd, "ILL\n");
	return OK;
}

/*
 *  Return a `set' of tuples matching allattributes in the request.  The
 *  request is "set value[,type] value[,type] ..."  The first valid attribute in
 *  the form: "system!login,db" is interpreted as the db to be searched.
 *  A request with no db attribute is resolved in the system db.
 */
int
doset(int fd, char *key, int *hint)
{
	Set *s = (Set *) 0;
	int status;
	Db *d;

	d = getdb(key,hint);
	key = key + strspn(key, " \t\n");

	if (d)
		s = lookup(key, d->o);

	if (Fprint(fd, "OK\n") < 0) {
		if (s) delete s;
		return DROPCLIENT;
	}
	if(s){
		if(d->origin)
			s->sort(d->origin->first);
		status = s->print(fd);
		delete s;
		if (status < 0) 
			return DROPCLIENT;
	}
	if (Fprint(fd, "DONE\n") < 0)
		return DROPCLIENT;
	if (Fflush(fd) < 0)
		return DROPCLIENT;
	return 0;
}

/*
 *  Return a single value.  The value is of one of the types listed in
 *  the first argument of the request.  The value comes from a tuple
 *  matching all attributes in the request.  The request is
 *  "value type1|type2|type3|... value[,type] value[,type] ..."
 *  The first valid attribute in the form: "system!login,db" is 
 *  interpreted as the db to be searched.  A request with no db 
 *  attribute is resolved in the system db.
 */
int
dovalue(int fd, char *typelist, char *key, int *hint)
{
	int n;
#define MAXTYPES 10
	char *types[MAXTYPES+1];
	Set *s;
	Db *d;

	d = getdb(key,hint);
	setfields("|");
	n = getmfields(typelist, types, MAXTYPES);
	if(n<=0) {
		fprint(fd, "ILL bad types\n");
		return -1;
	}
	types[MAXTYPES] = 0;

	if (d)
		s = lookup(key, d->o);
	Fprint(fd, "OK\n");
	if(s){
		if(d->origin)
			s->sort(d->origin->first);
		s->printvalue(fd, types);
		delete s;
	}
	Fprint(fd, "DONE\n");
	Fflush(fd);
	return 0;
}

/*
 *  return a usage menu
 */
void
dohelp(int fd)
{
	fprint(fd, "OK\n");
	fprint(fd, "\tset value[,type] value[,type] ...\n");
	fprint(fd, "\tvalue [type|type|]type value[,type] value[,type] ...\n");
	fprint(fd, "\thelp\n");
	fprint(fd, "\tquit\n");
	fprint(fd, "\treset\n");
	fprint(fd, "DONE\n");
}

/*
 * get the database to be used in resolving the query qstr.  Qstr is modified
 * to remove the db identifier.  The db identifier can have any of the following
 * forms:
 *        ,db                 :  system database
 *        system,db           :  system database if system == locsysname
 *        !user,db            :  database in .nsrc file of login user
 *        system!user,db      :  same as !user,db if system == locsysname
 *        (none)              :  system database if no db attribute
 *
 * The first valid db attribute in qstr is used as the db identifier.  If no
 * db attribute is found, the system database is used.  If only invalid attributes
 * are found, no database is used, i.e. (Db *) 0 is returned.
 */
Db *
getdb(char *qstr, int *hint)
{
	char		*commaloc;
	char		*dbname;
	char		*uname;
	char		*cp;
	struct passwd 	*pw;
	char		failurepending = 0;
	char		dbnamebuf[MAXDBNAME+1];
	Db		*d;

	for (; commaloc = strchr(qstr, ','); qstr = commaloc + 1) {
		if ((commaloc[1] == 'd') && (commaloc[2] == 'b') &&
		  ((commaloc[3]=='\n')||(commaloc[3]=='\t')||(commaloc[3]==' '))) {
			*commaloc = 0;
			for (dbname = commaloc - 1; dbname >= qstr; dbname--) {
				if ((*dbname == '\t') || (*dbname == ' ')) {
					dbname++;
					break;
				}
			}
			if (dbname < qstr)
				dbname++;
			if (dbname == commaloc) {
				commaloc[0] = commaloc[1] = commaloc[2] = ' ';
				break;	/* System wildcard: ",db" */
			}
			uname = strchr(dbname,'!');
			if (uname != dbname) {
				if (uname)
					*uname = 0;
				if (fstrcmp(dbname, locsysname)) {
					if (uname) *uname = '!';
					*commaloc = ',';
					failurepending = 1;
					continue;	/* System name differs */
				}
				if (!uname) {
					for (cp = dbname; cp < commaloc + 3; cp++)
						*cp = ' ';
					break;	/* Sys name matches: "system,db" */
				}
				*uname = '!';
			}
			if (pw = getpwnam(uname+1)) {
				if (d = finddb(&pw->pw_uid, hint)) {
					if (checkfiles(d))
						parsefiles(d);
				} else {
					strncpy(dbnamebuf,locsysname,MAXDBNAME);
					strncat(dbnamebuf,uname,MAXDBNAME);
					d = newdb(&pw->pw_uid, pw->pw_dir, 
						".nsrc", dbnamebuf, hint);
					parsefiles(d);
				}
				for (cp = dbname; cp < commaloc + 3; cp++)
					*cp = ' ';
				return d;  /* User name matches:             */
					   /* "system!user,db" or "!user,db" */
			}
			*commaloc = ',';
			failurepending = 1;
			continue;			/* User name differs */
		}
		qstr = commaloc + 1;
	}
	if (failurepending) 
		return (Db *) 0;			/* No db field matched 	 */

	return finddb((int *)0, hint);			/* System name default */
}
