701 lines
15 KiB
C
Executable File
701 lines
15 KiB
C
Executable File
/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
|
|
/* All Rights Reserved */
|
|
|
|
/* THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T */
|
|
/* The copyright notice above does not evidence any */
|
|
/* actual or intended publication of such source code. */
|
|
|
|
/*
|
|
* Copyright (c) 1986 AT&T
|
|
* All Rights Reserved
|
|
*/
|
|
|
|
#ident "@(#)eval.c 1.5 93/09/30 SMI" /* SVr4.0 1.22 */
|
|
|
|
#ifdef CACA
|
|
#define _DEBUG2 1
|
|
int _Debug=4;
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <ctype.h>
|
|
#include "wish.h"
|
|
#include "eval.h"
|
|
#include "terror.h"
|
|
#include "message.h"
|
|
#include "moremacros.h"
|
|
#include "interrupt.h"
|
|
|
|
#define MAXARGS 64
|
|
|
|
/* NOTE!!! the following flags compete for bits with
|
|
* the EV_**** flags in inc/eval.h Make sure
|
|
* there is no overlap.
|
|
*/
|
|
#define IN_DQ 1
|
|
#define IN_SQ 2
|
|
#define IN_BQ 4
|
|
#define IN_SQUIG 8
|
|
#define FROM_BQ 16
|
|
|
|
/*
|
|
* list of "special" characters, and flags in which they are not
|
|
* treated as special. NOTE that EV_SQUIG flag is opposite all
|
|
* others. set it in nflags if {} ARE to be treated as special.
|
|
*/
|
|
static char spchars[] = "\"'\\`$\n \t{}|&;<>2";
|
|
/*static char spchars[] = "\"'\\`$\n \t{}|&;<>";
|
|
abs */
|
|
static int nflags[] = {
|
|
FROM_BQ | IN_SQ, /* double quote */
|
|
FROM_BQ | IN_DQ, /* single quote */
|
|
FROM_BQ, /* backslash */
|
|
FROM_BQ | IN_SQ, /* back quote */
|
|
FROM_BQ | IN_SQ, /* dollar sign */
|
|
IN_SQUIG | IN_SQ | IN_DQ, /* new line */
|
|
FROM_BQ | IN_SQ | IN_DQ | EV_GROUP, /* space */
|
|
FROM_BQ | IN_SQ | IN_DQ | EV_GROUP, /* tab */
|
|
FROM_BQ | IN_SQUIG | IN_SQ | IN_DQ | IN_BQ | EV_SQUIG, /* open squig */
|
|
FROM_BQ | IN_SQ | IN_DQ | IN_BQ | EV_SQUIG, /* close squig */
|
|
FROM_BQ | IN_SQ | IN_DQ | EV_GROUP, /* pipe symbol */
|
|
FROM_BQ | IN_SQ | IN_DQ | EV_GROUP, /* ampersand */
|
|
FROM_BQ | IN_SQ | IN_DQ | EV_GROUP, /* semicolon */
|
|
FROM_BQ | IN_SQ | IN_DQ | EV_GROUP, /* less than */
|
|
FROM_BQ | IN_SQ | IN_DQ | EV_GROUP /* greater than */
|
|
,FROM_BQ | IN_SQ | IN_DQ | EV_GROUP /* digit two */
|
|
};
|
|
|
|
/* return code from most recently executed command */
|
|
int EV_retcode;
|
|
int EV_backquotes;
|
|
int Lasttok;
|
|
|
|
extern int in_an_if; /* (in an if statement) see evfuncs.c */
|
|
|
|
|
|
char *
|
|
special_char(c, instr)
|
|
register int c;
|
|
IOSTRUCT *instr;
|
|
{
|
|
char *strchr();
|
|
int c2;
|
|
|
|
if ((char)c == '2')
|
|
{
|
|
c2 = getac(instr);
|
|
if(c2 || instr->flags & EV_USE_FP) /* the other case: EV_USE_STRING */
|
|
ungetac(c2, instr); /* and c2 = EndOfString. don't unget cause */
|
|
/* it would unget c not c2. abs */
|
|
if ((char)c2 != '>')
|
|
return(NULL);
|
|
}
|
|
return(strchr(spchars, c));
|
|
}
|
|
|
|
|
|
eval(instr, outstr, flags)
|
|
IOSTRUCT *instr;
|
|
IOSTRUCT *outstr;
|
|
int flags;
|
|
{
|
|
register int c;
|
|
register int tok;
|
|
bool done;
|
|
|
|
flags ^= EV_SQUIG; /* flip flag so only have to set it
|
|
when special (one case) instead of
|
|
in all other cases. abs */
|
|
if (!(flags & IN_BQ))
|
|
EV_retcode = 0;
|
|
EV_backquotes = FALSE;
|
|
c = getac(instr);
|
|
/* skip leading white space */
|
|
if (flags & (EV_TOKEN | EV_GROUP)) {
|
|
while (isspace(c))
|
|
c = getac(instr);
|
|
if (c == '#') {
|
|
/*
|
|
* skip everything until end of line
|
|
*/
|
|
while ((c = getac(instr)) && c != '\n' && c != EOF)
|
|
;
|
|
}
|
|
}
|
|
/* handler `` at beginning of line if in GROUP mode */
|
|
if ((flags & EV_GROUP) && c == '`') {
|
|
eval_backquote(instr, outstr, flags);
|
|
io_flags(instr, io_flags(instr, 0) & ~FROM_BQ);
|
|
c = getac(instr);
|
|
}
|
|
#ifdef _DEBUG2
|
|
/*
|
|
if ((flags & EV_TOKEN) && (instr->flags & EV_USE_STRING)) {
|
|
_debug2(stderr, "input is '%.*s'\n", instr->mu.str.count - instr->mu.str.pos, instr->mu.str.val + instr->mu.str.pos);
|
|
}
|
|
*/
|
|
#endif
|
|
|
|
Lasttok = tok = ET_EOF;
|
|
for (done = FALSE; c; c = getac(instr)) {
|
|
register char *p;
|
|
char *strchr();
|
|
|
|
/* while (!(p = strchr(spchars, c))) {
|
|
abs */
|
|
while(!(p = special_char(c, instr))) {
|
|
Lasttok = ET_WORD;
|
|
putac(c, outstr);
|
|
if (!(c = getac(instr))) {
|
|
done = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
if (done)
|
|
break;
|
|
/* single | and & are correct here */
|
|
if ((instr->flags | flags) & nflags[tok = p - spchars])
|
|
tok = !!c;
|
|
else {
|
|
tok += ET_DQUOTE;
|
|
#ifdef _DEBUG2
|
|
_debug2(stderr, "eval: got special char 0x%x\n", tok);
|
|
#endif
|
|
}
|
|
switch (tok) {
|
|
case ET_EOF:
|
|
done = TRUE;
|
|
break;
|
|
case ET_WORD:
|
|
Lasttok = tok;
|
|
putac(c, outstr);
|
|
break;
|
|
case ET_DQUOTE:
|
|
flags ^= IN_DQ;
|
|
if (!(flags & EV_TOKEN))
|
|
putac(c, outstr);
|
|
break;
|
|
case ET_SQUOTE:
|
|
flags ^= IN_SQ;
|
|
if (!(flags & EV_TOKEN))
|
|
putac(c, outstr);
|
|
break;
|
|
case ET_BSLASH:
|
|
c = getac(instr);
|
|
/*
|
|
* if (not tokenizing or if we're in quotes and the
|
|
* next character is not special, leave backslash
|
|
* there
|
|
* else
|
|
* remove it (don't copy to output)
|
|
*/
|
|
if (!(flags & EV_TOKEN) || (flags & (IN_SQ | IN_DQ)) && (!(p = strchr(spchars, c)) || (instr->flags | flags) & nflags[p - spchars]))
|
|
putac('\\', outstr);
|
|
putac(c, outstr);
|
|
break;
|
|
case ET_BQUOTE:
|
|
if (flags & EV_TOKEN) {
|
|
if (flags & IN_BQ) {
|
|
if (Lasttok == ET_EOF) {
|
|
putac(c, outstr);
|
|
c = getac(instr);
|
|
Lasttok = tok;
|
|
}
|
|
done = TRUE;
|
|
}
|
|
else
|
|
eval_backquote(instr, outstr, flags);
|
|
}
|
|
else {
|
|
flags ^= IN_BQ;
|
|
putac(c, outstr);
|
|
}
|
|
EV_backquotes = TRUE;
|
|
break;
|
|
case ET_DOLLAR:
|
|
if (flags & EV_TOKEN)
|
|
eval_dollar(instr, outstr, flags);
|
|
else
|
|
putac(c, outstr);
|
|
break;
|
|
case ET_NEWLINE:
|
|
case ET_SPACE:
|
|
case ET_TAB:
|
|
Lasttok = ET_WORD;
|
|
if ((flags & EV_GROUP) && (flags & IN_BQ))
|
|
putac(c, outstr);
|
|
else
|
|
done = TRUE;
|
|
break;
|
|
case ET_OSQUIG:
|
|
case ET_CSQUIG:
|
|
putac(c, outstr);
|
|
if (flags & EV_GROUP)
|
|
flags ^= IN_SQUIG;
|
|
else if (flags & EV_TOKEN) {
|
|
if (Lasttok == ET_EOF) {
|
|
c = getac(instr);
|
|
Lasttok = tok;
|
|
}
|
|
done = TRUE;
|
|
}
|
|
break;
|
|
case ET_PIPE:
|
|
case ET_AMPERSAND:
|
|
case ET_SEMI:
|
|
case ET_LTHAN:
|
|
case ET_GTHAN:
|
|
if (flags & IN_BQ) {
|
|
if (Lasttok == ET_EOF) {
|
|
register int oldc;
|
|
|
|
putac(c, outstr);
|
|
oldc = c;
|
|
if ((c = getac(instr)) == oldc) {
|
|
putac(c, outstr);
|
|
c = getac(instr);
|
|
tok += DOUBLE;
|
|
}
|
|
Lasttok = tok;
|
|
}
|
|
done = TRUE;
|
|
}
|
|
else
|
|
putac(c, outstr);
|
|
break;
|
|
case ET_TWO:
|
|
if (flags & IN_BQ) {
|
|
if (Lasttok == ET_EOF) {
|
|
putac(c, outstr);
|
|
c = getac(instr); /* gets > known to follow 2 */
|
|
putac(c, outstr);
|
|
if ((c = getac(instr)) == '>') {
|
|
putac(c, outstr);
|
|
c = getac(instr);
|
|
tok += DOUBLE;
|
|
}
|
|
Lasttok = tok;
|
|
}
|
|
done = TRUE;
|
|
}
|
|
else
|
|
putac(c, outstr);
|
|
break;
|
|
}
|
|
if (done)
|
|
break;
|
|
Lasttok = ET_WORD;
|
|
}
|
|
if (c)
|
|
ungetac(c, instr);
|
|
#ifdef _DEBUG2
|
|
if (flags & EV_TOKEN) {
|
|
_debug2(stderr, "eval -> '%s'\n", io_ret_string(outstr));
|
|
_debug2(stderr, "eval returning 0x%x\n", Lasttok);
|
|
}
|
|
#endif
|
|
|
|
return Lasttok;
|
|
}
|
|
|
|
/*
|
|
* NOTE:
|
|
*
|
|
* In pre-4.0 releases of FMLI, the contents of environment variables
|
|
* (after expansion) were put back into the input string for further
|
|
* evaluation (lets call it "double evaluation").
|
|
*
|
|
* To remain backwards compatable, the global variable "Doublevars"
|
|
* will be set to TRUE if double evaluation should be performed on
|
|
* ALL environment variables.
|
|
*
|
|
* If Doublevars == FALSE, then only evaluate the "contents" of the
|
|
* variable if a "!" follows "$" (i.e., the "new" convention for double
|
|
* evaluation is "$!VARNAME").
|
|
*
|
|
*/
|
|
/*ARGSUSED*/
|
|
static
|
|
eval_dollar(instr, outstr, flags)
|
|
IOSTRUCT *instr;
|
|
IOSTRUCT *outstr;
|
|
int flags;
|
|
{
|
|
register char *p;
|
|
register int c;
|
|
register IOSTRUCT *iop;
|
|
char *expand();
|
|
int evalagain;
|
|
extern bool Doublevars;
|
|
|
|
iop = io_open(EV_USE_STRING, NULL);
|
|
putac('$', iop);
|
|
if (Doublevars == TRUE)
|
|
evalagain = TRUE;
|
|
else {
|
|
if ((c = getac(instr)) == '!') {
|
|
evalagain = TRUE;
|
|
}
|
|
else {
|
|
evalagain = FALSE;
|
|
ungetac(c, instr);
|
|
}
|
|
}
|
|
if ((c = getac(instr)) == '{') {
|
|
while (c != '}') {
|
|
putac(c, iop);
|
|
c = getac(instr);
|
|
}
|
|
putac(c, iop);
|
|
}
|
|
else {
|
|
while (isalpha(c) || isdigit(c) || c == '_') {
|
|
putac(c, iop);
|
|
c = getac(instr);
|
|
}
|
|
if (c)
|
|
ungetac(c, instr);
|
|
}
|
|
if (p = expand(io_ret_string(iop))) {
|
|
io_clear(iop);
|
|
if (evalagain) {
|
|
/*
|
|
* if the "contents" of the variable should
|
|
* be evaluated before passing it to outstr ...
|
|
*/
|
|
putastr(p, iop);
|
|
free(p);
|
|
p = (char *)NULL;
|
|
io_seek(iop, 0);
|
|
io_push(instr, iop); /* push it back in instr */
|
|
}
|
|
else {
|
|
/*
|
|
* simply put the variable's contents into outstr
|
|
*/
|
|
putastr(p, outstr);
|
|
free(p);
|
|
p = (char *)NULL;
|
|
io_close(iop);
|
|
}
|
|
return SUCCESS;
|
|
}
|
|
return FAIL;
|
|
}
|
|
|
|
static char *
|
|
eval_token(instr, flags)
|
|
IOSTRUCT *instr;
|
|
int flags;
|
|
{
|
|
register char *p;
|
|
static IOSTRUCT *tmp;
|
|
|
|
if (instr == NULL) {
|
|
io_close(tmp);
|
|
return NULL;
|
|
}
|
|
if (tmp == NULL)
|
|
tmp = io_open(EV_USE_STRING, NULL);
|
|
(void) eval(instr, tmp, flags);
|
|
p = io_string(tmp);
|
|
io_seek(tmp, 0);
|
|
return p;
|
|
}
|
|
|
|
static
|
|
eval_backquote(instr, outstr, flags)
|
|
IOSTRUCT *instr;
|
|
IOSTRUCT *outstr;
|
|
int flags;
|
|
{
|
|
int argc;
|
|
char *argv[MAXARGS];
|
|
bool doit;
|
|
int conditional = 0;
|
|
int if_elif = 0;
|
|
bool skip;
|
|
bool piped;
|
|
bool special;
|
|
IOSTRUCT *mystdin;
|
|
IOSTRUCT *mystdout;
|
|
IOSTRUCT *altstdout;
|
|
IOSTRUCT *altstderr;
|
|
|
|
#ifdef _DEBUG2
|
|
_debug2(stderr, "eval_backquote\n");
|
|
#endif
|
|
mystdin = io_open(EV_USE_STRING, NULL);
|
|
mystdout = io_open(EV_USE_STRING, NULL);
|
|
altstdout = NULL;
|
|
altstderr = NULL;
|
|
doit = skip = piped = special = FALSE;
|
|
|
|
for (argc = 0; ; ) {
|
|
conditional = 0;
|
|
argv[argc++] = eval_token(instr, EV_TOKEN | IN_BQ);
|
|
|
|
if (argc == 1) {
|
|
/*
|
|
* determine whether we've found an
|
|
* if/then/else/elif statement
|
|
*/
|
|
if_elif = 0;
|
|
|
|
#ifdef _DEBUG2
|
|
_debug2(stderr, "argv[0]=\"%s\"\n\r", argv[0]);
|
|
#endif
|
|
|
|
switch(argv[0][0]) {
|
|
case 'e': /* else, elif */
|
|
if (!strcmp(argv[0], "else"))
|
|
conditional = 1;
|
|
else if (!strcmp(argv[0], "elif")) {
|
|
conditional = 1;
|
|
if_elif = 1;
|
|
}
|
|
break;
|
|
case 'i': /* if */
|
|
if (argv[0][1] == 'f' && argv[0][2] == '\0') {
|
|
conditional = 1;
|
|
if_elif = 1;
|
|
}
|
|
break;
|
|
case 't': /* then */
|
|
if (!strcmp(argv[0], "then"))
|
|
conditional = 1;
|
|
break;
|
|
}
|
|
|
|
if (conditional) {
|
|
int a, nonwhite, start_look;
|
|
char *cp;
|
|
char ch;
|
|
|
|
/*
|
|
* Force call to if/then/else/elif built-in
|
|
* (no arguments) ... Don't modify the input
|
|
* string here, just put a semi-colon in the
|
|
* "argv" array and set Lasttok (last token
|
|
* received) to ET_SEMI (semi-colon).
|
|
*/
|
|
argv[argc++] = strsave(";");
|
|
Lasttok = ET_SEMI;
|
|
|
|
|
|
/*
|
|
* Though the implementation of if/then/else
|
|
* is done via built-ins ... don't allow
|
|
* semi-colons after if/then/else/elif !!
|
|
*/
|
|
start_look = instr->mu.str.pos;
|
|
cp = instr->mu.str.val + start_look;
|
|
nonwhite = 1;
|
|
for (a = 0; a < instr->mu.str.count - start_look + 1; a++) {
|
|
ch = *(cp + a);
|
|
|
|
if (ch == '\n' )
|
|
break;
|
|
else if (ch == ';') {
|
|
/*
|
|
* If all you've seen is
|
|
* white-space then produce
|
|
* an error message
|
|
*/
|
|
if (nonwhite) {
|
|
char errbuf[100];
|
|
|
|
sprintf(errbuf, "Syntax error - \";\" found after \"%s\"", argv[0]);
|
|
mess_temp(errbuf);
|
|
mess_lock();
|
|
Lasttok = ET_EOF;
|
|
in_an_if = 0;
|
|
}
|
|
break;
|
|
}
|
|
else if (ch != '\t' && ch != ' ') {
|
|
/*
|
|
* not a space, tab, new-line
|
|
* or semi-colon ......
|
|
*/
|
|
nonwhite = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
switch (Lasttok) {
|
|
case ET_EOF:
|
|
case ET_BQUOTE:
|
|
special = doit = TRUE;
|
|
break;
|
|
case ET_PIPE:
|
|
{
|
|
register FILE *fp;
|
|
FILE *tempfile();
|
|
|
|
if (altstdout) {
|
|
#ifdef _DEBUG2
|
|
_debug2(stderr, "PIPE and > in same eval command\n");
|
|
#endif
|
|
io_close(altstdout);
|
|
altstdout = NULL;
|
|
}
|
|
if (fp = tempfile(NULL, "w+"))
|
|
altstdout = io_open(EV_USE_FP, fp);
|
|
special = doit = piped = TRUE;
|
|
}
|
|
break;
|
|
case ET_AMPERSAND:
|
|
break;
|
|
case ET_SEMI:
|
|
special = doit = TRUE;
|
|
break;
|
|
case ET_LTHAN:
|
|
{
|
|
register FILE *fp;
|
|
register char *p;
|
|
|
|
special = TRUE;
|
|
p = eval_token(instr, EV_TOKEN | IN_BQ);
|
|
if (fp = fopen(p, "r")) {
|
|
io_close(mystdin);
|
|
mystdin = io_open(EV_USE_FP, fp);
|
|
}
|
|
else
|
|
warn(NOPEN, p);
|
|
free(p);
|
|
p = (char *)NULL;
|
|
}
|
|
break;
|
|
case ET_GTHAN:
|
|
case ET_GTHAN + DOUBLE: /* append symbol */
|
|
{
|
|
register FILE *fp;
|
|
register char *p;
|
|
int savetok=Lasttok;
|
|
special = TRUE;
|
|
if (altstdout) {
|
|
#ifdef _DEBUG2
|
|
_debug2(stderr, "2 >'s in eval command\n");
|
|
#endif
|
|
io_close(altstdout);
|
|
}
|
|
p = eval_token(instr, EV_TOKEN | IN_BQ);
|
|
if (fp = fopen(p, (savetok & DOUBLE)?"a":"w"))
|
|
altstdout = io_open(EV_USE_FP, fp);
|
|
else
|
|
warn(NOPEN, p);
|
|
free(p);
|
|
p = (char *)NULL;
|
|
}
|
|
break;
|
|
case ET_TWO:
|
|
case ET_TWO + DOUBLE: /* append stderr symbol (2>>)*/
|
|
{
|
|
register FILE *fp;
|
|
register char *p;
|
|
int savetok=Lasttok;
|
|
|
|
special = TRUE;
|
|
if (altstderr) {
|
|
#ifdef _DEBUG2
|
|
_debug2(stderr, "2 >'s in eval command\n");
|
|
#endif
|
|
io_close(altstderr);
|
|
altstderr = NULL;
|
|
}
|
|
p = eval_token(instr, EV_TOKEN | IN_BQ);
|
|
if (fp = fopen(p, (savetok & DOUBLE) ?"a":"w"))
|
|
altstderr = io_open(EV_USE_FP, fp);
|
|
else
|
|
warn(NOPEN, p);
|
|
free(p);
|
|
p = (char *)NULL;
|
|
}
|
|
break;
|
|
/* OR symbol */
|
|
case ET_PIPE + DOUBLE:
|
|
special = doit = TRUE;
|
|
break;
|
|
/* AND symbol */
|
|
case ET_AMPERSAND + DOUBLE:
|
|
special = doit = TRUE;
|
|
break;
|
|
/* semicolon (twice in a row) */
|
|
case ET_SEMI + DOUBLE:
|
|
special = doit = TRUE;
|
|
break;
|
|
/* here document */
|
|
case ET_LTHAN + DOUBLE:
|
|
break;
|
|
|
|
}
|
|
if (special) {
|
|
free(argv[--argc]);
|
|
argv[argc] = (char *)NULL;
|
|
special = FALSE;
|
|
}
|
|
if (doit || argc >= MAXARGS - 1) {
|
|
register int n;
|
|
|
|
doit = FALSE;
|
|
if (!skip && !Cur_intr.skip_eval && argc > 0) /* abs */
|
|
{
|
|
argv[argc] = NULL;
|
|
EV_retcode = evalargv(argc, argv, mystdin,
|
|
altstdout ? altstdout : mystdout,
|
|
altstderr);
|
|
/*
|
|
* if there is a syntax error in the
|
|
* conditional statement then terminate
|
|
* evaluation
|
|
*/
|
|
if (conditional && (EV_retcode == FAIL))
|
|
Lasttok = ET_EOF;
|
|
}
|
|
skip = (EV_retcode && Lasttok == ET_AMPERSAND + DOUBLE)
|
|
|| (!EV_retcode && Lasttok == ET_PIPE + DOUBLE);
|
|
for (n = 0; n < argc; n++)
|
|
if (argv[n]) { /* ehr3 */
|
|
free(argv[n]);
|
|
argv[n] = (char *)NULL;
|
|
}
|
|
argc = 0;
|
|
io_close(mystdin);
|
|
if (piped) {
|
|
mystdin = altstdout;
|
|
io_seek(mystdin, 0);
|
|
piped = FALSE;
|
|
}
|
|
else {
|
|
mystdin = io_open(EV_USE_STRING, NULL);
|
|
if (altstdout)
|
|
io_close(altstdout);
|
|
}
|
|
if (altstderr)
|
|
{
|
|
io_close(altstderr);
|
|
altstderr = NULL;
|
|
}
|
|
altstdout = NULL;
|
|
if (Lasttok == ET_EOF || Lasttok == ET_BQUOTE)
|
|
break;
|
|
}
|
|
}
|
|
if ((argc = unputac(mystdout)) && argc != '\n')
|
|
putac(argc, mystdout);
|
|
if (flags & EV_GROUP)
|
|
putac('\n', mystdout);
|
|
io_seek(mystdout, 0);
|
|
io_flags(mystdout, io_flags(mystdout, 0) | FROM_BQ);
|
|
io_push(instr, mystdout);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|