SCCSID	equ	"@(#)oplock.asm	6.4 92/03/15"
;***	oplock.asm - kernel support for opportunistic locks
;
;  The	correct	 functioning  of local	filesystems sharing  a machine with  a
;  server requires op-lock support.
;
;  In the network environment, certain key performance	enhancements rely on a
;  client opening a file in  an exclusive mode.  (Read-ahead  and write-behind
;  are the most dramatic winners.)
;
;  Most	 clients,  however, don't open files in exclusive mode, but  rather in
;  some shared mode.  This prevents the network	 software from offering	 these
;  performance optimizations to those clients.
;
;  The way to make these optimizations available to all	 clients is to	employ
;  opportunistic locking.  Here, the system behaves as though  the  client had
;  opened a file in an exclusive mode, until some other client opens the file.
;  When the second open occurs, the system turns off the optimizations for the
;  first client,  flushes buffers, etc, and then lets the second open proceed.
;  Servers could do this  by themselves if only remote clients	were involved,
;  but	dealing with  local clients on	the  server  machine  requires	kernel
;  support.  (Remember,	 the  OS2  net server  does  not  consume  the	entire
;  machine, so many  users  will run  the  server  to allow  generalized  file
;  sharing.)
;
;  Another  performance	 enhancement (opbatch)	occurs	when  the client redir
;  keeps open and buffers the data of a batch file that has been closed by the
;  client process when the redir has reason to believe that the client process
;  will	 be  reopening	and  rereading from the	 batch	file  soon.  It	 works
;  similarly  to oplocks,  except  that rename,	 delete,  and  set  attributes
;  requests must also be intercepted.
;
;  An  unpublished  enhancement	 to  DosOpen  and  two	unpublished  new  API,
;  DosOpLockWait and DosOpLockRelease, satisfy the requirements of the network
;  optimally.
;
;  There are two components to kernel support of opportunistic locking:
;
;      1)  A way for server to indicate a request for an opportunistic lock to
;      DosOpen and a return parameter indicating whether the lock was granted.
;
;      2)  A way  for the kernel to  notify server of pending opens  (deletes,
;      renames, and set attributes) against opportunistically locked files.
;
;  #1 is implemented by having the server indicate a request for an
;  opportunistic lock to DosOpen by passing a per-file	value (cookie)	which
;  the system can  return when	the  lock  is broken.  The cookie and flags
;  indicating oplock/opbatch  are be passed to DosOpen in TCB variables.
;  An unpublished flag indicating  whether the	opportunistic lock was granted
;  is returned in a TCB variable.  The flag has meaning only if the open
;  itself succeeded.
;
;  The system, upon receipt of an oplock/opbatch request, does the following:
;
;      if (no one has the file open)
;	  grant the oplock
;	  remember the cookie
;	  return oplock granted in action
;      else
;	  open the file regularly
;	  if (open succeeds)
;	     return oplock not granted in action word
;
;  #2 is be implemented as follows.  The server creates a thread that sleeps
;  until an open  (delete, rename, set attr) is issued for an
;  opportunistically-locked file.  At  that  point, the kernel fills in a
;  parameter packet with the filename  and  procruns the thread.  The  thread
;  breaking  the lock  procblocks and  the server thread cleans up.  When
;  clean-up is done, the sleeping thread is woken up.  Access to the parameter
;  packet is serialized, so if two op-lock  breaks occur at the same time, one
;  of them must wait  until the	 first has  been cleaned up and the server has
;  created another clean up thread.
;
;  Two unpublished API, DosOpLockWait and
;  DosOpLockRelease, manage server  notification and  clean-up	of broken
;  oplocks.  The server calls DosOpLockWait,  which enters the kernel
;  and	sleeps	until an oplock	 is broken.  Its parameters are a pointer to a
;  cookie (the network's per-file descriptor) and a pointer to a procblock key
;  (the kernel's identification  of the thread breaking the oplock)  which are
;  uninitialized on  entry to the kernel.  When	 woken by a broken oplock, the
;  DosOpLockWait thread returns to the server with the server's cookie for
;  the file  with the broken oplock  and  the  kernel's  procblock key  of the
;  thread breaking the oplock.	The kernel will have procblocked the violating
;  thread.  When  the  server is done cleaning up the oplock, it calls the
;  DosOpLockRelease routine with the procblock	key  returned by DosOpLockWait
;  and a  flag	indicating whether the	clean-up  was successful.  The	thread
;  which broke	the oplock  is woken up and  proceeds with the	open
;  (delete, rename, set attr) if the clean-up was successful.  If  it  was not
;  successful, an ERROR_SHARING_VIOLATION is be returned.
;
;  The kernel takes the following action if a request for an op-locked file is
;  received:
;
;     if (the server made the request)
;	 return the server's cookie for the oplocked file and kernel's
;	  procblock key for any other process requesting the oplocked file
;     else
;	 wake up the DosOpLockWait thread
;	 fill in pointers with cookie and procblock key
;	 sleep until woken up by DosOpLockRelease
;
;
include fnofs.inc
include basemaca.inc
include osmaca.inc
include dosseg.inc
include dossymc.inc
include error.inc
include mi.inc
include tkexport.inc
include mft.inc
include oplock.inc
include protmode.inc
include sft.inc
include osconst.inc
include pgexport.inc
include vmexport.inc
include codelock.inc

	ModName	, FNO_OPLOCK_ASM
	
	ReferTask	CurrTCB,WORD		; current thread control block
	EXTRNFAR	ProcBlock
	EXTRNFAR	ProcRun
	EXTRNFAR	SuWord

	EXTRN32		CodeLockProc
	EXTRN32		CodeUnlockProc
	EXTRNF32 	FNM

;The clean-up thread will be managed using a semaphore, oplock_sem.  The
;semaphore has four flags:
;
;    nothread - no thread available for clean-up
;    free - thread free for clean-up
;    busy - thread in use
;    waiting - broken oplocks waiting to use thread for clean-up
;
;There are also two global dwords, one for the kernel's procblock key and one
;for the server's file cookie.
;

DATA segment
oplock_sem	db  1	   ; serialization of clean-up thread. thread doesn't
			   ; exist, no one waiting for it, and it's free
oplock_cookie	dd  ?	   ; server's per-file cookie
oplock_key	dd  ?	   ; kernel's procblock key
DATA ends

;
;the mft_opflags field has four values:
;
;opnolock	 equ 0 ; no oplock/batch set or breaking
;oplock		 equ 1 ; file is oplocked.  opbreak may be set simultaneously
;opbatch	 equ 2 ; file is opbatched.  opbreak may be set simultaneously
;opbreak	 equ 4 ; oplock/batch was broken and is getting cleaned up. this
;		       ; bit is turned on without turning off oplock/batch bit.
;opbreakfailed	 equ 8 ; oplocked file was closed.
;
	FARCODE HIGH4
NMPIPESWAPCODE SEGMENT

	PUBLIC _StartOPLOCKGROUP
_StartOPLOCKGROUP label byte

	ASSUME	SS:TASKAREA

;***	w_OpLockWait - server thread for oplock clean-up
;
; The server calls this routine to provide a clean-up thread for broken
; oplocks.  The thread sleeps until an oplock break occurs.  It is then
; woken up and passed the server's cookie for the oplocked file and the
; kernel's procblock key of the thread breaking the oplock in a global
; data area.  It retrieves this data and returns to the server to clean up
; the broken oplock.
;
; Only one clean-up thread can exist at a time, so if one already exists,
; the call returns immediately with an ERROR_OPLOCK_THREAD_EXISTS.
;
; The clean-up thread will be managed using a semaphore, oplock_sem.  The
; semaphore has four flags:
;
;    nothread - no thread available for clean-up
;    free - thread free for clean-up
;    busy - thread in use
;    waiting - broken oplocks waiting to use thread for clean-up
;
;   ENTER   (DS:DX) -> dword to store procblock key - USER pointer
;	    (ES:DI) -> dword to store cookie - USER pointer
;
;
;   EXIT    'NC' - no error
;	       pointers filled in
;	    'CY' - error
;	       (AX) = ERROR_OPLOCK_THREAD_EXISTS
;	       (AX) = ERROR_INTERRUPT
;
;   USES    AX, BX, CX, DX, DS, ES, Flags
;
;   PSEUDOCODE
;   if (oplock_sem != nothread)		/* only one clean-up thread can exist */
;      return ERROR_OPLOCK_THREAD_EXISTS
;   oplock_sem |= free
;   oplock_sem &= NOT nothread
;   if (oplock_sem == waiting)
;      ProcRun(oplock_sem address)	/* run thread waiting for clean-up */
;   ProcBlock(oplock_cookie address) /* sleep until someone needs us */
;   get cookie and procblock key from oplock_cookie and oplock_key
;   oplock_sem &= waiting
;   oplock_sem |= nothread
;
Procedure w_OpLockWait,FARONLY
    ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA
LocalVar wolw_cookie,DWORD		; caller's ptr to cookie
LocalVar wolw_key,DWORD			; caller's ptr to key

	CodeLock OPLOCK_GROUP

	EnterProc <es,di,ds,dx>
	mov	si,CurrTCB
	cmp	TASKAREA:[si].TCBfSharing,-1	; server?
	mov	ax,ERROR_INVALID_FUNCTION	; no, go return error
	stc
	jne	wolw40j

	xor	ax,ax
	mov	es,ax			; Don't keep user selector in ES
	DosContext ds		; (DS) = HIGHDATA
	test	oplock_sem,nothread	; clean-up thread already exists?
	jz	wolw50			; yes, go return error
	or	oplock_sem,free		; free thread exists
	and	oplock_sem,NOT nothread ; turn off nothread bit
	test	oplock_sem,waiting	; anyone waiting for clean-up thread?
	jz	wolw10			; no, go sleep
;
; threads waiting for clean-up thread sleep on oplock_sem
;
	mov	ax,ds
	lea	bx,oplock_sem		; (AX:BX) = oplock sem address
	CALLFAR ProcRun			; wake up anyone waiting for clean-up
	and	oplock_sem,NOT waiting	; clear waiting bit
;
; clean-up thread sleeps on oplock_cookie
; (ds) = HIGHDATA
;
wolw10: mov	ax,ds
	lea	bx,oplock_cookie	; (AX:BX) = oplock cookie address
	MOV	CX,-1			; no timeout
	MOV	DI,CX
	xor	dh,dh			; interruptible
;
; (AX:BX) = event identifier
; (DI:CX) = timeout interval (in MS).  -1 if to never timeout
; (DH) = 0 if sleep is interruptible
;
	CALLFAR ProcBlock		; sleep until someone wakes us up
	jc	wolw60

;   oplock_sem &= waiting
;   oplock_sem |= nothread

	and	oplock_sem,waiting	; clear free bit
	or	oplock_sem,nothread	; set nothread bit
;
; when we get here, oplock_cookie and oplock_key are filled in and an oplock
; needs to be cleaned up.  return the values of oplock_cookie and oplock_key
; to server
; (DS) = HIGHDATA
;
	mov	bx,wolw_cookie.hiword
	mov	di,wolw_cookie.loword	; (BX:DI) -> user's dword for cookie
	lea	si,oplock_cookie	; (DS:SI) -> cookie
	lodsw
	CALLFAR SuWord			; store hiword of cookie
wolw40j: jc	wolw40
	lodsw
	CALLFAR SuWord			; store loword of cookie
	jc	wolw40

; return kernel's procblock key

	mov	bx,wolw_key.hiword
	mov	di,wolw_key.loword	; (BX:DI) -> user's dword for key
	lea	si,oplock_key		; (DS:SI) -> key
	lodsw
	CALLFAR SuWord			; store hiword of cookie
	jc	wolw40
	lodsw
	CALLFAR SuWord			; store loword of cookie

wolw40: LeaveProc
	CodeUnlock OPLOCK_GROUP
	ret

wolw50: mov	ax,ERROR_OPLOCK_THREAD_EXISTS
	stc
	jmp short wolw40

wolw60:
IFDEF FSSTRICT
	jnz	wolw70		; interrupted? if so, everything's ok
	InternalError <w_OpLockWait - unexpected ProcBlock return>
wolw70:
ENDIF
	mov	ax,ERROR_INTERRUPT
	stc
	jmp short wolw40

EndProc w_OpLockWait

	CpuMode 386

;***	OpLockCleanUp - do kernel part of oplock clean-up
;
;	ENTER	(ES:DI) -> SFT
;		(DS:SI) -> WFP
;
;	EXIT	'NC' - no error, cleanup succeeded and MFT flags are set
;		       appropriately
;		'CY' - error, AX contains proper error code and MFT flags are
;		       set appropriately
;			ERROR_OPLOCKED_FILE - server requested file
;
;	USES
;
;	PSEUDOCODE
;   if (MFT opflag == opbreak) {  /* make sure only first oplock-breaker does clean-up */
;      if (server process)
;	  return ERROR_OPLOCKED_FILE
;      else
;	  ProcBlock(key)
;   }
;   else {
;      MFT opflag |= oplock breaking
;      MFT opflag &= NOT oplockbreakfailed
;      if (fSharing) {		  /* server */
;	  return sft ptr to server as procblock key in TCBfSharing, TCBSrvAttrib, and TCBopflags
;	  stuff cookie in TCB variable (TCBOpCookie)
;	  return ERROR_OPLOCKED_FILE
;      }
;
;/* if the oplocked file was closed, all other processes waiting on the SFT
;   have been woken up by the call to MFTclose.  how can we tell that the file
;   has been closed?  it could have been closed and re-oplocked.  So we
;   prevent oplocked file from getting closed while waiting for cleanup thread
;   by locking the SFT. At this point, only a close by the process holding the
;   oplock could be competing for the SFT.  If a close is pending, we could
;   block waiting for the clean-up thread and the close could finish.  So after
;   we get access to the clean-up thread, we need to find the MFT again and
;   check that it's oplocked.  If it's oplocked, we proceed with clean-up.
;   Otherwise, we release the clean-up thread and run anyone waiting for it. */
;
;      GetOpLockThread
;      FNM
;      if (MFT.flags = oplock/batch) {
;	  if (MFT opflag == opbreak) {	/* make sure only first oplock-breaker does clean-up */
;	     oplock_sem |= free
;	     ProcRun(oplock_sem address)  /* other opbreaker is waiting for cleanup thread */
;	     ProcBlock(key)
;	  }
;	  else {
;	     MFT opflag |= oplock breaking
;	     store cookie and key in oplock_cookie and oplock_key
;
;/* no problem passing cookie and key in global data here since DosOpLockWait
;   thread retrieves them and access to clean-up thread is serialized.	*/
;
;	     ProcRun(oplock_cookie address)	 /* wake up DosOpLockWait thread */
;	     ProcBlock(key)			 /* put oplock-breaking thread to sleep */
;	  }
;      }
;      else {
;	  oplock_sem | free
;	  if (oplock_sem == waiting)
;	     ProcRun(oplock_sem address)
;      }
;   }
;/* we got woken up by 1) oplocked file getting closed (MFTclose) or 2)
;   DosOpLockRelease.  If DosOpLockRelease was called, if the file was
;   successfully unlocked by the server, MFT_opflags got set to opnolock.
;   If the unlock was unsuccessful, the opbreak bit in MFT_opflags was turned
;   off and the opbreakfailed bit in MFT_opflags was turned on.  If the
;   opbreakfailed bit is on, we return ERROR_SHARING_VIOLATION.  If MFTclose
;   was called, the SFT and MFT have been freed. They could have been
;   re-oplocked, so we re-find the MFT after returning from the ProcBlock (in
;   MFT_Enter). */
;
;   return
Procedure OpLockCleanUp,<faronly,far16>
    ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA

	; BUGBUG bryand 12-12-89 - masm bug - must use ss: instead of TASKAREA

LocalVar  olcu_pathptr,DWORD  ; pointer to full path

	CodeLock OPLOCK_GROUP
	SaveReg <ebx,esi>
	EnterProc <esi>

; if someone else is already doing clean-up, go to sleep.

	FlatContext	ds,,noassume
	mov	esi,es:[di].sf_MFT	    ; (DS:ESI) -> MFT
	test	[esi].mft_opflags,mft_opbreak ; someone already cleaning up?
	jz	SHORT olcu10		    ; no, go do it yourself

	mov	bx,CurrTCB
	cmp	ss:[bx].TCBfSharing,-1 ; server?
	jz	olcu15			    ; yes, go return cookie, etc.
	mov	ax,es
	mov	bx,di			    ; (AX:BX) = SFT address
	MOV	CX,-1			    ; no timeout
	MOV	DI,CX
	MOV	DH,1			    ; not interruptible
;
; (AX:BX) = event identifier
; (DI:CX) = timeout interval (in MS).  -1 if to never timeout
; (DH) = 1 if sleep is not interruptible
;
	CALLFAR ProcBlock		    ; sleep until someone wakes us up
olcu05:	LeaveProc
	RestoreReg <esi,ebx>
	CodeUnlock OPLOCK_GROUP
	ret				    ; clean-up is done, continue operation
;
; we have to do clean-up.  if server, return immediately to do clean-up rather
; than using DosOpLockWait thread to do clean-up.
;
;
olcu10: and	[esi].mft_opflags,NOT mft_opbreakfailed ; turn off failed bit
	mov	bx,CurrTCB
	cmp	ss:[bx].TCBfSharing,-1	    ; server?
	jnz	SHORT olcu20		    ; no, go continue clean-up
	or	[esi].mft_opflags,mft_opbreak ; we're breaking lock
olcu15: mov	word ptr ss:[bx].TCBfSharing,di
	mov	ss:[bx].TCBOpFlags,es	    ; return procblock key to server
;
;
; TCBOpCookie is set on entrance to DosOpen but not on other API calls, so it
; is necessary to reset it here.
;
;
	mov	eax,es:[di].sf_cookie	    ; (EAX) = cookie
	mov	ss:[bx].TCBOpCookie,eax	    ; return cookie to server
	mov	ax,ERROR_OPLOCKED_FILE	    ; return error
	stc
	jmp	short olcu05

olcu20: call	GetOpLockThread		    ; get possession of clean-up thread
;
; GetOpLockThread can block, so the file may have been closed under us.  We
; need to re-find MFT.	It may have been re-oplocked.  If so, we need to break
; it.
;
	mov	esi, olcu_pathptr           ; (Flat:ESI) -> file name          ;### AZ09831
;	lds	si,olcu_pathptr		    ; (DS:SI) -> WFP                   ;### AZ09831
	xor	al,al			    ; don't allow creation of MFT entry
	CALL32 	FNM			    ; (DS:BX) -> MFT
	jc	olcu70			    ; MFT doesn't exist, so no oplock
	test	[ebx].mft_opflags,mft_opanylock ; any lock on file?
	jz	olcu70			    ; no, go release clean-up thread
	test	[ebx].mft_opflags,mft_opbreak ; anyone breaking lock?
	jz	SHORT olcu30		    ; no, go do it ourselves
	mov	esi,ebx			    ; (DS:ESI) -> MFT
	DosContext es		    ; (ES) = HIGHDATA
	or	oplock_sem,free		    ; release clean-up thread
	lea	bx,oplock_sem
	mov	ax,es			    ; (AX:BX) = ProcBlock key
	CALLFAR ProcRun			    ; run threads waiting for clean-up thread
	mov	ax,[esi].mft_sptr.hiword
	mov	bx,[esi].mft_sptr.loword    ; (AX:BX) = ProcBlock key
	MOV	CX,-1			    ; no timeout
	MOV	DI,CX
	MOV	DH,1			    ; not interruptible
;
; (AX:BX) = event identifier
; (DI:CX) = timeout interval (in MS).  -1 if to never timeout
; (DH) = 1 if sleep is not interruptible
;
	CALLFAR ProcBlock		    ; sleep until someone wakes us up
	clc
	jmp	olcu05			    ; cleanup is done, return

olcu30: or	[ebx].mft_opflags,mft_opbreak ; we're breaking lock
	lds	si,[ebx].mft_sptr	    ; (DS:SI) -> SFT
	ASSUME ds:nothing
	DosContext es		    ; (ES) = HIGHDATA
	mov	oplock_key.hiword,ds
	mov	oplock_key.loword,si	    ; oplock_key = SFT address
	mov	ax,[si].sf_cookie.hiword
	mov	oplock_cookie.hiword,ax
	mov	ax,[si].sf_cookie.loword
	mov	oplock_cookie.loword,ax	    ; oplock_cookie = sf_cookie
	lea	bx,oplock_cookie
	mov	ax,es			    ; (AX:BX) = ProcBlock key
	CALLFAR ProcRun			    ; wake up clean-up thread

	mov	ax,ds
	mov	bx,si			    ; (AX:BX) = SFT pointer
	MOV	CX,-1			    ; no timeout
	MOV	DI,CX
	MOV	DH,1			    ; not interruptible
;
; (AX:BX) = event identifier
; (DI:CX) = timeout interval (in MS).  -1 if to never timeout
; (DH) = 1 if sleep is not interruptible
;
	CALLFAR ProcBlock		    ; sleep until cleanup is done
	clc
	jmp	olcu05			    ; cleanup is done, return

olcu70: DosContext ds		    ; (DS) = HIGHDATA
	or	oplock_sem,free		    ; free clean-up thread
	test	oplock_sem,waiting	    ; anyone waiting for thread
	jz	short olcu80			    ; no, go return
	mov	ax,ds
	lea	bx,oplock_sem		    ; (AX:BX) = procblock key
	CALLFAR ProcRun			    ; wake up threads waiting for clean-up thread
olcu80: clc
	jmp	olcu05

EndProc OpLockCleanUp


;***	GetOpLockThread - get possession of oplock clean-up thread
;
;   ENTER   none
;
;   EXIT    caller has possession of clean-up thread
;
;   USES    AX,BX,CX,DX,DI,DS,flags
;
;   PSEUDOCODE
;   while (oplock_sem != free) {
;      oplock_sem |= waiting
;      ProcBlock(oplock_sem address)
;   }
;   if (oplock_sem != shutdown)
;      oplock_sem &= NOT (free | waiting)
;
Procedure GetOpLockThread,NEAR,LOCAL
    ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA

	DosContext ds		; (DS) = HIGHDATA
	test	oplock_sem,free		; cleanup thread is free?
	jnz	short golt20			; yes, go grab it
	MOV	CX,-1			; no timeout
	MOV	DI,CX
	MOV	DH,1			; not interruptible
	lea	bx,oplock_sem		; (AX:BX) = oplock_sem address
golt10: mov	ax,ds
	or	oplock_sem,waiting	; oplock_sem |= waiting
;
; (AX:BX) = event identifier
; (DI:CX) = timeout interval (in MS).  -1 if to never timeout
; (DH) = 1 if sleep is not interruptible
;
	CALLFAR ProcBlock		; sleep until someone wakes us up
	test	oplock_sem,free		; cleanup thread is free?
	jz	short golt10			; no, go sleep again

golt20: test	oplock_sem,shutdown	; server shutdown?
	jnz	short golt30			; yes, so leave free bit on
	and	oplock_sem,NOT free	; turn off free and waiting bits
golt30: ret

EndProc GetOpLockThread

	CpuMode reset


;***	w_OpLockShutDown - clean up for server shutdown
;
; when the server shuts down, all threads waiting on oplocked files and
; clean-up threads need to be woken up.  threads waiting on oplocked files
; will be woken up because the server must close all remotely-opened files.
; but threads waiting for the DosOpLockWait clean-up thread will sleep
; indefinitely.  so this interface wakes up those threads waiting for the
; clean-up thread.  this is all that's necessary since the threads will
; check to see if the oplock still exists (it will have been broken by the
; server) and will continue if not.
;
; also, the clean-up thread may be sleeping.  wake it up.  the server will
; ignore its parameters.
;
;   ENTER   none
;
;   EXIT    'NC' - no error
;	    'CY' - error (server didn't issue call)
;		AX = ERROR_INVALID_FUNCTION
;
;   PSEUDOCODE
;      oplock_sem |= shutdown
;      ProcRun(oplock_sem)
;
Procedure w_OpLockShutDown,FARONLY
    ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA

;BUGBUG 26-Apr-1990 philipl - this code is fully preemptable
;	CodeLock OPLOCK_GROUP

	mov	si,CurrTCB
	cmp	TASKAREA:[si].TCBfSharing,-1	; server?
	mov	ax,ERROR_INVALID_FUNCTION	; no, go return error
	stc
	jne	wolsd10

	DosContext ds		; (DS) = HIGHDATA
	mov	oplock_sem,shutdown + free ; indicate shutdown
;
; threads waiting for clean-up thread sleep on oplock_sem
;
	mov	ax,ds
	lea	bx,oplock_sem		; (AX:BX) = oplock sem address
	CALLFAR ProcRun			; wake up anyone waiting for clean-up
;
; clean-up thread may be sleeping - wake it up
;
	mov	ax,ds
	lea	bx,oplock_cookie	; (AX:BX) = oplock cookie address
	CALLFAR ProcRun			; wake up anyone waiting for clean-up

wolsd10:
;BUGBUG 26-Apr-1990 philipl - this code is fully preemptable
;	CodeUnlock OPLOCK_GROUP
	ret

EndProc w_OpLockShutDown

	PUBLIC _EndOPLOCKGROUP
_EndOPLOCKGROUP label byte

NMPIPESWAPCODE ENDS

	FARCODE HIGH4
HIGH4CODE SEGMENT

	CpuMode 386

;***	w_OpLockRelease - indicate oplock break clean-up completed
;
;   When cleaning up a broken oplock, the server may close the file or just
;   flush its buffers.	If a close is issued, this routine is never called
;   because any threads sleeping on the file are woken up in MFTclose.	If
;   no close is issued, this routine is called.  The server guarantees that
;   if the oplock on "foo" is broken and the clean-up thread DosOpLockWait
;   returns to the server, no closes of "foo" other than by the clean-up
;   thread can occur until after this routine is called.  This means that if
;   this routine is called, the file is still open and it's safe to reference
;   the SFT, which is the procblock key passed in.
;
;   ENTER   (CX) = flag indicating success or failure
;	    (DS:SI) = procblock key (returned to server by DosOpLockWait or
;			other API if server broke oplock)
;
;   EXIT    none
;
;   USES    AX, BX, DI, ES, flags
;
;   PSEUDOCODE
;   if (action == success)
;      MFT->opflags = opnolock
;   else
;      MFT->opflags &= NOT(opbreak)
;   ProcRun(key)
;
Procedure w_OpLockRelease,FARONLY
    ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA

	CodeLock OPLOCK_GROUP

	; BUGBUG bryand 12-12-89 - masm bug - must use ss: instead of TASKAREA

	mov	di,CurrTCB
	cmp	ss:[di].TCBfSharing,-1		; server?
	mov	ax,ERROR_INVALID_FUNCTION	; no, go return error
	stc
	jne	SHORT wolr30

	FlatContext  es
	mov	edi,[si].sf_MFT	    ; (ES:EDI) -> MFT
	or	cx,cx		    ; did clean-up succeed?
	jnz	SHORT wolr10	    ; no, go set fail in MFT
	mov	es:[edi].mft_opflags,mft_opnolock    ; no lock on file
	jmp	SHORT wolr20

wolr10: and	es:[edi].mft_opflags,NOT mft_opbreak ; no one's attempting break
	or	es:[edi].mft_opflags,mft_opbreakfailed

wolr20: mov	ax,ds
	mov	bx,si	    ; (AX:BX) = SFT address
	CALLFAR ProcRun	    ; wake up everyone waiting on file
	CodeUnlock OPLOCK_GROUP
wolr30: ret

EndProc w_OpLockRelease

	CpuMode reset

HIGH4CODE ENDS
	END
