












                   Super File Manager v1.0

                     PROGRAMMER'S GUIDE




                        David Steiner
                        2035 J Apt. 6
                        Lincoln, NE  68510
                        (402)475-0601




     Written for:

                 Capitol PC User Group Inc.
             1987 Software Programming Contest.




     Permission is granted for Capitol PC and other not for 
profit organizations to publish the source and executable 
portions of this program.

                       >> OVERVIEW <<


     This documentation file is not a user's manual.  It is 
designed to provide helpful information about the 
techniques used in Super File Manager (SFM).

     This document is not designed to be a DOS technical 
reference either.  I do not claim to be any kind of 
authority on DOS or the other types of system calls used by 
this program.  It is entirely possible that information 
presented here is not completely accurate.  The only claim 
I can make is that these routines work for me.

     Before getting too far into this file I suggest that 
you first take a brief look at the source code for SFM.  
The style used may not be what your used to, but I like 
it.  In most cases the comments are kept to a simple 
procedure description in order to avoid clutter.

     Several items will be covered in this reference that I 
think you will find useful:

          Compiling and running SFM.
          General design of a DOS call routine.
          DOS function $32, non-documented but useful.
          Using absolute disk reads and writes.
          Trapping Turbo errors.
          Designing an interrupt handler.
          Trapping DOS critical errors.
          An overall look at SFM.
          Suggested references.
          Acknowledgments.

     This documentation file is being written fairly 
hastily so I will apologize now if it doesn't flow terribly 
well.  If I had more time I would have had a friend or two 
help work out the rough spots.


                  Super File Manager - 2 -
                     >> COMPILING SFM <<


     Although the executable code (.COM file) distributed 
with SFM will work on most any system, you will need 
Borland's Turbo Pascal version 3.0 if you wish to customize 
SFM to better suit your needs.  This technical 
documentation, however, presents ideas that may be 
generalized to any other language.  If you don't have Turbo 
Pascal, but are fairly good at language conversions you 
will also be able to make use of the source code.

     It is a requirement stated in the contest rules that I 
must describe all steps necessary to compile and run SFM.  
I apologize to those who find these instructions 
condescending.

     To compile SFM you must first make sure that all of 
the include files are present in the current directory.  
These are:

               SFM.PAS
               SFMVARS.INC
               SFMOTHER.INC
               SFMSCRN.INC
               SFMDOS.INC
               SFMFUNC.INC

The next thing to do is start Turbo.  Assuming this is done 
and that the files above are in the current directory, here 
is the command sequence for compiling to the file SFM.COM:

          M             ; Main file
          SFM <ENTER>   ; file name
          O             ; Options menu
          C             ; Compile to COM file
          Q             ; Quit options menu
          C             ; Compile
          Q             ; Quit Turbo
          SFM <ENTER>   ; Run SFM

The letters shown to the left are typed as shown, the ENTER 
key need only be pressed when explicitly listed.  SFM 
should now be up and running, and it is time to refer to 
the User's Guide if you aren't already familiar with SFM.


                  Super File Manager - 3 -
                       >> DOS CALLS <<


     Making a DOS or BIOS call from Turbo Pascal is not all 
that difficult.  This section just gives an outline of a 
good method for designing functions that make these calls, 
as well as some specifics about how Turbo handles such 
requests.

     For those of you new to system calls a short 
explanation is in order.  There exists a vault of functions 
available to you (as a programmer) that is always resident 
in memory.  Many of these functions are part of your 
system's hardware (BIOS) as part of the ROM (read-only 
memory).  Many more become available when your system loads 
DOS.

     In most cases BIOS functions are very low level and 
hard to work with.  An example of the more useful BIOS 
functions would be those for controlling the video 
display.  They allow you to alter the cursor, change the 
video mode, read a character from the screen... the list is 
a long one.

     The DOS functions form something of a buffer between 
you and the BIOS, but they are accessed similarly.  
Naturally, the majority of DOS functions have to do with 
disk access, but there are several other types also.  IBM 
suggests that you use the DOS functions whenever possible 
since the BIOS may be different on later models of the PC 
or even PC compatibles.

     Calling these functions is accomplished at the 
assembly language level through the use of the INT xx 
(interrupt) instruction, where xx is an interrupt number.  
The BIOS sets aside an area of memory for a table of 
addresses when the system is turned on.  Each entry in this 
table is given a number, the interrupt number.

     When you issue the INT instruction you are telling the 
system to save its place and basically jump to the address 
associated with that number.  After the system routine has 
finished it returns to where the program left off.

     Before making such a call you must initialize the 
registers so the interrupt will know exactly what to do and 
how to find the input it requires.  If you don't know what 
a register is you should stop here.  Find an introductory 
text on assembly language and spend enough time with it to 
get comfortable with the concept of registers before 
continuing.



                  Super File Manager - 4 -
DOS CALLS : continued

     How to initialize these registers varies greatly from 
one function to the next, and a comprehensive list is 
beyond the scope of this document.  Please see the 
references section below if you are looking for a good 
place to get this information.

     Now we will proceed with how to use this information 
from Turbo Pascal.  The Turbo procedures that we need to 
know are the following two:

          INTR( Interrupt : integer; var Regs : reg_T);
          
          MSDOS( var Regs : reg_T);

They both require an input of type REG_T.  Here is the best 
way I know of to define this record:

     reg_T = record case boolean of
               true  : (AX,BX,CX,DX,BP,SI,DI,DS,ES,Flags
                                                : integer);
               false : (AL,AH,BL,BH,CL,CH,DL,CH : byte);
             end;

This is known as a variant record and allows us to use the 
registers as full or half registers, just like we would 
from assembly language.

     The MSDOS procedure is really just a slight variation 
of INTR.  Most DOS functions are accessed through interrupt 
number $21 and that is why there is a separate procedure 
for them.  I suspect that the MSDOS procedure really looks 
something like this:

          procedure MSDOS( Regs : reg_T );
          begin
            Intr( $21, Regs );
          end;

Since this relationship has been pointed out I will only 
talk about the INTR procedure for now.  Although redundant, 
the MSDOS procedure does help your code's clarity and 
should be used when appropriate.

     INTR acts in the following manner.  First it saves the 
current registers and then puts the values you specified in 
the REGS record into the registers.  It then issues an INT 
instruction for the interrupt you specified.  Upon return 
Turbo puts the new register values into REGS and restores 
the values it saved to the actual registers.

     After a call to INTR the REGS record will contain the 
values you would expect from the system call.  I think that 
it's time for an example.


                  Super File Manager - 5 -
DOS CALLS : continued


An example of a BIOS call:

     The procedure below will ask BIOS what the current 
     video mode is.  This example shows what is known as a 
     subfunction.  We will be calling interrupt $10, named 
     the BIOS Video I/O interrupt.
     
     This interrupt performs all kinds of video functions, 
     and we may tell it which one we want by specifying a 
     subfunction.  The one we want is number $0F, called 
     Get the Current Video Mode.  The standard for 
     requesting a subfunction is to be put the function's 
     number into register AH.
     
     The only input is loading the subfunction number into 
     Regs.AH.
     
     Output from this interrupt is contained in the the AX 
     and BX registers as follows:
     
          AL = video mode number
                 $00   = black & white 40 columns X 25 rows
                 $01   = color 40 X 25
                 $02   = b&w   80 X 25
                 $03   = color 80 X 25
                 $07   = monochrome text, 80 X 25
               $04-$06,
               $08-$0F = different graphics modes
               
          AH = Number of columns
          
          BH = Active video page (0 if graphics mode)
     
     
     procedure GetMode(var ModeNum : integer );
     var
       Regs : reg_T;
     begin
       with Regs do
       begin
         AH := $0F;
         Intr( $10, Regs );
         ModeNum := AL;
       end;
     end;
     
     
     Since this procedure is just for getting the mode 
     number we don't bother saving the other register 
     values.


                  Super File Manager - 6 -
DOS CALLS : continued


A side note:  Once you start playing around with interrupts 
you may discover what lies behind some of Turbo's 
procedures and functions.  For example Turbo's TEXTMODE 
function apparently calls BIOS interrupt $10, subfunction 
$00 - Set Video Mode.  The Turbo constants used 
(bw40,bw80,c40,c80) for this call correspond to the values 
returned in the above GetMode example.  This implies that 
you could call TEXTMODE with some other number and actually 
set a graphics mode (e.g. $04 for medium-resolution CGA).  
I must admit that I haven't tried it yet, but it seems 
highly likely.

     If you have looked at the SFM source code you may have 
noted that whenever I used the INTR or MSDOS procedures the 
function call was named in a comment to the side.  This is 
a good habit that you should adopt.  I would have done it 
in the above example but these lines aren't wide enough to 
allow such comments.

     For you folks already familiar with using these Turbo 
procedures we're finally getting to something you can use.  
Now we get into those functions that may not always be 
successful.  The most common type of functions where this 
can happen are those accessing a disk, the domain of DOS.

     When using the MSDOS procedure (or INTR with $21) many 
of the functions will not always work.  DOS uses several 
methods for letting us know something went wrong.

     Much of the time the carry flag will be set if there 
was a problem executing one of the DOS functions.  You can 
check this bit (bit 0 of the Flags register) by ANDing the 
flags register with $01 and comparing this to zero:

     if ( (Flags AND $01) <> 0 ) then ERROR else OK;

If there was an error the registers will not contain the 
information you were expecting.  Instead DOS will have 
placed an error code in the AX register.  This error code 
will correspond to a message explaining what went wrong.  
Hopefully the reference you chose for DOS function calls 
has a list of these messages.

     Another method often used is setting the AL register 
equal to $FF if there was an error.  I'm just trying to 
give you the general idea here, you will have to check how 
errors are handled for each function that you use.



                  Super File Manager - 7 -
DOS CALLS : continued


     Well here is the main idea I want to get across for 
this section.  When you design a routine to make a system 
call that may not always be successful you should make it a 
Turbo function.  This way the function result can pass back 
the error code or at least a boolean value for whether or 
not it was successful.

     Then any procedure that calls your function can decide 
what to do with an error on its own.  You may have it abort 
the program, ignore the error, or (more likely) print an 
error message and allow the program to continue.

     When SFM uses this technique it is sometimes two or 
three levels deep in function calls, but the error code is 
just passed back on through until it gets to a routine that 
is designed for handling output.  Then the error message is 
finally printed out.  A pretty good example of this is in 
the ChangePath procedure found at the top of SFMFUNC.INC.

Another sample function:

     We'll write an example that tries to rename 
     "\SFMTECH.TXT" to "\TRASH\BULL.XXX".  We need to use 
     the MSDOS procedure, subfunction $56 - Rename a File.
     
     You may have noticed that the new name contains a 
     different path.  We can do that with this DOS 
     function.  If you look into the SFM source code you 
     will see that the move command uses this DOS function.
     
     If you're just learning this system call stuff you're 
     probably wondering how we're going to fit those 
     strings into the registers.  The answer is - we don't. 
     All DOS expects is the addresses of these two strings, 
     loaded into the DS:DX and ES:DI register pairs 
     respectively.
     
          To set these addresses you need to know a little 
     about two more Turbo procedures: OFS and SEG.  SEG 
     returns the "high" portion of the address of a 
     variable, or the segment.  OFS returns the "low" 
     portion, or offset within that segment.  That's not a 
     very complete description, but it gives you the 
     general idea.
     
     There is one more thing you must note when passing 
     strings to DOS functions.  DOS doesn't recognize 
     Turbo's string structure.  It expects the first 
     character at the address to be the start of the file 
     name and that the string be terminated by a NUL ($00) 
     character.  This is known as an ASCIIZ string.
     

                  Super File Manager - 8 -
RENAME EXAMPLE : continued


     The error codes returned from DOS calls like this 
     often have their own small set of error messages.  
     Those codes returned by this call (in AX) are:
     
          $02 : File not found
          $03 : Path not found
          $05 : Access denied
          $11 : Tried to rename to a different drive
     
     
     function RenameStuff : integer;
     var
       Regs           : reg_T;
       oldstr, newstr : string[80];
     begin
       oldstr := '\SFMTECH.TXT'    + #00;
       newstr := '\TRASH\BULL.XXX' + #00;
       with Regs do
       begin
         AH := $56;
         DS := seg( oldstr[1] );
         DX := ofs( oldstr[1] );
         ES := seg( newstr[1] );
         DI := ofs( newstr[1] );
         MsDos( Regs );
         if (Flags AND $01) <> 0 then
           RenameStuff := AX
         else
           RenameStuff := 0;
       end;
     end;
     
     
     Note that the OFS procedure calls are asking for the 
     offset to the first character in the string 
     (xstr[1]).  This index is not necessary for getting 
     the segment portion of the address, but is done anyway 
     for consistency.

Well that's about it for what I had to say about DOS 
calls.  We'll discuss them again a bit later in the section 
for trapping serious DOS errors.



                  Super File Manager - 9 -
                   >> DOS FUNCTION $32 <<


     If you already have a reference for MS-DOS calls 
you've probably noticed that there are several subfunction 
numbers that are "reserved for DOS."  Ever wonder what 
mystical operations those functions performed?  Well we are 
about to unravel one of those mysteries...

     Glenn Roberts authored an excellent article in PC Tech 
Journal that covers the $32 DOS function.  I must admit 
that SFM would be crippled, slower, and less reliable 
without it.  This function request will return an address 
to an immensely valuable table of diskette parameters.

     Before going into the actual description, I want to 
make sure you know that this is a NON-DOCUMENTED function.  
This means that it may or may not remain in future versions 
of DOS, MicroSoft doesn't guarantee anything.  From the 
information in Roberts' article it is valid for DOS 
versions 2.0 through 3.1 and I can verify that it is still 
there in version 3.2.


              $32 - Get Device Parameter Table


Input:
          AH = $32 for requesting the subfunction
          DL = The drive you want the table for
                 (A=1, B=2,...)

Output:
          DS = Segment of parameter table
          BX = Offset of table

Error:
          AL = $FF if the drive was invalid




                  Super File Manager - 10 -
FUNCTION $32 : continued


Table Contents:

     Byte(s)   Contents
     -------   ---------------------
        0      Assigned disk (A=0, B=1,...)
        1      Same as above, except 0 for RAM disk
       2-3     Bytes per cluster
        4      Sectors per cluster minus 1
        5      Number of heads minus 1
       6-7     Reserved sectors (bootstrap)
        8      Number of FAT copies
       9-10    Maximum number of root directory entries
      11-12    First usable sector
      13-14    Total cluster count plus 1
       15      Sectors per FAT
      16-17    First root directory sector
      18-21    Device driver address
      22-23    Media descriptor
      24-27    Chain to next disk table
      28-29    *Cluster of current working directory
      30-93    *64-byte current working directory
     
               * = valid only for DOS 2.x

If you plan on implementing this table in a Turbo program 
there is a record defined for it in the SFMVARS.INC file.

     Those of you paying attention may have noticed the 
above inconsistency for numbering the drives.  DOS is 
pretty poor on this count.  Notice that when you call 
function $32 you ask for a drive's table according to A=1, 
B=2... When information is returned in the table the drives 
are numbered A=0, B=1...  Keep this in mind when using DOS 
calls and be especially careful about specifying which 
drive you want to write to.

     When using this table remember that you are looking 
directly into the table that DOS uses.  Changing values in 
this table may affect the way DOS accesses the disk, which 
would not be good if it happened to want to do a write 
operation.

     The only reason I can think of for wanting to change 
these parameters would be during a format function.  SFM 
has no such function, but I did have one started before it 
became too difficult to implement properly.  I found that 
in some cases this table had to be altered in order to 
format diskette tracks.  I don't feel qualified to give any 
further discussion on formatting (I had a good start 
though, until I tried formatting one of the AT's high 
density drives at work).

                  Super File Manager - 11 -
                >> ABSOLUTE DISK ACCESSES <<


     Now I shall go into the Turbo INLINE command a bit.  
It seems that the best way to read from or write directly 
to diskette sectors would be to use the corresponding DOS 
interrupts.  There is something of a problem here though.  
For some unknown reason the DOS interrupts $25, Absolute 
Disk Read and $26, Absolute Disk Write both leave a copy of 
the Flags register on the stack after returning from the 
interrupt.

     DOS apparently does a no-no.  It seems to use the 
wrong type of return from an interrupt handler.  Something 
I didn't mention above is exactly how the INT operation 
works.  Well, it puts a copy of the Flags register on the 
stack, followed by two words for the return address.  
Whenever an interrupt handler is designed there is supposed 
to be an IRET instruction for returning to the calling 
program.  The IRET, of course, loads the return address and 
then restores the Flags register before making the jump 
back.

     DOS, however, seems to use the normal RET instruction 
to return from interrupts $25 and $26.  This means that the 
Flags don't get popped unless the calling procedure does so 
itself after the INT call.

     From the assembly language level this is just an 
annoying little quirk.  In Turbo Pascal the quirk develops 
into one of those things people buy Preparation H to 
remedy.  The INTR procedure is not designed to handle these 
special cases and those Flags left sitting on the stack 
wreak havoc with the system, often causing a lockup.

     The only ways to fix this problem from within Turbo is 
to write an external subroutine or resort to machine 
language using the INLINE command.  I prefer INLINE code 
since it doesn't require that you keep track of a separate 
binary file.  This command is used in the following manner:

     Inline( $xx/$xx ... /$xx );

That's right, I didn't mistype.  You must enter the actual 
machine codes (hex numbers) for Turbo to accept INLINE 
input.  The only break Turbo offers you is that you can use 
variable names when you need to, so you don't have to know 
their exact address (which isn't possible anyway).

     Fortunately there are alternatives to being a machine 
code wizard.  There are programs available that take small 
files of pseudo-assembly code and do all of the machine 
code conversions for you.  The one I use is listed below in 
the references section.


                  Super File Manager - 12 -
ABSOLUTE DISK ACCESS : continued

     There are still minor drawbacks even with these 
utilities.  Because such programs must interface with Turbo 
they often use a variation of assembly language.  This 
means learning a new dialect even if you are already 
familiar with assembly.  On the brighter side, alterations 
aren't too great and don't take long to master.

     An example is not presented here since both interrupts 
that have this problem are already written as part of SFM.  
They are two separate procedures and can be found in the 
SFMDOS.INC file.  Their names are LoadSectors and 
WriteSectors.

     You may also find examples of INLINE code below when 
we discuss interrupt handlers.


                  Super File Manager - 13 -
                 >> TRAPPING TURBO ERRORS <<


     The method used to trap Turbo run-time errors is 
really quite simple.  There is a Turbo variable named 
ERRORPTR that you can change to point to a procedure of 
your own.  If your error routine is called AbortOnError you 
can issue this statement

          ErrorPtr := ofs( AbortOnError );

near the beginning of your program and when an error occurs 
Turbo jumps there instead of jumping to its normal error 
handler, expecting you to handle the error and then exit 
the program.  You won't find this in the Turbo manual, 
instead it was put into a READ.ME file on one of the 
compiler diskettes.  I hope I never meet any of the 
manual's authors since I couldn't afford the lawsuit I'd 
get after punching them in the nose.

     The restrictions placed on the error handling 
procedure are that it must have two integers as its input 
and that it contain a HALT command since the program must 
be terminated.  The following procedure layout is correct:

          procedure AbortOnError( ErrNum, Addr : integer );
          begin
            { Your code goes here }
            halt;
          end;

Within this procedure you can do whatever you wish, except 
"end" it normally.  You can even call another procedure.  
The input parameters contain the following:

          Hi( ErrNum ) = The error type
                           $00 = user break
                           $01 = I/O error
                           $02 = run-time error
          Lo( ErrNum ) = The error code
          Addr         = Where the program was when
                           the error occurred.

     SFM contains just such an error routine.  While it 
isn't likely that it will be called, it is still there just 
in case.  The procedure is found towards the top of the 
SFMSCRN.INC file (named AbortOnError).

     The Addr value may be converted to hex and entered in 
the Find run-time error option from the Turbo Editor.  This 
will allow Turbo to place you where the error occurred in 
relation to the source code, and you can take it from 
there.


                  Super File Manager - 14 -
TRAPPING TURBO ERRORS : continued


     Note that the main reason that SFM has its own error 
handler is because it must perform several operations to 
return the system to normal before exiting.  You see, SFM 
uses custom interrupt handlers and these interrupts must be 
restored to normal or DOS will have a fit.  The next 
section will describe these interrupt handlers.

     As a final note there is one special situation you may 
not have considered yet.  If the program is terminated by a 
heap/stack collision (error $FF) you will not be able to 
call any subprograms from within your error handler.  To 
alleviate this problem you can provide your program with a 
means of freeing up some heap space.

     First you must declare a variable that can contain a 
pointer address.  The one used by SFM is:

          HeapStart : ^integer;

It doesn't matter much what type of variable HeapStart 
points to, but a pointer to an integer is a common method.  
Near the beginning of your program you simply issue a MARK 
procedure call:

          Mark( HeapStart );

Now HeapStart points at a memory location on the heap.  
Later in the AbortOnError procedure you just issue a 
RELEASE call:

          Release( HeapStart );

Once this is done there will be some free space in memory 
again and you can call other procedures if you like.  You 
must remember that the call to RELEASE will clear out 
everything put on the heap after the last MARK call.  This 
means that any dynamic variables you were using are now 
gone.

     In case you were wondering how the program's heap and 
stack are related, here is an explanation.  The heap and 
stack are both dynamic in nature.  This means that the 
amount of memory used by either can grow or diminish during 
the program execution.  It then makes sense that they share 
the same area of memory.



                  Super File Manager - 15 -
TRAPPING TURBO ERRORS : continued


     Let's take a look at where Turbo puts a program in 
memory when it is loaded.  First Turbo loads in the 
program's code segment, containing all of the procedures 
and typed constants defined.  Then it sets up the program's 
data segment, which consists of the global variables.  All 
of the memory left over becomes a playground for the stack 
and heap.

     Now let's look closer at the heap/stack space.  Turbo 
decides that the heap should start right after the the data 
segment.  Whenever more space is required for the heap 
Turbo designates another chunk of memory to it (NEW or 
GETMEM procedure calls).  The stack, on the other hand, 
starts at the outer limit of the program's memory.  
Whenever it needs more memory Turbo takes another chunk 
from that end (procedure calls and their local variables).

     When the program is finished with memory on the heap 
or stack it can be returned to the system.  This is 
accomplished through DISPOSE or RELEASE procedures for the 
heap space.  All that is required to restore stack space is 
the return from the procedure that allocated the memory.

     Turbo keeps track of how far out the heap and stack 
are extended by setting a pointer to their outermost memory 
location.  Should these pointers ever cross you will 
receive a heap/stack collision error message ($FF).  This 
means that the heap and stack were trying to use the same 
memory area for their data at the same time.

     One likely cause for such a collision would be to ask 
Turbo to give you a large chunk of memory using a GETMEM 
call.  This is the type of error possible with SFM.  
Another, not so obvious cause, is when a procedure or 
function sets up a large array in its local variable 
declarations.  A third example would be a recursive routine 
that calls itself many times.  Since Turbo must save quite 
a bit of information each time a procedure calls itself you 
may eventually run out of memory.


                  Super File Manager - 16 -
                  >> INTERRUPT HANDLERS <<


     Here is another use for Inline code.  You can design 
your own interrupt handlers.  This is how SFM traps all of 
the DOS critical errors and avoids the normal DOS message 
"Abort, Retry, or Ignore?"  Writing such a routine is no 
small task, but I will give you a general outline.

     The INT24 function in the SFMOTHER.INC file is such a 
routine and INT10 from SFMVARS.INC is another example.  The 
first routine takes over DOS interrupt $24, the Critical 
Error Handler Address.  The second takes over BIOS 
interrupt $10, the Video I/O interrupt.

     The INT24 routines were developed by Bela Lubkin and 
published in an article in Programmer's Journal.

     The first thing to look at when designing an interrupt 
handler is how the INT and IRET assembly instructions 
work.  These are covered well enough above in the absolute 
disk read/write section.

     Next we must take Turbo into account.  Turbo issues 
the following few lines of code at the beginning of every 
procedure or function:

          PUSH    BP
          MOV     BP, SP
          PUSH    SP

These instructions set the BP register to a location that 
can be used to index the variables in the subroutine's 
declaration.  Your major concern with these lines should be 
how to put things back in order before returning from your 
interrupt handler:

          MOV     SP, BP  ; code to exit
          POP     BP
          IRET

     This exit code must be inserted at the end of your 
interrupt handler to successfully return to the calling 
program.  You can't let your interrupt routine exit 
normally since the IRET instruction must be used.  An 
exception to this rule would be DOS interrupts $25 and $26 
for reasons explained in that section.



                  Super File Manager - 17 -
INTERRUPT HANDLERS : continued

     If you were wondering why there are two PUSH 
instructions inserted by Turbo but we only POP once I have 
the answer, though it did have me confused for a bit.  
Loading the SP register into BP effectively saves stack 
pointer right after the PUSH BP instruction.  The line 
"MOV  SP, BP" restores this value and has the same effect 
as popping SP (and any local variables that were declared) 
from the stack.  This means that you must not change the BP 
register, unless you take special precautions.

     In addition to this exit code you must save and 
restore all registers that are used by your routines.  
Exceptions to this are those registers that are used for 
output from the interrupt.  Before designing an interrupt 
handler you should know exactly which registers these are 
and what their output is supposed to look like.

     That covers the basics of the interrupt handling 
routine, now we must examine what is required to make the 
system use that code.  SFM's procedures for doing this are 
called INT24ON and INT10ON for the indicated interrupt 
handlers.

     Recalling that the BIOS sets up a table in memory for 
the addresses of the interrupt handlers (a vector table), 
it seems obvious that all we need to do is change the 
vector that corresponds to our interrupt.  Rather than 
attempting to locate and change this entry ourselves we 
will let DOS do the dirty work for us.

     DOS has a pair of subfunctions that will either get or 
set these vectors for us.  The function numbers are $35 for 
getting the address and $25 for setting it.  The input for 
this function call is:

          AH     = $35 or $25 as needed
          AL     = interrupt to get/set
          DS:DX  = new address when setting
          
     output:
          ES:BX  = current address when getting

Notice that the ES:BX register pair will contain the 
address that the vector currently points to after function 
$35 is called.  There is no output for the $25 function, 
but the vector will have been changed (even if the address 
points to an invalid memory location).



                  Super File Manager - 18 -
INTERRUPT HANDLERS : continued

     Before changing the interrupt handler you should save 
the old address.  This is done by setting aside a four byte 
space to contain the old vector.  That way the interrupt 
may be restored before the program exits.

An example:

     This interrupt handler will prevent the video screen 
     from being printed while the program is running.  It 
     is being presented as an entire program so you can see 
     everything that is required.


program NoPrint;

type
  Reg_T    = record case boolean of
               true  : (AX,BX,CX,DX,BP,SI,DI,DS,ES,Flags : integer);
               false : (AL,AH,BL,BH,CL,CH,DL,DH          : byte);
             end;

const
  DataSeg     : integer  =  0;
  OldInt05    : array[0..1] of integer  = (0,0);

procedure Int05;
begin
  {
     PUSH    BP      ; Done by Turbo
     MOV     BP, SP
     PUSH    SP
  }
  Inline(
    $50                    {        PUSH    AX  ; Save all regs   }
    /$53                   {        PUSH    BX  ;   so we can use }
    /$51                   {        PUSH    CX  ;   Turbo code    }
    /$52                   {        PUSH    DX                    }
    /$57                   {        PUSH    DI                    }
    /$56                   {        PUSH    SI                    }
    /$06                   {        PUSH    ES                    }
    /$1E                   {        PUSH    DS                    }
                           {        ;                             }
                           {        Set DS so we can use global   }
                           {          variables and the Turbo     }
                           {          write procedures.}          }
                           {        ;                             }
    /$2E/$A1/>DATASEG      {CS:     MOV     AX, [>DataSeg]        }
    /$8E/$D8               {        MOV     DS, AX                }
  );

  writeln( 'Sorry, can''t print the screen right now.' );
  write( #7 );


                  Super File Manager - 19 -
NOPRINT : continued

  InLine(
    $1F                    {        POP     DS  ; Restore all regs}
    /$07                   {        POP     ES                    }
    /$5E                   {        POP     SI                    }
    /$5F                   {        POP     DI                    }
    /$5A                   {        POP     DX                    }
    /$59                   {        POP     CX                    }
    /$5B                   {        POP     BX                    }
    /$58                   {        POP     AX                    }
                           {        ;                             }
    /$89/$EC               {        MOV     SP, BP   ; Exit code  }
    /$5D                   {        POP     BP                    }
    /$CF                   {        IRET                          }
  );
end;

procedure Int05ON;
var
  Regs : reg_T;
begin
  DataSeg := Dseg;
  with Regs do
  begin
    AH := $35;    { DOS function $35 - Get Interrupt Vector Address }
    AL := $05;    {      getting $05 - Print Screen }
    MsDos( Regs );
    OldInt05[1] := ES;
    OldInt05[0] := BX;
    AH := $25;    { DOS function $25 - Set Interrupt Vector Address }
    AL := $05;    {      setting $05 - Print Screen }
    DS := Cseg;
    DX := ofs( Int05 );
    MsDos( Regs );
  end;
end;

procedure Int05OFF;
var
  Regs : reg_T;
begin
  with Regs do
  begin
    AH := $25;    { DOS function $25 - Set Interrupt Vector Address }
    AL := $05;    {      setting $05 - Print Screen }
    DS := OldInt05[1];
    DX := OldInt05[0];
    MsDos( Regs );
  end;
end;



                  Super File Manager - 20 -
NOPRINT : continued

var
  ch : char;

begin
  DataSeg := Dseg;
  Int05ON;
  writeln( 'Hit the SPACE BAR to exit.' );
  writeln;
  repeat
    read( kbd, ch );
  until ch = #32;
  Int05OFF;
end.


As you can see there are three main parts to setting up an 
interrupt handler:  the actual handler code, the procedure 
to save the old vector and set yours, and finally the 
procedure to restore the old vector.  In addition to this 
you must set aside a bit of space to store the old vector 
and perhaps the data segment also.

     In this example the data segment was saved in order to 
allow us to use the Turbo write procedures.  I did some 
checking with DEBUG and it seems that Turbo keeps some 
information used by the write procedure in the program's 
data segment.  Restoring the DS register also allows your 
program to access the global variables.

     Even if you don't fully understand what I have 
presented here you can still used the above example as a 
template to create your own interrupt handlers.  All you 
need to do is change all occurrences of "05" into whatever 
interrupt you wish to customize.

     Now I must clarify that last statement a bit.  You 
must be careful what interrupt vectors you are changing.  
The majority of interrupt routines used by the BIOS or DOS 
have several subfunctions.  If you take control of such a 
vector you will either have to emulate all of its functions 
or be selective about which functions you are controlling.

     The INT10 procedure found in SFMVARS.INC is one of 
these cases.  Emulating all of the subfunctions of the BIOS 
Video Interrupt would take an incredible amount of code and 
essentially be wasted space.  For this reason I chose to 
compare the value in the AH register and let the BIOS have 
all function requests except the one that I wanted to 
alter, the $0E function (Write Character as a Teletype).


                  Super File Manager - 21 -
INTERRUPT HANDLERS : continued

     The transfer of control to the BIOS is accomplished by 
making a FAR CALL to the old vector address we have taken 
the precaution of saving.  INT10 then issues an IRET right 
away in order to return to the calling procedure.

     I won't cover this in any more detail since the 
example can be found in the SFM code if you wish to pursue 
the matter.

                  Super File Manager - 22 -
             >> TRAPPING DOS CRITICAL ERRORS <<


     Now that you have some idea of how to design an 
interrupt handler we can continue with a practical use.  
The INT24 interrupt handler mentioned above has become one 
of my most useful procedures and an addition to any program 
of conseqence.

     The DOS interrupt $24 is the one that prints that 
nasty "Abort, Retry, Ignore?" message that has blemished so 
many otherwise well designed display screens.  By taking 
control of this vector we may stop DOS from printing a 
message and print our own error message at our leisure.

     To make use of this routine from Turbo we must first 
take note of a special compiler directive.  This is the "I" 
compiler directive used for turning Turbo's DOS error 
checking on, {$I+}, or off, {$I-}.  The default is on, 
which allows DOS to print the error message mentioned 
above.

     When we turn this error checking off we assume 
responsibility for DOS errors.  If we don't do something 
about the error before the next DOS function call the 
program will abort whether or not Turbo was performing 
error checking.  This means that even if we intend to 
ignore the error we must at least check the error code 
using INT24RESULT (this also clears the error code).

     Using this error checking with the standard Turbo 
function IORESULT will catch most minor errors.  Such 
errors include messages like "File does not exist" or "File 
not Open."  It does not allow for more critical errors such 
as an unformatted disk or an open diskette drive.

     To use the IORESULT function you simply turn off the 
Turbo error checking before an operation that accesses the 
disk and then turn it back on right away.  Then you may 
access the IORESULT function to get an error code.  If this 
code is zero then no error occurred.  Here is a small 
example for changing the current directory:

          {$I-}
          chdir( newpath );
          {$I+}
          ErrCode := IOresult;

In this example NEWPATH is a string variable that contains 
the path we are attempting to change to.  ERRCODE is an 
integer variable that will hold the code returned by 
IORESULT.  This example could become the body of a DOS call 
function that follows the outline given in the first 
section of this documentation.


                  Super File Manager - 23 -
DOS CRITICAL ERRORS : continued

     Now we may extend this error checking to include 
errors that are considered critical to DOS.  If you 
implement the INT24 set of procedures you may use 
INT24RESULT in the same manner as you would normally use 
IORESULT.  The difference is that the new function result 
will actually contain two error codes.  There is one placed 
in both the high and low bytes of the integer result.  To 
see how to extract these two codes take a look into the 
ErrorMessage routine found in the SFMOTHER.INC file.

     Keeping this in mind we can now take the final step in 
designing a break-proof DOS call function that returns a 
comprehensive error code.  I think an example is the best 
way to illustrate this.  To avoid setting up an entirely 
new example we will just enhance the renaming function from 
the DOS calls section above:


     function RenameStuff : integer;
     var
       Regs           : reg_T;
       ErrCode        : integer;
       oldstr, newstr : string[80];
     begin
       oldstr := '\SFMTECH.TXT'    + #00;
       newstr := '\TRASH\BULL.XXX' + #00;
       with Regs do
       begin
         AH := $56;
         DS := seg( oldstr[1] );
         DX := ofs( oldstr[1] );
         ES := seg( newstr[1] );
         DI := ofs( newstr[1] );
         {$I-}
         MsDos( Regs );
         {$I+}
         ErrCode := Int24result;
         if ErrCode <> 0 then
           RenameStuff := ErrCode
         else
         begin
           if (Flags AND $01) <> 0 then
             RenameStuff := (AX SHL 8) OR $8000
           else
             RenameStuff := 0;
         end;
       end;
     end;




                  Super File Manager - 24 -
     Looking at this code may be a bit confusing, but it 
covers all three levels of DOS error messages.  The first 
two are taken care of by the INT24RESULT function which 
combines both Turbo and DOS error messages.  The third type 
of error is that mentioned in the DOS calls section.  This 
error code has been altered a bit in order to create an 
integer function result that can be sent to the 
ErrorMessage procedure.

     The alteration is this:  error codes that are returned 
by the function call itself are shifted left so the occupy 
the high byte along with the DOS critical error codes.  
Then the high bit is set to let ErrorMessage know that this 
error code is one returned from a DOS function call rather 
than the DOS critical error handler.

     Again, even if you don't fully understand this process 
you can still take the procedures and place them into your 
own program.  The only place you will need to make changes 
is in ErrorMessage, in order to make it fit your I/O 
routines and also to restore the error messages that I 
commented out.  You may then use INT24RESULT just as you 
would use the standard Turbo IORESULT.


                  Super File Manager - 25 -
                     >> SFM OVERVIEW <<


     Now that we have covered those techniques that I 
thought needed more background, we can cover Super File 
Manager's structure in specific.

     The first thing to note about SFM will be how it is 
broken down into include files:

     SFM.PAS        Naturally this is the main file.  It 
                    contains the initialization routines 
                    for setting up SFM.  It also contains 
                    the routines for the first level of 
                    I/O, the command menus.
                      
     sfmVARS.inc    This file contains all of the types 
                    that are used by SFM as well as the 
                    majority of global variables and 
                    constants.  Also included are a few 
                    routines that needed to be near the 
                    beginning, but didn't belong in the 
                    sfmOTHER.inc file.
                      
     sfmOTHER.inc   Here we have the two sets of routines 
                    that I borrowed from other sources.  
                    These are DISPLAY and the INT24 
                    routines.
                      
     sfmSCRN.inc    Here is the file for the majority of 
                    low-level I/O routines and help 
                    displays.  These include everything 
                    from setting the video mode on up to a 
                    custom string input function.
                      
     sfmDOS.inc     This file contains the low-level DOS 
                    function calls that generally don't 
                    perform much screen I/O.  Most of these 
                    functions return error codes as 
                    described in previous sections.
                      
     sfmFUNC.inc    This last include file contains the 
                    routines that merge the SCRN, DOS and 
                    SFM files.  In other words, they are 
                    called from SFM.PAS and use the 
                    routines in sfmSCRN.inc to set up calls 
                    to the routines in sfmDOS.inc (which do 
                    all the actual work).

Of course there are procedures in each file that may belong 
somewhere else, but in some cases this is not possible.

                  Super File Manager - 26 -
SFM : continued

     Actually, that breakdown gives a pretty good feel for 
how control is passed around within SFM.  We always start 
in SFM.PAS and the selection of a command usually sends us 
off to sfmFUNC.inc.  From there things jump around 
according to the function.

     As mentioned above, nearly all error checking is 
returned through function results and then the codes are 
sent off to the ErrorMessage routine.  Exceptions to this 
are warning messages or those errors specific to SFM (such 
as "Windows must have different paths").

     Within the sfmSCRN.inc file there are two error 
routines that bear special explanations.  These are the 
AbortProgram and AbortOnError routines.  Since we are using 
custom interrupt handlers these must be turned off before 
the program is exited, including if SFM happens to be 
halted by an error.

     For this reason all exits of the program are routed 
through AbortProgram, with the exception of normal 
termination.  This way it is less likely that we will 
forget to turn off an interrupt handler or to turn the 
cursor back on.  An example of an error SFM does not handle 
is the case of a bad file allocation table.  Rather than 
take the chance of making things worse SFM will quit.

     The AbortOnError routine sets up a special error 
message that takes the place of the normal Turbo 
termination message.  It displays the same information in a 
slightly different format and also gives us the chance to 
call AbortProgram in order to return the system to normal 
as explained above.

     If you want to see AbortOnError in action just go into 
the SFM source code and change the line containing "{$C-}" 
to "{$C+}".  Then run SFM normally and type Ctrl-Break.  
You will then see the AbortOnError message screen.

     The address displayed here can then be entered in the 
Turbo "Find run-time error" option from the editor.  This 
allows you to find out where SFM was when you pressed the 
Ctrl-Break key sequence.



                  Super File Manager - 27 -
               COMPILER DIRECTIVES USED BY SFM


     Super File Manager uses a few of the Turbo compiler 
directives.  Setting the "C-" option is done simply to 
speed up screen displays and allow the use of the keyboard 
buffer.  The "I-" and "I+" compiler directives are 
described above under the DOS critical errors section.

     A less known compiler directive is "K-".  This 
directive tells Turbo to stop checking for a heap/stack 
collision whenever a procedure or function is called.  
While this is not always a good idea, it is done here 
because it saves SFM about five kilobytes of code space.  
We only take back about 500 bytes of this when we perform 
our own stack checking where it is needed.

     SFM performs stack checking mostly by making a call to 
MemoryAvail to find the amount of free space before issuing 
a GETMEM call.  The function MemoryAvail makes a call to 
the standard Turbo function MaxAvail and then uses this 
value to calculate the number of free bytes.  Before 
returning this number, SFM subtracts the number of bytes 
specified by the constant MinStack in order to provide a 
safety margin for procedure calls.


                      THE COPY ROUTINES


     The most frequently used procedure in SFM has to be 
CopyEntries.  This is the routine that performs the actual 
copy operations.  Therefore it deserves a bit of special 
attention.

     The first item of interest here is the BUFFER array.  
This array is made up of a small record that contains 
information about the file being copied.  The address field 
is a pointer to the start of the buffer.  The next field 
contains the index in the source directory that the buffer 
belongs to.  The size is simply how big this buffer is, as 
an unsigned integer.  The last field tells whether or not 
the file was too large to fit into one buffer (files over 
64K) and that there is more to follow.

     Using this array of BUFFER records we may now begin a 
copy operation.  The first action is to open the file to 
read from and then allocate enough memory for that file.  
The OPENFILE procedure uses the newer DOS method for files, 
known as file handles.


                  Super File Manager - 28 -
COPYING FILES : continued

     If the file is too large for one buffer, or we run out 
of heap space, the MORE flag is set.  This is repeated 
until we run out of memory, files, or buffers.  Note that 
if we stop reading from a file before all of it is loaded 
the RHANDLE (read-handle) is not reset to zero.  This is 
our method for keeping track of whether or not part of a 
file has already been copied when we start the next read 
pass.  If a file is completely loaded then we call 
CLOSEFILE with RHANDLE, which also sets it to zero.

     After we have read in as much as possible we must then 
start writing back to the destination path.  We keep track 
of whether or not part of the file was already written by 
using the same method as with the read-handle.  If WHANDLE 
is set to zero then we must use CREATEFILE to make the new 
file.  Note that this function will fail if the file name 
already exists with the read-only attribute set, otherwise 
it will overwrite an existing file of the same name. If 
there is an error we allow the user the option of 
continuing with the next marked file or trying again.

     If the disk becomes full there are two possibilities.  
If the disk is in a floppy drive we can allow the user to 
continue by making a call to the ChangeCopyDisk procedure, 
otherwise we must abort the function.

     After a change of disks SFM will try to continue where 
it left off.  There are two cases where it will have to 
back up a bit and reload files that were already read in 
once before the disk change.

     One case is when the clear disk option has been used.  
The call to CLEARFAT caused SFM to set up a temporary disk 
transfer area in the program's heap space.  This transfer 
area occupies the same memory area as the copy buffers and 
will have overwritten the information there.  Therefore we 
must reload the files that were in the buffers.  Note that 
ChangeCopyDisk uses the SPLIT flag to indicate that a disk 
has been cleared.

     The second case offers insight into why I chose the 
name for the SPLIT flag.  This flag keeps an eye on the 
following special case:  



                  Super File Manager - 29 -
     While we are reading files into memory we hit the 
     memory or buffer limit and the current file is split 
     between two buffers.  Then after a successful write 
     pass we return to the read procedure and pick up 
     reading that file where we left off.  Let's say that 
     on the next write pass we run out of diskette space 
     while writing that file.  In this case the file is 
     deleted from the destination and we allow the user to 
     change disks.  Now we are ready to continue, but we 
     don't have the entire file in memory anymore.  We can 
     tell because the SPLIT flag is still true, so we must 
     return to the read pass and start over with that file.

That covers a not so obvious, but dangerous possibility.

     As a final step in the WriteTo procedure we must close 
the file handle and set the correct date and time.  When we 
close a file that was open for writing, DOS updates the 
time and date to the current system values.  To update 
these fields we are required to reopen the file for 
reading, use the appropriate DOS function for the update, 
and then close the file again.  The time and date we use is 
retrieved from the entry stored in memory for the source 
directory.


                       MENU TWO NOTES


     When menu two is entered SFM loads a copy of the 
directory into memory.  All of the functions from this menu 
will act on this copy and won't be updated on the disk 
until the update disk function is used.

     In addition to a copy of the current directory we also 
keep a copy of the file allocation table.  This FAT is only 
used for recovering files when using the undelete 
function.  If the disk is updated after an undelete 
operation then this FAT must also be written back to disk.


                      REARRANGING FILES


     While in menu two the directory may be sorted or 
rearranged using the pick up file function.  Both methods 
work only on the copy in memory and don't become permanent 
until updated.

     The Sort functions are fairly easy to understand.  
Just for the record, the sort used is an insertion sort.  
The major factors in choosing the type of sort used were 
whether or not it was stable and how difficult it would be 
to alter later.


                  Super File Manager - 30 -
REARRANGING FILES : continued


     You may notice the array of real numbers that gets set 
up for a couple of the sort fields.  This allows us to set 
up the keys for the directory once and thus avoid 
recalculating the keys every time we need them.  The time 
for one such pass is nothing compared to the exponential 
running time of the insertion sort.

     As a final note on the sort routines, I'm not sure why 
the character array comparisons work.  I thought the 
comparisons would have to be done on Turbo strings, but 
they seem to work fine with arrays of characters.  I 
couldn't find anything in the Turbo reference manual to 
either support or deny this.

     Now, about the procedures for picking up and dropping 
a directory entry.  If you look at them you'll see that I 
basically copied the routines that allow movement around 
the directory windows and modified them to suit this 
function.  I don't like this duplication of code, but lack 
the time to fix the problem.  Other that that, the 
procedures are fairly easy to understand.


                    SINGLE FLOPPY SYSTEMS


     Support has been provided for systems having a single 
floppy drive.  Persons with such systems may occasionally 
have use for a program such as SFM, but few programs 
provide support for them.  SFM does so by trapping the BIOS 
interrupt 10 (Video I/O).

     You may ask what this interrupt has to do with DOS and 
the answer is - not much.  However, DOS has a neat ability 
to allow a user to use a single drive as two drives.  When 
DOS wants access to the disk that is supposed to be in the 
other drive it asks the user to change disks.

     This works fine from the DOS command prompt, but not 
so well from within a program.  DOS uses the Write 
Character as Teletype BIOS Video function request to print 
its message.  When the bottom of the screen is reached this 
function scrolls the entire screen, without paying 
attention to any windows you may have specified.  When this 
happens the current display screen is ruined.



                  Super File Manager - 31 -
SINGLE FLOPPY SYSTEMS : continued


     The INT10 procedure in SFMVARS.INC handles this in a 
unique manner.  It takes over the BIOS interrupt 10 and 
when it is asked to write a character it uses the Turbo 
WRITE procedure instead.  This forces the message printed 
to conform to the current window specified.

     Granted, this is not the ideal way to handle the 
problem, but I don't know the DOS interrupt that handles 
the requests to change disks.  For this reason, I had to 
rely on a hunch that DOS used the specified BIOS interrupt 
to write its message.  What I ended up with is crude, but 
it works just fine.

                  Super File Manager - 32 -
                 >> SUGGESTED REFERENCES <<


     Here is a list of the books that I found useful in 
designing SFM.  The list isn't in a standard bibliography 
format, but then this isn't a Technical Writing class 
either.


Programmer's Reference Manual for IBM Personal Computers
     Steven Armbrust & Ted Forgeron
     1986: Dow Jones - Irwin
     
     This was by far the most used of my references.  It is 
     an excellent text that contains no-nonsense facts 
     about the majority of DOS and BIOS interrupts.  In 
     many cases there are short example programs written in 
     Assembly Language, Pascal and C (examples in this 
     document are not plagiarized, they are original).


The Complete Guide to IBM PC AT Assembly Language
     Harley Hahn
     1987: Scott, Foresman and Company
     
     Not used extensively for SFM, but is by far the best 
     Assembly Language book that I have seen.  It serves as 
     both an introductory text and reference manual.  You 
     don't have to own an AT to make use of it (my system 
     is an XT compatible).


The MS-DOS Handbook
     Richard Allen King
     1986: SYBEX, Inc.
     
     This is a pretty good reference, but I don't use it 
     nearly as much as the first book listed above.


That about wraps up my favorite references.  I have used 
others, but not enough to bother listing.

                  Super File Manager - 33 -
                    >> ACKNOWLEDGMENTS <<


     Here are some of the other sources that I found 
useful.


Finding Disk Parameters
     Glenn F. Roberts
     May 1986: PC Tech Journal
     Article covering the DOS function $32 request.


Dipping Into Directories
     Ted Mirecki
     February 1985: PC Tech Journal
     Article on some ideas for loading subdirectories into 
     memory and altering them.


INLINE Interrupts
     Charles C. Edwards
     December 1985: PC Tech Journal
     Article that contains some information on writing 
     interrupt handlers in Turbo Pascal.  It clarifies a 
     few problems with the Turbo documentation.


INT24
     Bela Lubkin
     May/June 1986: Programmer's Journal
     Article that contains the INT24 procedures used in 
     SFM.


DISPLAY.ARC
     Keith G. Chuvala
     File containing the DISPLAY procedure used in SFM.  It 
     also includes a documentation file.


INLINE.ARC
     L. David Baldwin
     1985,86
     File containing the inline assembler that I use and 
     another utility to turn inline code into its Assembly 
     Language equivalent.  Documentation is also included.
