;
; xenix file calls for MSDOS
;

INCLUDE DOSSEG.ASM

IFNDEF  KANJI
KANJI   EQU     0       ;FALSE
ENDIF

CODE    SEGMENT BYTE PUBLIC  'CODE'
        ASSUME  SS:DOSGROUP,CS:DOSGROUP

.xlist
.xcref
INCLUDE DOSSYM.ASM
INCLUDE DEVSYM.ASM
.cref
.list

TITLE   XENIX - IO system to mimic UNIX
NAME    XENIX

        i_need  NoSetDir,BYTE
        i_need  CURDRV,BYTE
        i_need  IOCALL,BYTE
        i_need  IOMED,BYTE
        i_need  IOSCNT,WORD
        i_need  IOXAD,DWORD
        i_need  DIRSTART,WORD
        i_need  ATTRIB,BYTE
        i_need  THISFCB,DWORD
        i_need  AuxStack,BYTE
        i_need  Creating,BYTE
        i_need  ThisDRV,BYTE
        i_need  NAME1,BYTE
        i_need  LastEnt,WORD
        i_need  ThisDPB,DWORD
        i_need  EntLast,WORD
        i_need  CurrentPDB,WORD
        i_need  sft_addr,DWORD              ; pointer to head of table
        i_need  CURBUF,DWORD                ; pointer to current buffer
        i_need  DMAADD,DWORD                ; pointer to current dma address

BREAK <Local data>

CODE        ENDS
DATA        SEGMENT BYTE PUBLIC 'DATA'

open_name   DW  ?
            DW  ?
open_access DB  ?
open_jfn    DW  ?                       ; accessed as DD
open_jfn_b  DW  ?                       ; accessed as DD with above
open_sfn    DW  ?
open_sfoff  DW  ?                       ; accessed as DD
open_sfn_b  DW  ?                       ; accessed as DD with above
open_devid  DB  ?
Cr_read_only    DB  ?
rename_source   DD  ?
rename_dest     DD  ?

DATA        ENDS
CODE        SEGMENT BYTE PUBLIC 'CODE'

BREAK <Validate_path - check to see if there are meta characters in path>

;
; Input: DS:DX is an ASCIZ path
; Output: Carry set if meta-characters present or path malformed and
;           Zero is set if the only problem is that meta-characters
;               are present in the last element of the path
procedure Validate_path,near
        ASSUME  DS:NOTHING,ES:NOTHING
        PUSH    AX
        PUSH    CX
        PUSH    SI
        MOV     SI,DX
        MOV     CX,0FFH                 ;No path seps yet
        MOV     AX,[SI]                 ; Get first two bytes
        OR      AL,AL
        JZ      validate_malformed      ; NUL path
        CMP     AH,':'
        JNZ     validate_loop           ; OK so far
        CMP     BYTE PTR [SI+2],0
        JZ      validate_malformed      ; NUL path (just d:)
validate_loop:
        LODSB
validate_loop1:

        IF      KANJI
        invoke  TESTKANJ
        JZ      NOTKANJ6
        INC     SI
        JMP     validate_loop

NOTKANJ6:
        ENDIF

        OR      AL,AL
        JZ      validate_end
        CMP     AL,"?"
        JZ      validate_error
        CMP     AL,"*"
        JZ      validate_error
        invoke  PathChrCmp
        JNZ     validate_loop
        JCXZ    validate_malformed      ;If path sep, cannot have meta yet
        LODSB                           ;Look ahead one char
        OR      AL,AL
        JZ      validate_checktslsh     ;Trailing path sep
        invoke  PathChrCmp
        JNZ     validate_loop1          ;Double path sep?
validate_malformed:
        INC     CX
        OR      CX,CX                   ;Reset zero
        JMP     SHORT validate_set_carry

validate_error:
        XOR     CX,CX                   ;Flag metas found
        JMP     validate_loop

validate_checktslsh:
;A bizarre case, "/" is OK, "d:/" is OK, anything else is an error
        SUB     SI,DX
        CMP     SI,2
        JZ      validate_end            ;Two chars, the '/' and the NUL
        CMP     SI,4
        JNZ     validate_malformed      ;Four chars, "D:/<NUL>"
        MOV     SI,DX
        CMP     BYTE PTR [SI+1],':'
        JNZ     validate_malformed      ;Second char must be a ':'

validate_end:
        OR      CX,CX                   ;Clears carry
        JNZ     validate_ok             ;No metas found, leave carry clear
validate_set_carry:
        STC
validate_ok:
        POP     SI
        POP     CX
        POP     AX
        return
validate_path   ENDP

BREAK <Access_path - determine if file found>

;
; Input: DS:DX point to a path
; Output: Carry reset - outputs of GetPath
;         carry set - AL has error code
;
        procedure   Access_path,NEAR
        ASSUME  DS:NOTHING,ES:NOTHING
        CALL    Validate_path
        JC      access_no_path
        MOV     SI,DX
        invoke  GetPath
        retnc
        MOV     AL,error_file_not_found
        OR      CL,CL
        JNZ     access_ret
access_no_path:
        MOV     AL,error_path_not_found
access_ret:
        STC
        return
access_path ENDP

BREAK <Find_free_jfn - return a free jfn in users PDB>
;
; system file table data
;

;
; The system file table is two linear tables.  The first table is the
; DOS initialization table containing a default number of FCBs.  The
; first word in the table is a link to the second table, which
; SYSINIT sets up, the second word is the number of FCBs in the table.
;

;
; find_free_jfn
; input:    none
; output:   JNC <found>
;               ES:DI is pointer to free JFN
;           JC  <no free jfns>
;               ES,DI indeterminate
;
        procedure   Find_free_jfn,NEAR
        ASSUME  DS:NOTHING,ES:NOTHING
        PUSH    AX
        PUSH    CX
        MOV     AL,0FFh
        MOV     ES,[CurrentPDB]
        MOV     DI,PDB_JFN_Table
        MOV     CX,FilPerProc
        REPNE   SCASB
        STC
        JNZ     Find_jfn_ret
        DEC     DI
        CLC
Find_jfn_ret:
        POP     CX
        POP     AX
        return
Find_free_jfn   ENDP

BREAK <find_free_sfn - return a free sfn and sf pointer>
;
; find_free_sfn
; input:    none
; output:   JNC <found>
;               ES:DI is free sf entry
;               SI is sfn
;           JC  <not found>
;               ES,DI,SI indeterminate
;
; sft_addr -->  (link) count (fcbs)
; links = -1 means end of list
;
        procedure   Find_free_sfn,NEAR
        ASSUME  DS:NOTHING,ES:NOTHING
        PUSH    BX
        PUSH    CX
        LES     BX,sft_addr             ; head of chain of tables
        XOR     SI,SI                   ; count of sfn

        ; ES:BX points to table... search through table
Find_sfn_in_table:
        CMP     BX,-1                   ; end of chain
        JZ      Find_no_free_sfns
        MOV     DI,sft_table            ; offset to sf entry
        MOV     CX,ES:[BX].sft_count    ; count of fcbs in table

Find_sfn:
        CMP     ES:BYTE PTR [BX+DI].sf_ref_count,0h
        JZ      Find_got_sfn            ; ref count is 0 -> free entry
        ADD     DI,SIZE sf_entry        ; look to next entry
        INC     SI                      ; bump sfn
        LOOP    Find_sfn
        LES     BX,ES:[BX].sft_link     ; link to next
        JMP     SHORT Find_sfn_in_table ; look for more

Find_no_free_sfns:
        STC
        JMP     SHORT find_ret
Find_got_sfn:
        ADD     DI,BX
        CLC
Find_ret:
        POP     CX
        POP     BX
        RET
Find_free_sfn   ENDP

BREAK <$Open - open a file handle>
;
;   Assembler usage:
;           LDS     DX, Name
;           MOV     AH, Open
;           MOV     AL, access
;           INT     int_command
;
;       ACCESS          Function
;       ------          --------
;       open_for_read   file is opened for reading
;       open_for_write  file is opened for writing
;       open_for_both   file is opened for both reading and writing.
;
;   Error returns:
;           AX = error_invalid_access
;              = error_file_not_found
;              = error_access_denied
;              = error_too_many_open_files
;

        procedure   $Open,NEAR
        ASSUME  DS:NOTHING,ES:NOTHING
        MOV     [Cr_read_only],0
Open_create:
        CMP     AL,open_for_both        ; validate access
        JBE     OPEN_get_jfn
        error   error_invalid_access

OPEN_get_jfn:
        MOV     [open_name+2],DS
        context DS
        MOV     open_name,DX
        MOV     open_access,AL

        invoke  Find_free_jfn           ; scan through user's area
        ; ES:DI is the jfn entry
        JNC     OPEN_get_sfn
OPEN_too_many:
        error   error_too_many_open_files

OPEN_get_sfn:
        MOV     OPEN_jfn_b,ES
        MOV     OPEN_jfn,DI
        invoke  Find_free_sfn           ; get a free sft entry
        ; ES:DI is the SFT entry that's free, SI is the sfn
        JC      OPEN_too_many

OPEN_file:
        MOV     OPEN_sfn,SI
        MOV     OPEN_sfoff,DI
        MOV     OPEN_sfn_b,ES
;
; open the file
;
        PUSH    DS
        LDS     DX,DWORD PTR [open_name]
        ASSUME  DS:NOTHING
        CALL    access_path
        POP     DS
        ASSUME  DS:DOSGROUP
        JNC     open_check_access       ; carry set -> error
        transfer    SYS_RET_ERR

open_check_access:
        MOV     ES,WORD PTR [CURBUF+2]           ; get buffer location
        MOV     open_devid,AH
        TEST    AH,080h
        JNZ     open_set_FCB_dev        ;is a device
        MOV     AL,ES:[BX].dir_attr
        TEST    AL,attr_directory       ; can't open directories
        JZ      open_try_volid

open_bad_access:
        error   error_access_denied

open_try_volid:
        TEST    AL,attr_volume_id       ; can't open volume ids
        JNZ     open_bad_access
        TEST    AL,attr_read_only       ; check write on read only
        JZ      open_set_FCB
        CMP     [Cr_read_only],0
        JNZ     open_set_FCB            ; ok if creating read only file
        CMP     open_access, open_for_read
        JNZ     open_bad_access         ; writing on a read only file
        JMP     SHORT open_set_FCB

open_set_FCB_dev:
        PUSH    SS
        POP     ES                      ;Device opens are DOSGROUP relative

open_set_FCB:
        MOV     CX,11                   ; copy name into FCB...
        PUSH    SI                      ; ES:BX is source, must change
        MOV     SI,BX                   ; ES:SI is source
        MOV     DI,open_sfoff           ; ??:DI is dest
        PUSH    DS
        PUSH    ES
        MOV     ES,open_sfn_b           ; ES:DI is dest
        POP     DS                      ; DS:SI is source
        ASSUME  DS:NOTHING
;
; need to save attribute for the close operation
;
        MOV     AH,DS:[BX.dir_attr]     ; save attribute for close
        MOV     ES:[DI.sf_attr],AH

        ADD     DI,sf_fcb+1             ; point to name

        IF      KANJI
        MOVSB
        CMP     BYTE PTR ES:[DI-1],5
        JNZ     NOTKTRAN
        MOV     BYTE PTR ES:[DI-1],0E5H
NOTKTRAN:
        DEC     CX
        ENDIF

        REP     MOVSB                   ; move in parsed name
        POP     DS
        ASSUME  DS:DOSGROUP
        POP     SI
        LES     DI,DWORD PTR [open_sfoff]
        ADD     DI,sf_fcb               ; offset on fcb in sf entry
        MOV     AH,open_devid
        invoke  DOOPEN                  ; let open code fill in blanks
        context DS
        LES     DI,DWORD PTR [open_sfoff]
        INC     ES:[DI].sf_ref_count    ; reference this FCB
        MOV     AL,open_access          ; stash the access
        MOV     ES:BYTE PTR [DI].sf_mode,AL
        XOR     AX,AX
        MOV     ES:WORD PTR [DI.sf_FCB.fcb_RR],AX       ; beginning of file
        MOV     ES:WORD PTR [DI.sf_FCB.fcb_RR+2],AX
        INC     AX
        MOV     ES:WORD PTR [DI.sf_FCB.fcb_RECSIZ],AX   ; byte io only
        LES     DI,DWORD PTR [open_jfn]
        MOV     AX,open_sfn
        MOV     ES:BYTE PTR [DI],AL     ; stash sfn in PDB
        SUB     DI,PDB_jfn_table        ; get jfn for user
        MOV     AX,DI
        transfer    SYS_RET_OK
$Open   ENDP


BREAK <$UNLINK - delete a file entry>
;
;   Assembler usage:
;           LDS     DX, name
;           MOV     AH, Unlink
;           INT     21h
;
;   Error returns:
;           AX = error_file_not_found
;              = error_access_denied
;
        procedure   $UNLINK,NEAR
        ASSUME  DS:NOTHING,ES:NOTHING
        CALL    access_path
        JNC     unlink_check_attr
        transfer    SYS_RET_ERR

unlink_check_attr:
        JZ      unlink_dir
        LDS     DI,DWORD PTR [CURBUF]   ; get directory entry
        TEST    DS:[BX.dir_attr],attr_read_only
        JZ      unlink_doit

unlink_dir:
        error   error_access_denied

unlink_doit:
        MOV     BYTE PTR DS:[BX.dir_name],0E5h  ; delete dir entry
        MOV     BYTE PTR DS:[DI.BUFDIRTY],1     ; dirty the buffer
        LODSW
        MOV     BX,AX
        AND     BX,0FFFh
        context DS
        JZ      unlink_flush
        invoke  RELEASE
unlink_flush:
        MOV     AL,BYTE PTR ES:[BP.DPB_drive]
        invoke  FLUSHBUF
        transfer    SYS_RET_OK
$UNLINK ENDP

BREAK <$CREAT - creat a new file and open him for input>
;
;   Assembler usage:
;           LDS     DX, name
;           MOV     AH, Creat
;           MOV     CX, access
;           INT     21h
;       ; AX now has the handle
;
;   Error returns:
;           AX = error_access_denied
;              = error_path_not_found
;              = error_too_many_open_files
;


        procedure   $CREAT,NEAR
        ASSUME  DS:NOTHING,ES:NOTHING
        CALL    Validate_path
        JNC     unlink_do_make
        error   error_path_not_found
unlink_do_make:
        PUSH    DX
        PUSH    DS
        context DS
        MOV     WORD PTR [CREATING],0E5FFh
        MOV     WORD PTR [ThisFCB+2],SS
        MOV     WORD PTR [ThisFCB],OFFSET DOSGROUP:AUXSTACK-40
        MOV     SI,DX
        MOV     AL,CL
        AND     CL,attr_read_only
        MOV     [Cr_read_only],CL
        POP     DS
        PUSH    DS
ASSUME  DS:NOTHING
        invoke  MakeNode
        POP     DS
        POP     DX
        OR      AL,AL
        JZ      creat_open
        CMP     AL,3
        JZ      creat_open
creat_no_access:
        error   error_access_denied
creat_open:
        MOV     AL,open_for_both
        JMP     Open_create

$CREAT ENDP


BREAK <$DUP - duplicate a jfn>
;
;   Assembler usage:
;           MOV     BX, fh
;           MOV     AH, Dup
;           INT     int_command
;         AX has the returned handle
;   Errors:
;           AX = dup_invalid_handle
;              = dup_too_many_open_files
        procedure   $DUP,NEAR
        ASSUME  DS:NOTHING,ES:NOTHING
        context DS
        invoke  Find_free_jfn
        JC      dup_no_free_handles

dup_force:
        PUSH    ES
        PUSH    DI
        invoke  Get_sf_from_jfn
        POP     SI
        POP     DS
        JC      dup_bad_handle
        ; ES:DI is pointer to sf entry
        ; DS:DI is pointer to jfn
        INC     ES:[DI].sf_ref_count    ; another jfn reference...
        MOV     AL,[BX].PDB_JFN_table   ; get old sfn
        MOV     [SI],AL                 ; store in new place
        SUB     SI,PDB_JFN_table        ; get jfn
        MOV     AX,SI
        transfer    SYS_RET_OK

dup_no_free_handles:
        error   error_too_many_open_files

dup_bad_handle:
        error   error_invalid_handle
$DUP    ENDP

BREAK <$DUP2 - force a dup on a particular jfn>
;
;   Assembler usage:
;           MOV     BX, fh
;           MOV     CX, newfh
;           MOV     AH, Dup2
;           INT     int_command
;   Error returns:
;           AX = error_invalid_handle
;
        procedure   $DUP2,NEAR
        ASSUME  DS:NOTHING,ES:NOTHING
        XCHG    BX,CX                   ; BX < destination jfn
        PUSH    BX
        PUSH    CX
        invoke  $CLOSE                  ; close BX
        context DS
        POP     CX
        POP     BX
        invoke  Get_jfn_pointer
        XCHG    BX,CX
        JNC     dup_force
lseek_bad_handle:
        error   error_invalid_handle
$DUP2   ENDP


BREAK <$CHMOD - change file attributes>
;
;   Assembler usage:
;           LDS     DX, name
;           MOV     CX, attributes
;           INT     21h
;   Error returns:
;           AX = error_path_not_found
;           AX = error_access_denied
;
        procedure   $CHMOD,NEAR
        ASSUME  DS:NOTHING,ES:NOTHING
        CMP     AL,1
        JBE     chmod_save
        error   error_invalid_function
chmod_save:
        JB      chmod_try_file
        MOV     BX,CX
        AND     BX,NOT attr_changeable
        JZ      chmod_try_file

chmod_bad:
        error   error_access_denied

chmod_bye:
        transfer    SYS_RET_ERR
chmod_try_file:
        PUSH    CX
        PUSH    AX
        CALL    access_path
        POP     DX
        POP     CX
        JC      chmod_bye
        LES     DI,[CURBUF]
        context DS
        OR      DL,DL
        JZ      chmod_fetch
        AND     BYTE PTR ES:[BX].dir_attr,NOT attr_changeable
        OR      BYTE PTR ES:[BX].dir_attr,CL
        MOV     ES:[DI.BUFDIRTY],1
        MOV     AL,-1
        invoke  FlushBuf
        transfer    SYS_RET_OK
chmod_fetch:
        XOR     CX,CX
        MOV     CL,BYTE PTR ES:[BX].dir_attr
        invoke  Get_user_stack
        MOV     [SI.user_CX],CX
        transfer    SYS_RET_OK
$chmod  ENDP

BREAK <$CURRENT_DIR - dump the current directory into user space>
;
;   Assembler usage:
;               LDS     SI,area
;               MOV     DL,drive
;               INT     21h
;           ; DS:SI is a pointer to 64 byte area that contains drive
;           ; current directory.
;   Error returns:
;           AX = error_invalid_drive
;
procedure   $CURRENT_DIR,NEAR
        ASSUME  DS:NOTHING,ES:NOTHING
        PUSH    DS
        PUSH    BX
        PUSH    SI
        invoke  $get_DPB
;
; ES:BP points to DPB.  DS:SI points to user stack, unless error
;
        CMP     AL,0FFh
        JNZ     current_copy
        POP     AX              ; Clean Stack
        POP     AX
        POP     AX
        error   error_invalid_drive

current_copy:
        POP     DI              ; where to move to
        POP     [SI.user_BX]    ; restore old BX
        POP     BX
        MOV     [SI.user_DS],BX ; and restore old DS
;
; ES:BP is pointer to DPB. BX:DI is pointer to destination
;
        CMP     ES:[BP.dpb_current_dir],-1
        JNZ     current_ok
        PUSH    BX
        PUSH    DI
        MOV     [ATTRIB],attr_all
        invoke  GETCURRDIR
        POP     DI
        POP     BX
current_ok:
        MOV     SI,BP           ; ES:SI is source
        PUSH    ES
        POP     DS              ; DS:SI is source
        MOV     ES,BX           ; ES:DI is destination
        CMP     [SI.dpb_current_dir],0
        JNZ     current_move
        MOV     BYTE PTR [SI.dpb_dir_text],0

current_move:
        ADD     SI,dpb_dir_text
        MOV     CX,DIRSTRLEN
current_loop:
        LODSB
        STOSB
        OR      AL,AL
        LOOPNZ  current_loop
        transfer    SYS_RET_OK
$CURRENT_DIR    ENDP


BREAK <$RENAME - move directory entries around>
;
;   Assembler usage:
;           LDS     DX, source
;           LES     DI, dest
;           MOV     AH, Rename
;           INT     21h
;
;   Error returns:
;           AX = error_file_not_found
;              = error_not_same_device
;              = error_access_denied
procedure   $RENAME,near

        MOV     WORD PTR [rename_source],DX
        MOV     WORD PTR [rename_source+2],DS
        MOV     WORD PTR [rename_dest],DI
        MOV     WORD PTR [rename_dest+2],ES
        CALL    Access_path
        JNC     rename_check_dir
        transfer    SYS_RET_ERR

rename_check_dir:
        JZ      rename_no_access
        MOV     DS,WORD PTR [CurBuf+2]
        PUSH    [BX.dir_date]
        PUSH    [BX.dir_first]
        PUSH    [BX.dir_size_h]
        PUSH    [BX.dir_size_l]
        PUSH    [BX.dir_time]
        PUSH    WORD PTR [BX.dir_attr]
        PUSH    WORD PTR [ThisDrv]
        LDS     SI,[rename_dest]
        invoke  GetPath
        POP     AX
        JC      rename_check_drives
rename_bad_access:
        ADD     SP,12
rename_no_access:
        error   error_access_denied
rename_check_drives:
        CMP     AL,[ThisDrv]
        JZ      rename_create
        ADD     SP,12
        error   error_not_same_device
rename_create:
        LDS     SI,[rename_dest]
        POP     AX
        PUSH    AX
        MOV     WORD PTR [Creating],0E5FFh
        MOV     WORD PTR [ThisFCB+2],SS
        MOV     WORD PTR [ThisFCB],OFFSET DOSGROUP:AUXStack-40
        invoke  MakeNode
        JC      rename_bad_access
        LDS     SI,[CurBuf]
        POP     AX
        MOV     [BX.dir_attr],AL
        POP     [BX.dir_time]
        POP     [BX.dir_size_l]
        POP     [BX.dir_size_h]
        POP     [BX.dir_first]
        POP     [BX.dir_date]
        MOV     [SI.BUFDIRTY],1
        LDS     SI,[rename_source]
        invoke  GetPath
        LDS     SI,[CurBuf]
        MOV     BYTE PTR [BX],0E5h
        MOV     [SI.BUFDIRTY],1
        context DS
        MOV     AL,0FFh
        invoke  FlushBuf
        transfer    SYS_RET_OK

$RENAME ENDP

BREAK <$FIND_FIRST - find first matching xenix filename>
;
;   Assembler usage:
;           MOV AH, FindFirst
;           LDS DX, name
;           MOV CX, attr
;           INT 21h
;       ; DMA address has datablock
;
;   Error Returns:
;           AX = error_file_not_found
;              = error_no_more_files
;
procedure   $FIND_FIRST,near
        ASSUME  DS:NOTHING,ES:NOTHING
        CALL    Validate_path
        JNC     find_get
        JZ      find_get
        error   error_file_not_found
find_get:
        MOV     SI,DX
        PUSH    CX
        INC     BYTE PTR [NoSetDir] ; if we find a dir, don't change to it
        MOV     WORD PTR [Creating],0E500h
        CALL    GetPath
        POP     CX
        MOV     [Attrib],CL
find_check:
        JNC     find_check_attr
find_no_more:
        error   error_no_more_files
find_check_attr:
        MOV     DS,WORD PTR [CURBUF+2]
        MOV     CH,[BX.dir_attr]
        invoke  MatchAttributes
        JZ      found_it
        PUSH    [LastEnt]
        MOV     BX,[DirStart]
        JMP     find_it_next
found_it:
        LES     DI,[DMAADD]
        MOV     AL,[Attrib]
        STOSB                           ; find_buf 0 = attribute in search
        MOV     AL,[ThisDrv]
        STOSB                           ; find_buf 1 = drive
        MOV     CX,11
        PUSH    BX
        MOV     SI,OFFSET DOSGROUP:NAME1; find_buf 2 = formatted name
        PUSH    DS
        PUSH    SS
        POP     DS

        IF      KANJI
        MOVSB
        CMP     BYTE PTR ES:[DI-1],5
        JNZ     NOTKANJB
        MOV     BYTE PTR ES:[DI-1],0E5H
NOTKANJB:
        DEC     CX
        ENDIF

        REP     MOVSB
        POP     DS
        MOV     AX,[LastEnt]
        STOSW                           ; find_buf 13 = LastEnt
        MOV     AX,WORD PTR [ThisDPB]
        STOSW                           ; find_buf 15 = ThisDPB
        MOV     AX,WORD PTR [ThisDPB+2]
        STOSW
        MOV     AX,[DirStart]
        STOSW                           ; find_buf 19 = DirStart
        MOV     AL,[BX].dir_attr
        STOSB                           ; find_buf 21 = attribute found
        MOV     AX,[BX].dir_time
        STOSW                           ; find_buf 22 = time
        MOV     AX,[BX].dir_date
        STOSW                           ; find_buf 24 = date
        MOV     AX,[BX].dir_size_l
        STOSW                           ; find_buf 26 = low(size)
        MOV     AX,[BX].dir_size_h
        STOSW                           ; find_buf 28 = high(size)
        POP     SI
        MOV     CX,8                    ; find_buf 30 = packed name
find_loop_name:
        LODSB
        STOSB
        CMP     AL," "
        LOOPNZ  find_loop_name
        JNZ     find_check_dot
        DEC     DI
find_check_dot:
        ADD     SI,CX
        CMP     BYTE PTR [SI]," "
        JZ      find_done
        MOV     AL,"."
        STOSB
        MOV     CX,3
find_loop_ext:
        LODSB
        STOSB
        CMP     AL," "
        LOOPNZ  find_loop_ext
        JNZ     find_done
        DEC     DI
find_done:
        XOR     AL,AL
        STOSB
        transfer    SYS_RET_OK
$FIND_FIRST ENDP

BREAK <$FIND_NEXT - scan for match in directory>
;
;   Assembler usage:
;       ; dma points at area returned by find_first
;           MOV AH, findnext
;           INT 21h
;       ; next entry is at dma
;
;   Error Returns:
;           AX = error_no_more_files
;
procedure   $FIND_NEXT,near
        ASSUME  DS:NOTHING,ES:NOTHING
        LDS     SI,[DMAADD]
        MOV     DX,SI
        INC     DX
        PUSH    SI
        invoke  MOVNAMENOSET
        POP     SI
        JNC     find_load
findnext_no_more:
        error   error_no_more_files
find_load:
        MOV     AX,[SI.find_buf_LastEnt]
        LES     BP,[SI.find_buf_ThisDPB]
        OR      AX,AX
        JS      findnext_no_more
        MOV     BX,[SI.find_buf_DirStart]
        MOV     DL,[SI.find_buf_sattr]
        MOV     [Attrib],DL
        PUSH    AX
        MOV     WORD PTR [ThisDPB],BP
        MOV     WORD PTR [ThisDPB+2],ES
find_it_next:
        invoke  SetDirSrch
        ASSUME  DS:DOSGROUP
        POP     AX
        MOV     [ENTLAST],-1
        invoke  GetEnt
        invoke  NextEnt
        JMP     find_check
$find_next  ENDP

do_ext

CODE    ENDS
    END
�������������������������������������������������������������������������������������������������������