Google
 

Trailing-Edge - PDP-10 Archives - SRI_NIC_PERM_FS_1_19910112 - c/lib5/usys/urt.c
There are 9 other files named urt.c in the archive. Click here to see a list.
/* URT - USYS Run Time support and startup
**
**	Copyright (c) 1987 by Ken Harrenstien & Ian Macky, SRI International
**	applies to edits made since name changed to URT.C.
**	Edits for ITS:  Copyright (C) 1988 Alan Bawden
**
**	This code is invoked by the CRT (C Run Time) startup and does
** various things (such as parsing any JCL) before invoking the user's
** "main" function.
**	The URT startup should, insofar as possible, limit itself to
** only USYS functions; that is, to simulating the UN*X startup.  This
** code should NOT use the higher-level library functions such as
** printf or malloc.
*/

#include "c-env.h"	/* Include environment/config defs */
#include "stdlib.h"	/* standard library definitions */
#include "urtsud.h"	/* URT StartUp Definition structure */
#include "stdio.h"
#include "errno.h"
#include "frkxec.h"	/* New KCC forkexec() call */
#include "sys/usysio.h"
#include "sys/usytty.h"	/* Internal TTY defs */
#include "sys/usysig.h"
#include "sys/file.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "ctype.h"
#include "string.h"	/* For str- and mem- defs */
#if SYS_T20+SYS_10X
#include "jsys.h"
#elif SYS_ITS
#include "sysits.h"
#endif

/* Exported functions (defined here) */
void _runtm();			/* URT startup entry point */

/* Imported functions (external) */
extern main();			/* From user program */
extern char *sbrk();		/* See the WALIGN_sbrk macro below */
extern void exit(), _exit();

/* Internal system-dependent functions */
static void init_uio(), setfil(), tty_set(), abortmsg();
static char *getjcl();

/* Internal functions */
static int shell_parse(), exec_parse();
static void argfset(), arg_set();	/* use these to set args in argv */

/* Global URT data.  Only "errno" is intended to be user-visible. */
int errno = 0;		/* Error # from last failing UN*X syscall */
int _vfrkf = 0;		/* Non-zero if memory shared as result of vfork() */
int _nfork = 0;		/* Count of inferiors created by this process.
			** Only used to determine whether a fork or vfork
			** was ever done, i.e. if exit() should wait().
			*/

/* Define macro so that sbrk()'s idea of the break is always word-aligned
** upon entry to the user program.
*/
static char *wdsbrk();			/* Use routine now, not macro */
#define WS (sizeof(int))		/* Word size */
#define WALIGN_sbrk(n) wdsbrk(n)	/* Word-aligned sbrk() */

/* Macro to reduce some typing; used with abortmsg(). */
#define LASTERR strerror(_ERRNO_LASTSYSERR)

#define MAX_ARG 32	/* # words of argv space to increment each time */

static int argc;		/* argument count (finally given to main()) */
static char **argv;		/* pointer to argv vector (ditto) */
static int argv_size;		/* current max size of argv */
/*
** _RUNTM()	UN*X Run Time C program startup!
*/

void
_runtm()
{
    int (*parser)();			/* parser function to call */
    char *p, *arg_vector[MAX_ARG];

    init_uio();				/* initialize uio layer */
    argc = 0;				/* no args on command line yet */
    argv = arg_vector;			/* start off using this array */
    argv_size = MAX_ARG;		/* size of argv array */
    if (_urtsud.su_parser_type != _URTSUD_NO_PARSE && (p = getjcl())) {
	switch (_urtsud.su_parser_type) {
	    default:
	    case _URTSUD_SHELL_PARSE: parser = shell_parse; break;
	    case _URTSUD_EXEC_PARSE: parser = exec_parse; break;
	}
	(*parser)(p);			/* invoke the parser */
    }
    tty_set();			/* Set up TTY stuff if any.  If so, it must */
				/* register a cleanup rtn with onexit()! */ 

    onexit(_cleanup);		/* Register STDIO cleanup rtn for exit() */
    exit(main(argc, argv));	/* Call user program, return value! */
}
/* EXEC_PARSE
 *	Parser for EXEC-style command lines.  takes a string of the form
 *	"<command>[<space><text>]<CRLF>" and breaks it into one or two
 *	parts.  if argc=1, then there's just a command.  if argc=2, then
 *	there a command plus an arg.  argv[0] points to the start of the
 *	command, argv[1] points to the start of the arg (maybe).  both
 *	skip initial whitespace and null-terminate their parts in place
 *	in the string buffer.
 *
 *	also, that CRLF might just be a CR, or maube just an LF.  be
 *	sure and handle all cases.
 */
static int
exec_parse(p)
char *p;
{
    int c;
    char *original_p;

    while ((c = *p) == ' ' || c == '\t')
	p++;				/* skip leading whitespace */
    original_p = p;			/* save this to see if it changes */
    while (c && c != ' ' && c != '\t' && c != '\r' && c != '\n')
	c = *++p;			/* skip text of command, if any */
    if (p == original_p) return;	/* oops, nothing really there. */
    *p = '\0';				/* tie off command. */
    arg_set(original_p);		/* save start of command */
    if (c != ' ' && c != '\t') return 1;/* if not a space, no arg. */
    while ((c = *++p) == ' ' || c == '\t')
	;				/* else, skip whitespace b4 arg. */
    if (c == '\r' || c == '\n')		/* if lots of blankspace but no */
	return;				/* real arg, then so be it. */
    arg_set(p);				/* else save start of arg */
    while ((c = *++p) && c != '\r' && c != '\n') ;
    if (c) *p = '\0';			/* skip to EOL, clobber CR or LF. */
    return;
}
/* SHELL_PARSE - unix-style shell parser.
*/

static char *indfparse();
static int argscan();
static int runpipe();
#if SYS_T20+SYS_10X
static char *mangle();
#endif

static int haswild = 0;	/* Set by argscan() if arg had unquoted wild chars */

#ifndef SYSFIL_QTCH
#if SYS_T20+SYS_10X
#define SYSFIL_QTCH (026)	/* CTRL-V - T20 quoting convention */
#elif SYS_ITS
#define SYSFIL_QTCH (021)	/* CTRL-Q - ITS quoting convention */
#else
#define SYSFIL_QTCH (-1)
#endif
#endif

static int
shell_parse(p)
char *p;
{
    register int c;
    char *cp, *ap;
    int pipeout = -1;
    int lev = 0;
    int append = 0;			/* No append for stdout redirect */
    char *in = NULL, *out = NULL;	/* No redirection */

    /*
     *	Read command line into a string.  We later drop some nulls into
     *	it to separate it into arguments, to save space.  We allocate
     *	space using sbrk() for the string; if there are no wildcard
     *	arguments this will be all we need to store arguments.
     */
    lev = 0;
    c = *(cp = p);				/* Initialize */
    while (c) {
	switch (c) {
	    default:
		if (!isgraph(c))	/* Ignore whitespace/cntrls */
		    break;
		ap = cp;		/* Have start of an arg, note it. */
		c = argscan(ap, &cp, lev);	/* Gobble the arg */
		argfset(ap);		/* Store the arg! */
		continue;		/* Loop, don't increment cp */

	    case '@':		/* Handle indirect file spec */
		cp = indfparse(cp, lev);
		c = *cp;
		continue;	/* Loop, don't increment cp */

	    case ';':		/* Ignore ;-commented lines */
		while (*++cp && *cp != '\n');
		if (!*cp) --cp;	/* Stop on next loop */
		break;

#if SYS_T20+SYS_10X
	    case '!':		/* Ignore !-commented phrases/lines */
		while (*++cp && *cp != '!' && *cp != '\n');
		if (!*cp) --cp;	/* Stop on next loop */
		break;

	    case '-':		/* Check for T20 "line continuation" */
		if (cp[1] == '\n' || cp[1] == '\r')
		    break;		/* Ignore it */
		ap = cp;		/* Have start of an arg, note it. */
		c = argscan(ap, &cp, lev);	/* Gobble the arg */
		argfset(ap);		/* Store the arg! */
		continue;		/* Loop, don't increment cp */
#endif /* T20+10X */

	/*
	 *	an ampersand means run in the background.  this is implemented
	 *	with the usual EXEC PRARG% hack, which if you don't have, you
	 *	should.  it always allows a fork to request being reset, or to
	 *	kept.
	 */
	case '&':
#if !SYS_T20
	    abortmsg("Background (&) not implemented on this system", NULL);
#else
	    if (procamper() == -1)	/* tell exec we need '&' action */
		abortmsg("Couldn't continue ourselves in background - ",
				LASTERR, NULL);
	    /* Now running in background... */
#endif /* T20 */
	    c = 0;		/* We shouldn't have anything after this */
	    continue;		/* Stop loop. */

	    case '|':		/* Handle pipe setup, <prog> | <prog2> */
#if !SYS_T20
		abortmsg("Pipes not supported on this system", NULL);
#else
		if (out != NULL || pipeout >= 0)
		    abortmsg("Multiple redirection of output", NULL);
		while (isspace(*++cp));	/* Move over '|' and flush wsp */
		pipeout = runpipe(cp);
#endif
		c = 0;			/* Remaining JCL went to child */
		continue;		/* Stop loop */

	    /* '<' means take the following file as primary input.
	     * Must not confuse "foo <dir>name" with a redirection request!
	     */
	    case '<':
#if SYS_T20+SYS_10X
		if (lev || mangle(cp)) {	/* Matched brackets? */
		    ap = cp;		/* Have start of an arg, note it. */
		    c = argscan(ap, &cp, lev);	/* Gobble the arg */
		    argfset(ap);		/* Store the arg! */
		    continue;		/* Loop, don't increment cp */
		}
#endif
		if (in != NULL)
		    abortmsg("Multiple redirection of input", NULL);
		while (isspace(c = *++cp));	/* Get next, flush wsp */
		in = cp;		/* Remember ptr to filename */
		c = argscan(in, &cp, lev);	/* Gobble past file name */
		continue;		/* Loop, don't increment cp */

	    case '>':			/* output redirection? */
		if (cp[1] == '>') {	/*   another one, for append? */
		    ++append;		/*   Yep, set flag */
		    ++cp;		/*   and skip over first bracket */
		}
		if (out != NULL || pipeout >= 0)
		    abortmsg("Multiple redirection of output", NULL);
		while (isspace(c = *++cp));	/* Get next, flush wsp */
		out = cp;		/* Remember ptr to filename */
		c = argscan(out, &cp, lev);	/* Gobble past file name */
		continue;		/* Loop, don't increment cp */
	}
	c = *++cp;	/* breaking from switch goes to next char */
    }

    if (pipeout >= 0) {			/* If we are piping output, */
	dup2(pipeout, STDOUT_CH);	/* redirect std output to pipe. */
	close(pipeout);			/* And flush this FD now */
    }

    if (in)				/* If desired, */
	setfil(in, STDIN_CH, 0);	/* redirect stdin to file */
    if (out)				/* and ditto for stdout */
	setfil(out, STDOUT_CH, append);
}
/* RUNPIPE - Set up pipe process
*/
#define PIPEIN	0	/* Indices into array used by pipe() call */
#define PIPEOUT	1

static int
runpipe(cp)
char *cp;
{
#if SYS_T20
    struct frkxec f;
    int pipes[2];		/* For pipe FDs */
    char *av[3];		/* Temporary argv array */
    char pgmnam[1000];		/* Very big program name */

    argscan(pgmnam, &cp, 0);	/* Get program name (always top level) */
    if (pipe(pipes) == -1)
	abortmsg("Couldn't make pipe - ", LASTERR, NULL);
    av[0] = pgmnam;		/* Arg 0 is prog name */
    av[1] = cp;			/* Rest is remaining JCL */
    av[2] = NULL;
    f.fx_flags = FX_PGMSRCH | FX_FDMAP;
    f.fx_name = pgmnam;		/* Program name to run */
    f.fx_argv = &av[0];
    f.fx_envp = NULL;
    f.fx_fdin = pipes[PIPEIN];		/* Map fork's input to this */
    f.fx_fdout = -1;		/* Leave output alone */
    if (forkexec(&f) < 0)
	abortmsg("Couldn't get next program in pipe - ", LASTERR, NULL);

    /* Special hack: forget about pipe's input UFX so we don't close it
     * by accident; there is no mechanism for sharing FD-use count
     * between forks!  Sigh.  If there was, we could just
     * dispense with the following two statements.
     */
    _uionopen[_uioufx[pipes[PIPEIN]]] = 0;	/* Can re-use UFX */
    _uioufx[pipes[PIPEIN]] = 0;		/* Quietly forget pipe-input UFX */

    return pipes[PIPEOUT];		/* Return FD for output to pipe */
#endif /* T20 */
}
/* ARGFSET - Add a filename arg to the argv array, bumping argc.
**	Expands wildcards if needed and performs other checking.
*/
static void
argfset(arg)
char *arg;
{
    /* Watch out for EXEC stupidness!  Make sure we don't have an
    ** EXEC noise keyword at start of JCL.  Time to check this is when
    ** we're about to add a 2nd arg -- check the first, and if it's bad,
    ** replace it with this one.
    */
#if SYS_ITS==0
    if (argc == 1) {
	static int caseall();
	static char *fktab[] = { "RUN", "RU", "R", "START", "ST", "S", 0 };
	register char **p = fktab;
	while (*p)
	    if (caseall(argv[0], *p++)) {
		--argc;			/* Barfo, flush the 1st arg */
		break;
	    }
    }
#endif /* not ITS */
#if SYS_T20+SYS_10X
    if (argc > 0 && haswild) {	/* Expand wildcard filename? */
	static int wfopen(), wfnext();
	static char *wfname();
	int wjfn;

	if ((wjfn = wfopen(arg)) == 0)	/* open wildcard spec */
	    abortmsg("No match for \"", arg, "\"", NULL);
	do
	    arg_set(wfname(wjfn));	/* Get new name and set it */
	while (wfnext(wjfn));
	return;
    }
#endif /* T20+10X */
    arg_set(arg);		/* Just set the arg */
}

/* ARG_SET - add an arg to the argv array, bumping argc.
**	If argv becomes full, it is expanded (with extra space so
**	we don't have to do it too often).  Note that an extra word is
**	kept on the end so that the array always ends with a NULL pointer.
*/

static char *lastsbrk = 0;	/* Last return value from sbrk */

static void
arg_set(p)
char *p;
{
    int i, new_size;			/* new size, if need to expand */
    char **newv;			/* New array pointer, if ditto */

    if (argc >= argv_size-1) {		/* need to expand? */
	new_size = argv_size + MAX_ARG;	/* increase by this much each time */

	if (lastsbrk == sbrk(0)) {	/* Can we just expand it? */
	    WALIGN_sbrk(MAX_ARG * sizeof(char *));	/* Yes, do so */
	}
	else {		/* No, must get new array, and copy old into it. */
	    newv = (char **)WALIGN_sbrk(new_size * sizeof(char *));
	    memcpy((char *)newv, (char *)argv, argv_size*sizeof(char *));
	    argv = newv;
	}
	argv_size = new_size;		/* new max # of args */
	lastsbrk = sbrk(0);		/* New value of last break */
    }
    argv[argc] = p;			/* set this arg. */
    argv[++argc] = NULL;		/* Ensure array always ends in 0 */
}

/* WDSBRK - word-aligned interface to sbrk() that checks for failure.
*/
static char *
wdsbrk(n)
int n;			/* # bytes to allocate */
{
    register char *ret;
    if ((ret = sbrk(((n+WS-1)/WS)*WS)) != (char *)-1)
	return ret;
    abortmsg("Out of memory during URT startup", NULL);
}

/* CASEALL - auxilary for ARGFSET.  Does uppercase string compare.
*/
static int
caseall(s,t)
char *s,*t;
{
    while (toupper(*s) == *t++)
	if (!*s++) return 1;
    return 0;
}
/* INDFPARSE - Parse indirect file.
**	Arg is pointer to start of "@filespec", returns updated pointer.
**	The contents of the file, on TOPS-20/TENEX, are parsed in a fashion
**	similar to that of COMND% indirect files.
*/
static char *
indfparse(p, lev)
char *p;
int lev;
{
    register int c;
    register char *cp;
    int fd;
    struct stat statb;
    char *buf, *newp;

    /* Scan over filename */
    cp = p++;			/* Copy arg, skipping initial '@' */
    c = argscan(cp, &p, lev);	/* Do it */

    if (lev > 10) {		/* Prevent infinite recursion */
	abortmsg("Indirect files nested too deep (10 max) - \"",
			cp, "\"", NULL);
    }
    if (!*cp)
	abortmsg("No filename for @ indirect file", NULL);
    if ((fd = open(cp, O_RDONLY|O_BINARY)) < 0)
	abortmsg("Cannot open indirect file \"", cp, "\" - ", LASTERR, NULL);
    if (fstat(fd, &statb) != 0)
	abortmsg("Cannot fstat indirect file \"", cp, "\" - ", LASTERR, NULL);
    buf = WALIGN_sbrk(statb.st_size+1);		/* Get room for file */
    if (read(fd, buf, statb.st_size) != statb.st_size)
	abortmsg("Cannot read indirect file \"", cp, "\" - ", LASTERR, NULL);
    close(fd);
    buf[statb.st_size] = '\0';		/* Ensure ends with null */
    *p = c;				/* No need to preserve filename now */

    /* Now scan the acquired buffer, adding args as we encounter them. */
    ++lev;			/* Increment level */
    c = *(cp = buf);
    for (;;) {
	switch (c) {
	    case '\0':		/* NUL means all done, return */
		return p;

	    case '@':
		cp = indfparse(cp, lev);
		c = *cp;
		continue;	/* Loop, don't increment cp */

	    case ';':		/* Ignore ;-commented lines */
		while (*++cp && *cp != '\n');
		if (!*cp) return p;
		break;

#if SYS_T20+SYS_10X
	    case '!':		/* Ignore !-commented phrases/lines */
		while (*++cp && *cp != '!' && *cp != '\n');
		if (!*cp) return p;
		break;

	    case '-':		/* Check for T20 "line continuation" */
		if (cp[1] == '\n' || cp[1] == '\r')
		    break;		/* Ignore it */
#endif /* T20+10X */

	    default:
		if (!isgraph(c))	/* Ignore whitespace/cntrls */
		    break;

		/* Start scanning over an argument */
		arg_set(newp = cp);
		c = argscan(cp, &newp, lev);
		cp = newp;
		continue;	/* Loop, don't increment cp */
	}
	c = *++cp;	/* breaking from switch goes to next char */
    }
}

/* ARGSCAN - scan over an argument, copying it.
**	Assumes "from" points to first char of argument.
**	The copy is necessary in order to flush embedded quotes and
** backslashes.  Some chars may not be terminators depending on whether parsing
** at top level or not.
**	Updates the source pointer ("from") and returns as value the char
** which it supposedly points to -- this is the next char that should be
** examined by the higher level scanner.  The pointer itself should not
** be used to examine that char, because it can happen that the NUL char
** written to terminate the arg string was written on top of the "next char".
**	Also (hack!) sets the flag "haswild" if any unquoted wildcard
** characters were seen in the arg ('%', '*')
*/
static int
argscan(to, from, lev)
char *to, **from;		/* Note "from" is addr of char ptr */
int lev;			/* 0 == top level */
{
    register char *wp = to-1, *cp = (*from)-1;	/* Set up for ILDB/IDPB */
    register int c;

    haswild = 0;		/* No wildcard chars seen so far */
    for (;;) {

	/* Check for special terminators. */
	switch (*++wp = *++cp) {
	    default:
		if (isgraph(*cp))	/* Collect printing chars */
		    continue;
		break;			/* Stop if hit any non-printing char */

	    case '%':		/* File wildcard chars */
	    case '*':
		haswild++;		/* Report an unquoted wildcard seen */
		continue;

	    case '-':			/* Check for - at EOL */
		if (!lev) continue;	/* Not special at top level */
		if (cp[1] == '\r' || cp[1] == '\n') {
		    --wp;		/* Flush '-' from end of arg */
		}			/* Next pass of loop will terminate */
		continue;

	    case '\\':
		if (!(*wp = *++cp))	/* Quote next char (overwrite \) */
		    break;		/* don't allow quote of NUL */
		if (*wp == '\r' && cp[1] == '\n')
		    *wp = *++cp;	/* \ CR LF becomes just LF */
		continue;		/* and keep going */

	    /* The following filename quote chars differ from '\' in that
	    ** they are KEPT in the argument, for passing on to the
	    ** operating system calls.
	    */
	    case SYSFIL_QTCH:
		if (!(*++wp = *++cp))	/* Quote next ch<ar (keep quote!) */
		    break;		/* don't allow quote of NUL */
		continue;		/* and keep going */


	    case '"':	/* Permit embedding of quoted strings */
	    case '\'':
		c = *cp;			/* Remember terminator char */
		--wp;				/* Back up to flush it */
		while (*++wp = *++cp) {
		    if (*cp == c) break;
		    if (*cp == '\\') {		/* Allow backslash quotes */
			if (!(*wp = *++cp))
			    break;		/* don't allow quote of NUL */
		    } else if (*cp == SYSFIL_QTCH)
			if (!(*++wp = *++cp))
			    break;		/* don't allow quote of NUL */
		}
		if (!*cp) break;
		--wp;	/* Found terminator, back up so term char not stored */
		continue;

	    case '&':
	    case '|':
	    case '>':
		if (lev) continue;	/* Special only at top level */
		break;
	    case '<':
		if (lev) continue;	/* Special only at top level */
#if SYS_T20+SYS_10X
		if (to = mangle(cp)) {	/* Matching angle brackets? */
		    while (to != cp) {		/* Yes, move over them */
			*++wp = *++cp;
			if (*cp == '%' || *cp == '*')
			    haswild++;		/* Check for wildcards */
		    }
		    continue;
		}
#endif
		break;
	    }
	break;		/* Breaking out of switch breaks out of loop */
    }
    *from = cp;		/* Update "from" pointer */
    c = *cp;		/* Save next char that should be processed */
    *wp = '\0';		/* Tie off copied arg (may clobber *cp) */
    return c;		/* Return saved next-char */
}

/* SETFIL - redirect a standard in/out/err file descriptor.
**	Note that the STDIO streams must also be changed; a fd and a stream
**	are not the same thing.  For now we punt on trying to 
**	change the buffering or stdio flags.
*/

/* This routine doesn't seem very system-dependent to me, so I moved it
 * out here into the open so that everybody could use it.  -Alan
 */

static void
setfil(name, fd, append)
char *name;
int fd, append;
{
    FILE *fp;
    int tmpf, flags;

    if (!name) return;			/* nothing to do. */
    switch (fd) {
	case STDIN_CH:
	    fp = stdin;
	    flags = O_RDONLY;
	    break;
	case STDOUT_CH:
	    fp = stdout;
	    flags = (O_WRONLY | O_CREAT | O_TRUNC | (append ? O_APPEND : 0));
	    break;
	case STDERR_CH:
	    fp = stderr;
	    flags = (O_WRONLY | O_CREAT | O_TRUNC | (append ? O_APPEND : 0));
	    break;
	default:
	    abortmsg("Bad redirection file fd", NULL);
    }
    if ((tmpf = open(name, flags, 0644)) < 0)
	abortmsg("Couldn't open redirection file \"", name, "\" - ",
			LASTERR, NULL);

    dup2(tmpf, fd);			/* Make temp FD be a STDxx FD */
    close(tmpf);			/* Then flush the temp fd */
    if (flags & O_APPEND)		/* If appending, */
	fseek(fp, 0L, SEEK_END);	/* ensure STDIO knows about it */
    if (fp == stderr)
	setbuf(stderr, (char *)NULL);	/* No buffering for stderr */
#if SYS_T20+SYS_10X+SYS_ITS
    else if (fp == stdout			/* If stdout to non-TTY, */
	    && _uiotype[_uioufx[fd]] != _DVTTY)
	setvbuf(fp, (char *)NULL, _IOFBF, 0);	/* then use full buffering */
#else
#error setfil() needs OS-dependent test for TTYness
#endif
}

#if SYS_T20+SYS_10X
/* MANGLE - Match Angle brackets, try to determine if '<' starts a
**	filename with a directory spec or not.
**	Returns NULL if no matching '>', else pointer to it.
*/
static char *
mangle(p)
char *p;
{
    for (;;)			/* until we find a special char */
	switch (*++p) {			/* check char (ignoring first open) */
	case ' ': case '\t': case '\n': case '\r':
	case ';': case '<':  case ':':  case '\0':
	    return NULL;		/* no close or second open, redirect */
	case '>':
	    return p;			/* found an angle, matched */
	}
}
#endif /* T20+10X */
/****************** System-Dependent Routines ****************************/
#if 0
	The system-dependent portion of URT should provide the
following routines:

init_uio()		- Initialize I/O at startup
getjcl()		- Read JCL to program
setfil(fname, fd, appf)	- Redirect FD to given file (append if appf set)
tty_set()		- Init TTY at startup; register reset rtn for exit.
abortmsg()		- Print message and halt

#endif
#if SYS_T20+SYS_10X			/* TOPS-20 and TENEX: */
/*
 *	TOPS-20/TENEX specific portions of higher-level runtimes
 */

/* INIT_UIO - initialize UIO data structures.
**	For now we always set FD 2 (std err) to .CTTRM, and
** and check .PRIIN and .PRIOU to set up FDs 0 and 1.
**	Later we may inherit a data page from previous process.
*/
static void
init_uio()
{
    int ifx, ofx, efx;
    union {
	int acs[5];
	struct { int junk[2];
		unsigned pin : 18;	/* LH of AC 2 */
		unsigned pout : 18;	/* RH of AC 2 */
	} res;
    } a;

    /* Initialize FD 2 (error output) as that is always constant */
    efx = _uiofx();			/* Get a free UFX */
    _uioch[efx] = _CTTRM;		/* Use this JFN */
    _uioflgs[efx] = _UIO_CONVERTED;	/* With this extra flag */
    _openufx(efx, O_RDWR);
    _uioufx[STDERR_CH] = efx;		/* FD 2 now open for business! */

    /* See what our primary JFNs really are */
    a.acs[1] = _FHSLF;
    jsys(GPJFN, a.acs);
    if (a.res.pin == _uioch[efx]) {	/* If stdin same as stderr, */
	_uioufx[STDIN_CH] = efx;	/* just point there. */
	_uionopen[efx]++;
    } else {
	ifx = _uiofx();
	_uioch[ifx] = a.res.pin;	/* Must set up with this JFN */
	_uioflgs[ifx] = _UIO_CONVERTED;	/* With this extra flag */
	_openufx(ifx, O_RDONLY);	/* Take care of rest of vars */
	_uioufx[STDIN_CH] = ifx;	/* Then make FD 0 open for business */
    }

    if (a.res.pout == _uioch[efx]) {	/* If stdout same as stderr, */
	_uioufx[STDOUT_CH] = efx;	/* just point there. */
	_uionopen[efx]++;
    } else {
	ofx = _uiofx();
	_uioch[ofx] = a.res.pout;	/* Must set up with this JFN */
	_uioflgs[ofx] = _UIO_CONVERTED;	/* With this extra flag */
	_openufx(ofx, O_WRONLY);	/* Take care of rest of vars */
	_uioufx[STDOUT_CH] = ofx;	/* Then make FD 1 open for business */
    }
}
/* ---------------------------------------- */
/*	set terminal mode word		    */
/* ---------------------------------------- */

#define CCOC_MAGIC	0525252525252

static int ccoc[2];
static void tty_reset();

static void
tty_set()
{
    int ablock[5];

    ablock[1] = _CTTRM;
    if (!jsys(RFCOC, ablock)) return;
    ccoc[0] = ablock[2];
    ccoc[1] = ablock[3];		/* save ccoc words */
    if (ccoc[0] != CCOC_MAGIC
     || ccoc[1] != CCOC_MAGIC) {
	ablock[2] = ablock[3] = CCOC_MAGIC;
	if (!jsys(SFCOC, ablock)) return;
    }
    onexit(tty_reset);			/* Won, register reset rtn for exit */
}

static void		/* Called to reset TTY upon normal exit() */
tty_reset()
{
    int ablock[5];

    ablock[1] = _CTTRM;
    ablock[2] = ccoc[0];
    ablock[3] = ccoc[1];
    jsys(SFCOC, ablock);
}
/* ABORTMSG(s1, s2, s3, s4, s5) - outputs string composed of given
**	concatenated strings, and exits.  A NULL argument terminates the
**	list of strings.
*/
static void
abortmsg(s, a,b,c,d,e)
char *s, *a,*b,*c,*d,*e;
{
    char tmpbuf[1000];
    int ac[5];

    tty_reset();
    strcpy(tmpbuf, s);
    if (a) { strcat(tmpbuf, a);
	if (b) { strcat(tmpbuf, b);
	    if (c) { strcat(tmpbuf, c);
		if (d) { strcat(tmpbuf, d);
		    if (e) { strcat(tmpbuf, e);
    } } } } }
    strcat(tmpbuf, "\r\n");	/* Terminate error string with CRLF */
    ac[1] = (int) (tmpbuf-1);
    jsys(ESOUT, ac);			/* Use ESOUT% to output it */
    _exit(1);				/* Then stop program with prejudice */
}
/* Wildcard filename routines */

/* WFOPEN - open wild card filename
**	Returns wild JFN for filespec, 0 if failure.
*/
static int
wfopen(name)
char *name;
{
    return _gtjfn(name, O_RDONLY | O_T20_WILD);
}

/* WFNAME - Return filename for wild JFN
**	Returns pointer to dynamically allocated filename string
*/
static char *
wfname(jfn)
int jfn;
{
    char *fp, fname[200];
    int ablock[5];

    ablock[1] = (int) (fname - 1);
    ablock[2] = jfn & 0777777;	/* jfn, no flags */
    ablock[3] = 0111110000001;	/* DEV+DIR+NAME+TYPE+VERS, punctuate */
    if (!jsys(JFNS, ablock))
	return NULL;		/* something bad happened */
    fp = WALIGN_sbrk(strlen(fname) + 1);
    strcpy(fp, fname);		/* copy the file name here */
    return fp;
}

/* WFNEXT - Make wild JFN point to next real file
**	Returns success or failure (not JFN)
*/
static int
wfnext(jfn)
int jfn;
{
    int ablock[5];

    ablock[1] = jfn;		/* save jfn and flags */
    return jsys(GNJFN, ablock);
}
/* GETJCL - Return command line
*/
static char *
getjcl()
{
    char *buf;

#if SYS_T20
    int n, acs[5];

    /* Ask to rescan command line, see if anything there. */
    acs[1] = _RSINI;
    if (jsys(RSCAN, acs) > 0) n = acs[1];	/* Find # chars avail */
    else n = 0;
    if (n <= 0)
	return NULL;			/* Nothing there */

    buf = WALIGN_sbrk(n + 1);	/* make room for chars and null */

    /* Have to gobble the RSCAN line directly since only way to access
    ** the rscan buffer is by using a JFN equivalent to the controlling TTY.
    ** If our .PRIIN has been redirected, a normal SIN or read() will not
    ** get the rscan stuff at all.
    ** TENEX is probably out of luck in this respect.
    */
    acs[1] = _CTTRM;
    acs[2] = (int) (buf-1);
    acs[3] = -n;
    jsys(SIN, acs);			/* Read the stuff */
    buf[n] = 0;				/* null-terminate */
#endif /* SYS_T20 */
#if SYS_10X
    char *cp;
    int c, n;
    char tmpbuf[1000];

    for (cp = tmpbuf; read(STDIN_CH, cp, 1) == 1; ++cp)
	if ((c  = *cp) == '\037' || c == '\r' || c == '\n') {
	    *cp++ = '\n';
	    break;
	}
    *cp = 0;
    n = strlen(tmpbuf);
    buf = WALIGN_sbrk(n + 1);
    strcpy(buf, tmpbuf);
#endif /* SYS_10X */
    return buf;
}
/* PROCAMPER -
 *	Tell EXEC via termination PRARG that we want to be continued in
 *	the background (e.g. we were invoked by foo &) requires slight
 *	EXEC modification
 */

static int
procamper()
{
    int arg_block[5], temp_arg;

    arg_block[1] = (_PRAST << 18) + _FHSLF;
    temp_arg = PRA_BACK << 18;
    arg_block[2] = (int) &temp_arg;
    arg_block[3] = 1;
    if (!jsys(PRARG, arg_block)) return -1;	/* some lossage */
    jsys(HALTF, arg_block);		/* stop and let exec continue us */
    return 0;				/* return to caller in background */
}
#endif	/* SYS_T20+SYS_10X */
#if SYS_WAITS
/*
**  WAITS specific portions of higher-level runtimes
*/
/* Old comments:
 *	. ch[fd] contains a channel number (usually fd)
 *	. channel 0 is unredirected TTY input, channel 1 output
 *	. command line is of the form
 *	. .RUN FOO ; arg1 arg2 arg3 ...
 *	. redirection to/from files is supported
 */

/* ---------------------- */
/*	set up files      */
/* ---------------------- */

static void
setup(in,out)
char *in, *out;
{
    _uioch[STDIN_CH] = (in != NULL) ? _uioch[open(in,0)] : STDIN_CH;
    _uioch[STDOUT_CH] = (out != NULL) ? _uioch[creat(out,0)] : STDOUT_CH;
    _uioch[STDERR_CH] = STDERR_CH;
    _uioeof[STDIN_CH] = 0;		/*   no eof on terminal yet */
}
/* --------------------------------------- */
/*	output error message and exit      */
/* --------------------------------------- */

static void
abortmsg(s, a,b,c,d,e)
char *s;
{
    while (*s) _putty(*s++);		/*   send it out a bit at a time */
    _putty('\n');			/*   with a CR to make a new line */
    _exit(1);				/* in either case, stop program */
}
/* ----------------------------- */
/*      return command line      */
/* ----------------------------- */

static char *
getjcl()
{
    char *argbuf, *p;
    int n;

    n = _rscan();			/* Rescan command line */
    argbuf = WALIGN_sbrk(n + 1);	/* make room for chars and null */
    p = argbuf;				/* point to start of buffer */
    while (--n) *p++ = _getty();	/* read the number of chars */
    _getty();				/* plus one trailer */
    *p = '\0';				/* terminate with a null */
    return argbuf;
}

static int
_rscan()
{
    asm("	RESCAN	1\n");	/* Rescan cmd line, get # chars avail in 1 */
}
#endif /* SYS_WAITS */
#if SYS_ITS
/*
 *	Runtime stuff for ITS
 */

static void tty_set() {}

static void init_uio()
{
    char *iname = " TTY:_CPROG INPUT";
    char *oname = " TTY:_CPROG OUTPUT";
    int ufx = _uiofx();			/* UFX for input */

    /* If TTY is not available, these just quietly fail to open the */
    /* channel since we set %TBNVR.  Later an channel not open error */
    /* will tell you that this happened... */

    SYSCALL3("sopen", SC_IMC(_UAI), _uioch[ufx], &iname);
    _uiobsize[ufx] = 7;
    _uioflgs[ufx] = _UIO_CONVERTED;
    _openufx(ufx, O_RDONLY);
    _uioufx[STDIN_CH] = ufx;

    ufx = _uiofx();			/* get another UFX for output */

    SYSCALL3("sopen", SC_IMC(_UAO), _uioch[ufx], &oname);
    _uiobsize[ufx] = 7;
    _uioflgs[ufx] = _UIO_CONVERTED;
    _openufx(ufx, (O_WRONLY | O_CREAT | O_TRUNC));
    _uioufx[STDOUT_CH] = ufx;

    dup2(STDOUT_CH, STDERR_CH);		/* stderr is just a dup */
}
/* GETJCL() - Return a char pointer to (simulated) JCL string */

static int jclsiz = 100;	/* Start with buffer of this many wds */

static char *getjcl()
{
#asm

extern $retz

	push 17,[8]		; 2 words for xjname + ^M + null
	pushj 17,sbrk
	adjsp 17,-1
	CAMN 1,[-1]		; If not enough memory,
	 JRST $RETZ		; just return a null pointer!
	push 17,1		; Else, remember the start of it
	hrli 1,440700
	push 17,1		; And a 7-bit byte pointer
	.suset [.rxjname,,2]
.rjcl0:	setzi 1,
	lshc 1,6
	addi 1,40
	idpb 1,(17)
	jumpn 2,.rjcl0
	.suset [.roption,,1]
	tlnn 1,%opcmd		; If superior has no command
	 jrst .rjclx		; then that's all.
	movei 1,40
	idpb 1,(17)

	MOVE 1,jclsiz		; Start off with this many words!

.RJCL1:	LSH 1,2			; Multiply by 4 to get # bytes
	PUSH 17,1		; Get this many bytes
	PUSHJ 17,sbrk		; From low-level sbrk() call.
	ADJSP 17,-1		; Remove stack arg.
	CAMN 1,[-1]		; If not enough memory,
	 JRST [	adjsp 17,-2	; just return a null pointer!
		JRST $RETZ]
	HRRZ 2,-1(17)		; Get word address of block start
	HRLZI 3,2(2)		; Put it in LH of 3
	HRRI 3,3(2)		; and addr+1 in RH
	ADD 2,jclsiz		; Now in 2 get 1st addr past end
	SETZM -1(3)		; Clear 1st word
	BLT 3,(2)		; Zap all remaining words but one
	SETOM 1(2)		; Set up non-zero fence at last word.

	HRRZ 3,-1(17)		; Now get start of block again.
	ADD 3,[..RJCL,,2]	; Read JCL from superior
	.BREAK 12,3		; Try to read command string.
	SKIPE (2)		; See if clobbered last zero word.
	 JRST [	MOVEI 1,100	; Add this many more words
		ADDB 1,jclsiz
		JRST .RJCL1]	; and go try again!

	hrli 3,440700		; Now we copy it down
.rjcl2:	ildb 1,3		; and standardize terminator
	caie 1,15		; ^M
	 cain 1,3		; ^C
	  jrst .rjclx
	caie 1,37		; ^_
	 cain 1,0		; ^@
	  jrst .rjclx
	idpb 1,(17)
	jrst .rjcl2

.rjclx:	movei 1,15		; CR at the end for parsers
	idpb 1,(17)
	setzi 1,		; Then a null for C
	idpb 1,(17)
	move 1,-1(17)		; Make 7-bit b.p.
	hrli 1,350700
	adjsp 17,-2

#endasm
}
/* ABORTMSG() - Print message and halt */

static void abortmsg(msg)		/* more strings until NULL */
    char *msg;
{
#asm

	.iopush 1,
	.open 1,[sixbit /  !TTY/
		setz
		setz]
	 .lose
	movei 10,-1(17)
abrtm0:	setoi 3,
	adjbp 3,(10)
	move 4,3
	setzi 5,
abrtm1:	ildb 6,4
	skipe 6
	 aoja 5,abrtm1
	.call [	setz
		sixbit /SIOT/
		movei 1
		move 3
		setz 5 ]
	 .lose
	skipe -1(10)
	 soja 10,abrtm0
	.iopop 1,

#endasm

    _exit(1);
}

#endif /* ITS */
#if 0
/*
 *	Old Runtime stuff for ITS
 */

#include "sysits.h"

static void
setup(in,out)
{
	setfil(0, in ? in : "TTY:", 0);
	setfil(1, out?out : "TTY:", 1);
	setfil(2, "TTY:", 1);
}

static void
setfil(fd, name, umode)
char *name;
{
    int fdx;

    if ((fdx = _ofile(name, umode, 7)) >= 0) {
	if (fdx != fd)
	    dup2(fdx, fd);
    } else abortmsg("Couldn't open redirection file", NULL);
}

static void
abortmsg(msg)
char *msg;
{
    char tmpbuf[1000];

    strcat(strcat(strcpy(tmpbuf,":\033 "),msg)," \033\r\n\033p");
    _valret(tmpbuf);
    _exit(1);
}


static char *
getjcl()
{
    char *_rjcl();

    return _rjcl();
}
/* _RJCL() - Return a char pointer to JCL */
static int jclsiz = 100;	/* Start with buffer of this many wds */

static char *_rjcl()
{
#asm
	.SUSET [.ROPTIO,,1]
	TLNN 1,%OPCMD		; Has our superior said it has a cmd?
	 JRST $RETZ		; No.
	PUSH 17,[0]		; Find out current break
	PUSHJ 17,sbrk		; by calling low-level sbrk()
	MOVEM 1,(17)		; Save result on stack
	MOVE 1,jclsiz		; Start off with this many words!

.RJCL1:	LSH 1,2			; Multiply by 4 to get # bytes
	PUSH 17,1		; Get this many bytes
	PUSHJ 17,sbrk		; From low-level sbrk() call.
	ADJSP 17,-1		; Remove stack arg.
	CAMN 1,[-1]		; If not enough memory,
	 JRST [	POP 17,1	; just return a null pointer!
		JRST $RETZ]
	HRRZ 2,(17)		; Get word address of block start
	HRLZI 3,(2)		; Put it in LH of 3
	HRRI 3,1(2)		; and addr+1 in RH
	ADD 2,jclsiz		; Now in 2 get 1st addr past end
	SETZM -1(3)		; Clear 1st word
	BLT 3,-2(2)		; Zap all remaining words but one
	SETOM -1(2)		; Set up non-zero fence at last word.

	HRRZ 3,(17)		; Now get start of block again.
	HRLI 3,5		; 5 = Read JCL from superior
	.BREAK 12,3		; Try to read command string.
	SKIPE -2(2)		; See if clobbered last zero word.
	 JRST [	MOVEI 1,100	; Add this many more words
		ADDB 1,jclsiz
		JRST .RJCL1]	; and go try again!
	POP 17,1		; Won!  Get ptr to beg of block.
	HRLI 1,350700		; Return 7-bit char pointer to ASCIZ.
#endasm
}

/* _VALRET() - Pass a word-aligned string to DDT.
**	We have to just hope the string starts on a word boundary.
*/
static void
_valret(str)
char *str;
{
#asm
	XMOVEI 1,@-1(17)	; We just hope it starts on wd boundary.
	.VALUE (1)
#endasm
}

#endif /* 0 (Old Runtime stuff for ITS) */