1336 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			C
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			1336 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			C
		
	
	
		
			Executable File
		
	
	
	
	
/****************************************************************/
 | 
						|
/*								*/
 | 
						|
/*	de.c							*/
 | 
						|
/*								*/
 | 
						|
/*		Main loop of the "Disk editor".			*/
 | 
						|
/*								*/
 | 
						|
/****************************************************************/
 | 
						|
/*  origination         1989-Jan-15        Terrence W. Holm	*/
 | 
						|
/****************************************************************/
 | 
						|
 | 
						|
 | 
						|
#include <minix/config.h>
 | 
						|
#include <sys/types.h>
 | 
						|
#include <sys/dir.h>
 | 
						|
#include <sys/stat.h>
 | 
						|
#include <sys/wait.h>
 | 
						|
#include <ctype.h>
 | 
						|
#include <errno.h>
 | 
						|
#undef ERROR			/* arrgghh, errno.h has this pollution */
 | 
						|
#include <fcntl.h>
 | 
						|
#include <limits.h>
 | 
						|
#include <signal.h>
 | 
						|
#include <stdarg.h>
 | 
						|
#include <stdlib.h>
 | 
						|
#include <string.h>
 | 
						|
#include <unistd.h>
 | 
						|
 | 
						|
#include <minix/const.h>
 | 
						|
#include <minix/type.h>
 | 
						|
#include "../../servers/fs/const.h"
 | 
						|
#include "../../servers/fs/type.h"
 | 
						|
#include "../../servers/fs/inode.h"
 | 
						|
 | 
						|
#include "de.h"
 | 
						|
 | 
						|
static char copyright[] = "de  (c) Terrence W. Holm 1989";
 | 
						|
 | 
						|
 | 
						|
_PROTOTYPE(void Push , (de_state *s ));
 | 
						|
_PROTOTYPE(int Get_Base , (int *base ));
 | 
						|
_PROTOTYPE(int Get_Filename , (de_state *s ));
 | 
						|
_PROTOTYPE(int Get_Count , (char *units , unsigned long *result ));
 | 
						|
_PROTOTYPE(void Exec_Shell , (void));
 | 
						|
_PROTOTYPE(void Sigint , (int));
 | 
						|
 | 
						|
 | 
						|
 | 
						|
/****************************************************************/
 | 
						|
/*								*/
 | 
						|
/*	main()							*/
 | 
						|
/*								*/
 | 
						|
/*		Initialize. Handle the "-r" recovery option if	*/
 | 
						|
/*		specified, else enter the main processing loop.	*/
 | 
						|
/*								*/
 | 
						|
/****************************************************************/
 | 
						|
 | 
						|
 | 
						|
void main( argc, argv )
 | 
						|
  int   argc;
 | 
						|
  char *argv[];
 | 
						|
 | 
						|
  {
 | 
						|
  static de_state s;		/* it is safer not to put it on the stack
 | 
						|
				 * and some things probably now rely on zero
 | 
						|
				 * initialization
 | 
						|
				 */  
 | 
						|
  char *command_name = argv[0];
 | 
						|
  int   recover = 0;
 | 
						|
 | 
						|
 | 
						|
  s.device_mode = O_RDONLY;
 | 
						|
 | 
						|
 | 
						|
  /*  Parse arguments  */
 | 
						|
 | 
						|
  if ( argc == 3  &&  strcmp( argv[1], "-r" ) == 0 )
 | 
						|
    {
 | 
						|
    recover = 1;
 | 
						|
    --argc;
 | 
						|
    ++argv;
 | 
						|
    }
 | 
						|
  else if ( argc == 3  &&  strcmp( argv[1], "-w" ) == 0 )
 | 
						|
    {
 | 
						|
    s.device_mode = O_RDWR;
 | 
						|
    --argc;
 | 
						|
    ++argv;
 | 
						|
    }
 | 
						|
 | 
						|
  if ( argc != 2  ||  *argv[1] == '-' )
 | 
						|
    {
 | 
						|
    fprintf( stderr, "Usage: %s [-w] /dev/device\n", command_name );
 | 
						|
    fprintf( stderr, "       %s -r lost_file_name\n", command_name );
 | 
						|
    exit( 1 );
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
  /*  Set the effective id to the real id. This eliminates	*/
 | 
						|
  /*  any increase in privilege done by a set-uid bit on the	*/
 | 
						|
  /*  executable file. We want to be "root" for recovering 	*/
 | 
						|
  /*  files, because we must be able to read the device.	*/
 | 
						|
  /*  However, in normal usage, de(1) should not let just 	*/
 | 
						|
  /*  anyone look at a file system, thus we drop the privilege.	*/
 | 
						|
  /*								*/
 | 
						|
  /*  NOTE: There is a security hole when using "-r" with a	*/
 | 
						|
  /*  set-uid de(1). Do not use set-uid root if there is any	*/
 | 
						|
  /*  way to externally access your Minix system.		*/
 | 
						|
 | 
						|
  if ( ! recover )
 | 
						|
    {
 | 
						|
    setuid( getuid() );
 | 
						|
    setgid( getgid() );
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
  /*  Set terminal characteristics, and ^C interrupt handler  */
 | 
						|
 | 
						|
  Save_Term();
 | 
						|
 | 
						|
  if ( signal( SIGINT, SIG_IGN ) != SIG_IGN )
 | 
						|
    {
 | 
						|
    signal( SIGINT,  Sigint );
 | 
						|
    signal( SIGQUIT, Sigint );
 | 
						|
    }
 | 
						|
 | 
						|
  Set_Term();
 | 
						|
 | 
						|
  if ( ! Init_Termcap() )
 | 
						|
    Error( "Requires a termcap entry" );
 | 
						|
 | 
						|
 | 
						|
 | 
						|
  /*  Get the device file name. If recovering, also open an output file.  */
 | 
						|
 | 
						|
  if ( recover )
 | 
						|
    {
 | 
						|
    char *dir_name;
 | 
						|
    char *file_name;
 | 
						|
    struct stat device_stat;
 | 
						|
    struct stat tmp_stat;
 | 
						|
 | 
						|
    /*  Split the path name into a directory and a file name.  */
 | 
						|
 | 
						|
    if ( strlen(argv[1]) > MAX_STRING )
 | 
						|
      Error( "Path name too long" );
 | 
						|
 | 
						|
    if ( ! Path_Dir_File( argv[1], &dir_name, &file_name ) )
 | 
						|
      Error( "Recover aborted" );
 | 
						|
 | 
						|
    /*  Find the device holding the directory.  */
 | 
						|
 | 
						|
    if ( (s.device_name = File_Device( dir_name )) == NULL )
 | 
						|
      Error( "Recover aborted" );
 | 
						|
 | 
						|
 | 
						|
    /*  The output file will be in /tmp with the same file name.  */
 | 
						|
 | 
						|
    strcpy( s.file_name, TMP );
 | 
						|
    strcat( s.file_name, "/" );
 | 
						|
    strcat( s.file_name, file_name );
 | 
						|
 | 
						|
 | 
						|
    /*  Make sure /tmp is not on the same device as the file we	   */
 | 
						|
    /*  are trying to recover (we don't want to use up the free	   */
 | 
						|
    /*  i-node and blocks before we get a chance to recover them). */
 | 
						|
 | 
						|
    if ( stat( s.device_name, &device_stat ) == -1 )
 | 
						|
      Error( "Can not stat(2) device %s", s.device_name );
 | 
						|
 | 
						|
    if ( stat( TMP, &tmp_stat ) == -1 )
 | 
						|
      Error( "Can not stat(2) directory %s", TMP );
 | 
						|
 | 
						|
    if ( device_stat.st_rdev == tmp_stat.st_dev )
 | 
						|
      Error( "Will not recover files on the same device as %s", TMP );
 | 
						|
 | 
						|
    if ( access( s.file_name, F_OK ) == 0 )
 | 
						|
      Error( "Will not overwrite file %s", s.file_name );
 | 
						|
 | 
						|
 | 
						|
    /*  Open the output file.  */
 | 
						|
 | 
						|
    if ( (s.file_f = fopen( s.file_name, "w" )) == NULL )
 | 
						|
      Error( "Can not open file %s", s.file_name );
 | 
						|
 | 
						|
    /*  Don't let anyone else look at the recovered file  */
 | 
						|
 | 
						|
    chmod( s.file_name, 0700 );
 | 
						|
 | 
						|
    /*  If running as root then change the owner of the  */
 | 
						|
    /*  restored file. If not running as root then the   */
 | 
						|
    /*  chown(2) will fail.				 */
 | 
						|
 | 
						|
    chown( s.file_name, getuid(), getgid() );
 | 
						|
    }
 | 
						|
  else
 | 
						|
    {
 | 
						|
    s.device_name = argv[1];
 | 
						|
    s.file_name[ 0 ] = '\0';
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
  /*  Open the device file.  */
 | 
						|
 | 
						|
  {
 | 
						|
  struct stat device_stat;
 | 
						|
  off_t size;
 | 
						|
 | 
						|
  if ( stat( s.device_name, &device_stat ) == -1 )
 | 
						|
    Error( "Can not find file %s", s.device_name );
 | 
						|
 | 
						|
  if ( (device_stat.st_mode & S_IFMT) != S_IFBLK  &&
 | 
						|
       (device_stat.st_mode & S_IFMT) != S_IFREG )
 | 
						|
    Error( "Can only edit block special or regular files" );
 | 
						|
 | 
						|
 | 
						|
  if ( (s.device_d = open( s.device_name, s.device_mode )) == -1 )
 | 
						|
    Error( "Can not open %s", s.device_name );
 | 
						|
 | 
						|
  if ( (size = lseek( s.device_d, 0L, SEEK_END )) == -1 )
 | 
						|
    Error( "Error seeking %s", s.device_name );
 | 
						|
 | 
						|
  if ( size % K != 0 )
 | 
						|
    {
 | 
						|
    Warning( "Device size is not a multiple of 1024" );
 | 
						|
    Warning( "The (partial) last block will not be accessible" );
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
 | 
						|
  /*  Initialize the rest of the state record  */
 | 
						|
 | 
						|
  s.mode = WORD;
 | 
						|
  s.output_base = 10;
 | 
						|
  s.search_string[ 0 ] = '\0';
 | 
						|
 | 
						|
  {
 | 
						|
  int i;
 | 
						|
 | 
						|
  for ( i = 0;  i < MAX_PREV;  ++i )
 | 
						|
    {
 | 
						|
    s.prev_addr[ i ] = 0L;
 | 
						|
    s.prev_mode[ i ] = WORD;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
 | 
						|
  sync();
 | 
						|
 | 
						|
  Read_Super_Block( &s );
 | 
						|
 | 
						|
  Read_Bit_Maps( &s );
 | 
						|
 | 
						|
  s.address = 0L;
 | 
						|
 | 
						|
 | 
						|
 | 
						|
  /*  Recover mode basically performs an 'x' and an 'X'  */
 | 
						|
 | 
						|
  if ( recover )
 | 
						|
    {
 | 
						|
    ino_t inode = Find_Deleted_Entry( &s, argv[1] );
 | 
						|
    off_t size;
 | 
						|
 | 
						|
    if ( inode == 0 )
 | 
						|
      {
 | 
						|
      unlink( s.file_name );
 | 
						|
      Error( "Recover aborted" );
 | 
						|
      }
 | 
						|
 | 
						|
    s.address = ( (long) s.first_data - s.inode_blocks ) * K
 | 
						|
		      + (long) (inode - 1) * s.inode_size;
 | 
						|
 | 
						|
    Read_Block( &s, s.buffer );
 | 
						|
 | 
						|
 | 
						|
    /*  Have found the lost i-node, now extract the blocks.  */
 | 
						|
 | 
						|
    if ( (size = Recover_Blocks( &s )) == -1L )
 | 
						|
      {
 | 
						|
      unlink( s.file_name );
 | 
						|
      Error( "Recover aborted" );
 | 
						|
      }
 | 
						|
 | 
						|
    Reset_Term();
 | 
						|
 | 
						|
    printf( "Recovered %ld bytes, written to file %s\n", size, s.file_name );
 | 
						|
 | 
						|
    exit( 0 );
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
  /*  Enter the main loop, first time redraw the screen  */
 | 
						|
  {
 | 
						|
  int rc = REDRAW;
 | 
						|
 | 
						|
 | 
						|
  do
 | 
						|
    {
 | 
						|
    if ( rc == REDRAW )
 | 
						|
      {
 | 
						|
      Read_Block( &s, s.buffer );
 | 
						|
      Draw_Screen( &s );
 | 
						|
      s.last_addr = s.address;
 | 
						|
      Draw_Pointers( &s );
 | 
						|
      }
 | 
						|
 | 
						|
    else if ( rc == REDRAW_POINTERS )
 | 
						|
      {
 | 
						|
      s.offset = (unsigned) (s.address & ~ K_MASK);
 | 
						|
      Draw_Pointers( &s );
 | 
						|
      }
 | 
						|
 | 
						|
    else if ( rc == ERROR )
 | 
						|
      {
 | 
						|
      Erase_Prompt();
 | 
						|
      putchar( BELL );
 | 
						|
      }
 | 
						|
    } while ( (rc = Process( &s, Arrow_Esc(Get_Char()) )) != EOF );
 | 
						|
  }
 | 
						|
 | 
						|
 | 
						|
  /*  If there is an open output file that was never written to  */
 | 
						|
  /*  then remove its directory entry. This occurs when no 'w' 	 */
 | 
						|
  /*  or 'W' command occurred between a 'c' command and exiting	 */
 | 
						|
  /*  the program.						 */
 | 
						|
 | 
						|
  if ( s.file_name[0] != '\0'  &&  ! s.file_written )
 | 
						|
    unlink( s.file_name );
 | 
						|
 | 
						|
 | 
						|
  Reset_Term();	   /*  Restore terminal characteristics  */
 | 
						|
 | 
						|
  exit( 0 );
 | 
						|
  }
 | 
						|
 | 
						|
 | 
						|
 | 
						|
/****************************************************************/
 | 
						|
/*								*/
 | 
						|
/*	Get_Base( base )					*/
 | 
						|
/*								*/
 | 
						|
/*		Get a new base value.				*/
 | 
						|
/*		Returns REDRAW or ERROR.			*/
 | 
						|
/*								*/
 | 
						|
/****************************************************************/
 | 
						|
 | 
						|
 | 
						|
 | 
						|
int Get_Base( base )
 | 
						|
  int *base;
 | 
						|
  {
 | 
						|
	switch ( Get_Char() )
 | 
						|
	  {
 | 
						|
	  case 'h' :	*base = 16;
 | 
						|
			break;
 | 
						|
 | 
						|
	  case 'd' :	*base = 10;
 | 
						|
			break;
 | 
						|
 | 
						|
	  case 'o' :	*base = 8;
 | 
						|
			break;
 | 
						|
 | 
						|
	  case 'b' :	*base = 2;
 | 
						|
			break;
 | 
						|
 | 
						|
	  default  :	return( ERROR );
 | 
						|
	  }
 | 
						|
 | 
						|
		return( REDRAW );
 | 
						|
  }
 | 
						|
 | 
						|
 | 
						|
 | 
						|
/****************************************************************/
 | 
						|
/*								*/
 | 
						|
/*	Process( state, input_char )				*/
 | 
						|
/*								*/
 | 
						|
/*		Determine the function requested by the 	*/
 | 
						|
/*		input character. Returns OK, REDRAW,		*/
 | 
						|
/*		REDRAW_POINTERS,  ERROR or EOF.			*/
 | 
						|
/*								*/
 | 
						|
/****************************************************************/
 | 
						|
 | 
						|
 | 
						|
int Process( s, c )
 | 
						|
  de_state  *s;
 | 
						|
  int  c;
 | 
						|
 | 
						|
  {
 | 
						|
  switch ( c )
 | 
						|
    {
 | 
						|
    case 'b' :				/*  Back up one block	*/
 | 
						|
    case ESC_PGUP :
 | 
						|
 | 
						|
		if ( s->address == 0 )
 | 
						|
		  return( ERROR );
 | 
						|
 | 
						|
		s->address = (s->address - K) & K_MASK;
 | 
						|
 | 
						|
		return( REDRAW );
 | 
						|
 | 
						|
 | 
						|
    case 'B' :				/*  Back up to home	*/
 | 
						|
    case ESC_HOME :
 | 
						|
 | 
						|
		if ( s->address == 0 )
 | 
						|
		  return( OK );
 | 
						|
 | 
						|
		Push( s );
 | 
						|
 | 
						|
		s->address = 0L;
 | 
						|
 | 
						|
		return( REDRAW );
 | 
						|
 | 
						|
 | 
						|
    case 'c' :				/*  Change file name	*/
 | 
						|
 | 
						|
		{
 | 
						|
		int rc = Get_Filename( s );
 | 
						|
 | 
						|
		return( rc == OK ? REDRAW : rc );
 | 
						|
		}
 | 
						|
 | 
						|
 | 
						|
    case 'd' :				/*  Down		*/
 | 
						|
    case ESC_DOWN :
 | 
						|
 | 
						|
		{
 | 
						|
		s->last_addr = s->address;
 | 
						|
 | 
						|
		switch ( s->mode )
 | 
						|
		  {
 | 
						|
		  case WORD :	s->address += 2;
 | 
						|
 | 
						|
				if ( (s->address & PAGE_MASK) == 0 )
 | 
						|
				  return( REDRAW );
 | 
						|
 | 
						|
				return( REDRAW_POINTERS );
 | 
						|
 | 
						|
		  case BLOCK :	s->address += 64;
 | 
						|
 | 
						|
				if ( (s->last_addr & K_MASK) !=
 | 
						|
				     (s->address   & K_MASK) )
 | 
						|
				  return( REDRAW );
 | 
						|
 | 
						|
				return( REDRAW_POINTERS );
 | 
						|
 | 
						|
		  case MAP :	s->address += 256;
 | 
						|
 | 
						|
				return( REDRAW );
 | 
						|
 | 
						|
		  default :	Error( "Internal fault (mode)" );
 | 
						|
		  }
 | 
						|
		}
 | 
						|
 | 
						|
 | 
						|
    case 'f' :				/*  Forward one block	*/
 | 
						|
    case ' ' :
 | 
						|
    case ESC_PGDN :
 | 
						|
 | 
						|
		if ( s->block == s->device_size - 1 )
 | 
						|
		  return( ERROR );
 | 
						|
 | 
						|
		s->address = (s->address + K) & K_MASK;
 | 
						|
 | 
						|
		return( REDRAW );
 | 
						|
 | 
						|
 | 
						|
    case 'F' :				/*  Forward to end	*/
 | 
						|
    case ESC_END :
 | 
						|
 | 
						|
		{
 | 
						|
		off_t  last_block = ( (long) s->device_size - 1 ) * K;
 | 
						|
 | 
						|
		if ( s->address == last_block )
 | 
						|
		  return( OK );
 | 
						|
 | 
						|
		Push( s );
 | 
						|
 | 
						|
		s->address = last_block;
 | 
						|
 | 
						|
		return( REDRAW );
 | 
						|
		}
 | 
						|
 | 
						|
 | 
						|
    case 'g' :				/*  Goto block		*/
 | 
						|
 | 
						|
		{
 | 
						|
		unsigned long block;
 | 
						|
 | 
						|
		if ( Get_Count( "Block?", &block ) )
 | 
						|
		  {
 | 
						|
		  if ( block >= s->zones )
 | 
						|
		    {
 | 
						|
		    Warning( "Block number too large" );
 | 
						|
		    return( REDRAW );
 | 
						|
		    }
 | 
						|
 | 
						|
		  Push( s );
 | 
						|
 | 
						|
		  s->address = (off_t) block * K;
 | 
						|
 | 
						|
		  return( REDRAW );
 | 
						|
		  }
 | 
						|
		else
 | 
						|
		  return( ERROR );
 | 
						|
		}
 | 
						|
 | 
						|
 | 
						|
    case 'G' :				/*  Goto block indirect	*/
 | 
						|
 | 
						|
		{
 | 
						|
		unsigned block = *( (word_t *) &s->buffer[ s->offset ] );
 | 
						|
 | 
						|
		if ( s->mode != WORD )
 | 
						|
		  {
 | 
						|
		  Warning( "Must be in visual mode \"word\"" );
 | 
						|
		  return( REDRAW );
 | 
						|
		  }
 | 
						|
 | 
						|
		if ( block >= s->zones )
 | 
						|
		  {
 | 
						|
		  Warning( "Block number too large" );
 | 
						|
		  return( REDRAW );
 | 
						|
		  }
 | 
						|
 | 
						|
		Push( s );
 | 
						|
 | 
						|
		s->mode = BLOCK;
 | 
						|
		s->address = (long) block * K;
 | 
						|
 | 
						|
		return( REDRAW );
 | 
						|
		}
 | 
						|
 | 
						|
 | 
						|
    case 'h' :				/*  Help		*/
 | 
						|
    case '?' :
 | 
						|
 | 
						|
		Draw_Help_Screen( s );
 | 
						|
 | 
						|
		Wait_For_Key();
 | 
						|
 | 
						|
		return( REDRAW );
 | 
						|
 | 
						|
 | 
						|
    case 'i' :				/*  Goto i-node		*/
 | 
						|
 | 
						|
		{
 | 
						|
		unsigned long inode;
 | 
						|
 | 
						|
		if ( Get_Count( "I-node?", &inode ) )
 | 
						|
		  {
 | 
						|
		  if ( inode < 1  || inode > s->inodes )
 | 
						|
		    {
 | 
						|
		    Warning( "Illegal i-node number" );
 | 
						|
		    return( REDRAW );
 | 
						|
		    }
 | 
						|
 | 
						|
		  Push( s );
 | 
						|
 | 
						|
		  s->mode = WORD;
 | 
						|
		  s->address = (off_t) (s->first_data - s->inode_blocks) * K
 | 
						|
				  + (off_t) (inode - 1) * s->inode_size;
 | 
						|
 | 
						|
		  return( REDRAW );
 | 
						|
		  }
 | 
						|
		else
 | 
						|
		  return( ERROR );
 | 
						|
		}
 | 
						|
 | 
						|
 | 
						|
    case 'I' :				/*  Filename to i-node	*/
 | 
						|
 | 
						|
		{
 | 
						|
		ino_t inode;
 | 
						|
		char *filename;
 | 
						|
 | 
						|
		Draw_Prompt( "File name?" );
 | 
						|
 | 
						|
		filename = Get_Line();
 | 
						|
 | 
						|
		if ( filename == NULL  ||  filename[0] == '\0' )
 | 
						|
		  return( ERROR );
 | 
						|
 | 
						|
		inode = Find_Inode( s, filename );
 | 
						|
 | 
						|
		if ( inode )
 | 
						|
		  {
 | 
						|
		  Push( s );
 | 
						|
 | 
						|
		  s->mode = WORD;
 | 
						|
		  s->address = ( (long) s->first_data - s->inode_blocks ) * K
 | 
						|
				  + (long) (inode - 1) * s->inode_size;
 | 
						|
		  }
 | 
						|
 | 
						|
		return( REDRAW );
 | 
						|
		}
 | 
						|
 | 
						|
 | 
						|
    case 'l' :				/*  Left		*/
 | 
						|
    case ESC_LEFT :
 | 
						|
 | 
						|
		{
 | 
						|
		s->last_addr = s->address;
 | 
						|
 | 
						|
		switch ( s->mode )
 | 
						|
		  {
 | 
						|
		  case WORD :	s->address = s->address - 32;
 | 
						|
 | 
						|
				return( REDRAW );
 | 
						|
 | 
						|
		  case BLOCK :	s->address -= 1;
 | 
						|
 | 
						|
				if ( (s->last_addr & K_MASK) !=
 | 
						|
				     (s->address   & K_MASK) )
 | 
						|
				  return( REDRAW );
 | 
						|
 | 
						|
				return( REDRAW_POINTERS );
 | 
						|
 | 
						|
		  case MAP :	s->address -= 4;
 | 
						|
 | 
						|
				if ( (s->last_addr & ~ MAP_MASK) !=
 | 
						|
				     (s->address   & ~ MAP_MASK) )
 | 
						|
				  return( REDRAW );
 | 
						|
 | 
						|
				return( REDRAW_POINTERS );
 | 
						|
 | 
						|
		  default :	Error( "Internal fault (mode)" );
 | 
						|
		  }
 | 
						|
		}
 | 
						|
 | 
						|
 | 
						|
    case 'm' :				/*  Invoke a Minix shell */
 | 
						|
 | 
						|
		Reset_Term();
 | 
						|
 | 
						|
		Exec_Shell();
 | 
						|
 | 
						|
		Set_Term();
 | 
						|
 | 
						|
		return( REDRAW );
 | 
						|
 | 
						|
 | 
						|
    case 'n' :				/*  Search for next	*/
 | 
						|
 | 
						|
		{
 | 
						|
		off_t addr;
 | 
						|
 | 
						|
		if ( s->search_string[0] == '\0' )
 | 
						|
		  {
 | 
						|
		  Warning( "No search string defined" );
 | 
						|
		  return( REDRAW );
 | 
						|
		  }
 | 
						|
 | 
						|
		Draw_Prompt( "Searching..." );
 | 
						|
 | 
						|
		if ( (addr = Search( s, s->search_string )) == -1L )
 | 
						|
		  {
 | 
						|
		  Warning( "Search string not found" );
 | 
						|
 | 
						|
		  Wait_For_Key();
 | 
						|
 | 
						|
		  return( REDRAW );
 | 
						|
		  }
 | 
						|
 | 
						|
		Push( s );
 | 
						|
		s->address = addr;
 | 
						|
 | 
						|
		return( REDRAW );
 | 
						|
		}
 | 
						|
 | 
						|
 | 
						|
    case 'o' :				/*  Set output base	*/
 | 
						|
 | 
						|
		Draw_Prompt( "Output base?" );
 | 
						|
 | 
						|
		return( Get_Base( &s->output_base ) );
 | 
						|
 | 
						|
 | 
						|
    case 'p' :				/*  Previous address	*/
 | 
						|
 | 
						|
		{
 | 
						|
		int  i;
 | 
						|
 | 
						|
		s->address = s->prev_addr[ 0 ];
 | 
						|
		s->mode    = s->prev_mode[ 0 ];
 | 
						|
 | 
						|
  		for ( i = 0;  i < MAX_PREV - 1;  ++i )
 | 
						|
		  {
 | 
						|
    		  s->prev_addr[ i ] = s->prev_addr[ i + 1 ];
 | 
						|
		  s->prev_mode[ i ] = s->prev_mode[ i + 1 ];
 | 
						|
		  }
 | 
						|
 | 
						|
		return( REDRAW );
 | 
						|
		}
 | 
						|
 | 
						|
 | 
						|
    case 'q' :				/*  Quit		 */
 | 
						|
    case EOF :
 | 
						|
    case CTRL_D :
 | 
						|
 | 
						|
		return( EOF );
 | 
						|
 | 
						|
 | 
						|
    case 'r' :				/*  Right		*/
 | 
						|
    case ESC_RIGHT :
 | 
						|
 | 
						|
		{
 | 
						|
		s->last_addr = s->address;
 | 
						|
 | 
						|
		switch ( s->mode )
 | 
						|
		  {
 | 
						|
		  case WORD :	s->address += 32;
 | 
						|
 | 
						|
				return( REDRAW );
 | 
						|
 | 
						|
		  case BLOCK :	s->address += 1;
 | 
						|
 | 
						|
				if ( (s->last_addr & K_MASK) !=
 | 
						|
				     (s->address   & K_MASK) )
 | 
						|
				  return( REDRAW );
 | 
						|
 | 
						|
				return( REDRAW_POINTERS );
 | 
						|
 | 
						|
		  case MAP :	s->address += 4;
 | 
						|
 | 
						|
				if ( (s->last_addr & ~ MAP_MASK) !=
 | 
						|
				     (s->address   & ~ MAP_MASK) )
 | 
						|
				  return( REDRAW );
 | 
						|
 | 
						|
				return( REDRAW_POINTERS );
 | 
						|
 | 
						|
		  default :	Error( "Internal fault (mode)" );
 | 
						|
		  }
 | 
						|
		}
 | 
						|
 | 
						|
    case 's' :				/*  Store word		*/
 | 
						|
 | 
						|
		{
 | 
						|
		unsigned long word;
 | 
						|
 | 
						|
		if ( s->mode != WORD )
 | 
						|
		  {
 | 
						|
		  Warning( "Must be in visual mode \"word\"" );
 | 
						|
		  return( REDRAW );
 | 
						|
		  }
 | 
						|
 | 
						|
		if ( s->device_mode == O_RDONLY )
 | 
						|
		  {
 | 
						|
		  Warning( "Use -w option to open device for writing" );
 | 
						|
		  return( REDRAW );
 | 
						|
		  }
 | 
						|
 | 
						|
		if ( Get_Count( "Store word?", &word ) )
 | 
						|
		  {
 | 
						|
		  if ( word != (word_t) word )
 | 
						|
		    {
 | 
						|
		      Warning( "Word is more than 16 bits" );
 | 
						|
		      return( REDRAW );
 | 
						|
		    }
 | 
						|
		  Write_Word( s, (word_t) word );
 | 
						|
 | 
						|
		  return( REDRAW );
 | 
						|
		  }
 | 
						|
		else
 | 
						|
		  return( ERROR );
 | 
						|
		}
 | 
						|
 | 
						|
 | 
						|
    case 'u' :				/*  Up			*/
 | 
						|
    case ESC_UP :
 | 
						|
 | 
						|
		{
 | 
						|
		s->last_addr = s->address;
 | 
						|
 | 
						|
		switch ( s->mode )
 | 
						|
		  {
 | 
						|
		  case WORD :	s->address -= 2;
 | 
						|
 | 
						|
				if ( (s->last_addr & PAGE_MASK) == 0 )
 | 
						|
				  return( REDRAW );
 | 
						|
 | 
						|
				return( REDRAW_POINTERS );
 | 
						|
 | 
						|
		  case BLOCK :	s->address -= 64;
 | 
						|
 | 
						|
				if ( (s->last_addr & K_MASK) !=
 | 
						|
				     (s->address   & K_MASK) )
 | 
						|
				  return( REDRAW );
 | 
						|
 | 
						|
				return( REDRAW_POINTERS );
 | 
						|
 | 
						|
		  case MAP :	s->address -= 256;
 | 
						|
 | 
						|
				return( REDRAW );
 | 
						|
 | 
						|
		  default :	Error( "Internal fault (mode)" );
 | 
						|
		  }
 | 
						|
		}
 | 
						|
 | 
						|
 | 
						|
    case 'v' :				/*  Visual mode		*/
 | 
						|
 | 
						|
		Draw_Prompt( "Visual mode?" );
 | 
						|
 | 
						|
		switch ( Get_Char() )
 | 
						|
		  {
 | 
						|
		  case 'w' :	s->mode = WORD;
 | 
						|
				break;
 | 
						|
 | 
						|
		  case 'b' :	s->mode = BLOCK;
 | 
						|
				break;
 | 
						|
 | 
						|
		  case 'm' :	{
 | 
						|
				/* Assume user knows if map mode is possible
 | 
						|
				char *tty = ttyname( 0 );
 | 
						|
 | 
						|
				if ( tty == NULL  ||
 | 
						|
				    strcmp( tty, "/dev/tty0" ) != 0 )
 | 
						|
				  Warning( "Must be at console" );
 | 
						|
				else
 | 
						|
				*/
 | 
						|
				  s->mode = MAP;
 | 
						|
 | 
						|
				break;
 | 
						|
				}
 | 
						|
 | 
						|
		  default  :	return( ERROR );
 | 
						|
		  }
 | 
						|
 | 
						|
		return( REDRAW );
 | 
						|
 | 
						|
 | 
						|
    case 'w' :				/*  Write ASCII block	*/
 | 
						|
 | 
						|
		if ( s->file_name[0] == '\0' )
 | 
						|
		  {
 | 
						|
		  int  rc = Get_Filename( s );
 | 
						|
 | 
						|
		  if ( rc != OK )
 | 
						|
		    return( rc );
 | 
						|
		  }
 | 
						|
 | 
						|
		/*  We have a successfully opened file  */
 | 
						|
 | 
						|
		/*  Eliminate non-ASCII characters	*/
 | 
						|
		{
 | 
						|
		int i;
 | 
						|
		char buf[ K ];
 | 
						|
		char *from = s->buffer;
 | 
						|
		char *to = buf;
 | 
						|
 | 
						|
		for ( i = 0;  i < K;  ++i, ++from )
 | 
						|
		  {
 | 
						|
		  *to = *from & 0x7f;
 | 
						|
 | 
						|
		  if ( *to != '\0'  &&  *to != '\177' )
 | 
						|
		    ++to;
 | 
						|
		  }
 | 
						|
 | 
						|
		if ( fwrite( buf, 1, (int)(to - buf), s->file_f ) != to - buf )
 | 
						|
		  Warning( "Problem writing out buffer" );
 | 
						|
 | 
						|
		s->file_written = 1;
 | 
						|
 | 
						|
		return( REDRAW );
 | 
						|
		}
 | 
						|
 | 
						|
 | 
						|
    case 'W' :				/*  Write block exactly	*/
 | 
						|
 | 
						|
		if ( s->file_name[0] == '\0' )
 | 
						|
		  {
 | 
						|
		  int  rc = Get_Filename( s );
 | 
						|
 | 
						|
		  if ( rc != OK )
 | 
						|
		    return( rc );
 | 
						|
		  }
 | 
						|
 | 
						|
		/*  We have a successfully opened file  */
 | 
						|
 | 
						|
		if ( fwrite( s->buffer, 1, K, s->file_f ) != K )
 | 
						|
		  Warning( "Problem writing out buffer" );
 | 
						|
 | 
						|
		s->file_written = 1;
 | 
						|
 | 
						|
		return( REDRAW );
 | 
						|
 | 
						|
 | 
						|
    case 'x' :				/*  eXtract lost entry	*/
 | 
						|
 | 
						|
		{
 | 
						|
		ino_t inode;
 | 
						|
		char *filename;
 | 
						|
 | 
						|
		Draw_Prompt( "Lost file name?" );
 | 
						|
 | 
						|
		filename = Get_Line();
 | 
						|
 | 
						|
		if ( filename == NULL  ||  filename[0] == '\0' )
 | 
						|
		  return( ERROR );
 | 
						|
 | 
						|
		inode = Find_Deleted_Entry( s, filename );
 | 
						|
 | 
						|
		if ( inode )
 | 
						|
		  {
 | 
						|
		  Push( s );
 | 
						|
 | 
						|
		  s->mode = WORD;
 | 
						|
		  s->address = ( (long) s->first_data - s->inode_blocks ) * K
 | 
						|
				  + (long) (inode - 1) * s->inode_size;
 | 
						|
		  }
 | 
						|
 | 
						|
		return( REDRAW );
 | 
						|
		}
 | 
						|
 | 
						|
 | 
						|
    case 'X' :				/*  eXtract lost blocks	*/
 | 
						|
 | 
						|
		{
 | 
						|
		int  rc;
 | 
						|
 | 
						|
		if ( s->mode != WORD )
 | 
						|
		  {
 | 
						|
		  Warning( "Must be in visual mode \"word\"" );
 | 
						|
		  return( REDRAW );
 | 
						|
		  }
 | 
						|
 | 
						|
 | 
						|
		/*  Force a new output file name.  */
 | 
						|
 | 
						|
		if ( (rc = Get_Filename( s )) != OK )
 | 
						|
		  return( rc );
 | 
						|
 | 
						|
 | 
						|
		Draw_Strings( s );
 | 
						|
 | 
						|
		Erase_Prompt();
 | 
						|
		Draw_Prompt( "Recovering..." );
 | 
						|
 | 
						|
		if ( Recover_Blocks( s ) == -1L )
 | 
						|
		  unlink( s->file_name );
 | 
						|
 | 
						|
		/*  Force closure of output file.  */
 | 
						|
 | 
						|
		fclose( s->file_f );
 | 
						|
		s->file_name[ 0 ] = '\0';
 | 
						|
 | 
						|
		return( REDRAW );
 | 
						|
		}
 | 
						|
 | 
						|
 | 
						|
    case '/' :				/*  Search		*/
 | 
						|
    case ESC_PLUS :
 | 
						|
 | 
						|
		{
 | 
						|
		off_t addr;
 | 
						|
		char *string;
 | 
						|
 | 
						|
		Draw_Prompt( "Search string?" );
 | 
						|
 | 
						|
		string = Get_Line();
 | 
						|
 | 
						|
		if ( string == NULL )
 | 
						|
		  return( ERROR );
 | 
						|
 | 
						|
		if ( string[0] != '\0' )
 | 
						|
		  {
 | 
						|
		  strcpy( s->search_string, string );
 | 
						|
		  Draw_Strings( s );
 | 
						|
		  }
 | 
						|
 | 
						|
		else if ( s->search_string[0] == '\0' )
 | 
						|
		  {
 | 
						|
		  Warning( "No search string defined" );
 | 
						|
		  return( REDRAW );
 | 
						|
		  }
 | 
						|
 | 
						|
		Erase_Prompt();
 | 
						|
		Draw_Prompt( "Searching..." );
 | 
						|
 | 
						|
		if ( (addr = Search( s, s->search_string )) == -1L )
 | 
						|
		  {
 | 
						|
		  Warning( "Search string not found" );
 | 
						|
 | 
						|
		  Wait_For_Key();
 | 
						|
 | 
						|
		  return( REDRAW );
 | 
						|
		  }
 | 
						|
 | 
						|
		Push( s );
 | 
						|
 | 
						|
		s->mode = BLOCK;
 | 
						|
		s->address = addr;
 | 
						|
 | 
						|
		return( REDRAW );
 | 
						|
		}
 | 
						|
 | 
						|
 | 
						|
    default:
 | 
						|
		return( ERROR );
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
/****************************************************************/
 | 
						|
/*								*/
 | 
						|
/*	Push( state )						*/
 | 
						|
/*								*/
 | 
						|
/*		Push current address and mode, used by the	*/
 | 
						|
/*		commands B, F, g, G, i, I, n, x and /.  This	*/
 | 
						|
/*		information is popped by the 'p' command.	*/
 | 
						|
/*								*/
 | 
						|
/****************************************************************/
 | 
						|
 | 
						|
 | 
						|
void Push( s )
 | 
						|
  de_state *s;
 | 
						|
 | 
						|
  {
 | 
						|
  int  i;
 | 
						|
 | 
						|
  for ( i = MAX_PREV - 1;  i > 0;  --i )
 | 
						|
    {
 | 
						|
    s->prev_addr[ i ] = s->prev_addr[ i - 1 ];
 | 
						|
    s->prev_mode[ i ] = s->prev_mode[ i - 1 ];
 | 
						|
    }
 | 
						|
 | 
						|
  s->prev_addr[ 0 ] = s->address;
 | 
						|
  s->prev_mode[ 0 ] = s->mode;
 | 
						|
  }
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
/****************************************************************/
 | 
						|
/*								*/
 | 
						|
/*	Get_Filename( state )					*/
 | 
						|
/*								*/
 | 
						|
/*		Read and check a filename.			*/
 | 
						|
/*								*/
 | 
						|
/****************************************************************/
 | 
						|
 | 
						|
 | 
						|
int Get_Filename( s )
 | 
						|
  de_state *s;
 | 
						|
 | 
						|
  {
 | 
						|
  char *filename;
 | 
						|
  char *name;
 | 
						|
  FILE *f;
 | 
						|
 | 
						|
  Draw_Prompt( "File name?" );
 | 
						|
 | 
						|
  filename = Get_Line();
 | 
						|
 | 
						|
  if ( filename == NULL  ||  filename[0] == '\0' )
 | 
						|
    return( ERROR );
 | 
						|
 | 
						|
 | 
						|
  for ( name = filename;  *name != '\0';  ++name )
 | 
						|
    if ( ! isgraph( *name ) )
 | 
						|
      {
 | 
						|
      Warning( "File name contains non-graphic characters" );
 | 
						|
      return( REDRAW );
 | 
						|
      }
 | 
						|
 | 
						|
 | 
						|
  if ( access( filename, F_OK ) == 0 )
 | 
						|
    {
 | 
						|
    Warning( "Will not overwrite file %s", filename );
 | 
						|
    return( REDRAW );
 | 
						|
    }
 | 
						|
 | 
						|
  if ( (f = fopen( filename, "w" )) == NULL )
 | 
						|
    {
 | 
						|
    Warning( "Can not open file %s", filename );
 | 
						|
    return( REDRAW );
 | 
						|
    }
 | 
						|
 | 
						|
  /*  If there is already an open output file then  */
 | 
						|
  /*  close it. If it was never written to then	    */
 | 
						|
  /*  remove its directory entry.		    */
 | 
						|
 | 
						|
  if ( s->file_name[0] != '\0' )
 | 
						|
    {
 | 
						|
    if ( ! s->file_written )
 | 
						|
      unlink( s->file_name );
 | 
						|
 | 
						|
    fclose( s->file_f );
 | 
						|
    }
 | 
						|
 | 
						|
  strcpy( s->file_name, filename );
 | 
						|
  s->file_f = f;
 | 
						|
  s->file_written = 0;
 | 
						|
 | 
						|
  return( OK );
 | 
						|
  }
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
/****************************************************************/
 | 
						|
/*								*/
 | 
						|
/*	Get_Count()						*/
 | 
						|
/*								*/
 | 
						|
/*		Read and check a number. Returns non-zero	*/
 | 
						|
/*		if successful.					*/
 | 
						|
/*								*/
 | 
						|
/****************************************************************/
 | 
						|
 | 
						|
 | 
						|
int Get_Count( units, result )
 | 
						|
  char *units;
 | 
						|
  unsigned long *result;
 | 
						|
 | 
						|
  {
 | 
						|
  char *number;
 | 
						|
 | 
						|
  Draw_Prompt( units );
 | 
						|
 | 
						|
  number = Get_Line();
 | 
						|
 | 
						|
  if ( number == NULL  ||  number[0] == '\0' )
 | 
						|
    return( 0 );
 | 
						|
 | 
						|
  errno = 0;
 | 
						|
  *result = strtoul( number, (char **) NULL, 0 );
 | 
						|
  return( errno == 0 );
 | 
						|
  }
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
/****************************************************************/
 | 
						|
/*								*/
 | 
						|
/*	In_Use( bit, map )					*/
 | 
						|
/*								*/
 | 
						|
/*		Is the bit set in the map?			*/
 | 
						|
/*								*/
 | 
						|
/****************************************************************/
 | 
						|
 | 
						|
 | 
						|
int In_Use( bit, map )
 | 
						|
  bit_t bit;
 | 
						|
  bitchunk_t *map;
 | 
						|
 | 
						|
  {
 | 
						|
  return( map[ (int) (bit / (CHAR_BIT * sizeof (bitchunk_t))) ] &
 | 
						|
	  (1 << ((unsigned) bit % (CHAR_BIT * sizeof (bitchunk_t)))) );
 | 
						|
  }
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
/****************************************************************/
 | 
						|
/*								*/
 | 
						|
/*	Find_Inode( state, filename )				*/
 | 
						|
/*								*/
 | 
						|
/*		Find the i-node for the given file name.	*/
 | 
						|
/*								*/
 | 
						|
/****************************************************************/
 | 
						|
 | 
						|
 | 
						|
ino_t Find_Inode( s, filename )
 | 
						|
  de_state *s;
 | 
						|
  char *filename;
 | 
						|
 | 
						|
  {
 | 
						|
  struct stat device_stat;
 | 
						|
  struct stat file_stat;
 | 
						|
  ino_t inode;
 | 
						|
 | 
						|
 | 
						|
  if ( fstat( s->device_d, &device_stat ) == -1 )
 | 
						|
    Error( "Can not fstat(2) file system device" );
 | 
						|
 | 
						|
#ifdef S_IFLNK
 | 
						|
  if ( lstat( filename, &file_stat ) == -1 )
 | 
						|
#else
 | 
						|
  if ( stat( filename, &file_stat ) == -1 )
 | 
						|
#endif
 | 
						|
    {
 | 
						|
    Warning( "Can not find file %s", filename );
 | 
						|
    return( 0 );
 | 
						|
    }
 | 
						|
 | 
						|
  if ( device_stat.st_rdev != file_stat.st_dev )
 | 
						|
    {
 | 
						|
    Warning( "File is not on device %s", s->device_name );
 | 
						|
    return( 0 );
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
  inode = file_stat.st_ino;
 | 
						|
 | 
						|
  if ( inode < 1  || inode > s->inodes )
 | 
						|
    {
 | 
						|
    Warning( "Illegal i-node number" );
 | 
						|
    return( 0 );
 | 
						|
    }
 | 
						|
 | 
						|
  return( inode );
 | 
						|
  }
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
/****************************************************************/
 | 
						|
/*								*/
 | 
						|
/*	Exec_Shell()						*/
 | 
						|
/*								*/
 | 
						|
/*		Fork off a sub-process to exec() the shell.	*/
 | 
						|
/*								*/
 | 
						|
/****************************************************************/
 | 
						|
 | 
						|
 | 
						|
void Exec_Shell()
 | 
						|
 | 
						|
  {
 | 
						|
  int pid = fork();
 | 
						|
 | 
						|
  if ( pid == -1 )
 | 
						|
    return;
 | 
						|
 | 
						|
 | 
						|
  if ( pid == 0 )
 | 
						|
    {
 | 
						|
    /*  The child process  */
 | 
						|
 | 
						|
    extern char **environ;
 | 
						|
    char *shell  =  getenv( "SHELL" );
 | 
						|
 | 
						|
    if ( shell == NULL )
 | 
						|
      shell = "/bin/sh";
 | 
						|
 | 
						|
    execle( shell, shell, (char *) 0, environ );
 | 
						|
 | 
						|
    perror( shell );
 | 
						|
    exit( 127 );
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
  /*  The parent process: ignore signals, wait for sub-process	*/
 | 
						|
 | 
						|
  signal( SIGINT,  SIG_IGN );
 | 
						|
  signal( SIGQUIT, SIG_IGN );
 | 
						|
 | 
						|
  {
 | 
						|
  int  status;
 | 
						|
  int  w;
 | 
						|
 | 
						|
  while ( (w=wait(&status)) != pid  &&  w != -1 );
 | 
						|
  }
 | 
						|
 | 
						|
  signal( SIGINT,  Sigint );
 | 
						|
  signal( SIGQUIT, Sigint );
 | 
						|
 | 
						|
  return;
 | 
						|
  }
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
/****************************************************************/
 | 
						|
/*								*/
 | 
						|
/*	Sigint()						*/
 | 
						|
/*								*/
 | 
						|
/*		Terminate the program on an interrupt (^C)	*/
 | 
						|
/*		or quit (^\) signal.				*/
 | 
						|
/*								*/
 | 
						|
/****************************************************************/
 | 
						|
 | 
						|
 | 
						|
void Sigint(n)
 | 
						|
int n;
 | 
						|
  {
 | 
						|
  Reset_Term();		/*  Restore terminal characteristics	*/
 | 
						|
 | 
						|
  putchar( '\n' );
 | 
						|
 | 
						|
  exit( 1 );
 | 
						|
  }
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
/****************************************************************/
 | 
						|
/*								*/
 | 
						|
/*	Error( text, ... )					*/
 | 
						|
/*								*/
 | 
						|
/*		Print an error message on stderr.		*/
 | 
						|
/*								*/
 | 
						|
/****************************************************************/
 | 
						|
 | 
						|
 | 
						|
#if __STDC__
 | 
						|
void Error( const char *text, ... )
 | 
						|
#else
 | 
						|
void Error( text )
 | 
						|
  char *text;
 | 
						|
#endif  
 | 
						|
 | 
						|
  {
 | 
						|
  va_list argp;
 | 
						|
 | 
						|
  Reset_Term();
 | 
						|
 | 
						|
  fprintf( stderr, "\nde: " );
 | 
						|
  va_start( argp, text );
 | 
						|
  vfprintf( stderr, text, argp );
 | 
						|
  va_end( argp );
 | 
						|
  if ( errno != 0 )
 | 
						|
    fprintf( stderr, ": %s", strerror( errno ) );
 | 
						|
  fprintf( stderr, "\n" );
 | 
						|
 | 
						|
  exit( 1 );
 | 
						|
  }
 |