TECHNICAL NOTES FOR DAMNSPOT Version 2.1 GENERAL These notes are for the programmer and for myself to remind me of things to do and why I did the things I did. Some may be of passing interest to others. It is often difficult to decide what is, or is not, too technical for the general user. Should things not appear to be working as advertised, you might browse through this looking for a clue. SOURCE FILES The DAMNSPOT source files, are DS.PAS, DS*.PAS and DAMNSPOT.HLP. The order of inclusion is relatively inflexible unless you like writing Forward statements. Source is available to anyone who wants it and will not cavil too much at imperfections. COMPILATION CONSIDERATIONS Since Turbo allocates variable storage, not just stack space, in high memory, one has to set an upper memory limit at compile time. I intend to distribute DS with this set to D000. That may founder on smaller machines, larger disks or just on people who like to run with lots of stuff, like Smartkey, in high memory. Also, Turbo needs a bit of room during compilation itself. If I load up memory with Snapshot, Smartkey and ZCPR2 the compiler bombs out on a Compiler Overflow error shortly after DSSPEC is included. The problem goes away if I just use Turbo in a bare ZCPR2 environment and omit error message loading. Somewhere in between those extremes is the critical point. To compile: turbo Run Turbo n No error messages m ds Main program DS.PAS o Options menu c COM file output ed000 End address D000 (or as big as Turbo allows) q Quit Options menu c Compile HELP FILE The help file DAMNSPOT.HLP has the format: %TITLE 1 Note: The title may contain blanks but it, Text and the % and & signs must be left ... justified. Text & %TITLE 2 Text ... Text & etc. The text may include the special symbols #G = Bell, and #D = Drive under test. The blank lines between entries are irrelevant; they are merely there to improve legibility. A help entry is invoked with the syntax: help('WHATEVER TITLE YOU WANT'); The HELP procedure first attempts to find the help file, and then scans it looking for a line beginning: %WHATEVER TITLE YOU WANT If found, successive lines are printed to the screen until a line beginning with an ampersand is found. Help messages are printed as: ---------------------------------------------------------------- Text ... Text ---------------------------------------------------------------- The first time that help is invoked, the help file is searched for beginning at the original default drive. On later invocations the search begins at the drive on which the help file was last found, if applicable. If the help file is not found on the first drive tried, any other available drives are searched in the order A B C ... skipping the one already tested. GLOBAL SPECIFICATIONS These are contained in DSSPEC.PAS. Only a few special purpose types are defined elsewhere - mostly strings of various lengths. DEBUGGING The constant DEBUG and two sets, FAKE_BAD_BLOCK and FAKE_BAD_SECTOR, are defined in DSSPEC. If DEBUG is TRUE, then DS will fake bad blocks and bad sectors when the B: drive is under test. Reading and writing is not inhibited; DS merely causes the I/O routine to signal an error even when the I/O succeeds. TYPE AND RANGE CHECKING Range checking cannot be used. The key arrays are declared with index ranges of the form [0..0] because their true size is not known. I suppose I could set the upper limits to large numbers but that would also be arbitrary. It might be possible to litter the program with {$R-} and {$R+} directives but to heck with it. Perhaps as a result, I have been rather sloppy on types. A lot of things are Integer where perhaps 0..Maxint would be more appropriate, and so on. DYNAMIC MEMORY USAGE The procedures used are Mark, Getmem and Release. In this version of DS, only live directory entries are stored in memory. In Version 2.0 memory had to accommodate the entire directory. Also three bytes of memory are needed for each block as well as a block-sized buffer. This may well preclude checking disks with large numbers of entries, blocks and block sizes. Memory is also needed to store the initial directory checksum vector for the disk under test but, small mercy, this will probably be zero length for a hard disk. ERROR HANDLING Most is left to Turbo. Sometimes it is turned off {$I-} for specific operations where a crash is definitely not wanted as the result of a typo or a missing disk file. The function IO_ERROR displays the same error message as Turbo but returns the error code from IORESULT rather than crashing. DISK PARAMETERS These are obtained from CP/M in the usual manner. In Version 2.1 the parameters are squirreled away in program assigned storage because it was found that the Kaypro IV, and perhaps other machines, do not always keep them resident at the indicated addresses. ABORTING POSSIBILITIES The Control-S / Control-C checking available with Turbo has been turned off with the directive {$C-}. Similar action has been faked with the routines KEYBOARD and PERMIT_ABORT. The latter should be invoked in any time consuming loop or in any loop that sends a lot of lines to the terminal. Most live key input, which is almost all input in DS, goes through KEYBOARD. If a Control-C is typed, the user is asked for confirmation of the abort and warned if there are changes pending. If a Control-S is typed, normal start and stop scroll occurs. The stop scroll depends on polling by PERMIT_ABORT. The Control-C aborts cause a Warm Boot. DISK HARDWARE ROUTINES These were a bit of fun, many lessons being learned (Hair for sale, a penny a handful). The key lessons were: 1. Do not use CP/M function 37 (RESET DRIVES). It logs a drive out, yes, but then only logs it in again if you do a select to the drive from a different drive. If you reset drive A, when A is default, change the disk, and reselect drive A, the disk does not get logged in so writes won't work. The only solution, other than doing a general Disk System Reset seems to be the hokey one of finding another drive (if one exists), selecting it, and then selecting the one you actually want. The long and short is that I threw out Function 37 and went to the simple Reset Disk Subsystem - CP/M function 13. 2. BDOS does not expect anyone else to issue BIOS select calls (or is the fault Turbo's ?). At any rate, the function DISK_INFO_LOC does a BIOS disk select. If this is a different disk than the default, then normal Pascal disk I/O gets loused up. The solution is the SYNC_DISK procedure which simply issues a BIOS disk select to the default disk. SYNC_DISK is required immediately after every invocation of DISK_INFO_LOC except for in the RW_SECTOR function. There, the SYNC_DISK is reserved until the I/O is complete. HEX NUMBER OUTPUT The function BEXW is just HEXW with leading zeros suppressed so that the first significant character is the first string character. CATEGORIZATION OF BLOCKS A block may have any reasonable combination of the following attributes: 1. A parent file; 2. Active - in file other than [UNUSED].BAD or BLOCKS.BAD; 3. Covered - in file [UNUSED].BAD or BLOCKS.BAD; 4. Checked - have checked readability (may be cleared on retry); 5. Bad - unreadable (or assumed so); 6. Wrong - readable but containing the wrong information. SCREEN OUTPUT ASSUMPTIONS It has been assumed that the screen will handle 80 characters in width, and that it performs a physical carriage return without linefeed in response to a CR character. If these assumptions are untrue the displays may be rather messy. MIXING PASCAL WITH BIOS DISK I/O Some caution is needed here because: 1. If you write a file to the disk, updating the directory in the process, then DS will overwrite that directory when (if) the user says Update. All reference to the file you wrote will be cancelled. 2. The file that you write may well use blocks that DS regards as its own. For example, if you succeed in retrieving data from a flaky block using the Retry option, DS will immediately squirrel it away in a good block. If you now write a file to the disk before DS updates the directory, the file may use that block clobbering the precious information that DS put there. An example of how to handle this from within DS is contained in procedure DIRECTORY_OUTPUT_DISK. The object is to avoid saving directory information files to the disk under test as long as its directory is subject to change. Before arriving at this point, the scan is complete so no further changes can occur to DS's view of the disk. Writing the file is conditioned by: 1. Updated_Ok - true if the disk directory has been successfully updated. 2. Change - true if an update would produce any change in the disk. 3. Trouble_Disk(Drive) - true if Drive contains the disk under test in the same form that it had originally The first two of these are boolean variables that the program must maintain. The third is a boolean function that compares the current CP/M checksum vector for the drive against that saved when the disk was first specified. Incidentally, a reference to trouble_disk(drive) must be preceded by invoking log_in(drive) in order to initialize the CP/M checksum vector. On first sight, it seems that this same function should be invoked when the user requests Moving the trouble disk to a new drive for another Update attempt. Unfortunately, one has to assume that the directory is corrupt at this point, so one cannot demand equality with the original as proof that the trouble disk really is on the new drive. Intruder programs cannot be prevented from lousing things up. For example, the program Snapshot used to capture a terminal session's screen output for documentation purposes, was invoked repeatedly while DS was running. Since Snapshot automatically writes to the default drive it was necessary to ensure that the initial default drive was different than the drive to be tested. Incidentally, the distinction between default drive and initial default drive is a quibble. DS changes the default drive only momentarily from time to time as part of the procedure to log in directories. It is always set back immediately, so you can assume that the default drive is the original. Any Pascal file I/O should specify a drive explicitly. SECTORS The sector numbers reported are logical. The actual read/write routines convert them using any CP/M translation vector but this is transparent to the user. For some reason, my copy of DU2 numbers logical sectors beginning at 1 rather than at 0. Hence what DS calls sector 5, DU2 will call sector 6. My CP/M manual makes the same error saying that, on a BIOS disk select, register BC contains the sector number from 1 - 26. Not true. DIVISION OF THE DISK INTO AREAS DS differentiates the System, Directory and User areas as follows: Directory This begins at the first non-reserved track and extends for as many blocks as are needed to hold (DRM + 1) entries. For a basic 64 entry directory this is 16 records. System This consists of all reserved tracks plus any pre- allocated blocks indicated by AL0 and AL1 in the disk parameter block minus the Directory. A hard disk will usually be split into two or more logical disks, the higher disks having a large number of reserved tracks. This isn't really 'system' area but DS thinks it is. User This consists of whatever is left, following the last pre- allocated block and extending to the highest user block.