Developer Links
SUPPORT FORUMS  
SAMPLE CODE & TIDS  
TEST/DEVELOPMENT KIT INFO
 
DEVELOPER LABS  
NOVELL SUPERLAB  
SUPPORT DOCUMENTS  
STRATEGIC/EXECUTIVE ISSUE TRACKING  
SYSOP PROGRAM  
     
Related Links
 
 
 
 
 
Related Links  
 

 
DEVELOPER SUPPORT HOME  

JUNE 1993 VOLUME 5 NUMBER 6


INDEX


MAD'S COLUMN

Hello and welcome to the June 1993 issue of Bullets!

As I was thinking of a column theme for this month's issue of Bullets, my thoughts kept coming back to implementation of developer support. I wondered if it was too close to my personal issues.

Discussions of the search for the elusive "best," "most encompassing," "all-in-one-you'll-ever-need" API sets for developers race across magazine headlines. In much smaller print appears news on the search for the means to support them. API diversity sets the tone for the wide range of support methods that pervade the PC world.

Thankfully, not all development efforts die in three to five years. Good legacy applications, and hence accompanying compilers, tools, TSRs and runtimes, will survive! How should these products be supported?

You, as an internal or external application developer, must face this question too. The quest to support these legacy products in conjunction with current flagship products as we round the corner with beta offerings on new products is real.

The task of defining and offering the appropriate support for all levels of developers and developer products is a challenge. Not a new task, certainly, but an evolving one. At Novell, "Two years ahead and gaining," applies to services as well as to engineering.

Watch out, Jean-Luc, as we "make it so."

Mad Poarch
Director, Developer Support/Service


ARTICLES:

Novell's DOS Protected Mode Services (DPMS)

In today's DOS environments resident software is used to provide extensions to DOS. It enhances DOS performance and capabilities, and provides the interface to external environments and peripherals, such as networks and optical drives. But while these memory resident extensions add functionality to DOS, where conventional memory is limited to 640K, they can also use up considerable amounts of memory needed to run DOS applications.

The DOS Protected Mode Services (DPMS) API from Novell is specifically designed to allow DOS device drivers and TSRs to execute in protected mode and reside in extended memory. DPMS frees conventional memory for applications and allows memory resident software to access up to 16 megabytes of extended memory on 286, 386 and 486 computers. This article provides a look at the philosophy behind DPMS, as well as a functional overview and specific coding examples of a DPMS client program provided with the DPMS Software Developer's Kit (SDK).

Memory Resident Software & Limited Space

As mentioned above, DOS programs have traditionally been limited to using 640K of memory. Additionally, they have had to share this limited space with a variety of resident software programs, such as TSRs and device drivers. And while DOS applications have continued to grow in size, so have the size, complexity and number of TSRs and drivers employed in a typical DOS user's configuration, leaving even less memory available for those applications. This is especially true in networked environments, where resident device drivers, transport protocols, shells and redirectors use up even more of that precious 640k.

Numerous technologies have emerged to alleviate the 640K restriction. DOS memory managers help to optimize memory by allowing device drivers and TSRs to load in "upper memory" between 640K and one megabyte on 386 computers. Examples of these memory managers are Qualitas 386MAX, Quarterdeck's QEMM and those provided in recent versions of MS-DOS and DR DOS. This approach works well for many situations, but does not fully solve the problem. For TSRs to "load high", they must now compete with the many BIOS routines, buffers and other system components that use upper memory. Because upper memory is fragmented and limited in size and availability, many TSRs still are forced to load in conventional memory.

Memory paging technologies, such as LIM-EMS, allow programs to access expanded memory through a small "window" in the addressable first megabyte of memory. However, this approach requires 64K for the paging window, and can only perform operations on data after it has been mapped into that window. Memory paging is an effective technique for applications like database and spreadsheet programs that need to manipulate large amounts of data, but it is not often well suited to the needs of resident system software designed to perform specific functions.

More recently DOS extender specifications, such as VCPI and DPMI, have emerged allowing DOS programs to use protected mode and access extended memory directly. While these technologies benefit large applications, they are generally too cumbersome for utility programs and TSRs.

The DPMS Approach

The DPMS API allows resident programs to access the protected mode capabilities of Intel 80286, 386 and 486 computers to store and execute code and data. Its primary functions are to allocate extended memory, manage protected mode descriptor tables and handle the transitions between real mode and protected mode execution. DPMS defines a "client-server" software architecture, where a client program calls the DPMS server to perform the necessary memory allocations and mode transitions.

In contrast to the technologies mentioned previously, the DPMS API is specifically tailored to the needs of TSRs and device drivers. Its smaller size and reduced complexity make it relatively easy and straightforward for developers to implement protected mode functionality into their programs. Vendors can easily port existing real mode drivers and TSRs to use DPMS, and allow them to run in real mode when a DPMS server is not resident.

DPMS client programs use very little upper or conventional memory, so more memory remains available for existing programs. In addition, DPMS supports current versions of DOS and coexists with existing memory managers, DOS extenders, and protected mode environments such as Microsoft Windows.

By using extended memory to execute and store data, DPMS client programs provide two notable benefits.

  • Because they can virtually eliminate their use of memory within the first megabyte, they do not constrain the amount of available conventional or upper memory. Users continue to add system enhancements without limiting their ability to run large applications.
  • By removing their own memory restrictions, DPMS clients can incorporate additional needed functionality and improve their performance. This ability will benefit all developers who write DOS TSRs. With the growing number of resident programs being used in today's DOS environments, the competition for the limited amount of DOS memory will continue to intensify. With DPMS, developers can use protected mode and extended memory to add extra functionality, and at the same time reduce their footprint in conventional memory. This gives them a significant advantage over competitors who must limit functionality to conserve memory.
The DPMS Software Developer's Kit (SDK) includes the Novell DPMS server. Developers may distribute the server component royalty-free with their DPMS client programs. DPMS is not tied to any specific DOS version, so clients can run on current versions of DOS. (Note: the currently shipping SDK includes a prerelease version of the DPMS server for developing prototypes. Once the DPMS server is finalized, developers will be updated and allowed to ship that server.)

Novell will be including the DPMS server and several DPMS clients in Novell DOS 7, scheduled to ship late this summer. It will include DPMS versions of its disk cache and disk compression driver, the peer-to-peer desktop server and CD- ROM file extensions.

DPMS Functional Overview

DPMS services are provided by the DPMS server, a device driver that must be loaded before any client can use its services. These services consist of about 25 functions that:
  • Handle mode transitions between real and protected mode
  • Allocate and manage protected mode descriptors
  • Map descriptors to real mode memory segments
  • Allocate and map linear memory
Once the DPMS server is installed, it hooks onto the INT 2F chain. DPMS clients should first call INT 2F to check for the presence of DPMS and find the entry point addresses for subsequent calls. Depending on how a client is written, it may fail to install if DPMS is not installed, or it may operate entirely in real mode.

DPMS clients are created as real mode, 16-bit DOS .EXE or .COM programs, using standard assemblers and linkers. A typical structure might be:

  • Initialization code to be discarded after it is finished
  • Real mode code and data "footprint"
  • Relocatable code and data, which would be executed in protected mode and moved to extended memory.
DPMS clients always require a small amount of memory within the first megabyte. At a minimum, the client must maintain pointers to DOS and code to perform the DPMS call up to protected mode. Some developers may find it convenient to have specific functions execute in real mode, while the remainder of the program uses protected mode.

DPMS Code Examples

The following code examples are taken from VDISK.ASM, a sample client included in the DPMS SDK. In the examples, INIT_TEXT contains the initialization code, PROT_TEXT contains code to be relocated in protected mode and the call down to real mode code, and LOW_TEXT contains the code to remain in real mode and the call up to the protected mode code. To simplify the code excerpts in this article, the global variables declared in Figure 1 apply for all other code excerpts.

FIGURE 1: Global variable declarations

real_func  dd  ?
;real_func will be set to point to DPMS's real mode entry
; point if a DPMS server is found. The code in this segment
; will then use this vector to call DPMS to request a call up
; to the protected mode code in PROT_TEXT.

prot_func  dd  ?     ;DPMS protected mode entry point.

prot_sel   dw  ?
;Protected mode data selector for a data segment descriptor
; that will be aliased to PROT_TEXT's (code) descriptor,
; allowing us to write into PROT_TEXT.

DPMS_Reg_Struc <0,0,0,,0,0,0,0,0,,,EFL,0, 0,, ,, ,, ,, ,>
;This is the DPMS register structure used to make DPMS calls.
;Order of the fields is:

; Bits        Register             Bits      Register
; 00h           EDI                20h         EIP
; 04h           ESI                24h         CS/reserved
; 08h           EBP                28h         EFLAGS
; 0Ch           reserved           30h         SS/reserved
; 10h           EBX                34h         ES/reserved
; 14h           EDX                38h         DS/reserved
; 18h           ECX                3Ch         FS/reserved
; 1Ch           EAX                40h         GS/reserved

END of FIGURE 1
The example in Figure 2 allocates the data descriptors to be used while in protected mode. The code allocates:
  • One data descriptor for the device driver request packet -the device driver request packet is a data structure passed to VDISK when ever DOS attempts to perform operations on the virtual disk.
  • One data descriptor for the (extended memory) data area in PROT_TEXT
  • One data descriptor for an alias to the LOW_TEXT real mode segment
FIGURE 2: Allocating protected mode descriptors
.
.
.
mov   cx,3                 ;Number of descriptors required
mov   ax,DPMS_D_ALLOC      ;DPMS function number 0200h
                           ;Allocate device request packet
                           ; descriptor
call  [real_func]          ;Call DPMS through real mode
                           ; entry point
jc    dpms_err
mov   request_sel,ax       ;This gives PROT_TEXT its ES.
                           ;It cannot be initialized yet.
add   ax,8                 ;Get next selector

mov   prot_sel,ax          ;Protected mode data selector,
                           ; which will be initialized
                           ; further down by DPMS_D_SET_BASE
                           ; to an alias of PROT_TEXT's code
                           ; segment descriptor.

add   ax,8                 ;Get next selector
mov   data_sel,ax          ;Local data descriptor which gives
                           ; PROT_TEXT its DS, and
mov   bx,ax                ; aliases the descriptor to.
mov   cx,ds                ; LOW_TEXT.
mov   ax,DPMS_D_ALIAS_REAL ;DPMS function number 0203h
call  [real_func]
jnc   dpms_fixup           ;If carry = 1, free descriptors
                           ; in reverse order using DPMS
                           ; function 0201h
.
.
.

END of FIGURE 2
The example code in Figure 3 moves the protected mode segment into extended memory through the following sequence of operations:
  • Relocate PROT_TEXT to extended memory.
  • Set the base of the descriptor to a linear address returned from DPMS_M_RELOC using the selector (prot_sel) for that descriptor.
  • Change the limit of the descriptor to make sure that data is only written via "prot_sel."
FIGURE 3: Relocating code into protected mode
rsCallup  DPMS_Reg_Struc

.
.
.
push  es
mov   ax,PROT_TEXT
mov   es,ax
xor   si,si                 ;ES:SI is the base of the
                            ; PROT_TEXT segment

mov   cx,offset PROT_TEXT:prot_end  ;size

dec   cx                    ;limit=size-1.
mov   bx,009Ah              ;Executable segment
xor   dx,dx                 ;DPMS to allocate a descriptor
mov   ax,DPMS_M_RELOC       ;DPMS function number 0400h
call  [real_func]           ; returns with selector in AX
mov   rsCallup.DPMS_CS,ax   ;Selector for executable
                            ; descriptor
                            ;DPMS_M_RELOC returns the linear
                            ; address in BX,CX

                            ;Next, call DPMS_D_SET_BASE to
                            ; make prot_sel into data alias
mov   dx,cx                 ; to same segment.
mov   cx,bx                 ;CX,DX is linear base address.
mov   bx,prot_sel
mov   ax,DPMS_D_SET_BASE    ;DPMS function number 0204h
call  [real_func]
                            ;Then, call DPMS_D_SET_LIMIT to
                            ; make sure you only write to the
                            ; data via prot_sel.

mov   dx,offset PROT_TEXT:prot_data_size

dec   dx                    ;limit=size-1
xor   cx,cx                 ;Limit in CX,DX. Since limit<64K,
                            ; then CX=0
mov   bx,prot_sel
mov   ax,DPMS_D_SET_LIMIT   ;DPMS function number 0205h
call  [real_func]
.
.
.

END of FIGURE 3
Figure 4 demonstrates how to call up from real mode segment to protected mode segment by performing the following operations:
  • Set up the descriptor that was reserved previously so that it points to the request packet by mapping a real mode segment (value is in ES) via a protected mode descriptor stored in request_sel
  • Pass control to the protected mode dispatch routine, which calls a procedure in protected mode. The DPMS_CALL_PROT function sets the CPU registers on entry to the values set up in the DPMS_Reg_Struc.
FIGURE 4: Calling up to protected mode
request_sel  equ   DPMS_ES    ;ES:BX points to request
                              ; structure
request_off  equ   DPMS_BX
data_sel     equ   DPMS_DS    ;Alias to the LOW_TEXT segment.

callup  PROC  FAR
        IRP   reg,
        push  reg
        ENDM

        mov   request_off,bx        ;Save the caller's BX

        mov   bx,request_sel        ;Alias selector
        mov   cx,es                 ;Request packet's segment
        mov   ax,DPMS_D_ALIAS_REAL  ;DPMS function 0203h
        call  [real_func]

        mov   ax,cs
        mov   es,ax                       ;ES:DI must point
                                          ; to rsCallup
        mov   di,offset LOW_TEXT:rsCallup ;VDISK code in
                                          ; protected mode.
        xor   cx,cx                      ;Copy 0 words from
                                          ; stack to stack
        mov   ax,DPMS_CALL_PROT          ;DPMS function
                                          ; number 0100h
        call  [real_func]
        mov   ax,rsCallup.DPMS_AX        ;Restore error code.

        IRP   reg,
        pop   reg
        ENDM
        ret
callup  ENDP

END of FIGURE 4
The example code in Figure 5 demonstrates how to make a call down from a protected mode segment to a real mode segment. Calling up requires a call to the DPMS_CALL_PROT function (the call to a protected mode segment). DPMS_CALL_PROT can only terminate with a RETF, so the only function needed to make a call down to a real mode segment is DPMS_CALL_REAL_RETF. This function must be called from protected mode.

FIGURE 5: Making a call down to real mode

rsCalldown  DPMS_Reg_Struc

xms_func_IP    equ  DPMS_IP
xms_func_CS    equ  DPMS_CS
prot_data_size  label byte          ;used in INIT_TEXT to set
                                    ; limit on prot_sel

dpms_callxms PROC  NEAR
             push  es
             mov   es,prot_sel            ;alias to PROT_TEXT
                                          ; descriptor
             mov   es:rsCalldown.DPMS_AH,XMSMOVE
             mov   di,offset PROT_TEXT:rsCalldown
                                          ;DPMS reg. offset
             xor   cx,cx
             mov   ax,DPMS_CALL_REAL_RETF ;DPMS func. 0101h
             call  [prot_func]            ;call DPMS via
                                          ; protected mode
             pop   es                     ; entry point.
             ret
dpms_callxms ENDP

END of FIGURE 5
In VDISK, DPMS_CALL_REAL_RETF is only used to call XMS (Extended Memory Specification), so some of the registers can be initialized with the correct values. In general, you might call down to more than one real mode entry point, so you would need to initialize the rsCalldown function each time you used it.

At the beginning of the code segment in Figure 5, the DS register points to LOW_TEXT, where the "prot_sel" variable holds a selector which is used for a data segment alias to the PROT_TEXT (code) descriptor. This is loaded into ES to allow you to write into PROT_TEXT since, in protected mode, you cannot write into a code segment.

For More Information

Novell's DOS Protected Mode Services (DPMS) SDK, is available to members of Novell Developer Programs. To purchase the DPMS SDK, contact Novell Developer Relations at 1-800-NETWARE (1-800-638-9273) or 1-801-429-5588.

Saving & Restoring Drive Mappings & the NetWare Client SDK

Many programming situations require you to save drive mappings and later restore the mappings to their original state. You may need to save and restore drive mappings when recovering critical applications after a connection is lost. This procedure also may be necessary for special processing requiring an application to login to a server that already has an active connection, which will cause the respective drives for that server to be cleared as well.

The February 1992 issue of Bullets featured an article on saving and restoring drive mappings using the NetWare C Interface for DOS v1.2. This article presents an update demonstrating a mechanism for obtaining and restoring drive mappings using the NetWare Client SDK. With either kit, the methods used are fundamentally the same, but certain underlying differences in the required APIs make the process easier with the NetWare Client SDK.

The article demonstrates methods for saving and restoring drive mappings through excerpts from an application called MAPIT2.C that can be downloaded from Novell's CompuServe forum, NDEVREL (Library, filename: MAPIT2.ZIP). MAPIT2.C uses environmental control functions to obtain and set the root environment variables. These functions are used to modify the PATH environment variable to obtain and set the search drives for both local and NetWare drive mappings. Although MAPIT2.C uses these routines, this article does not explicitly discuss the environmental control functions.

Saving Mappings

Required APIs:
  • NWGetDriveStatus( )
  • NWGetFileServerName( ) Required Functions:
  • Environmental control functions to GET and SET the PATH variable at the "root" environment for search drive manipulation.
The simplest approach for saving mappings is to use NWGetDriveStatus( ) to obtain the drive status (information) for all 26 valid drives (drives "A"[1] "Z"[26]). If you are using NETX, this API actually runs through 32 drives which includes currently mapped temporary drives as well (temporary drives are only mapped during the execution of the application that mapped them). NETX.VLM allows 26 valid drives ("A" through "Z").

The NWGetDriveStatus API accepts a drive number as input and allows a range of path formats to be returned depending on a "pathFormat" flag. The drive mappings are stored in "mapDrive," (see Figure 6 for the "mapDrive" structure definition).

FIGURE 6: "mapDrive" control structure

#define MAXMAPPINGS          32

/* Mapping Control Structure:
** Client SDK - use full paths with server name
** NOTE: the search drive order is not respect to the mapped
** drive indexes. They are essentially independent. */

struct {
  WORD          status;
                             // status type:used,network...
  NWCONN_HANDLE connHandle
                             // server connection Handle
  char          serverName[48
                             // server name
  BYTE          isSearch
                             // 0 if non-search drive,
                             // non-0 otherwise
  char          srchpath[256];
                             // search paths
  char          rootPath[256];
                             // root part or rootPath (base)
  char          relativePath[256];
                             // path off of the base
  } mapDrive[MAXMAPPINGS];

END of FIGURE 6
As shown in Figure 7, "mapDrive" is indexed by NetWare drive number.
     FIGURE 7: Saving drive mappings

for (drv = 0; drv < MAXMAPPINGS; drv++) {
  Code = NWGetDriveStatus(
                drv + 1,
                NW_FORMAT_NETWARE,
                &mapDrive[drv].status,
                &mapDrive[drv].connHandle,
                (char NWFAR *)&mapDrive[drv].rootPath[0],
                (char NWFAR *)&mapDrive[drv].relativePath[0],
                tmpPath);
  NWGetFileServerName(
                mapDrive[drv].connHandle,
                mapDrive[drv].serverName);
/*
  printf(
     "\n[%d] base=%s path=%s status %x connHandle %x\n\ttmppath=",
     drv,
     mapDrive[drv].rootPath,
     mapDrive[drv].relativePath,
     (int)mapDrive[drv].status,
     mapDrive[drv].connHandle,
     tmpPath);
*/
   }

END of FIGURE 7
For the purposes of this article, the NW_FORMAT_NETWARE constant is the best choice. The information returned by the API (status, connection handle, root path, relative path, and full path) fully describe each mapped drive. The "status" mask dictates what type of drive it is: NW_UNMAPPED_DRIVE, NW_LOCAL_FREE_DRIVE, NW_LOCAL_DRIVE, or NW_NETWORK_DRIVE. This drive type is defined in NWDPATH.H. The connection handle returned indicates the server to which the drive is mapped. If a server connection is dropped before you have a chance to reset the drive mapping, you should obtain the server name for each drive using NWGetFileServerName( ). The server name is required, since the connection handle may change when connections are reestablished.

Search drives are handled strictly by the PATH environment variable, and in all cases the ROOT environment is manipulated. Each drive/path specified in the PATH environment variable is parsed and the path order is recorded, taking into account the full paths specified for local drive mappings. The sample code in Figure 8 uses the "search" field in the structure to store full path names for all local mappings and only the drive letters for network mappings. The path order is dictated strictly by the index into "mapDrive" as shown in Figure 8.

FIGURE 8: Storing path names for local mappings & drive letters for network mapings

GetEnvironmentVar("PATH",thePath);
if (thePath[0] != NULL) {
   pntr = thePath;
   for (drv = 0; (drv < MAXMAPPINGS) &&
        (pntr = _fstrchr(pntr,':')); drv++ ) {
      pntr2 = _fstrchr(pntr,';');
      if (pntr2)
         _fmemcpy(mapDrive[drv].srchpath, pntr - 1,
                  (int)(pntr2 - pntr + 1));
      else
         _fstrcpy(mapDrive[drv].srchpath, pntr - 1);
// printf("\nSearch: %d: - %s",drv, mapDrive[drv].srchpath);
      *pntr = ' ';
// mark respective drive as search drive if netware drive
      if (mapDrive[*(BYTE *)(pntr - 1) - 'A'].connHandle) {
         mapDrive[*(BYTE *)(pntr - 1) - 'A'].isSearch = 1;
         }
      } /* while */
  }

END of FIGURE 8

Restoring Mappings

Required APIs:
  • NWSetDriveBase
  • NWGetConnectionHandle
Required Functions:
  • chdir( )
The sample code in Figure 9 assumes that you still have connections to the servers to which your drives were originally mapped. If not, you can use the "mapDrive" structure to determine what server connections are required to restore the mappings. Since the connection handle may change for new server connections, you should call NWGetConnectionHandle( ) before mapping a drive using NWSetDriveBase( ).

FIGURE 9: Restoring mapped NetWare Drives

// re-map all netware drives

for (drv = 0; drv < MAXMAPPINGS; drv++) {
  // If connection ID specified for drive, then reset it,
  // otherwise it's a local mapping only.

  switch (mapDrive[drv].status) {
    case NW_UNMAPPED_DRIVE:   // no need to handle this one
          break;

    case NW_LOCAL_DRIVE:      // map local drive
          break;

    case NW_NETWORK_DRIVE:    // redirected drive
       // printf("\nDrive:  %c := Redirected...not handled",
                 'A' + drv);

    case NW_NETWARE_DRIVE:    // netware drive

       // printf("\nDrive:   %c: = %s\\%s \\ %s",'A' + drv,
       //        serverName, mapDrive[drv].rootPath,
       //        mapDrive[drv].relativePath);

          cCode = NWGetConnectionHandle(
                                   mapDrive[drv].serverName,
                                   0,
                                   &mapDrive[drv].connHandle,
                                   NULL);
          if (cCode) {
            printf("\n\tNWGetConnectionHandle: %c: - error
                   %x",'A'+drv,cCode);
            break;
            }
          cCode = NWSetDriveBase(drv + 1,
                                 mapDrive[drv].connHandle,
                                 NULL,
                                 mapDrive[drv].rootPath,
                                 0);
          if (cCode) {
            printf("\n\tNWSetDriveBase: %c: - error %x",
                   'A'+drv,cCode);
            break;
            }

          // if there's a path off of base, then 'cd' to it

          if (mapDrive[drv].relativePath[0]) {
            sprintf(tmpPath,"%c:%s",'A' + drv,
                    mapDrive[drv].relativePath);
            if (cCode = chdir(tmpPath)) {
              printf("\n\tchdir: failed - %s (%d)",
                     tmpPath,
                     errno);
              }
            }
          break;

    case NW_LITE_DRIVE:
       // printf("\nDrive:  %c := NWLite...not handled",
                'A' + drv);
          break;
    } // switch
  } // for

END of FIGURE 9
The first required task is to map all network drives. The search drives are handled strictly by the PATH environment variable, which will be discussed later.

In the sample code, all drives are initially mapped to their rooted portion (base) using NWSetDriveBase(), then a chdir() (change directory) is used to move to the appropriate relative path. Both the "rootPath" and "relativePath" portions of each drive mapping were stored earlier for this operation. NWSetDriveBase sets the drive to a base (root) only, and the relative portion of the path must be set using the change directory function. In most cases, the "server\volume:" is the rooted portion and relative portion consists of the remainder of the path from the volume to a particular directory.

The "status" field is also required to map each drive, since it determines what kind of drive is being remapped. This field dictates whether the drive was local, free, a redirected drive, a NetWare drive, or some other kind. For this article, only NetWare drives are considered.

Search Drives

Search mappings are handled strictly by the root PATH environment variable. Both local drive mappings and NetWare search drives are restored by building a new PATH environment variable and writing/putting it back to the root environment. It is built quite easily from the data stored in the "mapDrive" structure using the "search" field and a sequencial "index" into "mapDrive." The drives and paths stored in the "search" field are concatenated repeatedly for search drives to build the new PATH environment variable. Note that the "search" field contains either full paths for all local drives, and only drive letters followed by a ":." for all NetWare drives. Figure 10 contains a code segment depicting creation of the PATH enviroment variable.

FIGURE 10: Creation of the PATH environment variable

// now reset the search drives & create the PATH variable
thePath[0] = NULL;
for (drv = 0; drv < MAXMAPPINGS && mapDrive[drv].srchpath[0];
     drv++)
  {   strcat(thePath,mapDrive[drv].srchpath);
  strcat(thePath,";");
  } /* for */
// update the root environments
strcpy(tmpPath,"PATH=");
strcat(tmpPath,thePath);
PutEnvironmentVar(tmpPath, masterEnvPntr, maxEnvSize);

END of FIGURE 10
Note: that the structure used for saving the drives is used for more than one purpose. The search order is strictly adhered to in the