[micro.ibm] Followup on "Opening files for Shared Read in Turbo Pascal"

ugogan@ecsvax.UUCP (04/11/87)

About a month ago, we posted the following request for information:

> Has anyone found a way to get Turbo Pascal to open random
> access files in read only mode to allow shared concurrent
> access over an MS-DOS 3.1/NETBIOS-based network?
> Normally, Turbo opens files in read/write mode only, producing
> "sharing violation ... abort, retry, ignore" error messages.
> (At least the version of Turbo we have does.)  We could
> trap those errors & have users wait till file is free, but
> would obviously prefer shared access.
> Thoughts, anyone??

Basically, the responses that we got back from this request were of two 
varieties. The first group said that unless we wanted to write all our own 
I/O routines, we were out of luck; the second group said that if we found out 
anything to let them know.

Although the idea of writing all of our own I/O routines was not appealing, 
we did decide to take a look at a compiled Turbo program and see how it 
opened files.  Not surprisingly, Turbo's run-time library uses DOS function 
call 3DH ("open file") with a mode byte of 02 (for read/write).  If you try 
to use the DOS ATTRIB command (or equivalent) to mark a file as "read only", 
when your Turbo program tries to open the file, you'll get an error message.
  
In the meantime, a letter to the editor in the April 1987 issue of PC Tech 
Journal suggested the possibility of changing this mode byte that Turbo 
Pascal passes to the DOS function call 3DH to a zero (for read-only access).  
Although this would provide one alternative to our problem, we decided to 
take this solution one step further and take advantage of the increased power 
of the 3DH function call under DOS 3.xx, as well as provide a means to avoid 
having to use DEBUG on every network application program that we develop 
under Turbo Pascal.

The following description of the solution we developed, as well as a Turbo 
Pascal procedure that can be used to set the desired sharing and access modes 
for any files that you want to open in your program, is detailed below by 
Todd Lewis.  If you have any specific questions about this method, please 
send your replies directly to Todd at UTODDL@UNC.BITNET.  If you do not have 
access to BITNET mail, feel free to reply to me and I will forward them to 
Todd.

Thanks to those who replied to our original request.

-- Jim Gogan (ugogan@ecsvax)
   University of North Carolina at Chapel Hill
   Microcomputing Support Center

--------- cut here for Todd's solution ----------

An instruction at 24FB in TURBO.COM (Ver. 3.01a) controls the
options for opening random access files with RESET. (You will have
to snoop around with DEBUG to find the offset for other versions of
Turbo.  It should still be in the same general area.)  It looks
like "xxxx:24FB B8023D MOV AX,3D02".  AX=3Dxx is the DOS function
to open a file; AL=00, 01, and 02 requests read access rights,
write access rights, and read/write acces rights, respectively, in
DOS 2.10.  In DOS 3.xx, things get a little more sophisticated.  In
3.xx, AL consists of four bit-oriented fields. They are:
   * Inheritance flag
   * Sharing mode field
   * Reserved field
   * Access field
The bit fields are mapped as follows:
                   <I> < S > <R> < A >
   Open Mode bits   7  6 5 4  3  2 1 0

I  Inheritance flag
   If I = 0, file is inherited by child processes
   If I = 1, file is private to the current process
S  Sharing Mode
   The file is opened as follows:
   If S = 000, compatability with DOS 2.xx
   If S = 001, Deny Read/Write mode (exclusive rights)
   If S = 010, Deny Write mode
   If S = 011, Deny Read mode
   If S = 100, Deny None mode
   Any other combinations are invalid.
R  Reserved (always 0).
A  Access (just like in DOS 2.xx)
   The file access is assigned as follows:
   If A = 000, Read access
   If A = 001, Write access
   If A = 010, Read/Write access
   Any other combinations are invalid.

The upshot is this: if you use the function below with the
parameters Share_Compatability, Access_Read, which zaps your
memory-image (not the .COM file on the disk),  you can do random
access reads through a typed file RESET on a Read-Only file. For my
purposes (I'm trying to have several programs reading from the same
pre-existing data file over a network), I will probably use
Share_DenyNone and Access_Read (any number of programs can read the
file simultaniously).  BUT if any of the programs tries to do a
WRITE, that program is gonna die!  If you really wanted to live
dangerously, you could have several programs open with
Share_DenyNone, Access_ReadWrite, and they could all read AND
CHANGE the file at the same time.  This wouldn't provide for any
record locking.  You would have to do that through explicit DOS
calls.

You should examine your TURBO.COM file with debug to make sure the
zapping function will work with your version.  I'm using 3.01a and
the code a la debug I'm zapping is:
     xxxx:24FB B8023D MOV AX,3D02
It's the 02 part that sets the access mode.  (You could zap a copy
of the compiler itself, but I don't recommend it.)

Note Well: I have not tested this with REWRITE of typed files and
don't intend to.  There is at least one other place in the run-time
memory image where B8023D shows up and it could well be used for
REWRITE.  Trying to trace through Turbo's code is beyond me, and if
I need to REWRITE a file I'll set everything back to the load-time
defaults first.

Below is a program which shows how to do this in ver. 3.01a.  Note
that the address constants given in the function will have to be
changed if you are using another version of Turbo.  It tries to do
a little checking, but it isn't really adequate.

PROGRAM ModifiedTypedFileReset;
(**************************************************
By:  Todd M. Lewis,
     Microcomputer Support Center
     UNC Chapel Hill
     (UTODDL@UNC.BITNET)
**************************************************)
TYPE
  SharingMode  = (Share_Compatability,
                  Share_DenyReadWrite,
                  Share_DenyWrite,
                  Share_DenyRead,
                  Share_DenyNone);
  AccessMode   = (Access_Read,
                  Access_Write,
                  Access_ReadWrite);
  TypedFileModeRetCode = ( TypedFileModeSetOK,
                           NotDOS3orAbove,
                           NotTurboVer301a);

FUNCTION SetTypedFileMode( s: SharingMode; a: AccessMode ): TypedFileModeRetCode;
{ N.B.: If this function returns anything other than TypedFileModeSetOK then
  the Sharing and Access modes are going to be whatever they were the last
  time the funtion returned TypedFileModeSetOK.  If the funtion has not been
  called and returned TypedFileModeSetOK, then you still have the load-time
  defaults (Share_Compatability, Access_ReadWrite).  }
  TYPE
    RegsIType = RECORD
                  AX,   BX,   CX,   DX,   BP,SI,DI,DS,ES,FLAGS: INTEGER;
                  END;
    RegsBType = RECORD
                  AL,AH,BL,BH,CL,CH,DL,DH                     : BYTE;
                  END;
  VAR
    RegsI : RegsIType;                 { This gives better control when setting}
    RegsB : RegsBType ABSOLUTE RegsI;  { regs -- Turbo sometimes screws up when}
                                       { using CASE INTEGER 1:(..); 2:(..) !   }
  BEGIN
    RegsB.AH := $30;  { Set for DOS function to get DOS version number  }
    Intr($21,RegsI);
    IF ( RegsB.AL >= $03         ) OR        { We are either using DOS 3.00 or  }
       ( s = Share_Compatability )           { above, or we are 2.x compatabile.}
       THEN IF ( Mem[CSeg:$24FB] = $B8 ) AND { The bytes before and after the patch}

               ( Mem[CSeg:$24FD] = $3D )     { in Turbo 3.01a are $B8 and $3D...   }

               THEN BEGIN
                    Mem[Cseg:$24FC] := BYTE( (ORD(s)*16) + ORD(a) );
                    SetTypedFileMode := TypedFileModeSetOK;
                    END
               ELSE SetTypedFileMode := NotTurboVer301a
       ELSE SetTypedFileMode := NotDOS3orAbove;
            { N.B.: We only get this if we are asking for some sharing}
            { that isn't provided for in earlier versions of DOS.     }
    END; (* SetTypedFileMode *)

PROCEDURE ShowResult(r:TypedFileModeRetCode);
  BEGIN
    CASE r OF
         TypedFileModeSetOK : Writeln('Typed File Mode Set OK.');
         NotDOS3orAbove     : Writeln('Not DOS 3.00 or Above.');
         NotTurboVer301a    : Writeln('Not Turbo Ver. 3.01a');
         END; (* CASE *)
    END; (* ShowResult *)

(****** Main Block Setup******)
CONST
  flName = '_TEST_.TMP';
VAR
  f1,f2: File of INTEGER;
  i,j    : INTEGER;
  r      : TypedFileModeRetCode;
BEGIN
  (* Note: For the Sharing stuff to make any difference, you must have
     run the DOS program 'SHARE'.  Note also that the PC-NetWork startup
     procedure runs 'SHARE'.

  First, we need a file to play with, so.... *)
  Assign(f1,flName);
  Rewrite(f1);
  FOR i := 1 TO 100 DO Write(f1,i);
  Close(f1);
                (* Now we have a file with some data to play with.
                   Let's try a few things. *)

  Write('Share_Compatability, Access_ReadWrite (Turbo default).  ');
  ShowResult(SetTypedFileMode(Share_Compatability,Access_ReadWrite));
  Assign(f1,flName);  Reset(f1);
  Assign(f2,flName);  Reset(f2);
  Seek(f1,5);   Read(f1,i);
  Seek(f2,7);   Read(f2,j);  Writeln(i,', ',j);
  close(f1);    close(f2);
  Writeln('Press <Enter>...'); Readln;

  Write('Share_DenyWrite, Access_Read.  ');
  ShowResult(SetTypedFileMode(Share_DenyWrite,Access_Read));
  Assign(f1,flName);  Reset(f1);
  Assign(f2,flName);  Reset(f2);
  Seek(f1,5);   Read(f1,i);
  Seek(f2,7);   Read(f2,j);  Writeln(i,', ',j);
  close(f1);    close(f2);
  Writeln('Press <Enter>...'); Readln;

  Writeln('Let''s try some read-only file.  Assume we are on a system disk.');
  Writeln('We''ll use \IBMBIO.COM.  If this isn''t a system disk, we''ll die here.')
;
  Write('Share_DenyNone, Access_Read.  ');
  ShowResult(SetTypedFileMode(Share_DenyNone,Access_Read));
  Assign(f1,'\IBMBIO.COM');  Reset(f1);
  Assign(f2,'\IBMBIO.COM');  Reset(f2);
  Seek(f1,5);   Read(f1,i);
  Seek(f2,7);   Read(f2,j);  Writeln(i,', ',j);
  close(f1);    close(f2);
  Writeln('Press <Enter>...'); Readln;

  Writeln('Now we''ll blow ourselves out of the water.  F1''s Share');
  Writeln('attributes will keep f2 from working.  We''ll abort with');
  Writeln('an I/O error 01 -- File Not Found -- when we Reset(f2).');
  Write('F1: Share_DenyRead, Access_Read.  ');
  ShowResult(SetTypedFileMode(Share_DenyRead,Access_Read));
  Assign(f1,flName);  Reset(f1);
  Write('F2: Share_DenyNone, Access_Read.  ');
  ShowResult(SetTypedFileMode(Share_DenyNone,Access_Read));
  Assign(f2,flName);  Reset(f2);
  Seek(f1,5);   Read(f1,i);
  Seek(f2,7);   Read(f2,j);  Writeln(i,', ',j);
  close(f1);    close(f2);
  Writeln('If you got this far and your return codes from calls to ');
  Writeln('''SetTypedFileMode'' are ''FileModeSetOK'', then you must');
  Writeln('not have run SHARE.');

  END.
(**************************************************
By:  Todd M. Lewis,
     Microcomputer Support Center
     UNC Chapel Hill
     (UTODDL@UNC.BITNET)
**************************************************)
==============================

-- 
     Jim Gogan                             mail:ugogan@ecsvax (UUCP/BITNET)
     Microcomputing Support Center
     University of North Carolina at Chapel Hill
     Chapel Hill, NC  27514