SCCSID  equ     "@(#)lock.asm   6.11 92/02/12"
TITLE   LOCK ROUTINES - Routines for file locking
NAME    LOCK

;       IBM/Microsoft Confidential
;
;       Copyright (c) IBM Corporation 1987, 1989
;       Copyright (c) Microsoft Corporation 1987, 1989
;
;       All Rights Reserved
;
;       Global          Local
;
;       w_LockOper
;       Lock_Violation
;       w_FileLocks
;       FLK_Get_Parms
;       Lock_list
;       Lckl_Unlock
;       Unlock_list
;       Set_Block
;       LockBlock
;       Clr_Block
;       clp
;       CheckRange
;       Chk_Block
;                       ScanLock
;       LockAlloc
;       ClrSFLock
;       RLRInit


.xlist
include fnofs.inc
include dosseg.inc
.xcref
include basemaca.inc
include osmaca.inc
include oldmaca.inc
include harderr.inc
include tkexport.inc
include mft.inc
include vmowner.inc
include mi.inc
include mult.inc
include argframe.inc
include sft.inc
include error.inc
include fileio.inc
include protmode.inc
include seldesc.inc
include virtsym.inc
include infoseg.inc
include mmpha.inc
.cref
.list

        ModName , FNO_LOCK_ASM

        ReferGDT    GDT_RLR,WORD

        ReferTask   ExtErr_Locus,BYTE       ; Extended Error Locus
        ReferTask   ExtErr,WORD
        ReferTask   CurrTCB,WORD            ; Current thread control block
        ReferTask   CurrentPDB,WORD

        ReferGlobal SISData,BYTE
        ReferGlobal RLRHead,WORD
        ReferFlat   _pTCBCur,dword

        EXTRNFAR        Bmp_Free
        EXTRNFAR        Bmp_Get
        EXTRNFAR        Bmp_Init
        EXTRNFAR        ProcBlock
        EXTRNFAR        SegAlloc
        EXTRNFAR        w_FileIO
        EXTRNFAR        ProcRun

        EXTRN       PhysHandle:NEAR         ; check for phys disk handle
        EXTRN       pJFNFromHandle:NEAR
        EXTRN       SFFromHandle:NEAR
        EXTRN       SFLock:NEAR
        EXTRN       SFUnlock:NEAR
        EXTRN       SFWaitIdle:NEAR
        EXTRN       SFLinkFree:NEAR
        EXTRN       Violation_Delay_Retry:NEAR
        EXTRN       FuBuff:NEAR
        EXTRN       Wake_plock:NEAR

        extrn           Bmp_SegSize:ABS

        FARCODE HIGH2
HIGH2CODE SEGMENT
    ASSUME  SS:TASKAREA


;***    w_LockOper - worker routine for LockOper.
;
; Entry:
;       AL = 0  lock region
;          = 1  unlock region
;       BX = handle
;       CX:DX = offset of region to be locked/unlocked
;       SI:DI = length of region to be locked/unlocked
;
; Exit:
;       Carry set (error):
;           AX = ERROR_INVALID_HANDLE
;              = ERROR_INVALID_FUNCTION
;              = ERROR_LOCK_VIOLATION
;
;       Carry clear:
;               No error
; Uses:
;       All
; Called by:
;       $LockOper
; Pseudo Code
;
;w_LockOper(function,handle,firstbyteaddr,length)
;
;       LkopLock->start = 0
;       LkopLock->length = 0
;       LkopUnLock->start = 0
;       LkopUnLock->length = 0
;
;       if function > 1 {
;          ExtErr_Locus = errLoc_Unk;
;          return (error_invalid_function);
;       }
;
;
;       if function == lock {
;         LkopLock->start = firstbyteaddr;
;         LkopLock->length = length;
;       }
;       else {
;         LkopUnLock->start = firstbyteaddr;
;         LkopUnLock->length = length;
;       }
;
;       fileio->start = firstbyteaddr;
;       fileio->length = length;
;
;       rc = w_Filelocks(&LkopUnlock,&lkopLock);
;

Procedure   w_LockOper,<HYBRID,far16>
    ASSUME  DS:NOTHING,ES:NOTHING
LocalVar        LkopUnlock,<size Filelock>
LocalVar        LkopLock,<size Filelock>

;       Maj/Min = 3203H, Data = (al, bx, dx, cx, di, si)
;       (indicator, handle, offset (lo:high), length (lo:high)


        MMPHPreUnlockLockFileRange      ; MMIOPH Performance Hook: 3203H
                                        ; Data = (al, bx, dx, cx, di, si)
                                        ; (indicator, handle, offset (lo:high),
                                        ; length (lo:high)


;***DT  preLockOper - w_LockOper pre-invocation internal trace point
;
;    TRACE MINOR = <RAS_PRE_INT_LOCKOPER>,
;          TP    = .w_LockOper,
;          TYPE  = (PRE,INT),
;          GROUP = FS,
;          DESC  = "(OS) File Lock/Unlock Pre-Invocation",
;          REGS  = (AX,BX,DX,CX,DI,SI),
;          FMT   = "Unlock Flag = <FMT_WORD>  Handle = <FMT_WORD>",
;          FMT   = "Region Offset = <FMT_DWORD>  Length = <FMT_DWORD>"

        .errnz   (size Filelock - 8)
        EnterProc <0,0,0,0,0,0,0,0>

        CMP     AL,1
        LJA     lock_bad_func
        PUSH    DI                      ; Save LengthLow
        push    dx
        push    cx
        call    SFFromHandle            ; ES:DI -> SFT
        pop     cx
        pop     dx
        LJC     lock_bad_hand

        push    bp
        mov     bp,SS:CurrTCB
        mov     [bp].TCBThisSFT.Offst,di    ; fill in TCBThisSFT
        mov     [bp].TCBThisSFT.Segmt,es    ;
        pop     bp                          ;


;(@PhysDisk start)    check for physical disk handle

lock_do:
        call    PhysHandle              ; do we have a physical disk handle?
        LJC     lock_phys_hand          ; yes - return with error

;(@PhysDisk end)

        test    ES:[DI.sf_flags],DEVID_DEVICE   ; locks not permitted on devices
        LJNZ    lock_bad_hand
        OR      AL,AL                   ; See if Lock or Unlock
        POP     AX                      ; LengthLow to AX
        JZ      DOS_Lock

; We have an unlock function we must fill in the unlock range and length in
; the buffer on the stack

        mov     LkopUnlock.FLK_Start.LowWord,dx    ; cx:dx = offset to unlock
        mov     LkopUnlock.FLK_Start.HighWord,cx   ;
        mov     LkopUnlock.FLK_Length.LowWord,ax   ; si:ax = unlock length
        mov     LkopUnlock.FLK_Length.HighWord,si  ;
        or      ax,si
        jz      lock_bad_len                    ; if zero length return error

        TaskContext DS
        lea     si,LkopUnlock                   ;
        xor     di,di
        mov     es,di
        jmp     short lkop1
DOS_Lock:

; We have an lock function we must fill in the lock range and length in the
; buffer on the stack

lkop0:  mov     LkopLock.FLK_Start.LowWord,dx  ;
        mov     LkopLock.FLK_Start.HighWord,cx ; cx:dx = offset to lock
        mov     LkopLock.FLK_Length.LowWord,ax ;
        mov     LkopLock.FLK_Length.HighWord,si ; si:ax = length to lock
        lea     di,LkopLock                     ;
        TaskContext ES
        or      ax,si
        jz      lock_bad_len                    ; if zero length return error
        xor     si,si
        mov     ds,si

;  BX      =  handle of file to lock or unlock
;  DS:SI  ->  pointer to range to unlock
;  ES:DI  ->  pointer to range to lock
;  CX:DX   =  highword:lowword of timeout value
;  AX      =  Flags (0 = exclusive   1= share)
        push    cx              ;save registers
        push    dx
        xor     cx,cx           ;set timeout to 0 = "immediate"
        xor     dx,dx
        xor     ax,ax           ;set flags to 0 = "exclusive"
lkop1:  mov     ah,80h          ; ah = no check of protected filehandle lockID
        call    w_FileLocks
        pop     dx
        pop     cx

    ASSUME  DS:NOTHING,ES:NOTHING
;
; Common return point.
;

comm_ret:

;       Maj/Min = 3283H, Data = none
        MMPHPostUnlockLockFileRange     ; MMIOPH Performance Hook: 3283H
                                        ; Data = none
        LeaveProc
        ret

lock_bad_hand:
        mov     ax,ERROR_INVALID_HANDLE ; set error code
        stc                             ; indicate error to caller by setting CF
lock_phys_hand:                         ; error code already set
        POP     DI                      ; Clean stack
        jmp     SHORT comm_ret

lock_bad_func:
        MOV     ExtErr_Locus,ERRLOC_UNK ; Extended Error Locus
        mov     ax,ERROR_INVALID_FUNCTION
        stc                             ; indicate error to caller by setting CF
        jmp     SHORT comm_ret

lock_bad_len:
        mov     ax,ERROR_LOCK_VIOLATION
        stc                             ; indicate error to caller by setting CF
        jmp     SHORT comm_ret

EndProc w_LockOper

; Inputs:
;       ES:DI -> sft of file with lock violation
; Function:
;       Handle Lock violation on compatibility (FCB) mode SFTs
; Outputs:
;       Carry set if user says FAIL, causes ERROR_LOCK_VIOLATION
;       Carry clear if user wants a retry
;
; DS, ES, DI, CX preserved, others destroyed
; USES: AX,BX,DX,SI,BP,FLAGS

Procedure   Lock_Violation
    ASSUME DS:TASKAREA, ES:NOTHING
        SegAssert SS,<DS>

        mov     si,CurrTCB              ; set allowed actions
        mov     TASKAREA:[si].TCBAllowed,ALLOWED_FAIL + ALLOWED_RETRY
        mov     ExtErr,ERROR_LOCK_VIOLATION
        mov     ax,HELock               ; set type of hard error
        call    Violation_Delay_Retry   ; es:di -> sft of file with lock viol.
    ASSUME      DS:NOTHING
        ret

EndProc Lock_Violation


;***    w_FileLocks - File lock manager
;
;       w_FileLocks performs the DOSFILELOCKS function call.  It unlocks
;       and/or locks a range in an open file.
;
;       ENTRY   AH - Flags (00h = check protected filehandle, 80h = no check)
;               AL - Flags (0 = exclusive    1 = share)
;               BX - Handle of file to be locked
;               CX:DX - msec before timeout (0xFFFFFFFF = indefinite)
;               CX:DX - File handle lockID; previous usage changed since
;                       16:16 is always defined to be zero.
;               DS:SI - pointer in user space to offset, range to unlock
;               ES:DI - pointer in user space to offset, range to lock
;
;       EXIT    Carry Clear
;                       No error
;               Carry Set
;                       AX = error code
;                               ERROR_INVALID_HANDLE
;                               ERROR_PROTECTION_FAULT
;       CALLS   w_LockOper
;
;       USES    AX,BX,CX,DX,DI,SI,DS,ES,FLAGS
;
;       NOTES
;               When modifying this routine be sure all exit have a LeaveProc
;               statement.
;
;Pseudo Code
;w_FileLocks(handle,unlock_ptr,lock_ptr,timeout,flags);
;
;       commandlistlen = 0;
;       if ((unlock_ptr->FileOffset != 0) || (unlock_ptr->RangeLength !=0)) {
;
;          command_ptr->cmd = unlock;
;          command_ptr->share = flags;          /* share permissions */
;          command_ptr->timeout = timeout;      /* try for how long? */
;          command_ptr->unlockcnt = 1;
;          command_ptr->start = unlock_ptr->FileOffset;
;          command_ptr->length = unlock_ptr->RangeLength;
;          commandlistlen = size unlock_cmd;
;          command_ptr +=commandlistlen;
;       }
;
;       if ((lock_ptr->FileOffset != 0) || (lock_ptr->RangeLength !=0)) {
;
;          command_ptr->cmd = lock;
;          command_ptr->share = flags;          /* share permissions */
;          command_ptr->timeout = timeout;      /* try for how long? */
;          command_ptr->lockcnt = 1;
;          command_ptr->start = lock_ptr->FileOffset;
;          command_ptr->length = lock_ptr->RangeLength;
;          commandlistlen = size lock_cmd;
;          command_ptr +=commandlistlen;
;       }
;       rc = 0;
;       if (commandlistlen !=0)
;          rc = w_FileIO(handle,command_ptr,commandlistlen,&error);
;
;       return (rc);

Procedure w_FileLocks,HYBRID
LocalVar        wfl_handle, WORD                ; file handle for lock/unlock
LocalVar        wfl_lockRange, DWORD            ; lock range in file
LocalVar        wfl_unlockRange, DWORD          ; unlock range in file
;LocalVar        wfl_timeout, DWORD              ; try for n msecs before fail
LocalVar        wfl_FileHandleLockID, DWORD     ; protected filehandle lockID
LocalVar        wfl_flags, WORD                 ; how to share the locked region
                                                ; and check/nocheck protect handle
LocalVar        wfl_Commandlistlen,WORD         ; number bytes in FileIO buffer
LocalVar        wfl_Offset,WORD                 ; offset into FileIO buffer
LocalVar        wfl_Buffer, <size Filelock>     ; buffer to store user parms
LocalVar        wfl_FileIOBuffer,FIO_Lock_Unlock_Size ; buffer for FileIO
LocalVar        wfl_FileIOError,WORD            ; place to store FileIO error
LocalVar        wfl_FileIOArgFrame,<size DosFileIOStruc>
    ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA
        PMONLY

        EnterProc <bx,es,di,ds,si,cx,dx,ax,0,0> ; set up local variables

        lea     bx,wfl_FileIOBuffer
        mov     wfl_Offset,bx
        mov     bx,wfl_unlockRange.Segmt
        call    FLK_Get_Parms           ; do set up for w_FileIO
        LJC     wfl3                    ; error, get out
        jz      wfl1                    ; If no Unlock needed go process lock

; Fill in the FileIO buffer with the unlock parameters ; cmd, lock count,
; Unlock range start, Unlock range length, and adjust the length and offset
; into the FileIO buffer.

        mov     bx,wfl_Offset           ; offset to area on stack
        mov     ax,1
        mov     es:[bx].FIO_Hdr_Cmd,FIO_CMD_UNLOCK     ; store cmd code
        mov     es:[bx].FIO_LockCnt,ax                 ; only 1 lock unlock

; CX:DX = unlock range
; SI:DI = unlock length

        mov     es:[bx+FIO_UnLockListOff].FIO_Unlock_Start.LowWord,dx
        mov     es:[bx+FIO_UnLockListOff].FIO_Unlock_Start.HighWord,cx
        mov     es:[bx+FIO_UnLockListOff].FIO_Unlock_Length.LowWord,di
        mov     es:[bx+FIO_UnLockListOff].FIO_Unlock_Length.HighWord,si
        mov     wfl_Commandlistlen,FIO_UnLock_Size     ; size of unlock packet
        add     bx,FIO_UnLock_Size
        mov     wfl_Offset,bx

wfl1:   mov     bx,wfl_lockRange.Segmt
        mov     si,wfl_lockRange.Offst
        call    FLK_Get_Parms                         ; do set up for w_FileIO
        jc      wfl3                                  ; error, get out
        jz      wfl2

; Fill in the FileIO buffer with the lock parameters ; cmd, lock count,
; timeout, start, length, and adjust the length of the FileIO buffer

        mov     bx,wfl_Offset                        ; offset to area on stack
        mov     es:[bx].FIO_Hdr_Cmd,FIO_CMD_LOCK     ; store cmd code
        mov     es:[bx].FIO_LockCnt,1                ; only 1 lock unlock

        ;milliseconds to wait before timeout
;         mov     ax,wfl_timeout.LowWord
    ; okay to set timeout to 0 (immediate return); only 32-bit code
    ; supports non-zero timeout
        xor     ax,ax
        mov     es:[bx].FIO_Lock_Timeout.LowWord,ax
;         mov     ax,wfl_timeout.HighWord
        mov     es:[bx].FIO_Lock_Timeout.HighWord,ax

; CX:DX = unlock range
; SI:DI = unlock length

        mov     ax,wfl_flags
        xor     ah,ah                   ; zero out check filehandle lockID flag
        mov     es:[bx+FIO_LockListOff].FIO_Lock_Share,ax
        mov     es:[bx+FIO_LockListOff].FIO_Lock_Start.LowWord,dx
        mov     es:[bx+FIO_LockListOff].FIO_Lock_Start.HighWord,cx
        mov     es:[bx+FIO_LockListOff].FIO_Lock_Length.LowWord,di
        mov     es:[bx+FIO_LockListOff].FIO_Lock_Length.HighWord,si
        add     wfl_Commandlistlen,FIO_Lock_Size     ; size of unlock packet

; Get addressibility to the FileIO buffer and FileIO error offset. If there
; are any locks or unlocks call w_FileIO to process them.

wfl2:   mov     DX,wfl_handle           ; get handle back
        CpuMode 386
        mov     eax,ss
        shl     eax,16
        lea     ax,wfl_FileIOBuffer     ; eax has 16:16 pointer to cmd list
        mov     [wfl_FileIOArgFrame].fio_CommandList,eax
        lea     ax,wfl_FileIOError      ; eax has 16:16 pointer to err offset
        mov     [wfl_FileIOArgFrame].fio_ErrorOffset,eax
        CpuMode reset
        mov     cx,wfl_Commandlistlen   ; size of fileIO buffer
        clc                             ; clear carry in case no locks
        jcxz    wfl3                    ; if no locks and unlocks return
        lea     bx,wfl_FileIOArgFrame   ; ss:bx -> arg frame for DosFileIO
        mov     si,wfl_FileHandleLockID.LowWord  ; file handle lockID lowword
        mov     di,wfl_FileHandleLockID.HighWord ; file handle lockID highword
        mov     ax,wfl_flags            ; ah = check filehandle lockID flag
        push    bp                      ; save frame pointer
        CALLFAR w_FileIO
        pop     bp                      ; restore frame pointer
wfl3:   LeaveProc
        ret
EndProc w_FileLocks


;***    FLK_Get_Parms - Gets the lock/unlock parameters from user buffer.
;
;       FLK_Get_Parms - Gets the lock/unlock offset and length from the user
;       buffer into registers and sets the zero flag if all are zero.
;
;       ENTRY
;               BX:SI -> user buffer
;
;       EXIT    Carry Clear
;                       'ZR' clear CX:DX = range SI:DI length of range
;                       'ZR' set means null pointer passed
;               Carry Set
;                       ax = error_protection_violation
;       CALLS   FuBuff
;
;       USES    AX,BX,CX,DX,DI,SI,DS,ES
;
;       NOTES
;
Procedure FLK_Get_Parms
    ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA

        or      SI,SI                   ; is SI equal to zero?
        jnz     FLGP0                   ; no jump down
        mov     cx,bx                   ; is bx also equal to zero?
        and     cx,NOT 3                ; If the selector is 0, the 286 could
                                        ; put something in the rpl in selector reg.
        jz      FLGP1                   ; yes, jump down
FLGP0:  TaskContext ES                  ; ES:DI -> wflBuffer
        lea     DI, wfl_Buffer
        mov     cx, size Filelock       ; count of bytes to transfer
        SaveReg <es,di>
        invoke  FuBuff                  ; preform transfer from user space
        RestoreReg <di,es>
        jc      FLGP1                   ; carry set -- protection fault

;       move values from buffer to register

        mov     DX, es:[di].FLK_Start.LowWord   ; offset in CX:DX
        mov     CX, es:[di].FLK_Start.HighWord
        mov     SI, es:[di].FLK_Length.HighWord
        mov     DI, es:[di].FLK_Length.LowWord  ; range in SI:DI
        xor     ax,ax
        inc     ax                      ; clear 'ZR' flag
FLGP1:  ret
EndProc FLK_Get_Parms


;***    Lock_list - Lock list processor
;
;
;       ENTRY   BX:SI   = pointer to list of regions to lock
;               CX      = number of locks to be locked
;               DI:DX   = Timeout value for entire lock list to be completed
;                         Timeout = 0ffffh     - wait indefinitely
;                                 = 0          - return immediately
;                                 = Otherwise  - ms timeout
;               AX:     = file handle
;               BX:SI- > Lock Struc
;                        Share   dw   ; 0 = exclusive 1 = read-Only
;                        Start   dd   ; start of locked region
;                        Length  dd   ; length of locked region
;                        Lock Ends
;       EXIT
;               Carry Clear
;               Carry Set
;                       AX = ERROR_SHARING_BUFFER_EXCEEDED
;                          = ERROR_SHARING_VIOLATION
;                          = ERROR_INVALID_PARAMETER
;
;       CALLS   FuBuff,Set_Block,Lckl_Unlock,Clr_Block
;
;       USES    AX,BX,CX,DX,DI,SI,DS,ES,FLAGS
;
;Lock_list(cmdbufptr,lockcnt)
;
;
;   foo:   get one lock record from user buffer
;          Set_Block (remaining timeout)
;          if (error)
;             unlock locks
;             return
;          else
;             go to foo with next lock record
;
Procedure Lock_list,HYBRID
    ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA

LocalVar lckl_count,WORD                ; number of locks in the lock list
LocalVar lckl_successcnt,WORD           ; number of locks successful
LocalVar lckl_Timeout,DWORD             ; timeout for entire lock list
LocalVar lckl_offset,WORD               ; offset into the lock list
LocalVar lckl_startoffset,WORD          ; starting offset into the lock list
LocalVar lckl_selector,WORD             ; selector of user buffer of lock list
LocalVar lckl_handle,WORD               ; file handle
LocalVar lckl_lock,<size FIOLock>       ; buffer for 1 lock structure

        or      cx,cx
        LJZ     lckl11                  ; 0 locks ? yes exit with no error
        EnterProc <cx,0,di,dx,si,si,bx,ax>
lckl1:  mov     cx,size FIOLock         ; CX = size of lock record
        mov     bx,lckl_selector        ; BX:SI -> lock record in users space
        TaskContext ES
        lea     di,lckl_lock            ; ES:DI -> lock record area on stack
        SaveReg <es,di>
        call    FuBuff                  ; Fetch users lock record
        RestoreReg <di,es>
        jc      lckl9                   ; if protection fault go return
        cmp     es:[di].FIO_Lock_Share,FIO_LOCK_SHARE_SHR   ;valid share flag ?
        ja      lckl12                  ; no, return error

        mov     cx,lckl_Timeout.HighWord
        mov     dx,lckl_Timeout.LowWord ; cx:dx = Timeout value for lock
        mov     ax, lckl_handle

; ES:DI -> lock record on the stack
; DS:SI -> sft
; CX:DX =  Timeout value for lock to complete
; AX    =  Handle

        call    Set_Block               ; attempt lock 2nd time with timeout
        jc      lckl9

        mov     lckl_Timeout.LowWord,dx
        mov     lckl_Timeout.HighWord,cx ; CX:DX = Timeout Remaining

; The lock was set successful. Adjust the successful count and lock list
; offset pointer and go process next lock if we are not done.

        inc     lckl_successcnt         ; one more lock successful
        add     lckl_offset,size FIOLock
        mov     si,lckl_offset
        mov     ax,lckl_count           ; AX = number of locks to set
        cmp     lckl_successcnt,ax      ; done with all locks ?
        jb      lckl1                   ; no, go process next lock in list
        jmp     short lckl10            ; exit with carry clear

; An error has occurred, we have to unlock all the other locks since each
; command is treated as an atomic operation.

lckl9:  call    Lckl_Unlock
        stc                             ; set error flag
lckl10: LeaveProc
lckl11: ret
lckl12: mov     ax,ERROR_INVALID_PARAMETER
        jmp     short lckl9

EndProc Lock_list

;***  Lckl_Unlock - Lock_list unlock procedure
;
;     Lckl_Unlock unlocks the specified number of locked regions.
;
;       ENTRY   SI              = offset in list of regions to unlock
;               BP              = LocalVar stack of Lock_List
;               lckl_selector   = selector of list of regions to unlock
;               lckl_successcnt = number of locks to unlock
;       EXIT
;               Carry Clear
;
;       CALLS   Unlock_list
;
;       USES    CX,DX,Flags
;
Procedure Lckl_Unlock,NEAR
    ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA

        SaveReg <ax,bx,es,di,ds,si>
        mov     si,lckl_startoffset
        lea     si,[si].FIO_Lock_Start  ; skip share word in lock record
        mov     bx,lckl_selector
        mov     ax,size FIOLock         ; size of lock record
        mov     cx,lckl_successcnt      ; number of locks to unlock

; BX:SI -> pointer to list of locks to be unlocked
; AX    =  size of Lock structure
; CX    =  number of locks succesffully locked. Maybe 0!

        call    Unlock_list             ; unlock the requested locks
        RestoreReg <si,ds,di,es,bx,ax>
        ret
EndProc Lckl_Unlock

;***  UnLock_list - UnLock list processor
;
;     UnLock_list unlocks the specified number of locked regions in the lock or
;     or unlock list. If an error occurs before we complete we return without
;     unlocking the other locks. We call Clr_Block to clear the locked region.
;
;       ENTRY   BX:SI   =  pointer to list of regions to unlock
;                        Lock Struc
;                        Share   dw   ; 0 = exclusive 1 = read-Only
;               BX:SI- > Start   dd   ; start of locked region
;                        Length  dd   ; length of locked region
;                        Lock Ends
;                OR
;
;               BX:SI- > UnLock Struc
;                        Start   dd   ; start of locked region
;                        Length  dd   ; length of locked region
;                        UnLock Ends
;               CX      =  number of locks in list to unlock
;               AX      =  size of Lock or size of UnLock
;       EXIT
;               Carry Clear
;               Carry Set
;                       AX = ERROR_SHARING_VIOLATION
;
;       CALLS   FuBuff,Clr_Block
;       USES    AX,BX,CX,DX,DI,SI,DS,ES,FLAGS
;
;UnLock_list (unlockptr,unlockcnt,lockpktsize)
;
;       for (i= 1; i <= unlockcnt; i++) {
;
;          if ((rc = Clr_Block(sft_ptr,unlockptr->start,unlockptr->length) !=0)
;            return (rc,i);
;
;          unlockptr += lockpktsize;
;       }
;       return (rc,unlockcnt);
;
Procedure Unlock_list,HYBRID
    ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA

LocalVar ulkl_size,WORD
LocalVar ulkl_offset,WORD
LocalVar ulkl_selector,WORD
LocalVar ulkl_count,WORD
LocalVar ulkl_lock,<size FIOLock>

        EnterProc <ax,si,bx>

        clc                             ; Clear carry in case of error
        jcxz    ulkl2                   ; CX = 0 ? yes
ulkl1:  mov     ulkl_count,cx           ; store lock count for later
        mov     cx,size FIOUnlock
        lea     di,ulkl_lock
        TaskContext ES                  ; ES:DI -> lock record on stack
        SaveReg <si,di,es>              ; BX:SI -> lock record in user space
        call    FuBuff                  ; fetch unlock offset and length
        RestoreReg <es,di,si>
        jc      ulkl2

; ES:DI -> pointer to offset and length to be unlocked

        xor     ax,ax
        call    Clr_Block               ; Clear lock
        jc      ulkl2                   ; if error go return
        mov     ax,ulkl_size            ; get size of lock packet
        add     ulkl_offset,ax          ;
        mov     si,ulkl_offset          ;
        mov     bx,ulkl_selector        ; BX:SI -> unlock list
        mov     cx,ulkl_count           ; get unlock count
        loop    ulkl1                   ; if not finished go get next lock
        clc
        mov     ax,ulkl_size
ulkl2:  LeaveProc
        ret
EndProc Unlock_list


;**     Set_Block - set byte range lock on a file
;
;       Set_Block sets a lock on a specified range of a file. If we find an
;       overlap and the t8imeout value is not 0 we ProcBlock on
;       GDT_RLR and the PID of the owner of the overlapping lock. We
;       also return the time remaining in timeout after we ProcBlocked.
;       Locks are cleared via Clr_Block.
;
;
;       ENTRY   (ES:DI) = Pointer to lock record
;                ES:DI-> Lock Struc
;                        Share   dw   ; 0 = exclusive 1 = read-Only
;                        Start   dd   ; start of locked region
;                        Length  dd   ; length of locked region
;                        Lock Ends
;
;               AX     = file handle
;
;               (CX:DX)  = Timeout = 0ffffh     - wait indefinitely
;                                  = 0          - return immediately
;                                  = Otherwise  - ms timeout
;               User_ID = 16-bit user id of issuer
;               Proc_ID = 16-bit process id of issuer
;               (SS) = TASKAREA
;
;       EXIT    'C' clear if no error
;               (CX:DX) = time remaining on lock;
;               'C' set if error
;                 (ax) = error code
;                           ('ERROR_LOCK_VIOLATION' if conflicting locks)
;                        error_interrupt
;       USES    AX,BX,CX,DX,ESI,DS
;
;       ALGORITHM
;
;       do {
;         convert lock range and scan for lock conflict
;         if (lock range error)
;           return error_lock_violation
;         else if (lock conflict)
;           if (timeout == 0)
;             return error_lock_violation
;           else
;             sleep until timeout or waken by others
;         else /* no error and no lock conflict */
;           break;
;       } while (1)
;
;       allocate a free lock
;       fill in new lock record
;       add new record to front of lock record chain
;       return remaining time

Procedure   Set_Block,<NEAR,FAR16>
    ASSUME  ES:NOTHING,DS:NOTHING,SS:TASKAREA

LocalVar sb_handle,WORD
LocalVar sb_Timeout,DWORD
LocalVar sb_lpSFT,DWORD
LocalVar sb_lock,DWORD              ; pointer to lock record
LocalVar sb_share,WORD
LocalVar sb_OldMsCount,DWORD
LocalVar sb_SFTLocked,BYTE          ; SFT locked flag
LocalVar sb_lenhigh,WORD
LocalVar sb_lenlow,WORD
LocalVar sb_starthigh,WORD
LocalVar sb_startlow,WORD
LocalVar sb_lpMFT,DWORD

        EnterProc <ax, cx, dx>
        SaveReg <es,di>
        xor     ax,ax
        mov     sb_SFTLocked,al            ; reset sft locked flag

        mov     ax,es:[di].FIO_Lock_Share
        mov     sb_share,ax

        mov     si,es:[di].FIO_Lock_Length.HighWord
        mov     sb_lenhigh,si
        mov     ax,es:[di].FIO_Lock_Length.LowWord
        mov     sb_lenlow,ax
        mov     cx,es:[di].FIO_Lock_Start.HighWord
        mov     sb_starthigh,cx
        mov     dx,es:[di].FIO_Lock_Start.LowWord
        mov     sb_startlow,dx

        mov     di,CurrTCB
        les     di,ss:[di].TCBThisSFT   ; es:di -> sft
        mov     sb_lpSFT.Offst,di
        mov     sb_lpSFT.Segmt,es       ; save sft pointer

        SaveReg <ax,di,si,es>
        call    LockAlloc               ; (DS:DI) = pointer to new, orphan lock record
        mov     sb_lock.Offst,di
        mov     sb_lock.Segmt,ds        ; save lock record pointer
        LJC     sblk8                   ; no more space
        RestoreReg <es,si,di,ax>

;       (ES:DI) = SFT
;       (SI:AX) = length of area to be locked
;       (CX:DX) = offset of area to be locked

sb02:   SaveReg <ax, si, cx, dx>

        cmp     es:[di].sf_MFT,SF_MFT_NULL
        LJZ     sblk9

ifdef FSSTRICT
        CpuMode 386
        mov     ebx,es:[di].sf_MFT
        mov     sb_lpMFT,ebx            ; save mft pointer
        CpuMode reset
endif

        mov     bh,RLR_READ_ACCESS      ; assume shared lock
        cmp     sb_share,RLR_SHARED     ; shared lock ?
        je      sb03                    ; yes, then we are correct
        mov     bh,RLR_WRITE_ACCESS     ; no, set write/exclusive access
sb03:   xor     bl,bl                   ;
        or      bl,RLR_FIND_OWN
        or      bl,bh
        call    clp                     ; do setup code
    ASSUME      DS:NOTHING
        jnc     sb05                    ; no error - set lock

        cmp     cx,RLR_LENGTH_ERROR     ; did we have true lock violation ?
        LJE     sblk9                   ; no,length error ret. without blocking

; DS:BX -> overlapping lock

        mov     cx,sb_Timeout.LowWord
        mov     di,sb_Timeout.HighWord  ; DI:CX = timeout value
        or      cx,cx                   ; lowword of timeout = 0 ?
        jnz     sb04                    ; no, go block
        or      di,di                   ; hiword of timeout = 0 ?
        LJZ     sblk9                   ; yes go exit

sb04:   push    ax                      ; save possible error code

; DS:BX -> overlapping lock

        les     di,sb_lpSFT             ; ES:DI -> SFT
        cmp     sb_SFTLocked,0
        jne     sbl02                   ; SFT already locked

        call    SFLock
        pop     ax
        LJC     sbl40                   ; SFT gone, return error

        ;  Since we may have blocked on the SFLock, we have to check
        ;  again for a lock conflict. i.e. the overlapping lock
        ;  may have gone away, and maybe even another one is in its place!


        inc     sb_SFTLocked            ; set SFT locked flag
        jmp     sbl20                   ; go try again

sbl02:  or      [bx].rlr_flags,RLR_WAITING ; so owner knows we wait

        ; set lock's PID        = our PID,
        ;            rlr_block  = offset of overlapping lock
        ;            rlr_handle = the handle

        lds     si,sb_lock              ; DS:SI -> lock
        mov     ds:[si].rlr_block, BX   ; offset of overlapping lock
        push    bp
        mov     bp,CurrTCB
        push    es
        CpuMode 386
        FlatContext <es>                ;
        mov     eax,es:[_pTCBCur]       ; save the TCB to wake up process!
        mov     ds:[si].rlr_sptr,eax
    ASSUME  ES:NOTHING
        CpuMode reset
        pop     es

        mov     ax,[bp].TCBProc_ID
        mov     ds:[si].rlr_PID,ax      ; our PID
        pop     bp
        mov     ax,sb_handle
        mov     ds:[si].rlr_handle,ax      ; the handle

        push    si
        mov     si,sb_lenhigh
        mov     ax,sb_lenlow
        mov     cx,sb_starthigh
        mov     dx,sb_startlow
        call    CheckRange
        pop     si
        mov     ds:[si].rlr_fba.hiword,ax
        mov     ds:[si].rlr_fba.loword,bx       ; store lock range
        mov     ds:[si].rlr_lba.hiword,cx       ; in pending lock record
        mov     ds:[si].rlr_lba.loword,dx

        ; chain pending lock at beginning of chain
        ; ES:DI -> SFT
        ; DS:SI -> lock

        xor     ax,ax
        mov     sb_SFTLocked,al         ; Reset SFT locked flag
        mov     ds:[si].rlr_prev,ax     ; lock.rlr_prev = SFT
        mov     ax, es:[di].sf_plock    ; ax = next pending lock
        mov     ds:[si].rlr_next,ax     ; lock.rlr_next = nextlock
        mov     es:[di].sf_plock,si     ; sft.plock = lock
        cmp     ax,0                    ; is there a nextlock?
        je      sbl05                   ; no
        mov     bx, ax
        mov     ds:[bx].rlr_prev,si     ; nextlock.rlr_rev = lock

sbl05:  call    SFUnlock
        SaveReg <es,di,ds,si>

        ; now go to sleep

        mov     cx,sb_Timeout.LowWord
        mov     di,sb_Timeout.HighWord  ; DI:CX = timeout value
        mov     bx, ds:[si].rlr_block   ; bx = offset of overlapping lock
        mov     ax,GSEL GDT_RLR         ; AX:BX = block id
        call    LockBlock               ;
        mov     sb_Timeout.LowWord,cx   ; DI:CX = Timeout remaining
        mov     sb_Timeout.HighWord,di
        RestoreReg <si,ds,di,es>
        pushf

        ; take our lock out of the chain
        ; DS:SI -> lock
        ; ES:DI -> SFT

        call    SFLock
        mov     dx,ds:[si].rlr_next     ; DS:DX -> nextlock
        mov     bx,ds:[si].rlr_prev     ; DS:BX -> prevlock
        cmp     dx,0                    ; is there a nextlock?
        je      sbl07                   ; no
        xchg    bx,dx
        mov     ds:[bx].rlr_prev,dx     ; nextlock.rlr_prev = prevlock
        xchg    bx,dx
sbl07:  cmp     bx,0                    ; is there a prevlock?
        je      sbl08                   ; no
        mov     ds:[bx].rlr_next,dx     ; prevlock.rlr_next = nextlock
        jmp     short sbl09
sbl08:  mov     es:[di].sf_plock,dx     ; sft.sf_plock = nextlock
sbl09:
        call    SFUnlock
        popf                            ; recover errors from LockBlock
        pop     ax
        jnc     sbl15

; have we been interrupted because of a DosCancelLockRequest or because
; the lock is released and the pending process is to grab the bytes in
; the file?
; DosCancelLockRequest means the plock record was cancelled

        test    ds:[si].rlr_flags,RLR_CANCELPLOCK
        jz      sbl13
        and     ds:[si].rlr_flags,NOT RLR_CANCELPLOCK  ; b724509
        mov     ax,ERROR_INTERRUPT
sbl13:
        jmp     sblk10

        ; make sure handle has not been closed
sbl15:

        mov     bx, sb_handle
        call    pJFNFromHandle
        push    es
        pop     ds
        mov     si,di                   ; DS:SI -> JFN entry
        les     di,sb_lpSFT             ; ES:DI -> SFT
        jc      sbl30
        mov     ax, es:[di].sf_fsi.sfi_selfsfn ; bx = original SFN
        cmp     ax, ds:[si]             ; same SFN ?
        jne     sbl30                   ; not same SFN, return bad handle

sbl20:  RestoreReg <dx, cx, si, ax>
        jmp     sb02                    ; go back and find a new lock record

        ; handle has been closed, do clean up

sbl30:  mov     bx,  es:[di].sf_plock
        cmp     bx,0                    ; pending locks left ?
        jne     sbl50                   ; yes, just return error
        mov     bx,  es:[di].sf_ref_count
        cmp     bx, 0                   ; more references to sft?
        jne     sbl50                   ; yes, just return error

        ; we have to free the sft

; #73633
;        call    SFWaitIdle
;        call    SFLinkFree              ; put SFT on free list
; #73633
        jmp     short sbl50             ; return error

sbl40:  call    SFUnlock
sbl50:  mov     ax, ERROR_INVALID_HANDLE
        jmp     sblk10

;       It's ok to set this lock.  fill its lock record
;
;       (AX:BX) = fba lock area
;       (CX:DX) = lba lock area
;       (DS:DI) = pointer to pointer to previous lock
;       (TOS) = saved (DS)

sb05:   lds     di,sb_lock              ; ds:di -> lock
        les     si,sb_lpSFT             ; es:si -> sft
        mov     [di].rlr_sptr.Offst,si  ; store SFT offset
        mov     [di].rlr_sptr.Segmt,es  ; store SFT segment
        mov     [di].rlr_fba.hiword,ax
        mov     [di].rlr_fba.loword,bx  ; store lock range
        mov     [di].rlr_lba.hiword,cx
        mov     [di].rlr_lba.loword,dx
        mov     ax,sb_share             ; 0 = exclusive 1 = shared
        mov     [di].rlr_flags,al       ; store type of lock

;       add to front of chain
;       (es:si) point to SFT

        CpuMode 386
        mov     esi,es:[si].sf_MFT
        FlatContext <es>                ; (es:esi) -> MFT entry

; ensure original MFT is same as SFT->MFT
ifdef FSSTRICT
        cmp     esi,sb_lpMFT
        jne     lockie
endif

        mov     ax,es:[esi].mft_lptr    ; link in new lock record
        mov     [di].rlr_next,ax        ; rlr_next -> old first record
        mov     es:[esi].mft_lptr,di    ; mft_lptr -> new lock record
    ASSUME  ES:NOTHING
        CpuMode reset

;
; Set lock's process ID, user ID, and PDB.  (PDB falls out for pmode apps.)
;
        mov     ax,CurrentPDB
        mov     [di].rlr_PDB,ax
        push    bp
        mov     bp,CurrTCB
        mov     ax,[bp].TCBProc_ID
        mov     [di].rlr_PID,ax
        mov     ax,[bp].TCBUser_ID
        mov     [di].rlr_UID,ax
        pop     bp

        cmp     sb_SFTLocked, 0         ; is SFT locked by us?
        jne     sb07                    ; no, go ahead
        les     di,sb_lpSFT             ; ES:DI -> SFT
        call    SFUnlock                ; unlock SFT

sb07:   clc

sblk8:  pop     bx                      ; discard input parameters
        pop     bx
        pop     bx
        pop     bx

        mov     dx,sb_Timeout.LowWord
        mov     cx,sb_Timeout.HighWord          ; cx:dx time remaining on lock
        RestoreReg <di,es>
        LeaveProc
        RETP                            ; return - all set

sblk9:  mov     ax,ERROR_LOCK_VIOLATION
sblk10: push    ax
        les     di,sb_lock          ; es:di -> lock
        CALLFAR Bmp_Free
        pop     ax
        stc
        jmp     sblk8

ifdef FSSTRICT
lockie: InternalError <Set_Block: bad MFT in SFT>
endif


EndProc Set_Block


;***    LockBlock - block until the ID is run or a time out occurs
;
;       Blocks until the block/run ID is run or the time out expires.
;
;       ENTRY:
;               AX:BX = Block/Run ID.
;               DI:CX = time out value.
;
;       EXIT:   Interrupts enabled
;               If carry clear, then
;                       DI:CX = amount of time out left.
;               If carry set, then
;                       AX = error code
;                               ERROR_LOCK_VIOLATION
;                               ERROR_INTERRUPT
;
;       USES: AX,CX,DX,DI,Flags
;
;       Things to do: 1. Handle case where the millisecond count rolls over
;                        from 0FFFFFFFFH.
;
;       LockBlock(BlockID, TimeOut)
;       {
;           StartTime = CurrentTime()
;           if(ProcBlock(BlockID, TimeOut) == ERROR_TIMEOUT) {
;               return(ERROR_TIMEOUT)
;           }
;           EndTime = CurrentTime()
;           TimeOut -= EndTime - StartTime
;           if(TimeOut < 0) {
;               TimeOut = 0
;           }
;           return(OK, TimeOut)
;       }
;

Procedure LockBlock,HYBRID
    ASSUME  ES:NOTHING,DS:NOTHING,SS:TASKAREA

LocalVar  OldMsCount,DWORD

        SaveReg <es>
        DosContext es
        EnterProc <SISData.SIS_MsCount.HighWord,SISData.SIS_MsCount.LowWord>

;       (AX:BX) = Block/Run ID
;       (DI:CX) = Time out value

lbl1:   xor     dh,dh                   ; block is interruptible
        cli                             ; disable interrupts
        CALLFAR ProcBlock
        jc      lbl3                    ; jump if error from ProcBlock

        cmp     cx,-1                   ; check if indefinate time out (-1)
        jne     lbl2
        cmp     di,cx
        je      lbl6                    ; carry clear jump to no error exit

lbl2:   cli                             ; disable ints to read infoseg counter
        mov     ax,SISData.SIS_MsCount.LowWord
        mov     dx,SISData.SIS_MsCount.HighWord ; (DX:AX) = current ms count

        sub     ax,OldMsCount.LowWord
        sbb     dx,OldMsCount.HighWord  ; (DX:AX) = number of ms passed

        sub     cx,ax
        sbb     di,dx                   ; (DI:CX) = new time out value
        jnc     lbl6
        xor     cx,cx
        mov     di,cx                   ; timeout = 0 if new timeout < 0
        jmp     short lbl6              ; jump to exit

lbl3:   mov     ax,ERROR_INTERRUPT
        jnz     lbl5                    ; jump if block was interrupted
lbl4:   mov     ax,ERROR_LOCK_VIOLATION

lbl5:   stc                             ; set error flag

lbl6:   sti                             ; enable interrupts
        LeaveProc
        RestoreReg <es>
        ret

EndProc   LockBlock


;**     Clr_Block - clear byte range lock on a file or just changes flags
;
;       Clr_Block clears a lock on a specified range of a file. If there are
;       threads waiting on this lock region to become cleared we wake them up
;       (ProcRun). We also unchain the lock record from the list and put it on
;       the free list. The requested unlock range must exactly to one of the
;       lock ranges specified in the lock record. The handle to the SFT,
;       process id and PDB must also match the lock record to be cleared.
;       Locks are set via Set_Block.
;       If AX == 1 on entry, the flags are to be changed in the lock record
;       as indicated in BX
;
;       ENTRY   AX = 1 atomic lock support just change flags
;                    BX = flags
;               AX = 0 do the complete clear of the lock record
;               (ES:DI) = pointer to offset and length of area to unlock
;               User_ID = 16-bit user id of issuer
;               Proc_ID = 16-bit process id of issuer
;               (SS) = TASKAREA
;
;       EXIT    'C' clear if no error
;               'C' set if error
;                 (ax) = error code
;                           ('ERROR_LOCK_VIOLATION' if conflicting locks or
;                           range does not exactly match previous lock)
;
;       USES    AX,BX,CX,DX,EDI,SI,DS,ES,FLAGS
;
;       ALGORITHM
;
;       CheckRange
;       Scan for exact matching lock
;       if found
;         unchain lock
;         wake up any waiting set_blockers
;       else
;         error

Procedure   Clr_Block,<NEAR,FAR16>
    ASSUME  ES:NOTHING,DS:NOTHING,SS:TASKAREA

LocalVar    atomic,WORD
LocalVar    flags,WORD
LocalVar    cblk_FBA_Low,WORD
LocalVar    cblk_SFT,DWORD
LocalVar    cblk_RLRHead,WORD   ; local copy of RLRHead to avoid DosContext
LocalVar    cblk_pMFT,DWORD     ; points to MFT

        EnterProc <ax,bx>  ; set up local variables

        mov     cx,es:[di].FIO_Unlock_Start.HighWord
        mov     dx,es:[di].FIO_Unlock_Start.LowWord
        mov     si,es:[di].FIO_Unlock_Length.HighWord
        mov     ax,es:[di].FIO_Unlock_Length.LowWord

        mov     di,CurrTCB
        les     di,TASKAREA:[di].TCBThisSFT
        cmp     es:[di].sf_MFT,SF_MFT_NULL
        LJE     cblk8                   ; carry is clear
        mov     cblk_SFT.Offst,di
        mov     cblk_SFT.Segmt,es

; CX:DX = offset of area to be unlocked
; SI:AX = length of area to be unlocked
; ES:DI -> SFT

        call    CheckRange
        LJC     cblk8

        DosContext <ds>
        mov     si,[RLRHead]            ; GDT_RLR:si -> dummy head node
        mov     cblk_RLRHead,si         ; save a local copy

        FlatContext DS
        mov     edi,es:[di].sf_MFT      ; ds:edi -> mft
        mov     cblk_pMFT,edi           ; save mft pointer for later use
        mov     di,[edi].mft_lptr       ; GDT_RLR:di -> 1st lock record

        RLRCONTEXT <ds>
        mov     [si].rlr_next,di        ; head node now points to 1st record

        mov     cblk_FBA_Low,bx

; DS:SI   -> first lock record
; (AX:BX) =  FBA of locked region
; (CX:DX) =  LBA of locked region

cblk0:  mov     di,si                   ; di = previous lock address
        mov     si,[si].rlr_next        ; advance to next lock

        or      si,si                   ; end of list ?
        LJZ     cblk8                   ; yes, return error

        SaveReg <ax,cx,dx>
        mov     bx,cblk_FBA_Low
        xor     ax,[si].rlr_fba.hiword  ; require a 4-word match
        xor     bx,[si].rlr_fba.loword
        xor     cx,[si].rlr_lba.hiword
        xor     dx,[si].rlr_lba.loword
        or      ax,bx
        or      ax,cx
        or      ax,dx                   ; 'Z' set if exact match
        RestoreReg <dx,cx,ax>
        jnz     cblk0                   ; 'NZ' set if not exact match

;
;       We've got the lock
;
; Now comes the tricky part.  Is the lock for us?  Does the lock match the SFT
; that was given us?  If not, then error.
;
        mov     bx,cblk_SFT.Offst
        cmp     [si].rlr_sptr.Offst,bx
        jnz     cblk0
        mov     bx,cblk_SFT.Segmt
        cmp     [si].rlr_sptr.Segmt,bx
        jnz     cblk0
        mov     bx,CurrTCB
        mov     bx,TASKAREA:[bx].TCBProc_ID
        cmp     [si].rlr_PID,bx
        jnz     cblk0                   ; not our process, skip it
        mov     bx,CurrTCB
        mov     bx,TASKAREA:[bx].TCBUser_ID
        cmp     [si].rlr_UID,bx
        jnz     cblk0                   ; not our user, skip it
        mov     bx,CurrentPDB
        cmp     [si].rlr_PDB,bx
        jnz     cblk0                   ; not our pdb, skip it

;
; The locks match the proper open invocation.  Unchain the lock
; save the flags

        cmp     atomic,1                ; atomic just change flags
        je      chgflg
        mov     ax,[si].rlr_next        ; ax = offset of next lock
        mov     [di].rlr_next,ax        ; chain out current lock

; the lock just chained out may be the 1st lock record, so update mft_lptr

        mov     di,cblk_RLRHead         ; ds:di -> dummy head node
        mov     ax,[di].rlr_next        ; ax = possibly updated 1st rec. ptr
        FlatContext <ds>
        mov     edi,cblk_pMFT           ; ds:edi -> mft
        mov     [edi].mft_lptr,ax       ; update pointer to 1st lock record

;       wake up any waiters and put defunct lock record on the free chain
;
;       (GDT_RLR:SI) = address of unchained lock rec

cblk4:  RLRCONTEXT <es>                         ; es:si -> unchained record
        test    es:[si].rlr_flags,RLR_WAITING   ; Anyone Waiting ?
        jz      cblk5                           ; No, go free it

        mov     ax,GSEL GDT_RLR                 ; high word of block id
        mov     bx,si                           ; rlr offset
        CALLFAR ProcRun                         ; AX:BX = Block ID

cblk5:  mov     di,si                   ; es:di -> unchained record
        CALLFAR Bmp_Free                ; recycle it

cblk7:  LeaveProc
        RETP

;       error - lock range not right

cblk8:  mov     ax,ERROR_LOCK_VIOLATION
        stc
cblk9:  jmp     cblk7

chgflg:
        mov     bx,flags
        mov     byte ptr [si].rlr_flags,bl ; change the flags
        jmp     short cblk7

EndProc Clr_Block


;***    clp - common lock preamble
;
;       This routine contains a code fragment for Set_Block.
;
;       ENTRY   (ES:DI) = SFT address
;               (CX:DX) = offset of area
;               (SI:AX) = length of area
;               (BL)    = xxxxxxx1 - Request for write/exclusive access
;                       = xxxxxx1x - Request for read/shared access
;                       = xxxxx1xx - to consider all locks (including own)
;
;       EXIT    'NC' if no overlap
;                 (ax:bx) = offset of first byte in range
;                 (cx:dx) = offset of last byte in range
;
;               'CY' if length violation
;                   (cx) = RLR_LENGTH_ERROR
;               'C' set if overlap
;                   (DS:BX) points to overlapping lock
;                   (cx) = RLR_VIOLATION_ERROR
;
;       USES    AX,BX,CX,DX,DI,ESI,DS,FLAGS
;
; clp(sft,firstbyteaddr,length,lastbyte,overlap)
;
;    if (rc = CheckRange(firstbyteaddr,length,&lastbyte) != 0)
;       return (rc);
;    rc = ScanLockEntry(firstbyteaddr,lastbyte,ignoreaccess,find_own,&overlap);
;
;    return (rc,overlap);       /* overlap is the ptr to overlapping lock */

Procedure   clp,NEAR
    ASSUME  ES:NOTHING,DS:NOTHING,SS:TASKAREA

        xor     bh,bh
        push    bx                      ; save access flags
        call    CheckRange
;   (ES:DI) is SFT
        CpuMode 386
        mov     esi,es:[di].sf_MFT      ; (FLAT:esi) -> MFT entry
        CpuMode reset
        pop     di                      ; restore access flags
        jc      clp3

        FlatContext ds                  ; (ds:esi) -> MFT entry

; (DS:ESI) -> MFT address
; (AX:BX) =  FBA of locked region
; (CX:DX) =  LBA of locked region
; (DI)    =  access flags

        call    ScanLock                ; if CY then ds:si -> overlapping lock
        jnc     clp3
        mov     cx,RLR_VIOLATION_ERROR
        mov     bx,si                   ; DS:BX -> overlapping lock
clp3:   ret
EndProc clp


;***    CheckRange - Check lock range
;
;       CheckRange verifies that the length is not 0 and that the starting
;       offset plus the length does not exceed 0xFFFFFFFF.
;
;       ENTRY   (CX:DX) = offset of area
;               (SI:AX) = length of area
;
;       EXIT    'C' clear if no overlap
;                 (ax:bx) = offset of first byte in range
;                 (cx:dx) = offset of last byte in range
;               'C' set if length violation
;                   (cx) = RLR_LENGTH_ERROR
;
;       USES    AX,BX,CX,DX,SI,FLAGS
;
;    if ((length == 0) || (firstbyteaddr + length > 0xFFFFFFFF)
;       return (ERROR_LOCK_VIOLATION);
;
;    lastbyte = firstbyteaddr + length;
;
Procedure   CheckRange,<NEAR,FAR16>
    assume ds:nothing,es:nothing

        mov     bx,dx                   ; shuffle arguments
        xchg    dx,ax
        xchg    ax,cx                   ; (ax:bx) = offset
        mov     cx,si                   ; (cx:dx) = length

        or      si,dx                   ; see if length is 0
        jz      chkr3                   ; if so, then error

        add     dx,bx
        adc     cx,ax                   ; (cx:dx) = lba+1
        jnc     chkr1                   ; no carry is ok
        mov     si,dx
        or      si,cx
        jnz     chkr3                   ; if > 0 then error

chkr1:  sub     dx,1                    ; (cx:dx) = lba of locked region
        sbb     cx,0
        clc
chkr2:  RETP
chkr3:  mov     ax,ERROR_LOCK_VIOLATION ; error
        mov     cx,RLR_LENGTH_ERROR
        stc
        jmp     short chkr2
EndProc    CheckRange

BREAK <Chk_Block - See if the specified I/O violates locks>
;***    Chk_Block - check range lock on a file
;
;       Chk_Block is called to interrogate the lock status of a region of a
;       file.
;
;       NOTE:  This routine is called for every disk I/O operation and MUST BE
;              FAST.  We deviate from the coding conventions by having
;              the common case (I/O to an MFT-managed object with no locks)
;              fall through.
;
;       ENTRY   (ES:DI) points to an SFT structure
;               (CX) is the number of bytes being read or written
;               (AL) is the type of access 2 = write/exclusive 4 = read/shared
;               User_ID = 16-bit user id of issuer
;               Proc_ID = 16-bit process id of issuer
;
;       EXIT    'C' clear if no error
;               'C' set if the region is locked by a
;                   process/user other than the issuer.
;
;       USES    AX,BX,DX,ESI,FLAGS


Procedure   Chk_Block,NEAR
    ASSUME  ES:NOTHING,DS:NOTHING,SS:TASKAREA

        CpuMode 386

        mov     esi,es:[di].sf_MFT
        cmp     esi, SF_MFT_NULL        ; MFT set?
        jz      SHORT chkb9             ; no, just return (CARRY CLEAR)

        push    ds                      ; preserve ds
        FlatContext ds                  ; (ds:esi) -> MFT entry

        test    [esi].mft_lptr,-1       ; is there a lock pointer?  CLEAR CARRY
        jnz     SHORT chkb1             ; yes, go scan for conflict         |
                                        ;                                   |
        pop     ds                      ; restore saved DS                  |
        ret                             ; return no error             <-----+

        CpuMode reset

;       (ds:esi) points to mft
;       (TOS) has saved DS
;       (CX) has count of bytes
;       (AL) has access type

chkb1:  jcxz    chkb8                   ; 0 bytes... that's OK! (CARRY CLEAR)

        SaveReg <DI,CX>

        dec     cx                      ; (cx) = count-1

        mov     dx,es:[di].sf_fsi.sfi_position.HighWord
        mov     bx,es:[di].sf_fsi.sfi_position.LowWord  ; (dx:bx) = FBA offset
        xor     di,di                   ; init flags = ignore own locks
        xor     ah,ah
        or      di,ax                   ; set access type
        mov     ax,dx                   ; (ax:bx) = FBA offset
        mov     dx,cx
        sub     cx,cx
        add     dx,bx
        adc     cx,ax                   ; (cx:dx) = lba of lock area
        call    ScanLock
        mov     ax,ERROR_LOCK_VIOLATION ; assume error

        RestoreReg <CX,DI>
chkb8:  pop     ds                      ; restore save DS
    ASSUME  DS:nothing

chkb9:  ret                             ; exit

EndProc Chk_Block


;**     ScanLock - Scan for Lock Entry
;
;       ScanLock scans a lock list looking for a lock range that overlaps the
;       caller-supplied range. If we find an overlap and the caller wanted
;       us to check for proper access, We do the following:
;       If the overlapping lock is a shared lock and the requested access is
;       read/shared we do not consider this an overlap. If the overlapping
;       lock is a shared lock and the requested access is write/exclusive we
;       fail the request. If the overlapping lock is an exclusive lock we
;       always fail the request regardless of access type.
;
;       ScanLock indicates:
;               no overlap
;               partial overlay
;
;       ENTRY   (AX:BX) = FBA of area
;               (CX:DX) = LBA of area
;               (DS:ESI) -> MFT
;               (DI)    = xxxxxxx1 - Request for write/exclusive access
;                       = xxxxxx1x - Request for read/shared access
;                       = xxxxx1xx - to consider all locks
;                       = xxxx1xxx - all locks that don't match exactly
;
;       EXIT    'NC' if no overlap
;                 AX,BX,CX,DX preserved
;               'CY' if overlap
;                 ds:si -> found record
;
;       USES    AX,BX,CX,DX,DI,SI,FLAGS  (SEE EXIT)
;
;ScanLock(firstbyteaddr,lastbyteaddr,mft,own_flag,access_flag,overlap)
;
; prevlock = RLRHead
; lock = GDT_MFT:firstlockptr->rlr_next
; prevlock.rlr_next = lock
;
; while (lock != 0) {
;
;   skip = false;
;   if (own_flag == ignore_own) {
;
;     if ((GDT_RLR:lock ->rlr_PDB == CurrentPDB) &&
;        (GDT_RLR:lock ->rlr_PID == TASKAREA:CurrTCB ->TCBProc_ID) &&
;        (GDT_RLR:lock ->rlr_UID == TASKAREA:CurrTCB ->TCBUser_ID) &&
;        (TASKAREA:CurrTCB->ThisSFT == GDT_RLR:lock->rlr_sft))
;
;        skip = true;
;     }
;   }
;
;   if (skip == false) {
;
;      /* if offset of the proposed last byte is >= offset of first byte of
;      /* current lock record and offset of proposed first byte is <= offset
;      /* of last byte of current lock record then we have a overlap        */
;
;      if ((lastbyteaddr >= GDT_RLR:lock ->rlr_firstbyteaddr) &&
;         ((firstbyteaddr <= GDT_RLR:lock ->rlr_lastbyteaddr))
;
;     /* we found an overlap check for proper access     */
;
;           if ((access_flag indicates we need to check for proper access) &&
;               (GDT_RLR:lock is a shared lock) &&
;               (access_flag indicates read access to the overlapping region)
;                 skip = true;
;           }                           /* end if proper access         */
;      }                                /* end if overlap               */
;   }                                   /* end if skip == false         */
;   if (skip != true) {
;     zeroflag = false;
;     if ((GDT_RLR:lock->rlr_firstbyteaddr == firstbyteaddr) &&
;         (GDT_RLR:lock->rlr_lastbyteaddr == lastbyteaddr)
;            zeroflag = true;
;     return(ERROR_LOCK_VIOLATION,GDT_RLR:prevlock,zeroflag)
;   }                                   /* end if skip != true          */
;
;   prevlock = lock;
;   lock = GDT_RLR:prevlock->rlr_next;
; }                                     /* end while   */
; return(noerror);

Procedure   ScanLock,<NEAR,FAR16>
    ASSUME  ES:NOTHING,DS:NOTHING,SS:TASKAREA
LocalVar SLE_Access,WORD
LocalVar SLE_Lockptr,WORD
        EnterProc <di>
        push    es
        test    di,RLR_FIND_OWN
        pushf                           ; Z set to ignore own locks

        CpuMode 386
        mov     si,[esi].mft_lptr       ; gdt_rlr:si -> 1st lock record
        CpuMode reset

        RLRCONTEXT <ds>                 ; ds:si -> 1st lock record

;       check out next lock
;
;       (DS:SI) = address of next lock record
;       (TOS)   = flags (Z set to ignore own locks)
;       (TOS+1) = Saved ES

sle1:   and     si,si
        LJZ     sle9                    ; list exhaused, ergo no overlap
        popf                            ; NOTE: IFL should be set
        pushf
        jnz     sle2                    ; am to check all locks

;       am to ignore own locks... check the user and proc IDs on this one

        SaveReg <ax,bx>

        mov     ax,[si].rlr_PDB
        cmp     ax,CurrentPDB
        jnz     sle1$5

        mov     bx,CurrTCB
        mov     ax,[si].rlr_PID
        cmp     ax,ss:[bx].TCBProc_ID
        jnz     sle1$5

        mov     ax,[si].rlr_UID
        cmp     ax,ss:[bx].TCBUser_ID
        jnz     sle1$5                  ; doesn't belong to user

        les     ax,[si].rlr_sptr        ; (es:ax) = SFT address
        cmp     TASKAREA:[bx].TCBThisSFT.Offst,ax
        jnz     sle1$5
        mov     ax,es
        cmp     TASKAREA:[bx].TCBThisSFT.Segmt,ax
        jnz     sle1$5

; The lock is owned by user. If the lock is also Exclusive or the lock is
; Shared and the owner/user wants Read_Access then we can ignore it.

        test    [si].rlr_flags,RLR_SHARED
        jz      sle1$5                  ; lock is exclusive to user - ignore
        test    SLE_Access,RLR_WRITE_ACCESS
                                        ; 'ZR' set means Read Access desired
sle1$5: RestoreReg <bx,ax>
        jz      sle3                    ; own by user with proper access-ignore

sle2:   mov     di,dx
        sub     di,[si].rlr_fba.loword  ; compare proposed last to first of record
        mov     di,cx
        sbb     di,[si].rlr_fba.hiword
        jc      sle3                    ; proposed is above current
        mov     di,[si].rlr_lba.loword
        sub     di,bx                   ; compare proposed first to last of record
        mov     di,[si].rlr_lba.hiword
        sbb     di,ax
        jc      sle3                    ; no overlap

        test    [si].rlr_flags,RLR_SHARED
        jz      sle4
        test    SLE_Access,RLR_READ_ACCESS
        jz      sle4

;       this entry is harmless... chain to the next one

sle3:   mov     si,[si].rlr_next        ; ds:si -> next lock record
        jmp     sle1

;       we have an overlap.
;
;       (DS:SI) = address of lock record
;       (TOS) = flags ('Z' set if to ignore own locks)
;       (TOS+1) = saved (ES)
sle4:                                   ; b724957
        test    SLE_Access,RLR_ATO_OVER
        jz      sle5                    ; don't check if the overlap is same
        cmp     bx,[si].rlr_fba.loword
        jne     sle5
        cmp     ax,[si].rlr_fba.hiword
        jne     sle5
        cmp     dx,[si].rlr_lba.loword
        jne     sle5
        cmp     cx,[si].rlr_lba.hiword
        je      sle3

sle5:   stc                             ; flag an overlap
        mov     ax,ERROR_LOCK_VIOLATION
sle9:   pop     di                      ; discard flags
        pop     es                      ; restore (es)
        LeaveProc
        RETP
EndProc ScanLock


;**     LockAlloc - obtain free lock-record
;
;       LockAlloc returns a free lock-record, if one can be had.
;
;       ENTRY   none
;       EXIT    'C' clear if OK
;                 (DS:DI) = FBA lock record
;                 ax preserved
;               'C' set if no space
;                 (ax) = error code
;       USES    ax, SI, DI, DS, ES, FLAGS

Procedure   LockAlloc,hybrid
    assume ds:nothing,es:nothing
        RLRCONTEXT <es>
        CALLFAR Bmp_Get                 ; es:di -> free record
        jc      ofl2

        RLRCONTEXT <ds>
        mov     si,di                   ; ds:si -> free record
ofl1:   ret

ofl2:   mov     ax,ERROR_SHARING_BUFFER_EXCEEDED
        ret
EndProc LockAlloc


;**     ClrSFLock - Clear SFT Locks
;
;       clears any locks associated with this SFT.
;
;       ENTRY   (ES:DI) = SFT address
;       EXIT    none
;       USES    ax,ebx,cx,dx,di,ds,es,flags

Procedure   ClrSFLock,NEAR
LocalVar    cl_spSFT,DWORD

        EnterProc <es, di>

        SaveReg <esi, bp>
        mov     esi,es:[di].sf_MFT
        FlatContext ds                  ; ds:esi -> MFT entry for this SFT
        mov     si,ds:[esi].mft_lptr    ; GDT_RLR:si -> lock record
        RLRCONTEXT ds                   ; ds:si -> lock record

; ds:bx points to the previous lock record of ds:si (in other words,
; ds:[bx].rlr_next = si) so that we can remove si record from the
; chain by setting ds:[bx].rlr_next to ds:[si].rlr_next. A special case is
; for the first lock record. The previous record for the first record is the
; mft_lptr field in the MFT segment. We set bx to 0 to indicate this.

        xor     bx,bx                   ; ds:bx -> special previous record
        mov     cx,es                   ; free up es:di for later use
        mov     dx,di

;       scan the locks looking for belongers.
;
;       (CX:DX) = SFT address
;       (DS:SI) = current lock record to examine
;       (DS:BX) = previous lock record

csl10:  or      si,si                   ; end of record list?
        jz      csl40                   ; yes, done
        cmp     dx,[si].rlr_sptr.Offst
        jne     csl20                   ; not my lock
        cmp     cx,[si].rlr_sptr.Segmt
        jne     csl20                   ; not my lock
;
; Make sure that the lock REALLY belongs to the correct process
;
        mov     bp,CurrTCB
        mov     ax,[bp].TCBProc_ID
        cmp     ax,[si].rlr_PID         ; is process ID of lock = this PID?
        jne     csl20                   ; nope, skip this lock
        mov     ax,[bp].TCBUser_ID
        cmp     ax,[si].rlr_UID         ; is user ID of lock = this UID?
        jne     csl20                   ; nope, skip this lock
        mov     ax,CurrentPDB
        cmp     ax,0                    ; if currentpdb = 0 then the   ;b731827
        je      csl12                   ; whole VDM process is         ;b731827
                                        ; terminating so clear the lock;b731827
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;        cmp     ax,[si].rlr_PDB         ; is PDB of lock = this PDB?
;;        jne     csl20                   ; nope, skip this lock
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; defect 72433 - scenario, dos app open files and spawns child passing
;;                file handle on cmd line. Child locks range in file and
;;                exits without closing file (or removing locks). We take
;;                a panic in RMN (remove MFT name record) because mft_lptr
;;                is not clear.
;;
;;                fix, if the PDB in the lock record isn't the current PDB
;;                and it also is no longer a valid selector, assume that
;;                the owning app is no longer around... free lock.
        cmp     ax,[si].rlr_PDB         ;; is PDB of lock = this PDB?
        je      csl12                   ;;  yes - unlock it

        push    ax
        mov     ax,[si].rlr_PDB         ;; selector
        lsl     ax,ax                   ;; load segment limit
        pop     ax
        jz      csl20                   ;; if pdb valid, leave lock alone
;;end 72433;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
csl12:                                                                 ;b731827
; got a lock to remove

        mov     di,si                   ; ds:di -> record to remove
        mov     si,[di].rlr_next        ; ds:si -> next record

        or      bx,bx                   ; is previous record in MFT?
        jz      csl17                   ; yes, go fix pointer in MFT

        mov     [bx],si                 ; link it out

csl15:  SaveReg <ax,bx>
        mov     ax,ds
        mov     bx,di
        CALLFAR ProcRun                 ; Wake threads blocked on this lock
        RestoreReg <bx,ax>

; GDT_RLR:di -> record to free
; ds:si -> next record
; ds:bx -> previous record

        RLRCONTEXT <es>                 ; es:di -> record to free
        CALLFAR Bmp_Free                ; return free space to Bmp
        JMP     SHORT csl10             ; go check next lock

; remove first lock record
; ds:si -> next record
; ds:di -> record to remove

csl17:  mov     es,cx
        mov     bx,dx                   ; es:bx -> sft
        mov     ebx,es:[bx].sf_MFT
        FlatContext <es>                ; es:ebx -> MFT entry
        mov     es:[ebx].mft_lptr,si    ; link head node to next record
        xor     bx,bx                   ; bx -> special record
        jmp     short csl15

; lock is not ours, goto next one

csl20:  mov     bx,si                   ; ds:bx -> current record
        mov     si,[si].rlr_next        ; ds:si -> next record
;b731827JMP     SHORT csl10
        JMP     csl10

;       Now wake up threads with pending locks

csl40:  RestoreReg <bp>
        les     di,cl_spSFT             ; es:di -> SFT
        mov     ax,1                    ; wake everybody
        call    Wake_plock

        RestoreReg <esi>
        LeaveProc
        ret

EndProc ClrSFLock


HIGH2CODE ENDS

        FARCODE INIT
INITCODE segment

;**     RLRInit - allocate and initialize RLR
;
;       Allocate and initialize the RLR segment and a temporary headnode.
;
;       ENTRY   none
;
;       EXIT    if 'NC' - success
;               if 'CY' - error
;                 ax = error code
;
;       USES    ax,bx,cx,dx,si,di,ds,es,flags

Procedure RLRInit,near
    assume ds:nothing,es:nothing

; allocate RLR segment

        mov     bx,Bmp_SegSize
        xor     ax,ax                   ; ax:bx = size
        .errnz  HF_MOVABLE
        xor     si,si
        mov     cx,(D_DATA0 shl 8) + SA_GDT + SA_ALLOCATED + SA_SPECIFIC
        mov     di,FSRLROWNER           ; (DI) = owner
        push    GSEL GDT_RLR            ; Allocate mem for this selector
        CALLFAR SegAlloc                ; AX = handle, BX = selector
        pop     ds                      ; (DS) = GDT_RLR
        jc      rli10                   ; failure, error exit

        mov     cx,size RLR_Entry       ; size of each block
        mov     dx,RLR_INCR             ; number of blocks to grow
        CALLFAR Bmp_Init                ; no error possible

; allocate a temporary headnode for used by clr_block.

        CALLFAR LockAlloc               ; ds:di -> free lock for use as head
        DosContext <ds>
        mov     [RLRHead],di            ; save for later use
    assume ds:nothing

rli10:  ret

EndProc RLRInit

INITCODE ends
        END
