;    File              : $UTILS.FDO$
;
;    Description       :
;
;    Original Author   : DIGITAL RESEARCH
;
;    Last Edited By    : $CALDERA$
;
;-----------------------------------------------------------------------;
;    Copyright Work of Caldera, Inc. All Rights Reserved.
;      
;    THIS WORK IS A COPYRIGHT WORK AND CONTAINS CONFIDENTIAL,
;    PROPRIETARY AND TRADE SECRET INFORMATION OF CALDERA, INC.
;    ACCESS TO THIS WORK IS RESTRICTED TO (I) CALDERA, INC. EMPLOYEES
;    WHO HAVE A NEED TO KNOW TO PERFORM TASKS WITHIN THE SCOPE OF
;    THEIR ASSIGNMENTS AND (II) ENTITIES OTHER THAN CALDERA, INC. WHO
;    HAVE ACCEPTED THE CALDERA OPENDOS SOURCE LICENSE OR OTHER CALDERA LICENSE
;    AGREEMENTS. EXCEPT UNDER THE EXPRESS TERMS OF THE CALDERA LICENSE
;    AGREEMENT NO PART OF THIS WORK MAY BE USED, PRACTICED, PERFORMED,
;    COPIED, DISTRIBUTED, REVISED, MODIFIED, TRANSLATED, ABRIDGED,
;    CONDENSED, EXPANDED, COLLECTED, COMPILED, LINKED, RECAST,
;    TRANSFORMED OR ADAPTED WITHOUT THE PRIOR WRITTEN CONSENT OF
;    CALDERA, INC. ANY USE OR EXPLOITATION OF THIS WORK WITHOUT
;    AUTHORIZATION COULD SUBJECT THE PERPETRATOR TO CRIMINAL AND
;    CIVIL LIABILITY.
;-----------------------------------------------------------------------;
;
;    *** Current Edit History ***
;    *** End of Current Edit History ***
;
;    $Log$
;    UTILS.FDO 1.39 94/11/30 13:39:18 
;    added share_delay function   
;    UTILS.FDO 1.37 94/07/13 15:42:01
;    Change to rename/delete of file open in compatibility modes
;    UTILS.FDO 1.36 94/06/28 11:10:10
;    Limit ddsc allocation to 1st 255
;    UTILS.FDO 1.34 94/04/25 19:33:04
;    Reject blank names (ie. all spaces) when parsing path
;    We used to die in rebuild_ldt_curdir (GATEWAY problem)
;    UTILS.FDO 1.33 93/12/16 13:57:06 
;    Fix path_prep bug when dir in path doesn't exist
;    UTILS.FDO 1.32 93/12/09 23:56:10 
;    Move non-inherited bit to correct place in file handle
;    UTILS.FDO 1.31 93/12/08 03:30:03 
;    Add extra check to offer_join: consider JOIN B: \FRED, SUBST L: \FRED\FRED
;    A CD L:\ would see FRED at the root of B: and change into it, so we
;    would end up at \FRED, so we only check if ROOTLEN=2
;    UTILS.FDO 1.27 93/11/19 17:45:14
;    Fix for SERVER print queue viewing problem
;    UTILS.FDO 1.26 93/11/08 16:30:12
;    Get dat/time on device handle returns current date/time
;    UTILS.FDO 1.25 93/09/14 20:03:42
;    Trust LFLG_PHYSICAL
;    UTILS.FDO 1.23 93/09/03 20:26:09
;    Add "no critical errors" support (int 21/6C)
;    UTILS.FDO 1.22 93/07/26 18:11:00
;    re-arrange DHNDL_DCNTHI for the benefit of Geoworks
;    UTILS.FDO 1.21 93/07/20 22:43:48
;    Even fewer checks on int 25/26 
;    ENDLOG
;	General utility include module for FDOS.A86

select_pb2:
;----------
	call	get_pb2_drive		; get drive from parameter block
;	jmp	select_unique		; select drive, make HDS unique

select_unique:
;-------------
;	entry:	AL = drive to select (0-15)

	mov	byte ptr path_drive,al	; save logical drive
	jmp	select_logical_drv	; select the drive


logical2physical:
;----------------
; On Entry:
;	AL = drive to select
; On Exit:
;	AL = appropriate physical drive to select
;
; This routine is called by low level routines (func_ddio, func_getdpb)
; and bypasses the checks for networked/joined drives together with the
; normal media change checks. It does however handle SUBST'd drives.
;
	call	get_ldt_raw		; ES:BX -> LDT for our drive
	 jc	logical2physical10	; if we don't have one must be physical
	test	es:LDT_FLAGS[bx],LFLG_JOINED
	 jnz	logical2physical10	; joined drive - treat as physical
	test	es:LDT_FLAGS[bx],LFLG_SUBST
	 jz	logical2physical10	; as long as we aren't SUBST'd it OK
	mov	al,es:LDT_NAME[bx]	; get the drive from the ascii name
	and	al,1fh			;  as the drive may require rebuilding
	dec	ax			; make it zero based
logical2physical10:
	ret


	Public	dbcs_lead

dbcs_lead:
;---------
; Return true if given byte is the first of a double byte character.
; Entry
;	al 	= byte to be tested
; Exit
;	Z Flag	= 1 - byte is a DBCS lead
;		  0 - byte is not a DBCS lead
; Lost
;	no registers changed

if KANJI
	push	si
	push	bx
	push	ax

; First get a pointer to the double byte lead table in the COUNTRY info.
	mov	si, offset DBCS_tbl+2	; ds:si -> double byte table

; Examine each entry in the table to see if it defines a range that includes
; the given character.
	mov	bl, al			; bl = byte to be tested
dbcs_loop:
	lods	ss:ax			; al/ah = start/end of range
	test 	ax, ax			; end of table?
	 jz	dbcs_no			;  yes - exit (not in table)
	cmp	al, bl			; start <= bl?
	 ja	dbcs_loop		;  no - try next range
	cmp	ah, bl			; bl <= end?
	 jb	dbcs_loop		;  no - try next range

	cmp	al, al			; return with Z flag set
	jmp	dbcs_exit

dbcs_no:
	cmp	al, 1			; return with Z flag reset

dbcs_exit:
	pop	ax
	pop	bx
	pop	si
	ret
else
	test	al, al			; force non-0 condition
	ret
endif


kanji_eos:
;---------
;	entry:	ES:DI -> string to find the end of
;	exit:	ES:DI -> character before NUL byte

	mov 	dx,di			; in case string is empty
kanji_eos1:
	mov	al,es:[di]		; get next character
	test	al,al			; is it the final NUL byte
	 jz	kanji_eos9		; return if NUL found
	mov	dx,di
	inc	di			; move to next character
if KANJI
	call	dbcs_lead		; is this first half of 16-bit char?
	 jne	kanji_eos1		; skip if normal character
	inc	di			; else skip 2nd half (should really check)
endif
	jmps	kanji_eos1		; loop back for next character

kanji_eos9:
	mov	di,dx			; ES:DI -> last character
	ret				; end of string found


path_chop:
;---------
;	entry:	ES:DI -> path = "d:\level1\level2\"
;	exit:	         path = "d:\level1\"

	mov	al,es:[di]		; get next character
	test	al,al			; end of string?
	 jz	path_chop2		; yes, string scanned
	inc	di			; next character
if KANJI
	call	dbcs_lead		; lead-in of 16-bit character?
	 jne	path_chop1		; no, normal 8-bit
	inc	di			; skip hi-byte (should really check)
	jmps	path_chop		; try again
path_chop1:
endif
	call	check_slash		; is this a path character
	 jne	path_chop		; loop back if not path character
	mov	dx,cx			; last but one '/' = last '/'
	mov	cx,di			; last '/' = current '/'
	jmps	path_chop		; repeat
path_chop2:
	mov	di,dx			; ES:DI -> last but one slash + 1
	sub	ax,ax			; get a NUL byte
	stosb				; chop off the last level
	ret

	Public	rebuild_ldt_root

rebuild_ldt_root:
;----------------
; On Entry:
;	ES:BX -> LDT_ to rebuild
;	fdos_hds = physical root for this drive
; On Exit:
;	ES:BX preserved
;	LDT_ROOT rebuilt from ASCII LDT_NAME
;
	push	ds
	push es ! pop ds		; DS:BX -> LDT_
	push ss ! pop es
	mov	di,offset temp_ldt	; ES:DI -> temp LDT_
	lea	si,LDT_NAME+3[bx]	; point to start of pathname
	mov	cx,LDT_ROOTLEN[bx]	; CX = end of root portion
	xor	ax,ax			; assume we want root block
	sub	cx,3			; skip the 'D:\'
	 jbe	rebuild_ldt_root10	; nothing to do unless SUBST'd
	rep	movsb			; copy the root portion of the name
	call	select_dir		; select this directory
	 jnc	rebuild_ldt_root10
	xor	ax,ax
	mov	LDT_ROOTLEN[bx],2	; force ourselves into the root
	mov	LDT_NAME+3[bx],al	;  as the media has changed
rebuild_ldt_root10:
	push ds ! pop es		; ES:BX -> LDT_
	pop	ds
	mov	ax,fdos_hds_blk
	mov	es:LDT_ROOT[bx],ax	; update our root block
if JOIN
	mov	al,fdos_hds_drv		;  and the physical drive
	mov	es:LDT_DRV[bx],al
endif
	ret


rebuild_ldt_curdir:
;------------------
; On Entry:
;	ES:BX -> LDT_ to rebuild
;	fdos_hds = logical root of this drive
; On Exit:
;	ES:BX preserved
;	LDT_DRV and LDT_BLK rebuilt from ASCII LDT_NAME
;
	push	ds
	push es ! pop ds		; DS:BX -> LDT_
	push ss ! pop es
	mov	di,offset temp_ldt	; ES:DI -> temp LDT_
	mov	si,LDT_ROOTLEN[bx]	; SI = end of root portion
	lea	si,LDT_NAME[bx+si]	; point to subdir entry
	lodsb				; get 1st char
	call	check_slash		; is it a leading '\' ?
	 je	rebuild_ldt_curdir10	; yes, discard it
	dec	si			; else leave it alone
rebuild_ldt_curdir10:
	test	al,al			; anything to do?
	 jz	rebuild_ldt_curdir40	;  no, we are already there
rebuild_ldt_curdir20:
	lodsb ! stosb			; copy the string
	test	al,al			; until we hit the terminating NUL
	 jnz	rebuild_ldt_curdir20
	dec	di
	call	select_dir		; select this directory
	 jnc	rebuild_ldt_curdir40
	mov	si,LDT_ROOTLEN[bx]	; SI = end of root portion
	cmp	si,3			; is root real root or a subdir ?
	 ja	rebuild_ldt_curdir30
	mov	si,3			; real root, leave '\' alone
rebuild_ldt_curdir30:
	mov	LDT_NAME[bx+si],0	; move ASCII to root
rebuild_ldt_curdir40:
	push ds ! pop es		; ES:BX -> LDT_
	pop	ds
	mov	ax,fdos_hds_blk
	mov	es:LDT_BLK[bx],ax	; update our curdir block
if JOIN
	mov	al,fdos_hds_drv		;  and the physical drive
	mov	es:LDT_DRV[bx],al
endif
	ret

select_dir:
;----------
; On Entry:
;	DS:BX -> LDT
;	ES:DI -> end of ASCII path
;	temp_ldt contains dir to select
; On Exit:
;	DS:BX preserved
;	CY set on error, root of original drive reselected
;
	mov	ax,'.\'			; append a "\." in case it's the root
	stosw				;  (and so a NULL path)
	xor	ax,ax
	stosb
	push ds ! push bx		; make sure LDT survives
	push ss ! pop ds		; DS back to SYSDAT
	mov	si,offset temp_ldt	; ES:SI -> directory to select
	call	path_prep_next		;  try to move into it
if JOIN
	call	offer_join		; are we opening a JOIN'd drive ?
	 jnc	select_dir20
endif
	call	finddfcbf		; find the directory entry
	 jz	select_dir10		; stop if we can't
	test	DATTS[bx],DA_DIR	; check if directory
	 jz	select_dir10		; fail if this is a file
	push ds ! pop es
	lea	di,DNAME[bx]		; ES:DI -> ASCII name to open
	mov	cx,8+3
	mov	al,' '
	repe	scasb			; is it all spaces ?
	 je	select_dir10		; if so reject it
	call	open_dir
	 jnc	select_dir20
select_dir10:
	mov	ax,fdos_hds_root	; move to the virtual root
	mov	fdos_hds_blk,ax	
	stc				; return error
select_dir20:
	pop bx ! pop ds
	ret


eject
;	Run down the path and parse final name
;	exit:	ds:dx -> info_fcb parsed at path end
;		cf = 1, and al = code on any error

path_prep:
	les	di,dword ptr fdos_pb+2	; es:di -> path name

path_prep_ptr:
	call	path_prep_check		; try to prepare path
	 jnc	path_prep_good		; skip if success
	jmp	fdos_error		; return error to application

path_prep_drive_error:
;	stc				; return CY set
	mov	ax,ED_DRIVE		; with correct error code
path_prep_good:
	ret

path_prep_check:
	call	get_path_drive		; from asciiz or default
	 jc	path_prep_drive_error	; continue if drive A: - Z:
path_prep_cont:
	push	di			; DX = drive code (0-15)
	push	es			; save string address
	push ds ! pop es		; ES = SYSDAT
	call	select_unique		; select the drive and
	pop	es
	pop	si			; es:si -> past drive

	lods	es:al			; get first character
	call	check_slash		; if '\' or '/' then start at root
	 jne	path_prep_curdir	;  else select current directory
	push	es
	call	path_prep_root		; fake a '.' entry for the root
	pop	es
	lods	es:al			; get next char
	dec	si			; forget we looked
	test	al,al			; if just a '\' stop now
	 jz	path_prep_done
    call    check_slash     
	 jne	path_prep_next		;  otherwise start processing from root
	mov	ax,ED_ACCESS		; get correct error code
	stc				;  and return if \\
	ret

path_prep_curdir:
; We need to select the current directory as a start for our operations
	dec	si			; forget about char we looked at
	push es ! push si		;  and save position in name
	mov	al,logical_drv		; get the current logical drive
	call	get_ldt			;  and hence the LDT structures
	 jc	path_prep_curdir30	; no LDT, leave at physical root
if JOIN
	mov	al,fdos_hds_drv		; are we on a known drive ?
	sub	al,es:LDT_DRV[bx]	; (we may be on joined drive)
	 jne	path_prep_curdir10	; if not better rebuild
	cbw ! dec ax			; AX = FFFF
else
	mov	ax,0FFFFh
endif
	cmp	ax,es:LDT_BLK[bx]	; do we need to do a rebuild
	 jne	path_prep_curdir20	;  or can we trust the media ?
path_prep_curdir10:
	call	rebuild_ldt_curdir	; better reselect current dir
path_prep_curdir20:
	mov	ax,es:LDT_BLK[bx]	; move to current directory block
	mov	fdos_hds_blk,ax
path_prep_curdir30:
	pop si ! pop es			; ES:SI -> name again

path_prep_next:
;--------------
; Called by disk change code to rebuild an HDS_ for a drive after
; media change detected.
; On Entry:
;	ES:SI -> pathname to rebuild
;	fdos_hds = HDS_ to rebuild
; On Exit:
;	CY set if problem (AX=error code)
;	ES=DS
;
	cmp	es:byte ptr [si],0	; can't have trailing '/' or '\'
	 je	path_prep_error		;   return this as an error

	mov	ax,path_drive
	inc	ax			; al = drive (one based)
	call	parse_path		; set up the info_fcb
	 jc	path_prep_error		; skip on any parse error

	test	al,al			; AL = delimiter
	 jz	path_prep_done		; are we at the end ?

	cmp	word ptr info_fcb+1,' .'
	 je	path_prep_next		; CHDIR (".") => stay where we are

	call	check_no_wild		; no wilds cards in path's
	 je	path_prep_error		; skip if wild cards found

if JOIN
	push	es
	push	si
	call	offer_join		; are we opening a JOIN'd drive ?
	pop	si
	pop	es
	 jnc	path_prep_next		; if so move on to next stage
endif
	push	es
	push	si			; save string address
	push ds ! pop es		; ES = local segment
	call	finddfcbf		; locate the directory entry
	pop	si
	pop	es			; restore string address
	 jz	path_prep_error		; no, missing directory in path

	test	DATTS[bx],DA_DIR	; check if directory
	 jz	path_prep_error		; fail if this is a file

	push	es ! push si		; save string address
	push	ds ! pop  es		; ES = local segment
if PASSWORD
	call	check_pwd_any		; check if PW req'd & supplied
endif
	call	open_dir		; go down one level
	pop	si ! pop  es		; restore string address
	 jnc	path_prep_next		; if open is good, repeat
path_prep_error:
	mov	ax,ED_PATH		; return code in case of error
	stc				; indicate error to caller
	ret


path_prep_done:
	cmp	info_fcb+1,'.'		; is it '.' or '..' ?
	 jne	path_prep_exit		; if so get its full name
if JOIN
	call	offer_join		; are we opening a JOIN'd drive ?
	 jnc	path_prep_root		;  if so move into the dir
endif
	call	finddfcbf		; find the directory entry
	 jz	path_prep_exit		; stop if we can't
	call	open_dir		; move into the directory
	 jc	path_prep_error		; stop if we can't
	mov	cx,DBLOCK1[bx]		; are we destined for the root ?
	 jcxz	path_prep_root		; yes, stop - we won't find anything
	push	fdos_hds_root
	mov	fdos_hds_root,0		; don't stop at virtual root
	call	find_parent		; find the parental entry
	pop	fdos_hds_root
	 jz	path_prep_error		; (shouldn't happen)
	mov	ax,DPWD[bx]		; get password hash code from entry
	mov	local_password,ax	; ensure we can get back down
	lea	si,DNAME[bx]		; point to it's name
	mov	cx,11
	push ds ! pop es
	mov	di,offset info_fcb+1
	rep	movsb			; copy parental name to info_fcb
path_prep_exit:
	push	ds ! pop  es		; restore ES to local segment
	clc
	ret


path_prep_root:
	push	ds ! pop es		; ES = local segment
	mov	al,info_fcb		; preserve drive setting
	call	clear_info_fcb
	mov	info_fcb+1,'.'		; fake a '.' directory
	ret


clear_info_fcb:
;--------------
; Sets up a clean info_fcb for later use
; On Entry:
;	AL = drive
; On Exit:
;	All regs preserved
;
	push di ! push cx ! push ax
	mov	di,offset info_fcb
	stosb				; set the drive code

	mov	cx,11
	mov	al,' '
	rep	stosb			; fill name with blanks
if PASSWORD
	mov	cx,8
	mov	di,offset password_buffer
	rep	stosb			; blank password buffer
	mov	es:local_password,cx	; zero out local password
endif
	pop ax ! pop cx ! pop di
	clc
	ret


eject
;	Get drive from path name, or if none, use default drive
; On Entry:
;	es:di -> path name
; On Exit:
;	AL = path_drive = sepcified or default drive
;	es:di -> past drive name
;	cf = 1 if illegal drive name

get_path_drive:
	cmp	es:byte ptr [di],0	; check if string is empty
	 je	get_path_error		;    which isn't O.K. at all
	cmp	es:byte ptr 1[di],':'	; if the second char is ':',
	 jz	get_path_explicit	;    then drive is in pathname
	call	current_dsk2al
	jmps	get_path_ok		; common code from here

get_path_explicit:
	mov	al,es:[di]		; grab the drive designator
	call	toupper			; make sure it is upper case
	sub	al,'A'			; correct offset. if too
	 jb	get_path_error		;   small, return error
	cmp	al,ss:last_drv
	 jae	get_path_error
	inc	di
	inc	di			; it's ok, bump the pointer
get_path_ok:
	xor	ah,ah			; zero ah and clc
	mov	path_drive,ax		; save for other functions
	ret

get_path_error:
	stc				; flag the error
	ret

eject
asciiz_dev_offer:
;----------------
; On Entry:
;	PB+2 -> pathname
; On Exit:
;	Only come back if not a device
;
; See if the filename is that of a simple device
; eg. 'CON', 'A:CON', 'CON.EXT'
; We should also accept '\DEV' format
; eg. '\DEV\CON', "A:\DEV\CON'
; More complicated forms should be ignored - they will be handled
; after the pathname is parsed.
; eg. 'A:\CON', '\CON', 'SUBDIR\CON.EXT'
;
	push	ds
	mov	si,2[bp]		; SI -> parameter block
	lds	si,ds:2[si]		; DS:SI -> file specification
	cmp	ds:byte ptr 0[si],0	; NUL names are stupid, but
	 je	asciiz_dev_offer30	;  you do get them....
	cmp	ds:byte ptr 1[si],':'	; is a drive specified
	 jne	asciiz_dev_offer10
	inc si ! inc si			; skip that, but no more
asciiz_dev_offer10:
	push ss ! pop es		; ES:DI -> scratch FCB in build
	mov	di,offset name_buf	;  name in pcmode data buffer
	mov	al,' '
	mov	cx,8+3
	rep	stosb			; start by clearing name

	mov	al,[si]			; beware of '\DEV\name' format..
	call	check_slash		; if not slash carry on
	 jne	asciiz_dev_offer15
	lodsb				; possible, lets look at rest
	lodsb
	call	toupper
	cmp	al,'D'			; is '\D'possible ?
	 jne	asciiz_dev_offer30
	lodsb
	call	toupper
	cmp	al,'E'			; is '\DE' on ?
	 jne	asciiz_dev_offer30
	lodsb
	call	toupper
	cmp	al,'V'			; is '\DEV' on ?
	 jne	asciiz_dev_offer30
	lodsb				; finally how about trailing '\'
	call	check_slash
	 jne	asciiz_dev_offer30
	mov	al,[si]			; check for delimiter
asciiz_dev_offer15:
	call	check_delim		; if first char = delimiter
	 jz	asciiz_dev_offer30	;  then it can't be a device

	mov	di,offset name_buf	; build name in scratch FCB
	mov	cx,8			; length of name field
	call	parse_one		; parse just the name
	cmp	al,'.'
	 jnz	asciiz_dev_offer20	; do we have to parse an extention ?
	mov	di,offset name_buf+8	; di -> fcb ext field
	mov	cx,3			; length of ext field
	call	parse_one		; parse just extension
asciiz_dev_offer20:
	test	al,al			; if not a NUL by now forget it
	 jnz	asciiz_dev_offer30
	push ss ! pop ds
	mov	si,offset name_buf	; DS:SI -> name
	call	check_device_common	; try to find the name
	 jnc	asciiz_dev_accept	; if we can handle it here
asciiz_dev_offer30:
	pop	ds			; DS back to normal
	push ss ! pop es		; ditto with ES
	ret				; not a device - proceed

asciiz_dev_accept:
;----------------
; We have found a match in the device at ES:BX
;
	mov	ss:word ptr current_device,bx
	mov	ss:word ptr current_device+WORD,es
	pop	ds			; DS = SYSDAT again
	pop	ax			; discard return address
	call	local_disk		; we need the MX	
	les	bx,ss:current_device	; ES:BX -> device header
	cmp	fdos_pb,FD_EXPAND
	 je	asciiz_dev_accept20
	cmp	fdos_pb,4Eh		; is it FD_FFIRST ?
	 je	asciiz_dev_accept10
	jmp	open_dev		; open the device locally
asciiz_dev_accept10:
	jmp	first_dev		; 'find' the device locally
asciiz_dev_accept20:
	jmp	expand_dev		; 'expand' the device locally

eject
chk_no_dev:		; check file is not a character device
;----------
; On Entry:
;	info_fcb contains parsed filename
; On Exit:
;	Don't return if it's a character device
;
	call	check_device		; is this a device ?
	 jnc	chk_not_dev10
	push ds ! pop es		; ES points to data again
	ret
chk_not_dev10:
	jmp	fdos_ED_ACCESS		; blow caller away

check_device:
;------------
; On Entry:
;	info_fcb contains parsed filename
; On Exit:
;	CY set if not found
;	else
;	CY clear if found
;	ES:BX -> device header
;
	mov	si,offset info_fcb+1	; DS:SI -> name to check
;	jmp	check_device_common

check_device_common:
;-------------------
; On Entry:
;	DS:SI -> 8 byte buffer to check
; On Exit:
;	CY set if not found
;	else
;	CY clear if found
;	ES:BX -> device header
;
	push ss ! pop es		; Get the PCMODE Data Segment
	mov	bx,offset dev_root	;  hence the Device List
	mov	ax,si			; keep copy of start in AX
check_device10:
	test	es:DH_ATTRIB[bx],DA_CHARDEV
	 jz	check_device20		; skip unless it's a character device
	lea	di,DH_NAME[bx]		; ES:DI -> device name
	mov	cx,8/2			; compare file name w/o extension
	repe	cmpsw			; compare until CX == 0 or mismatch
	 je	check_device30
check_device20:
	mov	si,ax			; restore starting address
	les	bx,es:DH_NEXT[bx]	; get next device driver
	cmp	bx,0FFFFh		; end of the chain ?
	 jne	check_device10
	stc				; indicate character device not found
check_device30:
	ret

eject

no_dir_vol:
;----------
	test	DATTS[bx],DA_DIR+DA_VOLUME
	 jnz	dir_vol_err		; return error if label or directory
	ret				; else it's O.K.
dir_vol_err:
	jmp	fdos_ED_ACCESS		; return "access denied"

eject


get_pb2_drive:
;-------------
	mov	al,byte ptr fdos_pb+2	; get requested drive code
	dec	al			; Get the default drive if
	 jns	get_pb2_drv1		; the requested drive is 00
	call	current_dsk2al		; AL = default drive
get_pb2_drv1:
	ret

eject

mkdir_init:
;----------
	push	ax			; Init 1st block of the directory
	call	zeroblk			; zero the block
	pop ax ! push ax		; get the block number
	xor	bx,bx			; seek to beginning of cluster
	call	fill_dirbuf		; DI -> directory entry
	pop dx ! push dx		; get our own block #
	mov	ax,' .'			; this is the "." directory
	call	init_dot		; set name, attrib, time, date, block1
	call	flush_dirbuf		; copy '.' entry to sector buffer
	pop	ax			; get the block number
	mov	bx,1			; do 2nd entry
	call	fill_dirbuf		; DI -> directory entry
	call	hdsblk
	xchg	ax,dx			; DX = parent directory
	mov	ax,'..'			; this is the ".." directory
;	call	init_dot		; fall into INIT_DOT
;	ret

init_dot:
	mov	dirp,di			; save directory entry for SETPCD
	push	di
	mov	DBLOCK1[di],dx		; our own block #
	stosw				; store "." or ".."
	mov	al,' '
	mov	cx,11-2
	rep	stosb			; pad the name with spaces
	mov	al,DA_DIR
	stosb				; attribute = directory
	call	GetTOD			; get time/date of creation
	pop	bx
	jmp	stamp_date_and_time	; set date DX and time AX in dir BX

eject

;	Utility functions for RMDIR and UNLINK

rmdir_ok:	; make sure directory not in use
;--------
;

	mov	bx,dirp			; get the directory entry
	mov	ax,DBLOCK1[bx]		; block number of directory
	xor	bx,bx			; start at beginning
rmdir_ok1:
	push	ax ! push bx		; save block, offset
	call	fill_dirbuf		; locate directory entry
	pop	bx ! pop ax		; restore offset, block
	cmp	DNAME[di],0		; is it virgin entry?
	 je	rmdir_ok4		;    yes, no entries above here
	cmp	DNAME[di],0E5h		; is it deleted entry?
	 je	rmdir_ok3		;    yes, no problems yet...
	cmp	DNAME[di],'.'		; is it "." or ".."?
	 je	rmdir_ok3
if DELWATCH
; We have found a dir entry - better check if it is a pending delete
; and that delwatch is installed. Then we can ignore it.
	test	DATTS[di],DA_VOLUME	; is the volume label bit set
	 jz	rmdir_not_empty		;  no, can't be pending delete
	xor	dx,dx			; (also sets DH = DELW_RDMASK)
;	cmp	dx,DBLOCK1[di]		; is it really a pending delete ?
;	 jz	rmdir_not_empty		; yes, fall thru to delwatch check
	xchg	ax,dx			; AH = DELW_RDMASK, DX = dir cluster
	mov	si,di			; -> directory buffer (for DELWATCH)
	callf	ss:fdos_stub		; is the delwatch TSR installed
	xchg	ax,dx			; AX = dir cluster
	 jnc	rmdir_ok3		; delwatch will handle pending deletes
rmdir_not_empty:
endif
	mov	ax,ED_ACCESS		; return "access denied" if not empty

rmdir_inuse:				; directory not empty or in use:
	jmp	fdos_error		; return the error

rmdir_ok3:				; else this entry O.K.
	inc	bx			; check next directory entry
	cmp	bx,dirperclu		; cluster completed?
	 jb	rmdir_ok1		; loop back if more to come
	call	getnblk			; get next block in directory
	sub	bx,bx			; start at beginning of block
	cmp	ax,lastcl		; end of cluster chain?
	 jb	rmdir_ok1		; loop back if not done yet
rmdir_ok4:				; directory is empty
	mov	al,adrive
	call	hshdscrd		; discard the hash values
	clc				; "go ahead with RMDIR..."
	ret				; return, ready for the evil deed...


chkcds:		; check if any process uses directory to delete
;------
; On Entry:
;	dirp -> directory entry of DIR to check
; On Exit:
;	CY clear if DIR is in use
;
	mov	bx,dirp
	mov	cx,DBLOCK1[bx]		; block number of directory to delete
	mov	dl,physical_drv		; get the drive the subdir is in
	mov	al,-1			; start with drive A:
chkcds10:
	inc	ax			; next drive
	call	get_ldt_raw		; ES:BX -> LDT for drive
	 jc	chkcds20		; bail if if bad drive
	test	es:byte ptr LDT_FLAGS+1[bx],(LFLG_NETWRKD+LFLG_PHYSICAL)/100h
	 js	chkcds10		; it can't be a network drive
	 jz	chkcds10		; it must be a physical drive
	cmp	dl,es:LDT_DRV[bx]	; does the drive match?
	 jne	chkcds10		; no, don't bother then
	cmp	es:LDT_BLK[bx],0ffffh	; is it valid
	 jne    chkcds19
	push	ax
	push	cx
	push	dx
	call	select_logical_drv	; select with media change check
	call	rebuild_ldt_curdir	; rebuild LDT_
	pop	dx
	pop	cx
	pop	ax
chkcds19:
if 0
; This didn't make the beta, so leave until the next release
; We really need to make sure we relog all SUBST's drives before
; we can be sure this is valid and fail the rmdir
	cmp	cx,es:LDT_ROOT[bx]	; is this our root block ?
	 je	chkcds20		; (ie. a SUBST'd drive)
endif
	cmp	cx,es:LDT_BLK[bx]	; does the block match
	 jne	chkcds10		; no, try next drive
chkcds20:
	ret


eject
;
; Go down one level in directory
; On Entry:
;	DIRP -> directory to open
;	PATH_DRIVE = drive to use
; On Exit:
;	AX = fdos_hds_blk (the current directory block)
;	CY clear on success
;	CY set on error
;

open_dir:
	mov	bx,dirp
	test	DATTS[bx],DA_DIR	; check if directory
	stc
	 jz	open_dir20		; fail if this is a file
	cmp	word ptr info_fcb+1,'..'
	 jne	open_dir10		; watch out if going up a level
	mov	ax,fdos_hds_blk		; get current block
	cmp	ax,fdos_hds_root	; check if at logical root already
	 jne	open_dir10		;  and if not carry on
	cmp	ax,DBLOCK1[bx]		; if we are already at the virtual root
	stc				;  and want to stay there that's OK
	 jne	open_dir20		; otherwise return an error
open_dir10:
	mov	al,physical_drv		; remember the drive
	mov	fdos_hds_drv,al
	mov	ax,DBLOCK1[bx]		; remember this directory block
	mov	fdos_hds_blk,ax
	clc				; success
open_dir20:
	ret


	Public	hdsblk
	
;======
hdsblk:					;/* check if we are in subdirectory */
;======
;
;	exit:	AX = directory block number
;		ZF = set if at root
;	regs:	others preserved

	mov	ax,fdos_hds_blk		; get current directory block
	test	ax,ax			; set ZF
	ret



parent2save_area:
;-----------------
; On Entry:
;	AX = cluster number of parent to find
; On Exit:
;	save_area contains parental name (DX = length of name)
;
	call	find_parent		; locate parent directory
	 jz	path_error		; stop in case we're in a mess
	lea	si,DNAME[bx]		; get parent directory name
	mov	di,offset save_area	; ES:DI -> scratch area
;	jmp	unparse			; make it ASCIIZ file name

;	build ASCIIZ string from directory entry
;	entry:	BX -> directory buffer
;		ES:DI -> output buffer
;	exit:	ES:DI -> next byte in buffer

unparse:
;-------
	push	di			; save base of name
	mov	cx,8			; remainder of up to 7 characters
	lea	si,DNAME[bx]		; SI -> directory name
	call	unparse_field		; copy name, strip trailing blanks
	mov	al,'.'
	stosb				; add the dot for start of extension
	push	di			; remember where extention starts
	mov	cx,3			; copy 3-char extension
	lea	si,DNAME+8[bx]		; SI -> directory extention
	call	unparse_field		; copy extension, strip trailing blanks
	pop	ax			; recover start of extension
	cmp	ax,di			; did we generate extension?
	 jne	unparse1		; skip if we did
	dec	di			; else eat the '.'
unparse1:
	xor	ax,ax
	stosb				; NUL-terminate the name
	pop	bx			; ES:BX -> base of name
	cmp	es:byte ptr [bx],05h
	 jne	unparse2		; if not mapped E5 (deleted entry/Kanji)
	mov	es:byte ptr [bx],0E5h	; else map back to E5 for Kanji support
unparse2:
	ret

unparse_field:
;-------------
;	entry:	DS:SI -> disk buffer
;		ES:DI -> ASCIIZ name to build
;		CX = field length
;	On Exit:
;		ES:DI -> end of name
;		BX preserved

	push	si			; save start of field
	add	si,cx			; SI -> end of field
	inc	cx			; one extra for LOOPE dec
unprsf10:
	dec	si			; lets look at the previous char
	cmp	ds:byte ptr [si],' '	; trailing space ?
	loope	unprsf10
	pop	si			; SI = start of field
	rep	movsb
	ret


path_error:
	jmp	fdos_ED_PATH		; return "invalid path" error

mkspace_parent:
;--------------
; save_area contains the parental name, DX bytes long. We wish to insert it
; into an ASCIIZ string so make DX bytes of space at ES:DI.
; On Entry:
;	ES:DI -> ASCIIZ, DX = byte count
; On Exit:
;	DS:SI -> parents name, CX = length of parent (DX on entry)
;
	mov	al,0			; find end of name
	mov	cx,128			; max. path length
	repne	scasb			; scan for end of path
	neg	cx
	add	cx,128			; CX = string length including NUL
	mov	ax,cx
	add	ax,dx
	cmp	ax,64
	 ja	path_error
	dec	di			; ES:DI -> '\0'
	mov	si,di			; SI -> source of copy
	add	di,dx			; point to beyond insertion
	push	ds
	push	es ! pop ds		; move string backwards to make space
	std ! rep movsb ! cld		; for directory name
	pop	ds
	mov	cx,dx			; CX = length of new directory name
	mov	si,offset save_area	; SI -> unparsed name
	ret

;	find parent directory starting with cluster AX
;	entry:	AX = cluster of parent to find
;	exit:	ZF = 1 if not found (shouldn't happen)
;			-or-
;		ZF = 0 if found, BX=DIRP -> dir entry

find_parent:
	mov	blk,ax			; save the block number
	push ds ! pop es
	mov	di,offset info_fcb+1
	mov	ax,'..' ! stosw		; file name is '..'
	mov	al,' '			; pad with spaces
	mov	cx,9 ! rep stosb
	call	finddfcbf		; find pointer to parent
	 jz	fndpar2			; shouldn't happen...
	call	open_dir		; go up one level
	 jc	fndpar3			; screwed up by security...
	call	setenddir		; search from beginning
fndpar1:
	sub	cx,cx
	call	getdir			; find next directory entry
	 jz	fndpar2			; end of directory
	mov	al,DNAME[bx]		; check if deleted file
	cmp	al,0E5h
	 je	fndpar1			; skip empty slots
	cmp	al,0
	 je	fndpar2			; end of directory
	test	DATTS[bx],DA_DIR	; try to find directory
	 jz	fndpar1			; skip plain files
	mov	ax,DBLOCK1[bx]		; get starting cluster
	cmp	ax,blk
	 jne	fndpar1
fndpar3:
	or	ax,0FFFFh		; force non-zero condition
fndpar2:
	ret				; ZF = 0 if found

eject
path_prep_chk:
;-------------
;	Run down the path and parse final name
;	exit:	ds:dx -> info_fcb parsed at path end

	call	path_prep		; prepare the path
	call	chk_no_dev		; devices not allowed
chk_no_dot_or_wild:
;------------------
	call	chk_no_dot		; no subdirs entries either
;	jmp	chk_no_wild		; wild cards not allowed

eject
chk_no_wild:		; make sure path doesn't contain wild cards
;-----------		;  (or is all spaces)
	call	check_no_wild		; error if any wildcards
	 jne	check_no_wild_ret	;  or if all spaces
	jmp	fdos_ED_FILE		; return "invalid filename"

check_no_wild:		; make sure path doesn't contain wild cards
;-------------		;  (or is all spaces) ZF set on problem
	push	es
	push	ds ! pop es		; ES -> SYSDAT
	mov	di,offset info_fcb+1
	mov	cx,11
	mov	al,'?'			; scan for wild cards
	repne	scasb
	 je	check_no_wild_exit	; skip if wild cards found
	mov	di,offset info_fcb+1
	mov	cx,11
	mov	al,' '			; scan for all spaces
	repe	scasb			; ZF set if a problem
check_no_wild_exit:
	pop	es
check_no_wild_ret:
	ret


chk_for_root:
;------------
; On Entry:
;	info_fcb -> name of failed search
;	fdos_hds -> dir we searched in
; On Exit:
;	ZF set if a search for root (or '.' in root)
;
	cmp	fdos_hds_blk,0		; are we in the root ?
	 jne	chk_for_root10		; no, no further checks required
	push ds ! pop es
	mov	di,offset info_fcb+1
	mov	al,'.'			;  check for root
	scasb				; is it a '.' entry ?
	 jne	chk_for_root10
	mov	cx,8+3-1
	mov	al,' '
	repe	scasb			; is it all spaces ?
chk_for_root10:
	ret


;	Parse a pathname into an info_fcb
;	entry:	es:si -> asciiz string
;		AX = drive code
;	exit:	es:si -> next asciiz name in path
;		dx -> fcb
;		CY clear, AL = 0 if end of string
;		CY set, AX = error code

parse_path:
;----------
	push	ds ! push  es
	pop	ds ! pop   es

	call	clear_info_fcb		; initialise to blanks and drive AL

	mov	dx,offset info_fcb	; use a scratch fcb
	mov	di,dx			; dx saves initial di
	inc	di

	mov	ax,[si]			; check first two chars
	cmp	al,'.'			; special case:  if name = '.'
	 jne	parse_path20		;   then we parse it differently
	movsb				; copy the '.'
	cmp	ah,'.'			; special case:  if name = '..'
	 jne	parse_path10		;   then we parse it differently
	movsb				; copy '..'
parse_path10:
	lodsb				; get next char
	cmp	al,' '			; skip all spaces
	 je	parse_path10
	jmps	parse_path30		; now exit as normal

parse_path20:
	call	check_delim		; if first char = delimeter
	 je	parse_path30		;   then only allow '\'

;	filename begins with a legal char, parse it normally

	mov	di,dx
	inc	di			; di -> fcb name field
	mov	cx,8			; length of name field
	call	parse_one		; parse just the name
	mov	di,dx			; DI -> FCB
	cmp	es:byte ptr 1[di],0E5h	; is first character E5?
	 jne	parse_path30		; skip if not
	mov	es:byte ptr 1[di],05h	; else make it internal synonym
parse_path30:
	cmp	al,'.'
	 jne	parse_path40		; skip if no extension
	add	di,9			; di -> fcb ext field
	mov	cx,3			; length of ext field
	call	parse_one		; parse just extension
parse_path40:
if PASSWORD
	cmp	al,';'			; check if password specified
	 jne	parse_path50		; skip if no password
	mov	di,offset password_buffer
	mov	cx,8			; length of password field
	call	parse_one		; parse just password
	push	ax
	push ds ! push si
	push ss ! pop ds		; DS:SI -> ASCII password
	mov	si,offset password_buffer
	call	hash_pwd		; AX = encrypted password
	mov	local_password,ax	; remember it in case we need it
	pop si ! pop ds
	pop	ax
endif
parse_path50:
	test	al,al			; a NUL is OK
	 jz	parse_path90
	call	check_slash		; if terminator != '\' or '/',
	stc				; assume an error
	 jne	parse_path80		; report it if so
parse_path60:
	lodsb				; get next character
	call	check_delim		; we expect a normal character
	 jne	parse_path80		;  here - exit if we've got one
	call	check_slash		; swallow '\'s at this point and leave
	 je	parse_path60		;  other delimiters for next time
	cmp	al,'.'			; trailing '\.' ?
	 jne	parse_path75
	mov	cx,si			; remember position of '.'
parse_path70:
	lodsb				; now discard trailing spaces
	cmp	al,' '
	 je	parse_path70		; keep going until we lose all spaces
	test	al,al			; stop at a NUL
	 jz	parse_path50
	call	check_slash		; if it's a '\' try again
	 je	parse_path50
	mov	si,cx			; retract to the '.'
parse_path75:
	mov	al,'\'			; return '\' as the delimiter
	clc				;  and exit with no problems
parse_path80:
	dec	si			; retract a byte (CY unaffected)
parse_path90:
	push  ds  !  push  es
	pop   ds  !  pop   es
	ret



	Public	parse_one
	
;	Parse a single name or extension
; On Entry:
;	DS:SI -> asciiz name
;	ES:DI -> start of fcb field
;	CX = field size
; On Exit:
;	AL = last char parsed
;
; nb. make no assumptions about DS and ES
;

parse_one:
	lodsb				; grab asciiz char
	cmp	al,'*'			; if char = *, then fill
	 jz	parse_one_wild		;   rest of field with '?'
	call	check_delim		; if char is not delimiter,
	 jnz	parse_one_char		;   then move it to fcb
	ret				; if delimiter, return

parse_one_wild:
	mov	al,'?'
	rep	stosb			; after filling
	jmps	parse_one_ignore	; skip until a delimiter

parse_one_char:
if KANJI
	call	dbcs_lead		; is it 1st byte of Kanji pair?
	 jnz	parse_one_skip		; skip if straight 8-bit
	inc	si			; assume both chars discarded
	dec	cx			; we will copy 2 bytes
	 jcxz	parse_one_ignore	; ignore both if only room for one
	stosb				; thats the first byte
	dec	si			; point at 2nd again
	lodsb				; get the 2nd byte
parse_one_skip:
endif
	stosb				; send char to fcb
	loop	parse_one		; get another character from ASCIIZ string
parse_one_ignore:
	lodsb
	call	check_delim		; ignore up to next delimiter
	 jnz	parse_one_ignore
	ret

;
;
;	Check for a path name delimiter
;	entry:	AL = ASCIIZ char
;	exit:	all registers preserved
;		ZF = 1 if char is a delimeter
;		ZF = 0 if char is legal in file names

	Public	check_delim

check_delim:
;-----------
	cmp	al,' '			; if any printable char,
	 jae	check_delim_char	;   then skip
	cmp	al,al			; set zf
	ret

check_delim_char:
if KANJI
	call	dbcs_lead		; if it's 1st of kanji pair
	 jne	check_delim10		; DON'T upper case it
	test	al,al			; clear zf
	ret				; (should really check the 2nd byte)
check_delim10:
endif
	call	toupper			; make it upper case
	push es ! push di ! push cx
	push cs ! pop es
	lea	di,delim_string		; es:di -> delimeters
	mov	cx,length delim_string
	cld
	repnz	scasb			; match al against the list
	pop cx ! pop di ! pop es
	clc				; never return cf set
	ret				; with zf set by scasb

delim_string	db  ':.;,=+\<>|/"[]'	; DOS delimeters


;	Check AX for '\\'

	Public	check_dslash

check_dslash:
	xchg	al,ah
	call	check_slash
	xchg	al,ah
	 jne	check_slash_done
;	jmp	check_slash

;	Check delimeter character for '\' or '/'
;	entry:	al = char
;	exit:	zf = 1 if either slash

check_slash:
	cmp	al,'\'			; if first char is a backslash
	 jz	check_slash_done	;   or a frontslash, then
	cmp	al,'/'			;   return with zf set
check_slash_done:
	clc				; never return cf set
	ret

;	Convert character to upper case
;	WARNING - may be called with DS <> SYSDAT

toupper:
;-------
	test	al,al
	 js	toupper_intl
	cmp	al,'a'
	 jb	isupper
	cmp	al,'z'
	 ja	isupper
	sub	al,'a'-'A'
isupper:
	ret

toupper_intl:
	callf	ss:intl_xlat		; call international upper case vector
	ret

eject
kill_file:	; release clusters for file/dir and delete entry
;---------
	mov	bx,dirp			; get pointer to directory entry
if DELWATCH
	call	hdsblk			; AX = directory root cluster
	xchg	ax,dx			; DX = dir cluster
	mov	cx,dcnt			; CX = directory index for entry
	mov	ah,DELW_DELETE		; we are about to delete this dir
	mov	al,physical_drv		;  directory entry so give delwatch
	callf	ss:fdos_stub		;  a chance to make it pending delete
	 jnc	kill_file10		; delwatch took it - just update dir
endif
	mov	al,0E5h			; deleted file mark
	xchg	al,DNAME[bx]		; delete the directory entry
	mov	DUNDEL[bx],al		; save 1st letter for UNDEL command
	mov	ax,DBLOCK1[bx]		; get starting block #
	call	delfat			; release all clusters
kill_file10:
	jmp	flush_dirbuf		; update the directory
					; done it! (DIR/FAT still dirty)
eject

mustbe_nolbl:
;------------
; On Entry:
;	None
; On Exit:
;	Only returns if no label exists
;	forces us to root of drive
;
	push	ds ! pop es		; ES = DS for string ops
	mov	si,offset info_fcb+1
	mov	di,offset save_area	; SI->search name, DI->save area
	mov	cx,11
	push	di			; save save_area
	push	si			; save info_fcb+1
	push	cx			; save length
	rep	movsb			; copy search name into save area
	pop	cx			; CX = length (11)
	pop	di			; DI = info_fcb+1
	push	di
	push	cx
	mov	al,'?'			; now fill info_fcb with wildcards
	rep	stosb
	call	find_labelf		; look for a volume label
	pop	cx			; CX = length (11)
	pop	di			; DI = info_fcb+1
	pop	si			; SI = save_area
	push	ds ! pop es		; ES = DS for string ops
	rep	movsb			; restore info_fcb
	 jnz	mustbe_nolbl10		; if we found a label bail out
	ret
mustbe_nolbl10:
	jmp	fdos_ED_ACCESS		; return access denied

find_labelf:				; find label only
;-----------				; forces us to root
; On Entry:
;	None
; On Exit:
;	ZF clear if volume label found
;	dirp/dcnt tell where label is
;
	call	setenddir		; start from beginning
;	jmp	find_label

find_label:				; find label only
;----------				; forces us to root
; On Entry:
;	dcnt -> location to search from
; On Exit:
;	ZF clear if volume label found
;	dirp/dcnt tell where label is
;
	mov	chdblk,0		; don't assume sequential access
	mov	fdos_hds_blk,0		; look for labels in the root
	mov	finddfcb_mask,000ffh	; return VOL labels, not pending dels
find_label30:
	call	finddfcb		; find matching file name
	 jz	find_label40		; skip if not found
	test	DATTS[bx],DA_VOLUME
	 jz	find_label30		; try again if not a volume label
find_label40:
	mov	finddfcb_mask,DA_VOLUME*256
	ret				; back to no VOL labels or pending dels

if UNDELETE
find_pending_delete: 			; find pending delete only
;-------------------
; On Entry:
;	dcnt -> location to search from
; On Exit:
;	ZF clear if pending delete entry found
;	dirp/dcnt tell where entry is
;
	mov	finddfcb_mask,0h	; return pending delete entries
find_pending_delete10:
	mov	al,05h			; replace 1st char with 05h
	xchg	al,info_fcb+1		;  saving char we really want
	push	ax
	call	finddfcb		; find matching file name
	pop	ax
	mov	info_fcb+1,al		; restore original 1st letter
	 jz	find_pending_delete30	; skip if not found
	test	DATTS[bx],DA_VOLUME	; Is it a pending delete entry
	 jz	find_pending_delete10	; No, try again if not a volume label
	cmp	word ptr DBLOCK1[bx],0 	; Is this a pending delete entry
	 jz	find_pending_delete10	; No, try again if not correct
	cmp	al,'?'			; wildcard is OK
	 je	find_pending_delete20
	cmp	al,DUNDEL[bx]		; does saved char match what we want?
	 jne	find_pending_delete10
find_pending_delete20:
	and	DATTS[bx],not DA_VOLUME	; mask out volume bit
	mov	al,DUNDEL[bx]		; move deleted character to normal
	mov	DNAME[bx],al		;  position for return
	or	al,al			; clear the zero flag (assumes al!=0)
find_pending_delete30:
	mov	finddfcb_mask,DA_VOLUME*256
	ret				; back to no VOL labels or pending dels

endif

eject
find_xfn:	; find spare external file number
;--------
;	exit:	DI = new handle

	
	push	es			; save ES while we play
	xor	di,di			; return handle 0 if don't have PSP
	call	get_xftptr
	 jc	fndxfn2
	mov	bx,di			; save the offset
	mov	al,0FFh			; look for unused slot
	repne	scasb			; loop while CX != 0 and *ES:DI != 0FFh
	 jne	fndxfn3			; ZF = 1 if match, else none found
	dec	di			; DI = matching offset
	sub	di,bx			; DI = handle index
fndxfn2:
	pop	es
	clc				; indicate no error
	ret

fndxfn3:
	pop	es
	stc				; indicate an error
	ret

	Public	alloc_dhndl

alloc_dhndl:
;-----------
; provisionally allocate a spare DHNDL_, do not return without one
; On Entry:
;	None
; On Exit:
;	AX = IFN of handle
;	ES:BX -> DHNDL_ structure
;	(All other regs preserved)
	call	find_dhndl		; try to find a spare DHNDL_
	 jc	fdos_ED_HANDLE		; bail out if we can't
	ret

mustbe_free_handle:
;------------------
; return an error to user if either an XFN or an IFN is unavailable
; On Entry:
;	None
; On Exit:
;	None
;
	call	alloc_dhndl		; make sure we can allocate a DHNDL
	mov	es:DHNDL_COUNT[bx],0	; free it in case open/creat fails
					; (we are protected by local_disk)
;	jmp	alloc_xfn		; make sure an XFN is also free

	Public	alloc_xfn

alloc_xfn:	; find spare external file number
;--------
;	exit:	DI = new handle

	call	find_xfn		; try to find spare slot in XFT
	 jc	fdos_ED_HANDLE		; "out of handles" if no luck
	mov	fdos_ret,di		; else save the resulting handle
	ret


fdos_ED_HANDLE:
	mov	ax,ED_HANDLE		; out of user file #s, all 20 in use
	jmp	fdos_error


;	Allocate & initialize file handle:
;	entry:	AX = open mode
;		DIRP -> directory entry
;	exit:	ES:BX = CURRENT_DHNDL = file handle
;		AX = fdos_ret = IFN
;			-or-
;		System call fails with ED_HANDLE

open_handle:
;-----------
	mov	bx,dirp			; else get directory entry
	test	DATTS[bx],DA_RO		; check if file is r/o - if so
	 jz	creat_handle		;  make handle r/o too
	and	ax,not DHM_RWMSK
creat_handle:
;------------
; entry point for create file - when you create a read-only file
; you still get a handle you can write with !
	push	ax			; save open mode
	xchg	ax,si			; SI = open mode
	mov	di,S_OM_COMPAT		; check if open/sharing modes are compatible
	call	check_with_share	; does SHARE approve ?
	call	alloc_dhndl		; allocate a DHNDL_ structure
	mov	fdos_ret,ax		; remember IFN in case it's FCB
	pop dx ! push dx		; DX = open mode
	push es ! push bx		; save DHNDL_ pointer
	test	dh,DHM_FCB/100h		; FCB call?
	 jne	creat_handle10		; skip XFN allocation if FCB
	push	ax			; save IFN
	call	alloc_xfn		; allocate spare XFN
	pop	ax			; recover IFN
	mov	bx,di			; BX = XFN
	call	get_xftptr		; ES:DI -> user file table
	 jc	creat_handle10			; skip if we don't have one
	mov	es:[di+bx],al		; set IFN in PSP
creat_handle10:
	pop bx ! pop es
	lea	di,DHNDL_COUNT[bx]	; point at open count
	mov	ax,1
	stosw				; open by one
	pop	ax			; recover open mode
	mov	cx,DHAT_TIMEOK+DHAT_CLEAN
	test	al,DHM_LOCAL		; is it private ?
	 jz	creat_handle20
	or	ch,DHAT_LOCAL/256	; rememmber it's local
	and	al,not DHM_LOCAL	; clear inherit bit
creat_handle20:
;	lea	di,DHNDL_MODE[bx]	; update the mode
	stosw
	mov	si,dirp
	mov	al,DATTS[si]		; get file attribute byte
	lea	di,DHNDL_DATRB[bx]	; now copy file attribute
	stosb				;  to DHNDL_
	xchg	ax,cx			; AX = attributes
	or	al,physical_drv		; get physical drive
;	lea	di,DHNDL_WATTR[bx]	; make as clean disk file
	stosw
;	lea	di,DHNDL_DEVOFF[bx]	; ES:DI -> dd entry in DHNDL_
	mov	ax,ss:word ptr current_ddsc
	stosw				; point to DDSC_
	mov	ax,ss:word ptr current_ddsc+WORD
	stosw
	mov	ax,DBLOCK1[si]		; get starting cluster of file
;	lea	di,DHNDL_BLK1[bx]
	stosw
	lea	si,DTIME[si]
;	lea	di,DHNDL_TIME[bx]
	movsw				; copy the time
;	lea	di,DHNDL_DATE[bx]
	movsw				; and the date
	lodsw				; skip 1st cluster (already done)
;	lea	di,DHNDL_SIZE[bx]
	movsw ! movsw			; copy the file size
;	lea	di,DHNDL_POS[bx]
	xor	ax,ax			; zero current position
	stosw ! stosw
;	lea	di,DHNDL_IDX[bx]
	stosw				; zero block index
if DOS5
	call	hdsblk			; get directory block
;	lea	di,DHNDL_DBLK[bx]
	stosw
	xor	ax,ax
	stosw
else
;	lea	di,DHNDL_BLK[bx]
	stosw				; and current block
	call	hdsblk			; get directory block
;	lea	di,DHNDL_DBLK[bx]
	stosw
endif
	mov	ax,dcnt			; set DCNT of file
;	lea	di,DHNDL_DCNTLO[bx]
	stosb				; store low byte of DCNT
	mov	es:DHNDL_DCNTHI[bx],ah	;  and hi byte
;	lea	di,DHNDL_NAME[bx]	; copy name from dir entry
	mov	si,dirp
	mov	cx,11
	rep	movsb
	xor	ax,ax
    stosw               ; zero DWORD 
    stosw               
	lea	di,DHNDL_SHARE[bx]	; zero sharing record
	stosw
if DOS5
	stosw ! stosw ! stosw		; zero DHNDL_BLK + IFS
endif
	callf	ss:share_stub+S_OPEN	; we have opened this handle
					;  ask SHARE to register it
	 jc	create_handle30
	mov	ax,fdos_ret		; AX = handle to return
	ret
create_handle30:			; free the handle again
	push	ax
	mov	ax,fdos_ret
	call	release_handle2
	pop	ax
	jmp	fdos_error



	Public	verify_handle

verify_handle:
;-------------
; On Exit:
;	ES:BX = DHNDL_
;	CY set if bad file handle (nb. device handle is bad)
;
	call	check_handle		; make sure we can access it
	 jc	vfy_hnd9		; return if character device

select_handle:		; select directory of current handle
;-------------
; On Entry:
;	ES:BX -> DHNDL_
; On Exit:
;	ES:BX preserved
;
	mov	al,es:DHNDL_ATTR[bx]	; get physical drive
	and	al,DHAT_DRVMSK		;  from attrib field
	push es ! push bx
	call	select_physical_drv	; select the drive
	pop bx ! pop es
	mov	ax,es:DHNDL_DBLK[bx]
	mov	fdos_hds_blk,ax		; copy HDS_BLK
	clc				; handle is OK file
vfy_hnd9:
	ret				; good handle


;	Checks if parameter is a legal file handle:
;	Entry:	CURRENT_DHNDL = handle to verify
;	Exit:	ES:BX = DHNDL_ if O.K.
;		CY clear if local disk file
;		CY set if device/network handle
;	Note:	doesn't return on error

check_handle:
;------------
	les	bx,current_dhndl
	test	es:DHNDL_WATTR[bx],DHAT_REMOTE+DHAT_DEV
	stc				; assume device/network file
	 jnz	chkhnd10		; return with CY = 0 if disk file
	callf	ss:share_stub+S_UPDATE	; make sure DHNDL_ info valid
	 jc	chkhnd20		; if not close it down
chkhnd10:
	ret

chkhnd20:
	call	get_xftptr		; ES:BX -> XFT
	 jc	fdos_ED_H_MATCH		; skip if not handle
	mov	bx,fdos_pb+2		; get XFN so we can poke
	mov	es:byte ptr [di+bx],0ffh
					;  PSP handle to closed
;	jmp	 fdos_ED_H_MATCH	; return "invalid handle"

fdos_ED_H_MATCH:
	mov	ax,ED_H_MATCH		; "invalid handle"
	jmp	fdos_error		; return an error

	public	vfy_dhndl_ptr

vfy_dhndl_ptr:
;=============
;	Verifies file handles at FDOS_xxxx calling level
;		before MXdisk is locked.
; On Entry:
;	SS:BP -> func #, parm off, parm seg
;	stack holds two ret addresses + above values
; On Exit:
;	ES:BX -> DHNDL_

	mov	si,2[bp]		; SI -> parameter block
	mov	ax,2[si]		; get file handle from parameter block
	test	ss:byte ptr remote_call+1,DHM_FCB/100h; if we are doing an FCB operation
	 jnz	vfy_dhndl10		;  deal only with IFN, forget PSP
	mov	es,ss:current_psp
	cmp	ax,PSP_XFNMAX		; CX = # entries in table
	 jae	vfy_dhndl_err
	les	di,PSP_XFTPTR		; ES:DI -> user file table
	mov	bx,ax			; get user file number (0-19)
	mov	al,es:[bx+di]		; get IFN for this handle
vfy_dhndl10:
	cmp	al,0ffh			; invalid handle?
	 je	vfy_dhndl_err		; 00h-FEh only
	les	bx,ss:file_ptr		; get the address of the first entry
vfy_dhndl20:
	cmp	ax,es:DCNTRL_COUNT[bx]	; handle in this block?
	 jae	vfy_dhndl50		; skip to try next block if not
    mov ah,DHNDL_LEN     
	mul	ah
	add	ax,DCNTRL_LEN		; skip the header
	add	bx,ax			; add to start of structure
	mov	cx,es:DHNDL_COUNT[bx]	; fail if the handle is not in use
     jcxz   vfy_dhndl_err
	inc	cx			; FFFF = pre-allocated is also failed
	 jz	vfy_dhndl_err
	cmp	ss:WindowsHandleCheck,26h
					; have we been patched
	 jne	vfy_dhndl30		;  yes, don't check owner
	mov	ax,ss:machine_id	; get current process
	cmp	ax,es:DHNDL_UID[bx]	;  are we the owning process
	 jne	vfy_dhndl_err		; no, return an error
vfy_dhndl30:
	mov	ss:word ptr current_dhndl,bx
	mov	ss:word ptr current_dhndl+WORD,es
	test	es:DHNDL_MODE[bx],DHM_NOCRIT
	 jnz	vfy_dhndl40		; are critical errors allowed ?
	ret

vfy_dhndl40:
	or	valid_flg,NO_CRIT_ERRORS
	ret				; remember no critical errors possible

vfy_dhndl50:
	sub	ax,es:DCNTRL_COUNT[bx]	; update the internal file number
	les	bx,es:DCNTRL_DSADD[bx]	; get the next entry and check
	cmp	bx,0FFFFh		;   for the end of the list
	 jnz	vfy_dhndl20
vfy_dhndl_err:
	add	sp,WORD			; pop return addr - return to caller
	mov	bx,ED_H_MATCH		;  with "invalid handle" error
	ret




; On Entry:
;	FDOS_PB+2 = external file handle to release
; On Exit:
;	ES:BX -> DHNDL_

release_handle:
;--------------
	mov	ax,fdos_pb+2		; get user file number (0-19)
release_handle2:
;---------------
	call	get_xftptr		; ES:DI -> XFN table
	 jc	release_ifn		; IFN = XFN if no PSP
	cmp	ax,cx			; more than in handle table?
	 jae	rlshnd_err		; return error if too large
	xchg	ax,bx			; BX = external file number
	mov	al,0FFh			; get old IFN, release XFN
	xchg	al,es:[bx+di]		; get IFN for this handle
	cmp	al,0FFh
	 je	rlshnd_err
release_ifn:
	call	ifn2dhndl		; ES:BX -> DHNDL_
	 jnc	check_no_dir_ret	; return if no error

rlshnd_err:				; else bad handle
	jmp	fdos_ED_H_MATCH		; return the error


eject
check_no_dir:		; check if entry is directory
;--------
;	entry:	BX -> directory entry

	test	DATTS[bx],DA_DIR	; test if directory
	 jnz	chk_ro_err		; skip if a directory
check_no_dir_ret:
	ret				; else return to caller

check_ro:		; check if file write protected
;--------
;	entry:	BX -> directory entry

	test	DATTS[bx],DA_RO		; test if file r/o
	 jnz	chk_ro_err		; skip if file r/o
	ret				; else return to caller
chk_ro_err:
	jmp	fdos_ED_ACCESS		; return "access denied" if r/o



discard_files:				; discard all handles on adrive
;-------------
	mov	al,adrive
	callf	ss:share_stub+S_DISCARD	; tell share to forget about files
	ret



;	Close file if it's us in compatibility modes

close_if_same_psp:
;-----------------
;	Note:	We first check if the file is open by anyone other
;		than the same UID and PSP, or is open in shared mode.
;		In either case we deny the operation.
;		Otherwise we fall through and close the file.
;		We could do it in one with a new share, but this way means
;		less changes.
;
	mov	di,S_DENY_IF_OPEN	; check if file already open
	call	check_with_share	; and stop if it is
;	jmp	close_if_open

;	Make sure our file is not open

close_if_open:
;-------------
;	entry:	HDSADR->BLK = block # of directory
;		HDSADR->DRV = drive number
;		DCNT = directory position
;	Note:	If the file is open by any other process,
;		error ED_SHAREFAIL is returned from the system call
;		If open by us (same UID, any PSP) in compatibility mode,
;		close it and allow us to proceed.

	mov	di,S_CLOSE_IF_OPEN	; check if file already open
;	jmps	check_with_share

check_with_share:
;----------------
; On Entry:
;	DI = S_ share query
; On Exit:
;	Come back if share says it's OK.
;
	call	hdsblk			; get directory block in AX
	xchg	ax,dx			; directory block
	mov	cx,dcnt			; get the directory count in CX
	mov	al,physical_drv		; get physical drive in AL
	callf	ss:share_stub[di]	; ask SHARE if it knows of anyone open
	 jc	check_with_share10
	ret				; must be OK.

check_with_share10:
	jmp	fdos_error		; bail out with an error


eject

eject
;
;	Return a pointer to the DOS Handle corresponding to the internal
;	handle number passed in AX
;
; On Entry:
;	AL = IFN
;
; On Exit:
;	ES:BX -> DOS handle (All other Regs preserved)
;		CY set if no corresponding valid DHNDL_
;
	Public	ifn2dhndl
ifn2dhndl:
	push	ax
	xor	ah,ah			; make IFN a word
	les	bx,ss:file_ptr		; get the address of the first entry

ifn2dh10:
	cmp	ax,es:DCNTRL_COUNT[bx]	; handle in this block?
	 jae	ifn2dh20		; skip if not
	mov	ah,DHNDL_LEN		;  calculate offset of the DOS Handle
	mul	ah
	add	bx,ax			; add structure offset (should be 0) 
	add	bx,DCNTRL_LEN		;    and then skip the header
	pop	ax
	clc
	ret				; ES:BX -> valid DHDNL_

ifn2dh20:
	sub	ax,es:DCNTRL_COUNT[bx]	; update the internal file number
	les	bx,es:DCNTRL_DSADD[bx]	; get the next entry and check
	cmp	bx,0FFFFh		;   for the end of the list
	 jnz	ifn2dh10 
ifn2dh15:
	pop	ax
	stc
	ret				; invalid file handle number



;
;	Allocate a DHNDL_ structure
;
; On Entry:
;	None
;
; On Exit:
;	CY clear if handle allocated
;	AX = IFN of handle
;	ES:BX -> DOS handle
;	(All other Regs preserved)
;
	Public	find_dhndl
find_dhndl:
	push cx ! push dx ! push di ! push si
	mov	ax,ss:machine_id	; get current process
	mov	dx,ss:owning_psp	; DX = owining PSP
	xor	si,si			; SI = IFN
	les	di,ss:file_ptr		; get the address of the first entry
find_dh10:
	mov	cx,es:DCNTRL_COUNT[di]	; get # handles in this block
	 jcxz	find_dh40		; skip if none
	lea	bx,DCNTRL_LEN[di]	; ES:BX -> 1st DHNDL_
find_dh20:
	push	cx
	cli				; be alone while looking at handles
	mov	cx,es:DHNDL_COUNT[bx]
	 jcxz	find_dh50		; if handle free grab it
	inc	cx			; FFFF = allocated but unused
	 jnz	find_dh30
	cmp	ax,es:DHNDL_UID[bx]	; was it allocated to us
	 jne	find_dh30
	cmp	dx,es:DHNDL_PSP[bx]	; if so use it again
	 je	find_dh60
find_dh30:
	sti				; finished with this handle
	pop	cx
	inc	si			; onto next IFN
	cmp	si,0FFh			; only handles 00-FE are valid
	 jae	find_dh45		;  so bail if out of range
	add	bx,DHNDL_LEN		; onto next handle in the block
	loop	find_dh20
find_dh40:
	les	di,es:DCNTRL_DSADD[di]	; get the next entry and check
	cmp	di,0FFFFh		;  it's valid
	 jne	find_dh10
find_dh45:
	stc				; no more handles,
	jmps	find_dh70		;  exit in failure..

find_dh50:
	mov	es:DHNDL_COUNT[bx],0FFFFh
	mov	es:DHNDL_UID[bx],ax	; allocate it to us
	mov	es:DHNDL_PSP[bx],dx
find_dh60:
	sti				; safe again
	pop	cx			; discard handle count
	xchg	ax,si			; AX = IFN
	mov	ss:word ptr current_dhndl,bx
	mov	ss:word ptr current_dhndl+WORD,es
	mov	ss:current_ifn,ax
	clc				; we have found and allocated a handle
find_dh70:
	pop di ! pop si ! pop dx ! pop cx
	ret


	Public	get_xftptr

get_xftptr:
;----------
; On Entry:
;	None
; On Exit:
;	ES:DI -> PSP_XFTPTR for current_psp
;	CX = # entries in it
;	CY set if not PSP operation (eg. FCB's)
;	(all other regs preserved)
;

	test	ss:byte ptr remote_call+1,DHM_FCB/100h; if we are doing an FCB operation
	 jnz	get_xftptr_err		;  deal only with IFN, forget PSP
	mov	es,ss:current_psp
	mov	cx,PSP_XFNMAX		; CX = # entries in table
	les	di,PSP_XFTPTR		; ES:DI -> user file table
	ret

get_xftptr_err:
	stc				; forget about XFN's
	ret


	Public	current_dsk2al
	
current_dsk2al:
;--------------
; AL = current default drive
	mov	al,ss:current_dsk
	ret

	Public	lds_si_dmaptr
	
lds_si_dmaptr:
;-------------
; On Entry:
;	None
; On Exit:
;	DS:SI -> current DMA address
;	(All other regs preserved)
	lds	si,ss:dword ptr dma_offset
	ret


	public	les_di_dmaptr

les_di_dmaptr:
;-------------
; On Entry:
;	None
; On Exit:
;	ES:DI -> current DMA address
;	(All other regs preserved)
	les	di,ss:dword ptr dma_offset
	ret

	Public copy_asciiz
	
copy_asciiz:
;----------
; Copy an ASCIIZ string from DS:SI to ES:DI
	lodsb ! stosb
	test	al,al
	 jnz	copy_asciiz
	ret

if JOIN

check_join:
;----------
; On Entry:
;	fdos_hds -> HDS we wish to check
; On Exit:
;	AH = drive (zero based) from fdos_hds_drv
;	AL = drive (zero based) of the JOIN root
;	if JOINed drive
;	    ZF clear
;	else
;	    ZF set
;	(All other regs presrved)
;
	push es ! push bx
	mov	al,fdos_hds_drv		; get drive from HDS_
	mov	ah,al			; save HDS_DRV in AH
 	cmp	join_drv,0		; need we do anything ?
	 je	check_join30		;  not if we haven't JOIN'd
	cmp	fdos_hds_root,0		; is virtual root the physical one ?
	 jne	check_join10		;  if not we can't be JOIN'd
	call	get_ldt			; ES:BX -> LDT for this drive
	 jc	check_join10		; bad LDT - we can't be joined
	test	es:LDT_FLAGS[bx],LFLG_JOINED
	 jz	check_join30
	mov	al,es:LDT_NAME[bx]	; get drive letter
	sub	al,'A'			; make drive letter zero based
	mov	bl,byte ptr path_drive	; get the logical drive we are on
	push	ax
	mov	al,bl			; AL = logical drive we are using
	call	get_ldt			; ES:BX -> LDT for this drive
	pop	ax
	 jc	check_join10		; no valid LDT..
	cmp	es:LDT_NAME+2[bx],'\'	; are we at the root ?
	 jne	check_join10		; no, it can't be match
	mov	bl,es:LDT_NAME[bx]	; get the root drive for this drive
	sub	bl,'A'			; make drive letter zero based
	cmp	al,bl			; are we on the JOIN'd drive ?
	mov	bl,0ffh			; assume we are
	 je	check_join20		; were we ?
check_join10:
	mov	al,ah			; restore HDS_DRV
	xor	bl,bl			; return with ZF clear
check_join20:
	test	bl,bl			; set ZF appropriately
check_join30:
	pop bx ! pop es
	ret

offer_join:		; are we opening a JOIN'd drive ?
;----------
; If we are at the root of a JOIN'd drive moving up ("..") fiddle HDS
; onto parental JOIN drive root.
; If we are at the root of a non-JOIN'd drive see if we are searching for
; a JOIN'd drive directory
; On Entry:
;	info_fcb = entry we are searching for
; On Exit:
;	CY set if not a JOIN'd drive
;

	cmp	join_drv,0		; need we do anything ?
	 jne	oj_dochecks		; not, if we haven't JOIN'd
oj_rejected:
	stc				; not a JOIN drive
	ret

oj_dochecks:
;-----------
; Before we do anything else we must be at the physical root of a drive
; with a valid LDT. We then check for two cases
; 1) Doing a '..' from a JOIN'd drive to it's parental root
; 2) Opening a directory correpsonding to a JOIN'd drive
;
	mov	al,info_fcb		; AL -> drive we are looking for
	call	get_ldt			; ES:BX -> LDT for this drive
	 jc	oj_rejected		; bad LDT - we can't be joined
	cmp	es:LDT_ROOTLEN[bx],2	; is root at top level ?
	 ja	oj_rejected		; no, skip the rest
	cmp	fdos_hds_blk,0		; are we at the root ?
	 jne	oj_rejected		; if not we needn't do anything

; We have validated HDS_, so now check for case 1)
	call	check_join		; is it a joined drive ?
	 jz	oj_dir			; if not skip to case 2)
	cmp	word ptr info_fcb+1,'..'; else is it a '..' in JOIN'd root?
	 jne	oj_rejected		; if not we needn't do anything
	call	mvhds_drvroot		; do '..' from root -> real root
	clc				; we handled it it !
	ret

oj_dir:
;------
; We are in the physical root of a non-joined drive. We now need to see if
; the dir we are searching for corresponds with a joined drive
;
	push si ! push di
	call	build_match_name	; join_name = what we are looking for
	call	look_for_match		; see if we can find it
	 jc	oj_dir10
	call	mvhds_drvroot		; we have a match - move into it
	clc				; say we handled it
oj_dir10:
	pop di ! pop si
	ret

	
look_for_match:
;--------------
; Compare join_name against available JOIN drives.
; Return with CY clear if we found it, ES:BX -> LDT, AL the drive
	xor	ch,ch
	mov	cl,join_drv		; search this many drives
	xor	ax,ax			; start with drive A:
	mov	ah,last_drv
	call	get_ldt			; ES:BX -> LDT for this drive
	 jnc	lfm20
	ret				; no LDT's...
lfm10:
	inc	al			; next drive
	cmp	al,ah			; paranioa - check if we have reached
	 jae	lfm50			;  last_drv and exit if so
	add	bx,LDT_LEN		; next LDT
lfm20:
	test	es:LDT_FLAGS[bx],LFLG_JOINED
	 jz	lfm10			; if not JOIN'd try next candidate

	lea	di,LDT_NAME[bx]		; ES:DI -> JOIN info
	mov	si,offset join_name	; lets see if it matches the
	push	ax
lfm30:
	lodsb				; get a byte
	scasb				; does it match ?
	 jne	lfm40			; no, forget it
	test	al,al			; end of the string yet ?
	 jnz	lfm30			; no, keep trying
lfm40:
	pop	ax
	 je	lfm60			; did we match ?
	loop	lfm10			; no, if any JOIN's left try them
lfm50:
	stc				; we didn't find it
lfm60:
	ret

build_match_name:
;----------------
; Fill join_name with the "C:\JOIN" we want to find
;
	mov	al,info_fcb		; AL -> drive we are looking for
	dec	al			; make it zero based
	call	get_ldt			; ES:BX -> LDT for this drive
	mov	al,es:LDT_NAME[bx]	; get the drive "D"
	push ds ! pop es		; ES -> SYSDAT
	mov	di,offset join_name	; construct the target name
	stosb				; plant the drive letter
	mov	ax,'\:'
	stosw				; now we have "d:\"
	mov	bx,offset info_fcb+1	; DS:SI -> name we are looking for
	jmp	unparse			; unparse the name

BDOS_DATA	dseg	word

join_name	db	'd:\filename.ext',0

BDOS_CODE	cseg

	Public	mv_join_root
	
mv_join_root:
;------------
; Poke the fdos_hds to be the root. If it's the physical root we then
; see if it is a JOIN'd drive. If it is we poke the drive and reselect
; the disk so we are at the real root of the drive.
;
	push bx ! push si ! push di	; save index registers
	mov	ax,fdos_hds_root
	mov	fdos_hds_blk,ax		; move us to virtual root
	test	ax,ax			; is it the real root ?
	 jnz	mvj_root10		; if not forget about JOIN'd drives
	call	check_join		; are we joined ?
	 jz	mvj_root10		; no, we've done enough
	call	mvhds_drvroot		; make it real root
mvj_root10:
	pop di ! pop si ! pop bx	; restore index registers
	ret


mvhds_drvroot:
;-------------
; On Entry:
;	AL = Drive (0 based physical)
; On Exit:
;	None
;
; Poke the HDS to be at the root of drive AL and select that drive
;
	mov	fdos_hds_drv,al		; change to joined drive
	xor	dx,dx
	mov	fdos_hds_blk,dx		; put us back to the root again
	mov	fdos_hds_root,dx
	cmp	al,physical_drv		; already there ?
	 je	mvhds_drvroot10		; then skip the selection
	call	select_physical_drv	; select the drive
mvhds_drvroot10:
	jmp	path_prep_root		; info_fcb = '.'
;	ret

endif	;JOIN

eject
stamp_dir_entry:
;---------------
; On Entry:
;	DIRP -> None
; On Exit:
;	None
;
; Apply current date/time stamp to a directory, along with any other
; security information required.
;
if PASSWORD
	mov	cx,local_password	; were we given a password ?
	 jcxz	stamp_dir_entry10	;  if so apply it
	mov	bx,dirp
	mov	DPWM[bx],PWM_ANY	; deny all for compatibility
	or	DATTS[bx],DA_HIDDEN	;  make dir entry hidden
	mov	DPWD[bx],cx		;  with this password
stamp_dir_entry10:
endif
	call	ReadTOD			; get current time/dat
	mov	bx,dirp
;	jmp	stamp_date_and_time
stamp_date_and_time:
; On Entry:
;	BX -> directory entry
;	AX = time
;	DX = date
; On Exit:
;	None
; stamp a directory entry with a given date and time
	mov	DDATE[bx],dx
	mov	DTIME[bx],ax
	mov	ah,PASSWD_CREAT		; call out to SECURITY TSR
	callf	ss:fdos_stub
	 jc	stamp_date_and_time10
	jmp	fdos_error		; return an error if required
stamp_date_and_time10:
	ret


	public	ReadTOD

ReadTOD:
;-------
; On Entry:
;	None
; On Exit:
;	DX = internal date format
;	AX = internal time format
;
	call	ReadTimeAndDate		; get current time/date from BIOS
;	jmp	GetTOD

GetTOD:
;-------
; On Entry:
;	None
; On Exit:
;	DX = internal date format
;	AX = internal time format
;
	mov	ax,yearsSince1980	; year is bits 9-15
	mov	cl,4
	shl	ax,cl
	add	al,month		; month is bits 4-8
	mov	cl,5
	shl	ax,cl
	add	al,dayOfMonth		; day is bits 0-3
	xchg	ax,dx			; DX = date

	mov	al,hour			; hour is bits 11-15
	mov	cl,6
	shl	ax,cl
	add	al,minute		; minute is bits 5-10
	mov	cl,5
	shl	ax,cl
	mov	cl,second		; second/2 is bits 0-4
	shr	cl,1			;
	add	al,cl
	ret



select_from_DTA:
;----------------
; called by search next/undelete/purge to select a disk and prepare for
; a directory search based upon DTA contents.
;
	push	ds
	call	lds_si_dmaptr		; DS:SI -> DMA address
	lodsb				; get search drive
	pop	ds
	dec	ax			; (stored 1-relative)
	mov	path_drive,ax		; restore path drive
	call	select_physical_drv	; select the drive in AL
	push	ds
	push ds ! pop es		; ES = local segment
	call	lds_si_dmaptr		; DS:SI -> DMA address
	inc	si			; skip the drive #
	mov	di,offset info_fcb+1	; copy FCB back to INFO_FCB
	mov	cx,11
	rep	movsb

	lodsb				; get search attribute
	mov	es:attributes,al

	lodsw				; get directory count
	mov	es:dcnt,ax

	lodsw				; get the directory block
	pop	ds			; restore data segment
	mov	fdos_hds_blk,ax
	ret

if PASSWORD

check_pwd_d:		; check if PW protected in any mode
;-----------
	mov	ax,PWM_D
	jmps	check_pwd

check_pwd_any:		; check if PW protected in any mode
;-------------
	mov	ax,PWM_ANY
;	jmps	check_pwd

check_pwd:		; check for password mismatch
;---------
;	entry:	AX = password mode to be checked
;		DIRP -> directory entry for file
;	exit:	BX, SI preserved

	push	ax
	push	bx
	mov	bx,dirp			; BX -> file to be opened/deleted/etc.
	xchg	ax,cx			; password mode in CX
	mov	ah,PASSWD_CHECK		; call out to SECURITY TSR
	callf	ss:fdos_stub
	 jnc	check_pwd20

	mov	ax,DPWD[bx]		; get password hash code from entry
	 jcxz	check_pwd10		; exit if no password
	cmp	ax,global_password	; compare with default password
	 je	check_pwd10		; yes, go ahead
	cmp	ax,local_password	; is it one we've just parsed ?
	 je	check_pwd10		; yes, go ahead
					; else we've got a password mismatch

	test	cx,DPWM[bx]		; test if password mode affects us
	 jz	check_pwd10		; skip if attempted access O.K.
	mov	ax,ED_PASSWORD		; return password error
check_pwd20:
    jmp fdos_error      

check_pwd10:
	pop	bx
	pop	ax
	ret

	Public	hash_pwd

hash_pwd:	; compute 16-bit hash code for 1st COMMON_DMA password
;--------
; On Entry:
;	DS:SI -> 8 character password
; On Exit:
;	AX = password hash code or 0000 if none (ZF set)
;	SI corrupted, all other regs preserved
;

	push	cx
	push	bp
	mov	cx,8			; ch = 0, cl = 8
	mov	bp,sp			; reverse and upper-case the name
	sub	sp,cx			;  using temp buffer on the stack
	xor	ax,ax			; zero null password flag

hash_pwd1:
	lodsb				; get next ASCII character
	call	toupper			; international upper case
	dec	bp
	mov	ss:[bp],al		; copy password char to encrypted buff
	or	al,al			; is password char zero?
	 jz	hash_pwd2		; yes
	cmp	al,' '			; is password char blank?
	 je	hash_pwd2		; yes
	inc	ah			; password is not null
hash_pwd2:
	add	ch,al			; add password char to encrypt CRC
	dec	cl
	 jnz	hash_pwd1

	mov	al,ch			; AL = password CRC
	mov	cx,8			; encrypt 8 characters
	or	ah,al			; if CRC = 0 and all 00 or ' '
	 jz	hash_pwd6		;    then there is no password

hash_pwd3:
	xor	ss:[bp],al		; encrypt password on stack
	inc	bp
	loop	hash_pwd3

	xor	bp,bp			; initialize hash code
	mov	cx,4			; 8 bytes = 4 words
hash_pwd4:
	pop	ax			; get two encrypted characters
	rol	ax,cl			; juggle them about a bit
	xor	bp,ax			; "add" them into hash code
	loop	hash_pwd4		; repeat for whole password
	 jnz	hash_pwd5		; skip if result is non-zero
	inc	bp			; else force it to be non-zero
hash_pwd5:
	xchg	ax,bp			; return hash code in AX
hash_pwd6:
	add	sp,cx			; tidy up stack if buffer not poped
	test	ax,ax			; set ZF flag appropriately
	pop	bp
	pop	cx
	ret

endif


	public	share_delay

; waste time before retrying operation that failed due to SHARE intervention
share_delay:
	cmp	ss:byte ptr remote_call,0
	 jne	share_delay_30
	push	cx
	mov	cx,ss:net_delay
	 jcxz	share_delay_20
share_delay_10:
	push	cx
	xor	cx,cx
	loop	$
	pop	cx
	loop	share_delay_10
share_delay_20:
	pop	cx
share_delay_30:
	ret
