Implement PS1, PS2 and PS4 expansions (variable expansions, arithmetic

expansions, and if enabled by the promptcmds option, command substitutions.)
This commit is contained in:
kre 2017-06-30 23:02:56 +00:00
parent 3b297678bf
commit 1fca9bbf62
6 changed files with 258 additions and 56 deletions

View File

@ -1,4 +1,4 @@
/* $NetBSD: expand.c,v 1.118 2017/06/19 02:46:50 kre Exp $ */ /* $NetBSD: expand.c,v 1.119 2017/06/30 23:02:56 kre Exp $ */
/*- /*-
* Copyright (c) 1991, 1993 * Copyright (c) 1991, 1993
@ -37,7 +37,7 @@
#if 0 #if 0
static char sccsid[] = "@(#)expand.c 8.5 (Berkeley) 5/15/95"; static char sccsid[] = "@(#)expand.c 8.5 (Berkeley) 5/15/95";
#else #else
__RCSID("$NetBSD: expand.c,v 1.118 2017/06/19 02:46:50 kre Exp $"); __RCSID("$NetBSD: expand.c,v 1.119 2017/06/30 23:02:56 kre Exp $");
#endif #endif
#endif /* not lint */ #endif /* not lint */
@ -177,7 +177,7 @@ expandarg(union node *arg, struct arglist *arglist, int flag)
line_number = arg->narg.lineno; line_number = arg->narg.lineno;
argstr(arg->narg.text, flag); argstr(arg->narg.text, flag);
if (arglist == NULL) { if (arglist == NULL) {
NULLTERM_4_TRACE(expdest); STACKSTRNUL(expdest);
CTRACE(DBG_EXPAND, ("expandarg: no arglist, done (%d) \"%s\"\n", CTRACE(DBG_EXPAND, ("expandarg: no arglist, done (%d) \"%s\"\n",
expdest - stackblock(), stackblock())); expdest - stackblock(), stackblock()));
return; /* here document expanded */ return; /* here document expanded */
@ -597,8 +597,10 @@ expbackq(union node *cmd, int quoted, int flag)
if (--in.nleft < 0) { if (--in.nleft < 0) {
if (in.fd < 0) if (in.fd < 0)
break; break;
INTON;
while ((i = read(in.fd, buf, sizeof buf)) < 0 && errno == EINTR) while ((i = read(in.fd, buf, sizeof buf)) < 0 && errno == EINTR)
continue; continue;
INTOFF;
VTRACE(DBG_EXPAND, ("expbackq: read returns %d\n", i)); VTRACE(DBG_EXPAND, ("expbackq: read returns %d\n", i));
if (i <= 0) if (i <= 0)
break; break;

View File

@ -1,4 +1,4 @@
/* $NetBSD: input.c,v 1.58 2017/06/07 05:08:32 kre Exp $ */ /* $NetBSD: input.c,v 1.59 2017/06/30 23:02:56 kre Exp $ */
/*- /*-
* Copyright (c) 1991, 1993 * Copyright (c) 1991, 1993
@ -37,7 +37,7 @@
#if 0 #if 0
static char sccsid[] = "@(#)input.c 8.3 (Berkeley) 6/9/95"; static char sccsid[] = "@(#)input.c 8.3 (Berkeley) 6/9/95";
#else #else
__RCSID("$NetBSD: input.c,v 1.58 2017/06/07 05:08:32 kre Exp $"); __RCSID("$NetBSD: input.c,v 1.59 2017/06/30 23:02:56 kre Exp $");
#endif #endif
#endif /* not lint */ #endif /* not lint */
@ -471,6 +471,7 @@ setinputfd(int fd, int push)
parsefile->buf = ckmalloc(BUFSIZ); parsefile->buf = ckmalloc(BUFSIZ);
parselleft = parsenleft = 0; parselleft = parsenleft = 0;
plinno = 1; plinno = 1;
CTRACE(DBG_INPUT, ("setinputfd(%d, %d); plinno=1\n", fd, push));
} }
@ -489,7 +490,7 @@ setinputstring(char *string, int push, int line1)
parselleft = parsenleft = strlen(string); parselleft = parsenleft = strlen(string);
parsefile->buf = NULL; parsefile->buf = NULL;
plinno = line1; plinno = line1;
TRACE(("setinputstring(\"%.20s%s\" (%d), %d, %d)\n", string, CTRACE(DBG_INPUT, ("setinputstring(\"%.20s%s\" (%d), %d, %d)\n", string,
(parsenleft > 20 ? "..." : ""), parsenleft, push, line1)); (parsenleft > 20 ? "..." : ""), parsenleft, push, line1));
INTON; INTON;
} }
@ -506,6 +507,10 @@ pushfile(void)
{ {
struct parsefile *pf; struct parsefile *pf;
VTRACE(DBG_INPUT, ("pushfile(): fd=%d nl=%d ll=%d \"%.*s\" plinno=%d\n",
parsefile->fd, parsenleft, parselleft,
parsenleft, parsenextc, plinno));
parsefile->nleft = parsenleft; parsefile->nleft = parsenleft;
parsefile->lleft = parselleft; parsefile->lleft = parselleft;
parsefile->nextc = parsenextc; parsefile->nextc = parsenextc;
@ -536,10 +541,40 @@ popfile(void)
parsenleft = parsefile->nleft; parsenleft = parsefile->nleft;
parselleft = parsefile->lleft; parselleft = parsefile->lleft;
parsenextc = parsefile->nextc; parsenextc = parsefile->nextc;
VTRACE(DBG_INPUT,
("popfile(): fd=%d nl=%d ll=%d \"%.*s\" plinno:%d->%d\n",
parsefile->fd, parsenleft, parselleft,
parsenleft, parsenextc, plinno, parsefile->linno));
plinno = parsefile->linno; plinno = parsefile->linno;
INTON; INTON;
} }
/*
* Return current file (to go back to it later using popfilesupto()).
*/
struct parsefile *
getcurrentfile(void)
{
return parsefile;
}
/*
* Pop files until the given file is on top again. Useful for regular
* builtins that read shell commands from files or strings.
* If the given file is not an active file, an error is raised.
*/
void
popfilesupto(struct parsefile *file)
{
while (parsefile != file && parsefile != &basepf)
popfile();
if (parsefile != file)
error("popfilesupto() misused");
}
/* /*
* Return to top level. * Return to top level.

View File

@ -1,4 +1,4 @@
/* $NetBSD: input.h,v 1.19 2017/06/07 04:44:17 kre Exp $ */ /* $NetBSD: input.h,v 1.20 2017/06/30 23:02:56 kre Exp $ */
/*- /*-
* Copyright (c) 1991, 1993 * Copyright (c) 1991, 1993
@ -45,8 +45,10 @@ extern int plinno;
extern int parsenleft; /* number of characters left in input buffer */ extern int parsenleft; /* number of characters left in input buffer */
extern const char *parsenextc; /* next character in input buffer */ extern const char *parsenextc; /* next character in input buffer */
extern int init_editline; /* 0 == not setup, 1 == OK, -1 == failed */ extern int init_editline; /* 0 == not setup, 1 == OK, -1 == failed */
extern int whichprompt; /* 1 ==> PS1, 2 ==> PS2 */
struct alias; struct alias;
struct parsefile;
char *pfgets(char *, int); char *pfgets(char *, int);
int pgetc(void); int pgetc(void);
@ -58,6 +60,8 @@ void setinputfile(const char *, int);
void setinputfd(int, int); void setinputfd(int, int);
void setinputstring(char *, int, int); void setinputstring(char *, int, int);
void popfile(void); void popfile(void);
struct parsefile *getcurrentfile(void);
void popfilesupto(struct parsefile *);
void popallfiles(void); void popallfiles(void);
void closescript(int); void closescript(int);

View File

@ -1,10 +1,10 @@
/* $NetBSD: option.list,v 1.4 2017/06/17 07:50:35 kre Exp $ */ /* $NetBSD: option.list,v 1.5 2017/06/30 23:02:56 kre Exp $ */
/* /*
* define the shell's settable options * define the shell's settable options
* *
* new options can be defined by adding them here, * new options can be defined by adding them here,
* but they do nothing untilcode to implement them * but they do nothing until code to implement them
* is added (using the "var name" field) * is added (using the "var name" field)
*/ */
@ -64,6 +64,7 @@ pflag nopriv p # preserve privs if set[ug]id
posix posix # be closer to POSIX compat posix posix # be closer to POSIX compat
qflag quietprofile q # disable -v/-x in startup files qflag quietprofile q # disable -v/-x in startup files
fnline1 local_lineno L on # number lines in funcs starting at 1 fnline1 local_lineno L on # number lines in funcs starting at 1
promptcmds promptcmds # allow $( ) in PS1 (et al).
// editline/history related options ("vi" is standard, 'V' and others are not) // editline/history related options ("vi" is standard, 'V' and others are not)
// only one of vi/emacs can be set, hence the "set" definition, value // only one of vi/emacs can be set, hence the "set" definition, value

View File

@ -1,4 +1,4 @@
/* $NetBSD: parser.c,v 1.139 2017/06/24 11:23:35 kre Exp $ */ /* $NetBSD: parser.c,v 1.140 2017/06/30 23:02:56 kre Exp $ */
/*- /*-
* Copyright (c) 1991, 1993 * Copyright (c) 1991, 1993
@ -37,7 +37,7 @@
#if 0 #if 0
static char sccsid[] = "@(#)parser.c 8.7 (Berkeley) 5/16/95"; static char sccsid[] = "@(#)parser.c 8.7 (Berkeley) 5/16/95";
#else #else
__RCSID("$NetBSD: parser.c,v 1.139 2017/06/24 11:23:35 kre Exp $"); __RCSID("$NetBSD: parser.c,v 1.140 2017/06/30 23:02:56 kre Exp $");
#endif #endif
#endif /* not lint */ #endif /* not lint */
@ -74,34 +74,34 @@ __RCSID("$NetBSD: parser.c,v 1.139 2017/06/24 11:23:35 kre Exp $");
#define OPENBRACE '{' #define OPENBRACE '{'
#define CLOSEBRACE '}' #define CLOSEBRACE '}'
struct HereDoc {
struct heredoc { struct HereDoc *next; /* next here document in list */
struct heredoc *next; /* next here document in list */
union node *here; /* redirection node */ union node *here; /* redirection node */
char *eofmark; /* string indicating end of input */ char *eofmark; /* string indicating end of input */
int striptabs; /* if set, strip leading tabs */ int striptabs; /* if set, strip leading tabs */
int startline; /* line number where << seen */ int startline; /* line number where << seen */
}; };
MKINIT struct parse_state parse_state;
union parse_state_p psp = { .c_current_parser = &parse_state };
static const struct parse_state init_parse_state = { /* all 0's ... */
static int noalias = 0; /* when set, don't handle aliases */ .ps_noalias = 0,
struct heredoc *heredoclist; /* list of here documents to read */ .ps_heredoclist = NULL,
int parsebackquote; /* nonzero if we are inside backquotes */ .ps_parsebackquote = 0,
int doprompt; /* if set, prompt the user */ .ps_doprompt = 0,
int needprompt; /* true if interactive and at start of line */ .ps_needprompt = 0,
int lasttoken; /* last token read */ .ps_lasttoken = 0,
MKINIT int tokpushback; /* last token pushed back */ .ps_tokpushback = 0,
char *wordtext; /* text of last word returned by readtoken */ .ps_wordtext = NULL,
MKINIT int checkkwd; /* 1 == check for kwds, 2 == also eat newlines */ .ps_checkkwd = 0,
struct nodelist *backquotelist; .ps_redirnode = NULL,
union node *redirnode; .ps_heredoc = NULL,
struct heredoc *heredoc; .ps_quoteflag = 0,
int quoteflag; /* set if (part of) last token was quoted */ .ps_startlinno = 0,
int startlinno; /* line # where last token started */ .ps_funclinno = 0,
int funclinno; /* line # where the current function started */ .ps_elided_nl = 0,
int elided_nl; /* count of \ \n (deleted \n's) we have seen */ };
STATIC union node *list(int, int); STATIC union node *list(int, int);
STATIC union node *andor(void); STATIC union node *andor(void);
@ -122,7 +122,6 @@ STATIC void synerror(const char *) __dead;
STATIC void setprompt(int); STATIC void setprompt(int);
STATIC int pgetc_linecont(void); STATIC int pgetc_linecont(void);
static const char EOFhere[] = "EOF reading here (<<) document"; static const char EOFhere[] = "EOF reading here (<<) document";
#ifdef DEBUG #ifdef DEBUG
@ -758,8 +757,8 @@ parsefname(void)
if (readtoken() != TWORD) if (readtoken() != TWORD)
synexpect(-1, 0); synexpect(-1, 0);
if (n->type == NHERE) { if (n->type == NHERE) {
struct heredoc *here = heredoc; struct HereDoc *here = heredoc;
struct heredoc *p; struct HereDoc *p;
if (quoteflag == 0) if (quoteflag == 0)
n->type = NXHERE; n->type = NXHERE;
@ -948,7 +947,7 @@ insert_elided_nl(char *str)
STATIC void STATIC void
readheredocs(void) readheredocs(void)
{ {
struct heredoc *here; struct HereDoc *here;
union node *n; union node *n;
int line, l; int line, l;
@ -1012,7 +1011,7 @@ readtoken(void)
#endif #endif
struct alias *ap; struct alias *ap;
top: top:
t = xxreadtoken(); t = xxreadtoken();
if (checkkwd) { if (checkkwd) {
@ -1045,6 +1044,9 @@ readtoken(void)
} }
if (!noalias && if (!noalias &&
(ap = lookupalias(wordtext, 1)) != NULL) { (ap = lookupalias(wordtext, 1)) != NULL) {
VTRACE(DBG_PARSE,
("alias '%s' recognized -> <:%s:>\n",
wordtext, ap->val));
pushstring(ap->val, strlen(ap->val), ap); pushstring(ap->val, strlen(ap->val), ap);
checkkwd = savecheckkwd; checkkwd = savecheckkwd;
goto top; goto top;
@ -1547,7 +1549,7 @@ parseredir(const char *out, int c)
np->nfile.fd = 0; np->nfile.fd = 0;
} }
np->type = NHERE; np->type = NHERE;
heredoc = stalloc(sizeof(struct heredoc)); heredoc = stalloc(sizeof(struct HereDoc));
heredoc->here = np; heredoc->here = np;
heredoc->startline = plinno; heredoc->startline = plinno;
if ((c = pgetc_linecont()) == '-') { if ((c = pgetc_linecont()) == '-') {
@ -2034,13 +2036,14 @@ parsearith: {
#ifdef mkinit #ifdef mkinit
RESET { INCLUDE "parser.h"
struct heredoc;
extern struct heredoc *heredoclist;
tokpushback = 0; RESET {
checkkwd = 0; psp.v_current_parser = &parse_state;
heredoclist = NULL;
parse_state.ps_tokpushback = 0;
parse_state.ps_checkkwd = 0;
parse_state.ps_heredoclist = NULL;
} }
#endif #endif
@ -2177,14 +2180,102 @@ pgetc_linecont(void)
const char * const char *
getprompt(void *unused) getprompt(void *unused)
{ {
char *p;
const char *cp;
if (!doprompt)
return "";
VTRACE(DBG_PARSE|DBG_EXPAND, ("getprompt %d\n", whichprompt));
switch (whichprompt) { switch (whichprompt) {
case 0: case 0:
return ""; return "";
case 1: case 1:
return ps1val(); p = ps1val();
break;
case 2: case 2:
return ps2val(); p = ps2val();
break;
default: default:
return "<internal prompt error>"; return "<internal prompt error>";
} }
if (p == NULL)
return "";
VTRACE(DBG_PARSE|DBG_EXPAND, ("prompt <<%s>>\n", p));
cp = expandstr(p, plinno);
VTRACE(DBG_PARSE|DBG_EXPAND, ("prompt -> <<%s>>\n", cp));
return cp;
}
/*
* Expand a string ... used for expanding prompts (PS1...)
*
* Never return NULL, always some string (return input string if invalid)
*/
const char *
expandstr(char *ps, int lineno)
{
union node n;
struct jmploc jmploc;
struct jmploc *const savehandler = handler;
struct parsefile *const savetopfile = getcurrentfile();
const int save_x = xflag;
struct parse_state new_state = init_parse_state;
struct parse_state *const saveparser = psp.v_current_parser;
struct stackmark smark;
const char *result = NULL;
setstackmark(&smark);
/*
* At this point we anticipate that there may be a string
* growing on the stack, but we have no idea how big it is.
* However we know that it cannot be bigger than the current
* allocated stack block, so simply reserve the whole thing,
* then we can use the stack without barfing all over what
* is there already... (the stack mark undoes this later.)
*/
(void) stalloc(stackblocksize());
if (!setjmp(jmploc.loc)) {
handler = &jmploc;
psp.v_current_parser = &new_state;
setinputstring(ps, 1, lineno);
readtoken1(pgetc(), DQSYNTAX, 1);
if (backquotelist != NULL && !promptcmds)
result = "-o promptcmds not set: ";
else {
n.narg.type = NARG;
n.narg.next = NULL;
n.narg.text = wordtext;
n.narg.lineno = lineno;
n.narg.backquote = backquotelist;
xflag = 0; /* we might be expanding PS4 ... */
expandarg(&n, NULL, 0);
result = stackblock();
}
INTOFF;
}
psp.v_current_parser = saveparser;
xflag = save_x;
popfilesupto(savetopfile);
handler = savehandler;
popstackmark(&smark);
if (result != NULL) {
INTON;
} else {
if (exception == EXINT)
exraise(SIGINT);
result = ps;
}
return result;
} }

View File

@ -1,4 +1,4 @@
/* $NetBSD: parser.h,v 1.22 2017/06/07 05:08:32 kre Exp $ */ /* $NetBSD: parser.h,v 1.23 2017/06/30 23:02:56 kre Exp $ */
/*- /*-
* Copyright (c) 1991, 1993 * Copyright (c) 1991, 1993
@ -70,21 +70,90 @@
#define VSTRIMRIGHTMAX 0x9 /* ${var%%pattern} */ #define VSTRIMRIGHTMAX 0x9 /* ${var%%pattern} */
#define VSLENGTH 0xa /* ${#var} */ #define VSLENGTH 0xa /* ${#var} */
union node *parsecmd(int);
void fixredir(union node *, const char *, int);
int goodname(char *);
const char *getprompt(void *);
const char *expandstr(char *, int);
struct HereDoc;
union node;
struct nodelist;
struct parse_state {
int ps_noalias; /* when set, don't handle aliases */
struct HereDoc *ps_heredoclist; /* list of here documents to read */
int ps_parsebackquote; /* nonzero inside backquotes */
int ps_doprompt; /* if set, prompt the user */
int ps_needprompt; /* true if interactive at line start */
int ps_lasttoken; /* last token read */
int ps_tokpushback; /* last token pushed back */
char *ps_wordtext; /* text of last word returned by readtoken */
int ps_checkkwd; /* 1 == check for kwds, 2 += eat newlines */
struct nodelist *ps_backquotelist; /* list of cmdsubs to process */
union node *ps_redirnode; /* node for current redirect */
struct HereDoc *ps_heredoc; /* current heredoc << beign parsed */
int ps_quoteflag; /* set if (part) of token was quoted */
int ps_startlinno; /* line # where last token started */
int ps_funclinno; /* line # of the current function */
int ps_elided_nl; /* count of \ \n pairs we have seen */
};
/*
* The parser references the elements of struct parse_state quite
* frequently - they used to be simple globals, so one memory ref
* per access, adding an indirect through global ptr would not be
* nice. The following gross hack allows most of that cost to be
* avoided, by allowing the compiler to understand that the global
* pointer is in fact constant in any function, and so its value can
* be cached, rather than needing to be fetched every time in case
* some other called function has changed it.
*
* The rule to make this work is that any function that wants
* to alter the global must restore it before it returns (and thus
* must have an error trap handler). That means that the struct
* used for the new parser state can be a local in that function's
* stack frame, it never needs to be malloc'd.
*/
union parse_state_p {
struct parse_state *const c_current_parser;
struct parse_state * v_current_parser;
};
extern union parse_state_p psp;
#define current_parser (psp.c_current_parser)
/*
* Perhaps one day emulate "static" by moving most of these definitions into
* parser.c ... (only checkkwd & tokpushback are used outside parser.c,
* and only in init.c as a RESET activity)
*/
#define tokpushback (current_parser->ps_tokpushback)
#define checkkwd (current_parser->ps_checkkwd)
#define noalias (current_parser->ps_noalias)
#define heredoclist (current_parser->ps_heredoclist)
#define parsebackquote (current_parser->ps_parsebackquote)
#define doprompt (current_parser->ps_doprompt)
#define needprompt (current_parser->ps_needprompt)
#define lasttoken (current_parser->ps_lasttoken)
#define wordtext (current_parser->ps_wordtext)
#define backquotelist (current_parser->ps_backquotelist)
#define redirnode (current_parser->ps_redirnode)
#define heredoc (current_parser->ps_heredoc)
#define quoteflag (current_parser->ps_quoteflag)
#define startlinno (current_parser->ps_startlinno)
#define funclinno (current_parser->ps_funclinno)
#define elided_nl (current_parser->ps_elided_nl)
/* /*
* NEOF is returned by parsecmd when it encounters an end of file. It * NEOF is returned by parsecmd when it encounters an end of file. It
* must be distinct from NULL, so we use the address of a variable that * must be distinct from NULL, so we use the address of a variable that
* happens to be handy. * happens to be handy.
*/ */
extern int tokpushback; #define NEOF ((union node *)&psp)
#define NEOF ((union node *)&tokpushback)
extern int whichprompt; /* 1 == PS1, 2 == PS2 */
union node *parsecmd(int);
void fixredir(union node *, const char *, int);
int goodname(char *);
const char *getprompt(void *);
#ifdef DEBUG #ifdef DEBUG
extern int parsing; extern int parsing;