/* -*-C-*- ** 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 * "[]" 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, | */ #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 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': 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 */ else if (fp == stdout /* If stdout to non-TTY, */ && _uiotype[_uioufx[fd]] != _DVTTY) setvbuf(fp, (char *)NULL, _IOFBF, 0); /* then use full buffering */ } #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 tty_set() {} static 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 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) */