[mod.amiga.sources] DiskSalv

doc@pucc-j.UUCP (08/12/86)

Reply-To: ihnp4!cbmvax!daveh (Dave Haynie)

Here's the DiskSalv README and source file, in shar form.  Please post this
to mod.amiga.sources.  It might be worthwhile to post the compiled object
to mod.amiga.binaries.  The source code compiles with Lattice 3.03, I haven't
tried it with any other compiler/version.  I think everything else is in
order -- these are the same file I've uploaded to CIS and other BBS systems.
The README file explains some of the operation of the program, and some of 
the differenced between this version (1.0) and the previous one (0.9).  I
never posted the source to 0.9, but there's a chance some usenet folks may
have encountered it, so I guess that the notes will be meaningful enough.
The program when compiled is self-documenting; the README is mainly just for
the user's curiosity. If there are any questions or problems, let me know.


-Dave Haynie	{allegra,caip,ihnp4,seismo}!cbmvax!daveh

#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create the files:
#	README
#	disksalv.c
# This archive created: Fri Aug  8 10:55:52 1986
export PATH; PATH=/bin:$PATH
echo shar: extracting "'README'" '(6981 characters)'
if test -f 'README'
then
	echo shar: will not over-write existing file "'README'"
else
sed 's/^X//' << \COLD_BEER > 'README'
X
X
X
X
X 				  DiskSalv
X
X			AmigaDOS Disk Salvage Program
X				    by
X				Dave Haynie
X
X			      (c) April 1986
X
X
X
X
X	This file describes my AmigaDOS disk salvaging program, DiskSalv.
XThis program, the source for it, and this text file are all copyrighted,
Xand are not to be considered in the public domain.  Nontheless, all of 
Xthese programs may be copied magnetically or electronically without any
Xadditional permission, providing no charge is requested	for the program.  
XThe only fees permitted are a moderate copying charge, in the case of 
Xmagnetic distribution, or any normal BBS charges, in the case of an 
Xelectronic download.  Any other uses may be negotiated by contacting me.
XAdditionally, feel free to use any part of the source for your personal
Xlearning, and any small part of the source may be incorporated into your 
Xown program.  If you are very pleased with this program or what you learn
Xfrom the source code, please consider sending no more than $10.00 to me.  I
Xuse such monies as an aid in creating more such programs.  In addition, I'd
Xappreciate reports of any bugs encountered so that I can maintain this 
Xprogram better.  Money and any other communication from users also serves as
Xan indicator of how useful the program is to the Amiga community and helps me
Xdecide what my next project will be and how much to support current programs.
XAll correspondence may be sent to:
X
X		Dave Haynie
X		645 Allen Avenue
X		Gibbstown, NJ 08027
X
X		Usenet:		{allegra,caip,seismo}!cbmvax!daveh
X		Compuserve:	76703,2047
X		QuantumLink:	Animal128
X
XThese addresses are current as of this writing, most are likely to remain
Xso for awhile.  And that's enough of that.
X
X
X	This program is designed to let the user salvage all or parts
Xof a damaged AmigaDOS disk.  It is used similarly to the standard 
XAmigaDOS DISKCOPY or COPY programs, with the format:
X
X		DISKSALV dfN: [TO] dfM:
X
XWhere the source disk is the corrupted disk, the target disk is	a formatted
XAmigaDOS disk, usually (but not necessarily) empty.  This command currently
Xtakes two drives; i.e. it won't salvage df0: to df0: or to RAM:.  This program
Xsalvages the bad disk by by-passing the normal disk root directory, which may 
Xbe corrupted (and as well is probably the most likely cause of a complete disk
Xbeing rejected by AmigaDOS).  It performs a scan to collect all directories
Xand their associated files, building an internal dynamic image of the bad
Xdisk.  Then each directory found is created through normal DOS call, then the
Xfiles belonging to that directory are placed under it.  The process continues
Xuntil no more files/directories exist.  There is no necessary correlation
Xbetween the physical location of files on the bad disk and the physical
Xlocation of the corresponding files on the newly created disk.  Once all 
Xlinkable files are restored, a directory of existing but unused data blocks is
Xcreated, in an effort to preserve anything left, which might be manually 
Xre-created for a file that had its file header badly destroyed.  This program
Xwill also copy a good disk, though inefficiently as compared to a straight 
XCopy command.  Also, this program recovers files that have been deleted but 
Xnot yet written over.  Thus, it can be used to recover deleted (but not 
Xoverwritten) files.
X
X	This version of DiskSalv (1.0) is meant to be a full featured version,
Xcompletely replacing the previous version (0.9).  As in 0.9, this program is
Xcapable of recovering disk directories on a bad disk, and any files in these
Xdirectories with vaild file headers.  When rebuilding a file, it uses the 
Xredundant information in AmigaDOS to resolve any file links that appear 
Xcorrupted.  Every AmigaDOS file has a header containing a list of block 
Xpointers, and a first block pointer.  This first pointer is a link to the 
Xfirst 512 byte data block in the file.  This first link is also echoed in the
Xaforementioned list of block pointers.  Each subsequent data block has a link
Xto the next block which as well can be verified by the block pointer list.  
XData blocks also contain sequence numbers, indicating their logical sequence
Xin the file, and backpointers to the file header block.  These last two items
Xare constantly checked, and are very good in helping to resolve any next block
Xconflicts that might arise.
X
X	This new version of DiskSalv does a few more intelligent things. It
Xmarks off any bad block that's found, and will never try to read it once it is
Xfound bad at one time in the disk scan.  Since the DOS does several retrys on
Xany bad block found, trying to read a bad block can take a comparatively long
Xperiod of time, so this marking saves scanning time.  The second thing that's
Xmarked is the writing of any particular data block.  If a data block is
Xwritten out once, it is marked and never used again.  This will eliminate any
Xerrors that might crop up with a circular list of data blocks, which can occur
Xwhen a block pointer is corrupted.  When creating a new directory, the program
Xwill check for the prior existance of such a directory.  If it already exists,
Xthe intended name will be modified slightly.  After 10 of these attempted name
Xchanges, the program will abort the attempt to write out the directory.  This
Xallows a non-empty disk to easily be used for the output disk without having to
Xworry about overwriting directories currently existing on the output disk.
X
X	Along with that goes another new feature.  If the output disk gets 
Xfilled up completely, the program will pause you and request another disk for
Xthe output disk.  Actually, the first time the disk becomes full, the system
Xwill pop up with a requestor box indicating that the disk has become full.
XClick the "CANCEL" box here and a prompt on the CLI comes up requesting a
Xchange of disks.  It will then recreate the currently active directory path,
Xand the file that it was working on before the swap took place.  Everything
Xfrom this point on proceeds as normal.
X
X	One final feature is the ability to save blocks that have lost their
Xfile headers.  These are often remains from old deleted files, but they could
Xeasily be blocks missing from a normal corrupted file.  These will appear in
Xa uniquely named SPARE-BLOCKS directory.  Each file in that directory will
Xhave a name indicating the block's block number, parent block number, and
Xsequence number.  Any program source or other ASCII text can be easily put
Xtogether by hand by following the proper sequence among files of similar
Xparentage if desired, either in an editor or via the Join command.  Any binary
Xfiles may be re-assembled similarly, but unless all of the blocks in the
Xfile are available, the reassembled file will generally be useless.
X
X					-Dave Haynie
X					 April 1986
X
X
X	"I gained nothing at all from Supreme Enlightenment, and
X	 for that very reason it is called Supreme Enlightenment."
X
X					-Gotama Buddha
X
X	"Let me control a planet's oxygen supply and I don't care
X	 who makes the laws."
X
X					-Great Cthulhu's Starry Wisdom Band
COLD_BEER
fi # end of overwriting check
echo shar: extracting "'disksalv.c'" '(33817 characters)'
if test -f 'disksalv.c'
then
	echo shar: will not over-write existing file "'disksalv.c'"
else
sed 's/^X//' << \COLD_BEER > 'disksalv.c'
X/* ====================================================================== */
X
X/* 				  DiskSalv
X
X			AmigaDOS Disk Salvage Program
X				    by
X				Dave Haynie
X
X			      (c) April 1986
X
X	May be copied magnetically or electronically without the permission
X	of the author, providing no charge is requested	for the program.  The
X	only fees permitted are a moderate copying charge, in the case of 
X	magnetic copying, or any normal BBS charges, in the case of an 
X	electronic download.  Any other uses may be negotiated by contacting
X	the author.  This program is copyrighted; portions of this program
X	source may be used as is in other programs with no necessary
X	acknowledgement, though acknowledgements are always appreciated.
X
X	If you are very pleased with this program or what you learn from
X	the source code, please consider sending no more than $10.00 to
X	the author.  In addition, any bug reports would be greatly
X	appreciated.  All correspondence may be sent to:
X
X		Dave Haynie
X		645 Allen Avenue
X		Gibbstown, NJ 08027
X
X		Usenet:		{allegra,caip,seismo}!cbmvax!daveh
X		Compuserve:	76703,2047
X		QuantumLink:	Animal128
X
X	These addresses are current as of this writing, most are likely to
X	remain so for awhile.
X	
X	ANYWAY, this program is designed to let the user salvage all or parts
X	of a damaged AmigaDOS disk.  It is used similarly to DISKCOPY or 
X	COPY, with the format:
X
X		DISKSALV dfN: [TO] dfM:
X
X	Where the source disk is the corrupted disk, the target disk is
X	a formatted AmigaDOS disk, usually empty.  This command 
X	currently takes two drives; i.e. it won't salvage df0: to df0:.
X
X	This program salvages the bad disk by by-passing the normal
X	disk root directory, which may be corrupted (and as well is
X	probably the most likely cause of a disk being rejected by
X	AmigaDOS).  It performs a scan to collect all directories and
X	their associated files, in pointer form.  Then the directories
X	are created one-by-one through normal DOS calls, then the files
X	are read and placed in the directories.  There is no necessary 
X	correlation between the physical location of files on the bad
X	disk and the physical location of the corresponding files on
X	the newly created disk.  Once all linkable files are restored,
X        a directory of existing but unused data blocks is created, in an
X	effort to preserve anything left, which might be manually 
X	re-created.  This program will also copy a good	disk, though 
X	inefficiently as compared to a straight DiskCopy.  The program
X	will prompt for a new disk if it is having trouble writing to
X	the given recovery disk at any time.
X*/
X
X#include <exec/types.h>
X#include <exec/exec.h>
X#include <devices/trackdisk.h>
X#include <libraries/dos.h>
X#include <libraries/dosextens.h>
X#include <lattice/ctype.h>
X#include <lattice/stdio.h>
X
X/* ====================================================================== */
X
X/* Global definitions. */
X
Xchar prog_version[] = "1.0";
X
X#define BLOCKSIZE	TD_SECTOR
X#define WORDSIZE	sizeof(ULONG)
X#define DISK_SIZE	(NUMHEADS*NUMCYLS*NUMSECS)
X#define SIZE		(BLOCKSIZE / WORDSIZE)
X#define LIST_SIZE	(SIZE - (50+6))
X#define READOK		0
X#define BSTR_MAX	31
X#define PATH_MAX	255
X
X#define FT_SHORT_FILE	2		/* Short file type */
X#define FT_DATA_BLOCK	8		/* Data block type */
X#define ST_FILE		0xfffffffd	/* File subtype */
X#define ST_USER_DIR	2		/* User directory subtype */
X
Xstruct IOExtTD *diskreq = NULL;		/* Pointer for extended disk commands */
Xstruct MsgPort *diskport = NULL;	/* Port for trackdisk replies */
X
XLONG diskChangeCount = 0;		/* Checks for changed disk */
Xint device = 1;				/* Default disk device number */
X
X/* ====================================================================== */
X
X/* Buffer access types.  Each of these types is a template of BLOCKSIZE
X   used to access a TRACKDISK data block's individual elements.  There are
X   several types of these data blocks, "root", the disk's root directory,
X   "directory", a user defined directory block, "file", a file header
X   block, "extens", a file extension block, and "data", a data block
X   (the real meat of what's out there on disk.  I also define a "generic"
X   block, which contains most of the things that you'd like to look at
X   in a block who's fundamental and secondary types haven't yet been
X   determined.  */
X
X/* This block type contains the elements that are found in most blocks
X   and usually required to identify the type of the block.  Called 
X   as .Gen in the block union. */
X
Xstruct generic_block {
X   ULONG blk_type, blk_key, blk_seq, blk_size, blk_link, blk_checksum;
X   UBYTE blk_data[BLOCKSIZE - 9*WORDSIZE];
X   ULONG blk_parent, blk_extension, blk_subtype;
X};
X
X/* This is the block that's used to store the actual data on the disk. Its
X   accesses as .Dat in the block union. */
X
Xstruct data_block {
X   ULONG blk_type, blk_key, blk_seq, blk_size, blk_next, blk_checksum;
X   UBYTE blk_data[BLOCKSIZE-6*WORDSIZE];
X};
X
X/* This block is used as the root directory for the entire disk.  Its
X   accessed as .Rot in the block union. */
X      
Xstruct root_block {
X   ULONG blk_type, blk_key, blk_seq, blk_size, blk_runused, blk_checksum;
X   ULONG blk_hashtable[LIST_SIZE];
X   ULONG blk_bmflag, blk_bitmap[49-23];
X   ULONG blk_days, blk_mins, blk_ticks;
X   UBYTE blk_name[(20-7)*WORDSIZE];
X   ULONG blk_createdays, blk_createmins, blk_createticks;
X   ULONG blk_nexthash, blk_parent, blk_extension, blk_subtype;
X};
X
X/* This block is a user directory block.  It is accessed as .Dir in the
X   block union. */
X
Xstruct directory_block {
X   ULONG blk_type, blk_key, blk_seq, blk_dunused1[2], blk_checksum;
X   ULONG blk_hashtable[LIST_SIZE];
X   ULONG blk_spare, blk_protect, blk_dunused2;
X   UBYTE blk_comment[(47-23)*WORDSIZE];
X   ULONG blk_days, blk_mins, blk_ticks;
X   UBYTE blk_name[(20-4)*WORDSIZE];
X   ULONG blk_nexthash, blk_parent, blk_extension, blk_subtype;
X};
X
X/* This block stores a file header, which contains the information used to
X   link together the individual elements of a file.  Its accessed as .Fil
X   in the block union. */
X
Xstruct file_block {
X   ULONG blk_type, blk_key, blk_seq, blk_size, blk_firstdata, blk_checksum;
X   ULONG blk_datalist[LIST_SIZE];
X   ULONG blk_spare, blk_protect, blk_bytesize;
X   UBYTE blk_comment[(47-23)*WORDSIZE];
X   ULONG blk_days, blk_mins, blk_ticks;
X   UBYTE blk_name[(20-4)*WORDSIZE];
X   ULONG blk_nexthash, blk_parent, blk_extension, blk_subtype;
X};
X
X/* This block stores a file extension block, which is only used if the file 
X   is so long that the main file header doesn't fil all of the data links it 
X   will need.  The file header can currently store data for (BLOCKSIZE-
X   WORDSIZE*(50+6)) = 72 data blocks, or (72*(BLOCKSIZE-6*WORDSIZE)) = 35,136 
X   bytes.  It is accessed as .Ext in the block union */
X
Xstruct extens_block {
X   ULONG blk_type, blk_key, blk_seq, blk_size, blk_firstdata, blk_checksum;
X   ULONG blk_datalist[LIST_SIZE];
X   UBYTE blk_eunused[(50-4)*WORDSIZE];
X   ULONG blk_nexthash, blk_parent, blk_extension, blk_subtype;
X};
X
X/* And this packs them all together */
X
Xunion block {
X   struct generic_block Gen;
X   struct data_block Dat;
X   struct root_block Rot;
X   struct directory_block Dir;
X   struct file_block Fil;
X   struct extens_block Ext;
X};
X
Xunion block *buf = NULL;		/* Global disk buffer pointer */
Xstruct data_block *data = NULL;		/* Global disk data block */
Xstruct extens_block *extn = NULL;	/* Global extension block */
X
X/* ====================================================================== */
X
X/* This section contains structures used to store located directory names
X   and pointers to the files to be restored below them. */
X
Xstruct d_tree {
X   struct d_tree *d_next;	/* Next Directory on same level */
X   struct d_tree *d_parent;	/* Directory on upper level */
X   struct d_tree *d_child;	/* First Directory on lower level */
X   char d_name[BSTR_MAX+1];	/* Directory Name */
X   int d_flags;			/* Directory build flags */
X   ULONG d_point;		/* Directory Disk Pointer */
X   struct f_list *d_list;	/* Files in directory */
X};
X
Xstruct f_list {
X   struct f_list *f_next;	/* Next file in directory */
X   ULONG f_file;		/* Disk location of file header */
X};
X
Xstruct d_tree *root = NULL;	/* Global directory root */
X
X/* ====================================================================== */
X
X/* This section defines the bit-maps. One will speed up the system, making
X   it read error blocks only once.  The other keeps track of used directory
X   blocks. */
X
XUBYTE usdmap[DISK_SIZE/8];	/* Used directory blocks. */
XUBYTE badmap[DISK_SIZE/8];	/* Bad directory blocks. */
X
X/* This function zeros out a given bitmap. */
X
XVOID zeromap(map)
XUBYTE map[];
X{
X   int i;
X
X   for (i=0; i < DISK_SIZE/8; i++) map[i] = 0;
X}
X
X/* This function returns TRUE if the given block number is marked
X   in the given bit-map. */
X
XBOOL checkmap(map,num)
XUBYTE map[];
XULONG num;
X{
X  return (BOOL) ((map[num>>3] & (1 << num%8)) != 0);
X}
X
X/* This function sets the given block number in the bit map. */
X
XVOID setmap(map,num)
XUBYTE map[];
XULONG num;
X{
X   map[num>>3] |= (1 << num%8);
X}
X
X/* ====================================================================== */
X
X/* This section contains basic functions. */
X
X/* This function converts a BSTR to a normal "C" string, returning that
X   C string as its value.  The passed C string pointer is assumed to
X   contains enough space to store the returned C string. */
X
Xchar *bstr_to_cstr(cstr,bstr)
Xchar *cstr;
XUBYTE bstr[];
X{
X   UBYTE size,i;
X   char *ptr;
X
X   ptr = cstr;
X   size = (bstr[0] <= BSTR_MAX) ? (bstr[0]) : (BSTR_MAX);
X   for (i=1;i <= size;i++) *ptr++ = bstr[i];
X   *ptr = '\0';
X   return cstr;
X}
X
X/* This function converts the given directory pointer into a string
X   path name, returning that path name.  The "str" pointer is assumed
X   to point to a long enough string to hold the entire path name. */
X
Xchar *strpath(str,dir)
Xchar *str;
Xstruct d_tree *dir;
X{
X   if (dir == NULL) return (char *) strcpy(str,"");
X   if (dir == root) return (char *) strcpy(str,":");
X   if (dir->d_parent != NULL) strpath(str,dir->d_parent);
X   if (strlen(str) != 1) strcat(str,"/");
X   return (char *) strcat(str,dir->d_name);
X}
X
X/* This function creates an extended IO request port. */
X
Xstruct IORequest *CreateExtIO(ioReplyPort,size)
Xstruct MsgPort *ioReplyPort;
XLONG size;
X{
X   struct IORequest *ioReq;
X
X   if (ioReplyPort == NULL) return ((struct IORequest *) NULL);
X   ioReq = (struct IORequest *) AllocMem(size, MEMF_CLEAR | MEMF_PUBLIC);
X   if (ioReq == NULL) return ((struct IORequest *) NULL);
X
X   ioReq->io_Message.mn_Node.ln_Type = NT_MESSAGE;
X   ioReq->io_Message.mn_Node.ln_Pri = 0;
X   ioReq->io_Message.mn_ReplyPort = ioReplyPort;
X   return (ioReq);
X}
X
X/* This function deletes an extended IO request port. */
X
XDeleteExtIO(ioExt,size)
Xstruct IORequest *ioExt;
XLONG size;
X{
X   ioExt->io_Message.mn_Node.ln_Type = 0xff;
X   ioExt->io_Device = (struct Device *) -1;
X   ioExt->io_Unit = (struct Unit *) -1;
X   FreeMem(ioExt,size);
X}
X
X/* ====================================================================== */
X
X/* This section contains basic disk control functions. */
X
X/* This function turns off the motor of the selected disk drive. */
X
XMotorOff()
X{
X   diskreq->iotd_Req.io_Length = 0;
X   diskreq->iotd_Req.io_Command = TD_MOTOR;
X   DoIO(diskreq);
X   return(0);
X}
X
X/* This function initializes the selected disk device. */
X
XInitDisk()
X{
X   diskreq->iotd_Req.io_Command = TD_CHANGENUM;
X   DoIO(diskreq);
X   diskChangeCount = diskreq->iotd_Req.io_Actual;
X}
X
X/* This function fills the buffer "buf" with the contents of the given
X   sector number (0-17xx).  Returns 0 if there's no error.  It uses the
X   bit map functions to set bit flags if the sector contains a hard
X   error.  This cuts down greatly on time, due to the fact that blocks 
X   with hard errors take longer to read (due to retrys). */
X
XBYTE ReadBuf(buf,sect)
Xunion block *buf;
XULONG sect;
X{
X   if (sect == 0) return 1;
X   if (checkmap(badmap,sect)) return 1;
X   diskreq->iotd_Req.io_Length = BLOCKSIZE;
X   diskreq->iotd_Req.io_Data = (APTR) buf;
X   diskreq->iotd_Req.io_Command = ETD_READ;
X   diskreq->iotd_Count = diskChangeCount;
X   diskreq->iotd_Req.io_Offset = TD_SECTOR * sect;
X   DoIO(diskreq);
X   if (diskreq->iotd_Req.io_Error != 0) {
X      setmap(badmap,sect);
X      setmap(usdmap,sect);
X   }      
X   return (diskreq->iotd_Req.io_Error);
X}
X
X/* ====================================================================== */
X
X/* This section contains functions that operate on the directory tree
X   and file list structures. */
X
X/* This function accepts a disk pointer for a file list link, which
X   it will create and initialize. */
X
Xstruct f_list *Alloc_f_list(point)
XULONG point;
X{
X   struct f_list *temp;
X
X   temp = (struct f_list *) AllocMem(sizeof(struct f_list),0);
X   temp->f_file = point;
X   temp->f_next = NULL;
X   return temp;
X}
X
X/* This function will deallocate a complete file pointer list. */
X
XVOID Free_f_list(list)
Xstruct f_list *list;
X{
X   if (list == NULL) return;
X   if (list->f_next != NULL) Free_f_list(list->f_next);
X   FreeMem(list,sizeof(struct f_list));
X}
X
X/* This function accepts a BSTR pointer, for which it will create a 
X   directory entry and initialize the name string from the BSTR.  */
X
Xstruct d_tree *Alloc_d_tree(point,bstr)
XULONG point;
XUBYTE bstr[];
X{
X   struct d_tree *newtree;
X
X   newtree = (struct d_tree *) AllocMem(sizeof(struct d_tree),0);
X   newtree->d_flags = 0;
X   newtree->d_next = NULL;
X   newtree->d_parent = NULL;
X   newtree->d_child = NULL;
X   newtree->d_point = point;
X   newtree->d_list = NULL;
X   bstr_to_cstr(newtree->d_name,bstr);
X   return newtree;
X}
X
X/* This function deallocates an entire d_tree. */
X
XFree_d_tree(tree)
Xstruct d_tree *tree;
X{
X   if (tree->d_next != NULL) Free_d_tree(tree->d_next);
X   if (tree->d_child != NULL) Free_d_tree(tree->d_child);
X   if (tree->d_list != NULL) Free_f_list(tree->d_list);
X   FreeMem(tree, sizeof(struct d_tree));
X}
X
X/* This function returns either the tree containing 'point' within 'tree',
X   or NULL if 'point' does not yet exist. */
X
Xstruct d_tree *Find_d_tree(tree,point)
Xstruct d_tree *tree;
XULONG point;
X{
X   struct d_tree *temp;
X
X   if (tree->d_point == point) return tree;
X   if (tree->d_next != NULL) 
X      if ((temp = Find_d_tree(tree->d_next,point)) != NULL) return temp;
X   if (tree->d_child != NULL)
X      if ((temp = Find_d_tree(tree->d_child,point)) != NULL) return temp;
X   return NULL;
X}
X
X/* This function adds the given tree to the child list of the given
X   parent tree. */
X
XAdd_Next_d_tree(parent, child)
Xstruct d_tree *parent, *child;
X{
X   struct d_tree *temp;
X
X   temp = parent->d_child;
X   parent->d_child = child;
X   child->d_next = temp;
X   child->d_parent = parent;
X}
X
X/* This function will seek out a file, returning a pointer to its link
X   structure if available, NULL otherwise.  It looks for the file in the
X   given directory structure, and only at that level. */
X
Xstruct f_list *Find_f_list(tree,point)
Xstruct d_tree *tree;
XULONG point;
X{
X   struct f_list *pnt;
X
X   for(pnt = tree->d_list; pnt != NULL; pnt = pnt->f_next)
X      if (pnt->f_file == point) break;
X   return pnt;
X}
X
X/* This function links the file list "list" into the given tree's file
X   list. */
X
XAdd_Next_f_list(tree,list)
Xstruct d_tree *tree;
Xstruct f_list *list;
X{
X   struct f_list *temp;
X
X   temp = tree->d_list;
X   tree->d_list = list;
X   list->f_next = temp;
X}
X
X/* ====================================================================== */
X
X/* This function accepts a disk pointer, which is assumed to be the disk
X   address of a directory structure that doesn't yet exist.  This function
X   will return a pointer to that directory after finding it on disk and
X   linking it into the directory tree.  If the directory can't be
X   found on disk it will return the root directory.  The effect of this
X   function is a recursive lookahead up the path of the requested 
X   directory, which is the initial scanning process that moves the disk head
X   in a non-linear fashion. */
X
Xstruct d_tree *MakeParent(dp)
XULONG dp;
X{
X   union block *tbuf;		/* Temporary Disk Buffer */
X   struct d_tree *tree;		/* Requested directory */
X   struct d_tree *dad;		/* Its parent directory */
X
X   tbuf = (union block *) AllocMem(BLOCKSIZE,MEMF_CHIP);
X   if (ReadBuf(tbuf,dp) != READOK) {
X      FreeMem(tbuf,BLOCKSIZE);
X      return root;
X   }
X   if (tbuf->Gen.blk_type != FT_SHORT_FILE 
X      || tbuf->Gen.blk_subtype != ST_USER_DIR) {
X      FreeMem(tbuf,BLOCKSIZE);
X      return root;
X   }
X   if (tbuf->Dir.blk_name[0] == 0 || tbuf->Dir.blk_name[1] == 0) {
X      FreeMem(tbuf,BLOCKSIZE);
X      return root;
X   }
X   if ((dad = Find_d_tree(root,tbuf->Dir.blk_parent)) == NULL)
X      dad = MakeParent(tbuf->Dir.blk_parent);
X   tree = Alloc_d_tree(dp,tbuf->Dir.blk_name);
X   Add_Next_d_tree(dad,tree);
X   FreeMem(tbuf,BLOCKSIZE);
X   return tree;
X}
X
X/* This function builds the directory list.  The top node is kind of a
X   default place, where any file who's directory is corrupted, as well 
X   as any files directly in the ROOT node, will be placed.  This routine
X   uses the global disk buffer "buf", so exercise caution when making
X   any modifications not to recursively overwrite this buffer.  This 
X   routine will tag any non-data block as "used" in the "usdmap" bitmap.
X   Only actual data blocks remain untagged beyond this routine. */
X
XBuildDirTree()
X{
X   ULONG sect;				/* Current block */
X   struct d_tree *dir, *sub;		/* Working directory trees */
X   struct f_list *fil;			/* Working file list */
X   char str[PATH_MAX];			/* Temporary string */
X
X   for (sect = 1; sect < DISK_SIZE; sect++) {
X      if (ReadBuf(buf,sect) != READOK) {
X         printf(" \233\113\2337mBlock %4d (ERROR)\2330m\n",sect);
X         continue;
X      }
X      switch (buf->Gen.blk_type) {		/* Primary Type */
X      case FT_SHORT_FILE : 
X         setmap(usdmap,sect);
X         switch (buf->Gen.blk_subtype) {		/* Secondary Type */
X         case ST_FILE :
X            printf(" \233\113Block %4d (FILE) Name \"",sect);
X            if ((dir = Find_d_tree(root,buf->Fil.blk_parent)) == NULL)
X               dir = MakeParent(buf->Fil.blk_parent);
X            printf("%s",strpath(str,dir));
X            if (str[strlen(str)-1] != ':') printf("/");
X            printf("%s",bstr_to_cstr(str,buf->Fil.blk_name));
X            if (sect != buf->Fil.blk_key) printf("\" Block Key Mismatch\n");
X            else printf("\"\n");
X            if (Find_f_list(dir,sect) != NULL) break;
X            fil = Alloc_f_list(sect);
X            Add_Next_f_list(dir,fil);
X            break;
X         case ST_USER_DIR : 
X            printf(" \233\113Block %4d (DIR)  Name \"",sect);
X            if ((dir = Find_d_tree(root,buf->Dir.blk_parent)) == NULL)
X               dir = MakeParent(buf->Dir.blk_parent);
X            if ((sub = Find_d_tree(dir,sect)) != NULL) {
X               printf("%s\n",strpath(str,sub)); break;
X            }
X            sub = Alloc_d_tree(sect,buf->Dir.blk_name);
X            Add_Next_d_tree(dir,sub);
X            printf("%s",strpath(str,sub));
X            if (sect != buf->Dir.blk_key) printf("\" Block Key Mismatch\n");
X            else printf("\"\n");
X            break;
X         default : 
X            printf(" \233\113Block %4d (???)\n\233\101",sect);
X         }
X         break;
X      case FT_DATA_BLOCK :
X         if (buf->Dat.blk_size == 0) setmap(usdmap,sect);
X         printf(" \233\113Block %4d (DATA)\n\233\101",sect); break;
X      default :
X         setmap(usdmap,sect);
X         printf(" \233\113Block %4d (UNUSED)\n\233\101",sect); break;
X      }
X   }
X}
X
X/* ====================================================================== */
X
X/* This section contains routines concerned with actually restoring the
X   file structure from the internal directory path structure. */
X
X/* This function resolves the order of likelihood of the two normal
X   block links, returning as functional value the best guess.  It will 
X   each check block on disk, and rule it out if either it specifies a 
X   disk location out of range or if its place on the disk is completely
X   destroyed.  There are two guesses for any unresolved block address, one 
X   for the data block link pointer, one for the file block header key 
X   pointer.  Its only necessary to call this routine if the two values are 
X   different, i.e., at least one of the pointers has been messed up.  This
X   now also checks the "used" bit-map to avoid repeated/circular files being
X   created. */
X
XULONG FindPoints(g1,g2,seq,head)
XULONG g1,g2,seq,head;
X{
X   struct data_block *blk;
X   UBYTE q1=0,q2=0;
X
X   printf("   \2337mResolving link conflict %d <-> %d\2330m\n",g1,g2);
X   if (g1 > DISK_SIZE) g1 = 0; if (g2 > DISK_SIZE) g2 = 0;
X   if (checkmap(usdmap,g1)) g1=0;
X   if (checkmap(usdmap,g2)) g2=0;
X   if (g1 == 0) return g2; if (g2 == 0) return g1;
X   blk = (struct data_block *) AllocMem(BLOCKSIZE,MEMF_CHIP);
X   if (ReadBuf(blk,g1) == READOK)
X      q1 = TRUE + (blk->blk_seq == seq) + (blk->blk_key == head) +
X           (blk->blk_type == FT_DATA_BLOCK);
X   if (ReadBuf(blk,g2) == READOK)
X      q2 = TRUE + (blk->blk_seq == seq) + (blk->blk_key == head) +
X           (blk->blk_type == FT_DATA_BLOCK);
X   FreeMem(blk,BLOCKSIZE);
X   if (q1 == 0) g1 = 0; if (q2 == 0) g2 = 0;
X   if (q1 > q2) return g1; else return g2;
X}
X
X/* This function returns the next available file header data link
X   pointer.  If the current file header is exhausted it will open
X   up an extension block based on the "eptr" disk pointer. */
X
XBOOL NewPoint(ptr,list,eptr)
XULONG *ptr, **list, *eptr;
X{
X   (*ptr)--;
X   if (*ptr < 0) {
X      if (ReadBuf(extn,*eptr) != READOK) {
X         printf("  \2337mERROR: Bad Extension Block - No More Link Check\2330m\n");
X         return FALSE;
X      } else {
X         printf("  Linking via Extension Block %d\n", extn->blk_key);
X         *list = extn->blk_datalist;
X         *eptr = extn->blk_extension;
X         *ptr = LIST_SIZE-1;
X      }   
X   }
X   return TRUE;
X}
X
X/* This function rebuilds a single file from the passed file buffer
X   information.  It uses the list of data block keys to check the
X   linking of the individual data blocks.  It can resolve a corruption
X   of either a data block chain pointer or of the data block key list.
X   If an extension block is required its called for here; though if the
X   extension block pointer has been corrupted then the rest of the
X   file rebuild will have to take palace without verification from a
X   data block key list.   This function isn't currently recursive, as 
X   disk buffers "data" and "ext" are global variables.  Every valid
X   data block found is marked in the "used" bitmap.  The function 
X   return TRUE if the remake works, FALSE if the remake fails. */
X
XBOOL FileRemake(file,blk)
Xstruct FileHandle *file;
Xstruct file_block *blk;
X{
X   ULONG kptr, *klst;			/* Manages file block keys. */
X   ULONG link;				/* Chained link key */
X   ULONG eptr;				/* Next extension block key. */
X   ULONG seq;				/* Block sequence number */
X   BOOL ver=TRUE;			/* Recover verification enabled */
X
X   kptr = LIST_SIZE - 1;
X   klst = blk->blk_datalist;
X   eptr = blk->blk_extension;
X   link = blk->blk_firstdata;
X   seq = 0;
X   data->blk_seq = 0;
X   while (link!=0 || seq<blk->blk_seq && data->blk_seq<=blk->blk_seq) {
X      seq++;
X      if (link != klst[kptr])
X         link = FindPoints(link,klst[kptr],seq,blk->blk_key);
X      if (link == 0) break;
X      if (ver) ver = NewPoint(&kptr,&klst,&eptr);
X      if (ReadBuf(data,link) == READOK) {
X         setmap(usdmap,link);
X         if (Write(file,data->blk_data,min(BLOCKSIZE-6*WORDSIZE,
X             data->blk_size)) == -1) {
X            if (IoErr() == ERROR_DISK_FULL) return FALSE;
X         }
X         link = data->blk_next;
X      } else if (ver) {
X         printf("  \2337mERROR: Disk Fault -- File may be incomplete\2330m\n");
X         link = klst[kptr];
X      } else {
X         printf("  \2337mERROR: Double Disk Fault -- File truncated\n\2330m");
X         break;
X      }
X   }            
X   return TRUE;
X}
X
X/* This function accepts a string name.  It creates a unique directory
X   under that name, if possible.  If it fails after a reasonable number
X   of attempts, it returns FALSE, otherwise TRUE. */
X
XBOOL MakeUniqueDir(name)
Xchar *name;
X{
X   struct FileLock *lock;
X   int len;
X   char addon;
X
X   if ((lock = (struct FileLock *) CreateDir(name)) != 0) {
X      printf("\2333mBuilding Directory \"%s\"\2330m\n", name);
X      UnLock(lock);
X      return TRUE;
X   }
X   printf("\2337mERROR: Bad Directory Create \"%s\", retrying...\2330m\n",name);
X
X   len = strlen(name);
X   name[len++] = '-';
X   name[len+1] = '\0';
X
X   for (addon = '0'; addon <= '9'; addon++) {
X      name[len] = addon;
X      if ((lock = (struct FileLock *) CreateDir(name)) != 0) {
X         printf("\2333mBuilding Directory \"%s\"\2330m\n", name);
X         UnLock(lock);
X         return TRUE;
X      }
X   }
X   return FALSE;
X} 
X
X/* This function is called when a disk is assumed to be full.  It will
X   prompt the user for a new disk, then create the current directory on
X   the new disk and exit, where the attempt to make the file take place
X   again.  */
X
XVOID BackTraceDir(fil)
Xchar *fil;
X{
X   char newdsk[PATH_MAX];
X   int i = 0;
X
X   while ((newdsk[i++] = *fil++) != ':'); newdsk[i] = '\0';
X   printf("\2337m\nDISK FULL: Insert New Disk In %s, type RETURN\2330m", newdsk);
X   getchar();
X   while (*fil) {
X      while ((newdsk[i++] = *fil++) != '/') if (!*fil) return;
X      newdsk[i-1] = '\0';
X      if (!MakeUniqueDir(newdsk)) return;
X      newdsk[i-1] = '/';
X   }
X}
X
X/* This function rebuilds a directory tree.   It will rebuild the contents of
X   directory "dir" (just the files), and any siblings that "dir" may have.  It
X   also check each sibling for any children, and will recursively run itself 
X   on these children to effectively rebuild the entire directory tree.  This 
X   function uses the global disk buffer "buf", so be very careful when making 
X   any recursive modifications that this buffer dosen't get overwritten. */
X
XVOID TreeRemake(dev,dir)
Xchar *dev;
Xstruct d_tree *dir;
X{
X   struct f_list *list;
X   struct d_tree *sibling;
X   char filespec[PATH_MAX];
X   UBYTE dirlen;
X   struct FileHandle *fh;
X   struct file_block *blk;
X   BOOL fails = FALSE;
X
X   blk = (struct file_block *) buf;
X   strcpy(filespec,dev);
X   for (sibling = dir; sibling != NULL; sibling = sibling->d_next) {
X      strpath(filespec+3,sibling);
X      if (sibling != root) if (!MakeUniqueDir(filespec)) continue;
X      dirlen = strlen(filespec);
X      if (filespec[dirlen-1] != ':') filespec[dirlen++] = '/';
X      for (list = sibling->d_list; list != NULL; list = list->f_next) {
X         if (ReadBuf(blk,list->f_file) != READOK) continue;
X         bstr_to_cstr(filespec+dirlen,blk->blk_name);
X         do {
X            fails = FALSE;
X            if ((fh = (struct FileHandle *) Open(filespec,MODE_NEWFILE)) == 0)
X               printf(" \2337mERROR: Can't Create File \"%s\"\2330m\n", filespec);
X            else {
X               printf(" Recovering File \"%s\"\n", filespec);
X               if (blk->blk_seq!=0 || blk->blk_firstdata!=0) {
X                  if (!FileRemake(fh,blk)) {
X                     Close(fh);
X                     DeleteFile(filespec);
X                     BackTraceDir(filespec);
X                     fails = TRUE;
X                  } else 
X                     Close(fh);
X               }
X            }
X         } while (fails);
X      }
X      if (sibling->d_child != NULL) TreeRemake(dev,sibling->d_child);
X   }
X}
X
X/* ====================================================================== */
X
X/* This function salvages any loose blocks found floating around on the
X   disk.  They must be data blocks, and they must not be empty.  The names
X   of the blocks as saved are "BLK-####-####-##", where the first field
X   is the block number found, the second field the indicated parent, and
X   the third field the file's sequence number.  They're stored in a
X   directory named SPARE-BLOCKS##.  The ## field is ususally 00, but
X   if a SPARE-BLOCKS file already exists, this will be incremented to keep
X   the directories unique. */
X
XVOID LooseSalv(dev)
Xchar *dev;
X{
X   int i;
X   BOOL clear = FALSE;
X   char filespec[PATH_MAX];
X   int dirext = -1;
X   struct FileLock *lock;
X   struct FileHandle *fh;
X
X   for (i=0; i < DISK_SIZE/8 && !clear; i++) clear = (usdmap[i] == 0);
X   if (clear) {
X      printf(" No Loose Blocks Encountered\n");
X      return;
X   }
X   while (TRUE) {
X      sprintf(filespec,"%sSPARE-BLOCKS%d",dev,++dirext);
X      if ((lock = (struct FileLock *) Lock(filespec,ACCESS_WRITE)) == 0)
X         break;
X      else
X         UnLock(lock);
X   }
X   lock = (struct FileLock *) CreateDir(filespec); UnLock(lock);
X   printf("\2333mCreated Directory %s\2330m\n",filespec);
X   for (i=0; i < DISK_SIZE; i++) 
X      if (!checkmap(usdmap,i)) {
X         ReadBuf(buf,i);
X         sprintf(filespec,"%sSPARE-BLOCKS%d/BLK-%d-%d-%d",
X                 dev,dirext,i,buf->Dat.blk_key,buf->Dat.blk_seq);
X         printf("  Saving %s\n",filespec);
X         fh = (struct FileHandle *) Open(filespec,MODE_NEWFILE);
X         Write(fh,buf->Dat.blk_data,
X               min(BLOCKSIZE-6*WORDSIZE,buf->Dat.blk_size));
X         Close(fh);
X      }
X}
X
X/* ====================================================================== */
X
X/* This function attempts to open the proper I/O devices and memory space,
X   then perform a disk salvage.  If at any point the disk salvage cannot
X   be properly done, the function returns FALSE.  If successful, it will
X   return TRUE.  Strings "old" and "new" are the names of the source and
X   destination drives.  */
X
XBOOL do_it(old,new)
Xchar *old, *new;
X{
X   char dummy;
X
X  /* This is the stuff that need opening... */
X   if ((diskport = (struct MsgPort *) CreatePort("diskreq.port",0)) == NULL) {
X      printf("\2337mERROR: Cannot create message port\2330m\n");
X      return FALSE;
X   }
X   if ((diskreq = (struct IOExtTD *) 
X      CreateExtIO(diskport,sizeof(struct IOExtTD))) == NULL) {
X      printf("\2337mERROR: Cannot create I/O port\2330m\n");
X      return FALSE;
X   }
X   if ((buf = (union block *)AllocMem(BLOCKSIZE,MEMF_CHIP))==NULL) 
X      return FALSE;
X   if ((data = (struct data_block *)AllocMem(BLOCKSIZE,MEMF_CHIP))==NULL)
X      return FALSE;
X   if ((extn = (struct extens_block *)AllocMem(BLOCKSIZE,MEMF_CHIP))==NULL)
X      return FALSE;
X   if (OpenDevice(TD_NAME,device,diskreq,0)) {
X      printf("\2337mERROR: Cannot open TrackDisk device\2330m\n");
X      return FALSE;
X   }
X  /* Looks like we made it.  Let's boogie. */
X   printf("DiskSalv %s by Dave Haynie\n\n",prog_version);
X   printf("Place disk to salvage FROM in drive %s\n", old);
X   printf("Place disk to salvage  TO  in drive %s\n", new);   
X   printf("Type RETURN to continue");
X   dummy = getchar();
X   InitDisk();
X  /* Let's clear the bitmaps */
X   zeromap(badmap);
X   zeromap(usdmap);
X   setmap(usdmap,0);
X  /* Its simple to build the directory tree from here. */
X   printf("\nBuilding Directory Map...\n");
X   root = Alloc_d_tree(880,"\001:0");
X   BuildDirTree();
X  /* And just as simple to copy all that stuff over to the new disk. */
X   printf("\nSalvaging Disk Structure...\n");
X   TreeRemake(new,root);
X  /* Now save any stray blocks. */
X   printf("\nSalvaging Loose Blocks...\n");
X   LooseSalv(new);
X  /* Finally a preliminary clean-up. */
X   printf("Cleaning Up...\n");
X   Free_d_tree(root);
X   return TRUE;
X}
X
X/* This is the main program.  It checks to make sure that you type things
X   the way I like them, then it calls the do_it routine to really do stuff. */
X
Xmain(argc,argv)
Xint argc;
Xchar *argv[];
X{
X   char *in_dev, *out_dev, temp, stat;
X
X   switch (argc) {
X   case 2:
X      if (argv[1][0] == '?') {
X         printf("DiskSalv %s (c)1986 by \"Hazy\" Dave Haynie\n\n", 
X         prog_version);
X         printf("  This program salvages a bad AmigaDOS disk by copying any\n");
X         printf("recoverable files or directories from the bad disk to a good\n");
X         printf("formatted disk.  It also copies loose data blocks (blocks\n");
X         printf("who's file header was destroyed).  The format is:\n\n");
X         printf("  DiskSalv dfM: [TO] dfN:\n\n");
X         printf("where N and M are different 3 1/2\" drives from 0 to 3.\n");
X         printf("  This program may be distributed free for any personal\n");
X         printf("usage without additional permission.  If you find this\n");
X         printf("program very useful, please consider paying $10 or less for\n");
X         printf("its use.  You may also direct any questions, bug reports,\n");
X         printf("or macadamia nuts to the author at:\n");
X         printf("   Dave Haynie\n   645 Allen Avenue\n   Gibbstown, NJ 08027\n");
X         printf("   Usenet    :  {allegra,caip,seismo}!cbmvax!daveh\n");
X         printf("   Compuserve:  76703,2047\n   QuantumLink: Animal128\n");
X         exit();
X      }
X   default:
X      printf("Usage: DiskSalv dfM: [TO] dfN:, type \"DiskSalv ?\" for help.\n");
X      exit();
X   case 3:
X      in_dev = argv[1];  out_dev = argv[2];  break;
X   case 4:
X      in_dev = argv[1];  out_dev = argv[3];  break;
X   }
X   device = in_dev[2] - '0';
X   if (device < 0 || device > 4) {
X      printf("\2337mERROR: Invalid input drive specification\2330m\n");
X      exit();
X   }
X   temp = out_dev[2] - '0';
X   if (temp < 0 || temp > 4) {
X      printf("\2337mERROR: Invalid output drive specification\2330m\n");
X      exit();
X   }
X   if (temp == device) {
X      printf("\2337mSORRY: Only 2-drive salvage is currently supported\2330m\n");
X      exit();
X   }
X   stat =  do_it(in_dev,out_dev);	/* Perform the recover function. */
X   if (data != NULL) FreeMem(data,BLOCKSIZE);
X   if (extn != NULL) FreeMem(extn,BLOCKSIZE);
X   if (buf != NULL)  FreeMem(buf,BLOCKSIZE);
X   if (diskreq != NULL) {
X      MotorOff();
X      CloseDevice(diskreq);
X      DeleteExtIO(diskreq, sizeof(struct IOExtTD));
X   }
X   if (diskport != NULL) DeletePort(diskport);
X   exit(stat);
X}
COLD_BEER
fi # end of overwriting check
#	End of shell archive
exit 0