static void readhere(struct ioword *iop) { int c; char *volatile eof; char *eofp; int skiptabs; XString xs; char *xp; int xpos; eof = evalstr(iop->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--; }
static char * do_phys_path(XString *xsp, char *xp, const char *path) { const char *p, *q; int len, llen; int savepos; char lbuf[PATH]; Xcheck(*xsp, xp); for (p = path; p; p = q) { while (*p == '/') p++; if (!*p) break; len = (q = strchr(p, '/')) ? q - p : strlen(p); if (len == 1 && p[0] == '.') continue; if (len == 2 && p[0] == '.' && p[1] == '.') { while (xp > Xstring(*xsp, xp)) { xp--; if (*xp == '/') break; } continue; } savepos = Xsavepos(*xsp, xp); Xput(*xsp, xp, '/'); XcheckN(*xsp, xp, len + 1); memcpy(xp, p, len); xp += len; *xp = '\0'; llen = readlink(Xstring(*xsp, xp), lbuf, sizeof(lbuf) - 1); if (llen < 0) { /* EINVAL means it wasn't a symlink... */ if (errno != EINVAL) return ((char *) 0); continue; } lbuf[llen] = '\0'; /* If absolute path, start from scratch.. */ xp = lbuf[0] == '/' ? Xstring(*xsp, xp) : Xrestpos(*xsp, xp, savepos); if (!(xp = do_phys_path(xsp, xp, lbuf))) return ((char *) 0); } return (xp); }
char * do_realpath(const char *upath) { char *xp, *ip, *tp, *ipath, *ldest = NULL; XString xs; size_t pos, len; int llen; struct stat sb; #ifdef MKSH__NO_PATH_MAX size_t ldestlen = 0; #define pathlen sb.st_size #define pathcnd (ldestlen < (pathlen + 1)) #else #define pathlen PATH_MAX #define pathcnd (!ldest) #endif /* max. recursion depth */ int symlinks = 32; if (mksh_abspath(upath)) { /* upath is an absolute pathname */ strdupx(ipath, upath, ATEMP); } else { /* upath is a relative pathname, prepend cwd */ if ((tp = ksh_get_wd()) == NULL || !mksh_abspath(tp)) return (NULL); ipath = shf_smprintf("%s%s%s", tp, "/", upath); afree(tp, ATEMP); } /* ipath and upath are in memory at the same time -> unchecked */ Xinit(xs, xp, strlen(ip = ipath) + 1, ATEMP); /* now jump into the deep of the loop */ goto beginning_of_a_pathname; while (*ip) { /* skip slashes in input */ while (*ip == '/') ++ip; if (!*ip) break; /* get next pathname component from input */ tp = ip; while (*ip && *ip != '/') ++ip; len = ip - tp; /* check input for "." and ".." */ if (tp[0] == '.') { if (len == 1) /* just continue with the next one */ continue; else if (len == 2 && tp[1] == '.') { /* strip off last pathname component */ while (xp > Xstring(xs, xp)) if (*--xp == '/') break; /* then continue with the next one */ continue; } } /* store output position away, then append slash to output */ pos = Xsavepos(xs, xp); /* 1 for the '/' and len + 1 for tp and the NUL from below */ XcheckN(xs, xp, 1 + len + 1); Xput(xs, xp, '/'); /* append next pathname component to output */ memcpy(xp, tp, len); xp += len; *xp = '\0'; /* lstat the current output, see if it's a symlink */ if (mksh_lstat(Xstring(xs, xp), &sb)) { /* lstat failed */ if (errno == ENOENT) { /* because the pathname does not exist */ while (*ip == '/') /* skip any trailing slashes */ ++ip; /* no more components left? */ if (!*ip) /* we can still return successfully */ break; /* more components left? fall through */ } /* not ENOENT or not at the end of ipath */ goto notfound; } /* check if we encountered a symlink? */ if (S_ISLNK(sb.st_mode)) { #ifndef MKSH__NO_SYMLINK /* reached maximum recursion depth? */ if (!symlinks--) { /* yep, prevent infinite loops */ errno = ELOOP; goto notfound; } /* get symlink(7) target */ if (pathcnd) { #ifdef MKSH__NO_PATH_MAX if (notoktoadd(pathlen, 1)) { errno = ENAMETOOLONG; goto notfound; } #endif ldest = aresize(ldest, pathlen + 1, ATEMP); } llen = readlink(Xstring(xs, xp), ldest, pathlen); if (llen < 0) /* oops... */ goto notfound; ldest[llen] = '\0'; /* * restart if symlink target is an absolute path, * otherwise continue with currently resolved prefix */ /* append rest of current input path to link target */ tp = shf_smprintf("%s%s%s", ldest, *ip ? "/" : "", ip); afree(ipath, ATEMP); ip = ipath = tp; if (!mksh_abspath(ldest)) { /* symlink target is a relative path */ xp = Xrestpos(xs, xp, pos); } else #endif { /* symlink target is an absolute path */ xp = Xstring(xs, xp); beginning_of_a_pathname: /* assert: (ip == ipath)[0] == '/' */ /* assert: xp == xs.beg => start of path */ /* exactly two leading slashes? (SUSv4 3.266) */ if (ip[1] == '/' && ip[2] != '/') { /* keep them, e.g. for UNC pathnames */ Xput(xs, xp, '/'); } } } /* otherwise (no symlink) merely go on */ } /* * either found the target and successfully resolved it, * or found its parent directory and may create it */ if (Xlength(xs, xp) == 0) /* * if the resolved pathname is "", make it "/", * otherwise do not add a trailing slash */ Xput(xs, xp, '/'); Xput(xs, xp, '\0'); /* * if source path had a trailing slash, check if target path * is not a non-directory existing file */ if (ip > ipath && ip[-1] == '/') { if (stat(Xstring(xs, xp), &sb)) { if (errno != ENOENT) goto notfound; } else if (!S_ISDIR(sb.st_mode)) { errno = ENOTDIR; goto notfound; } /* target now either does not exist or is a directory */ } /* return target path */ if (ldest != NULL) afree(ldest, ATEMP); afree(ipath, ATEMP); return (Xclose(xs, xp)); notfound: /* save; freeing memory might trash it */ llen = errno; if (ldest != NULL) afree(ldest, ATEMP); afree(ipath, ATEMP); Xfree(xs, xp); errno = llen; return (NULL); #undef pathlen #undef pathcnd }
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 = -1; states[0].ls_info.base = (Lex_state *) 0; 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; c2 = getsc(); if (c2 == '\0') ; else if (c2 == ' ' || c2 == '\t') ungetsc(c2); else if (c2 == '!') replace = hist_get_newest(0); else if (isdigit(c2) || c2 == '-' || isalpha(c2)) { int get = !isalpha(c2); char match[200], *str = match; *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; source = s; continue; } 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; } /* fall through.. */ Sbase1: /* includes *(...|...) pattern (*+?@!) */ if (c == '*' || c == '@' || c == '+' || c == '?' || c == '!') { c2 = getsc(); if (c2 == '(' /*)*/ ) { *wp++ = OPAT; *wp++ = c; PUSH_STATE(SPATTERN); break; } ungetsc(c2); } /* fall through.. */ Sbase2: /* doesn't include *(...|...) pattern (*+?@!) */ switch (c) { case '\\': c = getsc(); if (c) /* trailing \ is lost */ *wp++ = QCHAR, *wp++ = c; break; case '\'': *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 '$': case '`': *wp++ = QCHAR, *wp++ = c; break; default: 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); 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|C_DIGIT)); *wp++ = '\0'; *wp++ = CSUBST; *wp++ = 'X'; ungetsc(c); } else if (ctype(c, C_DIGIT|C_VAR1)) { 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 * "`..\"..`". * This is not done in posix mode (section * 3.2.3, Double Quotes: "The backquote shall * retain its special meaning introducing the * other form of command substitution (see * 3.6.3). The portion of the quoted string * from the initial backquote and the * characters up to the next backquote that * is not preceded by a backslash (having * escape characters removed) defines that * command whose output replaces `...` when * the word is expanded." * Section 3.6.3, Command Substitution: * "Within the backquoted style of command * substitution, backslash shall retain its * literal meaning, except when followed by * $ ` \."). */ statep->ls_sbquote.indquotes = 0; if (!Flag(FPOSIX)) { 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(); *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 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; } /* fall through.. */ 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 = (struct ioword *) 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 = (char *) 0; iop->delim = (char *) 0; iop->heredoc = (char *) 0; 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 = tsearch(&keywords, ident, h)) && (!(cf & ESACONLY) || p->val.i == ESAC || p->val.i == '}')) { afree(yylval.cp, ATEMP); return p->val.i; } if ((cf & ALIAS) && (p = tsearch(&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 readhere(struct ioword *iop) { int c; const char *eof, *eofp; XString xs; char *xp; size_t xpos; eof = evalstr(iop->delim, 0); if (!(iop->ioflag & IOEVAL)) ignore_backslash_newline++; Xinit(xs, xp, 256, ATEMP); heredoc_read_line: /* beginning of line */ eofp = eof; xpos = Xsavepos(xs, xp); if (iop->ioflag & IOSKIP) { /* skip over leading tabs */ while ((c = getsc()) == '\t') ; /* nothing */ goto heredoc_parse_char; } heredoc_read_char: c = getsc(); heredoc_parse_char: /* compare with here document marker */ if (!*eofp) { /* end of here document marker, what to do? */ switch (c) { case /*(*/ ')': if (!subshell_nesting_type) /*- * not allowed outside $(...) or (...) * => mismatch */ break; /* allow $(...) or (...) to close here */ ungetsc(/*(*/ ')'); /* FALLTHROUGH */ case 0: /* * Allow EOF here to commands without trailing * newlines (mksh -c '...') will work as well. */ case '\n': /* Newline terminates here document marker */ goto heredoc_found_terminator; } } else if (c == *eofp++) /* store; then read and compare next character */ goto heredoc_store_and_loop; /* nope, mismatch; read until end of line */ while (c != '\n') { if (!c) /* oops, reached EOF */ yyerror("%s '%s' unclosed\n", "here document", eof); /* store character */ Xcheck(xs, xp); Xput(xs, xp, c); /* read next character */ c = getsc(); } /* we read a newline as last character */ heredoc_store_and_loop: /* store character */ Xcheck(xs, xp); Xput(xs, xp, c); if (c == '\n') goto heredoc_read_line; goto heredoc_read_char; heredoc_found_terminator: /* jump back to saved beginning of line */ xp = Xrestpos(xs, xp, xpos); /* terminate, close and store */ Xput(xs, xp, '\0'); iop->heredoc = Xclose(xs, xp); if (!(iop->ioflag & IOEVAL)) ignore_backslash_newline--; }