;       SCCSID = @(#)chardd.asm 6.4 92/02/14
SCCSID  equ     "@(#)chardd.asm 6.4 92/02/14"
PAGE    ,132
TITLE   CHARDD - Management of Character Devices in the system
NAME    CHARDD

;*** Character device management in the system.
;
;
;   With the implementation of IFS support in the system, added support has
;   been put in to handle character devices. It is possible to specify
;   pseudo-character devices i.e. a name refers to a device which may be on
;   a remote machine but which can be accessed by an IFS.
;
;   The operation of the system in detecting and defining character devices
;   has been changed to have a more structured approach. The pre-IFS
;   implementation was very messy, and would have caused a lot of conflicts
;   and problems when used with installable file systems that support file
;   names using formats different from the 8.3 format. Additional problems
;   would have occurred when pseudo-character devices were created or
;   destroyed.
;
;   The pre-IFS system used to keep two linked lists of device headers (one
;   for real mode devices and one for bimodal devices). The reason for having
;   these chains was for comaptibility. A device driver could traverse the
;   chain to see what other devices had been installed into the system. For
;   bimodal devices, we only had this chain and, when the kernel needed to
;   know if a name represents a device, it just traversed the chain searching
;   for the name. For real mode devices, it was necessary to store additional
;   data in order to enable the kernel to search for a device name. Since the
;   real mode chain had to be maintained using real mode addresses (i.e.
;   seg:off), we had to have a way of scanning the names in protect mode. We
;   set up the data for the real mode devices in an RMP segment - the data
;   included the name, a GDT selector to access the device header, and the
;   address of the header.
;
;   All this was fine because every device had to have a device header that
;   we could link into the appropriate chain. With the concept of pseudo-
;   character devices, this is not necessarily the case. Also, additional
;   information must be kept to identify the type of device (remote, pseudo,
;   etc.) and the IFS that knows about the device. So, a reorganization is
;   necessary.
;
;
;   NEW IMPLEMENTATION
;   ------------------
;
;   In order to support variable length device names, we need a scheme that
;   will not waste space for devices with short names. We use variable
;   length records managed by RMP to contain the information for the devices
;   in the system.
;
;   The following facilities now exist:
;
;     - Can add new devices dynamically, not just at INIT time
;
;         We can install pseudo-character devices at any time. The base and
;         the installable device drivers, however, can only be installed
;         at system INIT time.
;
;     - Can search for device names in the system
;
;         We can scan for a name to see if it represents a device or not.
;         We also have a way of differentiating between real mode and
;         bimodal devices.
;
;     - Can remap physical devices with pseudo devices.
;
;         Pseudo-character devices can be added or removed from the system.
;         Other device drivers cannot be removed once set up.
;
;
;   Post-IFS Operation
;   ------------------
;
;   1) In the 3x Box, the operation has not changed except that we ignore
;      all path prefixes specified. We only examine the last component in
;      the path to see if it represents a device. This is different in that
;      we used to verify if the path was valid on the current volume before
;      we checked for the device. This verification is not necessary and
;      changing this part of the behaviour should not break any compatibility
;      with existing applications.
;
;      Thus, Filename extensions and path prefixes are ignored
;
;
;   2) In protect mode, we recognise the following names as attempting to
;      indicate a device:
;
;           - If the last component in the path specified is one of a small
;             set of devices (CON, PRN, NUL, COM1 - COM9, LPT1 - LPT9,
;             KBD$, MOUSE$, SCREEN$, POINTER$,CLOCK$), then we ignore the
;             path and examine the installed devices to see if this is one
;             of them. This means that the "special" devices can exist in
;             any directory.
;             NOTE that suffices and trailing '.' are significant.
;
;           - All other names are **REQUIRED** to have a prefix of \DEV\ in
;             order to be treated as devices (i.e. they can only exist in the
;             \DEV\ directory on any drive).
;
;
;   3) All other path names are treated as though they specify a file.
;
;   The maximum length of a pseudo-character device name is set such that
;   the length of <drive>:\DEV\<device name>NUL is limited to 260 bytes,
;   i.e. the maximum length of the <device name> component for a pseudo-
;   character device will be 252 bytes. (Other character devices will be
;   restricted to names that are at most 8 bytes long by the limitations
;   in the device headers).
;
;
;  Implementation Issues
;   ---------------------
;
;   1) Remapping devices with pseudo-devices
;      -------------------------------------
;
;       We have the facility to be able to remap existing installed devices
;       with pseudo devices, e.g. LPT1 may be remapped to a printer on a
;       remote server. However, it is not a requirement that a pseudo device
;       be remapped by another pseudo device (this is consistent with existing
;       redirector behaviour) - therefore, a pseudo character device must
;       be destroyed and recreated rather than remapped.
;
;       The following features exist:
;
;           - We allow the remapping of physical devices with pseudo devices.
;
;           - We do NOT allow remapping pseudo devices with another
;             pseudo device. The first pseudo device has to be de-installed
;             before the next one can be installed.
;
;           - Real mode only devices (used only by the 3x box) cannot be
;             remapped.
;
;
;   2) Syntax for specifying a device
;      ------------------------------
;
;       It is required for compatibility reasons that we recognize certain
;       device names without any path e.g. "PRN". We only allow this for
;       a small special set of names. All other names must have the \DEV\
;       prefix.
;       The prefix is required so that in future we can move over to a
;       more secure environment, and devices will be present only in the
;       \DEV directory.
;
;
;   3) Device names in sharing set
;      ---------------------------
;
;       The name used to identify a device in the sharing set is of the
;       form \DEV\<device name>. This will be the canonicalized form of
;       device names.
;
;       NOTE that it is possible to have a file name with a similar name
;       in the sharing set, i.e. with a \DEV\ prefix. If there is overlap
;       in names between a device and a file, the device name will have
;       a higher preference.
;
;
;   Data structures.
;   ----------------
;
;   See CHARDD.INC for the necessary data structures.
;
;   18-Nov-95    MLM   Defect 72417, reduce fscrit serialization.
;
.xlist
.xcref
include fnofs.inc
include dosseg.inc
include basemaca.inc
include osmaca.inc
include osconst.inc
include oldmaca.inc
include mi.inc
include defdbug.inc
include dossymc.inc
include chardd.inc
include curdir.inc
include dbcs.inc
include devhdr.inc
include error.inc
include meta.inc
include vmowner.inc
include virtsym.inc
include seldesc.inc
include proc.inc
include pgexport.inc
include tkexport.inc
;   Defect 72417 change starts
include filesys.inc
;   Defect 72417 change ends
.cref
.list
        ModName , FNO_CHARDD_ASM

        ReferGlobal     CharDevRMPRec,DWORD
        ReferGlobal     CharDevSem,DWORD
        ReferGlobal     SpoolDevRMPRec,DWORD
        ReferGlobal     SpoolDevSem,DWORD
        ReferGlobal     _UdelSem,DWORD
        ReferGlobal     CNotifySem,DWORD
        ReferGlobal     NULdev,DWORD

        ReferTask       ProcType,BYTE
;   Defect 72417 change starts
        ReferTask       CurrTCB,WORD
;   Defect 72417 change ends

        EXTRN           PRN_Device_Name:BYTE
        EXTRN           LPT1_Device_Name:BYTE

        EXTRNFAR        PathPref
        EXTRNFAR        pJFNFromHandle


        EXTRNFAR        FreeTempBuffer
        EXTRNFAR        GetTempBuffer
        EXTRNFAR        Rmp_Alloc
        EXTRNFAR        Rmp_Add
        EXTRNFAR        Rmp_Find
        EXTRNFAR        Rmp_FindN
        EXTRNFAR        Rmp_Rem
        EXTRNFAR        Rmp_Wait
        EXTRNFAR        SegAlloc
        EXTRNFAR        SegFree
        EXTRNFAR        StrCmp
        EXTRNFAR        StrCpy
        EXTRNFAR        UCase

        EXTRN32         _KSEMInit
        EXTRN32         _KSEMRequestMutex
        EXTRN32         _KSEMReleaseMutex


rparm struc
rp_flags        db      ?               ; flags
                db      ?               ; (unused)
rp_selector     dw      ?               ; GDT selector to use
rparm ends

         PUBLIC DevPrefix
DATA segment
DevPrefix   db "\DEV\",0                ; device dir prefix MUST BE UPPERCASE

DATA ends

        FARCODE INIT
INITCODE SEGMENT


;***    CharDevTabInit - initialize the char device driver table
;
;       CharDevTabInit allocates a GDT selector for storing the char table
;       entries. The segment will be managed by RMP.
;
;       ENTRY   NONE
;
;       EXIT    GDT selector stored in the RMPrecord at CharDevRMPRec
;
;       USES    AX, DI, ES, Flags
;
;       ALGORITHM
;           Rmp_Alloc(&CharDevRec)
;           If error
;               InternalError("Rmp_Alloc failed")
;           Initialize CharDevSem and SpoolDevSem;

        CpuMode 386

Procedure CharDevTabInit,NEAR
        ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA
;       PMONLY

;       Allocate RMP segment for Pseudo devices
;       Rmp_Alloc(ax=hobowner,bx=hobmte,esi=PG flags,es:di->rmpdata)

        mov     ax,CHARDEVRMPOWNER
        xor     bx,bx
        mov     esi,PG_RESIDENT
        DosContext <es>,di
        mov     di,offset CharDevRMPRec ; (es:di) -> RMP segment data
        CALLFAR Rmp_Alloc               ; Allocate RMP segment
        jc      short cdti2             ; Internal Error if Rmp_Alloc failed

;   Initialize the mutex semaphores:
;       CharDevSem:  Synchronizes the removing of entries and open.
;       SpoolDevSem: Synchronizes accesses to spool objects.

        SaveReg <ds>
        FlatContext <ds,es>,ax
        CCALL32 _KSEMInit,<<offset FLAT:CharDevSem>, KRNL_MUTEXSEM,0>
        CCALL32 _KSEMInit,<<offset FLAT:SpoolDevSem>,KRNL_MUTEXSEM,0>
        CCALL32 _KSEMInit,<<offset FLAT:_UdelSem>   ,KRNL_MUTEXSEM,0>
        CCALL32 _KSEMInit,<<offset FLAT:CNotifySem> ,KRNL_MUTEXSEM,0>
        RestoreReg <ds>
        ret

cdti2:  InternalError <CharDevTabInit: Rmp_Alloc>
EndProc CharDevTabInit

        CpuMode 286

;***    SetPMCharDevEntries - Set up entries in table for prot mode devices
;
;       SetPMCharDevEntries is called via a call-gate at system INIT time
;       AFTER the file system has been initialized, but BEFORE any device
;       is accessed. Thus, it only does the setup for the base device drivers.
;       SetPMCharDevEntries sets up an entry for each bimodal character
;       device that has been installed in the system.
;       Because the device headers are already in the LIFO order in the chain,
;       the chain, we just call AddRecFromDevHdr which will make sure that
;       the device entry is not installed if one already exists for a device
;       with the same name.
;
;       ENTRY   NONE
;
;       EXIT    'NC'
;                   Table updated with the new entries.
;               'C'
;                   Error allocating memory
;
;       USES    AX, BX, CX, DX, DI, SI, DS, ES, Flags
;
;       WARNING The routine expects at least one device to have been
;               installed before it is called.
;
;       ALGORITHM
;
;           Get first bimodal device in chain
;           While (device found) {
;               If (character device) {
;                   AddRecFromDevHdr(device,ProtDev)
;                   }
;               Get next device in chain
;           }
;
Procedure SetPMCharDevEntries,NEAR
        ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA
;       PMONLY

        DosContext <DS>,SI
        MOV     SI,OFFSET DOSGROUP:NULdev   ; DS:SI - first header in chain

sde10:  test    [SI.SDevAtt],DEV_CHAR_DEV
        jz      sde30                   ; Skip block devices (NET and LOCAL)
        mov     al,DEVTYPE_BIMODAL      ; AL = Type (bimodal device)
        mov     ah,ACT_DEVDONTADD       ; AH = Action (Don't add if dev exists)
        call    AddRecFromDevHdr
sde30:  cmp     WORD PTR [SI].SDevNext,-1
        jz      sde40                   ; There are no more devices in chain
        lds     si,DWORD PTR [SI].SDevNext  ; Get address of next device
        jmp     short sde10

sde40:  RET

EndProc SetPMCharDevEntries


;***    TransDevName - canonicalize device name
;
;       This routine is used to canonicalize installed device names at init
;       time.  Pseudo-character device names are passed to Transpath and
;       don't use this routine.
;       The spaces within the name are preserved, but trailing spaces are
;       truncated. The name is upper-cased and NULL terminated.  We don't
;       need to check for >8 bytes in the name because the device header
;       name field is limited to 8 bytes.
;
;       ENTRY   DS:SI -> name     (in device header)
;               ES:DI -> Location to put transformed name
;
;       EXIT    'NC' if name is valid,
;                   Name copied to destination (NULL terminated)
;                   ES:BX -> NULL at end of name
;               'C' if error
;
;       USES    AX, BX, CX, Flags
;
;       ALGORITHM
;
;           Count = 0
;           Done = FALSE
;           while (Count++ <= PDEVNAMELEN) && (! Done) {
;               Get a character(TempNamePtr ++)
;               UCASE
;               Store character
;               if (TestDBCS()) {
;                   Copy trailing byte
;                   LastNonSpPtr = TempNamePtr  /* Remember last non-space */
;               }
;               else {
;                   if (char != " ")
;                       LastNonSpPtr = TempNamePtr  /* Remember last non-space */
;               {
;           }
;           *LastNonSpPtr = NULL
;           if (Done)
;               return(DevNameEndPtr)
;           else
;               return(error)
;
Procedure   TransDevName,NEAR,local
    ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA
;       PMONLY

        SaveReg <SI,DI>

        SaveReg <ES,SI>
        mov     cx,DEVNAMELEN
        call    ChkVal                  ; check for illegal characters

;   dl = 0 -> only 8.3 characters
;   dl = 1 -> contains newly-valid characters

        RestoreReg <SI,ES>
        mov     ax,ERROR_INVALID_PATH
        stc
        jz      tdn30                   ; illegal chars, go return error
        or      dl,dl
        stc
        jnz     tdn30                   ; non-8.3 chars, go return error

        mov     cx,DEVNAMELEN
        mov     bx,di                   ; BX = Point where last blank was found
;
; We make sure that the name is all upper cased. This is so that we are
; always consistent in our checking.
;
tdn10:  lodsb
        CALLFAR UCase
        stosb
        TestDBCS <tdn12>
        jnz     tdn12                   ; It is not a DBCS character
;
; We copy over the trailing byte to the destination.
;
        dec     cx                      ; Make sure we don't exceed length
        movsb                           ; Copy over trailing byte
        mov     bx,di                   ; Update pointer to non-null character
        jmp     short tdn15             ; See if any more characters to copy.

;
; The character is a single-byte character. See if it is the end of string.
;
; AL = character
;
tdn12:  or      al,al
        jz      tdn20                   ; Got the end of string
        cmp     al,' '
        jz      tdn15                   ; Found a space. Skip pointer update.
        mov     bx,di                   ; Remember where last non-space found.
tdn15:  loop    tdn10
;
; Terminate the name with a NULL at the byte after the position where the
; last non-space was found.
; ES:BX -> Byte after the last non-space was found.
; CARRY clear
;
tdn20:  mov     byte ptr es:[bx],0      ; NULL terminate the string
tdn30:  RestoreReg <DI,SI>
        RET

EndProc     TransDevName


;***    ChkVal - check string of chars for filename validity.
;
;       ChkVal scans a series of characters, advancing over DBCS chars and
;       making sure that the single byte characters are legal in FCB filenames.
;
;       NOTE: this is a duplicate copy of procedure ChkVal in macro.asm.
;
;       ENTRY   (ds:si) point to chars to be scanned
;               (cx)    number of characters to scan
;
;       EXIT    zero set => name contains illegal chars
;               zero reset => name is valid
;                  dl = 0 -> only 8.3 characters
;                  dl = 1 -> contains newly-valid characters
;
;       USES    AL, BX, CX, DX, SI, ES
;
;
;       PSEUDO
;
;       dh = fFCB | fNewChar;
;       if (dbcs is inactive in current process)
;         for (thischar = characters in path) {
;           dh &= CharType[thischar]
;         }
;       else
;         for (thisbyte = bytes in path) {
;           if (thisbyte = DBCS lead byte)
;             ignore DBCS lead & trail bytes
;           else
;             dh &= CharType[thischar]
;         }
;       if (dh & fFCB == 0)     // invalid name
;         return ZR
;       if (dh & fNewChar == 0) // contains newly-valid char
;         dl = 1
;       else
;         dl = 0
;       return NZ

Procedure   ChkVal,NEAR,local
    ASSUME  DS:NOTHING, ES:NOTHING
        LEA     BX,DOSGROUP:CharType
        DosContext ES,,noassume         ; es:bx -> CharType table

.errnz FATNAME
.errnz NONFATNAME - 1

; We're interested in two bits in the CharType table: fFCB and fNewChar
; A valid name must have fFCB set for all character.
; To make this run fast, we initialize register dh to fFCB+fNewChar and AND it
; with CharType bits of each path character. If the fFCB becomes zero at the
; end, then the name is invalid. If the fNewChar bit remains set at the end
; then there are no newly valid characters in the path.

        mov     dl,fFCB or fNewChar
        TestDBCSProcess                 ; need to handle DBCS?
        jz      chv30                   ; yes, do DCBS specific version

; non-DBCS version

chv10:  lodsb                           ; al = char to examine
        xlat    byte ptr es:[bx]        ; al = char type bits
        and     dl,al                   ; detect non fFCB, fNewChar
        loop    chv10                   ; repeat for all

chv15:  test    dl,fFCB                 ; valid name?
        jz      chv20                   ; no, exit with zero set

; If newly valid chars are in the path, then fNewChar in dl will become 0.
; To match exit condition, we invert the bit and shift it to bit 0.

        not     dl                      ; fNewChar bit set if new char found
        and     dl,fNewChar             ; mask off other bits
        shr     dl,5                    ; shift fNewChar to bit 0
.errnz  fNewChar - 20h                  ; make sure shift count is right
        inc     cx                      ; set NZ

chv20:  ret

; DBCS version

chv30:  lodsb                           ; al = char or DBCS lead byte
        TestDBCSLeadByte                ; DBCS lead byte?
        jnz     chv40                   ; no

        dec     cx                      ; adjust count for extra DBCS byte
        jz      chv20                   ; half-DBCS in last byte; report error
        inc     si                      ; skip DBCS trail byte
        jmp     short chv50

chv40:  xlat    byte ptr es:[bx]        ; al = CharType bits
        and     dl,al                   ; detect non fFCB, fNewChar

chv50:  loop    chv30                   ; repeat for all
        jmp     short chv15

EndProc ChkVal


;***    AddRecFromDevHdr - Add a char dev record into the char dev segment
;                          using information in the device header
;
;       AddRecFromDevHdr makes an entry for the character device whose
;       header is passed to it.
;
;       ENTRY       DS:SI -> Device Header
;                   AL = Type of device (real or bimodal)
;                   AH = Action (Don't add, Replace)
;
;       EXIT        NONE
;
;       USES        AX, BX, CX, DX, DI, ES
;
;       ALGORITHM
;
;               Count = DEVNAMELEN      ; Max number of chars in device name
;               MemCpy(DevName,afd_DevName,DEVNAMELEN)
;               Store NULL at end of name
;               Error = AddCharDevEntry()
;               if (!Error)
;                   Set pointer to device header in DevEntry record for device
;               else
;                   InternalError('Out of memory')
;               return(Error)
;
Procedure   AddRecFromDevHdr,hybrid
    ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA
;       PMONLY

LocalVar    afd_DevName,DEVNAMELEN+1    ; Space to hold NULL terminated dev name

        EnterProc
        SaveReg <DS,SI>                 ; Save pointer to device header
        SaveReg <AX>                    ; Save device type
;
; Copy name from device header to afd_DevName, and terminate it with a NULL
;
        TaskContext <ES>,DI
        lea     si,[si].SDevName        ; DS:SI -> name in device header
        lea     di,TASKAREA:afd_DevName ; ES:DI -> afd_DevName
afd10:  call    TransDevName            ; uppercase, check for illegal chars, etc.
        jc      afd20                   ; illegal chars in device name
        RestoreReg <AX>                 ; Restore device Type in AL
        TaskContext <DS>,CX
        lea     SI,TASKAREA:afd_DevName ; DS:SI -> NULL terminated Device name
        RestoreReg <DI,ES>              ; ES:DI -> Device header
        SaveReg <ES,DI>                 ; Save pointer to device header for return
        xor     bl,bl                   ; flags = not an overlapping pseudochar device
        CALLFAR AddCharDevEntry
        jc      afd30                   ; Error. We InternalError.
        RestoreReg <SI,DS>              ; Restore pointer to device header
        LeaveProc
        RET
;
; Illegal chars in device name.  this shouldn't happen.
;
afd20:  InternalError <AddRecFromDevHdr: Illegal chars in device name>

;
; We got an error when trying to make a record. The only error that we
; could have got was an out of memory error when we tried to add the entry
; to the RMP segment. In this case, we cannot continue since the device
; may be crucial for the operation of the system, so we InternalError.
;
afd30:  InternalError <AddRecFromDevHdr: Not enough memory>

EndProc AddRecFromDevHdr


INITCODE ENDS


          FARCODE HIGH3
HIGH3CODE SEGMENT


;***    AddCharDevEntry - Makes an entry for the device and adds it to
;                         the RMP segment.
;
;       When we install a pseudo-character device into the system,
;       we need to first check to see if there is already a bimodal
;       device by that name in the system. If such a device exists, we
;       examine the Action flag to see what we need to do.
;       We do not examine real mode devices because these are only
;       relevant to the 3x box.
;
;       The Action flag indicates what action should be taken in case
;       there is already an entry in the table for a device with the
;       same name:
;
;           - Do not make entry - This would be used if we do not wish to
;             make an entry for the device if one has already been made for
;             the device name earlier. It would also be used for pseudo-char
;             devices to avoid remapping an existing pseudo device with
;             another one.
;
;           - Replace existing entry with new one - This would be done at
;             INIT time after the base devices have their entries made and
;             we install a new device with the name of an existing device.
;             In this case, we just replace the data in the existing entry
;             with that of the new device.
;
;
;       ENTRY   DS:SI -> NULL terminated name of device
;
;               ES:DI -> Device  (FSC for pseudo devices
;                                 Device header for physical devices)
;               AL       Type of device (real, bimodal, pseudo)
;                           and whether device is visible to callers of
;                           FindCharDevEntry.  this is so that pseudochar
;                           devices aren't used before they are initialized
;                           by the call to FS_ATTACH.
;
;               AH       Action (Don't add, Replace)
;               BL       Flags
;
;       EXIT    'NC' if success
;                   ES:DI -> DevEntry record for device
;               'C' if could not add entry for device
;                   AX = error code
;
;       NOTE    A temporary buffer is used to make a dummy entry for
;               the RMP segment.
;
;       WARNING The name *MUST* be uppercased on entry
;
;       USES    AX, BX, CX, DX, DI, SI, DS, ES, Flags
;
;       ALGORITHM
;
;           DevRecord = GetTempBuffer
;           Error = MakeRecordEntry(DevRecord)  ; Set up record
;           if (! Error) {              ; Name is OK
;               Save pointer to entry
;               Set length and pointers for next call
;               exist = Rmp_Find(DevRecord) ; See if a matching entry exists
;               if (exist && of same type) {
;                   if (Action == Replace) {
;                       Replace RoutePtr entry with that of new device
;                       Restore pointer to new entry
;                       Replace Name with user-specified name
;                       Error = FALSE
;                   }
;                   else if (Action == Don't Add) {
;                       Restore pointer to new entry
;                       Error = TRUE
;                   }
;               else {
;                   Restore pointer to entry
;                   SegAlloc (record size + 2 x name size)
;                   copy record in TempBuffer to segment
;                   copy user-specified name to segment
;                   Error = Rmp_Add(DevRecord)      ; Add new entry into table
;                   SegFree
;               }
;           }
;           FreeTempBuffer
;           return(Error)
;
Procedure   AddCharDevEntry,hybrid
    ASSUME  DS:NOTHING, ES:NOTHING, SS:TASKAREA
;       PMONLY

LocalVar    acde_RoutePtr,DWORD         ; Pointer to device
LocalVar    acde_DevName,DWORD          ; pointer to device name
LocalVar    acde_Parms,DBYTE,acde_Type,acde_Action  ; Type and Action parameters
LocalVar    acde_Flags,BYTE             ; dev flags
LocalVar    acde_DevEntPtr,DWORD        ; Pointer to record for RMP segment
LocalVar    acde_DevNameLen,WORD        ; Length of device name

        EnterProc <ES,DI,DS,SI,AX>      ; Set up stack frame
        mov     acde_Flags,bl           ; initialize flags
        CALLFAR GetTempBuffer           ; ES:DI -> temporary kernel buffer
        mov     acde_DevEntPtr.Segmt,ES
        mov     acde_DevEntPtr.Offst,DI ; Save pointer to buffer
        call    MakeRecordEntry
        mov     ax,ERROR_INVALID_PATH
        LJC     acde70                  ; Invalid name of device
        mov     acde_DevNameLen,cx      ; CX = length of device name

;
; ES:DI -> Record to put into RMP segment.
; Copy over the pointer to the device into the record.
;
        mov     si,[acde_RoutePtr].Segmt
        mov     es:[di].DES_RoutePtr.Segmt,si
        mov     si,[acde_RoutePtr].Offst
        mov     es:[di].DES_RoutePtr.Offst,si
        mov     bl,acde_Flags
        mov     es:[di].DES_Flags,bl    ; store flags
        PUSH    ES
        POP     DS
        lea     si,[di].DES_Type     ; DS:SI -> "search key"
        add     cx,(DES_Name - DES_Type)  ; CX = length of search key
        mov     bx,DES_Type          ; BX = offset into record of search key
        DosContext <ES>, DI
        mov     DI, Offset DOSGROUP:CharDevRMPRec   ; ES:DI -> RMP segment data
        CALLFAR Rmp_Find
        jnc     acde60                  ; Match found - see Action to decide what to do
;
; There is no existing entry that matches the device name. We go ahead and add
; this entry into the RMP segment.  allocate a segment to hold the record +
; the un-uppercased name, since tempbuffers aren't big enough to hold a 256
; byte name twice.
;
        mov     ax,acde_DevNameLen
        shl     ax,1                    ; save name twice
        add     AX,(DES_Name - DES_RoutePtr)  ; AX = size of RMP record
        mov     bx,ax
        xor     ax,ax                   ; (AX:BX) = size
        mov     ch,D_DATA0
        mov     cl,SA_GDT               ; SFlags and AFlags
        mov     si,HF_MOVABLE
        mov     di,FSCDEVTMPOWNER
        CALLFAR SegAlloc
        jc      acde70                  ; Error. Release buffer and exit
        mov     es,bx
        xor     di,di                   ; (ES:DI) -> segment
        lds     si,acde_DevEntPtr       ; DS:SI -> Record to add
        mov     CX,acde_DevNameLen
        add     CX,(DES_Name - DES_RoutePtr)  ; CX = size of RMP record
.errnz (DES_Name + PDEVNAMELEN + PDEVNAMELEN - size CharDevEntry)
.errnz (DES_UserName - PDEVNAMELEN - DES_Name)
.errnz (DES_RoutePtr)
        rep     movsb                   ; copy record and uppercased name
        lds     si,acde_DevName         ; DS:SI -> name as the user typed it
        mov     cx,acde_DevNameLen
        rep     movsb                   ; copy name

        mov     cx,acde_DevNameLen
        shl     cx,1                    ; save name twice
        add     CX,(DES_Name - DES_RoutePtr)  ; CX = size of RMP record
        push    es                      ; this copy of selector on stack for freeing
        push    es
        pop     ds
        xor     si,si                   ; ds:si -> record to add
        DosContext <ES>, DI
        mov     DI, Offset DOSGROUP:CharDevRMPRec   ; ES:DI -> RMP segment data
        CALLFAR Rmp_Add

; free segment

        pop     ax                      ; ax = selector
        PUSHF                           ; Save any error indication
        CALLFAR SegFree                 ; Free the segment
        POPF
        mov     ax,ERROR_NOT_ENOUGH_MEMORY  ; Error if "Add" failed (because of no more memory)
        jmp     short acde70            ; Free temp buffer and return
;
; We have found a device entry in the RMP segment. Check the Action flag to
; see what the caller wants us to do.
;
; If (ACT_DEVREPLACE)
;   Replace existing entry with new one
; Else if (ACT_DEVERREXISTS)
;   Return 'C' to indicate device exists
; Else
;   Return 'NC' to indicate "no error"
;
acde60: test    BYTE PTR [acde_Action],ACT_DEVREPLACE
        jnz     acde63                  ; Caller wants to add
        test    BYTE PTR [acde_Action],ACT_DEVERREXISTS
        jz      acde70                  ; Caller wants to 'ignore' this operation
        mov     ax,ERROR_INVALID_PATH
        stc                             ; Caller wants to see error.
        jmp     short acde70

;
; Replace existing entry with new one.
; There is no need to copy the uppercased name as well since it should not
; change. We therefore only copy over the mixed-case name.
;
acde63: MOV     AX,[acde_RoutePtr].Segmt
        MOV     WORD PTR ES:[DI].DES_RoutePtr.Segmt,AX
        MOV     AX,[acde_RoutePtr].Offst
        MOV     WORD PTR ES:[DI].DES_RoutePtr.Offst,AX
        lea     di,[di].DES_Name
        mov     cx,acde_DevNameLen
        add     di,cx                   ; (ES:DI) -> user form of name
        lds     si,acde_DevName         ; DS:SI -> name as the user typed it
        rep     movsb                   ; copy name

;
; We are done. If we get here, then
;   either 1) We found no matching entry and so added one for this device.
;   or     2) We found a matching entry and replaced it with this one.
;   or     3) We found a matching entry and so did not 'add' one for this
;             device.
;   or     4) The name supplied was invalid.
;
acde70: SaveReg <ES,DI>
        LES     DI,[acde_DevEntPtr]     ; Get pointer to temporary buffer
        pushf                           ; Save any error indication
        CALLFAR FreeTempBuffer
        popf                            ; Restore any error indication
        RestoreReg <DI,ES>              ; ES:DI -> DevEntry record for device
acde90: LeaveProc
        RET
EndProc AddCharDevEntry



;***    RemoveCharDevEntry - Removes an entry for a pseudo-character device
;                            from the table.
;
;       When we get a call to remove an existing entry, we need to make
;       sure that it is for a pseudo-character device (i.e. it is
;       removable).
;
;       ENTRY   DS:SI -> NULL terminated name of device
;               ES:DI -> temp buffer for scratch use
;               AL = dev type
;
;       EXIT    'C'  if error (device not found or not removable)
;               'NC' if successfully removed
;
;       USES    AX, BX, CX, DX, DI, SI, DS, ES, Flags
;
;       WARNING There is a potential race condition here between this
;               routine and an OPEN call where we might be setting up an
;               SFT. In order to avoid this, we use the BEGINNOBLOCK and
;               ENDNOBLOCK macros where this is important.
;
;       ALGORITHM
;
;           DevRecord = FindCharDevEntry
;           if (DevRecord) {            ; Found and entry for the device
;               Error = Rmp_Rem(DevRecord)
;           else
;               Error = "not found"
;           }
;           return(Error)
;
;
Procedure   RemCharDevEntry,HYBRID
    ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA
;       PMONLY

        call    FindCharDevEntry        ; See if entry for device exists
        jc      rcde20                  ; Entry does not exist. Exit.

;   While we're removing the entry  no open should be allowed for that
;   entry. The code for removing the entry is thus protected by
;   a mutex semaphore.

        SaveReg <DS,ES>
        .386P
        FlatContext <DS, ES>,BX
        CCALL32 _KSEMRequestMutex,<<offset FLAT:CharDevSem>,-1>

        CpuMode Reset
        RestoreReg <ES,DS>
;
; DS:SI -> DevEntry record for device. We call Rmp_Rem to remove the record.
;

        push    ds
        pop     es
        xchg    di,si                   ; ES:DI -> record to remove
        DosContext <DS>,BX
        mov     bx, Offset DOSGROUP:CharDevRMPRec   ; DS:BX -> RMP segment data
        CALLFAR Rmp_Rem                 ; Remove the record

;   Release the semaphore

        .386P
        FlatContext <DS, ES>,BX
        CCALL32 _KSEMReleaseMutex,<offset FLAT:CharDevSem>

        CpuMode Reset
rcde20: RET

EndProc RemCharDevEntry


;***    SetCharDevFields - turn off invisible bit and fill in devfsd
;
;       Find dev entry and turn off invisible bit.
;
;       ENTRY   DS:SI -> NULL terminated name of device
;               ES:DI -> temp buffer for scratch
;               AL = dev type
;               (CX:DX) = devfsd
;
;       EXIT    none
;
;       USES    AX, BX, DI, SI, DS, ES, Flags
;
;       ALGORITHM
;
;           DevRecord = FindCharDevEntry
;           if (DevRecord) {            ; Found and entry for the device
;               turn off bit
;           else
;               InternalError
;           }
;
;
Procedure   SetCharDevFields,FARONLY
    ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA
;       PMONLY

        push    cx
        call    FindCharDevEntry        ; See if entry for device exists
        pop     cx
        jc      scdvie                  ; Entry does not exist. Exit.
;
; DS:SI -> DevEntry record for device. We call Rmp_Rem to remove the record.
;
        mov     [si].DES_DevFsd.loword,dx   ; store devfsd
        mov     [si].DES_DevFsd.hiword,cx
        and     [si].DES_Flags,NOT DEVFLAGS_INVISIBLE
        ret

scdvie: InternalError <SetCharDevVisible: entry for dev does not exist>

EndProc SetCharDevFields


;***    FindCharDevEntry - Looks for an entry for a character device in
;                          the table.
;
;       This routine is case-insensitive.
;
;       ENTRY   DS:SI -> NULL terminated name of device
;               ES:DI -> temporary buffer for scratch use
;               AL       Type of device (real/bimodal/pseudo)
;
;       EXIT    'NC' if success
;                   DS:SI -> DevEntry record for device
;               'C'  if error
;
;       USES    AX, BX, CX, DI, SI, DS, ES, Flags
;
;       ALGORITHM
;
;           GetTempBuffer(DevRecord)
;           Error = MakeRecordEntry(DevRecord)  ; Set up record
;           if (! Error) {              ; Name is OK
;               Set length and pointers for next call
;               Rmp_Find(DevRecord)
;           }
;           FreeTempBuffer(DevRecord)
;           return(Error)
;
Procedure   FindCharDevEntry,HYBRID
    ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA
;       PMONLY

        CALLFAR GetTempBuffer
        SaveReg <ES,DI>
        call    MakeRecordEntry
        jc      fcde50                  ; Invalid name
        push    es
        pop     ds
        lea     si,[di].DES_Type     ; DS:SI -> search key
        add     cx,(DES_Name - DES_Type)
        mov     bx,DES_Type          ; BX = offset into record of search key
        DosContext <ES>,DI
        mov     DI, Offset DOSGROUP:CharDevRMPRec   ; ES:DI -> RMP segment data
        CALLFAR Rmp_Find
        jc      fcde50
        mov     ax,es
        mov     ds,ax
        mov     si,di                   ; DS:SI -> Found record
fcde50: RestoreReg <DI,ES>              ; ES:DI -> Temporary buffer
        pushf
        CALLFAR FreeTempBuffer
        popf
        RET

EndProc FindCharDevEntry


;***    GetCharDevEntry - Looks for an entry for a character device in
;                          the table.
;        ( input can be a combination of device types; The highest device
;          type found for the given combination is returned .
;          The ordering is as follows: Real,Bimodal,Pseudo )
;
;       ENTRY   DS:SI -> NULL terminated name of device
;               ES:DI -> temp buffer for scratch use
;               AL       Types of device
;                       (real/bimodal/pseudo or a combination of above)
;
;       EXIT    'NC' if success
;                   DS:SI -> DevEntry record for device
;                   BH  -> device type for the found entry
;               'C'  if error
;
;       USES    AX, BX, CX, DI, SI, DS, ES, Flags
;
;       ALGORITHM
;
;           Error = MakeRecordEntry(DevRecord)  ; Set up record
;           if (! Error) {              ; Name is OK
;               RMP_Wait                ;
;               NextDev: Set length and pointers for RMP call
;                       Rmp_FindN(DevRecord)
;                       if (no more record) goto getdevExit
;                       if (current dev type >= prev dev type) {
;                               store current dev type and dev ptr
;                       }
;                       go to NextDev
;           }
; GetDevExit:
;        if (no record found) return failure
;        else  return(success)
;
Procedure   GetCharDevEntry,HYBRID
    ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA
;       PMONLY
LocalVar        gcde_given_type,BYTE    ; Type of device to find
LocalVar        gcde_LastType,BYTE      ; Last type found
LocalVar        gcde_LastDevRec,DWORD   ; Last device entry record found

        EnterProc
        SaveReg <dx>
        mov     gcde_given_type,al
        mov     gcde_LastType,0

        call    MakeRecordEntry         ; CX = len of name
        jc      gcde50                  ; Invalid name

        push    es
        pop     ds
        lea     si,[di].DES_Name        ; DS:SI -> search key
        SaveReg <CX>
        mov     dl,1                    ; wait for semaphore
        DosContext <ES>,DI
        mov     DI, Offset DOSGROUP:CharDevRMPRec   ; ES:DI -> RMP segment data
        CALLFAR Rmp_Wait                ;
        RestoreReg <CX>
        xor     dx,dx                   ; start from first rec
        mov     ax,dx                   ; al = 0 -> consider all recs
;
; start of dev record search loop; At entry,
; al = 0/1 consider all recs/ skip this rec
; dx = rec. offset to start search from
; ds:si = ptr to pattern to match
; cx = pattern length
;
gcde20: mov     bx,DES_Name          ; BX = offset into record of search key
        DosContext <ES>,DI
        mov     DI, Offset DOSGROUP:CharDevRMPRec   ; ES:DI -> RMP segment data
;
; es:di -> ptr to handle to rmp segment
; bx -> offset within record to match
;
        push    cx                      ; save pattern length
        CALLFAR Rmp_FindN               ; es:di -> found rec
        pop     cx                      ; cx = pattern length
        jc      gcde30
;
; init for next rmp_findn
;
        mov     al,1                    ; start from next rec
        mov     dx,di                   ; offset of found rec

        mov     bh,es:[di].DES_Type     ; Set up BH for return
        test    bh,gcde_given_type      ;
        jz      gcde20
        cmp     bh,gcde_LastType        ;
        jb      gcde20                  ;
;
; we found a dev record that is better than the previous find. i.e. this record
; is probably of a different dev type than previous type
;
        cmp     bh,DEVTYPE_PSEUDO       ;
        jne     gcde25                  ;
;
; for pseudo devices, we should further check to see that they are visible.
; i.e. they are not in the process of being added
;
        test    es:[di].DES_Flags,DEVFLAGS_INVISIBLE
        jnz     gcde20                  ; Device is not valid;look for something
                                        ; else
;
; store the current dev ptr and continue search for some other entry
;
gcde25: mov     gcde_LastType,bh        ;
        mov     gcde_LastDevRec.Offst,di
        mov     gcde_LastDevRec.Segmt,es;
        jmp     gcde20
;
; End of record searching; let's see whether we found anything worthwhile
;
.errnz  (DEVTYPE_BIMODAL -1)            ; the following check depends on this

gcde30: cmp     gcde_LastType,DEVTYPE_BIMODAL ; we want CY if we didn't find
        jc      gcde50                  ; a single record; i.e. lasttype == 0
;
; we found some record; return with that dev record ptr
;
        lds     si,gcde_LastDevRec      ;
        mov     bh,gcde_LastType        ; return with NC

gcde50: RestoreReg      <dx>
        LeaveProc
        RET

EndProc GetCharDevEntry


;***    IsThisADevice - Determines if the name specified is that of a
;                       character device.
;
;       We first scan for a pseudo driver with that name. If that fails,
;       we look for a bimodal device with that name.
;
;
;       ENTRY   DS:SI -> NULL terminated name to examine
;               ES:DI -> temp buffer for scratch use
;
;       EXIT    'NC' if a device
;                   [DevPt] set up with pointer from DES_RoutePtr
;                   BH = DES_Type from record for device
;                   BL = DES_Flags from record for device
;                   DS:SI -> device name in RMP record
;               'C' if not a device
;
;       USES    AX, BX, CX, DI, ES, Flags
;
;       ALGORITHM
;
;           Types to look for = bimodal and pseudo
;           call GetCharDevEntry
;           if (failure) return ;
;            else return success with regs. loaded
;
Procedure   IsThisADevice,NEAR
    ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA
;       PMONLY

    LocalVar    itad_DevName,DWORD      ; To hold pointer to name

        EnterProc <DS,SI>
;
; DS:SI -> Name to be examined.
;
        mov     al,DEVTYPE_PSEUDO OR DEVTYPE_BIMODAL    ;
;;;     cmp     ProcType,PT_REALMODE
;;;     jnz     itad10                  ; This is not the 3x box.
;;;     or      al,DEVTYPE_REAL         ;
;
; Scan through the pseudo devices to see if one matches the name.  have to
; check for overlapping pseudo devs
;
itad10: call    GetCharDevEntry         ; DS:SI -> dev rec ptr; BH = devtype
        jc      itad40                  ; Not a device. Free buffer and exit.
;
; DS:SI -> DevEntry record for device. Save DES_RoutePtr value in
; [DevPt] for later use.
;
itad20: mov     ax,[SI].DES_RoutePtr.Segmt
                                        ;   Defect 72417 change starts
        SetBP_CurrTCB
        mov     TASKAREA:[bp].TCBDevPt.Segmt,AX
        mov     ax,[SI].DES_RoutePtr.Offst
        mov     TASKAREA:[bp].TCBDevPt.Offst,AX
        UnSetBP_CurrTCB
                                        ;   Defect 72417 change ends
        mov     bl,[SI].DES_Flags
        lea     si,[si].DES_Name        ; (DS:SI) -> name
itad30: LeaveProc
        RET

itad40: LDS     SI,[itad_DevName]       ; Restore pointer to name in DS:SI
        jmp short itad30

EndProc IsThisADevice



;***    MakeRecordEntry - Makes an entry for the RMP segment for the device
;
;       An entry is made in the buffer passed on entry. The device
;       name is also checked to make sure that it is shorter than
;       the maximum length allowed.
;
;       ENTRY   DS:SI -> NULL terminated name (NamePtr)
;               AL       Type of device - real, bimodal, pseudo
;               ES:DI    Pointer to record to fill in for RMP segment (DestPtr)
;
;       EXIT    'NC' - no error
;                  CX = length of device name
;               'CY' - error (device name too long)
;
;       USES    AX, BX, CX, SI, Flags
;
;       ALGORITHM
;
;           Set Type field in record
;           TempNamePtr = NamePtr
;           DestNamePtr = [DestPtr].DES_Name
;           NameEndPtr = TransDevName()
;           DevnameLen = NameEndPtr - NamePtr
;           if (Done)
;               return(DevNameLen)
;           }
;           else
;               return(error)
;
Procedure   MakeRecordEntry,NEAR,local
    ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA
;       PMONLY

        SaveReg <DI>                ; Save pointer to target buffer
        MOV     BYTE PTR ES:[DI].DES_Type,AL
        mov     es:[di].DES_DevFsd.Segmt,-1
        mov     es:[di].DES_DevFsd.Offst,-1
        mov     es:[di].DES_Flags,0

        lea     di,[di].DES_Name        ; ES:DI -> location of name for RMP record
;
; Copy and upper-case name and calculate its length (including NULL)
; ES:DI -> location of name for RMP record
; DS:SI -> string
; ES:DI -> destination
;
        SaveReg <di>                    ; Save offset of start of string
        CALLFAR StrCpy                  ; Copy and uppercase name
;
; Now scan backwards to see if there were any trailing blanks or dots in the
; name. If there are, then we can remove them before we check for the device
; name since they are not significant.
;
; This scanning works because both a blank and a dot are not valid DBCS lead
; or trail bytes.
;
; ES:DI -> past the NULL
;
        RestoreReg <si>                 ; ES:SI -> beginning of string
mre5:   dec     di                      ; first time es:di -> null,
                                        ; other times truncate a trailing char
        cmp     si,di                   ; back at beginning of string?
        je      mre10                   ; yes, we're done

        cmp     byte ptr es:[di-1],' '  ; trailing blank?
        je      mre5                    ; yes, go truncate it

        cmp     byte ptr es:[di-1],'.'  ; trailing dot?
        je      mre5                    ; yes, go truncate it

; es:di -> after last char

mre10:  xor     al,al
        stosb                           ; Terminate the string
;
; ES:DI -> past NULL in string
; ES:SI -> beginning of string
;
        mov     cx,di                   ; CX -> past NULL
        sub     cx,si                   ; CX = length of string
        cmp     cx,PDEVNAMELEN+1        ; Is it too long? (Compare with 1+max langth so that CMC works)
        cmc                             ; 'NC' if length is OK, 'C' if too long
        RestoreReg <DI>                 ; DI -> name
        RET

EndProc MakeRecordEntry


;***    CheckThisDevice - Check if the name specified refers to a device
;
;       The following rules are used to decide if the name represents a
;       device or not.
;
;       - for both 3xbox and prot mode, trailing spaces and dots are not
;         significant.
;
;       - For the 3x box
;               Only examine the last component in the path
;               Discard any extensions in the name
;               Enforce the 8.3 naming format (i.e. names longer than 8 bytes
;                 long will be truncated to 8 bytes before testing for device)
;
;       - For the protected mode processes, we treat device names in two
;         different ways depending on which device is being referred to.
;           - If the device name is one of the "popular" devices or an
;             installed character device, then we
;             ignore the path. We do NOT ignore extensions.
;           - If it is not, then we expect a prefix of \DEV\ in front of
;             the name.
;           - Any other path indicates a file.
;
;       - No drive specifier is allowed (Actually the caller should have
;         removed any drive specifier before calling this routine).
;
;       ENTRY   DS:SI -> String to examine
;               ES:DI -> scratch buffer
;
;       EXIT    'NC' if a device
;                   DS:SI -> device name
;                   BH = (DEVTYPE_PSEUDO, DEVTYPE_BIMODAL or DEVTYPE_REAL)
;                      if protmode
;                         DS:SI -> device header name
;                      else
;                         DS:SI -> Name1
;               'C' if not a device
;                   DS:SI preserved
;
;       USES    AX, BX, CX, SI, DS, Flags
;
;       ALGORITHM
;
;           /* Scan to last component in path and store name in Name1 */
;           PathLen = 0
;           PathSepPtr = Ptr        /* Remember location of last path sep */
;           while (char != NULL && char != ' ') {
;               char = (Ptr++)
;               PathLen++
;               if char is pathsep or null
;                   PathSepPtr = Ptr /* Remember location+1 of last path sep */
;           }
;           if last char was ' ' replace it with null
;           /* PathSepPtr has location+1 of last path separator */
;           if (RealModeProc) {
;               /*
;                * Enforce the 8.3 naming format.
;                * Discard any extensions from the name.
;                */
;               Copy up to 8 chars (until a '.' or NULL is found)
;               from the name into the buffer at NAME1. Convert them to
;               upper case. NULL terminate the name.
;               Set Ptr to point to Name1
;           }
;           else {
;               /*
;                * Check to see if it is one of the "popular" devices or
;                * installed devices.
;                * If not, then get original string back and examine it
;                * for a prefix of '\DEV\'.
;                */
;               if (!SpecialDevice(Ptr)) {
;                   Get back original string
;                   if (\DEV\ prefix in path)
;                       Set Ptr to point past the \DEV\ prefix
;                   else
;                       return (not a device)
;               }
;           }
;           if (RetCode = IsThisADevice(Ptr))
;               Restore pointer to original string
;           return(RetCode)
;
;
;       NOTE If we treat the device name "specially", i.e. we need to
;            discard extension, then it will be necessary to copy it
;            over to Name1 so that we can return a pointer to the
;            device name to the caller. If we just allocated a buffer
;            on the stack, it would go away when we returned to the caller.
;
Procedure   CheckThisDevice,<near,faronly,far16>
    ASSUME  DS:NOTHING, ES:NOTHING, SS:TASKAREA

    LocalVar    ctd_StrPtr,DWORD        ; To store pointer to string
    LocalVar    ctd_TempBufPtr,DWORD    ; Pointer to scratch buffer

        EnterProc <ds,si, es,di>
        SaveReg <ES,DI>
        cmp     byte ptr ds:[si+1], ':' ; Is there a drive specifier ?
        jne     ctd10                   ; No
        add     si, 2                   ; skip drive specifier
ctd10:  call    GetDeviceName           ; DS:SI -> trimmed-down name to examine
        cmp     [ProcType],PT_REALMODE
        je      ctd80                   ; 3x box. We have the name to check

;* Protect mode processes
;
; For the protect mode processes, we now determine if this the name of an
; installed device or the name of a pseudochar device that overlaps an installed
; device.  If it is, then we need process the name no further.
; DS:SI -> name to examine
;
        les     di,ctd_TempBufPtr       ; ES:DI -> scratch buffer
        call    IsThisADevice           ; is this an installed device?
        jc      ctd78                   ; no, go check for pseudochar device
        test    bx,DEVFLAGS_INSTOVERLAP + ((DEVTYPE_BIMODAL + DEVTYPE_REAL) shl 8)
                                        ; is this an installed device?
        jnz     ctd92                   ; yes, go check for "PRN" special case.

ctd78:  lds     si,[ctd_StrPtr]         ; No. DS:SI -> original string
    ASSUME DS:NOTHING
;
; Check for presence of \DEV\ as prefix
;
        DosContext es
        mov     di,offset DOSGROUP:DevPrefix     ; (es:di) -> prefix to match
        CALLFAR PathPref                ; Is \DEV\ a prefix? DS:SI -> after prefix
        stc
        jnz     ctd85                   ; No. This is not a device name
;* 3x Box and Protect mode processes
;
; Check all installed devices for a matching name.
; DS:SI -> Name without \DEV\ prefix.
;
ctd80:  les     di,ctd_TempBufPtr       ; ES:DI -> scratch buffer
        call    IsThisADevice
        jnc     ctd92                   ; It is a device. DS:SI -> device name.
ctd85:  lds     si,[ctd_StrPtr]         ; Restore DS:SI -> path string

        jmp     short ctd99             ; go return with an error indicated.

;
; If it is for "PRN", switch to "LPT1"
;
ctd92:  DosContext es                   ; Is it
        lea     di,es:[PRN_Device_Name] ; for
        CALLFAR StrCmp                  ; "PRN"?
        jnz     ctd97                   ; No, go return with no error indicated.

        DosContext ds                   ; Yes, point to
        lea     si,es:[LPT1_Device_Name]; "LPT1".
        les     di,ctd_TempBufPtr       ; Point to the scratch buffer.
        call    IsThisADevice           ; Is it a device (it should be)?
        jc      ctd85                   ; No, go report the error.

ctd97:  clc                             ; Show that it is a device.

ctd99:  RestoreReg <DI,ES>
        LeaveProc
        RET

EndProc CheckThisDevice


;***    GetDeviceName - Get the possible device name
;
;       The following rules are used to decide if the name represents a
;       device or not.
;
;       - For the 3x box
;               Only examine the last component in the path
;               Discard any extensions in the name
;               Enforce the 8.3 naming format (i.e. names longer than 8 bytes
;                 long will be truncated to 8 bytes before testing for device)
;
;       - For the protected mode processes, we treat device names in two
;         different ways depending on which device is being referred to.
;           - If the device name is one of the "popular" devices or an
;             installed character device, then we
;             ignore the path. We do NOT ignore extensions.
;           - If it is not, then we expect a prefix of \DEV\ in front of
;             the name.
;           - Any other path indicates a file.
;
;       - No drive specifier is allowed (Actually the caller should have
;         removed any drive specifier before calling this routine).
;
;       ENTRY   DS:SI -> String to examine
;
;       EXIT    'NC' if name is not invalid
;                   DS:SI -> name (in NAME1 for DOS box)
;               'C' if name is invalid
;                   DS:SI preserved
;
;       USES    AX, BX, CX, DI, SI, DS, ES, Flags
;
;       ALGORITHM
;
;           /* Scan to last component in path and store name in Name1 */
;           PathLen = 0
;           PathSepPtr = Ptr        /* Remember location of last path sep */
;           while (char != NULL && char != ' ') {
;               char = (Ptr++)
;               PathLen++
;               if char is pathsep or null
;                   PathSepPtr = Ptr /* Remember location+1 of last path sep */
;           }
;           if last char was ' ' replace it with null
;           /* PathSepPtr has location+1 of last path separator */
;           if (RealModeProc) {
;               /*
;                * Enforce the 8.3 naming format.
;                * Discard any extensions from the name.
;                */
;               Copy up to 8 chars (until a '.' or NULL is found)
;               from the name into the buffer at NAME1. Convert them to
;               upper case. NULL terminate the name.
;               Set Ptr to point to Name1
;           }
;
Procedure   GetDeviceName,<near,far16>
    ASSUME  DS:NOTHING, ES:NOTHING, SS:TASKAREA

        mov     CX,MAXPATHLEN+1         ; CX = maximum path length + 1
        mov     di,si
;
; DI = location+1 of last path separator (OR beginning of string if
;      no path separators found yet)
; CX = max. number of chars allowed in the rest of the string
; DS:SI -> next character in string
;
gddn20: lodsb
        TestDBCS <gddn30>
        jnz     gddn30                  ; It is not a DBCS character
;
; The character is a double byte character. Skip over trailing byte.
;
        dec     cx                      ; Make sure we don't exceed length
        inc     di                      ; Skip over trailing byte
        jmp     short gddn40            ; See if any more characters to copy.
;
; The character is a single-byte character.
;
gddn30: or      al,al
        jz      gddn50                  ; Got the end of string
        JNPathChar <gddn40>
        mov     di,si                   ; Remember position+1 of last path sep. found
gddn40: loop    gddn20

gddn50: mov     si,di                   ; DS:SI -> position after last path sep.
;
; DS:SI points either to the char after the path separator if the path had
; more than one component OR it points to the only component of the path.
;
; For the 3x box, we discard any extensions in the name. This is necessary
; for compatibility.
; For protected mode, we choose to not be fully compatible with earlier bad
; behaviour. We just take the name and examine it for a device.
;
        cmp     [ProcType],PT_REALMODE
        jne     gddn75                  ; Not the 3x box. We have the name.

;
; Copy over up to DEVNAMELEN chars into NAME1 until a '.' or a NULL
; is encountered. NULL terminate the name. (See NOTES above).
; This way we throw away any extensions that might have been present.
;
; DS:SI -> last component in path
;
; NOTE: The following works for DBCS because '.' and NULL are
;       not legal trailing bytes.
;
        mov     cx, DEVNAMELEN
        TaskContext <ES>,DI
                                        ;   Defect 72417 change starts
        mov     di,CurrTCB
        lea     di,TASKAREA:[di].TCBName1 ; ES:DI -> NAME1 (destination of name)
                                        ;   Defect 72417 change ends
        SaveReg <ES,DI>
gddn60: lodsb                           ; AL = next char in name
        CALLFAR UCase
        cmp     al,'.'
        jz      gddn70                  ; Reached a '.' Ignore any extensions.
        or      al,al
        jz      gddn70                  ; End of string.
        TestDBCS
        stosb                           ; Store this char. Continue processing.
        jnz     gddn65
        movsb                           ; Copy over trailing byte
gddn65: loop    gddn60                  ; Scan DEVNAMELEN characters max.
;
; ES:DI -> Position after last char in name. The name is DEVNAMELEN chars
; or less in length. Place NULL at end of name.
;
gddn70: xor     al,al
        stosb                           ; NULL terminate the name
        RestoreReg <SI,DS>              ; DS:SI -> NULL terminated name
gddn75: RET

EndProc     GetDeviceName


;***    GetDevEntry - Gets the device entry for the device specified.
;
;       This routine essentially gets a temp buffer and then calls
;       FindCharDevEntry. It is used by routines that don't already have
;       a scratch buffer for this use.
;
;       ENTRY   DS:SI -> NULL terminated name of device
;               AL       Type of device (real/bimodal/pseudo)
;
;       EXIT    'NC' if success
;                   DS:SI -> DevEntry record for device
;               'C'  if error
;
;       USES    AX, BX, CX, DI, SI, DS, ES, Flags
;
;       ALGORITHM
;
;           GetTempBuffer()
;           Error = FindCharDevEntry()
;           FreeTempBuffer()
;           return(Error)
;
Procedure   GetDevEntry,HYBRID
    ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA
;       PMONLY

        CALLFAR GetTempBuffer           ; ES:DI -> temp buffer
        SaveReg <es, di>                ; Preserve pointer
        call    FindCharDevEntry
        RestoreReg <di, es>             ; ES:DI = offset of temp buffer
        pushf
        CALLFAR FreeTempBuffer
        popf                            ; Restore any error indications
        ret

EndProc GetDevEntry


;***    AddSpoolDevEntry - Adds a Spool object entry
;
;
;       ENTRY   DS:SI -> NULL terminated name of device
;               ES:DI -> Struct_SpoolAttach structure
;
;       EXIT    'NC' if success
;               'C'  if error
;
;       USES    AX, BX, CX, DX, SI, DI, DS, ES, Flags
;
;       ALGORITHM
;
;           Get temporary segment
;           construct SpoolDevEntry record
;           Add record to RMP segment
;           Free temporary segment

        CpuMode 386

Procedure   AddSpoolDevEntry,HYBRID
    ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA
    LocalVar asde_TempBuffer,dword              ; pointer to temp. buffer

        EnterProc

;   Get buffer and copy device name.

        SaveReg <es, di>
        CALLFAR GetTempBuffer                   ; ES:DI -> temp buffer
        mov     asde_TempBuffer.Segmt,es
        mov     asde_TempBuffer.Offst,di        ; Save the pointer
        push    di                              ; remember for future reference
        add     di, (size SpoolDevEntry - 1)    ; ES:DI -> name field
        push    di
        CALLFAR StrCpy                          ; Copy and uppercase name
        pop     si                              ; ES:DI -> after end of name
        mov     cx,di                           ; ES:SI -> beginning of name
        sub     cx,si                           ; CX == name length
        pop     di                              ; ES:DI -> temp. buffer
        mov     es:[di].SDE_cbDevName, cl       ; Fill size
        mov     si, es
        mov     ds, si
        mov     si, di                          ; DS:SI -> buffer
        RestoreReg <di, es>                     ; ES:DI -> parameters

;   Get attached key
        mov     eax, es:[di].SA_ulKey
        mov     ds:[si].SDE_ulKey, eax

;   The parameters specify the handle of the named pipe used in communication
;   between kernel and spooler. We have to get the SFN corresponding to
;   this handle, because when the time comes to use it, we will be in the
;   context of some other process.

        mov     bx, es:[di].SA_hNmPipe          ; bx = handle
        CALLFAR pJFNFromHandle                  ; ES:DI = JFN entry
        jc      short asde92                    ; error, get out

        mov     ax, es:[di]                     ; AX == SFN of named pipe
        mov     ds:[si].SDE_Sfn, ax             ; Fill field in record.

        DosContext <es>,di
        mov     di,offset SpoolDevRMPRec        ; (es:di) -> RMP segment data
        cmp     es:[di].hiword,0
        clc
        jnz     short asde70                    ; jump if already allocated

;       Allocate RMP segment for spool objects
;       Rmp_Alloc(ax=hobowner,bx=hobmte,esi=PG flags,es:di->rmpdata)

        SaveReg <ds, si>
        mov     ax,SPLDEVRMPOWNER
        xor     bx,bx
        mov     esi,PG_RESIDENT
        CALLFAR Rmp_Alloc                       ; Allocate RMP segment
        RestoreReg <si, ds>                     ; DS:SI -> Record
        jc      short asde85                    ; Return Error if failed

asde70: xor     cx,cx
        add     cl,ds:[si].SDE_cbDevName
        add     cx,size SpoolDevEntry           ; (cx) = Packet size

;       Rmp_Add((cx)=length,(ds:si)->new record,(es:di)->rmp handle)

        CALLFAR Rmp_Add                         ; ES:DI -> Record in RMP segment

asde85: pushf
        push    ax
        les     di,asde_TempBuffer              ; ES:DI -> temp buffer
        CALLFAR FreeTempBuffer                  ; Free the temp buffer
        pop     ax
        popf

asde90: LeaveProc
        Ret

asde92: mov     ax, ERROR_INVALID_HANDLE
        RestoreReg <di, es>
        jmp     short asde85

EndProc AddSpoolDevEntry,NoCheck

        CpuMode 286

;***    RemSpoolDevEntry - Removes a Spool object entry
;
;
;       ENTRY   DS:SI -> NULL terminated name of device
;
;       EXIT    'NC' if success
;               'C'  if error
;
;       USES    ES, DI, DS, BX, Flags
;
;       ALGORITHM
;
;           Find spool object record
;           if (found)
;               Remove record
;
Procedure   RemSpoolDevEntry,HYBRID
    ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA
;       PMONLY

        EnterProc

        call    FindSpoolDevEntry               ; DS:SI -> record
        jc      short fsde90

        mov     di, ds
        mov     es, di
        mov     di, si                          ; ES:DI -> record
        DosContext <DS>, BX
        mov     bx, Offset DOSGROUP:SpoolDevRMPRec   ; DS:BX -> RMP segment data

        CALLFAR Rmp_Rem                         ; Remove Entry
        clc

fsde90: LeaveProc
        Ret
EndProc RemSpoolDevEntry



;***    FindSpoolDevEntry - Finds a Spool object entry
;
;       ENTRY   DS:SI -> NULL terminated name of device
;
;       EXIT    'NC' if success
;                   DS:SI -> Record found
;                   CX    -> Size of record
;               'C'  if error
;
;       USES    AX, BX, CX, DI, SI, Flags
;
;       ALGORITHM
;
;           Get search Key
;           Find spool object entry in RMP segment
;
Procedure   FindSpoolDevEntry,HYBRID
    ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA
;       PMONLY

        EnterProc

        CALLFAR GetTempBuffer
        SaveReg <es, di, di>

        CALLFAR StrCpy                          ; Copy and uppercase name
        RestoreReg <si>                         ; ES:SI -> uppercased name
        sub     di, si
        mov     cx, di                          ; CX == length of name

        push    es
        pop     ds                              ; DS:SI -> Name
        mov     bx, (SDE_Name - SDE_Sfn)        ; BX    -> Offset

        DosContext <ES>, DI
        mov     di, Offset DOSGROUP:SpoolDevRMPRec   ; ES:DI -> RMP segment data
        CALLFAR Rmp_Find
        jc      fsd90
        mov     si, es
        mov     ds, si
        mov     si,di
                                                ; DS:SI -> Record found
fsd90:  RestoreReg <di, es>
        pushf
        push    ax
        CALLFAR FreeTempBuffer
        pop     ax
        popf
        LeaveProc
        Ret

EndProc FindSpoolDevEntry



;***    CheckSpoolDevice - Check if spool device
;
;       ENTRY   DS:SI -> Formatted name
;
;       EXIT    'NC' if spool device
;                       DS:SI -> Spool record
;               'C'  if not spool device
;
;       USES    AX, BX, CX, DI, ES, DS, SI, Flags
;
;       ALGORITHM
;
;           If (drive)
;               return CY;
;           else if (not device)
;               return CY;
;           else
;               call FindSpoolDevEntry;
;               if (not spooled device)
;                   return CY;
;               call FindCharDevEntry                             @B727403
;               if (also a pseudo-device(LPT port is NET USEd) )
;                   return CY;
;               else
;                   return NC;
;
;
;
;
Procedure   CheckSpoolDevice,faronly,,,esp
    ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA

        EnterProc

        cmp     byte ptr ds:[si+1],':'          ; Is there a drive specifier?
        jne     csd10
        add     si,2                            ; yes, go pass it

; Check for presence of \DEV\ as prefix

csd10:  DosContext es
        mov     di,offset DOSGROUP:DevPrefix ; (es:di) -> prefix to match
        CALLFAR PathPref                        ; Is \DEV\ a prefix? DS:SI -> after prefix
        jnz     csd92                           ; No. This is not a device name
        push    ds                              ; Save pointer to device name
        push    si

        call    FindSpoolDevEntry               ; Is it a spool device?
        jc      csd90                           ; No - return @PEN
;
; Device is spooled.  Ensure it is not redirected to pseudo-device  @B727403
; This avoids locally spooling files when LPT port is Net USEd
;
        pop     ax                              ; Get device name(saved DS:SI)
        pop     bx                              ; BX:AX -> device name
        SaveReg <DS,SI>                         ; Save pointer to spool record
        mov     ds,bx
        mov     si,ax                           ; DS:SI -> device name again
        mov     ax,DEVTYPE_PSEUDO               ; Search for pseudo device only
        call    FindCharDevEntry                ; Is it redirected?
        jnc     csd80                           ; Yes - don't spool the file

        RestoreReg <SI,DS>                      ; Restore -> spool record
        clc                                     ; Spool the file(NC)
        jmp     csd91


csd80:  stc                                     ; Set CY - don't spool file

csd90:  pop     ax                              ; Clear stack of unneeded DS:SI
        pop     ax

csd91:  LeaveProc
        Ret

csd92:  stc
        jmp short csd91

EndProc CheckSpoolDevice



HIGH3CODE ENDS

        END
