#include #include "fido.h" #include "fidomem.h" #include /* Script file processor. This processes characters from a text file and provides a character stream for output to the modem, and performs functions on this incoming text stream. */ char *cpyscrarg(); #define MAXLEVEL 10 /* maximum call depth */ #define MAXARG 8 /* max. arguments (&1 - &8) */ #define MAXSIZE 32 /* max. size of string registers */ /* Our machine description: &A the numeric accumulator, runs 0 - 65535 &C a timer that continuously increments by seconds &T the trap register, script execution time limit &E the boolean register, set by various instructions (READ ONLY) &S the character shift register from the input stream (READ ONLY) &1 - &8 general purpose registers */ static int accum; /* the accumulator */ static char sr[MAXSIZE]; /* modem stream string compare shift register */ static char error; /* the error register */ static scrfile = -1; /* opened file */ static scrfn[SS]; /* its name */ static long trap_reg; /* trap time register */ static char trap_label[SS]; /* trap label */ static int trap_level; /* trap subroutine level */ static char clkflag = 0; /* to get it started first time (KLUDGE!) */ static long clk_tick; /* millisecond counter */ static long chr_timer; /* character output time */ static unsigned throttle = 10; /* character output speed */ static long char_tick; /* character output timer */ struct _stk { int file; /* file handle, */ char fname[SS]; /* file name, */ char arg[MAXARG][SS]; /* the string registers */ }; static struct _stk *stk; /* the stack itself, */ static int level = 0; /* call/return levels */ static char recog[MAXARG]; /* the recognizer */ static char sreg[MAXARG][MAXLEVEL]; /* the data stack */ static int slevel; /* data-stack pointer, */ static char scrtext[SS] = ""; /* buffer for text output */ static char *scrptr = scrtext; /* pointer to the text */ static int scrkdel; /* counter to slow keyboard poll */ static int scrunatt; /* 1 == unattended mode */ /* Return true if there is a script file running. */ isscript() { return(level); } /* If there is a character waiting, return it. */ getscr() { if (*scrptr) { if (char_tick > throttle) { /* issue characters no faster */ char_tick= 0L; /* than every N mS */ return(*scrptr++); } } return(NUL); } /* Put a string in the buffer for output to the modem. Dont overflow the buffer. */ static scrset(s) char *s; { int n; strcpy(scrtext,scrptr); /* copy remaining text to buff start */ n= strlen(scrtext); /* to increase avail. room */ scrptr= &scrtext[n]; while (++n < sizeof(scrtext)) { /* add new text to the end */ if (! *s) break; /* but dont exceed buff size */ *scrptr++= *s++; } *scrptr= NUL; scrptr= scrtext; /* point to next char */ } /* Check to see if the trap has gone off; if it has, pass control to the previously specified label in the correct subroutine. */ static check_trap() { if (trap_reg != 0L) { /* if the trap is set, */ if (clk_tick >= trap_reg) { /* and has gone off, */ trap_reg= 0; /* disable it, */ while (level > trap_level) doret(""); dojmp(trap_label); } } } /* Open a script file, try the current directory first, then look through the path. Return the handle or -1 if error. */ static openp(fn,mode) char *fn; int mode; { int f; char *cp,buff[SS]; char *getenv(); if ((f= open(fn,mode)) != -1) /* if in current directory, */ return(f); /* just return it */ cp= getenv("PATH"); /* else find the PATH, */ if ((int)cp == 0) cp= "\\"; /* if none, use the root */ while (*cp) { /* try each path, */ cpyarg(buff,cp); /* copy one pathname, */ if (buff[strlen(buff) - 1] != '\\') strcat(buff,"\\"); /* add a \ if none in the path */ strcat(buff,fn); /* add the filename, */ f= open(buff,mode); /* try to open, */ if (f != -1) return(f); /* return the open handle, */ cp= next_arg(cp); /* next ... */ } return(-1); } /* Output a character to the modem. */ static send_kc() { char c; if (c) modout(c); /* output any chars, */ } /* Check for incoming characters, clear the character counter and shift them in. */ static check_mc() { if (modstat()) { /* if a character available, */ chr_timer= 0L; /* clear the time-since counter, */ shiftin(modin(0)); /* shift it */ } } /* Display a message */ static mesg(m) char *m; { char buff[SS]; clputs(" $ "); _fspr(buff,0,&m); /* format it, */ clputs(buff); clputs("\r\n"); } /* Execute the given script file. Return 1 if connected OK, 0 if not connected and -1 if error. */ script(fn) char *fn; { char ln[256],c; int h1,h2,h3; int l; trap_reg= 0; /* turn off the trap */ scrunatt= 1; /* assume unattended */ stk= (struct _stk *)text; /* use this for work */ stk-> file= -1; /* no file open */ trap_reg= 0; /* turn off the trap */ slevel= 0; /* stack is empty */ clrsr(); /* clear the shift register */ doclose(""); /* close any open file */ docall(fn); /* start the script */ if (level) mesg("Running script %s",fn); else return(-1); /* something went wrong */ l= limit; limit= 0; /* disable the time limit */ h1= set_tick(&clk_tick); /* start the tickers running */ h2= set_tick(&char_tick); h3= set_tick(&chr_timer); while (level) { abort_scr(); /* check manual abort */ check_trap(); /* check trap timer */ if (rline(stk-> file,ln,sizeof(ln))) /* read a line */ script_line(ln); /* if empty file, */ else doret(""); /* close and return */ } clr_tick(h1); clr_tick(h2); clr_tick(h3); limit= l; /* restore the orig. limit */ switch (error) { /* convert the error */ case 0: return(1); /* connected */ case 1: return(0); /* not connected */ default: return(-1); /* error! */ } } /* Process one line in the script file. */ script_line(ln) char *ln; { char buff[SS]; char *ra; /* raw instruction arguments */ char fa[SS]; /* first expanded arg */ char na[SS]; /* next expanded arg */ char c,*ip; int n; unsigned a; /* ASSUMED TO BE AT LEAST TWO CHARS WIDE!!! */ while (delim(*ln)) { /* skip delimiters */ if (*ln == ';') return; /* ignore comment lines */ ++ln; } if (! *ln) return; /* and blank lines */ if (*ln == ':') return; /* ignore labels */ ip= ln; /* pointer to the instruction */ a= tolower(*ip) << 8; /* convert first two chars */ a |= tolower(ip[1]); /* into a word for switch() */ ln= next_arg(ln); /* skip the instruction */ ra= ln; /* ra == raw arguments */ ln= cpyscrarg(buff,ln); scrmac(fa,buff); /* fa == first argument */ n= atoi(fa); /* n == numeric first arg */ cpyscrarg(buff,skip_delim(ln)); scrmac(na,buff); /* na == next argument */ cmdflush(); /* flush extraneous keybd input */ switch (a) { case 'pu': dopush(fa); break; /* PUSH string */ case 'po': dopop(ra); break; /* POP register */ case 'fi': dofiles(fa); break; /* FILES filespec */ case 'un': scrunatt= n; break; /* UNATTENDED n */ case 'th': if (n <= 500) throttle= n; break; /* THROTTLE n */ case 'br': mbreak(); break; /* BREAK */ case 'dt': lower_dtr(); delay(50); raise_dtr(); break; /* DTR */ case 'cd': cd_bit= n; break; /* CD-BIT n */ case 'io': uninit(); iodev= n - 1; init(); /* IO-PORT n */ baud(linkrate); setbaud(datarate); break; case 'fo': doopen(fa); break; /* FOPEN filename */ case 'fn': docreat(fa); break; /* FNEW filename */ case 'fc': doclose(fa); break; /* FCLOSE */ case 'fr': doread(fa); break; /* FREAD */ case 'fw': dowrite(fa); break; /* FWRITE */ case 'de': dodelay(n); break; /* DELAY milliseconds */ case 'qu': doquiet(n); break; /* QUIET millisecs */ case 'sa': if (c= getscr()) send_kc(c); /* SAMPLE */ check_mc(); break; case 'me': domesg(fa); break; /* MESSAGE message */ case 'ou': scrset(fa); break; /* OUTPUT string */ case 'co': for (ln= fa; *ln; ) modout(*ln++); break; /* COPY string */ case 'ca': docall(ra); break; /* CALL scriptfile */ case 're': doret(fa); return; /* RETURN value */ case 'tr': dotrap(fa); break; /* TRAP label */ case '&r': setpat(recog,fa); break; /* &R= pattern */ case '&s': clrsr(); for (ln= fa; *ln; ++ln) /* &S= pattern */ shiftin(*ln); break; case '&1': setpat(stk-> arg[0],fa); break; /* &1= pattern */ case '&2': setpat(stk-> arg[1],fa); break; /* &2= pattern */ case '&3': setpat(stk-> arg[2],fa); break; /* &3= pattern */ case '&4': setpat(stk-> arg[3],fa); break; /* &4= pattern */ case '&5': setpat(stk-> arg[4],fa); break; /* &5= pattern */ case '&6': setpat(stk-> arg[5],fa); break; /* &6= pattern */ case '&7': setpat(stk-> arg[6],fa); break; /* &7= pattern */ case '&8': setpat(stk-> arg[7],fa); break; /* &8= pattern */ case 'ma': domatch(fa,na); break; /* MATCH pattern limit */ case 'if': setpat(buff,fa); if (match(buff)) dojmp(na); break; /* IF pattern label */ case '&a': accum= n; break; /* &A= value */ case '&b': if (setbaud(n)) datarate= n; break; /* &B= value */ case '&c': clk_tick= 1000L * n; break; /* &C= value */ case '&e': error= (n ? 1 : 0); break; /* &E= value */ case '&t': trap_reg= n * 1000L; clk_tick= 0L; break; /* &T= value */ case 'ad': accum += atoi(fa); break; /* ADD value */ case 'an': accum &= atoi(fa); break; /* AND value */ case 'su': accum -= atoi(fa); break; /* SUB value */ case 'jm': dojmp(fa); break; /* JMP label */ case 'jz': if (!accum) dojmp(fa); break; /* JZ label */ case 'jn': if (accum) dojmp(fa); break; /* JNZ label */ case 'dj': if (accum) --accum; /* DJNZ label */ if (!accum) dojmp(fa); break; case 'je': if (error) { error= 0; dojmp(fa); } /* JERROR label */ break; /* Commands not supported in Fido's script language. */ case 'ti': oops("TIME"); break; /* TIME hh:mm */ case 'ke': oops("KEYBD"); break; /* KEYBD char */ case 'as': oops("ASK"); break; /* ASK question */ case 'pa': oops("PAUSE"); break; /* PAUSE message */ default: mesg("Unknown instruction \"%s\"",ip); stop_script(); break; } } /* Complain we can't do this. */ static oops(m) char *m; { mesg("\"%s\" instruction: no effect",m); } /* PUSH string Put the string onto the stack; error if overflow. */ static dopush(cp) char *cp; { if (slevel >= MAXLEVEL) { mesg("Stack overflow, can't PUSH \"%s\"",cp); stop_script(); return; } cp[MAXSIZE]= NUL; strcpy(sreg[slevel++],cp); } /* POP register Pop the stack into the specified register; error if underflow. We are passed the raw register name so we have to parse a bit. */ static dopop(ra) char *ra; { char *cp,e; int n; if (slevel <= 0) { mesg("Can't POP %s, stack underflow",ra); stop_script(); return; } e= 0; /* assume no error */ cp= sreg[--slevel]; n= atoi(cp); /* prepare the stack value */ if (*ra == '"') ++ra; /* skip any leading quote */ if (*ra != '&') ++e; /* oops, must be reg name! */ else switch (ra[1]) { case 'a': accum= n; break; case 'c': clk_tick= n * 1000L; break; case 't': trap_reg= n * 1000L; clk_tick= 0L; break; case 'b': if (setbaud(n)) datarate= n; break; case 'e': error= (n ? 1 : 0); break; case 'r': strcpy(recog,cp); break; case 's': while (*cp) shiftin(*cp++); break; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': n= atoi(&ra[1]) - 1; if (n <= MAXARG) setpat(stk-> arg[n],cp); else ++e; break; default: ++e; break; } if (e) { mesg("No such register: POP \"%s\"",ra); stop_script(); } } /* FOPEN filename Open the specified file, error if one is already open. */ static doopen(np) char *np; { if (scrfile != -1) { mesg("File %s is already open",scrfn); stop_script(); return; } cpyarg(scrfn,np); /* make a clean filename */ scrfile= openp(scrfn,2); /* open it, */ error= (scrfile == -1); if (error && !scrunatt) { mesg("Can't find file \"%s\"",scrfn); stop_script(); return; } } /* FNEW filename Open the specified file, error if one is already open. */ static docreat(np) char *np; { if (scrfile != -1) { mesg("File %s is already open",scrfn); stop_script(); return; } cpyarg(scrfn,np); /* make a clean filename */ scrfile= creat(scrfn,2); if (scrfile == -1) { mesg("Can't create file \"%s\"?",scrfn); stop_script(); } } /* READ Read a line from the open file, parse it into the &1 - &n variables. */ static doread(np) char *np; { char buff[256]; if (scrfile == -1) { mesg("There is no file open to READ from!"); stop_script(); return; } error= rline(scrfile,buff,sizeof(buff)) ? 0 : 1;/* set &E if EOF */ scrargs(buff,level); /* set possible arguments */ } /* WRITE Write a line to the open file. */ static dowrite(np) char *np; { int i; char buff[SS]; if (scrfile == -1) { mesg("There is no file open to WRITE to!"); stop_script(); return; } lseek(scrfile,0L,2); /* write at EOF only */ for (i= 0; i < MAXARG; i++) { /* write each arg */ sprintf(buff,0,"\"%s\"",stk-> arg[i]); error= write(scrfile,buff,strlen(buff)) == strlen(buff) ? 0 : 1; if (error) { if (! scrunatt) { mesg("Error writing file\"%s\"",scrfn); stop_script(); return; } else break; } if (i < MAXARG - 1) write(scrfile,",",1); } write(scrfile,"\r\n",2); } /* CLOSE Close any open file. */ static doclose(np) char *np; { if (scrfile != -1) close(scrfile); scrfile= -1; } /* TRAP label Set the trap to go off to the label in the current script file. */ static dotrap(np) char *np; { if (!*np) { mesg("No TRAP label in script %s",stk-> fname); stop_script(); return; } strcpy(trap_label,np); /* remember the label */ trap_level= level; /* remember the level */ } /* MESSAGE Output the message to the command line. */ static domesg(np) char *np; { char *cp,buff[SS]; cp= buff; while (*np) { if (*np >= ' ') *cp++= *np; /* printable chars as-is */ else { /* expand control chars */ *cp++= '\\'; switch (*np) { case CR: *cp++= 'r'; break; case LF: *cp++= 'n'; break; case TAB: *cp++= 't'; break; default: sprintf(cp,0,"%02d",*np); while (*cp) ++cp; break; } } ++np; } *cp= NUL; mesg(buff); } /* CALL Start execution of a script file. If successful, this pushes the current file handle, and sets the new one as the current file. Note that this gets the raw, unprocessed, command line. */ static docall(rp) char *rp; { int f,e; char *cp,buff[SS],name[SS]; cp= cpyscrarg(buff,rp); /* copy off the script name */ scrmac(name,buff); /* expand it, etc */ scrmac(buff,skip_delim(cp)); /* expand possible arguments */ cp= name; while (*cp) { if (*cp == '.') break; /* find the extention */ ++cp; } strcpy(cp,".SCR"); /* make .SCR */ stoupper(name); /* for error messages, etc */ f= openp(name,0); /* try to open it */ if (f == -1) { mesg("Can't find %s script file",name); stop_script(); /* abort processing */ } else if (level + 1 >= MAXLEVEL) { mesg("Too many nested script files: CALL %s from %s",name,stk-> fname); close(f); stop_script(); } else { ++level; /* next level */ ++stk; /* next stack frame */ stk-> file= f; /* set new file handle */ strcpy(stk-> fname,name); /* current script name */ scrargs(buff,level); /* set possible arguments */ } } /* RETURN Close a script file, pop back up one level. If is non zero, set the ERROR value. */ static doret(np) char *np; { if (isdigit(*np)) error= atoi(np); /* set error value */ if (stk-> file != -1) close(stk-> file); /* close the file, */ if (level > 0) { --stk; --level; /* execute a return */ } else level= 0; } /* FILES Set the accumulator to the number of files that match the file specification. */ static dofiles(np) char *np; { char path[SS]; struct _xfbuf xfbuf; cpyarg(path,np); np= next_arg(np); xfbuf.s_attrib= 0; accum= 0; while(! _find(path,accum,&xfbuf)) ++accum; } /* TIME hh:mm Wait until the specified time of day. */ static dotime(np) char *np; { int h,m,lmin,hour,mins; char *cp; cp= np; hour= atoi(cp); /* read hour first, */ while (*cp) { /* look for the colon */ if (*cp == ':') break; ++cp; } if (*cp++ != ':') { mesg("Illegal time \"%s\"",np); stop_script(); return; } mins= atoi(cp); /* get minutes */ if ((hour > 23) || (mins > 59)) { mesg("Illegal time \"%s\"; 23:59 maximum!",np); stop_script(); return; } lmin= -1; do { do { h= gtod3(4); /* read the hour, */ m= gtod3(5); /* and the minute */ } while ((h != gtod3(4)) || (m != gtod3(5))); if (abort_scr()) break; /* keyboard abort */ if (m != lmin) { lmin= m; mesg("Time is now %02d:%02d, waiting for %02d:%02d", h,m, hour,mins); } } while ((h != hour) || (m != mins)); /* wait til time */ } /* DELAY Delay milliseconds. Delay 1 second if no argument is passed. */ static dodelay(n) int n; { int h; long timer; char c; if (!n) n= 1000; /* default to 1 sec, */ h= set_tick(&timer); /* get a milisec timer */ if (h == -1) { mesg("FidoTerm error: Cant allocate a DELAY timer"); stop_script(); return; } timer= 0L; while (timer < n) { if (abort_scr()) break; /* check keyboard */ if (c= getscr()) send_kc(c); /* script output, */ check_mc(); /* input modem characters */ } clr_tick(h); /* stop the timer */ } /* MATCH Search for , for up to one minute or seconds if present. If the string is not found &E is set true. */ static domatch(np,na) char *np; { int i,h; long timer,limit; char buff[SS],c; setpat(buff,np); /* working pattern */ limit= atoi(na) * 1000L; /* check possible 2nd arg */ if (limit == 0L) limit= 60000L; /* else default to 1 minute */ h= set_tick(&timer); /* get a milisec timer */ if (h == -1) { mesg("FidoTerm error: Cant allocate a MATCH timer"); stop_script(); return; } timer= 0L; /* global timer */ clrsr(); while ((timer < limit) || (limit == 0L)) { /* start counting */ if (abort_scr()) break; /* check keyboard */ if (c= getscr()) send_kc(c); /* script output, */ check_mc(); /* process modem characters */ if (match(buff)) break; /* compare against SR */ } clr_tick(h); /* stop the timer */ error= ((timer >= limit) && /* error if timeout */ (limit > 0L)); /* and non-zero limit */ } /* Install one character in the shift register. */ static shiftin(c) char c; { int i; for (i= 0; i < (sizeof(sr) - 2); i++) /* shift full length */ sr[i]= sr[i + 1]; /* shift the register */ sr[i]= tolower(c & 0x7f); /* install the new char */ } /* Clear the shift register. */ static clrsr() { int i; for (i= 0; i < sizeof(sr); i++) sr[i]= NUL; } /* Install a pattern in a string register; convert to all lower case and bound to maximum length. Return the string length. */ static setpat(reg,np) char *reg,*np; { int i; char *cp; cp= reg; for (i= MAXSIZE; i--;) if (! (*cp++= *np++)) break; *cp= NUL; /* force an end, */ stolower(reg); /* make lower case */ return(strlen(reg)); /* return the length */ } /* Return true if the pattern matches the text now in the shift register. */ static match(r) char *r; /* the pattern to match, */ { char *p; if (! *r) { /* special case null string */ p= sr + sizeof(sr) - 2; /* check for empty SR */ return(*p ? 0 : 1); /* match only if both null */ } p= sr + sizeof(sr) - strlen(r) - 1; /* the input stream */ while (*r) { /* match characters */ if ((*r != '?') && (*r != *p)) /* if not a wildcard or a match */ return(0); /* return mismatch */ ++r; ++p; } return(1); } /* JMP