/*
 * A set of support routines for /etc/rmtab file managment. These routines
 * are called from mountd.c.
 *
 * Written and Copyright by Dariush Shirazi, <dshirazi@uhl.uiowa.edu>
 *
 */

#include "nfsd.h"
#include "rmtab.h"

static _PRO(char * rmtab_gethost, (struct svc_req *));
static _PRO(int    rmtab_insert, (char *, char *));
static _PRO(void   rmtab_file, (char));

/*
 * global top to linklist
 */

static mountlist rmtablist = NULL;

/*
 * rmtab_add_client -- if client+path not in the list, add them.
 */

void rmtab_add_client(path, rqstp)
dirpath path;
struct svc_req *rqstp;
{
	char		*hostname;

	hostname = rmtab_gethost(rqstp);
	if (hostname != NULL) {
		rmtab_file('r');
		if (rmtab_insert(hostname, path))
			rmtab_file('w');
	}
}

/*
 * rmtab_lst_client -- return the top pointer.
 */

mountlist *rmtab_lst_client()
{
	rmtab_file('r');
	return(&rmtablist);
}

/*
 * rmtab_del_client -- delete a client+path
 */

void rmtab_del_client(path, rqstp)
dirpath path;
struct svc_req *rqstp;
{
	int		p0, p1, changed;
	char		*hostname;
	mountlist	cur, prv;

	hostname = rmtab_gethost(rqstp);
	Dprintf(D_RMTAB, "\trmtab_del path='%s' host='%s'\n", path, hostname);
	if (hostname == NULL)
		return;

	rmtab_file('r');
	changed = 0;

	for (cur = rmtablist, prv = NULL; cur; cur = cur->ml_next) {
		p0 = strcmp(cur->ml_hostname, hostname);
		p1 = strcmp(cur->ml_directory, path);
		if (p0 == 0 && p1 == 0)
			break;				/* already exists */
		prv = cur;
	}

	if (cur) {
		/*
		 * don't free both ml_hostname & ml_directory.
		 * See rmtab_insert for details.
		 */
		free(cur->ml_hostname);
		if (prv)
			prv->ml_next = cur->ml_next;
		else
			rmtablist    = cur->ml_next;
		free(cur);

		changed = 1;
	}

	if (changed)
		rmtab_file('w');
}

/*
 * rmtab_mdel_client -- delete all the entry points for a client
 */

void rmtab_mdel_client(rqstp)
struct svc_req *rqstp;
{
	int		p0, changed;
	char		*hostname;
	mountlist	cur, prv, tmp;

	hostname = rmtab_gethost(rqstp);
	Dprintf(D_RMTAB, "\trmtab_mdel host='%s'\n", hostname);
	if (hostname == NULL)
		return;

	rmtab_file('r');
	changed = 0;

	prv     = NULL;
	cur     = rmtablist;
	while (cur) {
		p0 = strcmp(cur->ml_hostname, hostname);
		if (p0 == 0) {
			/*
			 * don't free both ml_hostname & ml_directory.
			 * See rmtab_insert for details.
			 */
			tmp = cur;
			cur = cur->ml_next;
			if (prv)
				prv->ml_next = cur;
			else
				rmtablist    = cur;
			free(tmp->ml_hostname);
			free(tmp);

			changed = 1;
		} else if (p0 < 0) {
			prv = cur;
			cur = cur->ml_next;
		} else
			break;				/* not found */
	}

	if (changed)
		rmtab_file('w');
}

/*
 * rmtab_gethost -- return the hostname
 */

static char *rmtab_gethost(rqstp)
struct svc_req *rqstp;
{
	struct hostent *hp;
        struct in_addr addr;

	addr = svc_getcaller(rqstp->rq_xprt)->sin_addr;
	hp   = gethostbyaddr((char *) &addr, sizeof(addr), AF_INET);

	if (hp)
		return((char *) hp->h_name);

	return((char *) NULL);
}

/*
 * rmtab_insert -- a sorted link list
 */

static int rmtab_insert(hostname, path)
char *hostname, *path;
{
	int		hostlen, p0, p1;
	mountlist	cur, prv;

	Dprintf(D_RMTAB, "\trmtab_insert path='%s' host='%s'\n",
				path, hostname);

	for (cur = rmtablist, prv = NULL; cur; cur = cur->ml_next) {
		p0 = strcmp(cur->ml_hostname, hostname);
		p1 = strcmp(cur->ml_directory, path);
		if (p0 > 0 || (p0 == 0 && p1 > 0))
			break;				/* insert here */
		else if (p0 == 0 && p1 == 0)
			return(0);			/* already exists */
		prv = cur;
	}

	if ((cur = (mountlist) malloc(sizeof(mountbody))) == NULL) {
		Dprintf(L_ERROR, "failed to allocate memory for mountbody\n");
		return(0);
	}
	/*
	 * since the data we are storing is really small (ie. h.x.y.z:/cur),
	 * allocate one memory unit for both and split it.
	 */
	hostlen = strlen(hostname);
	if ((cur->ml_hostname = (char *) malloc(hostlen+strlen(path)+2)) == NULL) {
		Dprintf(L_ERROR, "failed to allocate memory for mountlist buffer\n");
		free(cur);
		return(0);
	}
	cur->ml_directory = cur->ml_hostname + (hostlen + 1);

	strcpy(cur->ml_hostname, hostname);
	strcpy(cur->ml_directory, path);

	if (prv) {
		cur->ml_next = prv->ml_next;
		prv->ml_next = cur;
	} else {
		cur->ml_next = rmtablist;
		rmtablist    = cur;
	}
	return(1);
}

/*
 * rmtab_file -- read/write the mount list from/to rmtab file.
 */

static void rmtab_file(op)
char op;
{
	register int	c, len;
	register char	*p;
	char		buff[256], *host, *path;
	FILE		*fp;
	mountlist	cur;
	struct stat	newstat;

	static time_t 	old_st_mtime = 0;

	if (op == 'r') {				/* read&update llist */

		/*
		 * get a new stat; if file not there, create it
		 */
		if (stat(_PATH_RMTAB, &newstat)) {
			int	zappa;

			if ((zappa = creat(_PATH_RMTAB, 0644)) < 0) {
				Dprintf(L_ERROR, "failed to create '%s'\n",
								_PATH_RMTAB);
				return;
			}
			close(zappa);

			if (stat(_PATH_RMTAB, &newstat)) {
				Dprintf(L_ERROR, "failed to stat '%s'\n",
								_PATH_RMTAB);
				return;
			}
			old_st_mtime = newstat.st_mtime;
			return;
		}

		if (old_st_mtime == newstat.st_mtime)
			return;				/* no change */

		if ((fp = fopen(_PATH_RMTAB, "r")) == NULL) {
			Dprintf(L_ERROR, "failed to open '%s'\n", _PATH_RMTAB);
			return;
		}

		while (rmtablist) {			/* free the old list */
			cur       = rmtablist;
			rmtablist = rmtablist->ml_next;
			/*
			 * don't free both ml_hostname & ml_directory.
			 * See rmtab_insert for details.
			 */
			free(cur->ml_hostname);
			free(cur);
		}

		while (! feof(fp)) {
			/*
			 * the reason this looks worse than it should is so
			 * we don't have to do bunch of passes on the buff.
			 * (fgets,strlen,strchr...)
			 */
			p    = buff;
			host = buff;
			path = NULL;
			len  = c = 0;
			while (!feof(fp) && (c = fgetc(fp))!='\n' && len<255) {
				if (c == ':') {
					c    = '\0';
					path = p+1;
				}
				*p++ = (char) c;
				len++;
			}
			*p = '\0';

			while (!feof(fp) && c != '\n')	/* skip if line > 255 */
				c = fgetc(fp);

			if (path)			/* skip bad input */
				if (*host && *path)
					rmtab_insert(host, path);
		}
		fclose(fp);
		old_st_mtime = newstat.st_mtime;

	} else if (op == 'w') {				/* write from llist */

		if ((fp = fopen(_PATH_RMTAB, "w")) == NULL) {
			Dprintf(L_ERROR, "failed to open '%s'\n", _PATH_RMTAB);
			return;
		}
		for (cur = rmtablist; cur; cur = cur->ml_next)
			fprintf(fp, "%s:%s\n", cur->ml_hostname,
					       cur->ml_directory);
		fclose(fp);

		if (stat(_PATH_RMTAB, &newstat)) {
			Dprintf(L_ERROR, "failed to stat '%s'\n", _PATH_RMTAB);
			fclose(fp);
			return;
		}
		old_st_mtime = newstat.st_mtime;

	} else
		Dprintf(L_ERROR, "rmtab_file bad flag '%c'\n", op);
}
