AUSAM/source/S/log.c

Compare this file to the similar file:
Show the results in this format:

/*
 *	log  remote_tty
 *
 *  File exchange package for remote unices.
 *
 *  Also has transparent mode for talking to
 *  other operating systems as if local tty.
 *
 *  For the following reasons this program must be
 *  setuid to root for successful use:
 *	1. The tty to be used for communication with other machine
 *	   is to be owned by root and is to have a mode of 600.
 *	2. Needs to run at fairly high priority,
 *	   so nice will be used to obtain same.
 *	3. The connect system call only allowed to be
 *	   used by root, to prevent abuse.
 *
 * Multiple opens of the same tty are assumed
 *  to be taken care of by the appropriate tty
 *  drivers (eg dz & dj) supporting the notion
 *  of certain ttys being single open, and that
 *  ttys used by 'log' for communications are
 *  setup as single open.
 *
 *	Ian Johnstone - AGSM - 1976
 *	Peter Ivanov  - UNSW - 1976
 *
 *	Modified for flow control
 *	 Piers Lauder - Sydney University - Aug '77
 *
 *	Modified for binary file protocol
 *	 Piers Lauder - Sydney University - Sep '77
 *
 *	Modified for settable quit code & shell escape & default file args
 *	 Piers Lauder - Sydney University - Feb '78
 *	 Ian Johnstone - AGSM - Feb '78
 *
 *	Updated to use 'connect' system call.
 *	 Ian Johnstone - AGSM - MAY '78
 *
 *	Updated to use 'access' to validate file access,
 *	 since 'log' will always be setuid to root from
 *	 this time onwards.
 *	 Ian Johnstone - AGSM - AUG '78
 *	 Piers Lauder - Sydney University - Aug '78
 *
 *	Corrected a timing bug that only showed up when using
 *	 high-speed log lines (say >= 4800 baud).  Corrected
 *	 some minor bugs and generally tidied up the program
 *	 (rewrote some grotty procedures!).
 *	 Kevin Hill - UNSW - JAN '79
 *
 *	Enabled handling of multiple lines to remote site.  Lines
 *	 to be called (in order of trying) xxx,xxx1,xxx2,...xxx9.
 *	 This enables a user to merely say 'log xxx', to have a
 *	 log attempted on all appropraitely named lines.
 *	 Ian Johnstone - AGSM - AUG '79
 *
 *	To enable realistic use of the new 'disconnect' command
 *	 (disconnect breaks a connection) the log special file
 *	 being used by log is set up to be owned by the real
 *	 user of log so that that user may reasonably break his/her
 *	 own log !!
 *	 Ian Johnstone - AGSM - AUG '79
 *
 *	Fix some more little bugs.  Only the super-user may log to
 *	 a device not in the `quits' table.  `Access' used if the user
 *	 tries to change directory.  Change owner of remote special back
 *	 to root when the logger has finished.  Complain if super-user
 *	 tries to log to a non-existent device.
 *	 Kevin Hill - UNSW - AUG 79
 */

#include	<local-system>
#include	<errnos.h>

#ifdef	AUSAM
#include	<stat16.h>
	/*
	 *	Piers Lauder	Feb '78
	 *
	 *	Use 16-bit user id.
	 */
#else
#include	<stat.h>
#endif	AUSAM


#define	HIPRIORITY	-40
#define	USERPRI		0


/*
 * Regulate file transfer with a file protocol as follows:-
 *
 *  Blocks consist of a header followed by data followed by a trailer.
 *  The header contains a sequence number (0/1) and the size
 *   (transmitted twice, 2nd as complement of first).
 *  The trailer contains a longitudinal parity check of the data,
 *   followed by a sum check of the data.
 *
 *  Block format is
 *	STX SEQ SIZ ~SIZ ...DATA... LPC SUM
 *
 *  Blocks are acknowledged by a 2 byte reply consisting of
 *   ACK or NAK followed by the relevant sequence number.
 *
 * Note: that the DATAZ is really dependant on the accuracy
 *	 of the crc check.
 */

#define	REPLYZ		2
#define	RTYPE		0
#define	RSEQ		1

struct
{
	char r_typ;
	char r_seq;
}
reply;

#define	HEADER		4
#define	BSEQ		1
#define	TRAILER		2
#define OVERHEAD	(HEADER+TRAILER)
#define	DATAZ		64
#define	DATAZL		64L
#define	BUFZ		(DATAZ+OVERHEAD)

/*
 *  Timeouts - these must be tailored to the line speed,
 *   the basic formulae used being:-
 *	receive timeout = receive time + turnaround time
 *	transmit   "    = 2 * receive timeout
 *	(all times in seconds)
 */
#define	STARTTIMEOUT	10
#define	FLUSHTIMEOUT	1
#define	TURNAROUND	3
#define	XRETRYS		4
#define	RRETRYS		5
#define	GARBAGE		2	/* max garbage chars tolerated before reset */
#define	EXTA		200	/* per DZ11 */
#define	EXTB		1920	/* per DZ11 */
#define	DEFAULTRATE	120	/* 1200 baud default */

int	byterate[16]	/* per DH11 */
{
	0,5,8,10,15,15,20,30,60,120,
	180,240,480,960,EXTA,EXTB
};

int	xtimeout, rtimeout;
long	xrate;			/* currently effective data transfer rate */
long	xtotal;			/* total bytes received/transmitted */
long	xtime;			/* total transmission time */

#define	SOH		001	/* remote accept */
#define	STX		002	/* start of block */
#define	ETX		003	/* remote finish */
#define	EOT		004	/* remote file access error */
#define	ENQ		005	/* remote timeout */
#define	ACK		006	/* xfer ok */
#define	DLE		020	/* file already exists */
#define	NAK		025	/* xfer failed */

#define	MSOH		"\01\01",2
#define	METX		"\03\03\03\03",4
#define	MEOT		"\04\04",2
#define	MTIMEOUT	"\05\05",2
#define	MDLE		"\020\020",2

#define	SIGTIMEOUT	15
#define	SIGQUIT		3
#define	SIGINTERRUPT	2

int	sav_modes;
#define	RAW	040
int	ttymode[3];
int	narg;
char	*cmd[10];
int	xfd	2;		/* fd for remote port */
char	otherm[]	"/dev/xxxxxxxxxxxxxxx";
char	c;
unsigned uid;
long	time();
long	filesize;

char exists[] "%s already exists - are you sure? ";

/* defines for use with access calls */
#define	R	04	/* read permission */
#define W	02	/* write permission */
#define	X	01	/* execute / directory search permission */


#define	ESCAPE	021	/* default escape char is ctrl/q */
#define	ESCAPED	"^q"	/* default escape description */
char	quit;		/* escape code */
char	*quitdescrpt;	/* escape code description */
struct
{
	char *name;
	char *descpt;	/* description of control code that follows */
	char  escape;	/* default escape code for this line */
	char  speed;	/* sets log line to this speed if non-zero */
}
quits[]
{
#ifdef	BASSER
	"agsm",		"^a",	001,	 9,
	"cyber",	"^d",   004,	 7,
#else
	"agsm",		"^a",	001,	12,
	"basser",	"^b",	002,	 9,
	"elec",		"^e",	005,	 0,
	"csu",		"^c",	003,	12,
	"forty",	"^f",	006,	13,
	"arch",		"^r",	022,	11,
	"cyber",	"^k",   013,	 7,
#endif	BASSER
};

struct	err
{
	int	e_count;
	char	*e_mess;
}
errs[]
{
#define	E_RETRYS	0
	{ 0,	"retrys" },
#define	E_SEQUENCE	1
	{ 0,	"seq" },
#define	E_SIZE		2
	{ 0,	"size" },
#define	E_OUTSIZE	3
	{ 0,	"outsize" },
#define	E_TIMEOUTS	4
	{ 0,	"timeouts" },
#define	E_CRC		5
	{ 0,	"crc" },
#define	E_WRITE		6
	{ 0,	"local write" },
#define	E_NAKS		7
	{ 0,	"naks" },
#define E_SYNC		8
	{ 0,	"sync" },
#define	E_READ		9
	{ 0,	"local read" }
};
#define	NERRS		((sizeof errs)/(sizeof errs[0]))

char	buf[BUFZ];

struct
{
	int hiword;
	unsigned loword;
};



/****************************************************************/

main (argc,argv)
register char	**argv;
register	argc;
{
	signal(SIGINTERRUPT, 1);
	signal(SIGQUIT, 1);
	nice(HIPRIORITY);
#ifdef	AUSAM
	uid = getreal();
#else
	uid = getuid() & 0377;
#endif	AUSAM

	sentinel();	/* catch all real-time limit expiries */

	switch (**argv)
	{
    case 'l':	if (okuse())
		{
			log (argc,argv);
			errors();	/* tell about the errors encountered */
		}
		else printf("Illegal usage!\n");
		break;

    case 's':	send (argc,argv);
		break;

    case 'r':	receive (argc,argv);
		break;

    default:	write(2, MEOT);
		return(1);
	}
}

/*****************************************************************/

log(argc,argv)
char **argv;
int argc;
{
	register char	*arg;
	register char	*otp;
	register int	i;
	extern char	*fquit();
	extern		finish();
	extern int errno;

	if (argc != 2)
	{
argerr:
		printf("Usage: log remote-special-file\n");
		exit(1);
	}

	arg = argv[1];
	otp = &otherm[5];
	for (i = 0; *otp++ = *arg++; ) if (++i >= 15) goto argerr;

	*otp-- = 0;		/* extra-null, address last null of name */
	while( (xfd = open(otherm,2)) < 0)
		if( (errno == EOPENFAIL) && (*otp!='9') )
		{
			if( *otp )
				(*otp)++;
			else
				*otp = '1';
		}
		else
		{
			if( errno == ENOENT && *otp)
				printf("%s busy\n",argv[1]);
			else
				perror(argv[1]);
			exit (2);
		}

	quit = ESCAPE;
	quitdescrpt = ESCAPED;
	for (i = 0; i < sizeof quits / sizeof quits[0]; i++)
	{
		arg = argv[1];
		otp = quits[i].name;
		while (*otp && *otp == *arg++) otp++;
		if (*otp == '\0')
		{
			quit = quits[i].escape;
			quitdescrpt = quits[i].descpt;
			break;
		}
	}
	if (i == sizeof quits / sizeof quits[0])	/* ran off the end */
	{
		if (uid)
		{
			printf("%s: illegal device\n", argv[1]);
			exit(1);
		}
		init_remote(xfd, 0);
	}
	else init_remote(xfd, quits[i].speed);
	chown(otherm,uid);	/* indicate current user of the log line */
				/* quicker than a ps */
	chmod(otherm,0600);	/* protect against other access, especially */
				/* from the disconnect command */
	signal(SIGINTERRUPT, finish);

	printf("* tty\n");
	tty();

	for (;;)
	{
		errors();
		printf("* ");

		switch(getline())
		{
	    case 'p':	if (narg == 3) put();	/* put a file to remote */
			break;

	    case 'g':	if (narg == 3) get();	/* get a file from remote */
			break;

	    case 't':	if (narg == 1) tty();	/* talk to remote as a terminal */
			break;

	    case 's':	if (narg == 1) finish();	/* stop this sillyness */
			break;

	    case 'q':	if (narg == 2)  quitdescrpt = fquit();	/* change quit code */
			break;

	    case '!':	sh();			/* local escape to shell */
			break;

	    case 'c':	if( narg == 2 )
			{
				if (access(cmd[1], X) ||
				 chdir(cmd[1]) == -1 ) perror(cmd[1]);
				break;
			}

	    default:	help();			/* unknown command */
			break;
		}
	}
}

/**************/
getline()
{
	static char	line[100];
	register char	*q;
	register	*p;
	register	toggle;

	if ((toggle = read(2,line,sizeof line)) == 1) return(-1);
	if (toggle <= 0) finish();
	if (line[toggle - 1] != '\n')
	{
		printf("Too verbose!!\n");
		do
			toggle = read(2, line, sizeof line);
		while (toggle > 0 && line[toggle - 1] != '\n');
		return -1;
	}
	for (q = line; q < &line[sizeof line] && *q == ' '; q++);
	if (*q == '!')
	{
		cmd[0] = q;
		narg = 1;
		q = &line[toggle - 1];
	}
	else
	{
		p = cmd; narg = 0; toggle = 1;
		for ( ; *q != '\n'; q++)
			if (*q == ' ')
			{
				toggle = 1;
				*q = 0;
			}
			else if (toggle)
			{
				*p++ = q;
				narg++;
				toggle = 0;
			}
	}
	*q = 0;
	q = cmd[0];
	if ((narg == 2) && ((*q == 'p') || (*q == 'g')))
	{
		*p++ = cmd[1];
		*p = 0;
		narg++;
	}
	if (narg == 0) return -1;
	else return *q;
}

/*******************/


fini_remote(fd)
register int fd;
{
	ttymode[2] = sav_modes;
	stty (fd, ttymode);
}

init_remote(fd, spd)
char spd;
register int fd;
{
	register speed;

	gtty (fd, ttymode);
	sav_modes = ttymode[2];
	ttymode[2] = RAW;
	if (spd) ttymode[0] = (spd << 8) | spd;
	stty (fd, ttymode);
	if ((speed = byterate[(ttymode[0] & 017)]) == 0)
		speed = DEFAULTRATE;
	xtimeout = 2 * (rtimeout = BUFZ / speed + TURNAROUND);
	xrate = (speed * DATAZL) / BUFZ;
}


set_rate(t)
long t;
{
	xtotal =+ filesize;
	xtime =+ t;
	xrate = xtotal/xtime;
}

/*********************/
finish()
{
	fini_remote(xfd);
	chown(otherm, 0);	/* change back to root */
#ifdef	BASSER
	chmod(otherm, 0606);	/* multi use line */
#endif	BASSER
	exit(0);
}

/*************************************************************/

char *fquit()
{
	static char	qs[3];
	register char	*qp = qs;

	if ((quit = *cmd[1] & 0177) == 037) return("US");	/* clear-screen char. */
	if (quit < 040)
	{
		*qp++ = '^';
		*qp++ = quit + 0140;
	}
	else *qp++ = quit;
	*qp = 0;
	return(qs);
}

/*************************************************************/

sh()
{
	char *args[4];
	register char **ap = args;
	register char *bp;
	register i = 0;
	static char shf[] "/bin/sh";

	cmd[0]++;
	*ap++ = shf+5;
	*ap++ = "-c";
	*ap++ = cmd[0];
	*ap = 0;

	if (!(i = fork()))
	{
		nice(USERPRI);
		setuid(uid);
		execv(shf, args);
		perror(shf);
		exit(-1);
	}
	if (i > 0)
	{
		signal(SIGINTERRUPT, 1);
		waitx(ap);
		signal(SIGINTERRUPT, finish);
	}
	else perror("try again");
}

/*************************************************************/

tty()
{

	/*
	 * logically connect local terminal to remote until quit char received
	 */
	printf("  %s -> %s   ('%s'=quit)\n",SYSTEMID,&otherm[5],quitdescrpt);
	if (connect(2, xfd, quit) < 0)
	{
		perror("connect failed");
		finish();
	}
}

/*************************************************************/

get()
{
	register	ofd;
	register	n;
	long		t;

	if ((ofd = accreat(cmd[2], 0)) < 0) return;

	transfer("send",cmd[1], 0);

	if (sync("open"))
	{
		printf(" -- receive rate approx. %D bytes/sec.\n", xrate);
		t = time();
		if (rproto(ofd, xfd) == 0)
		{
			if (!(t = time() - t))  t++;
			set_rate(t);
			printf("%s: %D bytes received in %D secs (%D bytes/sec)\n", cmd[2], filesize, t, filesize/t);
		}
		else printf(" -- transfer aborted after %D bytes\n", filesize);
	}

	lflush(xfd);
	close(ofd);
}

/*************************************************************/

put()
{
	register	ifd;
	register	n;
	register	fred;
	long		t;
	struct statbuf	statb;

	if (access(cmd[1],R) || ((ifd = open(cmd[1],0)) < 0))
	{
		perror(cmd[1]);
		return;
	}

	fstat(ifd, &statb);
	filesize.loword = statb.sb_size1;
	filesize.hiword = statb.sb_size0 & 0377;

	transfer("receive",cmd[2], 0);

	if ((n = sync("create")))
	{
		if (n < 0)
		{
			printf(exists, cmd[2]);
			fred = 0;
			if ((n = read(2, &c, 1)) == 1 && c == 'y') fred++;
			if (n == 1 && c != '\n')
				do
					n = read(2, &c, 1);
				while (n > 0 && c != '\n');
			if (fred == 0) return;
			lflush(xfd);
			transfer("receive", cmd[2], "-");
			if (sync("create") != 1) goto home;
		}

		printf(" -- estimated transmit time: %D secs\n", filesize/xrate);
		t = time();

		if ((n = xproto(ifd, xfd)) == 0)
		{
			if (!(t = time() - t))  t++;
			set_rate(t);
			printf("%s: %D bytes transmitted in %D secs (%D bytes/sec)\n", cmd[2], filesize, t, filesize/t);
		}
		else printf(" --%stransfer aborted after %D bytes\n", (n == 2) ? " remote terminate -- " : " ", filesize);
	}

home:
	lflush(xfd);
	close (ifd);
}

/*******************************/

transfer(s1, s2, s3)
char	*s1,*s2;
{
	register char	*p, *q;

	q = buf;

	*q++ = '@';
	*q++ = '@';	/* two will ALWAYS do the trick! */

	for (p = s1; *q++ = *p++; );
	q[-1] = ' ';
	for (p = s2; *q++ = *p++; );
	if (s3)
	{
		q[-1] = ' ';
		for (p = s3; *q++ = *p++; );
	}
	q[-1] = '\n';

	write (xfd, buf, q-buf);
}


/*******************************/

send(argc,argv)
char **argv;
{
	register  fd;

	init_remote(2, 0);

	if (argc != 2 || access(argv[1],R) || ((fd = open(argv[1],0))<0)) write(2, MEOT);
	else
	{
		write(2, MSOH);
		sleep(FLUSHTIMEOUT);
		xproto(fd, 2);
	}

	lflush(2);
	fini_remote(2);
}

/*******************************/

receive(argc,argv)
char **argv;
{
	register	fd;

	init_remote(2, 0);

	if (argc > 3 || (fd = accreat(argv[1], argc == 3 ? 2 : 1)) < 0) write(2, MEOT);
	else
	{
		write(2, MSOH);
		rproto(fd, 2);
	}

	lflush(2);
	fini_remote(2);
}

/********************************************************************/

int	timedout;

sentinel()
{
	signal(SIGTIMEOUT, sentinel);
	timedout++;
}


tread(f, b, s, timeout)
{
	clktim(timeout);
	timedout = 0;
	read(f, b, s);
	clktim(0);
	return(timedout);
}


lflush(fd)
{
	while (!tread(fd, &c, 1, FLUSHTIMEOUT));
	return(1);
}

/********************************************************************/

sync(remote_err)
char	*remote_err;
{
	do
	{
		if (tread(xfd, &c, 1, STARTTIMEOUT))
		{
			printf("no response from remote!\n");
			return(0);
		}
		if (c == EOT)
		{
			printf("can't %s remote file!\n", remote_err);
			return(0);
		}
		if (c == DLE) return -1;
	} while (c != SOH);

	lflush(xfd);
	return(1);
}

/******************************************************************/

xproto(local, remote)
{
	register  n, retrys, sync;

	buf[BSEQ] = 1;
	filesize = 0;

	while ((n = read(local, buf+HEADER, DATAZ)) >= 0)
	{
		crc(n);
		retrys = 0;

		do
		{
			write(remote, buf, n+OVERHEAD);

			sync = 0;
			while (tread(remote, &reply, 1, xtimeout) == 0)
			{
				switch (reply.r_typ)
				{
			     default:
					errs[E_SYNC].e_count++;
					if (++sync > GARBAGE)
						break;
					else
						continue;

			     case ENQ:
			     case ACK:
			     case NAK:
			     case ETX:
					tread(remote, &reply.r_seq, 1, xtimeout);
				}
				break;
			}

			if (reply.r_typ == ETX && reply.r_seq == ETX)
				return(n ? 2 : n);	/* remote finish */

		} while (

			  ( (sync > GARBAGE && lflush(remote))

			  || (timedout && ++errs[E_TIMEOUTS].e_count)

			  || (reply.r_typ != ACK && ++errs[E_NAKS].e_count)

			  || (reply.r_seq != buf[BSEQ] && ++errs[E_SEQUENCE].e_count)

			 )

			 && retrys++ < XRETRYS

			);

		errs[E_RETRYS].e_count =+ retrys;
		if (retrys > XRETRYS)
		{
			errs[E_RETRYS].e_count--;
			return(1);
		}

		if (n == 0)	/* eof */
			return(n);

		filesize =+ n;
	}

	errs[E_READ].e_count++;

	return(3);
}

/*******************************************************************/

/* case labels */
#define	NEWBLOCK	0
#define	SEQUENCE	1
#define	SIZE1		2
#define	SIZE2		3

rproto(local, remote)
{
	register  size, state, x;
	int  wretrys, lastsize, retrys, garbage;
	char  lastseq;

	state = NEWBLOCK;
	retrys = 0;
	reply.r_seq = 0;
	filesize = 0;
	lastseq = -1;
	garbage = 0;
	lastsize = 0;

	for (;;)
	{

		while (tread(remote, &c, 1, rtimeout))
			if (retrys++ < RRETRYS)
			{
				errs[E_TIMEOUTS].e_count++;
				write(remote, MTIMEOUT);
			}
			else
			{
	fail:
				write(remote, METX);
				return(1);
			}

		x = c & 0377;

		switch(state)
		{

	    case NEWBLOCK:
			if (x == STX)
			{
				state = SEQUENCE;
				retrys = 0;
				garbage = 0;
			}
			else
			{
				if (garbage++ >= GARBAGE)
				{
					lflush(remote);
					garbage = 0;
				}
			}
			break;

	    case SEQUENCE:
			if (x & ~1)
			{
				errs[E_SEQUENCE].e_count++;
fnak:
				state = NEWBLOCK;
				lflush(remote);
nak:
				reply.r_typ = NAK;
				errs[E_NAKS].e_count++;
rply:
				write(remote, &reply, REPLYZ);
				break;
			}
			reply.r_seq = x;
			state = SIZE1;
			break;

	    case SIZE1:
			if ((size = x) > DATAZ)
			{
				errs[E_OUTSIZE].e_count++;
				goto fnak;
			}
			state = SIZE2;
			break;

	    case SIZE2:
			state = NEWBLOCK;
			if (size != (~x & 0377))
			{
				errs[E_SIZE].e_count++;

				goto fnak;
			}
			if (size == 0)	/* eof */
			{
				write(remote, METX);
				return(0);
			}

			if ((tread(remote, buf+HEADER, size+TRAILER, rtimeout) && ++errs[E_TIMEOUTS].e_count)
				|| (crc(size) && ++errs[E_CRC].e_count)
			  )
				goto nak;

			if (reply.r_seq == lastseq && lastsize > 0)
			{
				seek(local, -lastsize, 1);
				filesize =- lastsize;
			}

			wretrys = 0;
			while ((lastsize = write(local, buf+HEADER, size)) != size)
			{
				errs[E_WRITE].e_count++;
				if (wretrys++ >= XRETRYS)
					goto fail;
				if (lastsize > 0)
					seek(local, -lastsize, 1);
			}
			filesize =+ size;

			reply.r_typ = ACK;
			lastseq = reply.r_seq;
			goto rply;
		}
	}
}

/********************************************************************/

/*
 *	not a true crc, but nearly so for small 's' (< 100 say)
 */

crc(s)
{
	register char *p;
	register char lpc, sum;
	char  *end;
	int  error;

	p = buf;  lpc = 0;  sum = 0;  error = 0;

	*p++ = STX;
	*p++ =^ 1;		/* flip sequence number */
	*p++ = s;  *p++ = ~s;

	end = buf+HEADER+s;
	for ( ; p < end; p++)
	{
		lpc =^ *p;
		sum =+ *p;
	}

	if (lpc != *p)  error++;
	*p++ = lpc;
	if (sum != *p)  error++;
	*p++ = sum;

	return(error);
}

/**********************************************************************/

errors()
{
	register struct err *ep;
	register nerrs;

	nerrs = 0;

	for (ep = errs; ep < &errs[NERRS]; ep++)
	if (ep->e_count)
	{
		if (nerrs++ == 0)
			printf("  file transfer protocol errors:-\n");
		printf("  %s: %d", ep->e_mess, ep->e_count);
		ep->e_count = 0;
	}

	if (nerrs)  printf("\n");
	return(nerrs);
}

/***********************************************************************/

help()
{
	printf("\nuse:\n");
	printf("\tt[ty]\n");
	printf("\tp[ut] %s-file [%s-file]\n", SYSTEMID, &otherm[5]);
	printf("\tg[et] %s-file [%s-file]\n", &otherm[5], SYSTEMID);
	printf("\tq[uit] new-code\n");
	printf("\t!{shell command}\n");
	printf("\tc[d] directory\n");
	printf("\ts[top]\n\n");
}

/*******************************************************************/

#ifdef	SPRINTF
#define		NUMBERS
#define		LONG
#include	<sprintf.h>
#endif	SPRINTF

/*
**	check for appropriate permissions on a file
**	passed as 'arg' and creat it if allowable.
*/

accreat(arg, flag)
char *arg;
{
	register char *p;
	int n;
	extern int errno;

	if ((n = access(arg, W)) == 0)		/* file already exists and have write permission */
	{
		if (flag == 0)
		{
			printf(exists, arg);
			if (getline() != 'y') return -1;
		}
		else if (flag == 1)
		{
			write(2, MDLE);
			return -1;
		}
	}
	else if (n && errno == ENOENT)
	{				/* isolate directory name and check it */
		p = arg;
		while (*++p);
		while (*--p != '/' && p > arg);
		if (*p != '/') n = access("", W|X);
		else if (p == arg) n = access("/", W|X);
		else
		{
			*p = 0;
			n = access(arg, W|X);
			*p = '/';
		}
	}
	if (n == 0)
	{			/* creat the file since access is granted */
		n = creat(arg, 0600);
		chown(arg, uid);
	}
	if (n < 0)
		perror(arg);
	return n;
}

/*
 *	validate that log is not being used with any
 *	standard input or output redirection - this
 *	includes asynchronous use.
 *	The test checks that file descriptors 0, 1, and 2 represent
 *	the same tty.
 */

okuse()
{
	struct
	{
		int majmin;	/* major minor device number for i-node */
		int inum;	/* inode number on device */
		int flags;	/* flags from inode */
#define	IFMT	060000
#define	ICHR	020000
		char dumy[30];	/* the rest ... */
	}
	sbuf[3];
	register i;

	for (i = 0; i < 3; i++)
		if (fstat(i, &sbuf[i]) == -1 ||		/* stat failed ??? */
		    sbuf[i].flags&IFMT != ICHR ||	/* not a tty */
		    i &&
		    (sbuf[i].majmin != sbuf[i-1].majmin ||
			sbuf[i].inum != sbuf[i-1].inum))	/* some sort of redirection */
			return 0;
	return 1;
}