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 ; file name O ; Options menu C ; Compile to COM file Q ; Quit options menu C ; Compile Q ; Quit Turbo SFM ; 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.