blob: c36d1204bd445d8f4634a43a7d457796c524ad4f [file] [log] [blame] [edit]
/*-
* Copyright (c) 1991, 1993
* The Regents of the University of California. All rights reserved.
* Copyright (c) 1997-2005
* Herbert Xu <herbert@gondor.apana.org.au>. 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.
* 3. 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.
*/
#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h> /* defines BUFSIZ */
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
/*
* This file implements the input routines used by the parser.
*/
#include "alias.h"
#include "error.h"
#include "eval.h"
#include "input.h"
#include "main.h"
#include "memalloc.h"
#include "myhistedit.h"
#include "options.h"
#include "output.h"
#include "parser.h"
#include "redir.h"
#include "shell.h"
#include "syntax.h"
#include "system.h"
#include "trap.h"
#define IBUFSIZ (BUFSIZ + PUNGETC_MAX + 1)
MKINIT
struct stdin_state {
tcflag_t canon;
off_t seekable;
struct termios tios;
int pip[2];
int pending;
};
MKINIT struct parsefile basepf; /* top level input file */
MKINIT char basebuf[IBUFSIZ]; /* buffer for top level input file */
MKINIT struct parsefile *toppf = &basepf;
MKINIT struct stdin_state stdin_state;
struct parsefile *parsefile = &basepf; /* current input file */
int whichprompt; /* 1 == PS1, 2 == PS2 */
int stdin_istty = -1;
MKINIT void input_init(void);
STATIC void pushfile(void);
static void popstring(void);
static int preadfd(void);
static void setinputfd(int fd, int push);
static int preadbuffer(void);
#ifdef mkinit
INCLUDE <stdio.h>
INCLUDE <string.h>
INCLUDE <termios.h>
INCLUDE <unistd.h>
INCLUDE "input.h"
INCLUDE "error.h"
INCLUDE "syntax.h"
INIT {
basepf.nextc = basepf.buf = basebuf;
basepf.linno = 1;
}
RESET {
int c;
/* clear input buffer */
popallfiles();
c = PEOF;
if (toppf->nextc - toppf->buf > toppf->unget)
c = toppf->nextc[-toppf->unget - 1];
while (c != '\n' && c != PEOF && !int_pending())
c = pgetc();
}
FORKRESET {
popallfiles();
if (parsefile->fd > 0) {
close(parsefile->fd);
parsefile->fd = 0;
}
if (stdin_state.pip[0]) {
close(stdin_state.pip[0]);
close(stdin_state.pip[1]);
memset(stdin_state.pip, 0, sizeof(stdin_state.pip));
}
}
POSTEXITRESET {
flush_input();
}
#endif
void input_init(void)
{
struct stdin_state *st = &stdin_state;
int istty;
istty = tcgetattr(0, &st->tios) + 1;
st->seekable = istty ? 0 : lseek(0, 0, SEEK_CUR) + 1;
st->canon = istty ? st->tios.c_lflag & ICANON : 0;
stdin_istty = istty;
}
static bool stdin_bufferable(void)
{
struct stdin_state *st = &stdin_state;
if (stdin_istty < 0)
input_init();
return st->canon || st->seekable;
}
static void flush_tee(void *buf, int nr, int pending)
{
while (pending > 0) {
int err;
err = read(0, buf, nr > pending ? pending : nr);
if (err > 0)
pending -= err;
}
}
static int stdin_tee(void *buf, int nr)
{
int err;
if (stdin_istty)
return 0;
if (!stdin_state.pip[0]) {
err = pipe(stdin_state.pip);
if (err < 0)
return err;
if (stdin_state.pip[0] < 10)
stdin_state.pip[0] = savefd(stdin_state.pip[0],
stdin_state.pip[0]);
if (stdin_state.pip[1] < 10)
stdin_state.pip[1] = savefd(stdin_state.pip[1],
stdin_state.pip[1]);
}
flush_tee(buf, nr, stdin_state.pending);
err = tee(0, stdin_state.pip[1], nr, 0);
stdin_state.pending = err;
return err;
}
static void freestrings(struct strpush *sp)
{
INTOFF;
do {
struct strpush *psp;
if (sp->ap) {
sp->ap->flag &= ~ALIASINUSE;
if (sp->ap->flag & ALIASDEAD) {
unalias(sp->ap->name);
}
}
psp = sp;
sp = sp->spfree;
if (psp != &(parsefile->basestrpush))
ckfree(psp);
} while (sp);
parsefile->spfree = NULL;
INTON;
}
static int __pgetc(void)
{
int c;
if (parsefile->unget) {
long unget = -(long)(unsigned)parsefile->unget--;
return parsefile->nextc[unget];
}
if (parsefile->nleft > 0) {
parsefile->nleft--;
c = (signed char)*parsefile->nextc++;
} else
c = preadbuffer();
return c;
}
/*
* Read a character from the script, returning PEOF on end of file.
* Nul characters in the input are silently discarded.
*/
int __attribute__((noinline)) pgetc(void)
{
struct strpush *sp = parsefile->spfree;
if (unlikely(sp))
freestrings(sp);
return __pgetc();
}
int pgetc_eoa(void)
{
return parsefile->strpush && parsefile->nleft == -1 &&
parsefile->strpush->ap ? PEOA : pgetc();
}
static int stdin_clear_nonblock(void)
{
int flags = fcntl(0, F_GETFL, 0);
if (flags >= 0) {
flags &=~ O_NONBLOCK;
flags = fcntl(0, F_SETFL, flags);
}
return flags;
}
static int
preadfd(void)
{
char *buf = parsefile->buf;
int fd = parsefile->fd;
int unget;
int pnr;
int nr;
nr = input_get_lleft(parsefile);
unget = parsefile->nextc - buf;
if (unget > PUNGETC_MAX)
unget = PUNGETC_MAX;
memmove(buf, parsefile->nextc - unget, unget + nr);
buf += unget;
parsefile->nextc = buf;
buf += nr;
nr = BUFSIZ - nr;
if (!IS_DEFINED_SMALL && !nr)
return nr;
pnr = nr;
retry:
nr = pnr;
#ifndef SMALL
if (fd == 0 && el) {
static const char *rl_cp;
static int el_len;
if (rl_cp == NULL) {
struct stackmark smark;
pushstackmark(&smark, stackblocksize());
rl_cp = el_gets(el, &el_len);
popstackmark(&smark);
}
if (rl_cp == NULL)
nr = 0;
else {
if (nr > el_len)
nr = el_len;
memcpy(buf, rl_cp, nr);
if (nr != el_len) {
el_len -= nr;
rl_cp += nr;
} else
rl_cp = 0;
}
return nr;
}
#endif
if (!fd && !stdin_bufferable()) {
nr = stdin_tee(buf, nr);
fd = stdin_state.pip[0];
if (nr <= 0) {
fd = 0;
nr = 1;
}
}
if (nr >= 0)
nr = read(fd, buf, nr);
if (nr < 0) {
if (errno == EINTR && !(basepf.prev && pending_sig))
goto retry;
if (fd == 0 && errno == EWOULDBLOCK &&
stdin_clear_nonblock() >= 0) {
out2str("sh: turning off NDELAY mode\n");
goto retry;
}
}
return nr;
}
/*
* Refill the input buffer and return the next input character:
*
* 1) If a string was pushed back on the input, pop it;
* 2) If we are reading from a string we can't refill the buffer, return EOF.
* 3) If there is more stuff in this buffer, use it else call read to fill it.
* 4) Process input up to the next newline, deleting nul characters.
*/
static int preadbuffer(void)
{
int first = whichprompt == 1;
int something;
char savec;
int more;
char *q;
if (unlikely(parsefile->strpush)) {
popstring();
return __pgetc();
}
if (parsefile->eof & 2) {
eof:
parsefile->eof = 3;
return PEOF;
}
flushall();
q = parsefile->nextc;
something = !first;
more = input_get_lleft(parsefile);
INTOFF;
if (more <= 0) {
int nr;
again:
nr = q - parsefile->nextc;
more = preadfd();
q = parsefile->nextc + nr;
if (more <= 0) {
input_set_lleft(parsefile, parsefile->nleft = 0);
if (!IS_DEFINED_SMALL && nr > 0)
goto save;
INTON;
goto eof;
}
}
/* delete nul characters */
for (;;) {
int c;
more--;
c = *q;
if (!c) {
memmove(q, q + 1, more);
goto check;
}
q++;
if (IS_DEFINED_SMALL)
goto check;
switch (c) {
case '\n':
goto done;
default:
something = 1;
/* fall through */
case '\t':
case ' ':
break;
}
check:
if (more <= 0) {
if (!IS_DEFINED_SMALL)
goto again;
break;
}
}
done:
input_set_lleft(parsefile, more);
save:
parsefile->nleft = q - parsefile->nextc - 1;
if (!IS_DEFINED_SMALL)
savec = *q;
*q = '\0';
if (parsefile->fd == 0 && hist && something) {
HistEvent he;
history(hist, &he, first ? H_ENTER : H_APPEND,
parsefile->nextc);
}
INTON;
if (vflag) {
out2str(parsefile->nextc);
#ifdef FLUSHERR
flushout(out2);
#endif
}
if (!IS_DEFINED_SMALL)
*q = savec;
return (signed char)*parsefile->nextc++;
}
void pungetn(int n)
{
parsefile->unget += n;
}
/*
* Undo a call to pgetc. Only two characters may be pushed back.
* PEOF may be pushed back.
*/
void
pungetc(void)
{
pungetn(1 - (parsefile->eof & 1));
parsefile->eof &= ~1;
}
/*
* Push a string back onto the input at this current parsefile level.
* We handle aliases this way.
*/
void
pushstring(char *s, void *ap)
{
struct strpush *sp;
size_t len;
len = strlen(s);
INTOFF;
/*dprintf("*** calling pushstring: %s, %d\n", s, len);*/
if ((unsigned long)parsefile->strpush |
(unsigned long)parsefile->spfree) {
sp = ckmalloc(sizeof (struct strpush));
sp->prev = parsefile->strpush;
parsefile->strpush = sp;
} else
sp = parsefile->strpush = &(parsefile->basestrpush);
sp->prevstring = parsefile->nextc;
sp->prevnleft = parsefile->nleft;
sp->unget = parsefile->unget;
sp->spfree = parsefile->spfree;
sp->ap = (struct alias *)ap;
if (ap) {
((struct alias *)ap)->flag |= ALIASINUSE;
sp->string = ((struct alias *)ap)->name;
}
parsefile->nextc = s;
parsefile->nleft = len;
parsefile->unget = 0;
parsefile->spfree = NULL;
INTON;
}
static void popstring(void)
{
struct strpush *sp = parsefile->strpush;
INTOFF;
if (sp->ap && parsefile->nextc > sp->string) {
if (parsefile->nextc[-1] == ' ' ||
parsefile->nextc[-1] == '\t') {
checkkwd |= CHKALIAS;
}
if (sp->string != sp->ap->name) {
ckfree(sp->string);
}
}
parsefile->nextc = sp->prevstring;
parsefile->nleft = sp->prevnleft;
parsefile->unget = sp->unget;
/*dprintf("*** calling popstring: restoring to '%s'\n", parsenextc);*/
parsefile->strpush = sp->prev;
parsefile->spfree = sp;
INTON;
}
/*
* Set the input to take input from a file. If push is set, push the
* old input onto the stack first.
*/
int
setinputfile(const char *fname, int flags)
{
int fd;
INTOFF;
fd = sh_open(fname, O_RDONLY, flags & INPUT_NOFILE_OK);
if (fd < 0)
goto out;
if (fd < 10)
fd = savefd(fd, fd);
setinputfd(fd, flags & INPUT_PUSH_FILE);
out:
INTON;
return fd;
}
/*
* Like setinputfile, but takes an open file descriptor. Call this with
* interrupts off.
*/
static void
setinputfd(int fd, int push)
{
pushfile();
if (!push)
toppf = parsefile;
parsefile->fd = fd;
parsefile->nextc = parsefile->buf = ckmalloc(IBUFSIZ);
}
/*
* Like setinputfile, but takes input from a string.
*/
void
setinputstring(char *string)
{
INTOFF;
pushfile();
parsefile->nextc = string;
parsefile->nleft = strlen(string);
parsefile->eof = 2;
INTON;
}
/*
* To handle the "." command, a stack of input files is used. Pushfile
* adds a new entry to the stack and popfile restores the previous level.
*/
STATIC void
pushfile(void)
{
struct parsefile *pf;
pf = (struct parsefile *)ckmalloc(sizeof (struct parsefile));
memset(pf, 0, sizeof(*pf));
pf->prev = parsefile;
pf->linno = 1;
pf->fd = -1;
parsefile = pf;
}
void pushstdin(void)
{
INTOFF;
basepf.prev = parsefile;
parsefile = &basepf;
INTON;
}
void
popfile(void)
{
struct parsefile *pf = parsefile;
INTOFF;
parsefile = pf->prev;
pf->prev = NULL;
if (pf == &basepf)
goto out;
if (pf->fd >= 0)
close(pf->fd);
ckfree(pf->buf);
if (parsefile->spfree)
freestrings(parsefile->spfree);
while (pf->strpush) {
popstring();
freestrings(parsefile->spfree);
}
ckfree(pf);
out:
INTON;
}
void __attribute__((noinline)) unwindfiles(struct parsefile *stop)
{
while (basepf.prev || parsefile != stop)
popfile();
}
/*
* Return to top level.
*/
void
popallfiles(void)
{
unwindfiles(toppf);
}
void __attribute__((noinline)) flush_input(void)
{
int left = basepf.nleft + input_get_lleft(&basepf);
INTOFF;
if (stdin_state.seekable && left)
lseek(0, -left, SEEK_CUR);
else if (stdin_state.pending > left) {
flush_tee(basebuf, BUFSIZ, stdin_state.pending - left);
stdin_state.pending = 0;
}
input_set_lleft(&basepf, basepf.nleft = 0);
INTON;
}
void reset_input(void)
{
stdin_istty = -1;
basepf.eof = 0;
flush_input();
}