/****************************************************************/
/*								*/
/*	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 );
  }
