(********************************************************************)
(*                           UNIT DRIVExx                           *)
(*                                                                  *)
(*                           Version 3.13                           *)
(*                                                                  *)
(*         Copyright (C) 1991, 1992 by NativSoft Computing          *)
(*                                                                  *)
(*           Portions Copyright (C) 1992 by PDG Software            *)
(*                                                                  *)
(*                         1155 College Ave                         *)
(*                         Adrian, MI 49221                         *)
(*                          (517) 265-6080                          *)
(*                         CIS [71160,1045]                         *)
(*                                                                  *)
(*                Written by:  Charles B. Little, Ph.D.             *)
(*                   Version:  3.13                                 *)
(*             Revision Date:  28 March 1992                        *)
(*                  Language:  Turbo Pascal v6                      *)
(*                                                                  *)
(*                       ALL RIGHTS RESERVED                        *)
(********************************************************************)


I.         INTRODUCTION:

DRIVExx is a collection of tools, written in Turbo Pascal v6, for
dealing with block devices in a DOS environment.  To the best of our
knowledge, no such tools exist in any "professional" programming
toolboxes, in any programming language.  The tools available here will
allow any Pascal programmer who uses disk I/O to gain that extra
measure of control over his or her application.  At the very least
they will have the ability to prevent access of a phantom drive so
their nice user interface won't get overwritten by non-interceptable
DOS messages like "Insert diskette for drive B: and ..."

No pretense is made that DRIVExx is flawless and can't be confused by
a determined user who knows all the SUBST, JOIN and ASSIGN tricks, but
it seems to work for the things most programmers need.  And just about
everything you'd want your applications to know about drives is
available WITHOUT ACCESSING THEM!  The only exceptions to hitting
drives are functions GETDPB and DiskWasChanged:  GETDPB will hit the
disk to read its boot sector, BUT ONLY IF YOU TELL IT TO; and
DiskWasChanged will access the disk only if the drive supports
changeline detection.  And even in these two cases, no unrecoverable
error will result if the removable drive doesn't have a disk in it.

Much of the required information is available only in undocumented DOS
functions and data structures.  Wherever a documented function will
return the same information, however, the undocumented function is
still used as a redundancy check.  Indeed, the source code for DRIVExx
is probably at least 50% longer than need be due to extensive
interlocking redundancy checking.

Most of the features of DRIVExx are demonstrated in the demo program
DRVDEMO.PAS.  The major exceptions are functions ReadDiskSectors and
WriteDiskSectors, and function MakeDriveActive, whose description
below should make clear why it is impractical to include it in
DRVDEMO.

DRIVExx is currently available only for TP6.  We made the decision to
use BASM for all assembler modules to eliminate the need to distribute
OBJ files.  This means that all you need to use DRIVExx is
DRIVEXX.TPU.  Consideration will be given to releasing a TP5 version -
or even a C version - if enough users express a desire.



II.        CONDITIONS:

DRIVExx.TPU is not free.  IT IS SHAREWARE AND IT IS COPYRIGHTED.  If
you use it you should pay for it -- that's how shareware works. 
However, you only pay the registration fee.  Absolutely no royalties
are expected or will be sought if you use it in any commercial
program.  It comes with the usual caveats (spelled out in the
REGISTRATION/ORDER FORM) about suitability, limits of liability, etc.

The registration fee as of 28 March 1992 is $15.  Fees for DRIVExx
should be accompanied by a completed REGISTRATION/ORDER FORM, found at
the end of this file.  A non-disclosure agreement with a major vendor
will make it impossible to release the ENTIRE source code as is.  A
working, compilable version - EXCLUDING the small amount of code
covered by the NDA - may be made available.  If so, we will tell you
where to find the "forbidden" information yourself in a book called
"PC Interrupts"; it will still be up to you to write the code.  Fees
and distribution agreements for source code have not yet been
established.

Since DRIVExx has not been tested on every conceivable platform and
under every flavor of DOS out there, special consideration (i.e., fee
waiver or free upgrades) will be given to any user who reports bugs or
anomalous behavior, and can suggest a fix.

You may distribute DRIVExx freely as long as all 6 files in the
original LZH file are distributed.  Those files are:

    DRIVExx.TPU  size = 23408, date = 3/28/92, time = 1:00pm

    DRIVExx.TXT  size = 49769, date = 3/28/92, time = 1:00pm
    (This file)

    DRVDEMO.PAS  size = 6205,  date = 3/28/92, time = 1:00pm
    (Demo program source code - must have DRIVExx.TPU and DRVDEMO.INC to
    recompile)

    DRVDEMO.INC  size = 5972,  date = 3/28/92, time = 1:00pm
    (INCLUDE file with procedure ShowDriveError and other miscellaneous
    procedures needed by DRVDEMO.PAS)

    DRVDEMO.EXE  size = 33536, date = 3/28/92, time = 1:00pm
    (Ready to run demo program)

    NTRFACE.TXT  size = 10416, date = 3/28/92, time = 1:00pm
    (Actual INTERFACE section from DRIVExx.PAS)


See section IX for details on product support.



III.       REQUIREMENTS:

DOS versions supported: MS-DOS or PC-DOS 3.0 to 5.0; DR DOS 5 or 6.
Hardware supported: IBM or IBM-compatible PC, XT, AT, or PS/2.




IV.        ACKNOWLEDGEMENTS:

MS-DOS is a trademark of the Microsoft Corporation
DR DOS is a trademark of Digital Research, Inc.
PC Tools Deluxe is a trademark of Central Point Software
NCACHE is a trademark of Symantec Corporation
Stacker is a trademark of Stac Electronics
NetWare is a trademark of Novell

Any PRODUCT NAME used in this document, but not mentioned above, is
likely to be a registered trademark of the company that makes the
product.  There.

DRIVExx is based on information found in the following monographs:

    The MS-DOS Encyclodedia, R. Duncan, ed., Microsoft Press, 1988
    MS-DOS Programmer's Reference, Microsoft Press, 1991
    Undocumented DOS, Schulman, et. al., eds., 1990
    DOS Programmer's Reference, 3rd ed, Dettmann & Johnson, eds., 1992
    PC Interrupts, Brown and Kyle, Addison-Wesley, 1991
    System BIOS for ..., Phoenix Technologies, Addison-Wesley, 1989
    The Programmer's PC Sourcebook, T. Hogan, Microsoft Press, 1991



V.         VERSION NOTES:

VERSION 3.00

a)     DR DOS bug:          Prior versions of DRIVExx were reporting all
                            drives as being NONDOS!
       Explanation:  The header for any DOS device driver contains an
       attribute word at offset 04h.  If bit 15 of this attribute word is
       clear, the driver controls a block device, otherwise it controls a
       character device.  For block devices, bit 13 of this attribute
       word ALSO is significant: we and others have interpreted this bit
       as signaling a NON IBM (NONDOS) format if set (MS-DOS Programmer's
       Reference just says DOS must supply the first sector of the first
       FAT when the driver calls Build_BPB if this bit is set).  For no
       good reason that we can see, DR DOS sets bit 13 for all block
       devices it installs!!  Thus, prior versions of DRIVExx were
       classifying ALL DRIVES in DR DOS systems as NONDOS!  As it
       happens, the routines to analyze the device driver attribute word
       were added to DRIVExx after our beta-tester migrated from DR DOS 5
       to MS DOS 5.  We now have a tester with DR DOS 6 installed, and
       all features seem to work as designed under that OS.

b)     NetWare bug:  DRIVExx was not finding network drives created by    
                     Novell's NetWare.
       Explanation:  We had no beta-testers with installed networks of
       any kind until very recently.  Nevertheless, we should have read
       "Undocumented DOS" more carefully when designing functions to deal
       with network drives!  DRIVExx relies heavily on the Current
       Directory Structure (CDS), but had we read pp 64-70 of this
       excellent reference we would have known that Novell's NetWare is a
       special exception among network systems that *ignores* the CDS
       system when creating network drives!  Our redundancy checks using
       DOCUMENTED Function 4409h (Is Drive Remote?) also failed to
       identify network drives created by NetWare!  Novell provides an
       interface to its products that lets us determine whether NetWare
       is installed and to find network drives.  And as a consequence of
       Novell's system design that allows 32 drives (characters #41h to
       #60h), the length of all strings that return drive letters has
       been enlarged from 26 to 32.

       We still have no access to networks other that NetWare 3.11, so
       this remains one of the weaker features of DRIVExx.  Feedback from
       users would be especially welcome on this point.


c)     Less dramatic changes in version 3.00 include the addition of
       three global variables, NCACHELoaded, StackerLoaded and
       NetWareLoaded.  None of these may be of any interest to most
       programmers;  to fix some design problems, we had to include
       special installation checks for these products anyway, so we just
       made the information available as boolean variables.  Interested
       parties will still have to do their own version checking for these
       products.

d)     In response to user requests, one function was added:
       SwappedWith(drv) will return a CHAR value showing a drive's
       original driveletter assignment at bootup.  This is a useful
       enhancement to DriveIsSwapped.

e)     One function name was changed in version 3.00.  "DriveSize" was
       changed to"DiskSize" to emphasize that it's the size of the DISK
       that's IN the drive, not the capacity of the DRIVE itself, that's
       being reported.  Well (he said sheepishly), this is the name of a
       function in TP6's DOS unit.  Of course, *our* DiskSize didn't hit
       the disk, but to avoid confusion the name of this function in
       DRIVExx 3.10 has been changed once again, to "DiskSyze".


VERSION 3.10

a)     JOIN bug:        DRIVExx 3.00 finds all drives, even NetWare drives,
                        EXCEPT JOINed drives.
       Explanation:  The fix for NetWare drives required that we no
       longer rely on the CDS array to determine unambiguously which
       drives are valid.  Although some redundancy was always built into
       the task of finding valid drives, the alternative method we
       adopted did not recognize JOINed drives and the redundancy checks
       only got confused.  A JOINed drive will now (once again) appear in
       the list of "valid drives" for a system.

       Be aware that a command like JOIN B: C:\TEMP will render drive B:
       "invalid" as far as DOS utilities are concerned.  For example,
       trying to list a directory of B: will get you an "Invalid drive
       specification" message; you cannot access drive B: directly for
       any purpose because DOS considers it unavailable.  HOWEVER, drive
       B: STILL has a CDS, and a DPB that contains valid data from the
       last disk access.  And furthermore any BIOS level access will
       ignore JOIN and *still* hit the physical drive (if there is one) -
       regardless of what DOS says about it.

b)     Classification of removable drives by INT 13h calls is now used
       only as a last resort.  While INT 13h calls SHOULD tell you what
       physical devices are installed, in fact they only provide
       information based on the CMOS setup you've chosen.  For example,
       if you've unplugged your B: drive but neglected to change your
       CMOS setup, DRIVExx would get confused because INT 13h fnc 08h
       would say drive B: is a real physical drive (BIOS drive #1)
       distinct from drive A: (BIOS drive #0), yet DOS will see drive B:
       as a phantom drive mapped to A:.  We could not find a suitable way
       to reconcile CMOS setup with DOS classification in all cases, so
       we chose to depend *first* on what DOS thinks your system
       resources are.  As a consequence, though, any tricks your users
       play to disguise the true PHYSICAL drive type with DRIVPARM will
       fool DRIVExx.  You can still use ShowBIOSDriveNum to get the
       correct BIOS drive number and make the BIOS call yourself.

       Another reason not to rely on INT 13h calls is that they seem to
       cause system lock-up on some old IBM-AT machines with IBM BIOSs
       that are SUPPOSED to support them (based on reported BIOS dates).

c)     Function ShowBIOSDriveNum has been added for those of you who may
       still want to use INT 13h functions to classify or control drives. 
       Function ShowBIOSriveNum will return the BIOS drive number for the
       physical drive associated with the logical drive passed to it. 
       You can then set up and make the call yourself. SEE DESCRIPTION IN
       SECTION VIII BELOW FOR IMPORTANT NOTES!!

d)     Functions ReadDiskSectors (INT 25h) and WriteDiskSectors (26h)
       have been added.  These functions work on any size disk under any
       DOS version.  See full description in section VIII.

e)     Function GetBOOT has been added to return the actual boot sector
       (in a "SectorArray" type variable) for the chosen drive.  Note
       that the type SectorArray added in version 3.10 is 1024 bytes. 
       Even though 512 bytes is the universal maximum sector size, DOS
       *does* allow formats based on sector sizes of 1024 bytes.  As far
       as we know, 1024 is THE maximum in the DOS world, so that's how
       big we made SectorArray.

f)     The FakeDPB structure has been expanded to include select data
       available from a disk's BPB (in its boot record), such as sectors-
       per-track.  This data is filled in from the boot record data, of
       course, so it ONLY gets filled in when GETDPB is called with
       HitTheDisk = TRUE, because we must hit the disk to read the boot
       sector!  Otherwise the FakeDPB variable will contain only
       information from the DPB in memory. (More below in Section VIII).

g)     Procedure ShowDriveError really didn't belong in DRIVExx.TPU.  It
       was removed from DRIVExx.TPU and put into an INCLUDE file for the
       demo program.  Its only purpose was to provide a brief description
       of error codes to non-registered users.  Registered users, of
       course, will still receive a comprehensive explanation of errors
       and their possible sources.


VERSION 3.11

a)     Bug fix for ReadDiskSectors and WriteDiskSectors:  For disks that
       support 32-bit sectoring INT 25h requires a "data packet" record. 
       In version 3.10 the high and low words of the StartSector
       (longint) were inadvertently reversed in the definition of this
       record.


VERSION 3.12, 3.13

a)     Bug fix for StackerLoaded:  this function was originally just a
       BASMed version of the Stacker installation check found in the
       Stacker verion 2.0 manual (Appendix B).  On closer examination we
       found that this published code used the stack as a temporary
       buffer.  This was a very bad idea that led to problems when
       DRIVExx was used in big programs -- StackerLoaded would overwrite
       data!  New StackerLoaded is still BASM, but was written from
       scratch and doesn't poke its nose where it doesn't belong. 
       Version 3.13 made a minor improvement on this BASM code.



VI.        DECISION TREE FOR DRIVE CLASSIFICATION:

    The following "decision tree" roughly illustrates the logic employed
    in the demo program DRVDEMO:


                               EXISTS
 
                              Swapped
                             CurrentDir
                            Defaultdrive
                            DeviceDriven

          normal               <===>                abnormal
         DiskSyze 

 removable <=> nonremovable             aliased <=> phantom <=> NONDOS
 removable-                                         mappedto 
   drivetype   Hard                     network
 changed       RAMDisk                  assigned
               Unknown                  substed
                                        joined
                                        IFS



VII.       DESCRIPTION OF TYPES AND GLOBAL VARIABLES:

Two variable types are defined in the INTERFACE section of DRIVExx. 
"FakeDPB" is so named because it does not conform to the real DPB
structure as defined under any single version of DOS.  It nevertheless
contains most useful information found in the actual DPB after DOS 3
as well as information from the BPB (if boot sector was read).  The
other type, SectorArray, is as big as the largest possible (we think)
sector size in DOS systems.


Types:

  SectorArray = array[0..1023] of byte;
  FakeDPB = record
    {start of BPB in boot record}
    bytespersex : word;    {BPB & DPB}
    sexperclust : byte;    {BPB & DPB}
    FATstart    : word;    {BPB & DPB}
    numFATS     : byte;    {BPB & DPB}
    RootdirEnts : word;    {BPB & DPB}
    placeholder : word;    {ignore this}
    mediabyte   : byte;    {BPB & DPB}
    sexperFAT   : word;    {BPB & DPB}
    SexperTrack : word;    {BPB ONLY}
    NumHeads    : word;    {BPB ONLY}
    HiddenSex   : longint; {BPB ONLY}
    TotSexonDisk: longint; {BPB ONLY}
    {end of standard BPB - 25 bytes}

    RootdirSex   : word;    {calculated}
    ROOTstart    : word;    {DPB}
    FirstDataSec : word;    {DPB}
    TotClusts    : word;    {calculated}
    TotDataClusts: word;    {DPB}
    Tracks       : word;    {calculated}
    Cylinders    : word;    {calculated}
    Size         : longint; {calculated}
    accessflag   : byte;    {DPB}
    ddunitnum    : byte;    {DPB}
    DPBDataValid : boolean; {calculated}
  end;


Global Variables:

DOSVER : real

    The currently running DOS version, expressed as a real number.  This
    information is critical to the proper operation of DRIVExx, since
    many internal DOS data structures vary from one DOS version to
    another.  You should be aware that some DOS versions do not return a
    value to this function equal to the product's version number.  For
    example, under MS DOS version 5 this function will return
    DOSVER = 5.00, but under DR DOS version 6 it will return
    DOSVER = 3.31!  These *known* situations with non-MS versions are
    dealt with, but full compatibility is assured ONLY under MS-DOS or
    IBM's PC-DOS.  Of course, DRIVExx can see through MS DOS 5's SETVER
    to get the *real* DOS version.


DRDOS : boolean

    DRIVExx uses an undocumented (but DR-approved) function call to
    determine SPECIFICALLY whether DR DOS is running.  This is important
    because DR DOS 5 is really a clone of MS DOS 3.3, with some internal
    data structures modified to accommodate volumes larger than 32M. 
    Thus, programs running under DR DOS 5 can't count on internal
    (undocumented) data structures to be the same as in any SINGLE
    version of MS DOS.  We also know that ASSIGN and SUBST are not
    reported the way they are in MS DOS, and suspect that other
    situations of "aliased" drives may be ambiguously reported under
    this OS.  Therefore, when running under DR DOS we recommend calling
    the generic DriveIsAliased function and not bothering to try to
    distinguish between SUBST, ASSIGN, JOIN, NETWORK and IFS
    (installable file system).  See function "DriveIsAliased" in the
    next section.


DriveError : longint
    
    A global error code that is the sum of all error codes returned
    during execution of UpdateDrives.  A value of zero means no errors
    were detected.  Only three errors will cause *immediate* exit from
    UpdateDrives: Error 00001 - wrong DOS version (or OS/2), Error 00002
    - not enough memory to create the variable DRIVES^, and Error
    00004 - failure to find the address of DOS's List-of-Lists.  Because
    these errors make it impossible to continue processing UpdateDrives,
    any one of them will result in all booleans being set to false and
    all strings being set to null.

    Less serious errors arise mostly as a result of redundancy checking
    on undocumented DOS functions and data structures, thus execution
    may continue and multiple errors can be reported.  It would then be
    up to the programmer or user to determine how to proceed.  In
    general it would be unwise to allow a program to proceed if
    DriveError were non-zero, and foolish to do so if DevDrvrChainValid
    were false (see below).

    The procedure ShowDriveError in the demo program's INCLUDE file will
    interpret the individual error codes summed in DriveError.  This
    procedure only shows a brief description of the error; REGISTERED
    USERS WILL RECEIVE A DETAILED DESCRIPTION OF THESE ERRORS, HOW THEY
    ARISE, AND WHICH DOS FUNCTION CALLS ARE INVOLVED.

    ANY NON-ZERO DRIVEERROR SHOULD BE REPORTED TO NativSoft IMMEDIATELY!


InternalFloppies : byte

    The number of BIOS-driven floppy drives, deduced from the equipment
    list word at $0040:$0010.


PhysicalFixed : byte

    The number of actual physical hard drives installed.


NumBlockDevs : byte

    The number of block devices, most accurately described as the number
    of Drive Parameter Blocks set up during the processing of the
    CONFIG.SYS file.  This is NOT ALWAYS the same as NumLogicalDrives
    since a logical drive can be "created" using SUBST or JOIN but won't
    necessarily have a DPB associated with it.  Every block device also
    has a device driver associated with it, whose address can be found
    in the drive's DPB.


DevDrvrChainValid : boolean

    During execution of UpdateDrives, the device driver chain is traced,
    all block devices in it are found, and their driver addresses are
    stored in a temporary table.  These addresses are then checked
    against the addresses for these drives contained in each drive's
    DPB.  But some software, notably Norton's NCACHE, will change the
    addresses reported in the DPBs so they no longer match what was
    found in the (unchanged) device driver chain!!  This makes it
    impossible to match up drives with device drivers unless
    program-specific remedies are used.

    The proper functioning of DRIVExx REQUIRES that drives be matched up
    with their block device drivers!!  If for any reason this cannot be
    done, DevDrvrChainValid (device driver chain valid) is set to FALSE
    and no DRIVExx functions are to be trusted.

    So far, only NCACHE is known to change device driver addresses in
    the DPBs, but there likely are other programs out there that play
    similar tricks.  A partial fix for NCACHE was implemented in DRIVExx
    version 2.00, and a complete rewrite of these functions occurred in
    DRIVExx version 3.00.  If you ever see DevDrvrChainValid = FALSE,
    please let us know ASAP!!


NCACHELoaded      : boolean   {3.00}

    NCACHE's unusual mode of operation (described immediately above)
    caused some unexpected problems in the development of DRIVExx; it's
    conceivable it could also cause other, unforeseen problems with
    programs that USE DRIVExx, or with ANY program that depends on
    information about device driver addresses.  Since we HAD to
    determine unambiguously whether NCACHE is installed, we simply made
    this information available with this variable.  Works for all
    versions of NCACHE.


StackerLoaded     : boolean   {3.00}

    As with NCACHE, we had to determine unambiguously whether Stacker is
    installed.  We just made the information available.  Works for all
    versions of Stacker.
    

NetWareLoaded     : boolean   {3.00}

    Tested only with NetWare version 3.11, but SHOULD be valid for all
    versions.  Distinguishing network drives from local drives under any
    other networking software should work, since only Novell's products
    are known (by us, anyway) to bypass the Current Directory Structure.


NumLogicaldrives : byte

    A short explanation of this variable is that NumLogicalDrives is
    simply the length of the string AllLogicalDrives, described below.


AllLogicaldrives : string[32]

    Logical drives are those associated with real drives, virtual
    drives, phantom drives (including phantom drives created with
    DRIVER.SYS), and "aliased" drives created by SUBST or ASSIGN.  For
    example, a two floppy system with a hard disk partitioned into C:
    and D: and a RAMdisk set up as E: would have 5 logical drives,
    A: thru E:, and AllLogicalDrives would be the string 'ABCDE'. 
    Having the statement LASTDRIVE=Y in your CONFIG.SYS file and
    executing SUBST P: C:\DOS would give you 6 logical drives, A: thru
    E: AND P:, and thus AllLogicalDrives would have the value 'ABCDEP'. 
    Note that P: is a "logical" drive, so NumLogicalDrives is 6 (A: thru
    E:, and P:), but it does NOT have a DPB associated with it so
    NumBlockDevs is still 5 (A: thru E: only).  Length of this variable
    was expanded to 32 to accommodate NetWare's six extra "driveletter"
    characters following 'Z' in the ASCII table.


BootableDrives : string[32]

    Driveletters of BIOS-driven drives, minus any phantom or aliased
    drives, and minus all hard disk partitions except the first one on
    the first physical hard disk.  We've been told that systems can also
    be booted from Bernoulli drives, but these removable drives were not
    considered here.  The purpose in defining this variable was to
    compile a list of drives available AT THE TIME UPDATEDRIVES IS RUN
    from which one might be able to boot the system.  This would exclude
    any device driven drive installed in CONFIG.SYS, but would include
    floppy drive B: if a REAL drive exists.  Length of this variable was
    expanded to 32 to accommodate NetWare's six extra "driveletter"
    characters following 'Z' in the ASCII table.

    There are circumstances under which this variable will be a null
    string.  See "DevDrvrChainValid" above.



Floppies, Hards : string[32]

    Strings of uppercase driveletters representing the drive types
    indicated.  Only "normal" drives will be reported in these strings,
    i.e., phantom, aliased and NONDOS drives will be excluded.  Thus,
    AllLogicalDrives may be 'ABC' for a one-floppy, one-hard disk
    system, but Floppies will just be 'A' since the phantom B: drive
    will be excluded.  Length of these variables was expanded to 32 to
    accommodate NetWare's six extra "driveletter" characters following
    'Z' in the ASCII table.


BiosDateString : string[8]

    The date of the system BIOS, in a non-zero-padded format: mm/dd/yy. 
    Example 3/3/89 (not 03/03/89).


ProcessorType : integer

    As with the BIOS date, the processor type must be determined and
    used internally, so we made the information available.  The only
    possible values this variable can take are:

                   0:   unknown
                   1:   8088/8086
                   2:   80286
                   3:   80386
                  -3:   80386SX
                   4:   80486


MachineID : byte

    Contains the IBM machine type.



VIII.      DESCRIPTION OF PROCEDURES AND FUNCTIONS:

This section describes all procedures and functions declared in the
INTERFACE of unit DRIVExx.  All functions below that require a
character argument will accept either uppercase or lowercase
driveletters; characters other than letters of the alphabet will
ALWAYS cause boolean functions to return FALSE since each of these
functions first calls DrivExists, which of course will return FALSE in
such cases.

Much of the information here is duplicated in the file NTRFACE.TXT,
which is the actual INTERFACE section of DRIVExx.


PROCEDURE UpdateDrives

    Creates or updates the DRIVES^ array.  This "dynamic" array is
    allocated on the heap and consists of one element for each valid
    drive (a total of NumLogicalDrives elements).  Each element requires
    150 bytes of heap space.  Because the dynamic array technique
    requires range checking to be OFF, it's especially important not to
    use an invalid drive letter as an array index, as this may send your
    program into outer space.  Thus, programmers should not have direct
    access to DRIVES^.  This is easily accomplished by keeping it in the
    IMPLEMENTATION section!  All information in this array is accessible
    ONLY through the functions described below.

    UpdateDrives is called in the unit initialization, and need not be
    called directly unless your program shells out to DOS and gives your
    user an opportunity to do something like access a phantom drive, or
    SUBSTed or JOINed a drive.  It should be called upon return to your
    main program to guard against such a situation.


FUNCTION DrivExists(drv:char) : boolean

    Self explanatory.  Returns FALSE if the drive doesn't exist.  If you
    type DIR Q: at the DOS prompt and get the message "invalid drive
    specification", you'll also get FALSE with DrivExists(Q). 
    Conversely, if you don't get "invalid drive specification" then
    DrivExists will return TRUE.  Simple.

    But of course there's an exception:  JOINed drives appear as invalid
    to DOS, but DRIVExx will still list them.  Suppose you have a real
    B: drive but JOIN it to a subdirectory on C: with the command JOIN
    B: C:\PUKED.  You cannot now access B: with DOS commands like DIR or
    CHKDSK.  But DOS INT 25h and INT 26h can still do absolute reads and
    writes to a disk in drive B:!  Thus B: still exists for the purposes
    of DRIVExx.


FUNCTION DriveisNormal(drv:char) : boolean

    This function will return FALSE only if a drive is phantom, aliased
    and NONDOS.  It was simply expedient, for many applications, to
    DEFINE these types of drives as non-normal.  Note that NO size or
    type information is to be trusted for a non-normal drive.  The demo
    program DRVDEMO illustrates what information is and isn't expected
    to be valid for non-normal drives.


FUNCTION DriveisNONDOS(drv:char) : boolean

    NONDOS drives are somewhat rare in our experience.  However, if you
    use a PC running IBM AS/400 or System38 emulation, you might have
    virtual drives created for "PC-Support".  These are the only
    examples of NONDOS drives with which we are familiar.  Just trying
    to access such a drive is dangerous, and trying to run DOS's CHKDSK
    on one will usually get you a "Memory allocation error. System
    halted" message.  While there's not much we can do to teach IBM to
    write decent block device drivers, DRIVExx should make their lousy
    code easier to live with by avoiding it altogether.


FUNCTION DriveisRemovable(drv:char) : boolean

    Self explanatory.  Again, we have yet to see it incorrectly classify
    a drive.  The TYPE of removable drive can be found with function
    RemovableDriveType described below.


FUNCTION RemovableDrivetype(drv:char) : byte

   Returns:
           -3 : INT $13 fnc $08 failed
           -2 : INT $13 fnc $08 returned value not in range 1-4
           -1 : INT $13 fnc $08 returned 0 for unknown reason
            0 : type can't be determined (no error) or drive MAY NOT
                be removable.
            1 : 5.25" 360K
            2 : 5.25" 1.2M
            3 : 3.5" 720K
            4 : 3.5" 1.44M
            5 : 3.5" 2.88M     {not tested in version 3.00 or later}
            6 : TAPE           {not tested in version 3.00 or later}
            7 : Bernoulli      {not tested in version 3.00 or later}


FUNCTION DriveisPhantom(drv:char) : boolean

    This function is somewhat problematic for DOS versions PRIOR to 3.2,
    in that "phantomness" in these environments has to be decided
    primarily based on the value of the byte at $0000:$0504.  In one-
    floppy systems this byte is 0 if the single floppy was most recently
    accessed as A:, or 1 if it was most recently accessed as B:. 
    However, some otherwise well-behaved programs such as PC Tools
    Deluxe version 6 WILL change the value at $0504 to $FF in certain
    situations, rendering this phantom-finding method ineffective. 
    Phantom-finding has been safe and straightforward since DOS 3.2, so
    only well-behaved and documented functions are used when running
    under these newer DOS versions.


PROCEDURE MakeDriveActive(drv:char)

    Whenever a phantom drive exists -- whether it's drive B: in a
    one-floppy system, or one created by DRIVER.SYS -- this procedure
    will make it active so you don't see the obnoxious "Insert diskette
    for drive B: and ... " message when you try to access it.  This
    message is NOT interceptable; the only way to avoid it is to avoid
    trying to access a phantom drive.  This procedure does that, AND
    ALSO CALLS UpdateDrives WHEN IT'S FINISHED!!


FUNCTION DriveMappedTo(drv:char) : char

    If drv is a removable drive and is currently classified as phantom,
    this function will return the uppercase driveletter of the physical
    drive to which the phantom is currently mapped.  If ANY drive is
    ASSIGNed, removable or not, this will tell you what drive letter
    it's assigned to.  Unlike JOIN and SUBST, ASSIGN does not affect the
    "current directory" reported in the CDS structure, so it's not as
    simple to determine drive mapping for this DOS function.


FUNCTION DriveisSubsted(drv:char) : boolean
FUNCTION DriveisJoined(drv:char) : boolean
FUNCTION DriveisAssigned(drv:char) : boolean
   (see "DriveMappedTo" above)
FUNCTION DriveisNetwork(drv:char) : boolean
FUNCTION DriveisIFS(drv:char) : boolean
FUNCTION DriveisAliased(drv:char) : boolean

    These are fairly self-explanatory.  The last function in this group,
    DriveIsAliased, is a shortcut that returns true if a drive is
    SUBSTed, JOINed, ASSIGNed, NETWORK or IFS (installable file system). 
    These functions have been tested extensively for SUBSTed, JOINed and
    ASSIGNed drives, but not for NETWORK or IFS drives.  User input
    would be appreciated!

    If one of these functions returns TRUE, there is little RELIABLE
    information that can be obtained for it other than whether it's
    device driven or swapped, as DRVDEMO shows.

    Under DR DOS, only DriveIsAliased should be called, since the other
    "aliased" functions may not operate as expected.  We KNOW this to be
    the case for ASSIGN and SUBST, but have not checked it out for
    NETWORK and IFS drives.


FUNCTION DriveisDeviceDriven(drv:char) : boolean

    Any drive that's run by a device driver installed in the CONFIG.SYS
    file will return TRUE here.  This is true for RAM disks as well as
    for REAL physical drives, like some 1.2M external 5.25" drives on
    IBM PS/2 machines.


FUNCTION DriveisSwapped(drv:char) : boolean

    Useful for systems that have a real drive swapped with a
    device-driven drive (such as STACKER volumes), especially if one of
    the swapped drives is your bootable hard disk partition.

    See variable "DevDrvrChainValid" above for limitations.


FUNCTION SwappedWith(drv:char) : char

    If DriveIsSwapped(drv) returns TRUE, then SwappedWith(drv) should
    identify the drive with which "drv" was swapped.  The correct
    operation of this function is assured only if DevDrvrChainValid is
    TRUE.  If a drive HASN'T been swapped, then SwappedWith(drv) = drv.


FUNCTION DriveisHard(drv:char) : boolean
FUNCTION DriveisRAMDisk(drv:char) : boolean
FUNCTION DriveisOtherfixed(drv:char) : boolean

    These are the only categories of non-removable drives; the functions
    are also self explanatory.  We have yet to see a non-removable drive
    that couldn't be classified as either hard or RAM disk, but function
    DriveisOtherfixed is included to take care of this possibility.

    We use a very common criterion to classify a drive as a RAM disk: if
    the DPB of a non-removable drive says there is only ONE copy of the
    FAT, it's called a RAM disk.  SuperStore drives that ordinarily
    would look like hard disks are sometimes incorrectly classified as
    RAM disks because they only have ONE FAT copy.  Even drives that
    only LOOK like hard drives (e.g., Stacker volumes => 2 FAT copies,
    media descriptor = $F8) will be reported as DEVICE DRIVEN.


FUNCTION ChangeLineSupported(drv:char) : boolean

    This should return true ONLY for removable drives, and then only if
    the device driver for the drive says it supports change line
    detection.


FUNCTION DiskWasChanged(drv:char) : boolean

    This function hits the disk only if there's support for changeline
    detection.  It always returns TRUE (disk WAS changed!) if there's no
    disk in the drive.


FUNCTION DiskSyze(drv:char) : longint

    Returns  0 : usually only for a drive (removable or not) that hasn't
                 been accessed since bootup, and so has no valid DPB
                 data to use
           > 0 : disk size IN BYTES; if the disk is REMOVABLE,
                 DiskSyze returns the size of last disk accessed in
                 the drive


FUNCTION CurrentDir(drv:char) : pathstr

    Returns the currently logged directory for the specified drive as a
    complete path, including driveletter, colon and final backslash, all
    in uppercase letters.  If the drive doesn't exist a null string is
    returned.

    This information is obtained from the Current Directory Structure
    and should be correct - except when Novell NetWare is running!  In
    this case the CDS for EACH AND EVERY drive - even local drives -
    reports that the ROOT directory is the current directory, even when
    it isn't!  We don't yet know of a NetWare function that will obtain
    the correct current directory information without hitting the disk,
    so this function returns a null string for ALL drives when NetWare
    is running.

    For JOINed and SUBSTed drives, CurrentDir will tell you which
    drive/subdirectory is really active.  This is not true for ASSIGNed
    drives.  See Function DriveMappedTo above (and program DRVDEMO) for
    more info.


FUNCTION DefaultDrive : char

    Identifies the current default drive as an uppercase letter.  No
    error checking is done on the returned value;  we know it works in
    every case when NetWare isn't loaded, though.


FUNCTION GETDPB(drv:char; var drvinfo:fakeDPB; hit:boolean) : word

    Puts useful drive data from DPB (and BPB) into variable 'DRVINFO'. 
    If HIT is TRUE, the function will hit the disk to update the DPB
    data in memory.  Otherwise it will return data already in memory,
    usually for the last disk that was accessed in the drive.  GETDPB
    returns FALSE if problems were encountered (drv is an invalid drive,
    no disk in drive when HIT is set to TRUE, etc.) and all fields of
    DRVINFO will be zeroed out.

    Please note that some information in the FakeDPB structure DRVINFO,
    such as sectors-per-track, is located in the disk's boot record and
    not in the DPB in memory.  Therefore, these fields will be filled in
    ONLY if this function is called with Hit = TRUE since the boot
    sector must be read from the disk.  Only drives that are *NORMAL*
    are allowed to be hit; aliased drives, phantom drives, non-DOS
    drives, etc, will not be hit.

    GETDPB(drv,F,FALSE)  returns
          0 : no error, F contains only DPB data
      $F7F7 : drive doesn't exist
              (no other return values possible)

    GETDPB(drv,F,TRUE)   returns
          0 : no error, F contains DPB data, BPB data and derived data
      $F4F4 : can't allocate buffer to receive boot sector
      $F5F5 : DPB says bytes per sector > 1024
      $F6F6 : drive is not normal (could be phantom or aliased)
      $F7F7 : drive doesn't exist
      $FF00 : DOS Fnc $32 (GetDPB) failed
      else  : error code generated by INT $25 (Absolute Read):
              Hi byte = BIOS error code
              Lo byte = DOS error code
             (eg, BIOS error 128 means drive not ready or no disk
              in drive, BIOS error 2,6 or 12 could mean disk isn't
              formatted).  All fields of F will be zeroed out.


    GetDPB will work for ANY drive that exists if Hit = FALSE, and will
    return data from the DPB in memory.  That's because the DPB doesn't
    know or care whether a drive is SUBSTed or phantom or otherwise NOT
    NORMAL.  If a non-normal drive is passed to GetDPB with Hit = TRUE,
    the function will IMMEDIATELY return $F6F6.

    We only allow the disk to be read when we're sure the drive letter
    passed corresponds to the physical drive that will be hit.  Since we
    use DOS absolute disk reads (INT $25), and not BIOS reads (INT $13,
    Fnc $02) to read the boot sector, DOS will redirect our absolute
    disk read if the drive is aliased.  For example, suppose drive A:
    has been SUBSTed with the command

              SUBST A: C:\TEMP

    If we allowed the drive to be accessed unconditionally, the command
    GetDPB('A',F,TRUE) would end up reading the boot sector of drive C:
    in this case, and the data in F would have nothing to do with drive
    A:.  THIS IS WHY WE DON'T ALLOW A DISK TO BE ACCESSED WITH THIS
    FUNCTION UNLESS IT'S A NORMAL DRIVE (not phantom, not network, not
    JOINed, etc.).



FUNCTION ReadDiskSectors(drv:char;frst:longint;num:word;var Buf):word

    This function implements DOS INT $25 for absolute disk reads.  The
    entire routines are in BASM so no OBJ files are required.  Error
    codes are $F7F7 if drive doesn't exist, or whatever error code is
    generated by DOS INT $25 (BIOS error code in high byte, DOS error
    code in low byte, just like GetBOOT below and GetDPB above).

    "num" is the number of sectors to be read, starting at sector
    "frst", and Buf is the variable to receive the data.  While we've
    NEVER seen it written in ANY reference ANYWHERE, we know that
    INT $25 will not read more that 64K at a time.  Assuming the
    standard 512-byte sector, this means "num" can be no more than 128
    sectors.  However, INT $25 will not *always* return an error when
    trying to read more than 128 sectors (or 64K) at a time!  On one
    machine we've seen this function work without returning an error
    until "num" exceeded 160 sectors (82K); of course, anything over 128
    just got wrapped around to the beginning of Buf.


FUNCTION WriteDiskSectors(drv:char;frst:longint;num:word;var Buf):word

    Implements DOS INT $26.  See ReadDiskSectors above.


FUNCTION ShowBIOSDriveNum(drv:char) : byte

    The purpose of this function is to find out which physical drive
    corresponds to a given logical drive.  Whatever PHYSICAL disk you
    would hit with a function like ReadDiskSectors(drv, 0, 1, buf) is
    what this returns.  It does not try to figure out DOS redirection
    commands like JOIN or ASSIGN.  If DRV is not a physical disk (eg,
    RAMDISK) or if DRV is a phantom, the function returns $FF.  Based on
    interception of DOS INT $25 call and determination of the
    drivenumber passed to INT $13 Fnc $02.  NO DISKS ARE HIT; THE INT
    $25 CALL IS ALWAYS INTERCEPTED AND CONTROL RETURNED WITHOUT ACTUAL
    DISK ACCESS.

    IMPORTANT: DOS INT $25 ignores JOIN, but DOES NOT ignore ASSIGN or
               SUBST!

     EXAMPLES:  SUBST A: C:\
                Y := ShowBIOSDriveNum('A');
                Y returns 128 (first hard disk), not 0 (first floppy)

                ASSIGN A: C:
                Y := ShowBIOSDriveNum('A');
                Y returns 128 (first hard disk), not 0 (first floppy)

      BUT...   JOIN A: C:\PUKED
               Y := ShowBIOSDriveNum('A');
               Y returns 0! (BIOS drive number for A:, not C:)

               Furthermore, after this JOIN command it is impossible
               to access drive A: with simple DOS commands, such as
               DIR and CHKDSK; they all return "Invalid drive
               specification".  Yet INT $25 will still read from a
               floppy in drive A:!  Worse yet, traversing drive C:
               with a file search program based on DOS FindFirst and
               FindNext, for example, will cause drive A: to be
               accessed when the search tries to read anything in
               directory C:\PUKED!


FUNCTION GETBOOT(drv:char; var S:SectorArray) : word

    Reads boot sector and puts it directly into array S.  THE ONLY 
    CHECKING DONE BY THIS PROCEDURE IS ON PHANTOM DRIVES.  ALL OTHERS
    WILL BE HIT BY INT $25 AND ANY REDIRECTION BY SUBST OR ASSIGN WILL
    AFFECT THE OUTCOME!

    returns
          0 : no error, S contains boot sector as read
      $F4F4 : can't allocate buffer to receive boot sector
      $F5F5 : DPB for drive says bytes per sector > 1024
      $F6F6 : drive is phantom
      $F7F7 : drive doesn't exist
      else  : error code generated by INT $25 (Absolute Read): 
              failed attempt to read boot sector - DOS errorcode in
              the low byte and BIOS error code in the high byte
              (eg, BIOS error 128 means no disk in drive).

    Though it's not common, it's possible for a disk's sector size to be
    greater than 512 bytes.  As far as we know, 1024 is the MAXIMUM
    sector size under any existing version of DOS, so that's how big we
    made SectorArray.  All unused portions of S will be zeroed out.


PROCEDURE ShowDriveError (removed from DRIVExx.TPU in version 3.10)

    Shows and interprets errors using global DriveError variable. 
    Complete description and explanation of error codes provided to
    registered users by return mail.



IX.        PRODUCT SUPPORT:

Product support is available at (517)-265-6080 after 6pm Weekdays
(Eastern), 10am to 4pm Saturdays, or by U.S. Mail (as often as the
mail runs).  General questions, bug reports and/or suggestions for
improvement are always welcome from ANYONE, whether registered or not. 
No collect calls can be accepted, but we can take a phone number and
return your call.

REGISTRATION/ORDER FORM


COMPANY NAME: _______________________________________________________

ADDRESS:      _______________________________________________________

              _______________________________________________________

              _______________________________________________________


WARRANTY DISCLAIMER:

THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY OF ANY KIND,
EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OR MERCHANTABILITY AND FITNESS FOR A PARTICULAR
REASON.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
SOFTWARE IS WITH THE CUSTOMER.  THE CUSTOMER SHALL ASSUME THE ENTIRE
COST OF SERVICING, REPAIR, AND CORRECTION.

IN NO EVENT SHALL NativSoft BE LIABLE TO THE CUSTOMER FOR ANY DAMAGES,
INCLUDING LOST PROFITS, LOST SAVINGS, OR OTHER INCIDENTAL OR
CONSEQUENTIAL DAMAGES, ARISING OUT OF THE USE OR INABILITY TO USE SUCH
PROGRAM, EVEN IF NativSoft OR AN AUTHORIZED REPRESENTATIVE HAS BEEN
ADVISED OF THE POSSIBILITY OF SUCH DAMAGES, OR FOR ANY CLAIM BY
ANOTHER PARTY.

I HAVE READ THE ABOVE AGREEMENT AND BY MY SIGNATURE BELOW ACCEPT ITS
TERMS.


NAME: _______________________    SIGNATURE: _______________________







_____ Copies of DRIVExx.TPU @ $15.00 ea                     : $_______

(Michigan Residents add 4% sales tax)                 Tax   : $_______


                                                    TOTAL   : $_______



Payment by check or money order in U.S. Dollars only.
Make check or money order payable to NativSoft Computing.

Mail this completed REGISTRATION form with payment to:

       NativSoft Computing
       1155 College Ave.
       Adrian, MI  49221

Fee will be waived if this REGISTRATION/ORDER form is accompanied by a
description of anomalous behavior or a bona fide bug/logic error.