;       SCCSID = @(#)volsubr.asm        6.9 92/03/05
SCCSID  equ     "@(#)volsubr.asm        6.9 92/03/05"

page    ,132
TITLE   VOLSUBR - volume ID management subroutines
NAME    VOLSUBR

;***    volume ID management for MSDOS
;
;
;       Global          Local
;
;       IncVPBRef
;       DecVPBRef
;       PathIncVPBRef
;       PathDecVPBRef
;       IncVPBSearch
;       DecVPBSearch
;       VPBAllocCopy
;       VPBFree
;       VPBLink
;       VPBFind
;       WaitVPB
;       WaitDPB
;       LockVBuf
;       WakeVPB
;       WakeDPB
;       UnlockVBuf
;       ToggleRequest
;                       Hysteresis
;       UnreadableVPB
;       CRC
;       DeleteVID
;       UpdateVID
;       WPTLockBuf
;       WPTUnlockBuf
;       fVolumeWriteable
;       ResetCurrency
;       chkvselhdprc
;
;                         Modifications
;-----------------------------------------------------------------------
;  Date    Name      Description
;----------------------------------------------------------------------
; 04/07/95 ChangeTeam Put FSSTRICT around internal error in 'Devvpbsearchcount'
;                     - needed for debugging, but should not IPE in the real
;                     world.  Tag : D117603
; 11/27/95 MLM        Changed VPBFind and ToggleRequest to hybrid procedure
;                     moved from PTDA to TCB: PathNameType
;
;-----------------------------------------------------------------------
; 140726  19-Oct-95   scott garfinkle     Serialize access to VPBs.
;-----------------------------------------------------------------------
; 150371  01-Apr-96   DR Brown            System Anchor Block support.
; ======================================================================
.xlist
.xcref
include dosseg.inc
include basemaca.inc
include osmaca.inc
include fnofs.inc
include defdbug.inc
include dossymc.inc
include buffer.inc
include curdir.inc
include dirent.inc
include dpb.inc
include devsym.inc
include error.inc
include filesys.inc
include harderr.inc
include infoseg.inc
include protmode.inc
include sft.inc
include tcbdata.inc
include mi.inc
include mult.inc
.cref
.list
include vpb.inc
include fsc.inc

        ReferGDT        GDT_VPB,WORD    ; VPB BMP Selector

        ReferGlobal     hVPBFirst,WORD  ; handle for first VPB in link
;       ReferGlobal     pVPBBuf,DWORD   ; sector buffer
        ReferGlobal     pVPBSem,BYTE    ; semaphore for buffer
        ReferFlat       ksemVPB,BYTE    ; sem for vpb list 140726
        ReferGlobal     SISData,BYTE
        ReferGlobal     lbl,BYTE
        ReferGlobal     lbl2,BYTE

                                        ;72417 removed PTDA entries now
                                        ;      in TCB

        ReferTask       CurrTCB,WORD    ; Current task control block
        ReferTask       ExtErr,WORD
;### PTR B720732        ReferTask       ThisCDS,DWORD

        extrn           lblLen:ABS
        extrn           lbl2Len:ABS


        EXTRN32         _KSEMRequestMutex                       ;140726
        EXTRN32         _KSEMReleaseMutex                       ;140726
        EXTRN           BufInvalidate:NEAR
        EXTRN           RlseBuffr:NEAR
        EXTRN           RemoveBufVPB:NEAR
        EXTRN           DoVolIO:NEAR
        EXTRN           NextCDS:NEAR
        EXTRN           PointComp:NEAR

        EXTRN           WhatVolumePerc:NEAR
        EXTRN           FSDMount:NEAR

        EXTRNFAR        Bmp_Get
        EXTRNFAR        Bmp_Free
        EXTRNFAR        ProcBlock
        EXTRNFAR        ProcRun
        EXTRNFAR        HardError
        EXTRNFAR        UnHogEAFile

        ModName , FNO_VOLSUBR_ASM

DATA segment

        public  _pbufWPT, pbufWPT       ; far ptr to buffer for Write Protect Test
_pbufWPT        label   DWORD           ; D150371
pbufWPT     dd  ?

        public  _semWPT, semWPT         ; semaphore for exclusion of pbufWPT
_semWPT         label   BYTE            ; D150371
semWPT      db  ?

AutoChkMask db  0FEh

DATA ends


          FARCODE HIGH2
HIGH2CODE SEGMENT

Break   <IncVPBRef - indicate a new referent to a VPB>

;
;   IncVPBRef is used whenever we store a new pointer to a VPB into a system
;   object.
;
;   Inputs:     BP is handle to VPB
;   Outputs:    vpb_ref_count is incremented in the VPB
;   Registers modified: FLAGS
;
        ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA

Procedure IncVPBRef,HYBRID
;       PMONLY
        DBPRT   80h,40h,<IncVPBRef(hVPB=$x>,<bp>
        PUSH    ES
        VPBContext ES,BP
        DBPRT   80h,40h,<ref=$x srch=$x)>,<ES:vpb_ref_count,ES:vpb_search_count>
        INC     ES:[bp].vpb_ref_count
        JZ      incie
        pop     es
        ret
incie:  InternalError   <IncVPBRef: ref count overflowed>
EndProc   IncVPBRef

BREAK   <DecVPBRef - signal that a referent to a VPB has been removed>

;   DecVPBRef decrements the reference count to a VPB and frees the VPB if
;   the ref count goes to 0.
;
;   ENTRY    (BP) = handle to VPB
;
;   EXIT     vpb_ref_count decremented.
;            zero flag is set if ref count = 0
;
;   USES     FLAGS
;unsigned int hVPB;
;{
;        struct vpb *vpb_ptr;
;
;        VPBContext (&vpb_ptr,hVPB);
;        if ((--(vpb_ptr -> vpb_ref_count)) == 0) {
;
;          if (vpb_ptr -> vpb_FSC.Offst < fsc_reserved)
;            FSDMount (UNMOUNT);
;
;          VPBFree(hVPB)
;        }
;}
;
        ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA

Procedure   DecVPBRef,HYBRID
;       PMONLY
        DBPRT   80h,40h,<DecVPBRef(hVPB=$x>,<bp>
        pusha
        push    ds
        push    es
        mov     ax,1
        jmp short dec00

Entry   DecVPBRefInv     ; unmount volume if only searches left on it
        pusha
        push    ds
        push    es
        xor     ax,ax

dec00:  VPBContext ES,BP
        DBPRT   80h,40h,<ref=$x srch=$x)>,<es:vpb_ref_count,es:vpb_search_count>
        dec     es:[bp].vpb_ref_count   ; one less reference
        jz      dec0                    ; free
;
; DecVPBRefInv entry point

        or      ax,ax                   ; check ref count?
        jnz     dec2                    ; no, go exit (NZ)
;
; we unmount the volume if the only referents are searches.  we leave
; the vpb around marked invalid so that future searches on it
;
        mov     bx,es:[bp].vpb_ref_count
        sub     bx,es:[bp].vpb_search_count
ifdef FSSTRICT
        jnz     decie                   ; not free, go return
else
        jnz     dec2
endif
        or      es:[bp].vpb_flags,VPBINVALID ; mark vpb invalid
;
; The ref count is now decremented to zero => this was the last referent
; we need to unlink the segment from the chain.
;
dec0:   cmp     es:[bp].vpb_FSC.Offst,FSC_LOCAL ; what file system?
        je      dec3                            ; FAT, go do FAT stuff
        ja      dec1                            ; invalid FSC, skip unmount

        test    es:[bp].vpb_fMisc,VPB_REMOTE_DRIVE
        jnz     dec1                            ; remote, skip unmount
        mov     bx,FSM_UNMOUNT                  ; Unmount flag
        call    FSDMount                        ; unmount volume

dec1:   VPBContext ES,BP
        cmp     es:[bp].vpb_ref_count,0         ; ref count zero?
        jne     dec2                            ; no, don't free vpb
        xor     bl,bl                           ; Unlink and Free VPB
        call    VPBFree                         ; ZF set

dec2:   pop     es
        pop     ds
        popa
        ret

; before freeing a FAT vpb, remove EA file from sharer

dec3:   mov     bx,bp                           ; bx = hVPB
        CALLFAR UnHogEAFile
        jmp     short dec1

ifdef FSSTRICT
decie:  InternalError <DecVPBRefInv: VPB not freed>
endif

EndProc   DecVPBRef,NoCheck

Break   <PathIncVPBRef - indicate a new referent to a VPB for PathName call>

;
;   PathIncVPBRef is used insure the VPB stays around until the PathName call
;   completes. We call IncVPBRef only if the path is for a local file.
;
;   Inputs:     PathNameType
;               TCBThishVPB is current
;
;   Outputs:    If path is a local file vpb_ref_count is incremented in the VPB
;
;   Registers modified: NONE
;
        ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA

Procedure PathIncVPBRef,HYBRID
;       PMONLY

        pushf
                                                ;72417 moved from PTDA to TCB
        push    bp
        mov     bp,CurrTCB
        test    [bp].TCBPathNameType,PNType_LOCFILE OR PNType_REMFILE OR PNType_UNC
        jz      pinc1                           ; No VPB. Return.
        mov     bp,TASKAREA:[bp].TCBThishVPB    ; BP = hVPB
        call    IncVPBRef
pinc1:  pop     bp
        popf
                                                ;72417 end
        ret

EndProc   PathIncVPBRef

BREAK   <PathDecVPBRef - signal that a referent to a VPB has been removed>

;   DecVPBRef calls DecVPBRef if the path is for a Local file.
;
;   Inputs:     PathNameType
;               TCBThishVPB is current
;
;   Outputs:    If path is a local file vpb_ref_count is decremented in the VPB
;
;   Registers modified: NONE
;
        ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA

Procedure PathDecVPBRef,HYBRID
;       PMONLY

        pushf
                                                ;72417 moved from PTDA to TCB
        push    bp
        mov     bp,CurrTCB
        test    [bp].TCBPathNameType,PNType_LOCFILE OR PNType_REMFILE OR PNType_UNC
        jz      pdec1                           ; No VPB. Return.
        mov     bp,TASKAREA:[bp].TCBThishVPB    ; BP = hVPB
        call    DecVPBRef
pdec1:  pop     bp
        popf
                                                ;72417 end
        ret

EndProc   PathDecVPBRef

;***    IncVPBSearch - Increment search count in VPB
;
;       Entry:  (BP) = handle to VPB
;
;       Exit:   Search count incremented.
;
;       Uses:   Flags.

        ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA

Procedure IncVPBSearch,NEAR
        DBPRT    80h,40h,<IncVPBSearch($x>,<bp>
;       PMONLY

        SaveReg <ES>
        VPBContext ES,BP                ; (ES:BP) = address of VPB
        DBPRT   80h,40h,<ref=$x srch=$x)>,<es:vpb_ref_count,es:vpb_search_count>
        INC     ES:[bp].vpb_search_count        ; Increment search count
        JZ      ivsie                   ; Search count is not valid
        RestoreReg  <ES>
        ret                             ; Return to caller
ivsie:  InternalError   <IncVPBSearch: search count overflowed>
EndProc  IncVPBSearch


;***    DecVPBSearch - Decrement search count in VPB
;
;       Entry:  (BP) = handle to VPB
;
;       Exit:   Search count decremented.
;
;       Uses:   Flags.
;
        ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA

Procedure DecVPBSearch,HYBRID
;       PMONLY
        DBPRT   80h,40h,<DecVPBSearch(hVPB=$x>,<bp>

        SaveReg <ES>
        VPBContext ES,BP                ; (ES:BP) = address of VPB
        DBPRT   80h,40h,<ref=$x srch=$x)>,<es:vpb_ref_count,es:vpb_search_count>
        CMP     ES:[bp].vpb_search_count,0

ifdef FFSTRICT                      ; D117603 - only error if debug kernel
        JE  dvsie                   ; Search count is 0
else                                ; D117603
        JE  @f                      ; D117603 - else continue
endif                               ; D117603

        DEC     ES:[bp].vpb_search_count; Decrement search count
@@:                                 ; D117603 If no debug - do not IPE
        RestoreReg  <ES>
        ret                              ; Return to caller

dvsie:  InternalError   <DecVPBSearch: search count was zero>

EndProc   DecVPBSearch

BREAK   <VPBAllocCopy - Allocate a VPB and Copy VPB From Stack Into That Area.

;   VPBAllocCopy allocates storage for VPB from the BMP segment and copies the
;   VPB on the Stack into the new storage area. We also stamp the VPB as Valid.
;
;   ENTRY    SI = offset of LocalVPB on Stack
;
;   EXIT     carry clear = no error
;            AX = handle to volume
;            carry set = error
;            AX = error code
;
;   USES     AX,CX,DI,ES
;
;        struct vpb *vpb_ptr;
;        unsigned short LocalVPBPtr;
;        char * far Sourceptr;
;        char * far Destptr;
;
;        TaskContext(&Sourceptr,LocalVPBPtr);
;        BMP_Get(GDT_VPB,&Destptr);
;        MemCopy (Sourceptr,Destptr);
;
;}
        ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA

Procedure   VPBAllocCopy,NEAR
;       PMONLY
        DBPRT   80h,40h,<VPBAllocCopy(hVPB=$x>,<bp>

        SaveReg <si,ds>

        VPBContext ES,,NOCHECK
        CALLFAR Bmp_Get                 ; es:di -> new block (VPB)
        jc      vpba1                   ; fail on no memory
        DBPRT   80h,40h,<new($x)>,<ax>

;       (ES:DI) -> global VPB for copy

        mov     ax,di                   ; Set up return value
        TaskContext ds
        mov     cx,size vpb
    rep movsb                           ; vpb is copied
        mov     di,ax
        mov     es:[di].vpb_signature,VPB_VALID ; Stamp VPB as valid
vpba1:  RestoreReg <ds,si>
        ret
EndProc VPBAllocCopy

BREAK   <VPBFree - Unlink a VPB and Free the block via BMP.

;   VPBFree removes the VPB from the link list and Frees its block from the
;   segment.
;
;   ENTRY    (BP) = handle to VPB
;            (BX) if 0, unlink VPB
;                 if !0, don't unlink VPB
;
;   EXIT     VPB unlinked and Freed
;
;   USES     bx,bp,cx,ds,es
;
;unsigned int hVPB;
;{
;        unsigned int flink,blink;
;        struct vpb *vpb_ptr;
;
;        VPBContext (&vpb_ptr,hVPB);
;
;        if (vpbfreeflag == FreeOnly) {
;           BMPFree (*vpb_ptr);
;           hVPB = 0;
;           return (hVPB);
;        }
;
;        flink = vpb_ptr -> vpb_flink
;        blink = vpb_ptr -> vpb_blink
;
;        if (blink != 0) {
;           VPBContext (&vpb_ptr,blink);
;           vpb_ptr -> vpb_flink = flink;
;        }
;        else
;           hVPBFirst = flink;
;
;        if (flink != 0) {
;           VPBContext (&vpb_ptr,flink);
;           vpb_ptr -> vpb_ptr -> blink = blink
;        }
;
;        VPBContext (&vpb_ptr,hVPB);
;        vpb_ptr -> vpb_signature = VPB_INVALID;
;        BMPFree (*vpb_ptr);
;}
         ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA

Procedure   VPBFree,HYBRID
;       PMONLY
        DBPRT   80h,40h,<VPBFree(hVPB=$x>,<bp>

        ; 140726 from the comments, it *shouldn't* be necessary to preserve
        ; es,ds but we'll do it anyway to prevent possible regressions....
        SaveReg <ax,bx,cx,dx,di,ds,es>                          ;140726
        FlatContext <ds,es>                                     ;140726
        CCALL32 _KSEMRequestMutex,<<offset FLAT:ksemVPB>,-1>    ;140726
        VPBContext ES,BP
;140726  SaveReg <ax,di>
        or      bl,bl                   ; Unlink and Free VPB ?
        jnz     nonxt                   ; No, Only Free VPB
        mov     cx,es:[bp].vpb_flink    ; forward link segment
        mov     bx,es:[bp].vpb_blink    ; backward link segment

        or      bx,bx                   ; if no back link then
        jz      decsh                   ; go and set the initial pointer
        mov     es:[bx].vpb_flink,cx    ; link forward past VPB we're deleteing
        jmp     short decnh

decsh:  DosContext ds
        mov     hVPBFirst,cx            ; since no prev => set first pointer

decnh:  jcxz    nonxt                   ; no, free VPB
        xchg    cx,bx
        mov     es:[bx].vpb_blink,cx    ; link back past VPB we're deleteing

nonxt:  mov     di,bp
        DBPRT   80h,40h,<free($x)>,<di>

        SaveReg <ds,si>

        cmp     es:[di].vpb_fsi.vpi_pDPB.Segmt,0
                                        ; if the dpb point is null, continue
                                        ;  invalidating the vpb
        jz      decinv
                                        ;### 88189
                                        ; Mark the DPB hVPB pointer as
                                        ; null if it is still pointing
                                        ; to the freed hVPB.  This will
                                        ; prevent WhatVolume from accessing
                                        ; a freed hVPB.
        lds     si, es:[di].vpb_fsi.vpi_pDPB
                                        ; Put the pointer to the DPB in
                                        ;  ds:si

        cmp     ds:[si].dpb_hVPB,bp     ;if the dpb doesn't point to the
                                        ; vpb we are freeing
        jnz     decinv                  ;go continue freeing it.
        mov     ds:[si].dpb_hVPB,NULLVPB
                                        ;otherwise mark the DPB as
                                        ; pointing to a NULLVPB
                                        ;End 88189

decinv: RestoreReg <si,ds>
        mov     es:[di].vpb_signature, VPB_INVALID ; Invalidate VPB
        CALLFAR Bmp_Free                ; Return VPB to pool
        xor     bp,bp                   ; Invalidate BP
;140726  RestoreReg <di,ax>
        FlatContext <ds,es>                                     ;140726
        CCALL32 _KSEMReleaseMutex,<offset FLAT:ksemVPB>         ;140726
        RestoreReg <es,ds,di,dx,cx,bx,ax>                       ;140726
        ret
EndProc VPBFree

BREAK   <VPBLink - Link new VPB into list.

;   VPBLink inserts the new VPB at the beginning of the list and adjusts
;   the forward and backlink fields of new VPB and the old first VPB.
;
;   ENTRY    ES:DI = New VPB
;
;   EXIT     VPB Linked into list.
;
;   USES     DS,SI
;
;unsigned int hVPB;
;{
;        struct vpb *vpb_ptr;
;
;        VPBContext (&vpb_ptr,hVPB);
;        vpb_ptr -> vpb_flink = hVPBFirst;
;        vpb_ptr -> vpb_blink = 0;
;
;        if (hVPBFirst != 0) {
;          VPBContext (&vpb_ptr,hVPBFirst);
;          vpb_ptr -> vpb_blink = hVPB;
;        }
;
;        hVPBFirst = hVPB;
;
;}
        ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA

Procedure   VPBLink,NEAR
;       PMONLY
        DBPRT   80h,40h,<VPBLink(hVPB=$x>,<bp>

        ; 140726 from the comments, it *shouldn't* be necessary to preserve
        ; ds but we'll do it anyway to prevent possible regressions....
        SaveReg <ax,bx,cx,dx,es>                                ;140726
        FlatContext <ds,es>                                     ;140726
        CCALL32 _KSEMRequestMutex,<<offset FLAT:ksemVPB>,-1>    ;140726
        RestoreReg <es>                                         ;140726
        DosContext ds                                           ;140726
        mov     si,[hVPBFirst]
        mov     es:[di].vpb_flink,si               ; link to chain
        mov     es:[di].vpb_blink,0                ; no back link
        or      si,si                              ; is chain empty?
        jz      vpbl0                              ; yes, no head to link back
        mov     es:[si].vpb_blink, di              ; back link to new VPB
vpbl0:  mov     [hVPBFirst],di                     ; chain now set up correctly
        SaveReg <ds,es>                                         ;140726
        FlatContext <ds,es>                                     ;140726
        CCALL32 _KSEMReleaseMutex,<offset FLAT:ksemVPB>         ;140726
        RestoreReg <es,ds,dx,cx,bx,ax>                          ;140726

        ret
EndProc VPBLink

BREAK   <VPBFind - Find a VPB with the same Vol. ID in the internal list.

;   VPBFind scans the internal list looking for a VPB with the same Vol. ID
;   as the input VPB.
;
;   ENTRY    DS:SI = Pointer to input VPB Vol. ID
;
;   EXIT     AX = hVPB if found
;            AX = 0 if not found
;
;   USES     AX,BX,CX,DI,ES
;
;char *VolIDS;
;{
;        struct vpb *vpb_ptr;
;        unsigned int hVPB;
;        char *VolIDD;
;
;        hVPB = hVPBFirst;
;
;        while (hVPB != NULLVPB) {
;
;          VPBContext(&vpb_ptr,hVPB);
;          VolIDD = &(vpb_ptr -> vpb_fsi.vpi_ID);
;
;          if ((strcmp(VolIDS,VolIDD) == 0)
;            Return(hVPB);
;
;          hVPB = vpb_ptr -> vpb_flink
;        }
;        Return(NULLVPB)
;}
        ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA

Procedure   VPBFind,hybrid              ; 72417 changed from near to hybrid
;       PMONLY
        DBPRT   80h,40h,<VPBFind(hVPB=$x>,<bp>

        DosContext es
        mov     bx,[hVPBFirst]          ; bx => VPB to examine
    ASSUME      es:NOTHING
vpbf0:  or      bx,bx                   ; are we at the end of the list?
        jz      vpbf20                  ; yes, go return
        VPBContext ES,BX                ; (es:bx) -> VPB we wish to examine
;
; NOTE that we are comparing a slightly different area in the VPB than what is
; done in OS/2 version 1.0. See VPB.INC for more details.
;
        lea     di,es:[bx].vpb_fsi.vpi_ID

;
; Make sure that the VPBs that we are comparing are not the same ones.
; Since this routine gets called for FSH_FindDuphVPB, the FSD may be passing
; one of our own VPBs in to look for DUPs.
;
        mov     ax,ds
        cmp     ax,GSEL GDT_VPB
        jne     vpbf3                   ; Selectors are not the same. Continue.
        cmp     di,si
        je      vpbf5                   ; VPBs are the same. Skip this one.

vpbf3:  mov     cx,VPBIDLEN
        push    si
        repz cmpsb                      ; are two IDs the same?
        pop     si
        jnz     vpbf5                   ; no, link to next VPB
        test    es:[bx].vpb_flags,VPBINVALID ; valid vpb?
        jz      vpbf20                  ; yes, BX is the requested handle

vpbf5:  mov     bx,es:[bx].vpb_flink    ; no, link to next VPB
        jmp     short vpbf0

vpbf20: mov     ax,bx
        ret
EndProc VPBFind

BREAK   <WaitVPB, WaitDPB, LockVBuf - wait until resources are unlocked>

;
; In the course of doing I/O, we need to wait for both the VPB associated with
; a request and the DPB associated with the VPB to become free.  We examine
; a locked flag and proc-block until the lock gets released.  If we blocked
; then we return an indicator that we may have to reexamine all other pending
; conditions.  During the course of verifying a volume, we use an internal
; static buffer; this must be semaphored too.
;

;
;   WaitVPB - wait until a flag is removed from a specified
;   VPB
;
;   Inputs:     ax  contains handle to VPB
;               bl  contains the flag bit to test in vpb_flags
;   Outputs:    carry set => vpb is now free but we blocked
;               carry clear => vpb is free and no block took place
;               es:bp contains valid pointer to VPB
;   Registers modified: bh bp
;
        ASSUME  DS:NOTHING,ES:NOTHING,SS:NOTHING

Procedure WaitVPB,hybrid
;       PMONLY
        push    dx                      ; preserve dx
        xor     dl,dl                   ; initialize blocked flag

        mov     bp,ax
        VPBContext ES,BP

;       (ax)    handle to VPB
;       (es:bp) point to VPB
;       (bl)    flag to test
;       (dl)    have blocked flag

wtv1:   test    es:[bp].vpb_flags,bl    ; is bit free?
        jz      wtv2                    ; yes, go return

        pusha
        xor     bh,bh                   ; hVPB:flag == (ax:bx) is procblock ID
        mov     dh,1                    ; blocked uninterruptibly
        mov     cx,-1                   ; no timeout
        mov     di,cx
        CALLFAR ProcBlock               ; wait
        popa

        mov     dl,2                    ; indicate a block took place
        jmp     wtv1

wtv2:   cmp     dl,1                    ; carry set if flag < 1
        cmc                             ; carry set if flag >=1
        pop     dx
        ret
EndProc  WaitVPB

;
;   WaitDPB - wait until a check-in-progress flag is removed from a specified
;   DPB
;
;   Inputs:     ds:si point to DPB
;   Outputs:    carry set => DPB is now free but we blocked
;               carry clear => DPB is free and no block took place
;   Registers modified: none
;
        ASSUME  DS:NOTHING,ES:NOTHING,SS:NOTHING

Procedure WaitDPB,NEAR
;       PMONLY
        push    dx
        xor     dl,dl                   ; initialize have blocked flag
;
; Top level.  We come here after ProcBlock and retest the DPB flag
;
; (ds:si)  DPB pointer
; (dl)     have blocked flag
;
wtd1:   test    [si].dpb_flags,DPBCHECK
        jz      wtd2                    ; jump if not locked

        pusha
        mov     ax,ds                   ; We block on DS:SI
        mov     bx,si
        mov     di,-1
        mov     cx,di                   ; no time out
        mov     dh,1                    ; don't allow me to be interrupted
        CALLFAR ProcBlock               ; and wait
        popa

        mov     dl,2                    ; signal that we blocked
        jmp     wtd1                    ; to back to retest the DPB
;
; The DPB is now free
;
wtd2:   cmp     dl,1                    ; carry set if flag < 1
        cmc                             ; carry set if flag >=1
        pop     dx
        ret
EndProc  WaitDPB

;
;   LockVBuf - wait for and lock the internal buffer
;
;   Inputs:     none
;   Outputs:    none
;   Registers modified: none
;
        ASSUME  DS:NOTHING,ES:NOTHING,SS:NOTHING

Procedure LockVBuf,NEAR
;       PMONLY
        push    ds
        push    cx
        DosContext ds

lbf1:   mov     cx,00FFh
        xchg    cl,pVPBSem              ; test and set
        jcxz    lbf2                    ; it is now ours
        pusha
        mov     ax,ds
        mov     bx,offset DOSGROUP:pVPBSem
        mov     di,-1
        mov     cx,di
        mov     dh,1
        CALLFAR ProcBlock
        popa
        jmp     lbf1

lbf2:   pop     cx
        pop     ds
    ASSUME      DS:NOTHING
        ret
EndProc   LockVBuf

BREAK   <WakeVPB, WakeDPB, UnlockVBuf - signal that an object is free>

;
;   WakeVPB signal that a VPB is now available for use; unmark the VPB also
;
;   Inputs:     ax contains VPB
;               bl contains flag to wake up
;   Outputs:    none
;   Registers modified: none
;
        ASSUME  DS:NOTHING,ES:NOTHING,SS:NOTHING

Procedure WakeVPB,hybrid
;       PMONLY
        pusha
        mov     bp,ax
        VPBContext ES,BP                ; es:bp ->VPB
        xor     bh,bh
        not     bx
        and     es:[bp].vpb_flags,bl    ; Unlock VPB
        not     bx
        CALLFAR ProcRun
        popa
        ret
EndProc   WakeVPB

;
;   WakeDPB signal that a DPB is now available for use
;
;   Inputs:     ds:si point to DPB
;   Outputs:    none
;   Registers modified: none
;
        ASSUME  DS:NOTHING,ES:NOTHING,SS:NOTHING

Procedure WakeDPB,NEAR
;       PMONLY
        SaveReg <ax,bx,cx,dx>
        mov     ax,ds
        mov     bx,si
        CALLFAR ProcRun
        RestoreReg  <dx,cx,bx,ax>
        ret
EndProc   WakeDPB

;
;   UnlockVBuf - free the internal sector buffer
;
;   Inputs:     none
;   Outputs:    none
;   Registers modified: none
;
        ASSUME  DS:NOTHING,ES:NOTHING,SS:NOTHING

Procedure UnlockVBuf,NEAR
;       PMONLY
        pusha
        push    ds
        DosContext ds
        mov     pVPBSem,0
        mov     ax,ds
        mov     bx,offset DOSGROUP:pVPBSem
        CALLFAR ProcRun
        pop     ds
    ASSUME  DS:NOTHING
        popa
        ret
EndProc   UnlockVBuf

BREAK   <ToggleRequest - request a change of disk from the user>

;
;       ToggleRequest - inform the user that the disk we require is not in
;       the specified drive and that he should place it there.  In order
;       to prevent thrashing on a disk, we have do
;       not request a change of disk unless > 10 seconds have passed since
;       last change occurred on that drive.
;
;       Inputs:         ax      is the hVPB we want
;                       es:bp   points to the VPB
;                       ds:si   point to dpb
;
;       Outputs:        carry set if error
;                       carry clear if no errors
;                           al has user-specified action from hard error
;                               1 => retry
;                               2 => abort
;                               3 => fail
;
;       Registers modified: AX, BX, CX, DX, SI, DI, DS, ES, Flags
;
        ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA

Procedure ToggleRequest,hybrid              ; 72417 changed from near to hybrid
;       PMONLY
        DBPRT   80h,40h,<ToggleRequest($x,$x,$x:$x>,<ax,es,ds,si>

        push    bp
;
; As in DoVolIO, we loop about until a known situation clears.  This situation
; is amazingly rare, so we will not waste a lot of time making it efficient.
; We will loop about only if we encounter two toggle requests within a very
; small period of time
;

trq1:   call    Hysteresis              ; (DS:SI) points to DPB

;
; Since we MIGHT have blocked above, there is a chance that someone else
; got to the DPB before us.  If so, we just block and then wait (potentially)
; some more.
;
        call    WaitDPB                 ; wait for DPB to become free
        jc      trq1                    ; we blocked, wait for free.
        mov     bl,VPBCHECK
        call    WaitVPB
        jc      trq1                    ; the VPB was locked.  Go back for more
;
; The DPB is ours.  Lock it down and signal the user.  One of the possible
; responses from the user is an indication that the volume may be on a different
; drive.
;
        chkvselhd es,bp
        or      [si].dpb_flags,DPBCHECK ; lock the DPB
        mov     di,bp                   ; (ES:DI) = VPB pointer

;*  Release disk buffer, if owned, to avoid deadlock between thread
;   owning buffer and hard error daemon (which may want same buffer).

        SaveReg <di,ds,es>
        mov     bp,CurrTCB
        test    [bp].TCBForce_Flg,FF_BUF    ; buffer assigned?
        jz      trq20                       ; no, just return

        lds     di,TASKAREA:[BP].TCBCurBuf  ; (ds:si) -> current buffer
    assume  ds:nothing
        test    [di].buf_flags,BUF_BUSY
        jz      trq20

        test    [di].buf_flags,BUF_DIRTY    ; dirty buffer?
        jnz     trq10                       ;   YES, just release it

;*  For a clean (i.e., not dirty) buffer, we do not know if it has valid
;   data, since we may have been trying to read into the buffer.  GetBuffr
;   fills in the header PRIOR to doing the read, so the header doesn't
;   tell us anything about the validity of the data.  In cases where we
;   are holding a buffer, but NOT reading into it, doing the invalidation
;   will degrade system performance (since the buffer IS valid).  However,
;   harderrors do not happen often enough for this to be of concern to us.

        call    RemoveBufVPB

trq10:  call    RlseBuffr
        or      TASKAREA:[BP].TCBBufHE,BufHE_CurBufFreed

trq20:  RestoreReg <es,ds,di>

        mov     ax,HESwapdisk           ; indicate swap-type of error
        mov     ExtErr,ERROR_WRONG_DISK
;
; call hard error handler to prompt user
;
        mov     bp,CurrTCB
        mov     dl,[bp].TCBAllowed      ; (DL) = old allowed flags
        mov     [bp].TCBAllowed,ALLOWED_FAIL + ALLOWED_RETRY
        CALLFAR HardError               ; let the user see it
                                        ; (ax) = user hard error response
        mov     [bp].TCBAllowed,dl      ; restore original allowed flags

        and     [si].dpb_flags,NOT DPBCHECK ; wake up everyone on old drive
        call    WakeDPB                 ; let everyone see the free dpb
        pop     bp
        DBPRT   80h,40h,<)>
        ret
EndProc   ToggleRequest

BREAK   <Hysteresis - wait for a while>

;
;       Hysteresis prevents thrashing on disk-change. If there are two processes
;       contending for two different volumes in a drive, we will prevent
;       signalling disk changes in a specified minimum (currently fixed at
;       10 seconds).  This is implemented by keeping a time-of-last-change
;       in the DPB and preventing a change until the delta is > 10 seconds
;
;       In DOS 5.0 we have added the ability to have system calls return
;       a failure code to the user rather than signal the hard error daemon.
;       It is useless to have any sort of delay if we are returning errors.
;       Therefore we will check at the begining of Hysteresis. If we are
;       returning errors, we will not delay.
;
;       Inputs:     ds:si point to DPB
;       Outputs:    none
;       Registers modified: none
;
        ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA

DELAY_TIME      EQU     10              ; Total time to delay in seconds
DELAY_INCR      EQU     1000            ; Delay time increment in ms

Procedure   Hysteresis,NEAR,LOCAL
;       PMONLY
;
; If we are returning failures then we do not want to wait.
;
        SaveReg <bp>
        mov     bp, CurrTCB
        test    [bp].TCBJfnFlag,JFN_FAIL
        RestoreReg <bp>
        jz      hy1                     ; Jump if JFN_FAIL bit is clear
        ret

hy1:    SaveReg <ax,bx,cx,dx>
;
; We sleep in DELAY_INCR increments until the read time is > DELAY_TIME after
; toggled time.
;

hyTop:  SaveReg <ds>
        DosContext <ds>
        cli
        mov     ax,SISData.SIS_BigTime.HighWord         ; Grab the time from
        mov     bx,SISData.SIS_BigTime.LowWord          ; the InfoSeg
        sti
        RestoreReg <ds>
    ASSUME  DS:NOTHING
;
; Check the time against the toggle time.
; We assume that the delay time is < 65536 seconds.
;
        cmp     ax,[si].dpb_toggle_time.HighWord
        jnz     hydone                                  ; Jump if the high
                                                        ; word has changed
        sub     bx,[si].dpb_toggle_time.LowWord
        cmp     bx,DELAY_TIME
        jae     hydone                                  ; Jump if the low word
                                                        ; has changed by
                                                        ; >= DELAY_TIME
;
; The time indicates that it is < DELAY_TIME since the last toggle request
; for this drive.  We need to sleep for DELAY_INCR and try again.
;
        mov     ax, 0FFFFH
        mov     bx, ax                  ; ax:bx = Event ID
        SaveReg <di>
        xor     di, di
        mov     cx, DELAY_INCR          ; di:cx = Timeout in ms
        mov     dh, 1                   ; Not interruptable
        CALLFAR ProcBlock
        RestoreReg <di>
        jmp     SHORT hyTop
;
; When we get here, it is known that >= DELAY_TIME has passed.
; Stuff the new toggle time and return.
;

hydone: mov     [si].dpb_toggle_time.LowWord,bx
        mov     [si].dpb_toggle_time.HighWord,ax
        RestoreReg <dx,cx,bx,ax>
        ret
EndProc   Hysteresis


;***    UnreadableVPB - Check to see if this was a VPB of an unreadable volume
;
;       UnreadableVPB checks to see if this VPB refers to a previously
;       unreadable volume (i.e. unformatted medium).
;
;       ENTRY   AX  Handle of VPB
;
;       EXIT    'Z' if it was an unreadable volume
;               'NZ' if readable
;
;       USES    Flags
;
        ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA

Procedure UnreadableVPB,HYBRID
        SaveReg <DS,ES,CX,SI,DI>
        mov     si,ax
        VPBContext es,si                ; (ES:SI) = address of VPB
        cmp     es:[si].vpb_fsi.vpi_ID.LowWord,UNREAD_ID1
        jnz     uv5
        cmp     es:[si].vpb_fsi.vpi_ID.HighWord,UNREAD_ID2
        jnz     uv5
        DosContext ds
        lea     di,es:[si].vpb_fsi.vpi_text     ; (ES:DI) -> VPB text
        mov     si,offset DOSGROUP:lbl2 ; (DS:SI) -> "UNREADABLE",0
        mov     cx,lbl2Len
        repz    cmpsb                   ; See if the two IDs are the same
uv5:    RestoreReg <DI,SI,CX,ES,DS>
        ret
EndProc   UnreadableVPB

BREAK   <CRC - checksum a sector in some meaningful way>

;
;   CRC - compute a 32 bit checksum of a sector
;
;   Inputs:     ds:si point to sector
;               cx is length in bytes of sector
;   Outputs:    dx:ax contain checksum
;
        ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA

Procedure CRC,<hybrid,far16>
;       PMONLY
        cld
        push    bx
        xor     ax,ax
        mov     bx,ax
        mov     dx,ax                   ; dx:bx is intermediate result
        shr     cx,1                    ; convert bytes insector to words
crc1:   lodsw                           ; get next word of sector
        adc     bx,ax                   ; add it in to sum and propogate
                                        ; last carry
        adc     dx,0                    ; propogate carry to high word
        rcr     bx,1                    ; move bits around
        rcr     dx,1
        loop    crc1
        mov     ax,bx
        pop     bx
        ret
EndProc   CRC

Break   <Write Protect Test routines>

;***    WPTLockBuf - Gain exclusive access to Write Protect Test buffer
;
;   ENTRY   none
;
;   EXIT    semWPT = -1
;
;   USES    flags
;
;   EFFECTS Switches context

Procedure WPTLockBuf,NEAR
    assume  ds:nothing,es:nothing,ss:TASKAREA

        SaveReg <cx,ds>
        DosContext <ds>

wlb10:  mov     cx,00FFh
        xchg    cl,semWPT               ; test and set
        jcxz    wlb20                   ; it is now ours

        pusha
        mov     ax,ds
        mov     bx,offset DOSGROUP:semWPT ; (ax:bx) = block id
        mov     di,-1                   ; (di:cx) = no timeout
        mov     cx,di
        mov     dh,1                    ; (dh) = NOT interruptible
        CALLFAR ProcBlock
        popa
        jmp     wlb10                   ; Try again

wlb20:  RestoreReg <ds,cx>
    assume  ds:nothing
        ret
EndProc   WPTLockBuf


;***    WPTUnlockBuf - Release exclusive access to Write Protect Test buffer
;
;   ENTRY   semWPT = -1
;
;   EXIT    semWPT = 0
;           Any waiters woken up
;
;   USES    flags
;
;   EFFECTS Switches context

Procedure WPTUnlockBuf,NEAR
    assume  ds:nothing,es:nothing ,ss:TASKAREA

        pusha
        SaveReg <ds>
        DosContext <ds>
        mov     semWPT,0
        mov     ax,ds
        mov     bx,offset DOSGROUP:semWPT
        CALLFAR ProcRun
        RestoreReg <ds>
    assume  ds:nothing
        popa
        ret
EndProc   WPTUnlockBuf


;***    fVolumeWriteable - determine if volume is writeable
;
;       Many of the File System functions cause a disk buffer to be dirtied.
;       We want to know, with a high probability, that a dirty buffer CAN be
;       committed to the volume BEFORE we dirty the buffer.  If we wait until
;       the buffer is ALREADY dirty, then we get into the rats nest of trying
;       to prevent the user from losing data while at the same time avoiding
;       the appearance of "phantom" files.  All File System functions call us
;       at as high a level as possible if they are going to write to the disk.
;       These include DOS_Create, DOS_Delete, DOS_MkDir, DOS_RmDir, DOS_Rename,
;       DOS_Write, and DOS_Commit.
;
;       Our approach is to maintain a bit in the VPB that indicates whether if
;       the volume is writeable.  If the bit is set, we return quickly with
;       success.  If the bit is clear, we go play with the volume to see if it
;       is writeable.  Whenever we get a media uncertain error from a disk
;       device driver, we reset the writeable bit.  This allows ensures that
;       we catch a user playing with the write-protect status of his removable
;       media.  [See GenhVPB below.]
;
;       Note that we could make the optimization of not testing the volume
;       every time if 1) we have already checked once -and- 2) the drive has
;       not reported media uncertain.  The problem with this is: a) the user
;       doesn't get write-protect pop-ups any more -and- 2) it takes more code.
;
;       Since trying to modify a write-protected volume is a bit obnoxious of
;       the user, we will make it painful for the user.  Note that this is also
;       compatible with DOS.
;
;       ENTRY   TASKAREA:[CurrTCB].ThishVPB = volume to check
;               No buffer held!
;
;       EXIT    Carry Clear, volume is writeable
;               Carry Set, volume is NOT writeable
;                   AX = ERROR_WRITE_PROTECT
;                        ERROR_NOT_READY
;                        other error codes
;               If a buffer was held on entry, it MAY have been released.
;
;       USES    AX,flags
;
;       PSEUDO CODE
;
;       pVPB = TASKAREA:[CurrTCB].TCBThishVPB;
;       IF (we know volume is writeable) THEN
;           RETURN success;
;       ELSE
;           IncVPBRef;  /* Don't let VPB evaporate */
;           Lock WPT buffer;
;           Save HardError settings;
;           Turn OFF HardError percolation;
;           Set HardError allowed responses to ABORT+FAIL+RETRY;
;           Read boot sector into WPT buffer;
;           IF (read fails) THEN
;               status = fail;
;           ELSE
;               Write boot sector from WPT buffer;
;               IF (write fails) THEN
;                   status = fail;
;               ELSE
;                   mark VPB as writeable;
;                   status = success;
;               ENDIF
;           ENDIF
;           Restore HardError settings;
;           Unlock WPT buffer;
;           DecVPBRef;  /* Release VPB count */
;           RETURN status;
;       ENDIF

Procedure   fVolumeWriteable,hybrid
    assume  ds:nothing,es:nothing,ss:TASKAREA

        SaveReg <bp,es>
        mov     bp,CurrTCB
        mov     bp,TASKAREA:[bp].TCBThishVPB
        VPBContext <es>,bp              ; (es:bp) = VPB
        test    es:[bp].vpb_fMisc,VPB_FM_WRITEABLE ; Is volume writeable?
        jz      fvw00                   ;   NO, skip
        jmp     fvw40                   ;   YES, return success (CLC)

fvw00:  call    IncVPBRef               ; Don't let VPB evaporate
        call    WPTLockBuf
        SaveReg <bx,cx,dx,di,si,ds>
        mov     di,CurrTCB
        mov     ah,TASKAREA:[di].TCBAllowed ; (ah) = HE allowed responses
        mov     al,TASKAREA:[di].TCBBufHE  ; (al) = HE handling bits
        push    ax                      ; Save HE settings
        mov     TASKAREA:[di].TCBAllowed,ALLOWED_ABORT + ALLOWED_FAIL + ALLOWED_RETRY
        and     TASKAREA:[di].TCBBufHE,not BufHE_Percolate ; Don't percolate

;*  Read boot sector
;
;   (bp) = hVPB

        mov     ax,CMDINPUT             ; (ax) = do read
        DosContext <ds>
        lds     bx,pbufWPT              ; (ds:bx) -> WPT buffer
    assume  ds:nothing
        mov     cx,1                    ; (cx) = one sector
        xor     dx,dx
        xor     di,di                   ; (di:dx) = boot sector
        xor     si,si                   ; si = advisory bits
        call    DoVolIO     ; DoVolIO(ax=op ds:bx=buf cx=cnt di:dx=sec bp=hVPB)
    assume  ds:nothing,es:nothing
        jc      fvw10                   ;   FAIL, go return error

;*  Try writing boot sector
;
;   (bp) = hVPB     (from DoVolIO)

        mov     ax,CMDOUTPUT            ; (ax) = do write
        DosContext <ds>
        lds     bx,pbufWPT              ; (ds:bx) -> WPT buffer
    assume  ds:nothing
        mov     cx,1                    ; (cx) = one sector
        xor     dx,dx
        xor     di,di                   ; (di:dx) = boot sector
        xor     si,si                   ; si = advisory bits
        call    DoVolIO     ; DoVolIO(ax=op ds:bx=buf cx=cnt di:dx=sec bp=hVPB)
    assume  ds:nothing,es:nothing
        jnc     fvw20                   ;   SUCCESS, go mark VPB and exit

;*  FAILed.  Get error code and exit.
;
; (bp) = hVPB    (from DoVolIO)

fvw10:  mov     di,CurrTCB
        mov     ax,TASKAREA:[di].TCBFailErr ; (ax) = error code
        jmp     short fvw30             ; Clean up and exit

;*  Writing is allowed
;
; (es:bp) -> VPB    (from DoVolIO)

fvw20:  or      es:[bp].vpb_fMisc,VPB_FM_WRITEABLE  ; Remember writability
                                        ; Carry cleared

;*  Restore TCBbufHE and release references
;
; (bp) = hVPB

fvw30:  pop     bx                      ; (bx) = saved HE settings
        mov     di,CurrTCB
        mov     TASKAREA:[di].TCBAllowed,bh ; Restore HE allowed responses
        mov     TASKAREA:[di].TCBBufHE,bl ; Restore HE handling bits
        RestoreReg  <ds,si,di,dx,cx,bx>
        pushf
        call    WPTUnlockBuf            ; Release WPT buffer
        call    DecVPBRef               ; Done with VPB
        popf

fvw40:  RestoreReg  <es,bp>
        ret

EndProc     fVolumeWriteable

;*** FatDirtyBitToggle - Toggle dirty bit on a fat volume
;
;       ENTRY   si = hVPB volume to check
;               Al = Value to set fat dirty bit to (01h for dirty, 0 for not)
;               No buffer held!
;
;       EXIT    Carry Clear, volume is writeable
;               Carry Set, volume is NOT writeable
;                   AX = ERROR_WRITE_PROTECT
;                        ERROR_NOT_READY
;                        other error codes
;               If a buffer was held on entry, it MAY have been released.
;
;       USES    AX,flags
;
;       PSEUDO CODE
;
;           IncVPBRef;  /* Don't let VPB evaporate */
;           Lock WPT buffer;
;           Save HardError settings;
;           Turn OFF HardError percolation;
;           Set HardError allowed responses to ABORT+FAIL+RETRY;
;           Read boot sector into WPT buffer;
;           IF (read fails) THEN
;               status = fail;
;           ELSE
;               Write boot sector from WPT buffer;
;               IF (write fails) THEN
;                   status = fail;
;               ELSE
;                   mark VPB as writeable;
;                   status = success;
;               ENDIF
;           ENDIF
;           Restore HardError settings;
;           Unlock WPT buffer;
;           DecVPBRef;  /* Release VPB count */
;           RETURN status;
;       ENDIF

Procedure   FatDirtyBitToggle,<hybrid,far16>
    assume  ds:nothing,es:nothing,ss:TASKAREA

        SaveReg <bp,es,bx>
        mov     bp,si
        VPBContext es,bp                        ; es:bp-VPB
        xor     bx,bx
        mov     bx,es:[bp].vpb_fsd.vpdFAT_eaflags ; Clean volume          ;### AC DCR 1520
        cmp     al,bh
        jne     fdbt0
        jmp     fdbt40

;88189 set the flag in the vbp prior to the io so no one else will try
;      to do it twice
fdbt0:  SaveReg <cx,dx,di,si,ds>
        mov     dx,es:[bp].vpb_fsd.vpdFAT_eaflags ; Clean volume         ;### AC DCR 1520
        mov     dh,al
        mov     es:[bp].vpb_fsd.vpdFAT_eaflags,dx ; Clean volume         ;### AC DCR 1520
        call    IncVPBRef               ; Don't let VPB evaporate
        call    WPTLockBuf
        mov     dx,ax                   ; Grab action
        mov     di,CurrTCB
        mov     ah,TASKAREA:[di].TCBAllowed ; (ah) = HE allowed responses
        mov     al,TASKAREA:[di].TCBBufHE  ; (al) = HE handling bits
        push    ax                      ; Save HE settings
        mov     TASKAREA:[di].TCBAllowed,ALLOWED_ABORT + ALLOWED_FAIL + ALLOWED_RETRY
        and     TASKAREA:[di].TCBBufHE,not BufHE_Percolate ; Don't percolate

;*  Read boot sector
;
;   (bp) = hVPB

        mov     ax,CMDINPUT             ; (ax) = do read
        DosContext <ds>
        push    dx                      ; Save action
        lds     bx,pbufWPT              ; (ds:bx) -> WPT buffer
    assume  ds:nothing
        mov     cx,1                    ; (cx) = one sector
        xor     dx,dx
        xor     di,di                   ; (di:dx) = boot sector
        xor     si,si                   ; si = advisory bits
        call    DoVolIO     ; DoVolIO(ax=op ds:bx=buf cx=cnt di:dx=sec bp=hVPB)
        pop     dx                      ; Restore action
    assume  ds:nothing,es:nothing
        jc      fdbt10                   ;   FAIL, go return error

;*  Try writing boot sector
;
;   (bp) = hVPB     (from DoVolIO)

        DosContext <ds>
        lds     bx,pbufWPT              ; (ds:bx) -> WPT buffer
        mov     al,ds:[bx+37]           ;
        test    al,01h
        jz      fdbt1
        mov     al, 0feh
        and     ds:[bx+37],al
        jmp     short fdbt2
fdbt1:  or      ds:[bx+37],dl
fdbt2:  mov     ax,CMDOUTPUT            ; (ax) = do write
        push    dx
    assume  ds:nothing
        mov     cx,1                    ; (cx) = one sector
        xor     dx,dx
        xor     di,di                   ; (di:dx) = boot sector
        xor     si,si                   ; si = advisory bits
        call    DoVolIO     ; DoVolIO(ax=op ds:bx=buf cx=cnt di:dx=sec bp=hVPB)
        pop     dx          ; Pop off the action that was originally in ax
    assume  ds:nothing,es:nothing
        jnc     fdbt30                   ; SUCCESS, clean up and exit

;*  FAILed.  Get error code and exit.
;
; (bp) = hVPB    (from DoVolIO)

fdbt10:
        xor     dl,01                    ; 88189:Toggle the flag they requested,
                                         ;  we need to reset the flag in the
                                         ;  vpb since the io failed.
        xor     ax,es:[bp].vpb_fsd.vpdFAT_eaflags ; Clean volume          ;### AC DCR 1520
        mov     ah,dl
        mov     es:[bp].vpb_fsd.vpdFAT_eaflags,ax ; Clean volume          ;### AC DCR 1520
        mov     di,CurrTCB
        mov     ax,TASKAREA:[di].TCBFailErr ; (ax) = error code


;*  Restore TCBbufHE and release references
;
; (bp) = hVPB

fdbt30: pop     bx                      ; (bx) = saved HE settings
        mov     di,CurrTCB
        mov     TASKAREA:[di].TCBAllowed,bh ; Restore HE allowed responses
        mov     TASKAREA:[di].TCBBufHE,bl ; Restore HE handling bits
        RestoreReg  <ds,si,di,dx,cx>
        pushf
        call    WPTUnlockBuf            ; Release WPT buffer
        call    DecVPBRef               ; Done with VPB
        popf
        clc
fdbt40: RestoreReg  <bx,es,bp>
        ret

EndProc     FatDirtyBitToggle

BREAK   <ResetCurrency - adjust all CDS currency information>

;
;   ResetCurrency is called whenever we notice that the volume in a particular
;   drive changes.  This means that all recency/currency information is no
;   longer valid.  This currency information is:
;
;   cluster of the current directory
;   clean buffers in cache
;
;   Inputs:     ds:si point to dpb that was just changed
;               ax is vpb that was previously in the drive
;   Outputs:    none
;   Registers modified: none
;
        ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA

Procedure ResetCurrency,NEAR
;       PMONLY
        DBPRT   80h,40h,<ResetCurrency called with ax = $x\n>,<ax>
        SaveReg <ds,es,di,ax>
;
; Remove any CDS currency information
;
        xor     ax,ax
        mov     di,ax                   ; first time indicator for NextCDS
        dec     ax                      ; (AX) = -1

rc1:    call    NextCDS
        jc      rc2                     ; all done
        test    es:[di].cd_flags,CD_ISNET
        jnz     rc1                     ; Leave NET guys alone!!
        push    es
        push    di
        les     di,es:[di].cd_devptr
        call    PointComp
        pop     di
        pop     es
        jnz     rc1                                 ; CDS not for this drive
        or      es:[di].cd_fsi.cdi_flags,CDI_MEDIASWAPPED   ; mark !current
        jmp     rc1

rc2:    mov     di,TASKAREA:CurrTCB             ; (DI) = current TCB     ;### PTR B720732

        cmp     TASKAREA:[di].TCBThisCDS.Segmt, 0   ;PCR CP20 B733576
        je      rc3                                 ;PCR CP20 B733576

        les     di,TASKAREA:[di].TCBThisCDS     ; (ES:DI) = CDS pointer  ;### PTR B720732
;### PTR B720732 rc2:    les     di,ss:ThisCDS                       ; mark [ThisCDS] !current
        cmp     di,-1                               ; ThisCDS = -1 if invalid
        jz      rc3                                 ; ThisCDS is not valid
        or      es:[di].cd_fsi.cdi_flags,CDI_MEDIASWAPPED   ; mark !current

;*  Invalidate all clean buffers for this volume.
;   Purge any path cache entries that may exist.

rc3:    pop     ax                      ; (AX) = VPB to invalidate
        push    ax
        call    BufInvalidate           ; Free all clean buffers
        RestoreReg  <ax,di,es,ds>
        ret
EndProc   ResetCurrency


IFDEF VOLPTRCHK
;***    Check that a VPB pointer is reasonable.
;
;       Check that ES:BP points to a VPB.  ie Signature is correct, within
;       the BMP segment, etc.  Doesn't prove that the VPB is valid, or the
;       correct one, just that it is a VPB.
;
;       ENTRY   ES:BP -> VPB
;
;       EXIT    NONE
;
;       USES    NONE
;
;       NOTE    Will InternalError if VPB isn't reasonable.
;
;       WARNING We use "popf" rather than "popf", do not call this procedure
;               with interrupts off.
;
        ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA

Procedure chkvselhdprc,FAR
;       PMONLY
        pushf
        or      bp,bp
        jz      chkvselhdc

        SaveReg <ds,ax>

        DosContext <ds>

        mov     ax, es
        cmp     ax, GSEL GDT_VPB
        jne     chkvselhda

        cmp     es:[bp].vpb_signature,VPB_VALID
        jne     chkvselhdb

;       If we get here, things are cool

        RestoreReg <ax, ds>
        popf
        ret

chkvselhda:
        InternalError <Bad VPB selector in ES>,<chkvselhdprc_a>
chkvselhdb:
        InternalError <Bad VPB signature in BP>,<chkvselhdprc_b>
chkvselhdc:
        InternalError <BP is NullVPB>,<chkvselhdprc_c>

EndProc  chkvselhdprc

ENDIF

HIGH2CODE ENDS


          FARCODE HIGH4
HIGH4CODE SEGMENT

BREAK   <DeleteVID - change text of VPB to "UNLABELED">

;***    DeleteVID - change text of VPB to "UNLABELED"
;
;       The volume label has been deleted.
;
;       ENTRY:  (BP) = handle to VPB
;
;       ENTRY:  VPB is adjusted appropriately
;
;       USES:   AX, CX, SI, DI, ES, Flags
;
        ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA

Procedure DeleteVID,NEAR
;       PMONLY
        DosContext ds
        mov     si,offset DOSGROUP:lbl
        VPBContext es,bp                ; (ES:BP) = pointer to VPB
        lea     di,[bp].vpb_fsi.vpi_text
        mov     cx,lblLen
        rep movsb                       ; move in new name
        ret
EndProc   DeleteVID


BREAK   <UpdateVID - change text of VPB>

;***    UpdateVID - change text of VPB
;
;       The volume label has been changed or created.  The
;       following strings are used now for vpb_fsi.vpi_text:
;
;               UNREADABLE
;               UNLABELED
;               Volume ID
;
;       ENTRY:  (BP) = handle to VPB
;               (DS:SI) = pointer to new volume label
;
;       ENTRY:  VPB is adjusted appropriately
;
;       USES:   AX, CX, SI, DI, ES, Flags
;

Procedure UpdateVID,NEAR
;       PMONLY
        VPBContext es,bp                ; (ES:BP) = pointer to VPB
        lea     di,[bp].vpb_fsi.vpi_text
        mov     cx,11
        rep movsb                       ; move in new name
        xor     al,al
        stosb                           ; nul terminate
        ret
EndProc   UpdateVID

Break <Find_VPB - Find a VPB from a drive number>

;***    Find_VPB - Find a VPB that corresponds to a drive number
;
;   ENTRY   AL has drive number A = 0
;
;   EXIT    Carry Set
;               No VPB for this drive number
;           Carry Clear
;               ES:DI points to VPB for drive
;
;   USES    ES,DI
;
Procedure Find_VPB,<near,faronly,far16>
    ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA

        DosContext es
        mov     di,[hVPBFirst]          ; di => VPB to examine
    ASSUME      es:NOTHING
fvpb0:  or      di,di                   ; are we at the end of the list?
        stc
        jz      fvpbx                   ; yes, go return
        VPBContext ES,DI                ; (es:di) -> VPB we wish to examine
        cmp     es:[di].vpb_fsi.vpi_drive,al ; same unit ?
        je      fvpbx                   ; yes, go return carry clear
        mov     di,es:[di].vpb_flink    ; no, link to next VPB
        jmp     short fvpb0
fvpbx:  RET
EndProc Find_VPB

HIGH4CODE ENDS
        END
