************************************************************************** * * The GNO Shell Project * * Developed by: * Jawaid Bazyar * Tim Meekins * * $Id: cmd.asm,v 1.12 1999/11/30 17:53:27 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 * 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 using vardata 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 and #$00FF sta 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 ldx #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 ldx #INQUOTE bra chkkeep neut7 if2 @a,ne,#"'",neut8 startsingle ldx #SINGQUOTE chkkeep ldy varkeepquote ; Is KEEPQUOTE env var set? beq neut10 ; NO: just set the state bra neut9a ; YES: save quote character neut8 if2 @a,ne,#'\',neut9 lda [buf] and #$FF incad buf if2 @a,eq,#13,neut10a neut9 ldx #INWORD ; Default neut9a sta [word] ; Save character in buffer incad word ; and increment pointer. neut10 stx state ; Set current state. neut10a jmp loop ; Check next character. ; ; 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 ; If it's a quoted character, lda [buf] ; load up the next character incad buf ; and bump the pointer. putword sta [word] incad word jmp loop quote2 if2 @a,ne,#'"',putword bra chkkeep2 ; ; CASE SINGLEQUOTE ; case_single anop if2 ch,ne,#"'",putword ; For both ' and ": if KEEPQUOTE env var is set, save quote char in result. chkkeep2 ldy varkeepquote beq nokeep sta [word] incad word nokeep ld2 INWORD,state ; Always: set state to INWORD jmp loop ; and get the next character. ; ; 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 jmp 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 using vardata 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 maxline_size Allocate maxline_size bytes ~NEW and pointer in cmdline. sta cmdline stx cmdline+2 lda #0 Initialize to null C string. sta [cmdline] jsl allocmaxline 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 lda varkeepquote If env var KEEPQUOTE is set, bne appword required quotes are already in place. inc needq Otherwise, quotes are 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 param 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 allocmaxline 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 allocmaxline 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 allocmaxline 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 freemaxline ex1 anop lda srcfile ora srcfile+2 beq ex2 ldx srcfile+2 lda srcfile jsl freemaxline ex2 anop lda errfile ora errfile+2 beq ex3 ldx errfile+2 lda errfile jsl freemaxline ex3 anop ldx word+2 lda word jsl freemaxline 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 using vardata 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. clc Add command length to adc cmdstrt starting address to sta cmdend get ending address. lda #0 adc cmdstrt+2 sta cmdend+2 cpy #0 If number of bytes in command jeq chk_cmd = 0, just skip it. 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 ; Was there a variable error? ora ptr_envexp+2 bne expglob pha Put null pointer pha on stack jmp errexit and go to error exit. ; Expand wildcard characters in the modified command line expglob phx lda ptr_envexp pha jsl glob sta ptr_glob stx ptr_glob+2 ldx ptr_envexp+2 Free memory allocated lda ptr_envexp for env var expansion jsl freemaxline ; Was there a globbing error? lda ptr_glob ora ptr_glob+2 bne expalias pha Put null pointer pha on stack jmp errexit Go to error exit. ; Expand aliases in the modified command line (final expansion) expalias ldx ptr_glob+2 phx lda ptr_glob pha jsl expandalias sta exebuf stx exebuf+2 phx Put exebuf on stack for pha nullfree at endcmd. ldx ptr_glob+2 Free memory allocated lda ptr_glob for globbing expansion. jsl freemaxline ; Was there a alias error? lda exebuf ora exebuf+2 jeq errexit ; echo expanded command if echox is set. ldy varechox beq noechox ldx exebuf+2 lda exebuf jsr puts jsr newline noechox anop * 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