	Page 84,132 ;


Title	FASTOPEN
;/*
; *                      Microsoft Confidential
; *                      Copyright (C) Microsoft Corporation 1991
; *                      All Rights Reserved.
; */
;Date: Jan 15,1989
;==============================================================================
;		 EQUATES
;==============================================================================
Is_drive_head	   EQU	   00000001b								   ;AN000;
Is_delete	   EQU	   00000010b								   ;AN000;
Is_insert	   EQU	   00000100b								   ;AN000;
Not_drive_head	   EQU	   11111110b								   ;AN000;
Not_delete	   EQU	   11111101b								   ;AN000;
Not_insert	   EQU	   11111011b

DYNA_ALLOC         EQU     2               ; EMS dynamic allocation (for IBM)

EMS_SAVE_STATE	   EQU	   4FH									   ;AN000;
EMS_RESTORE_STATE  EQU	   4FH									   ;AN000;
EMS_INT 	   EQU	   67H									   ;AN000;
DOS_PAGE1	   EQU	   0FEH 								   ;AN000;


;==============================================================================
;		   INCLUDE  FILES
;==============================================================================
.xcref
.xlist
debug=0 		   ; an equate only for DOSMAC.inc				     ;AN000;
INCLUDE  DOSMAC.inc										    ;AN000;
.list
.cref
INCLUDE  dirent.inc										    ;AN000;
INCLUDE  fastsegs.inc	   ; this cannot include in Fastopen.inc
INCLUDE  fastopen.inc	   ; this include file also contains DOS equates			    ;AN000;
include  version.inc

BUFFERFLAG	EQU	TRUE            ;M005


;==============================================================================




; IBM Revision History:
;	     AN002  -  Jan 10,1989  EMS Dynamic page allocation

;============================================================================

CSEG_MAIN   SEGMENT   PARA   PUBLIC 'code'
	ASSUME	cs:cseg_main, ds:nothing,es:nothing,ss:nothing
;============================================================================

PUBLIC	MAIN											   ;AN000;

;;;; johnhe 12/31/89 ;;; PUBLIC	SAVE_EMS_PAGE_STATE

PUBLIC	EMS_PAGE_NUMBER
PUBLIC	EMS_PAGE_MAP
PUBLIC	EMS_PAGE_ARRAY

PUBLIC	RESTORE_PAGE_STATE
PUBLIC	EMS_SAVE_HANDLE1

PUBLIC	Main_name_cache_seg									   ;AN000;
PUBLIC	Main_Num_Of_drives									   ;AN000;


PUBLIC	Main_name_cache_Buff									   ;AN000;
PUBLIC	Main_EMS_FLAG			; for IBM, 2= /X, 1=/XS, 2=/XD, 0=EMS not enabled             ;AN000;
PUBLIC	Main_Res_Segs										   ;AN000;
PUBLIC	Main_EMS_PAGE_SIZE									   ;AN000;
PUBLIC	Main_EMS_PAGE_SEG									   ;AN000;


PUBLIC	Main_Total_Name_Count									   ;AN000;
PUBLIC	Main_Name_Drive_Buff									   ;AN000;
PUBLIC	Main_ParamBuff										   ;AN000;

PUBLIC	FOPEN_Insert										   ;AN000;
PUBLIC	FOPEN_Update										   ;AN000;
PUBLIC	FOPEN_Delete										   ;AN000;
PUBLIC	FOPEN_Lookup										   ;AN000;
PUBLIC  FOPEN_Rename                    ;BN00X                                                      ;AN000;
PUBLIC	FOPEN_PURGE										  ;AN000;


PUBLIC	 VECTOR_LookUp										  ;AN000;



; Following data variables are accessed by all other segments
call_cnt	       DW    0									;AN000;
Purge_Flag	       DW    0		   ; =1 if last call is PURGE function
Prev_drv_id	       DB    -1 	   ; previous request drive id
Main_name_cache_seg    DW    Cseg_Init	   ; default to Init1 seg				;AN000;
Main_Num_Of_drives     DW    0		   ; number of drives					;AN000;


Main_name_cache_Buff   DW    0		   ; address of Name cache buffer			;AN000;


Main_EMS_FLAG	       DW    0		   ; EMI flag  1= if EMI is enabled			;AN000;
				           ; IBM: EMS flag 1=/X  2=/XS 3=/XD  0=No EMS              ;AN000;
Main_Res_Segs	       DW    0		   ; number of segs to be stay resident 		;AN000;


Main_Total_Name_Count  DW    0		   ; Total name count entries				;AN000;
Main_Name_Drive_Buff   DW    0		   ; EMS data page segment ID				;AN000;
Main_ParamBuff	       DW    50  dup (0)   ; Drive ID/extent count buffer			;AN000;

; The following structure is for saving and restoring EMS page state

EMS_PAGE_MAP	     LABEL    WORD
Main_EMS_SEG_COUNT     DW    1		   ; EMS segment count
Main_EMS_PAGE_SEG      DW    0		   ; EMS page segment ID				;AN000;


Main_EMS_PAGE_SIZE     DW    0		   ; EMS page size					;AN000;
EMS_PAGE_ARRAY	       DW  30	dup  (0)   ; EMS state save array

; The following data values are used by MAIN segment
EMS_SAVE_LOG_PAGE1     DW    ?		   ;HOLDS PREVIOUS PAGE1				    ;AN000;
EMS_SAVE_HANDLE1       DW    ?		   ;HOLDS PREVIOUS handle1				    ;AN000;

EMS_PAGE_NUMBER		DW	?			; holds the ems 
							; physical page no.
;
;-----------------------------------------------------------------------------
;	  Fastopen/Fastseek function jump vectors
; Inititally the jump vectors have default offset and segment values.
; If the modules are relocated, the offset and the segID in the jump vectors
; may be changed to the new segID of the new location.
;-----------------------------------------------------------------------------
FOPEN_Insert	 DD   Insert									 ;AN000;
FOPEN_Update	 DD   Update									 ;AN000;
FOPEN_Delete	 DD   delete									 ;AN000;
FOPEN_Lookup	 DD   lookup									 ;AN000;
FOPEN_Rename     DD   Rename           ;BN00X                                                           ;AN000;
FOPEN_Purge	 DD   FP_purge									 ;AN000;


VECTOR_LookUp	 DD   LookUp	      ; jump vector to LookUp used by Insert call



;==============================================================================

MAIN	PROC	FAR			  ; FAR procedure for FAR call from DOS
	push	cx			  ; save DOS registers				     ;AN000;
	push	dx			  ; makesure to restore the necessary			   ;AN000;
	push	ds			  ; ones on return					  ;AN000;
	push	es										   ;AN000;
	push	bp										   ;AN000;
	push	di										   ;AN000;
	push	bx										   ;AN000;

;-----------------------------------------------------------------------------
; The cache buffers are maintained in a seperate segement whose segment ID is
; in Name_Cache_Seg.  The ES will be used as the seg register during the access
; of data in the cache buffers, while DS will be used to access the Fastopen
; resident and non-resident data area.
;-----------------------------------------------------------------------------
	cmp	cs:Main_EMS_flag,0	  ; EMS enabled ??					     ;AN000;
	je	dispatch_funcs		  ; no - dispatch functions			   ;AN000;
					  ; yes - save EMS page state

	; NOTE:
	;	If BUFFERFLAG == TRUE it means that we're not using
	;	IBMs method of leaving the EMS pages always mapped in
	;	and instead are using the Microsoft method of mapping
	;	the pages in only when they are needed.
	;

IF	NOT BUFFERFLAG

	CMP	Main_EMS_Flag,DYNA_ALLOC   ;AN002; dynamic allocation
	JE	DYNA_SAVE_STATE 	  ;AN002; yes - do save for dynamic
;-----------------------------------------------------------------------------
; SAVE EMS PAGE STATE
;-----------------------------------------------------------------------------

;;;; johnhe 12/31/89;	CALL	FAR PTR SAVE_EMS_PAGE_STATE  ; save EMS page context
	call	Save_Ems_Page_State	  ; johnhe 12/31/89

	JNC	DISPATCH_FUNCS		  ; if no error, dispatch Fastopen functions
	JMP	ERROR_RET		  ; else error

DYNA_SAVE_STATE:

ENDIF

;------------------------------------------------------------HKN 8/26/88--
;	Before dispatching off the fastopen functions we must do the 
;	following:
;		1. save the map for this page
;		2. map this page to log. page 0 with the fastopen handle in
;		   ems_save_handle1.
;		3. dispatch
;

;;;; johnhe 12/31/89;	call	far ptr	save_ems_page_state
	call	Save_Ems_Page_State	  ; johnhe 12/31/89

	jc	ems_failed

	call	map_page
	jnc	DISPATCH_FUNCS

EMS_FAILED:
	JMP	ERROR_RET		 ; error return

;-----------------------------------------------------------------------------
; FASTOPEN/FASTSEEK DISPATCHER
;-----------------------------------------------------------------------------

DISPATCH_FUNCS:
	cmp	al,5		      ; buffer purge ??
	je	Check_Drive_id	      ; yes - check drive id
	jmp	SHORT Dispatch_fopen  ; yes - dispatch Fastopen functions			  ;AN000;

;-----------------------------------------------------------------------------
; Check to see the Drive ID in DL is the valid. If not error and return DI=1
; if Fastseek LookUp function. Makesure to preserve AL, DS, SI and DI
;-----------------------------------------------------------------------------
CHECK_DRIVE_ID:
	cmp	cs:Prev_drv_id, dl	    ; current id same as previous valid
	je	DISPATCH_FUNCTION	    ; yes - dont check drive ID

	push	si										   ;AN000;
	push	bx			    ;DS=addressability to Cseg_Main			   ;AN000;
	push	cx										   ;AN000;
	lea	si,cs:Main_ParamBuff	    ; DS:SI-->drive ID buffer				   ;AN000;
	mov	cx,cs:Main_Num_Of_Drives    ; number of drives					   ;AN000;

Get_Drive_Id:											   ;AN000;
	mov	bx,cs:[si]									   ;AN000;
	cmp	bl,dl			    ; drive ID match ?? 				   ;AN000;
	je	drive_found		    ; yes, drive ID found				   ;AN000;
	add	si,4			    ; (2/11) no, move pointer to next ID		   ;AN000;
	LOOP	get_drive_id		    ; check next drive id				   ;AN000;

Drive_Not_Found:			    ; drive id not found
	pop	cx			    ; restore registers 				   ;AN000;
	pop	bx										   ;AN000;
	pop	si										   ;AN000;
	jmp	Error_Exit		    ; return

Drive_Found:				    ; drive ID found
	mov	cs:Prev_drv_id,dl	    ; save drive id as prev drive id
	pop	cx			    ; restore registers 				   ;AN000;
	pop	bx			    ; and do the specified function			   ;AN000;
	pop	si										   ;AN000;

;-----------------------------------------------------------------------------

DISPATCH_FUNCTION:			  ; RMFS

	cmp	al,010H
	jle	Fsk_Cont
	inc	cs:call_cnt									   ;AN000;

Fsk_Cont:
	push	cs			   ; set addressability
	pop	ds			   ; CS = DS = Cseg_Main segment
	ASSUME	ds:Cseg_Main
	cmp	al,FONC_Purge		   ; PURGE call ??					       ;AN000;
	je	chk_05			   ; yes - continue						;AN000;

	mov	cs:Purge_Flag, 0	   ; reset purge flag


Chk_05:
	cmp	cs:Purge_Flag, 1	   ; previous call is purge ??				    ;AN000;
	jne	Purge_buffs		   ; no - purge the buffers
	clc				   ; yes - exit
	jmp	short exit										      ;AN000;

Purge_Buffs:
	mov	cs:Purge_Flag,1 	   ; set purge flag


Reset_Fopen:
	cmp	CS:Main_Total_Name_Count,0	    ; reset fopen buffs??
	je	Reset_Exit		   ; no - reset f
	CALL	CS:FOPEN_PURGE		      ; reset extent cache			       ;AN000;

Reset_Exit:
	clc
	jmp	short exit										 ;AN000;


; NOTE: Carry Flag state from Function calls must be correctly returned
;	to the DOS, especially from Fastseek Lookup function


;-----------------------------------------------------------------------------
;	FASTOPEN FUNCTION DISPATCHER
;-----------------------------------------------------------------------------
DISPATCH_FOPEN: 		      ; dispatch FOPEN functions
	cld											   ;AN000;
	mov	cs:Purge_Flag, 0      ; reset purge flag
	cmp	al, FONC_update 								   ;AN000;
	jne	Chk_02										   ;AN000;
	CALL	CS:Fopen_Update       ; UPDATE							   ;AN000;
	jmp	short exit										 ;AN000;

Chk_02:
	cmp	al, FONC_insert 								   ;AN000;
	jne	Chk_01										   ;AN000;
	CALL	CS:Fopen_Insert       ; INSERT							   ;AN000;
	jmp	short exit										 ;AN000;
Chk_01:
	cmp	al, FONC_look_up								   ;AN000;
	jne	chk_06
	CALL	CS:Fopen_lookup       ; LOOKUP							   ;AN000;
	jmp	short exit										 ;AN000;
Chk_06: 			      ;BN00X
	cmp	al, FONC_Rename       ;BN00X							   ;AN000;
	jne	chk_03		      ;BN00X							   ;AN000;
	CALL	CS:Fopen_Rename       ;BN00X  RENAME							 ;AN000;
	jmp	short exit	      ;BN00X								 ;AN000;
Chk_03:
	cmp	al, FONC_delete 								   ;AN000;
	jne	Error_Exit									   ;AN000;
	CALL	CS:Fopen_delete       ; DELETE							   ;AN000;
	jmp	short exit										 ;AN000;



;-----------------------------------------------------------------------------
;  EXIT TO DOS FROM FUNCTIONS
;-----------------------------------------------------------------------------

ERROR_EXIT:	     ; EXIT from invalid drive id search loop
	CALL	RESTORE_PAGE_STATE     ; restore frame buff status				   ;AN000;
				       ; on return AX should have function code
	pop	bx		       ; restore first two regs of DOS
	pop	di										   ;AN000;


	clc											   ;AN000;
	jmp	short Exit_1									   ;AN000;


; Normal Exit from Fastopen Functions except Fastseek Lookup function
EXIT:
	CALL	RESTORE_PAGE_STATE     ; restore EMS page state 				   ;AN000;;AN000;
	pop	bx		       ; restore BX						   ;AN000;
	pop	di		       ; restore DI						   ;AN000;


; Exit from FastSeek Lookup function. Dont restore BX and DI
EXIT_1:
	pop	bp		       ; restore remaining DOS registers
	pop	es		       ; except BX and DI since they contain								;AN000;
	pop	ds		       ; return values. 					    ;AN000;
	pop	dx										   ;AN000;
	pop	cx										   ;AN000;

ERROR_RET:
	ret											   ;AN000;

MAIN	ENDP


;---------------------------------------------------------------------------
;	Procedure name	:	save_ems_page_state
;	
;	Description:
;		Saves the state of the page whose physical segment value is
;	specified in Main_EMS_PAGE_SEG. 
;---------------------------------------------------------------------------

SAVE_EMS_PAGE_STATE	PROC	NEAR

	PUSH	AX			  ; save registers
	PUSH	DS										   ;AN000;
	PUSH	ES										   ;AN000;
	PUSH	SI										   ;AN000;
	PUSH	DI										   ;AN000;

	MOV	AX, SEG EMS_PAGE_MAP	  ; get segid
	MOV	DS,AX
	LEA	SI,EMS_PAGE_MAP 	  ; DS:SI-->page map struc
	MOV	AX, SEG EMS_PAGE_ARRAY	  ; get segid
	MOV	ES,AX
	LEA	DI,EMS_PAGE_ARRAY	  ; ES:DI-->Page ARRAY
	MOV	AH,EMS_SAVE_STATE	  ;
	MOV	AL,0			  ; subfunction code
	INT	EMS_INT 		  ; save page state							    ;AN000;

	add	AH,0ffh 		  ; Set carry if AH != 0

	POP	DI										   ;AN000;
	POP	SI										   ;AN000;
	POP	ES										   ;AN000;
	POP	DS										   ;AN000;
	POP	AX
	RET

SAVE_EMS_PAGE_STATE	ENDP



;-----------------------------------------------------------------------------
; PROCERDURE:  RESTORE_PAGE_STATE
;
; Function:    Restore state of EMS page
;
; Input:       None
; Output:      Page is restored
;
;-----------------------------------------------------------------------------

RESTORE_PAGE_STATE    PROC  NEAR	;RESTORE EMS PAGE STATE

	PUSHF				;save flag					     ;AN000;
	CMP	CS:MAIN_EMS_FLAG, 0	;EMS enabled ?? 				     ;AN000;
	JNE	REST_PUSH_REGS		;yes, restore registers
	JMP	SHORT RESTORE_EXIT	;no, exit					     ;AN000;
					;yes, restore page registers
REST_PUSH_REGS:
	PUSH	AX			; save function code
	PUSH	DS									     ;AN000;
	PUSH	SI									     ;AN000;

	MOV	AX, SEG EMS_PAGE_ARRAY
	MOV	DS,AX
	LEA	SI,EMS_PAGE_ARRAY	; DS:SI-->Page array
	MOV	AH,EMS_RESTORE_STATE	;
	MOV	AL,1			;
	INT	EMS_INT 		; restre page state
					; Don't care about error cause
					; there's nothing that can be
					; done at this point if there
					; one

	POP	SI									      ;AN000;
	POP	DS									      ;AN000;
	POP	AX			; restore function code

RESTORE_EXIT:
	POPF
	RET										      ;AN000;

RESTORE_PAGE_STATE    ENDP



;---------------------------------------------------------HKN 8/26/88-------
;	procedure name		:	map_page
;	Inputs			: 	ems_page_number = physical page frame
;							  number.
;					ems_save_handle1 = emm_handle.
;	Output			: 	CY - error
;					NC - page is mapped to logical page 0
;----------------------------------------------------------------------------
map_page	proc	near

	push	ax
	push	bx
	push	dx

	xor	bx, bx
	mov	ax, cs:ems_page_number	; contains the page number obtained 
					; during fastopen intialization.
	mov	ah, 44h
	mov	dx, cs:ems_save_handle1	; contains the emm handle that was
					; obtained during fast init.
	int	ems_int
	add	AH,0ffh 		; Set carry if AH != 0

map_page_done:
	pop	dx
	pop	bx
	pop	ax
	ret

map_page	endp

	
					 ; NOTE:
CSEG_MAIN   ENDS			 ; End of the first portion of the
					 ; Cseg_Main segment.  Remaining
					 ; portion is in Fastinit.asm

;-----------------------------------------------------------------------------





;==============================================================================
; All Fastopen functions are kept in a seperate segment.  These are accessed
; by a FAR indirect call from the MAIN routine.
; ADDRESSABILTY: CS is used for accessing local data in Cseg_Open segment
;		 DS is used for accessing data in the drive cache buffer
;					  in the Cseg_Init segment
;		 ES is used for accessing data in the name cache buffer
;					  in the Cseg_Init segment
;
;*****************************************************************************
CSEG_OPEN   SEGMENT   PARA   PUBLIC 'code'
  ASSUME  cs:cseg_open,ds:nothing,es:nothing,ss:nothing
;*****************************************************************************

PUBLIC	 Open_name_cache_seg									   ;AN000;
PUBLIC	 Open_name_Drive_Buff									   ;AN000;
PUBLIC	 End_Open										   ;AN000;
PUBLIC	 Chk_Flag										   ;AN000;

;---- FastOpen Functions Local Variables --------------

Current_Node	     DW    ?	     ;address of current node entry buffer		     ;AN000;
Current_Sibling      DW    ?	     ;address of current sibling node entry buffer		;AN000;
Current_Drive	     DW    ?	     ;address of current drive header			     ;AN000;
Matching_Node	     DW    -1	     ;flag						       ;AN000;
From_Delete	     DW    0	     ;= 1 if call is from DELETE function		    ;AN000;
Old_SI		     DW    0	     ;SI save area				     ;AN000;
Flag		     DB    0									;AN000;
Level		     DB    0	     ;depth level of the path					;AN000;
Dir_Info_Buffer      DD    ?	     ;Dir_Info buffer inside DOS				;AN000;
Extended_Info_Buffer DD    ?	     ;Extended Info buffer inside DOS			;AN000;
New_FEI_clusnum      DW    0									;AN000;
Packed_Name	     DB    11 dup (0)	   ;Space for packed dir name				;AN000;
Top		     DW    0									;AN000;
Temp		     DW    0									;AN000;
Bottom		     DW    0									;AN000;
Depth		     DB    0									;AN000;

Chk_Flag	     dw    0	     ; flag used by the analyser
func_cod	     db    0	     ; function code for analyser

;Following data area is filled during initialization
Open_name_cache_seg	   DW	 Cseg_Init     ; address of name cache buffer
Open_name_Drive_Buff	   DW	 0	       ; address of first drive buffer




;
;==============================================================================
;		    Pathname Tree Search
;
;  Element of each path name is represented by a node in the tree.  First
;  node is connected to the the Drive header through first child pointer
;  (DCH_Child_Ptr).  The first node may have one or more nodes underneath.
;  The first one is called the Child of this node and the others are the siblings
;  of the child node.  Previous node is connected to the first node through
;  the child pointer (nChild_Ptr) and the siblings are connected through the
;  sibling pointer (nSibling_Ptr).  Each node is connected to the previous
;  node through a backward pointer (nBackward_Ptr).  For example, to go to the
;  previous node from any of the siblings. It is necessary to go to the
;  child through previous siblings (if any) and then to the previous from the
;  child.  All this backward movement is using nBackward_Ptr.
;  Similarly to go to a child or sibling, nChild_ptr or nSibling_Ptr should be
;  used.  The strucure of drive header and the node are defined in Fastopen.inc
;

;==============================================================================
;Subroutine:  LOOKUP
;
;INPUT:
;    DS:SI -> path  (drive letter D: will be validated by Find_Drive_Cache_hdr)
;    ES:DI -> DIR_INFO buffer to be returned inside DOS
;    ES:CX -> FASTOPEN_Extended_Info buffer inside DOS
;    ES:BP -> Drive_Cache_Heade
;
;
;OUTPUT:
;     If the whole path is found,
;	 DS:SI--> 0
;	 ES:DI--> DIR_INFO buffer is filled with directory info
;	 ES:CX--> EXT_INFO buffer is filled with extended info
;
;     If partially found the path,
;	 DS:SI--> '\' after the matching directory name
;	 ES:DI--> DIR_INFO buffer is filled with directory info
;	 ES:CX--> EXT_INFO buffer is filled with extended info
;
;     If the Name_cache tree is empty, i.e.,no root directory name,
;	 DS:SI--> '\' after ':'
;	 ES:DI--> DIR_INFO buffer is undetermined
;	 ES:CX--> EXT_INFO buffer is undetermined
;
;==============================================================================

LOOKUP	PROC   FAR

	mov	cs:func_cod,al		   ; save function code
	cmp	From_Delete, 0		   ;call from DELETE function ??
	je	Look_Pack_Dir		   ;no, dont restore DS
	mov	DS,bx			   ;yes, restore DS
	ASSUME	DS:Cseg_Init
	jmp	short Look_save_regs	   ;save registers

Look_Pack_Dir:
	CALL	PACK_DIR_NAME		   ;on return drive letter => DL,

	CALL	FIND_DRIVE_CACHE_HEADER    ;find drive header address
					   ;on return ES:BP-->drive header
	jnc	look_save_regs		   ;drive buffer found
	jmp	lookup_error		   ;drive buffer not found

Look_Save_Regs:
	push	es
	push	di
	push	cx
	mov	ax, cs:Open_Name_Cache_Seg							   ;AN000;
	mov	es, ax			   ;ES = Name_Cache_Seg 				      ;AN000;
	ASSUME	es:Cseg_Init									   ;AN000;
	CALL	SET_LRU 		   ;set the Real LRU, if any.

	or	cs:Flag,Is_drive_head	   ;level of the tree. Drive header
	mov	cs:Matching_Node, -1	   ;Nothing found yet.
	mov	cs:Current_Drive, BP	   ;drive header
	mov	cs:Level, 0		   ;path level is 0

Lookup_Path:
	mov	cs:Current_Node, BP	   ;save current node address
	mov	cs:Current_Sibling,0fffeh  ;set no sibligs yet.
	mov	cs:Old_SI, si		   ;save current path address

	CALL	PACK_DIR_NAME		   ;get the next dir name from the path
	jc	Lookup_Done		   ;yes, found the whole path.

	test	cs:Flag, Is_drive_head	   ;dir name = drive header ?
	jz	Lp_Path1		   ;no-

	push	ds			   ;yes-drive header
	mov	ds,cs:Open_Name_Cache_Seg
	ASSUME	ds:Cseg_Init
	mov	BP, DS:[BP.DCH_Child_ptr]  ;BP-->first child node under drive hdr
	pop	ds
	ASSUME	ds:nothing
	jmp	short Lp_Path2

Lp_Path1:
	mov	BP, ES:[BP.nChild_ptr]	   ;BP--> child of current node

Lp_Path2:
	cmp	BP, -1			   ;no child?
	je	Lookup_Done		   ;Not found or partially found
	mov	cs:Current_Node, BP	   ;current_node = found node
	and	cs:Flag, Not_drive_head    ;reset the flag.

Lp_Cmpare:
	CALL	CMPARE			   ;look for path in current node
	je	Lookup_Found		   ;Yes, found a match. Next level for
					   ;possible remianing path

	mov	BP, ES:[BP.nSibling_ptr]   ;not found. Any siblings?
	mov	cs:Current_Sibling,BP
	cmp	BP, -1			   ;any more sibling?
	je	Lookup_Done		   ;no - done

	mov	cs:Current_Node, BP	   ;yes- make the found sibling as a current
	jmp	short Lp_Cmpare 	   ;node and search path in this node

Lookup_Found:
	inc	cs:Level
	mov	cs:Matching_Node,BP	   ;Used by Unfold_Name_Record

	CALL	PRE_LRU_STACK		   ;set the TEMP_LRU_Stack
	jmp	Lookup_Path		   ;continue to the next dir

Lookup_Done:
	mov	si, cs:Old_SI
	pop	cx			   ;restore Extended_Info buffer
	pop	di			   ;restore Dir_Info buffer
	pop	es			   ;the segment for the above buffers

	cmp	ax, -1
	je	Lookup_ERR		   ;error occured in Pack_Dir_Name.
	clc				   ;clear carry.
	jmp	short Lookup_Done1

Lookup_ERR:				   ;error exit
	stc

Lookup_Done1:
	test	cs:Flag, is_delete	   ;called by delete?
	jnz	Lookup_Return
	jc	Lookup_Exit		   ;If it was an error, don't change the carry flag

	CALL	UNFOLD_NAME_RECORD	   ;unfold the current node's record
Lookup_Exit:
	jmp	short Lookup_Return	   ;return to DOS.

Lookup_Error:				   ;error exit
	stc
	mov    ax,-1

Lookup_Return:				   ;return to Delete routine.
	CALL	Check_It		   ;check tree structure
	ret

LOOKUP	 ENDP





;==============================================================================
;SUBROUTINE: INSERT
;
;INPUT:    DS:DI -> Dir_Info in DOS
;	   ES:BX -> Fastopen_Extended_Info in DOS
;	   Current_Node, Current_Sibling, Current_Drive, Flag
;
;OUTPUT:   Information inserted into Name_cache_tree.
;
;   Any Sequential Insert operation should be preceded by a Look_up
;   operation. For ex., if the DOS wants to insert C:\DIR1\DIR2\File1
;   and suppose there is no matching name cache record for DIR1 in the tree.
;   Firstly DOS will try to look up C:\DIR1\DIR2\File1.  FASTOPEN will
;   return to DOS with DS:SI points to "\" after the drive letter.
;   Then, DOS will simply ask an insert operation with DS:DI, ES:BX
;   points to the information on "DIR1".  FASTOPEN will insert DIR1
;   onto the tree.  After that DOS will ask another insert
;   operation for DIR2.  FASTOPEN will insert DIR2.  Finally DOS will
;   ask to insert File1.
;
;   Suppose when DOS try to look up C:\DIR1\DIR2\File2 at this moment.
;   FASTOPEN will return to DOS with DS:SI points to "\" after DIR2 (since
;   DIR2 information is already in the name cache tree).  Then DOS will ask
;   to insert File2.
;
;   Any Insert operation of subdirectory name which is deeper than (Number_
;   of_Entries - 1) will not be inserted but will just return.
;   Also, for the safety reason, if the would be freed node (=LRU node) is
;   the same as the Current_Node, there will be no insertion. (This is a simple
;   safety valve.  A more smart logic can look for the next **legitimately
;   available** LRU node to use, or sometimes, simply replace the contents of the
;   entry if adequate. But this will increase the complexity greatly, and I
;   think the current logic is still practical enough to use despite of the
;   possible small window of performance degradation in a very special cases. J.K.)
;
;==============================================================================

INSERT	PROC   FAR
	mov	cs:func_cod,al			; save function code
	inc	cs:Level			;increment directory level
	xor	ax,ax
	mov	al, cs:Level
	inc	al
	mov	bp, cs:Current_Drive		;BP-->address of current drive header
	push	ds
	mov	ds,cs:Open_Name_Cache_Seg	;DS=name cache segment

	ASSUME	ds:Cseg_Init									   ;AN000;
	cmp	ax, ds:[bp.DCH_Num_Entries]	;Level > (Num_Entries - 1) ?
	pop	ds
	ASSUME	ds:nothing
	jbe	Insert_it			;no- insert it
	jmp	short Insert_return		;yes return

Insert_it:
	or	cs:Flag, is_insert

	CALL	GET_FREE_NODE		   ;AX = offset value of the available
					   ;name_record in Name_Cache_Seg.
	jc	I_Exit			   ;Current node = would-be freed node.

	CALL	MAKE_NAME_RECORD	   ;Fill the above name record entry.
					   ;ES was changed to Name_Cache_Seg.

	mov	bp, cs:Current_Node	   ;set BP to current_node
	mov	bx, bp			   ;save it into bx
	cmp	cs:Current_Sibling,0fffeh  ;current node sibling node ??
	je	I_Child 		   ;no-child of preceding node
	mov	es:[bp.nSibling_ptr], ax   ;yes-make new node sibling of
	jmp	short I_Done		   ;current node

I_Child:				   ;set nChild_ptr
	test	cs:Flag,Is_drive_head	   ;drive level?
	jnz	I_Child_first		   ;Yes, must be the first child
	mov	es:[bp.nChild_ptr], ax	   ;no-make ndew node child of
	jmp	short I_Done		   ;current node

I_Child_first:				   ;this is the first child in this drive.
	push	ds
	mov	ds,cs:Open_Name_Cache_Seg							   ;AN000;
	ASSUME	ds:Cseg_Init									   ;AN000;
	mov	ds:[bp.DCH_Child_ptr],ax   ;make new node 1st child current drive
	pop	ds
	ASSUME	ds:nothing
	mov	bx, cs:Current_Drive	   ;change bx to Current_Drive
	and	cs:Flag, Not_drive_head    ;reset the flag

I_Done:
	mov	bp, ax
	mov	es:[bp.nBackward_ptr],bx ;set the backward ptr of the inserted node.

	CALL	PRE_LRU_STACK		   ;save this inserted record temporarily.

	mov	cs:Current_Node,bp	   ;make new node current node
					   ;any subsequent insert operation
	mov	cs:Current_Sibling,0fffeh  ;should be installed as a child

I_Exit:
	and	cs:Flag, not_insert	   ;set not insert flag

Insert_return:
	CALL	Check_It		   ;check tree structure
	ret				   ;return

INSERT	ENDP






;==============================================================================
;Subroutine: DELETE
;
;INPUT:   DS:SI -> path
;	  ES:BP -> drive_cache_header (for Look_Up operation)
;
;OUTPUT:  if found, then remove the matching Name_Record will be removed from
;	  the tree and from the LRU chain.  The freed entry will be placed
;	  on top of the LRU chain.
;
;==============================================================================

DELETE	 PROC	FAR

	mov	cs:func_cod,al		   ; save function code
	CALL	PACK_DIR_NAME		   ;drive letter => DL, 				      ;AN000;

	CALL	FIND_DRIVE_CACHE_HEADER    ;find drive header address
					   ;on return ES:BP-->drive header
	jc	d_err_exit		   ;error exit

	or	cs:Flag, is_delete	   ;set the flag for Look_up.
	push	ds			   ;save DS in BX since it is going to be
	pop	bx			   ;changed for jumping to other segment
	push	ds
	mov	ax,cseg_Main
	mov	ds,ax			   ;DS=Main segment ID
	assume	ds:Cseg_Main
	mov	cs:From_Delete, 1	   ;set flag indicate that the call
					   ;is from DELETE function
	CALL	VECTOR_LOOKUP		   ;FAR call to Lookup function

	mov	cs:From_Delete, 0	   ;reset from delete flag
	pop	ds
	ASSUME	ds:nothing
	jc	D_err_Exit		   ;indirectly in the same segment

	cmp	byte ptr ds:[si], 0	   ;found the whole path?
	jne	D_err_Exit		   ;No.

;At this point, Current_Node = BP.
	mov	bx, cs:Open_Name_Cache_Seg
	mov	es, bx			   ;set ES to name_cache_seg.
	ASSUME	es:Cseg_Init

Delete_Node:
	cmp	es:[bp.nChild_ptr], -1	   ;No children?
	jne	D_err_Exit
	CALL	REMOVEFROMTREE		   ;remove the node while maintaing the
					   ;integrity of the tree.

	mov	es:[bp.nCmpct_Dir_Info], ' ' ;mark that this entry is free!!!

D_LRU_MRU:
	CALL	REMOVEFROMLRUCHAIN	   ;Remove BP from the LRU,MRU chain

	mov	si, cs:Current_Drive	   ;Now let the deleted node to be the
	push	ds			   ; LRU node
	mov	ds,cs:Open_Name_Cache_Seg
	ASSUME	ds:Cseg_Init									   ;AN000;

	mov	bx, ds:[si.DCH_LRU_ROOT]   ;es:bx -> first node
	mov	es:[bp.nLRU_ptr],bx	   ;Target.nLRU_ptr -> first node
	mov	es:[bx.nMRU_ptr],bp	   ;First_node.nMRU_ptr -> target
	mov	ds:[si.DCH_LRU_ROOT],bp    ;LRU_ROOT -> target
	mov	es:[bp.nMRU_ptr],-1
	pop	ds
	ASSUME	ds:nothing
	jmp	short	D_Exit		   ;exit

D_err_Exit:				   ;error exit
	stc
	mov	ax, -1

D_Exit:
	and	cs:Flag, not_delete	   ;reset the flag
	CALL	Check_It		   ;check tree structure
	ret				   ;return

DELETE	 ENDP






;==============================================================================
;Subroutine: RENAME
;
;INPUT:   DS:SI -> path
;	  DS:DI -> New directory name
;
;OUTPUT:  If found, rename the current directory name entry by the destination
;	  name.
;==============================================================================

RENAME	 PROC	FAR

	PUSH	DI			   ;;BN00X save new name address
	mov	cs:func_cod,al		   ;;BN00X save function code
	CALL	PACK_DIR_NAME		   ;;BN00X drive letter => DL,					     ;AN000;

	CALL	FIND_DRIVE_CACHE_HEADER    ;;BN00X find drive header address
					   ;;BN00X on return ES:BP-->drive header
	POP	DI			   ;;BN00X restore new name address
	jc	R_err_exit		   ;;BN00X error exit

	or	cs:Flag, is_delete	   ;;BN00X set the flag for Look_up.
	push	ds			   ;;BN00X save DS in BX since it is going to be
	pop	bx			   ;;BN00X changed for jumping to other segment
	push	ds
	mov	ax,cseg_Main
	mov	ds,ax			   ;;BN00X DS=Main segment ID
	assume	ds:Cseg_Main
	mov	cs:From_Delete, 1	   ;;BN00X set flag indicate that the call
					   ;;BN00X is from DELETE function
	PUSH	DI			   ;;BN00X save new name address
	CALL	VECTOR_LOOKUP		   ;;BN00X FAR call to Lookup function

	mov	cs:From_Delete, 0	   ;;BN00X reset from delete flag
	POP	DI			   ;;BN00X restore new name address
	pop	ds
	ASSUME	ds:nothing
	jc	D_err_Exit		   ;;BN00X indirectly in the same segment

	cmp	byte ptr ds:[si], 0	   ;;BN00X found the whole path?
	jne	D_err_Exit		   ;;BN00X No.

;At this point, Current_Node = BP.
	mov	bx, cs:Open_Name_Cache_Seg ;;BN00X
	mov	es, bx			   ;;BN00X set ES to name_cache_seg.
	ASSUME	es:Cseg_Init

; Rename the directory name entry by the destination name
Rename_Node:
	mov	si,di			   ;;BN00X SI-->new name
	mov	di,bp			   ;;BN00X DI-->directory name entry
	add	di,nCmpct_Dir_Info	   ;;BN00X ES:DI-->directory area
	mov	cx,11			   ;;BN00X 11 bytes
	REP	MOVSB			   ;;BN00X copy new name to the entry
	CLC
	JMP	SHORT R_Exit		   ;;BN00X then return

R_err_Exit:				   ;;BN00X error exit
	stc				   ;;BN00X
	mov	ax, -1			   ;;BN00X

R_Exit:
	and	cs:Flag, not_delete	   ;;BN00X reset the flag
	CALL	Check_It		   ;;BN00X check tree structure
	ret				   ;;BN00X return

RENAME	 ENDP				   ;;BN00X




;==============================================================================
;Subroutine:  UPDATE
;
;INPUT:  If AH = 0, then update Dir_Entry area.
;	     ES:DI -> Dir_entry ("dir_first" is the key to search).
;	     DL = Logical Drive number (0 = A, 1 = B, ...).
;
;	 If AH = 1, then update "Fastopen_extended_info.FEI_clusnum".
;	     DL = Logical Drive number (0 = A, 1 = B, ...)
;	     CX = The value of "dir_first" to search.
;	     BP = new value of FEI_clusnum in the extended_info area.
;
;	If AH = 2, then delete the entry. Same effect as Delete function
;	     except this time the keys used to delete are;
;	     DL = logical drive number
;	     CX = the value of "dir_first" to search.
;
;	If AH = 3, then delete the entry. Same effect as Delete function
;	     except this time the keys used to delete are;
;	     DL = logical drive number
;	     DH = directory position
;	     DI = directory sector (low value)
;	     CX = directory sector (high value)
;
;
;OUT:  if found, then data is updated
;      else CY and AX = -1.
;
;    This routine use "starting cluster number" and "drive letter"
;    as a key to find the name record.	Usually the reason is DOS
;    does not have any "full path" information about the file when
;    it has to call this routine to update the information.
;    It follows the MRU chain until it finds the name record or
;    until it reaches the free name record (identified by the
;    Directory name starting with ' '), or until the end of
;    the MRU chain.
;
;==============================================================================

UPDATE	PROC	FAR

	mov	cs:func_cod,al		 ; save function code
	cmp	ah, 0			 ;update directory entry ?
	je	Update_Dir_Entry	 ;yes-
	cmp	ah, 1			 ;update extended info ?
	je	Update_Extended_clusnum  ;yes-
	cmp	ah, 2			 ;delete based on first clus num ?
	je	Update_Delete		 ;yes-
	cmp	ah, 3			 ;delete based directory sector ?
	je	Update_Delete1		 ;yes-

U_ERROR:				 ;no - error exit
	stc
	jmp	short  Update_Exit

Update_Delete:				; same as delete
	CALL	FIND_CLUSTER_NUMBER	; find name entry using first cluster
	jc	U_ERROR
	jmp	Delete_Node		; if found, delete entry

Update_Delete1: 			; same as delete (PTR P3718  3/10/88)
	CALL	FIND_DIR_SECTOR 	; find name entry using directory
	jc	U_ERROR 		; sector and directory position
	jmp	Delete_Node		; if found, delete node

Update_Dir_Entry:
	mov	cx, es:[di.dir_first]
	push	es			;save Dir_Info pointer ES:DI
	push	di
	CALL	FIND_CLUSTER_NUMBER
	pop	si			;restore Dir_Info pointer in DS:SI
	pop	ds
	jc	U_ERROR 		;error-if not found

	push	bp			;found the entry
	pop	di
	add	di, nCmpct_Dir_Info	;ES:DI->Name_Record.nCmpct_Dir_Info
	mov	cx, ODI_head_leng
	REP	MOVSB			;update Cmpct_dir_info head section
	add	si, ODI_skip_leng
	mov	cx, ODI_tail_leng
	REP	MOVSB			;update tail section
	jmp	short  Update_Exit	;exit

Update_Extended_clusnum:		;update extended info field
	mov	cs:New_FEI_clusnum,bp
	CALL	FIND_CLUSTER_NUMBER	;Find entry based first cluster number
	jc	U_ERROR

	add	bp, nExtended_Info	;es:bp -> Name_record.nExtended_Info
	mov	bx, cs:New_FEI_clusnum
	mov	es:[bp.FEI_clusnum],bx

Update_Exit:
	CALL	Check_It		   ;check tree structure
	ret				;return

UPDATE	 ENDP






;==============================================================================
;Subroutine:  FP_PURGE	  Rest Name Cache Buffers
;
;INPUT:  Main_Name_Drive_Buff  -  Offset to Name cache buffer
;	 Main_Name_Cache_Seg   -  Name cache seg id
;	 DL = Drive ID
;
;OUT:	 Buffer is purged
;
;==============================================================================

FP_PURGE   PROC    FAR

	mov	si,Open_Name_Drive_Buff        ; SI-->first Name drive cache buff
	mov	es,Open_Name_Cache_Seg	       ; ES = name cache seg ID
	mov	bx,es:[si].DCH_Name_Buff       ; BX-->Name cache buffer
	inc	dl			       ; DL=drive number
	add	dl,040H 		       ; convert drive num to drive letter

; Search for the name drive header corresponds to the drive letter
Purge_Drv_Loop:
	cmp	es:[si].DCH_Drive_Letter,dl    ; drive letter match ??
	je	Purge_drive_cache	       ; yes - set drive cache
	add	si, size Drive_Cache_Header    ; no - get address of next drive cache
	jmp	purge_drv_loop		       ; try next name drive header

Purge_Drive_Cache:			       ; SI-->drive header
	mov	bx,es:[si].DCH_Name_Buff       ; BX-->Name cache buffer
	mov	cx,es:[si].DCH_num_entries     ; get number of name records
	mov	ax,bx			       ; save last name record address
	mov	es:[bx].nMRU_ptr, -1	       ; make first MRU -1
	jmp	short set_start

Set_Up_Names:
	mov	es:[bx].nMRU_ptr,ax		;save last name record as MRU entry
	add	ax, size Name_Record		;AX = last name record = current name record

Set_Start:
	mov	es:[bx].nChild_ptr, -1		;no children or siblings
	mov	es:[bx].nsibling_ptr, -1	;right now
	mov	es:[bx].nBackward_ptr, -1

	push	di
	push	ax
	mov	ax, '  '                        ;AX = ' '
	mov	di, bx				;DI-->current name record
	add	di, nCmpct_Dir_Info		;blank out the Dir name area
	stosb					;in the name record
	stosw
	stosw
	stosw
	stosw
	stosw
	pop	ax				; AX = last name record address
	pop	di

	dec	cx				;update record count
	jcxz	purge_exit			;exit if last name record is done
	mov	dx,bx
	add	dx, size Name_Record		;DX-->next name record
	mov	es:[bx].nLRU_ptr,dx		   ;set LRU pointer - next name record
	add	bx, size Name_Record
	jmp	set_up_names			;set next name record

Purge_exit:
	clc
	ret

FP_PURGE    ENDP



;----------------------------------------------------------------------------
;		    FASTOPEN  SUPPORT ROUTINES
;----------------------------------------------------------------------------
;
; PROCEDURE:  Find_Drive_Cache_Header
;
; Function:  Validate drive ID and find address of drive cache header
;
;IN:   DL - drive letter
;      Drive_Header_Start ;label
;      Flag.
;
;OUT:  If CY = 0    Drive Header found
;	     ES:BP -> Drive_Cache_Header,
;
;      If CY = 1    Drive Header not found
;
;----------------------------------------------------------------------------

FIND_DRIVE_CACHE_HEADER   PROC	  NEAR

	mov	bp, cs:Open_name_drive_buff
	push	ds
	mov	ds,cs:Open_Name_Cache_Seg							   ;AN000;
	ASSUME	ds:Cseg_Init				;DS:BP-->first drive header    ;AN000;

FDCH_while:
	cmp	byte ptr ds:[bp.DCH_Drive_Letter], dl	; drive letter match
	jne	fdch_chk_end				; no - check next header
	clc						; yes - exit
	jmp	short FDCH_Exit

FDCH_Chk_End:
	cmp	byte ptr ds:[bp.DCH_Sibling_ptr], -1	; is this last header ?
	je	FDCH_Not_Found				; yes - header not found
	add	bp, size Drive_Cache_Header		; no - get next header
	jmp	short	FDCH_while			; look for match

FDCH_Not_Found:
	stc						;not found

FDCH_Exit:						;ES:BP-->header if found
	pop    ds
	ASSUME ds:nothing				;return
	ret

FIND_DRIVE_CACHE_HEADER     endp




;----------------------------------------------------------------------
; PROCEDURE:  GET_FREE_NODE
;
; called by Insert. The LRU node pointed DCH_LRU_ROOT is returned in AX
; and DCH_LRU_ROOT points to the following node in LRU chain.
; If the node is not an empty node, then it will be removed from the
; tree.
;
; IN:  Current_Drive, Current_Node
;
; OUT: AX = offset of the free node in Name_Cache_Seg
;     Other registers saved.
;----------------------------------------------------------------------

GET_FREE_NODE	PROC	NEAR

	push	es				 ;save registers
	push	di
	push	si
	push	bp

	mov	ax, cs:Open_Name_Cache_Seg							   ;AN000;
	mov	es, ax				;ES=Name cache segment				  ;AN000;
	ASSUME	es:Cseg_Init									   ;AN000;
	mov	si, cs:Current_Drive		;SI-->drive_cache_header
	push	ds
	mov	ds,cs:Open_Name_Cache_Seg
	ASSUME	ds:Cseg_Init
	mov	ax, ds:[si.DCH_LRU_ROOT]	;get the LRU node
	pop	ds
	ASSUME	ds:nothing

	cmp	ax, cs:current_Node		;LRU node=Current Node ??
	je	GFN_skip			;yes-

	mov	bp, ax				;BP=Current node
	mov	di, es:[bp.nLRU_ptr]		;DI= current LRU node's following node
	mov	es:[di.nMRU_ptr],-1		;set that node's MRU ptr
	push	ds
	mov	ds,cs:Open_Name_Cache_Seg	;DS=Name cache segment
	ASSUME	ds:Cseg_Init
	mov	ds:[si.DCH_LRU_ROOT],di 	;connect previous node to
	pop	ds				;next node
	ASSUME	ds:nothing

	cmp	byte ptr es:[bp.nCmpct_Dir_Info],' ';an empty node?
	je	GFN_OK			      ;then no problem.

	CALL	RemoveFromTree		    ;otherwise, it should be removed
					    ;from the tree.
GFN_OK:
	clc
	jmp	short	GFN_ret

GFN_Skip:
	stc

GFN_ret:
	pop	bp
	pop	si
	pop	di
	pop	es
	ret				    ;return

GET_FREE_NODE	endp




;
;----------------------------------------------------------------------
; PROCEDURE:   PRE_LRU_STACK
;
; When called by Look_up, Insert routine, the requested target node (BP)
; will be temporarily removed from LRU,MRU chain (until SET_LRU routine
; call), and will be pushed into a logical stack.  Actually, this routine
; will not use a stack, but try to get the effect of the use of stack
; to keep the history of target nodes in "REVERSE" LRU order as follows;
; {    inc Depth;
;      if Depth == 1 then Bottom = BP;
;			  Bottom.LRU_ptr = -1;
;			  Bottom.MRU_ptr = -1;
; else if Depth == 2 then Top = BP;
;			  Top.LRU_ptr = Bottom;
;			  Bottom.MRU_ptr = Top;
;			  Top.MRU_ptr = -1;
; else if Depth >= 3 then Temp = Top;
;			  Top = BP;
;			  Top.LRU_ptr = Temp;
;			  Temp.MRU_ptr = Top;
;			  Top.MRU_ptr = -1;
; }
;
;IN:  Depth, Top, Bottom, Temp,
;     Requested target node (BP)
;     ES = Name_Cache_Seg
;
;OUT: Target node removed from LRU,MRU chain.
;     Target node's history saved in reverse LRU order.
;     If called by "Delete" routine, then will just exit.
;     If called by "Insert" routine, then will not attempt
;     to remove the target node.
;----------------------------------------------------------------------

PRE_LRU_STACK	PROC	NEAR

	test	cs:Flag, is_delete	   ;invoked by Delete routine
	jnz	PLS_Exit
	test	cs:Flag, is_insert	   ;called by Insert routine
	jnz	PLS_Push

	CALL	RemoveFromLRUChain	;remove BP from the chain.

PLS_Push:
	push	di

	inc	cs:Depth
	cmp	cs:Depth, 1
	jne	PLS_Top
	mov	cs:Bottom, bp		   ;bottom = bp
	mov	es:[bp.nLRU_ptr], -1
	jmp	short PLS_Done

PLS_Top:
	cmp	cs:Depth, 2
	jne	PLS_Temp
	mov	cs:Top, bp		   ;Top = bp
	mov	di, cs:bottom		   ;di = bottom

PLS_com:
	mov	es:[bp.nLRU_ptr],di	;Top.LRU_ptr = bottom
	mov	es:[di.nMRU_ptr],bp	;Bottom.MRU_ptr = top
	jmp	short PLS_Done

PLS_Temp:
	mov	di, cs:Top		   ;di = Top
	mov	cs:Temp, di		   ;Temp = di
	mov	cs:Top, bp		   ;Top = bp
	jmp	short PLS_com

PLS_Done:
	mov	es:[bp.nMRU_ptr],-1
	pop	di

PLS_Exit:
	ret

PRE_LRU_STACK	endp
;





;----------------------------------------------------------------------
;PROCEDURE:  SET_LRU
;
;INPUT:  Depth, Top, Bottom, Current_Drive, ES = Name_Cache_Seg
;
;OUT: If Depth == 0 then exit
; Pre_LRU_Stack procedure already maintained a reverse order LRU
; mini chain.  Set_LRU will just put the top after the last node
; of the current LRU chain;
; { Get the last node of LRU chain.
;   if Depth == 0 then exit;
;   if Depth == 1 then Last_Node.LRU_ptr = Bottom;
;		       Bottom.MRU_ptr = Last_Node;
;		       MRU_ROOT = Bottom;
;   if Depth >= 2 then Last_Node.LRU_ptr = Top;
;		       Top.MRU_ptr = Last_Node;
;		       MRU_ROOT = Bottom;
;   Depth = 0;
; }
;----------------------------------------------------------------------

SET_LRU   PROC	  NEAR

	cmp	cs:Depth, 0		   ;nothing in the stack?
	je	SL_Exit

	push	si
	push	di
	push	bx
	mov	si, cs:Current_Drive	   ;cs:si -> Drive_Cache_Header
	push	ds
	mov	ds,cs:Open_Name_Cache_Seg							   ;AN000;
	ASSUME	ds:Cseg_Init									   ;AN000;
	mov	di, ds:[si.DCH_MRU_ROOT] ;es:di -> Last node in LRU chain

	cmp	cs:Depth, 1
	jne	SL_Other

	mov	bx, cs:Bottom
	mov	es:[di.nLRU_ptr],bx	;Last_Node.LRU_ptr = Bottom
	mov	es:[bx.nMRU_ptr],di	;Bottom.MRU_ptr = Last_Node
	mov	ds:[si.DCH_MRU_ROOT],bx ;MRU_ROOT = Bottom
	jmp	short SL_Done

SL_Other:				;Depth >= 2
	mov	bx, cs:Top
	mov	es:[di.nLRU_ptr],bx
	mov	es:[bx.nMRU_ptr],di
	mov	bx, cs:Bottom
	mov	ds:[si.DCH_MRU_ROOT],bx

SL_Done:
	pop	ds
	ASSUME	ds:nothing
	mov	cs:Depth, 0		   ;reset the Depth
	pop	bx
	pop	di
	pop	si

SL_Exit:
	ret

Set_LRU endp





;----------------------------------------------------------------------
; Procedure RemoveFromLRUChain
;
;IN:  Target node (BP) to be removed
;     Current_drive
;     ES - Name_Cache_Seg
;
;OUT:  Target node removed from the LRU,MRU chain. LRU,MRU chain
;     updated.
;
;----------------------------------------------------------------------

RemoveFromLRUChain     PROC    near

	push	bx
	push	di
	push	si

	mov	si, cs:Current_drive	   ;cs:si-> Drive_cache_header
	mov	bx, es:[bp.nMRU_ptr]	;es:bx-> Preceding node
	mov	di, es:[bp.nLRU_ptr]	;es:di-> Following node
	cmp	bx, -1			;Is target the first node?
	je	RFLC_first_node
	cmp	di, -1			;Is target the last node of LRU chain?
	je	RFLC_last_node
	mov	es:[bx.nLRU_ptr],di	;Preceding.LRU_ptr->following node
	mov	es:[di.nMRU_ptr],bx	;Following.MRU_ptr->preceding node
	jmp	short RFLC_done

RFLC_first_node:
	push	ds
	mov	ds,cs:Open_Name_Cache_Seg							   ;AN000;
	ASSUME	ds:Cseg_Init									   ;AN000;
	mov	ds:[si.DCH_LRU_ROOT],di ;LRU_ROOT-> following node
	pop	ds
	ASSUME	ds:nothing
	mov	es:[di.nMRU_ptr], -1	;Following node's MRU_ptr
	jmp	short RFLC_done

RFLC_last_node:
	push	ds
	mov	ds,cs:Open_Name_Cache_Seg
	ASSUME	ds:Cseg_Init
	mov	ds:[si.DCH_MRU_ROOT],bx ;MRU_ROOT-> preceding node
	mov	es:[bx.nLRU_ptr], -1	;Preceding node's LRU_ptr
	pop	ds
	ASSUME	ds:nothing

RFLC_done:
	pop	si
	pop	di
	pop	bx
	ret

RemoveFromLRUChain	endp
;



;----------------------------------------------------------------------
; Proceure  RemoveFromTree
;
;IN:  BP - offset of node to be removed from the tree
;	   This node shoud not be a subdirectory that is not empty!!!
;      ES - Name_Cache_Seg
;      Current_Drive
;
;OUT:  The node will be freed from the tree.
;     The neighbor's Child_ptr, Sibling_ptr, Backward_ptr are adjusted
;     accordingly.
;     The freed node's child_ptr, sibling_ptr, backward_ptr are reset to -1.
;----------------------------------------------------------------------

REMOVEFROMTREE	 PROC	 NEAR

	push	bx
	push	dx

	mov	bx, es:[bp.nBackward_ptr]	;get the preceding node
	mov	dx, es:[bp.nSibling_ptr]	;get the sibling node
	cmp	bx, cs:Current_Drive
	je	RFT_First_Child 	;bp is the first child
	cmp	es:[bx.nChild_ptr],bp
	je	RFT_Child		;bp is the child of the preceding node
	mov	es:[bx.nSibling_ptr],dx ;bp is the Sibling of the preceding node
					;Update the preceding node's Sibling ptr
	jmp	short RFT_Reset

RFT_First_Child:
	push	ds
	mov	ds,cs:Open_Name_Cache_Seg							   ;AN000;
	ASSUME	ds:Cseg_Init									   ;AN000;
	mov	ds:[bx.DCH_Child_ptr],dx
	pop	ds
	ASSUME	ds:nothing
	jmp	short RFT_Reset

RFT_Child:
	mov	es:[bx.nChild_ptr],dx

RFT_Reset:				;reset the deleted node's tree pointers
	mov	es:[bp.nChild_ptr],-1
	mov	es:[bp.nSibling_ptr],-1
	mov	es:[bp.nBackward_ptr],-1

	xchg	dx,bx			;now, dx=preceding node, bx=following node
	cmp	bx,-1			;end of sibling?
	je	RFT_ret
	mov	es:[bx.nBackward_ptr],dx;modify backward_ptr of the sibling node

RFT_ret:
	pop	dx
	pop	bx
	ret				;return

REMOVEFROMTREE	endp
;




;----------------------------------------------------------------------
; Procedure CMPARE
;
;IN: Packed name
;    BP = target node
;
;OUT: ZERO flag set when compare O.K.
;     DI destroyed.
;----------------------------------------------------------------------

CMPARE	PROC	near

	push	ds
	push	si
	mov	cx, 11
	push	cs
	pop	ds
	mov	si, offset cs:Packed_Name	   ;ds:si -> Packed_Name
	mov	di,bp
	add	di,nCmpct_Dir_Info		;es:di -> bp.nCmpact_Dir_Info
	REPE	CMPSB
	pop	si
	pop	ds
	ret
CMPARE	endp




;
;----------------------------------------------------------------------
; Procedure:  MAKE_NAME_RECORD
;
;IN:  DS:DI -> Dir_Info, ES:BX -> Extended_Info
;     AX = offset of the Name_Record entry in Name_Cache_Seg.
;
;OUT: Name_Record in Name_Cache_Seg filled.
;     nLRU_ptr, nChild_ptr, nSibling_ptr and nMRU_ptr are set to -1 for now.
;     ES, SI, DI destroyed.  ES will be Name_Cache_Seg.
;----------------------------------------------------------------------

MAKE_NAME_RECORD      PROC     NEAR

	push	ds			;save DS
	push	ax

	push	es			;save Extended_Info seg in DOS
	push	di
	pop	si			;DS:SI -> Dir_Info
	mov	di, cs:Open_Name_Cache_Seg							   ;AN000;
	mov	es, di										   ;AN000;
	ASSUME	es:Cseg_Init									   ;AN000;
	mov	di, ax			;ES:DI -> Name_Record
	mov	ax, -1
	mov	es:[di.nLRU_ptr],ax	;initialize pointers
	mov	es:[di.nChild_ptr],ax
	mov	es:[di.nSibling_ptr],ax
	mov	es:[di.nMRU_ptr],ax
	add	di, nCmpct_Dir_Info	;ES:DI -> Name_Record.nCmpct_Dir_Info
	mov	cx, ODI_head_leng	;currently 10.
	rep	movsb			;Move header part
	add	si, ODI_skip_leng	;DS:SI -> tail part of Dir_Info
	mov	cx, ODI_tail_leng
	REP	MOVSB			;move tail part.

	pop	ds			;restore Extended_Info seg in DS!!!
	mov	si, bx			;DS:SI -> Extended_Info
	mov	cx, size Fastopen_Extended_Info
	rep	movsb			;Move Extended_Info
	pop	ax
	pop	ds			;Restore DS

	ret				;return

MAKE_NAME_RECORD	ENDP
;




;----------------------------------------------------------------------
; Procedure Unfold_Name_Record
;
;IN:  Matching_Node, ES:DI -> Dir_Info buffer, ES:CX -> Extended_Info buffer
;
;OUT: if no matching node is found, then just return
;     else Dir_Info, Extended_Info buffer are filled.
;----------------------------------------------------------------------

Unfold_Name_Record     PROC	near

	cmp	cs:Matching_Node, -1
	je	UNR_Exit		;just exit
	push	ds
	push	si
	push	di
	push	cx			;save extended_info addr

	mov	si, cs:Open_Name_Cache_Seg							   ;AN000;
	mov	ds, si										   ;AN000;
	ASSUME	ds:Cseg_Init									   ;AN000;
	mov	si, cs:Matching_Node
	add	si, nCmpct_Dir_Info	;DS:SI -> Cmpct_Dir_Info

	mov	cx, ODI_head_leng	;Dir_Info header length
	REP	MOVSB			;Cmpct_Dir_Info.CDI_file_name -> ODI_head

	add	di, ODI_skip_leng	;length of Skiped part of Dir_Info
	mov	cx, ODI_tail_leng	;Dir_Info tail length
	REP	movsb			;Cmpct_Dir_Info.CDI_Time -> ODI_tail
					;At this moment, SI -> nExtended_Info

	pop	di			;ES:DI -> Extended_info
	push	di			;save di again for cx.
	mov	cx, size Fastopen_Extended_Info
	REP	movsb

	pop	cx			;restore extended_info addr
	pop	di
	pop	si
	pop	ds
	ASSUME	ds:nothing
UNR_Exit:
	ret				;return

Unfold_Name_Record	endp
;




;----------------------------------------------------------------------
; PROCEDURE:  PACK DIR_NAME
;
; Parse the name off of DS:SI into Packed_Name.  If called first time and
; DS:[SI+1] = ':' then it is ASSUMEd to be a drive letter and it will be
; returned in DL and SI will points to '\' after ':'.  If it was a directory
; name then Packed_Name will be set and SI points to '\' or 0 after the
; parsed directory name or filename.  This routine will check DS:[SI] when
; called to see if it points to '\' or 0.  If it points to '\' then
; it is ASSUMEd that the user want to skip the delimiter.  If it was 0,
; then this routine will set carry.  So, with a given drive,path string,
; the user is going to keep calling this routine until it returns
; with carry set that tells the end.
;----------------------------------------------------------------------

PACK_DIR_NAME	PROC	NEAR

	cmp	byte ptr ds:[si], 0	;end of path ??
	jne	PDN_Drive		;no-check for drive letter
	stc
	jmp	short PDN_Exit		;yes-exit

PDN_Drive:
	cmp	byte ptr ds:[si+1], ':' ;drive letter terminater?
	jnz	PDN_chk_skip		;no -
	mov	dl, byte ptr ds:[si]	;yes-set DL to the drive letter
	inc	si
	inc	si			;set SI -> '\' after ':'
	jmp	short PDN_Exit		;then exit

PDN_chk_skip:
	cmp	byte ptr ds:[si], '\'   ;delimeter?
	jne	PDN_Path		;no-
	inc	si			;yes-skip delimiter
	cmp	byte ptr ds:[si], 0	;end of path ??
	jne	PDN_Path		;no-pack path name
	stc				;yes-In fact, the input from DOS was
	mov	ax, -1			;D:\,0.  FASTOPEN will treate
	jmp	short PDN_Exit		;this as an error.

PDN_Path:				;pack path name
	push	es
	push	di
	push	ax

	push	cs
	pop	es
	mov	di, offset cs:Packed_Name   ;ES:DI-->pack buffer

	mov	ax,'  '
	STOSB				;blank out the Packed_Name
	STOSW
	STOSW
	STOSW
	STOSW
	STOSW
	mov	di, offset cs:Packed_Name

PDN_GetName:
	LODSB				;DS:SI => AL, SI++
	cmp	al,'.'
	jz	PDN_SetExt
	or	al,al
	jz	PDN_GetDone
	cmp	al,'\'
	jz	PDN_GetDone
	STOSB

ifdef DBCS
	call	IsDBCSLeadByte
	jnz	PDN_GetName		; if this is not lead byte
	movsb				; copy tail byte
endif

	jmp	short PDN_GetName

PDN_SetExt:
	mov	di, offset cs:Packed_Name+8

PDN_GetExt:
	LODSB
	or	al,al
	jz	PDN_GetDone
	cmp	al,'\'
	jz	PDN_GetDone

PDN_StoExt:
	STOSB

ifdef DBCS
	call	IsDBCSLeadByte
	jnz	PDN_GetEXT		; if this is not lead byte
	movsb				; copy tail byte
endif

	jmp	PDN_GetExt

PDN_GetDone:
	dec	si			;set SI back to the delimeter or 0.
	pop	ax
	pop	di
	pop	es

PDN_Exit:
	ret

PACK_DIR_NAME	endp





;----------------------------------------------------------------------
; PROCEDURE:  FIND_CLUSTER_NUMBER
;
;IN:  DL = driver # (0 = A, 1 = B,...)
;     CX = The value of Dir_First in Name_Record to search.
;     Search Name_Record entries to find the matching starting cluster number.
;     The search uses MRU chain for efficiency.
;
;OUT: ES = Name_Cache_Seg
;     BP = Name_Record
;     if not found, carry bit.
;     ES, BP register changed.
;----------------------------------------------------------------------

FIND_CLUSTER_NUMBER    PROC    NEAR

	push	ax
	push	cx
	push	dx
	add	dl, 'A'                 ;convert to a drive letter

	CALL	FIND_DRIVE_CACHE_HEADER ;ES:BP -> driver header if found
	jc	FCN_exit		;exit if not found

	mov	dx, cx			;save the key in DX					   ;AN000;
	mov	ax, cs:Open_Name_Cache_Seg							   ;AN000;
	mov	es, ax										   ;AN000;
	ASSUME	es:Cseg_Init

	CALL	SET_LRU 		;clean up the LRU stack

	mov	cs:Current_Drive,bp   ;set Current_Drive (You should not set
					;Current_Drive before SET_LRU at any time!!!
	push	ds
	mov	ds,cs:Open_Name_Cache_Seg
	ASSUME	ds:Cseg_Init
	mov	cx, ds:[bp.DCH_Num_Entries]	;Max number to try
	mov	bp, ds:[bp.DCH_MRU_ROOT]	;get the start of MRU chaing
	pop	ds
	ASSUME	ds:nothing

FCN_while:
	cmp	es:[bp.nCmpct_Dir_Info], ' ' ;Is it a free node ?
	je	FCN_not_found		;then no reason to continue search.

	cmp	dx, es:[bp.nCmpct_Dir_Info.CDI_cluster] ;matching starting cluster # ?
	je	FCN_exit		;found it!!!

	mov	bp, es:[bp.nMRU_ptr]	;next MRU entry address
	cmp	bp, -1			;It was the end of MRU chain?
	je	FCN_not_found		;not found. End of search
	LOOP	FCN_while		;else compare cluster and contine...

FCN_Not_found:
	stc

FCN_exit:
	pop	dx
	pop	cx
	pop	ax
	ret

FIND_CLUSTER_NUMBER	ENDP




;----------------------------------------------------------------------
; PROCEDURE:  FIND_DIR_SECTOR	 (PTR 3718  3/10/88)
;
;     Search Name_Record using directory sector and directory position
;     for the name entry.
;
;IN:  DL = driver # (0 = A, 1 = B,...)
;     DI = Dirctory sector Low	value
;     CX = Dirctory sector high value
;     DH = Dirctory position
;
;OUT: ES = Name_Cache_Seg
;     BP = Name_Record
;     if not found, carry bit.
;     ES, BP register changed.
;----------------------------------------------------------------------

FIND_DIR_SECTOR   PROC	  NEAR

	push	ax
	push	cx
	push	dx
	add	dl, 'A'                  ;convert to a drive letter

	CALL	FIND_DRIVE_CACHE_HEADER  ;ES:BP -> driver header if found
	jc	FDIR_exit		 ; error if not found

	mov	ax, cs:Open_Name_Cache_Seg							   ;AN000;
	mov	es, ax										   ;AN000;
	ASSUME	es:Cseg_Init

	CALL	SET_LRU 		;clean up the LRU stack

	mov	ax,cx			; save directory sector high value
	mov	cs:Current_Drive,bp	;set Current_Drive (You should not set
					;Current_Drive before SET_LRU at any time!!!
	push	ds
	mov	ds,cs:Open_Name_Cache_Seg
	ASSUME	ds:Cseg_Init
	mov	cx, ds:[bp.DCH_Num_Entries]	;Max number to try
	mov	bp, ds:[bp.DCH_MRU_ROOT]	;get the start of MRU chaing
	pop	ds
	ASSUME	ds:nothing

FDIR_while:
	cmp	es:[bp.nCmpct_Dir_Info], ' ' ;Is it a free node ?
	je	FDIR_NOT_FOUND		;then no reason to continue search.

	cmp	di, word ptr es:[bp.nExtended_Info.FEI_dirsec] ;matching directory sector hi?
	jne	FDIR_Next		;check next entry

	cmp	ax, word ptr es:[bp.nExtended_Info.FEI_dirsec+2] ;matching directory sector low ?
	jne	FDIR_Next		;check next entry

	cmp	dh, es:[bp.nExtended_Info.FEI_dirpos] ;matching directory postion ?
	je	FDIR_Exit		;check next entry

FDIR_Next:
	mov	bp, es:[bp.nMRU_ptr]	;next MRU entry address
	cmp	bp, -1			;It was the end of MRU chain?
	je	FDIR_not_found		;not found. End of search
	loop	FDIR_while		;else compare cluster and contine...

FDIR_Not_found: 			; no found
	stc

FDIR_exit:
	pop	dx
	pop	cx
	pop	ax
	ret

FIND_DIR_SECTOR     ENDP





;--------------------------------------------------------------------------
; Procedure: CHECK_IT	   Call Fastopen  Tree Analyser to check the
;			   consistency of the Directory/File Tree strucutre.
;--------------------------------------------------------------------------
CHECK_IT   PROC    NEAR

       pushf				      ; save all registers
       push   ax
       push   bx
       push   cx
       push   dx
       push   si
       push   di
       push   ds
       push   es
       cmp    cs:Chk_flag,0		      ;Fastopen analyser enabled ??
       je     Check_Exit		      ;no - exit

       mov    ax,cs:Open_Name_Cache_Seg       ;yes-set multiplex function call
       mov    es,ax
       mov    ah,091h			      ;load Multiplex ID
       xor    al,al
       xor    cx,cx
       mov    cl,cs:func_cod		      ;CL=Fastopen Function code
       mov    di,cs:Current_Drive	      ;ES:DI-->current drive header
       INT    2FH			      ;call the analyser

Check_Exit:
       pop    es			      ;restore all registers
       pop    ds
       pop    di
       pop    si
       pop    dx
       pop    cx
       pop    bx
       pop    ax
       popf				      ;return
       ret

CHECK_IT    ENDP

ifdef DBCS
;
;	Test if the character is DBCS Lead Byte
;
;	input:	AL = character to check
;	outpit:	ZF = 1 if DBCS Lead Byte
;

	public	DBCSLeadByteTable
DBCSLeadByteTable	dd	0

IsDBCSLeadByte		proc	near
	push	ax
	push	si
	push	ds
	lds	si,cs:DBCSLeadByteTable
idlb_check:
	cmp	word ptr [si],0
	jz	idlb_not		; if end of table
	cmp	al,[si]
	jb	idlb_next		; if below low value
	cmp	al,[si+1]
	jbe	idlb_yes		; if below high value
idlb_next:
	add	si,2			; do next
	jmp	short idlb_check
idlb_not:
	or	al,1			; reset ZF
	jmp	short idlb_end
idlb_yes:
	and	al,0			; set ZF
idlb_end:
	pop	ds
	pop	si
	pop	ax
	ret
IsDBCSLeadByte		endp

endif





; Calculate the size of the CSEG_OPEN Module in bytes
	IF  ($-Cseg_Open) MOD 16								   ;AN000;
	   ORG ($-Cseg_Open)+16-(($-Cseg_Open) MOD 16)						   ;AN000;
	ENDIF											   ;AN000;

END_OPEN   label   word




CSEG_OPEN    ends
     end
