diff options
Diffstat (limited to 'expect.c')
-rw-r--r-- | expect.c | 3270 |
1 files changed, 3270 insertions, 0 deletions
diff --git a/expect.c b/expect.c new file mode 100644 index 0000000..73f79c9 --- /dev/null +++ b/expect.c @@ -0,0 +1,3270 @@ +/* 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) */ + +#include "expect_cf.h" + +#ifdef HAVE_SYS_WAIT_H +#include <sys/wait.h> +#endif + +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif + +#include "tclInt.h" + +#include "string.h" + +#include "exp_rename.h" +#include "exp_prog.h" +#include "exp_command.h" +#include "exp_log.h" +#include "exp_event.h" +#include "exp_tty_in.h" +#include "exp_tstamp.h" /* this should disappear when interact */ + /* loses ref's to it */ +#ifdef TCL_DEBUGGER +#include "tcldbg.h" +#endif + +#include "retoglob.c" /* RE 2 GLOB translator C variant */ + +/* 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; +int exp_default_close_on_eof = TRUE; + +/* user variable names */ +#define EXPECT_TIMEOUT "timeout" +#define EXPECT_OUT "expect_out" + +extern int Exp_StringCaseMatch _ANSI_ARGS_((Tcl_UniChar *string, int strlen, + Tcl_UniChar *pattern,int plen, + int nocase,int *offset)); + +typedef struct ThreadSpecificData { + int timeout; +} ThreadSpecificData; + +static Tcl_ThreadDataKey dataKey; + +/* + * addr of these placeholders appear as clientData in ExpectCmd * when called + * as expect_user and expect_tty. It would be nicer * to invoked + * expDevttyGet() but C doesn't allow this in an array initialization, sigh. + */ +static ExpState StdinoutPlaceholder; +static ExpState DevttyPlaceholder; + +/* 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; + Tcl_Obj *pat; /* original pattern spec */ + Tcl_Obj *body; /* ptr to body to be executed upon match */ + Tcl_Obj *gate; /* For PAT_RE, a gate-keeper glob pattern + * which is quicker to match and reduces + * the number of calls into expensive RE + * matching. Optional. + */ +#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 (chars) 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 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? */ +}; + +/* 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( + struct exp_cmd_descriptor *cmd, + int cmdtype, + int duration) +{ + 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 */ + +#ifdef SIMPLE_EVENT +static int alarm_fired; /* if alarm occurs */ +#endif + +void exp_background_channelhandlers_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 */ + Tcl_Interp *interp, + struct exp_cmd_descriptor *ecmd, + struct exp_i *exp_i); +static char *exp_indirect_update2( /* 2-part Tcl variable names */ + 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. */ + +#ifdef SIMPLE_EVENT +/*ARGSUSED*/ +static RETSIGTYPE +sigalarm_handler(int n) /* unused, for compatibility with STDC */ +{ + alarm_fired = TRUE; +} +#endif /*SIMPLE_EVENT*/ + +/* free up everything in ecase */ +static void +free_ecase( + Tcl_Interp *interp, + struct ecase *ec, + int free_ilist) /* if we should free ilist */ +{ + if (ec->i_list->duration == EXP_PERMANENT) { + if (ec->pat) { Tcl_DecrRefCount(ec->pat); } + if (ec->gate) { Tcl_DecrRefCount(ec->gate); } + if (ec->body) { Tcl_DecrRefCount(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( + 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(char *s) +{ + char *news = ckalloc(strlen(s) + 1); + strcpy(news,s); + return(news); +} +#endif + +/* 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(Tcl_Obj *objPtr) /* INTL */ +{ + int seen_nl = FALSE; + char *p = Tcl_GetString(objPtr); + + for (;*p;p++) { + if (*p == '\n') { + seen_nl = TRUE; + continue; + } + + if (!isspace(*p)) { /* INTL: ISO space */ + 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 a list object containing the new set of arguments */ +/* Caller then has to either reinvoke itself, or better, simply replace + * its current argumnts */ +/*ARGSUSED*/ +Tcl_Obj* +exp_eval_with_one_arg( + ClientData clientData, + Tcl_Interp *interp, + Tcl_Obj *CONST objv[]) /* Argument objects. */ +{ + Tcl_Obj* res = Tcl_NewListObj (1,objv); + +#define NUM_STATIC_OBJS 20 + Tcl_Token *tokenPtr; + CONST char *p; + CONST char *next; + int rc; + int bytesLeft, numWords; + Tcl_Parse parse; + + /* + * Prepend the command name and the -nobrace switch so we can + * reinvoke without recursing. + */ + + Tcl_ListObjAppendElement (interp, res, Tcl_NewStringObj("-nobrace", -1)); + + p = Tcl_GetStringFromObj(objv[1], &bytesLeft); + + /* + * Treat the pattern/action block like a series of Tcl commands. + * For each command, parse the command words, perform substititions + * on each word, and add the words to an array of values. We don't + * actually evaluate the individual commands, just the substitutions. + */ + + do { + if (Tcl_ParseCommand(interp, p, bytesLeft, 0, &parse) + != TCL_OK) { + rc = TCL_ERROR; + goto done; + } + numWords = parse.numWords; + if (numWords > 0) { + /* + * Generate an array of objects for the words of the command. + */ + + /* + * For each word, perform substitutions then store the + * result in the objs array. + */ + + for (tokenPtr = parse.tokenPtr; numWords > 0; + numWords--, tokenPtr += (tokenPtr->numComponents + 1)) { + /* FUTURE: Save token information, do substitution later */ + + Tcl_Obj* w = Tcl_EvalTokens(interp, tokenPtr+1, + tokenPtr->numComponents); + /* w has refCount 1 here, if not NULL */ + if (w == NULL) { + Tcl_DecrRefCount (res); + res = NULL; + goto done; + + } + Tcl_ListObjAppendElement (interp, res, w); + Tcl_DecrRefCount (w); /* Local reference goes away */ + } + } + + /* + * Advance to the next command in the script. + */ + next = parse.commandStart + parse.commandSize; + bytesLeft -= next - p; + p = next; + Tcl_FreeParse(&parse); + } while (bytesLeft > 0); + + done: + return res; +} + +static void +ecase_clear(struct ecase *ec) +{ + ec->i_list = 0; + ec->pat = 0; + ec->body = 0; + ec->transfer = TRUE; + ec->simple_start = 0; + ec->indices = FALSE; + ec->iread = FALSE; + ec->timestamp = FALSE; + ec->Case = CASE_NORM; + ec->use = PAT_GLOB; + ec->gate = NULL; +} + +static struct ecase * +ecase_new(void) +{ + 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( + Tcl_Interp *interp, + struct exp_cmd_descriptor *eg, + ExpState *default_esPtr, /* suggested ExpState if called as expect_user or _tty */ + int objc, + Tcl_Obj *CONST objv[]) /* Argument objects. */ +{ + int i; + char *string; + struct ecase ec; /* temporary to collect args */ + + 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+(objc/2))); + + eg->ecd.count = 0; + + for (i = 1;i<objc;i++) { + int index; + string = Tcl_GetString(objv[i]); + if (string[0] == '-') { + static char *flags[] = { + "-glob", "-regexp", "-exact", "-notransfer", "-nocase", + "-i", "-indices", "-iread", "-timestamp", "-timeout", + "-nobrace", "--", (char *)0 + }; + enum flags { + EXP_ARG_GLOB, EXP_ARG_REGEXP, EXP_ARG_EXACT, + EXP_ARG_NOTRANSFER, EXP_ARG_NOCASE, EXP_ARG_SPAWN_ID, + EXP_ARG_INDICES, EXP_ARG_IREAD, EXP_ARG_TIMESTAMP, + EXP_ARG_DASH_TIMEOUT, EXP_ARG_NOBRACE, EXP_ARG_DASH + }; + + /* + * Allow abbreviations of switches and report an error if we + * get an invalid switch. + */ + + if (Tcl_GetIndexFromObj(interp, objv[i], flags, "flag", 0, + &index) != TCL_OK) { + return TCL_ERROR; + } + switch ((enum flags) index) { + case EXP_ARG_GLOB: + case EXP_ARG_DASH: + i++; + /* assignment here is not actually necessary */ + /* since cases are initialized this way above */ + /* ec.use = PAT_GLOB; */ + if (i >= objc) { + Tcl_WrongNumArgs(interp, 1, objv,"-glob pattern"); + return TCL_ERROR; + } + goto pattern; + case EXP_ARG_REGEXP: + i++; + if (i >= objc) { + Tcl_WrongNumArgs(interp, 1, objv,"-regexp regexp"); + return TCL_ERROR; + } + ec.use = PAT_RE; + + /* + * Try compiling the expression so we can report + * any errors now rather then when we first try to + * use it. + */ + + if (!(Tcl_GetRegExpFromObj(interp, objv[i], + TCL_REG_ADVANCED))) { + goto error; + } + + /* Derive a gate keeper glob pattern which reduces the amount + * of RE matching. + */ + + { + Tcl_Obj* g; + Tcl_UniChar* str; + int strlen; + + str = Tcl_GetUnicodeFromObj (objv[i], &strlen); + g = exp_retoglob (str, strlen); + + if (g) { + ec.gate = g; + + expDiagLog("Gate keeper glob pattern for '%s'",Tcl_GetString(objv[i])); + expDiagLog(" is '%s'. Activating booster.\n",Tcl_GetString(g)); + } else { + /* Ignore errors, fall back to regular RE matching */ + expDiagLog("Gate keeper glob pattern for '%s'",Tcl_GetString(objv[i])); + expDiagLog(" is '%s'. Not usable, disabling the",Tcl_GetString(Tcl_GetObjResult (interp))); + expDiagLog(" performance booster.\n"); + } + } + + goto pattern; + case EXP_ARG_EXACT: + i++; + if (i >= objc) { + Tcl_WrongNumArgs(interp, 1, objv, "-exact string"); + return TCL_ERROR; + } + ec.use = PAT_EXACT; + goto pattern; + case EXP_ARG_NOTRANSFER: + ec.transfer = 0; + break; + case EXP_ARG_NOCASE: + ec.Case = CASE_LOWER; + break; + case EXP_ARG_SPAWN_ID: + i++; + if (i>=objc) { + Tcl_WrongNumArgs(interp, 1, objv, "-i spawn_id"); + goto error; + } + ec.i_list = exp_new_i_complex(interp, + Tcl_GetString(objv[i]), + eg->duration, exp_indirect_update2); + if (!ec.i_list) goto error; + 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; + break; + case EXP_ARG_INDICES: + ec.indices = TRUE; + break; + case EXP_ARG_IREAD: + ec.iread = TRUE; + break; + case EXP_ARG_TIMESTAMP: + ec.timestamp = TRUE; + break; + case EXP_ARG_DASH_TIMEOUT: + i++; + if (i>=objc) { + Tcl_WrongNumArgs(interp, 1, objv, "-timeout seconds"); + goto error; + } + if (Tcl_GetIntFromObj(interp, objv[i], + &eg->timeout) != TCL_OK) { + goto error; + } + eg->timeout_specified_by_flag = TRUE; + break; + case EXP_ARG_NOBRACE: + /* 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. */ + break; + } + /* + * Keep processing arguments, we aren't ready for the + * pattern yet. + */ + continue; + } else { + /* + * We have a pattern or keyword. + */ + + static char *keywords[] = { + "timeout", "eof", "full_buffer", "default", "null", + (char *)NULL + }; + enum keywords { + EXP_ARG_TIMEOUT, EXP_ARG_EOF, EXP_ARG_FULL_BUFFER, + EXP_ARG_DEFAULT, EXP_ARG_NULL + }; + + /* + * Match keywords exactly, otherwise they are patterns. + */ + + if (Tcl_GetIndexFromObj(interp, objv[i], keywords, "keyword", + 1 /* exact */, &index) != TCL_OK) { + Tcl_ResetResult(interp); + goto pattern; + } + switch ((enum keywords) index) { + case EXP_ARG_TIMEOUT: + ec.use = PAT_TIMEOUT; + break; + case EXP_ARG_EOF: + ec.use = PAT_EOF; + break; + case EXP_ARG_FULL_BUFFER: + ec.use = PAT_FULLBUFFER; + break; + case EXP_ARG_DEFAULT: + ec.use = PAT_DEFAULT; + break; + case EXP_ARG_NULL: + ec.use = PAT_NULL; + break; + } +pattern: + /* 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_esPtr != EXP_SPAWN_ID_BAD) { + eg->i_list = exp_new_i_simple(default_esPtr,eg->duration); + } else { + default_esPtr = expStateCurrent(interp,0,0,1); + if (!default_esPtr) goto error; + eg->i_list = exp_new_i_simple(default_esPtr,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 */ + + ec.pat = objv[i]; + if (eg->duration == EXP_PERMANENT) { + Tcl_IncrRefCount(ec.pat); + if (ec.gate) { + Tcl_IncrRefCount(ec.gate); + } + } + + i++; + if (i < objc) { + ec.body = objv[i]; + if (eg->duration == EXP_PERMANENT) Tcl_IncrRefCount(ec.body); + } else { + ec.body = NULL; + } + + *(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_esPtr != EXP_SPAWN_ID_BAD) { + eg->i_list = exp_new_i_simple(default_esPtr,eg->duration); + } else { + default_esPtr = expStateCurrent(interp,0,0,1); + if (!default_esPtr) goto error; + eg->i_list = exp_new_i_simple(default_esPtr,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); + + 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 */ + ExpState *esPtr; /* ExpState that matched */ + Tcl_UniChar* matchbuf; /* Buffer that matched, */ + int matchlen; /* and #chars that matched, or + * #chars in buffer at EOF */ + /* This points into the esPtr->input.buffer ! */ +}; + + + + +/* + *---------------------------------------------------------------------- + * + * string_case_first -- + * + * Find the first instance of a pattern in a string. + * + * Results: + * Returns the pointer to the first instance of the pattern + * in the given string, or NULL if no match was found. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +Tcl_UniChar * +string_case_first( /* INTL */ + register Tcl_UniChar *string, /* String (unicode). */ + int length, /* length of above string */ + register char *pattern) /* Pattern, which may contain + * special characters (utf8). */ +{ + Tcl_UniChar *s; + char *p; + int offset; + register int consumed = 0; + Tcl_UniChar ch1, ch2; + Tcl_UniChar *bufend = string + length; + + while ((*string != 0) && (string < bufend)) { + s = string; + p = pattern; + while ((*s) && (s < bufend)) { + ch1 = *s++; + consumed++; + offset = TclUtfToUniChar(p, &ch2); + if (Tcl_UniCharToLower(ch1) != Tcl_UniCharToLower(ch2)) { + break; + } + p += offset; + } + if (*p == '\0') { + return string; + } + string++; + consumed++; + } + return NULL; +} + +Tcl_UniChar * +string_first( /* INTL */ + register Tcl_UniChar *string, /* String (unicode). */ + int length, /* length of above string */ + register char *pattern) /* Pattern, which may contain + * special characters (utf8). */ +{ + Tcl_UniChar *s; + char *p; + int offset; + register int consumed = 0; + Tcl_UniChar ch1, ch2; + Tcl_UniChar *bufend = string + length; + + while ((*string != 0) && (string < bufend)) { + s = string; + p = pattern; + while ((*s) && (s < bufend)) { + ch1 = *s++; + consumed++; + offset = TclUtfToUniChar(p, &ch2); + if (ch1 != ch2) { + break; + } + p += offset; + } + if (*p == '\0') { + return string; + } + string++; + consumed++; + } + return NULL; +} + +Tcl_UniChar * +string_first_char( /* INTL */ + register Tcl_UniChar *string, /* String. */ + register Tcl_UniChar pattern) +{ + /* unicode based Tcl_UtfFindFirst */ + + Tcl_UniChar find; + + while (1) { + find = *string; + if (find == pattern) { + return string; + } + if (*string == '\0') { + return NULL; + } + string ++; + } + return NULL; +} + +/* 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( + Tcl_Interp *interp, + struct ecase *e, + ExpState *esPtr, + struct eval_out *o, /* 'output' - i.e., final case of interest */ +/* next two args are for debugging, when they change, reprint buffer */ + ExpState **last_esPtr, + int *last_case, + char *suffix) +{ + Tcl_RegExp re; + Tcl_RegExpInfo info; + Tcl_Obj* buf; + Tcl_UniChar *str; + int numchars, flags, dummy, globmatch; + int result; + + str = esPtr->input.buffer; + numchars = esPtr->input.use; + + /* if ExpState or case changed, redisplay debug-buffer */ + if ((esPtr != *last_esPtr) || e->Case != *last_case) { + expDiagLog("\r\nexpect%s: does \"",suffix); + expDiagLogU(expPrintifyUni(str,numchars)); + expDiagLog("\" (spawn_id %s) match %s ",esPtr->name,pattern_style[e->use]); + *last_esPtr = esPtr; + *last_case = e->Case; + } + + if (e->use == PAT_RE) { + expDiagLog("\""); + expDiagLogU(expPrintify(Tcl_GetString(e->pat))); + expDiagLog("\"? "); + + if (e->gate) { + int plen; + Tcl_UniChar* pat = Tcl_GetUnicodeFromObj(e->gate,&plen); + + expDiagLog("Gate \""); + expDiagLogU(expPrintify(Tcl_GetString(e->gate))); + expDiagLog("\"? gate="); + + globmatch = Exp_StringCaseMatch(str, numchars, pat, plen, + (e->Case == CASE_NORM) ? 0 : 1, + &dummy); + } else { + expDiagLog("(No Gate, RE only) gate="); + + /* No gate => RE matching always */ + globmatch = 1; + } + if (globmatch < 0) { + expDiagLogU(no); + /* i.e. no match */ + } else { + expDiagLog("yes re="); + + if (e->Case == CASE_NORM) { + flags = TCL_REG_ADVANCED; + } else { + flags = TCL_REG_ADVANCED | TCL_REG_NOCASE; + } + + re = Tcl_GetRegExpFromObj(interp, e->pat, flags); + + /* ZZZ: Future optimization: Avoid copying */ + buf = Tcl_NewUnicodeObj (str, numchars); + Tcl_IncrRefCount (buf); + result = Tcl_RegExpExecObj(interp, re, buf, 0 /* offset */, + -1 /* nmatches */, 0 /* eflags */); + Tcl_DecrRefCount (buf); + if (result > 0) { + o->e = e; + + /* + * Retrieve the byte offset of the end of the + * matched string. + */ + + Tcl_RegExpGetInfo(re, &info); + o->matchlen = info.matches[0].end; + o->matchbuf = str; + o->esPtr = esPtr; + expDiagLogU(yes); + return(EXP_MATCH); + } else if (result == 0) { + expDiagLogU(no); + } else { /* result < 0 */ + return(EXP_TCLERROR); + } + } + } else if (e->use == PAT_GLOB) { + int match; /* # of chars that matched */ + + expDiagLog("\""); + expDiagLogU(expPrintify(Tcl_GetString(e->pat))); + expDiagLog("\"? "); + if (str) { + int plen; + Tcl_UniChar* pat = Tcl_GetUnicodeFromObj(e->pat,&plen); + + match = Exp_StringCaseMatch(str,numchars, pat, plen, + (e->Case == CASE_NORM) ? 0 : 1, + &e->simple_start); + if (match != -1) { + o->e = e; + o->matchlen = match; + o->matchbuf = str; + o->esPtr = esPtr; + expDiagLogU(yes); + return(EXP_MATCH); + } + } + expDiagLogU(no); + } else if (e->use == PAT_EXACT) { + int patLength; + char *pat = Tcl_GetStringFromObj(e->pat, &patLength); + Tcl_UniChar *p; + + if (e->Case == CASE_NORM) { + p = string_first(str, numchars, pat); /* NEW function in this file, see above */ + } else { + p = string_case_first(str, numchars, pat); + } + + expDiagLog("\""); + expDiagLogU(expPrintify(Tcl_GetString(e->pat))); + expDiagLog("\"? "); + if (p) { + /* Bug 3095935. Go from #bytes to #chars */ + patLength = Tcl_NumUtfChars (pat, patLength); + + e->simple_start = p - str; + o->e = e; + o->matchlen = patLength; + o->matchbuf = str; + o->esPtr = esPtr; + expDiagLogU(yes); + return(EXP_MATCH); + } else expDiagLogU(no); + } else if (e->use == PAT_NULL) { + CONST Tcl_UniChar *p; + expDiagLogU("null? "); + p = string_first_char (str, 0); /* NEW function in this file, see above */ + + if (p) { + o->e = e; + o->matchlen = p-str; /* #chars */ + o->matchbuf = str; + o->esPtr = esPtr; + expDiagLogU(yes); + return EXP_MATCH; + } + expDiagLogU(no); + } else if (e->use == PAT_FULLBUFFER) { + expDiagLogU(Tcl_GetString(e->pat)); + expDiagLogU("? "); + /* this must be the same test as in expIRead */ + /* We drop one third when are at least 2/3 full */ + /* condition is (size >= max*2/3) <=> (size*3 >= max*2) */ + if (((expSizeGet(esPtr)*3) >= (esPtr->input.max*2)) && (numchars > 0)) { + o->e = e; + o->matchlen = numchars; + o->matchbuf = str; + o->esPtr = esPtr; + expDiagLogU(yes); + return(EXP_FULLBUFFER); + } else { + expDiagLogU(no); + } + } + 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( + Tcl_Interp *interp, + struct exp_cmd_descriptor *eg, + ExpState *esPtr, + struct eval_out *o, /* 'output' - i.e., final case of interest */ +/* next two args are for debugging, when they change, reprint buffer */ + ExpState **last_esPtr, + int *last_case, + int status, + ExpState *(esPtrs[]), + int mcount, + char *suffix) +{ + int i; + ExpState *em; /* ExpState 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_state_list *slPtr; + + for (slPtr=e->i_list->state_list; slPtr ;slPtr=slPtr->next) { + em = slPtr->esPtr; + if (expStateAnyIs(em) || em == esPtr) { + 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_state_list *slPtr; + int j; + + e = eg->ecd.cases[i]; + if (e->use == PAT_TIMEOUT || + e->use == PAT_DEFAULT || + e->use == PAT_EOF) continue; + + for (slPtr = e->i_list->state_list; slPtr; slPtr = slPtr->next) { + em = slPtr->esPtr; + /* if em == EXP_SPAWN_ID_ANY, then user is explicitly asking */ + /* every case to be checked against every ExpState */ + if (expStateAnyIs(em)) { + /* test against each spawn_id */ + for (j=0;j<mcount;j++) { + status = eval_case_string(interp,e,esPtrs[j],o, + last_esPtr,last_case,suffix); + if (status != EXP_NOMATCH) return(status); + } + } else { + /* reject things immediately from wrong spawn_id */ + if (em != esPtr) continue; + + status = eval_case_string(interp,e,esPtr,o,last_esPtr,last_case,suffix); + if (status != EXP_NOMATCH) return(status); + } + } + } + return(EXP_NOMATCH); +} + +static void +ecases_remove_by_expi( + 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( + 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( + 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_state( + Tcl_Interp *interp, + struct exp_cmd_descriptor *ecmd, + ExpState *esPtr, + int direct) +{ + struct exp_i *exp_i, *next; + struct exp_state_list **slPtr; + + for (exp_i=ecmd->i_list;exp_i;exp_i=next) { + next = exp_i->next; + + if (!(direct & exp_i->direct)) continue; + + for (slPtr = &exp_i->state_list;*slPtr;) { + if (esPtr == ((*slPtr)->esPtr)) { + struct exp_state_list *tmp = *slPtr; + *slPtr = (*slPtr)->next; + exp_free_state_single(tmp); + + /* if last bg ecase, disarm spawn id */ + if ((ecmd->cmdtype == EXP_CMD_BG) && (!expStateAnyIs(esPtr))) { + esPtr->bg_ecount--; + if (esPtr->bg_ecount == 0) { + exp_disarm_background_channelhandler(esPtr); + esPtr->bg_interp = 0; + } + } + + continue; + } + slPtr = &(*slPtr)->next; + } + + /* if left with no ExpStates (and is direct), get rid of it */ + /* and any dependent ecases */ + if (exp_i->direct == EXP_DIRECT && !exp_i->state_list) { + exp_i_remove_with_ecases(interp,ecmd,exp_i); + } + } +} + +/* this is called from exp_close to clean up the ExpState */ +void +exp_ecmd_remove_state_direct_and_indirect( + Tcl_Interp *interp, + ExpState *esPtr) +{ + ecmd_remove_state(interp,&exp_cmds[EXP_CMD_BEFORE],esPtr,EXP_DIRECT|EXP_INDIRECT); + ecmd_remove_state(interp,&exp_cmds[EXP_CMD_AFTER],esPtr,EXP_DIRECT|EXP_INDIRECT); + ecmd_remove_state(interp,&exp_cmds[EXP_CMD_BG],esPtr,EXP_DIRECT|EXP_INDIRECT); + + /* force it - explanation in exp_tk.c where this func is defined */ + exp_disarm_background_channelhandler_force(esPtr); +} + +/* arm a list of background ExpState's */ +static void +state_list_arm( + Tcl_Interp *interp, + struct exp_state_list *slPtr) +{ + /* for each spawn id in list, arm if necessary */ + for (;slPtr;slPtr=slPtr->next) { + ExpState *esPtr = slPtr->esPtr; + if (expStateAnyIs(esPtr)) continue; + + if (esPtr->bg_ecount == 0) { + exp_arm_background_channelhandler(esPtr); + esPtr->bg_interp = interp; + } + esPtr->bg_ecount++; + } +} + +/* return TRUE if this ecase is used by this fd */ +static int +exp_i_uses_state( + struct exp_i *exp_i, + ExpState *esPtr) +{ + struct exp_state_list *fdp; + + for (fdp = exp_i->state_list;fdp;fdp=fdp->next) { + if (fdp->esPtr == esPtr) return 1; + } + return 0; +} + +static void +ecase_append( + Tcl_Interp *interp, + struct ecase *ec) +{ + if (!ec->transfer) Tcl_AppendElement(interp,"-notransfer"); + if (ec->indices) Tcl_AppendElement(interp,"-indices"); + if (!ec->Case) Tcl_AppendElement(interp,"-nocase"); + + if (ec->use == PAT_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,Tcl_GetString(ec->pat)); + Tcl_AppendElement(interp,ec->body?Tcl_GetString(ec->body):""); +} + +/* append all ecases that match this exp_i */ +static void +ecase_by_exp_i_append( + 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( + 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_state_list *fdp; + + /* if more than one element, add braces */ + if (exp_i->state_list->next) { + Tcl_AppendResult(interp," {",(char *)0); + } + + for (fdp = exp_i->state_list;fdp;fdp=fdp->next) { + char buf[25]; /* big enough for a small int */ + sprintf(buf,"%ld", (long)fdp->esPtr); + Tcl_AppendElement(interp,buf); + } + + if (exp_i->state_list->next) { + Tcl_AppendResult(interp,"} ",(char *)0); + } +} +} + +/* return current setting of the permanent expect_before/after/bg */ +int +expect_info( + Tcl_Interp *interp, + struct exp_cmd_descriptor *ecmd, + int objc, + Tcl_Obj *CONST objv[]) /* Argument objects. */ +{ + struct exp_i *exp_i; + int i; + int direct = EXP_DIRECT|EXP_INDIRECT; + char *iflag = 0; + int all = FALSE; /* report on all fds */ + ExpState *esPtr = 0; + + static char *flags[] = {"-i", "-all", "-noindirect", (char *)0}; + enum flags {EXP_ARG_I, EXP_ARG_ALL, EXP_ARG_NOINDIRECT}; + + /* start with 2 to skip over "cmdname -info" */ + for (i = 2;i<objc;i++) { + /* + * Allow abbreviations of switches and report an error if we + * get an invalid switch. + */ + + int index; + if (Tcl_GetIndexFromObj(interp, objv[i], flags, "flag", 0, + &index) != TCL_OK) { + return TCL_ERROR; + } + switch ((enum flags) index) { + case EXP_ARG_I: + i++; + if (i >= objc) { + Tcl_WrongNumArgs(interp, 1, objv,"-i spawn_id"); + return TCL_ERROR; + } + break; + case EXP_ARG_ALL: + all = TRUE; + break; + case EXP_ARG_NOINDIRECT: + direct &= ~EXP_INDIRECT; + break; + } + } + + 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 (!(esPtr = expStateCurrent(interp,0,0,0))) { + return TCL_ERROR; + } + } else if (!(esPtr = expStateFromChannelName(interp,iflag,0,0,0,"dummy"))) { + /* not a valid ExpState so assume it is an indirect variable */ + 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_state(exp_i,esPtr)) continue; + ecase_by_exp_i_append(interp,ecmd,exp_i); + } + + return TCL_OK; +} + +/* Exp_ExpectGlobalObjCmd is invoked to process expect_before/after/background */ +/*ARGSUSED*/ +int +Exp_ExpectGlobalObjCmd( + ClientData clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[]) /* Argument objects. */ +{ + int result = TCL_OK; + struct exp_i *exp_i, **eip; + struct exp_state_list *slPtr; /* temp for interating over state_list */ + struct exp_cmd_descriptor eg; + int count; + Tcl_Obj* new_cmd = NULL; + + struct exp_cmd_descriptor *ecmd = (struct exp_cmd_descriptor *) clientData; + + if ((objc == 2) && exp_one_arg_braced(objv[1])) { + /* expect {...} */ + + new_cmd = exp_eval_with_one_arg(clientData,interp,objv); + if (!new_cmd) return TCL_ERROR; + } else if ((objc == 3) && streq(Tcl_GetString(objv[1]),"-brace")) { + /* expect -brace {...} ... fake command line for reparsing */ + + Tcl_Obj *new_objv[2]; + new_objv[0] = objv[0]; + new_objv[1] = objv[2]; + + new_cmd = exp_eval_with_one_arg(clientData,interp,new_objv); + if (!new_cmd) return TCL_ERROR; + } + + if (new_cmd) { + /* Replace old arguments with result of the reparse */ + Tcl_ListObjGetElements (interp, new_cmd, &objc, (Tcl_Obj***) &objv); + } + + if (objc > 1 && (Tcl_GetString(objv[1])[0] == '-')) { + if (exp_flageq("info",Tcl_GetString(objv[1])+1,4)) { + int res = expect_info(interp,ecmd,objc,objv); + if (new_cmd) { Tcl_DecrRefCount (new_cmd); } + return res; + } + } + + exp_cmd_init(&eg,ecmd->cmdtype,EXP_PERMANENT); + + if (TCL_ERROR == parse_expect_args(interp,&eg,EXP_SPAWN_ID_BAD, + objc,objv)) { + if (new_cmd) { Tcl_DecrRefCount (new_cmd); } + 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 (slPtr=exp_i->state_list;slPtr;slPtr=slPtr->next) { + ExpState *esPtr = slPtr->esPtr; + + /* validate all input descriptors */ + if (!expStateAnyIs(esPtr)) { + if (!expStateCheck(interp,esPtr,1,1,"expect")) { + result = TCL_ERROR; + goto cleanup; + } + } + + /* remove spawn id from exp_i */ + ecmd_remove_state(interp,ecmd,esPtr,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) { + /* Note: The exp_indirect_ functions are Tcl_VarTraceProc's, and + * are used as such in other places of Expect. We cannot use a + * Tcl_Obj* as return value :( + */ + char *msg = exp_indirect_update1(interp,ecmd,exp_i); + if (msg) { + /* unusual way of handling error return */ + /* because of Tcl's variable tracing */ + Tcl_SetResult (interp, msg, TCL_VOLATILE); + 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) { + state_list_arm(interp,exp_i->state_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_channelhandlers_run_all(); + } + + if (new_cmd) { Tcl_DecrRefCount (new_cmd); } + return(result); +} + +/* adjusts file according to user's size request */ +void +expAdjust(ExpState *esPtr) +{ + int new_msize, excess; + Tcl_UniChar *string; + + /* + * Resize buffer to user's request * 3 + 1. + * + * x3: in case the match straddles two bufferfuls, and to allow + * reading a bufferful even when we reach near fullness of two. + * (At shuffle time this means we look for 2/3 full buffer and + * drop a 1/3, i.e. half of that). + * + * NOTE: The unmodified expect got the same effect by comparing + * apples and oranges in shuffle mgmt, i.e bytes vs. chars, + * and automatically extending the buffer (Tcl_Obj string) + * to hold that much. + * + * +1: for trailing null. + */ + + new_msize = esPtr->umsize * 3 + 1; + + if (new_msize != esPtr->input.max) { + + if (esPtr->input.use > new_msize) { + /* + * too much data, forget about data at beginning of buffer + */ + + string = esPtr->input.buffer; + excess = esPtr->input.use - new_msize; /* #chars */ + + memcpy (string, string + excess, new_msize * sizeof (Tcl_UniChar)); + esPtr->input.use = new_msize; + + } else { + /* + * too little data - length < new_mbytes + * Make larger if the max is also too small. + */ + + if (esPtr->input.max < new_msize) { + esPtr->input.buffer = (Tcl_UniChar*) \ + Tcl_Realloc ((char*)esPtr->input.buffer, + new_msize * sizeof (Tcl_UniChar)); + } + } + + esPtr->key = expect_key++; + esPtr->input.max = new_msize; + } +} + +#if OBSOLETE +/* Strip parity */ +static void +expParityStrip( + Tcl_Obj *obj, + int offsetBytes) +{ + char *p, ch; + + int changed = FALSE; + + for (p = Tcl_GetString(obj) + offsetBytes;*p;p++) { + ch = *p & 0x7f; + if (ch != *p) changed = TRUE; + else *p &= 0x7f; + } + + if (changed) { + /* invalidate the unicode rep */ + if (obj->typePtr->freeIntRepProc) { + obj->typePtr->freeIntRepProc(obj); + } + } +} + +/* This function is only used when debugging. It checks when a string's + internal UTF is sane and whether an offset into the string appears to + be at a UTF boundary. +*/ +static void +expValid( + Tcl_Obj *obj, + int offset) +{ + char *s, *end; + int len; + + s = Tcl_GetStringFromObj(obj,&len); + + if (offset > len) { + printf("offset (%d) > length (%d)\n",offset,len); + fflush(stdout); + abort(); + } + + /* first test for null terminator */ + end = s + len; + if (*end != '\0') { + printf("obj lacks null terminator\n"); + fflush(stdout); + abort(); + } + + /* check for valid UTF sequence */ + while (*s) { + Tcl_UniChar uc; + + s += TclUtfToUniChar(s,&uc); + if (s > end) { + printf("UTF out of sync with terminator\n"); + fflush(stdout); + abort(); + } + } + s += offset; + while (*s) { + Tcl_UniChar uc; + + s += TclUtfToUniChar(s,&uc); + if (s > end) { + printf("UTF from offset out of sync with terminator\n"); + fflush(stdout); + abort(); + } + } +} +#endif /*OBSOLETE*/ + +/* Strip nulls from object, beginning at offset */ +static int +expNullStrip( + ExpUniBuf* buf, + int offsetChars) +{ + Tcl_UniChar *src, *src2, *dest, *end; + int newsize; /* size of obj after all nulls removed */ + + src2 = src = dest = buf->buffer + offsetChars; + end = buf->buffer + buf->use; + + while (src < end) { + if (*src) { + *dest = *src; + dest ++; + } + src ++; + } + newsize = offsetChars + (dest - src2); + buf->use = newsize; + return newsize; +} + +/* returns # of bytes 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 +expIRead( /* INTL */ + Tcl_Interp *interp, + ExpState *esPtr, + int timeout, + int save_flags) +{ + int cc = EXP_TIMEOUT; + int size; + + /* We drop one third when are at least 2/3 full */ + /* condition is (size >= max*2/3) <=> (size*3 >= max*2) */ + if (expSizeGet(esPtr)*3 >= esPtr->input.max*2) + exp_buffer_shuffle(interp,esPtr,save_flags,EXPECT_OUT,"expect"); + size = expSizeGet(esPtr); + +#ifdef SIMPLE_EVENT + restart: + + alarm_fired = FALSE; + + if (timeout > -1) { + signal(SIGALRM,sigalarm_handler); + alarm((timeout > 0)?timeout:1); + } +#endif + + cc = Tcl_ReadChars(esPtr->channel, esPtr->input.newchars, + esPtr->input.max - esPtr->input.use, + 0 /* no append */); + i_read_errno = errno; + + if (cc > 0) { + memcpy (esPtr->input.buffer + esPtr->input.use, + Tcl_GetUnicodeFromObj (esPtr->input.newchars, NULL), + cc * sizeof (Tcl_UniChar)); + esPtr->input.use += cc; + } + +#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)); + } + goto restart; + } + } + } +#endif + return cc; +} + +/* + * expRead() 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), expRead 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 +expRead( + Tcl_Interp *interp, + ExpState *(esPtrs[]), /* If 0, then esPtrOut already known and set */ + int esPtrsMax, /* number of esPtrs */ + ExpState **esPtrOut, /* Out variable to leave new ExpState. */ + int timeout, + int key) +{ + ExpState *esPtr; + + int size; + 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 (esPtrs == 0) { + /* we already know the ExpState, just find out what happened */ + cc = exp_get_next_event_info(interp,*esPtrOut); + tcl_set_flags = TCL_GLOBAL_ONLY; + } else { + cc = exp_get_next_event(interp,esPtrs,esPtrsMax,esPtrOut,timeout,key); + tcl_set_flags = 0; + } + + esPtr = *esPtrOut; + + if (cc == EXP_DATA_NEW) { + /* try to read it */ + cc = expIRead(interp,esPtr,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) { + /* successfully read data */ + } else { + /* failed to read data - some sort of error was encountered such as + * an interrupt with that forced an error return + */ + } + } else if (cc == EXP_DATA_OLD) { + 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 fd=%d): %s",esPtr->fdin, + Tcl_PosixError(interp)); + if (esPtr->close_on_eof) { + exp_close(interp,esPtr); + } + } + 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 + */ + + size = expSizeGet(esPtr); + if (size) write_count = size - esPtr->printed; + else write_count = 0; + + if (write_count) { + /* + * Show chars to user if they've requested it, UNLESS they're seeing it + * already because they're typing it and tty driver is echoing it. + * Also send to Diag and Log if appropriate. + */ + expLogInteractionU(esPtr,esPtr->input.buffer + esPtr->printed, write_count); + + /* + * strip 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 (esPtr->rm_nulls) size = expNullStrip(&esPtr->input,esPtr->printed); + esPtr->printed = 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( /* INTL */ + Tcl_Interp *interp, + ExpState *esPtr, + int save_flags, + char *array_name, + char *caller_name) +{ + Tcl_UniChar *str; + Tcl_UniChar *p; + int numchars, newlen, skiplen; + Tcl_UniChar lostChar; + + /* + * allow user to see data we are discarding + */ + + expDiagLog("%s: set %s(spawn_id) \"%s\"\r\n", + caller_name,array_name,esPtr->name); + Tcl_SetVar2(interp,array_name,"spawn_id",esPtr->name,save_flags); + + /* + * The internal storage buffer object should only be referred + * to by the channel that uses it. We always copy the contents + * out of the object before passing the data to anyone outside + * of these routines. This ensures that the object always has + * a refcount of 1 so we can safely modify the contents in place. + */ + + str = esPtr->input.buffer; + numchars = esPtr->input.use; + + skiplen = numchars/3; + p = str + skiplen; + + /* + * before doing move, show user data we are discarding + */ + + lostChar = *p; + /* temporarily stick null in middle of string */ + *p = 0; + + expDiagLog("%s: set %s(buffer) \"",caller_name,array_name); + expDiagLogU(expPrintifyUni(str,numchars)); + expDiagLogU("\"\r\n"); + Tcl_SetVar2Ex(interp,array_name,"buffer", + Tcl_NewUnicodeObj (str, skiplen), + save_flags); + + /* + * restore damage + */ + *p = lostChar; + + /* + * move 2nd half of string down to 1st half + */ + + newlen = numchars - skiplen; + memmove(str, p, newlen * sizeof(Tcl_UniChar)); + esPtr->input.use = newlen; + + esPtr->printed -= skiplen; + if (esPtr->printed < 0) esPtr->printed = 0; +} + +/* map EXP_ style return value to TCL_ style return value */ +/* not defined to work on TCL_OK */ +int +exp_tcl2_returnvalue(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; + } + /* Must not reach this location. Can happen only if x is an + * illegal value. Added return to suppress compiler warning. + */ + return -1000; +} + +/* map from EXP_ style return value to TCL_ style return values */ +int +exp_2tcl_returnvalue(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; + } + /* Must not reach this location. Can happen only if x is an + * illegal value. Added return to suppress compiler warning. + */ + return -1000; +} + +/* 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( + 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(Tcl_Interp *interp) +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + CONST char *t; + + if (NULL != (t = exp_get_var(interp,EXPECT_TIMEOUT))) { + tsdPtr->timeout = atoi(t); + } + return(tsdPtr->timeout); +} + +/* make a copy of a linked list (1st arg) and attach to end of another (2nd +arg) */ +static int +update_expect_states( + struct exp_i *i_list, + struct exp_state_list **i_union) +{ + struct exp_i *p; + + /* for each i_list in an expect statement ... */ + for (p=i_list;p;p=p->next) { + struct exp_state_list *slPtr; + + /* for each esPtr in the i_list */ + for (slPtr=p->state_list;slPtr;slPtr=slPtr->next) { + struct exp_state_list *tmpslPtr; + struct exp_state_list *u; + + if (expStateAnyIs(slPtr->esPtr)) continue; + + /* check this one against all so far */ + for (u = *i_union;u;u=u->next) { + if (slPtr->esPtr == u->esPtr) goto found; + } + /* if not found, link in as head of list */ + tmpslPtr = exp_new_state(slPtr->esPtr); + tmpslPtr->next = *i_union; + *i_union = tmpslPtr; + found:; + } + } + return TCL_OK; +} + +char * +exp_cmdtype_printable(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 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_channelhandlers_run_all(); + + return msg; +} + +static char * +exp_indirect_update1( + Tcl_Interp *interp, + struct exp_cmd_descriptor *ecmd, + struct exp_i *exp_i) +{ + struct exp_state_list *slPtr; /* temp for interating over state_list */ + + /* + * disarm any ExpState's that lose all their active spawn ids + */ + + if (ecmd->cmdtype == EXP_CMD_BG) { + /* clean up each spawn id used by this exp_i */ + for (slPtr=exp_i->state_list;slPtr;slPtr=slPtr->next) { + ExpState *esPtr = slPtr->esPtr; + + if (expStateAnyIs(esPtr)) 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 (!expStateCheck(interp,slPtr->esPtr,1,0,"")) continue; + + /* check before decrementing, ecount may not be */ + /* positive if update is called before ecount is */ + /* properly synchronized */ + if (esPtr->bg_ecount > 0) { + esPtr->bg_ecount--; + } + if (esPtr->bg_ecount == 0) { + exp_disarm_background_channelhandler(esPtr); + esPtr->bg_interp = 0; + } + } + } + + /* + * reread indirect variable + */ + + exp_i_update(interp,exp_i); + + /* + * check validity of all fd's in variable + */ + + for (slPtr=exp_i->state_list;slPtr;slPtr=slPtr->next) { + /* validate all input descriptors */ + + if (expStateAnyIs(slPtr->esPtr)) continue; + + if (!expStateCheck(interp,slPtr->esPtr,1,1, + exp_cmdtype_printable(ecmd->cmdtype))) { + /* Note: Cannot construct a Tcl_Obj* here, the function is a + * Tcl_VarTraceProc and the API wants a char*. + * + * DANGER: The buffer may overflow if either the existing result, + * the variable name, or both become to large. + */ + static char msg[200]; + sprintf(msg,"%s from indirect variable (%s)", + Tcl_GetStringResult (interp),exp_i->variable); + return msg; + } + } + + /* for each spawn id in list, arm if necessary */ + if (ecmd->cmdtype == EXP_CMD_BG) { + state_list_arm(interp,exp_i->state_list); + } + + return (char *)0; +} + +int +expMatchProcess( + Tcl_Interp *interp, + struct eval_out *eo, /* final case of interest */ + int cc, /* EOF, TIMEOUT, etc... */ + int bg, /* 1 if called from background handler, */ + /* else 0 */ + char *detail) +{ + ExpState *esPtr = 0; + Tcl_Obj *body = 0; + Tcl_UniChar *buffer; + struct ecase *e = 0; /* points to current ecase */ + int match = -1; /* characters matched */ + /* uprooted by a NULL */ + int result = TCL_OK; + +#define out(indexName, value) \ + expDiagLog("%s: set %s(%s) \"",detail,EXPECT_OUT,indexName); \ + expDiagLogU(expPrintify(value)); \ + expDiagLogU("\"\r\n"); \ + Tcl_SetVar2(interp, EXPECT_OUT,indexName,value,(bg ? TCL_GLOBAL_ONLY : 0)); + + /* The numchars argument allows us to avoid sticking a \0 into the buffer */ +#define outuni(indexName, value,numchars) \ + expDiagLog("%s: set %s(%s) \"",detail,EXPECT_OUT,indexName); \ + expDiagLogU(expPrintifyUni(value,numchars)); \ + expDiagLogU("\"\r\n"); \ + Tcl_SetVar2Ex(interp, EXPECT_OUT,indexName,Tcl_NewUnicodeObj(value,numchars),(bg ? TCL_GLOBAL_ONLY : 0)); + + if (eo->e) { + e = eo->e; + body = e->body; + if (cc != EXP_TIMEOUT) { + esPtr = eo->esPtr; + match = eo->matchlen; + buffer = eo->matchbuf; + } + } else if (cc == EXP_EOF) { + /* read an eof but no user-supplied case */ + esPtr = eo->esPtr; + match = eo->matchlen; + buffer = eo->matchbuf; + } + + if (match >= 0) { + char name[20], value[20]; + int i; + + if (e && e->use == PAT_RE) { + Tcl_RegExp re; + int flags; + Tcl_RegExpInfo info; + Tcl_Obj *buf; + + /* No gate keeper required here, we know that the RE + * matches, we just do it again to get all the captured + * pieces + */ + + if (e->Case == CASE_NORM) { + flags = TCL_REG_ADVANCED; + } else { + flags = TCL_REG_ADVANCED | TCL_REG_NOCASE; + } + + re = Tcl_GetRegExpFromObj(interp, e->pat, flags); + Tcl_RegExpGetInfo(re, &info); + + buf = Tcl_NewUnicodeObj (buffer,esPtr->input.use); + for (i=0;i<=info.nsubs;i++) { + int start, end; + Tcl_Obj *val; + + start = info.matches[i].start; + end = info.matches[i].end-1; + if (start == -1) continue; + + if (e->indices) { + /* start index */ + sprintf(name,"%d,start",i); + sprintf(value,"%d",start); + out(name,value); + + /* end index */ + sprintf(name,"%d,end",i); + sprintf(value,"%d",end); + out(name,value); + } + + /* string itself */ + sprintf(name,"%d,string",i); + val = Tcl_GetRange(buf, start, end); + expDiagLog("%s: set %s(%s) \"",detail,EXPECT_OUT,name); + expDiagLogU(expPrintifyObj(val)); + expDiagLogU("\"\r\n"); + Tcl_SetVar2Ex(interp,EXPECT_OUT,name,val,(bg ? TCL_GLOBAL_ONLY : 0)); + } + Tcl_DecrRefCount (buf); + } else if (e && (e->use == PAT_GLOB || e->use == PAT_EXACT)) { + Tcl_UniChar *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 = esPtr->input.buffer + e->simple_start; + outuni("0,string",str,match); + + /* 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) { + expDiagLogU("expect_background: full buffer\r\n"); + } + } + + /* this is broken out of (match > 0) (above) since it can be */ + /* that an EOF occurred with match == 0 */ + if (eo->esPtr) { + Tcl_UniChar *str; + int numchars; + + out("spawn_id",esPtr->name); + + str = esPtr->input.buffer; + numchars = esPtr->input.use; + + /* Save buf[0..match] */ + outuni("buffer",str,match); + + /* "!e" means no case matched - transfer by default */ + if (!e || e->transfer) { + int remainder = numchars-match; + /* delete matched chars from input buffer */ + esPtr->printed -= match; + if (numchars != 0) { + memmove(str,str+match,remainder*sizeof(Tcl_UniChar)); + } + esPtr->input.use = remainder; + } + + if (cc == EXP_EOF) { + /* exp_close() deletes all background bodies */ + /* so save eof body temporarily */ + if (body) { Tcl_IncrRefCount(body); } + if (esPtr->close_on_eof) { + exp_close(interp,esPtr); + } + } + } + + if (body) { + if (!bg) { + result = Tcl_EvalObjEx(interp,body,0); + } else { + result = Tcl_EvalObjEx(interp,body,TCL_EVAL_GLOBAL); + if (result != TCL_OK) Tcl_BackgroundError(interp); + } + if (cc == EXP_EOF) { Tcl_DecrRefCount(body); } + } + return result; +} + +/* this function is called from the background when input arrives */ +/*ARGSUSED*/ +void +exp_background_channelhandler( /* INTL */ + ClientData clientData, + int mask) +{ + char backup[EXP_CHANNELNAMELEN+1]; /* backup copy of esPtr channel name! */ + + ExpState *esPtr; + Tcl_Interp *interp; + int cc; /* number of bytes returned in a single read */ + /* or negative EXP_whatever */ + struct eval_out eo; /* final case of interest */ + ExpState *last_esPtr; /* for differentiating when multiple esPtrs */ + /* to print out better debugging messages */ + int last_case; /* as above but for case */ + + /* restore our environment */ + esPtr = (ExpState *)clientData; + + /* backup just in case someone zaps esPtr in the middle of our work! */ + strcpy(backup,esPtr->name); + + interp = esPtr->bg_interp; + + /* temporarily prevent this handler from being invoked again */ + exp_block_background_channelhandler(esPtr); + + /* + * 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 { + esPtr->notifiedMask = mask; + esPtr->notified = FALSE; + cc = expRead(interp,(ExpState **)0,0,&esPtr,EXP_TIME_INFINITY,0); + } + +do_more_data: + eo.e = 0; /* no final case yet */ + eo.esPtr = 0; /* no final file selected yet */ + eo.matchlen = 0; /* nothing matched yet */ + + /* force redisplay of buffer when debugging */ + last_esPtr = 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], + esPtr,&eo,&last_esPtr,&last_case,cc,&esPtr,1,"_background"); + cc = eval_cases(interp,&exp_cmds[EXP_CMD_BG], + esPtr,&eo,&last_esPtr,&last_case,cc,&esPtr,1,"_background"); + cc = eval_cases(interp,&exp_cmds[EXP_CMD_AFTER], + esPtr,&eo,&last_esPtr,&last_case,cc,&esPtr,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.esPtr = esPtr; + eo.matchlen = expSizeGet(eo.esPtr); + eo.matchbuf = eo.esPtr->input.buffer; + expDiagLogU("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: + expMatchProcess(interp, &eo, cc, 1 /* bg */,"expect_background"); + + /* + * 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. + */ + + /* + * Connection could have been closed on us. In this case, + * exitWhenBgStatusUnblocked will be 1 and we should disable the channel + * handler and release the esPtr. + */ + + /* First check that the esPtr is even still valid! */ + /* This ought to be sufficient. */ + if (0 == Tcl_GetChannel(interp,backup,(int *)0)) { + expDiagLog("expect channel %s lost in background handler\n",backup); + return; + } + + if ((!esPtr->freeWhenBgHandlerUnblocked) && (esPtr->bg_status == blocked)) { + if (0 != (cc = expSizeGet(esPtr))) { + goto do_more_data; + } + } + finish: + exp_unblock_background_channelhandler(esPtr); + if (esPtr->freeWhenBgHandlerUnblocked) + expStateFree(esPtr); +} + +/*ARGSUSED*/ +int +Exp_ExpectObjCmd( + ClientData clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[]) /* Argument objects. */ +{ + int cc; /* number of chars returned in a single read */ + /* or negative EXP_whatever */ + ExpState *esPtr = 0; + + int i; /* misc temporary */ + struct exp_cmd_descriptor eg; + struct exp_state_list *state_list; /* list of ExpStates to watch */ + struct exp_state_list *slPtr; /* temp for interating over state_list */ + ExpState **esPtrs; + int mcount; /* number of esPtrs 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 */ + + ExpState *last_esPtr; /* 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? */ + Tcl_Time temp_time; + Tcl_Obj* new_cmd = NULL; + + if ((objc == 2) && exp_one_arg_braced(objv[1])) { + /* expect {...} */ + + new_cmd = exp_eval_with_one_arg(clientData,interp,objv); + if (!new_cmd) return TCL_ERROR; + } else if ((objc == 3) && streq(Tcl_GetString(objv[1]),"-brace")) { + /* expect -brace {...} ... fake command line for reparsing */ + + Tcl_Obj *new_objv[2]; + new_objv[0] = objv[0]; + new_objv[1] = objv[2]; + + new_cmd = exp_eval_with_one_arg(clientData,interp,new_objv); + if (!new_cmd) return TCL_ERROR; + } + + if (new_cmd) { + /* Replace old arguments with result of the reparse */ + Tcl_ListObjGetElements (interp, new_cmd, &objc, (Tcl_Obj***) &objv); + } + + Tcl_GetTime (&temp_time); + start_time_total = temp_time.sec; + start_time = start_time_total; + reset_timer = TRUE; + + if (&StdinoutPlaceholder == (ExpState *)clientData) { + clientData = (ClientData) expStdinoutGet(); + } else if (&DevttyPlaceholder == (ExpState *)clientData) { + clientData = (ClientData) expDevttyGet(); + } + + /* make arg list for processing cases */ + /* do it dynamically, since expect can be called recursively */ + + exp_cmd_init(&eg,EXP_CMD_FG,EXP_TEMPORARY); + state_list = 0; + esPtrs = 0; + if (TCL_ERROR == parse_expect_args(interp,&eg, (ExpState *)clientData, + objc,objv)) { + if (new_cmd) { Tcl_DecrRefCount (new_cmd); } + return TCL_ERROR; + } + + restart_with_update: + /* validate all descriptors and flatten ExpStates into array */ + + if ((TCL_ERROR == update_expect_states(exp_cmds[EXP_CMD_BEFORE].i_list,&state_list)) + || (TCL_ERROR == update_expect_states(exp_cmds[EXP_CMD_AFTER].i_list, &state_list)) + || (TCL_ERROR == update_expect_states(eg.i_list,&state_list))) { + result = TCL_ERROR; + goto cleanup; + } + + /* declare ourselves "in sync" with external view of close/indirect */ + configure_count = exp_configure_count; + + /* count and validate state_list */ + mcount = 0; + for (slPtr=state_list;slPtr;slPtr=slPtr->next) { + mcount++; + /* validate all input descriptors */ + if (!expStateCheck(interp,slPtr->esPtr,1,1,"expect")) { + result = TCL_ERROR; + goto cleanup; + } + } + + /* make into an array */ + esPtrs = (ExpState **)ckalloc(mcount * sizeof(ExpState *)); + for (slPtr=state_list,i=0;slPtr;slPtr=slPtr->next,i++) { + esPtrs[i] = slPtr->esPtr; + } + + restart: + if (first_time) first_time = 0; + else { + Tcl_GetTime (&temp_time); + start_time = temp_time.sec; + } + + 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_esPtr = 0; + + /* + * end of restart code + */ + + eo.e = 0; /* no final case yet */ + eo.esPtr = 0; /* no final ExpState selected yet */ + eo.matchlen = 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) { + Tcl_GetTime (&temp_time); + current_time = temp_time.sec; + 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 = expRead(interp,esPtrs,mcount,&esPtr,remtime,key); + } + + /*SUPPRESS 530*/ + if (cc == EXP_EOF) { + /* do nothing */ + } else if (cc == EXP_TIMEOUT) { + expDiagLogU("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 */ + + /* below here, cc as general status */ + cc = EXP_NOMATCH; + + /* force redisplay of buffer when debugging */ + last_esPtr = 0; + } + + cc = eval_cases(interp,&exp_cmds[EXP_CMD_BEFORE], + esPtr,&eo,&last_esPtr,&last_case,cc,esPtrs,mcount,""); + cc = eval_cases(interp,&eg, + esPtr,&eo,&last_esPtr,&last_case,cc,esPtrs,mcount,""); + cc = eval_cases(interp,&exp_cmds[EXP_CMD_AFTER], + esPtr,&eo,&last_esPtr,&last_case,cc,esPtrs,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.esPtr = esPtr; + eo.matchlen = expSizeGet(eo.esPtr); + eo.matchbuf = eo.esPtr->input.buffer; + expDiagLogU("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 */ + esPtr->force_read = TRUE; + + if (timeout != EXP_TIME_INFINITY) { + Tcl_GetTime (&temp_time); + current_time = temp_time.sec; + remtime = end_time - current_time; + } + } + + goto done; + +error: + result = exp_2tcl_returnvalue(cc); + done: + if (result != TCL_ERROR) { + result = expMatchProcess(interp, &eo, cc, 0 /* not bg */,"expect"); + } + + cleanup: + if (result == EXP_CONTINUE_TIMER) { + reset_timer = FALSE; + result = EXP_CONTINUE; + } + + if ((result == EXP_CONTINUE) && (configure_count == exp_configure_count)) { + expDiagLogU("expect: continuing expect\r\n"); + goto restart; + } + + if (state_list) { + exp_free_state(state_list); + state_list = 0; + } + if (esPtrs) { + ckfree((char *)esPtrs); + esPtrs = 0; + } + + if (result == EXP_CONTINUE) { + expDiagLogU("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); + + if (new_cmd) { Tcl_DecrRefCount (new_cmd); } + return(result); +} + +/*ARGSUSED*/ +static int +Exp_TimestampObjCmd( + ClientData clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[]) /* Argument objects. */ +{ + char *format = 0; + time_t seconds = -1; + int gmt = FALSE; /* local time by default */ + struct tm *tm; + Tcl_DString dstring; + int i; + + static char* options[] = { + "-format", + "-gmt", + "-seconds", + NULL + }; + enum options { + TS_FORMAT, + TS_GMT, + TS_SECONDS + }; + + for (i=1; i<objc; i++) { + char *name; + int index; + + name = Tcl_GetString(objv[i]); + if (name[0] != '-') { + break; + } + if (Tcl_GetIndexFromObj(interp, objv[i], options, "flag", 0, + &index) != TCL_OK) { + return TCL_ERROR; + } + switch ((enum options) index) { + case TS_FORMAT: + i++; + if (i >= objc) goto usage_error; + format = Tcl_GetString (objv[i]); + break; + case TS_GMT: + gmt = TRUE; + break; + case TS_SECONDS: { + int sec; + i++; + if (i >= objc) goto usage_error; + if (TCL_OK != Tcl_GetIntFromObj (interp, objv[i], &sec)) { + goto usage_error; + } + seconds = sec; + } + break; + } + } + + if (i < objc) goto usage_error; + + if (seconds == -1) { + time(&seconds); + } + + if (format) { + if (gmt) { + tm = gmtime(&seconds); + } else { + tm = localtime(&seconds); + } + Tcl_DStringInit(&dstring); + exp_strftime(format,tm,&dstring); + Tcl_DStringResult(interp,&dstring); + } else { + Tcl_SetObjResult (interp, Tcl_NewIntObj (seconds)); + } + + return TCL_OK; + usage_error: + exp_error(interp,"args: [-seconds #] [-format format] [-gmt]"); + return TCL_ERROR; + +} + +/* Helper function hnadling the common processing of -d and -i options of + * various commands. + */ + +static int +process_di _ANSI_ARGS_ ((Tcl_Interp* interp, + int objc, + Tcl_Obj *CONST objv[], /* Argument objects. */ + int* at, + int* Default, + ExpState **esOut, + CONST char* cmd)); + +static int +process_di ( + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[], /* Argument objects. */ + int* at, + int* Default, + ExpState **esOut, + CONST char* cmd) +{ + static char* options[] = { + "-d", + "-i", + NULL + }; + enum options { + DI_DEFAULT, + DI_ID + }; + int def = FALSE; + char* chan = NULL; + int i; + ExpState *esPtr; + + for (i=1; i<objc; i++) { + char *name; + int index; + + name = Tcl_GetString(objv[i]); + if (name[0] != '-') { + break; + } + if (Tcl_GetIndexFromObj(interp, objv[i], options, "flag", 0, + &index) != TCL_OK) { + return TCL_ERROR; + } + switch ((enum options) index) { + case DI_DEFAULT: + def = TRUE; + break; + case DI_ID: + i++; + if (i >= objc) { + exp_error(interp,"-i needs argument"); + return(TCL_ERROR); + } + chan = Tcl_GetString (objv[i]); + break; + } + } + + if (def && chan) { + exp_error(interp,"cannot do -d and -i at the same time"); + return(TCL_ERROR); + } + + /* Not all arguments processed, more than two remaining, only at most one + * remaining is expected/allowed. + */ + if (i < (objc-1)) { + exp_error(interp,"too many arguments"); + return(TCL_OK); + } + + if (!def) { + if (!chan) { + esPtr = expStateCurrent(interp,0,0,0); + } else { + esPtr = expStateFromChannelName(interp,chan,0,0,0,(char*)cmd); + } + if (!esPtr) return(TCL_ERROR); + } + + *at = i; + *Default = def; + *esOut = esPtr; + return TCL_OK; +} + + +/*ARGSUSED*/ +int +Exp_MatchMaxObjCmd( + ClientData clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[]) /* Argument objects. */ +{ + int size = -1; + ExpState *esPtr = 0; + int Default = FALSE; + int i; + + if (TCL_OK != process_di (interp, objc, objv, &i, &Default, &esPtr, "match_max")) + return TCL_ERROR; + + /* No size argument */ + if (i == objc) { + if (Default) { + size = exp_default_match_max; + } else { + size = esPtr->umsize; + } + Tcl_SetObjResult (interp, Tcl_NewIntObj (size)); + return(TCL_OK); + } + + /* + * All that's left is to set the size + */ + + if (TCL_OK != Tcl_GetIntFromObj (interp, objv[i], &size)) { + return TCL_ERROR; + } + + if (size <= 0) { + exp_error(interp,"must be positive"); + return(TCL_ERROR); + } + + if (Default) exp_default_match_max = size; + else esPtr->umsize = size; + + return(TCL_OK); +} + +/*ARGSUSED*/ +int +Exp_RemoveNullsObjCmd( + ClientData clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[]) /* Argument objects. */ +{ + int value = -1; + ExpState *esPtr = 0; + int Default = FALSE; + int i; + + if (TCL_OK != process_di (interp, objc, objv, &i, &Default, &esPtr, "remove_nulls")) + return TCL_ERROR; + + /* No flag argument */ + if (i == objc) { + if (Default) { + value = exp_default_rm_nulls; + } else { + value = esPtr->rm_nulls; + } + Tcl_SetObjResult (interp, Tcl_NewIntObj (value)); + return(TCL_OK); + } + + /* all that's left is to set the value */ + + if (TCL_OK != Tcl_GetBooleanFromObj (interp, objv[i], &value)) { + return TCL_ERROR; + } + + if ((value != 0) && (value != 1)) { + exp_error(interp,"must be 0 or 1"); + return(TCL_ERROR); + } + + if (Default) exp_default_rm_nulls = value; + else esPtr->rm_nulls = value; + + return(TCL_OK); +} + +/*ARGSUSED*/ +int +Exp_ParityObjCmd( + ClientData clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[]) /* Argument objects. */ +{ + int parity; + ExpState *esPtr = 0; + int Default = FALSE; + int i; + + if (TCL_OK != process_di (interp, objc, objv, &i, &Default, &esPtr, "parity")) + return TCL_ERROR; + + /* No parity argument */ + if (i == objc) { + if (Default) { + parity = exp_default_parity; + } else { + parity = esPtr->parity; + } + Tcl_SetObjResult (interp, Tcl_NewIntObj (parity)); + return(TCL_OK); + } + + /* all that's left is to set the parity */ + + if (TCL_OK != Tcl_GetIntFromObj (interp, objv[i], &parity)) { + return TCL_ERROR; + } + + if (Default) exp_default_parity = parity; + else esPtr->parity = parity; + + return(TCL_OK); +} + +/*ARGSUSED*/ +int +Exp_CloseOnEofObjCmd( + ClientData clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[]) /* Argument objects. */ +{ + int close_on_eof; + ExpState *esPtr = 0; + int Default = FALSE; + int i; + + if (TCL_OK != process_di (interp, objc, objv, &i, &Default, &esPtr, "close_on_eof")) + return TCL_ERROR; + + /* No flag argument */ + if (i == objc) { + if (Default) { + close_on_eof = exp_default_close_on_eof; + } else { + close_on_eof = esPtr->close_on_eof; + } + Tcl_SetObjResult (interp, Tcl_NewIntObj (close_on_eof)); + return(TCL_OK); + } + + /* all that's left is to set the close_on_eof */ + + if (TCL_OK != Tcl_GetIntFromObj (interp, objv[i], &close_on_eof)) { + return TCL_ERROR; + } + + if (Default) exp_default_close_on_eof = close_on_eof; + else esPtr->close_on_eof = close_on_eof; + + return(TCL_OK); +} + +#if DEBUG_PERM_ECASES +/* This big chunk of code is just for debugging the permanent */ +/* expect cases */ +void +exp_fd_print(struct exp_state_list *slPtr) +{ + if (!slPtr) return; + printf("%d ",slPtr->esPtr); + exp_fd_print(slPtr->next); +} + +void +exp_i_print(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("ExpStates: "); + exp_fd_print(exp_i->state_list); printf("\n"); + exp_i_print(exp_i->next); +} + +void +exp_ecase_print(struct ecase *ecase) +{ + printf("pat <%s>\n",ecase->pat); + printf("exp_i = %x\n",ecase->i_list); +} + +void +exp_ecases_print(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(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(void) +{ + 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 clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[]) /* Argument objects. */ +{ + exp_cmds_print(); + return TCL_OK; +} +#endif /*DEBUG_PERM_ECASES*/ + +void +expExpectVarsInit(void) +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + tsdPtr->timeout = INIT_EXPECT_TIMEOUT; +} + +static struct exp_cmd_data +cmd_data[] = { +{"expect", Exp_ExpectObjCmd, 0, (ClientData)0, 0}, +{"expect_after",Exp_ExpectGlobalObjCmd, 0, (ClientData)&exp_cmds[EXP_CMD_AFTER],0}, +{"expect_before",Exp_ExpectGlobalObjCmd,0, (ClientData)&exp_cmds[EXP_CMD_BEFORE],0}, +{"expect_user", Exp_ExpectObjCmd, 0, (ClientData)&StdinoutPlaceholder,0}, +{"expect_tty", Exp_ExpectObjCmd, 0, (ClientData)&DevttyPlaceholder,0}, +{"expect_background",Exp_ExpectGlobalObjCmd,0, (ClientData)&exp_cmds[EXP_CMD_BG],0}, + {"match_max", Exp_MatchMaxObjCmd, 0, (ClientData)0, 0}, + {"remove_nulls", Exp_RemoveNullsObjCmd, 0, (ClientData)0, 0}, + {"parity", Exp_ParityObjCmd, 0, (ClientData)0, 0}, + {"close_on_eof", Exp_CloseOnEofObjCmd, 0, (ClientData)0, 0}, + {"timestamp", Exp_TimestampObjCmd, 0, (ClientData)0, 0}, +{0}}; + +void +exp_init_expect_cmds(Tcl_Interp *interp) +{ + exp_create_commands(interp,cmd_data); + + Tcl_SetVar(interp,EXPECT_TIMEOUT,INIT_EXPECT_TIMEOUT_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_CreateObjCommand(interp,"x",cmdX,(ClientData)0,exp_deleteProc); +#endif +} + +void +exp_init_sig(void) { +#if 0 + signal(SIGALRM,sigalarm_handler); + signal(SIGINT,sigint_handler); +#endif +} + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */ |