diff options
Diffstat (limited to 'expect/expect.c')
-rw-r--r-- | expect/expect.c | 3035 |
1 files changed, 3035 insertions, 0 deletions
diff --git a/expect/expect.c b/expect/expect.c new file mode 100644 index 00000000000..a105d944c33 --- /dev/null +++ b/expect/expect.c @@ -0,0 +1,3035 @@ +/* expect.c - expect commands + +Written by: Don Libes, NIST, 2/6/90 + +Design and implementation of this program was paid for by U.S. tax +dollars. Therefore it is public domain. However, the author and NIST +would appreciate credit if this program or parts of it are used. + +*/ + +#include <sys/types.h> +#include <stdio.h> +#include <signal.h> +#include <errno.h> +#include <ctype.h> /* for isspace */ +#include <time.h> /* for time(3) */ +#if 0 +#include <setjmp.h> +#endif + +#include "expect_cf.h" + +#ifdef HAVE_SYS_WAIT_H +#include <sys/wait.h> +#endif + +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif + +#include "tcl.h" + +#include "string.h" + +#include "tclRegexp.h" +#include "exp_rename.h" +#include "exp_prog.h" +#include "exp_command.h" +#include "exp_log.h" +#include "exp_event.h" +#include "exp_tty.h" +#include "exp_tstamp.h" /* this should disappear when interact */ + /* loses ref's to it */ +#ifdef TCL_DEBUGGER +#include "Dbg.h" +#endif + +/* CYGNUS LOCAL: dj/expect */ +#if defined(__CYGWIN__) || defined(__CYGWIN32__) +/* Hack. expect is a mongrel win32/cygwin app, so we can't rely on tcl to + properly poll our descriptors. This define lets timeouts work. */ +#define SIMPLE_EVENT +#endif +/* END CYGNUS LOCAL: dj/expect */ + +/* initial length of strings that we can guarantee patterns can match */ +int exp_default_match_max = 2000; +#define INIT_EXPECT_TIMEOUT_LIT "10" /* seconds */ +#define INIT_EXPECT_TIMEOUT 10 /* seconds */ +int exp_default_parity = TRUE; +int exp_default_rm_nulls = TRUE; + +/* user variable names */ +#define EXPECT_TIMEOUT "timeout" +#define EXPECT_OUT "expect_out" + +/* 1 ecase struct is reserved for each case in the expect command. Note that +eof/timeout don't use any of theirs, but the algorithm is simpler this way. */ + +struct ecase { /* case for expect command */ + struct exp_i *i_list; + char *pat; /* original pattern spec */ + char *body; /* ptr to body to be executed upon match */ +#define PAT_EOF 1 +#define PAT_TIMEOUT 2 +#define PAT_DEFAULT 3 +#define PAT_FULLBUFFER 4 +#define PAT_GLOB 5 /* glob-style pattern list */ +#define PAT_RE 6 /* regular expression */ +#define PAT_EXACT 7 /* exact string */ +#define PAT_NULL 8 /* ASCII 0 */ +#define PAT_TYPES 9 /* used to size array of pattern type descriptions */ + int use; /* PAT_XXX */ + int simple_start;/* offset from start of buffer denoting where a */ + /* glob or exact match begins */ + int transfer; /* if false, leave matched chars in input stream */ + int indices; /* if true, write indices */ +/* int iwrite;*/ /* if true write spawn_id */ + int iread; /* if true, reread indirects */ + int timestamp; /* if true, write timestamps */ +#define CASE_UNKNOWN 0 +#define CASE_NORM 1 +#define CASE_LOWER 2 + int Case; /* convert case before doing match? */ + regexp *re; /* if this is 0, then pattern match via glob */ +}; + +/* descriptions of the pattern types, used for debugging */ +char *pattern_style[PAT_TYPES]; + +struct exp_cases_descriptor { + int count; + struct ecase **cases; +}; + +/* This describes an Expect command */ +static +struct exp_cmd_descriptor { + int cmdtype; /* bg, before, after */ + int duration; /* permanent or temporary */ + int timeout_specified_by_flag; /* if -timeout flag used */ + int timeout; /* timeout period if flag used */ + struct exp_cases_descriptor ecd; + struct exp_i *i_list; +} exp_cmds[4]; +/* note that exp_cmds[FG] is just a fake, the real contents is stored + in some dynamically-allocated variable. We use exp_cmds[FG] mostly + as a well-known address and also as a convenience and so we allocate + just a few of its fields that we need. */ + +static void +exp_cmd_init(cmd,cmdtype,duration) +struct exp_cmd_descriptor *cmd; +int duration; +int cmdtype; +{ + cmd->duration = duration; + cmd->cmdtype = cmdtype; + cmd->ecd.cases = 0; + cmd->ecd.count = 0; + cmd->i_list = 0; +} + +static int i_read_errno;/* place to save errno, if i_read() == -1, so it + doesn't get overwritten before we get to read it */ +#if 0 +static jmp_buf env; /* for interruptable read() */ + /* longjmp(env,1) times out the read */ + /* longjmp(env,2) restarts the read */ +static int env_valid = FALSE; /* whether we can longjmp or not */ +#endif + +#ifdef SIMPLE_EVENT +static int alarm_fired; /* if alarm occurs */ +#endif + +void exp_background_filehandlers_run_all(); + +/* exp_indirect_updateX is called by Tcl when an indirect variable is set */ +static char *exp_indirect_update1(); /* 1-part Tcl variable names */ +static char *exp_indirect_update2(); /* 2-part Tcl variable names */ + +static int exp_i_read _ANSI_ARGS_((Tcl_Interp *,int,int,int)); + +#ifdef SIMPLE_EVENT +/*ARGSUSED*/ +static RETSIGTYPE +sigalarm_handler(n) +int n; /* unused, for compatibility with STDC */ +{ + alarm_fired = TRUE; +#if 0 + /* check env_valid first to protect us from the alarm occurring */ + /* in the window between i_read and alarm(0) */ + if (env_valid) longjmp(env,1); +#endif /*0*/ +} +#endif /*SIMPLE_EVENT*/ + +#if 0 +/*ARGSUSED*/ +static RETSIGTYPE +sigalarm_handler(n) +int n; /* unused, for compatibility with STDC */ +{ +#ifdef REARM_SIG + signal(SIGALRM,sigalarm_handler); +#endif + + /* check env_valid first to protect us from the alarm occurring */ + /* in the window between i_read and alarm(0) */ + if (env_valid) longjmp(env,1); +} +#endif /*0*/ + +#if 0 + +/* upon interrupt, act like timeout */ +/*ARGSUSED*/ +static RETSIGTYPE +sigint_handler(n) +int n; /* unused, for compatibility with STDC */ +{ +#ifdef REARM_SIG + signal(SIGINT,sigint_handler);/* not nec. for BSD, but doesn't hurt */ +#endif + +#ifdef TCL_DEBUGGER + if (exp_tcl_debugger_available) { + /* if the debugger is active and we're reading something, */ + /* force the debugger to go interactive now and when done, */ + /* restart the read. */ + + Dbg_On(exp_interp,env_valid); + + /* restart the read */ + if (env_valid) longjmp(env,2); + + /* if no read is in progess, just let debugger start at */ + /* the next command. */ + return; + } +#endif + +#if 0 +/* the ability to timeout a read via ^C is hereby removed 8-Mar-1993 - DEL */ + + /* longjmp if we are executing a read inside of expect command */ + if (env_valid) longjmp(env,1); +#endif + + /* if anywhere else in code, prepare to exit */ + exp_exit(exp_interp,0); +} +#endif /*0*/ + +/* remove nulls from s. Initially, the number of chars in s is c, */ +/* not strlen(s). This count does not include the trailing null. */ +/* returns number of nulls removed. */ +static int +rm_nulls(s,c) +char *s; +int c; +{ + char *s2 = s; /* points to place in original string to put */ + /* next non-null character */ + int count = 0; + int i; + + for (i=0;i<c;i++,s++) { + if (0 == *s) { + count++; + continue; + } + if (count) *s2 = *s; + s2++; + } + return(count); +} + +/* free up everything in ecase */ +static void +free_ecase(interp,ec,free_ilist) +Tcl_Interp *interp; +struct ecase *ec; +int free_ilist; /* if we should free ilist */ +{ + if (ec->re) ckfree((char *)ec->re); + + if (ec->i_list->duration == EXP_PERMANENT) { + if (ec->pat) ckfree(ec->pat); + if (ec->body) ckfree(ec->body); + } + + if (free_ilist) { + ec->i_list->ecount--; + if (ec->i_list->ecount == 0) + exp_free_i(interp,ec->i_list,exp_indirect_update2); + } + + ckfree((char *)ec); /* NEW */ +} + +/* free up any argv structures in the ecases */ +static void +free_ecases(interp,eg,free_ilist) +Tcl_Interp *interp; +struct exp_cmd_descriptor *eg; +int free_ilist; /* if true, free ilists */ +{ + int i; + + if (!eg->ecd.cases) return; + + for (i=0;i<eg->ecd.count;i++) { + free_ecase(interp,eg->ecd.cases[i],free_ilist); + } + ckfree((char *)eg->ecd.cases); + + eg->ecd.cases = 0; + eg->ecd.count = 0; +} + + +#if 0 +/* no standard defn for this, and some systems don't even have it, so avoid */ +/* the whole quagmire by calling it something else */ +static char *exp_strdup(s) +char *s; +{ + char *news = ckalloc(strlen(s) + 1); + strcpy(news,s); + return(news); +} +#endif + +/* In many places, there is no need to malloc a copy of a string, since it */ +/* will be freed before we return to Tcl */ +static void +save_str(lhs,rhs,nosave) +char **lhs; /* left hand side */ +char *rhs; /* right hand side */ +int nosave; +{ + if (nosave || (rhs == 0)) { + *lhs = rhs; + } else { + *lhs = ckalloc(strlen(rhs) + 1); + strcpy(*lhs,rhs); + } +} + +/* return TRUE if string appears to be a set of arguments + The intent of this test is to support the ability of commands to have + all their args braced as one. This conflicts with the possibility of + actually intending to have a single argument. + The bad case is in expect which can have a single argument with embedded + \n's although it's rare. Examples that this code should handle: + \n FALSE (pattern) + \n\n FALSE + \n \n \n FALSE + foo FALSE + foo\n FALSE + \nfoo\n TRUE (set of args) + \nfoo\nbar TRUE + + Current test is very cheap and almost always right :-) +*/ +int +exp_one_arg_braced(p) +char *p; +{ + int seen_nl = FALSE; + + for (;*p;p++) { + if (*p == '\n') { + seen_nl = TRUE; + continue; + } + + if (!isspace(*p)) { + return(seen_nl); + } + } + return FALSE; +} + +/* called to execute a command of only one argument - a hack to commands */ +/* to be called with all args surrounded by an outer set of braces */ +/* returns TCL_whatever */ +/*ARGSUSED*/ +int +exp_eval_with_one_arg(clientData,interp,argv) +ClientData clientData; +Tcl_Interp *interp; +char **argv; +{ + char *buf; + int rc; + char *a; + + /* + 11 is for " -nobrace " and null at end */ + buf = ckalloc(strlen(argv[0]) + strlen(argv[1]) + 11); + /* recreate statement (with -nobrace to prevent recursion) */ + sprintf(buf,"%s -nobrace %s",argv[0],argv[1]); + + /* + * replace top-level newlines with blanks + */ + + /* Should only be necessary to run over argv[1] and then sprintf */ + /* that into the buffer, but the ICEM guys insist that writing */ + /* back over the original arguments makes their Tcl compiler very */ + /* unhappy. */ + for (a=buf;*a;) { + extern char *TclWordEnd(); + + for (;isspace(*a);a++) { + if (*a == '\n') *a = ' '; + } +#if TCL_MAJOR_VERSION < 8 + a = TclWordEnd(a,0,(int *)0)+1; +#else + a = TclWordEnd(a,&a[strlen(a)],0,(int *)0)+1; +#endif + } + + rc = Tcl_Eval(interp,buf); + + ckfree(buf); + return(rc); +} + +static void +ecase_clear(ec) +struct ecase *ec; +{ + ec->i_list = 0; + ec->pat = 0; + ec->body = 0; + ec->transfer = TRUE; + ec->indices = FALSE; +/* ec->iwrite = FALSE;*/ + ec->iread = FALSE; + ec->timestamp = FALSE; + ec->re = 0; + ec->Case = CASE_NORM; + ec->use = PAT_GLOB; +} + +static struct ecase * +ecase_new() +{ + struct ecase *ec = (struct ecase *)ckalloc(sizeof(struct ecase)); + + ecase_clear(ec); + return ec; +} + +/* + +parse_expect_args parses the arguments to expect or its variants. +It normally returns TCL_OK, and returns TCL_ERROR for failure. +(It can't return i_list directly because there is no way to differentiate +between clearing, say, expect_before and signalling an error.) + +eg (expect_global) is initialized to reflect the arguments parsed +eg->ecd.cases is an array of ecases +eg->ecd.count is the # of ecases +eg->i_list is a linked list of exp_i's which represent the -i info + +Each exp_i is chained to the next so that they can be easily free'd if +necessary. Each exp_i has a reference count. If the -i is not used +(e.g., has no following patterns), the ref count will be 0. + +Each ecase points to an exp_i. Several ecases may point to the same exp_i. +Variables named by indirect exp_i's are read for the direct values. + +If called from a foreground expect and no patterns or -i are given, a +default exp_i is forced so that the command "expect" works right. + +The exp_i chain can be broken by the caller if desired. + +*/ + +static int +parse_expect_args(interp,eg,default_spawn_id,argc,argv) +Tcl_Interp *interp; +struct exp_cmd_descriptor *eg; +int default_spawn_id; /* suggested master if called as expect_user or _tty */ +int argc; +char **argv; +{ + int i; + char *arg; + struct ecase ec; /* temporary to collect args */ + + argv++; + argc--; + + eg->timeout_specified_by_flag = FALSE; + + ecase_clear(&ec); + + /* Allocate an array to store the ecases. Force array even if 0 */ + /* cases. This will often be too large (i.e., if there are flags) */ + /* but won't affect anything. */ + + eg->ecd.cases = (struct ecase **)ckalloc( + sizeof(struct ecase *) * (1+(argc/2))); + + eg->ecd.count = 0; + + for (i = 0;i<argc;i++) { + arg = argv[i]; + + if (exp_flageq("timeout",arg,7)) { + ec.use = PAT_TIMEOUT; + } else if (exp_flageq("eof",arg,3)) { + ec.use = PAT_EOF; + } else if (exp_flageq("full_buffer",arg,11)) { + ec.use = PAT_FULLBUFFER; + } else if (exp_flageq("default",arg,7)) { + ec.use = PAT_DEFAULT; + } else if (exp_flageq("null",arg,4)) { + ec.use = PAT_NULL; + } else if (arg[0] == '-') { + arg++; + if (exp_flageq1('-',arg) /* "--" is deprecated */ + || exp_flageq("glob",arg,2)) { + i++; + /* assignment here is not actually necessary */ + /* since cases are initialized this way above */ + /* ec.use = PAT_GLOB; */ + } else if (exp_flageq("regexp",arg,2)) { + i++; + ec.use = PAT_RE; + TclRegError((char *)0); + if (!(ec.re = TclRegComp(argv[i]))) { + exp_error(interp,"bad regular expression: %s", + TclGetRegError()); + goto error; + } + } else if (exp_flageq("exact",arg,2)) { + i++; + ec.use = PAT_EXACT; + } else if (exp_flageq("notransfer",arg,1)) { + ec.transfer = 0; + continue; + } else if (exp_flageq("nocase",arg,3)) { + ec.Case = CASE_LOWER; + continue; + } else if (exp_flageq1('i',arg)) { + i++; + if (i>=argc) { + exp_error(interp,"-i requires following spawn_id"); + goto error; + } + + ec.i_list = exp_new_i_complex(interp,argv[i], + eg->duration,exp_indirect_update2); + + ec.i_list->cmdtype = eg->cmdtype; + + /* link new i_list to head of list */ + ec.i_list->next = eg->i_list; + eg->i_list = ec.i_list; + + continue; + } else if (exp_flageq("indices",arg,2)) { + ec.indices = TRUE; + continue; + } else if (exp_flageq("iwrite",arg,2)) { +/* ec.iwrite = TRUE;*/ + continue; + } else if (exp_flageq("iread",arg,2)) { + ec.iread = TRUE; + continue; + } else if (exp_flageq("timestamp",arg,2)) { + ec.timestamp = TRUE; + continue; + } else if (exp_flageq("timeout",arg,2)) { + i++; + if (i>=argc) { + exp_error(interp,"-timeout requires following # of seconds"); + goto error; + } + + eg->timeout = atoi(argv[i]); + eg->timeout_specified_by_flag = TRUE; + continue; + } else if (exp_flageq("nobrace",arg,7)) { + /* nobrace does nothing but take up space */ + /* on the command line which prevents */ + /* us from re-expanding any command lines */ + /* of one argument that looks like it should */ + /* be expanded to multiple arguments. */ + continue; + } else { + exp_error(interp,"usage: unrecognized flag <%s>",arg); + goto error; + } + } + + /* if no -i, use previous one */ + if (!ec.i_list) { + /* if no -i flag has occurred yet, use default */ + if (!eg->i_list) { + if (default_spawn_id != EXP_SPAWN_ID_BAD) { + eg->i_list = exp_new_i_simple(default_spawn_id,eg->duration); + } else { + /* it'll be checked later, if used */ + (void) exp_update_master(interp,&default_spawn_id,0,0); + eg->i_list = exp_new_i_simple(default_spawn_id,eg->duration); + } + } + ec.i_list = eg->i_list; + } + ec.i_list->ecount++; + + /* save original pattern spec */ + /* keywords such as "-timeout" are saved as patterns here */ + /* useful for debugging but not otherwise used */ + save_str(&ec.pat,argv[i],eg->duration == EXP_TEMPORARY); + save_str(&ec.body,argv[i+1],eg->duration == EXP_TEMPORARY); + + i++; + + *(eg->ecd.cases[eg->ecd.count] = ecase_new()) = ec; + + /* clear out for next set */ + ecase_clear(&ec); + + eg->ecd.count++; + } + + /* if no patterns at all have appeared force the current */ + /* spawn id to be added to list anyway */ + + if (eg->i_list == 0) { + if (default_spawn_id != EXP_SPAWN_ID_BAD) { + eg->i_list = exp_new_i_simple(default_spawn_id,eg->duration); + } else { + /* it'll be checked later, if used */ + (void) exp_update_master(interp,&default_spawn_id,0,0); + eg->i_list = exp_new_i_simple(default_spawn_id,eg->duration); + } + } + + return(TCL_OK); + + error: + /* very hard to free case_master_list here if it hasn't already */ + /* been attached to a case, ugh */ + + /* note that i_list must be avail to free ecases! */ + free_ecases(interp,eg,0); + + /* undo temporary ecase */ + /* free_ecase doesn't quite handle this right, so do it by hand */ + if (ec.re) ckfree((char *)ec.re); + if (eg->duration == EXP_PERMANENT) { + if (ec.pat) ckfree(ec.pat); + if (ec.body) ckfree(ec.body); + } + + if (eg->i_list) + exp_free_i(interp,eg->i_list,exp_indirect_update2); + return(TCL_ERROR); +} + +#define EXP_IS_DEFAULT(x) ((x) == EXP_TIMEOUT || (x) == EXP_EOF) + +static char yes[] = "yes\r\n"; +static char no[] = "no\r\n"; + +/* this describes status of a successful match */ +struct eval_out { + struct ecase *e; /* ecase that matched */ + struct exp_f *f; /* struct exp_f that matched */ + char *buffer; /* buffer that matched */ + int match; /* # of chars in buffer that matched */ + /* or # of chars in buffer at EOF */ +}; + + +/* like eval_cases, but handles only a single cases that needs a real */ +/* string match */ +/* returns EXP_X where X is MATCH, NOMATCH, FULLBUFFER, TCLERRROR */ +static int +eval_case_string(interp,e,m,o,last_f,last_case,suffix) +Tcl_Interp *interp; +struct ecase *e; +int m; +struct eval_out *o; /* 'output' - i.e., final case of interest */ +/* next two args are for debugging, when they change, reprint buffer */ +struct exp_f **last_f; +int *last_case; +char *suffix; +{ + struct exp_f *f = exp_fs + m; + char *buffer; + + /* if -nocase, use the lowerized buffer */ + buffer = ((e->Case == CASE_NORM)?f->buffer:f->lower); + + /* if master or case changed, redisplay debug-buffer */ + if ((f != *last_f) || e->Case != *last_case) { + debuglog("\r\nexpect%s: does \"%s\" (spawn_id %d) match %s ", + suffix, + dprintify(buffer),f-exp_fs, + pattern_style[e->use]); + *last_f = f; + *last_case = e->Case; + } + + if (e->use == PAT_RE) { + debuglog("\"%s\"? ",dprintify(e->pat)); + TclRegError((char *)0); + if (buffer && TclRegExec(e->re,buffer,buffer)) { + o->e = e; + o->match = e->re->endp[0]-buffer; + o->buffer = buffer; + o->f = f; + debuglog(yes); + return(EXP_MATCH); + } else { + debuglog(no); + if (TclGetRegError()) { + exp_error(interp,"-re failed: %s",TclGetRegError()); + return(EXP_TCLERROR); + } + } + } else if (e->use == PAT_GLOB) { + int match; /* # of chars that matched */ + + debuglog("\"%s\"? ",dprintify(e->pat)); + if (buffer && (-1 != (match = Exp_StringMatch( + buffer,e->pat,&e->simple_start)))) { + o->e = e; + o->match = match; + o->buffer = buffer; + o->f = f; + debuglog(yes); + return(EXP_MATCH); + } else debuglog(no); + } else if (e->use == PAT_EXACT) { + char *p = strstr(buffer,e->pat); + debuglog("\"%s\"? ",dprintify(e->pat)); + if (p) { + e->simple_start = p - buffer; + o->e = e; + o->match = strlen(e->pat); + o->buffer = buffer; + o->f = f; + debuglog(yes); + return(EXP_MATCH); + } else debuglog(no); + } else if (e->use == PAT_NULL) { + int i = 0; + debuglog("null? "); + for (;i<f->size;i++) { + if (buffer[i] == 0) { + o->e = e; + o->match = i+1; /* in this case, match is */ + /* just the # of chars + 1 */ + /* before the null */ + o->buffer = buffer; + o->f = f; + debuglog(yes); + return EXP_MATCH; + } + } + debuglog(no); + } else if ((f->size == f->msize) && (f->size > 0)) { + debuglog("%s? ",e->pat); + o->e = e; + o->match = f->umsize; + o->buffer = f->buffer; + o->f = f; + debuglog(yes); + return(EXP_FULLBUFFER); + } + return(EXP_NOMATCH); +} + +/* sets o.e if successfully finds a matching pattern, eof, timeout or deflt */ +/* returns original status arg or EXP_TCLERROR */ +static int +eval_cases(interp,eg,m,o,last_f,last_case,status,masters,mcount,suffix) +Tcl_Interp *interp; +struct exp_cmd_descriptor *eg; +int m; +struct eval_out *o; /* 'output' - i.e., final case of interest */ +/* next two args are for debugging, when they change, reprint buffer */ +struct exp_f **last_f; +int *last_case; +int status; +int *masters; +int mcount; +char *suffix; +{ + int i; + int em; /* master of ecase */ + struct ecase *e; + + if (o->e || status == EXP_TCLERROR || eg->ecd.count == 0) return(status); + + if (status == EXP_TIMEOUT) { + for (i=0;i<eg->ecd.count;i++) { + e = eg->ecd.cases[i]; + if (e->use == PAT_TIMEOUT || e->use == PAT_DEFAULT) { + o->e = e; + break; + } + } + return(status); + } else if (status == EXP_EOF) { + for (i=0;i<eg->ecd.count;i++) { + e = eg->ecd.cases[i]; + if (e->use == PAT_EOF || e->use == PAT_DEFAULT) { + struct exp_fd_list *fdl; + + for (fdl=e->i_list->fd_list; fdl ;fdl=fdl->next) { + em = fdl->fd; + if (em == EXP_SPAWN_ID_ANY || em == m) { + o->e = e; + return(status); + } + } + } + } + return(status); + } + + /* the top loops are split from the bottom loop only because I can't */ + /* split'em further. */ + + /* The bufferful condition does not prevent a pattern match from */ + /* occurring and vice versa, so it is scanned with patterns */ + for (i=0;i<eg->ecd.count;i++) { + struct exp_fd_list *fdl; + int j; + + e = eg->ecd.cases[i]; + if (e->use == PAT_TIMEOUT || + e->use == PAT_DEFAULT || + e->use == PAT_EOF) continue; + + for (fdl = e->i_list->fd_list; fdl; fdl = fdl->next) { + em = fdl->fd; + /* if em == EXP_SPAWN_ID_ANY, then user is explicitly asking */ + /* every case to be checked against every master */ + if (em == EXP_SPAWN_ID_ANY) { + /* test against each spawn_id */ + for (j=0;j<mcount;j++) { + status = eval_case_string(interp,e,masters[j],o,last_f,last_case,suffix); + if (status != EXP_NOMATCH) return(status); + } + } else { + /* reject things immediately from wrong spawn_id */ + if (em != m) continue; + + status = eval_case_string(interp,e,m,o,last_f,last_case,suffix); + if (status != EXP_NOMATCH) return(status); + } + } + } + return(EXP_NOMATCH); +} + +static void +ecases_remove_by_expi(interp,ecmd,exp_i) +Tcl_Interp *interp; +struct exp_cmd_descriptor *ecmd; +struct exp_i *exp_i; +{ + int i; + + /* delete every ecase dependent on it */ + for (i=0;i<ecmd->ecd.count;) { + struct ecase *e = ecmd->ecd.cases[i]; + if (e->i_list == exp_i) { + free_ecase(interp,e,0); + + /* shift remaining elements down */ + /* but only if there are any left */ + if (i+1 != ecmd->ecd.count) { + memcpy(&ecmd->ecd.cases[i], + &ecmd->ecd.cases[i+1], + ((ecmd->ecd.count - i) - 1) * + sizeof(struct exp_cmd_descriptor *)); + } + ecmd->ecd.count--; + if (0 == ecmd->ecd.count) { + ckfree((char *)ecmd->ecd.cases); + ecmd->ecd.cases = 0; + } + } else { + i++; + } + } +} + +/* remove exp_i from list */ +static void +exp_i_remove(interp,ei,exp_i) +Tcl_Interp *interp; +struct exp_i **ei; /* list to remove from */ +struct exp_i *exp_i; /* element to remove */ +{ + /* since it's in middle of list, free exp_i by hand */ + for (;*ei; ei = &(*ei)->next) { + if (*ei == exp_i) { + *ei = exp_i->next; + exp_i->next = 0; + exp_free_i(interp,exp_i,exp_indirect_update2); + break; + } + } +} + +/* remove exp_i from list and remove any dependent ecases */ +static void +exp_i_remove_with_ecases(interp,ecmd,exp_i) +Tcl_Interp *interp; +struct exp_cmd_descriptor *ecmd; +struct exp_i *exp_i; +{ + ecases_remove_by_expi(interp,ecmd,exp_i); + exp_i_remove(interp,&ecmd->i_list,exp_i); +} + +/* remove ecases tied to a single direct spawn id */ +static void +ecmd_remove_fd(interp,ecmd,m,direct) +Tcl_Interp *interp; +struct exp_cmd_descriptor *ecmd; +int m; +int direct; +{ + struct exp_i *exp_i, *next; + struct exp_fd_list **fdl; + + for (exp_i=ecmd->i_list;exp_i;exp_i=next) { + next = exp_i->next; + + if (!(direct & exp_i->direct)) continue; + + for (fdl = &exp_i->fd_list;*fdl;) { + if (m == ((*fdl)->fd)) { + struct exp_fd_list *tmp = *fdl; + *fdl = (*fdl)->next; + exp_free_fd_single(tmp); + + /* if last bg ecase, disarm spawn id */ + if ((ecmd->cmdtype == EXP_CMD_BG) && (m != EXP_SPAWN_ID_ANY)) { + exp_fs[m].bg_ecount--; + if (exp_fs[m].bg_ecount == 0) { + exp_disarm_background_filehandler(m); + exp_fs[m].bg_interp = 0; + } + } + + continue; + } + fdl = &(*fdl)->next; + } + + /* if left with no fds (and is direct), get rid of it */ + /* and any dependent ecases */ + if (exp_i->direct == EXP_DIRECT && !exp_i->fd_list) { + exp_i_remove_with_ecases(interp,ecmd,exp_i); + } + } +} + +/* this is called from exp_close to clean up the fd */ +void +exp_ecmd_remove_fd_direct_and_indirect(interp,m) +Tcl_Interp *interp; +int m; +{ + ecmd_remove_fd(interp,&exp_cmds[EXP_CMD_BEFORE],m,EXP_DIRECT|EXP_INDIRECT); + ecmd_remove_fd(interp,&exp_cmds[EXP_CMD_AFTER],m,EXP_DIRECT|EXP_INDIRECT); + ecmd_remove_fd(interp,&exp_cmds[EXP_CMD_BG],m,EXP_DIRECT|EXP_INDIRECT); + + /* force it - explanation in exp_tk.c where this func is defined */ + exp_disarm_background_filehandler_force(m); +} + +/* arm a list of background fd's */ +static void +fd_list_arm(interp,fdl) +Tcl_Interp *interp; +struct exp_fd_list *fdl; +{ + /* for each spawn id in list, arm if necessary */ + for (;fdl;fdl=fdl->next) { + int m = fdl->fd; + if (m == EXP_SPAWN_ID_ANY) continue; + + if (exp_fs[m].bg_ecount == 0) { + exp_arm_background_filehandler(m); + exp_fs[m].bg_interp = interp; + } + exp_fs[m].bg_ecount++; + } +} + +/* return TRUE if this ecase is used by this fd */ +static int +exp_i_uses_fd(exp_i,fd) +struct exp_i *exp_i; +int fd; +{ + struct exp_fd_list *fdp; + + for (fdp = exp_i->fd_list;fdp;fdp=fdp->next) { + if (fdp->fd == fd) return 1; + } + return 0; +} + +static void +ecase_append(interp,ec) +Tcl_Interp *interp; +struct ecase *ec; +{ + if (!ec->transfer) Tcl_AppendElement(interp,"-notransfer"); + if (ec->indices) Tcl_AppendElement(interp,"-indices"); +/* if (ec->iwrite) Tcl_AppendElement(interp,"-iwrite");*/ + if (!ec->Case) Tcl_AppendElement(interp,"-nocase"); + + if (ec->re) Tcl_AppendElement(interp,"-re"); + else if (ec->use == PAT_GLOB) Tcl_AppendElement(interp,"-gl"); + else if (ec->use == PAT_EXACT) Tcl_AppendElement(interp,"-ex"); + Tcl_AppendElement(interp,ec->pat); + Tcl_AppendElement(interp,ec->body?ec->body:""); +} + +/* append all ecases that match this exp_i */ +static void +ecase_by_exp_i_append(interp,ecmd,exp_i) +Tcl_Interp *interp; +struct exp_cmd_descriptor *ecmd; +struct exp_i *exp_i; +{ + int i; + for (i=0;i<ecmd->ecd.count;i++) { + if (ecmd->ecd.cases[i]->i_list == exp_i) { + ecase_append(interp,ecmd->ecd.cases[i]); + } + } +} + +static void +exp_i_append(interp,exp_i) +Tcl_Interp *interp; +struct exp_i *exp_i; +{ + Tcl_AppendElement(interp,"-i"); + if (exp_i->direct == EXP_INDIRECT) { + Tcl_AppendElement(interp,exp_i->variable); + } else { + struct exp_fd_list *fdp; + + /* if more than one element, add braces */ + if (exp_i->fd_list->next) + Tcl_AppendResult(interp," {",(char *)0); + + for (fdp = exp_i->fd_list;fdp;fdp=fdp->next) { + char buf[10]; /* big enough for a small int */ + sprintf(buf,"%d",fdp->fd); + Tcl_AppendElement(interp,buf); + } + + if (exp_i->fd_list->next) + Tcl_AppendResult(interp,"} ",(char *)0); + } +} + +#if 0 +/* delete ecases based on named -i descriptors */ +int +expect_delete(interp,ecmd,argc,argv) +Tcl_Interp *interp; +struct exp_cmd_descriptor *ecmd; +int argc; +char **argv; +{ + while (*argv) { + if (streq(argv[0],"-i") && argv[1]) { + iflag = argv[1]; + argc-=2; argv+=2; + } else if (streq(argv[0],"-all")) { + all = TRUE; + argc--; argv++; + } else if (streq(argv[0],"-noindirect")) { + direct &= ~EXP_INDIRECT; + argc--; argv++; + } else { + exp_error(interp,"usage: -delete [-all | -i spawn_id]\n"); + return TCL_ERROR; + } + } + + if (all) { + /* same logic as at end of regular expect cmd */ + free_ecases(interp,ecmd,0); + exp_free_i(interp,ecmd->i_list,exp_indirect_update2); + return TCL_OK; + } + + if (!iflag) { + if (0 == exp_update_master(interp,&m,0,0)) { + return TCL_ERROR; + } + } else if (Tcl_GetInt(interp,iflag,&m) != TCL_OK) { + /* handle as in indirect */ + + struct exp_i **old_i; + + for (old_i=&ecmd->i_list;*old_i;) { + struct exp_i *tmp; + + if ((*old_i)->direct == EXP_DIRECT) continue; + if (!streq((*old_i)->variable,iflag)) continue; + + ecases_remove_by_expi(interp,ecmd,*old_i); + + /* unlink from middle of list */ + tmp = *old_i; + *old_i = tmp->next; + tmp->next = 0; + exp_free_i(interp,tmp_i,exp_indirect_update2); + } else { + old_i = &(*old_i)->next; + } + return TCL_OK; + } + + /* delete ecases of this direct_fd */ + /* unfinish after this ... */ + for (exp_i=ecmd->i_list;exp_i;exp_i=exp_i->next) { + if (!(direct & exp_i->direct)) continue; + if (!exp_i_uses_fd(exp_i,m)) continue; + + /* delete each ecase that uses this exp_i */ + + + ecase_by_exp_i_append(interp,ecmd,exp_i); + } + + return TCL_OK; +} +#endif + +/* return current setting of the permanent expect_before/after/bg */ +int +expect_info(interp,ecmd,argc,argv) +Tcl_Interp *interp; +struct exp_cmd_descriptor *ecmd; +int argc; +char **argv; +{ + struct exp_i *exp_i; + int i; + int direct = EXP_DIRECT|EXP_INDIRECT; + char *iflag = 0; + int all = FALSE; /* report on all fds */ + int m; + + while (*argv) { + if (streq(argv[0],"-i") && argv[1]) { + iflag = argv[1]; + argc-=2; argv+=2; + } else if (streq(argv[0],"-all")) { + all = TRUE; + argc--; argv++; + } else if (streq(argv[0],"-noindirect")) { + direct &= ~EXP_INDIRECT; + argc--; argv++; + } else { + exp_error(interp,"usage: -info [-all | -i spawn_id]\n"); + return TCL_ERROR; + } + } + + if (all) { + /* avoid printing out -i when redundant */ + struct exp_i *previous = 0; + + for (i=0;i<ecmd->ecd.count;i++) { + if (previous != ecmd->ecd.cases[i]->i_list) { + exp_i_append(interp,ecmd->ecd.cases[i]->i_list); + previous = ecmd->ecd.cases[i]->i_list; + } + ecase_append(interp,ecmd->ecd.cases[i]); + } + return TCL_OK; + } + + if (!iflag) { + if (0 == exp_update_master(interp,&m,0,0)) { + return TCL_ERROR; + } + } else if (Tcl_GetInt(interp,iflag,&m) != TCL_OK) { + /* handle as in indirect */ + Tcl_ResetResult(interp); + for (i=0;i<ecmd->ecd.count;i++) { + if (ecmd->ecd.cases[i]->i_list->direct == EXP_INDIRECT && + streq(ecmd->ecd.cases[i]->i_list->variable,iflag)) { + ecase_append(interp,ecmd->ecd.cases[i]); + } + } + return TCL_OK; + } + + /* print ecases of this direct_fd */ + for (exp_i=ecmd->i_list;exp_i;exp_i=exp_i->next) { + if (!(direct & exp_i->direct)) continue; + if (!exp_i_uses_fd(exp_i,m)) continue; + ecase_by_exp_i_append(interp,ecmd,exp_i); + } + + return TCL_OK; +} + +/* Exp_ExpectGlobalCmd is invoked to process expect_before/after */ +/*ARGSUSED*/ +int +Exp_ExpectGlobalCmd(clientData, interp, argc, argv) +ClientData clientData; +Tcl_Interp *interp; +int argc; +char **argv; +{ + int result = TCL_OK; + struct exp_i *exp_i, **eip; + struct exp_fd_list *fdl; /* temp for interating over fd_list */ + struct exp_cmd_descriptor eg; + int count; + + struct exp_cmd_descriptor *ecmd = (struct exp_cmd_descriptor *) clientData; + + if ((argc == 2) && exp_one_arg_braced(argv[1])) { + return(exp_eval_with_one_arg(clientData,interp,argv)); + } else if ((argc == 3) && streq(argv[1],"-brace")) { + char *new_argv[2]; + new_argv[0] = argv[0]; + new_argv[1] = argv[2]; + return(exp_eval_with_one_arg(clientData,interp,new_argv)); + } + + if (argc > 1 && (argv[1][0] == '-')) { + if (exp_flageq("info",&argv[1][1],4)) { + return(expect_info(interp,ecmd,argc-2,argv+2)); + } + } + + exp_cmd_init(&eg,ecmd->cmdtype,EXP_PERMANENT); + + if (TCL_ERROR == parse_expect_args(interp,&eg,EXP_SPAWN_ID_BAD, + argc,argv)) { + return TCL_ERROR; + } + + /* + * visit each NEW direct exp_i looking for spawn ids. + * When found, remove them from any OLD exp_i's. + */ + + /* visit each exp_i */ + for (exp_i=eg.i_list;exp_i;exp_i=exp_i->next) { + if (exp_i->direct == EXP_INDIRECT) continue; + + /* for each spawn id, remove it from ecases */ + for (fdl=exp_i->fd_list;fdl;fdl=fdl->next) { + int m = fdl->fd; + + /* validate all input descriptors */ + if (m != EXP_SPAWN_ID_ANY) { + if (!exp_fd2f(interp,m,1,1,"expect")) { + result = TCL_ERROR; + goto cleanup; + } + } + + /* remove spawn id from exp_i */ + ecmd_remove_fd(interp,ecmd,m,EXP_DIRECT); + } + } + + /* + * For each indirect variable, release its old ecases and + * clean up the matching spawn ids. + * Same logic as in "expect_X delete" command. + */ + + for (exp_i=eg.i_list;exp_i;exp_i=exp_i->next) { + struct exp_i **old_i; + + if (exp_i->direct == EXP_DIRECT) continue; + + for (old_i = &ecmd->i_list;*old_i;) { + struct exp_i *tmp; + + if (((*old_i)->direct == EXP_DIRECT) || + (!streq((*old_i)->variable,exp_i->variable))) { + old_i = &(*old_i)->next; + continue; + } + + ecases_remove_by_expi(interp,ecmd,*old_i); + + /* unlink from middle of list */ + tmp = *old_i; + *old_i = tmp->next; + tmp->next = 0; + exp_free_i(interp,tmp,exp_indirect_update2); + } + + /* if new one has ecases, update it */ + if (exp_i->ecount) { + char *msg = exp_indirect_update1(interp,ecmd,exp_i); + if (msg) { + /* unusual way of handling error return */ + /* because of Tcl's variable tracing */ + strcpy(interp->result,msg); + result = TCL_ERROR; + goto indirect_update_abort; + } + } + } + /* empty i_lists have to be removed from global eg.i_list */ + /* before returning, even if during error */ + indirect_update_abort: + + /* + * New exp_i's that have 0 ecases indicate fd/vars to be deleted. + * Now that the deletions have been done, discard the new exp_i's. + */ + + for (exp_i=eg.i_list;exp_i;) { + struct exp_i *next = exp_i->next; + + if (exp_i->ecount == 0) { + exp_i_remove(interp,&eg.i_list,exp_i); + } + exp_i = next; + } + if (result == TCL_ERROR) goto cleanup; + + /* + * arm all new bg direct fds + */ + + if (ecmd->cmdtype == EXP_CMD_BG) { + for (exp_i=eg.i_list;exp_i;exp_i=exp_i->next) { + if (exp_i->direct == EXP_DIRECT) { + fd_list_arm(interp,exp_i->fd_list); + } + } + } + + /* + * now that old ecases are gone, add new ecases and exp_i's (both + * direct and indirect). + */ + + /* append ecases */ + + count = ecmd->ecd.count + eg.ecd.count; + if (eg.ecd.count) { + int start_index; /* where to add new ecases in old list */ + + if (ecmd->ecd.count) { + /* append to end */ + ecmd->ecd.cases = (struct ecase **)ckrealloc((char *)ecmd->ecd.cases, count * sizeof(struct ecase *)); + start_index = ecmd->ecd.count; + } else { + /* append to beginning */ + ecmd->ecd.cases = (struct ecase **)ckalloc(eg.ecd.count * sizeof(struct ecase *)); + start_index = 0; + } + memcpy(&ecmd->ecd.cases[start_index],eg.ecd.cases, + eg.ecd.count*sizeof(struct ecase *)); + ecmd->ecd.count = count; + } + + /* append exp_i's */ + for (eip = &ecmd->i_list;*eip;eip = &(*eip)->next) { + /* empty loop to get to end of list */ + } + /* *exp_i now points to end of list */ + + *eip = eg.i_list; /* connect new list to end of current list */ + + cleanup: + if (result == TCL_ERROR) { + /* in event of error, free any unreferenced ecases */ + /* but first, split up i_list so that exp_i's aren't */ + /* freed twice */ + + for (exp_i=eg.i_list;exp_i;) { + struct exp_i *next = exp_i->next; + exp_i->next = 0; + exp_i = next; + } + free_ecases(interp,&eg,1); + } else { + if (eg.ecd.cases) ckfree((char *)eg.ecd.cases); + } + + if (ecmd->cmdtype == EXP_CMD_BG) { + exp_background_filehandlers_run_all(); + } + + return(result); +} + +/* adjusts file according to user's size request */ +void +exp_adjust(f) +struct exp_f *f; +{ + int new_msize; + + /* get the latest buffer size. Double the user input for */ + /* two reasons. 1) Need twice the space in case the match */ + /* straddles two bufferfuls, 2) easier to hack the division */ + /* by two when shifting the buffers later on. The extra */ + /* byte in the malloc's is just space for a null we can slam on the */ + /* end. It makes the logic easier later. The -1 here is so that */ + /* requests actually come out to even/word boundaries (if user */ + /* gives "reasonable" requests) */ + new_msize = f->umsize*2 - 1; + if (new_msize != f->msize) { + if (!f->buffer) { + /* allocate buffer space for 1st time */ + f->buffer = ckalloc((unsigned)new_msize+1); + f->lower = ckalloc((unsigned)new_msize+1); + f->size = 0; + } else { + /* buffer already exists - resize */ + + /* if truncated, forget about some data */ + if (f->size > new_msize) { + /* copy end of buffer down */ + memmove(f->buffer,f->buffer+(f->size - new_msize),new_msize); + memmove(f->lower, f->lower +(f->size - new_msize),new_msize); + f->size = new_msize; + + f->key = expect_key++; + } + + f->buffer = ckrealloc(f->buffer,new_msize+1); + f->lower = ckrealloc(f->lower,new_msize+1); + } + f->msize = new_msize; + f->buffer[f->size] = '\0'; + f->lower[f->size] = '\0'; + } +} + + +/* + + expect_read() does the logical equivalent of a read() for the +expect command. This includes figuring out which descriptor should +be read from. + +The result of the read() is left in a spawn_id's buffer rather than +explicitly passing it back. Note that if someone else has modified a +buffer either before or while this expect is running (i.e., if we or +some event has called Tcl_Eval which did another expect/interact), +expect_read will also call this a successful read (for the purposes if +needing to pattern match against it). + +*/ +/* if it returns a negative number, it corresponds to a EXP_XXX result */ +/* if it returns a non-negative number, it means there is data */ +/* (0 means nothing new was actually read, but it should be looked at again) */ +int +expect_read(interp,masters,masters_max,m,timeout,key) +Tcl_Interp *interp; +int *masters; /* If 0, then m is already known and set. */ +int masters_max; /* If *masters is not-zero, then masters_max */ + /* is the number of masters. */ + /* If *masters is zero, then masters_max */ + /* is used as the mask (ready vs except). */ + /* Crude but simplifies the interface. */ +int *m; /* Out variable to leave new master. */ +int timeout; +int key; +{ + struct exp_f *f; + int cc; + int write_count; + int tcl_set_flags; /* if we have to discard chars, this tells */ + /* whether to show user locally or globally */ + + if (masters == 0) { + /* we already know the master, just find out what happened */ + cc = exp_get_next_event_info(interp,*m,masters_max); + tcl_set_flags = TCL_GLOBAL_ONLY; + } else { + cc = exp_get_next_event(interp,masters,masters_max,m,timeout,key); + tcl_set_flags = 0; + } + + if (cc == EXP_DATA_NEW) { + /* try to read it */ + + cc = exp_i_read(interp,*m,timeout,tcl_set_flags); + + /* the meaning of 0 from i_read means eof. Muck with it a */ + /* little, so that from now on it means "no new data arrived */ + /* but it should be looked at again anyway". */ + if (cc == 0) { + cc = EXP_EOF; + } else if (cc > 0) { + f = exp_fs + *m; + f->buffer[f->size += cc] = '\0'; + + /* strip parity if requested */ + if (f->parity == 0) { + /* do it from end backwards */ + char *p = f->buffer + f->size - 1; + int count = cc; + while (count--) { + *p-- &= 0x7f; + } + } + } /* else { + assert(cc < 0) in which case some sort of error was + encountered such as an interrupt with that forced an + error return + } */ + } else if (cc == EXP_DATA_OLD) { + f = exp_fs + *m; + cc = 0; + } else if (cc == EXP_RECONFIGURE) { + return EXP_RECONFIGURE; + } + + if (cc == EXP_ABEOF) { /* abnormal EOF */ + /* On many systems, ptys produce EIO upon EOF - sigh */ + if (i_read_errno == EIO) { + /* Sun, Cray, BSD, and others */ + cc = EXP_EOF; + } else if (i_read_errno == EINVAL) { + /* Solaris 2.4 occasionally returns this */ + cc = EXP_EOF; + } else { + if (i_read_errno == EBADF) { + exp_error(interp,"bad spawn_id (process died earlier?)"); + } else { + exp_error(interp,"i_read(spawn_id=%d): %s",*m, + Tcl_PosixError(interp)); + exp_close(interp,*m); + } + return(EXP_TCLERROR); + /* was goto error; */ + } + } + + /* EOF, TIMEOUT, and ERROR return here */ + /* In such cases, there is no need to update screen since, if there */ + /* was prior data read, it would have been sent to the screen when */ + /* it was read. */ + if (cc < 0) return (cc); + + /* update display */ + + if (f->size) write_count = f->size - f->printed; + else write_count = 0; + + if (write_count) { + if (logfile_all || (loguser && logfile)) { + fwrite(f->buffer + f->printed,1,write_count,logfile); + } + /* don't write to user if they're seeing it already, */ + /* that is, typing it! */ + if (loguser && !exp_is_stdinfd(*m) && !exp_is_devttyfd(*m)) + fwrite(f->buffer + f->printed, + 1,write_count,stdout); + if (debugfile) fwrite(f->buffer + f->printed, + 1,write_count,debugfile); + + /* remove nulls from input, since there is no way */ + /* for Tcl to deal with such strings. Doing it here */ + /* lets them be sent to the screen, just in case */ + /* they are involved in formatting operations */ + if (f->rm_nulls) { + f->size -= rm_nulls(f->buffer + f->printed,write_count); + } + f->buffer[f->size] = '\0'; + + /* copy to lowercase buffer */ + exp_lowmemcpy(f->lower+f->printed, + f->buffer+f->printed, + 1 + f->size - f->printed); + + f->printed = f->size; /* count'm even if not logging */ + } + return(cc); +} + +/* when buffer fills, copy second half over first and */ +/* continue, so we can do matches over multiple buffers */ +void +exp_buffer_shuffle(interp,f,save_flags,array_name,caller_name) +Tcl_Interp *interp; +struct exp_f *f; +int save_flags; +char *array_name; +char *caller_name; +{ + char spawn_id[10]; /* enough for a %d */ + char match_char; /* place to hold char temporarily */ + /* uprooted by a NULL */ + + int first_half = f->size/2; + int second_half = f->size - first_half; + + /* + * allow user to see data we are discarding + */ + + sprintf(spawn_id,"%d",f-exp_fs); + debuglog("%s: set %s(spawn_id) \"%s\"\r\n", + caller_name,array_name,dprintify(spawn_id)); + Tcl_SetVar2(interp,array_name,"spawn_id",spawn_id,save_flags); + + /* temporarily null-terminate buffer in middle */ + match_char = f->buffer[first_half]; + f->buffer[first_half] = 0; + + debuglog("%s: set %s(buffer) \"%s\"\r\n", + caller_name,array_name,dprintify(f->buffer)); + Tcl_SetVar2(interp,array_name,"buffer",f->buffer,save_flags); + + /* remove middle-null-terminator */ + f->buffer[first_half] = match_char; + + memcpy(f->buffer,f->buffer+first_half,second_half); + memcpy(f->lower, f->lower +first_half,second_half); + f->size = second_half; + f->printed -= first_half; + if (f->printed < 0) f->printed = 0; +} + +/* map EXP_ style return value to TCL_ style return value */ +/* not defined to work on TCL_OK */ +int +exp_tcl2_returnvalue(x) +int x; +{ + switch (x) { + case TCL_ERROR: return EXP_TCLERROR; + case TCL_RETURN: return EXP_TCLRET; + case TCL_BREAK: return EXP_TCLBRK; + case TCL_CONTINUE: return EXP_TCLCNT; + case EXP_CONTINUE: return EXP_TCLCNTEXP; + case EXP_CONTINUE_TIMER: return EXP_TCLCNTTIMER; + case EXP_TCL_RETURN: return EXP_TCLRETTCL; + } +} + +/* map from EXP_ style return value to TCL_ style return values */ +int +exp_2tcl_returnvalue(x) +int x; +{ + switch (x) { + case EXP_TCLERROR: return TCL_ERROR; + case EXP_TCLRET: return TCL_RETURN; + case EXP_TCLBRK: return TCL_BREAK; + case EXP_TCLCNT: return TCL_CONTINUE; + case EXP_TCLCNTEXP: return EXP_CONTINUE; + case EXP_TCLCNTTIMER: return EXP_CONTINUE_TIMER; + case EXP_TCLRETTCL: return EXP_TCL_RETURN; + } +} + +/* returns # of chars read or (non-positive) error of form EXP_XXX */ +/* returns 0 for end of file */ +/* If timeout is non-zero, set an alarm before doing the read, else assume */ +/* the read will complete immediately. */ +/*ARGSUSED*/ +static int +exp_i_read(interp,m,timeout,save_flags) +Tcl_Interp *interp; +int m; +int timeout; +int save_flags; +{ + struct exp_f *f; + int cc = EXP_TIMEOUT; + + f = exp_fs + m; + if (f->size == f->msize) + exp_buffer_shuffle(interp,f,save_flags,EXPECT_OUT,"expect"); + +#ifdef SIMPLE_EVENT + restart: + + alarm_fired = FALSE; + + if (timeout > -1) { + signal(SIGALRM,sigalarm_handler); + alarm((timeout > 0)?timeout:1); + } +#endif + + cc = read(m,f->buffer+f->size, f->msize-f->size); + i_read_errno = errno; + +#ifdef SIMPLE_EVENT + alarm(0); + + if (cc == -1) { + /* check if alarm went off */ + if (i_read_errno == EINTR) { + if (alarm_fired) { + return EXP_TIMEOUT; + } else { + if (Tcl_AsyncReady()) { + int rc = Tcl_AsyncInvoke(interp,TCL_OK); + if (rc != TCL_OK) return(exp_tcl2_returnvalue(rc)); + } + if (!f->valid) { + exp_error(interp,"spawn_id %d no longer valid",f-exp_fs); + return EXP_TCLERROR; + } + goto restart; + } + } + } +#endif + return(cc); +} + +/* variables predefined by expect are retrieved using this routine +which looks in the global space if they are not in the local space. +This allows the user to localize them if desired, and also to +avoid having to put "global" in procedure definitions. +*/ +char * +exp_get_var(interp,var) +Tcl_Interp *interp; +char *var; +{ + char *val; + + if (NULL != (val = Tcl_GetVar(interp,var,0 /* local */))) + return(val); + return(Tcl_GetVar(interp,var,TCL_GLOBAL_ONLY)); +} + +static int +get_timeout(interp) +Tcl_Interp *interp; +{ + static int timeout = INIT_EXPECT_TIMEOUT; + char *t; + + if (NULL != (t = exp_get_var(interp,EXPECT_TIMEOUT))) { + timeout = atoi(t); + } + return(timeout); +} + +/* make a copy of a linked list (1st arg) and attach to end of another (2nd +arg) */ +static int +update_expect_fds(i_list,fd_union) +struct exp_i *i_list; +struct exp_fd_list **fd_union; +{ + struct exp_i *p; + + /* for each i_list in an expect statement ... */ + for (p=i_list;p;p=p->next) { + struct exp_fd_list *fdl; + + /* for each fd in the i_list */ + for (fdl=p->fd_list;fdl;fdl=fdl->next) { + struct exp_fd_list *tmpfdl; + struct exp_fd_list *u; + + if (fdl->fd == EXP_SPAWN_ID_ANY) continue; + + /* check this one against all so far */ + for (u = *fd_union;u;u=u->next) { + if (fdl->fd == u->fd) goto found; + } + /* if not found, link in as head of list */ + tmpfdl = exp_new_fd(fdl->fd); + tmpfdl->next = *fd_union; + *fd_union = tmpfdl; + found:; + } + } + return TCL_OK; +} + +char * +exp_cmdtype_printable(cmdtype) +int cmdtype; +{ + switch (cmdtype) { + case EXP_CMD_FG: return("expect"); + case EXP_CMD_BG: return("expect_background"); + case EXP_CMD_BEFORE: return("expect_before"); + case EXP_CMD_AFTER: return("expect_after"); + } +#ifdef LINT + return("unknown expect command"); +#endif +} + +/* exp_indirect_update2 is called back via Tcl's trace handler whenever */ +/* an indirect spawn id list is changed */ +/*ARGSUSED*/ +static char * +exp_indirect_update2(clientData, interp, name1, name2, flags) +ClientData clientData; +Tcl_Interp *interp; /* Interpreter containing variable. */ +char *name1; /* Name of variable. */ +char *name2; /* Second part of variable name. */ +int flags; /* Information about what happened. */ +{ + char *msg; + + struct exp_i *exp_i = (struct exp_i *)clientData; + exp_configure_count++; + msg = exp_indirect_update1(interp,&exp_cmds[exp_i->cmdtype],exp_i); + + exp_background_filehandlers_run_all(); + + return msg; +} + +static char * +exp_indirect_update1(interp,ecmd,exp_i) +Tcl_Interp *interp; +struct exp_cmd_descriptor *ecmd; +struct exp_i *exp_i; +{ + struct exp_fd_list *fdl; /* temp for interating over fd_list */ + + /* + * disarm any fd's that lose all their ecases + */ + + if (ecmd->cmdtype == EXP_CMD_BG) { + /* clean up each spawn id used by this exp_i */ + for (fdl=exp_i->fd_list;fdl;fdl=fdl->next) { + int m = fdl->fd; + + if (m == EXP_SPAWN_ID_ANY) continue; + + /* silently skip closed or preposterous fds */ + /* since we're just disabling them anyway */ + /* preposterous fds will have been reported */ + /* by code in next section already */ + if (!exp_fd2f(interp,fdl->fd,1,0,"")) continue; + + exp_fs[m].bg_ecount--; + if (exp_fs[m].bg_ecount == 0) { + exp_disarm_background_filehandler(m); + exp_fs[m].bg_interp = 0; + } + } + } + + /* + * reread indirect variable + */ + + exp_i_update(interp,exp_i); + + /* + * check validity of all fd's in variable + */ + + for (fdl=exp_i->fd_list;fdl;fdl=fdl->next) { + /* validate all input descriptors */ + if (fdl->fd == EXP_SPAWN_ID_ANY) continue; + + if (!exp_fd2f(interp,fdl->fd,1,1, + exp_cmdtype_printable(ecmd->cmdtype))) { + static char msg[200]; + sprintf(msg,"%s from indirect variable (%s)", + interp->result,exp_i->variable); + return msg; + } + } + + /* for each spawn id in list, arm if necessary */ + if (ecmd->cmdtype == EXP_CMD_BG) { + fd_list_arm(interp,exp_i->fd_list); + } + + return (char *)0; +} + +void +exp_background_filehandlers_run_all() +{ + int m; + struct exp_f *f; + + /* kick off any that already have input waiting */ + for (m=0;m<=exp_fd_max;m++) { + f = exp_fs + m; + if (!f->valid) continue; + + /* is bg_interp the best way to check if armed? */ + if (f->bg_interp && (f->size > 0)) { + exp_background_filehandler((ClientData)f->fd_ptr,0/*ignored*/); + } + } +} + +/* this function is called from the background when input arrives */ +/*ARGSUSED*/ +void +exp_background_filehandler(clientData,mask) +ClientData clientData; +int mask; +{ + int m; + + Tcl_Interp *interp; + int cc; /* number of chars returned in a single read */ + /* or negative EXP_whatever */ + struct exp_f *f; /* file associated with master */ + + int i; /* trusty temporary */ + + struct eval_out eo; /* final case of interest */ + struct exp_f *last_f; /* for differentiating when multiple f's */ + /* to print out better debugging messages */ + int last_case; /* as above but for case */ + + /* restore our environment */ + m = *(int *)clientData; + f = exp_fs + m; + interp = f->bg_interp; + + /* temporarily prevent this handler from being invoked again */ + exp_block_background_filehandler(m); + + /* if mask == 0, then we've been called because the patterns changed */ + /* not because the waiting data has changed, so don't actually do */ + /* any I/O */ + + if (mask == 0) { + cc = 0; + } else { + cc = expect_read(interp,(int *)0,mask,&m,EXP_TIME_INFINITY,0); + } + +do_more_data: + eo.e = 0; /* no final case yet */ + eo.f = 0; /* no final file selected yet */ + eo.match = 0; /* nothing matched yet */ + + /* force redisplay of buffer when debugging */ + last_f = 0; + + if (cc == EXP_EOF) { + /* do nothing */ + } else if (cc < 0) { /* EXP_TCLERROR or any other weird value*/ + goto finish; + /* if we were going to do this right, we should */ + /* differentiate between things like HP ioctl-open-traps */ + /* that fall out here and should rightfully be ignored */ + /* and real errors that should be reported. Come to */ + /* think of it, the only errors will come from HP */ + /* ioctl handshake botches anyway. */ + } else { + /* normal case, got data */ + /* new data if cc > 0, same old data if cc == 0 */ + + /* below here, cc as general status */ + cc = EXP_NOMATCH; + } + + cc = eval_cases(interp,&exp_cmds[EXP_CMD_BEFORE], + m,&eo,&last_f,&last_case,cc,&m,1,"_background"); + cc = eval_cases(interp,&exp_cmds[EXP_CMD_BG], + m,&eo,&last_f,&last_case,cc,&m,1,"_background"); + cc = eval_cases(interp,&exp_cmds[EXP_CMD_AFTER], + m,&eo,&last_f,&last_case,cc,&m,1,"_background"); + if (cc == EXP_TCLERROR) { + /* only likely problem here is some internal regexp botch */ + Tcl_BackgroundError(interp); + goto finish; + } + /* special eof code that cannot be done in eval_cases */ + /* or above, because it would then be executed several times */ + if (cc == EXP_EOF) { + eo.f = exp_fs + m; + eo.match = eo.f->size; + eo.buffer = eo.f->buffer; + debuglog("expect_background: read eof\r\n"); + goto matched; + } + if (!eo.e) { + /* if we get here, there must not have been a match */ + goto finish; + } + + matched: +#define out(i,val) debuglog("expect_background: set %s(%s) \"%s\"\r\n",EXPECT_OUT,i, \ + dprintify(val)); \ + Tcl_SetVar2(interp,EXPECT_OUT,i,val,TCL_GLOBAL_ONLY); + { +/* int iwrite = FALSE;*/ /* write spawn_id? */ + char *body = 0; + char *buffer; /* pointer to normal or lowercased data */ + struct ecase *e = 0; /* points to current ecase */ + int match = -1; /* characters matched */ + char match_char; /* place to hold char temporarily */ + /* uprooted by a NULL */ + char *eof_body = 0; + + if (eo.e) { + e = eo.e; + body = e->body; +/* iwrite = e->iwrite;*/ + if (cc != EXP_TIMEOUT) { + f = eo.f; + match = eo.match; + buffer = eo.buffer; + } +#if 0 + if (e->timestamp) { + char value[20]; + + time(¤t_time); + elapsed_time = current_time - start_time; + elapsed_time_total = current_time - start_time_total; + sprintf(value,"%d",elapsed_time); + out("seconds",value); + sprintf(value,"%d",elapsed_time_total); + out("seconds_total",value); + /* deprecated */ + exp_timestamp(interp,¤t_time,EXPECT_OUT); + } +#endif + } else if (cc == EXP_EOF) { + /* read an eof but no user-supplied case */ + f = eo.f; + match = eo.match; + buffer = eo.buffer; + } + + if (match >= 0) { + char name[20], value[20]; + + if (e && e->use == PAT_RE) { + regexp *re = e->re; + + for (i=0;i<NSUBEXP;i++) { + int offset; + + if (re->startp[i] == 0) continue; + + if (e->indices) { + /* start index */ + sprintf(name,"%d,start",i); + offset = re->startp[i]-buffer; + sprintf(value,"%d",offset); + out(name,value); + + /* end index */ + sprintf(name,"%d,end",i); + sprintf(value,"%d", + re->endp[i]-buffer-1); + out(name,value); + } + + /* string itself */ + sprintf(name,"%d,string",i); + + /* temporarily null-terminate in */ + /* middle */ + match_char = *re->endp[i]; + *re->endp[i] = 0; + out(name,re->startp[i]); + *re->endp[i] = match_char; + } + /* redefine length of string that */ + /* matched for later extraction */ + match = re->endp[0]-buffer; + } else if (e && (e->use == PAT_GLOB || e->use == PAT_EXACT)) { + char *str; + + if (e->indices) { + /* start index */ + sprintf(value,"%d",e->simple_start); + out("0,start",value); + + /* end index */ + sprintf(value,"%d",e->simple_start + match - 1); + out("0,end",value); + } + + /* string itself */ + str = f->buffer + e->simple_start; + /* temporarily null-terminate in middle */ + match_char = str[match]; + str[match] = 0; + out("0,string",str); + str[match] = match_char; + + /* redefine length of string that */ + /* matched for later extraction */ + match += e->simple_start; + } else if (e && e->use == PAT_NULL && e->indices) { + /* start index */ + sprintf(value,"%d",match-1); + out("0,start",value); + /* end index */ + sprintf(value,"%d",match-1); + out("0,end",value); + } else if (e && e->use == PAT_FULLBUFFER) { + debuglog("expect_background: full buffer\r\n"); + } + } + + /* this is broken out of (match > 0) (above) since it can */ + /* that an EOF occurred with match == 0 */ + if (eo.f) { + char spawn_id[10]; /* enough for a %d */ + +/* if (iwrite) {*/ + sprintf(spawn_id,"%d",f-exp_fs); + out("spawn_id",spawn_id); +/* }*/ + + /* save buf[0..match] */ + /* temporarily null-terminate string in middle */ + match_char = f->buffer[match]; + f->buffer[match] = 0; + out("buffer",f->buffer); + /* remove middle-null-terminator */ + f->buffer[match] = match_char; + + /* "!e" means no case matched - transfer by default */ + if (!e || e->transfer) { + /* delete matched chars from input buffer */ + f->size -= match; + f->printed -= match; + if (f->size != 0) { + memmove(f->buffer,f->buffer+match,f->size); + memmove(f->lower,f->lower+match,f->size); + } + f->buffer[f->size] = '\0'; + f->lower[f->size] = '\0'; + } + + if (cc == EXP_EOF) { + /* exp_close() deletes all background bodies */ + /* so save eof body temporarily */ + if (body) { + eof_body = ckalloc(strlen(body)+1); + strcpy(eof_body,body); + body = eof_body; + } + + exp_close(interp,f - exp_fs); + } + + } + + if (body) { + int result = Tcl_GlobalEval(interp,body); + if (result != TCL_OK) Tcl_BackgroundError(interp); + + if (eof_body) ckfree(eof_body); + } + + + /* + * Event handler will not call us back if there is more input + * pending but it has already arrived. bg_status will be + * "blocked" only if armed. + */ + if (exp_fs[m].valid && (exp_fs[m].bg_status == blocked) + && (f->size > 0)) { + cc = f->size; + goto do_more_data; + } + } + finish: + /* fd could have gone away, so check before using */ + if (exp_fs[m].valid) + exp_unblock_background_filehandler(m); +} +#undef out + +/*ARGSUSED*/ +int +Exp_ExpectCmd(clientData, interp, argc, argv) +ClientData clientData; +Tcl_Interp *interp; +int argc; +char **argv; +{ + int cc; /* number of chars returned in a single read */ + /* or negative EXP_whatever */ + int m; /* before doing an actual read, attempt */ + /* to match upon any spawn_id */ + struct exp_f *f; /* file associated with master */ + + int i; /* trusty temporary */ + struct exp_cmd_descriptor eg; + struct exp_fd_list *fd_list; /* list of masters to watch */ + struct exp_fd_list *fdl; /* temp for interating over fd_list */ + int *masters; /* array of masters to watch */ + int mcount; /* number of masters to watch */ + + struct eval_out eo; /* final case of interest */ + + int result; /* Tcl result */ + + time_t start_time_total;/* time at beginning of this procedure */ + time_t start_time = 0; /* time when restart label hit */ + time_t current_time = 0;/* current time (when we last looked)*/ + time_t end_time; /* future time at which to give up */ + time_t elapsed_time_total;/* time from now to match/fail/timeout */ + time_t elapsed_time; /* time from restart to (ditto) */ + + struct exp_f *last_f; /* for differentiating when multiple f's */ + /* to print out better debugging messages */ + int last_case; /* as above but for case */ + int first_time = 1; /* if not "restarted" */ + + int key; /* identify this expect command instance */ + int configure_count; /* monitor exp_configure_count */ + + int timeout; /* seconds */ + int remtime; /* remaining time in timeout */ + int reset_timer; /* should timer be reset after continue? */ + + if ((argc == 2) && exp_one_arg_braced(argv[1])) { + return(exp_eval_with_one_arg(clientData,interp,argv)); + } else if ((argc == 3) && streq(argv[1],"-brace")) { + char *new_argv[2]; + new_argv[0] = argv[0]; + new_argv[1] = argv[2]; + return(exp_eval_with_one_arg(clientData,interp,new_argv)); + } + + time(&start_time_total); + start_time = start_time_total; + reset_timer = TRUE; + + /* make arg list for processing cases */ + /* do it dynamically, since expect can be called recursively */ + + exp_cmd_init(&eg,EXP_CMD_FG,EXP_TEMPORARY); + fd_list = 0; + masters = 0; + if (TCL_ERROR == parse_expect_args(interp,&eg, + *(int *)clientData,argc,argv)) + return TCL_ERROR; + + restart_with_update: + /* validate all descriptors */ + /* and flatten fds into array */ + + if ((TCL_ERROR == update_expect_fds(exp_cmds[EXP_CMD_BEFORE].i_list,&fd_list)) + || (TCL_ERROR == update_expect_fds(exp_cmds[EXP_CMD_AFTER].i_list, &fd_list)) + || (TCL_ERROR == update_expect_fds(eg.i_list,&fd_list))) { + result = TCL_ERROR; + goto cleanup; + } + + /* declare ourselves "in sync" with external view of close/indirect */ + configure_count = exp_configure_count; + + /* count and validate fd_list */ + mcount = 0; + for (fdl=fd_list;fdl;fdl=fdl->next) { + mcount++; + /* validate all input descriptors */ + if (!exp_fd2f(interp,fdl->fd,1,1,"expect")) { + result = TCL_ERROR; + goto cleanup; + } + } + + /* make into an array */ + masters = (int *)ckalloc(mcount * sizeof(int)); + for (fdl=fd_list,i=0;fdl;fdl=fdl->next,i++) { + masters[i] = fdl->fd; + } + + restart: + if (first_time) first_time = 0; + else time(&start_time); + + if (eg.timeout_specified_by_flag) { + timeout = eg.timeout; + } else { + /* get the latest timeout */ + timeout = get_timeout(interp); + } + + key = expect_key++; + + result = TCL_OK; + last_f = 0; + + /* end of restart code */ + + eo.e = 0; /* no final case yet */ + eo.f = 0; /* no final file selected yet */ + eo.match = 0; /* nothing matched yet */ + + /* timeout code is a little tricky, be very careful changing it */ + if (timeout != EXP_TIME_INFINITY) { + /* if exp_continue -continue_timer, do not update end_time */ + if (reset_timer) { + time(¤t_time); + end_time = current_time + timeout; + } else { + reset_timer = TRUE; + } + } + + /* remtime and current_time updated at bottom of loop */ + remtime = timeout; + + for (;;) { + if ((timeout != EXP_TIME_INFINITY) && (remtime < 0)) { + cc = EXP_TIMEOUT; + } else { + cc = expect_read(interp,masters,mcount,&m,remtime,key); + } + + /*SUPPRESS 530*/ + if (cc == EXP_EOF) { + /* do nothing */ + } else if (cc == EXP_TIMEOUT) { + debuglog("expect: timed out\r\n"); + } else if (cc == EXP_RECONFIGURE) { + reset_timer = FALSE; + goto restart_with_update; + } else if (cc < 0) { /* EXP_TCLERROR or any other weird value*/ + goto error; + } else { + /* new data if cc > 0, same old data if cc == 0 */ + + f = exp_fs + m; + + /* below here, cc as general status */ + cc = EXP_NOMATCH; + + /* force redisplay of buffer when debugging */ + last_f = 0; + } + + cc = eval_cases(interp,&exp_cmds[EXP_CMD_BEFORE], + m,&eo,&last_f,&last_case,cc,masters,mcount,""); + cc = eval_cases(interp,&eg, + m,&eo,&last_f,&last_case,cc,masters,mcount,""); + cc = eval_cases(interp,&exp_cmds[EXP_CMD_AFTER], + m,&eo,&last_f,&last_case,cc,masters,mcount,""); + if (cc == EXP_TCLERROR) goto error; + /* special eof code that cannot be done in eval_cases */ + /* or above, because it would then be executed several times */ + if (cc == EXP_EOF) { + eo.f = exp_fs + m; + eo.match = eo.f->size; + eo.buffer = eo.f->buffer; + debuglog("expect: read eof\r\n"); + break; + } else if (cc == EXP_TIMEOUT) break; + /* break if timeout or eof and failed to find a case for it */ + + if (eo.e) break; + + /* no match was made with current data, force a read */ + f->force_read = TRUE; + + if (timeout != EXP_TIME_INFINITY) { + time(¤t_time); + remtime = end_time - current_time; + } + } + + goto done; + +error: + result = exp_2tcl_returnvalue(cc); + done: +#define out(i,val) debuglog("expect: set %s(%s) \"%s\"\r\n",EXPECT_OUT,i, \ + dprintify(val)); \ + Tcl_SetVar2(interp,EXPECT_OUT,i,val,0); + + if (result != TCL_ERROR) { +/* int iwrite = FALSE;*/ /* write spawn_id? */ + char *body = 0; + char *buffer; /* pointer to normal or lowercased data */ + struct ecase *e = 0; /* points to current ecase */ + int match = -1; /* characters matched */ + char match_char; /* place to hold char temporarily */ + /* uprooted by a NULL */ + char *eof_body = 0; + + if (eo.e) { + e = eo.e; + body = e->body; +/* iwrite = e->iwrite;*/ + if (cc != EXP_TIMEOUT) { + f = eo.f; + match = eo.match; + buffer = eo.buffer; + } + if (e->timestamp) { + char value[20]; + + time(¤t_time); + elapsed_time = current_time - start_time; + elapsed_time_total = current_time - start_time_total; + sprintf(value,"%d",elapsed_time); + out("seconds",value); + sprintf(value,"%d",elapsed_time_total); + out("seconds_total",value); + + /* deprecated */ + exp_timestamp(interp,¤t_time,EXPECT_OUT); + } + } else if (cc == EXP_EOF) { + /* read an eof but no user-supplied case */ + f = eo.f; + match = eo.match; + buffer = eo.buffer; + } + + if (match >= 0) { + char name[20], value[20]; + + if (e && e->use == PAT_RE) { + regexp *re = e->re; + + for (i=0;i<NSUBEXP;i++) { + int offset; + + if (re->startp[i] == 0) continue; + + if (e->indices) { + /* start index */ + sprintf(name,"%d,start",i); + offset = re->startp[i]-buffer; + sprintf(value,"%d",offset); + out(name,value); + + /* end index */ + sprintf(name,"%d,end",i); + sprintf(value,"%d", + re->endp[i]-buffer-1); + out(name,value); + } + + /* string itself */ + sprintf(name,"%d,string",i); + + /* temporarily null-terminate in */ + /* middle */ + match_char = *re->endp[i]; + *re->endp[i] = 0; + out(name,re->startp[i]); + *re->endp[i] = match_char; + } + /* redefine length of string that */ + /* matched for later extraction */ + match = re->endp[0]-buffer; + } else if (e && (e->use == PAT_GLOB || e->use == PAT_EXACT)) { + char *str; + + if (e->indices) { + /* start index */ + sprintf(value,"%d",e->simple_start); + out("0,start",value); + + /* end index */ + sprintf(value,"%d",e->simple_start + match - 1); + out("0,end",value); + } + + /* string itself */ + str = f->buffer + e->simple_start; + /* temporarily null-terminate in middle */ + match_char = str[match]; + str[match] = 0; + out("0,string",str); + str[match] = match_char; + + /* redefine length of string that */ + /* matched for later extraction */ + match += e->simple_start; + } else if (e && e->use == PAT_NULL && e->indices) { + /* start index */ + sprintf(value,"%d",match-1); + out("0,start",value); + /* end index */ + sprintf(value,"%d",match-1); + out("0,end",value); + } else if (e && e->use == PAT_FULLBUFFER) { + debuglog("expect: full buffer\r\n"); + } + } + + /* this is broken out of (match > 0) (above) since it can */ + /* that an EOF occurred with match == 0 */ + if (eo.f) { + char spawn_id[10]; /* enough for a %d */ + +/* if (iwrite) {*/ + sprintf(spawn_id,"%d",f-exp_fs); + out("spawn_id",spawn_id); +/* }*/ + + /* save buf[0..match] */ + /* temporarily null-terminate string in middle */ + match_char = f->buffer[match]; + f->buffer[match] = 0; + out("buffer",f->buffer); + /* remove middle-null-terminator */ + f->buffer[match] = match_char; + + /* "!e" means no case matched - transfer by default */ + if (!e || e->transfer) { + /* delete matched chars from input buffer */ + f->size -= match; + f->printed -= match; + if (f->size != 0) { + memmove(f->buffer,f->buffer+match,f->size); + memmove(f->lower,f->lower+match,f->size); + } + f->buffer[f->size] = '\0'; + f->lower[f->size] = '\0'; + } + + if (cc == EXP_EOF) { + /* exp_close() deletes all background bodies */ + /* so save eof body temporarily */ + if (body) { + eof_body = ckalloc(strlen(body)+1); + strcpy(eof_body,body); + body = eof_body; + } + + exp_close(interp,f - exp_fs); + } + + } + + if (body) { + result = Tcl_Eval(interp,body); + + if (eof_body) ckfree(eof_body); + } + } + + cleanup: + if (result == EXP_CONTINUE_TIMER) { + reset_timer = FALSE; + result = EXP_CONTINUE; + } + + if ((result == EXP_CONTINUE) + && (configure_count == exp_configure_count)) { + debuglog("expect: continuing expect\r\n"); + goto restart; + } + + if (fd_list) { + exp_free_fd(fd_list); + fd_list = 0; + } + if (masters) { + ckfree((char *)masters); + masters = 0; + } + + if (result == EXP_CONTINUE) { + debuglog("expect: continuing expect after update\r\n"); + goto restart_with_update; + } + + free_ecases(interp,&eg,0); /* requires i_lists to be avail */ + exp_free_i(interp,eg.i_list,exp_indirect_update2); + + return(result); +} +#undef out + +/* beginning of deprecated code */ + +#define out(elt) Tcl_SetVar2(interp,array,elt,ascii,0); +void +exp_timestamp(interp,timeval,array) +Tcl_Interp *interp; +time_t *timeval; +char *array; +{ + struct tm *tm; + char *ascii; + + tm = localtime(timeval); /* split */ + ascii = asctime(tm); /* print */ + ascii[24] = '\0'; /* zap trailing \n */ + + out("timestamp"); + + sprintf(ascii,"%ld",*timeval); + out("epoch"); + + sprintf(ascii,"%d",tm->tm_sec); + out("sec"); + sprintf(ascii,"%d",tm->tm_min); + out("min"); + sprintf(ascii,"%d",tm->tm_hour); + out("hour"); + sprintf(ascii,"%d",tm->tm_mday); + out("mday"); + sprintf(ascii,"%d",tm->tm_mon); + out("mon"); + sprintf(ascii,"%d",tm->tm_year); + out("year"); + sprintf(ascii,"%d",tm->tm_wday); + out("wday"); + sprintf(ascii,"%d",tm->tm_yday); + out("yday"); + sprintf(ascii,"%d",tm->tm_isdst); + out("isdst"); +} +/* end of deprecated code */ + +/*ARGSUSED*/ +static int +Exp_TimestampCmd(clientData, interp, argc, argv) +ClientData clientData; +Tcl_Interp *interp; +int argc; +char **argv; +{ + char *format = 0; + time_t seconds = -1; + int gmt = FALSE; /* local time by default */ + struct tm *tm; + Tcl_DString dstring; + + argc--; argv++; + + while (*argv) { + if (streq(*argv,"-format")) { + argc--; argv++; + if (!*argv) goto usage_error; + format = *argv; + argc--; argv++; + } else if (streq(*argv,"-seconds")) { + argc--; argv++; + if (!*argv) goto usage_error; + seconds = atoi(*argv); + argc--; argv++; + } else if (streq(*argv,"-gmt")) { + gmt = TRUE; + argc--; argv++; + } else break; + } + + if (argc) goto usage_error; + + if (seconds == -1) { + time(&seconds); + } + + Tcl_DStringInit(&dstring); + + if (format) { + if (gmt) { + tm = gmtime(&seconds); + } else { + tm = localtime(&seconds); + } +/* exp_strftime(interp->result,TCL_RESULT_SIZE,format,tm);*/ + exp_strftime(format,tm,&dstring); + Tcl_DStringResult(interp,&dstring); + } else { + sprintf(interp->result,"%ld",seconds); + } + + return TCL_OK; + usage_error: + exp_error(interp,"args: [-seconds #] [-format format]"); + return TCL_ERROR; + +} + +/* lowmemcpy - like memcpy but it lowercases result */ +void +exp_lowmemcpy(dest,src,n) +char *dest; +char *src; +int n; +{ + for (;n>0;n--) { + *dest = ((isascii(*src) && isupper(*src))?tolower(*src):*src); + src++; dest++; + } +} + +/*ARGSUSED*/ +int +Exp_MatchMaxCmd(clientData,interp,argc,argv) +ClientData clientData; +Tcl_Interp *interp; +int argc; +char **argv; +{ + int size = -1; + int m = -1; + struct exp_f *f; + int Default = FALSE; + + argc--; argv++; + + for (;argc>0;argc--,argv++) { + if (streq(*argv,"-d")) { + Default = TRUE; + } else if (streq(*argv,"-i")) { + argc--;argv++; + if (argc < 1) { + exp_error(interp,"-i needs argument"); + return(TCL_ERROR); + } + m = atoi(*argv); + } else break; + } + + if (!Default) { + if (m == -1) { + if (!(f = exp_update_master(interp,&m,0,0))) + return(TCL_ERROR); + } else { + if (!(f = exp_fd2f(interp,m,0,0,"match_max"))) + return(TCL_ERROR); + } + } else if (m != -1) { + exp_error(interp,"cannot do -d and -i at the same time"); + return(TCL_ERROR); + } + + if (argc == 0) { + if (Default) { + size = exp_default_match_max; + } else { + size = f->umsize; + } + sprintf(interp->result,"%d",size); + return(TCL_OK); + } + + if (argc > 1) { + exp_error(interp,"too many arguments"); + return(TCL_OK); + } + + /* all that's left is to set the size */ + size = atoi(argv[0]); + if (size <= 0) { + exp_error(interp,"must be positive"); + return(TCL_ERROR); + } + + if (Default) exp_default_match_max = size; + else f->umsize = size; + + return(TCL_OK); +} + +/*ARGSUSED*/ +int +Exp_RemoveNullsCmd(clientData,interp,argc,argv) +ClientData clientData; +Tcl_Interp *interp; +int argc; +char **argv; +{ + int value = -1; + int m = -1; + struct exp_f *f; + int Default = FALSE; + + argc--; argv++; + + for (;argc>0;argc--,argv++) { + if (streq(*argv,"-d")) { + Default = TRUE; + } else if (streq(*argv,"-i")) { + argc--;argv++; + if (argc < 1) { + exp_error(interp,"-i needs argument"); + return(TCL_ERROR); + } + m = atoi(*argv); + } else break; + } + + if (!Default) { + if (m == -1) { + if (!(f = exp_update_master(interp,&m,0,0))) + return(TCL_ERROR); + } else { + if (!(f = exp_fd2f(interp,m,0,0,"remove_nulls"))) + return(TCL_ERROR); + } + } else if (m != -1) { + exp_error(interp,"cannot do -d and -i at the same time"); + return(TCL_ERROR); + } + + if (argc == 0) { + if (Default) { + value = exp_default_match_max; + } else { + value = f->rm_nulls; + } + sprintf(interp->result,"%d",value); + return(TCL_OK); + } + + if (argc > 1) { + exp_error(interp,"too many arguments"); + return(TCL_OK); + } + + /* all that's left is to set the value */ + value = atoi(argv[0]); + if (value != 0 && value != 1) { + exp_error(interp,"must be 0 or 1"); + return(TCL_ERROR); + } + + if (Default) exp_default_rm_nulls = value; + else f->rm_nulls = value; + + return(TCL_OK); +} + +/*ARGSUSED*/ +int +Exp_ParityCmd(clientData,interp,argc,argv) +ClientData clientData; +Tcl_Interp *interp; +int argc; +char **argv; +{ + int parity; + int m = -1; + struct exp_f *f; + int Default = FALSE; + + argc--; argv++; + + for (;argc>0;argc--,argv++) { + if (streq(*argv,"-d")) { + Default = TRUE; + } else if (streq(*argv,"-i")) { + argc--;argv++; + if (argc < 1) { + exp_error(interp,"-i needs argument"); + return(TCL_ERROR); + } + m = atoi(*argv); + } else break; + } + + if (!Default) { + if (m == -1) { + if (!(f = exp_update_master(interp,&m,0,0))) + return(TCL_ERROR); + } else { + if (!(f = exp_fd2f(interp,m,0,0,"parity"))) + return(TCL_ERROR); + } + } else if (m != -1) { + exp_error(interp,"cannot do -d and -i at the same time"); + return(TCL_ERROR); + } + + if (argc == 0) { + if (Default) { + parity = exp_default_parity; + } else { + parity = f->parity; + } + sprintf(interp->result,"%d",parity); + return(TCL_OK); + } + + if (argc > 1) { + exp_error(interp,"too many arguments"); + return(TCL_OK); + } + + /* all that's left is to set the parity */ + parity = atoi(argv[0]); + + if (Default) exp_default_parity = parity; + else f->parity = parity; + + return(TCL_OK); +} + +#if DEBUG_PERM_ECASES +/* This big chunk of code is just for debugging the permanent */ +/* expect cases */ +void +exp_fd_print(fdl) +struct exp_fd_list *fdl; +{ + if (!fdl) return; + printf("%d ",fdl->fd); + exp_fd_print(fdl->next); +} + +void +exp_i_print(exp_i) +struct exp_i *exp_i; +{ + if (!exp_i) return; + printf("exp_i %x",exp_i); + printf((exp_i->direct == EXP_DIRECT)?" direct":" indirect"); + printf((exp_i->duration == EXP_PERMANENT)?" perm":" tmp"); + printf(" ecount = %d\n",exp_i->ecount); + printf("variable %s, value %s\n", + ((exp_i->variable)?exp_i->variable:"--"), + ((exp_i->value)?exp_i->value:"--")); + printf("fds: "); + exp_fd_print(exp_i->fd_list); printf("\n"); + exp_i_print(exp_i->next); +} + +void +exp_ecase_print(ecase) +struct ecase *ecase; +{ + printf("pat <%s>\n",ecase->pat); + printf("exp_i = %x\n",ecase->i_list); +} + +void +exp_ecases_print(ecd) +struct exp_cases_descriptor *ecd; +{ + int i; + + printf("%d cases\n",ecd->count); + for (i=0;i<ecd->count;i++) exp_ecase_print(ecd->cases[i]); +} + +void +exp_cmd_print(ecmd) +struct exp_cmd_descriptor *ecmd; +{ + printf("expect cmd type: %17s",exp_cmdtype_printable(ecmd->cmdtype)); + printf((ecmd->duration==EXP_PERMANENT)?" perm ": "tmp "); + /* printdict */ + exp_ecases_print(&ecmd->ecd); + exp_i_print(ecmd->i_list); +} + +void +exp_cmds_print() +{ + exp_cmd_print(&exp_cmds[EXP_CMD_BEFORE]); + exp_cmd_print(&exp_cmds[EXP_CMD_AFTER]); + exp_cmd_print(&exp_cmds[EXP_CMD_BG]); +} + +/*ARGSUSED*/ +int +cmdX(clientData, interp, argc, argv) +ClientData clientData; +Tcl_Interp *interp; +int argc; +char **argv; +{ + exp_cmds_print(); + return TCL_OK; +} +#endif /*DEBUG_PERM_ECASES*/ + +/* need address for passing into cmdExpect */ +static int spawn_id_bad = EXP_SPAWN_ID_BAD; +static int spawn_id_user = EXP_SPAWN_ID_USER; + +static struct exp_cmd_data +cmd_data[] = { +{"expect", exp_proc(Exp_ExpectCmd), (ClientData)&spawn_id_bad, 0}, +{"expect_after",exp_proc(Exp_ExpectGlobalCmd),(ClientData)&exp_cmds[EXP_CMD_AFTER],0}, +{"expect_before",exp_proc(Exp_ExpectGlobalCmd),(ClientData)&exp_cmds[EXP_CMD_BEFORE],0}, +{"expect_user", exp_proc(Exp_ExpectCmd), (ClientData)&spawn_id_user, 0}, +{"expect_tty", exp_proc(Exp_ExpectCmd), (ClientData)&exp_dev_tty, 0}, +{"expect_background",exp_proc(Exp_ExpectGlobalCmd),(ClientData)&exp_cmds[EXP_CMD_BG],0}, +{"match_max", exp_proc(Exp_MatchMaxCmd), 0, 0}, +{"remove_nulls",exp_proc(Exp_RemoveNullsCmd), 0, 0}, +{"parity", exp_proc(Exp_ParityCmd), 0, 0}, +{"timestamp", exp_proc(Exp_TimestampCmd), 0, 0}, +{0}}; + +void +exp_init_expect_cmds(interp) +Tcl_Interp *interp; +{ + exp_create_commands(interp,cmd_data); + + Tcl_SetVar(interp,EXPECT_TIMEOUT,INIT_EXPECT_TIMEOUT_LIT,0); + Tcl_SetVar(interp,EXP_SPAWN_ID_ANY_VARNAME,EXP_SPAWN_ID_ANY_LIT,0); + + exp_cmd_init(&exp_cmds[EXP_CMD_BEFORE],EXP_CMD_BEFORE,EXP_PERMANENT); + exp_cmd_init(&exp_cmds[EXP_CMD_AFTER ],EXP_CMD_AFTER, EXP_PERMANENT); + exp_cmd_init(&exp_cmds[EXP_CMD_BG ],EXP_CMD_BG, EXP_PERMANENT); + exp_cmd_init(&exp_cmds[EXP_CMD_FG ],EXP_CMD_FG, EXP_TEMPORARY); + + /* preallocate to one element, so future realloc's work */ + exp_cmds[EXP_CMD_BEFORE].ecd.cases = 0; + exp_cmds[EXP_CMD_AFTER ].ecd.cases = 0; + exp_cmds[EXP_CMD_BG ].ecd.cases = 0; + + pattern_style[PAT_EOF] = "eof"; + pattern_style[PAT_TIMEOUT] = "timeout"; + pattern_style[PAT_DEFAULT] = "default"; + pattern_style[PAT_FULLBUFFER] = "full buffer"; + pattern_style[PAT_GLOB] = "glob pattern"; + pattern_style[PAT_RE] = "regular expression"; + pattern_style[PAT_EXACT] = "exact string"; + pattern_style[PAT_NULL] = "null"; + +#if 0 + Tcl_CreateCommand(interp,"x", + cmdX,(ClientData)0,exp_deleteProc); +#endif +} + +void +exp_init_sig() { +#if 0 + signal(SIGALRM,sigalarm_handler); + signal(SIGINT,sigint_handler); +#endif +} |