[net.micro.amiga] Disk Salvage Program

daveh@cbmvax.UUCP (04/30/86)

The following is a program I've written to recover files from a trashed
AmigaDOS disk.  It requires the Lattice C compiler to compile, I haven't
tried it with any other.  It also requires two drives to do the actual
recovery. This program is designed to let the user salvage all or parts
of a damaged AmigaDOS disk.  It is used similarly to the standard AmigaDOS
DISKCOPY or COPY programs, with the format:

		DISKSALV dfN: [TO] dfM:

Where the source disk is the corrupted disk, the target disk is	a formatted
AmigaDOS disk, usually (but not necessarily) empty.  This command currently
takes two drives; i.e. it won't salvage df0: to df0: or to RAM:.  This 
program salvages the bad disk by by-passing the normal disk root directory, 
which may be corrupted (and as well is probably the most likely cause of a 
complete disk being rejected by AmigaDOS).  It performs a scan to collect
all directories and their associated files, building an internal dynamic
image of the bad disk.  Then each directory found is created through normal
DOS calls, then the files belonging to that directory are placed under it
via block copies from bad disk to good disk.  When creating a new directory,
the program will check for the prior existance of such a directory.  If it
already exists, the intended name will be modified slightly.  After 10 of
these attempted name changes, the program will abort the attempt to write
out the directory.  This allows a non-empty disk to easily be used for the
output disk without having to worry about overwriting directories currently
existing on the output disk.

This copy routine is smart, and will attempt to resolve any corrupted file
links using the redundant information in AmigaDOS.  Every AmigaDOS file has
a header containing a list of block pointers, and a first block pointer.  
This first pointer is a link to the first 512 byte data block in the file.
This first link is also echoed in the aforementioned list of block pointers.
Each subsequent data block has a link to the next block which as well can
be verified by the block pointer list.  Data blocks also contain sequence
numbers, indicating their logical sequence in the file, and backpointers to
the file header block.  These last two items are constantly checked, and
are very good in helping to resolve any next block conflicts that might
arise.  The copy routine can resolve as well missing blocks and circularly
linked files reasonably well.

The process continues until no more files/directories exist.  There is no
necessary correlation between the physical location of files on the bad
disk and the physical location of the corresponding files on the newly
created disk.  Once all linkable files are restored, a directory of
existing but not yet used data blocks is created, in an effort to preserve
anything left, which might be manually re-created for a file that had its
file header badly destroyed.  These will appear in a uniquely named
SPARE-BLOCKS directory.  Each file in that directory will have a name
indicating the block's block number, parent block number, and sequence
number.  Any program source or other ASCII text can be easily put together
by hand by following the proper sequence among files of similar parentage
if desired, either in an editor or via the Join command.  Any binary files
may be re-assembled similarly, but unless all of the blocks in the file are
available, the reassembled file will generally be useless.

This program will also copy a good disk, though inefficiently as compared
to a straight Copy command.  Also, this program recovers files that have
been deleted but not yet written over.  Thus, it can be used to recover
deleted (but not overwritten) files.  If the output disk gets filled up
completely at any time, the program will pause you and request another
disk for the output disk.  Actually, the first time the disk becomes full,
the system will pop up with a requestor box indicating that the disk has
become full.  Click the "CANCEL" box here and a prompt on the CLI comes up
requesting a change of disks.  It will then recreate the currently active 
directory path, and the file that it was working on before the swap took
place.  Everything from this point on proceeds as normal.

					-Dave Haynie
					 April 1986


	"I gained nothing at all from Supreme Enlightenment, and
	 for that very reason it is called Supreme Enlightenment."

					-Gotama Buddha

	"Let me control a planet's oxygen supply and I don't care
	 who makes the laws."

					-Great Cthulhu's Starry Wisdom Band

      -------------------------CUT HERE-------------------------

/* ====================================================================== */

/* 				  DiskSalv

			AmigaDOS Disk Salvage Program
				    by
				Dave Haynie

			      (c) April 1986

	May be copied magnetically or electronically without the permission
	of the author, providing no charge is requested	for the program.  The
	only fees permitted are a moderate copying charge, in the case of 
	magnetic copying, or any normal BBS charges, in the case of an 
	electronic download.  Any other uses may be negotiated by contacting
	the author.  This program is copyrighted; portions of this program
	source may be used as is in other programs with no necessary
	acknowledgement, though acknowledgements are always appreciated.

	If you are very pleased with this program or what you learn from
	the source code, please consider sending no more than $10.00 to
	the author.  In addition, any bug reports would be greatly
	appreciated.  All correspondence may be sent to:

		Dave Haynie
		645 Allen Avenue
		Gibbstown, NJ 08027

		Usenet:		{allegra,caip,seismo}!cbmvax!daveh
		Compuserve:	76703,2047
		QuantumLink:	Animal128

	These addresses are current as of this writing, most are likely to
	remain so for awhile.
	
	ANYWAY, this program is designed to let the user salvage all or parts
	of a damaged AmigaDOS disk.  It is used similarly to DISKCOPY or 
	COPY, with the format:

		DISKSALV dfN: [TO] dfM:

	Where the source disk is the corrupted disk, the target disk is
	a formatted AmigaDOS disk, usually empty.  This command 
	currently takes two drives; i.e. it won't salvage df0: to df0:.

	This program salvages the bad disk by by-passing the normal
	disk root directory, which may be corrupted (and as well is
	probably the most likely cause of a disk being rejected by
	AmigaDOS).  It performs a scan to collect all directories and
	their associated files, in pointer form.  Then the directories
	are created one-by-one through normal DOS calls, then the files
	are read and placed in the directories.  There is no necessary 
	correlation between the physical location of files on the bad
	disk and the physical location of the corresponding files on
	the newly created disk.  Once all linkable files are restored,
        a directory of existing but unused data blocks is created, in an
	effort to preserve anything left, which might be manually 
	re-created.  This program will also copy a good	disk, though 
	inefficiently as compared to a straight DiskCopy.  The program
	will prompt for a new disk if it is having trouble writing to
	the given recovery disk at any time.
*/

#include <exec/types.h>
#include <exec/exec.h>
#include <devices/trackdisk.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>
#include <lattice/ctype.h>
#include <lattice/stdio.h>

/* ====================================================================== */

/* Global definitions. */

char prog_version[] = "1.0";

#define BLOCKSIZE	TD_SECTOR
#define WORDSIZE	sizeof(ULONG)
#define DISK_SIZE	(NUMHEADS*NUMCYLS*NUMSECS)
#define SIZE		(BLOCKSIZE / WORDSIZE)
#define LIST_SIZE	(SIZE - (50+6))
#define READOK		0
#define BSTR_MAX	31
#define PATH_MAX	255

#define FT_SHORT_FILE	2		/* Short file type */
#define FT_DATA_BLOCK	8		/* Data block type */
#define ST_FILE		0xfffffffd	/* File subtype */
#define ST_USER_DIR	2		/* User directory subtype */

struct IOExtTD *diskreq = NULL;		/* Pointer for extended disk commands */
struct MsgPort *diskport = NULL;	/* Port for trackdisk replies */

LONG diskChangeCount = 0;		/* Checks for changed disk */
int device = 1;				/* Default disk device number */

/* ====================================================================== */

/* Buffer access types.  Each of these types is a template of BLOCKSIZE
   used to access a TRACKDISK data block's individual elements.  There are
   several types of these data blocks, "root", the disk's root directory,
   "directory", a user defined directory block, "file", a file header
   block, "extens", a file extension block, and "data", a data block
   (the real meat of what's out there on disk.  I also define a "generic"
   block, which contains most of the things that you'd like to look at
   in a block who's fundamental and secondary types haven't yet been
   determined.  */

/* This block type contains the elements that are found in most blocks
   and usually required to identify the type of the block.  Called 
   as .Gen in the block union. */

struct generic_block {
   ULONG blk_type, blk_key, blk_seq, blk_size, blk_link, blk_checksum;
   UBYTE blk_data[BLOCKSIZE - 9*WORDSIZE];
   ULONG blk_parent, blk_extension, blk_subtype;
};

/* This is the block that's used to store the actual data on the disk. Its
   accesses as .Dat in the block union. */

struct data_block {
   ULONG blk_type, blk_key, blk_seq, blk_size, blk_next, blk_checksum;
   UBYTE blk_data[BLOCKSIZE-6*WORDSIZE];
};

/* This block is used as the root directory for the entire disk.  Its
   accessed as .Rot in the block union. */
      
struct root_block {
   ULONG blk_type, blk_key, blk_seq, blk_size, blk_runused, blk_checksum;
   ULONG blk_hashtable[LIST_SIZE];
   ULONG blk_bmflag, blk_bitmap[49-23];
   ULONG blk_days, blk_mins, blk_ticks;
   UBYTE blk_name[(20-7)*WORDSIZE];
   ULONG blk_createdays, blk_createmins, blk_createticks;
   ULONG blk_nexthash, blk_parent, blk_extension, blk_subtype;
};

/* This block is a user directory block.  It is accessed as .Dir in the
   block union. */

struct directory_block {
   ULONG blk_type, blk_key, blk_seq, blk_dunused1[2], blk_checksum;
   ULONG blk_hashtable[LIST_SIZE];
   ULONG blk_spare, blk_protect, blk_dunused2;
   UBYTE blk_comment[(47-23)*WORDSIZE];
   ULONG blk_days, blk_mins, blk_ticks;
   UBYTE blk_name[(20-4)*WORDSIZE];
   ULONG blk_nexthash, blk_parent, blk_extension, blk_subtype;
};

/* This block stores a file header, which contains the information used to
   link together the individual elements of a file.  Its accessed as .Fil
   in the block union. */

struct file_block {
   ULONG blk_type, blk_key, blk_seq, blk_size, blk_firstdata, blk_checksum;
   ULONG blk_datalist[LIST_SIZE];
   ULONG blk_spare, blk_protect, blk_bytesize;
   UBYTE blk_comment[(47-23)*WORDSIZE];
   ULONG blk_days, blk_mins, blk_ticks;
   UBYTE blk_name[(20-4)*WORDSIZE];
   ULONG blk_nexthash, blk_parent, blk_extension, blk_subtype;
};

/* This block stores a file extension block, which is only used if the file 
   is so long that the main file header doesn't fil all of the data links it 
   will need.  The file header can currently store data for (BLOCKSIZE-
   WORDSIZE*(50+6)) = 72 data blocks, or (72*(BLOCKSIZE-6*WORDSIZE)) = 35,136 
   bytes.  It is accessed as .Ext in the block union */

struct extens_block {
   ULONG blk_type, blk_key, blk_seq, blk_size, blk_firstdata, blk_checksum;
   ULONG blk_datalist[LIST_SIZE];
   UBYTE blk_eunused[(50-4)*WORDSIZE];
   ULONG blk_nexthash, blk_parent, blk_extension, blk_subtype;
};

/* And this packs them all together */

union block {
   struct generic_block Gen;
   struct data_block Dat;
   struct root_block Rot;
   struct directory_block Dir;
   struct file_block Fil;
   struct extens_block Ext;
};

union block *buf = NULL;		/* Global disk buffer pointer */
struct data_block *data = NULL;		/* Global disk data block */
struct extens_block *extn = NULL;	/* Global extension block */

/* ====================================================================== */

/* This section contains structures used to store located directory names
   and pointers to the files to be restored below them. */

struct d_tree {
   struct d_tree *d_next;	/* Next Directory on same level */
   struct d_tree *d_parent;	/* Directory on upper level */
   struct d_tree *d_child;	/* First Directory on lower level */
   char d_name[BSTR_MAX+1];	/* Directory Name */
   int d_flags;			/* Directory build flags */
   ULONG d_point;		/* Directory Disk Pointer */
   struct f_list *d_list;	/* Files in directory */
};

struct f_list {
   struct f_list *f_next;	/* Next file in directory */
   ULONG f_file;		/* Disk location of file header */
};

struct d_tree *root = NULL;	/* Global directory root */

/* ====================================================================== */

/* This section defines the bit-maps. One will speed up the system, making
   it read error blocks only once.  The other keeps track of used directory
   blocks. */

UBYTE usdmap[DISK_SIZE/8];	/* Used directory blocks. */
UBYTE badmap[DISK_SIZE/8];	/* Bad directory blocks. */

/* This function zeros out a given bitmap. */

VOID zeromap(map)
UBYTE map[];
{
   int i;

   for (i=0; i < DISK_SIZE/8; i++) map[i] = 0;
}

/* This function returns TRUE if the given block number is marked
   in the given bit-map. */

BOOL checkmap(map,num)
UBYTE map[];
ULONG num;
{
  return (BOOL) ((map[num>>3] & (1 << num%8)) != 0);
}

/* This function sets the given block number in the bit map. */

VOID setmap(map,num)
UBYTE map[];
ULONG num;
{
   map[num>>3] |= (1 << num%8);
}

/* ====================================================================== */

/* This section contains basic functions. */

/* This function converts a BSTR to a normal "C" string, returning that
   C string as its value.  The passed C string pointer is assumed to
   contains enough space to store the returned C string. */

char *bstr_to_cstr(cstr,bstr)
char *cstr;
UBYTE bstr[];
{
   UBYTE size,i;
   char *ptr;

   ptr = cstr;
   size = (bstr[0] <= BSTR_MAX) ? (bstr[0]) : (BSTR_MAX);
   for (i=1;i <= size;i++) *ptr++ = bstr[i];
   *ptr = '\0';
   return cstr;
}

/* This function converts the given directory pointer into a string
   path name, returning that path name.  The "str" pointer is assumed
   to point to a long enough string to hold the entire path name. */

char *strpath(str,dir)
char *str;
struct d_tree *dir;
{
   if (dir == NULL) return (char *) strcpy(str,"");
   if (dir == root) return (char *) strcpy(str,":");
   if (dir->d_parent != NULL) strpath(str,dir->d_parent);
   if (strlen(str) != 1) strcat(str,"/");
   return (char *) strcat(str,dir->d_name);
}

/* This function creates an extended IO request port. */

struct IORequest *CreateExtIO(ioReplyPort,size)
struct MsgPort *ioReplyPort;
LONG size;
{
   struct IORequest *ioReq;

   if (ioReplyPort == NULL) return ((struct IORequest *) NULL);
   ioReq = (struct IORequest *) AllocMem(size, MEMF_CLEAR | MEMF_PUBLIC);
   if (ioReq == NULL) return ((struct IORequest *) NULL);

   ioReq->io_Message.mn_Node.ln_Type = NT_MESSAGE;
   ioReq->io_Message.mn_Node.ln_Pri = 0;
   ioReq->io_Message.mn_ReplyPort = ioReplyPort;
   return (ioReq);
}

/* This function deletes an extended IO request port. */

DeleteExtIO(ioExt,size)
struct IORequest *ioExt;
LONG size;
{
   ioExt->io_Message.mn_Node.ln_Type = 0xff;
   ioExt->io_Device = (struct Device *) -1;
   ioExt->io_Unit = (struct Unit *) -1;
   FreeMem(ioExt,size);
}

/* ====================================================================== */

/* This section contains basic disk control functions. */

/* This function turns off the motor of the selected disk drive. */

MotorOff()
{
   diskreq->iotd_Req.io_Length = 0;
   diskreq->iotd_Req.io_Command = TD_MOTOR;
   DoIO(diskreq);
   return(0);
}

/* This function initializes the selected disk device. */

InitDisk()
{
   diskreq->iotd_Req.io_Command = TD_CHANGENUM;
   DoIO(diskreq);
   diskChangeCount = diskreq->iotd_Req.io_Actual;
}

/* This function fills the buffer "buf" with the contents of the given
   sector number (0-17xx).  Returns 0 if there's no error.  It uses the
   bit map functions to set bit flags if the sector contains a hard
   error.  This cuts down greatly on time, due to the fact that blocks 
   with hard errors take longer to read (due to retrys). */

BYTE ReadBuf(buf,sect)
union block *buf;
ULONG sect;
{
   if (sect == 0) return 1;
   if (checkmap(badmap,sect)) return 1;
   diskreq->iotd_Req.io_Length = BLOCKSIZE;
   diskreq->iotd_Req.io_Data = (APTR) buf;
   diskreq->iotd_Req.io_Command = ETD_READ;
   diskreq->iotd_Count = diskChangeCount;
   diskreq->iotd_Req.io_Offset = TD_SECTOR * sect;
   DoIO(diskreq);
   if (diskreq->iotd_Req.io_Error != 0) {
      setmap(badmap,sect);
      setmap(usdmap,sect);
   }      
   return (diskreq->iotd_Req.io_Error);
}

/* ====================================================================== */

/* This section contains functions that operate on the directory tree
   and file list structures. */

/* This function accepts a disk pointer for a file list link, which
   it will create and initialize. */

struct f_list *Alloc_f_list(point)
ULONG point;
{
   struct f_list *temp;

   temp = (struct f_list *) AllocMem(sizeof(struct f_list),0);
   temp->f_file = point;
   temp->f_next = NULL;
   return temp;
}

/* This function will deallocate a complete file pointer list. */

VOID Free_f_list(list)
struct f_list *list;
{
   if (list == NULL) return;
   if (list->f_next != NULL) Free_f_list(list->f_next);
   FreeMem(list,sizeof(struct f_list));
}

/* This function accepts a BSTR pointer, for which it will create a 
   directory entry and initialize the name string from the BSTR.  */

struct d_tree *Alloc_d_tree(point,bstr)
ULONG point;
UBYTE bstr[];
{
   struct d_tree *newtree;

   newtree = (struct d_tree *) AllocMem(sizeof(struct d_tree),0);
   newtree->d_flags = 0;
   newtree->d_next = NULL;
   newtree->d_parent = NULL;
   newtree->d_child = NULL;
   newtree->d_point = point;
   newtree->d_list = NULL;
   bstr_to_cstr(newtree->d_name,bstr);
   return newtree;
}

/* This function deallocates an entire d_tree. */

Free_d_tree(tree)
struct d_tree *tree;
{
   if (tree->d_next != NULL) Free_d_tree(tree->d_next);
   if (tree->d_child != NULL) Free_d_tree(tree->d_child);
   if (tree->d_list != NULL) Free_f_list(tree->d_list);
   FreeMem(tree, sizeof(struct d_tree));
}

/* This function returns either the tree containing 'point' within 'tree',
   or NULL if 'point' does not yet exist. */

struct d_tree *Find_d_tree(tree,point)
struct d_tree *tree;
ULONG point;
{
   struct d_tree *temp;

   if (tree->d_point == point) return tree;
   if (tree->d_next != NULL) 
      if ((temp = Find_d_tree(tree->d_next,point)) != NULL) return temp;
   if (tree->d_child != NULL)
      if ((temp = Find_d_tree(tree->d_child,point)) != NULL) return temp;
   return NULL;
}

/* This function adds the given tree to the child list of the given
   parent tree. */

Add_Next_d_tree(parent, child)
struct d_tree *parent, *child;
{
   struct d_tree *temp;

   temp = parent->d_child;
   parent->d_child = child;
   child->d_next = temp;
   child->d_parent = parent;
}

/* This function will seek out a file, returning a pointer to its link
   structure if available, NULL otherwise.  It looks for the file in the
   given directory structure, and only at that level. */

struct f_list *Find_f_list(tree,point)
struct d_tree *tree;
ULONG point;
{
   struct f_list *pnt;

   for(pnt = tree->d_list; pnt != NULL; pnt = pnt->f_next)
      if (pnt->f_file == point) break;
   return pnt;
}

/* This function links the file list "list" into the given tree's file
   list. */

Add_Next_f_list(tree,list)
struct d_tree *tree;
struct f_list *list;
{
   struct f_list *temp;

   temp = tree->d_list;
   tree->d_list = list;
   list->f_next = temp;
}

/* ====================================================================== */

/* This function accepts a disk pointer, which is assumed to be the disk
   address of a directory structure that doesn't yet exist.  This function
   will return a pointer to that directory after finding it on disk and
   linking it into the directory tree.  If the directory can't be
   found on disk it will return the root directory.  The effect of this
   function is a recursive lookahead up the path of the requested 
   directory, which is the initial scanning process that moves the disk head
   in a non-linear fashion. */

struct d_tree *MakeParent(dp)
ULONG dp;
{
   union block *tbuf;		/* Temporary Disk Buffer */
   struct d_tree *tree;		/* Requested directory */
   struct d_tree *dad;		/* Its parent directory */

   tbuf = (union block *) AllocMem(BLOCKSIZE,MEMF_CHIP);
   if (ReadBuf(tbuf,dp) != READOK) {
      FreeMem(tbuf,BLOCKSIZE);
      return root;
   }
   if (tbuf->Gen.blk_type != FT_SHORT_FILE 
      || tbuf->Gen.blk_subtype != ST_USER_DIR) {
      FreeMem(tbuf,BLOCKSIZE);
      return root;
   }
   if (tbuf->Dir.blk_name[0] == 0 || tbuf->Dir.blk_name[1] == 0) {
      FreeMem(tbuf,BLOCKSIZE);
      return root;
   }
   if ((dad = Find_d_tree(root,tbuf->Dir.blk_parent)) == NULL)
      dad = MakeParent(tbuf->Dir.blk_parent);
   tree = Alloc_d_tree(dp,tbuf->Dir.blk_name);
   Add_Next_d_tree(dad,tree);
   FreeMem(tbuf,BLOCKSIZE);
   return tree;
}

/* This function builds the directory list.  The top node is kind of a
   default place, where any file who's directory is corrupted, as well 
   as any files directly in the ROOT node, will be placed.  This routine
   uses the global disk buffer "buf", so exercise caution when making
   any modifications not to recursively overwrite this buffer.  This 
   routine will tag any non-data block as "used" in the "usdmap" bitmap.
   Only actual data blocks remain untagged beyond this routine. */

BuildDirTree()
{
   ULONG sect;				/* Current block */
   struct d_tree *dir, *sub;		/* Working directory trees */
   struct f_list *fil;			/* Working file list */
   char str[PATH_MAX];			/* Temporary string */

   for (sect = 1; sect < DISK_SIZE; sect++) {
      if (ReadBuf(buf,sect) != READOK) {
         printf(" \233\113\2337mBlock %4d (ERROR)\2330m\n",sect);
         continue;
      }
      switch (buf->Gen.blk_type) {		/* Primary Type */
      case FT_SHORT_FILE : 
         setmap(usdmap,sect);
         switch (buf->Gen.blk_subtype) {		/* Secondary Type */
         case ST_FILE :
            printf(" \233\113Block %4d (FILE) Name \"",sect);
            if ((dir = Find_d_tree(root,buf->Fil.blk_parent)) == NULL)
               dir = MakeParent(buf->Fil.blk_parent);
            printf("%s",strpath(str,dir));
            if (str[strlen(str)-1] != ':') printf("/");
            printf("%s",bstr_to_cstr(str,buf->Fil.blk_name));
            if (sect != buf->Fil.blk_key) printf("\" Block Key Mismatch\n");
            else printf("\"\n");
            if (Find_f_list(dir,sect) != NULL) break;
            fil = Alloc_f_list(sect);
            Add_Next_f_list(dir,fil);
            break;
         case ST_USER_DIR : 
            printf(" \233\113Block %4d (DIR)  Name \"",sect);
            if ((dir = Find_d_tree(root,buf->Dir.blk_parent)) == NULL)
               dir = MakeParent(buf->Dir.blk_parent);
            if ((sub = Find_d_tree(dir,sect)) != NULL) {
               printf("%s\n",strpath(str,sub)); break;
            }
            sub = Alloc_d_tree(sect,buf->Dir.blk_name);
            Add_Next_d_tree(dir,sub);
            printf("%s",strpath(str,sub));
            if (sect != buf->Dir.blk_key) printf("\" Block Key Mismatch\n");
            else printf("\"\n");
            break;
         default : 
            printf(" \233\113Block %4d (???)\n\233\101",sect);
         }
         break;
      case FT_DATA_BLOCK :
         if (buf->Dat.blk_size == 0) setmap(usdmap,sect);
         printf(" \233\113Block %4d (DATA)\n\233\101",sect); break;
      default :
         setmap(usdmap,sect);
         printf(" \233\113Block %4d (UNUSED)\n\233\101",sect); break;
      }
   }
}

/* ====================================================================== */

/* This section contains routines concerned with actually restoring the
   file structure from the internal directory path structure. */

/* This function resolves the order of likelihood of the two normal
   block links, returning as functional value the best guess.  It will 
   each check block on disk, and rule it out if either it specifies a 
   disk location out of range or if its place on the disk is completely
   destroyed.  There are two guesses for any unresolved block address, one 
   for the data block link pointer, one for the file block header key 
   pointer.  Its only necessary to call this routine if the two values are 
   different, i.e., at least one of the pointers has been messed up.  This
   now also checks the "used" bit-map to avoid repeated/circular files being
   created. */

ULONG FindPoints(g1,g2,seq,head)
ULONG g1,g2,seq,head;
{
   struct data_block *blk;
   UBYTE q1=0,q2=0;

   printf("   \2337mResolving link conflict %d <-> %d\2330m\n",g1,g2);
   if (g1 > DISK_SIZE) g1 = 0; if (g2 > DISK_SIZE) g2 = 0;
   if (checkmap(usdmap,g1)) g1=0;
   if (checkmap(usdmap,g2)) g2=0;
   if (g1 == 0) return g2; if (g2 == 0) return g1;
   blk = (struct data_block *) AllocMem(BLOCKSIZE,MEMF_CHIP);
   if (ReadBuf(blk,g1) == READOK)
      q1 = TRUE + (blk->blk_seq == seq) + (blk->blk_key == head) +
           (blk->blk_type == FT_DATA_BLOCK);
   if (ReadBuf(blk,g2) == READOK)
      q2 = TRUE + (blk->blk_seq == seq) + (blk->blk_key == head) +
           (blk->blk_type == FT_DATA_BLOCK);
   FreeMem(blk,BLOCKSIZE);
   if (q1 == 0) g1 = 0; if (q2 == 0) g2 = 0;
   if (q1 > q2) return g1; else return g2;
}

/* This function returns the next available file header data link
   pointer.  If the current file header is exhausted it will open
   up an extension block based on the "eptr" disk pointer. */

BOOL NewPoint(ptr,list,eptr)
ULONG *ptr, **list, *eptr;
{
   (*ptr)--;
   if (*ptr < 0) {
      if (ReadBuf(extn,*eptr) != READOK) {
         printf("  \2337mERROR: Bad Extension Block - No More Link Check\2330m\n");
         return FALSE;
      } else {
         printf("  Linking via Extension Block %d\n", extn->blk_key);
         *list = extn->blk_datalist;
         *eptr = extn->blk_extension;
         *ptr = LIST_SIZE-1;
      }   
   }
   return TRUE;
}

/* This function rebuilds a single file from the passed file buffer
   information.  It uses the list of data block keys to check the
   linking of the individual data blocks.  It can resolve a corruption
   of either a data block chain pointer or of the data block key list.
   If an extension block is required its called for here; though if the
   extension block pointer has been corrupted then the rest of the
   file rebuild will have to take palace without verification from a
   data block key list.   This function isn't currently recursive, as 
   disk buffers "data" and "ext" are global variables.  Every valid
   data block found is marked in the "used" bitmap.  The function 
   return TRUE if the remake works, FALSE if the remake fails. */

BOOL FileRemake(file,blk)
struct FileHandle *file;
struct file_block *blk;
{
   ULONG kptr, *klst;			/* Manages file block keys. */
   ULONG link;				/* Chained link key */
   ULONG eptr;				/* Next extension block key. */
   ULONG seq;				/* Block sequence number */
   BOOL ver=TRUE;			/* Recover verification enabled */

   kptr = LIST_SIZE - 1;
   klst = blk->blk_datalist;
   eptr = blk->blk_extension;
   link = blk->blk_firstdata;
   seq = 0;
   data->blk_seq = 0;
   while (link!=0 || seq<blk->blk_seq && data->blk_seq<=blk->blk_seq) {
      seq++;
      if (link != klst[kptr])
         link = FindPoints(link,klst[kptr],seq,blk->blk_key);
      if (link == 0) break;
      if (ver) ver = NewPoint(&kptr,&klst,&eptr);
      if (ReadBuf(data,link) == READOK) {
         setmap(usdmap,link);
         if (Write(file,data->blk_data,min(BLOCKSIZE-6*WORDSIZE,
             data->blk_size)) == -1) {
            if (IoErr() == ERROR_DISK_FULL) return FALSE;
         }
         link = data->blk_next;
      } else if (ver) {
         printf("  \2337mERROR: Disk Fault -- File may be incomplete\2330m\n");
         link = klst[kptr];
      } else {
         printf("  \2337mERROR: Double Disk Fault -- File truncated\n\2330m");
         break;
      }
   }            
   return TRUE;
}

/* This function accepts a string name.  It creates a unique directory
   under that name, if possible.  If it fails after a reasonable number
   of attempts, it returns FALSE, otherwise TRUE. */

BOOL MakeUniqueDir(name)
char *name;
{
   struct FileLock *lock;
   int len;
   char addon;

   if ((lock = (struct FileLock *) CreateDir(name)) != 0) {
      printf("\2333mBuilding Directory \"%s\"\2330m\n", name);
      UnLock(lock);
      return TRUE;
   }
   printf("\2337mERROR: Bad Directory Create \"%s\", retrying...\2330m\n",name);

   len = strlen(name);
   name[len++] = '-';
   name[len+1] = '\0';

   for (addon = '0'; addon <= '9'; addon++) {
      name[len] = addon;
      if ((lock = (struct FileLock *) CreateDir(name)) != 0) {
         printf("\2333mBuilding Directory \"%s\"\2330m\n", name);
         UnLock(lock);
         return TRUE;
      }
   }
   return FALSE;
} 

/* This function is called when a disk is assumed to be full.  It will
   prompt the user for a new disk, then create the current directory on
   the new disk and exit, where the attempt to make the file take place
   again.  */

VOID BackTraceDir(fil)
char *fil;
{
   char newdsk[PATH_MAX];
   int i = 0;

   while ((newdsk[i++] = *fil++) != ':'); newdsk[i] = '\0';
   printf("\2337m\nDISK FULL: Insert New Disk In %s, type RETURN\2330m", newdsk);
   getchar();
   while (*fil) {
      while ((newdsk[i++] = *fil++) != '/') if (!*fil) return;
      newdsk[i-1] = '\0';
      if (!MakeUniqueDir(newdsk)) return;
      newdsk[i-1] = '/';
   }
}

/* This function rebuilds a directory tree.   It will rebuild the contents of
   directory "dir" (just the files), and any siblings that "dir" may have.  It
   also check each sibling for any children, and will recursively run itself 
   on these children to effectively rebuild the entire directory tree.  This 
   function uses the global disk buffer "buf", so be very careful when making 
   any recursive modifications that this buffer dosen't get overwritten. */

VOID TreeRemake(dev,dir)
char *dev;
struct d_tree *dir;
{
   struct f_list *list;
   struct d_tree *sibling;
   char filespec[PATH_MAX];
   UBYTE dirlen;
   struct FileHandle *fh;
   struct file_block *blk;
   BOOL fails = FALSE;

   blk = (struct file_block *) buf;
   strcpy(filespec,dev);
   for (sibling = dir; sibling != NULL; sibling = sibling->d_next) {
      strpath(filespec+3,sibling);
      if (sibling != root) if (!MakeUniqueDir(filespec)) continue;
      dirlen = strlen(filespec);
      if (filespec[dirlen-1] != ':') filespec[dirlen++] = '/';
      for (list = sibling->d_list; list != NULL; list = list->f_next) {
         if (ReadBuf(blk,list->f_file) != READOK) continue;
         bstr_to_cstr(filespec+dirlen,blk->blk_name);
         do {
            fails = FALSE;
            if ((fh = (struct FileHandle *) Open(filespec,MODE_NEWFILE)) == 0)
               printf(" \2337mERROR: Can't Create File \"%s\"\2330m\n", filespec);
            else {
               printf(" Recovering File \"%s\"\n", filespec);
               if (blk->blk_seq!=0 || blk->blk_firstdata!=0) {
                  if (!FileRemake(fh,blk)) {
                     Close(fh);
                     DeleteFile(filespec);
                     BackTraceDir(filespec);
                     fails = TRUE;
                  } else 
                     Close(fh);
               }
            }
         } while (fails);
      }
      if (sibling->d_child != NULL) TreeRemake(dev,sibling->d_child);
   }
}

/* ====================================================================== */

/* This function salvages any loose blocks found floating around on the
   disk.  They must be data blocks, and they must not be empty.  The names
   of the blocks as saved are "BLK-####-####-##", where the first field
   is the block number found, the second field the indicated parent, and
   the third field the file's sequence number.  They're stored in a
   directory named SPARE-BLOCKS##.  The ## field is ususally 00, but
   if a SPARE-BLOCKS file already exists, this will be incremented to keep
   the directories unique. */

VOID LooseSalv(dev)
char *dev;
{
   int i;
   BOOL clear = FALSE;
   char filespec[PATH_MAX];
   int dirext = -1;
   struct FileLock *lock;
   struct FileHandle *fh;

   for (i=0; i < DISK_SIZE/8 && !clear; i++) clear = (usdmap[i] == 0);
   if (clear) {
      printf(" No Loose Blocks Encountered\n");
      return;
   }
   while (TRUE) {
      sprintf(filespec,"%sSPARE-BLOCKS%d",dev,++dirext);
      if ((lock = (struct FileLock *) Lock(filespec,ACCESS_WRITE)) == 0)
         break;
      else
         UnLock(lock);
   }
   lock = (struct FileLock *) CreateDir(filespec); UnLock(lock);
   printf("\2333mCreated Directory %s\2330m\n",filespec);
   for (i=0; i < DISK_SIZE; i++) 
      if (!checkmap(usdmap,i)) {
         ReadBuf(buf,i);
         sprintf(filespec,"%sSPARE-BLOCKS%d/BLK-%d-%d-%d",
                 dev,dirext,i,buf->Dat.blk_key,buf->Dat.blk_seq);
         printf("  Saving %s\n",filespec);
         fh = (struct FileHandle *) Open(filespec,MODE_NEWFILE);
         Write(fh,buf->Dat.blk_data,
               min(BLOCKSIZE-6*WORDSIZE,buf->Dat.blk_size));
         Close(fh);
      }
}

/* ====================================================================== */

/* This function attempts to open the proper I/O devices and memory space,
   then perform a disk salvage.  If at any point the disk salvage cannot
   be properly done, the function returns FALSE.  If successful, it will
   return TRUE.  Strings "old" and "new" are the names of the source and
   destination drives.  */

BOOL do_it(old,new)
char *old, *new;
{
   char dummy;

  /* This is the stuff that need opening... */
   if ((diskport = (struct MsgPort *) CreatePort("diskreq.port",0)) == NULL) {
      printf("\2337mERROR: Cannot create message port\2330m\n");
      return FALSE;
   }
   if ((diskreq = (struct IOExtTD *) 
      CreateExtIO(diskport,sizeof(struct IOExtTD))) == NULL) {
      printf("\2337mERROR: Cannot create I/O port\2330m\n");
      return FALSE;
   }
   if ((buf = (union block *)AllocMem(BLOCKSIZE,MEMF_CHIP))==NULL) 
      return FALSE;
   if ((data = (struct data_block *)AllocMem(BLOCKSIZE,MEMF_CHIP))==NULL)
      return FALSE;
   if ((extn = (struct extens_block *)AllocMem(BLOCKSIZE,MEMF_CHIP))==NULL)
      return FALSE;
   if (OpenDevice(TD_NAME,device,diskreq,0)) {
      printf("\2337mERROR: Cannot open TrackDisk device\2330m\n");
      return FALSE;
   }
  /* Looks like we made it.  Let's boogie. */
   printf("DiskSalv %s by Dave Haynie\n\n",prog_version);
   printf("Place disk to salvage FROM in drive %s\n", old);
   printf("Place disk to salvage  TO  in drive %s\n", new);   
   printf("Type RETURN to continue");
   dummy = getchar();
   InitDisk();
  /* Let's clear the bitmaps */
   zeromap(badmap);
   zeromap(usdmap);
   setmap(usdmap,0);
  /* Its simple to build the directory tree from here. */
   printf("\nBuilding Directory Map...\n");
   root = Alloc_d_tree(880,"\001:0");
   BuildDirTree();
  /* And just as simple to copy all that stuff over to the new disk. */
   printf("\nSalvaging Disk Structure...\n");
   TreeRemake(new,root);
  /* Now save any stray blocks. */
   printf("\nSalvaging Loose Blocks...\n");
   LooseSalv(new);
  /* Finally a preliminary clean-up. */
   printf("Cleaning Up...\n");
   Free_d_tree(root);
   return TRUE;
}

/* This is the main program.  It checks to make sure that you type things
   the way I like them, then it calls the do_it routine to really do stuff. */

main(argc,argv)
int argc;
char *argv[];
{
   char *in_dev, *out_dev, temp, stat;

   switch (argc) {
   case 2:
      if (argv[1][0] == '?') {
         printf("DiskSalv %s (c)1986 by \"Hazy\" Dave Haynie\n\n", 
         prog_version);
         printf("  This program salvages a bad AmigaDOS disk by copying any\n");
         printf("recoverable files or directories from the bad disk to a good\n");
         printf("formatted disk.  It also copies loose data blocks (blocks\n");
         printf("who's file header was destroyed).  The format is:\n\n");
         printf("  DiskSalv dfM: [TO] dfN:\n\n");
         printf("where N and M are different 3 1/2\" drives from 0 to 3.\n");
         printf("  This program may be distributed free for any personal\n");
         printf("usage without additional permission.  If you find this\n");
         printf("program very useful, please consider paying $10 or less for\n");
         printf("its use.  You may also direct any questions, bug reports,\n");
         printf("or macadamia nuts to the author at:\n");
         printf("   Dave Haynie\n   645 Allen Avenue\n   Gibbstown, NJ 08027\n");
         printf("   Usenet    :  {allegra,caip,seismo}!cbmvax!daveh\n");
         printf("   Compuserve:  76703,2047\n   QuantumLink: Animal128\n");
         exit();
      }
   default:
      printf("Usage: DiskSalv dfM: [TO] dfN:, type \"DiskSalv ?\" for help.\n");
      exit();
   case 3:
      in_dev = argv[1];  out_dev = argv[2];  break;
   case 4:
      in_dev = argv[1];  out_dev = argv[3];  break;
   }
   device = in_dev[2] - '0';
   if (device < 0 || device > 4) {
      printf("\2337mERROR: Invalid input drive specification\2330m\n");
      exit();
   }
   temp = out_dev[2] - '0';
   if (temp < 0 || temp > 4) {
      printf("\2337mERROR: Invalid output drive specification\2330m\n");
      exit();
   }
   if (temp == device) {
      printf("\2337mSORRY: Only 2-drive salvage is currently supported\2330m\n");
      exit();
   }
   stat =  do_it(in_dev,out_dev);	/* Perform the recover function. */
   if (data != NULL) FreeMem(data,BLOCKSIZE);
   if (extn != NULL) FreeMem(extn,BLOCKSIZE);
   if (buf != NULL)  FreeMem(buf,BLOCKSIZE);
   if (diskreq != NULL) {
      MotorOff();
      CloseDevice(diskreq);
      DeleteExtIO(diskreq, sizeof(struct IOExtTD));
   }
   if (diskport != NULL) DeletePort(diskport);
   exit(stat);
}
-- 

Dave Haynie    {caip,inhp4,allegra,seismo}!cbmvax!daveh
               "There, beyond the bounds of your weak imagination
                Lie the noble towers of my city, bright and gold"
								-Genesis