#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/label.h>
#include <signal.h>
#include <ipc.h>
#include <errno.h>
#include <libc.h>
#include <pwd.h>
#include <grp.h>

typedef struct {
	char *address;
	char *arg;
	char *mpoint;
	char *callusr;
	int rootino;
	dev_t rootdev;
	char proto;
	char stat;
	char devnum;
	char debug;
	int retry;
} Friend;

#define	SOK	01	/* filesystem running ok */
#define	SFOUND	02	/* found in most recent friends pass */

#define	MAXRETRY	60	/* slow down after this many retries */
#define	LONGRETRY	20	/* after slowdown, retry after this many cycles */

#define	MAXFRIENDS 128
Friend friends[MAXFRIENDS];	/* should probably be dynamic */

#define	MAXMSG	5	/* largest message 5*MSGBLOCK -- known to kernel too */
#define	MSGBLOCK 1024

#define	NBFS	4	/* our filesystem type */

char frfile[] = "/usr/netb/friends";	/* should be an argument */

char *estr();
Friend *findmpoint();

main(argc, argv)
int argc;
char **argv;
{
	Friend f;
	int fd;

	if (argc <= 1) {
		mkdaemon();
		rundaemon();
	}
	if (argc < 6 || argc > 8) {
		fprintf(stderr, "usage: setup netaddr arg mountpoint protocol devnum [debug userid]\n");
		exit(1);
	}
	f.address = argv[1];
	f.arg = argv[2];
	f.mpoint = argv[3];
	f.proto = argv[4][0];
	f.devnum = atoi(argv[5]);
	if (argc <= 6)
		f.debug = 0;
	else
		f.debug = atoi(argv[6]);
	if (argc <= 7)
		f.callusr = "daemon";
	else
		f.callusr = argv[7];
	if ((fd = callsys(&f)) < 0) {
		fprintf(stderr, "%s %s %c %s: %s\n", f.address, f.arg, f.proto, f.callusr, errstr);
		exit(1);
	}
	if (setup(fd, &f) < 0)
		exit(1);
	exit(0);
}

nulltrap(s)
{
	signal(s, nulltrap);
}

/*
 * prepare to be a daemon
 */

mkdaemon()
{
	switch (fork()) {
	case -1:
		perror("fork");
		exit(1);
	default:	/* parent */
		exit(0);
	case 0:		/* child */
		break;
	}
	signal(SIGINT, SIG_IGN);
	signal(SIGQUIT, SIG_IGN);
	signal(SIGHUP, SIG_IGN);
	signal(SIGPIPE, SIG_IGN);	/* needed? */
	signal(SIGALRM, nulltrap);	/* miscellaneous alarms */
}

/*
 * the daemon:
 * once a minute,
 *	see if the filesystems we've already mounted are ok
 *	see if the friends file has changed, and read it if so
 *	try to mount any filesystem that isn't
 */

rundaemon()
{
	static time_t fmtime;
	struct stat st;
	register Friend *fp;
	int fd;

	for (;; sleep(60)) {
		for (fp = friends; fp < &friends[MAXFRIENDS]; fp++) {
			if (fp->address == NULL || (fp->stat & SOK) == 0)
				continue;
			alarm(60);
			if (stat(fp->mpoint, &st) < 0) {
				plog("stat %s: %s\n", fp->mpoint, estr());
				fp->stat &=~ SOK;
			} else if (st.st_dev != fp->rootdev
			       ||  st.st_ino != fp->rootino) {
				plog("%s: not root: want 0x%x:%d, have 0x%x:%d\n",
				  fp->mpoint, fp->rootdev, fp->rootino,
				  st.st_dev, st.st_ino);
				fp->stat &=~ SOK;
			}
			alarm(0);
		}
		if (stat(frfile, &st) < 0)
			plog("stat %s: %s\n", frfile, estr());
		else if (fmtime != st.st_mtime) {
			fmtime = st.st_mtime;
			newfriends();
		}
		for (fp = friends; fp < &friends[MAXFRIENDS]; fp++) {
			if (fp->address == NULL || fp->stat & SOK)
				continue;
			if (fp->retry > MAXRETRY) {
				if (fp->retry < MAXRETRY+LONGRETRY) {
					fp->retry++;
					continue;	/* skip this time */
				}
				fp->retry = MAXRETRY;	/* do this one, then skip */
			}
			plog("%s: calling %s %s %c %s\n",
				fp->mpoint, fp->address, fp->arg, fp->proto, fp->callusr);
			if ((fd = callsys(fp)) < 0) {
				plog("%s: %s\n", fp->address, errstr);
				fp->retry++;
				continue;
			}
			if (setup(fd, fp) < 0) {
				plog("%s: setup failed\n", fp->mpoint);
				alarm(30);
				close(fd);
				alarm(0);
				fp->retry++;
				continue;
			}
			close(fd);
			plog("%s started\n", fp->mpoint);
			fp->stat |= SOK;
			fp->retry = 0;
		}
	}
}

/*
 * reread the friends file
 * the algorithm is quadratic,
 * but we don't do it often
 */

#define	ASIZE	8
newfriends()
{
	FILE *f;
	char line[512];		/* huge */
	char *args[ASIZE];
	char proto, devnum, debug;
	char *callusr;
	register Friend *fp;

	if ((f = fopen(frfile, "r")) == NULL) {
		plog("can't open %s: %s\n", frfile, estr());
		return;
	}
	setfields(" \t\n");
	for (fp = friends; fp < &friends[MAXFRIENDS]; fp++)
		fp->stat &=~ SFOUND;
	while (fgets(line, sizeof(line), f)) {
		if (line[0] == '#')
			continue;
		/*
		 * backward compatibility:
		 * login name may be omitted
		 */
		switch (getmfields(line, args, ASIZE)) {
		default:
			continue;

		case 6:
			callusr = "daemon";
			break;

		case 7:
			callusr = args[6];
			break;
		}
		proto = args[3][0];
		devnum = atoi(args[4]);
		debug = atoi(args[5]);
		fp = findmpoint(args[2]);
		if (fp->address			/* didn't return an empty */
		&&  strcmp(fp->address, args[0]) == 0
		&&  strcmp(fp->arg, args[1]) == 0
		/* mount point already checked */
		&&  strcmp(fp->callusr, callusr) == 0
		&&  fp->proto == proto
		&&  fp->devnum == devnum
		&&  fp->debug == debug) {
			fp->stat |= SFOUND;
			fp->retry = 0;
			continue;		/* it hasn't changed */
		}
		if (fp->address) {		/* was already there, clear it */
			plog("changing %s\n", fp->mpoint);
			free(fp->address);
			free(fp->arg);
			free(fp->mpoint);
			free(fp->callusr);
		}
		fp->address = strdup(args[0]);
		fp->arg = strdup(args[1]);
		fp->mpoint = strdup(args[2]);
		fp->callusr = strdup(callusr);
		fp->proto = proto;
		fp->devnum = devnum;
		fp->debug = debug;
		fp->stat = SFOUND;	/* nb OK not set yet */
		fp->retry = 0;
	}
	fclose(f);
	for (fp = friends; fp < &friends[MAXFRIENDS]; fp++) {
		if (fp->address == NULL || fp->stat & SFOUND)
			continue;
		plog("dropping %s\n", fp->mpoint);
		free(fp->address);
		free(fp->arg);
		free(fp->mpoint);
		fp->address = NULL;
	}
}

Friend *
findmpoint(mp)
char *mp;
{
	register Friend *fp, *ep;
	static Friend junkfriend;

	ep = NULL;
	for (fp = friends; fp < &friends[MAXFRIENDS]; fp++) {
		if (fp->address == NULL) {
			if (ep == NULL)
				ep = fp;
			continue;
		}
		if (strcmp(fp->mpoint, mp) == 0)
			return (fp);
	}
	if (ep)
		return (ep);
	plog("too many friends; no room for %s\n", mp);
	return (&junkfriend);
}

/*
 * call a server
 * according to network protocol
 */

callsys(fp)
register Friend *fp;
{
	register int fd;

	alarm(120);
	setlogname(fp->callusr);	/* who the network thinks we are */
	switch (fp->proto) {
	case 'd':	/* `datakit' -- general connection */
		fd = ipcopen(ipcpath(fp->address, "dk", "fsb"), "heavy hup");
		break;

	case 'e':	/* same, but use rexec -- why bother? */
		fd = ipcexec(ipcpath(fp->address, "dk", "exec"), "heavy hup", fp->arg);
		break;

	case 't':
		fd = ipcrexec(fp->address, "heavy hup", fp->arg);
		break;

	default:
		errstr = "unknown setup protocol";
		fd = -1;
		break;
	}
	alarm(0);
	return (fd);
}

/*
 * initial protocol with server:
 * client sends 16 bytes:
 *	max buffer size in 1024-byte units
 *	device number in use
 *	protocol letter
 *	debugging flag
 *	12 unused
 * server sends one byte: 1
 * client sends a list of userids: maxsize bytes
 *	if list won't fit in one buffer, last username is `:'
 *	server acks with one byte 012; client sends more
 * server sends one byte: 2
 * client sends a list of groupids: maxsize bytes
 *	if list won't fit in one buffer, last groupname is `:'
 *	server acks with one byte 013; client sends more
 * server sends one byte: 3
 * then we mount it, and further messages come from the kernel
 */

#define	M1LEN	16

setup(fd, fp)
int fd;
register Friend *fp;
{
	char m[M1LEN];
	register int n;
	struct label lab, clab;
	struct stat st;

	getplab(&lab, &clab);
	lab.lb_t = lab.lb_u = 0;
	memset(m, 0, M1LEN);
	m[0] = MAXMSG;
	m[1] = fp->devnum;
	m[2] = fp->proto;
	if (m[2] == 'e')
		m[2] = 'd';	/* hack */
	m[3] = fp->debug;
	alarm(600);
	if ((n = write(fd, m, M1LEN)) != M1LEN) {
		plog("%s setup 1: write returned %d; %s\n", fp->mpoint, n, estr());
		alarm(0);
		return (-1);
	}
	if (getresp(fd, 1, fp->mpoint) == 0)
		return (-1);
	alarm(600);
	if (senduid(fd, fp->mpoint) < 0) {
		alarm(0);
		return (-1);
	}
	alarm(600);
	if (sendgid(fd, fp->mpoint) < 0) {
		alarm(0);
		return (-1);
	}
	alarm(60);
	funmount(fp->mpoint);	/* bad -- might unmount wrong fs */
	if (fmount5(NBFS, fd, fp->mpoint, makedev(fp->devnum, 0), &lab) < 0) {
		plog("%s: fmount: %s\n", fp->mpoint, estr());
		alarm(0);
		return (-1);
	}
	alarm(60);
	if (stat(fp->mpoint, &st) < 0) {
		plog("%s: initial root stat: %s\n", fp->mpoint, estr());
		alarm(0);
		return (-1);
	}
	alarm(0);
	fp->rootino = st.st_ino;
	fp->rootdev = st.st_dev;
	return (0);
}

/*
 * get a single-byte response from server
 * try to cope with unexpected noise,
 * usually something like /etc/motd or `You have mail.'
 * fortunately server responses are all characters
 * unlikely to appear in ordinary ASCII text (viz. octal 1 2 3).
 * it is more important to copy the junk to the logfile
 * (so someone can get rid of it) than to survive it
 */

getresp(fd, id, fs)
int fd, id;
char *fs;
{
	register int i, n;
	char m[400];
	int tries, tmo;

	tmo = 600;		/* timeout */
	for (tries = 0; tries < 5 && tmo > 0; tries++) {
		alarm(tmo);
		n = read(fd, m, sizeof(m)-1);
		tmo = alarm(0);		/* assume errno untouched */
		m[n] = 0;
		if (n <= 0) {
			plog("%s setup %d: read returned %d: %s\n", fs, id, n, estr());
			return (0);
		}
		for (i = 0; i < n; i++)
			if (m[i] == id)
				break;
		if (i) {
			m[i] = 0;
			plog("%s setup %d: read junk: %s\n", fs, id, m);
		}
		if (i < n)		/* found it */
			return (1);
	}
	plog("%s setup %d: no response\n", fs, id);
	return (0);
}

#define	SLOP	50

senduid(fd, mpoint)
int fd;
char *mpoint;
{
	char idbuf[MAXMSG*MSGBLOCK+SLOP];
	char onebuf[SLOP];
	register char *p;
	register struct passwd *pw;
	register int n, wn;

	setpwent();
	p = idbuf;
	while ((pw = getpwent()) != NULL) {
		sprintf(onebuf, "%s %d\n", pw->pw_name, pw->pw_uid);
		n = strlen(onebuf);
		if (n > &idbuf[MAXMSG*MSGBLOCK] - p - 4) { /* room for ": 1\n" */
			strcpy(p, ": 1\n");	/* `more coming' */
			if ((wn = write(fd, idbuf, MAXMSG*MSGBLOCK)) != MAXMSG*MSGBLOCK) {
				plog("%s: write uid: %d; %s\n", mpoint, wn, estr());
				return (-1);
			}
			if (getresp(fd, 012, mpoint) == 0)
				return (-1);
			p = idbuf;
		}
		strcpy(p, onebuf);
		p += n;
	}
	endpwent();
	if ((wn = write(fd, idbuf, MAXMSG*MSGBLOCK)) != MAXMSG*MSGBLOCK) {
		plog("%s: write uid: %d; %s\n", mpoint, wn, estr());
		return (-1);
	}
	if (getresp(fd, 2, mpoint) == 0)
		return (-1);
	return (0);
}

sendgid(fd, mpoint)
int fd;
char *mpoint;
{
	char idbuf[MAXMSG*MSGBLOCK+SLOP];
	char onebuf[SLOP];
	register char *p;
	register struct group *gr;
	register int n, wn;

	setgrent();
	p = idbuf;
	while ((gr = getgrent()) != NULL) {
		sprintf(onebuf, "%s %d\n", gr->gr_name, gr->gr_gid);
		n = strlen(onebuf);
		if (n > &idbuf[MAXMSG*MSGBLOCK] - p - 4) { /* room for ": 1\n" */
			strcpy(p, ": 1\n");	/* `more coming' */
			if ((wn = write(fd, idbuf, MAXMSG*MSGBLOCK)) != MAXMSG*MSGBLOCK) {
				plog("%s: write gid: %d; %s\n", mpoint, wn, estr());
				return (-1);
			}
			if (getresp(fd, 013, mpoint) == 0)
				return (-1);
			p = idbuf;
		}
		strcpy(p, onebuf);
		p += n;
	}
	endgrent();
	if ((wn = write(fd, idbuf, MAXMSG*MSGBLOCK)) != MAXMSG*MSGBLOCK) {
		plog("%s: write gid: %d; %s\n", mpoint, wn, estr());
		return (-1);
	}
	if (getresp(fd, 3, mpoint) == 0)
		return (-1);
	return (0);
}

/*
 * error logging stuff
 * just print to stderr for now
 */

/* PRINTFLIKE1 */
plog(s, a, b, c, d, e, f)
char *s;
{
	long now;

	time(&now);
	fseek(stderr, 0L, 2);
	fprintf(stderr, "%.15s ", ctime(&now)+4);	/* trim day of week, year */
	fprintf(stderr, s, a, b, c, d, e, f);
	fflush(stderr);
}

char *
estr()
{
	static char buf[] = "Error -2147483648";

	if (errno < 0 || errno >= sys_nerr) {
		sprintf(buf, "Error %d", errno);
		return (buf);
	}
	return (sys_errlist[errno]);
}
