/* ====================================================================== */ /* 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 #include #include #include #include #include #include /* ====================================================================== */ /* 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 || seqblk_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); }