eval.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 1993 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/* All Rights Reserved */
#pragma ident "%Z%%M% %I% %E% SMI"
#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);
}