Files
RomWBW/Source/BPBIOS/ZCPR33/zcpr33.z80
Wayne Warthen 32228eb89c BPBIOS Updates
- Removed concept of BPBIOS internal proxy (configuration N).
2021-10-07 15:56:54 -07:00

4045 lines
124 KiB
Z80 Assembly
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
; PROGRAM: ZCPR
; VERSION: 3.3
; DERIVATION: ZCPR30
; AUTHOR: Jay Sage
; DATE: May 28, 1987
; ZCPR33 is copyright 1987 by Echelon, Inc. All rights reserved. End-user
; distribution and duplication permitted for non-commercial purposes only.
; Any commercial use of ZCPR33, defined as any situation where the duplicator
; recieves revenue by duplicating or distributing ZCPR33 by itself or in
; conjunction with any hardware or software product, is expressly prohibited
; unless authorized in writing by Echelon.
;
; Echelon specifically disclaims any warranties, expressed or implied,
; including but not limited to implied warranties of merchantability and
; fitness for a particular purpose. In no event will Echelon be liable for
; any loss of profit or any other commercial damage, including but not limited
; to special, incidental, consequential, or other damages.
;
; Echelon can be contacted at:
; Echelon, Inc.
; 885 N. San Antonio Road
; Los Altos, California USA 94022
; (415) 948-3820
;-----------------------------------------------------------------------------
;
; A C K N O W L E D G M E N T S
;
;-----------------------------------------------------------------------------
; Many people have played a role in the development of ZCPR in general and
; ZCPR33 in particular. It all started when "The CCP Group," including
; Richard Conn, Ron Fowler, Keith Petersen, Frank Wancho, Charlie Strom, and
; Bob Mathias decided that by rewriting the CP/M command processor to take
; advantage of Zilog-specific opcodes they could save enough code to enhance
; some of the features. Richard Conn then extended that development through
; ZCPR2 to ZCPR3 (3.0). Just a little over two years ago, I took the first
; step to enhance ZCPR3 by making it get the maximum drive and user values
; from the environment instead of hard coding them in. This version was
; distributed privately as ZCPR31. Along the way to what is now ZCPR
; version 3.3 a number of others have made valuable contributions: Steve
; Kitahata, Michael Bate, Bruce Morgen, Roger Warren, Dreas Nielsen, Bob Freed,
; Al Hawley, Howard Goldstein, and many others who have stimulated developments
; by pointing out problems or asking questions.
; I would like particularly to acknowledge two people who have played a very
; significant role in these developments. One is Al Hawley. He introduced
; the idea of having the DUOK flag in the environment control how the CPR
; would respond to the DU: form of directory reference. He also originated
; the idea of using the high bit of the first character of each command name
; to control whether or not it would be wheel restricted. Finally, he
; contributed the basic structure of the highly efficient, elegant, and more
; general number evaluation routines in the code.
; My biggest debt of gratitude is to Howard Goldstein. His role in the
; development of ZCPR33 goes back about a year, when he contributed the first
; correct implementation of the minpath feature. More recently, during the
; period of intense development since Echelon expressed its interest in my
; writing the official 3.3 version, he and I have shared an especially
; enjoyable and fruitful relationship. Most of the newest ideas have been
; worked out jointly, and Howard has done a great deal to keep my code and
; concepts on track. He discovered many ways to pare the code down and, more
; importantly, uncovered numerous subtle bugs. He recoded the SAVE command
; to make it more compact and reliable.
;
; Jay Sage
; May 28,1987
;-----------------------------------------------------------------------------
;
; U S E R C O N F I G U R A T I O N
;
;-----------------------------------------------------------------------------
; The following MACLIB statements load all the user-selected equates
; which are used to customize ZCPR33 for the user's working environment.
; NOTE -- TRUE & FALSE are defined in Z3BASE.
memtop equ 0FE00H - 1 ; Reserve memory above this for HBIOS
base equ 0
maclib z3base.lib
maclib z33hdr.lib
; Check that the configuration includes the required facilities
; A ZCPR33 system is assumed to include certain minimal features, including
; an external file control block, external path, shell stack, message buffer,
; external environment descriptor, multiple command line, and external stack.
; If wheel checking is enabled in the Z33HDR.LIB file, then there must be
; an address defined for the wheel byte in the Z3BASE.LIB file.
errflag defl extfcb eq 0 ; External command FCB
errflag defl errflag or [ expath eq 0 ] ; Symbolic path
errflag defl errflag or [ shstk eq 0 ] ; Shell stack
errflag defl errflag or [ z3msg eq 0 ] ; Message buffer
errflag defl errflag or [ z3env eq 0 ] ; Environment descriptor
errflag defl errflag or [ z3cl eq 0 ] ; Multiple command line
errflag defl errflag or [ extstk eq 0 ] ; External stack
if wheel or wdu or wpass or wprefix or whldir
errflag defl errflag or [ z3whl eq 0 ] ; Wheel byte
endif ;wheel or wdu or wpass or wprefix or whldir
if errflag
*** NOT ALL REQUIRED ZCPR3 FACILITIES ARE SUPPORTED ***
else ; go ahead with the assembly
;-----------------------------------------------------------------------------
;
; D E F I N I T I O N S S E C T I O N
;
;-----------------------------------------------------------------------------
; ---------- Macro definitions
maclib z33mac.lib ; Library of macros for ZCPR33
; ---------- ASCII definitions
ctrlc equ 03h
bell equ 07h
tab equ 09h
lf equ 0ah
cr equ 0dh
; ---------- Operating system addresses
wboot equ base+0000h ; CP/M warm boot address
udflag equ base+0004h ; User number in high nybble, disk in low
bdos equ base+0005h ; BDOS function call entry point
tfcb equ base+005ch ; Default FCB buffer
tfcb2 equ tfcb+16 ; 2nd FCB
tbuff equ base+0080h ; Default disk I/O buffer
tpa equ base+0100h ; Base of TPA
;bios equ ccp+0800h+0e00h ; BIOS location
; ---------- Error codes
; ZCPR33 uses the error byte at the beginning of the message buffer as a flag
; to show what kind of error occurred. Advanced error handlers will be able
; to help the user further by suggesting the possible cause of the error.
; Error code 6 for an ECP error is determined by the code and cannot be
; changed (without increasing code length).
ecduchg equ 1 ; Directory change error -- attempt to change
; ..logged directory when under control of
; ..wheel byte and wheel is off
ecbaddir equ 2 ; Bad directory specification -- logging of
; ..user number beyond legal range,
; ..nonexistent named directory
ecbadpass equ 3 ; Bad password -- incorrect password entered
ecbadcmd equ 5 ; Bad command form -- wildcard or file type
; ..present in command verb
ececperr equ 6 ; ECP error -- command could not be executed
; ..by ECP, error handling was forced by a
; ..transient for its own reasons
; (DO NOT CHANGE FROM 6)
ecnocmd equ 7 ; Command file not found -- command that skips
; ..ECP could not be executed, GET could not
; ..find file to load
ecambig equ 8 ; Ambiguous file specification where not
; ..allowed (SAVE, GET, REN)
ecbadnum equ 9 ; Bad numerical value -- not a number where
; ..number expected, number out of range
ecnofile equ 10 ; File not found -- REN, TYPE, LIST could not
; ..find a specified file
ecdiskfull equ 11 ; Disk directory or data area full
ectpafull equ 12 ; TPA overflow error
; ---------- Multiple command line equates
; The multiple command line buffer is located in a protected area in memory so
; that it is not overwritten during warm boots. It includes some pointers so
; that when ZCPR33 starts it can tell where to start reading the command line.
; BUFSIZ and CHRCNT are not used by ZCPR33 but are provided so that the BDOS
; line input function can be used to read in a command line.
nxtchr equ z3cl ; Address where pointer to next command to
; ..process is kept
bufsiz equ z3cl+2 ; Address where size of buffer is kept
chrcnt equ z3cl+3 ; Address where length of string actually in
; ..the buffer is kept (not always reliable)
cmdlin equ z3cl+4 ; Address of beginning of command line buffer
buflen equ z3cls ; Length of command line buffer
; ---------- Command file control block
; In ZCPR33 the file control block for commands must be located in protected
; memory. This not only frees up valuable space in the command processor for
; code but also makes it possible for programs to determine by what name they
; were invoked.
cmdfcb equ extfcb
; ---------- External CPR stack
stack equ extstk+48 ; Set top-of-stack address
pwlin equ extstk ; Place line at bottom of stack
; ---------- Environment
quietfl equ z3env+28h ; Quiet flag
maxdrenv equ z3env+2ch ; Maximum drive value
maxusrenv equ z3env+2dh ; Maximum user value
duokfl equ z3env+2eh ; Flag indicating acceptance of DU: form
crttxt0 equ z3env+33h ; Address of number of lines of text on the
; ..screen of CRT0
; ---------- Message buffer
ecflag equ z3msg ; Error return code flag
ifptrfl equ z3msg+1 ; Pointer to current IF level
ifstatfl equ z3msg+2 ; Flow control status byte
cmdstatfl equ z3msg+3 ; Command status flag
cmdptr equ z3msg+4 ; Pointer to currently running command
zexinpfl equ z3msg+7 ; ZEX input status/control flag
zexrunfl equ z3msg+8 ; ZEX running flag
errcmd equ z3msg+10h ; Error handling command line
xsubflag equ z3msg+2ch ; XSUB input redirection flag
subflag equ z3msg+2dh ; Submit running flag
curusr equ z3msg+2eh ; Currently logged user
curdr equ z3msg+2fh ; Currently logged drive
;-----------------------------------------------------------------------------
;
; C O D E M O D U L E S S E C T I O N
;
;-----------------------------------------------------------------------------
page
; ZCPR33-1.Z80
;=============================================================================
;
; E N T R Y P O I N T S A N D H E A D E R S T R U C T U R E
;
;=============================================================================
if not rel ; If generating absolute code
org ccp
else
common /_BIOS_/
bios equ $
cseg
endif ;not rel
; ENTRY POINTS INTO ZCPR33
;
; For compatibility with CP/M, two entry points are provided here. In
; standard CP/M if the code is entered from the first entry point, then the
; command in the resident command buffer is executed; if entered from the
; second entry point, the command line is flushed. With ZCPR33 and its
; multiple command line buffer, these two entry points function identically
; and go to the same address.
;
; We have kept the entry points in their standard locations but have used a
; relative jump for the second entry point and replaced the last byte with the
; version number. In this way the version number occupies a position that
; would otherwise contain the page number at which the CPR runs. It will
; always be possible, therefore, to distinguish ZCPR33 and later versions
; from other command processors. The first jump is kept as an absolute jump
; so that 1) the code will be compatible with Z-COM and Z3-DOT-COM and 2) the
; execution address of a CPR module can always be determined.
entry:
jp zcpr
jr zcpr
version:
defb 33h ; Version ID squeezed in here (offset = 5)
;-----------------------------------------------------------------------------
; Configuration information
options: ; (offset = 6)
optflag badduecp,rootonly,ndrenv,fcpenv,rcpenv,inclenv,aduenv,duenv
optflag highuser,drvprefix,scancur,incldir,incldu,dufirst,accptdir,accptdu
optflag no,pwcheck,pwnoecho,wdu,wpass,wprefix,fastecp,skippath
attdir defl [ comatt eq 80h ] or [ comatt eq 01h ] or [ not attchk ]
attsys defl [ comatt eq 00h ] or [ comatt eq 01h ] or [ not attchk ]
subquiet defl [ subnoise eq 1 ]
subecho defl [ subnoise gt 1 ]
optflag shellif,attsys,attdir,attchk,subecho,subquiet,subclue,subon
; Byte with information about the alternate colon option. If the byte is
; zero, the option is not supported. Otherwise the byte contains the
; prefix character that serves as an alias for a colon prefix. Offset = 10.
if altcolon
defb altchar
else
defb 0
endif ;altcolon
; Byte with information about the FASTECP implementation (option bit above
; indicates whether the feature is enabled at all). If no character appears
; here (zero byte), then only a leading space can be used. Otherwise, the
; first seven bits contain the character, and the high bit, if set, indicates
; that ONLY this character will be recognized and not a space. Offset = 11.
if fastecp and altspace
if altonly
defb ecpchar + 80h
else ;not altonly
defb ecpchar
endif ;altonly
else ;no alternate character
defb 0
endif ;fastecp and altspace
defb 0,0,0,0 ; Space reserved for expansion
;-----------------------------------------------------------------------------
; Entry points to file name parsing code.
; Entry point REPARSE. A call to this point can be used to parse a command
; line tail into the default file control blocks at 5CH and 6CH. Each time
; the parser is called it leaves the starting address of the second token in
; the PARESPTR address below so that successive calls to the routine reparse
; the command tail one token later. A program can load its own pointer into
; PARSEPTR as well. Offset = 16 (10h).
reparse:
parseptr equ $+1 ; Pointer for in-the-code modification
ld hl,0
jp parsetail
; Entry point SCAN. A call to this point can be used to parse a single token
; pointed to by HL into the FCB pointed to by DE. Offset 22 (16h).
scan:
jp scanner
;-----------------------------------------------------------------------------
; BUFFERS
;
; In this area various data items are kept. First comes the list of commands
; supported by ZCPR33; then comes the name of the extended command processor
; (ECP). By putting these items here, an 'H' command in the RCP or a utility
; like SHOW.COM can find this information and report it to the user.
; ---------- RESIDENT COMMAND TABLE
; The command table entry is structured as follows: First there is a byte
; which indicates the number of characters in each command. Then there is a
; series of entries comprising the name of a command followed by the address
; of the entry point to the code for carrying out that command. Finally,
; there is a null byte (00h) to mark the end of the table. Offset = 25 (19h).
cmdtbl:
defb cmdsize ; Length of command names
ctable ; Define table via macro in Z33HDR.LIB
defb 0 ; End of table
; ---------- NAME FOR EXTENDED COMMAND PROCESSOR
; The name of the extended command processor is placed here after the command
; table so that utilities like SHOW or an RCP 'H' command can find it.
ecpfcb:
ecpname ; From Z33HDR.LIB
; ---------- FILE TYPE FOR TRANSIENT COMMANDS (usually COM)
; This file type also applies to the extended command processor name.
commsg:
comtyp ; From Z33HDR.LIB
; ---------- SUBMIT FILE CONTROL BLOCK
if subon ; If submit facility enabled ...
subfcb:
defb subdrv-'A'+1 ; Explicit drive for submit file
defb '$$$ ' ; File name
subtyp ; From Z33HDR.LIB
defb 0 ; Extent number
defb 0 ; S1 (user number 0)
subfs2:
defs 1 ; S2
subfrc:
defs 1 ; Record count
defs 16 ; Disk group map
subfcr:
defs 1 ; Current record number
endif ; subon
; End ZCPR33-1.Z80
page
; ZCPR33-2.Z80
;=============================================================================
;
; C O M M A N D L I N E P R O C E S S I N G C O D E
;
;=============================================================================
; MAIN ENTRY POINT TO CPR
; This is the main entry point to the command processor. On entry the C
; register must contain the value of the user/drive to be used as the current
; directory.
zcpr:
ld sp,stack ; Reset stack
if pwnoecho
ld a,0c3h ; Reenable BIOS conout routine
ld (bios+0ch),a ; ..after a warmboot
endif ;pwnoecho
ld b,0fh ; Keep nibble mask in B
; If the HIGHUSER option is enabled, we compare the user/drive in the login
; byte in C to the values stored in the message buffer. If, ignoring bit 4
; of the user number, they match, then we remain in the current area, which
; may be a user area above 15.
if highuser
ld a,c ; Copy user/drive byte to A
and b ; Isolate drive
ld d,a ; ..and move to D
ld a,c ; Get full byte back
swap ; Swap nibbles
and b ; Isolate user number
ld e,a ; ..and move to E
ld hl,(curusr) ; Get old curdr/curusr into HL
sbc hl,de ; Subtract new values from old (carry is clear)
ex de,hl ; Switch new values into HL, diff into DE
ld a,d ; Combine two parts of difference
or e
and b ; Ignore bit for high user numbers
jr z,zcpr1 ; Skip update if no change in DU
ld (curusr),hl ; Update values of current drive and user
zcpr1:
else ;not highuser
ld a,c ; Copy user/drive byte to A
and b ; Isolate drive
ld h,a ; ..and move to H
ld a,c ; Get full byte back
swap ; Swap nibbles
and b ; Isolate user number
ld l,a ; ..and move to L
ld (curusr),hl ; ..and save them
endif ;highuser
; This block of code is executed when submit processing is enabled. We log
; into user area 0, where the submit file is kept, and we search the
; designated drive for the file. The result is kept in SUBFLAG. This code
; only has to be executed on reentry to the command processor at the main
; entry point. Commands that do not reboot but simply return to the CPR will
; execute without the disk reset and file search required here. Ron Fowler
; pointed out a shortcut based on the fact that after a disk reset, the A
; regiser contains a value of 0 if there is no file on drive A with a '$' in
; the file name and 0FFH if there is such a file. Thus if A = 0, there can
; be no '$$$.SUB' file on drive A. This trick is, unfortunately, not reliable
; under some versions of ZRDOS. Therefore, an option has been included to
; use or not use this shortcut.
if subon ; If submit facility enabled ..
call defltdma ; Set DMA address to 80H
ld a,0 ; Log into user area 0
call setuser
ld c,0dh ; Reset disk system (returns 0FFH if a $$$.SUB
call bdossave ; ..file might exist in user 0)
ld de,subfcb ; Point to submit file FCB with explicit drive
if subclue
call nz,srchfst ; Search only if flag says it could exist
else ;not subclue
call srchfst ; Search for the file unconditionally
endif ;subclue
ld (subflag),a ; Set flag for result (0 = no $$$.SUB)
else ;not subon
ld c,0dh ; Reset disk system
call bdossave
endif ; subon
jr nextcmd ; Go to entry point for processing next command
;-----------------------------------------------------------------------------
; NEW COMMAND LINE ENTRY POINT
; This entry point is used when ZCPR33 finds the command line empty. A call to
; READBUF gets the next command line from the following possible sources in
; this order:
; 1) a running ZEX script
; 2) the submit file $$$.SUB (if enabled)
; 3) the shell stack
; 4) the user
; If the line comes from the shell stack, then the shell bit in the command
; status flag is set.
restart:
ld sp,stack ; Reset stack
xor a
ld (cmdstatfl),a ; Reset ZCPR3 command status flag
inc a ; Set ZEX message byte to 1 to
ld (zexinpfl),a ; ..indicate command prompt
if subon
ld (xsubflag),a ; Ditto for XSUB flag
endif ;subon
ld hl,cmdlin ; HL --> beginning of command line buffer
ld (nxtchr),hl ; Save as pointer to next character to process
ld (hl),0 ; Zero out command line (in case of warm boot)
push hl ; Save pointer to command line
call readbuf ; Input command line (ZEX, submit, shell,
; ..or user)
pop hl ; Get back pointer to command line
ld a,(hl) ; Check for comment line
cp comment ; Begins with comment character?
jr z,restart ; If so, go back for another line
; Otherwise, fall through
;-----------------------------------------------------------------------------
; COMMAND CONTINUATION PROCESSING ENTRY POINT
; This is the entry point for continuing the processing of an existing command
; line. The current drive and user values as known to the CPR are combined
; and made into the user/drive byte that CP/M keeps at location 0004. If the
; HIGHUSER option is enabled, the user number for this byte is forced to be
; in the range 0..15. Next the command status flag is processed. The error
; and ECP bits in the actual flag are reset, and the original flag is checked
; for an ECP error return (both ECP bit and error bit set). In that case,
; control is transferred to the error handler.
nextcmd:
ld hl,(curusr) ; Get currently logged drive and user
ld a,l ; Work on user number
if highuser
and 0fh ; Keep value modulo 16
endif ;highuser
swap ; Get user into high nibble
or h ; ..and drive into low nibble
ld (udflag),a ; Set user/disk flag in page 0
ld a,2 ; Turn ZEX input redirection off
ld (zexinpfl),a
if subon
ld (xsubflag),a ; Turn off XSUB input redirection
endif ;subon
ld hl,cmdstatfl ; Point to the command status flag (CSF)
ld a,(hl) ; Get a copy into register A
res 1,(hl) ; Reset the actual error bit
res 2,(hl) ; Reset the actual ECP bit
and 110b ; Select ECP and error bits in original flag
cp 110b ; Test for an ECP error
jp z,error ; Process ECP error with error handler
nextcmd1:
ld sp,stack ; Reset stack
call logcurrent ; Return to default directory
ld hl,(nxtchr) ; Point to first character of next command
push hl ; Save pointer to next character to process
; We have to capitalize the command line each time because an alias or other
; command line generator may have stuck some new text in. The code is shorter
; if we simply capitalize the entire command rather than trying to capitalize
; only the one command we are about to execute.
capbuf: ; Capitalize the command line
ld a,(hl) ; Get character
call ucase ; Convert to upper case
ld (hl),a ; Put it back
inc hl ; Point to next one
or a ; See if end of line (marked with null)
jr nz,capbuf ; If not, loop back
pop hl ; Restore pointer to next character to process
nextcmd3:
; ZCPR33 provides a convenience feature to make it easier to enter a leading
; colon to force the current directory to be scanned and to make the CPR skip
; resident commands. If ALTCOLON is active, an alternate character can be
; entered as the first character of a command. The default (and recommended)
; alternative character is the period (it could not have any other meaning
; here). If FASTECP (see below) is not enabled or if ALTONLY is enabled,
; leading spaces on the command line are skipped before looking for the
; alternate character for the colon
if [ not fastecp ] or [ fastecp and altonly ]
call sksp
endif ;[ not fastecp ] or [ fastecp and altonly ]
if altcolon ; If allowing alias character for leading colon
; Set B = ':' and C = alias character ('.')
ld bc,':' shl 8 + altchar
ld a,(hl) ; Get first character in new command line
cp c ; If first character is ALTCHAR, treat as ':'
jr nz,nextcmd3a ; Branch if not '.'
ld (hl),b ; Else replace with colon
nextcmd3a:
endif ;altcolon
; ZCPR33 supports three new options that can speed up command processing.
; FASTECP allows commands with a leading space to bypass the search for
; resident commands or transient commands (COM files) along the path and go
; directly to the extended command processor. With SKIPPATH enabled, when
; a command is prefixed by an explicit directory specification (but not a
; lone colon), searching of the path and invocation of the ECP are disabled.
; If the command is not found in the specified directory, the error handler
; is invoked immediately. Finally, if BADDUECP is enabled, when an attempt
; is made to log into an invalid directory, the command is sent directly to
; the ECP, which can provide special handling. To implement these three
; features, the first actual character of the command line is saved as a
; flag in FIRSTCHAR. My apologies for the complexity of these nested
; conditionals.
if fastecp or skippath or badduecp
; With FASTECP we store the first actual
; ..character and then skip over spaces (unless ALTONLY is
; ..enabled, in which case we skipped spaces above)
if fastecp
if altspace ; If allowing alias character for leading space
; Set B = ' ' and C = alias character ('/')
ld bc,' ' shl 8 + ecpchar
ld a,(hl) ; Get first character in new command line
cp c ; If first character is ECPCHAR treat as ' '
jr nz,nextcmd3b ; Branch if not '/' (alternate character)
ld (hl),b ; Else replace with space
nextcmd3b:
endif ;altspace
ld a,(hl) ; Get first character in command line
ld (firstchar),a ; Save it in flag
call sksp ; Then skip leading spaces
endif ;fastecp
; With SKIPPATH but not FASTECP we store the first
; ..character of the command (spaces were skipped above)
if [ not fastecp ] and skippath
ld (firstchar),a ; Store first nonspace character
endif ;[ not fastecp ] and skippath
; With only BADDUECP (and neither SKIPPATH nor FASTECP)
; ..we store a null in the FIRSTCHAR flag
if [ not fastecp ] and [ not skippath ]
xor a
ld (firstchar),a
endif ;[ not fastecp ] and [ not skippath ]
endif ;fastecp or skippath or badduecp
; Resume processing of the command line
or a ; Now at end of line?
jr z,restart ; If so, get a new command line
cp ctrlc ; Flush ^C to prevent error-handler
jr z,restart ; ..invocation on warm boots
cp cmdsep ; Is it a command separator?
jr nz,nextcmd4 ; If not, skip ahead to process the command
inc hl ; If it is, skip over it
jr nextcmd3 ; ..and process next command
nextcmd4:
; Unless we are now running the external error handler, the following code
; saves the address of the current command in Z3MSG+4 for use by programs
; to determine the command line with which they were invoked.
ld a,(cmdstatfl) ; Get command status flag
bit 1,a ; Test for error handler invocation
jr nz,nextcmd5 ; If so, skip over next instruction
ld (cmdptr),hl
nextcmd5:
call parser ; Parse entire command line, then look for
; ..the command
;=============================================================================
; C O M M A N D S E A R C H C O D E
;=============================================================================
; CODE FOR FINDING AND RUNNING THE COMMAND
; Here is the code for running a command. Commands are searched for and
; processed in the following order:
;
; 1) flow control package (FCP) commands and IF state testing
; 2) resident command package (RCP)
; 3) command processor (CPR)
; 4) transient (COM file or extended command processor)
; 5) external error handler
; 6) internal error message and processing
;
; Special notes:
;
; a) If the current command is a shell command, special handling of flow
; control is required. If SHELLIF is enabled so that flow commands are
; allowed in shell alias scripts, then we reset the flow state to its
; initial condition (none) with each shell invocation (and after each
; command is run, we reset the shell bit in the code after CALLPROG).
; In this case shells will run regardless of flow state, and residual
; conditionals from the last running of the shell are flushed. Each
; shell input sequence begins afresh. On the other hand, if SHELLIF is
; off, flow control commands inside a shell script must be flushed so
; that they do not interfere with user entered commands.
; b) Directory prefixes are ignored for flow commands, since all flow control
; processing must pass through the FCP (the command must run even when
; the current flow state is false).
; c) If the command is not found in the FCP, then the current flow state is
; tested. If it is false, the command is flushed and the code branches
; back to get the next command.
; d) If the command had a directory prefix (a colon alone is sufficient),
; then steps #2 and #3 are skipped over,and the command is processed
; immediately as a transient program.
; e) In ZCPR33, unlike ZCPR30, RCP commands are scanned before CPR commands.
; This has been done so that more powerful RCP commands can supercede
; CPR commands.
; f) If the SKIPPATH option is enabled, when an explicit directory is
; specified with a command (but not just a colon), searching of the path
; is bypassed. If the FASTECP option is enabled, commands with leading
; spaces are sent directly to the ECP for processing.
; g) If no external command can be found, ZCPR33 performs extensive error
; handling. If the command error occurred while looking for a shell
; program, then the shell stack is popped. Otherwise, ZCPR33 tries to
; invoke an external, user-specified error handling command line. If
; none was specified or if the error handler invoked by that command
; line cannot be found, the internal error message (step #6) is displayed.
;-----------------------------------------------------------------------------
runcmd:
if shellif ; If shells reininitialize flow control...
ld a,(cmdstatfl) ; Get command status flag
bit 0,a ; Shell bit set?
jr z,fcpcmd ; If not a shell, process command
xor a ; Otherwise, shell is running, so
ld (ifptrfl),a ; ..reinitialize the IF system and continue
endif ;shellif
; ---------- Module <<1>>: Flow Control Processing
; An option is supported here to allow the address of the FCP to be obtained
; from the environment descriptor. This is logically consistent with the
; pholosopy of the Z-System and is useful when one wants to have a single block
; of FCP/RCP memory that can be allocated dynamically between FCP and RCP
; functions.
fcpcmd:
if fcp ne 0 ; Omit code if FCP not implemented
if fcpenv ; If getting FCP address from Z3ENV
ld e,12h ; Offset in Z3ENV to FCP address
call pkgoff ; Set HL to FCP+5
jr z,runcmd1 ; Skip if no FCP present
else ; using fixed FCP address
ld hl,fcp+5 ; Get address from Z3BASE.LIB
endif ;fcpenv
; If flow control processing is not allowed in shell aliases (scripts running
; as shell commands), then we have to make sure that we flush any flow control
; commmands, otherwise the CPR will attempt to execute them as transients,
; with dire consequences. In the code below we check the shell bit. If it
; is not set, we proceed normally. If it is set, we scan for flow commands
; and then jump past the flow testing to RUNFCP2, where the code will flush
; the command if it was a flow command and execute it unconditionally if not.
if not shellif
ld a,(cmdstatfl) ; Get command status flag
bit 0,a ; If shell bit not set,
jr z,runfcp1 ; ..we do normal processing
call cmdscan ; Otherwise, check for flow command
jr runfcp2 ; ..and flush if so using code below
endif ;not shellif
runfcp1:
call cmdscan ; Scan command table in the module
jr z,callprog ; Run if found (with no leading CRLF)
; This is where we test the current IF state. If it is false, we skip this
; command.
call iftest ; Check current IF status
runfcp2: ; If false, skip this command and go on to next
if drvprefix ; If DRVPREFIX we can use code below
jr z,jpnextcmd ; ..to save a byte
else ; Otherwise, we have to do an
jp z,nextcmd ; ..absolute jump
endif ;drvprefix
endif ;fcp ne 0
runcmd1:
if fastecp or badduecp
ld a,(firstchar) ; If FIRSTCHAR flag set for ECP invocation,
cp ' ' ; ..then go straight to transient processing
jr z,com
endif ;fastecp or badduecp
colon equ $+1 ; Flag for in-the-code modification
ld a,0 ; If command had a directory prefix (even just
or a ; ..a colon) then skip over resident commands
jr nz,comdir
; ---------- Module <<2>>: RCP Processing
; An option is supported here to allow the address of the RCP to be obtained
; from the environment descriptor. This is logically consistent with the
; pholosopy of the Z-System and is useful when one wants to have a single block
; of FCP/RCP memory that can be allocated dynamically between FCP and RCP
; functions.
if rcp ne 0 ; Omit code if RCP not implemented
rcpcmd:
if rcpenv ; If getting address of rcp from Z3ENV
ld e,0ch ; Offset in Z3ENV to RCP address
call pkgoff ; Set HL to address of RCP+5
jr z,cprcmd ; Skip if no RCP
else ; using fixed RCP address
ld hl,rcp+5 ; Get address from Z3BASE.LIB
endif ; rcpenv
call cmdscan ; Check for command in RCP
jr z,callproglf ; If so, run it (with leading CRLF)
endif ;rcp ne 0
; ---------- Module <<3>>: CPR-Resident Command Processing
cprcmd:
ld hl,cmdtbl ; Point to CPR-resident command table
call cmdscan ; ..and scan for the command
jr z,callprog ; If found, run it (with no leading CRLF)
; ---------- Module <<4>>: Transient Command Processing
comdir: ; Test for DU: or DIR: only (directory change)
if drvprefix
ld a,(cmdfcb+1) ; Any command name?
cp ' '
jr nz,com ; If so, must be transient or error
; Entry point for change of directory only
if wdu ; If controlled by wheel..
ld a,(z3whl) ; Get wheel byte
or a ; If wheel on, go on ahead
jr nz,comdir1
if badduecp
ld (colon),a ; Pretend there is no colon
ld a,' ' ; Force invocation of ECP
ld (firstchar),a
jr com
else ;not badduecp
ld a,ecduchg
jr z,error
endif ;badduecp
endif ; wdu
comdir1:
ld hl,(tempusr) ; Get temporary drive and user bytes
if not highuser ; If only users 0..15 can be logged
ld a,l ; Get user number and
cp 16 ; ..make sure not above 15
jr nc,baddirerr ; If out of range, invoke error handling
endif ;not highuser
dec h ; Shift drive to range 0..15
ld (curusr),hl ; Make the temporary DU into the current DU
call logcurrent ; Log into the new current directory
jpnextcmd:
jp nextcmd ; Resume command line processing
else ;not drvprefix
if badduecp
xor a ; Pretend there is no colon
ld (colon),a
ld a,' ' ; Force invocation of ECP
ld (firstchar),a
else ;not badduecp
ld a,ecduchg
jr z,error
endif ;badduecp
endif ;drvprefix
com: ; Process transient command
ld a,(cmdstatfl) ; Check command status flag to see if
and 2 ; ..error handler is running
ld (zexinpfl),a ; Store result in ZEX control flag (2 will turn
; ..ZEX input redirection off (0 = on)
if subon
ld (xsubflag),a ; Turn off XSUB input redirection also
endif ;subon
ld hl,tpa ; Set default execution/load address
ld a,3 ; Dynamically load type-3 and above ENVs
call mload ; Load memory with file specified in cmd line
ld a,(cmdstatfl) ; Check command status flag to see if
and 100b ; ..ECP running (and suppress leading CRLF)
; CALLPROG is the entry point for the execution of the loaded program. At
; alternate entry point CALLPROGLF if the zero flag is set, a CRLF is sent to
; the console before running the program.
callproglf:
call z,crlf ; Leading new line
callprog:
; Copy command tail into TBUFF
tailsv equ $+1 ; Pointer for in-the-code modification
ld hl,0 ; Address of first character of command tail
ld de,tbuff ; Point to TBUFF
push de ; Save pointer
ld bc,7e00h ; C=0 (byte counter) and B=7E (max bytes)
inc de ; Point to first char
tail:
ld a,(hl) ; Get character from tail
call tsteol ; Check for EOL
jr z,tail1 ; Jump if we are done
ld (de),a ; Put character into TBUFF
inc hl ; Advance pointers
inc de
inc c ; Increment character count
djnz tail ; If room for more characters, continue
call print ; Display overflow message
db bell ; ..ring bell
db 'Ovf','l'+80h ; ..then continue anyway
tail1:
xor a ; Store ending zero
ld (de),a
pop hl ; Get back pointer to character count byte
ld (hl),c ; Store the count
; Run loaded transient program
call defltdma ; Set DMA to 0080h standard value
; Perform automatic installation of Z3 programs (unless type-2 environment)
ld hl,(execadr) ; Get current execution address
call z3chk ; See if file is a Z3 program
jr nz,noinstall ; Branch if not
cp 2 ; If type-2 (internal) environment
jr z,noinstall ; ..do not perform installation
inc hl ; Advance to place for ENV address
ld (hl),low z3env ; Put in low byte of environment address
inc hl
ld (hl),high z3env ; Put in high byte
noinstall:
; Execution of the program occurs here by calling it as a subroutine
ld hl,z3env ; Pass environment address to program in HL
execadr equ $+1 ; Pointer for in-line code modification
call 0 ; Call transient
; Return from execution
if shellif ; If flow processing allowed in shells...
ld hl,cmdstatfl ; Reset the shell bit in the command status
res 0,(hl) ; ..flag so multiple-command shells will work
endif ;shellif
; Continue command processing
if drvprefix ; If DRVPREFIX we can save a byte by
jr jpnextcmd ; ..doing a two-step relative jump
else ; Otherwise, we just have to do
jp nextcmd ; ..the absolute jump
endif ;drvprefix
; ---------- Module <<5>>: External Error Handler Processing
baddirerr:
ld a,ecbaddir ; Error code for bad directory specification
error:
; If we are returning from an external command to process an error, we want
; to leave the error return code as it was set by the transient program.
ld hl,cmdstatfl ; Point to command status flag
bit 3,(hl) ; Check transient error flag bit
jr nz,error1 ; If set, leave error code as set externally
ld (ecflag),a ; Otherwise, save error code from A register
error1:
res 2,(hl) ; Reset the ECP bit to prevent recursion of
; ..error handler by programs that don't
; ..clear the bit
bit 0,(hl) ; Was error in attempting to run a shell?
jr nz,errsh ; If so, pop shell stack
; The following code is included to avoid a catastrophic infinite loop when
; the external error handler cannot be found. After one unsuccessful try,
; the internal code is invoked.
bit 1,(hl) ; Was an error handler already called?
jr nz,errintrnl ; If so, use internal error handler
; If the current IF state is false, we would like to ignore the error and just
; go on with the next command. Unfortunately, for some errors (e.g., a bad
; command format such as a command with a wildcard character) the error handler
; is invoked before the pointer in the multiple command line buffer is set up
; to the next command. In that case, we fall into an infinite loop. We also
; must not allow the external error handler to run, since it will not run and
; we will again fall into an infinite loop. The present code is not so bad, of
; course, since even a command in a false part of a command sequence should not
; have a true error in it. We have already put in code to bypass password
; checking during a false IF state, since a command with a password is not an
; invalid command.
if fcp ne 0
call iftest ; If we are in a false IF state, external
jr z,errintrnl ; ..handler will not run, so use built-in
endif ;fcp ne 0
set 1,(hl) ; Set command status flag for error invocation
ld hl,errcmd ; Point to error handler command line
ld a,(hl) ; Check first byte for presence of an
or a ; ..error command line
jr z,errintrnl ; If no error handler, use built-in one
ld (nxtchr),hl ; Else, use error command line as next command
jp nextcmd1 ; Run command without resetting status flag
; ---------- Module <<6>>: Resident Error Handler Code
; If the error is with the invocation of a shell command, we pop the bad shell
; command off the stack to prevent recursion of the error. We then use the
; the internal error handler to echo the bad shell command.
errsh:
ld de,shstk ; Point to current entry in shell stack
ld hl,shstk+shsize ; Point to next entry in stack
ld bc,[shstks-1]*shsize ; Bytes to move
ldir ; Pop the stack
xor a ; Clear the last entry position
ld (de),a
errintrnl:
if subon
call subkil ; Terminate active submit file if any
endif ;subon
call crlf ; New line
ld hl,(cmdptr) ; Point to beginning of bad command
call printhl ; Echo it to console
call print ; Print '?'
defb '?'+80h
jp restart ; Restart CPR
; End ZCPR33-2.Z80
page
; ZCPR33-3.Z80
;=============================================================================
;
; C O M M A N D L I N E P A R S I N G C O D E
;
;=============================================================================
; This code parses the command line pointed to by HL. The command verb is
; parsed, placing the requested program name into the command file control
; block. The drive and user bytes are set. If an explicit DU or DIR was
; given, the COLON flag is set so that the processor knows about this later
; when the command search path is built.
parser:
ld de,cmdfcb ; Point to the command FCB
push de
call initfcb ; Initialize the FCB
pop de
ld (duerrflag),a ; Store zero (INITFCB ends with A=0) into flag
call scanner ; Parse first token on command line into FCB
jr nz,badcmd ; Invoke error handler if '?' in command
duerrflag equ $+1 ; Pointer for in-the-code modification
ld a,0 ; See if bad DU/DIR specified with command verb
or a
if badduecp
jr z,parser1 ; If DU/DIR is OK, skip ahead
ld a,(cmdstatfl) ; If ECP already running
bit 2,a ; ..skip ahead
jr nz,parser1
ld a,(cmdfcb+1) ; If not a directory change command
sub ' ' ; ..invoke error handler
jr nz,baddirerr
; If bad directory change attempt,
ld (tmpcolon),a ; ..pretend there is no colon (A=0)
ld a,' ' ; ..and force immediate ECP invocation
ld (firstchar),a ; ..when command is processed
else ; If errors not processed by ECP then
jr nz,baddirerr ; ..invoke error handler
endif ; badduecp
parser1:
ld de,cmdfcb+9 ; Make sure no explicit file type was given
ld a,(de) ; Get first character of file type
cp ' ' ; Must be blank
badcmd:
ld a,ecbadcmd ; Error code for illegal command form
jr nz,error ; If not, invoke error handler
push hl ; Save pointer to next byte of command
ld hl,commsg ; Place default file type (COM) into FCB
ld bc,3
ldir
pop hl ; Get command line pointer back
; The following block of code is arranged so that the COLON flag is set only
; when an explicit directory specification is detected in the command verb.
; Other parses also change the TMPCOLON flag, but only when passing here does
; the flag get transferred to COLON.
tmpcolon equ $+1 ; Pointer for in-the-code modification
ld a,0 ; ..by SCANNER routine
ld (colon),a ; If explicit DU/DIR, set COLON flag
; Find the end of this command and set up the pointer to the next command.
push hl ; Save command line pointer
dec hl ; Adjust for preincrementing below
parser2: ; Find end of this command
inc hl ; Point to next character
ld a,(hl) ; ..and get it
call tsteol ; Test for end of command
jr nz,parser2 ; Keep looping if not
ld (nxtchr),hl ; Set pointer to next command
pop hl ; Get back pointer to current command tail
; This block of code parses two tokens in the command line into the two
; default FCBs at 5Ch and 6Ch. It also sets a pointer to the command tail
; for later copying into the command tail buffer at 80h. This code is used
; first when attempting to parse a normal command line and possibly again
; later when the entire user's command is treated as a tail to the extended
; command processor. The resident JUMP and SAVE commands use it also, and
; the entry point is available at location CCP+9 for use by other programs.
parsetail:
ld (tailsv),hl ; Save pointer to command tail
; Process first token
ld de,tfcb ; Point to first default FCB
push de ; Save pointer while initializing
call initfcb ; Initialize both default FCBs
pop de
call sksp ; Skip over spaces in command line
call nz,scanner ; If not end of line, parse the token
; ..into first FCB
ld (parseptr),hl ; Save pointer to second token for reparsing
; Process second token
call sksp ; Skip over spaces
ret z ; Done if end of line or end of command
ld de,tfcb2 ; Point to second default FCB
; ..and fall through to SCANNER routine
;-----------------------------------------------------------------------------
; This routine processes a command line token pointed to by HL. It attempts
; to interpret the token according to the form [DU:|DIR:]NAME.TYP and places
; the corresponding values into the FCB pointed to by DE. On exit, HL points
; to the delimiter encountered at the end of the token. The Z flag is set if
; a wild card was detected in the token.
scanner:
xor a ; Initialize various flags
ld (tmpcolon),a ; Set no colon
ld bc,(curusr) ; Get current drive and user into BC
inc b ; Shift drive range from 0..15 to 1..16
ld (tempusr),bc ; Initialize temporary DU
call scanfld8 ; Extract possible file name
cp ':' ; Was terminating character a colon?
jr nz,scantype ; If not, go on to extract file type
ld (tmpcolon),a ; Otherwise, set colon and process DU/DIR
inc hl ; Point to character after colon
; Code for resolving directory specifications (macro RESOLVE is defined in
; Z33MAC.LIB). RESOLVE returns with a nonzero value and a NZ flag setting
; if the DU/DIR specification cannot be resolved. There are quite a few
; possibilities here.
; Case where both forms are accepted
if accptdir and accptdu
if dufirst
resolve du,dir ; Check DU: form before DIR: form
else
resolve dir,du ; Check DIR: form before DU: form
endif ;dufirst
endif ;accptdir and accptdu
; Cases of only one form accepted
if accptdu and not accptdir
resolve du, ; Check only DU: form
endif ;accptdu and not accptdir
if accptdir and not accptdu
resolve dir, ; Check only DIR: form
endif ;accptdir and not accptdu
; Case of neither form accepted
if not accptdir and not accptdu
push hl ; Save pointer to command string
inc de ; Point to first character of name
ld a,(de) ; Get it
dec de ; Restore the pointer
sub ' ' ; If no name is there, A=0 and Z flag set
endif ;not accptdir and not accptdu
push de ; Save pointer to FCB again
push af ; Save bad directory flag
ld a,(tempdr) ; Set designated drive
ld (de),a ; ..into FCB
inc de ; Point to file name field
call ifcb ; Perform partial init (set user code)
pop af ; Get bad directory flag back
ld (duerrflag),a ; Save flag in parser code
jr z,scanner1 ; Branch if valid directory specified
dec de ; Back up to record count byte
dec de
ld (de),a ; Store error flag there (NZ if error)
scanner1:
pop de ; Get FCB pointer back
pop hl ; Restore pointer to command string
call scanfld8 ; Scan for file name
; This code processes the file type specification in the token
scantype:
ld a,(hl) ; Get ending character of file name field
ex de,hl ; Switch FCB pointer into HL
ld bc,8 ; Offset to file type field
add hl,bc
ex de,hl ; Switch pointers back
ld b,3 ; Maximum characters in file type
cp '.' ; See if file type specified
jr nz,scantype2 ; If not, skip over file type parsing
inc hl ; Point to character after '.'
push de ; Save pointer to FCB file type
call scanfield ; Parse file type into FCB
pop de
scantype2:
ex de,hl ; Swap pointers again
ld bc,5 ; Offset from file type to S1 field in FCB
add hl,bc
ex de,hl ; Swap pointers back
ld a,(tempusr) ; Get specified user number
ld (de),a ; ..and store in S1 byte of FCB
scan3: ; Skip to space character, character after an
; ..equal sign, or to end of command
ld a,(hl) ; Get next character
cp ' '+1 ; Done if less than space
jr c,scan4
call tsteol ; Done if end of line or end of command
jr z,scan4
inc hl ; Skip on to next character
cp '=' ; If not equal sign
jr nz,scan3 ; ..keep scanning
scan4: ; Set zero flag if '?' in filename.typ
qmcnt equ $+1 ; Pointer for in-the-code modification
ld a,0 ; Number of question marks
or a ; Set zero flag
ret
; This routine invokes SCANFIELD for a file name field. It initializes the
; question mark count and preserves the FCB pointer.
scanfld8:
xor a ; Initialize question mark count
ld (qmcnt),a
push de ; Save pointer to FCB
ld b,8 ; Scan up to 8 characters
call scanfield
pop de ; Restore pointer to FCB
ret
; This routine scans a command-line token pointed to by HL for a field whose
; maximum length is given by the contents of the B register. The result is
; placed into the FCB buffer pointed to by DE. The FCB must have had its name
; and type fields initialized before this routine is called. Wild cards of
; '?' and '*' are expanded. On exit, HL points to the terminating delimiter.
scanfield:
call sdelm ; Done if delimiter encountered
ret z
inc de ; Point to next byte in FCB
cp '*' ; Is character a wild card?
jr nz,scanfld1 ; Continue if not
ld a,'?' ; Process '*' by filling with '?'s
ld (de),a
call qcountinc ; Increment count of question marks
jr scanfld2 ; Skip so HL pointer left on '*'
scanfld1: ; Not wildcard character '*'
ld (de),a ; Store character in FCB
inc hl ; Point to next character in command line
cp '?' ; Check for question mark (wild)
call z,qcountinc ; Increment question mark count
scanfld2:
djnz scanfield ; Decrement char count until limit reached
scanfld3:
call sdelm ; Skip until delimiter
ret z ; Zero flag set if delimiter found
inc hl ; Pt to next char in command line
jr scanfld3
; Subroutine to increment the count of question mark characters in the
; parsed file name.
qcountinc:
push hl
ld hl,qmcnt ; Point to count
inc (hl) ; Increment it
pop hl
ret
;-----------------------------------------------------------------------------
; Validate the password pointed to by HL. Prompt user for password entry
; and return zero if it is correct.
if pwcheck
passck:
push hl ; Save pointer to password
call print ; Prompt user
defb cr,lf,'PW?',' '+80h
ld hl,pwlin ; Set up buffer for user input
ld bc,90ah ; Set 0ah (BDOS readln function) in C
ld (hl),b ; ..and 9 (max character count) in B
ex de,hl ; Switch buffer pointer to DE
if pwnoecho
ld a,0c9h ; Disable BIOS conout routine to
ld (bios+0ch),a ; ..suppress password echoing
call bdossave ; Get user input
ld a,0c3h ; Reenable BIOS conout routine
ld (bios+0ch),a
else ;not pwnoecho
call bdossave ; Get user input
endif ;pwnoecho
ex de,hl ; Restore pointer to HL
inc hl ; Point to count of characters entered
ld a,(hl) ; Get character count
inc hl ; Point to first character
push hl ; Save pointer while marking end of input
call addah ; Advance HL to just past last character
ld (hl),' ' ; Place space there
pop de ; Restore pointer to beginning of user input
pop hl ; Restore pointer to password from NDR
ld b,8 ; Maximum characters to compare
pwck:
ld a,(de) ; Get next user character
call ucase ; Capitalize it
cp (hl) ; Compare to NDR
ret nz ; No match
cp ' ' ; If last user character matched space in
ret z ; ..NDR, then we have a complete match
inc hl ; If not done, point to next characters
inc de
djnz pwck ; (flags not affected by DJNZ)
xor a ; Set zero flag and
ret ; ..return Z to show success
endif ; pwcheck
;-----------------------------------------------------------------------------
; This code attempts to interpret the token in the FCB pointed to by register
; pair DE as a DIR (named directory) prefix. If it is successful, the drive
; and user values are stored in TEMPDR and TEMPUSR, the zero flag is set, and
; a value of zero is returned in register A.
;
; If the named directory is found to be password restricted, then the user is
; asked for the password (unless the directory is the one currently logged or
; the current IF state is false). If an incorrect password is entered, the
; error handler is generally invoked directly. The exception to this is when
; the transient program bit is set in the command status flag (this bit would
; be set by a non-CPR program that calls REPARSE). In this case the default
; directory is returned, the zero flag is reset, and a nonzero value in
; returned in register A to show a bad directory. In addition, the code in
; SCANNER will set record-count byte in the FCB to a nonzero value so that
; the calling program can detect the error. [Note: if DU processing is also
; allowed and it follows DIR processing, DUSCAN will also be called. Unless
; there is a passworded directory with a DU form, this will cause no trouble.]
if accptdir
dirscan:
; If the DU form is not allowed, we have to detect a colon-only condition here.
; Otherwise DUSCAN will take care of it.
inc de ; Point to first byte of directory form
if not accptdu
ld a,(de) ; Get first character of directory
sub ' ' ; If it is a blank space
ret z ; ..we have a successful directory resolution
endif ;not accptdu
ex de,hl ; Switch pointer to FCB to HL
if ndrenv ; If getting NDR address for Z3ENV
ld e,15h ; Offset to NDR address
push hl ; Preserve pointer to FCB
call pkgoff ; Get NDR address from ENV into DE
pop hl
jr z,direrr ; Branch if no NDR implemented
else ; using fixed address of NDR buffer
ld de,z3ndir ; Point to first entry in NDR
endif ; ndrenv
dirscan1:
ld a,(de) ; Get next character
or a ; Zero if end of NDR
jr z,direrr
inc de ; Point to name of directory
inc de
push hl ; Save pointer to name we are looking for
push de ; Save pointer to NDR entry
ld b,8 ; Number of characters to compare
dirscan2:
ld a,(de)
cp (hl)
jr nz,dirscan3 ; If no match, quit and go on to next DIR
inc hl ; Point to next characters to compare
inc de
djnz dirscan2 ; Count down
dirscan3:
pop de ; Restore pointers
pop hl
jr z,dirscan4 ; Branch if we have good match
ex de,hl ; Advance to next entry in NDR
ld bc,16 ; 8 bytes for name + 8 bytes for password
add hl,bc
ex de,hl
jr dirscan1 ; Continue comparing
; If ACCPTDU is enabled, we can share similar code in DUSCAN and do not need
; the code here.
if not accptdu
direrr: ; No match found
dec a
ret
endif ;not accptdu
dirscan4: ; Match found
ex de,hl ; Switch pointer to NDR entry into HL
push hl ; ..and save it for later
dec hl ; Point to user corresponding to the DIR
ld c,(hl) ; Get user value into C
dec hl ; Point to drive
ld b,(hl) ; Get it into B
if pwcheck
ld hl,(curusr) ; Get current drive/user into HL
inc h ; Shift drive to range 1..16
xor a ; Clear carry flag
sbc hl,bc ; Compare
pop hl ; Restore pointer to NDR entry
jr z,setdu ; If same, accept values without PW checking
; If WPASS is set, then password checking is bypassed when the wheel byte is
; set.
if wpass
ld a,(z3whl) ; Get wheel byte
or a ; If wheel byte set
jr nz,setdu ; ..skip checking passwords
endif ;wpass
; This code is a bit tricky. We do not want to be asked for passwords for
; named directory references in commands when the current IF state is false.
; So, first we check to see if there is a password on the directory. If not,
; we proceed to set the temporary DU to the specified directory. If there is
; a password, we check the current IF state. If it is false, we do not check
; passwords and pretend there was no password. However, we leave the current
; directory in effect. This will work properly in all but one rare
; circumstance. When the command is an 'OR' command with a reference to a
; passworded named directory (e.g., "OR EXIST SECRET:FN.FT"), the password
; will not be requested and the current directory will be used instead of the
; specified one.
push bc ; Save requested drive/user
ld bc,8 ; Point to password in NDR
add hl,bc
ld a,(hl) ; Get first character of password
cp ' ' ; Is there a password?
if fcp eq 0 ; If FCP not implemented ...
call nz,passck ; Perform password checking if pw present
else ;fcp ne 0 ; FCP implemented ...
jr z,dirscan5 ; If no pw, skip ahead
call iftest ; Otherwise, test current IF state
pop bc ; Restore BC in case we return now
ret z ; If false IF in effect, fake success without
; ..checking password (but TEMPDR/TEMPUSR not
; ..set)
push bc ; Otherwise, save BC again
call passck ; Perform password checking
endif ;fcp eq 0
dirscan5:
pop bc ; Restore requested drive/user
jr z,setdu ; If not bad password, set it up
ld a,(cmdstatfl) ; See if external invocation (disable
bit 3,a ; ..error handling if so)
ret nz ; Return NZ to show bad directory
ld a,ecbadpass ; Error code for bad password
jp error
else ;not pwcheck
pop hl ; Clean up stack
if accptdu ; If we cannot fall through, branch
jr setdu
endif ;accptdu
endif ;pwcheck
if not accptdu ; If NOT ACCPTDU, we have to supply code here
setdu:
ld (tempusr),bc
xor a ; Set Z to flag success
ret
endif ;not accptdu
endif ;accptdir
;-----------------------------------------------------------------------------
; This code attempts to interpret the token in the FCB pointed to by register
; pair DE as a DU (drive/user) prefix. If it is successful, the drive and
; user values are stored in TEMPDR and TEMPUSR, the zero flag is set, and a
; value of zero is returned in register A. Otherwise the zero flag is reset
; and a nonzero value is returned in register A.
;
; The ADUENV option allows acceptance of the DU form to be controlled by the
; DUOK flag in the environment descriptor. An additional feature of this code
; when the ADUENV option is enabled is that a DU value is always accepted,
; even if DUOK is off and even if it is outside the normally allowed range,
; if it corresponds to a named directory with no password. The currently
; logged directory is unconditionally acceptable (if you got there once, you
; can stay as long as you like without further hassles).
if accptdu ; Allow DU: form
direrr: ; This code may do double duty for DIRSCAN
; ..above
duerr:
xor a ; Return NZ to show failure
dec a
ret
duscan:
ex de,hl ; Switch FCB pointer to HL
inc hl ; Point to first byte of file name in FCB
ld bc,(curusr) ; Preset C to current user, B to current drive
ld a,(hl) ; Get possible drive specification
sub 'A' ; Otherwise convert to number 0..15
jr c,duscan1 ; If < 0, leave B as is
cp 16
jr nc,duscan1 ; If > 15, leave B as is
ld b,a ; Otherwise use value given
inc hl ; ..and point to next character
duscan1:
inc b ; Shift drive to range 1..16
ld a,(hl) ; Get possible user specification
cp ' '
jr z,duscan2 ; If none present, leave C as is
push bc ; Save DU values in BC
call decimal1 ; Get specified decimal user number into BC
pop hl ; Restore values to HL
jr c,duerr ; Return NZ if invalid decimal conversion
ld a,b ; Get high byte of result
or a ; Make sure it is zero
ret nz ; If not, return NZ to show bad user number
ld b,h ; DU value is now in BC
; If the specified directory is the currently logged directory, accept it
; even if it is out of range and/or password protected.
duscan2:
ld hl,(curusr) ; Get current drive/user into HL
inc h ; Shift drive to range 1..16
xor a ; Clear carry flag
sbc hl,bc ; Compare values
jr z,setdu
; If the specified DU corresponds to a named directory with no password, or
; if WPASS is enabled so that password checking is not performed when the
; wheel byte is set, then accept it.
if z3ndir ne 0
call du2dir ; See if there is a matching named directory
jr z,duscan3 ; If not, skip on
if pwcheck ; If passwords are being checked...
if wpass
ld a,(z3whl) ; Get wheel byte
or a ; If wheel byte set, skip checking passwords
jr nz,setdu ; ..and accept the DU values
endif ;wpass
ld de,9 ; Advance to password
add hl,de
ld a,(hl) ; Get first character of password
cp ' '
jr z,setdu ; If none, we have a valid DU
else ;not pwcheck
jr setdu ; Set the DU
endif ;pwcheck
endif ;z3ndir ne 0
duscan3:
if aduenv ; Check DUOK flag in ENV
ld a,(duokfl) ; Get flag
or a ; If DU not accepted
jr z,duerr ; ..skip over the DU scan
endif ;aduenv
if duenv ; If getting max drive and user from ENV
ld hl,(maxdrenv) ; Get max drive into L and max user into H
ld a,l ; Test drive value
cp b
jr c,duerr
ld a,h ; Test user value
cp c
jr c,duerr
else ; Using fixed values of max DU
ld a,maxdisk
cp b
jr c,duerr
ld a,maxusr
cp c
jr c,duerr
endif ;duenv
setdu:
ld (tempusr),bc
xor a ; Set Z to flag success
ret
endif ; accptdu
; End ZCPR33-3.Z80
page
; ZCPR33-4.Z80
;=============================================================================
;
; G E N E R A L S U B R O U T I N E S S E C T I O N
;
;=============================================================================
;-----------------------------------------------------------------------------
;
; CHARACTER I/O BDOS ROUTINES
;
;-----------------------------------------------------------------------------
; Get uppercase character from console (with ^S processing). Registers B,
; D, H, and L are preserved. The character is returned in A.
conin:
ld c,1 ; BDOS conin function
call bdossave
; Fall through to UCASE
;--------------------
; Convert character in A to upper case. All registers except A are preserved.
ucase:
and 7fh ; Mask out msb
cp 61h ; Less than lower-case 'a'?
ret c ; If so, return
cp 7bh ; Greater than lower-case 'z'?
ret nc ; If so, return
and 5fh ; Otherwise capitalize
ret
;----------------------------------------
; Output CRLF
crlf:
call print
db cr
db lf or 80h
ret
;----------------------------------------
; Output character in A to the console. All registers are preserved.
conout:
push de
push bc
ld c,2 ; BDOS conout function
output: ; Entry point for LCOUT below
ld e,a
call bdossave
pop bc
pop de
ret
;----------------------------------------
; Print the character string immediately following the call to this routine.
; The string terminates with a character whose high bit is set or with a null.
; At entry point PRINTC the string is automatically preceded by a
; carriage-return-linefeed sequence. All registers are preserved except A.
printc:
call crlf ; New line
print:
ex (sp),hl ; Get pointer to string
call printhl ; Print string
ex (sp),hl ; Restore HL and set return address
ret
;----------------------------------------
; Print the character string pointed to by HL. Terminate on character with
; the high bit set or on a null character. On return HL points to the byte
; after the last character displayed. All other registers except A are
; preserved.
printhl:
ld a,(hl) ; Get a character
inc hl ; Point to next byte
or a ; End of string null?
ret z
push af ; Save flags
and 7fh ; Mask out msb
call conout ; Print character
pop af ; Get flags
ret m ; String terminated by msb set
jr printhl
;-----------------------------------------------------------------------------
;
; FILE I/O BDOS ROUTINES
;
;-----------------------------------------------------------------------------
; Read a record from a file to be listed or typed
if lton ; Only needed for LIST and TYPE functions
readf:
ld de,tfcb
jr read
endif ; lton
;----------------------------------------
; Read a record from the command file named in CMDFCB
readcmd:
ld de,cmdfcb
; Read a record from file whose FCB is pointed to by DE
read:
ld c,14h ; Read-sequential function
; Fall through to BDOSSAVE
;--------------------
; Call BDOS for read and write operations. The flags are set appropriately.
; The BC, DE, and HL registers are preserved.
bdossave:
putreg
call bdos
getreg
or a ; Set flags
note: ; This return is used for NOTE command, too
ret
;-----------------------------------------------------------------------------
;
; MISCELLANEOUS BDOS ROUTINES
;
;-----------------------------------------------------------------------------
; Set DMA address. At the entry point DEFLTDMA the address is set to the
; default value of 80H. At the entry point DMASET it is set to the value
; passed in the DE registers.
defltdma:
ld de,tbuff
dmaset:
ld c,1ah
jr bdossave
;----------------------------------------
; Log in the drive value passed in the A register (A=0).
setdrive:
ld e,a
ld c,0eh
jr bdossave
;----------------------------------------
; Open a file. At entry point OPENCMD the file is the one specified in
; CMDFCB, and the current record is set to zero. At entry point OPEN
; the file whose FCB is pointed to by DE is used.
opencmd:
xor a ; Set current record to 0
ld (cmdfcb+32),a
ld de,cmdfcb ; Command file control block
; Fall through to open
open:
ld c,0fh ; BDOS open function
; Fall through to BDOSTEST
;--------------------
; Invoke BDOS for disk functions. This routine increments the return code in
; register A so that the zero flag is set if there was an error. Registers
; BC, DE, and HL are preserved.
bdostest:
call bdossave
inc a ; Set zero flag for error return
ret
;----------------------------------------
; Close file whose FCB is pointed to by DE.
if saveon or subon
close:
ld c,10h
jr bdostest
endif ;saveon or subon
;----------------------------------------
; Search for first matching file. At entry point SRCHFST1 the first default FCB
; is used. At entry point SRCHFST the FCB pointed to by DE is used.
if diron or eraon or renon or saveon
srchfst1:
ld de,tfcb ; Use first default FCB
endif ;diron or eraon or renon or saveon
srchfst:
ld c,11h
jr bdostest
;-----------------------------------------------------------------------------
; Search for next matching file whose FCB is pointed to by DE.
if diron or eraon ; Only needed by DIR and ERA functions
srchnxt:
ld c,12h
jr bdostest
endif ; diron or eraon
;-----------------------------------------------------------------------------
; Kill any submit file that is executing.
if subon
subkil:
ld hl,subflag ; Check for submit file in execution
ld a,(hl)
or a ; 0=no
ret z ; If none executing, return now
; Kill submit file
xor a
ld (hl),a ; Zero submit flag
call setuser ; Log in user 0
ld de,subfcb ; Delete submit file
; ..by falling through to delete routine
endif ; subon
;--------------------
; Delete file whose FCB is pointed to by DE.
if eraon or renon or saveon or subon
delete:
ld c,13h
jr bdossave
endif ;eraon or renon or saveon or subon
;-----------------------------------------------------------------------------
; Get and set user number. Registers B, D, H, and L are preserved. Register
; E is also preserved at entry point SETUSER1.
getuser:
ld a,0ffh ; Get current user number
setuser:
ld e,a ; User number in E
setuser1:
ld c,20h ; Get/Set BDOS function
jr bdossave
;-----------------------------------------------------------------------------
;
; GENERAL UTILITY ROUTINES
;
;-----------------------------------------------------------------------------
; This subroutine checks to see if a program loaded at an address given by HL
; has a Z3ENV header. If the header is not present, the zero flag is reset.
; If it is present, the zero flag is set, and on return HL points to the
; environment-type byte and A contains that byte.
z3chk:
ld de,z3env+3 ; Point to 'Z3ENV' string in ENV
inc hl ; Advance three bytes to possible program
inc hl ; ..header
inc hl
ld b,5 ; Characters to compare
z3chk1: ; Check for Z3 ID header
ld a,(de) ; Get character from ENV descriptor
cp (hl) ; Compare it to loaded file
ret nz ; Quit now if mismatch
inc hl ; If same, advance to next characters
inc de ; ..and continue comparing
djnz z3chk1 ; (flags not affected by DJNZ)
ld a,(hl) ; Return the environment type in A
ret ; Return Z if all 5 characters match
;----------------------------------------
; Subroutine to skip over spaces in the buffer pointed to by HL. On return,
; the zero flag is set if we encountered the end of the line or a command
; separator character.
sksp:
ld a,(hl) ; Get next character
inc hl ; Point to the following character
cp ' ' ; Space?
jr z,sksp ; If so, keep skipping
dec hl ; Back up to non-space
; ..and fall through
;--------------------
; Subroutine to check if character is the command separator or marks the end
; of the line.
tsteol:
or a ; End of command line?
ret z ; Return with zero flag set
cp cmdsep ; Command separator?
ret ; Return with flag set appropriately
;----------------------------------------
; Initialize complete FCB pointed to by DE
initfcb:
xor a
ld (de),a ; Set default disk (dn byte is 0)
inc de ; Point to file name field
call ifcb ; Fill 1st part of FCB
; Fall through to IFCB to run again
;--------------------
; Initialize part of FCB whose file name field is pointed to by DE on entry.
; The file name and type are set to space characters; the EX, S2, RC, and the
; following CR (current record ) or DN (disk number) fields are set to zero.
; The S1 byte is set to the current user number. On exit, DE points to the
; byte at offset 17 in the FCB (two bytes past the record count byte).
ifcb:
ld b,11 ; Store 11 spaces for file name and type
ld a,' '
call fill
xor a
ld (de),a ; Set extent byte to zero
inc de
ld a,(curusr)
ld (de),a ; Set S1 byte to current user
inc de
ld b,3 ; Store 3 zeroes
xor a ; Fall thru to fill
;--------------------
; Fill memory pointed to by DE with character in A for B bytes
fill:
ld (de),a ; Fill with byte in A
inc de ; Point to next
djnz fill
ret
;----------------------------------------
; Subroutine to display the 'no file' error message for the built-in
; commands DIR, ERA, LIST, TYPE, and/or REN.
if diron or eraon
prnnf:
call printc ; No file message
defb 'No Fil','e'+80h
ret
endif ; diron or eraon
;----------------------------------------
; Calculate address of command table in package from Z3ENV. On entry, E
; contains the offset to the address of the package in the environment. On
; exit, DE points to the beginning of the package and HL points to the fifth
; byte (where the command table starts in the RCP and FCP modules). The zero
; flag is set on return if the package is not supported.
if fcpenv or rcpenv or ndrenv
pkgoff:
ld hl,z3env ; Point to beginning of ENV descriptor
ld d,0 ; Make DE have offset
add hl,de ; ..and add it
ld a,(hl) ; Get low byte of package address
inc hl ; Point to high byte
ld h,(hl) ; ..and get it
ld l,a ; Move full address into HL
or h ; Set zero flag if no package
ld de,5 ; Offset to start of table
ex de,hl ; Preserve start address of package in DE
add hl,de ; Pointer to 5th byte of package in HL
ret ; Return with zero flag set appropriately
endif ;fcpenv or rcpenv or ndrenv
;----------------------------------------
; This subroutine checks to see if we are in a false IF state. If that is
; the case, the routine returns with the zero flag set. If there is not active
; IF state or if it is true, then the zero flag is reset.
if fcp ne 0 ; Omit code if FCP not implemented
iftest:
ld bc,(ifptrfl) ; Current IF pointer into C, IF status into B
ld a,c ; See if any IF in effect
or a
jr z,iftest1 ; Branch if no IF state is active
and b ; Mask the current IF status
ret
iftest1:
dec a ; Reset the zero flag
ret
endif ;fcp ne 0
;----------------------------------------
; Print the command prompt with DU and/or DIR (but without any trailing
; character). This is also the code in which the current drive and user
; will be stored. The conditional assemblies are somewhat involved because
; of the possibilities of either or both of the DU or DIR forms being omitted
; from the prompt.
prompt:
call crlf
if incldu ; If drive/user in prompt
ld hl,(curusr) ; Get current drive/user into HL
; If INCLENV is enabled, the drive and user (DU) will be included in the
; prompt based on the state of the DUOK flag in the environment. If INCLENV
; is disabled, the DU form will always be included if INCLDU is on.
if inclenv
ld a,(duokfl) ; If ENV disallows DU,
or a ; ..then don't show it in
jr z,prompt2 ; ..the prompt, either
endif ;inclenv
ld a,h ; Get current drive
add a,'A' ; Convert to ascii A-P
call conout
ld a,l ; Get current user
if supres ; If suppressing user # report for user 0
or a
jr z,prompt2
endif
cp 10 ; User < 10?
jr c,prompt1
if highuser ; If allowing users 16..31
ld c,'0'-1
prompt0:
inc c
sub 10
jr nc,prompt0
add a,10
ld b,a
ld a,c
call conout
ld a,b
else ;using only standard user numbers 0..15
sub 10 ; Subtract 10 from user number
push af ; Save low digit
call print ; Display a '1' for tens digit
defb '1' or 80h
pop af
endif ;highuser
prompt1:
add a,'0' ; Output 1's digit (convert to ascii)
call conout
prompt2:
endif ; incldu
; Display named directory
if incldir
if incldu
ld b,h ; Copy drive/user from HL to BC
ld c,l ; ..(saves a byte)
else
ld bc,(curusr) ; Get current drive and user into BC
endif ;incldu
inc b ; Switch drive to range 1..16
call du2dir ; See if there is a corresponding DIR form
ret z ; If not, return now
if incldu ; Separate DU and DIR with colon
if inclenv
ld a,(duokfl) ; If not displaying DU, then
or a ; ..don't send separator, either
ld a,':' ; Make the separator
call nz,conout ; ..and send if permitted
else
call print ; Put in colon separator
defb ':' or 80h
endif ;inclenv
endif ; incldu
ld b,8 ; Max of 8 chars in DIR name
prompt3:
inc hl ; Point to next character in DIR name
ld a,(hl) ; ..and get it
cp ' ' ; Done if space
ret z
call conout ; Print character
djnz prompt3 ; Count down
endif ; incldir
ret
;-----------------------------------------------------------------------------
; Subroutine to convert DU value in BC into pointer to a matching entry in
; the NDR. If there is no match, the routine returns with the zero flag set.
; If a match is found, the zero flag is reset, and the code returns with HL
; pointing to the byte before the directory name.
if z3ndir ne 0
du2dir:
if ndrenv ; If getting NDR address from environment
ld e,15h ; Offset to NDR in Z3ENV
call pkgoff ; Get address of NDR into DE
ex de,hl ; ..and switch into HL
ret z ; If no NDR, return with zero flag set
jr du2dir2
else
ld hl,z3ndir-17 ; Scan directory for match
endif ;ndrenv
du2dir1: ; Advance to next entry in NDR
ld de,16+1 ; Skip user (1 byte) and name/pw (16 bytes)
add hl,de
du2dir2:
ld a,(hl) ; End of NDR?
or a
ret z ; If so, return with zero flag set
inc hl ; Point to user number in NDR entry
cp b ; Compare drive values
jr nz,du2dir1 ; If mismatch, back for another try
ld a,(hl) ; Get user number
sub c ; ..and compare
jr nz,du2dir1 ; If mismatch, back for another try
dec a ; Force NZ to show successful match
ret
endif ;z3ndir ne 0
;-----------------------------------------------------------------------------
; This routine gets the next line of input for the command buffer. The
; following order of priority is followed:
; If ZEX is active, the next line is obtained from ZEX
; If a submit file is running, its last record provides the input
; If there is a command line on the shell stack, use it
; Finally, if none of the above, the input is obtained from the user
readbuf:
ld a,(zexrunfl) ; Get ZEX-running flag
or a
jr nz,userinput ; If ZEX running, go directly to user input
if subon ; If submit facility is enabled, check for it
ld a,(subflag) ; Test for submit file running
or a
jr z,shellinput ; If not, go on to possible shell input
xor a ; Log into user 0
call setuser
call defltdma ; Initialize DMA pointer
ld de,subfcb ; Point to submit file FCB
call open ; Try to open file
jr z,readbuf1 ; Branch if open failed
ld hl,subfrc ; Point to record count in submit FCB
ld a,(hl) ; Get the number of records in file
dec a ; Reduce to number of last record
ld (subfcr),a ; ..and put into current record field
call read ; Attempt to read submit file
jr nz,readbuf1 ; Branch if read failed
dec (hl) ; Reduce file record cound
dec hl ; Point to S2 byte of FCB (yes, this is req'd!)
ld (hl),a ; Stuff a zero in there (A=0 from call to READ)
call close ; Close the submit file one record smaller
jr z,readbuf1 ; Branch if close failed
; Now we copy the line read from the file into the multiple command line
; buffer
ld de,chrcnt ; Point to command length byte in command buffer
ld hl,tbuff ; Point to sector read in from submit file
if buflen gt 7fh ; If command line buffer is longer than record,
ld bc,80h ; ..then copy entire record from $$$.SUB file
else ;buflen le 7fh ; Otherwise copy only enough to fill
ld bc,buflen+1 ; ..the command line buffer
endif ;buflen gt 7fh
ldir ; Transfer line from submit file to buffer
; We now deal with various options that control the display of commands fed
; to the command processor from a submit file.
if subnoise gt 0 ; If subnoise = 0 we omit all this display code
if subnoise eq 1 ; If subnoise = 1 we follow the quiet flag
ld a,(quietfl)
or a
jr nz,readbuf0 ; If quiet, skip echoing the command
endif ;subnoise eq 1
call prompt ; Print prompt
call print ; Print submit prompt trailer
defb sprmpt or 80h
ld hl,cmdlin ; Print command line
call printhl
endif ;subnoise gt 0
readbuf0:
call break ; Check for abort (any char)
ret nz ; If no ^C, return to caller and run
readbuf1:
call subkil ; Kill submit file and abort
jp restart ; Restart CPR
endif ; subon
shellinput:
ld hl,shstk ; Point to shell stack
ld a,(hl) ; Check first byte
cp ' '+1 ; See if any entry
jr c,userinput ; Get user input if none
ld de,cmdlin ; Point to first character of command line
ld bc,shsize ; Copy shell line into command line buffer
ldir ; Do copy
ex de,hl ; HL points to end of line
ld a,1 ; Set command status flag to show
ld (cmdstatfl),a ; ..that a shell has been invoked
jr readbuf3 ; Store ending zero and exit
userinput:
call prompt ; Print prompt
call print ; Print prompt trailer
defb cprmpt or 80h
ld c,0ah ; Read command line from user
ld de,bufsiz ; Point to buffer size byte of command line
call bdos
; Store null at end of line
ld hl,chrcnt ; Point to character count
ld a,(hl) ; ..and get its value
inc hl ; Point to first character of command line
call addah ; Make pointer to byte past end of command line
readbuf3:
ld (hl),0 ; Store ending zero
ret
;-----------------------------------------------------------------------------
; Check for any character from the user console. Return with the character
; in A. If the character is a control-C, then the zero flag will be set.
if subon or diron or eraon or lton
break:
ld c,0bh ; BDOS console status function
call bdossave ; Call BDOS and set flags
call nz,conin ; Get input character if there is one
cp 'C'-'@' ; Check for abort
ret
endif ; subon or diron or eraon or lton
;-----------------------------------------------------------------------------
; Add A to HL (HL=HL+A)
addah:
add a,l
ld l,a
ret nc
inc h
ret
;-----------------------------------------------------------------------------
; The routine NUMBER evaluates a string in the first FCB as either a decimal
; or, if terminated with the NUMBASE hexadecimal marker, a HEX number. If the
; conversion is successful, the value is returned as a 16-bit quantity in BC.
; If an invalid character is encountered in the string, the routine returns
; with the carry flag set and HL pointing to the offending character.
if saveon
number:
ld hl,tfcb+8 ; Set pointer to end of number string
ld bc,8 ; Number of characters to scan
ld a,numbase ; Scan for HEX identifier
cpdr ; Do the search
jr nz,decimal ; Branch if HEX identifier not found
inc hl ; Point to HEX marker
ld (hl),' ' ; Replace HEX marker with valid terminator
; ..and fall through to HEXNUM
endif ;saveon
;----------------------------------------
; At this entry point the character string in the first default FCB is
; converted as a hexadecimal number (there must NOT be a HEX marker).
hexnum:
ld hl,tfcb+1 ; Point to string in first FCB
; At this entry point the character string pointed to by HL is converted
; as a hexadecimal number (there must be NO HEX marker at the end).
hexnum1:
ld de,16 ; HEX radix base
jr radbin ; Invoke the generalized conversion routine
;----------------------------------------
; This entry point performs decimal conversion of the string in the first
; default FCB.
decimal:
ld hl,tfcb+1 ; Set pointer to number string
; This entry point performs decimal conversion of the string pointed to
; by HL.
decimal1:
ld de,10 ; Decimal radix base
; Fall through to generalized
; ..radix conversion routine
; This routine converts the string pointed to by HL using the radix passed in
; DE. If the conversion is successful, the value is returned in BC. HL points
; to the character that terminated the number, and A contains that character.
; If an invalid character is encountered, the routine returns with the carry
; flag set, and HL points to the offending character.
radbin:
ld bc,0 ; Initialize result
radbin1:
or a ; Make sure carry is reset
call sdelm ; Test for delimiter (returns Z if delimiter)
ret z ; Return if delimiter encountered
sub '0' ; See if less than '0'
ret c ; Return with carry set if so
cp 10 ; See if in range '0'..'9'
jr c,radbin2 ; Branch if it is valid
cp 'A'-'0' ; Bad character if < 'A'
ret c ; ..so we return with carry set
sub 7 ; Convert to range 10..15
radbin2:
cp e ; Compare to radix in E
ccf ; Carry should be set; this will clear it
ret c ; If carry now set, we have an error
inc hl ; Point to next character
push bc ; Push the result we are forming onto the stack
ex (sp),hl ; Now HL=result, (sp)=source pointer
call mpy16 ; HLBC = previous$result * radix
ld h,0 ; Discard high 16 bits and
ld l,a ; ..move current digit into HL
add hl,bc ; Form new result
ld c,l ; Move it into BC
ld b,h
pop hl ; Get string pointer back
jr radbin1 ; Loop until delimiter
;-----------------------------------------------------------------------------
; This routine multiplies the 16-bit values in DE and HL and returns the
; 32-bit result in HLBC (HL has high 16 bits; BC has low 16 bits). Register
; pair AF is preserved.
mpy16:
ex af,af' ; Save AF
ld a,h ; Transfer factor in HL to A and C
ld c,l
ld hl,0 ; Initialize product
ld b,16 ; Set bit counter
rra ; Shift AC right so first multiplier bit
rr c ; ..is in carry flag
mp161:
jr nc,mp162 ; If carry not set, skip the addition
add hl,de ; Add multiplicand
mp162:
rr h ; Rotate HL right, low bit into carry
rr l
rra ; Continue rotating through AC, with
rr c ; ..next multiplier bit moving into carry
djnz mp161 ; Loop through 16 bits
ld b,a ; Move A to B so result is in HLBC
ex af,af' ; Restore original AF registers
ret
;-----------------------------------------------------------------------------
; This routine checks for a delimiter character pointed to by HL. It returns
; with the character in A and the zero flag set if it is a delimiter. All
; registers are preserved except A.
sdelm:
ld a,(hl) ; Get the character
exx ; Use alternate register set (shorter code)
ld hl,deldat ; Point to delimiter list
ld bc,delend-deldat; Length of delimiter list
cpir ; Scan for match
exx ; Restore registers
ret ; Returns Z if delimiter
deldat: ; List of delimiter characters
db ' '
db '='
db '_'
db '.'
db ':'
db ';'
db '<'
db '>'
db ','
db 0
if cmdsep ne ';'
db cmdsep
endif ;cmdsep ne ';'
delend:
;-----------------------------------------------------------------------------
; Log into DU contained in FCB pointed to by DE. Registers DE are preserved;
; all others are changed. Explicit values for the temporary drive and user
; are extracted from the FCB. If the record-count byte has an FF in it, that
; is a signal that the directory specification was invalid. We then invoke
; the error handler.
if diron or eraon or lton or renon or saveon
fcblog:
push de ; Save pointer to FCB
ex de,hl
ld a,(hl) ; Get drive
ld bc,13 ; Offset to S1 field
add hl,bc
ld c,(hl) ; Get user into C
or a ; See if drive value was 0
jr nz,fcblog1 ; If not, branch ahead
ld a,(curdr) ; Otherwise substitute current drive
inc a ; ..shifted to range 1..16
fcblog1:
ld b,a ; Get drive into B
ld (tempusr),bc ; Set up temporary DU values
call logtemp ; ..and log into it
pop de ; Restore pointer to FCB
; Now check to make sure that the directory specification was valid.
inc hl ; Advance pointer to record-count byte
inc hl
ld a,(hl) ; See if it is nonzero
or a
jp nz,baddirerr ; If so, invoke error handler
ret ; Otherwise return
endif ;diron or eraon or lton or renon or saveon
;-----------------------------------------------------------------------------
; Log into the temporary directory. Registers B, H, and L are preserved.
logtemp:
ld de,(tempusr) ; Set D = tempdr, E = tempusr
call setuser1 ; Register D is preserved during this call
ld a,d ; Move drive into A
dec a ; Adjust for drive range 0..15
jp setdrive ; Log in new drive and return
;-----------------------------------------------------------------------------
; This routine scans the command table pointed to by HL for the command name
; stored in the command FCB. If the command is not found, the routine returns
; with the zero flag reset. If the command is found, the address vector is
; stored in EXECADR and the zero flag is set.
cmdscan:
ld b,(hl) ; Get length of each command
inc hl ; Point to first command name
scannext:
ld a,(hl) ; Check for end of table
or a
jr z,scanend ; Branch if end
ld de,cmdfcb+1 ; Point to name of requested command
push bc ; Save size of commands in table
if wheel
; Ignore commands with high bit set in first
; ..char of command name if wheel is false
ld a,(z3whl) ; Get the wheel byte
or a
ld c,0ffh ; Make a mask that passes all characters
jr z,scancmp ; Use this mask if wheel not set
endif ; wheel
ld c,7fh ; Use mask to block high bit if wheel set
; ..or not in use
scancmp:
ld a,(de) ; Compare against table entry
xor (hl)
and c ; Mask high bit of comparison
jr nz,scanskip ; No match, so skip rest of command name
inc de ; Advance to next characters to compare
inc hl
res 7,c ; Mask out high bit on characters after first
djnz scancmp ; Count down
ld a,(de) ; See if next character in input command
cp ' ' ; ..is a space
jr nz,scanbad ; If not, user command is longer than commands
; ..in the command table
; Matching command found
pop bc ; Clear stack
ld a,(hl) ; Get address from table into HL
inc hl
ld h,(hl)
ld l,a
ld (execadr),hl ; Set execution address
xor a ; Set zero flag to show that command found
ret
scanskip:
inc hl ; Skip to next command table entry
djnz scanskip
scanbad:
pop bc ; Get back size of each command
inc hl ; Skip over address vector
inc hl
jr scannext ; Try scanning next entry in table
scanend:
xor a ; Reset zero flag to show
dec a ; ..that command was not found
ret
; End ZCPR33-4.Z80
page
; ZCPR33-5.Z80
;=============================================================================
;
; R E S I D E N T C O M M A N D C O D E
;
;=============================================================================
; Command: DIR
; Function: To display a directory of the files on disk
; Forms:
; DIR <afn> Displays the DIR-attribute files
; DIR Same as DIR *.*
; DIR <afn> S Displays the SYS-attribute files
; DIR /S Same as DIR *.* S
; DIR <afn> A Display both DIR and SYS files
; DIR /A Same as DIR *.* A
if diron
dir:
ld de,tfcb ; Point to target FCB
push de ; ..and save the pointer for later
inc de ; Point to file name
ld a,(de) ; Get first character
if slashfl ; If allowing "DIR /S" and "DIR /A" formats
cp '/' ; If name does not start with '/'
jr nz,dir1 ; ..branch and process normally
inc de ; Point to second character
ld a,(de) ; Get option character after slash
ld (tfcb2+1),a ; ..and put it into second FCB
dec de ; Back to first character
ld a,' ' ; Simulate empty FCB
endif ;slashfl
dir1:
cp ' ' ; If space, make all wild
jr nz,dir2
ld b,11
ld a,'?'
call fill
dir2:
pop de ; Restore pointer to FCB
call fcblog ; Log in the specified directory
if whldir
ld a,(z3whl) ; Check wheel status
or a ; If not set, then ignore options
jr z,dir2a
endif ;whldir
ld a,(tfcb2+1) ; Check for any option letter
ld b,1 ; Flag for both DIR and SYS files
cp allchar ; See if all (SYS and DIR) option letter
jr z,dirpr ; Branch if so
dec b ; B = 0 for SYS files only
cp syschar ; See if SYS-only option letter
jr z,dirpr ; Branch if so
dir2a:
ld b,80h ; Flag for DIR-only selection
; Drop into DIRPR to print directory
endif ; diron
;--------------------
; Directory display routine
; On entry, if attribute checking is required, the B register is
; set as follows:
; 00H for SYS files only
; 80H for DIR files only
; 01H for both
if diron or eraon
dirpr:
if diron ; Attribute checking needed only for DIR
ld a,b ; Get flag
ld (systst),a ; Set system test flag
endif
ld e,0 ; Set column counter to zero
push de ; Save column counter (E)
call srchfst1 ; Search for specified file (first occurrence)
jr nz,dir3
call prnnf ; Print no-file message
pop de ; Restore DE
xor a ; Set Z to show no files found
ret
; Entry selection loop. On entering this code, A contains the offset in the
; directory block as returned by the search-first or search-next call.
dir3:
if diron ; Attribute checking needed only for DIR cmd
call getsbit ; Get and test for type of files
jr z,dir6
else ;not diron
dec a ; Adjust returned value from 1..4 to 0..3
rrca ; Multiply by 32 to convert number to
rrca ; ..offset into TBUFF
rrca
ld c,a ; C = offset to entry in TBUFF
endif ;diron
pop de ; Restore count of
ld a,e ; ..entries displayed
inc e ; Increment entry counter
push de ; Save it
and 03h ; Output CRLF if 4 entries printed in line
jr nz,dir4
call crlf ; New line
jr dir5
dir4:
call print
if wide
defb ' ' ; 2 spaces
defb fence ; Then fence char
defb ' ',' '+80h ; Then 2 more spaces
else ;not wide
defb ' ' ; Space
defb fence ; Then fence char
defb ' '+80h ; Then space
endif ; wide
dir5:
ld a,1
call dirptr ; HL now points to 1st byte of file name
call prfn ; Print file name
dir6:
call break ; Check for abort
jr z,dir7
call srchnxt ; Search for next file
jr nz,dir3 ; Continue if file found
dir7:
pop de ; Restore stack
dec a ; Set NZ flag
ret
endif ; diron or eraon
;-----------------------------------------------------------------------------
if diron or attchk or eraon
; This routine returns a pointer in HL to the directory entry in TBUFF that
; corresponds to the offset specified in registers C (file offset) and C
; (byte offset within entry).
dirptr:
ld hl,tbuff
add a,c ; Add the two offset contributions
call addah ; Set pointer to desired byte
ld a,(hl) ; Get the desired byte
ret
endif ; diron or attchk or eraon
;-----------------------------------------------------------------------------
; Test File in FCB for existence, ask user to delete if so, and abort if he
; choses not to
if saveon or renon
extest:
ld de,tfcb ; Point to FCB
push de ; ..and save it for later
call fcblog ; Log into specified directory
call srchfst1 ; Look for specified file
pop de ; Restore pointer
ret z ; OK if not found, so return
call printc
if bellfl
defb bell
endif ;bellfl
defb 'Erase',' '+80h
ld hl,tfcb+1 ; Point to file name field
call prfn ; Print it
call print ; Add question mark
defb '?' or 80h
call conin ; Get user response
cp 'Y' ; Test for permission to erase file
jp nz,restart ; If not, flush the entire command line
jp delete ; Delete the file
endif ; saveon or renon
;-----------------------------------------------------------------------------
; Print file name pointed to by HL
if diron or renon or saveon
prfn:
ld b,8 ; Display 8 characters in name
call prfn1
call print ; Put in dot
defb '.' or 80h
ld b,3 ; Display 3 characters in type
prfn1:
ld a,(hl) ; Get character
inc hl ; Point to next
call conout ; Print character
djnz prfn1 ; Loop through them all
ret
endif ;diron or renon or saveon
;-----------------------------------------------------------------------------
; This routine returns NZ if the file has the required attributes and Z if
; it does not. It works by performing the 'exclusive or' of the mask passed
; in register A and the filename attribute obtained by masking out all but
; the highest bit of the character. For the 'both' case, setting any bit
; in the mask other than bit 7 will guarantee a nonzero result.
;
; File name: : X 0 0 0 0 0 0 0 (After 80H mask, X=1 if SYS, 0 if DIR)
;
; SYS-ONLY : 0 0 0 0 0 0 0 0 (XOR gives 00H if X=0 and 80H if X=1)
; DIR-ONLY : 1 0 0 0 0 0 0 0 (XOR gives 80H if X=0 and 00H if X=1)
; BOTH : 0 0 0 0 0 0 0 1 (XOR gives 01H if X=0 and 81H if X=1)
if diron or attchk
getsbit:
dec a ; Adjust to returned value from 1..4 to 0..3
rrca ; Multiply by 32 to convert number to
rrca ; ..offset into TBUFF
rrca
ld c,a ; Save offset in TBUFF in C
ld a,10 ; Add 10 to point to SYS attribute bit
call dirptr ; A = SYS byte
and 80h ; Look only at attribute bit
systst equ $+1 ; In-the-code variable
xor 0 ; If SYSTST=0, SYS only; if SYSTST=80H, DIR
; ..only; if SYSTST=1, both SYS and DIR
ret ; NZ if OK, Z if not OK
endif ;diron or attchk
;-----------------------------------------------------------------------------
; Command: REN
; Function: To change the name of an existing file
; Forms: REN <New UFN>=<Old UFN>
; Notes: If either file spec is ambiguous, or if the source file does
; not exist, the error handler will be entered. If a file with
; the new name already exists, the user is prompted for deletion
; and ZEX is turned off during the prompt.
if renon
ren:
ld hl,tfcb ; Check for ambiguity in first file name
call ambchk
call fcblog ; Login to fcb
ld hl,tfcb2 ; Check for ambiguity in second file name
call ambchk
xor a ; Use current drive for 2nd file
ld (de),a
call srchfst ; Check for old file's existence
jr nz,ren0a ; Branch if file exists
jpnofile:
ld a,ecnofile ; Set error code for file not found
jp error ; ..and invoke error handler
ren0a:
call extest ; Test for file existence and return if not
ld b,12 ; Exchange new and old file names
push de ; Save pointer to FCB
ld hl,tfcb2 ; Point to FCB for old file name
ren0:
ld a,(de) ; Get character of old name
ld c,a ; ..into C register
ld a,(hl) ; Get character of new name
ld (de),a ; ..into place in old name
ld (hl),c ; Put character of old name into new name
inc hl ; Advance pointers
inc de
djnz ren0
; Perform rename function
pop de ; Restore pointer to FCB
ld c,17h ; BDOS rename function
jp bdostest
endif ;renon
;-----------------------------------------------------------------------------
; Command: ERA
; Function: Erase files
; Forms:
; ERA <afn> Erase specified files and dislay their names
; ERA <afn> I Display names of files to be erased and prompt for
; inspection before erase is performed. (Character 'I'
; is defined by INSPCH in Z33HDR.LIB; if it is ' ', then
; any character triggers inspection.)
if eraon
era:
if inspfl and eraok; 'I' flag and verification enabled?
ld a,(tfcb2+1) ; Get flag, if any, entered by user
ld (eraflg),a ; Save it in code below
endif ;erav and eraok
ld de,tfcb ; Point to target FCB
call fcblog ; ..and log into the specified directory
if diron or attchk ; Attribute checking only in these cases
ld b,1 ; Display all matching files
endif ;diron or attchk
call dirpr ; Print directory of erased files
ret z ; Abort if no files
if eraok ; Print prompt
if inspfl ; Test verify flag
eraflg equ $+1 ; Address of flag (in-the-code modification)
ld a,0
cp inspch ; Is it an inspect option?
if inspch ne ' ' ; If an explicit inspect character is specified
jr nz,era2 ; ..skip prompt if it is not that character
else ; If INSPCH is the space character
jr z,era2 ; ..then skip prompt only if FCB has a space
endif ;inspch ne ' '
endif ;inspfl
call printc
defb 'OK to Erase','?'+80h
call conin ; Get reply
cp 'Y' ; Yes?
ret nz ; Abort if not
endif ; eraok
era2:
ld de,tfcb
jp delete ; Delete files and return
endif ; Eraon
;-----------------------------------------------------------------------------
; Command: LIST
; Function: Print out specified file on the LST: device
; Forms: LIST <ufn> Print file (No Paging)
; Notes: The flags which apply to TYPE do not take effect with LIST
if lton
list:
ld a,0ffh ; Turn on printer flag
jr type0
;-----------------------------------------------------------------------------
; Command: TYPE
; Function: Print out specified file on the CON: Device
; Forms: TYPE <ufn> Print file with default paging option
; TYPE <ufn> P Print file with paging option reversed
type:
xor a ; Turn off printer flag
; Common entry point for LIST and TYPE functions
type0:
ld (prflg),a ; Set printer/console flag
ld a,(tfcb2+1) ; Check for user page toggle ('P') option
ld (pgflg),a ; Save it as a flag in code below
ld hl,tfcb ; Point to target file FCB
call ambchk ; Check for ambiguous file spec (vectors to
; ..error handler if so)
call fcblog ; Log into specified directory
call open ; Open the file
if renon ; If REN on, share code
jr z,jpnofile
else ;not renon ; Otherwise repeat code here
ld a,ecnofile
jp z,error
endif ;renon
call crlf ; New line
ld a,(crttxt0) ; Set line count using value from the
; ..environment for CRT0
inc a ; One extra the first time through
ld (pagcnt),a
ld bc,080h ; Set character position and tab count
; (B = 0 = tab, C = 080h = char position)
; Main loop for loading next block
type2:
ld a,c ; Get character count
cp 80h ; If not end of disk record
jr c,type3 ; ..then skip
call readf ; Read next record of file
ret nz ; Quit if end of file
ld c,0 ; Reset character count
ld hl,tbuff ; Point to first character
; Main loop for printing characters in TBUFF
type3:
ld a,(hl) ; Get next character
and 7fh ; Mask out MSB
cp 1ah ; Check for end of file (^z)
ret z ; Quit if so
; Output character to CON: or LST: device with tabulation
cp cr ; If carriage return,
jr z,type4 ; ..branch to reset tab count
cp lf ; If line feed, then output
jr z,type4a ; ..with no change in tab count
cp tab ; If tab
jr z,type5 ; ..expand to spaces
; Output character and increment character count
call lcout ; Output character
inc b ; Increment tab count
jr type6
; Output CR and reset tab count
type4:
ld b,0 ; Reset tab counter
; Output LF and leave tab count as is
type4a:
call lcout ; Output <cr> or <lf>
jr type6
; Process tab character
type5:
ld a,' ' ; Space
call lcout
inc b ; Increment tab count
ld a,b
and 7
jr nz,type5 ; Loop until column = n * 8 + 7
; Continue processing
type6:
inc c ; Increment character count
inc hl ; Point to next character
push bc
call break ; Check for user abort
pop bc
ret z ; Quit if so
jr type2 ; Else back for more
;--------------------
; Output character in A to console or list device depending on a flag.
; Registers are preserved. This code is used only by the LIST and TYPE
; commands.
lcout:
push af ; Save character
prflg equ $+1 ; Pointer for in-the-code modification
ld a,0 ; ..to determine destination (CON or LST)
or a ; Z=type, NZ=list
jr z,lc1
; Output to list device
pop af ; Get character back
push de
push bc
ld c,5 ; LISTOUT function
jp output
; Output to console with paging
lc1:
pop af ; Get character back
push af ; Save it again for page check
call conout ; Output to console
pop af ; Get character back again
cp lf ; Check for new line (paging)
ret nz ; If not new line, we are done
; Paging routines
pager:
push hl
ld hl,pagcnt ; Decrement lines remaining on screen
dec (hl)
jr nz,pager1 ; Jump if not end of page
; New page
ld a,(crttxt0) ; Get full page count from environment
ld (hl),a ; Reset count to a full page
pgflg equ $+1 ; Pointer to in-the-code buffer pgflg
ld a,0
cp pagech ; Page default override option wanted?
if pagech ne ' ' ; If using explicit character for page toggle
if pagefl ; If paging is default
jr z,pager1 ; ..PAGECH means no paging
else ; If paging not default
jr nz,pager1 ; ..PAGECH means please paginate
endif ;pagefl
else ; Any character toggles paging
if pagefl ; If paging is default
jr nz,pager1 ; ..any character means no paging
else ; If paging not default
jr z,pager1 ; ..any character means please paginate
endif ;pagefl
endif ;pagech ne ' '
; End of page
push bc
call bios+9 ; Wait for user input (BIOS console input)
pop bc
cp 'C'-'@' ; Did user enter control-c?
jp z,nextcmd ; If so, terminate this command
pager1:
pop hl ; Restore HL
ret
endif ; lton
;-----------------------------------------------------------------------------
; Command: SAVE
; Function: To save the contents of the TPA onto disk as a file
; Forms:
; SAVE <Number of Pages> <ufn>
; Save specified number of pages (starting at 100H) from TPA
; into specified file
;
; SAVE <Number of Sectors> <ufn> <S>
; Like SAVE above, but numeric argument specifies
; number of sectors rather than pages
if saveon
; Entry point for SAVE command
save:
call number ; Extract number from command line
jr c,badnumber ; Invoke error handler if bad number
push bc ; Save the number
call reparse ; Reparse tail after number of sectors/pages
pop hl ; Get sector/page count back into HL
ld a,(tfcb2+1) ; Check sector flag in second FCB
cp sectch
if sectch ne ' ' ; If using a specific character, then jump
jr z,save0 ; ..if it is that character
else ; If allowing any character (SECTCH=' ')
jr nz,save0 ; ..jump if it is anything other than space
endif ;sectch ne ' '
add hl,hl ; Double page count to get sector count
save0:
ld a,1 ; Maximum allowed value in H
cp h ; Make sure sector count < 512 (64K)
jr c,badnumber ; If >511, invoke error handler
push hl ; Save sector count
ld hl,tfcb
call ambchk ; Check for ambiguous file spec (vectors to
; ..error handler if so)
call extest ; Test for existence of file and abort if so
ld c,16h ; BDOS make file function
call bdostest
jr z,save3 ; Branch if error in creating file
pop bc ; Get sector count into BC
ld hl,tpa-80h ; Set pointer to one record before TPA
save1:
ld a,b ; Check for BC = 0
or c
dec bc ; Count down on sectors (flags unchanged,
; ..B=0FFH if all records written successfully)
jr z,save2 ; If BC=0, save is done so branch
push bc ; Save sector count
ld de,80h ; Advance address by one record
add hl,de
push hl ; Save address on stack
ex de,hl ; Put address into DE for BDOS call
call dmaset ; Set DMA address for write
ld de,tfcb ; Write sector
ld c,15h ; BDOS write sector function
call bdossave
pop hl ; Get address back into HL
pop bc ; Get sector count back into BC
jr z,save1 ; If write successful, go back for more
ld b,0 ; B=0 if write failed
save2:
call close ; Close file even if last write failed
and b ; Combine close return code with
; ..write success flag
ret nz ; Return if all ok
save3: ; Disk must be full
ld a,ecdiskfull ; Disk full error code
jr jperror
endif ; saveon
;-----------------------------------------------------------------------------
if lton or saveon or renon or geton
; Check file control block pointed to by HL for any wildcard characters ('?').
; Return to calling program if none found. Otherwise branch to error handler.
; The routine also treats an empty file name as ambiguous.
ambchk:
push hl ; Save pointer to FCB
inc hl ; Point to first character in file name
ld a,(hl) ; See if first character is a space
cp ' '
jr z,ambchk1 ; If so, branch to error return
ld a,'?' ; Set up for scan for question mark
ld bc,11 ; Scan 11 characters
cpir
pop de ; Restore pointer to FCB in DE
ret nz ; Return if no '?' found
ambchk1:
ld a,ecambig ; Error code for ambiguous file name
jr jperror
endif ;lton or renon or saveon or geton
if lton or renon or saveon or geton or jumpon
badnumber:
ld a,ecbadnum ; Error code for bad number value
jperror: ; Local entry point for relative jump
jp error ; ..to go to error handler
endif ;lton or renon or saveon or geton or jumpon
;-----------------------------------------------------------------------------
; Command: JUMP
; Function: To execute a program already loaded into some specified memory
; address
; Forms: JUMP <adr> <tail>
; The address is in hex; the tail will be parsed as usual
if jumpon
jump:
call hexnum ; Get load address into BC
jr c,badnumber ; If bad number, invoke error handling
push bc ; ..and save it
call reparse ; Reparse tail after address value
pop hl ; Restore execution address to HL
jr getproglf ; Perform call via code below
endif ;jumpon
;-----------------------------------------------------------------------------
; Command: GO
; Function: To Call the program in the TPA without loading
; loading from disk. Same as JUMP 100H, but much
; more convenient, especially when used with
; parameters for programs like STAT. Also can be
; allowed on remote-access systems with no problems.
;
;Form: GO <tail>
if goon
go:
ld hl,tpa ; Set up TPA as the execution address
endif ; goon
if jumpon or goon ; Common code
getproglf:
ld (execadr),hl
xor a ; Set zero flag to enable leading CRLF
jp callproglf ; Perform call (with leading CRLF)
endif ;jumpon or goon
;-----------------------------------------------------------------------------
; Command: GET
; Function: To load the specified file from disk to the specified address
; Forms: GET <adr> <ufn>
; Loads the specified file to the specified hexadecimal address
; Note that the normal file search path is used to find the file.
; If SCANCUR is off, the file may not be found in the current
; directory unless a colon is included in the file spec.
if geton
get:
; TMPCOLON was set when the file name was parsed. We use that as the colon
; flag so that the file will be loaded from a directory just as if it had
; been entered as the command name.
if drvprefix and [not scancur]
ld a,(tmpcolon) ; Allow GET to load from specified
ld (colon),a ; directory
endif ;drvprefix and [not scancur]
ld hl,tfcb2 ; Copy TFCB2 to CMDFCB for load
push hl
ld de,cmdfcb
ld bc,14
ldir
pop hl
call ambchk ; Make sure file is not ambiguous (vectors
; ..to error handler if so)
; If GET fails to find the specified file along the search path, we do not
; want the ECP to be engaged. To prevent that, we fool the command processor
; by telling it that the ECP is already engaged.
ld hl,cmdstatfl ; Point to command status flag
set 2,(hl) ; Turn on ECP flag to prevent use of ECP
call hexnum ; Get load address into BC
jr c,badnumber ; If invalid number, invoke error handler
if not fullget
ld a,b ; If trying to load into base page
or a ; ..treat as error
jr z,badnumber
endif ;not fullget
ld h,b ; Move address into HL
ld l,c
ld a,0ffh ; Disable dynamic loading
; Fall through to mload
endif ; geton
; End ZCPR33-5.Z80
page
; ZCPR33-6.Z80
;=============================================================================
;
; P A T H S E A R C H A N D F I L E L O A D I N G C O D E
;
;=============================================================================
; This block of code loads a file into memory. The normal address at which
; loading is to begin is passed to the routine in the HL register. The name
; of the file to load is passed in the command file control block.
;
; This code supports an advanced option that loads files to a dynamic address
; specified in the header to the file using a new type-3 environment. In a
; type-3 environment, the execution/load address is stored in the word
; following the environment descriptor address. A value is passed to MLOAD in
; the A register that controls this dynamic loading mechanism. The value
; specifies the lowest environment type value for which dynamic loading will
; be performed. This value will be 3 when MLOAD is called for normal COM file
; execution and will be 0FFH when chained to from the GET command. In the
; latter case, the user-specified load address must be used.
;
; MLOAD guards against loading a file over the operating system. It computes
; the lower of the following two addresses: 1) the CPR entry point; 2) the
; bottom of protected memory as indicated by the DOS entry address stored at
; address 0006H. If the load would exceed this limit, error handling is
; engaged (except for the GET command when FULLGET is enabled).
mload:
ld (envtype),a ; Set up in-the-code modification below
ld (execadr),hl ; Set up execution/load address
call defltdma ; Set DMA address to 80H for file searches
; This code sets the attributes of COM files which are acceptable. If both
; SYS and DIR type files are acceptable, there is no need to include this code,
; and ATTCHK can be set to false.
if attchk ; Only if attribute checking enabled
ld a,comatt ; Attributes specified in Z33HDR.LIB
ld (systst),a ; Set flag
endif ;attchk
;-----------------------------------------------------------------------------
; PATH BUILDING CODE
; In ZCPR33 the minpath feature, optional in ZCPR30, is always used. To
; minimize the size of the CPR code, however, there is an option to place the
; minpath in an external buffer (outside the CPR). If the path is short
; enough, the minpath can be placed at the bottom of the system stack.
ld de,path ; Point to first element in user's symbolic path
ld hl,mpath ; Point to minpath buffer
xor a
ld (hl),a ; Initialize to empty minpath
; If DRVPREFIX is enabled, the CPR will recognize an explicit directory
; reference in a command. The first element of the path will then be this
; explicit directory. If no explicit directory was given in the command,
; then no entry is made into the search path. If the WPREFIX option is
; on, explicit directory prefixes will be recognized only when the wheel
; byte is on.
if drvprefix ; Pay attention to du:com prefix?
ld a,(colon) ; See if colon was present in command
or a
jr z,makepath2 ; If not, skip ahead
if wprefix
ld a,(z3whl) ; See if wheel byte is on
or a
jr z,makepath2 ; If not, skip ahead
endif ;wprefix
ld a,(cmdfcb) ; Get drive from command FCB
ld (hl),a ; Put drive into minpath
inc hl ; Advance pointer
ld a,(cmdfcb+13) ; Get user number from command FCB
ld (hl),a ; Put it into minpath
inc hl ; Advance pointer to next path element
xor a ; A=0
ld (hl),a ; Store ending 0 in mpath
makepath2:
endif ; drvprefix
; If SCANCUR is enabled in Z33HDR.LIB, then we always include the current
; directory automatically, even without a '$$' element in the user's path.
; If WPREFIX is enabled, however, we do not want to allow the current
; directory to be included, but we must make sure that it is included in
; the building of the root path, in case the user's symbolic path is empty.
if scancur ; Scan current directory at all times?
ld bc,(curusr) ; C = current user, B = current drive
inc b ; Set drive to range 1..16
if wprefix
ld a,(z3whl) ; See if wheel byte is on
or a
jr nz,addpath ; If it is, add element to path; if not,
; ..fall through to MAKEPATH3
else ;not wprefix
jr addpath ; Begin loop of placing entries into mpath
endif ;wprefix
else ;not scancur
; If SCANCUR is off and ROOTONLY is in effect, we have to make sure that some
; directory values are put into the root path in the case where the user's
; path is completely empty. To do so, we preset BC for directory A0.
if rootonly
ld bc,0100h ; Setup for drive A (B=1), user 0 (C=0)
endif ;rootonly
endif ;scancur
; Convert symbolic entries in user's path into absolute DU values in minpath.
; Entries are read one-by-one from the symbolic path. If the 'current' drive
; or user indicator is present (default symbol is '$'), then the current
; drive or user value is fetched. Otherwise the explicit binary value from the
; path is used. After each absolute DU value is formed, the minpath as it
; exists so far is scanned to see if this DU value is already there. If it is
; not, then the DU value is appended to the path. Otherwise it is ignored.
makepath3:
ld a,(de) ; Get next symbolic path entry
or a ; If 0, we are at end of path
jr z,makepath6
ld bc,(curusr) ; C = current user, B = current drive
inc b ; Set drive to range 1..16
cp curind ; Check for current drive symbol (default '$')
jr z,makepath4 ; If so, leave current drive in B
ld b,a ; Else move specified drive into B
makepath4:
inc de ; Point to user value in symbolic path
ld a,(de) ; Get user
inc de ; Point to next element in symbolic path
cp curind ; Check for current user symbol (default '$')
jr z,makepath5 ; If so, leave current drive in C
ld c,a ; Else move specified user into C
makepath5:
; At this point in the code we have a potential path element in BC. We first
; have to scan the minpath we have so far to see if that element is already
; there. In that case we ignore it; otherwise we add it to the end of the path.
addpath:
; Skip path if directory given explicitly
if skippath
if wprefix
ld a,(z3whl) ; See if wheel byte is on
or a
call nz,skipchk ; If not, fall through
else ;not wprefix
call skipchk ; See if path should be skipped
endif ;wprefix
jr nz,makepath3 ; If so, branch out of ADDPATH
endif ;skippath
ld hl,mpath ; Point to beginning of minpath
addpath1: ; Point of reentry
ld a,(hl) ; Get drive value
or a ; Check for end of minpath
jr z,addpath2 ; If end, jump and add BC to minpath
inc hl ; Increment pointer to user
cp b ; Check for drive match
ld a,(hl) ; Get user from minpath
inc hl ; Point to next minpath entry
jr nz,addpath1 ; If drive was different, loop back again
cp c ; Check for user match
jr nz,addpath1 ; If user is different, loop back again
jr makepath3 ; Branch if we have a duplicate
; We have a new DU; add it to minpath
addpath2:
ld (hl),b ; Store drive
inc hl
ld (hl),c ; Store user
inc hl
ld (hl),0 ; Store ending 0
jr makepath3 ; Continue scanning user's path
; If the ECP facility is set up to use the root directory, then create a
; root path. BC presently contains the proper DU.
makepath6:
if rootonly
ld hl,rootpth ; Point to special path to contain root
ld (hl),b ; Store disk
inc hl
ld (hl),c ; Store user
endif ;rootonly
;-----------------------------------------------------------------------------
; This is the code for loading the specified file by searching the minpath.
xor a ; Always use current disk specification in the
ld (cmdfcb),a ; ..command FCB
mload1:
ld hl,mpath ; Point to beginning of minpath
mload2:
; Either the FASTECP or BADDUECP option may have set FIRSTCHAR to a space
; character as a signal to go directly to extended command processing. If
; neither option is enabled but SKIPPATH is, then the FIRSTCHAR data is
; stored in the routine below where path skipping is implemented.
if fastecp or badduecp
ld a,(cmdstatfl) ; If ECP is running
bit 2,a ; ..we branch to look for ECP along path
jr nz,mload2a
firstchar equ $+1 ; Pointer for in-the-code modification
ld a,0
cp ' ' ; Was command invoked with leading space?
jr z,ecprun ; If so, go directly to ECP code
endif ;fastecp or badduecp
mload2a:
ld a,(hl) ; Get drive from path
or a ; If end of path, command not found
jr nz,mload3 ; If not end of path, skip over ECP code
;-----------------------------------------------------------------------------
; EXTENDED COMMAND PROCESSING
; At this point we have exhausted the search path. We now engage the
; extended command processor.
ecprun:
if skippath
call skipchk ; See if path should be skipped
jr nz,jnzerror ; If so, invoke error handler
endif ;skippath
ld hl,cmdstatfl ; Point to command status flag
ld a,(hl) ; ..and get value
and 110b ; Isolate ECP and error handler bits
jnzerror: ; If either is set,
ld a,ecnocmd ; Error code for command not found
jp nz,error ; ..process as an error
set 2,(hl) ; Set ECP bit
ld hl,ecpfcb ; Copy name of ECP to command FCB
ld de,cmdfcb
ld bc,12 ; Only 12 bytes required
ldir
ld hl,(cmdptr) ; Get pointer to current command line
call parsetail ; Parse entire command as the command tail
if rootonly ; Look for ECP in root directory only
ld hl,rootpth ; Point to path containing root directory only
jr mload2 ; Search for command
else ; not rootonly
jr mload1 ; Search the entire minpath for the ECP
endif ; rootonly
;-----------------------------------------------------------------------------
mload3:
ld b,a ; Drive into B
inc hl ; Point to user number
ld c,(hl) ; User into C
ld (tempusr),bc ; Save the values
inc hl ; Point to next entry in path
call logtemp ; Log in path-specified user/drive
if attchk ; If allowing execution only of COM files with
; ..specific attributes
ld de,cmdfcb ; Point to command FCB
call srchfst ; Look for directory entry for file
jr z,mload2a ; Continue path search if file not found
push hl ; Save path pointer
call getsbit ; Check system attribute bit
pop hl ; Restore path pointer
jr z,mload2a ; Continue if attributes do not match
call opencmd ; Open file for input
jr z,mload2a ; If open failed, back to next path element
else ;not attchk
call opencmd ; Open file for input
jr z,mload2a ; If open failed, back to next path element
endif ; attchk
call readcmd ; Read first record into default DMA address
jr nz,mload5 ; Branch if zero-length file
xor a ; Set file current record back to zero
ld (cmdfcb+20h),a
ld hl,80h ; Pointer to start of code
call z3chk
jr nz,mload3a ; If not Z3 file, branch
; The following test is modified by earlier code. For normal COM file loading,
; a 3 is inserted for the minimum environment type for dynamic load address
; determination. For the GET command, where the user-specified address should
; be used, a value of 0FFH is put in here so the carry flag will always be set.
envtype equ $+1 ; Pointer for in-the-code modification
cp 3 ; See if no higher than a type-3 environment
jr c,mload3a ; If higher than type 3, branch
inc hl ; Advance to load address word
inc hl
inc hl
ld a,(hl) ; Get load address into HL
inc hl
ld h,(hl)
ld l,a
ld (execadr),hl ; Set new execution/load address
mload3a:
ld hl,(execadr) ; Get initial loading address
; Load the file, making sure neither CPR nor protected memory is overwritten
mload4:
if fullget
ld a,(envtype) ; If ENVTYPE is FF (from GET command)
inc a ; ..then skip memory limit checking
jr z,mload4b
endif ;fullget
if rel
ld bc,entry ; We have to use a relocatable form to get
dec b ; ..highest page below the CPR
else ;not rel
ld b,high entry - 1 ; We can use shorter code for absolute form
endif ;rel
ld a,(0007h) ; Get highest page below
dec a ; ..protected memory
cp b ; If A is lower value,
jr c,mload4a ; ..branch
ld a,b ; Otherwise use lower value in B
mload4a:
cp h ; Are we going to overwrite protected memory?
ld a,ectpafull ; Get ready with TPA overflow error code
jp c,error ; Error if about to overwrite protected memory
mload4b:
push hl ; Save this load address
ex de,hl ; Set DMA address
call dmaset
call readcmd
pop hl ; Get last load address back
jr nz,mload5 ; Read error or eof?
ld de,128 ; Increment load address by 128
add hl,de
jr mload4 ; Continue loading
; In case a program would like to find out in what directory the command
; processor found the program, temporary DU is stored in bytes 13 (user) and
; 14 (drive) in the command FCB.
mload5:
tempusr equ $+1 ; Pointers for in-the-code modification
tempdr equ $+2
ld hl,0
ld (cmdfcb+13),hl
logcurrent: ; Return to original logged directory
ld hl,(curusr) ; Set L = current user, H = current drive
ld a,h
call setdrive ; Login current drive
ld a,l
jp setuser ; Log in new user and return from MLOAD
;----------------------------------------
; This routine checks to see if building the path or running the ECP should
; be skipped. If there is a colon in the command (an explicit directory
; given) but it was not a lone colon (indicating desire to skip resident
; commands), then the routine returns with the zero flag reset.
if skippath
skipchk:
ld a,(colon) ; Was there a colon in the command?
or a
ret z ; Return with zero flag set if not
if fastecp or badduecp
ld a,(firstchar) ; See if the first character was the colon
else
firstchar equ $+1 ; Put data here if other two options are
ld a,0 ; ..false (in-the-code modification)
endif ;fastecp or badduecp
cp ':'
ret ; Return: Z if lone colon, NZ otherwise
endif ;skippath
; End ZCPR33-6.Z80
page
;-----------------------------------------------------------------------------
;
; D A T A A R E A D E F I N I T I O N S
;
;-----------------------------------------------------------------------------
; ---------- Page line count buffer
if lton ; Needed only if TYPE command included
pagcnt:
defs 1 ; Lines left on page (filled in by code)
endif ;lton
; ---------- Minpath/Rootpth buffers
if extmpath
mpath equ extmpathadr ; Assign external minpath address
else
mpath:
if drvprefix
defs 2 ; Two bytes for specified DU
endif
if scancur
defs 2 ; Two bytes for current DU
endif
defs 2 * expaths ; Space for path from path buffer
defs 1 ; One byte for ending null
endif ; not extmpath
if rootonly
rootpth:
defs 2 ; Special path for root dir only
defb 0 ; End of path
endif ; rootonly
;-----------------------------------------------------------------------------
; The following will cause an error message to appear if
; the size of ZCPR33 is over 2K bytes.
if [ $ - entry ] gt 800h
*** ZCPR33 IS LARGER THAN 2K BYTES ***
endif
endif ;errflag
end ; ZCPR33