/* $OpenBSD: lex.c,v 1.80 2024/04/28 16:43:15 florian Exp $ */ /* * lexical analysis and source input */ #include #include #include #include #include #include #include "sh.h" /* * states while lexing word */ #define SINVALID -1 /* invalid state */ #define SBASE 0 /* outside any lexical constructs */ #define SWORD 1 /* implicit quoting for substitute() */ #define SLETPAREN 2 /* inside (( )), implicit quoting */ #define SSQUOTE 3 /* inside '' */ #define SDQUOTE 4 /* inside "" */ #define SBRACE 5 /* inside ${} */ #define SCSPAREN 6 /* inside $() */ #define SBQUOTE 7 /* inside `` */ #define SASPAREN 8 /* inside $(( )) */ #define SHEREDELIM 9 /* parsing <<,<<- delimiter */ #define SHEREDQUOTE 10 /* parsing " in <<,<<- delimiter */ #define SPATTERN 11 /* parsing *(...|...) pattern (*+?@!) */ #define STBRACE 12 /* parsing ${..[#%]..} */ #define SBRACEQ 13 /* inside "${}" */ /* Structure to keep track of the lexing state and the various pieces of info * needed for each particular state. */ typedef struct lex_state Lex_state; struct lex_state { int ls_state; union { /* $(...) */ struct scsparen_info { int nparen; /* count open parenthesis */ int csstate; /* XXX remove */ #define ls_scsparen ls_info.u_scsparen } u_scsparen; /* $((...)) */ struct sasparen_info { int nparen; /* count open parenthesis */ int start; /* marks start of $(( in output str */ #define ls_sasparen ls_info.u_sasparen } u_sasparen; /* ((...)) */ struct sletparen_info { int nparen; /* count open parenthesis */ #define ls_sletparen ls_info.u_sletparen } u_sletparen; /* `...` */ struct sbquote_info { int indquotes; /* true if in double quotes: "`...`" */ #define ls_sbquote ls_info.u_sbquote } u_sbquote; Lex_state *base; /* used to point to next state block */ } ls_info; }; typedef struct State_info State_info; struct State_info { Lex_state *base; Lex_state *end; }; static void readhere(struct ioword *); static int getsc__(void); static void getsc_line(Source *); static int getsc_bn(void); static char *get_brace_var(XString *, char *); static int arraysub(char **); static const char *ungetsc(int); static void gethere(void); static Lex_state *push_state_(State_info *, Lex_state *); static Lex_state *pop_state_(State_info *, Lex_state *); static char *special_prompt_expand(char *); static int dopprompt(const char *, int, const char **, int); int promptlen(const char *cp, const char **spp); static int backslash_skip; static int ignore_backslash_newline; Source *source; /* yyparse/yylex source */ YYSTYPE yylval; /* result from yylex */ struct ioword *heres[HERES], **herep; char ident[IDENT+1]; char **history; /* saved commands */ char **histptr; /* last history item */ uint32_t histsize; /* history size */ /* optimized getsc_bn() */ #define getsc() (*source->str != '\0' && *source->str != '\\' \ && !backslash_skip ? *source->str++ : getsc_bn()) /* optimized getsc__() */ #define getsc_() ((*source->str != '\0') ? *source->str++ : getsc__()) #define STATE_BSIZE 32 #define PUSH_STATE(s) do { \ if (++statep == state_info.end) \ statep = push_state_(&state_info, statep); \ state = statep->ls_state = (s); \ } while (0) #define POP_STATE() do { \ if (--statep == state_info.base) \ statep = pop_state_(&state_info, statep); \ state = statep->ls_state; \ } while (0) /* * Lexical analyzer * * tokens are not regular expressions, they are LL(1). * for example, "${var:-${PWD}}", and "$(size $(whence ksh))". * hence the state stack. */ int yylex(int cf) { Lex_state states[STATE_BSIZE], *statep; State_info state_info; int c, state; XString ws; /* expandable output word */ char *wp; /* output word pointer */ char *sp, *dp; int c2; Again: states[0].ls_state = SINVALID; states[0].ls_info.base = NULL; statep = &states[1]; state_info.base = states; state_info.end = &states[STATE_BSIZE]; Xinit(ws, wp, 64, ATEMP); backslash_skip = 0; ignore_backslash_newline = 0; if (cf&ONEWORD) state = SWORD; else if (cf&LETEXPR) { *wp++ = OQUOTE; /* enclose arguments in (double) quotes */ state = SLETPAREN; statep->ls_sletparen.nparen = 0; } else { /* normal lexing */ state = (cf & HEREDELIM) ? SHEREDELIM : SBASE; while ((c = getsc()) == ' ' || c == '\t') ; if (c == '#') { ignore_backslash_newline++; while ((c = getsc()) != '\0' && c != '\n') ; ignore_backslash_newline--; } ungetsc(c); } if (source->flags & SF_ALIAS) { /* trailing ' ' in alias definition */ source->flags &= ~SF_ALIAS; /* In POSIX mode, a trailing space only counts if we are * parsing a simple command */ if (!Flag(FPOSIX) || (cf & CMDWORD)) cf |= ALIAS; } /* Initial state: one of SBASE SHEREDELIM SWORD SASPAREN */ statep->ls_state = state; /* collect non-special or quoted characters to form word */ while (!((c = getsc()) == 0 || ((state == SBASE || state == SHEREDELIM) && ctype(c, C_LEX1)))) { Xcheck(ws, wp); switch (state) { case SBASE: if (Flag(FCSHHISTORY) && (source->flags & SF_TTY) && c == '!') { char **replace = NULL; int get, i; char match[200] = { 0 }, *str = match; size_t mlen; c2 = getsc(); if (c2 == '\0' || c2 == ' ' || c2 == '\t') ; else if (c2 == '!') replace = hist_get_newest(0); else if (isdigit(c2) || c2 == '-' || isalpha(c2)) { get = !isalpha(c2); *str++ = c2; do { if ((c2 = getsc()) == '\0') break; if (c2 == '\t' || c2 == ' ' || c2 == '\n') { ungetsc(c2); break; } *str++ = c2; } while (str < &match[sizeof(match)-1]); *str = '\0'; if (get) { int h = findhistrel(match); if (h >= 0) replace = &history[h]; } else { int h = findhist(-1, 0, match, true); if (h >= 0) replace = &history[h]; } } /* * XXX ksh history buffer saves un-expanded * commands. Until the history buffer code is * changed to contain expanded commands, we * ignore the bad commands (spinning sucks) */ if (replace && **replace == '!') ungetsc(c2); else if (replace) { Source *s; /* do not strdup replacement via alloc */ s = pushs(SREREAD, source->areap); s->start = s->str = *replace; s->next = source; s->u.freeme = NULL; source = s; continue; } else if (*match != '\0') { /* restore what followed the '!' */ mlen = strlen(match); for (i = mlen-1; i >= 0; i--) ungetsc(match[i]); } else ungetsc(c2); } if (c == '[' && (cf & (VARASN|ARRAYVAR))) { *wp = EOS; /* temporary */ if (is_wdvarname(Xstring(ws, wp), false)) { char *p, *tmp; if (arraysub(&tmp)) { *wp++ = CHAR; *wp++ = c; for (p = tmp; *p; ) { Xcheck(ws, wp); *wp++ = CHAR; *wp++ = *p++; } afree(tmp, ATEMP); break; } else { Source *s; s = pushs(SREREAD, source->areap); s->start = s->str = s->u.freeme = tmp; s->next = source; source = s; } } *wp++ = CHAR; *wp++ = c; break; } /* FALLTHROUGH */ Sbase1: /* includes *(...|...) pattern (*+?@!) */ if (c == '*' || c == '@' || c == '+' || c == '?' || c == '!') { c2 = getsc(); if (c2 == '(' /*)*/ ) { *wp++ = OPAT; *wp++ = c; PUSH_STATE(SPATTERN); break; } ungetsc(c2); } /* FALLTHROUGH */ Sbase2: /* doesn't include *(...|...) pattern (*+?@!) */ switch (c) { case '\\': c = getsc(); if (c) /* trailing \ is lost */ *wp++ = QCHAR, *wp++ = c; break; case '\'': if ((cf & HEREDOC) || state == SBRACEQ) { *wp++ = CHAR, *wp++ = c; break; } *wp++ = OQUOTE; ignore_backslash_newline++; PUSH_STATE(SSQUOTE); break; case '"': *wp++ = OQUOTE; PUSH_STATE(SDQUOTE); break; default: goto Subst; } break; Subst: switch (c) { case '\\': c = getsc(); switch (c) { case '\\': case '$': case '`': *wp++ = QCHAR, *wp++ = c; break; case '"': if ((cf & HEREDOC) == 0) { *wp++ = QCHAR, *wp++ = c; break; } /* FALLTHROUGH */ default: if (cf & UNESCAPE) { *wp++ = QCHAR, *wp++ = c; break; } Xcheck(ws, wp); if (c) { /* trailing \ is lost */ *wp++ = CHAR, *wp++ = '\\'; *wp++ = CHAR, *wp++ = c; } break; } break; case '$': c = getsc(); if (c == '(') /*)*/ { c = getsc(); if (c == '(') /*)*/ { PUSH_STATE(SASPAREN); statep->ls_sasparen.nparen = 2; statep->ls_sasparen.start = Xsavepos(ws, wp); *wp++ = EXPRSUB; } else { ungetsc(c); PUSH_STATE(SCSPAREN); statep->ls_scsparen.nparen = 1; statep->ls_scsparen.csstate = 0; *wp++ = COMSUB; } } else if (c == '{') /*}*/ { *wp++ = OSUBST; *wp++ = '{'; /*}*/ wp = get_brace_var(&ws, wp); c = getsc(); /* allow :# and :% (ksh88 compat) */ if (c == ':') { *wp++ = CHAR, *wp++ = c; c = getsc(); } /* If this is a trim operation, * treat (,|,) specially in STBRACE. */ if (c == '#' || c == '%') { ungetsc(c); PUSH_STATE(STBRACE); } else { ungetsc(c); if (state == SDQUOTE || state == SBRACEQ) PUSH_STATE(SBRACEQ); else PUSH_STATE(SBRACE); } } else if (ctype(c, C_ALPHA)) { *wp++ = OSUBST; *wp++ = 'X'; do { Xcheck(ws, wp); *wp++ = c; c = getsc(); } while (ctype(c, C_ALPHA) || digit(c)); *wp++ = '\0'; *wp++ = CSUBST; *wp++ = 'X'; ungetsc(c); } else if (ctype(c, C_VAR1) || digit(c)) { Xcheck(ws, wp); *wp++ = OSUBST; *wp++ = 'X'; *wp++ = c; *wp++ = '\0'; *wp++ = CSUBST; *wp++ = 'X'; } else { *wp++ = CHAR, *wp++ = '$'; ungetsc(c); } break; case '`': PUSH_STATE(SBQUOTE); *wp++ = COMSUB; /* Need to know if we are inside double quotes * since sh/at&t-ksh translate the \" to " in * "`..\"..`". */ statep->ls_sbquote.indquotes = 0; Lex_state *s = statep; Lex_state *base = state_info.base; while (1) { for (; s != base; s--) { if (s->ls_state == SDQUOTE) { statep->ls_sbquote.indquotes = 1; break; } } if (s != base) break; if (!(s = s->ls_info.base)) break; base = s-- - STATE_BSIZE; } break; default: *wp++ = CHAR, *wp++ = c; } break; case SSQUOTE: if (c == '\'') { POP_STATE(); if (state == SBRACEQ) { *wp++ = CHAR, *wp++ = c; break; } *wp++ = CQUOTE; ignore_backslash_newline--; } else *wp++ = QCHAR, *wp++ = c; break; case SDQUOTE: if (c == '"') { POP_STATE(); *wp++ = CQUOTE; } else goto Subst; break; case SCSPAREN: /* $( .. ) */ /* todo: deal with $(...) quoting properly * kludge to partly fake quoting inside $(..): doesn't * really work because nested $(..) or ${..} inside * double quotes aren't dealt with. */ switch (statep->ls_scsparen.csstate) { case 0: /* normal */ switch (c) { case '(': statep->ls_scsparen.nparen++; break; case ')': statep->ls_scsparen.nparen--; break; case '\\': statep->ls_scsparen.csstate = 1; break; case '"': statep->ls_scsparen.csstate = 2; break; case '\'': statep->ls_scsparen.csstate = 4; ignore_backslash_newline++; break; } break; case 1: /* backslash in normal mode */ case 3: /* backslash in double quotes */ --statep->ls_scsparen.csstate; break; case 2: /* double quotes */ if (c == '"') statep->ls_scsparen.csstate = 0; else if (c == '\\') statep->ls_scsparen.csstate = 3; break; case 4: /* single quotes */ if (c == '\'') { statep->ls_scsparen.csstate = 0; ignore_backslash_newline--; } break; } if (statep->ls_scsparen.nparen == 0) { POP_STATE(); *wp++ = 0; /* end of COMSUB */ } else *wp++ = c; break; case SASPAREN: /* $(( .. )) */ /* todo: deal with $((...); (...)) properly */ /* XXX should nest using existing state machine * (embed "..", $(...), etc.) */ if (c == '(') statep->ls_sasparen.nparen++; else if (c == ')') { statep->ls_sasparen.nparen--; if (statep->ls_sasparen.nparen == 1) { /*(*/ if ((c2 = getsc()) == ')') { POP_STATE(); *wp++ = 0; /* end of EXPRSUB */ break; } else { char *s; ungetsc(c2); /* mismatched parenthesis - * assume we were really * parsing a $(..) expression */ s = Xrestpos(ws, wp, statep->ls_sasparen.start); memmove(s + 1, s, wp - s); *s++ = COMSUB; *s = '('; /*)*/ wp++; statep->ls_scsparen.nparen = 1; statep->ls_scsparen.csstate = 0; state = statep->ls_state = SCSPAREN; } } } *wp++ = c; break; case SBRACEQ: /*{*/ if (c == '}') { POP_STATE(); *wp++ = CSUBST; *wp++ = /*{*/ '}'; } else goto Sbase2; break; case SBRACE: /*{*/ if (c == '}') { POP_STATE(); *wp++ = CSUBST; *wp++ = /*{*/ '}'; } else goto Sbase1; break; case STBRACE: /* Same as SBRACE, except (,|,) treated specially */ /*{*/ if (c == '}') { POP_STATE(); *wp++ = CSUBST; *wp++ = /*{*/ '}'; } else if (c == '|') { *wp++ = SPAT; } else if (c == '(') { *wp++ = OPAT; *wp++ = ' '; /* simile for @ */ PUSH_STATE(SPATTERN); } else goto Sbase1; break; case SBQUOTE: if (c == '`') { *wp++ = 0; POP_STATE(); } else if (c == '\\') { switch (c = getsc()) { case '\\': case '$': case '`': *wp++ = c; break; case '"': if (statep->ls_sbquote.indquotes) { *wp++ = c; break; } /* FALLTHROUGH */ default: if (c) { /* trailing \ is lost */ *wp++ = '\\'; *wp++ = c; } break; } } else *wp++ = c; break; case SWORD: /* ONEWORD */ goto Subst; case SLETPAREN: /* LETEXPR: (( ... )) */ /*(*/ if (c == ')') { if (statep->ls_sletparen.nparen > 0) --statep->ls_sletparen.nparen; /*(*/ else if ((c2 = getsc()) == ')') { c = 0; *wp++ = CQUOTE; goto Done; } else ungetsc(c2); } else if (c == '(') /* parenthesis inside quotes and backslashes * are lost, but at&t ksh doesn't count them * either */ ++statep->ls_sletparen.nparen; goto Sbase2; case SHEREDELIM: /* <<,<<- delimiter */ /* XXX chuck this state (and the next) - use * the existing states ($ and \`..` should be * stripped of their specialness after the * fact). */ /* here delimiters need a special case since * $ and `..` are not to be treated specially */ if (c == '\\') { c = getsc(); if (c) { /* trailing \ is lost */ *wp++ = QCHAR; *wp++ = c; } } else if (c == '\'') { PUSH_STATE(SSQUOTE); *wp++ = OQUOTE; ignore_backslash_newline++; } else if (c == '"') { state = statep->ls_state = SHEREDQUOTE; *wp++ = OQUOTE; } else { *wp++ = CHAR; *wp++ = c; } break; case SHEREDQUOTE: /* " in <<,<<- delimiter */ if (c == '"') { *wp++ = CQUOTE; state = statep->ls_state = SHEREDELIM; } else { if (c == '\\') { switch (c = getsc()) { case '\\': case '"': case '$': case '`': break; default: if (c) { /* trailing \ lost */ *wp++ = CHAR; *wp++ = '\\'; } break; } } *wp++ = CHAR; *wp++ = c; } break; case SPATTERN: /* in *(...|...) pattern (*+?@!) */ if ( /*(*/ c == ')') { *wp++ = CPAT; POP_STATE(); } else if (c == '|') { *wp++ = SPAT; } else if (c == '(') { *wp++ = OPAT; *wp++ = ' '; /* simile for @ */ PUSH_STATE(SPATTERN); } else goto Sbase1; break; } } Done: Xcheck(ws, wp); if (statep != &states[1]) /* XXX figure out what is missing */ yyerror("no closing quote\n"); /* This done to avoid tests for SHEREDELIM wherever SBASE tested */ if (state == SHEREDELIM) state = SBASE; dp = Xstring(ws, wp); if ((c == '<' || c == '>') && state == SBASE && ((c2 = Xlength(ws, wp)) == 0 || (c2 == 2 && dp[0] == CHAR && digit(dp[1])))) { struct ioword *iop = alloc(sizeof(*iop), ATEMP); if (c2 == 2) iop->unit = dp[1] - '0'; else iop->unit = c == '>'; /* 0 for <, 1 for > */ c2 = getsc(); /* <<, >>, <> are ok, >< is not */ if (c == c2 || (c == '<' && c2 == '>')) { iop->flag = c == c2 ? (c == '>' ? IOCAT : IOHERE) : IORDWR; if (iop->flag == IOHERE) { if ((c2 = getsc()) == '-') iop->flag |= IOSKIP; else ungetsc(c2); } } else if (c2 == '&') iop->flag = IODUP | (c == '<' ? IORDUP : 0); else { iop->flag = c == '>' ? IOWRITE : IOREAD; if (c == '>' && c2 == '|') iop->flag |= IOCLOB; else ungetsc(c2); } iop->name = NULL; iop->delim = NULL; iop->heredoc = NULL; Xfree(ws, wp); /* free word */ yylval.iop = iop; return REDIR; } if (wp == dp && state == SBASE) { Xfree(ws, wp); /* free word */ /* no word, process LEX1 character */ switch (c) { default: return c; case '|': case '&': case ';': if ((c2 = getsc()) == c) c = (c == ';') ? BREAK : (c == '|') ? LOGOR : (c == '&') ? LOGAND : YYERRCODE; else if (c == '|' && c2 == '&') c = COPROC; else ungetsc(c2); return c; case '\n': gethere(); if (cf & CONTIN) goto Again; return c; case '(': /*)*/ if (!Flag(FSH)) { if ((c2 = getsc()) == '(') /*)*/ /* XXX need to handle ((...); (...)) */ c = MDPAREN; else ungetsc(c2); } return c; /*(*/ case ')': return c; } } *wp++ = EOS; /* terminate word */ yylval.cp = Xclose(ws, wp); if (state == SWORD || state == SLETPAREN) /* ONEWORD? */ return LWORD; ungetsc(c); /* unget terminator */ /* copy word to unprefixed string ident */ for (sp = yylval.cp, dp = ident; dp < ident+IDENT && (c = *sp++) == CHAR; ) *dp++ = *sp++; /* Make sure the ident array stays '\0' padded */ memset(dp, 0, (ident+IDENT) - dp + 1); if (c != EOS) *ident = '\0'; /* word is not unquoted */ if (*ident != '\0' && (cf&(KEYWORD|ALIAS))) { struct tbl *p; int h = hash(ident); /* { */ if ((cf & KEYWORD) && (p = ktsearch(&keywords, ident, h)) && (!(cf & ESACONLY) || p->val.i == ESAC || p->val.i == '}')) { afree(yylval.cp, ATEMP); return p->val.i; } if ((cf & ALIAS) && (p = ktsearch(&aliases, ident, h)) && (p->flag & ISSET)) { Source *s; for (s = source; s->type == SALIAS; s = s->next) if (s->u.tblp == p) return LWORD; /* push alias expansion */ s = pushs(SALIAS, source->areap); s->start = s->str = p->val.s; s->u.tblp = p; s->next = source; source = s; afree(yylval.cp, ATEMP); goto Again; } } return LWORD; } static void gethere(void) { struct ioword **p; for (p = heres; p < herep; p++) readhere(*p); herep = heres; } /* * read "<delim, 0); if (!(iop->flag & IOEVAL)) ignore_backslash_newline++; Xinit(xs, xp, 256, ATEMP); for (;;) { eofp = eof; skiptabs = iop->flag & IOSKIP; xpos = Xsavepos(xs, xp); while ((c = getsc()) != 0) { if (skiptabs) { if (c == '\t') continue; skiptabs = 0; } if (c != *eofp) break; Xcheck(xs, xp); Xput(xs, xp, c); eofp++; } /* Allow EOF here so commands with out trailing newlines * will work (eg, ksh -c '...', $(...), etc). */ if (*eofp == '\0' && (c == 0 || c == '\n')) { xp = Xrestpos(xs, xp, xpos); break; } ungetsc(c); while ((c = getsc()) != '\n') { if (c == 0) yyerror("here document `%s' unclosed\n", eof); Xcheck(xs, xp); Xput(xs, xp, c); } Xcheck(xs, xp); Xput(xs, xp, c); } Xput(xs, xp, '\0'); iop->heredoc = Xclose(xs, xp); if (!(iop->flag & IOEVAL)) ignore_backslash_newline--; } void yyerror(const char *fmt, ...) { va_list va; /* pop aliases and re-reads */ while (source->type == SALIAS || source->type == SREREAD) source = source->next; source->str = null; /* zap pending input */ error_prefix(true); va_start(va, fmt); shf_vfprintf(shl_out, fmt, va); va_end(va); errorf(NULL); } /* * input for yylex with alias expansion */ Source * pushs(int type, Area *areap) { Source *s; s = alloc(sizeof(Source), areap); s->type = type; s->str = null; s->start = NULL; s->line = 0; s->cmd_offset = 0; s->errline = 0; s->file = NULL; s->flags = 0; s->next = NULL; s->areap = areap; if (type == SFILE || type == SSTDIN) { char *dummy; Xinit(s->xs, dummy, 256, s->areap); } else memset(&s->xs, 0, sizeof(s->xs)); return s; } static int getsc__(void) { Source *s = source; int c; while ((c = *s->str++) == 0) { s->str = NULL; /* return 0 for EOF by default */ switch (s->type) { case SEOF: s->str = null; return 0; case SSTDIN: case SFILE: getsc_line(s); break; case SWSTR: break; case SSTRING: break; case SWORDS: s->start = s->str = *s->u.strv++; s->type = SWORDSEP; break; case SWORDSEP: if (*s->u.strv == NULL) { s->start = s->str = "\n"; s->type = SEOF; } else { s->start = s->str = " "; s->type = SWORDS; } break; case SALIAS: if (s->flags & SF_ALIASEND) { /* pass on an unused SF_ALIAS flag */ source = s->next; source->flags |= s->flags & SF_ALIAS; s = source; } else if (*s->u.tblp->val.s && isspace((unsigned char)strchr(s->u.tblp->val.s, 0)[-1])) { source = s = s->next; /* pop source stack */ /* Note that this alias ended with a space, * enabling alias expansion on the following * word. */ s->flags |= SF_ALIAS; } else { /* At this point, we need to keep the current * alias in the source list so recursive * aliases can be detected and we also need * to return the next character. Do this * by temporarily popping the alias to get * the next character and then put it back * in the source list with the SF_ALIASEND * flag set. */ source = s->next; /* pop source stack */ source->flags |= s->flags & SF_ALIAS; c = getsc__(); if (c) { s->flags |= SF_ALIASEND; s->ugbuf[0] = c; s->ugbuf[1] = '\0'; s->start = s->str = s->ugbuf; s->next = source; source = s; } else { s = source; /* avoid reading eof twice */ s->str = NULL; break; } } continue; case SREREAD: if (s->start != s->ugbuf) /* yuck */ afree(s->u.freeme, ATEMP); source = s = s->next; continue; } if (s->str == NULL) { s->type = SEOF; s->start = s->str = null; return '\0'; } if (s->flags & SF_ECHO) { shf_puts(s->str, shl_out); shf_flush(shl_out); } } return c; } static void getsc_line(Source *s) { char *xp = Xstring(s->xs, xp); int interactive = Flag(FTALKING) && s->type == SSTDIN; int have_tty = interactive && (s->flags & SF_TTY); /* Done here to ensure nothing odd happens when a timeout occurs */ XcheckN(s->xs, xp, LINE); *xp = '\0'; s->start = s->str = xp; if (have_tty && ksh_tmout) { ksh_tmout_state = TMOUT_READING; alarm(ksh_tmout); } if (have_tty && (0 #ifdef VI || Flag(FVI) #endif /* VI */ #ifdef EMACS || Flag(FEMACS) || Flag(FGMACS) #endif /* EMACS */ )) { int nread; nread = x_read(xp, LINE); if (nread < 0) /* read error */ nread = 0; xp[nread] = '\0'; xp += nread; } else { if (interactive) { pprompt(prompt, 0); } else s->line++; while (1) { char *p = shf_getse(xp, Xnleft(s->xs, xp), s->u.shf); if (!p && shf_error(s->u.shf) && s->u.shf->errno_ == EINTR) { shf_clearerr(s->u.shf); if (trap) runtraps(0); continue; } if (!p || (xp = p, xp[-1] == '\n')) break; /* double buffer size */ xp++; /* move past null so doubling works... */ XcheckN(s->xs, xp, Xlength(s->xs, xp)); xp--; /* ...and move back again */ } /* flush any unwanted input so other programs/builtins * can read it. Not very optimal, but less error prone * than flushing else where, dealing with redirections, * etc.. * todo: reduce size of shf buffer (~128?) if SSTDIN */ if (s->type == SSTDIN) shf_flush(s->u.shf); } /* XXX: temporary kludge to restore source after a * trap may have been executed. */ source = s; if (have_tty && ksh_tmout) { ksh_tmout_state = TMOUT_EXECUTING; alarm(0); } s->start = s->str = Xstring(s->xs, xp); strip_nuls(Xstring(s->xs, xp), Xlength(s->xs, xp)); /* Note: if input is all nulls, this is not eof */ if (Xlength(s->xs, xp) == 0) { /* EOF */ if (s->type == SFILE) shf_fdclose(s->u.shf); s->str = NULL; } else if (interactive) { char *p = Xstring(s->xs, xp); if (cur_prompt == PS1) while (*p && ctype(*p, C_IFS) && ctype(*p, C_IFSWS)) p++; if (*p) { s->line++; histsave(s->line, s->str, 1); } } if (interactive) set_prompt(PS2); } static char * special_prompt_expand(char *str) { char *p = str; while ((p = strstr(p, "\\$")) != NULL) { *(p+1) = 'p'; } return str; } void set_prompt(int to) { char *ps1; Area *saved_atemp; cur_prompt = to; switch (to) { case PS1: /* command */ ps1 = str_save(str_val(global("PS1")), ATEMP); saved_atemp = ATEMP; /* ps1 is freed by substitute() */ newenv(E_ERRH); if (sigsetjmp(genv->jbuf, 0)) { prompt = safe_prompt; /* Don't print an error - assume it has already * been printed. Reason is we may have forked * to run a command and the child may be * unwinding its stack through this code as it * exits. */ } else { /* expand \$ before other substitutions are done */ char *tmp = special_prompt_expand(ps1); prompt = str_save(substitute(tmp, 0), saved_atemp); } quitenv(NULL); break; case PS2: /* command continuation */ prompt = str_val(global("PS2")); break; } } static int dopprompt(const char *sp, int ntruncate, const char **spp, int doprint) { char strbuf[1024], tmpbuf[1024], *p, *str, nbuf[32], delimiter = '\0'; int len, c, n, totlen = 0, indelimit = 0, counting = 1, delimitthis; const char *cp = sp; struct tm *tm; time_t t; if (*cp && cp[1] == '\r') { delimiter = *cp; cp += 2; } while (*cp != 0) { delimitthis = 0; if (indelimit && *cp != delimiter) ; else if (*cp == '\n' || *cp == '\r') { totlen = 0; sp = cp + 1; } else if (*cp == '\t') { if (counting) totlen = (totlen | 7) + 1; } else if (*cp == delimiter) { indelimit = !indelimit; delimitthis = 1; } if (*cp == '\\') { cp++; if (!*cp) break; /* Expand \h and \$ for both, sh(1) and ksh(1) */ if (Flag(FSH) && !(*cp == 'h' || *cp == 'p')) snprintf(strbuf, sizeof strbuf, "\\%c", *cp); else switch (*cp) { case 'a': /* '\' 'a' bell */ strbuf[0] = '\007'; strbuf[1] = '\0'; break; case 'd': /* '\' 'd' Dow Mon DD */ time(&t); tm = localtime(&t); if (tm) strftime(strbuf, sizeof strbuf, "%a %b %d", tm); else strbuf[0] = '\0'; break; case 'D': /* '\' 'D' '{' strftime format '}' */ p = strchr(cp + 2, '}'); if (cp[1] != '{' || p == NULL) { snprintf(strbuf, sizeof strbuf, "\\%c", *cp); break; } strlcpy(tmpbuf, cp + 2, sizeof tmpbuf); p = strchr(tmpbuf, '}'); if (p) *p = '\0'; time(&t); tm = localtime(&t); if (tm) strftime(strbuf, sizeof strbuf, tmpbuf, tm); else strbuf[0] = '\0'; cp = strchr(cp + 2, '}'); break; case 'e': /* '\' 'e' escape */ strbuf[0] = '\033'; strbuf[1] = '\0'; break; case 'h': /* '\' 'h' shortened hostname */ gethostname(strbuf, sizeof strbuf); p = strchr(strbuf, '.'); if (p) *p = '\0'; break; case 'H': /* '\' 'H' full hostname */ gethostname(strbuf, sizeof strbuf); break; case 'j': /* '\' 'j' number of jobs */ snprintf(strbuf, sizeof strbuf, "%d", j_njobs()); break; case 'l': /* '\' 'l' basename of tty */ p = ttyname(0); if (p) p = basename(p); if (p) strlcpy(strbuf, p, sizeof strbuf); break; case 'n': /* '\' 'n' newline */ strbuf[0] = '\n'; strbuf[1] = '\0'; totlen = 0; /* reset for prompt re-print */ sp = cp + 1; break; case 'p': /* '\' '$' $ or # */ strbuf[0] = ksheuid ? '$' : '#'; strbuf[1] = '\0'; break; case 'r': /* '\' 'r' return */ strbuf[0] = '\r'; strbuf[1] = '\0'; totlen = 0; /* reset for prompt re-print */ sp = cp + 1; break; case 's': /* '\' 's' basename $0 */ strlcpy(strbuf, kshname, sizeof strbuf); break; case 't': /* '\' 't' 24 hour HH:MM:SS */ time(&t); tm = localtime(&t); if (tm) strftime(strbuf, sizeof strbuf, "%T", tm); else strbuf[0] = '\0'; break; case 'T': /* '\' 'T' 12 hour HH:MM:SS */ time(&t); tm = localtime(&t); if (tm) strftime(strbuf, sizeof strbuf, "%l:%M:%S", tm); else strbuf[0] = '\0'; break; case '@': /* '\' '@' 12 hour am/pm format */ time(&t); tm = localtime(&t); if (tm) strftime(strbuf, sizeof strbuf, "%r", tm); else strbuf[0] = '\0'; break; case 'A': /* '\' 'A' 24 hour HH:MM */ time(&t); tm = localtime(&t); if (tm) strftime(strbuf, sizeof strbuf, "%R", tm); else strbuf[0] = '\0'; break; case 'u': /* '\' 'u' username */ strlcpy(strbuf, username, sizeof strbuf); break; #ifndef SMALL case 'v': /* '\' 'v' version (short) */ p = strchr(ksh_version, ' '); if (p) p = strchr(p + 1, ' '); if (p) { p++; strlcpy(strbuf, p, sizeof strbuf); p = strchr(strbuf, ' '); if (p) *p = '\0'; } break; case 'V': /* '\' 'V' version (long) */ strlcpy(strbuf, ksh_version, sizeof strbuf); break; #endif /* SMALL */ case 'w': /* '\' 'w' cwd */ p = str_val(global("PWD")); n = strlen(str_val(global("HOME"))); if (strcmp(p, "/") == 0) { strlcpy(strbuf, p, sizeof strbuf); } else if (strcmp(p, str_val(global("HOME"))) == 0) { strbuf[0] = '~'; strbuf[1] = '\0'; } else if (strncmp(p, str_val(global("HOME")), n) == 0 && p[n] == '/') { snprintf(strbuf, sizeof strbuf, "~/%s", str_val(global("PWD")) + n + 1); } else strlcpy(strbuf, p, sizeof strbuf); break; case 'W': /* '\' 'W' basename(cwd) */ p = str_val(global("PWD")); if (strcmp(p, str_val(global("HOME"))) == 0) { strbuf[0] = '~'; strbuf[1] = '\0'; } else strlcpy(strbuf, basename(p), sizeof strbuf); break; case '!': /* '\' '!' history line number */ snprintf(strbuf, sizeof strbuf, "%d", source->line + 1); break; case '#': /* '\' '#' command line number */ snprintf(strbuf, sizeof strbuf, "%d", source->line - source->cmd_offset + 1); break; case '0': /* '\' '#' '#' ' #' octal numeric handling */ case '1': case '2': case '3': case '4': case '5': case '6': case '7': if ((cp[1] > '7' || cp[1] < '0') || (cp[2] > '7' || cp[2] < '0')) { snprintf(strbuf, sizeof strbuf, "\\%c", *cp); break; } n = (cp[0] - '0') * 8 * 8 + (cp[1] - '0') * 8 + (cp[2] - '0'); snprintf(strbuf, sizeof strbuf, "%c", n); cp += 2; break; case '\\': /* '\' '\' */ strbuf[0] = '\\'; strbuf[1] = '\0'; break; case '[': /* '\' '[' .... stop counting */ strbuf[0] = '\0'; counting = 0; break; case ']': /* '\' ']' restart counting */ strbuf[0] = '\0'; counting = 1; break; default: snprintf(strbuf, sizeof strbuf, "\\%c", *cp); break; } cp++; str = strbuf; len = strlen(str); if (ntruncate) { if (ntruncate >= len) { ntruncate -= len; continue; } str += ntruncate; len -= ntruncate; ntruncate = 0; } if (doprint) shf_write(str, len, shl_out); if (counting && !indelimit && !delimitthis) totlen += len; continue; } else if (*cp != '!') c = *cp++; else if (*++cp == '!') c = *cp++; else { shf_snprintf(p = nbuf, sizeof(nbuf), "%d", source->line + 1); len = strlen(nbuf); if (ntruncate) { if (ntruncate >= len) { ntruncate -= len; continue; } p += ntruncate; len -= ntruncate; ntruncate = 0; } if (doprint) shf_write(p, len, shl_out); if (counting && !indelimit && !delimitthis) totlen += len; continue; } if (counting && ntruncate) --ntruncate; else if (doprint) { shf_putc(c, shl_out); } if (counting && !indelimit && !delimitthis) totlen++; } if (doprint) shf_flush(shl_out); if (spp) *spp = sp; return (totlen); } void pprompt(const char *cp, int ntruncate) { dopprompt(cp, ntruncate, NULL, 1); } int promptlen(const char *cp, const char **spp) { return dopprompt(cp, 0, spp, 0); } /* Read the variable part of a ${...} expression (ie, up to but not including * the :[-+?=#%] or close-brace. */ static char * get_brace_var(XString *wsp, char *wp) { enum parse_state { PS_INITIAL, PS_SAW_HASH, PS_IDENT, PS_NUMBER, PS_VAR1, PS_END } state; char c; state = PS_INITIAL; while (1) { c = getsc(); /* State machine to figure out where the variable part ends. */ switch (state) { case PS_INITIAL: if (c == '#') { state = PS_SAW_HASH; break; } /* FALLTHROUGH */ case PS_SAW_HASH: if (letter(c)) state = PS_IDENT; else if (digit(c)) state = PS_NUMBER; else if (ctype(c, C_VAR1)) state = PS_VAR1; else state = PS_END; break; case PS_IDENT: if (!letnum(c)) { state = PS_END; if (c == '[') { char *tmp, *p; if (!arraysub(&tmp)) yyerror("missing ]\n"); *wp++ = c; for (p = tmp; *p; ) { Xcheck(*wsp, wp); *wp++ = *p++; } afree(tmp, ATEMP); c = getsc(); /* the ] */ } } break; case PS_NUMBER: if (!digit(c)) state = PS_END; break; case PS_VAR1: state = PS_END; break; case PS_END: /* keep gcc happy */ break; } if (state == PS_END) { *wp++ = '\0'; /* end of variable part */ ungetsc(c); break; } Xcheck(*wsp, wp); *wp++ = c; } return wp; } /* * Save an array subscript - returns true if matching bracket found, false * if eof or newline was found. * (Returned string double null terminated) */ static int arraysub(char **strp) { XString ws; char *wp; char c; int depth = 1; /* we are just past the initial [ */ Xinit(ws, wp, 32, ATEMP); do { c = getsc(); Xcheck(ws, wp); *wp++ = c; if (c == '[') depth++; else if (c == ']') depth--; } while (depth > 0 && c && c != '\n'); *wp++ = '\0'; *strp = Xclose(ws, wp); return depth == 0 ? 1 : 0; } /* Unget a char: handles case when we are already at the start of the buffer */ static const char * ungetsc(int c) { if (backslash_skip) backslash_skip--; /* Don't unget eof... */ if (source->str == null && c == '\0') return source->str; if (source->str > source->start) source->str--; else { Source *s; s = pushs(SREREAD, source->areap); s->ugbuf[0] = c; s->ugbuf[1] = '\0'; s->start = s->str = s->ugbuf; s->next = source; source = s; } return source->str; } /* Called to get a char that isn't a \newline sequence. */ static int getsc_bn(void) { int c, c2; if (ignore_backslash_newline) return getsc_(); if (backslash_skip == 1) { backslash_skip = 2; return getsc_(); } backslash_skip = 0; while (1) { c = getsc_(); if (c == '\\') { if ((c2 = getsc_()) == '\n') /* ignore the \newline; get the next char... */ continue; ungetsc(c2); backslash_skip = 1; } return c; } } static Lex_state * push_state_(State_info *si, Lex_state *old_end) { Lex_state *new = areallocarray(NULL, STATE_BSIZE, sizeof(Lex_state), ATEMP); new[0].ls_info.base = old_end; si->base = &new[0]; si->end = &new[STATE_BSIZE]; return &new[1]; } static Lex_state * pop_state_(State_info *si, Lex_state *old_end) { Lex_state *old_base = si->base; si->base = old_end->ls_info.base - STATE_BSIZE; si->end = old_end->ls_info.base; afree(old_base, ATEMP); return si->base + STATE_BSIZE - 1; }