gno/bin/gsh/cmd.asm
tribby 6269a8ca25 Changes for gsh version 2.0d8:
Fixed several mutual exclusion problems, including a particularly nasty
one that would cause gsh to loop forever inside the memory manager. (You
could identify this one by the "BRA (-23)" at the bottom of the infinite
loop.)

Fixed the string vector print routine to properly handle all numbers of
entries in the hash table.  Previously, it would always print duplicate
entries if there were < 6 commands hashed, and would sometimes print
duplicates (depending on previous contents of an internal table) for
other numbers of commands.

gsh would wait on background processes started from an exec file
(executed, not sourced). Now the exec file does not wait on the process,
but the background process is not associated with the parent shell after
the exec file terminates.

Made gsh globbing work more like csh: if none of the requested patterns
are found, print "No match" and exit.

At startup, if /etc/glogin, $HOME/glogin, or $HOME/gshrc does not exist,
don't report a "file not found" error message. (PR#100)
1998-12-31 18:29:14 +00:00

1627 lines
30 KiB
NASM

**************************************************************************
*
* The GNO Shell Project
*
* Developed by:
* Jawaid Bazyar
* Tim Meekins
*
* $Id: cmd.asm,v 1.9 1998/12/31 18:29:12 tribby Exp $
*
**************************************************************************
*
* CMD.ASM
* By Tim Meekins
* Modified by Dave Tribby for GNO 2.0.6
*
* Command line parsing routines.
*
* Note: text set up for tabs at col 16, 22, 41, 49, 57, 65
* | | | | | |
* ^ ^ ^ ^ ^ ^
**************************************************************************
*
* Interfaces defined in this file:
*
* gettoken subroutine (4:word,4:stream)
* Returns value of token in Accumulator
*
* command subroutine (4:waitpid,2:inpipe,2:jobflag,2:inpipe2,
* 4:pipesem,4:stream,4:awaitstatus)
* Called by execute to act on a single command
* Returns next token in Accumulator
*
* argfree subroutine (4:path,2:argc,4:argv)
*
* ShellExec subroutine (4:path,2:argc,4:argv,2:jobflag)
* Reads and executes commands from an exec file.
* Returns completion status in Accumulator
*
* execute subroutine (4:cmdline,2:jobflag)
* Interpret a command line.
* Returns completion status in Accumulator
*
* system Defined for libc; interface in <stdlib.h>
* User interface to execute commands via shell
* int system (char *command)
*
**************************************************************************
mcopy /obj/gno/bin/gsh/cmd.mac
dummycmd start ; ends up in .root
end
setcom 60
SIGINT gequ 2
SIGSTOP gequ 17
SIGTSTP gequ 18
SIGCHLD gequ 20
;
; TOKENS used by the parser
;
T_WORD gequ 1
T_BAR gequ 2
T_AMP gequ 3
T_SEMI gequ 4
T_GT gequ 5
T_GTGT gequ 6
T_GTAMP gequ 7
T_GTGTAMP gequ 8
T_LT gequ 9
T_NL gequ 10
T_EOF gequ 11
T_ERROR gequ 12
T_NULL gequ 13
MAXARG gequ 256
BADFD gequ -2
**************************************************************************
*
* Read a token from the buffer.
*
* Input: word points to a buffer to place a word is parsed.
*
* Ouput: the token is placed in the accumulator
*
**************************************************************************
gettoken START
buf equ 1
state equ buf+4
ch equ state+2
space equ ch+2
stream equ space+3
word equ stream+4
end equ word+4
; subroutine (4:word,4:stream),space
tsc
sec
sbc #space-1
tcs
phd
tcd
;
; What state are we in
;
NEUTRAL equ 0 ;a neutral state, get anything
GTGT equ 1 ;looking for a second '>'
INQUOTE equ 2 ;parsing a quoted string
INWORD equ 3 ;parsing a word
SINGQUOTE equ 4 ;single quote string
;
; Start in the neutral state
;
ld2 NEUTRAL,state
lda [stream]
sta buf
ldy #2
lda [stream],y
sta buf+2
;
; Main loop: get character and take action based upon state.
;
loop lda [buf]
incad buf
and2 @a,#$FF,ch ch = next character.
bne switch
; End of string detected. Action depends upon current state.
if2 state,ne,#INWORD,loop2
jmp endword state INWORD: end the word.
loop2 anop
dec buf Went too far: back up pointer.
if2 @a,ne,#GTGT,loop3
lda #T_GT state GTGT: return single GT.
jmp done
loop3 if2 @a,eq,#INQUOTE,error1 INQUOTE: error.
if2 @a,eq,#SINGQUOTE,error2 SINGQUOTE: error.
lda #T_EOF must be NEUTRAL: return EOF.
jmp done
error1 ldx #^errstr1 Report string errors.
lda #errstr1
bra error0
error2 ldx #^errstr2
lda #errstr2
error0 jsr errputs
lda #T_ERROR
jmp done
;
; jump to the current state
;
switch lda state
asl a
tax
jmp (statetbl,x)
statetbl dc a2'case_neutral'
dc a2'case_gtgt'
dc a2'case_inquote'
dc a2'case_inword'
dc a2'case_single'
;
; CASE NEUTRAL
; Check for special characters:
; ; & | < creturn EOF -- set token value and go to done
; space tab -- ignore and stay in loop
; > -- Change state to GTGT and stay in loop
; # -- Eat characters to creturn or lf, then stay in loop
; " -- Change state to INQUOTE and stay in loop
; ' -- Change state to SINGQUOTE and stay in loop
; \ -- Get next character, change state to INWORD, and stay in loop
; All other characters: change state to INWORD and stay in loop
case_neutral if2 ch,ne,#';',neut1
lda #T_SEMI
jmp done
neut1 if2 @a,ne,#'&',neut2
lda #T_AMP
jmp done
neut2 if2 @a,ne,#'|',neut3
lda #T_BAR
jmp done
neut3 if2 @a,ne,#'<',neut4
lda #T_LT
jmp done
neut5 cmp #' ' ;space
jeq loop
cmp #9 ;tab
jeq loop
if2 @a,ne,#'>',neut6
lda #GTGT
bra neut10
neut4 if2 @a,ne,#13,neut4a ;return
lda #T_NL
jmp done
neut4a if2 @a,ne,#0,neut4b ;EOF [Is this possible?? DMT]
lda #T_EOF
jmp done
neut4b if2 @a,ne,#'#',neut5 ;comment
neut4c lda [buf]
and #$7F
beq neut4d
incad buf
if2 @a,eq,#13,neut4d
if2 @a,eq,#10,neut4d
bra neut4c
neut4d jmp loop
neut6 if2 @a,ne,#'"',neut7
startquote lda #INQUOTE
bra neut10
neut7 if2 @a,ne,#"'",neut8
startsingle lda #SINGQUOTE
bra neut10
neut8 if2 @a,ne,#'\',neut9
lda [buf]
and #$FF
incad buf
if2 @a,eq,#13,neut10a
neut9 sta [word] ;default
incad word
lda #INWORD
neut10 sta state
neut10a jmp loop
;
; CASE GTGT
;
case_gtgt if2 ch,eq,#'>',gtgt2
if2 @a,eq,#'&',gtgt1
dec buf
lda #T_GT
jmp done
gtgt1 lda #T_GTAMP
jmp done
gtgt2 lda [buf]
and #$FF
if2 @a,eq,#'&',gtgt3
lda #T_GTGT
jmp done
gtgt3 incad buf
lda #T_GTGTAMP
jmp done
;
; CASE INQUOTE
;
case_inquote if2 ch,ne,#'\',quote2 ;is it a quoted character?
lda [buf]
incad buf
putword sta [word]
incad word
jmp loop
quote2 if2 @a,ne,#'"',putword
ld2 INWORD,state
jmp loop
;
; CASE SINGLEQUOTE
;
case_single anop
if2 ch,ne,#"'",putword
ld2 INWORD,state
jmp loop
;
; CASE INWORD
;
case_inword if2 ch,eq,#000,endword
if2 @a,eq,#';',endword
if2 @a,eq,#'&',endword
if2 @a,eq,#'|',endword
if2 @a,eq,#'>',endword
if2 @a,eq,#'<',endword
if2 @a,eq,#' ',endword
if2 @a,eq,#009,endword
if2 @a,eq,#013,endword
cmp #'"'
jeq startquote
cmp #"'"
jeq startsingle
if2 @a,ne,#'\',putword
lda [buf]
incad buf
and #$FF
if2 @a,eq,#13,word2
bra putword
word2 jmp loop
endword dec buf
finiword lda #0
sta [word]
lda #T_WORD
done tax
lda buf
sta [stream]
lda buf+2
ldy #2
sta [stream],y
lda space
sta end-3
lda space+1
sta end-2
pld
tsc
clc
adc #end-4
tcs
txa
rtl
errstr1 dc c'gsh: Missing ending ".',h'0d00'
errstr2 dc c"gsh: Missing ending '.",h'0d00'
END
**************************************************************************
*
* Parse a single command
*
**************************************************************************
command START
pipefds equ 1
errappend equ pipefds+4
errfile equ errappend+2
srcfile equ errfile+4
dstfile equ srcfile+4
needq equ dstfile+4
argv equ needq+2
word equ argv+4
cmdline equ word+4
pid equ cmdline+4
append equ pid+2
temp equ append+2
argc equ temp+4
token equ argc+2
space equ token+2
awaitstatus equ space+3
stream equ awaitstatus+4
pipesem equ stream+4
inpipe2 equ pipesem+4
jobflag equ inpipe2+2
inpipe equ jobflag+2
waitpid equ inpipe+2
end equ waitpid+4
; subroutine (4:waitpid,2:inpipe,2:jobflag,2:inpipe2,4:pipesem,4:stream,4:awaitstatus),space
tsc
sec
sbc #space-1
tcs
phd
tcd
ph4 #1024 Allocate 1024 bytes
~NEW and pointer in cmdline.
sta cmdline
stx cmdline+2
lda #0 Initialize to null C string.
sta [cmdline]
jsl alloc1024 Allocate memory for token.
sta word
stx word+2
ph4 #MAXARG*4 Allocate argv parameter pointer array
~NEW
sta argv
stx argv+2
stz argc Argument count = 0
stz srcfile Clear I/O redirection pointers
stz srcfile+2
stz dstfile
stz dstfile+2
stz errfile
stz errfile+2
stz pipefds
stz pipefds+2
lda #-3
sta [waitpid]
loop pei (word+2) Get next token from input stream.
pei (word)
pei (stream+2)
pei (stream)
jsl gettoken
sta token Jump to appropriate token handler.
asl a
tax
jmp (toktbl,x)
toktbl dc a2'loop'
dc a2'tok_word'
dc a2'tok_bar'
dc a2'tok_amp'
dc a2'tok_semi'
dc a2'tok_gt'
dc a2'tok_gtgt'
dc a2'tok_gtamp'
dc a2'tok_gtgtamp'
dc a2'tok_lt'
dc a2'tok_nl'
dc a2'tok_eof'
dc a2'tok_error'
;
; Parse a word token
;
tok_word if2 argc,ne,#MAXARG,word1
lda #err00 ;Too many arguments
jmp error
word1 pei (word+2)
pei (word)
jsr cstrlen
inc a
pea 0
pha
~NEW
sta temp
stx temp+2
ora temp+2
bne word2
lda #err01 ;Out of memory for arguments
jmp error
word2 lda argc ;Copy word to argv[argc]
asl2 a
tay
lda temp
sta [argv],y
lda temp+2
iny2
sta [argv],y
pei (word+2)
pei (word)
pei (temp+2)
pei (temp)
jsr copycstr
;
; Determine whether parameter needs surrounding quotes
;
stz needq
lda [word] If this is a null parameter,
and #$FF
beq qneeded it needs to be quoted.
ldy #0
chkchar if2 @a,eq,#' ',qneeded
if2 @a,eq,#'&',qneeded
if2 @a,eq,#'|',qneeded
if2 @a,eq,#'<',qneeded
if2 @a,eq,#'>',qneeded
if2 @a,eq,#';',qneeded
iny Not special character.
lda [word],y Get next character in parameter.
and #$FF
beq appword Done if null character.
bra chkchar
qneeded inc needq Quotes needed.
;
; Append word to command line (optionally, with quotes)
;
appword anop
pei (word+2)
pei (word)
pei (cmdline+2)
pei (cmdline)
jsr cstrlen
tay
ldx argc
beq nospace
lda #' '
sta [cmdline],y
iny
nospace lda needq If special char is in parameter,
beq noquote
lda [word]
and #$FF
cmp #'"' and it doesn't start with '"',
bne doquote
stz needq
bra noquote
doquote lda #'"' add a '"' at start.
sta [cmdline],y
iny
noquote pei (cmdline+2)
add2 @y,cmdline,@a
pha
jsr copycstr Copy the parameter.
lda needq If quote was added at start,
beq noquote2
pei (cmdline+2)
pei (cmdline)
jsr cstrlen
tay
lda #'"' add another at end.
sta [cmdline],y
iny
lda #0
sta [cmdline],y
noquote2 inc argc ;increment argument count
goloop jmp loop
;
; Parse a '<' token
;
tok_lt lda srcfile
ora srcfile+2
beq lt1
lda #err02 ;Extra < encountered
jmp error
lt1 lda inpipe
beq lt2
lda #err09 ;< conflicts with |
jmp error
lt2 jsl alloc1024
stx srcfile+2
sta srcfile
phx
pha
pei (stream+2)
pei (stream)
jsl gettoken
if2 @a,eq,#T_WORD,goloop
lda #err03 ;Illegal < specified
jmp error
;
; Parse a '>' or '>>'
;
tok_gt anop
tok_gtgt lda dstfile
ora dstfile+2
beq gt1
lda #err04 ;Extra > or >> encountered
jmp error
gt1 jsl alloc1024
stx dstfile+2
sta dstfile
phx
pha
pei (stream+2)
pei (stream)
jsl gettoken
if2 @a,eq,#T_WORD,gt2
lda #err05 ;Illegal > or >> specified
jmp error
gt2 stz append
if2 token,ne,#T_GTGT,gt3
inc append
gt3 jmp loop
;
; Parse a '>&' or '>>&'
;
tok_gtamp anop
tok_gtgtamp lda errfile
ora errfile+2
beq ga1
lda #err06 ;Extra >& or >>& encountered
jmp error
ga1 jsl alloc1024
stx errfile+2
sta errfile
phx
pha
pei (stream+2)
pei (stream)
jsl gettoken
if2 @a,eq,#T_WORD,ga2
lda #err07 ;Illegal >& or >>& specified
jmp error
ga2 stz errappend
if2 token,ne,#T_GTGTAMP,ga3
inc errappend
ga3 jmp loop
;
; Parse a command terminator
;
tok_bar anop
tok_amp anop
tok_semi anop
tok_nl anop
tok_eof anop
lda argc If number of arguments == 0,
bne nonnull
lda srcfile If any of the file pointers
ora srcfile+2 are != NULL,
ora dstfile
ora dstfile+2
ora errfile
ora errfile+2
beq nulldone
ldx #^spcmdstr print error message:
lda #spcmdstr specify a command before redirecting.
jsr errputs
nulldone anop
pei (cmdline+2) Free buffers
pei (cmdline) allocated for
jsl nullfree command line
pei (argv+2) and argv.
pei (argv)
jsl nullfree
lda #0 Clear the waitpid
sta [waitpid] and return as if
lda #T_NULL nothing were parsed.
jmp exit
nonnull asl2 a Terminate the argv list
tay with a null poiner.
lda #0
sta [argv],y
iny2
sta [argv],y
;
; See if there is a conflict between > or >> and |
;
lda token
if2 @a,ne,#T_BAR,runit
lda dstfile
ora dstfile+2
beq bar2
lda #err08 Yes: there is a conflict!
jmp error
bar2 clc Calculate 32-bit address
tdc of direct page variable
adc #pipefds pipefds in X and A.
ldx #0
pipe @xa Allocate 2 file descriptor pipe
; >> NOTE: what if pipe returns error?
;
; Call invoke param size:name
;
runit pei (argc) 2: argc
pei (argv+2) 4: argv
pei (argv)
pei (srcfile+2) 4: sfile
pei (srcfile)
pei (dstfile+2) 4: dfile
pei (dstfile)
pei (errfile+2) 4: efile
pei (errfile)
pei (append) 2: app
pei (errappend) 2: eapp
ldx #0
if2 token,ne,#T_AMP,run2
inx 2: bg
run2 phx
pei (cmdline+2) 4: cline
pei (cmdline)
pei (jobflag) 2: jobflag
pei (inpipe) 2: pipein (param passed in)
pei (pipefds+2) 2: pipeout (allocated: read end)
pei (inpipe2) 2: pipein2 (param passed in)
pei (pipefds) 2: pipeout2 (allocated: write end)
pei (pipesem+2) 4: pipesem (param passed in)
pei (pipesem)
pei (awaitstatus+2) 4: awaitstatus (address) [New for v2.0]
pei (awaitstatus)
lda #-1 Set waitstatus = -1; it will be set to
sta [awaitstatus] 0 or 1 iff unforked builtin is called.
jsl invoke
sta pid
cmp #-1 If invoke detected an error,
beq exit all done.
; If next token is "|", recursively call command.
if2 token,ne,#T_BAR,run3
lda #-1 Pre-set for error flag.
ldx pid If no child was forked,
beq exit all done.
pei (waitpid+2) 4: waitpid
pei (waitpid)
pei (pipefds) 2: inpipe
pei (jobflag) 2: jobflag
pei (pipefds+2) 2: inpipe2
pei (pipesem+2) 4: pipesem
pei (pipesem)
pei (stream+2) 4: stream
pei (stream)
pei (awaitstatus+2) 4: awaitstatus
pei (awaitstatus)
jsl command
bra exit
run3 lda pid
sta [waitpid]
lda token
;
; Free allocated memory and return to caller
;
exit pha Hold return status on stack.
lda dstfile
ora dstfile+2
beq ex1
ldx dstfile+2
lda dstfile
jsl free1024
ex1 anop
lda srcfile
ora srcfile+2
beq ex2
ldx srcfile+2
lda srcfile
jsl free1024
ex2 anop
lda errfile
ora errfile+2
beq ex3
ldx errfile+2
lda errfile
jsl free1024
ex3 anop
ldx word+2
lda word
jsl free1024
ply Get return value.
lda space
sta end-3
lda space+1
sta end-2
pld
tsc
clc
adc #end-4
tcs
tya
rtl
;
; Print error message, deallocate memory, and return with value -1
;
error ldx #^err00 (Add high word of error address)
jsr errputs
tok_error pei (cmdline+2)
pei (cmdline)
jsl nullfree
ph4 #0 (no path to be freed)
pei (argc)
pei (argv+2)
pei (argv)
jsl argfree
lda #-1
jmp exit
err00 dc c'gsh: Too many arguments, so no dessert tonight.',h'0d00'
err01 dc c'gsh: Not enough memory for arguments.',h'0d00'
err02 dc c'gsh: Extra ''<'' encountered.',h'0d00'
err03 dc c'gsh: No file specified for ''<''.',h'0d00'
err04 dc c'gsh: Extra ''>'' or ''>>'' encountered.',h'0d00'
err05 dc c'gsh: No file specified for ''>'' or ''>>''.',h'0d00'
err06 dc c'gsh: Extra ''>&'' or ''>>&'' encountered.',h'0d00'
err07 dc c'gsh: No file specified for ''>&'' or ''>>&''.',h'0d00'
err08 dc c'gsh: ''|'' conflicts with ''>'' or ''>>''.',h'0d00'
err09 dc c'gsh: ''|'' conflicts with ''<''.',h'0d00'
spcmdstr dc c'gsh: Specify a command before redirecting.',h'0d00'
END
**************************************************************************
*
* dispose the argv
*
**************************************************************************
argfree START
space equ 0
subroutine (4:path,2:argc,4:argv),space
pei path+2 Free the path.
pei path
jsl nullfree
free1 lda argc Free each of the argv elements.
beq free2
dec a
asl2 a
tay
lda [argv],y
tax
iny2
lda [argv],y
pha
phx
jsl nullfree
dec argc
bra free1
free2 pei (argv+2) Free the argv array.
pei (argv)
jsl nullfree
return
END
**************************************************************************
*
* Read and execute commands from an exec file (shell script)
* This is overly complicated so that it can be run concurrently.
*
**************************************************************************
ShellExec START
using vardata
using global
count equ 1
data equ count+2
CRec equ data+4
RRec equ CRec+4
NRec equ RRec+4
ORec equ NRec+4
ptr equ ORec+4
sptr equ ptr+4
status equ sptr+2
space equ status+2
jobflag equ space+3
argv equ jobflag+2
argc equ argv+4
path equ argc+2
end equ path+4
; subroutine (4:path,2:argc,4:argv,2:jobflag),space
tsc
sec
sbc #space-1
tcs
phd
tcd
lock mutex
;
; Set the environment variables $0 ... argc
;
lda argc Get number of variables.
jeq vars_set If 0, there are none to set.
stz count Start with argv[0].
parmloop lda count Get index
asl2 a into address array.
tay
lda [argv],y Copy argument
sta ptr pointer to
iny2 ptr.
lda [argv],y
sta ptr+2
lda count If parameter number
cmp #10
bcs digits2or3 < 10,
adc #'0' Convert to single digit
sta pname_text and store in name string.
lda #1 Set length of string to 1.
sta pname
bra set_value
digits2or3 cmp #100 If parameter number
bcs digits3 >= 10 && < 99,
ldx #2 length = 2
bra setit otherwise
digits3 ldx #3 length = 3
;
; Store length (2 or 3) and convert number to text
;
setit stx pname
Int2Dec (@a,#pname_text,pname,#0)
;
; Build parameter block on stack and call SetGS (p 427 in ORCA/M manual)
;
set_value anop
tsx Save current stack pointer.
stx sptr
pea 0 Export flag
ph4 ptr Convert value string
jsr c2gsstr to a GS/OS string.
stx ptr+2 (Save for later
sta ptr deallocation).
phx
pha
ph4 #pname Address of variable name.
pea 3 Number of parameters
tsc Calculate Param Block addr
inc a (stack ptr + 1)
pea 0 and push 32 bits
pha onto stack
pea $0146 SetGS call number
jsl $E100B0 Call GS/OS
lda sptr Restore stack pointer.
tcs
ph4 ptr Free the value buffer.
jsl nullfree
inc count Bump the parameter counter.
lda count If more to do,
cmp argc
jcc parmloop stay in loop.
;
; $0 ... $n environment variables have all been set
;
vars_set unlock mutex
;
; Allocate memory for GS/OS calls
;
ph4 #4 CloseGS parms
~NEW
sta CRec
stx CRec+2
ph4 #10 OpenGS parameter block
~NEW
sta ORec
stx ORec+2
ph4 #12 NewLineGS parameter block
~NEW
sta NRec
stx NRec+2
ph4 #16 ReadGS parameter block
~NEW
sta RRec
stx RRec+2
ph4 #1000 Data buffer (1000 bytes)
~NEW
sta data
stx data+2
;
; Set parameters in OpenGS parameter block
;
lda #3 Number of parameters = 3.
sta [ORec]
pei (path+2) Convert exec file
pei (path) pathname to GS/OS string
jsr c2gsstr
ldy #4 Store in PB, offset bytes 4-7.
sta [ORec],y
sta ptr
iny2
txa
sta [ORec],y
sta ptr+2
lda #1 Read access only
ldy #8 Store in PB, offset bytes 8-9.
sta [ORec],y
pei (ORec+2) OpenGS(ORec)
pei (ORec)
ph2 #$2010
jsl $E100B0
bcc ok If there was an error,
cmp #$46 and it's not "file not found"
bne openerr on glogin or gshrc,
ldx jobflag
bmi godone
;
; Error opening source file. Assemble parameter block on stack and call
; ErrorGS (p 393 in ORCA/M manual)
;
openerr anop
tsx Save current stack pointer.
stx sptr
pha Push error number
pea 1 Push number of parameters
tsc Calculate PB addr
inc a (stack ptr + 1)
pea 0 and push 32 bits
pha onto stack
pea $0145 ErrorGS call number
jsl $E100B0 Call GS/OS
lda sptr Restore stack pointer.
tcs
godone jmp done
ok ldy #2 Copy file ref num
lda [ORec],y from OpenGS PB into
sta [NRec],y NewLineGS PB,
sta [RRec],y ReadGS PB,
sta [CRec],y and CloseGS PB.
;
; Set parameters in NewLineGS parameter block
;
lda #4 Number of parameters = 4.
sta [NRec]
ldy #4 enableMask
lda #$7F = $7F (each input character
sta [NRec],y is ANDed with this mask)
iny2 numChars
lda #1 = 1 (number of newline chars
sta [NRec],y contained in newline char table)
iny2 newlineTable pointer
lda #NLTable = NLTable
sta [NRec],y
iny2
lda #^NLTable
sta [NRec],y
pei (NRec+2) NewLineGS(NRec)
pei (NRec)
ph2 #$2011
jsl $E100B0
bcc ok2 If there was an error,
tsx Save current stack pointer.
stx sptr
pha
pea 1
tsc
inc a
pea 0
pha
pea $0145
jsl $E100B0 Call ErrorGS to print a message
lda sptr Restore stack pointer.
tcs
jmp close_ex
;
; Set up ReadGS parameter block
;
ok2 lda #4 Number of parameters = 4
sta [RRec]
tay dataBuffer (offset 4)
lda data = data
sta [RRec],y
iny2
lda data+2
sta [RRec],y
iny2 requestCount (offset 4)
lda #999 = 999 (save 1 byte for \0)
sta [RRec],y
iny2
lda #0
sta [RRec],y
;
; Read lines from the exec file
;
ReadLoop anop
pei (RRec+2) ReadGS(RRec)
pei (RRec)
ph2 #$2012
jsl $E100B0
bcs close_ex Check for error
ldy #12 Get transferCount
lda [RRec],y
tay Store null byte
lda #0 at end of buffer.
sta [data],y
lda varecho If $ECHO environment
beq noecho variable is set,
ldx data+2
lda data
jsr puts print the line
jsr newline and a newline.
noecho lda [data] If first character
and #$FF in data buffer is
if2 @a,eq,#'#',ReadLoop "#", read next line.
* call execute: subroutine (4:cmdline,2:jobflag)
pei (data+2)
pei (data)
lda jobflag Remove "startup" flag bit
and #$7FFF from jobflag before passing it.
pha
jsl execute
sta status
lda exit_requested If exit not requested,
bne close_ex
bra ReadLoop stay in read loop.
;
; Close the exec file
;
close_ex anop
lda #1 Number of parameters = 1.
sta [CRec]
pei (CRec+2) CloseGS(CRec)
pei (CRec)
ph2 #$2014
jsl $E100B0
stz exit_requested Clear the "exit gsh" flag.
done pei (CRec+2) Free the parameter blocks,
pei (CRec)
jsl nullfree
pei (NRec+2)
pei (NRec)
jsl nullfree
pei (RRec+2)
pei (RRec)
jsl nullfree
pei (ORec+2)
pei (ORec)
jsl nullfree
pei (data+2) data buffer,
pei (data)
jsl nullfree
pei (ptr+2) and path name.
pei (ptr)
jsl nullfree
;
; Restore stack and return to caller
;
exit1a anop
lda space+1
sta end-2
lda space
sta end-3
pld
tsc
clc
adc #end-4
tcs
lda status Pass back status value.
rtl
NLTable dc h'0d' Newline Table
;
; Name of argv parameter ($1 to $999) to be set; GS/OS string
;
pname ds 2 Length
pname_text dc c'000' Text (up to 3 digits)
mutex key
END
**************************************************************************
*
* Execute a command line by calling command for each command in the buffer
*
**************************************************************************
execute START
exebuf equ 1
pipesem equ exebuf+4
ptr_glob equ pipesem+2
waitstatus equ ptr_glob+4
ptr_envexp equ waitstatus+2
pid equ ptr_envexp+4
term equ pid+2
cmdstrt equ term+2
cmdend equ cmdstrt+4
end_char equ cmdend+4
inquote equ end_char+2
space equ inquote+2
jobflag equ space+3 ;set if not a job
cmdline equ jobflag+2
end equ cmdline+4
; subroutine (4:cmdline,2:jobflag),space
tsc
sec
sbc #space-1
tcs
phd
tcd
; ---------------------------------------------------------------
; New code for gsh 2.0 (Dave Tribby): within execute, loop through
; the command line to separate each command and expand it separately,
; rather than passing multiple commands to the next level. This is
; done so that commands that depend upon each other will work, e.g.
; set testnum=2 ; echo "This is test $testnum"
;
; Find beginning and end of next command in the command line
;
lda cmdline Initialize cmdstrt to
sta cmdstrt beginning of cmdline.
lda cmdline+2
sta cmdstrt+2
; Skip over leading whitespace
chkws lda [cmdstrt] Get next character.
and #$FF
jeq goback If at end of line, nothing to do!
cmp #" " If it's a space
beq bump_strt
cmp #9 or a tab,
bne found_start
bump_strt incad cmdstrt bump the start pointer
bra chkws and look for more whitespace.
; Initialize pointer to end of command
found_start anop
; Scan the command line for next semi-colon. Need to account for
; quoted strings, backslash-escaped characters, and comments.
; Take advantage of 65816's BIT command for the "in quotes" flag. Use
; $8000 for single-quote bit and $4000 for double-quote bit. After
; bit inquote
; can do a bmi to check for $8000 set and bvs for $4000 set. Can
; check for either with lda inquote followed by beq or bne.
stz inquote Clear the "in quotes" flag.
ldy #$FFFF Clear index into cmdstrt
find_end anop
iny
lda [cmdstrt],y Get next character.
and #$FF If at end of string,
beq found_end all done looking.
; Check for special quote characters
cmp #"'"
beq s_quote
cmp #'"'
beq d_quote
cmp #'\'
beq b_slash
; "#" and ";" are special only if we aren't in a quoted string
ldx inquote
bne find_end
cmp #"#"
beq found_end
cmp #";"
beq found_end
; Not a special character. Keep looking.
bra find_end
; "'" found
s_quote bit inquote Check the "in quotes" flag.
bvs find_end In double quotes...keep looking.
lda inquote Toggle the single_quote
eor #$8000 bit in the "in quotes" flag.
sta inquote
bra find_end Keep looking.
; '"' found
d_quote bit inquote Check the "in quotes" flag.
bmi find_end In single quotes...keep looking.
lda inquote Toggle the double_quote
eor #$4000 bit in the "in quotes" flag.
sta inquote
bra find_end Keep looking.
; "\" found: accept next character without examining it in detail
b_slash iny Bump index.
lda [cmdstrt],y Get next character.
and #$FF If not at end of string,
bne find_end keep looking.
;
; Found a ";", "#", or null byte.
;
found_end anop
sta end_char Save the ending character.
tya Get number of bytes in command.
jeq goback If none, just skip it.
clc Add command length to
adc cmdstrt starting address to
sta cmdend get ending address.
lda #0
adc cmdstrt+2
sta cmdend+2
lda end_char Get the termination character.
beq expand If it's not a null byte,
lda #0
short m
sta [cmdend] store null byte in string.
long m
; Continue with command-line expansions for the single command
expand anop
; ---------------------------------------------------------------
stz pipesem
stz waitstatus
; Expand $ (environment variables) and ~ in the raw command line
pei (cmdstrt+2)
pei (cmdstrt)
jsl expandvars
sta ptr_envexp
stx ptr_envexp+2
; Expand wildcard characters in the modified command line
phx
pha
jsl glob
sta ptr_glob
stx ptr_glob+2
; Was there a globbing error?
ora ptr_glob+2
bne expalias
pha Put null pointer
pha on stack
jmp errexit and go to error exit.
; Expand aliases in the modified command line (final expansion)
expalias phx
lda ptr_glob
pha
jsl expandalias
sta exebuf
stx exebuf+2
phx Put exebuf on stack for
pha nullfree at endcmd.
* >> Temporary debug code: echo expanded command if echo is set.
using vardata
ldy varecho
beq noecho
jsr puts NOTE: x/a = exebuf
jsr newline
noecho anop
ldx ptr_envexp+2 Free memory allocated
lda ptr_envexp for env var expansion
jsl free1024
ldx ptr_glob+2 and globbing.
lda ptr_glob
jsl free1024
;
; If exebuf pointer is null, bail out.
; >> NOTE: if exebuf is checked for null, shouldn't the other ptrs?
;
lda exebuf
ora exebuf+2
bne loop
pla
pla
stz term
lda #0
jmp chk_cmd
* command subroutine (4:waitpid,2:inpipe,2:jobflag,2:inpipe2,
* 4:pipesem,4:stream,4:awaitstatus)
loop pea 0 ;Bank 0 waitpid (hi)
tdc
clc
adc #pid
pha waitpid (low)
pea 0 inpipe = 0
pei (jobflag) jobflag
pea 0 inpipe2 = 0
pea 0 ;Bank 0 pipesem (hi)
tdc
clc
adc #pipesem
pha pipesem (low)
pea 0 ;Bank 0 stream (hi)
tdc
clc
adc #exebuf
pha stream (low)
pea 0 ;Bank 0 status (hi) [New: v2.0]
tdc
clc
adc #waitstatus
pha status (low)
jsl command
sta term Save result in term.
jmi errexit If < 0, all done.
; If waitstatus != -1, executed command was a non-forked builtin,
; and waitstatus is its completion status.
lda waitstatus
cmp #-1
beq chkpid
jsr setstatus Set $status.
bra godonewait No need to wait.
chkpid lda pid Get child process id.
beq godonewait If 0 (no fork), no need to wait.
cmp #-1 If -1 (error), all done.
jeq errexit
if2 term,eq,#T_AMP,jobwait If last token was "&"
lda jobflag or jobflag is set,
beq jobwait do more complicated wait.
;
; Uncomplicated wait: simply call wait() to get child's termination status
;
signal (#SIGINT,#0) Use default interrupt and
phx keyboard stop signal handlers,
pha and put address of current
signal (#SIGTSTP,#0) handlers on the stack.
phx
pha
otherwait anop
ldx #0
clc
tdc
adc #waitstatus
wait @xa Wait for child completion.
cmp pid
bne otherwait
lda waitstatus
and #$FF
cmp #$7F Check for WSTOPPED status.
beq otherwait Something else...wait again.
lda waitstatus
jsr setstatus Set process's $status.
pla Restore gsh's interrupt and
plx keyboard stop signal handlers.
signal (#SIGTSTP,@xa)
pla
plx
signal (#SIGINT,@xa)
godonewait bra donewait
;
; jobflag = 0: need more complicated wait for child
;
jobwait anop
signal (#SIGCHLD,#pchild) Ensure child sig handler active.
phx Save address of previous sig handler.
pha
kill (pid,#0) If child no longer exists
beq wait4job
pei pid
jsl removejentry Remove its pid from the list.
beq restoresigh Pid not in list: assume $status set.
ldx #0
clc
tdc
adc #waitstatus
wait @xa Get child completion status.
lda waitstatus
jsr setstatus Set process's $status.
bra restoresigh
;
; Child is active: wait for it to complete and get its status.
; NOTE: $status is set by SIGCHLD signal handler, pchild
;
wait4job jsl pwait Wait for child using pchild
restoresigh pla Restore previous child completion
plx signal handler.
signal (#SIGCHLD,@xa)
;
; Done waiting for completion status. Check the token last parsed
; from the command line.
;
donewait if2 term,eq,#T_EOF,endcmd If last token was EOF
lda [exebuf] or if next character is \0,
and #$FF
beq endcmd all done with this command.
jmp loop Process the next command.
;
; Underlying routine detected an error. Set waitstatus = -1
;
errexit lda #-1
sta waitstatus
jsr setstatus Set process's $status.
;
; We have completed processing of a command
;
endcmd jsl nullfree Free exebuf (addr on stack).
lda term Return -1 if error
bmi chk_cmd
lda waitstatus Get completion status, and convert
xba from wait() format to byte value.
and #$FF
;
; Is there another command waiting in the buffer?
;
chk_cmd ldx end_char Was the original ending character
cpx #";" a semi-colon?
bne goback NO -- all done.
; Set cmdstrt to point to the character beyond cmdend
lda cmdend
ldx cmdend+2
ina
bne set_strt
inx
set_strt sta cmdstrt
stx cmdstrt+2
jmp chkws Parse the next command.
;
; All done.
;
goback tay Hold return status in Y-reg.
lda space+1 Set up stack
sta end-2 for rtl.
lda space
sta end-3
pld
tsc
clc
adc #end-4
tcs
tya Restore return status from Y-reg.
rtl
END
**************************************************************************
*
* System() call vector
*
**************************************************************************
system START
retval equ 0
space equ retval+2
subroutine (4:str),space ;need the phk/plb
lda str If user passes a
ora str+2 null pointer,
bne makecall
dec a return -1 to caller.
bra setrtn
;
; Let execute(str,1) do the work
;
makecall pei (str+2)
pei (str)
ph2 #1 jobflag=1
jsl execute
;
; Set status and go back to the caller
;
setrtn sta retval
return 2:retval
END