464 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			464 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/* input.c -- functions to perform buffered input with synchronization. */
 | 
						|
 | 
						|
/* Copyright (C) 1992 Free Software Foundation, Inc.
 | 
						|
 | 
						|
   This file is part of GNU Bash, the Bourne Again SHell.
 | 
						|
 | 
						|
   Bash is free software; you can redistribute it and/or modify it under
 | 
						|
   the terms of the GNU General Public License as published by the Free
 | 
						|
   Software Foundation; either version 2, or (at your option) any later
 | 
						|
   version.
 | 
						|
 | 
						|
   Bash is distributed in the hope that it will be useful, but WITHOUT ANY
 | 
						|
   WARRANTY; without even the implied warranty of MERCHANTABILITY or
 | 
						|
   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 | 
						|
   for more details.
 | 
						|
 | 
						|
   You should have received a copy of the GNU General Public License along
 | 
						|
   with Bash; see the file COPYING.  If not, write to the Free Software
 | 
						|
   Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */
 | 
						|
 | 
						|
/* similar to stdio, but input-only. */
 | 
						|
 | 
						|
#include "bashtypes.h"
 | 
						|
#include <sys/file.h>
 | 
						|
#include "filecntl.h"
 | 
						|
#include "posixstat.h"
 | 
						|
#include <stdio.h>
 | 
						|
#include <errno.h>
 | 
						|
 | 
						|
#include "bashansi.h"
 | 
						|
#include "config.h"
 | 
						|
#include "command.h"
 | 
						|
#include "general.h"
 | 
						|
#include "input.h"
 | 
						|
 | 
						|
#if !defined (errno)
 | 
						|
extern int errno;
 | 
						|
#endif /* !errno */
 | 
						|
 | 
						|
#define MAX_INPUT_BUFFER_SIZE	8192
 | 
						|
 | 
						|
#if !defined (SEEK_CUR)
 | 
						|
#  define SEEK_CUR 1
 | 
						|
#endif /* !SEEK_CUR */
 | 
						|
 | 
						|
void free_buffered_stream ();
 | 
						|
 | 
						|
extern int interactive_shell;
 | 
						|
 | 
						|
int bash_input_fd_changed;
 | 
						|
/* This provides a way to map from a file descriptor to the buffer
 | 
						|
   associated with that file descriptor, rather than just the other
 | 
						|
   way around.  This is needed so that buffers are managed properly
 | 
						|
   in constructs like 3<&4.  buffers[x]->b_fd == x -- that is how the
 | 
						|
   correspondence is maintained. */
 | 
						|
BUFFERED_STREAM **buffers = (BUFFERED_STREAM **)NULL;
 | 
						|
static int nbuffers = 0;
 | 
						|
 | 
						|
#define max(a, b)  (((a) > (b)) ? (a) : (b))
 | 
						|
 | 
						|
#define ALLOCATE_BUFFERS(n) \
 | 
						|
	do { if ((n) >= nbuffers) allocate_buffers (n); } while (0)
 | 
						|
 | 
						|
/* Make sure `buffers' has at least N elements. */
 | 
						|
static void
 | 
						|
allocate_buffers (n)
 | 
						|
     int n;
 | 
						|
{
 | 
						|
  register int i, orig_nbuffers;
 | 
						|
 | 
						|
  orig_nbuffers = nbuffers;
 | 
						|
  nbuffers = n + 20;
 | 
						|
  buffers = (BUFFERED_STREAM **)xrealloc
 | 
						|
    (buffers, nbuffers * sizeof (BUFFERED_STREAM *));
 | 
						|
 | 
						|
  /* Zero out the new buffers. */
 | 
						|
  for (i = orig_nbuffers; i < nbuffers; i++)
 | 
						|
    buffers[i] = (BUFFERED_STREAM *)NULL;
 | 
						|
}
 | 
						|
 | 
						|
/* Construct and return a BUFFERED_STREAM corresponding to file descriptor
 | 
						|
   FD, using BUFFER. */
 | 
						|
static BUFFERED_STREAM *
 | 
						|
make_buffered_stream (fd, buffer, bufsize)
 | 
						|
     int fd;
 | 
						|
     char *buffer;
 | 
						|
     int bufsize;
 | 
						|
{
 | 
						|
  BUFFERED_STREAM *bp;
 | 
						|
 | 
						|
  bp = (BUFFERED_STREAM *)xmalloc (sizeof (BUFFERED_STREAM));
 | 
						|
  ALLOCATE_BUFFERS (fd);
 | 
						|
  buffers[fd] = bp;
 | 
						|
  bp->b_fd = fd;
 | 
						|
  bp->b_buffer = buffer;
 | 
						|
  bp->b_size = bufsize;
 | 
						|
  bp->b_used = 0;
 | 
						|
  bp->b_inputp = 0;
 | 
						|
  bp->b_flag = 0;
 | 
						|
  if (bufsize == 1)
 | 
						|
    bp->b_flag |= B_UNBUFF;
 | 
						|
  return (bp);
 | 
						|
}
 | 
						|
 | 
						|
/* Allocate a new BUFFERED_STREAM, copy BP to it, and return the new copy. */
 | 
						|
static BUFFERED_STREAM *
 | 
						|
copy_buffered_stream (bp)
 | 
						|
     BUFFERED_STREAM *bp;
 | 
						|
{
 | 
						|
  BUFFERED_STREAM *nbp;
 | 
						|
 | 
						|
  if (!bp)
 | 
						|
    return ((BUFFERED_STREAM *)NULL);
 | 
						|
 | 
						|
  nbp = (BUFFERED_STREAM *)xmalloc (sizeof (BUFFERED_STREAM));
 | 
						|
  xbcopy ((char *)bp, (char *)nbp, sizeof (BUFFERED_STREAM));
 | 
						|
  return (nbp);
 | 
						|
}
 | 
						|
 | 
						|
/* Check that file descriptor FD is not the one that bash is currently
 | 
						|
   using to read input from a script.  FD is about to be duplicated onto,
 | 
						|
   which means that the kernel will close it for us.  If FD is the bash
 | 
						|
   input file descriptor, we need to seek backwards in the script (if
 | 
						|
   possible and necessary -- scripts read from stdin are still unbuffered),
 | 
						|
   allocate a new file descriptor to use for bash input, and re-initialize
 | 
						|
   the buffered stream. */
 | 
						|
int
 | 
						|
check_bash_input (fd)
 | 
						|
     int fd;
 | 
						|
{
 | 
						|
  int nfd;
 | 
						|
 | 
						|
  if (fd > 0 && ((bash_input.type == st_bstream && bash_input.location.buffered_fd == fd) ||
 | 
						|
  		 (interactive_shell == 0 && default_buffered_input == fd)))
 | 
						|
    {
 | 
						|
      /* Sync the stream so we can re-read from the new file descriptor.  We
 | 
						|
	 might be able to avoid this by copying the buffered stream verbatim
 | 
						|
	 to the new file descriptor. */
 | 
						|
      if (buffers[fd])
 | 
						|
	sync_buffered_stream (fd);
 | 
						|
 | 
						|
      /* Now take care of duplicating the file descriptor that bash is
 | 
						|
	 using for input, so we can reinitialize it later. */
 | 
						|
      nfd = fcntl (fd, F_DUPFD, 10);
 | 
						|
      if (nfd == -1)
 | 
						|
	{
 | 
						|
	  if (fcntl (fd, F_GETFD, 0) == 0)
 | 
						|
	    report_error
 | 
						|
	      ("cannot allocate new file descriptor for bash input from fd %d: %s",
 | 
						|
		fd, strerror (errno));
 | 
						|
	  return -1;
 | 
						|
	}
 | 
						|
 | 
						|
      if (buffers[nfd])
 | 
						|
	{
 | 
						|
	  /* What's this?  A stray buffer without an associated open file
 | 
						|
	     descriptor?  Free up the buffer and report the error. */
 | 
						|
	  report_error ("check_bash_input: buffer already exists for new fd %d", nfd);
 | 
						|
	  free_buffered_stream (buffers[nfd]);
 | 
						|
	}
 | 
						|
 | 
						|
      /* Reinitialize bash_input.location. */
 | 
						|
      if (bash_input.type == st_bstream)
 | 
						|
	{
 | 
						|
	  bash_input.location.buffered_fd = nfd;
 | 
						|
	  fd_to_buffered_stream (nfd);
 | 
						|
	  close_buffered_fd (fd);	/* XXX */
 | 
						|
	}
 | 
						|
      else
 | 
						|
	/* If the current input type is not a buffered stream, but the shell
 | 
						|
	   is not interactive and therefore using a buffered stream to read
 | 
						|
	   input (e.g. with an `eval exec 3>output' inside a script), note
 | 
						|
	   that the input fd has been changed.  pop_stream() looks at this
 | 
						|
	   value and adjusts the input fd to the new value of
 | 
						|
	   default_buffered_input accordingly. */
 | 
						|
	bash_input_fd_changed++;
 | 
						|
 | 
						|
      if (default_buffered_input == fd)
 | 
						|
	default_buffered_input = nfd;
 | 
						|
    }
 | 
						|
  return 0;
 | 
						|
}
 | 
						|
      
 | 
						|
/* This is the buffered stream analogue of dup2(fd1, fd2).  The
 | 
						|
   BUFFERED_STREAM corresponding to fd2 is deallocated, if one exists.
 | 
						|
   BUFFERS[fd1] is copied to BUFFERS[fd2].  This is called by the
 | 
						|
   redirect code for constructs like 4<&0 and 3</etc/rc.local. */
 | 
						|
duplicate_buffered_stream (fd1, fd2)
 | 
						|
     int fd1, fd2;
 | 
						|
{
 | 
						|
  int is_bash_input, m;
 | 
						|
 | 
						|
  if (fd1 == fd2)
 | 
						|
    return 0;
 | 
						|
 | 
						|
  m = max (fd1, fd2);
 | 
						|
  ALLOCATE_BUFFERS (m);
 | 
						|
 | 
						|
  /* If FD2 is the file descriptor bash is currently using for shell input,
 | 
						|
     we need to do some extra work to make sure that the buffered stream
 | 
						|
     actually exists (it might not if fd1 was not active, and the copy
 | 
						|
     didn't actually do anything). */
 | 
						|
  is_bash_input = (bash_input.type == st_bstream) &&
 | 
						|
		  (bash_input.location.buffered_fd == fd2);
 | 
						|
 | 
						|
  if (buffers[fd2])
 | 
						|
    free_buffered_stream (buffers[fd2]);
 | 
						|
  buffers[fd2] = copy_buffered_stream (buffers[fd1]);
 | 
						|
  if (buffers[fd2])
 | 
						|
    buffers[fd2]->b_fd = fd2;
 | 
						|
 | 
						|
  if (is_bash_input)
 | 
						|
    {
 | 
						|
      if (!buffers[fd2])
 | 
						|
	fd_to_buffered_stream (fd2);
 | 
						|
    }
 | 
						|
  return (fd2);
 | 
						|
}
 | 
						|
 | 
						|
/* Return 1 if a seek on FD will succeed. */
 | 
						|
#define fd_is_seekable(fd) (lseek ((fd), 0L, SEEK_CUR) >= 0)
 | 
						|
 | 
						|
/* Take FD, a file descriptor, and create and return a buffered stream
 | 
						|
   corresponding to it.  If something is wrong and the file descriptor
 | 
						|
   is invalid, return a NULL stream. */
 | 
						|
BUFFERED_STREAM *
 | 
						|
fd_to_buffered_stream (fd)
 | 
						|
     int fd;
 | 
						|
{
 | 
						|
  char *buffer;
 | 
						|
  int size;
 | 
						|
  struct stat sb;
 | 
						|
 | 
						|
  if (fstat (fd, &sb) < 0)
 | 
						|
    {
 | 
						|
      close (fd);
 | 
						|
      return ((BUFFERED_STREAM *)NULL);
 | 
						|
    }
 | 
						|
 | 
						|
  if (fd_is_seekable (fd) == 0)
 | 
						|
    size = 1;
 | 
						|
  else
 | 
						|
    size = (sb.st_size > MAX_INPUT_BUFFER_SIZE) ? MAX_INPUT_BUFFER_SIZE
 | 
						|
						: sb.st_size;
 | 
						|
      
 | 
						|
  buffer = (char *)xmalloc (size);
 | 
						|
 | 
						|
  return (make_buffered_stream (fd, buffer, size));
 | 
						|
}
 | 
						|
 | 
						|
/* Return a buffered stream corresponding to FILE, a file name. */
 | 
						|
BUFFERED_STREAM *
 | 
						|
open_buffered_stream (file)
 | 
						|
     char *file;
 | 
						|
{
 | 
						|
  int fd;
 | 
						|
 | 
						|
  fd = open (file, O_RDONLY);
 | 
						|
  if (fd == -1)
 | 
						|
    return ((BUFFERED_STREAM *)NULL);
 | 
						|
  return (fd_to_buffered_stream (fd));
 | 
						|
}
 | 
						|
 | 
						|
/* Deallocate a buffered stream and free up its resources.  Make sure we
 | 
						|
   zero out the slot in BUFFERS that points to BP. */
 | 
						|
void
 | 
						|
free_buffered_stream (bp)
 | 
						|
     BUFFERED_STREAM *bp;
 | 
						|
{
 | 
						|
  int n;
 | 
						|
 | 
						|
  if (!bp)
 | 
						|
    return;
 | 
						|
 | 
						|
  n = bp->b_fd;
 | 
						|
  if (bp->b_buffer)
 | 
						|
    free (bp->b_buffer);
 | 
						|
  free (bp);
 | 
						|
  buffers[n] = (BUFFERED_STREAM *)NULL;
 | 
						|
}
 | 
						|
 | 
						|
/* Close the file descriptor associated with BP, a buffered stream, and free
 | 
						|
   up the stream.  Return the status of closing BP's file descriptor. */
 | 
						|
int
 | 
						|
close_buffered_stream (bp)
 | 
						|
     BUFFERED_STREAM *bp;
 | 
						|
{
 | 
						|
  int fd;
 | 
						|
 | 
						|
  if (!bp)
 | 
						|
    return (0);
 | 
						|
  fd = bp->b_fd;
 | 
						|
  free_buffered_stream (bp);
 | 
						|
  return (close (fd));
 | 
						|
}
 | 
						|
 | 
						|
/* Deallocate the buffered stream associated with file descriptor FD, and
 | 
						|
   close FD.  Return the status of the close on FD. */
 | 
						|
int
 | 
						|
close_buffered_fd (fd)
 | 
						|
     int fd;
 | 
						|
{
 | 
						|
  if (fd >= nbuffers || !buffers || !buffers[fd])
 | 
						|
    return (close (fd));
 | 
						|
  return (close_buffered_stream (buffers[fd]));
 | 
						|
}
 | 
						|
 | 
						|
/* Read a buffer full of characters from BP, a buffered stream. */
 | 
						|
static int
 | 
						|
b_fill_buffer (bp)
 | 
						|
     BUFFERED_STREAM *bp;
 | 
						|
{
 | 
						|
  do
 | 
						|
    {
 | 
						|
      bp->b_used = read (bp->b_fd, bp->b_buffer, bp->b_size);
 | 
						|
    }
 | 
						|
  while (bp->b_used < 0 && errno == EINTR);
 | 
						|
  if (bp->b_used <= 0)
 | 
						|
    {
 | 
						|
      bp->b_buffer[0] = 0;
 | 
						|
      if (bp->b_used == 0)
 | 
						|
	bp->b_flag |= B_EOF;
 | 
						|
      else
 | 
						|
	bp->b_flag |= B_ERROR;
 | 
						|
      return (EOF);
 | 
						|
    }
 | 
						|
  bp->b_inputp = 0;
 | 
						|
  return (bp->b_buffer[bp->b_inputp++] & 0xFF);
 | 
						|
}
 | 
						|
 | 
						|
/* Get a character from buffered stream BP. */
 | 
						|
#define bufstream_getc(bp) \
 | 
						|
  (bp->b_inputp == bp->b_used || !bp->b_used) \
 | 
						|
  		? b_fill_buffer (bp) \
 | 
						|
		: bp->b_buffer[bp->b_inputp++] & 0xFF
 | 
						|
 | 
						|
/* Push C back onto buffered stream BP. */
 | 
						|
static int
 | 
						|
bufstream_ungetc(c, bp)
 | 
						|
     int c;
 | 
						|
     BUFFERED_STREAM *bp;
 | 
						|
{
 | 
						|
  if (c == EOF || bp->b_inputp == 0)
 | 
						|
    return (EOF);
 | 
						|
 | 
						|
  bp->b_buffer[--bp->b_inputp] = c;
 | 
						|
  return (c);
 | 
						|
}
 | 
						|
 | 
						|
/* Seek backwards on file BFD to synchronize what we've read so far
 | 
						|
   with the underlying file pointer. */
 | 
						|
int
 | 
						|
sync_buffered_stream (bfd)
 | 
						|
     int bfd;
 | 
						|
{
 | 
						|
  BUFFERED_STREAM *bp;
 | 
						|
  int chars_left;
 | 
						|
 | 
						|
  bp = buffers[bfd];
 | 
						|
  if (!bp)
 | 
						|
    return (-1);
 | 
						|
  chars_left = bp->b_used - bp->b_inputp;
 | 
						|
  if (chars_left)
 | 
						|
    lseek (bp->b_fd, -chars_left, SEEK_CUR);
 | 
						|
  bp->b_used = bp->b_inputp = 0;
 | 
						|
  return (0);
 | 
						|
}
 | 
						|
 | 
						|
int
 | 
						|
buffered_getchar ()
 | 
						|
{
 | 
						|
  return (bufstream_getc (buffers[bash_input.location.buffered_fd]));
 | 
						|
}
 | 
						|
 | 
						|
int
 | 
						|
buffered_ungetchar (c)
 | 
						|
     int c;
 | 
						|
{
 | 
						|
  return (bufstream_ungetc (c, buffers[bash_input.location.buffered_fd]));
 | 
						|
}
 | 
						|
 | 
						|
/* Make input come from file descriptor BFD through a buffered stream. */
 | 
						|
void
 | 
						|
with_input_from_buffered_stream (bfd, name)
 | 
						|
     int bfd;
 | 
						|
     char *name;
 | 
						|
{
 | 
						|
  INPUT_STREAM location;
 | 
						|
 | 
						|
  location.buffered_fd = bfd;
 | 
						|
  /* Make sure the buffered stream exists. */
 | 
						|
  fd_to_buffered_stream (bfd);
 | 
						|
  init_yy_io (buffered_getchar, buffered_ungetchar, st_bstream, name, location);
 | 
						|
}
 | 
						|
 | 
						|
#if defined (TEST)
 | 
						|
 | 
						|
char *
 | 
						|
xmalloc(s)
 | 
						|
int s;
 | 
						|
{
 | 
						|
	return ((char *)malloc (s));
 | 
						|
}
 | 
						|
 | 
						|
char *
 | 
						|
xrealloc(s, size)
 | 
						|
char	*s;
 | 
						|
int	size;
 | 
						|
{
 | 
						|
	if (!s)
 | 
						|
		return((char *)malloc (size));
 | 
						|
	else
 | 
						|
		return((char *)realloc (s, size));
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
init_yy_io ()
 | 
						|
{
 | 
						|
}
 | 
						|
 | 
						|
process(bp)
 | 
						|
BUFFERED_STREAM *bp;
 | 
						|
{
 | 
						|
	int c;
 | 
						|
 | 
						|
	while ((c = bufstream_getc(bp)) != EOF)
 | 
						|
		putchar(c);
 | 
						|
}
 | 
						|
 | 
						|
BASH_INPUT bash_input;
 | 
						|
 | 
						|
struct stat dsb;		/* can be used from gdb */
 | 
						|
 | 
						|
/* imitate /bin/cat */
 | 
						|
main(argc, argv)
 | 
						|
int	argc;
 | 
						|
char	**argv;
 | 
						|
{
 | 
						|
	register int i;
 | 
						|
	BUFFERED_STREAM *bp;
 | 
						|
 | 
						|
	if (argc == 1) {
 | 
						|
		bp = fd_to_buffered_stream (0);
 | 
						|
		process(bp);
 | 
						|
		exit(0);
 | 
						|
	}
 | 
						|
	for (i = 1; i < argc; i++) {
 | 
						|
		if (argv[i][0] == '-' && argv[i][1] == '\0') {
 | 
						|
			bp = fd_to_buffered_stream (0);
 | 
						|
			if (!bp)
 | 
						|
				continue;
 | 
						|
			process(bp);
 | 
						|
			free_buffered_stream (bp);
 | 
						|
		} else {
 | 
						|
			bp = open_buffered_stream (argv[i]);
 | 
						|
			if (!bp)
 | 
						|
				continue;
 | 
						|
			process(bp);
 | 
						|
			close_buffered_stream (bp);
 | 
						|
		}
 | 
						|
	}
 | 
						|
	exit(0);
 | 
						|
}
 | 
						|
#endif
 |