home *** CD-ROM | disk | FTP | other *** search
Text File | 1997-01-13 | 68.5 KB | 2,404 lines |
- /* x-recover.c 0·11
- * Based (partly) on x-check.c by Andy Armstrong
- * Needs chunks.h from the X-files distribution
- *
- * © Nicholas Clark 1996/1997
- *
- * Version history:
- *
- * 0·01 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-)
- *
- * 0·02 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))
- *
- * 0·03 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.
- *
- * 0·04 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.]
- *
- * 0·05 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
- *
- * 0·06 02/01/97
- * Merged x-guess as option -g
- * Added déjà 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?
- *
- * 0·07 03/01/97
- * Added -a and -f flags. (and implemented them!)
- *
- * 0·08 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 ( déjà vu flag and sprintf to a possible NULL pointer )
- * See below for our policy on bugs.
- *
- * 0·09 07/01/97
- * Fixed design - now get the number of chunks from chunktable chunk 0 rather
- * than the header if a chunktable offset is specified
- *
- * 0·10 08/01/97
- * The 'spec' may say that dirHashes are padded to the word boundary with zeros,
- * but it doesn't say anything about the full leafname. Whoops.
- * Method 3 remains to be implemented...
- *
- * 0·11 13/01/97
- * What was that about bugs? printChunkDir() now realises that a directory in
- * chunk 'zero' isn't actually in a known chunk.
- * The syntax message kicks in if you forgot to supply a 'problem'
- * (I was getting to clever with all the stuff to cope with -t1024 -t 1024 )
- * Fixed the malloc for the chunkTable to use the size we are going to read
- * (see version 0·09 comment) rather than the size in the header. If we read
- * more than the header... ***boom***
- *
- *
- * 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 100Kb, 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 déjà 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, ®s, ®s ), 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, ®s, ®s );
- }
- #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 )
- {
- if( chunkNum )
- {
- fprintf( where, "Chunk %d ", chunkNum );
- }
- else
- {
- /* Chunk 0 is the chunkTable. Therefore it can't be a directory. So this
- must be a directory referenced by file offset, and we don't know it's
- chunk */
- fputs( "Chunk ? ", where );
- }
-
- 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 */
- if( (*aardvark)-> usage == FREE ) return -1;
- return (*zygote)-> 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( "Déjà 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( *(int *) &header != xFiles_DIRSIG )
- {
- printChunkDir( stderr, chunkNum, pathToHere );
- fprintf( stderr, "Missing xFiles_DIRSIG - read %8X, should be %8X\n",
- *(int *) &header, xFiles_DIRSIG );
- }
-
- 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=%u used=%u\n", header.size, header.used );
- }
-
- /* Whoops - they are unsigned. Can't compare. Changes %d to %u to remind me */
- /*if( header.used < 0 || header.size < 0 )
- {
- printChunkDir( stderr, chunkNum, pathToHere );
- fprintf( stderr, "size of directory (%d) and used entries (%d) should both "
- "be positive\n", header.size, header.used );
- }
- else*/ if( header.used > header.size )
- {
- printChunkDir( stderr, chunkNum, pathToHere );
- fprintf( stderr, "size of directory (%u) < used entries (%u)\n",
- header.size, header.used );
- }
-
- if( 0 == ( dirHash = malloc( sizeof( xFiles_dirHash ) * header.used ) ) )
- {
- printChunkDir( stderr, chunkNum, pathToHere );
- fprintf( stderr, "failed to get %u 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 0·56 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 %u bytes of filename (got %u)\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;
- /* Check hash == name */
- if( currentDirHash->nameStart[0] )
- {
- if( !( currentDirHash->nameStart[0] == buffer.file.name[0]
- && currentDirHash->nameStart[1] == buffer.file.name[1]
- && ( currentDirHash->nameStart[1] == 0
- || ( currentDirHash->nameStart[2] == buffer.file.name[2]
- && ( currentDirHash->nameStart[2] == 0
- || ( currentDirHash->nameStart[3] == buffer.file.name[3]
- ) ) ) ) ) )
- {
- /* Would it be faster to use *int compares if len > 3 and strcmp()
- * where len < 4 ? */
- 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] );
- }
- /* else it matches */
- }
- else
- {
- printChunkDirEntry( stderr, chunkNum, pathToHere, dirEntry,
- buffer.file.name, 0 );
- fprintf( stderr, "has illegal empty 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 %u 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 = ¤t;
- 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 %u bytes (got %u)\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 %u 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 %u bytes (got %u)\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 %u 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 %u bytes (got %u)\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, ®s, ®s );
- 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 %u 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, ®s, ®s ), 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, ®s, ®s ), 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, ®s, ®s );
- 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 %u 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( sizeof( xFiles_chunk ) * maxChunk ) ) )
- {
- fprintf( stderr, "Failed to get %u bytes of memory for chunkTable\n",
- sizeof( xFiles_chunk ) * maxChunk );
- fclose( in );
- free( outNameBuffer );
- return 1;
- }
- if( 0 == ( sortedChunkTable = malloc( sizeof( xFiles_chunk * ) * maxChunk ) ) )
- {
- fprintf( stderr, "Failed to get %u 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 %u 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 && !suppressFiles) )
- /* I alledge that this is not a special case. Well it is ½ a special
- * case. The useAlloc *is* but the !suppressFiles is cosmetic.
- * If it were not there, one gets file0000 as the header, and file0002
- * (if necessary) as the free space at the end of 'problem'
- */
- {
- 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( !problem || ( ( 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 );
- }
-
-
-