;       SCCSID = @(#)vollock.asm	6.1 90/11/17
SCCSID	equ	"@(#)vollock.asm	13.9 90/09/17"
;***	VOLLOCK.asm - Volume Locking/Unlocking routines
;
; file/dir operations volume access serialization
;
; In order to prevent races conditions between certain operations, i.e. rmdir
; and create, a form of sharing will be added to directories.  It was
; decided that this sharing should occur at the FAT-FS level, DOS_xxxxx.
; The method is to allow one move dir XOR one rmdir XOR any number of the
; set: chdir, mkdir, file move, create, on a particular volume at one time.
; If access to the volume is denied, the process sleeps until the volume is
; free.  A word in the VPB keeps track of whether a move dir/rmdir is
; occurring or how many other operations are in process.  Since open, close,
; and delete are not involved in race conditions with directories, no volume
; ref count is maintained for them.
;
;	Volume_Access_Lock
;	Volume_Access_Unlock
;	Volume_Alloc_Lock
;	Volume_Alloc_UnLock
;
include basemaca.inc
include osmaca.inc
include fnofs.inc
include dosseg.inc
include mi.inc
include protmode.inc
include tcbdata.inc
include vpb.inc

	ModName , FNO_VOLLOCK_ASM

	ReferGDT	GDT_VPB,WORD

	ReferTask	CurrTCB,WORD

	EXTRNFAR	ProcBlock
	EXTRNFAR	ProcRun

	FARCODE HIGH2
HIGH2CODE SEGMENT

;***	Volume_Access_Lock - get permission to do operation to a volume
;
;	This routine allows either: one rmdir XOR one move dir XOR any of the
;	set: chdir, mkdir, move file, create to a volume.  The serialization
;	of rmdir/move dir eliminates race conditions such as rmdir/create.
;	A rmdir or move dir causes the high bit of the vpb_access to be set.
;	A chdir, mkdir, move file, or create causes a reference count in the lowest
;	15 bits to be incremented.  Open, close and delete are not involved
;	in any race conditions and thus do not need to be included.
;	Up to 32,767 concurrent chdirs, file moves, and creates are allowed.
;	This number will never be reached due to other system limitations.
;
;	ENTRY	TCBThishVPB set
;		CX = VPB_ACC_EXCL if rmdir or move dir
;		   = VPB_ACC_SHARE if chdir, file move, or create
;
;	EXIT	If rmdir or move dir, all other file/dir operations are
;		locked out of access to the volume.
;
;		If chdir, file move, or create, rmdir and move dir operations
;		are not allowed on the volume.
;
;	USES	AX, BX, ES, flags
;
;	PSEUDOCODE
;	while (TRUE) {
;	   if (no one has access) {
;	      give caller access
;	      return
;	   }
;	   if (someone has shared access)
;	      if (desired access is shared) {
;		 give caller shared access
;		 return
;	      }
;	   set wait bit
;	   set up block key
;	   block
;	}
;
;	EFFECTS Switches context

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

	mov	bx,CurrTCB
	mov	bx,TASKAREA:[bx].TCBThishVPB ;
	VPBContext es,bx	; (ES:BX) -> VPB
val1:	test	es:[bx].vpb_fsd.vpdFAT_access,-1 ; does anyone have access?
	jnz	val2		; yes, go see if desired access is shared
	add	es:[bx].vpb_fsd.vpdFAT_access,cx ; give caller access
	ret
;
; these equates are hardwired into the code and CANNOT change.  vpb_access
; is either 8000 (one process has exclusive access), 0 (no one has access),
; or 1-(8000-1) (any number of processes have shared access.  the code takes
; advantage of this knowledge.
;
.ERRNZ VPB_ACC_EXCL-8000h
.ERRNZ VPB_ACC_SHARE-1

val2:	js	val3		; someone has exclusive access, go block
	or	cx,cx		; is desired access shared?
	js	val3		; no, go block
	add	es:[bx].vpb_fsd.vpdFAT_access,cx ; give caller shared access
	js	valie		; shared access overflowed into exclusive bit
	ret

val3:	or	es:[bx].vpb_fsd.vpdFAT_accwait,cx ; who's waiting
	pusha
	mov	ax,cx		; exclusive/shared access, (AX:BX) = block key
	mov	cx,-1		; timeout = -1 => forever
	mov	di,cx		;(di:cx = timeout)
	mov	dh,1		; non-interruptable
	CALLFAR ProcBlock
	popa
	jmp short val1		; try again

valie:	InternalError <Volume access overflow>
EndProc Volume_Access_Lock

;***	Volume_Access_Unlock - release volume
;
;	This routine clears access to a volume and allows other processes
;	waiting for the volume to run.  Processes waiting for exclusive
;	access, VPB_ACC_EXCL, are allowed to run first to prevent starvation.
;
;	Entry	TCBThishVPB set
;		CX = VPB_ACC_EXCL if rmdir or move dir
;		   = VPB_ACC_SHARE if chdir, file move, or create
;
;	EXIT	none
;
;	USES	ES
;
;	PSEUDOCODE
;	clear access
;	if (no one has access)
;	   if (exclusive access op waiting) {
;	      clear exclusive access wait
;	      ProcRun(exclusive key)
;	   }
;	   else if (shared access op waiting) {
;	      clear shared access wait
;	      ProcRun(shared key)
;	   }
;	 return
;

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

	SaveReg <ax,bx>
	pushf
	mov	bx,CurrTCB
	mov	bx,TASKAREA:[bx].TCBThishVPB ;
	VPBContext es,bx	; (ES:BX) -> VPB
	sub	es:[bx].vpb_fsd.vpdFAT_access,cx   ; clear access
	jc	vauie		; bad lock word, go internal error
	jnz	vau2		; vpb is not free, go return

; vpb is free, wake up any waiting processes

	mov	ax,VPB_ACC_EXCL ; let exclusive run first, (AX:BX) = block key
	test	es:[bx].vpb_fsd.vpdFAT_accwait,-1 ; anyone waiting?
	js	vau1		; exclusive waiting, go run it
	mov	ax,VPB_ACC_SHARE  ; let shared run
	jz	vau2		; no one waiting, go return
vau1:	xor	es:[bx].vpb_fsd.vpdFAT_accwait,ax ; clear access
	CALLFAR ProcRun
vau2:	popf
	RestoreReg <bx,ax>
	ret

vauie:	InternalError <Volume_Access_Unlock - invalid unlock on volume >

EndProc Volume_Access_Unlock
; Volume Alloc locks are used whenever a disk file allocation/release
; cluster operation is done. An alloc process takes a share lock and
; a release process takes an exclusive lock. The release process should
; take a exclusive lock, release clusters, and commit the FAT/DIR entry
; to disk and then only release the lock; An alloc process can take
; a share lock just for the duration of an Allocate call. 
; Share Lock is taken for DOSWRITE, & NewSize operations
; Exclusive lock is taken by Newsize (or doswrite with 0 bytes) in certain
;			cases, DOSDELETE,DOSRMDIR , Replace open of a file
; 		(and also DOSMKDIR/CREATE file to create a new entry in a 
;		   directory -
;		 We only need a share lock for new entry but we take an
;		exclusive lock to avoid race between two creates in the
;		same subdirectory, when it is being grown.
;		 This is done just to simplify code and since these operations
;		 are not frequently done).	

;***	Volume_Alloc_Lock - get permission to do alloc/release cluster
;		in the volume
;
;	ENTRY	TCBThishVPB set
;		CX = VPB_ALLOC_EXCL  iff shrinking file
;		   = VPB_ALLOC_SHARE iff growing file 
;
;	EXIT	caller has exclusive or shared access to volume
;
;	USES	ES, flags
;
;	PSEUDOCODE
;	while (TRUE) {
;	   if (no one has access) {
;	      give caller access
;	      return
;	   }
;	   if (someone has shared access)
;	      if (desired access is shared) {
;		 give caller shared access
;		 return
;	      }
;	   set wait bit
;	   set up block key
;	   block
;	}
;
;	EFFECTS	Switches context

Procedure Volume_Alloc_Lock,hybrid
    ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA

	pusha
	mov	bx,CurrTCB
	mov	bx,TASKAREA:[bx].TCBThishVPB ;
	or	bx,bx		; null handle (device)???
	jz	vall20		; yes, go return
	VPBContext es,bx	; (ES:BX) -> VPB
vall10:	test	es:[bx].vpb_fsd.vpdFAT_alloc,-1 ; does anyone have access?
	jnz	vall30		; yes, go see if desired access is shared
	add	es:[bx].vpb_fsd.vpdFAT_alloc,cx ; give caller access
vall20: popa
	ret
;
; these equates are hardwired into the code and CANNOT change.	vpb_Alloc
; is either 8000 (one process has exclusive access), 0 (no one has access),
; or 1-(8000-1) (any number of processes have shared access.  the code takes
; advantage of this knowledge.
;
.ERRNZ VPB_ALLOC_EXCL-8000h
.ERRNZ VPB_ALLOC_SHARE-1

vall30:	js	vall40		; someone has exclusive access, go block
	or	cx,cx		; is desired access shared?
	js	vall40		; no, go block
	add	es:[bx].vpb_fsd.vpdFAT_alloc,cx ; give caller shared access
	js	vallie		; shared access overflowed into exclusive bit
	jmp	short vall20	;

vall40:	mov	ax,VPB_ALLOC_SHARE_KEY
			; exclusive/shared access, (AX:BX) = block key
	push	di
	push	cx
	or	cx,cx		;
	mov	cx,VPB_FM_ALLOCSHWAIT	;
	jns	vall50
	mov	cx,VPB_FM_ALLOCEXWAIT
	mov	ax,VPB_ALLOC_EXCL_KEY
vall50:	or	es:[bx].vpb_fMisc,cl	;
	push	dx
	mov	di,0ffffh
	mov	cx,di		; timeout = -1 = forever
	mov	dh,1		; non-interruptable
	CALLFAR ProcBlock
	pop	dx
	pop	cx
	pop	di
	jmp short vall10		; try again

vallie:	InternalError <Volume access overflow>
EndProc Volume_Alloc_Lock

;***	Volume_Alloc_Unlock - release volume
;
;	This routine clears access to a volume and allows other processes
;	waiting for the volume to run.  Processes waiting for exclusive
;	access, VPB_ACC_EXCL, are allowed to run first to prevent starvation.
;
;	Entry	TCBThishVPB set
;		CX = VPB_ACC_EXCL
;		   = VPB_ACC_SHARE
;
;	EXIT	none
;
;	USES	none.
;
;	PSEUDOCODE
;	clear alloc access
;	if (no one has alloc lock )
;	   if (exclusive alloc  op waiting) {
;	      clear exclusive alloc  wait
;	      ProcRun(exclusive key)
;	   }
;	   else if (shared alloc op waiting) {
;	      clear shared alloc wait
;	      ProcRun(shared key)
;	   }
;	 return
;

Procedure Volume_Alloc_Unlock,hybrid
    ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA

	pushf
	pusha
	push	es
	mov	bx,CurrTCB
	mov	bx,TASKAREA:[bx].TCBThishVPB ;
	or	bx,bx		; null handle (device)??
	jz	vaul2		; yes, go return
	VPBContext es,bx	; (ES:BX) -> VPB
	sub	es:[bx].vpb_fsd.vpdFAT_alloc,cx	; clear access
	jc	vaulie		; bad lock word, go internal error
	jnz	vaul2		; vpb is not free, go return

; vpb is free, wake up any waiting processes

	mov	ax,VPB_ALLOC_EXCL_KEY
				; let exclusive run first, (AX:BX) = block key
	mov	cl,es:[bx].vpb_fMisc	;
	and	es:[bx].vpb_fMisc,NOT VPB_FM_ALLOCEXWAIT 
	test	cl,VPB_FM_ALLOCEXWAIT	; anyone waiting?
	jnz	vaul1		;  exclusive waiting, go run it
	test	cl,VPB_FM_ALLOCSHWAIT	;
	jz	vaul2		; no one waiting, go return
	mov	ax,VPB_ALLOC_SHARE_KEY ; let shared run
	and	es:[bx].vpb_fMisc,NOT VPB_FM_ALLOCSHWAIT
vaul1:	CALLFAR ProcRun
vaul2:	pop	es
	popa
	popf
	ret

vaulie:	InternalError <Volume_Alloc_Unlock - invalid unlock on volume >

EndProc Volume_Alloc_Unlock

HIGH2CODE ENDS
    END
