SCCSID	equ	"@(#)fsc.asm	6.6 91/12/16"
	PAGE	,132
TITLE	FSC	File System Control block management code.
;***	File System Control Block management code

;	IBM/Microsoft Confidential

;	Copyright (c) IBM Corporation 1987, 1989
;	Copyright (c) Microsoft Corporation 1987, 1989

;	All Rights Reserved
;
;
;	Bryan Willman, Microsoft, 1987.
;
;	SEE fsc.inc for description of fsc structure and the fsc segment.
;
;	fscInit	    - Init the FSC segment.
;	fscAdd	    - Add an FSC to the FSC segment.
;	fscCheck    - Confirm that FSD is acceptable for loader code.
;	fscNext	    - Step through the list of FSCs.
;	fscFind	    - Find an FSC given an FSD name.
;	fscStrCmp   - Perform name comparison (internal)
;	InitFSCTemplate - build fsc for an fsd
;
;	NOTE ON TERMS
;
;	fsc = file system control block, original structure name, and still
;	      the name for the segment that holds all the control structures.
;
;	fs_entry = an entry in the fsc, names designed to match external
;		   interface names.
;
;	fsd = file system driver, the thing the fs_entry describes.
;

.xlist
.xcref
include dosseg.inc
include basemaca.inc
include fnofs.inc
include osmaca.inc
include dossymc.inc
include dbcs.inc
include error.inc
include vmowner.inc
include protmode.inc
include mi.inc
include shutdown.inc
include vpb.inc
include osconst.inc
include pgexport.inc
include vmexport.inc
include selxport.inc

.cref
.list

	ModName	 , FNO_FSC_ASM
	CpuMode 386

include fsc.inc

	EXTRNFAR	GenRemotehVPB
	EXTRNFAR	w_getprocaddr
	EXTRN		_SELAllocKHB:near
	EXTRN		_SELReallocKHB:near
	EXTRN		UCase:NEAR

	ReferGDT	GDT_FSC,WORD

	ReferInitGlobal pDosGetProcAddr,DWORD

;*  Fixed fields at front of FSC segment

CONSTANTS segment

; This macro MUST correspond to the uncfscentrstruc in fsc.inc.
uncfscentr	macro	vpbid
	db	"&vpbid&"
	dd	0
	dw	0
	dw	FSC_BAD
		endm

; ** Warning ** If this list changes update FSC.H
; Double-word align the UNCfscList (the VPB_IDs must be 32 bits/4 bytes)

	ALIGN 4
	PUBLIC	UNCfscList
UNCfscList  label byte
uncfscentr  UNC1
uncfscentr  UNC2
uncfscentr  UNC3
	PUBLIC	UNCfscLast
UNCfscLast  label byte
uncfscentr  UNC4

CONSTANTS ends

INITDATA segment

;*  fscNameList lists all the names of FS_ entries in the form needed by
;   the FSD loader.
;
;   WARNING ** The names in fscNameList must match the names in FS_ENTRY     **
;	    ** both in spelling and in order.  See fsc.inc.		     **

; for miniFSDs.

mfscnameent	macro	name
	local	a
	db	a-($+1)
	db	"&name&"
	db	0
a	label	byte
		endm
;
;
; macro to create a pointer to a FAR routine without generating linker warnings
; about frame-relative fixups
;
GenPtr	macro	sg,routine
	EXTRNFAR  routine
	CatPrefix <dw offset sg:>,%HYBPRE_,<routine>
	CatPrefix <dw seg    sg:>,%HYBPRE_,<routine>
	endm

; for FSDs.  If an entry point has no emulation, just leave the emulation
; field blank.
; This macro MUST correspond to the fscnameentstruc in fsc.inc.

fscnameent	macro	group,name,emulate
	local	a
	IFNB	<emulate>	; generate extrnfar to emulation routine
	   GenPtr emulate	; store address of emulation routine
	ELSE
	   dd	    0		; otherwise just fill with 0 (no emulation
	ENDIF			; address was given)
	db	group		; functionality group
	db	a-($+1)		; length of name
	db	"&name&"
	db	0
a	label	byte
; BUGBUG may want this dword aligned
		endm

	PUBLIC	fscNameList
fscNameList	label	byte

fscnameent  FSGroup_REQUIRED,FS_ATTRIBUTE
fscnameent  FSGroup_REQUIRED,FS_NAME

fscnameent  FSGroup_REQUIRED,FS_ATTACH
fscnameent  FSGroup_REQUIRED,FS_CHDIR
fscnameent  FSGroup_REQUIRED,FS_CHGFILEPTR
fscnameent  FSGroup_REQUIRED,FS_CLOSE
fscnameent  FSGroup_REQUIRED,FS_COPY
fscnameent  FSGroup_REQUIRED,FS_DELETE
fscnameent  FSGroup_REQUIRED,FS_EXIT
fscnameent  FSGroup_REQUIRED,FS_FILEATTRIBUTE
fscnameent  FSGroup_REQUIRED,FS_FILEINFO
fscnameent  FSGroup_REQUIRED,FS_FILEIO
fscnameent  FSGroup_REQUIRED,FS_FINDCLOSE
fscnameent  FSGroup_REQUIRED,FS_FINDFIRST
fscnameent  FSGroup_REQUIRED,FS_FINDFROMNAME
fscnameent  FSGroup_REQUIRED,FS_FINDNEXT
fscnameent  FSGroup_REQUIRED,FS_FINDNOTIFYCLOSE
fscnameent  FSGroup_REQUIRED,FS_FINDNOTIFYFIRST
fscnameent  FSGroup_REQUIRED,FS_FINDNOTIFYNEXT
fscnameent  FSGroup_REQUIRED,FS_FSINFO
fscnameent  FSGroup_REQUIRED,FS_INIT
fscnameent  FSGroup_REQUIRED,FS_IOCTL
fscnameent  FSGroup_REQUIRED,FS_MKDIR
fscnameent  FSGroup_REQUIRED,FS_MOUNT
fscnameent  FSGroup_REQUIRED,FS_MOVE
fscnameent  FSGroup_REQUIRED,FS_NEWSIZE
fscnameent  FSGroup_REQUIRED,FS_NMPIPE
fscnameent  FSGroup_REQUIRED,FS_OPENCREATE
fscnameent  FSGroup_REQUIRED,FS_PATHINFO
fscnameent  FSGroup_REQUIRED,FS_PROCESSNAME
fscnameent  FSGroup_REQUIRED,FS_READ
fscnameent  FSGroup_REQUIRED,FS_RMDIR
fscnameent  FSGroup_REQUIRED,FS_SETSWAP
fscnameent  FSGroup_REQUIRED,FS_WRITE

fscnameent  FSGroup_PAGEIO,FS_OPENPAGEFILE,<DOSCODE,FS_EM_OpenPageFile>
fscnameent  FSGroup_PAGEIO,FS_ALLOCATEPAGESPACE,<DOSHIGH2CODE,FS_EM_AllocatePageSpace>

; The following item is required by FSDs that set the LOCKINFO bit

fscnameent  FSGroup_FILELOCKS,FS_CANCELLOCKREQUEST
fscnameent  FSGroup_FILELOCKS,FS_FILELOCKS

; The following item is required by UNC redirectors

fscnameent  FSGroup_UNCREQRD,FS_VERIFYUNCNAME

; The following items are last to simplify shutdown

fscnameent  FSGroup_REQUIRED,FS_COMMIT
fscnameent  FSGroup_PAGEIO,FS_DOPAGEIO,<DOSHIGH2CODE,FS_EM_DoPageIO>
fscnameent  FSGroup_REQUIRED,FS_FSCTL
fscnameent  FSGroup_REQUIRED,FS_FLUSHBUF
fscnameent  FSGroup_REQUIRED,FS_SHUTDOWN

; The following items are only used at shutdown time

fscnameent  FSGroup_SDTIMEONLY,FS_SDCHGFILEPTR
fscnameent  FSGroup_SDTIMEONLY,FS_SDFSINFO
fscnameent  FSGroup_SDTIMEONLY,FS_SDREAD
fscnameent  FSGroup_SDTIMEONLY,FS_SDWRITE

	    db	    0,0,0,0,0,0		; Mark end of fscNameList table with
					; 0 length entry.

	PUBLIC	mfscNameList
mfscNameList	label	byte

mfscnameent  MFS_CHGFILEPTR
mfscnameent  MFS_CLOSE
mfscnameent  MFS_INIT
mfscnameent  MFS_OPEN
mfscnameent  MFS_READ
mfscnameent  MFS_TERM

	    db	    0			; Mark end of mfscNameList table with
					; 0 length entry.

INITDATA ends


INITBSS segment
;*  fscInitTemplate is filled in with data values for new FSC entry which
;   fscAdd will then copy into FSC segment.  (Called of fscAdd fills in
;   template, then calls fscAdd, then goes on to next FSD.)  fscInitTemplate
;   is only used for init time serial operations, so simple static storage
;   is adequate.

	PUBLIC	fscInitTemplate
fscInitTemplate FS_ENTRY	<>
	PUBLIC	mfscInitTemplate
mfscInitTemplate mFS_ENTRY	<>

INITBSS ends


	FARCODE INIT
INITCODE segment

;***	fscInit - Init the FSC segment.
;
;	Allocate FSC segment and map GDT_FSC to it, initialize data values.
;
;	EXIT	Success
;		    GDT_FSC selector mapped to FSD segment, segment inited.
;		Failure
;		    InternalError.
;
;	USES	AX, BX, CX, DX, SI, DI, DS, ES, Flags
;
;	ALGORITHM
;	    _SELAllocKHB()
;	    if (error)
;		InternalError
;	    fss_Limit = FSCBASE
;	    fss_ShutdownFlags = NotShutdown
;
Procedure fscInit,NEAR
    ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA
	LocalVar vmacHeapBlockInfo,<size vmac_s>
	EnterProc

	; initialize selector in vmacHeapBlockInfo
	mov	vmacHeapBlockInfo.ac_sel,GSEL GDT_FSC
	; _SELAllocKHB(VM_HKRH_PUBRW,FSCBASE,0,SELAL_ALLOCATED,
	;	       FSCOWNER,&vmacHeapBlockInfo)
	FlatContext <ds,es>,ax
	SSToDS	eax,vmacHeapBlockInfo
	CCALL32 _SELAllocKHB,<VM_HKRH_PUBRW,FSCBASE,0,SELAL_ALLOCATED,FSCOWNER,eax>
	or	eax,eax
	jnz	fsie1			    ; allocation failed
	mov	ax,GSEL GDT_FSC
	mov	ds,ax			    ; (DS) = GDT_FSC = FSC segment
	mov	ds:[fss_Limit],FSCBASE
	MOV	DS:[fss_ShutdownFlags],NotShutdown
	MOV	DS:[fss_SDWaitCount],0	; no processes in critical sections

	LeaveProc
	ret

fsie1:	InternalError <fscInit: Could not allocate FSC segment>

EndProc	  fscInit


;***	fscAdd - Add an FSC to the FSC segment
;
;	fscAdd makes an FSC entry in the FSC segment using the pointers
;	in fscInitTemplate.  No checking is done on the rationality of
;	the template, the caller is responsible for that.
;	(see fscCheck)
;
;	fscAdd will save FSC pointer and hVPB when a UNC FSD is installed.
;
;	ENTRY	fscInitTemplate contains the FSC to be added
;
;	EXIT	(AX) = 0
;		    FSC added to segment, control vars updated, etc.
;
;		(AX) = ERROR_NOT_ENOUGH_MEMORY
;		    FSC address would have overlapped reserved area, OR
;		    GDT_FSC could not be grown
;
;	USES	AX, BX, CX, DX, SI, DI, DS, ES, flags
;
;	ALGORITHM
;	    if ((fss_Limit + size FS_ENTRY) >= FSC_RESERVED)
;		return carry
;
;	    if (_SELReallocKHB(GDT_FSC, fss_limit + size FS_ENTRY) == error)
;		return carry
;
;	    memcpy(fscInitTemplate, GDT_FSC:fss_limit, size FS_ENTRY)
;
;	    p = fscInitTemplate.FS_ATTRIBUTE
;	    if (*p & FS_ATTR_UNC)
;		UNCfsclist.currententry.fscptr = fss_limit;
;		UNCfsclist.currententry.hvpb   = genremotehvpb();
;
;	    fss_limit = fss_limit + size FS_ENTRY
;
;	    return carry clear
;
Procedure fscAdd,NEAR
    ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA
	LocalVar ulHeapBlockAddr,DWORD
	LocalVar fsa_VPB,<size vpb>	; Temp space for generating a new VPB
	EnterProc
	MOV	EBX,GSEL GDT_FSC	 ; (BX) = GDT_FSC
	MOV	DS,BX			 ; (DS) -> FSC segment

	CMP	DS:[fss_Limit],FSC_RESERVED-(SIZE FS_ENTRY)
	JAE	fsa15			; Overrun reserves, go complain

	MOVZX	EAX,DS:[fss_Limit]	; (EAX) = current size
	ADD	EAX,size FS_ENTRY	; (EAX) = new size, check against
					; FSC_RESERVED prohibits > 64K.

	; _SELReallocKHB(VM_HKRH_PUBRW,GDT_FSC,newsize,0,&ulHeapBlockAddr)
	FlatContext <ds,es>,cx
	SSToDS	ecx,ulHeapBlockAddr
	CCALL32 _SELReallocKHB,<VM_HKRH_PUBRW,ebx,eax,0,ecx>
	or	eax,eax
	JNZ	fsa15			; grow failed, go complain.

	MOV	DI,GSEL GDT_FSC
	MOV	ES,DI
    ASSUME ES:NOTHING
	MOV	DI,ES:[fss_Limit]	; (ES:DI) -> new FSC entry

	MOV	SI,offset DOSINITDATA:fscInitTemplate
	DosInitContext DS		; (DS:SI) -> Template

	.errnz	(size FS_ENTRY AND 1)
	MOV	CX,size FS_ENTRY / 2
	REP	MOVSW			; Perform copy into FSC segment

	LDS	SI,fscInitTemplate.FS_ATTRIBUTE	 ; (DS:SI) -> new FSD attributes

	.errnz	(FS_ATTR_UNC GT 0ffh)
	TEST	byte ptr DS:[SI],FS_ATTR_UNC
	JZ	fsa05			; NOT UNC, go skip UNCfsc update
;
; For UNC FSDs, we save the FSC pointer in the current UNCfscList entry.
; We take the VPB_ID from the current UNCfscList entry and put it in the VPB.
; We also allocate a VPB for the FSD that will never go away. This simplifies
; the code a little since there is less special casing to be done for UNC names
; when manipulating VPBs, and most of the code then falls through.
;

	mov	ax,es:[fss_Limit]
	SaveReg <es,di>
	DosContext ds
	mov	si,offset ds:UNCfscList
fsa03:	cmp	ds:[si].UNCfsc_Active,FSC_BAD  ; Is this UNC holder being used?
	je	fsa04
	add	si,size uncfscentrstruc
	cmp	si,offset ds:UNCfscLast
	jbe	fsa03
	stc
	mov	ax,ERROR_UNC_DRIVER_NOT_INSTALLED
	pop	di			; Restore di,es pushed onto stack
	pop	es			; before jumping to exit the routine
	jmp short fsa10
fsa04:	mov	ds:[si].UNCfsc_FSCptr.Offst,ax
	mov	ds:[si].UNCfsc_FSCptr.Segmt,es
	lea	di,fsa_VPB			; TASKAREA:DI -> VPB
	mov	TASKAREA:[di].vpb_FSC.Offst,ax
	mov	TASKAREA:[di].vpb_FSC.Segmt,es	; Set up vpb_FSC
	mov	eax,ds:[si].UNCfsc_VpbID
	mov	TASKAREA:[di].vpb_fsi.vpi_ID,eax ; Put the VPB_ID into the vpb

	SaveReg <ds,si>
	xor	al,al			; Tell GenRemote that this a UNC VPB

	CALLFAR GenRemotehVPB		; Make a VPB for the UNC FSD

	RestoreReg <si,ds>

	RestoreReg <di,es>		; DI = new limit for FSC
					; ES = FSC
	jc	fsa15			; Could not add another VPB. Return error.

	mov	ds:[si].UNCfsc_hVPB,ax	; store hVPB handle
	mov	ds:[si].UNCfsc_Active,FS_ATTR_UNC

fsa05:	mov	es:[fss_Limit],di	; Store new limit
	xor	ax,ax			; report success

fsa10:	LeaveProc
	RET

fsa15:	MOV	AX,ERROR_NOT_ENOUGH_MEMORY	; report failure
	jmp short fsa10


EndProc	  fscAdd


;***	fscCheck - Check that FSC/FSD is acceptable for loading
;
;	fscCheck confirms that an FSC doesn't contain any intolerable
;	errors, so that the loader can go ahead and init it.
;
;	List of things to check:
;	1.  That only one FSD is installed with a given name.
;	2.  That UNC FSD has VERIFY entry point defined.
;	3.  That FILEINFO FSD has FILELOCKS entry point defined.
;	4.  That the attribute doubleword is correct
;
;	ENTRY	fscInitTemplate contains the FSC to be checked
;
;	EXIT  NC  & (AX) = 0
;		    FSC is OK as far as fscCheck can tell
;	      CY  - error code in AX
;			(AX) = ERROR_ALREADY_EXISTS
;				duplicate FSD name
;			(AX) = ERROR_UNC_DRIVER_NOT_INSTALLED
;				invalid UNC FSD handler
;			(AX) = ERROR_INVALID_ATTR
;				invalid FSD_ATTRIBUTE
;
;	USES	AX, SI, DI, DS, ES, flags
;
;	ALGORITHM
;	    qStr = fscInitTemplate.FS_NAME
;	    if (fscFind(qStr) == found)
;		return AX = ERROR_ALREADY_EXISTS
;
;	    qAttr = fscInitTemplate.FS_ATTRIBUTE
;	    if (invalid attribute)
;		returns (ERROR_INVALID_ATTR);
;	    if (qAttr & FS_ATTR_UNC)
;		if (fscInitTemplate.UNCVERIFYNAME.segmt == 0)
;		    return AX = ERROR_UNC_DRIVER_NOT_INSTALLED
;	    if (qAttr & FS_ATTR_LOCKINFO)
;		if (fscInitTemplate.FILELOCKS.segmt == 0)
;		    return AX = ERROR_BAD_DRIVER_LEVEL
;
;	    return AX = 0
;
Procedure fscCheck,NEAR
    ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA

;	Check for FSD with name that conficts with previous FSD's name.
	DosInitContext DS
	LDS	SI,DS:fscInitTemplate.FS_NAME	; (DS:SI) -> new FSD's name
	CALLFAR fscFind			; C = no match, NC = match
	CMC				; C = match, NC = no match
	MOV	AX,ERROR_ALREADY_EXISTS ; Report error
	JC	fcc30			; C, go return error

;	Check for UNC FSD conflict.  (Name is OK if we get here.)

	DosInitContext DS
	lds	si,ds:fscInitTemplate.FS_ATTRIBUTE

; test FS_ATTRIBUTE for reserved bits set

	test	word ptr [SI].LowWord,FS_ATTR_LOW_RESV
	mov	AX,ERROR_INVALID_ATTR	;invalid FS_ATTRIBUTE ERROR
	JNZ	fcc30
	TEST	word ptr [SI].HighWord,FS_ATTR_HI_RESV
	MOV	AX,ERROR_INVALID_ATTR	;invalid FS_ATTRIBUTE ERROR
	JNZ	fcc30
;
	XOR	AX,AX			; report success
	.errnz	(FS_ATTR_UNC GT 0ffh)
	TEST	byte ptr DS:[SI],FS_ATTR_UNC
	JZ	fcc10			; we're not UNC, go report OK.
	push	ds
	push	si
	DosInitContext DS
	mov	si,DS:fscInitTemplate.FS_VERIFYUNCNAME.Segmt
	cmp	si,ax
	mov	ax,ERROR_UNC_DRIVER_NOT_INSTALLED
	pop	si
	pop	ds
	je	fcc30			; UNC FSD did not export
					; verifyuncname, report error
fcc10:
	xor	AX,AX			; report success
	.errnz	(FS_ATTR_LOCKINFO GT 0ffh)
	test	byte ptr DS:[SI],FS_ATTR_LOCKINFO
	jz	fcc20			; we're not LOCKINFO, go report OK.

	DosInitContext DS
	mov	si,DS:fscInitTemplate.FS_FILELOCKS.Segmt
	cmp	si,ax
	mov	ax,ERROR_BAD_DRIVER_LEVEL
	je	fcc30			; LOCKINFO FSD did not export

fcc20:	clc				; report success
	ret

; error exit

fcc30:	stc
	ret

EndProc	  fscCheck

INITCODE ends


	FARCODE INITR3
INITR3CODE segment

;***	InitFSCTemplate - initialize fscInitTemplate for an FSD
;***	InitFSCTemplateR3 - same, but for ring 3 caller
;
;	This routine initializes the fsc template for an FSD, effectively
;	constructing the fsc.  A loop runs through all the entries in the
;	fscNameList, calling DosGetProcAddr on each entry point.  For optional
;	entry points, the address of an emulation routine will used in the fsc
;	template if DosGetProcAddr returns an error.  Checks are made to ensure
;	that if any of a given functional group of optional fsd entry points
;	are exported by an FSD, that _ALL_ the entry points in that group
;	must be exported.  We do not allow emulation of only some of the
;	FS_ routines for a given group.
;
;	We keep track of this using bit vectors.  In the newgroup vector, a bit
;	will be set if any entry points from that group have been encountered.
;	In the emugroup vector, a bit will be set if that group is being
;	emulated, clear if that group is being supported by the FSD.  Together
;	these bits allow us to make the determination we need.
;
;	This is an init time routine.  Therefore it can be called in ring 0 or
;	ring 3.  Since we effectively need to call an api (DosGetProcAddr), we
;	require the caller to tell us what ring we're in so we can compensate
;	and avoid priveledge problems.
;
;	ENTRY:	AX = FSD module handle
;		DS = DosInitContext
;
;	EXIT:	NC: FSC template constructed in FSCInitTemplate
;		CY: error. FSD is invalid.
;
;	USES:	AX,EBX,DX,DI, flags
;
;	PSEUDOCODE:
;
;	get ptr to fscInitTemplate
;	get ptr to fscNameList
;	while(not at end of fscNameList) {
;	    if(ring0) {
;		proc_addr = w_getprocaddr(ax=module handle, es:di=procname);
;		*(current template entry) = proc_addr
;	    } else {				/* ring 3, use call gate */
;		push module handle, &procname, &(current FSCInitTemplate entry)
;		call [pDosGetProcAddr]
;	    }
;	    if(DosGetProcAddr or w_getprocaddr returns no error) {
;		if(this is first occurrence of a member of functional group)
;		    goto getnext;
;		else if(other members of this group were emulated)
;		    RETURN(error, carry set)
;	    } else {				/* error, need to emulate */
;		if(this is first occurrence of a member of functional group) OR
;		  (other members of this group will be emulated)
;		{
;		    emulate_addr = (current ent in fscNameList).emulate_address
;		    *(current entry in fscInitTemplate) = emulation_addr
;		} else
;		    RETURN(error, carry set)
;	    }
;	  getnext:
;	    point to next entry in fscNameList
;	    point to next entry in FscInitTemplate
;	}
;	RETURN(no error, carry clear)
;
;

Procedure InitFSCTemplateR3

LocalVar	ifsr_newgroup,WORD	; bits for whether a group has been
					; seen already
LocalVar	ifsr_emugroup,WORD	; bit vector; 0=using FSD entry points,
					; 1=using emulation routine addresses

	ASSUME	DS:DOSINITDATA,ES:NOTHING,FS:NOTHING,GS:NOTHING,SS:TASKAREA
;
; In order to avoid special casing on the required group of fsd entry points,
; we initialize the bit vectors to indicate that we have already seen a
; member of the required group, and that we are not emulating this group.
; Then the first missing entry point in the required group will cause this
; routine to return error.
;
	EnterProc <1 SHL FSGroup_REQUIRED,0> ; initilizes REQUIRED group

	SaveReg <es,si>
	DosInitContext	es

	mov	si,offset DOSINITDATA:fscInitTemplate
	mov	di,offset DOSINITDATA:fscNameList

ifsr10: movzx	dx,ds:[di].FSCNmEnt_NameLen	; length of name
	or	dl,dl			; end of list?  (clears carry flag)
	jz	ifsrx			; yes, exit, no errors

	push	di			; save proc info base
	add	di,FSCNmEnt_ProcName	; es:di -> proc name

	SaveReg <ax>			; save module handle
	push	ax			; module handle
	push	es			; push addr of proc name
	push	di
	push	ds			; push ptr to ptr to FS_ procedure
	push	si			; (entry in fsc template)
	call	pDosGetProcAddr
	or	ax,ax			; error?
	RestoreReg <ax>			; restore module handle, proc info base
	pop	di			; avoids need for NoCheck
	jnz	ifsr50			; yes, try eumlation address
;
; We get here if the DosGetProcAddr call gives us an entry point (ie, if the
; FSD exports the function).  Make sure all other entry points of this group
; that we've seen so far have also been exported (ie are not being emulated).
; A set bit in the emugroup bit vector means we are emulating this group.
;
ifsr30: movzx	bx,ds:[di].FSCNmEnt_Group ; bx = functionality group
	bts	ifsr_newgroup,bx	; first instance of this group?
	jnc	ifsr40			; yes, skip check on emulate flag
	bt	ifsr_emugroup,bx	; fsd exporting this group so far?
	jc	ifsrx			; no, error
;
; We get here when the entry point was processed properly, and we need to
; set up to deal with the next entry point.
;
ifsr40: add	si,4			; si --> next fsc entry
	add	di,FSCNmEnt_ProcName
	add	di,dx			; di --> next procedure name and info
	jmp	short ifsr10		; go process next entry point
;
; Branch here if the DosGetProcAddr call returns error.  Use emulation
; address, but first make sure that no other entry points in this functional
; group were exported by the FSD.  This ensures that ALL entry points in
; this group will be emulated if _any_ are.
;
ifsr50: movzx	bx,ds:[di].FSCNmEnt_Group ; bx = functionality group
	bts	ifsr_newgroup,bx	; first instance of this group?
	jc	ifsr60			; no, go check emulate flag
	bts	ifsr_emugroup,bx	; yes, set emulate flag for group
ifsr60: bts	ifsr_emugroup,bx	; are we emulating this group?
	jnc	ifsrex			; no, error.
;
; We get here if we have to plug the address of an emulation routine into
; the fsc template.
;
	mov	ebx,ds:[di].FSCNmEnt_Emulation	; address of emulation routine
	mov	dword ptr ds:[si],ebx	; store in fsc template
	jmp	short ifsr40		; go process next entry

ifsrex: stc
ifsrx:	RestoreReg <si,es>
	LeaveProc
	ret

EndProc InitFSCTemplateR3

INITR3CODE ends


	FARCODE INIT
INITCODE segment

Procedure InitFSCTemplate

LocalVar	ifsc_newgroup,WORD	; bits for whether a group has been
					; seen already
LocalVar	ifsc_emugroup,WORD	; bit vector; 0=using FSD entry points,
					; 1=using emulation routine addresses

	ASSUME	DS:DOSINITDATA,ES:NOTHING,FS:NOTHING,GS:NOTHING,SS:TASKAREA
;
; In order to avoid special casing on the required group of fsd entry points,
; we initialize the bit vectors to indicate that we have already seen a
; member of the required group, and that we are not emulating this group.
; Then the first missing entry point in the required group will cause this
; routine to return error.
;
	EnterProc <1 SHL FSGroup_REQUIRED,0> ; initilizes REQUIRED group

	SaveReg <es,si>
	DosInitContext	es

	mov	si,offset DOSINITDATA:fscInitTemplate
	mov	di,offset DOSINITDATA:fscNameList

ifsc10: movzx	dx,ds:[di].FSCNmEnt_NameLen	; length of name
	or	dl,dl			; end of list?  (clears carry flag)
	jz	ifscx			; yes, exit, no errors

	push	di			; save proc info base
	add	di,FSCNmEnt_ProcName	; es:di -> proc name

	SaveReg <ax,cx,dx,es>
	SaveReg <si,ds>
	CALLFAR w_getprocaddr		; go get proc address
	RestoreReg <ds,si>
	mov	ds:[si].Segmt,dx	; save possible proc addr
	mov	ds:[si].Offst,ax

	RestoreReg <es,dx,cx,ax>
	pop	di			; avoids need for NoCheck
	jc	ifsc50			; if error, try emulation address

; We get here if the DosGetProcAddr call gives us an entry point (ie, if the
; FSD exports the function).  Make sure all other entry points of this group
; that we've seen so far have also been exported (ie are not being emulated).
; A set bit in the emugroup bit vector means we are emulating this group.

	movzx	bx,ds:[di].FSCNmEnt_Group ; bx = functionality group
	bts	ifsc_newgroup,bx	; first instance of this group?
	jnc	ifsc40			; yes, skip check on emulate flag
	bt	ifsc_emugroup,bx	; fsd exporting this group so far?
	jc	ifscx			; no, error
;
; We get here when the entry point was processed properly, and we need to
; set up to deal with the next entry point.
;
ifsc40: add	si,4			; si --> next fsc entry
	add	di,FSCNmEnt_ProcName
	add	di,dx			; di --> next procedure name and info
	jmp	short ifsc10		; go process next entry point
;
; Branch here if the DosGetProcAddr call returns error.  Use emulation
; address, but first make sure that no other entry points in this functional
; group were exported by the FSD.  This ensures that ALL entry points in
; this group will be emulated if _any_ are.
;
ifsc50: movzx	bx,ds:[di].FSCNmEnt_Group ; bx = functionality group
	bts	ifsc_newgroup,bx	; first instance of this group?
	jc	ifsc60			; no, go check emulate flag
	bts	ifsc_emugroup,bx	; yes, set emulate flag for group
ifsc60: bts	ifsc_emugroup,bx	; are we emulating this group?
	jnc	ifscex			; no, error.
;
; We get here if we have to plug the address of an emulation routine into
; the fsc template.
;
	mov	ebx,ds:[di].FSCNmEnt_Emulation	; address of emulation routine
	mov	dword ptr ds:[si],ebx	; store in fsc template
	jmp	short ifsc40		; go process next entry

ifscex: stc
ifscx:	RestoreReg <si,es>
	LeaveProc
	ret

EndProc InitFSCTemplate


INITCODE ends


	FARCODE HIGH2
HIGH2CODE SEGMENT

;***	fscNext - Step through the list of FSCs.
;
;	fscNext steps through the set of FSCs.  In particular, it steps
;	through the set of FSCs that represent actual FSDs.  (At the moment
;	this means all of them, but could change in the future.)
;
;	ENTRY	(ES:DI) = Search pointer
;			  DI = 0 -> Don't care about ES, return first FSC
;			  DI != 0 -> ES:DI is FSC pointer returned by previous
;				     call to fscNext, return next FSC
;
;	EXIT	'NC' - success
;		    (ES:DI) -> Next FSC
;
;		'C' - end of list
;		    (ES:DI) = random trash
;
;	USES	DI, ES, flags
;
;	ALGORITHM
;	    if (DI == 0)
;		DI = FSCBASE
;		ES = GDT_FSC
;	    else
;		DI = DI + size FS_ENTRY
;	    if (DI >= fss_Limit)
;		return 'C'		; No more FSCs, ES:DI = trash
;	    else
;		return 'NC'		; Found another, ES:DI -> FSC
;
Procedure fscNext,HYBRID
    ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA

	OR	DI,DI			; Start at beginning?
	JNZ	fcn1			; not start from front, go continue

	MOV	DI,GSEL GDT_FSC
	MOV	ES,DI			; ES = GDT_FSC
	MOV	DI,FSCBASE - size FS_ENTRY  ; ES:DI -> First FSC - it's size

;	If we're to start from the beginning, ES:DI is such that adding
;	    size FS_ENTRY will make it point to the first FSC.
;
;	If we're to continue, ES:DI is the last value we returned, so
;	    adding size FS_ENTRY will advance to the next entry.

fcn1:	ADD	DI,size FS_ENTRY	; ES:DI -> next FSC

	CMP	DI,ES:[fss_Limit]	; Past end of segment?
					; yes -> 'NC' complement to ret 'C'
					; no  -> 'C' complement to ret 'NC'
	CMC
	RET

EndProc	  fscNext


;***	fscFind - Find an FSC given an FSD name.
;
;	fscFind scans the FSC segment, looking for an FSC whose name matches
;	the name passed in as an argument.  If such an FSC is found, its
;	full address is returned.
;
;	ENTRY	(DS:SI) -> Query String, FSD name to look for.
;
;	EXIT	'NC' - success
;		    (ES:DI) -> FSC describing FSD whose name matched
;
;		'C' - failure
;		    No match
;
;	USES	AX, DI, ES, flags
;
;	ALGORITHM
;	    qStr (query string) is name we're looking for
;	    i = FSCBASE
;	    while (i < fss_Limit) {
;		mStr = GDT_FSC.i.FS_NAME    ; mStr -> name
;		result = fscStrCmp(qStr, MStr)
;		if (result == found)
;		    return NC, i
;		i = i + size FS_ENTRY
;	    }
;	    return CF
;
Procedure fscFind,HYBRID
    ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA

	XOR	DI,DI			; Start with first FSC
fsf1:	CALL	fscNext
	JC	fsf2			; No more FSCs to examine, go ret 'C'

	SaveReg <ES,DI>			; Save FSC pointer

	LES	DI,ES:[DI].FS_NAME	; ES:DI -> current FSD name

;	(DS:SI) -> Query String
;	(ES:DI) -> Match String

	CALL	fscStrCmp		; 'Z' match, 'NZ' no match

	RestoreReg <DI,ES>		; Restore FSC pointer

	JNZ	fsf1			; no match, go try next FSC
	CLC				; match, ret ES:DI, 'NC'

fsf2:	RET

EndProc	  fscFind


;***	fscStrCmp - Compare two strings, case insignificant, ascii terminated
;
;	fscStrCmp compares two strings, using the rules for matching of
;	FSD names.  This means that all characters are significant, the
;	strings are terminated by ascii NULLs, and that case is insignificant.
;	Strings of different lengths don't match.  DBCS will be dealt with
;	correctly.
;	(Like StrCmp, but without goofy path character matching.)
;
;	ENTRY	(DS:SI) -> Query String
;		(ES:DI) -> Match String
;
;	EXIT	ZF if strings match,
;		NZ if strings don't match
;		(JZ MATCH, JNZ NOMATCH)
;
;	USES	ax, flags
;
;	ALGORITHM
;	    do {
;		if (TestDBCS(*qStr)) {
;		    if (*qStr != *mStr)
;			return NZ
;		    qStr++
;		    mStr++
;		    if (*qStr != *mStr)
;			return NZ
;		}
;		if (UCase(*qStr) != UCase(*mStr))
;		    return NZ
;
;	    } until (*qStr == NULL)
;	    return ZF
;
Procedure fscStrCmp,NEAR,LOCAL
    ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA

	SaveReg <SI,DI>

fcmp1:	LODSB				; AL = Query Byte
	TestDBCS			; double byte character?
	JNZ	fcmp2			; no, continue below
	CMP	AL, ES:[DI]		; compare lead byte
	JNZ	fcmp4			; mismatch, go report
	LODSB				; fetch trail byte
	INC	DI
	CMP	AL, ES:[DI]		; compare trail byte
	JNZ	fcmp4
	INC	DI
	JMP	SHORT fcmp3

fcmp2:	CALL	UCase			; convert to upper case
	MOV	AH,AL			; AH = UCase(Query Byte)
	MOV	AL,ES:[DI]		; AL = match byte
	INC	DI
	CALL	UCase			; AL = UCase(match byte)
	CMP	AH,AL
	JNZ	fcmp4			; Strings dif
fcmp3:	OR	AL,AL			; Terminating null?
	JNZ	fcmp1			; no, go do more comparisons

fcmp4:	RestoreReg  <DI,SI>
	RET

EndProc	  fscStrCmp

HIGH2CODE ENDS

	end
