#ifndef lint
static char sccsid[] = "@(#)rfsutils.c	1.2 (UKC) 24/5/85";
#endif  lint

/*	rfsutils.c
 *		utilities required by rfscalls.c which need real system calls
 *		and functions from trans.c and libtsb.a
 *
 *	exports:
 *		rfsinitialise(): initialise data structures for this module
 *		int rfsfirstcall: has rfsinitialise() not been called?
 *		rfsparse(): take raw filename; detect local/remote
 *		rfsufalloc(): take real file descriptor and fd open to the host
 *			it lives on; return fake file descriptor for the user
 *		rfsufree(): take fake file descriptor and mark it as closed.
 *		rfsufile[]: table describing user file descriptors;
 *			exported for quick local/remote checks by calling prog
 */

#include <stdio.h>
#include <sys/errno.h>	/* for ENETUNREACH and ENOENT*/
#include <sys/types.h>	/* for succ. */
#include <sys/stat.h>	/* for struct stat in initialise() */
#include <sys/tsbsp.h>	/* for TS_ACCEPT and tscmd_t */
#include <sys/tserrno.h>/* for TE_BUSY */
#include "rfs.h"

char *rindex();
char *malloc();
char *strcpy();
char *strncpy();
tscmd_t *tsconnect();

extern int errno;

/*
 * rfsufile:	table describing the file descriptors in which the main program
 *		deals. Contains the file descriptor open to the host upon which
 *		the file is *really* open, and the actual file descriptor on
 *		that (or this) host.
 *
 *		_NFILE is the maximum number of open file descriptors, almost
 *		always == 20. From stdio.h. Dunno whether it is standard or not.
 */

rfsufile_t  rfsufile[_NFILE];

int rfsfirstcall = 1;

rfsinitialise()
{
	register i;
	struct stat stbuf;
	
	for (i=0; i<_NFILE; i++) {
		/* fstat only ever fails if the fd is closed */
		if (fstat(i, &stbuf) == 0) {
			rfsufile[i].where = LOCAL;
			rfsufile[i].fd = i;
		} else {
			rfsufile[i].where = CLOSED;
		}
	}
	rfsfirstcall = 0;
}

/*
 *	ufalloc: allocate user file descriptor in same manner as kernel
 *	ie lowest closed file descriptor first and associate it with
 *	real file descriptor fd on the host to which we are connected by
 *	file descriptor tsfd.
 *
 *	Some programs say things like:
 *		close(1);
 *		open("outputfile",1);
 *	and rely on getting fd 1.
 *
 *	tsfd may == LOCAL indicating a local real fd or	CLOSED if it is so.
 */

int
rfsufalloc(where,realfd)
{
	register i;

	for (i=0; i < _NFILE; i++) {
		if (rfsufile[i].where == CLOSED) {
			rfsufile[i].where = where;
			rfsufile[i].fd = realfd;
#ifdef DEBUG
fprintf(stderr, "ufalloc: allocating real fd %d on tsfd %d to ufd %d\n",realfd,where,i);
#endif
			return(i);
		}
	}
	return(-1);	/* 20 file descriptors already open */
}

/*
 *	This ufd is closed; may be allocated again.
 */

rfsufree(ufd)
{
	WHERE(ufd) = CLOSED;
}

/* rfsparse:
 *	string argument is an unprocesses filename which may be local or remote.
 *	returns a struct rfsfile which contains two elements; a pointer to the
 *	actual filename with the hostname stripped of (if necessary) and
 *	the number of the file descriptor open to the remote machine if it is a
 *	remote file, or LOCAL (-1) if it is so, CLOSED (-2) if it is a remote
 *	file but is inaccessable (eg, no such host) in which case errno is set
 *	to something appropriate. The string pointed to by the argument must not
 *	be modified; copy part of it if necessary.
 *
 *	If this is to be compiled with a compiler which does not support
 *	structure assignment, make it return a pointer to said structure.
 *
 *	Local filenames are of the form	[^:]*
 *	Remote filenames are		[^:]*:.*
 *		where the part before the first colon is the hostname and
 *		the part after is the actual filename.
 *
 *	Remote filenames of the form "host:user:filename" are projected where
 *	"user" is the name of the account on the remote machine under whose
 *	authority the access is to be made.
 *	This will be implemented along with the validation software.
 *
 *	struct rfsfile {
 *		int tsfd;
 *		char *name;
 *	};
 */

struct rfsfile
rfsparse(name)
char *name;
{
	struct rfsfile rfsfile;
	char host[256];
	register int hostnamelen;

	if ( (rfsfile.name = rindex(name, ':')) == NULL ) {
		rfsfile.name = name;
		rfsfile.tsfd = LOCAL;
	} else {
		/* It is a remote file */
		hostnamelen = rfsfile.name - name;
		(void) strncpy(host, name, hostnamelen);
		host[hostnamelen] = '\0';
		if ( (rfsfile.tsfd = rfsgtsfdbhn(host)) == -1) {
			rfsfile.tsfd = CLOSED;
		}
	}
	rfsfile.name++;	/* one on from the colon */
	return (rfsfile);
}

/*
 *	rfsgtsfdbhn: remote file system get transport service file descriptor by host name (!)
 *	return the number of a fd open to tsb port connected to a server
 *	on that machine. If there is not already a conn to it, establish one.
 *	return -1 if connection cannot be made to remote host.
 */

static char * rhostname[_NFILE];	/* name of host to which we are connected by a tsb port on this file descriptor */
					/* indexed by the open file descriptor */
					/* easy for name lookup, kernel guarantees no clashes */

static int
rfsgtsfdbhn(host)
char *host;
{
	register i;

	for (i=0; i<_NFILE; i++) {
		/* first test saves a little time */
		if (rhostname[i] && strcmp(host, rhostname[i]) == 0) return(i);
	}
	
	/* There is no existing connection to that host */
	i = estabsrvonhost(host);
	if (i == -1) return(-1);
	rhostname[i] = malloc( (unsigned) strlen(host));
	(void) strcpy(rhostname[i], host);
	return(i);
}

/*
 *	Esatablish connection to a server on a named host.
 *	Return the file descriptor opened to it, or -1 on failure
 *	(set errno accordingly)
 */

static int
estabsrvonhost(host)
char *host;
{
	register tsfd;
	register i;
	register tscmd_t * reply;
	char called[256];

	if ((tsfd = gettsb()) == -1) {
		errno = ENETUNREACH;	/* nearest approximation */
		return(-1);
	}
	(void) sprintf(called,"%s/srv/rpcsrv",host);
	i=0;
again:	
	reply = tsconnect(tsfd, called, "rfs", NULLP, NULLP);
	switch ( reply->ts_command ) {
	case TS_ACCEPT:
		/* validation goes here; THIS IS IMPORTANT */
		return(tsfd);

	case TS_RESET:
		errno = ECONNRESET;
		return(-1);

	case TS_DISCONNECT:
		switch ( reply->ts_par[0].ts_data[0] ) {
		case (char) TE_BUSY:
			if (++i < 12) {
				sleep(1);
				goto again;
			}
			errno = ETIMEDOUT;
			break;
		case TE_NSNONAME: /* Illegal name sent to name server */
			errno = EHOSTUNREACH; /* No route to host */
			break;
		default:
			errno = ECONNREFUSED;
		}
		return(-1);

	default:
		errno = ECONNREFUSED;
		return(-1);
	}
}
