;       SCCSID = @(#)lockdrv.asm        6.5 92/02/27
;      SCCSID = @(#)lockdrv.asm 6.5 92/02/27
PAGE    ,132
TITLE   Lockdrv - Lock drive support
NAME    Lockdrv

;***    Drive locking
;
;
;       Global          Local
;
;       DosIOAllowed
;       RetryLockedDrive
;       DirectIOAllowed
;       LockDrive
;       UnlockDrive
;
; 150371  04/01/96  DRB      System Anchor Block
;

.xlist
.xcref
include fnofs.inc
include dosseg.inc
include defdbug.inc
include basemaca.inc
include osmaca.inc
include oldmaca.inc
include dossymc.inc
include curdir.inc
include dpb.inc
include error.inc
include exec.inc
include filesys.inc
include harderr.inc
include mi.inc
include protmode.inc
include osconst.inc
include pgexport.inc
include tkexport.inc
include vmexport.inc
include codelock.inc
.cref
.list
include vpb.inc

        ModName lockdrv,FNO_LOCKDRV_ASM

        ReferTask       CurrTCB,WORD    ; Current thread control block
        ReferTask       ExtErr,WORD
        ReferTask       Pid,WORD        ; process ID

        ReferGDT        GDT_VPB,WORD

        EXTRNFAR        Find_DPB
        EXTRNFAR        WhatVolume
        EXTRNFAR        UnreadableVPB
        EXTRNFAR        FSD_FlushBuf
        EXTRNFAR        DecVPBRef
        EXTRNFAR        ProcRun
        EXTRNFAR        ProcBlock
        EXTRNFAR        HardError

        EXTRN32         CodeLockProc
        EXTRN32         CodeUnlockProc
        EXTRN32         FreeVPBCacheData


DRIVE_LOCK_DELAY        EQU     2*1000  ; Delay between retries for locked drive

public  _initfatchkdsk, initfatchkdsk   ;Value used for chkdsk at init time    ;### AC DCR 1520
DATA segment                                                                   ;### AC DCR 1520
_initfatchkdsk  label   BYTE            ; D150371
initfatchkdsk  db   1                                                          ;### AC DCR 1520
DATA ends                                                                      ;### AC DCR 1520

        FARCODE HIGH4
HIGH4CODE SEGMENT
        ASSUME  SS:TASKAREA

BREAK   <Drive Locking Routines>

PAGE


;***    DosIOAllowed - Check if standard DOS I/O is allowed
;
;       Verifies that the drive is not locked by another process.
;       This is called by WhatVolume on behalf of standard DOS
;       I/O requests to make sure there is no INT 13/25/26 activity
;       on the drive.
;
;
;       Entry:  (DS:SI) = address of DPB for drive
;
;       Exit:   Carry Clear => Drive IS NOT locked by another process.
;
;               Carry Set   => Drive IS locked by another process.
;               (AX) = error return code
;
;       Uses:   Flags.

        ASSUME  ES:NOTHING,SS:TASKAREA

Procedure   DosIOAllowed, HYBRID

        SaveReg <CX>
        SaveReg <AX>                    ; Save (AX), may be replaced with error code

dia1:   MOV     CX,DS:[SI].dpb_drive_lock ; (CX) = drive lock word
        CMP     CX,0                    ; Set flags.  (JCXZ doesn't!)
        JE      dia2                    ; Lock is free
        CMP     CX,SS:Pid               ; Who has the lock?
        JE      dia2                    ; We have the lock
        SaveReg <BX,DX>
        CALL    RetryLockedDrive        ; See if user wants try for lock again
        RestoreReg  <DX,BX>
        JNC     dia1                    ; Try to get lock again
        POP     CX                      ; Get rid of saved (AX) on stack
        JMP     SHORT diax              ; Go exit, error return in (AX)

dia2:   RestoreReg <AX>                 ; Restore caller's (AX)

diax:   RestoreReg <CX>
        return                          ; Return to caller

EndProc DosIOAllowed

PAGE
;***    RetryLockedDrive - See if user wants to retry operation on locked drive
;
;       Entry:  (DS:SI) = address of DPB of drive
;
;       Exit:   Carry Clear => retry attempt to lock drive
;
;               Carry Set   => abort attempt to lock drive
;               (AX)    = Error code.
;
;       Uses:   AX,BX,CX,DX.

        ASSUME  ES:NOTHING,SS:TASKAREA

Procedure  RetryLockedDrive,NEAR

        push    bp
        mov     bp,CurrTCB
        mov     [bp].TCBAllowed,ALLOWED_FAIL + ALLOWED_RETRY
        pop     bp
        MOV     AX,HEDrvLocked          ; Drive Locked error
        MOV     BL,DS:[SI].dpb_drive    ; (BL) = drive number
        MOV     ExtErr,ERROR_DRIVE_LOCKED ; Set extended error code
        push    ExtErr
        CALLFAR HardError               ; Ask user what to do
        pop     ax                      ; (AX) = error code from HardError
        JC      rldx                    ; User selected abort or fail

;*      User selected RETRY.  Sleep for a while on the DPB before retrying.
;       This may give the process with the lock enough time to finish, or
;       allow the user to fix the situation.

        SaveReg <BP,DI>
        MOV     AX,DS                   ; (AX) = segment of DPB }-- 32-bit id
        MOV     BX,SI                   ; (BX) = offset of DPB  }
        XOR     DI,DI
        MOV     CX,DRIVE_LOCK_DELAY     ; (DI:CX) = Timeout period
        MOV     DH,1                    ; (DH) = non-interruptible sleep
        CALLFAR ProcBlock               ; Wait for lock
        RestoreReg <DI,BP>
        CLC                             ; User selected retry
rldx:
        return

EndProc RetryLockedDrive

HIGH4CODE ENDS

PAGE

        FARCODE HIGH4
NMPIPESWAPCODE SEGMENT

        PUBLIC _StartDRVLOCKGROUP
_StartDRVLOCKGROUP label byte

;***    DirectIOAllowed - Check if direct disk I/O is allowed
;
;       If the process wants to read, then it is allowed only if the
;       drive is not locked by some other process.  If the process wants
;       to write, then it is allowed to only if the direct write behavior
;       bit is set, the drive is not locked by some other process, and
;       there is no DOS I/O active on the drive.
;
;       WARNING We'll lock the drive if the caller's trying to do Writes.
;               Nobody will unlock it until the caller exits.  This has
;               the effect of doing a proper drive lock for the caller,
;               which the caller can't do itself.  This also means that
;               the caller MUST exit in order to release the drive.
;
;       Entry:  (AL) = drive number (0 = A)
;               (AH) = DO_DIRECT_WRITE, if caller wants to write
;                    = DO_DIRECT_READ,  if caller wants to read
;
;       Exit:   Carry Clear => Drive IS NOT locked by another process.
;
;               Carry Set   => Drive IS locked by another process.
;                  (AX) = Device driver error code.
;
;       Uses:   Flags.

        ASSUME  ES:NOTHING,SS:TASKAREA

Procedure   DirectIOAllowed,NEAR
        PMONLY

        CodeLock DRVLOCK_GROUP

        SaveReg <BX,CX,DX,DI,SI,DS,ES>
        SaveReg <AX>                    ; Save (AX): may return error instead
        CALLFAR Find_DPB                ; (DS:SI) = address of DPB
        JNC     dio1                    ; Found the DPB
        MOV     AL,ERROR_I24_BAD_UNIT   ; (AX) = bad drive specified
        JMP     SHORT dio4              ; Go clean up and exit

dio1:   CMP     AH,DO_DIRECT_READ
        JE      dio3                    ; Caller wants to read disk

dio2:
        xor     al,al                   ; @@ indicate logical drive lock
        call    LockDrive               ; Try to lock the drive
        JNC     dio5                    ; We got (or already had) drive lock
        CALL    RetryLockedDrive        ; See if user wants try for lock again
        JNC     dio2                    ; Try to get lock again
        MOV     AL,ERROR_I24_NOT_READY  ; (AX) = drive is locked
        JMP     SHORT dio4              ; Go clean up and exit

dio3:   CALL    DosIOAllowed            ; See if read is allowed
        JNC     SHORT dio5              ; Read is allowed

dio4:   POP     BX                      ; Get rid of (AX) on stack
        JMP     SHORT diox              ; Go to clean up and exit

dio5:   RestoreReg <AX>                 ; Restore caller's (AX)

diox:   RestoreReg <ES,DS,SI,DI,DX,CX,BX>

        CodeUnlock DRVLOCK_GROUP

        return                          ; Return to caller

EndProc DirectIOAllowed

PAGE
;***    LockDrive - Lock a disk drive.
;
;       We want to ensure exclusive access for the caller to the requested
;       drive.  Before granting this privilege, however, we must make sure
;       that no other process is USING the drive.  In order to do this,
;       we must:
;
;           1)  Check if the drive is locked already by some other process.
;           2)  If the volume is a DOS volume (e.g., WhatVolume can map the
;               volume in the drive to a VPB), we check if there are any
;               open files, on the volume.  If there is any open files in
;               addition to the one we have open, lock will fail.
;
;       See discussion below for rationale.
;
;
;       Entry:  (DS:SI) = address of DPB for drive
;
;       Exit:   Carry Clear => Drive lock successful.
;
;               Carry Set   => Drive lock failed.
;                   (AX) = packet status
;
;       Uses:   AX,BX,CX,DX,Flags.
;
;
;       We use the reference counts in the VPB to determine what kinds of
;       DOS I/O are active on the volume:
;
;           vpb_ref_count  = 1 for the DPB pointing to this VPB +
;                            1 for each buffer pointing to this VPB +
;                            1 for each search in progress +
;                            1 for each open file (SFT reference)
;
;           vpb_srch_count = 1 for each search in progress
;
;       As stated above, we will lock the drive only if is not already locked
;       and if there are no open files in addition to the one that we have
;       open, in order to do the ioctl.  Why?  Note that the "vpb_ref_count"
;       field in the VPB reflects four different kinds of volume references.
;
;       a)  We know about the DPB reference, since the volume IS in the drive.
;       b)  We don't need to bother with buffer references, since we will flush
;           these to the disk before we lock it.
;       c)  We will ignore search references, since we don't know when a
;           process is done with a search unless a SEARCH NEXT fails.  Consider
;           the case where someone types DIR and does CTRL-C before the DIR is
;           done!  So, we may stop some process's search earlier than it should
;           have.  This is not super nice, but it would seem to be a good
;           compromise between compatibility and safety.
;       d)  This leaves us with the open files.
;
;       So, we call WhatVolume to determine what volume is in the drive. If
;       WhatVolume returns an error we fail the lock operation.
;
;       If WhatVolume came back okay, we examine the vpb_fsi.vpi_ID field to see if
;       we have an unformatted diskette in the drive. If we do, we lock the
;       drive since we cannot have any open files on the medium. Otherwise,
;       we flush all the buffers for the volume.
;       Then, we subtract the number of searches from the total reference
;       count.  If this is exactly two (2), then we know that the only
;       references to the volume is by the DPB, the calling process' SFT, and
;       (possibly) searches.
;
;       Now, why didn't we just scan the SFT tables?  Probably because this
;       approach is faster.
;
;       BUGBUG - This call currently does not give the caller ANY indication
;                that a flushbuf call failed.  It probably should at least
;                report such errors.
;
;       @@ Changed to distinguish call from logical lock vs. physical lock
;       @@ AL = 0, logical     AL = 1, physical
;
        ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA

Procedure   LockDrive, HYBRID

LocalVar    locktype,WORD               ; @@

        CodeLock DRVLOCK_GROUP

        DBPRT   80h,40h,<LockDrive($x:$x,ax=>,<ds,si,ax>
        EnterProc                       ; @@
        MOV     locktype,AX             ; @@ save lock type
        MOV     CX,DS:[SI].dpb_drive_lock ; (CX) = drive lock word
        JCXZ    ld1                     ; Lock is free, go check DOS activity
        CMP     CX,SS:Pid               ; Do we have the lock already?
        JE      ldx                     ; We have lock, go exit
        JMP     ld6                     ; Lock held by someone else, go fail

ld1:    SaveReg <DI,SI,DS,ES,BP>
        MOV     BP,CurrTCB
        MOV     [BP].TCBModeFlags,fLOCKDRV  ; Special-case this operation
        CALLFAR WhatVolume              ; Get volume information
        MOV     BP,CurrTCB
        MOV     [BP].TCBModeFlags,0     ; We are done
        RestoreReg <BP,ES,DS,SI,DI>
;
;   If we had an error returned from WhatVolume, we assume it was a
;   "drive not ready" error and fail the lock operation.
;
        MOV     AX,ERROR_NOT_READY      ; (AX) = extended error code
        JC      ldx                     ; Go return with error
;
; Examine the vpb_fsi.vpi_ID field for the "unique" 32-bit ID that indicates that
; the volume is unreadable (i.e. unformatted). This field is set up by
; WhatVolume.
;

ld2:    MOV     AX,DS:[SI].dpb_hVPB
        CALLFAR UnreadableVPB
        mov     bx,ax
        JZ      ld2a                    ; This is an unreadable volume
                                        ; Skip over flushing the buffers.
        PUSH    BX                      ; (tos) = hVPB
        SaveReg <SI,DI,DS,ES>
        mov     dx,FLU_DISCARD
        CALLFAR FSD_FlushBuf            ; flush and discard buffers
        RestoreReg <ES,DS,DI,SI>
        POP     BX                      ; (BX) = hVPB

        SaveReg <BX,ES,DI,SI,DS>
        mov     di,bx
        FlatContext <ds,es>
        CCALL32  FreeVPBCacheData,,nocall
        RestoreReg <DS,SI,DI,ES,BX>
        ASSUME DS:NOTHING,ES:NOTHING

ld2a:   SaveReg <ES>
        VPBContext ES,BX                ; (ES:BX) -> VPB
        MOV     AX,ES:[BX].vpb_ref_count    ; (AX) = total reference count
        SUB     AX,ES:[BX].vpb_search_count ; (AX) = count of open files+DPB ref
        RestoreReg <ES>

        CMP     locktype,0
        JE      ld3                     ; @@ jump if true
        CMP     AX,01h                  ; @@ must be physical, test for 1
        JMP     SHORT ld4               ; @@ jump to test jump
ld3:    push    ds
        DosContext ds
        cmp     initfatchkdsk,1         ; If trying to chkdsk a fat boot partition ;### AC DCR 1520
        jne     ld3a                    ; the ref count can be ignored.            ;### AC DCR 1520
        pop     ds
        jmp     short ld5                                                          ;### AC DCR 1520
ld3a:   pop     ds
        CMP     AX,02h                                                             ;### AC DCR 1520
;### AC DCR 1520
;### This is the spot to jump over if flag is set for chkdsk
ld4:    JA      ld6                     ; DOS I/O active, go indicate failure

ld5:    MOV     BX,[Pid]                ; (BX) = process ID
        MOV     DS:[SI].dpb_drive_lock,BX ; Grab the drive lock
        CLC                             ; (CF) = 0, indicating success
        JMP     SHORT ldx               ; Go to common exit

ld6:    MOV     AX,ERROR_DRIVE_LOCKED   ; (AX) = error return value
        STC                             ; Set error indication

ldx:    LeaveProc                       ; @@ leave procedure
        DBPRT   80h,40h,<)\n>

        CodeUnlock DRVLOCK_GROUP
        ret                             ; Return to caller


EndProc LockDrive

PAGE
;***    UnlockDrive - Unlock a drive.
;
;       If the drive is currently locked by this process, unlock it
;       and start up any processes waiting for the lock.  If the hVPB
;       field in the DPB is not zero, we zero it and decrement the VPB
;       reference count.  If it is zero, then it was because an unreadable
;       disk was in the drive when it was locked.
;
;       Entry:  (DS:SI) = address of DPB for drive
;
;       Exit:   Drive lock released, if we held it.
;               DPB.hVPB = 0.
;
;       Uses:   Flags.

        ASSUME  ES:NOTHING,SS:TASKAREA

Procedure UnlockDrive, HYBRID

        CodeLock DRVLOCK_GROUP

        DBPRT   80h,40h,<UnLockDrive($x:$x>,<ds,si>
        SaveReg <AX,BX,CX,DX,BP>
        MOV     CX,DS:[SI].dpb_drive_lock ; (CX) = drive lock word
        CMP     CX,SS:Pid               ; Do we have the lock already?
        JNE     udx                     ; We don't have the lock
        MOV     DS:[SI].dpb_drive_lock,0 ; Free the drive lock

;;;     XOR     BP,BP                   ; (BP) = 0
;;;     XCHG    BP,DS:[SI].dpb_hVPB     ; (BP) = old VPB handle
;;;                                     ; dpb.hVPB = 0
;;;     OR      BP,BP                   ; Was there an old VPB?
;;;     JZ      ud1                     ; No old VPB, so no ref count to dec
;;;     CALLFAR DecVPBRef               ; Remove DPB -> VPB reference count

;*      Start up any processes waiting on the DPB (presumably waiting to
;       lock the drive.

ud1:    MOV     AX,DS                   ; (AX) = segment of DPB
        MOV     BX,DI                   ; (BX) = offset of DPB
        CALLFAR ProcRun                 ; Wake up all waiting processes
udx:    RestoreReg  <BP,DX,CX,BX,AX>
        DBPRT   80h,40h,<)\n>

        CodeUnlock DRVLOCK_GROUP
        ret                             ; Return to caller

EndProc  UnlockDrive

        PUBLIC _EndDRVLOCKGROUP
_EndDRVLOCKGROUP label byte

NMPIPESWAPCODE    ENDS
        END
