diff options
Diffstat (limited to 'exp_inter.c')
-rw-r--r-- | exp_inter.c | 2263 |
1 files changed, 2263 insertions, 0 deletions
diff --git a/exp_inter.c b/exp_inter.c new file mode 100644 index 0000000..a7c2fbe --- /dev/null +++ b/exp_inter.c @@ -0,0 +1,2263 @@ +/* interact (using select) - give user keyboard control + +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 "expect_cf.h" +#include <stdio.h> +#ifdef HAVE_INTTYPES_H +# include <inttypes.h> +#endif +#include <sys/types.h> +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif + +#ifdef TIME_WITH_SYS_TIME +# include <sys/time.h> +# include <time.h> +#else +# if HAVE_SYS_TIME_H +# include <sys/time.h> +# else +# include <time.h> +# endif +#endif + +#ifdef HAVE_SYS_WAIT_H +#include <sys/wait.h> +#endif + +#include <ctype.h> + +#include "tclInt.h" +#include "string.h" + +#include "exp_tty_in.h" +#include "exp_rename.h" +#include "exp_prog.h" +#include "exp_command.h" +#include "exp_log.h" +#include "exp_event.h" /* exp_get_next_event decl */ + +/* Tcl 8.5+ moved this internal - needed for when I compile expect against 8.5. */ +#ifndef TCL_REG_BOSONLY +#define TCL_REG_BOSONLY 002000 +#endif + +typedef struct ThreadSpecificData { + Tcl_Obj *cmdObjReturn; + Tcl_Obj *cmdObjInterpreter; +} ThreadSpecificData; + +static Tcl_ThreadDataKey dataKey; + +#define INTER_OUT "interact_out" +#define out(var,val) \ + expDiagLog("interact: set %s(%s) ",INTER_OUT,var); \ + expDiagLogU(expPrintify(val)); \ + expDiagLogU("\"\r\n"); \ + Tcl_SetVar2(interp,INTER_OUT,var,val,0); + +/* + * tests if we are running this using a real tty + * + * these tests are currently only used to control what gets written to the + * logfile. Note that removal of the test of "..._is_tty" means that stdin + * or stdout could be redirected and yet stdout would still be logged. + * However, it's not clear why anyone would use log_file when these are + * redirected in the first place. On the other hand, it is reasonable to + * run expect as a daemon in which case, stdin/out do not appear to be + * ttys, yet it makes sense for them to be logged with log_file as if they + * were. + */ +#if 0 +#define real_tty_output(x) (exp_stdout_is_tty && (((x)==1) || ((x)==exp_dev_tty))) +#define real_tty_input(x) (exp_stdin_is_tty && (((x)==0) || ((x)==exp_dev_tty))) +#endif + +#define real_tty_output(x) ((x->fdout == 1) || (expDevttyIs(x))) +#define real_tty_input(x) (exp_stdin_is_tty && ((x->fdin==0) || (expDevttyIs(x)))) + +#define new(x) (x *)ckalloc(sizeof(x)) + +struct action { + Tcl_Obj *statement; + int tty_reset; /* if true, reset tty mode upon action */ + int iread; /* if true, reread indirects */ + int iwrite; /* if true, write spawn_id element */ + struct action *next; /* chain only for later for freeing */ +}; + +struct keymap { + Tcl_Obj *keys; /* original pattern provided by user */ + int re; /* true if looking to match a regexp. */ + int null; /* true if looking to match 0 byte */ + int case_sensitive; + int echo; /* if keystrokes should be echoed */ + int writethru; /* if keystrokes should go through to process */ + int indices; /* true if should write indices */ + struct action action; + struct keymap *next; +}; + +struct output { + struct exp_i *i_list; + struct action *action_eof; + struct output *next; +}; + +struct input { + struct exp_i *i_list; + struct output *output; + struct action *action_eof; + struct action *action_timeout; + struct keymap *keymap; + int timeout_nominal; /* timeout nominal */ + int timeout_remaining; /* timeout remaining */ + struct input *next; +}; + +/* + * Once we are handed an ExpState from the event handler, we can figure out + * which "struct input *" it references by using expStateToInput. This has is + * populated by expCreateStateToInput. + */ + +struct input * +expStateToInput( + Tcl_HashTable *hash, + ExpState *esPtr) +{ + Tcl_HashEntry *entry = Tcl_FindHashEntry(hash,(char *)esPtr); + + if (!entry) { + /* should never happen */ + return 0; + } + return ((struct input *)Tcl_GetHashValue(entry)); +} + +void +expCreateStateToInput( + Tcl_HashTable *hash, + ExpState *esPtr, + struct input *inp) +{ + Tcl_HashEntry *entry; + int newPtr; + + entry = Tcl_CreateHashEntry(hash,(char *)esPtr,&newPtr); + Tcl_SetHashValue(entry,(ClientData)inp); +} + +static void free_input(Tcl_Interp *interp, struct input *i); +static void free_keymap(struct keymap *km); +static void free_output(Tcl_Interp *interp, struct output *o); +static void free_action(struct action *a); +static struct action *new_action(struct action **base); +static int inter_eval( + Tcl_Interp *interp, + struct action *action, + ExpState *esPtr); + +/* intMatch() accepts user keystrokes and returns one of MATCH, +CANMATCH, or CANTMATCH. These describe whether the keystrokes match a +key sequence, and could or can't if more characters arrive. The +function assigns a matching keymap if there is a match or can-match. +A matching keymap is assigned on can-match so we know whether to echo +or not. + +intMatch is optimized (if you can call it that) towards a small +number of key mappings, but still works well for large maps, since no +function calls are made, and we stop as soon as there is a single-char +mismatch, and go on to the next one. A hash table or compiled DFA +probably would not buy very much here for most maps. + +The basic idea of how this works is it does a smart sequential search. +At each position of the input string, we attempt to match each of the +keymaps. If at least one matches, the first match is returned. + +If there is a CANMATCH and there are more keymaps to try, we continue +trying. If there are no more keymaps to try, we stop trying and +return with an indication of the first keymap that can match. + +Note that I've hacked up the regexp pattern matcher in two ways. One +is to force the pattern to always be anchored at the front. That way, +it doesn't waste time attempting to match later in the string (before +we're ready). The other is to return can-match. + +*/ + +static int +intMatch( + ExpState *esPtr, + struct keymap *keymap, /* linked list of keymaps */ + struct keymap **km_match, /* keymap that matches or can match */ + int *matchLen, /* # of bytes that matched */ + int *skip, /* # of chars to skip */ + Tcl_RegExpInfo *info) +{ + Tcl_UniChar *string; + struct keymap *km; + char *ks; /* string from a keymap */ + + Tcl_UniChar *start_search; /* where in string to start searching */ + int offset; /* # of chars from string to start searching */ + + Tcl_UniChar *string_end; + int numchars; + int rm_nulls; /* skip nulls if true */ + Tcl_UniChar ch; + + string = esPtr->input.buffer; + numchars = esPtr->input.use; /* Actually #chars */ + + /* assert (*km == 0) */ + + /* a shortcut that should help master output which typically */ + /* is lengthy and has no key maps. Otherwise it would mindlessly */ + /* iterate on each character anyway. */ + if (!keymap) { + *skip = numchars; + return(EXP_CANTMATCH); + } + + rm_nulls = esPtr->rm_nulls; + + string_end = string + numchars; + + /* + * Maintain both a character index and a string pointer so we + * can easily index into either the UTF or the Unicode representations. + */ + + for (start_search = string, offset = 0; + start_search < string_end; + start_search ++, offset++) { + + ch = *start_search; + + if (*km_match) break; /* if we've already found a CANMATCH */ + /* don't bother starting search from positions */ + /* further along the string */ + + for (km=keymap;km;km=km->next) { + Tcl_UniChar *s; /* current character being examined */ + + if (km->null) { + if (ch == 0) { + *skip = start_search-string; + *matchLen = 1; /* s - start_search == 1 */ + *km_match = km; + return(EXP_MATCH); + } + } else if (!km->re) { + int kslen; + Tcl_UniChar sch, ksch; + + /* fixed string */ + + ks = Tcl_GetString(km->keys); + for (s = start_search;; s++, ks += kslen) { + /* if we hit the end of this map, must've matched! */ + if (*ks == 0) { + *skip = start_search-string; + *matchLen = s-start_search; + *km_match = km; + return(EXP_MATCH); + } + + /* if we ran out of user-supplied characters, and */ + /* still haven't matched, it might match if the user */ + /* supplies more characters next time */ + + if (s == string_end) { + /* skip to next key entry, but remember */ + /* possibility that this entry might match */ + if (!*km_match) *km_match = km; + break; + } + + sch = *s; + kslen = Tcl_UtfToUniChar(ks, &ksch); + + if (sch == ksch) continue; + if ((sch == '\0') && rm_nulls) { + kslen = 0; + continue; + } + break; + } + } else { + /* regexp */ + Tcl_RegExp re; + int flags; + int result; + Tcl_Obj* buf; + + re = Tcl_GetRegExpFromObj(NULL, km->keys, + TCL_REG_ADVANCED|TCL_REG_BOSONLY|TCL_REG_CANMATCH); + flags = (offset > 0) ? TCL_REG_NOTBOL : 0; + + /* ZZZ: Future optimization: Avoid copying */ + buf = Tcl_NewUnicodeObj (esPtr->input.buffer, esPtr->input.use); + Tcl_IncrRefCount (buf); + result = Tcl_RegExpExecObj(NULL, re, buf, offset, + -1 /* nmatches */, flags); + Tcl_DecrRefCount (buf); + if (result > 0) { + *km_match = km; + *skip = start_search-string; + Tcl_RegExpGetInfo(re, info); + *matchLen = info->matches[0].end; + return EXP_MATCH; + } else if (result == 0) { + Tcl_RegExpGetInfo(re, info); + + /* + * Check to see if there was a partial match starting + * at the current character. + */ + if (info->extendStart == 0) { + if (!*km_match) *km_match = km; + } + } + } + } + } + + if (*km_match) { + /* report CANMATCH for -re and -ex */ + + /* + * since canmatch is only detected after we've advanced too far, + * adjust start_search back to make other computations simpler + */ + start_search--; + + *skip = start_search - string; + *matchLen = string_end - start_search; + return(EXP_CANMATCH); + } + + *skip = start_search-string; + return(EXP_CANTMATCH); +} + +/* put regexp result in variables */ +static void +intRegExpMatchProcess( + Tcl_Interp *interp, + ExpState *esPtr, + struct keymap *km, /* ptr for above while parsing */ + Tcl_RegExpInfo *info, + int offset) +{ + char name[20], value[20]; + int i; + Tcl_Obj* buf = Tcl_NewUnicodeObj (esPtr->input.buffer,esPtr->input.use); + + for (i=0;i<=info->nsubs;i++) { + int start, end; + Tcl_Obj *val; + + start = info->matches[i].start + offset; + if (start == -1) continue; + end = (info->matches[i].end-1) + offset; + + if (km->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("interact: set %s(%s) \"",INTER_OUT,name); + expDiagLogU(expPrintifyObj(val)); + expDiagLogU("\"\r\n"); + Tcl_SetVar2Ex(interp,INTER_OUT,name,val,0); + } + Tcl_DecrRefCount (buf); +} + +/* + * echo chars + */ +static void +intEcho( + ExpState *esPtr, + int skipBytes, + int matchBytes) +{ + int seenBytes; /* either printed or echoed */ + int echoBytes; + int offsetBytes; + + /* write is unlikely to fail, since we just read from same descriptor */ + seenBytes = esPtr->printed + esPtr->echoed; + if (skipBytes >= seenBytes) { + echoBytes = matchBytes; + offsetBytes = skipBytes; + } else if ((matchBytes + skipBytes - seenBytes) > 0) { + echoBytes = matchBytes + skipBytes - seenBytes; + offsetBytes = seenBytes; + } + + (void) expWriteCharsUni(esPtr, + esPtr->input.buffer + offsetBytes, + echoBytes); + + esPtr->echoed = matchBytes + skipBytes - esPtr->printed; +} + +/* + * intRead() does the logical equivalent of a read() for the interact command. + * Returns # of bytes read or negative number (EXP_XXX) indicating unusual event. + */ +static int +intRead( + Tcl_Interp *interp, + ExpState *esPtr, + int warnOnBufferFull, + int interruptible, + int key) +{ + Tcl_UniChar *eobOld; /* old end of buffer */ + int cc; + int numchars; + Tcl_UniChar *str; + + str = esPtr->input.buffer; + numchars = esPtr->input.use; + eobOld = str + numchars; + + /* We drop one third when are at least 2/3 full */ + /* condition is (size >= max*2/3) <=> (size*3 >= max*2) */ + if (numchars*3 >= esPtr->input.max*2) { + /* + * In theory, interact could be invoked when this situation + * already exists, hence the "probably" in the warning below + */ + if (warnOnBufferFull) { + expDiagLogU("WARNING: interact buffer is full, probably because your\r\n"); + expDiagLogU("patterns have matched all of it but require more chars\r\n"); + expDiagLogU("in order to complete the match.\r\n"); + expDiagLogU("Dumping first half of buffer in order to continue\r\n"); + expDiagLogU("Recommend you enlarge the buffer or fix your patterns.\r\n"); + } + exp_buffer_shuffle(interp,esPtr,0,INTER_OUT,"interact"); + } + if (!interruptible) { + cc = Tcl_ReadChars(esPtr->channel, esPtr->input.newchars, + esPtr->input.max - esPtr->input.use, + 0 /* no append */); + } else { +#ifdef SIMPLE_EVENT + cc = intIRead(esPtr->channel, esPtr->input.newchars, + esPtr->input.max - esPtr->input.use, + 0 /* no append */); +#endif + } + + if (cc > 0) { + memcpy (esPtr->input.buffer + esPtr->input.use, + Tcl_GetUnicodeFromObj (esPtr->input.newchars, NULL), + cc * sizeof (Tcl_UniChar)); + esPtr->input.use += cc; + + expDiagLog("spawn id %s sent <",esPtr->name); + expDiagLogU(expPrintifyUni(eobOld,cc)); + expDiagLogU(">\r\n"); + + esPtr->key = key; + } + return cc; +} + + + +#ifdef SIMPLE_EVENT + +/* + +The way that the "simple" interact works is that the original Expect +process reads from the tty and writes to the spawned process. A child +process is forked to read from the spawned process and write to the +tty. It looks like this: + + user + --> tty >-- + / \ + ^ v + child original + process Expect + ^ process + | v + \ / + < spawned < + process + +*/ + + + +#ifndef WEXITSTATUS +#define WEXITSTATUS(stat) (((*((int *) &(stat))) >> 8) & 0xff) +#endif + +#include <setjmp.h> + +#ifdef HAVE_SIGLONGJMP +static sigjmp_buf env; /* for interruptable read() */ +#else +static jmp_buf env; /* for interruptable read() */ +#endif /* HAVE_SIGLONGJMP */ + +static int reading; /* while we are reading */ + /* really, while "env" is valid */ +static int deferred_interrupt = FALSE; /* if signal is received, but not */ + /* in expIRead record this here, so it will */ + /* be handled next time through expIRead */ + +static void +sigchld_handler() +{ + if (reading) { +#ifdef HAVE_SIGLONGJMP + siglongjmp(env,1); +#else + longjmp(env,1); +#endif /* HAVE_SIGLONGJMP */ + } + deferred_interrupt = TRUE; +} + +#define EXP_CHILD_EOF -100 + +/* + * Name: expIRead, do an interruptable read + * + * intIRead() reads from chars from the user. + * + * It returns early if it detects the death of a proc (either the spawned + * process or the child (surrogate). + */ +static int +intIRead( + Tcl_Channel channel, + Tcl_Obj *obj, + int size, + int flags) +{ + int cc = EXP_CHILD_EOF; + + if (deferred_interrupt) return(cc); + + if ( +#ifdef HAVE_SIGLONGJMP + 0 == sigsetjmp(env,1) +#else + 0 == setjmp(env) +#endif /* HAVE_SIGLONGJMP */ + ) { + reading = TRUE; + cc = Tcl_ReadChars(channel,obj,size,flags); + } + reading = FALSE; + return(cc); +} + +/* exit status for the child process created by cmdInteract */ +#define CHILD_DIED -2 +#define SPAWNED_PROCESS_DIED -3 + +static void +clean_up_after_child( + Tcl_Interp *interp, + ExpState *esPtr) +{ + expWaitOnOne(); /* wait for slave */ + expWaitOnOne(); /* wait for child */ + + deferred_interrupt = FALSE; + if (esPtr->close_on_eof) { + exp_close(interp,esPtr); +} +} +#endif /*SIMPLE_EVENT*/ + +static int +update_interact_fds( + Tcl_Interp *interp, + int *esPtrCount, + Tcl_HashTable **esPtrToInput, /* map from ExpStates to "struct inputs" */ + ExpState ***esPtrs, + struct input *input_base, + int do_indirect, /* if true do indirects */ + int *config_count, + int *real_tty_caller) +{ + struct input *inp; + struct output *outp; + struct exp_state_list *fdp; + int count; + + int real_tty = FALSE; + + *config_count = exp_configure_count; + + count = 0; + for (inp = input_base;inp;inp=inp->next) { + + if (do_indirect) { + /* do not update "direct" entries (again) */ + /* they were updated upon creation */ + if (inp->i_list->direct == EXP_INDIRECT) { + exp_i_update(interp,inp->i_list); + } + for (outp = inp->output;outp;outp=outp->next) { + if (outp->i_list->direct == EXP_INDIRECT) { + exp_i_update(interp,outp->i_list); + } + } + } + + /* revalidate all input descriptors */ + for (fdp = inp->i_list->state_list;fdp;fdp=fdp->next) { + count++; + /* have to "adjust" just in case spawn id hasn't had */ + /* a buffer sized yet */ + if (!expStateCheck(interp,fdp->esPtr,1,1,"interact")) { + return(TCL_ERROR); + } + } + + /* revalidate all output descriptors */ + for (outp = inp->output;outp;outp=outp->next) { + for (fdp = outp->i_list->state_list;fdp;fdp=fdp->next) { + /* make user_spawn_id point to stdout */ + if (!expStdinoutIs(fdp->esPtr)) { + if (!expStateCheck(interp,fdp->esPtr,1,0,"interact")) + return(TCL_ERROR); + } + } + } + } + if (!do_indirect) return TCL_OK; + + if (*esPtrToInput == 0) { + *esPtrToInput = (Tcl_HashTable *)ckalloc(sizeof(Tcl_HashTable)); + *esPtrs = (ExpState **)ckalloc(count * sizeof(ExpState *)); + } else { + /* if hash table already exists, delete it and start over */ + Tcl_DeleteHashTable(*esPtrToInput); + *esPtrs = (ExpState **)ckrealloc((char *)*esPtrs,count * sizeof(ExpState *)); + } + Tcl_InitHashTable(*esPtrToInput,TCL_ONE_WORD_KEYS); + + count = 0; + for (inp = input_base;inp;inp=inp->next) { + for (fdp = inp->i_list->state_list;fdp;fdp=fdp->next) { + /* build map to translate from spawn_id to struct input */ + expCreateStateToInput(*esPtrToInput,fdp->esPtr,inp); + + /* build input to ready() */ + (*esPtrs)[count] = fdp->esPtr; + + if (real_tty_input(fdp->esPtr)) real_tty = TRUE; + + count++; + } + } + *esPtrCount = count; + + *real_tty_caller = real_tty; /* tell caller if we have found that */ + /* we are using real tty */ + + return TCL_OK; +} + +/*ARGSUSED*/ +static char * +inter_updateproc( + 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. */ +{ + exp_configure_count++; + return 0; +} + +#define finish(x) { status = x; goto done; } + +static char return_cmd[] = "return"; +static char interpreter_cmd[] = "interpreter"; + +/*ARGSUSED*/ +int +Exp_InteractObjCmd( + ClientData clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST initial_objv[]) /* Argument objects. */ +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + Tcl_Obj *CONST *objv_copy; /* original, for error messages */ + Tcl_Obj **objv = (Tcl_Obj **) initial_objv; + char *string; + Tcl_UniChar *ustring; + +#ifdef SIMPLE_EVENT + int pid; +#endif /*SIMPLE_EVENT*/ + + /*declarations*/ + int input_count; /* count of struct input descriptors */ + + Tcl_HashTable *esPtrToInput = 0; /* map from ExpState to "struct inputs" */ + ExpState **esPtrs; + struct keymap *km; /* ptr for above while parsing */ + Tcl_RegExpInfo reInfo; + ExpState *u = 0; + ExpState *esPtr = 0; + Tcl_Obj *chanName = 0; + int need_to_close_master = FALSE; /* if an eof is received */ + /* we use this to defer close until later */ + + int next_tty_reset = FALSE; /* if we've seen a single -reset */ + int next_iread = FALSE;/* if we've seen a single -iread */ + int next_iwrite = FALSE;/* if we've seen a single -iread */ + int next_re = FALSE; /* if we've seen a single -re */ + int next_null = FALSE; /* if we've seen the null keyword */ + int next_writethru = FALSE;/*if macros should also go to proc output */ + int next_indices = FALSE;/* if we should write indices */ + int next_echo = FALSE; /* if macros should be echoed */ + int status = TCL_OK; /* final return value */ + int i; /* misc temp */ + int size; /* size temp */ + + int timeout_simple = TRUE; /* if no or global timeout */ + + int real_tty; /* TRUE if we are interacting with real tty */ + int tty_changed = FALSE;/* true if we had to change tty modes for */ + /* interact to work (i.e., to raw, noecho) */ + int was_raw; + int was_echo; + exp_tty tty_old; + + Tcl_Obj *replace_user_by_process = 0; /* for -u flag */ + + struct input *input_base; +#define input_user input_base + struct input *input_default; + struct input *inp; /* overused ptr to struct input */ + struct output *outp; /* overused ptr to struct output */ + + int dash_input_count = 0; /* # of "-input"s seen */ + int dash_o_count = 0; /* # of "-o"s seen */ + int arbitrary_timeout; + int default_timeout; + struct action action_timeout; /* common to all */ + struct action action_eof; /* common to all */ + struct action **action_eof_ptr; /* allow -input/ouput to */ + /* leave their eof-action assignable by a later */ + /* -eof */ + struct action *action_base = 0; + struct keymap **end_km; + + int key; + int configure_count; /* monitor reconfigure events */ + 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; + + /* Replace old arguments with result of reparse */ + Tcl_ListObjGetElements (interp, new_cmd, &objc, &objv); + + } 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; + /* Replace old arguments with result of reparse */ + Tcl_ListObjGetElements (interp, new_cmd, &objc, &objv); + } + + objv_copy = objv; + + objv++; + objc--; + + default_timeout = EXP_TIME_INFINITY; + arbitrary_timeout = EXP_TIME_INFINITY; /* if user specifies */ + /* a bunch of timeouts with EXP_TIME_INFINITY, this will be */ + /* left around for us to find. */ + + input_user = new(struct input); + input_user->i_list = exp_new_i_simple(expStdinoutGet(),EXP_TEMPORARY); /* stdin by default */ + input_user->output = 0; + input_user->action_eof = &action_eof; + input_user->timeout_nominal = EXP_TIME_INFINITY; + input_user->action_timeout = 0; + input_user->keymap = 0; + + end_km = &input_user->keymap; + inp = input_user; + action_eof_ptr = &input_user->action_eof; + + input_default = new(struct input); + input_default->i_list = exp_new_i_simple((ExpState *)0,EXP_TEMPORARY); /* fix up later */ + input_default->output = 0; + input_default->action_eof = &action_eof; + input_default->timeout_nominal = EXP_TIME_INFINITY; + input_default->action_timeout = 0; + input_default->keymap = 0; + input_default->next = 0; /* no one else */ + input_user->next = input_default; + + /* default and common -eof action */ + action_eof.statement = tsdPtr->cmdObjReturn; + action_eof.tty_reset = FALSE; + action_eof.iread = FALSE; + action_eof.iwrite = FALSE; + + /* + * Parse the command arguments. + */ + for (;objc>0;objc--,objv++) { + string = Tcl_GetString(*objv); + if (string[0] == '-') { + static char *switches[] = { + "--", "-exact", "-re", "-input", + "-output", "-u", "-o", "-i", + "-echo", "-nobuffer", "-indices", "-f", + "-reset", "-F", "-iread", "-iwrite", + "-eof", "-timeout", "-nobrace", (char *)0 + }; + enum switches { + EXP_SWITCH_DASH, EXP_SWITCH_EXACT, + EXP_SWITCH_REGEXP, EXP_SWITCH_INPUT, + EXP_SWITCH_OUTPUT, EXP_SWITCH_USER, + EXP_SWITCH_OPPOSITE, EXP_SWITCH_SPAWN_ID, + EXP_SWITCH_ECHO, EXP_SWITCH_NOBUFFER, + EXP_SWITCH_INDICES, EXP_SWITCH_FAST, + EXP_SWITCH_RESET, EXP_SWITCH_CAPFAST, + EXP_SWITCH_IREAD, EXP_SWITCH_IWRITE, + EXP_SWITCH_EOF, EXP_SWITCH_TIMEOUT, + EXP_SWITCH_NOBRACE + }; + int index; + + /* + * Allow abbreviations of switches and report an error if we + * get an invalid switch. + */ + + if (Tcl_GetIndexFromObj(interp, *objv, switches, "switch", 0, + &index) != TCL_OK) { + goto error; + } + switch ((enum switches) index) { + case EXP_SWITCH_DASH: + case EXP_SWITCH_EXACT: + objc--; + objv++; + goto pattern; + case EXP_SWITCH_REGEXP: + if (objc < 1) { + Tcl_WrongNumArgs(interp,1,objv_copy,"-re pattern"); + goto error; + } + next_re = TRUE; + objc--; + objv++; + + /* + * 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, + TCL_REG_ADVANCED|TCL_REG_BOSONLY))) { + goto error; + } + goto pattern; + case EXP_SWITCH_INPUT: + dash_input_count++; + if (dash_input_count == 2) { + inp = input_default; + input_user->next = input_default; + } else if (dash_input_count > 2) { + struct input *previous_input = inp; + inp = new(struct input); + previous_input->next = inp; + } + inp->output = 0; + inp->action_eof = &action_eof; + action_eof_ptr = &inp->action_eof; + inp->timeout_nominal = default_timeout; + inp->action_timeout = &action_timeout; + inp->keymap = 0; + end_km = &inp->keymap; + inp->next = 0; + objc--;objv++; + if (objc < 1) { + Tcl_WrongNumArgs(interp,1,objv_copy,"-input spawn_id"); + goto error; + } + inp->i_list = exp_new_i_complex(interp,Tcl_GetString(*objv), + EXP_TEMPORARY,inter_updateproc); + if (!inp->i_list) { + goto error; + } + break; + case EXP_SWITCH_OUTPUT: { + struct output *tmp; + + /* imply a "-input" */ + if (dash_input_count == 0) dash_input_count = 1; + + outp = new(struct output); + + /* link new output in front of others */ + tmp = inp->output; + inp->output = outp; + outp->next = tmp; + + objc--;objv++; + if (objc < 1) { + Tcl_WrongNumArgs(interp,1,objv_copy,"-output spawn_id"); + goto error; + } + outp->i_list = exp_new_i_complex(interp,Tcl_GetString(*objv), + EXP_TEMPORARY,inter_updateproc); + if (!outp->i_list) { + goto error; + } + outp->action_eof = &action_eof; + action_eof_ptr = &outp->action_eof; + break; + } + case EXP_SWITCH_USER: + objc--;objv++; + if (objc < 1) { + Tcl_WrongNumArgs(interp,1,objv_copy,"-u spawn_id"); + goto error; + } + replace_user_by_process = *objv; + + /* imply a "-input" */ + if (dash_input_count == 0) dash_input_count = 1; + break; + case EXP_SWITCH_OPPOSITE: + /* apply following patterns to opposite side */ + /* of interaction */ + + end_km = &input_default->keymap; + + if (dash_o_count > 0) { + exp_error(interp,"cannot use -o more than once"); + goto error; + } + dash_o_count++; + + /* imply two "-input" */ + if (dash_input_count < 2) { + dash_input_count = 2; + inp = input_default; + action_eof_ptr = &inp->action_eof; + } + break; + case EXP_SWITCH_SPAWN_ID: + /* substitute master */ + + objc--;objv++; + chanName = *objv; + /* will be used later on */ + + end_km = &input_default->keymap; + + /* imply two "-input" */ + if (dash_input_count < 2) { + dash_input_count = 2; + inp = input_default; + action_eof_ptr = &inp->action_eof; + } + break; + case EXP_SWITCH_ECHO: + next_echo = TRUE; + break; + case EXP_SWITCH_NOBUFFER: + next_writethru = TRUE; + break; + case EXP_SWITCH_INDICES: + next_indices = TRUE; + break; + case EXP_SWITCH_RESET: + next_tty_reset = TRUE; + break; + case EXP_SWITCH_IREAD: + next_iread = TRUE; + break; + case EXP_SWITCH_IWRITE: + next_iwrite= TRUE; + break; + case EXP_SWITCH_EOF: { + struct action *action; + + objc--;objv++; + expDiagLogU("-eof is deprecated, use eof\r\n"); + *action_eof_ptr = action = new_action(&action_base); + action->statement = *objv; + action->tty_reset = next_tty_reset; + next_tty_reset = FALSE; + action->iwrite = next_iwrite; + next_iwrite = FALSE; + action->iread = next_iread; + next_iread = FALSE; + break; + } + case EXP_SWITCH_TIMEOUT: { + int t; + struct action *action; + expDiagLogU("-timeout is deprecated, use timeout\r\n"); + + objc--;objv++; + if (objc < 1) { + Tcl_WrongNumArgs(interp,1,objv_copy,"-timeout time"); + goto error; + } + + if (Tcl_GetIntFromObj(interp, *objv, &t) != TCL_OK) { + goto error; + } + objc--;objv++; + if (t != -1) + arbitrary_timeout = t; + /* we need an arbitrary timeout to start */ + /* search for lowest one later */ + + timeout_simple = FALSE; + action = inp->action_timeout = new_action(&action_base); + inp->timeout_nominal = t; + + action->statement = *objv; + action->tty_reset = next_tty_reset; + next_tty_reset = FALSE; + action->iwrite = next_iwrite; + next_iwrite = FALSE; + action->iread = next_iread; + next_iread = FALSE; + break; + } + case EXP_SWITCH_FAST: + case EXP_SWITCH_CAPFAST: + /* noop compatibility switches for fast mode */ + break; + case EXP_SWITCH_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; + } + continue; + } else { + static char *options[] = { + "eof", "timeout", "null", (char *)0 + }; + enum options { + EXP_OPTION_EOF, EXP_OPTION_TIMEOUT, EXP_OPTION_NULL + }; + int index; + + /* + * Match keywords exactly, otherwise they are patterns. + */ + + if (Tcl_GetIndexFromObj(interp, *objv, options, "option", + 1 /* exact */, &index) != TCL_OK) { + Tcl_ResetResult(interp); + goto pattern; + } + switch ((enum options) index) { + case EXP_OPTION_EOF: { + struct action *action; + + objc--;objv++; + *action_eof_ptr = action = new_action(&action_base); + + action->statement = *objv; + + action->tty_reset = next_tty_reset; + next_tty_reset = FALSE; + action->iwrite = next_iwrite; + next_iwrite = FALSE; + action->iread = next_iread; + next_iread = FALSE; + break; + } + case EXP_OPTION_TIMEOUT: { + int t; + struct action *action; + + objc--;objv++; + if (objc < 1) { + Tcl_WrongNumArgs(interp,1,objv_copy,"timeout time [action]"); + goto error; + } + if (Tcl_GetIntFromObj(interp, *objv, &t) != TCL_OK) { + goto error; + } + objc--;objv++; + + /* we need an arbitrary timeout to start */ + /* search for lowest one later */ + if (t != -1) arbitrary_timeout = t; + + timeout_simple = FALSE; + action = inp->action_timeout = new_action(&action_base); + inp->timeout_nominal = t; + + if (objc >= 1) { + action->statement = *objv; + } else { + action->statement = 0; + } + + action->tty_reset = next_tty_reset; + next_tty_reset = FALSE; + action->iwrite = next_iwrite; + next_iwrite = FALSE; + action->iread = next_iread; + next_iread = FALSE; + break; + } + case EXP_OPTION_NULL: + next_null = TRUE; + goto pattern; + } + continue; + } + + /* + * pick up the pattern + */ + + pattern: + km = new(struct keymap); + + /* so that we can match in order user specified */ + /* link to end of keymap list */ + *end_km = km; + km->next = 0; + end_km = &km->next; + + km->echo = next_echo; + km->writethru = next_writethru; + km->indices = next_indices; + km->action.tty_reset = next_tty_reset; + km->action.iwrite = next_iwrite; + km->action.iread = next_iread; + + next_indices = next_echo = next_writethru = FALSE; + next_tty_reset = FALSE; + next_iwrite = next_iread = FALSE; + + km->keys = *objv; + + km->null = FALSE; + km->re = 0; + if (next_re) { + km->re = TRUE; + next_re = FALSE; + } + if (next_null) { + km->null = TRUE; + next_null = FALSE; + } + + objc--;objv++; + if (objc >= 1) { + km->action.statement = *objv; + } else { + km->action.statement = 0; + } + + expDiagLogU("defining key "); + expDiagLogU(Tcl_GetString(km->keys)); + expDiagLogU(", action "); + expDiagLogU(km->action.statement?expPrintify(Tcl_GetString(km->action.statement)):"interpreter"); + expDiagLogU("\r\n"); + + /* imply a "-input" */ + if (dash_input_count == 0) dash_input_count = 1; + } + + /* if the user has not supplied either "-output" for the */ + /* default two "-input"s, fix them up here */ + + if (!input_user->output) { + struct output *o = new(struct output); + if (!chanName) { + if (!(esPtr = expStateCurrent(interp,1,1,0))) { + goto error; + } + o->i_list = exp_new_i_simple(esPtr,EXP_TEMPORARY); + } else { + o->i_list = exp_new_i_complex(interp,Tcl_GetString(chanName), + EXP_TEMPORARY,inter_updateproc); + if (!o->i_list) { + goto error; + } + } + o->next = 0; /* no one else */ + o->action_eof = &action_eof; + input_user->output = o; + } + + if (!input_default->output) { + struct output *o = new(struct output); + o->i_list = exp_new_i_simple(expStdinoutGet(),EXP_TEMPORARY);/* stdout by default */ + o->next = 0; /* no one else */ + o->action_eof = &action_eof; + input_default->output = o; + } + + /* if user has given "-u" flag, substitute process for user */ + /* in first two -inputs */ + if (replace_user_by_process) { + /* through away old ones */ + exp_free_i(interp,input_user->i_list, inter_updateproc); + exp_free_i(interp,input_default->output->i_list,inter_updateproc); + + /* replace with arg to -u */ + input_user->i_list = exp_new_i_complex(interp, + Tcl_GetString(replace_user_by_process), + EXP_TEMPORARY,inter_updateproc); + if (!input_user->i_list) + goto error; + input_default->output->i_list = exp_new_i_complex(interp, + Tcl_GetString(replace_user_by_process), + EXP_TEMPORARY,inter_updateproc); + if (!input_default->output->i_list) + goto error; + } + + /* + * now fix up for default spawn id + */ + + /* user could have replaced it with an indirect, so force update */ + if (input_default->i_list->direct == EXP_INDIRECT) { + exp_i_update(interp,input_default->i_list); + } + + if (input_default->i_list->state_list + && (input_default->i_list->state_list->esPtr == EXP_SPAWN_ID_BAD)) { + if (!chanName) { + if (!(esPtr = expStateCurrent(interp,1,1,0))) { + goto error; + } + input_default->i_list->state_list->esPtr = esPtr; + } else { + /* discard old one and install new one */ + exp_free_i(interp,input_default->i_list,inter_updateproc); + input_default->i_list = exp_new_i_complex(interp,Tcl_GetString(chanName), + EXP_TEMPORARY,inter_updateproc); + if (!input_default->i_list) + goto error; + } + } + + /* + * check for user attempting to interact with self + * they're almost certainly just fooling around + */ + + /* user could have replaced it with an indirect, so force update */ + if (input_user->i_list->direct == EXP_INDIRECT) { + exp_i_update(interp,input_user->i_list); + } + + if (input_user->i_list->state_list && input_default->i_list->state_list + && (input_user->i_list->state_list->esPtr == input_default->i_list->state_list->esPtr)) { + exp_error(interp,"cannot interact with self - set spawn_id to a spawned process"); + goto error; + } + + esPtrs = 0; + + /* + * all data structures are sufficiently set up that we can now + * "finish()" to terminate this procedure + */ + + status = update_interact_fds(interp,&input_count,&esPtrToInput,&esPtrs,input_base,1,&configure_count,&real_tty); + if (status == TCL_ERROR) finish(TCL_ERROR); + + if (real_tty) { + tty_changed = exp_tty_raw_noecho(interp,&tty_old,&was_raw,&was_echo); + } + + for (inp = input_base,i=0;inp;inp=inp->next,i++) { + /* start timers */ + inp->timeout_remaining = inp->timeout_nominal; + } + + key = expect_key++; + + /* declare ourselves "in sync" with external view of close/indirect */ + configure_count = exp_configure_count; + +#ifndef SIMPLE_EVENT + /* loop waiting (in event handler) for input */ + for (;;) { + int te; /* result of Tcl_Eval */ + int rc; /* return code from ready. This is further refined by matcher. */ + int cc; /* # of chars from read() */ + struct action *action = 0; + time_t previous_time; + time_t current_time; + int matchLen; /* # of chars matched */ + int skip; /* # of chars not involved in match */ + int print; /* # of chars to print */ + int oldprinted; /* old version of u->printed */ + int change; /* if action requires cooked mode */ + int attempt_match = TRUE; + struct input *soonest_input; + int timeout; /* current as opposed to default_timeout */ + Tcl_Time temp_time; + + /* calculate how long to wait */ + /* by finding shortest remaining timeout */ + if (timeout_simple) { + timeout = default_timeout; + } else { + timeout = arbitrary_timeout; + + for (inp=input_base;inp;inp=inp->next) { + if ((inp->timeout_remaining != EXP_TIME_INFINITY) && + (inp->timeout_remaining <= timeout)) { + soonest_input = inp; + timeout = inp->timeout_remaining; + } + } + + Tcl_GetTime (&temp_time); + previous_time = temp_time.sec; + /* timestamp here rather than simply saving old */ + /* current time (after ready()) to account for */ + /* possibility of slow actions */ + + /* timeout can actually be EXP_TIME_INFINITY here if user */ + /* explicitly supplied it in a few cases (or */ + /* the count-down code is broken) */ + } + + /* update the world, if necessary */ + if (configure_count != exp_configure_count) { + status = update_interact_fds(interp,&input_count, + &esPtrToInput,&esPtrs,input_base,1, + &configure_count,&real_tty); + if (status) finish(status); + } + + rc = exp_get_next_event(interp,esPtrs,input_count,&u,timeout,key); + if (rc == EXP_TCLERROR) + goto error; + if (rc == EXP_RECONFIGURE) continue; + if (rc == EXP_TIMEOUT) { + if (timeout_simple) { + action = &action_timeout; + goto got_action; + } else { + action = soonest_input->action_timeout; + /* arbitrarily pick first fd out of list */ + u = soonest_input->i_list->state_list->esPtr; + } + } + if (!timeout_simple) { + int time_diff; + + Tcl_GetTime (&temp_time); + current_time = temp_time.sec; + time_diff = current_time - previous_time; + + /* update all timers */ + for (inp=input_base;inp;inp=inp->next) { + if (inp->timeout_remaining != EXP_TIME_INFINITY) { + inp->timeout_remaining -= time_diff; + if (inp->timeout_remaining < 0) + inp->timeout_remaining = 0; + } + } + } + + /* at this point, we have some kind of event which can be */ + /* immediately processed - i.e. something that doesn't block */ + + /* figure out who we are */ + inp = expStateToInput(esPtrToInput,u); + + /* reset timer */ + inp->timeout_remaining = inp->timeout_nominal; + + switch (rc) { + case EXP_DATA_NEW: + cc = intRead(interp,u,1,0,key); + if (cc > 0) break; + + rc = EXP_EOF; + /* + * FALLTHRU + * + * Most systems have read() return 0, allowing + * control to fall thru and into this code. On some + * systems (currently HP and new SGI), read() does + * see eof, and it must be detected earlier. Then + * control jumps directly to this EXP_EOF label. + */ + case EXP_EOF: + action = inp->action_eof; + attempt_match = FALSE; + skip = expSizeGet(u); + expDiagLog("interact: received eof from spawn_id %s\r\n",u->name); + /* actual close is done later so that we have a */ + /* chance to flush out any remaining characters */ + need_to_close_master = TRUE; + break; + case EXP_DATA_OLD: + cc = 0; + break; + case EXP_TIMEOUT: + action = inp->action_timeout; + attempt_match = FALSE; + skip = expSizeGet(u); + break; + } + + km = 0; + + if (attempt_match) { + rc = intMatch(u,inp->keymap,&km,&matchLen,&skip,&reInfo); + if ((rc == EXP_MATCH) && km && km->re) { + intRegExpMatchProcess(interp,u,km,&reInfo,skip); + } + } else { + attempt_match = TRUE; + } + + /* + * dispose of chars that should be skipped + * i.e., chars that cannot possibly be part of a match. + */ + if (km && km->writethru) { + print = skip + matchLen; + } else print = skip; + + if (km && km->echo) { + intEcho(u,skip,matchLen); + } + oldprinted = u->printed; + + /* + * If expect has left characters in buffer, it has + * already echoed them to the screen, thus we must + * prevent them being rewritten. Unfortunately this + * gives the possibility of matching chars that have + * already been output, but we do so since the user + * could have avoided it by flushing the output + * buffers directly. + */ + if (print > u->printed) { /* usual case */ + for (outp = inp->output;outp;outp=outp->next) { + struct exp_state_list *fdp; + for (fdp = outp->i_list->state_list;fdp;fdp=fdp->next) { + /* send to channel (and log if chan is stdout or devtty) */ + /* + * Following should eventually be rewritten to ...WriteCharsAnd... + */ + int wc = expWriteBytesAndLogIfTtyU(fdp->esPtr, + u->input.buffer + u->printed, + print - u->printed); + if (wc < 0) { + expDiagLog("interact: write on spawn id %s failed (%s)\r\n",fdp->esPtr->name,Tcl_PosixError(interp)); + action = outp->action_eof; + change = (action && action->tty_reset); + + if (change && tty_changed) + exp_tty_set(interp,&tty_old,was_raw,was_echo); + te = inter_eval(interp,action,u); + + if (change && real_tty) tty_changed = + exp_tty_raw_noecho(interp,&tty_old,&was_raw,&was_echo); + switch (te) { + case TCL_BREAK: + case TCL_CONTINUE: + finish(te); + case EXP_TCL_RETURN: + finish(TCL_RETURN); + case TCL_RETURN: + finish(TCL_OK); + case TCL_OK: + /* god knows what the user might */ + /* have done to us in the way of */ + /* closed fds, so .... */ + action = 0; /* reset action */ + continue; + default: + finish(te); + } + } + } + } + u->printed = print; + } + + /* u->printed is now accurate with respect to the buffer */ + /* However, we're about to shift the old data out of the */ + /* buffer. Thus size, printed, and echoed must be */ + /* updated */ + + /* first update size based on skip information */ + /* then set skip to the total amount skipped */ + + size = expSizeGet(u); + if (rc == EXP_MATCH) { + action = &km->action; + + skip += matchLen; + size -= skip; + if (size) { + ustring = u->input.buffer; + memmove(ustring, ustring + skip, size * sizeof(Tcl_UniChar)); + } + } else { + ustring = u->input.buffer; + if (skip) { + size -= skip; + memcpy(ustring, ustring + skip, size * sizeof(Tcl_UniChar)); + } + } + u->input.use = size; + + /* now update printed based on total amount skipped */ + + u->printed -= skip; + /* if more skipped than printed (i.e., keymap encountered) */ + /* for printed positive */ + if (u->printed < 0) u->printed = 0; + + /* if we are in the middle of a match, force the next event */ + /* to wait for more data to arrive */ + u->force_read = (rc == EXP_CANMATCH); + + /* finally reset echoed if necessary */ + if (rc != EXP_CANMATCH) { + if (skip >= oldprinted + u->echoed) u->echoed = 0; + } + + if (rc == EXP_EOF) { + if (u->close_on_eof) { + exp_close(interp,u); + } + need_to_close_master = FALSE; + } + + if (action) { +got_action: + change = (action && action->tty_reset); + if (change && tty_changed) + exp_tty_set(interp,&tty_old,was_raw,was_echo); + + te = inter_eval(interp,action,u); + + if (change && real_tty) tty_changed = + exp_tty_raw_noecho(interp,&tty_old,&was_raw,&was_echo); + switch (te) { + case TCL_BREAK: + case TCL_CONTINUE: + finish(te); + case EXP_TCL_RETURN: + finish(TCL_RETURN); + case TCL_RETURN: + finish(TCL_OK); + case TCL_OK: + /* god knows what the user might */ + /* have done to us in the way of */ + /* closed fds, so .... */ + action = 0; /* reset action */ + continue; + default: + finish(te); + } + } + } + +#else /* SIMPLE_EVENT */ +/* deferred_interrupt = FALSE;*/ +{ + int te; /* result of Tcl_Eval */ + ExpState *u; /*master*/ + int rc; /* return code from ready. This is further */ + /* refined by matcher. */ + int cc; /* chars count from read() */ + struct action *action = 0; + time_t previous_time; + time_t current_time; + int matchLen, skip; + int change; /* if action requires cooked mode */ + int attempt_match = TRUE; + struct input *soonest_input; + int print; /* # of chars to print */ + int oldprinted; /* old version of u->printed */ + + int timeout; /* current as opposed to default_timeout */ + + if (-1 == (pid = fork())) { + exp_error(interp,"fork: %s",Tcl_PosixError(interp)); + finish(TCL_ERROR); + } + if (pid == 0) { + /* + * This is a new child process. + * It exists only for this interact command and will go away when + * the interact returns. + * + * The purpose of this child process is to read output from the + * spawned process and send it to the user tty. + * (See diagram above.) + */ + + exp_close(interp,expStdinoutGet()); + + u = esPtrs[1]; /* get 2nd ExpState */ + input_count = 1; + + while (1) { + + /* calculate how long to wait */ + /* by finding shortest remaining timeout */ + if (timeout_simple) { + timeout = default_timeout; + } else { + timeout = arbitrary_timeout; + + for (inp=input_base;inp;inp=inp->next) { + if ((inp->timeout_remaining != EXP_TIME_INFINITY) && + (inp->timeout_remaining < timeout)) + soonest_input = inp; + timeout = inp->timeout_remaining; + } + + Tcl_GetTime (&temp_time); + previous_time = temp_time.sec; + /* timestamp here rather than simply saving old */ + /* current time (after ready()) to account for */ + /* possibility of slow actions */ + + /* timeout can actually be EXP_TIME_INFINITY here if user */ + /* explicitly supplied it in a few cases (or */ + /* the count-down code is broken) */ + } + + /* +1 so we can look at the "other" file descriptor */ + rc = exp_get_next_event(interp,esPtrs+1,input_count,&u,timeout,key); + if (!timeout_simple) { + int time_diff; + + Tcl_GetTime (&temp_time); + current_time = temp_time.sec; + time_diff = current_time - previous_time; + + /* update all timers */ + for (inp=input_base;inp;inp=inp->next) { + if (inp->timeout_remaining != EXP_TIME_INFINITY) { + inp->timeout_remaining -= time_diff; + if (inp->timeout_remaining < 0) + inp->timeout_remaining = 0; + } + } + } + + /* at this point, we have some kind of event which can be */ + /* immediately processed - i.e. something that doesn't block */ + + /* figure out who we are */ + inp = expStateToInput(esPtrToInput,u); + + switch (rc) { + case EXP_DATA_NEW: + cc = intRead(interp,u,0,0,key); + if (cc > 0) break; + /* + * FALLTHRU + * + * Most systems have read() return 0, allowing + * control to fall thru and into this code. On some + * systems (currently HP and new SGI), read() does + * see eof, and it must be detected earlier. Then + * control jumps directly to this EXP_EOF label. + */ + case EXP_EOF: + action = inp->action_eof; + attempt_match = FALSE; + skip = expSizeGet(u); + rc = EXP_EOF; + expDiagLog("interact: child received eof from spawn_id %s\r\n",u->name); + exp_close(interp,u); + break; + case EXP_DATA_OLD: + cc = 0; + break; + } + + km = 0; + + if (attempt_match) { + rc = intMatch(u,inp->keymap,&km,&matchLen,&skip,&reInfo); + if ((rc == EXP_MATCH) && km && km->re) { + intRegExpMatchProcess(interp,u,km,&reInfo,skip); + } + } else { + attempt_match = TRUE; + } + + /* dispose of chars that should be skipped */ + + /* skip is chars not involved in match */ + /* print is with chars involved in match */ + + if (km && km->writethru) { + print = skip + matchLen; + } else print = skip; + + if (km && km->echo) { + intEcho(u,skip,matchLen); + } + oldprinted = u->printed; + + /* If expect has left characters in buffer, it has */ + /* already echoed them to the screen, thus we must */ + /* prevent them being rewritten. Unfortunately this */ + /* gives the possibility of matching chars that have */ + /* already been output, but we do so since the user */ + /* could have avoided it by flushing the output */ + /* buffers directly. */ + if (print > u->printed) { /* usual case */ + for (outp = inp->output;outp;outp=outp->next) { + struct exp_state_list *fdp; + for (fdp = outp->i_list->state_list;fdp;fdp=fdp->next) { + /* send to channel (and log if chan is stdout or devtty) */ + int wc = expWriteBytesAndLogIfTtyU(fdp->esPtr, + u->input.buffer + u->printed, + print - u->printed); + if (wc < 0) { + expDiagLog("interact: write on spawn id %s failed (%s)\r\n",fdp->esPtr->name,Tcl_PosixError(interp)); + action = outp->action_eof; + + te = inter_eval(interp,action,u); + + switch (te) { + case TCL_BREAK: + case TCL_CONTINUE: + finish(te); + case EXP_TCL_RETURN: + finish(TCL_RETURN); + case TCL_RETURN: + finish(TCL_OK); + case TCL_OK: + /* god knows what the user might */ + /* have done to us in the way of */ + /* closed fds, so .... */ + action = 0; /* reset action */ + continue; + default: + finish(te); + } + } + } + } + u->printed = print; + } + + /* u->printed is now accurate with respect to the buffer */ + /* However, we're about to shift the old data out of the */ + /* buffer. Thus size, printed, and echoed must be */ + /* updated */ + + /* first update size based on skip information */ + /* then set skip to the total amount skipped */ + + size = expSizeGet(u); + if (rc == EXP_MATCH) { + action = &km->action; + + skip += matchLen; + size -= skip; + if (size) { + memcpy(u->buffer, u->buffer + skip, size); + } + } else { + if (skip) { + size -= skip; + memcpy(u->buffer, u->buffer + skip, size); + } + } + Tcl_SetObjLength(size); + + /* now update printed based on total amount skipped */ + + u->printed -= skip; + /* if more skipped than printed (i.e., keymap encountered) */ + /* for printed positive */ + if (u->printed < 0) u->printed = 0; + + /* if we are in the middle of a match, force the next event */ + /* to wait for more data to arrive */ + u->force_read = (rc == EXP_CANMATCH); + + /* finally reset echoed if necessary */ + if (rc != EXP_CANMATCH) { + if (skip >= oldprinted + u->echoed) u->echoed = 0; + } + + if (action) { + te = inter_eval(interp,action,u); + switch (te) { + case TCL_BREAK: + case TCL_CONTINUE: + finish(te); + case EXP_TCL_RETURN: + finish(TCL_RETURN); + case TCL_RETURN: + finish(TCL_OK); + case TCL_OK: + /* god knows what the user might */ + /* have done to us in the way of */ + /* closed fds, so .... */ + action = 0; /* reset action */ + continue; + default: + finish(te); + } + } + } + } else { + /* + * This is the original Expect process. + * + * It now loops, reading keystrokes from the user tty + * and sending them to the spawned process. + * (See diagram above.) + */ + +#include <signal.h> + +#if defined(SIGCLD) && !defined(SIGCHLD) +#define SIGCHLD SIGCLD +#endif + expDiagLog("fork = %d\r\n",pid); + signal(SIGCHLD,sigchld_handler); +/* restart:*/ +/* tty_changed = exp_tty_raw_noecho(interp,&tty_old,&was_raw,&was_echo);*/ + + u = esPtrs[0]; /* get 1st ExpState */ + input_count = 1; + + while (1) { + /* calculate how long to wait */ + /* by finding shortest remaining timeout */ + if (timeout_simple) { + timeout = default_timeout; + } else { + timeout = arbitrary_timeout; + + for (inp=input_base;inp;inp=inp->next) { + if ((inp->timeout_remaining != EXP_TIME_INFINITY) && + (inp->timeout_remaining < timeout)) + soonest_input = inp; + timeout = inp->timeout_remaining; + } + + Tcl_GetTime (&temp_time); + previous_time = temp_time.sec; + /* timestamp here rather than simply saving old */ + /* current time (after ready()) to account for */ + /* possibility of slow actions */ + + /* timeout can actually be EXP_TIME_INFINITY here if user */ + /* explicitly supplied it in a few cases (or */ + /* the count-down code is broken) */ + } + + rc = exp_get_next_event(interp,esPtrs,input_count,&u,timeout,key); + if (!timeout_simple) { + int time_diff; + + Tcl_GetTime (&temp_time); + current_time = temp_time.sec; + time_diff = current_time - previous_time; + + /* update all timers */ + for (inp=input_base;inp;inp=inp->next) { + if (inp->timeout_remaining != EXP_TIME_INFINITY) { + inp->timeout_remaining -= time_diff; + if (inp->timeout_remaining < 0) + inp->timeout_remaining = 0; + } + } + } + + /* at this point, we have some kind of event which can be */ + /* immediately processed - i.e. something that doesn't block */ + + /* figure out who we are */ + inp = expStateToInput(esPtrToInput,u); + + switch (rc) { + case EXP_DATA_NEW: + cc = intRead(interp,u,0,1,key); + if (cc > 0) { + break; + } else if (cc == EXP_CHILD_EOF) { + /* user could potentially have two outputs in which */ + /* case we might be looking at the wrong one, but */ + /* the likelihood of this is nil */ + action = inp->output->action_eof; + attempt_match = FALSE; + skip = expSizeGet(u); + rc = EXP_EOF; + expDiagLogU("interact: process died/eof\r\n"); + clean_up_after_child(interp,esPtrs[1]); + break; + } + /* + * FALLTHRU + * + * Most systems have read() return 0, allowing + * control to fall thru and into this code. On some + * systems (currently HP and new SGI), read() does + * see eof, and it must be detected earlier. Then + * control jumps directly to this EXP_EOF label. + */ + case EXP_EOF: + action = inp->action_eof; + attempt_match = FALSE; + skip = expSizeGet(u); + rc = EXP_EOF; + expDiagLogU("user sent EOF or disappeared\n\n"); + break; + case EXP_DATA_OLD: + cc = 0; + break; + } + + km = 0; + + if (attempt_match) { + rc = intMatch(u,inp->keymap,&km,&matchLen,&skip,&reInfo); + if ((rc == EXP_MATCH) && km && km->re) { + intRegExpMatchProcess(interp,u,km,&reInfo,skip); + } + } else { + attempt_match = TRUE; + } + + /* dispose of chars that should be skipped */ + + /* skip is chars not involved in match */ + /* print is with chars involved in match */ + + if (km && km->writethru) { + print = skip + matchLen; + } else print = skip; + + if (km && km->echo) { + intEcho(u,skip,matchLen); + } + oldprinted = u->printed; + + /* If expect has left characters in buffer, it has */ + /* already echoed them to the screen, thus we must */ + /* prevent them being rewritten. Unfortunately this */ + /* gives the possibility of matching chars that have */ + /* already been output, but we do so since the user */ + /* could have avoided it by flushing the output */ + /* buffers directly. */ + if (print > u->printed) { /* usual case */ + for (outp = inp->output;outp;outp=outp->next) { + struct exp_state_list *fdp; + for (fdp = outp->i_list->state_list;fdp;fdp=fdp->next) { + /* send to channel (and log if chan is stdout or devtty) */ + int wc = expWriteBytesAndLogIfTtyU(fdp->esPtr, + u->input.buffer + u->printed, + print - u->printed); + if (wc < 0) { + expDiagLog("interact: write on spawn id %s failed (%s)\r\n",fdp->esPtr->name,Tcl_PosixError(interp)); + clean_up_after_child(interp,fdp->esPtr); + action = outp->action_eof; + change = (action && action->tty_reset); + if (change && tty_changed) + exp_tty_set(interp,&tty_old,was_raw,was_echo); + te = inter_eval(interp,action,u); + + if (change && real_tty) tty_changed = + exp_tty_raw_noecho(interp,&tty_old,&was_raw,&was_echo); + switch (te) { + case TCL_BREAK: + case TCL_CONTINUE: + finish(te); + case EXP_TCL_RETURN: + finish(TCL_RETURN); + case TCL_RETURN: + finish(TCL_OK); + case TCL_OK: + /* god knows what the user might */ + /* have done to us in the way of */ + /* closed fds, so .... */ + action = 0; /* reset action */ + continue; + default: + finish(te); + } + } + } + } + u->printed = print; + } + + /* u->printed is now accurate with respect to the buffer */ + /* However, we're about to shift the old data out of the */ + /* buffer. Thus size, printed, and echoed must be */ + /* updated */ + + /* first update size based on skip information */ + /* then set skip to the total amount skipped */ + + size = expSizeGet(u); + if (rc == EXP_MATCH) { + action = &km->action; + + skip += matchLen; + size -= skip; + if (size) { + memcpy(u->buffer, u->buffer + skip, size); + } + } else { + if (skip) { + size -= skip; + memcpy(u->buffer, u->buffer + skip, size); + } + } + Tcl_SetObjLength(size); + + /* now update printed based on total amount skipped */ + + u->printed -= skip; + /* if more skipped than printed (i.e., keymap encountered) */ + /* for printed positive */ + if (u->printed < 0) u->printed = 0; + + /* if we are in the middle of a match, force the next event */ + /* to wait for more data to arrive */ + u->force_read = (rc == EXP_CANMATCH); + + /* finally reset echoed if necessary */ + if (rc != EXP_CANMATCH) { + if (skip >= oldprinted + u->echoed) u->echoed = 0; + } + + if (action) { + change = (action && action->tty_reset); + if (change && tty_changed) + exp_tty_set(interp,&tty_old,was_raw,was_echo); + + te = inter_eval(interp,action,u); + + if (change && real_tty) tty_changed = + exp_tty_raw_noecho(interp,&tty_old,&was_raw,&was_echo); + switch (te) { + case TCL_BREAK: + case TCL_CONTINUE: + finish(te); + case EXP_TCL_RETURN: + finish(TCL_RETURN); + case TCL_RETURN: + finish(TCL_OK); + case TCL_OK: + /* god knows what the user might */ + /* have done to us in the way of */ + /* closed fds, so .... */ + action = 0; /* reset action */ + continue; + default: + finish(te); + } + } + } + } +} +#endif /* SIMPLE_EVENT */ + + done: +#ifdef SIMPLE_EVENT + /* force child to exit upon eof from master */ + if (pid == 0) { + exit(SPAWNED_PROCESS_DIED); + } +#endif /* SIMPLE_EVENT */ + + if (need_to_close_master && u->close_on_eof) exp_close(interp,u); + + if (tty_changed) exp_tty_set(interp,&tty_old,was_raw,was_echo); + if (esPtrs) ckfree((char *)esPtrs); + if (esPtrToInput) Tcl_DeleteHashTable(esPtrToInput); + free_input(interp,input_base); + free_action(action_base); + + if (new_cmd) { Tcl_DecrRefCount (new_cmd); } + return(status); + + error: + if (new_cmd) { Tcl_DecrRefCount (new_cmd); } + return TCL_ERROR; +} + +/* version of Tcl_Eval for interact */ +static int +inter_eval( + Tcl_Interp *interp, + struct action *action, + ExpState *esPtr) +{ + int status; + + if (action->iwrite) { + out("spawn_id",esPtr->name); + } + + if (action->statement) { + status = Tcl_EvalObjEx(interp,action->statement,0); + } else { + expStdoutLogU("\r\n",1); + status = exp_interpreter(interp,(Tcl_Obj *)0); + } + + return status; +} + +static void +free_keymap(struct keymap *km) +{ + if (km == 0) return; + free_keymap(km->next); + + ckfree((char *)km); +} + +static void +free_action(struct action *a) +{ + struct action *next; + + while (a) { + next = a->next; + ckfree((char *)a); + a = next; + } +} + +static void +free_input( + Tcl_Interp *interp, + struct input *i) +{ + if (i == 0) return; + free_input(interp,i->next); + + exp_free_i(interp,i->i_list,inter_updateproc); + free_output(interp,i->output); + free_keymap(i->keymap); + ckfree((char *)i); +} + +static struct action * +new_action(struct action **base) +{ + struct action *o = new(struct action); + + /* stick new action into beginning of list of all actions */ + o->next = *base; + *base = o; + + return o; +} + +static void +free_output( + Tcl_Interp *interp, + struct output *o) +{ + if (o == 0) return; + free_output(interp,o->next); + exp_free_i(interp,o->i_list,inter_updateproc); + + ckfree((char *)o); +} + + +static struct exp_cmd_data cmd_data[] = { +{"interact", Exp_InteractObjCmd, 0, 0, 0}, +{0}}; + +void +exp_init_interact_cmds(Tcl_Interp *interp) +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + exp_create_commands(interp,cmd_data); + + tsdPtr->cmdObjReturn = Tcl_NewStringObj("return",6); + Tcl_IncrRefCount(tsdPtr->cmdObjReturn); +#if 0 + tsdPtr->cmdObjInterpreter = Tcl_NewStringObj("interpreter",11); + Tcl_IncrRefCount(tsdPtr->cmdObjInterpreter); +#endif +} + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */ |