Blame io.c

Packit 4e1bf9
/* io.c: i/o routines for the ed line editor */
Packit 4e1bf9
/*  GNU ed - The GNU line editor.
Packit 4e1bf9
    Copyright (C) 1993, 1994 Andrew Moore, Talke Studio
Packit 4e1bf9
    Copyright (C) 2006-2017 Antonio Diaz Diaz.
Packit 4e1bf9
Packit 4e1bf9
    This program is free software: you can redistribute it and/or modify
Packit 4e1bf9
    it under the terms of the GNU General Public License as published by
Packit 4e1bf9
    the Free Software Foundation, either version 2 of the License, or
Packit 4e1bf9
    (at your option) any later version.
Packit 4e1bf9
Packit 4e1bf9
    This program is distributed in the hope that it will be useful,
Packit 4e1bf9
    but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 4e1bf9
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit 4e1bf9
    GNU General Public License for more details.
Packit 4e1bf9
Packit 4e1bf9
    You should have received a copy of the GNU General Public License
Packit 4e1bf9
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
Packit 4e1bf9
*/
Packit 4e1bf9
Packit 4e1bf9
#include <errno.h>
Packit 4e1bf9
#include <stdio.h>
Packit 4e1bf9
#include <string.h>
Packit 4e1bf9
Packit 4e1bf9
#include "ed.h"
Packit 4e1bf9
Packit 4e1bf9
Packit 4e1bf9
static const line_t * unterminated_line = 0;	/* last line has no '\n' */
Packit 4e1bf9
int linenum_ = 0;				/* script line number */
Packit 4e1bf9
Packit 4e1bf9
void reset_unterminated_line( void ) { unterminated_line = 0; }
Packit 4e1bf9
Packit 4e1bf9
void unmark_unterminated_line( const line_t * const lp )
Packit 4e1bf9
  { if( unterminated_line == lp ) unterminated_line = 0; }
Packit 4e1bf9
Packit 4e1bf9
static bool unterminated_last_line( void )
Packit 4e1bf9
  { return ( unterminated_line != 0 &&
Packit 4e1bf9
             unterminated_line == search_line_node( last_addr() ) ); }
Packit 4e1bf9
Packit 4e1bf9
int linenum( void ) { return linenum_; }
Packit 4e1bf9
Packit 4e1bf9
Packit 4e1bf9
/* print text to stdout */
Packit 4e1bf9
static void print_line( const char * p, int len, const int pflags )
Packit 4e1bf9
  {
Packit 4e1bf9
  const char escapes[] = "\a\b\f\n\r\t\v\\";
Packit 4e1bf9
  const char escchars[] = "abfnrtv\\";
Packit 4e1bf9
  int col = 0;
Packit 4e1bf9
Packit 4e1bf9
  if( pflags & GNP ) { printf( "%d\t", current_addr() ); col = 8; }
Packit 4e1bf9
  while( --len >= 0 )
Packit 4e1bf9
    {
Packit 4e1bf9
    const unsigned char ch = *p++;
Packit 4e1bf9
    if( !( pflags & GLS ) ) putchar( ch );
Packit 4e1bf9
    else
Packit 4e1bf9
      {
Packit 4e1bf9
      if( ++col > window_columns() ) { col = 1; fputs( "\\\n", stdout ); }
Packit 4e1bf9
      if( ch >= 32 && ch <= 126 && ch != '\\' ) putchar( ch );
Packit 4e1bf9
      else
Packit 4e1bf9
        {
Packit 4e1bf9
        char * const p = strchr( escapes, ch );
Packit 4e1bf9
        ++col; putchar('\\');
Packit 4e1bf9
        if( ch && p ) putchar( escchars[p-escapes] );
Packit 4e1bf9
        else
Packit 4e1bf9
          {
Packit 4e1bf9
          col += 2;
Packit 4e1bf9
          putchar( ( ( ch >> 6 ) & 7 ) + '0' );
Packit 4e1bf9
          putchar( ( ( ch >> 3 ) & 7 ) + '0' );
Packit 4e1bf9
          putchar( ( ch & 7 ) + '0' );
Packit 4e1bf9
          }
Packit 4e1bf9
        }
Packit 4e1bf9
      }
Packit 4e1bf9
    }
Packit 4e1bf9
  if( !traditional() && ( pflags & GLS ) ) putchar('$');
Packit 4e1bf9
  putchar('\n');
Packit 4e1bf9
  }
Packit 4e1bf9
Packit 4e1bf9
Packit 4e1bf9
/* print a range of lines to stdout */
Packit 4e1bf9
bool print_lines( int from, const int to, const int pflags )
Packit 4e1bf9
  {
Packit 4e1bf9
  line_t * const ep = search_line_node( inc_addr( to ) );
Packit 4e1bf9
  line_t * bp = search_line_node( from );
Packit 4e1bf9
Packit 4e1bf9
  if( !from ) { set_error_msg( "Invalid address" ); return false; }
Packit 4e1bf9
  while( bp != ep )
Packit 4e1bf9
    {
Packit 4e1bf9
    const char * const s = get_sbuf_line( bp );
Packit 4e1bf9
    if( !s ) return false;
Packit 4e1bf9
    set_current_addr( from++ );
Packit 4e1bf9
    print_line( s, bp->len, pflags );
Packit 4e1bf9
    bp = bp->q_forw;
Packit 4e1bf9
    }
Packit 4e1bf9
  return true;
Packit 4e1bf9
  }
Packit 4e1bf9
Packit 4e1bf9
Packit 4e1bf9
/* return the parity of escapes at the end of a string */
Packit 4e1bf9
static bool trailing_escape( const char * const s, int len )
Packit 4e1bf9
  {
Packit 4e1bf9
  bool odd_escape = false;
Packit 4e1bf9
  while( --len >= 0 && s[len] == '\\' ) odd_escape = !odd_escape;
Packit 4e1bf9
  return odd_escape;
Packit 4e1bf9
  }
Packit 4e1bf9
Packit 4e1bf9
Packit 4e1bf9
/* If *ibufpp contains an escaped newline, get an extended line (one
Packit 4e1bf9
   with escaped newlines) from stdin.
Packit 4e1bf9
   Return line length in *lenp, including the trailing newline. */
Packit 4e1bf9
bool get_extended_line( const char ** const ibufpp, int * const lenp,
Packit 4e1bf9
                        const bool strip_escaped_newlines )
Packit 4e1bf9
  {
Packit 4e1bf9
  static char * buf = 0;
Packit 4e1bf9
  static int bufsz = 0;
Packit 4e1bf9
  int len;
Packit 4e1bf9
Packit 4e1bf9
  for( len = 0; (*ibufpp)[len++] != '\n'; ) ;
Packit 4e1bf9
  if( len < 2 || !trailing_escape( *ibufpp, len - 1 ) )
Packit 4e1bf9
    { if( lenp ) *lenp = len; return true; }
Packit 4e1bf9
  if( !resize_buffer( &buf, &bufsz, len ) ) return false;
Packit 4e1bf9
  memcpy( buf, *ibufpp, len );
Packit 4e1bf9
  --len; buf[len-1] = '\n';			/* strip trailing esc */
Packit 4e1bf9
  if( strip_escaped_newlines ) --len;		/* strip newline */
Packit 4e1bf9
  while( true )
Packit 4e1bf9
    {
Packit 4e1bf9
    int len2;
Packit 4e1bf9
    const char * const s = get_stdin_line( &len2 );
Packit 4e1bf9
    if( !s ) return false;			/* error */
Packit 4e1bf9
    if( len2 <= 0 ) return false;		/* EOF */
Packit 4e1bf9
    if( !resize_buffer( &buf, &bufsz, len + len2 ) ) return false;
Packit 4e1bf9
    memcpy( buf + len, s, len2 );
Packit 4e1bf9
    len += len2;
Packit 4e1bf9
    if( len2 < 2 || !trailing_escape( buf, len - 1 ) ) break;
Packit 4e1bf9
    --len; buf[len-1] = '\n';			/* strip trailing esc */
Packit 4e1bf9
    if( strip_escaped_newlines ) --len;		/* strip newline */
Packit 4e1bf9
    }
Packit 4e1bf9
  if( !resize_buffer( &buf, &bufsz, len + 1 ) ) return false;
Packit 4e1bf9
  buf[len] = 0;
Packit 4e1bf9
  *ibufpp = buf;
Packit 4e1bf9
  if( lenp ) *lenp = len;
Packit 4e1bf9
  return true;
Packit 4e1bf9
  }
Packit 4e1bf9
Packit 4e1bf9
Packit 4e1bf9
/* Read a line of text from stdin.
Packit 4e1bf9
   Incomplete lines (lacking the trailing newline) are discarded.
Packit 4e1bf9
   Returns pointer to buffer and line size (including trailing newline),
Packit 4e1bf9
   or 0 if error, or *sizep = 0 if EOF */
Packit 4e1bf9
const char * get_stdin_line( int * const sizep )
Packit 4e1bf9
  {
Packit 4e1bf9
  static char * buf = 0;
Packit 4e1bf9
  static int bufsz = 0;
Packit 4e1bf9
  int i = 0;
Packit 4e1bf9
Packit 4e1bf9
  while( true )
Packit 4e1bf9
    {
Packit 4e1bf9
    const int c = getchar();
Packit 4e1bf9
    if( !resize_buffer( &buf, &bufsz, i + 2 ) ) { *sizep = 0; return 0; }
Packit 4e1bf9
    if( c == EOF )
Packit 4e1bf9
      {
Packit 4e1bf9
      if( ferror( stdin ) )
Packit 4e1bf9
        {
Packit 4e1bf9
        show_strerror( "stdin", errno );
Packit 4e1bf9
        set_error_msg( "Cannot read stdin" );
Packit 4e1bf9
        clearerr( stdin );
Packit 4e1bf9
        *sizep = 0; return 0;
Packit 4e1bf9
        }
Packit 4e1bf9
      if( feof( stdin ) )
Packit 4e1bf9
        {
Packit 4e1bf9
        set_error_msg( "Unexpected end-of-file" );
Packit 4e1bf9
        clearerr( stdin );
Packit 4e1bf9
        buf[0] = 0; *sizep = 0; if( i > 0 ) ++linenum_;	/* discard line */
Packit 4e1bf9
        return buf;
Packit 4e1bf9
        }
Packit 4e1bf9
      }
Packit 4e1bf9
    else
Packit 4e1bf9
      {
Packit 4e1bf9
      buf[i++] = c; if( !c ) set_binary(); if( c != '\n' ) continue;
Packit 4e1bf9
      ++linenum_; buf[i] = 0; *sizep = i;
Packit 4e1bf9
      return buf;
Packit 4e1bf9
      }
Packit 4e1bf9
    }
Packit 4e1bf9
  }
Packit 4e1bf9
Packit 4e1bf9
Packit 4e1bf9
/* Read a line of text from a stream.
Packit 4e1bf9
   Returns pointer to buffer and line size (including trailing newline
Packit 4e1bf9
   if it exists and is not added now) */
Packit 4e1bf9
static const char * read_stream_line( FILE * const fp, int * const sizep,
Packit 4e1bf9
                                      bool * const newline_addedp )
Packit 4e1bf9
  {
Packit 4e1bf9
  static char * buf = 0;
Packit 4e1bf9
  static int bufsz = 0;
Packit 4e1bf9
  int c, i = 0;
Packit 4e1bf9
Packit 4e1bf9
  while( true )
Packit 4e1bf9
    {
Packit 4e1bf9
    if( !resize_buffer( &buf, &bufsz, i + 2 ) ) return 0;
Packit 4e1bf9
    c = getc( fp ); if( c == EOF ) break;
Packit 4e1bf9
    buf[i++] = c;
Packit 4e1bf9
    if( !c ) set_binary(); else if( c == '\n' ) break;
Packit 4e1bf9
    }
Packit 4e1bf9
  buf[i] = 0;
Packit 4e1bf9
  if( c == EOF )
Packit 4e1bf9
    {
Packit 4e1bf9
    if( ferror( fp ) )
Packit 4e1bf9
      {
Packit 4e1bf9
      show_strerror( 0, errno );
Packit 4e1bf9
      set_error_msg( "Cannot read input file" );
Packit 4e1bf9
      return 0;
Packit 4e1bf9
      }
Packit 4e1bf9
    else if( i )
Packit 4e1bf9
      {
Packit 4e1bf9
      buf[i] = '\n'; buf[i+1] = 0; *newline_addedp = true;
Packit 4e1bf9
      if( !isbinary() ) ++i;
Packit 4e1bf9
      }
Packit 4e1bf9
    }
Packit 4e1bf9
  *sizep = i;
Packit 4e1bf9
  return buf;
Packit 4e1bf9
  }
Packit 4e1bf9
Packit 4e1bf9
Packit 4e1bf9
/* read a stream into the editor buffer;
Packit 4e1bf9
   return total size of data read, or -1 if error */
Packit 4e1bf9
static long read_stream( FILE * const fp, const int addr )
Packit 4e1bf9
  {
Packit 4e1bf9
  line_t * lp = search_line_node( addr );
Packit 4e1bf9
  undo_t * up = 0;
Packit 4e1bf9
  long total_size = 0;
Packit 4e1bf9
  const bool o_isbinary = isbinary();
Packit 4e1bf9
  const bool appended = ( addr == last_addr() );
Packit 4e1bf9
  const bool o_unterminated_last_line = unterminated_last_line();
Packit 4e1bf9
  bool newline_added = false;
Packit 4e1bf9
Packit 4e1bf9
  set_current_addr( addr );
Packit 4e1bf9
  while( true )
Packit 4e1bf9
    {
Packit 4e1bf9
    int size = 0;
Packit 4e1bf9
    const char * const s = read_stream_line( fp, &size, &newline_added );
Packit 4e1bf9
    if( !s ) return -1;
Packit 4e1bf9
    if( size <= 0 ) break;
Packit 4e1bf9
    total_size += size;
Packit 4e1bf9
    disable_interrupts();
Packit 4e1bf9
    if( !put_sbuf_line( s, size + newline_added ) )
Packit 4e1bf9
      { enable_interrupts(); return -1; }
Packit 4e1bf9
    lp = lp->q_forw;
Packit 4e1bf9
    if( up ) up->tail = lp;
Packit 4e1bf9
    else
Packit 4e1bf9
      {
Packit 4e1bf9
      up = push_undo_atom( UADD, current_addr(), current_addr() );
Packit 4e1bf9
      if( !up ) { enable_interrupts(); return -1; }
Packit 4e1bf9
      }
Packit 4e1bf9
    enable_interrupts();
Packit 4e1bf9
    }
Packit 4e1bf9
  if( addr && appended && total_size && o_unterminated_last_line )
Packit 4e1bf9
    fputs( "Newline inserted\n", stdout );		/* before stream */
Packit 4e1bf9
  else if( newline_added && ( !appended || !isbinary() ) )
Packit 4e1bf9
    fputs( "Newline appended\n", stdout );		/* after stream */
Packit 4e1bf9
  if( !appended && isbinary() && !o_isbinary && newline_added )
Packit 4e1bf9
    ++total_size;
Packit 4e1bf9
  if( appended && isbinary() && ( newline_added || total_size == 0 ) )
Packit 4e1bf9
    unterminated_line = search_line_node( last_addr() );
Packit 4e1bf9
  return total_size;
Packit 4e1bf9
  }
Packit 4e1bf9
Packit 4e1bf9
Packit 4e1bf9
/* read a named file/pipe into the buffer; return line count, or -1 if error */
Packit 4e1bf9
int read_file( const char * const filename, const int addr )
Packit 4e1bf9
  {
Packit 4e1bf9
  FILE * fp;
Packit 4e1bf9
  long size;
Packit 4e1bf9
  int ret;
Packit 4e1bf9
Packit 4e1bf9
  if( *filename == '!' ) fp = popen( filename + 1, "r" );
Packit 4e1bf9
  else fp = fopen( strip_escapes( filename ), "r" );
Packit 4e1bf9
  if( !fp )
Packit 4e1bf9
    {
Packit 4e1bf9
    show_strerror( filename, errno );
Packit 4e1bf9
    set_error_msg( "Cannot open input file" );
Packit 4e1bf9
    return -1;
Packit 4e1bf9
    }
Packit 4e1bf9
  size = read_stream( fp, addr );
Packit 4e1bf9
  if( *filename == '!' ) ret = pclose( fp ); else ret = fclose( fp );
Packit 4e1bf9
  if( size < 0 ) return -1;
Packit 4e1bf9
  if( ret != 0 )
Packit 4e1bf9
    {
Packit 4e1bf9
    show_strerror( filename, errno );
Packit 4e1bf9
    set_error_msg( "Cannot close input file" );
Packit 4e1bf9
    return -1;
Packit 4e1bf9
    }
Packit 4e1bf9
  if( !scripted() ) printf( "%lu\n", size );
Packit 4e1bf9
  return current_addr() - addr;
Packit 4e1bf9
  }
Packit 4e1bf9
Packit 4e1bf9
Packit 4e1bf9
/* write a range of lines to a stream */
Packit 4e1bf9
static long write_stream( FILE * const fp, int from, const int to )
Packit 4e1bf9
  {
Packit 4e1bf9
  line_t * lp = search_line_node( from );
Packit 4e1bf9
  long size = 0;
Packit 4e1bf9
Packit 4e1bf9
  while( from && from <= to )
Packit 4e1bf9
    {
Packit 4e1bf9
    int len;
Packit 4e1bf9
    char * p = get_sbuf_line( lp );
Packit 4e1bf9
    if( !p ) return -1;
Packit 4e1bf9
    len = lp->len;
Packit 4e1bf9
    if( from != last_addr() || !isbinary() || !unterminated_last_line() )
Packit 4e1bf9
      p[len++] = '\n';
Packit 4e1bf9
    size += len;
Packit 4e1bf9
    while( --len >= 0 )
Packit 4e1bf9
      if( fputc( *p++, fp ) == EOF )
Packit 4e1bf9
        {
Packit 4e1bf9
        show_strerror( 0, errno );
Packit 4e1bf9
        set_error_msg( "Cannot write file" );
Packit 4e1bf9
        return -1;
Packit 4e1bf9
        }
Packit 4e1bf9
    ++from; lp = lp->q_forw;
Packit 4e1bf9
    }
Packit 4e1bf9
  return size;
Packit 4e1bf9
  }
Packit 4e1bf9
Packit 4e1bf9
Packit 4e1bf9
/* write a range of lines to a named file/pipe; return line count */
Packit 4e1bf9
int write_file( const char * const filename, const char * const mode,
Packit 4e1bf9
                const int from, const int to )
Packit 4e1bf9
  {
Packit 4e1bf9
  FILE * fp;
Packit 4e1bf9
  long size;
Packit 4e1bf9
  int ret;
Packit 4e1bf9
Packit 4e1bf9
  if( *filename == '!' ) fp = popen( filename + 1, "w" );
Packit 4e1bf9
  else fp = fopen( strip_escapes( filename ), mode );
Packit 4e1bf9
  if( !fp )
Packit 4e1bf9
    {
Packit 4e1bf9
    show_strerror( filename, errno );
Packit 4e1bf9
    set_error_msg( "Cannot open output file" );
Packit 4e1bf9
    return -1;
Packit 4e1bf9
    }
Packit 4e1bf9
  size = write_stream( fp, from, to );
Packit 4e1bf9
  if( *filename == '!' ) ret = pclose( fp ); else ret = fclose( fp );
Packit 4e1bf9
  if( size < 0 ) return -1;
Packit 4e1bf9
  if( ret != 0 )
Packit 4e1bf9
    {
Packit 4e1bf9
    show_strerror( filename, errno );
Packit 4e1bf9
    set_error_msg( "Cannot close output file" );
Packit 4e1bf9
    return -1;
Packit 4e1bf9
    }
Packit 4e1bf9
  if( !scripted() ) printf( "%lu\n", size );
Packit 4e1bf9
  return ( from && from <= to ) ? to - from + 1 : 0;
Packit 4e1bf9
  }