/* x-recover.c 009
 * Based (partly) on x-check.c by Andy Armstrong
 * Needs chunks.h from the X-files distribution
 *
 *  Nicholas Clark 1996/1997
 *
 * Version history:
 *
 * 001 28/12/96
 * First version. Successfully extracts all chunks from an (intact) X-File.
 * Not really tested - did get all of the Perl 5.003 source (640 files)
 * Checkmap still happy afterwards 8-)
 *
 * 002 30/12/96
 * Added options to specify file offset of chunktable and root directory
 * (Get this from x-guess)
 * Implemented chunktable. Root directory may prove more problematic.
 * (Currently do entire directory in one go by malloc()ing a buffer and reading
 * the lot in. I guess I'm going to have to hunt the root directory in the chunk
 * table to find out how big it is (xFiles_dirHeader.size is the *number* of
 * entries, rather than the (floating) size of the names))
 *
 * 003 30/12/96
 * Re-wrote dir code to read directories file by file. This allows x-recover to
 * 1: Cope with directories that have an incorrect size in the chunkTable
 *    (I have one of these I made accidentally during testing - I copied an
 *     x-File after the chunktable was written, but before the cached directory
 *     was written)
 * 2: Read directories (eg the root directory) from an arbitrary disc location.
 *
 * 004 01/01/97
 * Method 2 fully implemented
 * Recovers all files that correlate between directories and chunktable
 * Attempts to match chunkless files to chunks of the same size.
 * Recovers these files.
 * Recovers all so-far unrecovered chunks
 * [Methods 1 and 2 still rely on an intact chunktable. If your chunktable is
 * missing (and x-guess can't find it) or destroyed, you have a problem.
 * It's just like trying to recover a disc with no discmap - you have to guess
 * file type and size from the contents (assuming that there is some
 * characteristic to recognise.
 * Until someone writes a centralised file type database containing:
 *   mime type
 *   dos extension
 *   unix suffix
 *   mac type
 *   identification by contents (/etc/magic in Unix)
 *   [optionally icon]
 * xrecover_guess-o-matic will have to wait.]
 *
 * 005 02/01/97
 * Spellchecked the source code!
 * eg snytax is now syntax
 * Important lesson: Check you executables before distribution
 * It did compile, but it didn't work. Fixed it
 * Much now tidier if xrecover_recoverChunkChkdsk fails to read any bytes
 * Tidied up "no dirhash for filename entry" check
 *
 * 006 02/01/97
 * Merged x-guess as option -g
 * Added dj vu flag to stop infinite loops if the directory tree becomes
 * tangled (precursor to method 3 - readDirsRaw which will have to reparent
 * directories)
 * Fixed -r to figure out the chunk the root dir occupies (if possible)
 * (Usually 100 !) Wrote the documentation. We do documentation?
 *
 * 007 03/01/97
 * Added -a and -f flags. (and implemented them!)
 *
 * 008 04/01/97
 * Documentation up to date.
 * Can now do options all run together - eg
 * x-recover  -nvv1t 1024 $.Yaffel.dump.Perl.perl5003
 * (or x-recover  -nvv1t1024 $.Yaffel.dump.Perl.perl5003)
 * although one must have a space after the offset of -r or -t
 * Fixed two bugs ( dj vu flag and sprintf to a possible NULL pointer )
 * See below for our policy on bugs.
 *
 * 009 07/01/97
 * Fixed design - now believe the get the number of chunks from chunktable chunk
 * 0 rather than the header if a chunktable offset is specified
 *
 * Bugs: We don't do bugs.
 *
 * None known. Please report bugs (preferably with fixes) to <bagpuss@done.net>
 * If you can supply an x-File to demonstrate then this would be useful.
 * Currently I'm quite happy for relevant e-mail up to 1Mb, but if
 * bagpuss.done.net is up then use anonymous ftp to upload problem files.
 * (Files up to 100Mb acceptable by this method. No, I'm not confusing Kb with
 * Mb. If you're on Janet then you should be able to shift it to me in 7
 * minutes.)
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdarg.h>
#include <limits.h>
#include "chunks.h"

#ifdef __riscos
#include "kernel.h"
#include "SWIs.h"
#endif

#ifdef __GNUC__
#include "unistd.h"
#endif

#define _max(x, y) ((x) > (y) ? (x) : (y))
#define _min(x, y) ((x) < (y) ? (x) : (y))
#define _four( N ) ((((N)-1)|3)+1)

#ifndef FREE
#define FREE 0x45455246
#endif

#ifdef __riscos
static const char *xrecover_dirSep = ".";
#else
/* Should work Unix, Amiga, DOG (DOG internals don't care whether it's \ or /
 * Not that I care about DOG. Well I do actually - I care that it is extant and
 * still wasting people's time, and I work towards its extinction )
 * "Dummies Guide to Memory Management" - Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaargh
 * *Any* decent system just works. (usually because it comes with a flat memory
 * model.)
 */
static const char *xrecover_dirSep = "/";
#endif

enum {
  xrecover_bufferSize = xFiles_ALLOCATIONUNIT * 64
};
/* If I do #define then I get text substitution everywhere (including functions)
 * I believe that const int will allocate memory (so that the address can be
 * taken)
 */

typedef union {
  xFiles_header		header;
  xFiles_chunk		chunk;
  xFiles_dirHeader	dirHeader;
  char			padding[xFiles_ALLOCATIONUNIT];
} xguess_block;


typedef struct
{
  xFiles_dirEntry	dirEntry;
  char			name[xFiles_MAXNAME+1];
  /* Does xFiles_MAXNAME include the '\0' ? */
} xrecover_dirEntryWithName;


typedef enum {
  xrecover_untouched	= 0x00,
  xrecover_touched	= 0x01,
  xrecover_foundChunk	= 0x02,
  xrecover_sizeMatches	= 0x04,
  xrecover_started	= 0x08,
  /* An attempt to recover named file has been made. Stops list iterator
   * attempting to reopen an illegal filename several times.
   */
  xrecover_readWhole	= 0x10,
  xrecover_crossLinked	= 0x20,
  xrecover_openFail	= 0x40,
  xrecover_dejaVu	= 0x80,	/* Flag on entering a directory, to stop
				 * infinite loops if directory tree has become
				 * tangled */
  xrecover_sorted	= 0x1F	/* Everything hunky-dory with this file */
} xrecover_fileStatus;

typedef xrecover_fileStatus xrecover_chunkStatus;

typedef struct xrecover_filenameList_struct
{
  struct xrecover_filenameList_struct	*next;
  xrecover_fileStatus			status;
  unsigned				node;	/* only found in dirHash */
  xFiles_dirEntry			dirEntry;
} xrecover_filenameListEntry;

typedef struct
{
  xrecover_chunkStatus		status;
  /* Currently only used for the dj vu flag.
   * If it's going to be used by anything else check that every fileList
   * operation correctly updates it */
  xrecover_filenameListEntry	*file;
  unsigned long			references;
/* Not entirely sure what xFiles_chunk::usage is for - maybe I should use that
 * to record how many directory entries I find pointing to the chunk */
} xrecover_chunkTableCrossCheck;



typedef union {
  xFiles_dirHeader		dirHeader;
  xFiles_dirEntry		dirEntry;
  xrecover_dirEntryWithName	file;
} xrecover_block;

typedef enum {
  xrecover_qualityEvery1024,	/* Read in 1024 byte quanta. Check each quantum
				 * for dir signature. If dir, skip through it
				 * If not, append to previous file quanta */
  xrecover_qualityChkdsk,	/* Read in chunktable. Write out all file
				 * chunks */
  xrecover_qualityReadDirs,	/* Read in chunktable. Read in dir structure.
				 * Attempt to find each named file. Write out
				 * all orphaned file chunks */
  xrecover_qualityReadDirsRaw	/* As above, but run after collecting all known
				 * dirs, scan x-file for other dir chunks. */
} xrecover_qualityLevel;

/* This is for return values */
typedef enum {
  xrecover_chunkSuccess,
  xrecover_chunkDejaVu,
  xrecover_chunkCantOpen,
  xrecover_chunkReadFail,
  xrecover_chunkWriteFail,
  xrecover_chunkAttrFail
} xrecover_chunkResult;

typedef enum {
  xrecover_inDirLoad,
  xrecover_inDirExec,
  xrecover_inDirSize,
  xrecover_inDirAttr,
  xrecover_inDirNameLen,
  xrecover_inDirName,
  xrecover_inDirUnknown
} xrecover_inDirWhere;		/* The ill-fated 1024 byte block dir-streamer */

typedef struct {
  BOOL valid;
  unsigned long offset;
} xrecover_Offset;


/* Both these strings have the property that the printf()ed output is the same
 * length as the string 8-)
 * The program assumes this further on 8-(
 *
 * (BUG - will overflow for X-Files with > 9999 entries)
 */
static const char *chkdskString	  = "file%04d";
static const char *chkdskStringMS = "FILE%04d/CHK";	/* 8-) */

static const char *xrecover_FileType( signed long load, xFiles_attr attr )
{
  static char buffer[10];

  if( attr & xFiles_isDir )
  {
    strcpy( buffer, "Directory" );
  }
  else
  {
    strcpy( buffer, "         " );

    if( ( load >> 24 ) == -1 )
#ifdef __riscos
    {
      _kernel_swi_regs regs;
      _kernel_oserror *err;

      regs.r[0] = 18;
      regs.r[2] = (int) ( load >> 8 ) & 0xFFF;

      if( err = _kernel_swi( OS_FSControl, &regs, &regs ), err )
      {
	fprintf( stderr, "%s\n", err->errmess );
      }
      else
      {
	*( (int *) buffer ) = regs.r[2];
	*( 1 + ( (int *) buffer ) ) = regs.r[3];
      }
    }
#else
    {
      sprintf( buffer, "&%03lX     ", ( load >> 8 ) & 0xFFF );
    }
#endif
  }
  return buffer;
}

static _kernel_oserror *mkdir( const char *dirname )
#ifdef __riscos
{
  _kernel_swi_regs regs;

  regs.r[0] = 8;
  regs.r[1] = (int) dirname;
  regs.r[4] = xFiles_GROWDIRBY;	/* Why not? */

  return _kernel_swi( OS_File, &regs, &regs );
}
#else
#error Wot no mkdir function?
Should be fun on Unix [hint system( mkdir ) ]
#endif
/* Will have to re-do this subroutine for other OSes */

static const char *xrecover_Attr( xFiles_attr attr )
{
  static char buffer[8];
  char *pos = buffer;

  if( attr & xFiles_isDir ) *pos++ = 'D';
  if( attr & xFiles_locked ) *pos++ = 'L';
  if( attr & xFiles_meRead ) *pos++ = 'R';
  if( attr & xFiles_meWrite ) *pos++ = 'W';

  *pos++ = '/';

  if( attr & xFiles_youRead ) *pos++ = 'r';
  if( attr & xFiles_youWrite ) *pos++ = 'w';

  *pos = '\0';

  return buffer;
}

static BOOL xrecover_ReadOffset( xrecover_Offset *offset, const char *value )
{
  char *end;

  offset->valid = FALSE;
  if( 0 == value ) return FALSE;

  offset->offset = strtoul( value, &end, 10 );

  if( end == value )
  {
    fprintf( stderr, "Failed to make sense of offset '%s'\n", value );
    return FALSE;
  }

  offset->valid = TRUE;
  return TRUE;
}

/* 2 functions from x-check */
static int power2( int x )
{
  while( x && !( x & 1 ) ) x >>= 1;
  return x == 1;
}

static void test( int c, const char *fmt, ... )
{
  va_list ap;
  va_start( ap, fmt );

  if( !c )
  {
    vfprintf( stderr, fmt, ap );
  }

   va_end( ap );
}

static unsigned xrecover_chunkFromOffset( const xFiles_chunk *chunkTable,
					  unsigned maxChunk,
					  const unsigned long offset )
{
  while( maxChunk-- )
  {
    if( chunkTable[maxChunk].usage != FREE
	&& chunkTable[maxChunk].offset == offset ) return maxChunk;
  }
  return 0;
}


 static void printChunkDir( FILE *where, const int chunkNum,
			   const char *pathToHere )
{
  fprintf( where, "Chunk %d ", chunkNum );
  if( pathToHere )
  {
    fprintf( where, ( *pathToHere ) ? "[directory '%s'] "
				    : "[root directory] ", pathToHere );
  }
}

static void printChunkDirEntry( FILE *where, const int chunkNum,
				const char *pathToHere, const unsigned entry,
				const char *filename, const unsigned node )
{
  printChunkDir( where, chunkNum, pathToHere );
  fprintf( where, "entry %d ", entry );
  if( filename )
  {
    fprintf( where, "('%s') ", filename );
  }
  if( node )
  {
    fprintf( stderr, "->[chunk %d] ", node );
  }
}

static unsigned roundDown( xFiles_header *header, unsigned pos )
{
  return pos - pos % header->allocationUnit;
}

static unsigned roundUp( xFiles_header *header, unsigned pos )
{
  return pos - ( pos % header->allocationUnit ) + header->allocationUnit;
}

static int okOffset( xFiles_header *header, unsigned offset )
{
   return ( offset % header->allocationUnit ) == 0;
}

static int hashCmp( const void *alpha, const void *omega )
{
  const xFiles_dirHash *zero = alpha;
  const xFiles_dirHash *inf = omega;

  return (int) zero->entryPos - (int) inf->entryPos;
}

static int filenameListEntryCmp( const void *e, const void *to_the )
{
  xrecover_filenameListEntry *const *pi = e;
  xrecover_filenameListEntry *const *i = to_the;

  return (int) (*pi)->dirEntry.size - (int) (*i)->dirEntry.size;
}

static int chunkSizeCmp( const void *plus, const void *one )
{
  const xFiles_chunk *const *equals = plus;
  const xFiles_chunk *const *zero = one;

  /* Currently this function is only used on known non-free chunks */
  return (int) (*equals)->size - (int) (*zero)->size;
}

static int chunkOffsetCmp( const void *alpha, const void *omega )
{
  /* I think it was the OED which had aardvark and zygote as its first and last
   * real words.
   *
   * (Unfortunately, all geologists know that aa is blocky lava) */
  const xFiles_chunk *const *aardvark = alpha;
  const xFiles_chunk *const *zygote = omega;

  /* Get all free chunks at one end */
  return (*aardvark)-> usage == FREE ? -1
	 : (int) (*aardvark)->offset - (int) (*zygote)->offset;
}

static int xrecover_xguess( const char *suspect )
{
  xguess_block block;
  FILE *in;
  unsigned whereIsRootChunk;
  long where;

  if( sizeof( xguess_block ) != xFiles_ALLOCATIONUNIT )
  {
    fprintf( stderr,
	     "Awooga, awooga: sizeof( xguess_block ) != xFiles_ALLOCATIONUNIT"
	     "\n\t\t%d != %d\n",
	     sizeof( xguess_block ), xFiles_ALLOCATIONUNIT );
    return 255;
  }

  if( 0 == ( in = fopen( suspect, "rb" ) ) )
  {
    fprintf( stderr, "Could not open file '%s'\n", suspect );
  }
  else if( 0 == fread( &block, sizeof( block ), 1, in ) )
  {
    fprintf( stderr, "Could not read header from file '%s'\n", suspect );
  }
  else
  {
    test( block.header.sig == xFiles_SIG,
	  "Missing xFiles_SIG - read %8X, should be %8X\n",
	  block.header.sig, xFiles_SIG );
    test( block.header.hdrSize == sizeof( xFiles_header ),
	  "Illegal hdrSize %d\n",
	  block.header.hdrSize );

    test( block.header.structureVersion == xFiles_STRUCTUREVERSION,
	  "Illegal structureVersion %d\n",
	  block.header.structureVersion );

    test( block.header.directoryVersion == xFiles_DIRECTORYVERSION,
	  "Illegal directoryVersion %d\n",
	  block.header.directoryVersion );

    test( power2( block.header.allocationUnit ),
	  "Illegal allocationUnit %d\n",
	  block.header.allocationUnit );

    printf( "File '%s' header states:\n"
	    "         Chunktable at %d\n",
	    suspect, block.header.chunkTable.offset );

    whereIsRootChunk =	 block.header.chunkTable.offset
		       + block.header.rootChunk * sizeof( xFiles_chunk );

    if( fseek( in, whereIsRootChunk, SEEK_SET ) )
    {
      fprintf( stderr, "Could not seek to root chunk at %d\n",
	       whereIsRootChunk );
    }
    else if( 0 == fread( &block, sizeof( xFiles_chunk ), 1, in ) )
    {
      fprintf( stderr, "Could not read root directory chunk at %d\n",
	       whereIsRootChunk );
    }
    else
    {
      printf( "Root directory is in chunk %d, file offset %d\n",
	      block.header.rootChunk, block.chunk.offset );
    }
  }

  rewind( in );

  while( ( where = ftell( in ) ),
	 ( 0 != fread( &block, sizeof( block ), 1, in ) ) )
  {
    if(	   ( block.chunk.offset == where )
	&& ( block.chunk.size % sizeof( xFiles_chunk ) == 0 ) )
    {
      /* Smells like the chunktable if the first 'chunk' points to this
       * position in the file (which is read into where before the chunktable)
       * and the size of the 'chunktable' is a multiple of the size of a chunk
       */
       printf( "Could be chunktable at %ld\n", where );
    }
    else if(	( block.dirHeader.sig == xFiles_DIRSIG )
	     && ( block.dirHeader.parent == 0 ) )
    {
      /* Smells like the root dir if there is a dir signature ('ANDY') and
       * the parent dir is 0
       * [So what's wrong with using 'Nick' as a dir signature 8-) ]
       */
       printf( "Could be root directory at                  %ld\n", where );
    }
  }

  return 0;
}

static void xrecover_clearList( const unsigned chunkNum,
				xrecover_chunkTableCrossCheck *crossCheckEntry,
				xrecover_filenameListEntry *fileTree,
				int verbose )
{
  if( verbose > 0 )
  {
    printf( "Unlinking: chunk %d currently has %ld reference(s)\n", chunkNum,
	    crossCheckEntry->references );
  }
  while( fileTree )
  {
    if( fileTree->node == chunkNum )
    {
      fprintf( stderr, "Unlinking '%s' from chunk %d\n",
	       ( (char *) &(fileTree->dirEntry) ) + sizeof( xFiles_dirEntry ),
	       chunkNum );
      if( ( fileTree->status & ~xrecover_crossLinked ) == 0 )
      {
	fputs( "Wasn't cross linked", stderr );
      }
      /* Clear these flags */
      fileTree->status &= ~( xrecover_foundChunk | xrecover_sizeMatches
			     | xrecover_crossLinked );

      if( crossCheckEntry->references-- == 0 )
      {
	fprintf( stderr, "Chunk %d reference count incorrect\n", chunkNum );
	crossCheckEntry->references = 0;
      }
    }

    fileTree = fileTree->next;
  }
  if( verbose > 0 )
  {
    printf( "                          now has %ld reference(s)\n",
	    crossCheckEntry->references );
  }

  crossCheckEntry->status &= ~xrecover_crossLinked;
  /* As it's clear it can't be cross linked */
}

static void xrecover_printList( FILE *where,
				const xFiles_chunk *chunkTable,
				const xrecover_chunkTableCrossCheck
				  *crossCheckTable,
				const xrecover_filenameListEntry *fileTree,
				int verbose )
{
  while( fileTree )
  {
    if( verbose == 0 )
    {
      fprintf( where, "%s\n",
	       ( (char *) &(fileTree->dirEntry) ) + sizeof( xFiles_dirEntry ) );
    }
    else
    {
      fprintf( where, "%08X %08X %8d %-7s %s %s\n", fileTree->dirEntry.load,
	       fileTree->dirEntry.exec, fileTree->dirEntry.size,
	       xrecover_Attr( fileTree->dirEntry.attr ),
	       xrecover_FileType( fileTree->dirEntry.load,
				  fileTree->dirEntry.attr ),
	       ( (char *) &(fileTree->dirEntry) ) + sizeof( xFiles_dirEntry ) );
      if( verbose > 1 )
      {
	if( fileTree->status & xrecover_foundChunk )
	{
	  fprintf( where, "  chunk %d ", fileTree->node );
	  fprintf( where,
		   ( chunkTable[fileTree->node].size
		      == fileTree->dirEntry.size )
		   ? "size=%d" : "chunk.size=%d, dirEntry.size=%d",
		   chunkTable[fileTree->node].size, fileTree->dirEntry.size );
	  if( ( chunkTable[fileTree->node].size == fileTree->dirEntry.size )
		== !!( fileTree->status & xrecover_sizeMatches ) )
	  {
	    fputc( '\n', where );
	  }
	  else
	  {
	    fputs( " [incorrectly flagged]\n", where );
	  }
	  if( fileTree->status & xrecover_crossLinked )
	  {
	    if( 1 == crossCheckTable[fileTree->node].references )
	    {
	      fputs( "  error - flagged as cross linked, but chunk has exactly "
		     "one reference\n", where );
	    }
	    else
	    {
	      fprintf( where, "  cross linked: total %ld cross references\n",
		       crossCheckTable[fileTree->node].references );
	    }
	  }
	  else
	  {
	    if( 1 != crossCheckTable[fileTree->node].references )
	    {
	      fprintf( where, "  error - not flagged as cross linked, chunk has"
		       " %ld cross references\n",
		       crossCheckTable[fileTree->node].references );
	    }
	  }
	}
	else
	{
	  fputs( "  [no chunk found]\n", where );
	}
      }
    }
    fileTree = fileTree->next;
  }
}

static void
xrecover_getProblemList( xrecover_filenameListEntry *fileTree,
			 xrecover_filenameListEntry **problems,
			 unsigned numProblems )
{
  xrecover_filenameListEntry **end = problems + numProblems;
  while( fileTree )
  {
    if( ( fileTree->status & xrecover_foundChunk ) == 0 )
    {
      *problems = fileTree;
      if( ++problems > end )
      {
	fputs( "Miscounted problems in the fileTree\n", stderr );
	return;
      }
    }
    fileTree = fileTree->next;
  }
}

static void xrecover_freeList( xrecover_filenameListEntry **fileTree )
{
  xrecover_filenameListEntry *temp;
  while( *fileTree )
  {
    temp = (*fileTree)->next;
    free( *fileTree );
    *fileTree = temp;
  }
}

static xrecover_chunkResult
xrecover_recoverDir( FILE *in, FILE *out, const char *victim,
		     const char *pathToHere,
		     const unsigned long start,
		     const unsigned chunkNum,
		     const xFiles_chunk *thisChunk,
		     const xFiles_chunk *chunkTable,
		     xrecover_chunkTableCrossCheck *crossCheckTable,
		     xrecover_filenameListEntry **fileTree,
		     const unsigned maxChunk, int verbose )
{
  /* out != NULL for text output
   * pathToHere != NULL for full recursion
   * if dirHeader == 0 need to read it from file
   * start points to file offset of start of directory (good for seeking)
   */
  xrecover_block buffer;
  xFiles_dirHeader header;
  unsigned dirEntry = 0;
  size_t maxSize;
  unsigned long where = -1L;	/* ANSI's error result from ftell() */
  xFiles_dirHash *dirHash, *currentDirHash, *dirHashLimit;
  unsigned bytesRead, bytesToRead;
  BOOL haveHash = FALSE;
  const size_t pathLen = pathToHere ? ( 1 + strlen( pathToHere )
				    + strlen( xrecover_dirSep ) ) : 0;
  /* One for the '\0' */


  if( thisChunk == 0 && chunkTable && chunkNum )
  {
    /* chunk 0 is always the chunkTable, so it can never be a directory */
    thisChunk = chunkTable + chunkNum;
  }

  if( chunkNum && crossCheckTable &&
      ( crossCheckTable[chunkNum].status & xrecover_dejaVu ) )
  {
    printChunkDir( stderr, chunkNum, pathToHere );
    fputs( "Dj vu - been to this directory before", stderr );
    if( crossCheckTable[chunkNum].file )
    {
      fprintf( stderr, " as '%s'\n",
	       ( (char *) &(crossCheckTable[chunkNum].file->dirEntry) )
		 + sizeof( xFiles_dirEntry ) );
    }
    else putc( '\n', stderr );

    if( out ) fclose( out );
    return xrecover_chunkDejaVu;
  }

  rewind( in );
  if( fseek( in, start, SEEK_SET ) )
  {
    printChunkDir( stderr, chunkNum, pathToHere );
    fprintf( stderr, "could not seek to start (offset %ld)\n", start );
    if( out ) fclose( out );
    return xrecover_chunkReadFail;
  }

  if( 0 == fread( &header, sizeof( xFiles_dirHeader ), 1, in ) )
  {
    printChunkDir( stderr, chunkNum, pathToHere );
    fputs( "could not read dirHeader\n", stderr );
    if( out ) fclose( out );
    return xrecover_chunkReadFail;
  }


  if( verbose > 1 )
  {
    fputs( "Directory", stdout );
    /* This has to be fputs() as puts() appends '\n'
     * Why are puts, fputs and *printf so inconsistent? */
    if( pathToHere )
    {
      printf( " '%s'", *pathToHere ? pathToHere : "<Root>" );
    }
    printf( ": size=%d used=%d\n", header.size, header.used );
  }

  if( 0 == ( dirHash = malloc( sizeof( xFiles_dirHash ) * header.used ) ) )
  {
    printChunkDir( stderr, chunkNum, pathToHere );
    fprintf( stderr, "failed to get %d bytes of memory for dir hash\n",
	     sizeof( xFiles_dirHash ) * header.used );
    if( out ) fclose( out );
    return xrecover_chunkReadFail;
  }

  bytesRead = fread( dirHash, sizeof( xFiles_dirHash ), header.used, in );
  if( bytesRead != header.used )
  {
    printChunkDir( stderr, chunkNum, pathToHere );
    fprintf( stderr, "could only read %d of %d dirHash(es)\n",
	     bytesRead, header.used );
    if( out ) fclose( out );
    free( dirHash );
    return xrecover_chunkReadFail;
  }
  /* Sort into position order. As far as I can tell X-Files 056 keeps its
   * hash sorted by position order, but this doesn't seem to be part of the
   * specification, so I won't assume it */

  qsort( dirHash, header.used, sizeof( xFiles_dirHash ), hashCmp );

  currentDirHash = dirHash;
  dirHashLimit = dirHash + header.used;

  if( fseek( in, sizeof( xFiles_dirHash ) * ( header.size - header.used ),
	     SEEK_CUR ) )
  {
    printChunkDir( stderr, chunkNum, pathToHere );
    fputs( "could not seek to start of dir entries\n", stderr );
    if( out ) fclose( out );
    free( dirHash );	/* Something says that putting goto exit; would
			 * reduce the possibility of bugs (in this case a
			 * memory leak on dirHash and an open file on out ) */
    return xrecover_chunkReadFail;
  }

  for( ; dirEntry < header.used; dirEntry++ )
  {
    where = ftell( in );
    if( haveHash ) currentDirHash++;
    /* Used currentDirHash, so advance it one */
    if( 0 == fread( &buffer, sizeof( xFiles_dirEntry ), 1, in ) )
    {
      printChunkDir( stderr, chunkNum, pathToHere );
      fprintf( stderr, "could not read directory entry %d\n",
	       dirEntry );
    }
    else
    {
      if( buffer.dirEntry.nameLen > xFiles_MAXNAME )
      {
	printChunkDirEntry( stderr, chunkNum, pathToHere, dirEntry, 0, 0 );
	fprintf( stderr, "reports nameLen as %d - truncating to %d\n",
		 buffer.dirEntry.nameLen, xFiles_MAXNAME );
	buffer.dirEntry.nameLen = xFiles_MAXNAME;
      }

      bytesToRead = _four( buffer.dirEntry.nameLen + 1 );
      bytesRead = fread( &(buffer.file.name), 1, bytesToRead, in );
      if( bytesRead < bytesToRead )
      {
	/* Terminate what we got */
	buffer.file.name[bytesRead] = '\0';
	printChunkDirEntry( stderr, chunkNum, pathToHere, dirEntry,
			    buffer.file.name, 0 );
	fprintf( stderr, "could not read %d bytes of filename (Got %d)\n",
		 bytesToRead, bytesRead );
      }

      while( ( currentDirHash < dirHashLimit )
	     && ( currentDirHash->entryPos < ( where - start ) ) )
      {
	printChunkDirEntry( stderr, chunkNum, pathToHere, dirEntry, 0, 0 );
	fprintf( stderr, "no filename entry for dirhash:\n"
		 "\t\t'%c%c%c%c' chunk %d entryPos %d\n",
		 currentDirHash->nameStart[0], currentDirHash->nameStart[1],
		 currentDirHash->nameStart[2], currentDirHash->nameStart[3],
		 currentDirHash->node, currentDirHash->entryPos );
	currentDirHash++;
      }

      if( ( currentDirHash >= dirHashLimit )
	  || ( currentDirHash->entryPos != ( where - start ) ) )
      {
	haveHash = FALSE;
	printChunkDirEntry( stderr, chunkNum, pathToHere, dirEntry,
			    buffer.file.name, 0 );
	fputs( "no dirhash for filename entry\n", stderr );
      }
      else
      {
	haveHash = TRUE;
	if( *( (int *) currentDirHash ) != *( (int *) buffer.file.name ) )
	{
	/* Check hash == name */
	printChunkDirEntry( stderr, chunkNum, pathToHere, dirEntry,
			    buffer.file.name, 0 );
	fprintf( stderr, "does not match dirhash '%c%c%c%c'\n",
		 currentDirHash->nameStart[0], currentDirHash->nameStart[1],
		 currentDirHash->nameStart[2], currentDirHash->nameStart[3] );
	}
      }

      where += sizeof( xFiles_dirEntry ) + bytesToRead;
      /* Where we *should* be */
      if( out )
      {
	fprintf( out, "%08X %08X %8d %-7s %s %s\n", buffer.dirEntry.load,
		   buffer.dirEntry.exec, buffer.dirEntry.size,
		   xrecover_Attr( buffer.dirEntry.attr ),
		   xrecover_FileType( buffer.dirEntry.load,
				      buffer.dirEntry.attr ),
		   buffer.file.name );
      }

      if( fileTree && pathToHere )
      {
	xrecover_filenameListEntry *current =
	  malloc( sizeof( xrecover_filenameListEntry )
			  + buffer.dirEntry.nameLen + pathLen );
	char *entryPath = current ? ( (char *) &(current->dirEntry) )
				    + sizeof( xFiles_dirEntry )
				  : 0;
	/* Relative path from x-file root to this entry */

	if( 0 == current ) continue;	/* Implicit goto the next loop */

	current->dirEntry = buffer.dirEntry;
	if( *pathToHere )
	{
	  /* Make a path */
	  sprintf( entryPath, "%s%s%s", pathToHere, xrecover_dirSep,
		   buffer.file.name );
	}
	else
	{
	  strcpy( entryPath, buffer.file.name );
	}
	current->status = xrecover_touched;
	current->node = haveHash ? currentDirHash->node : 0;

	if( current->node )
	{
	  if( current->node >= maxChunk )
	  {
	    printChunkDirEntry( stderr, chunkNum, pathToHere, dirEntry,
				buffer.file.name, 0 );
	    fprintf( stderr, "chunk %d is out of range (%d)\n", current->node,
		     maxChunk );
	    current->node = 0;
	  }
	  else
	  {
	    if( chunkTable[current->node].usage == FREE )
	    {
	       printChunkDirEntry( stderr, chunkNum, pathToHere, dirEntry,
				   buffer.file.name, current->node );
	       fputs( "is marked free\n", stderr );
	       current->node = 0;
	    }
	    else
	    {
	      current->status |= xrecover_foundChunk;
	      /* So we have a chunk... */
	      if( crossCheckTable[current->node].file )
	      {
		xrecover_filenameListEntry *crossLink =
		  (crossCheckTable[current->node].file);
		printChunkDirEntry( stderr, chunkNum, pathToHere, dirEntry,
				    buffer.file.name, current->node );
		fprintf( stderr, "is cross linked with file '%s'\n",
			 ( (char *) &(crossLink->dirEntry) )
			 + sizeof( xFiles_dirEntry ) );
		/* Eeek. But wait for it */

		if( crossLink->status & xrecover_sizeMatches )
		{
		  /* They think that they have their chunk */
		  if( chunkTable[current->node].size == buffer.dirEntry.size )
		  {
		    /* And so do we */
		    crossLink->status |= xrecover_crossLinked;
		    current->status |= xrecover_crossLinked
				       | xrecover_sizeMatches;
		    crossCheckTable[current->node].references += 1;
		  }
		  else
		  {
		    /* They match and we don't */
		    current->status &= ~xrecover_foundChunk;
		    fprintf( stderr, "File '%s' size %d tallies with "
			     "chunktable - unlinking '%s'\n",
			     ( (char *) &(crossLink->dirEntry) )
			     + sizeof( xFiles_dirEntry ),
			     chunkTable[current->node].size, buffer.file.name );
		    current->node = 0;
		  }
		}
		else
		{
		  /* Their size doesn't match */
		  if( chunkTable[current->node].size == buffer.dirEntry.size )
		  {
		    /* But ours does */
		    current->status |= xrecover_sizeMatches;
		    crossCheckTable[current->node].file = current;
		    xrecover_clearList( current->node,
					crossCheckTable + current->node,
					*fileTree, verbose );

		    if( crossCheckTable[current->node].references )
		    {
		      fprintf( stderr, "Awooga - failed to clear the list - %ld"
			       " references remain",
			       crossCheckTable[current->node].references );
		      current->status |= xrecover_crossLinked;
		    }
		    else
		    {
		      crossCheckTable[current->node].references = 1;
		    }
		  }
		  else
		  {
		    /* Our size doesn't match either */
		    crossLink->status |= xrecover_crossLinked;
		    current->status |= xrecover_crossLinked;
		    crossCheckTable[current->node].references += 1;
		  }
		}

	      }
	      else
	      {
		/* We have the chunk all to ourselves */
		if( chunkTable[current->node].size == buffer.dirEntry.size )
		{
		  /* And the size matched 8-) */
		  current->status |= xrecover_sizeMatches;
		}
		crossCheckTable[current->node].file = current;
		crossCheckTable[current->node].references = 1;
	      }

	      if( buffer.dirEntry.attr & xFiles_isDir )
	      {
		size_t size = victim ? strlen( victim )
				       + strlen( xrecover_dirSep )
				       + strlen( entryPath ) + 1
				     : 0;
		char *tempBuffer = size ? malloc( size ) : 0;
		_kernel_oserror *err = 0;

		if( size )
		{
		  if( tempBuffer == 0 )
		  {
		    printChunkDirEntry( stderr, chunkNum, pathToHere, dirEntry,
					buffer.file.name, current->node );
		    fprintf( stderr, "failed to get %d bytes of memory for dir "
			     "name.\nCannot create output sub-directory, so "
			     "will not recurse into input sub-directory\n",
			     size );
		    err = (_kernel_oserror *) -5;
		    /* Should address exception/abort on data transfer if anyone
		     * is silly enough to try to read errmess
		     */
		    current->status |= xrecover_started;
		    /* Flag to stop list routine stamping all over any offending
		     * file that got in our way */
		  }
		  else
		  {
		    sprintf( tempBuffer, "%s%s%s", victim, xrecover_dirSep,
			     entryPath );
		  }
		}
		if( size && ( 0 != ( err = mkdir( tempBuffer ) ) ) )
		{
		  printChunkDirEntry( stderr, chunkNum, pathToHere, dirEntry,
				      buffer.file.name, current->node );
		  fprintf( stderr, "%s\n", err->errmess );
		}
		free( tempBuffer );

		if( 0 == err )
		{
		  xrecover_recoverDir( in, out, victim,
				       entryPath,
				       chunkTable[current->node].offset,
				       current->node, 0, chunkTable,
				       crossCheckTable, fileTree,
				       maxChunk, verbose );
		  rewind( in );	/* Clear any error */
		  if( fseek( in, where, SEEK_SET ) )
		  {
		    printChunkDirEntry( stderr, chunkNum, pathToHere, dirEntry,
					buffer.file.name, current->node );
		    fputs( "could not seek ready for next directory entry\n",
			   stderr );
		    if( out ) fclose( out );
		    free( dirHash );
		    current->next = *fileTree;
		    fileTree = &current;
		    return xrecover_chunkReadFail;
		  }
		}
	      }
	    } /* End of having a chunk */
	  }
	}
	else
	{
	  printChunkDir( stderr, chunkNum, pathToHere );
	  fprintf( stderr, "no chunk found for '%s'\n", buffer.file.name );
	}

	/* Add this filename to the head of the list */
	current->next = *fileTree;
	*fileTree = current;
      }
    }
  }

  if( where != -1L && start != -1L && thisChunk
      && ( ( maxSize = (unsigned int) ( where - start ) ) > thisChunk->size ) )
  {
    printChunkDir( stderr, chunkNum, pathToHere );
    fprintf( stderr, "chunkTable reports size as %d, appears to be %d\n",
	     thisChunk->size, maxSize );
  }
  if( out ) fclose( out );
  free( dirHash );
  return xrecover_chunkSuccess;
}

static xrecover_chunkResult
xrecover_recoverArbitaryArea( FILE *in, const char *victim,
			      const unsigned long offset,
			      unsigned long size, const int verbose )
{
  xrecover_chunkResult status = xrecover_chunkSuccess;
  FILE *out;

  rewind( in );	/* Seems to need this to reset stream after seek beyond end */
  if( fseek( in, offset, SEEK_SET ) )
  {
    fprintf( stderr, "Could not seek to offset %ld\n", offset );
    return xrecover_chunkReadFail;
  }

  remove( victim );	/* This will ensure the correct file type (Data)
			 * and that we can open R/ files */

  if( 0 == ( out  = fopen( victim, "wb" ) ) )
  {
    fprintf( stderr, "Offset %ld could not open file '%s' for output\n",
	     offset, victim ? victim : "<NULL POINTER>" );
    return xrecover_chunkCantOpen;
  }


  while( size )
  {
    char buffer[xrecover_bufferSize];
    unsigned bytesWritten;
    unsigned bytesToRead = (unsigned) _min( size, sizeof( buffer ) );
    unsigned bytesRead;

    memset( buffer, 0, bytesToRead );

    bytesRead = fread( buffer, 1, bytesToRead, in );
    if( bytesRead != bytesToRead )
    {
      if( status == xrecover_chunkSuccess )
      {
	status = xrecover_chunkReadFail;
      }
      fprintf( stderr, "Offset %ld could not read %d bytes (Got %d)\n",
	       offset, bytesToRead, bytesRead );
      if( bytesRead == 0 )
      {
	if( verbose > 0 ) puts( "Bailing out" );
	break;
      }
      bytesRead = bytesToRead;	/* Pretend that we got them */
    }

    {
      bytesWritten = fwrite( buffer, 1, bytesRead, out );
      if( bytesRead != bytesWritten )
      {
	fprintf( stderr, "Offset %ld, file '%s' - only wrote %d byte(s) out of "
		 "%d\n", offset, victim, bytesWritten, bytesRead );
	if( status == xrecover_chunkSuccess )
	{
	  status = xrecover_chunkWriteFail;
	}
      }
    size -= bytesRead;
    }
  }

  fclose( out );
  return status;
}

static xrecover_chunkResult
xrecover_recoverChunkChkdsk( FILE *in, const char *victim, unsigned chunkNum,
			     const xFiles_chunk *chunk, unsigned forceSize,
			     BOOL dirsAsText, BOOL suppressFiles, int verbose )
/* Leave forceSize as 0 to believe the chunkTable entry
 * Otherwise, you take your life into your own hands!
 */
{
  FILE *out = 0;
  char buffer[xrecover_bufferSize];
  unsigned bytesToCopy = forceSize ? forceSize : chunk->size;
  unsigned bytesRead, bytesWritten;
  unsigned bytesToRead = _min( bytesToCopy, sizeof( buffer ) );
  BOOL isDir = FALSE;
  xrecover_chunkResult status = xrecover_chunkSuccess;	/* optimism */

  rewind( in );	/* Seems to need this to reset stream after seek beyond end */
  if( fseek( in, chunk->offset, SEEK_SET ) )
  {
    fprintf( stderr, "Chunk %d could not seek to start (offset %d)\n", chunkNum,
	     chunk->offset );
    return xrecover_chunkReadFail;
  }

  /* printf( "off=%d f=%d c=%d using %d\n", chunk->offset, forceSize, chunk->size, bytesToCopy ); */
  if( verbose > 3 )
  {
    printf( "Sought %d, got to %ld\n", chunk->offset, ftell( in ) );
  }

  memset( buffer, 0, sizeof( buffer ) );

  bytesRead = fread( buffer, 1, bytesToRead, in );
  if( bytesRead < bytesToRead )
  {
    fprintf( stderr, "Chunk %d could not read %d bytes (Got %d)\n",
	     chunkNum, sizeof( buffer ), bytesRead );
    return xrecover_chunkReadFail;
  }

  if( *(int *) buffer == xFiles_DIRSIG && dirsAsText )
  {
    /* Smells like a directory. */
    isDir = TRUE;
  }
  else if( suppressFiles ) return xrecover_chunkSuccess;

  if( victim )
  {
    remove( victim );	/* This will ensure the correct file type (Text or Data)
			 * and that we can open R/ files */
    out = fopen( victim, isDir ? "w": "wb" );
  }

  if( ( victim != 0 ) && ( out == 0 ) )
  {
    status = xrecover_chunkCantOpen;
    fprintf( stderr, "Chunk %d could not open file '%s' for output\n", chunkNum,
	     victim );
  }

  if( isDir )
  {
    /* It did use to pass in the header, with code to read the header in
     * xrecover_recoverDir() if a NULL pointer was passed, but I figured that
     * the amount of code generated was probably greater than the time saved for
     * a buffered filing system re-reading 16 bytes! */
    return xrecover_recoverDir( in, out, 0, 0, chunk->offset, chunkNum, chunk,
				0, 0, 0, 0, verbose );
  }

  if( bytesRead > bytesToCopy ) bytesRead = bytesToCopy;
  /* Cope with short files */

  while( bytesToCopy )
  {
    /* I was going to write a stream based dir converter, but then I thought:
     * Sod it, let's just malloc a buffer big enough.
    if( isDir )
    {

    }
    else
     */
    {
      if( out )
      {
	bytesWritten = fwrite( buffer, 1, bytesRead, out );
	if( bytesRead != bytesWritten )
	{
	  fprintf( stderr, "Chunk %d, file '%s' - only wrote %d byte(s) out of "
		   "%d\n", chunkNum, victim, bytesWritten, bytesRead );
	  if( status == xrecover_chunkSuccess )
	  {
	    status = xrecover_chunkWriteFail;
	  }
	}
      }
    }
    bytesToCopy -= bytesRead;
    if( bytesToCopy != 0 )
    {
      bytesToRead = _min( bytesToCopy, sizeof( buffer ) );


      memset( buffer, 0, bytesToRead );
      bytesRead = fread( buffer, 1, bytesToRead, in );
      if( bytesRead != bytesToRead )
      {
	fprintf( stderr, "Chunk %d could not read %d bytes (Got %d)\n",
		 chunkNum, bytesToRead, bytesRead );
	if( ferror( in ) )
	{
	  perror( "Error on input x-file" );
	}
	else
	{
	  fprintf( stderr, "Reached end of input x-file - now at %ld\n",
		   ftell( in ) );
	}
	if( status == xrecover_chunkSuccess )
	{
	  status = xrecover_chunkReadFail;
	}
	if( bytesRead == 0 )
	{
	  if( verbose > 0 ) puts( "Bailing out" );
	  break;
	}
	bytesRead = bytesToRead;	/* Pretend that we got them */
      }
    }

  }
  if( out ) fclose( out );
  return status;
}


static unsigned
xrecover_doGoodInList( FILE *in, const char *victim,
		       const xFiles_chunk *chunkTable,
		       xrecover_chunkTableCrossCheck *crossCheckTable,
		       xrecover_filenameListEntry *fileTree,
		       const BOOL useAlloc, const int verbose )
{
  unsigned problemFiles = 0;
  xrecover_chunkResult result;
  size_t prefixNameLen = strlen( victim ) + strlen( xrecover_dirSep ) + 1;

  while( fileTree )
  {
    if( fileTree->status & xrecover_foundChunk )
    {
      if( victim && 0 == ( fileTree->status & xrecover_started ) )
      {
	char *outPathname;
	const xFiles_chunk *thisChunk = chunkTable + fileTree->node;
	unsigned getSize = 0;	/* Believe the chunk by default */
	const char *filename = ( (char *) &(fileTree->dirEntry) )
			       + sizeof( xFiles_dirEntry );

	if( !( fileTree->status & xrecover_sizeMatches )
	    && !( fileTree->dirEntry.attr & xFiles_isDir ) )
	/* Already flagged problem directories
	 * directories report size in parent as 0, but have non-zero size in the
	 * chunktable, hence they will always error here */
	{
	  /* Which has it bigger - the directory or the chunk? */
	  getSize = _max( fileTree->dirEntry.size, thisChunk->size );

	  fprintf( stderr, "File '%s' chunk.size=%d, dirEntry.size=%d "
		   "chunk.allocSize=%d\n"
		   "Will try to recover %d%s\n", filename, thisChunk->size,
		   fileTree->dirEntry.size, thisChunk->allocSize,
		   getSize, ( getSize > thisChunk->allocSize )
			      ? " [which is greater than the allocated size]"
			      : "" );
	}

	if( useAlloc ) getSize = _max( getSize, thisChunk->allocSize );

	outPathname = malloc( prefixNameLen + strlen( filename ) );

	if( outPathname == 0 )
	{
	  fprintf( stderr, "Could not get buffer for file '%s' - will add chunk"
		   " %d to unclaimed list\n", filename, fileTree->node );
	  result = xrecover_chunkCantOpen;
	}
	else
	{
	  sprintf( outPathname, "%s%s%s", victim, xrecover_dirSep, filename );
	  /* Go for it */
	  result = ( fileTree->dirEntry.attr & xFiles_isDir )
		   ? xrecover_chunkSuccess	/* Directory is already done */
		   : xrecover_recoverChunkChkdsk( in, outPathname,
						  fileTree->node, thisChunk,
						  getSize, FALSE, FALSE,
						  verbose );
	}

	switch( result )
	{
	  case xrecover_chunkSuccess:
	  case xrecover_chunkWriteFail:
	  /* Not my problem if your media won't write */
	  {
	    fileTree->status |= xrecover_readWhole;

#ifdef __riscos
	    if( 0 == ( fileTree->status & xrecover_openFail ) )
	    {
	      _kernel_swi_regs regs;
	      _kernel_oserror *err;
	      regs.r[0] = 1;
	      regs.r[1] = (int) outPathname;
	      regs.r[2] = fileTree->dirEntry.load;
	      regs.r[3] = fileTree->dirEntry.exec;
	      regs.r[5] = fileTree->dirEntry.attr & 0xff;

	      err = _kernel_swi( OS_File, &regs, &regs );
	      if( err )
	      {
		fprintf( stderr, "%s\n", err->errmess );
	      }
	    }
#endif
	  }
	  break;

	  case xrecover_chunkCantOpen:
	  {
	    /* This chunk becomes one of the list of unrecovered chunks */
	    fileTree->status |= xrecover_openFail;
	    /* Make sure that we don't try to re-link this file with its chunk
	     */
	    if( 0 == --(crossCheckTable[fileTree->node].references) )
	    {
	      crossCheckTable[fileTree->node].file = NULL;
	    }
	    /* OK. Not beautiful - ideally each chunk should have a linked list
	     * (or variable array - time for genArray.c to meet the outside
	     * world?) of files that (cross)link to it, but ideally there should
	     * be no cross links.
	     *
	     * If you'd like a copy of genArray.c, email <bagpuss@done.net>
	     */


	    /* ++problemFiles; */
	    /* Don't need this, as *this file* wasn't the problem, rather it was
	     * the output media
	     */
	    break;
	  }
	free( outPathname );
	}
      }
      /* Else there was no chunk found for this file, or we have already made an
       * attempt to open it for output, and it has failed.
       */
      fileTree->status |= xrecover_started;
      /* Either we started it this run, or it was already started, so this makes
       * no difference
       */
    }

    else ++problemFiles;
    fileTree = fileTree->next;
  }

  return problemFiles;
}

static int xrecover_RecoverTree( FILE *in, const char *victim,
				 const char *outNameBuffer,
				 char *victimGoesHere,
				 unsigned chunkCount,
				 const xFiles_header *header,
				 const xFiles_chunk *chunkTable,
				 const unsigned long *rootDirOffset,
				 const unsigned maxChunk, BOOL dirsAsText,
				 BOOL suppressFiles, const BOOL useAlloc,
				 const int verbose )
{
  unsigned rootDirChunk = header ? header->rootChunk : 0;
  unsigned long useRootDirOffset;
  xrecover_chunkTableCrossCheck *crossCheckTable;
  xrecover_filenameListEntry *fileTree = 0;
  unsigned loop;
  unsigned problemFiles, unclaimedChunks = 0;

  if( rootDirOffset == 0 )
  {
    if( rootDirChunk == 0 || chunkTable == 0 )
    {
      fputs( "Can't find root directory.\n", stderr );
      return 1;
    }
    useRootDirOffset = ( chunkTable + rootDirChunk )->offset;
  }
  else
  {
    useRootDirOffset = *rootDirOffset;
    rootDirChunk = xrecover_chunkFromOffset( chunkTable, maxChunk,
					     useRootDirOffset );
    if( verbose > 1 )
    {
      printf( "Root dir offset %ld ", useRootDirOffset );
      if( rootDirChunk )
      {
	printf( "corresponds to chunk %d\n", rootDirChunk );
      }
      else
      {
	puts( "does not correspond to any used chunk" );
      }
    }
  }

  if( fseek( in, useRootDirOffset, SEEK_SET ) )
  {
    fprintf( stderr, "Could not seek to root directory at %ld\n",
	     useRootDirOffset );
    return 1;
  }
  else if( verbose > 1 )
  {
    printf( "Using root directory at %ld\n", useRootDirOffset );
  }

  if( 0 == ( crossCheckTable = malloc( sizeof( xrecover_chunkTableCrossCheck )
				       * maxChunk ) ) )
  {
    fprintf( stderr, "Failed to get %d bytes of memory for crossCheckTable\n",
	     sizeof( xrecover_chunkTableCrossCheck ) * maxChunk );
    return 1;
  }

  for( loop = maxChunk; loop-- != 0; )
  {
    crossCheckTable[loop].file = NULL;
    crossCheckTable[loop].status = xrecover_untouched;

  }

  xrecover_recoverDir( in, 0, victim, "", useRootDirOffset, rootDirChunk, 0,
		       chunkTable, crossCheckTable, &fileTree, maxChunk,
		       verbose );

  /*
  xrecover_printList( stdout, chunkTable, crossCheckTable, fileTree, verbose );	   */

  problemFiles = xrecover_doGoodInList( in, suppressFiles ? 0 : victim,
					chunkTable, crossCheckTable,
					fileTree, useAlloc, verbose );


  for( loop = maxChunk; --loop > 0; )
  /* 1 rather than 0 so that we miss the chunkTable chunk */
  {
    if( chunkTable[loop].usage != FREE && loop != rootDirChunk )
    {
      if( crossCheckTable[loop].file == NULL )
      {
	unclaimedChunks++;
      }
      else
      {
	test( crossCheckTable[loop].references == 1,
	      "Chunk %d has %d references\n", loop,
	      crossCheckTable[loop].references );
      }
    }
  }

  if( verbose > 0 )
  {
    if( problemFiles == 1 )
    {
      fputs( "There is one problem file", stdout );
    }
    else
    {
      printf( "There are %d problem files", problemFiles );
    }
    printf( " and %d unclaimed chunk%s\n", unclaimedChunks,
	    ( unclaimedChunks == 1 ) ? "" : "s" );
  }


  if( problemFiles || unclaimedChunks )
  {
    /* Here goes. Let's try to match them up */
    xrecover_filenameListEntry **problemFile;
    const xFiles_chunk **unclaimedChunk, **currentChunk;

    problemFile = malloc( sizeof( xrecover_filenameListEntry *)
			  * problemFiles );
    unclaimedChunk = malloc( sizeof( xFiles_chunk ** ) * unclaimedChunks );

    if( problemFile == 0 && unclaimedChunk == 0 )
    {
      fputs( "Could not get memory to sort problem files and unclaimed "
	     "chunks\n", stderr );
    }
    else
    {
      unsigned int lastSize = UINT_MAX;
      currentChunk = unclaimedChunk;
      /* Entry can never (reasonably) be this big as there will be some bytes
       * used for the (header/chunkTable/dir) */
      for( loop = maxChunk; --loop > 0 ; )
      {
	if( chunkTable[loop].usage != FREE && loop != rootDirChunk
	    && crossCheckTable[loop].file == NULL )
	{
	  *currentChunk++ = &(chunkTable[loop]);
	}
      }

      qsort( (void *) unclaimedChunk, unclaimedChunks, sizeof( xFiles_chunk * ),
	     chunkSizeCmp );

      xrecover_getProblemList( fileTree, problemFile, problemFiles );

      qsort( problemFile, problemFiles, sizeof( xrecover_filenameListEntry * ),
	     filenameListEntryCmp );

      test( currentChunk == unclaimedChunk + unclaimedChunks,
	    "Awooga - can't count - first time there were %d unclaimed chunks, "
	    "now there are %d\n", unclaimedChunks,
	    currentChunk - unclaimedChunk );

      for( loop = problemFiles; loop-- > 0 ; )
      {
	const char *thisFile = ( (char *) &(problemFile[loop]->dirEntry) )
			       + sizeof( xFiles_dirEntry );
	const unsigned thisSize = problemFile[loop]->dirEntry.size;
	unsigned index;

	/* If this file has the same size as the previous file ignore it */
	if( thisSize == lastSize ) continue;
	/* If this file has the same size as the next file,
	   0: Make sure that the next file is ignored too
	   1: Ignore it
	 */
	if( loop > 0 && ( problemFile[loop-1]->dirEntry.size == thisSize ) )
	{
	  lastSize = thisSize;
	  if( verbose > 0 )
	  {
	    printf( "As files '%s' and '%s' have same size (%d) "
		    "cannot attempt to match to unclaimed chunks\n", thisFile,
		    ( (char *) &(problemFile[loop - 1]->dirEntry) )
		      + sizeof( xFiles_dirEntry ), thisSize );
	  }
	  continue;
	}


	while( ( currentChunk-- > unclaimedChunk )
	       && chunkTable[ index = *currentChunk - chunkTable ].size
		  > thisSize );
	/* 8-). index = *currentChunk - chunkTable; somewhere in the middle of
	 * all that. Anyway, we get here when a chunk size matches or we run out
	 * of chunks
	 */

	if( currentChunk < unclaimedChunk	/* Out of chunks */
	    || chunkTable[index].size < thisSize )
	/* index won't be used here before it is set */
	{
	  if( verbose > 0 )
	  {
	    printf( "Cannot find chunk to match file '%s' size %d\n", thisFile,
		    thisSize );
	  }
	}
	else if( currentChunk > unclaimedChunk	/* At least one more chunk */
		 && chunkTable[ *( currentChunk - 1 ) - chunkTable ].size
		    == thisSize )
	{
	  if( verbose > 0 )
	  {
	    printf( "More than one chunk to match file '%s' size %d\n",
		    thisFile, thisSize );
	  }
	  /* Skip past chunks of this size */
	  while( ( currentChunk-- > unclaimedChunk )
	       && chunkTable[ *currentChunk - chunkTable ].size == thisSize );
	}
	else
	{
	  /* !Hit!!
	   * Exactly one file of this size matched exactly one chunk.
	   * Let's brag about it on stderr
	   */
	  fprintf( stderr, "Chunk %d size %d matches file '%s' size %d\n"
		   "Will attempt recovery of this chunk as this file\n",
		   index, chunkTable[index].size, thisFile, thisSize );
	  /* Reset the flags so that a new attempt on recovery will be made */
	  problemFile[loop]->status |= xrecover_foundChunk
				       | xrecover_sizeMatches;
	  problemFile[loop]->status &= ~xrecover_started;
	  problemFile[loop]->node = index;
	  crossCheckTable[index].references = 1;
	  crossCheckTable[index].file = problemFile[loop];
	}
      }



    }

    free( problemFile );
    free( (void *) unclaimedChunk );

    /* Let's have another go */
    problemFiles = xrecover_doGoodInList( in, suppressFiles ? 0 : victim,
					  chunkTable, crossCheckTable,
					  fileTree, useAlloc, verbose );
  }

  xrecover_freeList( &fileTree );
  /* Don't need this anymore. Now go for the unclaimed chunks */

  if( victim )
  {
    for( loop = maxChunk; --loop > 0 ; )
    {
      if( chunkTable[loop].usage == FREE || loop == rootDirChunk
	  || crossCheckTable[loop].file != 0 )
	continue;

      /* Unreferenced, non-free chunk. Looks ripe for recovery */
      if( victim )
      {
	sprintf( victimGoesHere, chkdskString, chunkCount++ );
      }
      /* outNameBuffer == 0 iff victim == 0 */
      xrecover_recoverChunkChkdsk( in, outNameBuffer, loop,
				   chunkTable + loop,
				   useAlloc ? chunkTable[loop].allocSize
					    : 0,
				   dirsAsText, suppressFiles, verbose );
    }
  }

  free( crossCheckTable );
  return 0;
}

static int xrecover_RecoverFile( const char *problem, const char *victim,
				 const unsigned long *chunkTableOffset,
				 const unsigned long *rootDirOffset,
				 const xrecover_qualityLevel qualityOfJob,
				 const BOOL dirsAsText,
				 const BOOL canCreateVictim,
				 const BOOL suppressFiles, const BOOL writeFree,
				 const BOOL useAlloc, const int verbose )
{
  /* Victim == 0 to suppress all writes */
  FILE *in;
  long problemSize;
  size_t victimPathLen = victim ? strlen( victim ) + strlen( xrecover_dirSep )
				: 0;
  size_t outNameBufferLen = victim ? ( victimPathLen
				       + strlen( chkdskString ) + 1  )
				   : 0;
  char *outNameBuffer = victim ? malloc( outNameBufferLen ) : 0;
  unsigned maxChunk;
  unsigned readChunks;
  unsigned chunkLoop;
  unsigned chunkCount = 0;
  unsigned long seekTo;
  xFiles_header header;
  xFiles_chunk *chunkTable;
  xFiles_chunk rootChunk;
  const xFiles_chunk **sortedChunkTable, **currentChunk, **endSorted;
  unsigned long endLastChunk = 0;
#ifdef __riscos
  _kernel_swi_regs regs;
  _kernel_oserror *err;
  int type;
#endif


#ifdef __riscos
  if( victim )
  {
    regs.r[0] = 5;
    regs.r[1] = (int) victim;


    if( err = _kernel_swi( OS_File, &regs, &regs ), err )
    {
      fprintf( stderr, "%s\n", err->errmess );
      return 1;
    }

    type = regs.r[0];

    if( type == 0 && canCreateVictim )
    {
      if( verbose > 0 ) printf( "Creating x-file '%s'\n", victim );
      regs.r[0] = 11;
      regs.r[2] = xFiles_TYPE;
      regs.r[4] = 0;
      regs.r[5] = 0;
      if( err = _kernel_swi( OS_File, &regs, &regs ), err )
      {
	fprintf( stderr, "%s\n", err->errmess );
	return 1;
      }
    }
    else if( 0 == ( type & 2 ) )
    {
      /* Use OS File to make a message */
      regs.r[0] = 19;
      regs.r[2] = type;
      err = _kernel_swi( OS_File, &regs, &regs );
      if( !err ) err = (_kernel_oserror *) regs.r[0];
      /* Get the generated error message if no erroneous error occurred  */
      fprintf( stderr, "%s\n", err->errmess );
      return 1;
    }
  }
#endif	/* end of Risc OS specific bits */

  /* hopefully from now on is ANSI */

  if( victim )
  {
    if( 0 == outNameBuffer )
    {
      fprintf( stderr, "Failed to get %d bytes of memory for outNameBuffer\n",
	     outNameBufferLen );
      return 1;
    }
    sprintf( outNameBuffer, "%s%s", victim, xrecover_dirSep );
  }

  if( 0 == ( in = fopen( problem, "rb" ) ) )
  {
    fprintf( stderr, "Could not open file '%s'\n", problem );
    free( outNameBuffer );
    return 1;
  }

  if( fseek( in, 0, SEEK_END ) || ( -1L == ( problemSize = ftell( in ) ) ) )
  {
    fprintf( stderr, "Could not find size of file '%s'\n", problem );
    free( outNameBuffer );
    return 1;
  }

  rewind( in );

  if( 0 == fread( &header, sizeof( header ), 1, in ) )
  {
    fprintf( stderr, "Could not read header from file '%s'\n", problem );
    free( outNameBuffer );
    return 1;
  }

  test( header.sig == xFiles_SIG,
	"Missing xFiles_SIG - read %8X, should be %8X\n",
	header.sig, xFiles_SIG );
  if( verbose > 1 )
  {
    test( header.hdrSize == sizeof( xFiles_header ),
	  "Illegal hdrSize %d\n",
	  header.hdrSize );

    test( header.structureVersion == xFiles_STRUCTUREVERSION,
	  "Illegal structureVersion %d\n",
	    header.structureVersion );

    test( header.directoryVersion == xFiles_DIRECTORYVERSION,
	"Illegal directoryVersion %d\n",
	  header.directoryVersion );

  /* testChunk(&info, 0, &  header.chunkTable ); */

  test( power2( header.allocationUnit ),
	"Illegal allocationUnit %d\n", header.allocationUnit );
  }

  /*
  test( ( roundUp( &header, sizeof( header ) ) == header.chunkTable.offset ),
	"chunkTable not found 1 allocationUnit into file\n"
	"Expected %d\n"
	"Actually %d\n", roundUp( &header, sizeof( header ) ),
	header.chunkTable.offset ); */

  seekTo = chunkTableOffset ? *chunkTableOffset : header.chunkTable.offset;
  if( fseek( in, seekTo, SEEK_SET ) )
  {
    fprintf( stderr, "Could not seek to chunkTable at %ld\n", seekTo );
    free( outNameBuffer );
    return 1;
  }

   ;

  if( 0 == fread( &rootChunk, sizeof( xFiles_chunk ), 1, in ) )
  {
    fprintf( stderr,
	     "Could not read first chunk of chunkTable from file '%s'\n",
	     problem );
    free( outNameBuffer );
    return 1;
  }

  /* Derive size from header, unless chunkTable offset was specified */
  maxChunk = ( chunkTableOffset ? rootChunk.size
  				: header.chunkTable.size )
  				 / sizeof( xFiles_chunk );

  test( 0 == ( header.chunkTable.size % sizeof( xFiles_chunk ) ),
	"chunkTable size %d (from header) is not a multiple of %d\n",
	header.chunkTable.size, sizeof( xFiles_chunk ) );

  test( 0 == ( rootChunk.size % sizeof( xFiles_chunk ) ),
	"chunkTable size %d (from chunkTable) is not a multiple of %d\n",
	rootChunk.size, sizeof( xFiles_chunk ) );

  if( 0 == ( chunkTable = malloc( header.chunkTable.size ) ) )
  {
    fprintf( stderr, "Failed to get %d bytes of memory for chunkTable\n",
	     header.chunkTable.size );
    fclose( in );
    free( outNameBuffer );
    return 1;
  }
  if( 0 == ( sortedChunkTable = malloc( sizeof( xFiles_chunk ** ) * maxChunk ) )    )
  {
    fprintf( stderr, "Failed to get %d bytes of memory to sort chunkTable\n",
	     sizeof( xFiles_chunk ** ) * maxChunk );
    free( chunkTable );
    free( outNameBuffer );
    fclose( in );
    return 1;
  }

  if( verbose > 0 )
  {
    printf( "'%s' has %d chunk(s)\n", problem, maxChunk );
  }

  *chunkTable = rootChunk;
  
  readChunks = fread( chunkTable + 1, sizeof( xFiles_chunk ), maxChunk - 1,
  		      in );

  if( readChunks < maxChunk - 1 )
  {
    fprintf( stderr,
	     "Only read %d chunks out of %d allegedly in chunkTable\n"
	     "Will only deal with these %d chunkss\n",
	     readChunks + 1, maxChunk, readChunks + 1);
    maxChunk = readChunks + 1;
  }


  for( chunkLoop = maxChunk ; chunkLoop-- > 0;
       sortedChunkTable[chunkLoop] = &(chunkTable[chunkLoop]) );

  qsort( (void *) sortedChunkTable, maxChunk, sizeof( xFiles_chunk * ),
	 chunkOffsetCmp );

  if( verbose > 1 )
  {
    puts( "Chunk  Offset    Size   Usage Allocated" );
    /* Puts adds \n. Isn't the ANSI C library wonderfully consistent:
     * puts( a ) == fputs( a + '\n', stdout ) == fprintf( stdout, "%s\n", a ) */
  }

  /* OK. Go through these in offset order */
  for( currentChunk = sortedChunkTable, endSorted = sortedChunkTable + maxChunk;
       currentChunk < endSorted; currentChunk++ )
  {
    chunkLoop = *currentChunk - chunkTable;

    if( chunkTable[chunkLoop].usage == FREE )
    {
      if( verbose > 4 )
      {
	printf( " %4d %7d            FREE\n", chunkLoop,
		chunkTable[chunkLoop].offset );
      }
      test( chunkTable[chunkLoop].offset < maxChunk,
	    "Chunk %d (free) bad offset %08x\n", chunkLoop,
	    chunkTable[chunkLoop].offset );

      test( chunkTable[chunkLoop].size == FREE,
	    "Chunk %d (free) size is %08x (should be %08x)\n",
	    chunkLoop, chunkTable[chunkLoop].size, FREE );

      test( chunkTable[chunkLoop].allocSize == FREE,
	    "Chunk %d (free) allocSize is %08x (should be %08x)\n",
	    chunkLoop, chunkTable[chunkLoop].allocSize, FREE );
    }
    else
    {
      test( okOffset( &header, chunkTable[chunkLoop].offset ),
	    "Chunk %d (used) bad offset %08x\n", chunkLoop,
	    chunkTable[chunkLoop].offset );

      if( endLastChunk != -1L )
      {
	/* offset is unsigned - if it's 0 for start of file then offset can
	   *never* be less than endLastChunk */
	if( chunkTable[chunkLoop].offset < endLastChunk )
	{
	  fprintf( stderr, "Chunk %d offset %d overlaps end of previous chunk"
		   " (%d, ends at %ld)\n", chunkLoop,
		   chunkTable[chunkLoop].offset,
		   *( currentChunk - 1 ) - chunkTable, endLastChunk );
	}
	else if( chunkTable[chunkLoop].offset > endLastChunk )
	{
	  if( verbose > 1 )
	  {
	    printf( "%ld byte(s) %s between ",
	    chunkTable[chunkLoop].offset - endLastChunk,
	    endLastChunk ? "free space" : "x-file header" );

	    if( endLastChunk )
	    {
	      printf( "chunk %d", *( currentChunk - 1 ) - chunkTable );
	    }
	    else
	    {
	      fputs( "start of file", stdout );	/* puts appends '\n' */
	    }

	    printf( " and chunk %d\n", chunkLoop );

	  }
	  if( writeFree && victim )
	  {
	    sprintf( outNameBuffer + victimPathLen, chkdskString,
		     chunkCount++ );
	    xrecover_recoverArbitaryArea( in, outNameBuffer, endLastChunk,
					  chunkTable[chunkLoop].offset
					  - endLastChunk, verbose );
	    }
	}

      }

      if( verbose > 1 )
      {
	printf( " %4d %7.d %7.d %7.d %9.d\n", chunkLoop,
		chunkTable[chunkLoop].offset, chunkTable[chunkLoop].size,
		chunkTable[chunkLoop].usage, chunkTable[chunkLoop].allocSize );
      }
      test( (chunkTable[chunkLoop].size <= chunkTable[chunkLoop].allocSize ),
	    "Chunk %d (used) bad size %08x (allocSize %08x)\n",
	    chunkLoop, chunkTable[chunkLoop].size,
	    chunkTable[chunkLoop].allocSize );
      test( okOffset( &header, chunkTable[chunkLoop].allocSize ),
	    "Chunk %d (used) bad allocSize %08x\n", chunkLoop,
	    chunkTable[chunkLoop].allocSize );
      test( ( problemSize > chunkTable[chunkLoop].offset ),
	    "Chunk %d (used) offset %08x > size of '%s' (%08x)\n", chunkLoop,
	    chunkTable[chunkLoop].offset, problem, problemSize );

      endLastChunk = chunkTable[chunkLoop].offset
		     + chunkTable[chunkLoop].allocSize;

      /* Whatever the method, recover the chunktable here if useAlloc is set */
      if( qualityOfJob == xrecover_qualityChkdsk
	  || ( chunkLoop == 0 && useAlloc ) )
      {
	if( victim )
	{
	  sprintf( outNameBuffer + victimPathLen, chkdskString, chunkCount++ );
	}
	/* outNameBuffer == 0 iff victim == 0 */
	xrecover_recoverChunkChkdsk( in, outNameBuffer, chunkLoop,
				     chunkTable + chunkLoop,
				     useAlloc ? chunkTable[chunkLoop].allocSize
					      : 0,
				     dirsAsText, suppressFiles, verbose );
      }
    }
  }

  if( endLastChunk != -1L && problemSize > endLastChunk )
  {
    /* offset is unsigned - if it's 0 for start of file then offset can
       *never* be less than endLastChunk */
    if( verbose > 1 )
    {
      printf( "%ld byte(s) free space between chunk %d and end of file\n",
	      problemSize - endLastChunk, *( currentChunk - 1 ) - chunkTable );
    }

    if( writeFree && victim )
    {
      sprintf( outNameBuffer + victimPathLen, chkdskString, chunkCount++ );
      xrecover_recoverArbitaryArea( in, outNameBuffer, endLastChunk,
				    problemSize - endLastChunk, verbose );
    }
  }

  switch( qualityOfJob )
  {
    case xrecover_qualityChkdsk:
    break;	/* Done this while checking chunk table */

    case xrecover_qualityReadDirs:
    {
      xrecover_RecoverTree( in, victim, outNameBuffer,
			    outNameBuffer + victimPathLen, chunkCount,
			    &header, chunkTable, rootDirOffset,
			    maxChunk, dirsAsText, suppressFiles, useAlloc,
			    verbose );
    }
    break;

    default:
    fprintf( stderr, "Unimplemented quality %d\n", qualityOfJob );
  }
  free( sortedChunkTable );
  free( chunkTable );
  free( outNameBuffer );
  /* Some systems will memory leak if one doesn't do this */
  /* (Not freeing things is a Vaxism (and Unixism) ) */
  return 0;
}

int main( int argc, char *argv[] )
{
  int argn;
  int verbose = 0;
  const char *problem = 0;	/* aka x-file to fix */
  const char  *victim = 0;	/* aka destination 'directory' */
  BOOL dirsAsText = TRUE;
  BOOL suppressOutput = FALSE;
  BOOL canCreateVictim = FALSE;
  BOOL suppressFiles = FALSE;
  BOOL xguess = FALSE;
  BOOL useAlloc = FALSE;
  BOOL writeFree = FALSE;
  xrecover_Offset rootDir = { FALSE, 0 };
  xrecover_Offset chunkTable = { FALSE, 0 };
  xrecover_qualityLevel qualityOfJob = xrecover_qualityReadDirs;

#ifdef __riscos
#ifdef __GNUC__
  __uname_control |= __UNAME_NO_PROCESS;
#endif
#endif
  for( argn = 1; ( argn < argc ) && ( *argv[argn] == '-' ); argn++ )
  {
    const char *current = argv[argn] + 1;
    if( *current == '-' )
    {
      argn++;
      break;	/* Closet goto out of the for loop */
      /* -- marks end of options  la Unix */
    }
    while( current && *current )
    {
      switch( tolower( *current ) )
      {
	case 'a':
	  useAlloc = TRUE;
	  break;
#ifdef __riscos
	case 'c':
	  canCreateVictim = TRUE;
	  break;
#endif
	case 'd':
	  dirsAsText = FALSE;
	  break;
	case 'f':
	  writeFree = TRUE;
	  break;
	case 'g':
	  xguess = TRUE;
	case 'n':
	  suppressOutput = TRUE;
	  break;
	case 's':
	  suppressFiles = TRUE;
	  break;
	case 'v':
	  verbose += 1;
	  break;

	case 'r':
	  if( *(current + 1) )
	  {
	    if( FALSE == xrecover_ReadOffset( &rootDir, current + 1 ) )
	    {
	      argn = argc + 1;
	    }
	  }
	  else
	  {
	    argn = xrecover_ReadOffset( &rootDir, argv[argn+1] ) ? argn + 1
								 : argc + 1;
	  }
	  current = 0; current--;
	  /* Whatever happens, skip on to next argv */
	  break;
	/* If TRUE, value is successfully read, so skip over value
	 * Else argn = argc will break out of this loop
	 * AND trigger syntax message */
	case 't':
	  if( *(current + 1) )
	  {
	    if( FALSE == xrecover_ReadOffset( &chunkTable, current + 1 ) )
	    {
	      argn = argc + 1;
	    }
	  }
	  else
	  {
	    argn = xrecover_ReadOffset( &chunkTable, argv[argn+1] ) ? argn + 1
								    : argc + 1;
	  }
	  current = 0; current--;
	  /* Whatever happens, skip on to next argv */
	  break;

	case '0':
	  qualityOfJob= xrecover_qualityEvery1024;
	  break;
	case '1':
	  qualityOfJob= xrecover_qualityChkdsk;
	  break;
	case '2':
	  qualityOfJob= xrecover_qualityReadDirs;
	  break;

	case 'm':
	  if( *(current + 1) == '$' )
	  {
	    chkdskString = chkdskStringMS;
	    current++;
	    break;
	  } /* Else fall through to this: */
	default:
	  fprintf( stderr, "Ignoring unknown option '%c'\n", *current );
	  break;
      }
      current++;
    }	/* while( current && *current ) */
  }

  if( argn < argc )
  {
    problem = argv[argn++];
  }
  if( argn < argc )
  {
    victim = argv[argn++];
  }

  if( ( ( victim == 0 ) && !suppressOutput ) || ( argn != argc ) )
  {
    /* When is Acorn's F***ing Run time going to do tabs properly? */
    /* Use gcc - free software always offers better value for money */
    fputs( "Syntax:\tx-recover [options] <x-file> <dest-dir>\n"
	   "\t\t-v for verbose info (more v's for more verbosity!)\n"
#ifdef __riscos
	   "\t\t-c to create dest-dir as an x-file if necessary\n"
#endif
	   "\t\t-d To output directories in raw binary form\n"
	   "\t\t-g To scan file to find the chunk table and root directory\n"
	   "\t\t   (implies -n)\n"
	   "\t\t-n To suppress all disc output (still report integrity checks)"
	   "\n"
	   "\t\t-r <offset> to specify the offset of the root directory\n"
	   "\t\t-s To suppress all file output (but create directory tree)\n"
	   "\t\t-t <offset> to specify the offset of the chunkTable\n"
	   "\t\t-a To round up all file sizes to the size of the chunk\n"
	   "\t\t-f To write out all free space between chunks as files\n"
/*	   "\t\t-e To round up the x-file to the size allocated on disc\n" */
/* nasty filing system fills it with zeros, so not much point. I don't think
 * ADFS did this on Risc OS 2 (Network filing systems did, being security
 * concious */
/*	   "\t\t-0 Attempt to split chunks ignoring chunk table\n" */
	   "\t\t-1 Output all used chunks as files (needs intact chunk table)\n"
	   "\t\t-2 Attempt to traverse the directory tree and recreate the \n"
	   "\t\t   x-file (default method)\n",
	   stderr );
    return 1;
  }

  if( xguess ) return xrecover_xguess( problem );

  if( verbose > 0 )
  {
    if( victim )
    {
      printf( "%s -> %s (v=%d)\n", problem, victim, verbose );
    }
    else
    {
      printf( "%s [suppress output] (v=%d)\n", problem, verbose );
    }
    if( rootDir.valid )
    {
      printf( "Read root directory from file offset %ld\n", rootDir.offset );
    }
    if( chunkTable.valid )
    {
      printf( "Read chunk table from file offset %ld\n", chunkTable.offset );
    }
  }
  return xrecover_RecoverFile( problem, suppressOutput ? 0 : victim,
			       chunkTable.valid ? &(chunkTable.offset) : 0,
			       rootDir.valid ? &(rootDir.offset) : 0,
			       qualityOfJob, dirsAsText,
			       canCreateVictim, suppressFiles, writeFree,
			       useAlloc, verbose );
}


