Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Design

Network Access to CD-ROMs


AUG93: Network Access to CD-ROMs

Network Access to CD-ROMs

Client/server software for extending CD-ROM access across a NetBIOS-based network

John H. McCoy and Wuhsiung Lu

John and Wuhsiung are members of the Mathematical and Information Sciences faculty of Sam Houston State University, P.O. Box 2206, Huntsville, TX 77341.


There are two commonly used methods of providing network access to CD-ROMs. One is to use Microsoft's MS-DOS CD-ROM Extensions (MSCDEX) or equivalent redirector to make the CD-ROM appear to be a hard disk, which is then mapped across the network. This approach works with LAN servers that run as MS-DOS applications and do I/O with standard INT 21 services to access the drive letter instead of bypassing MS-DOS. To the client, the CD-ROM drives are indistinguishable from other server drives. The client, however, cannot communicate with MSCDEX or access its ancillary functions.

A second approach to sharing CD-ROMs is shown in Figure 1. Here MSCDEX runs on each client workstation along with a pseudo CD-ROM driver that accepts normal CD-ROM driver requests from MSCDEX. These requests are transmitted over the network to a pseudo redirector on a server, which then submits the request to a bona fide CD-ROM device driver. The response from the CD-ROM is returned via the network to the client pseudo driver that, in turn, responds to MSCDEX. So long as the client pseudo CD-ROM driver responds appropriately, MSCDEX will be unaware that the actual drives are located on a remote machine. This is the scheme implemented by the client/server program presented here.

Jim Harper provided an in-depth look at implementing a DOS redirector (see "A DOS Redirector for SCSI CD-ROM," DDJ, March 1993) to make a CD-ROM appear to be a conventional, read-only hard disk. Jim wrote his redirector to interface to his own SCSI device driver. MSCDEX is designed for use with drivers that have a standard DOS-character device interface and respond to an extended set of DOS-device driver commands. Either combination will make High Sierra and ISO-9660 CD-ROMs appear as read-only hard disks to DOS.

This article describes a client/server software package which extends CD-ROM access across a NetBIOS-based network. It supports file redirection and the ancillary MSCDEX functions as well as digital sound files that are treated as data files. Thus, programs such as Desert Storm work as they should. Normal CD audio is not supported, so some programs run properly but silently.

The server program was written in Ada. It runs in real mode under DOS (or, a specific version of DOS under OS/2) and takes advantage of Ada's built-in multitasking to support concurrent access to seven CD-ROMs by up to 32 users.

The client program is written in MASM. It runs under DOS in conjunction with MSCDEX. Multiple copies of the client can be loaded for simultaneous access to multiple servers. The client can be loaded high with QEMM and can be unloaded. Unfortunately, MSCDEX loads after the client and therefore cannot be unloaded.

The CD-ROM Device Driver

CD-ROM device drivers have the usual DOS format for device drivers. That is, a device header followed by the strategy and interrupt procedures.

The device header is an extension of the normal character device header; see Figure 2. The Attributes field identifies the device as a character device supporting IOCTL and OPEN/CLOSE.

The extension consists of the three fields after the device name: Reserved, DriveLetter, and NumberOfUnits. DriveLetter is a read-only field for the driver and both it and Reserved should be initialized to 0. MSCDEX uses DriveLetter when it assigns the devices supported by the driver to a drive letter. According to my documentation, for drivers which support more than one drive, the drive letter will indicate the first unit and each successive unit will be assigned the next-higher driver letter. My observation, however, is that MSCDEX puts the last, rather than the first, drive letter assigned to this driver in the drive-letter field.

The device driver sets NumberOfUnits to the number of devices attached when the driver loads. This is not the same as the NumberOfUnits returned to DOS during the INIT call when a device is installed from CONFIG.SYS. DOS must always be returned a value of 1 for a character device in the INIT field. The number of units field in the device header, however, is read and used by MSCDEX and never seen by DOS.

A program requests the services of a device driver by sending the driver a request header message containing a command code and related data. The request is passed to the driver in two calls. The first call is to the driver strategy routine with the ES:BX registers pointing to the request header. The strategy routine is expected to save the pointer in local storage for use by the interrupt routine when it processes the request. Immediately following the call to the strategy routine a second call is made to the interrupt routine, which retrieves the request and processes it.

DOS rigidly adheres to this strategy/interrupt calling sequence; MSCDEX does not. Instead, MSCDEX assumes it has exclusive control of the driver. It uses a DOS call to open the device driver and gets the driver's header address through an IOCTL input request. The device is then closed to release the handle.

MSCDEX uses the device-header address to locate the driver's interrupt-routine entry point. Subsequent calls are directed to the interrupt routine and assume the request-header address was stored on the original open call. Consequently, calls to the driver which bypass MSCDEX change the stored request-header address and invalidate this assumption. The system usually crashes on the next MSCDEX call. Hence, communications with the driver should use the MSCDEX-supported send-device-driver request rather than calling the driver directly.

In the usual DOS sequence that I've observed, the call to the interrupt routine immediately follows the call to the strategy routine and the ES:BX register values are not changed. While MSCDEX bypasses the strategy call, it does point ES:BX at the request header when it calls the interrupt routine. Thus, the strategy routine can be reduced to a return statement and the request-header address stored upon entry to the interrupt routine. This is nonstandard but it will allow direct calls to the driver without fatal conflicts with MSCDEX.

Another common problem is the failure of many programs to fill in the request-header information correctly. Numerous programs do not correctly specify the control-block length for IOCTL input and output and require a "lookup" fix in the client. Guinness Disk of Records (a CD-ROM version of the standard Guinness Book of Records, available from Compton's New Media) doesn't even get the request-header length correct on the IOCTL output commands that control the analog audio settings.

In addition to the fields added to the device header, the minimal CD-ROM driver must support five of the standard DOS-driver commands and two of the extended commands for locating and reading CD-ROM sectors. These commands are shown in Table 1, where codes 128 and higher are unique to CD-ROMs.

The Pseudo CD-ROM Driver

On a client workstation, the pseudo CD-ROM driver replaces the CD-ROM device driver supplied with the controller card, as in Figure 1. It installs from the command line like a TSR rather than being loaded from CONFIG.SYS.

The executable file consists of approximately 3K of code and is divided into two parts: a resident part that implements the driver functions, and a transient part that loads/unloads and initializes the resident part and is then discarded.

During installation, command-line parameters, if present, are parsed and used to replace default values for the driver name, the CD-ROM server name, and/or the client's net name. Installation fails if the net name cannot be added to the NetBIOS name table for any reason except a duplicate name in the table. This exception allows the driver to be installed more than once using the same net name for simultaneous access to more than one CD-ROM server. A different driver name must be used for each instance or the second installation will hide the first in the driver chain.

The resident part of the client driver implements the driver functions that simulate the actions of a true CD-ROM driver. It is essentially a large CASE statement which switches on the request- header command code. All required CD-ROM driver functions and the optional READ LONG PREFETCH function are supported. The CLOSE command is processed locally. The others transfer the request to the CD-ROM server through NetBIOS calls implemented in an include file.

When a device driver is loaded from CONFIG.SYS, DOS calls INIT as part of the installation procedure. When the client driver is loaded as a TSR, as it is done here, the normal initialization is done by the transient loader. The first (and only) call that DOS makes to the driver is when MSCDEX opens it to locate the device header. Thus, INIT isn't needed, so it's omitted. The OPEN function establishes a connection with the server and uses a dummy INIT call to the server to obtain the number of CD-ROMs available. This number is put in the client driver header for later use by MSCDEX, and DOS is simply told that the driver has been opened.

The network connection is simple. The request header is sent to the server first, followed by any accompanying data. After the server has processed the request, the request header is returned to the client first, followed by the data. Dynamically allocated buffers are used in the server. Only cooked-data read mode is supported. The NetBIOS maximum message size (64K--1) restricts READ LONG requests to 31 sectors in a single transfer. When a request exceeds this, the client makes multiple requests to the server.

The Server Side

The server is implemented as a group of non-preemptive tasks, as shown in Figure 1. The SCHEDULER handles new calls and assigns a separate, available SESSION to each client. NET handles the interaction between NETBIOS and the SESSIONs, and CD is the interface task between the SESSIONs and the CD-ROM driver. The net names of the attached clients are displayed by the CONSOLE task, which is not shown in the figure. The main program, SERVER, locates the CD-ROM driver and statically activates all the tasks. After initialization, the session tasks call the scheduler and are queued to be assigned to client calls in a fifo basis. The scheduler in turn makes a listen call to NetBIOS. When a client call is received, it is passed on to the session task. This is repeated until all the sessions have been assigned clients. When the scheduler queue is empty, there will not be an active LISTEN and the server will not respond to new client calls. The session tasks receive the request header and any associated data from the client via NetBIOS packets and pass it on to the CD-ROM task. The CD-ROM's response is in turn encoded in NetBIOS packets and returned to the client. When a session terminates, the session requeues itself with the scheduler.

Non-preemptive tasking is used. To ensure the needed cooperation, the calls to NetBIOS are all nonblocking calls and pauses are inserted at appropriate points in the various tasks.

Real World Problems

Most of the server is written in the usual, straightforward style characteristic of Ada. However, Meridian Software's implementation (the system we used) of certain data structures makes interfacing to externally generated data streams difficult. If you remain totally within an environment of "correct" Ada programs, there's no problem. Unfortunately, the real world seldom affords such luxury.

The first problem relates to alignment holes. Alignment holes occur when space is inserted between the components of a record to satisfy alignment requirements of the individual components. For instance, a far pointer which uses four bytes of storage may have an alignment requirement that its storage address be divisible by four. When an object of such a type is included in a record type it is aligned at an offset divisible by four with filler bytes inserted as needed to effect the alignment. This makes it difficult to match externally defined records which ignore the alignment constraints.

To work around the alignment-hole problem, first you declare two new numeric data types: byte (range 0..255) and word (range 0..65535). A third type bytes is then declared to be an unconstrained Byte array. Type bytes can then be used to declare multibyte fields without introducing alignment holes. The declaration of W to be a 2-byte subtype of Bytes and DW to be a 4-byte subtype accommodates the two most common multibyte fields encountered in externally generated records. Typecasting functions convert objects of type W to/from type Word and objects of type DW to/from type system.address. Long_Integers are also declared to simplify the handling of these object types. These declarations are in the TYPES package shown in Listing One (page 113). Their use in record descriptions is shown in the Drivers package in Listing Two (page 113). Other required files are provided electronically; see "Availability," page 5.

The second problem relates to discriminant records. Discriminant records are used when a record may have any one of several different descriptions, depending upon the value in a specified field called the discriminant. (A strongly typed version of Cobol's redefine facility.) The restriction that the discriminant field must be the first component of the record imposed by some Ada implementations, can often be worked around easily through judicious type declarations. Meridian Ada introduces a further complication for discriminant records whose discriminant may change during the execution of the program by preceding the discriminant with a constrained flag byte. This flag will have a value of 0 unless the record object is constrained.

A discriminant record is used for the internal representation of the externally generated device-driver request header. The command code is the discriminant that determines the variant-record description. The external form of the request header is treated as a packet of type bytes (that is, a byte array). When the packet arrives from the client via NetBIOS, the command code and all subsequent bytes of the packet are treated as a byte slice and are shifted right one byte in the array. The third byte, formerly the command-code byte, is zeroed and becomes the constrained-flag byte when an unchecked_conversion is used to typecast the packet as a request header; see Figure 3.

The request header must be converted back to a packet before being returned to the client via NetBIOS. Similarly, when the request header is sent on to the CD-ROM, it is first converted to a packet before it is sent, and back to a request header when it is returned. Fortunately, request headers are only about 30 bytes in length and there is not a great deal of overhead associated with the conversion.

Putting It All Together

The client was assembled using MASM 6.0b, and the server was compiled and linked using Meridian Software's Ada 286 Version 4.1.4. The compiler switch, -K, was used to retain the internal form information needed for optimization in the link step and the switch, -fs, used to turn off runtime checks. The latter improves performance significantly.

The link parameters, -G -s 27500 -m 38000, were used to invoke global optimization and to increase the tasking stack and main-program stack space, respectively.

The server must be started before MSCDEX is started on the client workstation but after NetBIOS and the CD-ROM driver are loaded on the server machine. The CD-ROM driver should be installed with the device name CD001 (for example, use the command-line parameter /d:CD001). When the server starts, it attaches to CD001 and installs itself on the network using the net name SHSU-CD-SERVER. There are no command-line parameters and the server will have to be recompiled to change either the server name or the driver name.

Mapping the Request Header

The client program, CDNET, can be loaded at any time after NetBIOS is started on the client machine. A list of the command-line parameters supported is displayed if CDNET is started with the /? switch. Normally, at least the /c: switch will be needed to assign the workstation a unique net name. When CDNET is loaded, it installs itself and validates the client name. This takes a few seconds while the name is broadcast. The client software will not load if the client name is already in use on the network. The client does not attempt to establish a session with the server until MSCDEX is loaded.

Assuming NetBIOS is installed, a typical sequence to load CDNET and MSCDEX might look something like this (case is not important):

CDNET /c:Monique

MSCDEX /d:CD-NET

Additional drivers, either local or network, can be included using the /d: switch on the MSCDEX command line. Prior to loading MSCDEX, CDNET can be unloaded with the /U switch. Unfortunately, MSCDEX is not unloadable, and it is best to not unload CDNET after MSCDEX has been loaded.

Both the client and the server have been tested on stand-alone, DOS-based machines and in specific DOS sessions under OS/2 2.0 using Novell's NetBIOS. However, David Schwaderer's C Programmer's Guide to NetBIOS, IPX, and SPX (Sams, 1992), based upon IBM's NetBIOS implementation, was the source book for coding both the Ada and MASM NetBIOS interfaces. Thus, the programs should run with any NetBIOS implementation that supports the 5Ch interrupt.

The server is configured to handle 32 simultaneous users. To do so, however, NetBIOS must be configured to handle at least 32 sessions and 32 outstanding commands. Novell's NetBIOS defaults to handling a maximum of 12 outstanding commands and thus must be configured to handle additional commands. The test servers were configured by placing the commands NETBIOS COMMANDS=32 and NETBIOS SESSIONS=32 in the NET.CFG file. The commands are holdover SHELL commands and should be left-justified. If the commands are placed at the end of the file, make certain that the last line is terminated with a return.

NetBIOS configuration commands written in the newer NET.CFG format are shown in some books written about NetWare. Perhaps they will work for you. NetBIOS displays the configuration information in its installation message (that is, if it's configured for 32 commands, it will say so).

Figure 1: Networked CD-ROMs.

Figure 2: CD-ROM driver header.

 DeviceHeader     label     word
   NextDriver     dword     -1
   Attributes     word      0C800h
                  word      Strategy
                  word      Interrupt
   DeviceName     byte      'CD-NET  '
   Reserved       word      0
   DriveLetter    byte      0
   NumberOfUnits  byte      1

Table 1: DOS driver commands (required commands are in upper case, optional commands in lower case, inapplicable commands in italics).

  0     INIT
  1     <I>media check</I>
  2     <I>build bpb</I>
  3     IOCTL INPUT
  4     <I>input</I>
  5     <I>input nowait</I>
  6     <I>input status</I>
  7     input flush
  8     <I>output</I>
  9     <I>output with verify</I>
  10    <I>output status</I>
  11    output flush
  12    IOCTL OUTPUT
  13    DEVICE OPEN
  14    DEVICE CLOSE
  15    <I>removable media</I>
  16    <I>output until busy</I>
  128   READ LONG
  129   <I>reserved</I>
  130   read long prefetch
  131   SEEK
  132   play audio
  133   stop audio
  134   write long
  135   write long verify
  136   resume audio

Figure 3: Mapping the request header (a = length, b = sub unit,

c = constrained flag, d = command, e = status).


[LISTING ONE]

(Text begins on page 72.)

--*****************************************************
--  TYPES.ADS
--  A copyright-reserved, free use program.
--  (c)John H. McCoy, 1993
--  Sam Houston St. Univ., TX 77341-2206
--*****************************************************

with system, memory;
with unchecked_conversion;

package Types is

type byte is range 0..255;
type word is range 0..65_535;

--  The following types and functions are declared for use
--  instead of integer, and system.address to avoid "align-
--  ment holes" in record declarations.  This may not be a

--  problem with other than Meridian's implementation of ADA.

type bytes is array (word range <>) of byte;
type words is array (word range <>) of word;
subtype  W is bytes(1..2);
subtype DW is bytes(1..4);
function W_to_Word is new unchecked_conversion(W, Word);
function DW_to_SA is new unchecked_conversion(DW, system.address);
function DW_to_Long is new unchecked_conversion(DW, Long_Integer);
function Word_to_W is new unchecked_conversion(Word, W);
function SA_to_DW is new unchecked_conversion(system.address, DW);
function Long_to_DW is new unchecked_conversion(Long_Integer, DW);


subtype string8 is string(1..8);
subtype string16 is string(1..16);


function "+"(left, right: byte) return byte;
function "OR"(left, right: byte) return byte;
function "+"(left, right: word) return word;
function "OR"(left, right: word) return word;
function "+"(left, right: W) return W;
function "OR"(left, right: W) return W;

end Types;

[LISTING TWO]
<a name="0232_000e">

--******************************************************************
--  DRIVERS.ADS     Excerpted Listing
--  A copyright-reserved, free use program.
--  (c)John H. McCoy, 1993, Sam Houston St. Univ., TX 77341-2206
--******************************************************************
with system, memory, unchecked_conversion;
with Types; use Types;

package drivers is

type DEV_CommandCodes is new byte;
   DeviceReadLong   : constant DEV_CommandCodes := 128;
type DEV_ReturnCodes is new W;
   DeviceDone   : constant DEV_ReturnCodes := Word_to_W(16#0100#);
type rhIoctlIns is record
      Status        : DEV_ReturnCodes;
      reserved      : bytes(6..13);
      MediaDesc     : byte;
      CBPtr         : DW;
      TransferCount : W;
      Start         : W;
      VolIdPtr      : DW;
   end record;
type rhXs (Command: DEV_CommandCodes := DeviceReadLong) is record
   case Command is
      when DeviceInit               =>  Init    : rhInits;
      when DeviceIoctlInput         =>  IoctlIn : rhIoctlIns;
      when DeviceIoctlOutput        =>  IoctlOut: rhIoctlOuts;
      when DeviceReadLong |
           DeviceReadLongPrefetch   =>  ReadLong: rhReadLongs;
      when DeviceSeek               =>  Seek    : rhSeeks;
      when others                   =>  Other   : rhOthers;
   end case;
   end record;
type rhs is record
      Length    : byte;
      SubUnit   : byte;
      rhX       : rhXs;
   end record;
subtype pkts is bytes(1..rhs'size/8);

function Rhs_to_Pkts is new unchecked_conversion(Rhs, Pkts);
function Pkts_to_Rhs is new unchecked_conversion(Pkts, Rhs);
end drivers;
<B>End Listings</B>


Copyright © 1993, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.