freebsd-nq/bin/sh/parser.c
Jilles Tjoelker d358fa780b wordexp: Rewrite to make WRDE_NOCMD reliable.
Shell syntax is too complicated to detect command substitution and unquoted
operators reliably without implementing much of sh's parser. Therefore, have
sh do this detection.

While changing sh's support anyway, also read input from a pipe instead of
arguments to avoid {ARG_MAX} limits and improve privacy, and output count
and length using 16 instead of 8 digits.

The basic concept is:
execl("/bin/sh", "sh", "-c", "freebsd_wordexp ${1:+\"$1\"} -f "$2",
    "", flags & WRDE_NOCMD ? "-p" : "", <pipe with words>);

The WRDE_BADCHAR error is still implemented in libc. POSIX requires us to
fail strings containing unquoted braces with code WRDE_BADCHAR. Since this
is normally not a syntax error in sh, there is still a need for checking
code in libc, we_check().

The new we_check() is an optimistic check that all the characters
  <newline> | & ; < > ( ) { }
are quoted. To avoid duplicating too much sh logic, such characters are
permitted when quoting characters are seen, even if the quoting characters
may themselves be quoted. This code reports all WRDE_BADCHAR errors; bad
characters that get past it and are a syntax error in sh return WRDE_SYNTAX.

Although many implementations of WRDE_NOCMD erroneously allow some command
substitutions (and ours even documented this), there appears to be code that
relies on its security (codesearch.debian.net shows quite a few uses).
Passing untrusted data to wordexp() still exposes a denial of service
possibility and a fairly large attack surface.

Reviewed by:	wblock (man page only)
MFC after:	2 weeks
Relnotes:	yes
Security:	fixes command execution with wordexp(untrusted, WRDE_NOCMD)
2015-09-30 21:32:29 +00:00

2114 lines
45 KiB
C

/*-
* Copyright (c) 1991, 1993
* The Regents of the University of California. All rights reserved.
*
* This code is derived from software contributed to Berkeley by
* Kenneth Almquist.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#ifndef lint
#if 0
static char sccsid[] = "@(#)parser.c 8.7 (Berkeley) 5/16/95";
#endif
#endif /* not lint */
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include "shell.h"
#include "parser.h"
#include "nodes.h"
#include "expand.h" /* defines rmescapes() */
#include "syntax.h"
#include "options.h"
#include "input.h"
#include "output.h"
#include "var.h"
#include "error.h"
#include "memalloc.h"
#include "mystring.h"
#include "alias.h"
#include "show.h"
#include "eval.h"
#include "exec.h" /* to check for special builtins */
#ifndef NO_HISTORY
#include "myhistedit.h"
#endif
/*
* Shell command parser.
*/
#define PROMPTLEN 128
/* values of checkkwd variable */
#define CHKALIAS 0x1
#define CHKKWD 0x2
#define CHKNL 0x4
/* values returned by readtoken */
#include "token.h"
struct heredoc {
struct heredoc *next; /* next here document in list */
union node *here; /* redirection node */
char *eofmark; /* string indicating end of input */
int striptabs; /* if set, strip leading tabs */
};
struct parser_temp {
struct parser_temp *next;
void *data;
};
static struct heredoc *heredoclist; /* list of here documents to read */
static int doprompt; /* if set, prompt the user */
static int needprompt; /* true if interactive and at start of line */
static int lasttoken; /* last token read */
static int tokpushback; /* last token pushed back */
static char *wordtext; /* text of last word returned by readtoken */
static int checkkwd;
static struct nodelist *backquotelist;
static union node *redirnode;
static struct heredoc *heredoc;
static int quoteflag; /* set if (part of) last token was quoted */
static int startlinno; /* line # where last token started */
static int funclinno; /* line # where the current function started */
static struct parser_temp *parser_temp;
#define NOEOFMARK ((const char *)&heredoclist)
static union node *list(int);
static union node *andor(void);
static union node *pipeline(void);
static union node *command(void);
static union node *simplecmd(union node **, union node *);
static union node *makename(void);
static union node *makebinary(int type, union node *n1, union node *n2);
static void parsefname(void);
static void parseheredoc(void);
static int peektoken(void);
static int readtoken(void);
static int xxreadtoken(void);
static int readtoken1(int, const char *, const char *, int);
static int noexpand(char *);
static void consumetoken(int);
static void synexpect(int) __dead2;
static void synerror(const char *) __dead2;
static void setprompt(int);
static int pgetc_linecont(void);
static void *
parser_temp_alloc(size_t len)
{
struct parser_temp *t;
INTOFF;
t = ckmalloc(sizeof(*t));
t->data = NULL;
t->next = parser_temp;
parser_temp = t;
t->data = ckmalloc(len);
INTON;
return t->data;
}
static void *
parser_temp_realloc(void *ptr, size_t len)
{
struct parser_temp *t;
INTOFF;
t = parser_temp;
if (ptr != t->data)
error("bug: parser_temp_realloc misused");
t->data = ckrealloc(t->data, len);
INTON;
return t->data;
}
static void
parser_temp_free_upto(void *ptr)
{
struct parser_temp *t;
int done = 0;
INTOFF;
while (parser_temp != NULL && !done) {
t = parser_temp;
parser_temp = t->next;
done = t->data == ptr;
ckfree(t->data);
ckfree(t);
}
INTON;
if (!done)
error("bug: parser_temp_free_upto misused");
}
static void
parser_temp_free_all(void)
{
struct parser_temp *t;
INTOFF;
while (parser_temp != NULL) {
t = parser_temp;
parser_temp = t->next;
ckfree(t->data);
ckfree(t);
}
INTON;
}
/*
* Read and parse a command. Returns NEOF on end of file. (NULL is a
* valid parse tree indicating a blank line.)
*/
union node *
parsecmd(int interact)
{
int t;
/* This assumes the parser is not re-entered,
* which could happen if we add command substitution on PS1/PS2.
*/
parser_temp_free_all();
heredoclist = NULL;
tokpushback = 0;
checkkwd = 0;
doprompt = interact;
if (doprompt)
setprompt(1);
else
setprompt(0);
needprompt = 0;
t = readtoken();
if (t == TEOF)
return NEOF;
if (t == TNL)
return NULL;
tokpushback++;
return list(1);
}
/*
* Read and parse words for wordexp.
* Returns a list of NARG nodes; NULL if there are no words.
*/
union node *
parsewordexp(void)
{
union node *n, *first = NULL, **pnext;
int t;
/* This assumes the parser is not re-entered,
* which could happen if we add command substitution on PS1/PS2.
*/
parser_temp_free_all();
heredoclist = NULL;
tokpushback = 0;
checkkwd = 0;
doprompt = 0;
setprompt(0);
needprompt = 0;
pnext = &first;
while ((t = readtoken()) != TEOF) {
if (t != TWORD)
synexpect(TWORD);
n = makename();
*pnext = n;
pnext = &n->narg.next;
}
return first;
}
static union node *
list(int nlflag)
{
union node *ntop, *n1, *n2, *n3;
int tok;
checkkwd = CHKNL | CHKKWD | CHKALIAS;
if (!nlflag && tokendlist[peektoken()])
return NULL;
ntop = n1 = NULL;
for (;;) {
n2 = andor();
tok = readtoken();
if (tok == TBACKGND) {
if (n2 != NULL && n2->type == NPIPE) {
n2->npipe.backgnd = 1;
} else if (n2 != NULL && n2->type == NREDIR) {
n2->type = NBACKGND;
} else {
n3 = (union node *)stalloc(sizeof (struct nredir));
n3->type = NBACKGND;
n3->nredir.n = n2;
n3->nredir.redirect = NULL;
n2 = n3;
}
}
if (ntop == NULL)
ntop = n2;
else if (n1 == NULL) {
n1 = makebinary(NSEMI, ntop, n2);
ntop = n1;
}
else {
n3 = makebinary(NSEMI, n1->nbinary.ch2, n2);
n1->nbinary.ch2 = n3;
n1 = n3;
}
switch (tok) {
case TBACKGND:
case TSEMI:
tok = readtoken();
/* FALLTHROUGH */
case TNL:
if (tok == TNL) {
parseheredoc();
if (nlflag)
return ntop;
} else if (tok == TEOF && nlflag) {
parseheredoc();
return ntop;
} else {
tokpushback++;
}
checkkwd = CHKNL | CHKKWD | CHKALIAS;
if (!nlflag && tokendlist[peektoken()])
return ntop;
break;
case TEOF:
if (heredoclist)
parseheredoc();
else
pungetc(); /* push back EOF on input */
return ntop;
default:
if (nlflag)
synexpect(-1);
tokpushback++;
return ntop;
}
}
}
static union node *
andor(void)
{
union node *n;
int t;
n = pipeline();
for (;;) {
if ((t = readtoken()) == TAND) {
t = NAND;
} else if (t == TOR) {
t = NOR;
} else {
tokpushback++;
return n;
}
n = makebinary(t, n, pipeline());
}
}
static union node *
pipeline(void)
{
union node *n1, *n2, *pipenode;
struct nodelist *lp, *prev;
int negate, t;
negate = 0;
checkkwd = CHKNL | CHKKWD | CHKALIAS;
TRACE(("pipeline: entered\n"));
while (readtoken() == TNOT)
negate = !negate;
tokpushback++;
n1 = command();
if (readtoken() == TPIPE) {
pipenode = (union node *)stalloc(sizeof (struct npipe));
pipenode->type = NPIPE;
pipenode->npipe.backgnd = 0;
lp = (struct nodelist *)stalloc(sizeof (struct nodelist));
pipenode->npipe.cmdlist = lp;
lp->n = n1;
do {
prev = lp;
lp = (struct nodelist *)stalloc(sizeof (struct nodelist));
checkkwd = CHKNL | CHKKWD | CHKALIAS;
t = readtoken();
tokpushback++;
if (t == TNOT)
lp->n = pipeline();
else
lp->n = command();
prev->next = lp;
} while (readtoken() == TPIPE);
lp->next = NULL;
n1 = pipenode;
}
tokpushback++;
if (negate) {
n2 = (union node *)stalloc(sizeof (struct nnot));
n2->type = NNOT;
n2->nnot.com = n1;
return n2;
} else
return n1;
}
static union node *
command(void)
{
union node *n1, *n2;
union node *ap, **app;
union node *cp, **cpp;
union node *redir, **rpp;
int t;
int is_subshell;
checkkwd = CHKNL | CHKKWD | CHKALIAS;
is_subshell = 0;
redir = NULL;
n1 = NULL;
rpp = &redir;
/* Check for redirection which may precede command */
while (readtoken() == TREDIR) {
*rpp = n2 = redirnode;
rpp = &n2->nfile.next;
parsefname();
}
tokpushback++;
switch (readtoken()) {
case TIF:
n1 = (union node *)stalloc(sizeof (struct nif));
n1->type = NIF;
if ((n1->nif.test = list(0)) == NULL)
synexpect(-1);
consumetoken(TTHEN);
n1->nif.ifpart = list(0);
n2 = n1;
while (readtoken() == TELIF) {
n2->nif.elsepart = (union node *)stalloc(sizeof (struct nif));
n2 = n2->nif.elsepart;
n2->type = NIF;
if ((n2->nif.test = list(0)) == NULL)
synexpect(-1);
consumetoken(TTHEN);
n2->nif.ifpart = list(0);
}
if (lasttoken == TELSE)
n2->nif.elsepart = list(0);
else {
n2->nif.elsepart = NULL;
tokpushback++;
}
consumetoken(TFI);
checkkwd = CHKKWD | CHKALIAS;
break;
case TWHILE:
case TUNTIL:
t = lasttoken;
if ((n1 = list(0)) == NULL)
synexpect(-1);
consumetoken(TDO);
n1 = makebinary((t == TWHILE)? NWHILE : NUNTIL, n1, list(0));
consumetoken(TDONE);
checkkwd = CHKKWD | CHKALIAS;
break;
case TFOR:
if (readtoken() != TWORD || quoteflag || ! goodname(wordtext))
synerror("Bad for loop variable");
n1 = (union node *)stalloc(sizeof (struct nfor));
n1->type = NFOR;
n1->nfor.var = wordtext;
while (readtoken() == TNL)
;
if (lasttoken == TWORD && ! quoteflag && equal(wordtext, "in")) {
app = &ap;
while (readtoken() == TWORD) {
n2 = makename();
*app = n2;
app = &n2->narg.next;
}
*app = NULL;
n1->nfor.args = ap;
if (lasttoken != TNL && lasttoken != TSEMI)
synexpect(-1);
} else {
static char argvars[5] = {
CTLVAR, VSNORMAL|VSQUOTE, '@', '=', '\0'
};
n2 = (union node *)stalloc(sizeof (struct narg));
n2->type = NARG;
n2->narg.text = argvars;
n2->narg.backquote = NULL;
n2->narg.next = NULL;
n1->nfor.args = n2;
/*
* Newline or semicolon here is optional (but note
* that the original Bourne shell only allowed NL).
*/
if (lasttoken != TNL && lasttoken != TSEMI)
tokpushback++;
}
checkkwd = CHKNL | CHKKWD | CHKALIAS;
if ((t = readtoken()) == TDO)
t = TDONE;
else if (t == TBEGIN)
t = TEND;
else
synexpect(-1);
n1->nfor.body = list(0);
consumetoken(t);
checkkwd = CHKKWD | CHKALIAS;
break;
case TCASE:
n1 = (union node *)stalloc(sizeof (struct ncase));
n1->type = NCASE;
consumetoken(TWORD);
n1->ncase.expr = makename();
while (readtoken() == TNL);
if (lasttoken != TWORD || ! equal(wordtext, "in"))
synerror("expecting \"in\"");
cpp = &n1->ncase.cases;
checkkwd = CHKNL | CHKKWD, readtoken();
while (lasttoken != TESAC) {
*cpp = cp = (union node *)stalloc(sizeof (struct nclist));
cp->type = NCLIST;
app = &cp->nclist.pattern;
if (lasttoken == TLP)
readtoken();
for (;;) {
*app = ap = makename();
checkkwd = CHKNL | CHKKWD;
if (readtoken() != TPIPE)
break;
app = &ap->narg.next;
readtoken();
}
ap->narg.next = NULL;
if (lasttoken != TRP)
synexpect(TRP);
cp->nclist.body = list(0);
checkkwd = CHKNL | CHKKWD | CHKALIAS;
if ((t = readtoken()) != TESAC) {
if (t == TENDCASE)
;
else if (t == TFALLTHRU)
cp->type = NCLISTFALLTHRU;
else
synexpect(TENDCASE);
checkkwd = CHKNL | CHKKWD, readtoken();
}
cpp = &cp->nclist.next;
}
*cpp = NULL;
checkkwd = CHKKWD | CHKALIAS;
break;
case TLP:
n1 = (union node *)stalloc(sizeof (struct nredir));
n1->type = NSUBSHELL;
n1->nredir.n = list(0);
n1->nredir.redirect = NULL;
consumetoken(TRP);
checkkwd = CHKKWD | CHKALIAS;
is_subshell = 1;
break;
case TBEGIN:
n1 = list(0);
consumetoken(TEND);
checkkwd = CHKKWD | CHKALIAS;
break;
/* A simple command must have at least one redirection or word. */
case TBACKGND:
case TSEMI:
case TAND:
case TOR:
case TPIPE:
case TENDCASE:
case TFALLTHRU:
case TEOF:
case TNL:
case TRP:
if (!redir)
synexpect(-1);
case TWORD:
tokpushback++;
n1 = simplecmd(rpp, redir);
return n1;
default:
synexpect(-1);
}
/* Now check for redirection which may follow command */
while (readtoken() == TREDIR) {
*rpp = n2 = redirnode;
rpp = &n2->nfile.next;
parsefname();
}
tokpushback++;
*rpp = NULL;
if (redir) {
if (!is_subshell) {
n2 = (union node *)stalloc(sizeof (struct nredir));
n2->type = NREDIR;
n2->nredir.n = n1;
n1 = n2;
}
n1->nredir.redirect = redir;
}
return n1;
}
static union node *
simplecmd(union node **rpp, union node *redir)
{
union node *args, **app;
union node **orig_rpp = rpp;
union node *n = NULL;
int special;
int savecheckkwd;
/* If we don't have any redirections already, then we must reset */
/* rpp to be the address of the local redir variable. */
if (redir == 0)
rpp = &redir;
args = NULL;
app = &args;
/*
* We save the incoming value, because we need this for shell
* functions. There can not be a redirect or an argument between
* the function name and the open parenthesis.
*/
orig_rpp = rpp;
savecheckkwd = CHKALIAS;
for (;;) {
checkkwd = savecheckkwd;
if (readtoken() == TWORD) {
n = makename();
*app = n;
app = &n->narg.next;
if (savecheckkwd != 0 && !isassignment(wordtext))
savecheckkwd = 0;
} else if (lasttoken == TREDIR) {
*rpp = n = redirnode;
rpp = &n->nfile.next;
parsefname(); /* read name of redirection file */
} else if (lasttoken == TLP && app == &args->narg.next
&& rpp == orig_rpp) {
/* We have a function */
consumetoken(TRP);
funclinno = plinno;
/*
* - Require plain text.
* - Functions with '/' cannot be called.
* - Reject name=().
* - Reject ksh extended glob patterns.
*/
if (!noexpand(n->narg.text) || quoteflag ||
strchr(n->narg.text, '/') ||
strchr("!%*+-=?@}~",
n->narg.text[strlen(n->narg.text) - 1]))
synerror("Bad function name");
rmescapes(n->narg.text);
if (find_builtin(n->narg.text, &special) >= 0 &&
special)
synerror("Cannot override a special builtin with a function");
n->type = NDEFUN;
n->narg.next = command();
funclinno = 0;
return n;
} else {
tokpushback++;
break;
}
}
*app = NULL;
*rpp = NULL;
n = (union node *)stalloc(sizeof (struct ncmd));
n->type = NCMD;
n->ncmd.args = args;
n->ncmd.redirect = redir;
return n;
}
static union node *
makename(void)
{
union node *n;
n = (union node *)stalloc(sizeof (struct narg));
n->type = NARG;
n->narg.next = NULL;
n->narg.text = wordtext;
n->narg.backquote = backquotelist;
return n;
}
static union node *
makebinary(int type, union node *n1, union node *n2)
{
union node *n;
n = (union node *)stalloc(sizeof (struct nbinary));
n->type = type;
n->nbinary.ch1 = n1;
n->nbinary.ch2 = n2;
return (n);
}
void
forcealias(void)
{
checkkwd |= CHKALIAS;
}
void
fixredir(union node *n, const char *text, int err)
{
TRACE(("Fix redir %s %d\n", text, err));
if (!err)
n->ndup.vname = NULL;
if (is_digit(text[0]) && text[1] == '\0')
n->ndup.dupfd = digit_val(text[0]);
else if (text[0] == '-' && text[1] == '\0')
n->ndup.dupfd = -1;
else {
if (err)
synerror("Bad fd number");
else
n->ndup.vname = makename();
}
}
static void
parsefname(void)
{
union node *n = redirnode;
consumetoken(TWORD);
if (n->type == NHERE) {
struct heredoc *here = heredoc;
struct heredoc *p;
if (quoteflag == 0)
n->type = NXHERE;
TRACE(("Here document %d\n", n->type));
if (here->striptabs) {
while (*wordtext == '\t')
wordtext++;
}
if (! noexpand(wordtext))
synerror("Illegal eof marker for << redirection");
rmescapes(wordtext);
here->eofmark = wordtext;
here->next = NULL;
if (heredoclist == NULL)
heredoclist = here;
else {
for (p = heredoclist ; p->next ; p = p->next);
p->next = here;
}
} else if (n->type == NTOFD || n->type == NFROMFD) {
fixredir(n, wordtext, 0);
} else {
n->nfile.fname = makename();
}
}
/*
* Input any here documents.
*/
static void
parseheredoc(void)
{
struct heredoc *here;
union node *n;
while (heredoclist) {
here = heredoclist;
heredoclist = here->next;
if (needprompt) {
setprompt(2);
needprompt = 0;
}
readtoken1(pgetc(), here->here->type == NHERE? SQSYNTAX : DQSYNTAX,
here->eofmark, here->striptabs);
n = makename();
here->here->nhere.doc = n;
}
}
static int
peektoken(void)
{
int t;
t = readtoken();
tokpushback++;
return (t);
}
static int
readtoken(void)
{
int t;
struct alias *ap;
#ifdef DEBUG
int alreadyseen = tokpushback;
#endif
top:
t = xxreadtoken();
/*
* eat newlines
*/
if (checkkwd & CHKNL) {
while (t == TNL) {
parseheredoc();
t = xxreadtoken();
}
}
/*
* check for keywords and aliases
*/
if (t == TWORD && !quoteflag)
{
const char * const *pp;
if (checkkwd & CHKKWD)
for (pp = parsekwd; *pp; pp++) {
if (**pp == *wordtext && equal(*pp, wordtext))
{
lasttoken = t = pp - parsekwd + KWDOFFSET;
TRACE(("keyword %s recognized\n", tokname[t]));
goto out;
}
}
if (checkkwd & CHKALIAS &&
(ap = lookupalias(wordtext, 1)) != NULL) {
pushstring(ap->val, strlen(ap->val), ap);
goto top;
}
}
out:
if (t != TNOT)
checkkwd = 0;
#ifdef DEBUG
if (!alreadyseen)
TRACE(("token %s %s\n", tokname[t], t == TWORD ? wordtext : ""));
else
TRACE(("reread token %s %s\n", tokname[t], t == TWORD ? wordtext : ""));
#endif
return (t);
}
/*
* Read the next input token.
* If the token is a word, we set backquotelist to the list of cmds in
* backquotes. We set quoteflag to true if any part of the word was
* quoted.
* If the token is TREDIR, then we set redirnode to a structure containing
* the redirection.
* In all cases, the variable startlinno is set to the number of the line
* on which the token starts.
*
* [Change comment: here documents and internal procedures]
* [Readtoken shouldn't have any arguments. Perhaps we should make the
* word parsing code into a separate routine. In this case, readtoken
* doesn't need to have any internal procedures, but parseword does.
* We could also make parseoperator in essence the main routine, and
* have parseword (readtoken1?) handle both words and redirection.]
*/
#define RETURN(token) return lasttoken = token
static int
xxreadtoken(void)
{
int c;
if (tokpushback) {
tokpushback = 0;
return lasttoken;
}
if (needprompt) {
setprompt(2);
needprompt = 0;
}
startlinno = plinno;
for (;;) { /* until token or start of word found */
c = pgetc_macro();
switch (c) {
case ' ': case '\t':
continue;
case '#':
while ((c = pgetc()) != '\n' && c != PEOF);
pungetc();
continue;
case '\\':
if (pgetc() == '\n') {
startlinno = ++plinno;
if (doprompt)
setprompt(2);
else
setprompt(0);
continue;
}
pungetc();
/* FALLTHROUGH */
default:
return readtoken1(c, BASESYNTAX, (char *)NULL, 0);
case '\n':
plinno++;
needprompt = doprompt;
RETURN(TNL);
case PEOF:
RETURN(TEOF);
case '&':
if (pgetc_linecont() == '&')
RETURN(TAND);
pungetc();
RETURN(TBACKGND);
case '|':
if (pgetc_linecont() == '|')
RETURN(TOR);
pungetc();
RETURN(TPIPE);
case ';':
c = pgetc_linecont();
if (c == ';')
RETURN(TENDCASE);
else if (c == '&')
RETURN(TFALLTHRU);
pungetc();
RETURN(TSEMI);
case '(':
RETURN(TLP);
case ')':
RETURN(TRP);
}
}
#undef RETURN
}
#define MAXNEST_static 8
struct tokenstate
{
const char *syntax; /* *SYNTAX */
int parenlevel; /* levels of parentheses in arithmetic */
enum tokenstate_category
{
TSTATE_TOP,
TSTATE_VAR_OLD, /* ${var+-=?}, inherits dquotes */
TSTATE_VAR_NEW, /* other ${var...}, own dquote state */
TSTATE_ARITH
} category;
};
/*
* Check to see whether we are at the end of the here document. When this
* is called, c is set to the first character of the next input line. If
* we are at the end of the here document, this routine sets the c to PEOF.
* The new value of c is returned.
*/
static int
checkend(int c, const char *eofmark, int striptabs)
{
if (striptabs) {
while (c == '\t')
c = pgetc();
}
if (c == *eofmark) {
int c2;
const char *q;
for (q = eofmark + 1; c2 = pgetc(), *q != '\0' && c2 == *q; q++)
;
if ((c2 == PEOF || c2 == '\n') && *q == '\0') {
c = PEOF;
if (c2 == '\n') {
plinno++;
needprompt = doprompt;
}
} else {
pungetc();
pushstring(eofmark + 1, q - (eofmark + 1), NULL);
}
} else if (c == '\n' && *eofmark == '\0') {
c = PEOF;
plinno++;
needprompt = doprompt;
}
return (c);
}
/*
* Parse a redirection operator. The variable "out" points to a string
* specifying the fd to be redirected. The variable "c" contains the
* first character of the redirection operator.
*/
static void
parseredir(char *out, int c)
{
char fd = *out;
union node *np;
np = (union node *)stalloc(sizeof (struct nfile));
if (c == '>') {
np->nfile.fd = 1;
c = pgetc_linecont();
if (c == '>')
np->type = NAPPEND;
else if (c == '&')
np->type = NTOFD;
else if (c == '|')
np->type = NCLOBBER;
else {
np->type = NTO;
pungetc();
}
} else { /* c == '<' */
np->nfile.fd = 0;
c = pgetc_linecont();
if (c == '<') {
if (sizeof (struct nfile) != sizeof (struct nhere)) {
np = (union node *)stalloc(sizeof (struct nhere));
np->nfile.fd = 0;
}
np->type = NHERE;
heredoc = (struct heredoc *)stalloc(sizeof (struct heredoc));
heredoc->here = np;
if ((c = pgetc_linecont()) == '-') {
heredoc->striptabs = 1;
} else {
heredoc->striptabs = 0;
pungetc();
}
} else if (c == '&')
np->type = NFROMFD;
else if (c == '>')
np->type = NFROMTO;
else {
np->type = NFROM;
pungetc();
}
}
if (fd != '\0')
np->nfile.fd = digit_val(fd);
redirnode = np;
}
/*
* Called to parse command substitutions.
*/
static char *
parsebackq(char *out, struct nodelist **pbqlist,
int oldstyle, int dblquote, int quoted)
{
struct nodelist **nlpp;
union node *n;
char *volatile str;
struct jmploc jmploc;
struct jmploc *const savehandler = handler;
size_t savelen;
int saveprompt;
const int bq_startlinno = plinno;
char *volatile ostr = NULL;
struct parsefile *const savetopfile = getcurrentfile();
struct heredoc *const saveheredoclist = heredoclist;
struct heredoc *here;
str = NULL;
if (setjmp(jmploc.loc)) {
popfilesupto(savetopfile);
if (str)
ckfree(str);
if (ostr)
ckfree(ostr);
heredoclist = saveheredoclist;
handler = savehandler;
if (exception == EXERROR) {
startlinno = bq_startlinno;
synerror("Error in command substitution");
}
longjmp(handler->loc, 1);
}
INTOFF;
savelen = out - stackblock();
if (savelen > 0) {
str = ckmalloc(savelen);
memcpy(str, stackblock(), savelen);
}
handler = &jmploc;
heredoclist = NULL;
INTON;
if (oldstyle) {
/* We must read until the closing backquote, giving special
treatment to some slashes, and then push the string and
reread it as input, interpreting it normally. */
char *oout;
int c;
int olen;
STARTSTACKSTR(oout);
for (;;) {
if (needprompt) {
setprompt(2);
needprompt = 0;
}
CHECKSTRSPACE(2, oout);
c = pgetc_linecont();
if (c == '`')
break;
switch (c) {
case '\\':
c = pgetc();
if (c != '\\' && c != '`' && c != '$'
&& (!dblquote || c != '"'))
USTPUTC('\\', oout);
break;
case '\n':
plinno++;
needprompt = doprompt;
break;
case PEOF:
startlinno = plinno;
synerror("EOF in backquote substitution");
break;
default:
break;
}
USTPUTC(c, oout);
}
USTPUTC('\0', oout);
olen = oout - stackblock();
INTOFF;
ostr = ckmalloc(olen);
memcpy(ostr, stackblock(), olen);
setinputstring(ostr, 1);
INTON;
}
nlpp = pbqlist;
while (*nlpp)
nlpp = &(*nlpp)->next;
*nlpp = (struct nodelist *)stalloc(sizeof (struct nodelist));
(*nlpp)->next = NULL;
if (oldstyle) {
saveprompt = doprompt;
doprompt = 0;
}
n = list(0);
if (oldstyle) {
if (peektoken() != TEOF)
synexpect(-1);
doprompt = saveprompt;
} else
consumetoken(TRP);
(*nlpp)->n = n;
if (oldstyle) {
/*
* Start reading from old file again, ignoring any pushed back
* tokens left from the backquote parsing
*/
popfile();
tokpushback = 0;
}
STARTSTACKSTR(out);
CHECKSTRSPACE(savelen + 1, out);
INTOFF;
if (str) {
memcpy(out, str, savelen);
STADJUST(savelen, out);
ckfree(str);
str = NULL;
}
if (ostr) {
ckfree(ostr);
ostr = NULL;
}
here = saveheredoclist;
if (here != NULL) {
while (here->next != NULL)
here = here->next;
here->next = heredoclist;
heredoclist = saveheredoclist;
}
handler = savehandler;
INTON;
if (quoted)
USTPUTC(CTLBACKQ | CTLQUOTE, out);
else
USTPUTC(CTLBACKQ, out);
return out;
}
/*
* Called to parse a backslash escape sequence inside $'...'.
* The backslash has already been read.
*/
static char *
readcstyleesc(char *out)
{
int c, vc, i, n;
unsigned int v;
c = pgetc();
switch (c) {
case '\0':
synerror("Unterminated quoted string");
case '\n':
plinno++;
if (doprompt)
setprompt(2);
else
setprompt(0);
return out;
case '\\':
case '\'':
case '"':
v = c;
break;
case 'a': v = '\a'; break;
case 'b': v = '\b'; break;
case 'e': v = '\033'; break;
case 'f': v = '\f'; break;
case 'n': v = '\n'; break;
case 'r': v = '\r'; break;
case 't': v = '\t'; break;
case 'v': v = '\v'; break;
case 'x':
v = 0;
for (;;) {
c = pgetc();
if (c >= '0' && c <= '9')
v = (v << 4) + c - '0';
else if (c >= 'A' && c <= 'F')
v = (v << 4) + c - 'A' + 10;
else if (c >= 'a' && c <= 'f')
v = (v << 4) + c - 'a' + 10;
else
break;
}
pungetc();
break;
case '0': case '1': case '2': case '3':
case '4': case '5': case '6': case '7':
v = c - '0';
c = pgetc();
if (c >= '0' && c <= '7') {
v <<= 3;
v += c - '0';
c = pgetc();
if (c >= '0' && c <= '7') {
v <<= 3;
v += c - '0';
} else
pungetc();
} else
pungetc();
break;
case 'c':
c = pgetc();
if (c < 0x3f || c > 0x7a || c == 0x60)
synerror("Bad escape sequence");
if (c == '\\' && pgetc() != '\\')
synerror("Bad escape sequence");
if (c == '?')
v = 127;
else
v = c & 0x1f;
break;
case 'u':
case 'U':
n = c == 'U' ? 8 : 4;
v = 0;
for (i = 0; i < n; i++) {
c = pgetc();
if (c >= '0' && c <= '9')
v = (v << 4) + c - '0';
else if (c >= 'A' && c <= 'F')
v = (v << 4) + c - 'A' + 10;
else if (c >= 'a' && c <= 'f')
v = (v << 4) + c - 'a' + 10;
else
synerror("Bad escape sequence");
}
if (v == 0 || (v >= 0xd800 && v <= 0xdfff))
synerror("Bad escape sequence");
/* We really need iconv here. */
if (initial_localeisutf8 && v > 127) {
CHECKSTRSPACE(4, out);
/*
* We cannot use wctomb() as the locale may have
* changed.
*/
if (v <= 0x7ff) {
USTPUTC(0xc0 | v >> 6, out);
USTPUTC(0x80 | (v & 0x3f), out);
return out;
} else if (v <= 0xffff) {
USTPUTC(0xe0 | v >> 12, out);
USTPUTC(0x80 | ((v >> 6) & 0x3f), out);
USTPUTC(0x80 | (v & 0x3f), out);
return out;
} else if (v <= 0x10ffff) {
USTPUTC(0xf0 | v >> 18, out);
USTPUTC(0x80 | ((v >> 12) & 0x3f), out);
USTPUTC(0x80 | ((v >> 6) & 0x3f), out);
USTPUTC(0x80 | (v & 0x3f), out);
return out;
}
}
if (v > 127)
v = '?';
break;
default:
synerror("Bad escape sequence");
}
vc = (char)v;
/*
* We can't handle NUL bytes.
* POSIX says we should skip till the closing quote.
*/
if (vc == '\0') {
while ((c = pgetc()) != '\'') {
if (c == '\\')
c = pgetc();
if (c == PEOF)
synerror("Unterminated quoted string");
if (c == '\n') {
plinno++;
if (doprompt)
setprompt(2);
else
setprompt(0);
}
}
pungetc();
return out;
}
if (SQSYNTAX[vc] == CCTL)
USTPUTC(CTLESC, out);
USTPUTC(vc, out);
return out;
}
/*
* If eofmark is NULL, read a word or a redirection symbol. If eofmark
* is not NULL, read a here document. In the latter case, eofmark is the
* word which marks the end of the document and striptabs is true if
* leading tabs should be stripped from the document. The argument firstc
* is the first character of the input token or document.
*
* Because C does not have internal subroutines, I have simulated them
* using goto's to implement the subroutine linkage. The following macros
* will run code that appears at the end of readtoken1.
*/
#define PARSESUB() {goto parsesub; parsesub_return:;}
#define PARSEARITH() {goto parsearith; parsearith_return:;}
static int
readtoken1(int firstc, char const *initialsyntax, const char *eofmark,
int striptabs)
{
int c = firstc;
char *out;
int len;
struct nodelist *bqlist;
int quotef;
int newvarnest;
int level;
int synentry;
struct tokenstate state_static[MAXNEST_static];
int maxnest = MAXNEST_static;
struct tokenstate *state = state_static;
int sqiscstyle = 0;
startlinno = plinno;
quotef = 0;
bqlist = NULL;
newvarnest = 0;
level = 0;
state[level].syntax = initialsyntax;
state[level].parenlevel = 0;
state[level].category = TSTATE_TOP;
STARTSTACKSTR(out);
loop: { /* for each line, until end of word */
if (eofmark && eofmark != NOEOFMARK)
/* set c to PEOF if at end of here document */
c = checkend(c, eofmark, striptabs);
for (;;) { /* until end of line or end of word */
CHECKSTRSPACE(4, out); /* permit 4 calls to USTPUTC */
synentry = state[level].syntax[c];
switch(synentry) {
case CNL: /* '\n' */
if (state[level].syntax == BASESYNTAX)
goto endword; /* exit outer loop */
USTPUTC(c, out);
plinno++;
if (doprompt)
setprompt(2);
else
setprompt(0);
c = pgetc();
goto loop; /* continue outer loop */
case CSBACK:
if (sqiscstyle) {
out = readcstyleesc(out);
break;
}
/* FALLTHROUGH */
case CWORD:
USTPUTC(c, out);
break;
case CCTL:
if (eofmark == NULL || initialsyntax != SQSYNTAX)
USTPUTC(CTLESC, out);
USTPUTC(c, out);
break;
case CBACK: /* backslash */
c = pgetc();
if (c == PEOF) {
USTPUTC('\\', out);
pungetc();
} else if (c == '\n') {
plinno++;
if (doprompt)
setprompt(2);
else
setprompt(0);
} else {
if (state[level].syntax == DQSYNTAX &&
c != '\\' && c != '`' && c != '$' &&
(c != '"' || (eofmark != NULL &&
newvarnest == 0)) &&
(c != '}' || state[level].category != TSTATE_VAR_OLD))
USTPUTC('\\', out);
if ((eofmark == NULL ||
newvarnest > 0) &&
state[level].syntax == BASESYNTAX)
USTPUTC(CTLQUOTEMARK, out);
if (SQSYNTAX[c] == CCTL)
USTPUTC(CTLESC, out);
USTPUTC(c, out);
if ((eofmark == NULL ||
newvarnest > 0) &&
state[level].syntax == BASESYNTAX &&
state[level].category == TSTATE_VAR_OLD)
USTPUTC(CTLQUOTEEND, out);
quotef++;
}
break;
case CSQUOTE:
USTPUTC(CTLQUOTEMARK, out);
state[level].syntax = SQSYNTAX;
sqiscstyle = 0;
break;
case CDQUOTE:
USTPUTC(CTLQUOTEMARK, out);
state[level].syntax = DQSYNTAX;
break;
case CENDQUOTE:
if (eofmark != NULL && newvarnest == 0)
USTPUTC(c, out);
else {
if (state[level].category == TSTATE_VAR_OLD)
USTPUTC(CTLQUOTEEND, out);
state[level].syntax = BASESYNTAX;
quotef++;
}
break;
case CVAR: /* '$' */
PARSESUB(); /* parse substitution */
break;
case CENDVAR: /* '}' */
if (level > 0 &&
((state[level].category == TSTATE_VAR_OLD &&
state[level].syntax ==
state[level - 1].syntax) ||
(state[level].category == TSTATE_VAR_NEW &&
state[level].syntax == BASESYNTAX))) {
if (state[level].category == TSTATE_VAR_NEW)
newvarnest--;
level--;
USTPUTC(CTLENDVAR, out);
} else {
USTPUTC(c, out);
}
break;
case CLP: /* '(' in arithmetic */
state[level].parenlevel++;
USTPUTC(c, out);
break;
case CRP: /* ')' in arithmetic */
if (state[level].parenlevel > 0) {
USTPUTC(c, out);
--state[level].parenlevel;
} else {
if (pgetc_linecont() == ')') {
if (level > 0 &&
state[level].category == TSTATE_ARITH) {
level--;
USTPUTC(CTLENDARI, out);
} else
USTPUTC(')', out);
} else {
/*
* unbalanced parens
* (don't 2nd guess - no error)
*/
pungetc();
USTPUTC(')', out);
}
}
break;
case CBQUOTE: /* '`' */
out = parsebackq(out, &bqlist, 1,
state[level].syntax == DQSYNTAX &&
(eofmark == NULL || newvarnest > 0),
state[level].syntax == DQSYNTAX || state[level].syntax == ARISYNTAX);
break;
case CEOF:
goto endword; /* exit outer loop */
case CIGN:
break;
default:
if (level == 0)
goto endword; /* exit outer loop */
USTPUTC(c, out);
}
c = pgetc_macro();
}
}
endword:
if (state[level].syntax == ARISYNTAX)
synerror("Missing '))'");
if (state[level].syntax != BASESYNTAX && eofmark == NULL)
synerror("Unterminated quoted string");
if (state[level].category == TSTATE_VAR_OLD ||
state[level].category == TSTATE_VAR_NEW) {
startlinno = plinno;
synerror("Missing '}'");
}
if (state != state_static)
parser_temp_free_upto(state);
USTPUTC('\0', out);
len = out - stackblock();
out = stackblock();
if (eofmark == NULL) {
if ((c == '>' || c == '<')
&& quotef == 0
&& len <= 2
&& (*out == '\0' || is_digit(*out))) {
parseredir(out, c);
return lasttoken = TREDIR;
} else {
pungetc();
}
}
quoteflag = quotef;
backquotelist = bqlist;
grabstackblock(len);
wordtext = out;
return lasttoken = TWORD;
/* end of readtoken routine */
/*
* Parse a substitution. At this point, we have read the dollar sign
* and nothing else.
*/
parsesub: {
char buf[10];
int subtype;
int typeloc;
int flags;
char *p;
static const char types[] = "}-+?=";
int bracketed_name = 0; /* used to handle ${[0-9]*} variables */
int linno;
int length;
int c1;
c = pgetc_linecont();
if (c == '(') { /* $(command) or $((arith)) */
if (pgetc_linecont() == '(') {
PARSEARITH();
} else {
pungetc();
out = parsebackq(out, &bqlist, 0,
state[level].syntax == DQSYNTAX &&
(eofmark == NULL || newvarnest > 0),
state[level].syntax == DQSYNTAX ||
state[level].syntax == ARISYNTAX);
}
} else if (c == '{' || is_name(c) || is_special(c)) {
USTPUTC(CTLVAR, out);
typeloc = out - stackblock();
USTPUTC(VSNORMAL, out);
subtype = VSNORMAL;
flags = 0;
if (c == '{') {
bracketed_name = 1;
c = pgetc_linecont();
subtype = 0;
}
varname:
if (!is_eof(c) && is_name(c)) {
length = 0;
do {
STPUTC(c, out);
c = pgetc_linecont();
length++;
} while (!is_eof(c) && is_in_name(c));
if (length == 6 &&
strncmp(out - length, "LINENO", length) == 0) {
/* Replace the variable name with the
* current line number. */
linno = plinno;
if (funclinno != 0)
linno -= funclinno - 1;
snprintf(buf, sizeof(buf), "%d", linno);
STADJUST(-6, out);
STPUTS(buf, out);
flags |= VSLINENO;
}
} else if (is_digit(c)) {
if (bracketed_name) {
do {
STPUTC(c, out);
c = pgetc_linecont();
} while (is_digit(c));
} else {
STPUTC(c, out);
c = pgetc_linecont();
}
} else if (is_special(c)) {
c1 = c;
c = pgetc_linecont();
if (subtype == 0 && c1 == '#') {
subtype = VSLENGTH;
if (strchr(types, c) == NULL && c != ':' &&
c != '#' && c != '%')
goto varname;
c1 = c;
c = pgetc_linecont();
if (c1 != '}' && c == '}') {
pungetc();
c = c1;
goto varname;
}
pungetc();
c = c1;
c1 = '#';
subtype = 0;
}
USTPUTC(c1, out);
} else {
subtype = VSERROR;
if (c == '}')
pungetc();
else if (c == '\n' || c == PEOF)
synerror("Unexpected end of line in substitution");
else if (BASESYNTAX[c] != CCTL)
USTPUTC(c, out);
}
if (subtype == 0) {
switch (c) {
case ':':
flags |= VSNUL;
c = pgetc_linecont();
/*FALLTHROUGH*/
default:
p = strchr(types, c);
if (p == NULL) {
if (c == '\n' || c == PEOF)
synerror("Unexpected end of line in substitution");
if (flags == VSNUL)
STPUTC(':', out);
if (BASESYNTAX[c] != CCTL)
STPUTC(c, out);
subtype = VSERROR;
} else
subtype = p - types + VSNORMAL;
break;
case '%':
case '#':
{
int cc = c;
subtype = c == '#' ? VSTRIMLEFT :
VSTRIMRIGHT;
c = pgetc_linecont();
if (c == cc)
subtype++;
else
pungetc();
break;
}
}
} else if (subtype != VSERROR) {
if (subtype == VSLENGTH && c != '}')
subtype = VSERROR;
pungetc();
}
STPUTC('=', out);
if (state[level].syntax == DQSYNTAX ||
state[level].syntax == ARISYNTAX)
flags |= VSQUOTE;
*(stackblock() + typeloc) = subtype | flags;
if (subtype != VSNORMAL) {
if (level + 1 >= maxnest) {
maxnest *= 2;
if (state == state_static) {
state = parser_temp_alloc(
maxnest * sizeof(*state));
memcpy(state, state_static,
MAXNEST_static * sizeof(*state));
} else
state = parser_temp_realloc(state,
maxnest * sizeof(*state));
}
level++;
state[level].parenlevel = 0;
if (subtype == VSMINUS || subtype == VSPLUS ||
subtype == VSQUESTION || subtype == VSASSIGN) {
/*
* For operators that were in the Bourne shell,
* inherit the double-quote state.
*/
state[level].syntax = state[level - 1].syntax;
state[level].category = TSTATE_VAR_OLD;
} else {
/*
* The other operators take a pattern,
* so go to BASESYNTAX.
* Also, ' and " are now special, even
* in here documents.
*/
state[level].syntax = BASESYNTAX;
state[level].category = TSTATE_VAR_NEW;
newvarnest++;
}
}
} else if (c == '\'' && state[level].syntax == BASESYNTAX) {
/* $'cstylequotes' */
USTPUTC(CTLQUOTEMARK, out);
state[level].syntax = SQSYNTAX;
sqiscstyle = 1;
} else {
USTPUTC('$', out);
pungetc();
}
goto parsesub_return;
}
/*
* Parse an arithmetic expansion (indicate start of one and set state)
*/
parsearith: {
if (level + 1 >= maxnest) {
maxnest *= 2;
if (state == state_static) {
state = parser_temp_alloc(
maxnest * sizeof(*state));
memcpy(state, state_static,
MAXNEST_static * sizeof(*state));
} else
state = parser_temp_realloc(state,
maxnest * sizeof(*state));
}
level++;
state[level].syntax = ARISYNTAX;
state[level].parenlevel = 0;
state[level].category = TSTATE_ARITH;
USTPUTC(CTLARI, out);
if (state[level - 1].syntax == DQSYNTAX)
USTPUTC('"',out);
else
USTPUTC(' ',out);
goto parsearith_return;
}
} /* end of readtoken */
/*
* Returns true if the text contains nothing to expand (no dollar signs
* or backquotes).
*/
static int
noexpand(char *text)
{
char *p;
char c;
p = text;
while ((c = *p++) != '\0') {
if ( c == CTLQUOTEMARK)
continue;
if (c == CTLESC)
p++;
else if (BASESYNTAX[(int)c] == CCTL)
return 0;
}
return 1;
}
/*
* Return true if the argument is a legal variable name (a letter or
* underscore followed by zero or more letters, underscores, and digits).
*/
int
goodname(const char *name)
{
const char *p;
p = name;
if (! is_name(*p))
return 0;
while (*++p) {
if (! is_in_name(*p))
return 0;
}
return 1;
}
int
isassignment(const char *p)
{
if (!is_name(*p))
return 0;
p++;
for (;;) {
if (*p == '=')
return 1;
else if (!is_in_name(*p))
return 0;
p++;
}
}
static void
consumetoken(int token)
{
if (readtoken() != token)
synexpect(token);
}
/*
* Called when an unexpected token is read during the parse. The argument
* is the token that is expected, or -1 if more than one type of token can
* occur at this point.
*/
static void
synexpect(int token)
{
char msg[64];
if (token >= 0) {
fmtstr(msg, 64, "%s unexpected (expecting %s)",
tokname[lasttoken], tokname[token]);
} else {
fmtstr(msg, 64, "%s unexpected", tokname[lasttoken]);
}
synerror(msg);
}
static void
synerror(const char *msg)
{
if (commandname)
outfmt(out2, "%s: %d: ", commandname, startlinno);
else if (arg0)
outfmt(out2, "%s: ", arg0);
outfmt(out2, "Syntax error: %s\n", msg);
error((char *)NULL);
}
static void
setprompt(int which)
{
whichprompt = which;
#ifndef NO_HISTORY
if (!el)
#endif
{
out2str(getprompt(NULL));
flushout(out2);
}
}
static int
pgetc_linecont(void)
{
int c;
while ((c = pgetc_macro()) == '\\') {
c = pgetc();
if (c == '\n') {
plinno++;
if (doprompt)
setprompt(2);
else
setprompt(0);
} else {
pungetc();
/* Allow the backslash to be pushed back. */
pushstring("\\", 1, NULL);
return (pgetc());
}
}
return (c);
}
/*
* called by editline -- any expansions to the prompt
* should be added here.
*/
char *
getprompt(void *unused __unused)
{
static char ps[PROMPTLEN];
const char *fmt;
const char *pwd;
int i, trim;
static char internal_error[] = "??";
/*
* Select prompt format.
*/
switch (whichprompt) {
case 0:
fmt = "";
break;
case 1:
fmt = ps1val();
break;
case 2:
fmt = ps2val();
break;
default:
return internal_error;
}
/*
* Format prompt string.
*/
for (i = 0; (i < 127) && (*fmt != '\0'); i++, fmt++)
if (*fmt == '\\')
switch (*++fmt) {
/*
* Hostname.
*
* \h specifies just the local hostname,
* \H specifies fully-qualified hostname.
*/
case 'h':
case 'H':
ps[i] = '\0';
gethostname(&ps[i], PROMPTLEN - i);
/* Skip to end of hostname. */
trim = (*fmt == 'h') ? '.' : '\0';
while ((ps[i+1] != '\0') && (ps[i+1] != trim))
i++;
break;
/*
* Working directory.
*
* \W specifies just the final component,
* \w specifies the entire path.
*/
case 'W':
case 'w':
pwd = lookupvar("PWD");
if (pwd == NULL)
pwd = "?";
if (*fmt == 'W' &&
*pwd == '/' && pwd[1] != '\0')
strlcpy(&ps[i], strrchr(pwd, '/') + 1,
PROMPTLEN - i);
else
strlcpy(&ps[i], pwd, PROMPTLEN - i);
/* Skip to end of path. */
while (ps[i + 1] != '\0')
i++;
break;
/*
* Superuser status.
*
* '$' for normal users, '#' for root.
*/
case '$':
ps[i] = (geteuid() != 0) ? '$' : '#';
break;
/*
* A literal \.
*/
case '\\':
ps[i] = '\\';
break;
/*
* Emit unrecognized formats verbatim.
*/
default:
ps[i++] = '\\';
ps[i] = *fmt;
break;
}
else
ps[i] = *fmt;
ps[i] = '\0';
return (ps);
}
const char *
expandstr(const char *ps)
{
union node n;
struct jmploc jmploc;
struct jmploc *const savehandler = handler;
const int saveprompt = doprompt;
struct parsefile *const savetopfile = getcurrentfile();
struct parser_temp *const saveparser_temp = parser_temp;
const char *result = NULL;
if (!setjmp(jmploc.loc)) {
handler = &jmploc;
parser_temp = NULL;
setinputstring(ps, 1);
doprompt = 0;
readtoken1(pgetc(), DQSYNTAX, NOEOFMARK, 0);
if (backquotelist != NULL)
error("Command substitution not allowed here");
n.narg.type = NARG;
n.narg.next = NULL;
n.narg.text = wordtext;
n.narg.backquote = backquotelist;
expandarg(&n, NULL, 0);
result = stackblock();
INTOFF;
}
handler = savehandler;
doprompt = saveprompt;
popfilesupto(savetopfile);
if (parser_temp != saveparser_temp) {
parser_temp_free_all();
parser_temp = saveparser_temp;
}
if (result != NULL) {
INTON;
} else if (exception == EXINT)
raise(SIGINT);
return result;
}