/* buffer.c: scratch-file buffer routines for the ed line editor. */
/* GNU ed - The GNU line editor.
Copyright (C) 1993, 1994 Andrew Moore, Talke Studio
Copyright (C) 2006-2017 Antonio Diaz Diaz.
This program 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 of the License, or
(at your option) any later version.
This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/file.h>
#include <sys/stat.h>
#include "ed.h"
static int current_addr_ = 0; /* current address in editor buffer */
static int last_addr_ = 0; /* last address in editor buffer */
static bool isbinary_ = false; /* if set, buffer contains ASCII NULs */
static bool modified_ = false; /* if set, buffer modified since last write */
static bool seek_write = false; /* seek before writing */
static FILE * sfp = 0; /* scratch file pointer */
static long sfpos = 0; /* scratch file position */
static line_t buffer_head; /* editor buffer ( linked list of line_t )*/
static line_t yank_buffer_head;
int current_addr( void ) { return current_addr_; }
int inc_current_addr( void )
{ if( ++current_addr_ > last_addr_ ) current_addr_ = last_addr_;
return current_addr_; }
void set_current_addr( const int addr ) { current_addr_ = addr; }
int last_addr( void ) { return last_addr_; }
bool isbinary( void ) { return isbinary_; }
void set_binary( void ) { isbinary_ = true; }
bool modified( void ) { return modified_; }
void set_modified( const bool m ) { modified_ = m; }
int inc_addr( int addr )
{ if( ++addr > last_addr_ ) addr = 0; return addr; }
int dec_addr( int addr )
{ if( --addr < 0 ) addr = last_addr_; return addr; }
/* link next and previous nodes */
static void link_nodes( line_t * const prev, line_t * const next )
{ prev->q_forw = next; next->q_back = prev; }
/* insert line node into circular queue after previous */
static void insert_node( line_t * const lp, line_t * const prev )
{
link_nodes( lp, prev->q_forw );
link_nodes( prev, lp );
}
/* add a line node in the editor buffer after the given line */
static void add_line_node( line_t * const lp )
{
line_t * const prev = search_line_node( current_addr_ );
insert_node( lp, prev );
++current_addr_;
++last_addr_;
}
/* return a pointer to a copy of a line node, or to a new node if lp == 0 */
static line_t * dup_line_node( line_t * const lp )
{
line_t * const p = (line_t *) malloc( sizeof (line_t) );
if( !p )
{
show_strerror( 0, errno );
set_error_msg( "Memory exhausted" );
return 0;
}
if( lp ) { p->pos = lp->pos; p->len = lp->len; }
return p;
}
/* Insert text from stdin (or from command buffer if global) to after
line n; stop when either a single period is read or EOF.
Returns false if insertion fails. */
bool append_lines( const char ** const ibufpp, const int addr,
bool insert, const bool isglobal )
{
int size = 0;
undo_t * up = 0;
current_addr_ = addr;
while( true )
{
if( !isglobal )
{
*ibufpp = get_stdin_line( &size );
if( !*ibufpp ) return false; /* error */
if( size <= 0 ) return true; /* EOF */
}
else
{
if( !**ibufpp ) return true;
for( size = 0; (*ibufpp)[size++] != '\n'; ) ;
}
if( size == 2 && **ibufpp == '.' ) { *ibufpp += size; return true; }
disable_interrupts();
if( insert ) { insert = false; if( current_addr_ > 0 ) --current_addr_; }
if( !put_sbuf_line( *ibufpp, size ) )
{ enable_interrupts(); return false; }
if( up ) up->tail = search_line_node( current_addr_ );
else
{
up = push_undo_atom( UADD, current_addr_, current_addr_ );
if( !up ) { enable_interrupts(); return false; }
}
*ibufpp += size;
modified_ = true;
enable_interrupts();
}
}
static void clear_yank_buffer( void )
{
line_t * lp = yank_buffer_head.q_forw;
disable_interrupts();
while( lp != &yank_buffer_head )
{
line_t * const p = lp->q_forw;
link_nodes( lp->q_back, lp->q_forw );
free( lp );
lp = p;
}
enable_interrupts();
}
/* close scratch file */
bool close_sbuf( void )
{
clear_yank_buffer();
clear_undo_stack();
if( sfp )
{
if( fclose( sfp ) != 0 )
{
show_strerror( 0, errno );
set_error_msg( "Cannot close temp file" );
return false;
}
sfp = 0;
}
sfpos = 0;
seek_write = false;
return true;
}
/* copy a range of lines; return false if error */
bool copy_lines( const int first_addr, const int second_addr, const int addr )
{
line_t *lp, *np = search_line_node( first_addr );
undo_t * up = 0;
int n = second_addr - first_addr + 1;
int m = 0;
current_addr_ = addr;
if( addr >= first_addr && addr < second_addr )
{
n = addr - first_addr + 1;
m = second_addr - addr;
}
for( ; n > 0; n = m, m = 0, np = search_line_node( current_addr_ + 1 ) )
for( ; n-- > 0; np = np->q_forw )
{
disable_interrupts();
lp = dup_line_node( np );
if( !lp ) { enable_interrupts(); return false; }
add_line_node( lp );
if( up ) up->tail = lp;
else
{
up = push_undo_atom( UADD, current_addr_, current_addr_ );
if( !up ) { enable_interrupts(); return false; }
}
modified_ = true;
enable_interrupts();
}
return true;
}
/* delete a range of lines */
bool delete_lines( const int from, const int to, const bool isglobal )
{
line_t *n, *p;
if( !yank_lines( from, to ) ) return false;
disable_interrupts();
if( !push_undo_atom( UDEL, from, to ) )
{ enable_interrupts(); return false; }
n = search_line_node( inc_addr( to ) );
p = search_line_node( from - 1 ); /* this search_line_node last! */
if( isglobal ) unset_active_nodes( p->q_forw, n );
link_nodes( p, n );
last_addr_ -= to - from + 1;
current_addr_ = min( from, last_addr_ );
modified_ = true;
enable_interrupts();
return true;
}
/* return line number of pointer */
int get_line_node_addr( const line_t * const lp )
{
const line_t * p = &buffer_head;
int addr = 0;
while( p != lp && ( p = p->q_forw ) != &buffer_head ) ++addr;
if( addr && p == &buffer_head )
{ set_error_msg( "Invalid address" ); return -1; }
return addr;
}
/* get a line of text from the scratch file; return pointer to the text */
char * get_sbuf_line( const line_t * const lp )
{
static char * buf = 0;
static int bufsz = 0;
int len;
if( lp == &buffer_head ) return 0;
seek_write = true; /* force seek on write */
/* out of position */
if( sfpos != lp->pos )
{
sfpos = lp->pos;
if( fseek( sfp, sfpos, SEEK_SET ) != 0 )
{
show_strerror( 0, errno );
set_error_msg( "Cannot seek temp file" );
return 0;
}
}
len = lp->len;
if( !resize_buffer( &buf, &bufsz, len + 1 ) ) return 0;
if( (int)fread( buf, 1, len, sfp ) != len )
{
show_strerror( 0, errno );
set_error_msg( "Cannot read temp file" );
return 0;
}
sfpos += len; /* update file position */
buf[len] = 0;
return buf;
}
/* open scratch buffer; initialize line queue */
bool init_buffers( void )
{
/* Read stdin one character at a time to avoid i/o contention
with shell escapes invoked by nonterminal input, e.g.,
ed - <<EOF
!cat
hello, world
EOF */
setvbuf( stdin, 0, _IONBF, 0 );
if( !open_sbuf() ) return false;
link_nodes( &buffer_head, &buffer_head );
link_nodes( &yank_buffer_head, &yank_buffer_head );
return true;
}
/* replace a range of lines with the joined text of those lines */
bool join_lines( const int from, const int to, const bool isglobal )
{
static char * buf = 0;
static int bufsz = 0;
int size = 0;
line_t * const ep = search_line_node( inc_addr( to ) );
line_t * bp = search_line_node( from );
while( bp != ep )
{
const char * const s = get_sbuf_line( bp );
if( !s || !resize_buffer( &buf, &bufsz, size + bp->len ) ) return false;
memcpy( buf + size, s, bp->len );
size += bp->len;
bp = bp->q_forw;
}
if( !resize_buffer( &buf, &bufsz, size + 2 ) ) return false;
buf[size++] = '\n';
buf[size++] = 0;
if( !delete_lines( from, to, isglobal ) ) return false;
current_addr_ = from - 1;
disable_interrupts();
if( !put_sbuf_line( buf, size ) ||
!push_undo_atom( UADD, current_addr_, current_addr_ ) )
{ enable_interrupts(); return false; }
modified_ = true;
enable_interrupts();
return true;
}
/* move a range of lines */
bool move_lines( const int first_addr, const int second_addr, const int addr,
const bool isglobal )
{
line_t *b1, *a1, *b2, *a2;
int n = inc_addr( second_addr );
int p = first_addr - 1;
disable_interrupts();
if( addr == first_addr - 1 || addr == second_addr )
{
a2 = search_line_node( n );
b2 = search_line_node( p );
current_addr_ = second_addr;
}
else if( !push_undo_atom( UMOV, p, n ) ||
!push_undo_atom( UMOV, addr, inc_addr( addr ) ) )
{ enable_interrupts(); return false; }
else
{
a1 = search_line_node( n );
if( addr < first_addr )
{
b1 = search_line_node( p );
b2 = search_line_node( addr ); /* this search_line_node last! */
}
else
{
b2 = search_line_node( addr );
b1 = search_line_node( p ); /* this search_line_node last! */
}
a2 = b2->q_forw;
link_nodes( b2, b1->q_forw );
link_nodes( a1->q_back, a2 );
link_nodes( b1, a1 );
current_addr_ = addr + ( ( addr < first_addr ) ?
second_addr - first_addr + 1 : 0 );
}
if( isglobal ) unset_active_nodes( b2->q_forw, a2 );
modified_ = true;
enable_interrupts();
return true;
}
/* open scratch file */
bool open_sbuf( void )
{
isbinary_ = false; reset_unterminated_line();
sfp = tmpfile();
if( !sfp )
{
show_strerror( 0, errno );
set_error_msg( "Cannot open temp file" );
return false;
}
return true;
}
int path_max( const char * filename )
{
long result;
if( !filename ) filename = "/";
errno = 0;
result = pathconf( filename, _PC_PATH_MAX );
if( result < 0 ) { if( errno ) result = 256; else result = 1024; }
else if( result < 256 ) result = 256;
return result;
}
/* append lines from the yank buffer */
bool put_lines( const int addr )
{
undo_t * up = 0;
line_t *p, *lp = yank_buffer_head.q_forw;
if( lp == &yank_buffer_head )
{ set_error_msg( "Nothing to put" ); return false; }
current_addr_ = addr;
while( lp != &yank_buffer_head )
{
disable_interrupts();
p = dup_line_node( lp );
if( !p ) { enable_interrupts(); return false; }
add_line_node( p );
if( up ) up->tail = p;
else
{
up = push_undo_atom( UADD, current_addr_, current_addr_ );
if( !up ) { enable_interrupts(); return false; }
}
modified_ = true;
lp = lp->q_forw;
enable_interrupts();
}
return true;
}
/* write a line of text to the scratch file and add a line node to the
editor buffer; return a pointer to the end of the text, or 0 if error */
const char * put_sbuf_line( const char * const buf, const int size )
{
const char * const p = (const char *) memchr( buf, '\n', size );
line_t * lp;
int len;
if( !p ) { set_error_msg( "Line too long" ); return 0; }
len = p - buf;
/* out of position */
if( seek_write )
{
if( fseek( sfp, 0L, SEEK_END ) != 0 )
{
show_strerror( 0, errno );
set_error_msg( "Cannot seek temp file" );
return 0;
}
sfpos = ftell( sfp );
seek_write = false;
}
if( (int)fwrite( buf, 1, len, sfp ) != len ) /* assert: interrupts disabled */
{
sfpos = -1;
show_strerror( 0, errno );
set_error_msg( "Cannot write temp file" );
return 0;
}
lp = dup_line_node( 0 );
if( !lp ) return 0;
lp->pos = sfpos; lp->len = len;
add_line_node( lp );
sfpos += len; /* update file position */
return p + 1;
}
/* return pointer to a line node in the editor buffer */
line_t * search_line_node( const int addr )
{
static line_t * lp = &buffer_head;
static int o_addr = 0;
disable_interrupts();
if( o_addr < addr )
{
if( o_addr + last_addr_ >= 2 * addr )
while( o_addr < addr ) { ++o_addr; lp = lp->q_forw; }
else
{
lp = buffer_head.q_back; o_addr = last_addr_;
while( o_addr > addr ) { --o_addr; lp = lp->q_back; }
}
}
else if( o_addr <= 2 * addr )
while( o_addr > addr ) { --o_addr; lp = lp->q_back; }
else
{ lp = &buffer_head; o_addr = 0;
while( o_addr < addr ) { ++o_addr; lp = lp->q_forw; } }
enable_interrupts();
return lp;
}
/* copy a range of lines to the cut buffer */
bool yank_lines( const int from, const int to )
{
line_t * const ep = search_line_node( inc_addr( to ) );
line_t * bp = search_line_node( from );
line_t * lp = &yank_buffer_head;
line_t * p;
clear_yank_buffer();
while( bp != ep )
{
disable_interrupts();
p = dup_line_node( bp );
if( !p ) { enable_interrupts(); return false; }
insert_node( p, lp );
bp = bp->q_forw; lp = p;
enable_interrupts();
}
return true;
}
static undo_t * ustack = 0; /* undo stack */
static int usize = 0; /* ustack size (in bytes) */
static int u_ptr = 0; /* undo stack pointer */
static int u_current_addr = -1; /* if < 0, undo disabled */
static int u_last_addr = -1; /* if < 0, undo disabled */
static bool u_modified = false;
void clear_undo_stack( void )
{
while( u_ptr-- )
if( ustack[u_ptr].type == UDEL )
{
line_t * const ep = ustack[u_ptr].tail->q_forw;
line_t * bp = ustack[u_ptr].head;
while( bp != ep )
{
line_t * const lp = bp->q_forw;
unmark_line_node( bp );
unmark_unterminated_line( bp );
free( bp );
bp = lp;
}
}
u_ptr = 0;
u_current_addr = current_addr_;
u_last_addr = last_addr_;
u_modified = modified_;
}
void reset_undo_state( void )
{
clear_undo_stack();
u_current_addr = u_last_addr = -1;
u_modified = false;
}
/* return pointer to intialized undo node */
undo_t * push_undo_atom( const int type, const int from, const int to )
{
disable_interrupts();
if( !resize_undo_buffer( &ustack, &usize, ( u_ptr + 1 ) * sizeof (undo_t) ) )
{
show_strerror( 0, errno );
set_error_msg( "Memory exhausted" );
if( ustack )
{
clear_undo_stack();
free( ustack );
ustack = 0;
usize = u_ptr = 0;
u_current_addr = u_last_addr = -1;
}
enable_interrupts();
return 0;
}
enable_interrupts();
ustack[u_ptr].type = type;
ustack[u_ptr].tail = search_line_node( to );
ustack[u_ptr].head = search_line_node( from );
return ustack + u_ptr++;
}
/* undo last change to the editor buffer */
bool undo( const bool isglobal )
{
int n;
const int o_current_addr = current_addr_;
const int o_last_addr = last_addr_;
const bool o_modified = modified_;
if( u_ptr <= 0 || u_current_addr < 0 || u_last_addr < 0 )
{ set_error_msg( "Nothing to undo" ); return false; }
search_line_node( 0 ); /* reset cached value */
disable_interrupts();
for( n = u_ptr - 1; n >= 0; --n )
{
switch( ustack[n].type )
{
case UADD: link_nodes( ustack[n].head->q_back, ustack[n].tail->q_forw );
break;
case UDEL: link_nodes( ustack[n].head->q_back, ustack[n].head );
link_nodes( ustack[n].tail, ustack[n].tail->q_forw );
break;
case UMOV:
case VMOV: link_nodes( ustack[n-1].head, ustack[n].head->q_forw );
link_nodes( ustack[n].tail->q_back, ustack[n-1].tail );
link_nodes( ustack[n].head, ustack[n].tail ); --n;
break;
}
ustack[n].type ^= 1;
}
/* reverse undo stack order */
for( n = 0; 2 * n < u_ptr - 1; ++n )
{
undo_t tmp = ustack[n];
ustack[n] = ustack[u_ptr-1-n]; ustack[u_ptr-1-n] = tmp;
}
if( isglobal ) clear_active_list();
current_addr_ = u_current_addr; u_current_addr = o_current_addr;
last_addr_ = u_last_addr; u_last_addr = o_last_addr;
modified_ = u_modified; u_modified = o_modified;
enable_interrupts();
return true;
}