1 /* exp_command.c - the bulk of the Expect commands
3 Written by: Don Libes, NIST, 2/6/90
5 Design and implementation of this program was paid for by U.S. tax
6 dollars. Therefore it is public domain. However, the author and NIST
7 would appreciate credit if this program or parts of it are used.
11 #include "expect_cf.h"
14 #include <sys/types.h>
15 /*#include <sys/time.h> seems to not be present on SVR3 systems */
16 /* and it's not used anyway as far as I can tell */
18 /* AIX insists that stropts.h be included before ioctl.h, because both */
19 /* define _IO but only ioctl.h checks first. Oddly, they seem to be */
20 /* defined differently! */
22 # include <sys/stropts.h>
24 #include <sys/ioctl.h>
26 #ifdef HAVE_SYS_FCNTL_H
27 # include <sys/fcntl.h>
36 #if defined(SIGCLD) && !defined(SIGCHLD)
37 #define SIGCHLD SIGCLD
41 #include <sys/ptyio.h>
46 # if defined(HAVE_TERMIOS)
58 #include <math.h> /* for log/pow computation in send -h */
59 #include <ctype.h> /* all this for ispunct! */
61 #include "tclInt.h" /* need OpenFile */
62 /*#include <varargs.h> tclInt.h drags in varargs.h. Since Pyramid */
63 /* objects to including varargs.h twice, just */
69 #include "expect_tcl.h"
70 #include "exp_rename.h"
72 #include "exp_command.h"
74 #include "exp_event.h"
76 #include "exp_tty_in.h"
82 * These constants refer to the UTF string that encodes a null character.
85 #define NULL_STRING "\300\200" /* hex C080 */
88 #define SPAWN_ID_VARNAME "spawn_id"
90 void exp_ecmd_remove_state_direct_and_indirect(Tcl_Interp *interp, ExpState *esPtr);
93 int exp_forked = FALSE; /* whether we are child process */
95 /* the following are use to create reserved addresses, to be used as ClientData */
96 /* args to be used to tell commands how they were called. */
97 /* The actual values won't be used, only the addresses, but I give them */
98 /* values out of my irrational fear the compiler might collapse them all. */
99 static int sendCD_error = 2; /* called as send_error */
100 static int sendCD_user = 3; /* called as send_user */
101 static int sendCD_proc = 4; /* called as send or send_spawn */
102 static int sendCD_tty = 6; /* called as send_tty */
105 * expect_key is just a source for generating a unique stamp. As each
106 * expect/interact command begins, it generates a new key and marks all
107 * the spawn ids of interest with it. Then, if someone comes along and
108 * marks them with yet a newer key, the old command will recognize this
109 * reexamine the state of the spawned process.
114 * exp_configure_count is incremented whenever a spawned process is closed
115 * or an indirect list is modified. This forces any (stack of) expect or
116 * interact commands to reexamine the state of the world and adjust
119 int exp_configure_count = 0;
122 /* slaveNames provides a mapping from the pty slave names to our */
123 /* spawn id entry. This is needed only on HPs for stty, sigh. */
124 static Tcl_HashTable slaveNames;
125 #endif /* HAVE_PTYTRAP */
127 typedef struct ThreadSpecificData {
129 * List of all exp channels currently open. This is per thread and is
130 * used to match up fd's to channels, which rarely occurs.
134 ExpState *stderrX; /* grr....stderr is a macro */
136 ExpState *any; /* for any_spawn_id */
138 Tcl_Channel *diagChannel; /* Unused - exp_log.c has its own. */
139 Tcl_DString diagDString; /* Unused */
140 int diagEnabled; /* Unused */
142 /* Table of structures for all Tcl channels used as -open argument
143 * in a exp_spawn call. For refCounting of Tcl channels used by
144 * more than one Expect channel.
147 Tcl_HashTable origins;
149 } ThreadSpecificData;
151 static Tcl_ThreadDataKey dataKey;
155 init_traps(RETSIGTYPE (*traps[])())
159 for (i=1;i<NSIG;i++) {
165 /* Do not terminate format strings with \n!!! */
168 exp_error TCL_VARARGS_DEF(Tcl_Interp *,arg1)
169 /*exp_error(va_alist)*/
177 interp = TCL_VARARGS_START(Tcl_Interp *,arg1,args);
178 fmt = va_arg(args,char *);
179 vsprintf(buffer,fmt,args);
180 Tcl_SetResult(interp,buffer,TCL_VOLATILE);
184 /* returns current ExpState or 0. If 0, may be immediately followed by return TCL_ERROR. */
192 static char *user_spawn_id = "exp0";
194 char *name = exp_get_var(interp,SPAWN_ID_VARNAME);
195 if (!name) name = user_spawn_id;
197 return expStateFromChannelName(interp,name,opened,adjust,any,SPAWN_ID_VARNAME);
208 if (open && !esPtr->open) {
209 exp_error(interp,"%s: spawn id %s not open",msg,esPtr->name);
212 if (adjust) expAdjust(esPtr);
217 expStateFromChannelName(
227 CONST char *chanName;
230 if (0 == strcmp(name,EXP_SPAWN_ID_ANY_LIT)) {
231 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
236 channel = Tcl_GetChannel(interp,name,(int *)0);
237 if (!channel) return(0);
239 chanName = Tcl_GetChannelName(channel);
240 if (!isExpChannelName(chanName)) {
241 exp_error(interp,"%s: %s is not an expect channel - use spawn -open to convert",msg,chanName);
245 esPtr = (ExpState *)Tcl_GetChannelInstanceData(channel);
247 return expStateCheck(interp,esPtr,open,adjust,msg);
250 /* zero out the wait status field */
252 exp_wait_zero(WAIT_STATUS_TYPE *status)
256 for (i=0;i<sizeof(WAIT_STATUS_TYPE);i++) {
257 ((char *)status)[i] = 0;
261 /* called just before an ExpState entry is about to be invalidated */
263 exp_state_prep_for_invalidation(
267 exp_ecmd_remove_state_direct_and_indirect(interp,esPtr);
269 exp_configure_count++;
271 if (esPtr->fg_armed) {
272 exp_event_disarm_fg(esPtr);
278 exp_trap_on(int master)
281 if (master == -1) return;
282 exp_slave_control(master,1);
283 #endif /* HAVE_PTYTRAP */
287 exp_trap_off(char *name)
293 Tcl_HashEntry *entry = Tcl_FindHashEntry(&slaveNames,name);
295 expDiagLog("exp_trap_off: no entry found for %s\n",name);
299 esPtr = (ExpState *)Tcl_GetHashValue(entry);
301 exp_slave_control(esPtr->fdin,0);
305 return name[0]; /* pacify lint, use arg and return something */
311 expBusy(ExpState *esPtr)
313 int x = open("/dev/null",0);
314 if (x != esPtr->fdin) {
315 fcntl(x,F_DUPFD,esPtr->fdin);
318 expCloseOnExec(esPtr->fdin);
319 esPtr->fdBusy = TRUE;
327 if (0 == expStateCheck(interp,esPtr,1,0,"close")) return TCL_ERROR;
330 /* restore blocking for some shells that would otherwise be */
331 /* surprised finding stdio or /dev/tty nonblocking */
332 (void) Tcl_SetChannelOption(interp,esPtr->channel,"-blocking","on");
334 /* Since we're closing the channel, not Tcl, we need to get Tcl's
335 buffers flushed. Because the channel was nonblocking, EAGAINs
336 could leave things buffered. They need to be synchronously
338 Tcl_Flush(esPtr->channel);
341 * Ignore close errors from ptys. Ptys on some systems return errors for
342 * no evident reason. Anyway, receiving an error upon pty-close doesn't
343 * mean anything anyway as far as I know.
347 if (esPtr->fd_slave != EXP_NOFD) close(esPtr->fd_slave);
348 if (esPtr->fdin != esPtr->fdout) close(esPtr->fdout);
350 if (esPtr->chan_orig) {
351 esPtr->chan_orig->refCount --;
352 if (esPtr->chan_orig->refCount <= 0) {
354 * Ignore close errors from Tcl channels. They indicate things
355 * like broken pipelines, etc, which don't affect our
356 * subsequent handling.
359 ThreadSpecificData* tsdPtr = TCL_TSD_INIT(&dataKey);
360 char* cName = Tcl_GetChannelName(esPtr->chan_orig->channel_orig);
361 Tcl_HashEntry* entry = Tcl_FindHashEntry(&tsdPtr->origins,cName);
362 ExpOrigin* orig = (ExpOrigin*) Tcl_GetHashValue(entry);
364 Tcl_DeleteHashEntry(entry);
365 ckfree ((char*)orig);
367 if (!esPtr->leaveopen) {
368 Tcl_VarEval(interp,"close ", cName, (char *)0);
374 if (esPtr->slave_name) {
375 Tcl_HashEntry *entry;
377 entry = Tcl_FindHashEntry(&slaveNames,esPtr->slave_name);
378 Tcl_DeleteHashEntry(entry);
380 ckfree(esPtr->slave_name);
381 esPtr->slave_name = 0;
385 exp_state_prep_for_invalidation(interp,esPtr);
387 if (esPtr->user_waited) {
388 if (esPtr->registered) {
389 Tcl_UnregisterChannel(interp,esPtr->channel);
390 /* at this point esPtr may have been freed so don't touch it
400 /* report whether this ExpState represents special spawn_id_any */
401 /* we need a separate function because spawn_id_any is thread-specific */
402 /* and can't be seen outside this file */
404 expStateAnyIs(ExpState *esPtr)
406 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
408 return (esPtr == tsdPtr->any);
412 expDevttyIs(ExpState *esPtr)
414 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
416 return (esPtr == tsdPtr->devtty);
420 expStdinoutIs(ExpState *esPtr)
422 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
424 return (tsdPtr->stdinout == esPtr);
430 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
432 return tsdPtr->stdinout;
438 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
440 return tsdPtr->devtty;
444 exp_init_spawn_id_vars(Tcl_Interp *interp)
446 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
448 Tcl_SetVar(interp, "user_spawn_id", tsdPtr->stdinout->name,0);
449 Tcl_SetVar(interp,"error_spawn_id", tsdPtr->stderrX->name,0);
450 Tcl_SetVar(interp, "any_spawn_id", EXP_SPAWN_ID_ANY_LIT,0);
452 /* user_spawn_id is NOT /dev/tty which could (at least in theory
453 * anyway) be later re-opened on a different fd, while stdin might
454 * have been redirected away from /dev/tty
457 if (exp_dev_tty != -1) {
458 Tcl_SetVar(interp,"tty_spawn_id",tsdPtr->devtty->name,0);
463 exp_init_spawn_ids(Tcl_Interp *interp)
465 static ExpState any_placeholder; /* can be shared process-wide */
467 /* note whether 0,1,2 are connected to a terminal so that if we */
468 /* disconnect, we can shut these down. We would really like to */
469 /* test if 0,1,2 are our controlling tty, but I don't know any */
470 /* way to do that portably. Anyway, the likelihood of anyone */
471 /* disconnecting after redirecting to a non-controlling tty is */
472 /* virtually zero. */
474 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
476 tsdPtr->stdinout = expCreateChannel(interp,0,1,isatty(0)?exp_getpid:EXP_NOPID);
477 tsdPtr->stdinout->keepForever = 1;
478 /* hmm, now here's an example of a output-only descriptor!! */
479 tsdPtr->stderrX = expCreateChannel(interp,2,2,isatty(2)?exp_getpid:EXP_NOPID);
480 tsdPtr->stderrX->keepForever = 1;
482 if (exp_dev_tty != -1) {
483 tsdPtr->devtty = expCreateChannel(interp,exp_dev_tty,exp_dev_tty,exp_getpid);
484 tsdPtr->devtty->keepForever = 1;
487 /* set up a dummy channel to give us something when we need to find out if
488 people have passed us "any_spawn_id" */
489 tsdPtr->any = &any_placeholder;
491 /* Set up the hash table for managing the channels used via
495 Tcl_InitHashTable (&tsdPtr->origins, TCL_STRING_KEYS);
499 expCloseOnExec(int fd)
501 (void) fcntl(fd,F_SETFD,1);
504 #define STTY_INIT "stty_init"
508 * DEBUGGING UTILITIES - DON'T DELETE */
516 fprintf(stderr,"getting pgrp for %s\n",string);
517 if (-1 == ioctl(fd,TIOCGETPGRP,&pgrp)) perror("TIOCGETPGRP");
518 else fprintf(stderr,"%s pgrp = %d\n",string,pgrp);
519 if (-1 == ioctl(fd,TIOCGPGRP,&pgrp)) perror("TIOCGPGRP");
520 else fprintf(stderr,"%s pgrp = %d\n",string,pgrp);
521 if (-1 == tcgetpgrp(fd,pgrp)) perror("tcgetpgrp");
522 else fprintf(stderr,"%s pgrp = %d\n",string,pgrp);
528 int pgrp = getpgrp(0);
529 if (-1 == ioctl(fd,TIOCSETPGRP,&pgrp)) perror("TIOCSETPGRP");
530 if (-1 == ioctl(fd,TIOCSPGRP,&pgrp)) perror("TIOCSPGRP");
531 if (-1 == tcsetpgrp(fd,pgrp)) perror("tcsetpgrp");
540 /* required on BSD side of MIPS OS <jmsellen@watdragon.waterloo.edu> */
541 # include <sysv/sys.s>
542 syscall(SYS_setpgrp);
561 Tcl_HashEntry *entry;
563 /* save slave name */
564 esPtr->slave_name = ckalloc(strlen(exp_pty_slave_name)+1);
565 strcpy(esPtr->slave_name,exp_pty_slave_name);
567 entry = Tcl_CreateHashEntry(&slaveNames,exp_pty_slave_name,&newptr);
568 Tcl_SetHashValue(entry,(ClientData)esPtr);
569 #endif /* HAVE_PTYTRAP */
572 /* arguments are passed verbatim to execvp() */
576 ClientData clientData,
579 Tcl_Obj *CONST objv[]) /* Argument objects. */
585 /* tell Saber to ignore non-use of ttyfd */
588 #endif /* TIOCNOTTY */
589 int errorfd; /* place to stash fileno(stderr) in child */
590 /* while we're setting up new stderr */
592 int write_master; /* write fd of Tcl-opened files */
597 int pty_only = FALSE;
601 /* Allow user to reset signals in child */
602 /* The following array contains indicates */
603 /* whether sig should be DFL or IGN */
604 /* ERR is used to indicate no initialization */
605 RETSIGTYPE (*traps[NSIG])();
607 int ignore[NSIG]; /* if true, signal in child is ignored */
608 /* if false, signal gets default behavior */
609 int i; /* trusty overused temporary */
611 char *argv0 = Tcl_GetString (objv[0]);
613 int leaveopen = FALSE;
615 CONST char *stty_init;
616 int slave_write_ioctls = 1;
617 /* by default, slave will be write-ioctled this many times */
619 /* by default, slave will be opened this many times */
620 /* first comes from initial allocation */
621 /* second comes from stty */
622 /* third is our own signal that stty is done */
633 static char* options[] = {
663 Tcl_DStringInit(&dstring);
668 /* don't ignore any signals in child by default */
669 for (i=1;i<NSIG;i++) {
673 /* Check and process switches */
675 for (i=1; i<objc; i++) {
679 name = Tcl_GetString(objv[i]);
680 if (name[0] != '-') {
683 if (Tcl_GetIndexFromObj(interp, objv[i], options, "flag", 0,
687 switch ((enum options) index) {
688 case SPAWN_NOTTYINIT:
690 slave_write_ioctls--;
693 case SPAWN_NOTTYCOPY:
708 exp_error(interp,"usage: -open file-identifier");
711 chanName = Tcl_GetString (objv[i]);
713 case SPAWN_LEAVEOPEN:
716 exp_error(interp,"usage: -open file-identifier");
719 chanName = Tcl_GetString (objv[i]);
726 exp_error(interp,"usage: -ignore signal");
729 sig = exp_string_to_signal(interp,Tcl_GetString (objv[i]));
731 exp_error(interp,"usage: -ignore %s: unknown signal name",Tcl_GetString (objv[i]));
739 /* objv[i+1] is list of signals */
740 /* objv[i+2] is action */
742 static char* actions [] = {
743 "SIG_DFL", "SIG_IGN", NULL
746 ACTION_SIGDFL, ACTION_SIGIGN;
751 RETSIGTYPE (*sig_handler)();
752 int lc; /* number of signals in list */
753 Tcl_Obj** lv; /* list of signals */
755 if ((objc - i) < 3) {
756 exp_error(interp,"usage: -trap siglist SIG_DFL or SIG_IGN");
760 /* Check and process action */
762 if (Tcl_GetIndexFromObj(interp, objv[i+2], actions, "action", 0,
763 &theaction) != TCL_OK) {
764 exp_error(interp,"usage: -trap siglist SIG_DFL or SIG_IGN");
767 switch ((enum actions) theaction) {
769 sig_handler = SIG_DFL;
772 sig_handler = SIG_IGN;
776 /* Check and process list of signals */
778 if (TCL_OK != Tcl_ListObjGetElements (inter, objv[i+1], &lc, &lv)) {
779 expErrorLogU(Tcl_GetStringResult(interp));
780 expErrorLogU("\r\n");
781 exp_error(interp,"usage: -trap {siglist} ...");
786 int sig = exp_string_to_signal(interp,Tcl_GetString (lv[j]));
790 traps[sig] = sig_handler;
800 /* Additional checking of arguments */
802 if (chanName && (i < objc)) {
803 exp_error(interp,"usage: -[leave]open [fileXX]");
807 if (!pty_only && !chanName && (i == objc)) {
808 exp_error(interp,"usage: spawn [spawn-args] program [program-args]");
815 stty_init = exp_get_var(interp,STTY_INIT);
817 slave_write_ioctls++;
821 /* any extraneous ioctl's that occur in slave must be accounted for
822 when trapping, see below in child half of fork */
823 #if defined(TIOCSCTTY) && !defined(CIBAUD) && !defined(sun) && !defined(hp9000s300)
824 slave_write_ioctls++;
828 exp_pty_slave_name = 0;
830 Tcl_ReapDetachedProcs();
835 expStdoutLogU(argv0,0);
836 for (c = 1; c < objc; c++) {
837 expStdoutLogU(" ",0);
838 expStdoutLogU(Tcl_GetString (objv[c]),0);
840 expStdoutLogU("\r\n",0);
843 if (0 > (master = exp_getptymaster())) {
845 * failed to allocate pty, try and figure out why
846 * so we can suggest to user what to do about it.
852 exp_error(interp,"%s",exp_pty_error);
856 if (expChannelCountGet() > 10) {
857 exp_error(interp,"The system only has a finite number of ptys and you have many of them in use. The usual reason for this is that you forgot (or didn't know) to call \"wait\" after closing each of them.");
861 testfd = open("/",0);
865 exp_error(interp,"The system has no more ptys. Ask your system administrator to create more.");
867 exp_error(interp,"- You have too many files are open. Close some files or increase your per-process descriptor limit.");
872 /* ordinarily channel creation takes care of close-on-exec
873 * but because that will occur *after* fork, force close-on-exec
876 expCloseOnExec(master);
878 #define SPAWN_OUT "spawn_out"
879 Tcl_SetVar2(interp,SPAWN_OUT,"slave,name",exp_pty_slave_name,0);
882 write_master = master;
886 * process "-open $channel"
889 ClientData rfdc, wfdc;
892 expStdoutLogU(argv0,0);
893 expStdoutLogU(" [open ...]\r\n",0);
895 if (!(channel = Tcl_GetChannel(interp,chanName,&mode))) {
899 exp_error(interp,"channel is neither readable nor writable");
902 if (mode & TCL_READABLE) {
903 if (TCL_ERROR == Tcl_GetChannelHandle(channel, TCL_READABLE, &rfdc)) {
906 rfd = (int)(long) rfdc;
908 if (mode & TCL_WRITABLE) {
909 if (TCL_ERROR == Tcl_GetChannelHandle(channel, TCL_WRITABLE, &wfdc)) {
912 wfd = (int)(long) wfdc;
914 master = ((mode & TCL_READABLE)?rfd:wfd);
916 /* make a new copy of file descriptor */
917 if (-1 == (write_master = master = dup(master))) {
918 exp_error(interp,"fdopen: %s",Tcl_PosixError(interp));
922 /* if writefilePtr is different, dup that too */
923 if ((mode & TCL_READABLE) && (mode & TCL_WRITABLE) && (wfd != rfd)) {
924 if (-1 == (write_master = dup(wfd))) {
925 exp_error(interp,"fdopen: %s",Tcl_PosixError(interp));
931 * It would be convenient now to tell Tcl to close its
932 * file descriptor. Alas, if involved in a pipeline, Tcl
933 * will be unable to complete a wait on the process.
934 * So simply remember that we meant to close it. We will
935 * do so later in our own close routine.
939 if (chanName || pty_only) {
940 esPtr = expCreateChannel(interp,master,write_master,EXP_NOPID);
943 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
944 Tcl_HashEntry *entry = Tcl_FindHashEntry(&tsdPtr->origins,chanName);
947 esPtr->chan_orig = (ExpOrigin*) Tcl_GetHashValue(entry);
948 esPtr->chan_orig->refCount ++;
952 ExpOrigin* orig = (ExpOrigin*) ckalloc (sizeof (ExpOrigin));
954 esPtr->chan_orig = orig;
955 orig->channel_orig = channel;
958 entry = Tcl_CreateHashEntry(&tsdPtr->origins,chanName,&newptr);
959 Tcl_SetHashValue(entry, (ClientData) orig);
962 esPtr->leaveopen = leaveopen;
965 if (exp_pty_slave_name) set_slave_name(esPtr,exp_pty_slave_name);
967 /* make it appear as if process has been waited for */
968 esPtr->sys_waited = TRUE;
969 exp_wait_zero(&esPtr->wait);
971 /* tell user of new spawn id */
972 Tcl_SetVar(interp,SPAWN_ID_VARNAME,esPtr->name,0);
978 * open the slave side in the same process to support
982 if (0 > (esPtr->fd_slave = exp_getptyslave(ttycopy,ttyinit,
984 exp_error(interp,"open(slave pty): %s\r\n",Tcl_PosixError(interp));
988 exp_slave_control(master,1);
990 sprintf(value,"%d",esPtr->fd_slave);
991 Tcl_SetVar2(interp,SPAWN_OUT,"slave,fd",value,0);
993 Tcl_SetObjResult (interp, Tcl_NewIntObj (EXP_NOPID));
994 expDiagLog("spawn: returns {%s}\r\n",Tcl_GetStringResult(interp));
999 command = Tcl_TranslateFileName(interp,Tcl_GetString (cmdObj),&dstring);
1000 if (NULL == command) {
1004 if (-1 == pipe(sync_fds)) {
1005 exp_error(interp,"too many programs spawned? could not create pipe: %s",Tcl_PosixError(interp));
1009 if (-1 == pipe(sync2_fds)) {
1012 exp_error(interp,"too many programs spawned? could not create pipe: %s",Tcl_PosixError(interp));
1016 if (-1 == pipe(status_pipe)) {
1019 close(sync2_fds[0]);
1020 close(sync2_fds[1]);
1021 exp_error(interp,"too many programs spawned? could not create pipe: %s",Tcl_PosixError(interp));
1025 if ((pid = fork()) == -1) {
1026 exp_error(interp,"fork: %s",Tcl_PosixError(interp));
1030 if (pid) { /* parent */
1032 close(sync2_fds[0]);
1033 close(status_pipe[1]);
1035 esPtr = expCreateChannel(interp,master,master,pid);
1037 if (exp_pty_slave_name) set_slave_name(esPtr,exp_pty_slave_name);
1044 * wait for slave to initialize pty before allowing
1045 * user to send to it
1048 expDiagLog("parent: waiting for sync byte\r\n");
1049 while (((rc = read(sync_fds[0],&sync_byte,1)) < 0) && (errno == EINTR)) {
1053 expErrorLogU("parent: sync byte read: ");
1054 expErrorLogU(Tcl_ErrnoMsg(errno));
1055 expErrorLogU("\r\n");
1059 /* turn on detection of eof */
1060 exp_slave_control(master,1);
1063 * tell slave to go on now, now that we have initialized pty
1066 expDiagLog("parent: telling child to go ahead\r\n");
1067 wc = write(sync2_fds[1]," ",1);
1069 expErrorLog("parent: sync byte write: %s\r\n",Tcl_ErrnoMsg(errno));
1073 expDiagLog("parent: now unsynchronized from child\r\n");
1075 close(sync2_fds[1]);
1077 /* see if child's exec worked */
1079 switch (read(status_pipe[0],&child_errno,sizeof child_errno)) {
1081 if (errno == EINTR) goto retry;
1082 /* well it's not really the child's errno */
1083 /* but it can be treated that way */
1084 child_errno = errno;
1087 /* child's exec succeeded */
1091 /* child's exec failed; child_errno contains exec's errno */
1092 close(status_pipe[0]);
1093 waitpid(pid, NULL, 0);
1094 /* in order to get Tcl to set errorcode, we must */
1095 /* hand set errno */
1096 errno = child_errno;
1097 exp_error(interp, "couldn't execute \"%s\": %s",
1098 command,Tcl_PosixError(interp));
1101 close(status_pipe[0]);
1103 /* tell user of new spawn id */
1104 Tcl_SetVar(interp,SPAWN_ID_VARNAME,esPtr->name,0);
1106 Tcl_SetObjResult (interp, Tcl_NewIntObj (pid));
1107 expDiagLog("spawn: returns {%s}\r\n",Tcl_GetStringResult(interp));
1109 Tcl_DStringFree(&dstring);
1113 /* child process - do not return from here! all errors must exit() */
1116 close(sync2_fds[1]);
1117 close(status_pipe[0]);
1118 expCloseOnExec(status_pipe[1]);
1120 if (exp_dev_tty != -1) {
1126 (void) close(master);
1129 /* ultrix (at least 4.1-2) fails to obtain controlling tty if setsid */
1130 /* is called. setpgrp works though. */
1131 #if defined(POSIX) && !defined(ultrix)
1148 /* Pyramid lacks this defn */
1150 ttyfd = open("/dev/tty", O_RDWR);
1152 (void) ioctl(ttyfd, TIOCNOTTY, (char *)0);
1153 (void) close(ttyfd);
1155 #endif /* TIOCNOTTY */
1158 #endif /* DO_SETSID */
1160 /* save stderr elsewhere to avoid BSD4.4 bogosity that warns */
1161 /* if stty finds dev(stderr) != dev(stdout) */
1163 /* save error fd while we're setting up new one */
1164 errorfd = fcntl(2,F_DUPFD,3);
1165 /* and here is the macro to restore it */
1166 #define restore_error_fd {close(2);fcntl(errorfd,F_DUPFD,2);}
1172 /* since we closed fd 0, open of pty slave must return fd 0 */
1174 /* since exp_getptyslave may have to run stty, (some of which work on fd */
1175 /* 0 and some of which work on 1) do the dup's inside exp_getptyslave. */
1177 if (0 > (slave = exp_getptyslave(ttycopy,ttyinit,stty_init))) {
1180 if (exp_pty_error) {
1181 expErrorLog("open(slave pty): %s\r\n",exp_pty_error);
1183 expErrorLog("open(slave pty): %s\r\n",Tcl_ErrnoMsg(errno));
1190 expErrorLog("exp_getptyslave: slave = %d but expected 0\n",slave);
1194 /* The test for hpux may have to be more specific. In particular, the */
1195 /* code should be skipped on the hp9000s300 and hp9000s720 (but there */
1196 /* is no documented define for the 720!) */
1198 /*#if defined(TIOCSCTTY) && !defined(CIBAUD) && !defined(sun) && !defined(hpux)*/
1199 #if defined(TIOCSCTTY) && !defined(sun) && !defined(hpux)
1200 /* 4.3+BSD way to acquire controlling terminal */
1201 /* according to Stevens - Adv. Prog..., p 642 */
1202 /* Oops, it appears that the CIBAUD is on Linux also */
1203 /* so let's try without... */
1205 if (tcsetct(0, getpid()) == -1) {
1207 expErrorLog("failed to get controlling terminal using TIOCSCTTY");
1211 (void) ioctl(0,TIOCSCTTY,(char *)0);
1212 /* ignore return value - on some systems, it is defined but it
1213 * fails and it doesn't seem to cause any problems. Or maybe
1214 * it works but returns a bogus code. Noone seems to be able
1215 * to explain this to me. The systems are an assortment of
1216 * different linux systems (and FreeBSD 2.5), RedHat 5.2 and
1224 (void) ioctl(0,TCSETCTTY,0);
1226 if (open("/dev/tty", O_RDWR) < 0) {
1228 expErrorLog("open(/dev/tty): %s\r\n",Tcl_ErrnoMsg(errno));
1235 setptyutmp(); /* create a utmp entry */
1237 /* _CRAY2 code from Hal Peterson <hrp@cray.com>, Cray Research, Inc. */
1240 * Interpose a process between expect and the spawned child to
1241 * keep the slave side of the pty open to allow time for expect
1242 * to read the last output. This is a workaround for an apparent
1243 * bug in the Unicos pty driver on Cray-2's under Unicos 6.0 (at
1246 if ((pid = fork()) == -1) {
1248 expErrorLog("second fork: %s\r\n",Tcl_ErrnoMsg(errno));
1253 /* Intermediate process. */
1258 /* How long should we wait? */
1259 if (t = exp_get_var(interp,"pty_timeout"))
1261 else if (t = exp_get_var(interp,"timeout"))
1262 timeout = atoi(t)/2;
1266 /* Let the spawned process run to completion. */
1267 while (wait(&status) < 0 && errno == EINTR)
1270 /* Wait for the pty to clear. */
1273 /* Duplicate the spawned process's status. */
1274 if (WIFSIGNALED(status))
1275 kill(getpid(), WTERMSIG(status));
1277 /* The kill may not have worked, but this will. */
1278 exit(WEXITSTATUS(status));
1283 if (console) exp_console_set();
1286 for (i=1;i<NSIG;i++) {
1287 if (traps[i] != SIG_ERR) {
1291 #endif /* FULLTRAPS */
1293 for (i=1;i<NSIG;i++) {
1294 signal(i,ignore[i]?SIG_IGN:SIG_DFL);
1298 * avoid fflush of cmdfile, logfile, & diagfile since this screws up
1299 * the parents seek ptr. There is no portable way to fclose a shared
1303 /* (possibly multiple) masters are closed automatically due to */
1304 /* earlier fcntl(,,CLOSE_ON_EXEC); */
1306 /* tell parent that we are done setting up pty */
1307 /* The actual char sent back is irrelevant. */
1309 /* expDiagLog("child: telling parent that pty is initialized\r\n");*/
1310 wc = write(sync_fds[1]," ",1);
1313 expErrorLog("child: sync byte write: %s\r\n",Tcl_ErrnoMsg(errno));
1318 /* wait for master to let us go on */
1319 while (((rc = read(sync2_fds[0],&sync_byte,1)) < 0) && (errno == EINTR)) {
1325 expErrorLog("child: sync byte read: %s\r\n",Tcl_ErrnoMsg(errno));
1328 close(sync2_fds[0]);
1330 /* expDiagLog("child: now unsynchronized from parent\r\n"); */
1332 argv = (char**) ckalloc ((objc+1)*sizeof(char*));
1333 for (k=0, i=cmdIndex;i<objc;k++,i++) {
1334 argv[k] = ckalloc (1+strlen(Tcl_GetString (objv[i])));
1335 strcpy (argv[k],Tcl_GetString (objv[i]));
1339 execvp(command,argv);
1341 for (k=0,i=cmdIndex;i<objc;k++,i++) {
1344 ckfree((char*)argv);
1346 /* Alas, by now we've closed fd's to stderr, logfile and diagfile.
1347 * The only reasonable thing to do is to send back the error as part of
1348 * the program output. This will be picked up in an expect or interact
1352 /* if exec failed, communicate the reason back to the parent */
1353 write(status_pipe[1], &errno, sizeof errno);
1357 Tcl_DStringFree(&dstring);
1359 exp_close(interp,esPtr);
1360 waitpid(esPtr->pid,(int *)&esPtr->wait,0);
1361 if (esPtr->registered) {
1362 Tcl_UnregisterChannel(interp,esPtr->channel);
1371 ClientData clientData,
1374 Tcl_Obj *CONST objv[]) /* Argument objects. */
1377 ExpState *esPtr = 0;
1379 static char* options[] = { "-i", NULL };
1380 enum options { PID_ID };
1383 for (i=1; i<objc; i++) {
1387 name = Tcl_GetString(objv[i]);
1388 if (name[0] != '-') {
1391 if (Tcl_GetIndexFromObj(interp, objv[i], options, "flag", 0,
1392 &index) != TCL_OK) {
1395 switch ((enum options) index) {
1398 if (i >= objc) goto usage;
1399 chanName = Tcl_GetString (objv[i]);
1405 esPtr = expStateFromChannelName(interp,chanName,0,0,0,"exp_pid");
1407 esPtr = expStateCurrent(interp,0,0,0);
1409 if (!esPtr) return TCL_ERROR;
1411 Tcl_SetObjResult (interp, Tcl_NewIntObj (esPtr->pid));
1414 exp_error(interp,"usage: -i spawn_id");
1420 Exp_GetpidDeprecatedObjCmd(
1421 ClientData clientData,
1424 Tcl_Obj *CONST objv[]) /* Argument objects. */
1426 expDiagLog("getpid is deprecated, use pid\r\n");
1427 Tcl_SetObjResult (interp, Tcl_NewIntObj (getpid()));
1434 ClientData clientData,
1437 Tcl_Obj *CONST objv[]) /* Argument objects. */
1442 exp_error(interp,"must have one arg: seconds");
1446 if (TCL_OK != Tcl_GetDoubleFromObj (interp, objv[1], &s)) {
1447 if (0 == strlen (Tcl_GetString(objv[1]))) {
1448 /* Keep undocumented acceptance of "" as 0 = no delay */
1454 return exp_dsleep(interp,s);
1462 /* returns 0 for success, -1 for failure */
1468 int sc; /* return from scanf */
1469 CONST char *s = exp_get_var(interp,"send_slow");
1471 exp_error(interp,"send -s: send_slow has no value");
1474 if (2 != (sc = sscanf(s,"%d %lf",&x->size,&x->time))) {
1475 exp_error(interp,"send -s: found %d value(s) in send_slow but need 2",sc);
1479 exp_error(interp,"send -s: size (%d) in send_slow must be positive", x->size);
1483 exp_error(interp,"send -s: time (%f) in send_slow must be larger",x->time);
1489 /* returns 0 for success, -1 for failure, pos. for Tcl return value */
1496 struct slow_arg *arg)
1500 while (rembytes > 0) {
1501 int i, bytelen, charlen;
1505 charlen = (arg->size<rembytes?arg->size:rembytes);
1507 /* count out the right number of UTF8 chars */
1508 for (i=0;i<charlen;i++) {
1513 if (0 > expWriteChars(esPtr,buffer,bytelen)) { return(-1); }
1514 rembytes -= bytelen;
1517 /* skip sleep after last write */
1519 rc = exp_dsleep(interp,arg->time);
1520 if (rc>0) return rc;
1527 float alpha; /* average interarrival time in seconds */
1528 float alpha_eow; /* as above but for eow transitions */
1529 float c; /* shape */
1533 /* returns -1 if error, 0 if success */
1537 struct human_arg *x)
1539 int sc; /* return from scanf */
1540 CONST char *s = exp_get_var(interp,"send_human");
1543 exp_error(interp,"send -h: send_human has no value");
1546 if (5 != (sc = sscanf(s,"%f %f %f %f %f",
1547 &x->alpha,&x->alpha_eow,&x->c,&x->min,&x->max))) {
1548 if (sc == EOF) sc = 0; /* make up for overloaded return */
1549 exp_error(interp,"send -h: found %d value(s) in send_human but need 5",sc);
1552 if (x->alpha < 0 || x->alpha_eow < 0) {
1553 exp_error(interp,"send -h: average interarrival times (%f %f) must be non-negative in send_human", x->alpha,x->alpha_eow);
1557 exp_error(interp,"send -h: variability (%f) in send_human must be positive",x->c);
1563 exp_error(interp,"send -h: minimum (%f) in send_human must be non-negative",x->min);
1567 exp_error(interp,"send -h: maximum (%f) in send_human must be non-negative",x->max);
1570 if (x->max < x->min) {
1571 exp_error(interp,"send -h: maximum (%f) must be >= minimum (%f) in send_human",x->max,x->min);
1577 /* Compute random numbers from 0 to 1, for expect's send -h */
1578 /* This implementation sacrifices beauty for portability */
1582 /* current implementation is pathetic but works */
1583 /* 99991 is largest prime in my CRC - can't hurt, eh? */
1584 return((float)(1+(rand()%99991))/99991.0);
1588 exp_init_unit_random()
1593 /* This function is my implementation of the Weibull distribution. */
1594 /* I've added a max time and an "alpha_eow" that captures the slight */
1595 /* but noticable change in human typists when hitting end-of-word */
1597 /* returns 0 for success, -1 for failure, pos. for Tcl return value */
1603 struct human_arg *arg)
1613 expDiagLog("human_write: avg_arr=%f/%f 1/shape=%f min=%f max=%f\r\n",
1614 arg->alpha,arg->alpha_eow,arg->c,arg->min,arg->max);
1616 for (sp = buffer;*sp;sp += size) {
1617 size = Tcl_UtfToUniChar(sp, &ch);
1618 /* use the end-of-word alpha at eow transitions */
1619 if (in_word && (Tcl_UniCharIsPunct(ch) || Tcl_UniCharIsSpace(ch)))
1620 alpha = arg->alpha_eow;
1621 else alpha = arg->alpha;
1622 in_word = !(Tcl_UniCharIsPunct(ch) || Tcl_UniCharIsSpace(ch));
1624 t = alpha * pow(-log((double)unit_random()),arg->c);
1626 /* enforce min and max times */
1627 if (t<arg->min) t = arg->min;
1628 else if (t>arg->max) t = arg->max;
1630 /* skip sleep before writing first character */
1632 wc = exp_dsleep(interp,(double)t);
1633 if (wc > 0) return wc;
1636 wc = expWriteChars(esPtr, sp, size);
1637 if (0 > wc) return(wc);
1642 struct exp_i *exp_i_pool = 0;
1643 struct exp_state_list *exp_state_list_pool = 0;
1645 #define EXP_I_INIT_COUNT 10
1646 #define EXP_FD_INIT_COUNT 10
1655 /* none avail, generate some new ones */
1656 exp_i_pool = i = (struct exp_i *)ckalloc(
1657 EXP_I_INIT_COUNT * sizeof(struct exp_i));
1658 for (n=0;n<EXP_I_INIT_COUNT-1;n++,i++) {
1664 /* now that we've made some, unlink one and give to user */
1667 exp_i_pool = exp_i_pool->next;
1676 struct exp_state_list *
1677 exp_new_state(ExpState *esPtr)
1680 struct exp_state_list *fd;
1682 if (!exp_state_list_pool) {
1683 /* none avail, generate some new ones */
1684 exp_state_list_pool = fd = (struct exp_state_list *)ckalloc(
1685 EXP_FD_INIT_COUNT * sizeof(struct exp_state_list));
1686 for (n=0;n<EXP_FD_INIT_COUNT-1;n++,fd++) {
1692 /* now that we've made some, unlink one and give to user */
1694 fd = exp_state_list_pool;
1695 exp_state_list_pool = exp_state_list_pool->next;
1697 /* fd->next is assumed to be changed by caller */
1702 exp_free_state(struct exp_state_list *fd_first)
1704 struct exp_state_list *fd, *penultimate;
1706 if (!fd_first) return;
1708 /* link entire chain back in at once by first finding last pointer */
1709 /* making that point back to pool, and then resetting pool to this */
1712 for (fd = fd_first;fd;fd=fd->next) {
1715 penultimate->next = exp_state_list_pool;
1716 exp_state_list_pool = fd_first;
1719 /* free a single fd */
1721 exp_free_state_single(struct exp_state_list *fd)
1723 fd->next = exp_state_list_pool;
1724 exp_state_list_pool = fd;
1731 Tcl_VarTraceProc *updateproc)/* proc to invoke if indirect is written */
1733 if (i->next) exp_free_i(interp,i->next,updateproc);
1735 exp_free_state(i->state_list);
1737 if (i->direct == EXP_INDIRECT) {
1738 Tcl_UntraceVar(interp,i->variable, TCL_GLOBAL_ONLY|TCL_TRACE_WRITES,
1739 updateproc, (ClientData)i);
1742 /* here's the long form
1743 if duration & direct free(var) free(val)
1748 Also if i->variable was a bogus variable name, i->value might not be
1749 set, so test i->value to protect this
1750 TMP in this case does NOT mean from the "expect" command. Rather
1751 it means "an implicit spawn id from any expect or expect_XXX
1752 command". In other words, there was no variable name provided.
1755 && (((i->direct == EXP_DIRECT) && (i->duration == EXP_PERMANENT))
1756 || ((i->direct == EXP_INDIRECT) && (i->duration == EXP_TEMPORARY)))) {
1758 } else if (i->duration == EXP_PERMANENT) {
1759 if (i->value) ckfree(i->value);
1760 if (i->variable) ckfree(i->variable);
1763 i->next = exp_i_pool;
1767 /* generate a descriptor for a "-i" flag */
1768 /* can only fail on bad direct descriptors */
1769 /* indirect descriptors always succeed */
1773 char *arg, /* spawn id list or a variable containing a list */
1774 int duration, /* if we have to copy the args */
1775 /* should only need do this in expect_before/after */
1776 Tcl_VarTraceProc *updateproc) /* proc to invoke if indirect is written */
1783 i->direct = (isExpChannelName(arg) || (0 == strcmp(arg, EXP_SPAWN_ID_ANY_LIT))?EXP_DIRECT:EXP_INDIRECT);
1785 i->direct = (isdigit(arg[0]) || (arg[0] == '-'))?EXP_DIRECT:EXP_INDIRECT;
1787 if (i->direct == EXP_DIRECT) {
1788 stringp = &i->value;
1790 stringp = &i->variable;
1793 i->duration = duration;
1794 if (duration == EXP_PERMANENT) {
1795 *stringp = ckalloc(strlen(arg)+1);
1796 strcpy(*stringp,arg);
1802 if (TCL_ERROR == exp_i_update(interp,i)) {
1803 exp_free_i(interp,i,(Tcl_VarTraceProc *)0);
1807 /* if indirect, ask Tcl to tell us when variable is modified */
1809 if (i->direct == EXP_INDIRECT) {
1810 Tcl_TraceVar(interp, i->variable,
1811 TCL_GLOBAL_ONLY|TCL_TRACE_WRITES,
1812 updateproc, (ClientData) i);
1823 struct exp_state_list *new_state;
1825 new_state = exp_new_state(esPtr);
1826 new_state->next = i->state_list;
1827 i->state_list = new_state;
1830 /* this routine assumes i->esPtr is meaningful */
1831 /* returns TCL_ERROR only on direct */
1832 /* indirects always succeed */
1838 struct ExpState *esPtr;
1844 if (Tcl_SplitList(NULL, p, &argc, &argv) != TCL_OK) goto error;
1846 for (j = 0; j < argc; j++) {
1847 esPtr = expStateFromChannelName(interp,argv[j],1,0,1,"");
1848 if (!esPtr) goto error;
1849 exp_i_add_state(i,esPtr);
1851 ckfree((char*)argv);
1854 expDiagLogU("exp_i_parse_states: ");
1855 expDiagLogU(Tcl_GetStringResult(interp));
1859 /* updates a single exp_i struct */
1860 /* return TCL_ERROR only on direct variables */
1861 /* indirect variables always succeed */
1867 char *p; /* string representation of list of spawn ids */
1869 if (i->direct == EXP_INDIRECT) {
1870 p = Tcl_GetVar(interp,i->variable,TCL_GLOBAL_ONLY);
1873 /* *really* big variable names could blow up expDiagLog! */
1874 expDiagLog("warning: indirect variable %s undefined",i->variable);
1878 if (streq(p,i->value)) return TCL_OK;
1880 /* replace new value with old */
1883 i->value = ckalloc(strlen(p)+1);
1886 exp_free_state(i->state_list);
1889 /* no free, because this should only be called on */
1890 /* "direct" i's once */
1893 return exp_i_parse_states(interp, i);
1899 int duration) /* if we have to copy the args */
1900 /* should only need do this in expect_before/after */
1906 i->direct = EXP_DIRECT;
1907 i->duration = duration;
1909 exp_i_add_state(i,esPtr);
1917 ClientData clientData,
1920 Tcl_Obj *CONST objv[]) /* Argument objects. */
1922 static char* options[] = { "--", NULL };
1923 enum options { LOG_QUOTE };
1926 for (i=1; i<objc; i++) {
1930 name = Tcl_GetString(objv[i]);
1931 if (name[0] != '-') {
1934 if (Tcl_GetIndexFromObj(interp, objv[i], options, "flag", 0,
1935 &index) != TCL_OK) {
1938 if (((enum options) index) == LOG_QUOTE) {
1944 if (i != (objc-1)) goto usage;
1946 expLogDiagU(Tcl_GetString (objv[i]));
1950 exp_error(interp,"usage: send [args] string");
1955 /* I've rewritten this to be unbuffered. I did this so you could shove */
1956 /* large files through "send". If you are concerned about efficiency */
1957 /* you should quote all your send args to make them one single argument. */
1961 ClientData clientData,
1964 Tcl_Obj *CONST objv[])
1966 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
1967 ExpState *esPtr = 0;
1968 int rc; /* final result of this procedure */
1969 struct human_arg human_args;
1970 struct slow_arg slow_args;
1971 #define SEND_STYLE_STRING_MASK 0x07 /* mask to detect a real string arg */
1972 #define SEND_STYLE_PLAIN 0x01
1973 #define SEND_STYLE_HUMAN 0x02
1974 #define SEND_STYLE_SLOW 0x04
1975 #define SEND_STYLE_ZERO 0x10
1976 #define SEND_STYLE_BREAK 0x20
1977 int send_style = SEND_STYLE_PLAIN;
1978 int want_cooked = TRUE;
1979 char *string; /* string to send */
1980 int len = -1; /* length of string to send */
1981 int zeros; /* count of how many ascii zeros to send */
1984 struct exp_state_list *state_list;
1988 static char *options[] = {
1989 "-i", "-h", "-s", "-null", "-0", "-raw", "-break", "--", (char *)0
1992 SEND_SPAWNID, SEND_HUMAN, SEND_SLOW, SEND_NULL, SEND_ZERO,
1993 SEND_RAW, SEND_BREAK, SEND_LAST
1996 for (j = 1; j < objc; j++) {
2000 name = Tcl_GetString(objv[j]);
2001 if (name[0] != '-') {
2004 if (Tcl_GetIndexFromObj(interp, objv[j], options, "flag", 0,
2005 &index) != TCL_OK) {
2008 switch ((enum options) index) {
2011 chanName = Tcl_GetString(objv[j]);
2019 if (-1 == get_human_args(interp,&human_args))
2021 send_style = SEND_STYLE_HUMAN;
2025 if (-1 == get_slow_args(interp,&slow_args))
2027 send_style = SEND_STYLE_SLOW;
2035 } else if (Tcl_GetIntFromObj(interp, objv[j], &zeros)
2039 if (zeros < 1) return TCL_OK;
2040 send_style = SEND_STYLE_ZERO;
2041 string = "<zero(s)>";
2045 want_cooked = FALSE;
2049 send_style = SEND_STYLE_BREAK;
2055 if (send_style & SEND_STYLE_STRING_MASK) {
2058 exp_error(interp,"usage: send [args] string");
2061 string = Tcl_GetStringFromObj(objv[j], &len);
2063 len = strlen(string);
2066 if (clientData == &sendCD_user) esPtr = tsdPtr->stdinout;
2067 else if (clientData == &sendCD_error) esPtr = tsdPtr->stderrX;
2068 else if (clientData == &sendCD_tty) {
2069 esPtr = tsdPtr->devtty;
2071 exp_error(interp,"send_tty: cannot send to controlling terminal in an environment when there is no controlling terminal to send to!");
2074 } else if (!chanName) {
2075 /* we want to check if it is open */
2076 /* but since stdin could be closed, we have to first */
2077 /* get the fd and then convert it from 0 to 1 if necessary */
2078 if (!(esPtr = expStateCurrent(interp,0,0,0))) return(TCL_ERROR);
2082 i = exp_new_i_simple(esPtr,EXP_TEMPORARY);
2084 i = exp_new_i_complex(interp,chanName,FALSE,(Tcl_VarTraceProc *)0);
2085 if (!i) return TCL_ERROR;
2088 #define send_to_stderr (clientData == &sendCD_error)
2089 #define send_to_proc (clientData == &sendCD_proc)
2090 #define send_to_user ((clientData == &sendCD_user) || \
2091 (clientData == &sendCD_tty))
2094 want_cooked = FALSE;
2095 expDiagLogU("send: sending \"");
2096 expDiagLogU(expPrintify(string));
2097 expDiagLogU("\" to {");
2098 /* if closing brace doesn't appear, that's because an error */
2099 /* was encountered before we could send it */
2101 expLogDiagU(string);
2104 for (state_list=i->state_list;state_list;state_list=state_list->next) {
2105 esPtr = state_list->esPtr;
2108 expDiagLog(" %s ",esPtr->name);
2111 /* check validity of each - i.e., are they open */
2112 if (0 == expStateCheck(interp,esPtr,1,0,"send")) {
2116 if (want_cooked) string = exp_cook(string,&len);
2118 switch (send_style) {
2119 case SEND_STYLE_PLAIN:
2120 rc = expWriteChars(esPtr,string,len);
2122 case SEND_STYLE_SLOW:
2123 rc = slow_write(interp,esPtr,string,len,&slow_args);
2125 case SEND_STYLE_HUMAN:
2126 rc = human_write(interp,esPtr,string,&human_args);
2128 case SEND_STYLE_ZERO:
2129 for (;zeros>0;zeros--) {
2130 rc = expWriteChars(esPtr,NULL_STRING,NULL_LENGTH);
2132 /* catching error on last write is sufficient */
2134 case SEND_STYLE_BREAK:
2135 exp_tty_break(interp,esPtr->fdout);
2142 exp_error(interp,"write(spawn_id=%d): %s",esPtr->fdout,Tcl_PosixError(interp));
2148 if (send_to_proc) expDiagLogU("}\r\n");
2152 exp_free_i(interp,i,(Tcl_VarTraceProc *)0);
2159 ClientData clientData,
2162 Tcl_Obj *CONST objv[]) /* Argument objects. */
2164 static char resultbuf[1000];
2166 int leaveOpen = FALSE;
2172 static char* options[] = {
2188 for (i=1; i<objc; i++) {
2192 name = Tcl_GetString(objv[i]);
2193 if (name[0] != '-') {
2196 if (Tcl_GetIndexFromObj(interp, objv[i], options, "flag", 0,
2197 &index) != TCL_OK) {
2200 switch ((enum options) index) {
2205 resultbuf[0] = '\0';
2206 if (expLogChannelGet()) {
2207 /* FUTURE: Use List-ops to construct a proper Tcl_Obj */
2208 if (expLogAllGet()) strcat(resultbuf,"-a ");
2209 if (!expLogAppendGet()) strcat(resultbuf,"-noappend ");
2210 if (expLogFilenameGet()) {
2211 strcat(resultbuf,expLogFilenameGet());
2213 if (expLogLeaveOpenGet()) {
2214 strcat(resultbuf,"-leaveopen ");
2216 strcat(resultbuf,Tcl_GetChannelName(expLogChannelGet()));
2218 Tcl_SetResult(interp,resultbuf,TCL_STATIC);
2221 case LOGFILE_LEAVEOPEN:
2223 if (i >= objc) goto usage_error;
2224 chanName = Tcl_GetString (objv[i]);
2227 case LOGFILE_NOAPPEND:
2232 if (i >= objc) goto usage_error;
2233 chanName = Tcl_GetString (objv[i]);
2238 if (i == (objc - 1)) {
2239 filename = Tcl_GetString (objv[i]);
2240 } else if (objc > i) {
2241 /* too many arguments */
2245 if (chanName && filename) {
2249 /* check if user merely wants to change logAll (-a) */
2250 if (expLogChannelGet() && (chanName || filename)) {
2251 if (filename && (0 == strcmp(filename,expLogFilenameGet()))) {
2252 expLogAllSet(logAll);
2254 } else if (chanName &&
2255 (0 == strcmp(chanName,Tcl_GetChannelName(expLogChannelGet())))) {
2256 expLogAllSet(logAll);
2259 exp_error(interp,"cannot start logging without first stopping logging");
2265 if (TCL_ERROR == expLogChannelOpen(interp,filename,append)) {
2268 } else if (chanName) {
2269 if (TCL_ERROR == expLogChannelSet(interp,chanName)) {
2273 expLogChannelClose(interp);
2275 exp_error(interp,"cannot use -a without a file or channel");
2279 expLogAllSet(logAll);
2280 expLogLeaveOpenSet(leaveOpen);
2285 exp_error(interp,"usage: log_file [-info] [-noappend] [[-a] file] [-[leave]open [open ...]]");
2292 ClientData clientData,
2295 Tcl_Obj *CONST objv[]) /* Argument objects. */
2297 int old_loguser = expLogUserGet();
2299 if (objc == 0 || (objc == 2 && streq(Tcl_GetString (objv[1]),"-info"))) {
2301 } else if (objc == 2) {
2303 if (TCL_OK != Tcl_GetBooleanFromObj (interp, objv[1], &flag)) {
2304 if (0 == strlen (Tcl_GetString(objv[1]))) {
2305 /* Keep undocumented acceptance of "" as 0. */
2311 expLogUserSet(flag);
2313 exp_error(interp,"usage: [-info|1|0]");
2316 Tcl_SetObjResult (interp, Tcl_NewIntObj (old_loguser));
2324 ClientData clientData,
2327 Tcl_Obj *CONST objv[]) /* Argument objects. */
2329 int now = FALSE; /* soon if FALSE, now if TRUE */
2330 int exp_tcl_debugger_was_available = exp_tcl_debugger_available;
2332 static char* options[] = { "-now", NULL };
2333 enum options { DEBUG_NOW };
2336 if (objc > 3) goto usage;
2339 Tcl_SetObjResult (interp, Tcl_NewIntObj (exp_tcl_debugger_available));
2343 for (i=1; i<objc; i++) {
2347 name = Tcl_GetString(objv[i]);
2348 if (name[0] != '-') {
2351 if (Tcl_GetIndexFromObj(interp, objv[i], options, "flag", 0,
2352 &index) != TCL_OK) {
2355 switch ((enum options) index) {
2365 exp_tcl_debugger_available = 1;
2371 if (TCL_OK != Tcl_GetBooleanFromObj (interp, objv[i], &flag)) {
2376 exp_tcl_debugger_available = 0;
2379 exp_tcl_debugger_available = 1;
2382 Tcl_SetObjResult (interp, Tcl_NewBooleanObj (exp_tcl_debugger_was_available));
2385 exp_error(interp,"usage: [[-now] 1|0]");
2393 Exp_ExpInternalObjCmd(
2394 ClientData clientData,
2397 Tcl_Obj *CONST objv[]) /* Argument objects. */
2399 int newChannel = FALSE;
2400 Tcl_Channel oldChannel;
2401 static char resultbuf[1000];
2404 static char* options[] = {
2414 for (i=1; i<objc; i++) {
2418 name = Tcl_GetString(objv[i]);
2419 if (name[0] != '-') {
2422 if (Tcl_GetIndexFromObj(interp, objv[i], options, "flag", 0,
2423 &index) != TCL_OK) {
2426 switch ((enum options) index) {
2428 /* FUTURE: Construct a proper list Tcl_Obj here */
2429 /* Should check that there are no arguments coming after -info */
2431 resultbuf[0] = '\0';
2432 oldChannel = expDiagChannelGet();
2434 sprintf(resultbuf,"-f %s ",expDiagFilename());
2436 strcat(resultbuf,expDiagToStderrGet()?"1":"0");
2437 Tcl_SetResult(interp,resultbuf,TCL_STATIC);
2441 if (i >= objc) goto usage;
2442 expDiagChannelClose(interp);
2443 if (TCL_OK != expDiagChannelOpen(interp,Tcl_GetString (objv[i]))) {
2451 if (i >= objc) goto usage;
2453 if (TCL_OK != Tcl_GetBooleanFromObj (interp, objv[i], &flag)) {
2457 /* if no -f given, close file */
2459 expDiagChannelClose(interp);
2462 expDiagToStderrSet(flag);
2465 exp_error(interp,"usage: [-f file] 0|1");
2469 char *exp_onexit_action = 0;
2474 ClientData clientData,
2477 Tcl_Obj *CONST objv[]) /* Argument objects. */
2485 if (exp_flageq(Tcl_GetString (objv[0]),"-onexit",3)) {
2490 char* act = Tcl_GetStringFromObj (objv[0], &len);
2492 if (exp_onexit_action)
2493 ckfree(exp_onexit_action);
2495 exp_onexit_action = ckalloc(len + 1);
2496 strcpy(exp_onexit_action,act);
2498 } else if (exp_onexit_action) {
2499 Tcl_AppendResult(interp,exp_onexit_action,(char *)0);
2502 } else if (exp_flageq(Tcl_GetString (objv[0]),"-noexit",3)) {
2505 exp_exit_handlers((ClientData)interp);
2511 if (Tcl_GetIntFromObj(interp, objv[0], &value) != TCL_OK) {
2517 * Restore previous definition of close. Needed when expect is
2518 * dynamically loaded after close has been redefined
2519 * e.g. the virtual file system in tclkit
2521 Tcl_Eval(interp, "rename _close.pre_expect close");
2529 Exp_ConfigureObjCmd(
2530 ClientData clientData,
2533 Tcl_Obj *CONST objv[]) /* Argument objects. */
2535 /* Magic configuration stuff. */
2538 static CONST84 char* options [] = {
2539 "-strictwrite", NULL
2545 if ((objc < 3) || (objc % 2 == 0)) {
2546 Tcl_WrongNumArgs (interp, 1, objv, "-strictwrite value");
2550 for (i=1; i < objc; i+=2) {
2551 if (Tcl_GetIndexFromObj (interp, objv [i], options, "option",
2552 0, &opt) != TCL_OK) {
2556 case EXP_STRICTWRITE:
2557 if (Tcl_GetBooleanFromObj (interp, objv [i+1], &val) != TCL_OK) {
2560 exp_strict_write = val;
2571 ClientData clientData,
2574 Tcl_Obj *CONST objv[]) /* Argument objects. */
2576 int onexec_flag = FALSE; /* true if -onexec seen */
2578 int slave_flag = FALSE;
2579 ExpState *esPtr = 0;
2583 static char* options[] = {
2595 for (i=1; i<objc; i++) {
2599 name = Tcl_GetString(objv[i]);
2600 if (name[0] != '-') {
2603 if (Tcl_GetIndexFromObj(interp, objv[i], options, "flag", 0,
2604 &index) != TCL_OK) {
2607 switch ((enum options) index) {
2611 exp_error(interp,"usage: -i spawn_id");
2614 chanName = Tcl_GetString(objv[i]);
2620 exp_error(interp,"usage: -onexec 0|1");
2624 if (TCL_OK != Tcl_GetBooleanFromObj (interp, objv[i], &close_onexec)) {
2635 /* doesn't look like our format, it must be a Tcl-style file */
2636 /* handle. Lucky that formats are easily distinguishable. */
2637 /* Historical note: we used "close" long before there was a */
2638 /* Tcl builtin by the same name. */
2640 Tcl_CmdInfo* close_info;
2642 Tcl_ResetResult(interp);
2644 close_info = (Tcl_CmdInfo*) Tcl_GetAssocData (interp, EXP_CMDINFO_CLOSE, NULL);
2645 return(close_info->objProc(close_info->objClientData,interp,objc,objv));
2649 esPtr = expStateFromChannelName(interp,chanName,1,0,0,"close");
2651 esPtr = expStateCurrent(interp,1,0,0);
2653 if (!esPtr) return TCL_ERROR;
2656 if (esPtr->fd_slave != EXP_NOFD) {
2657 close(esPtr->fd_slave);
2658 esPtr->fd_slave = EXP_NOFD;
2660 exp_slave_control(esPtr->fdin,1);
2664 exp_error(interp,"no such slave");
2670 /* heck, don't even bother to check if fd is open or a real */
2671 /* spawn id, nothing else depends on it */
2672 fcntl(esPtr->fdin,F_SETFD,close_onexec);
2676 return(exp_close(interp,esPtr));
2682 ClientData clientData,
2685 CONST char *command,
2686 Tcl_Command cmdInfo,
2688 Tcl_Obj *CONST objv[]) /* Argument objects. */
2692 /* come out on stderr, by using expErrorLog */
2693 expErrorLog("%2d",level);
2694 for (i = 0;i<level;i++) expErrorLogU(" ");
2695 expErrorLogU((char*)command);
2696 expErrorLogU("\r\n");
2701 tcl_tracer_del(ClientData clientData)
2709 ClientData clientData,
2712 Tcl_Obj *CONST objv[]) /* Argument objects. */
2714 static int trace_level = 0;
2715 static Tcl_Trace trace_handle;
2717 if (objc > 1 && streq(Tcl_GetString (objv[1]),"-info")) {
2718 Tcl_SetObjResult (interp, Tcl_NewIntObj (trace_level));
2723 exp_error(interp,"usage: trace level");
2726 /* tracing already in effect, undo it */
2727 if (trace_level > 0) Tcl_DeleteTrace(interp,trace_handle);
2729 /* get and save new trace level */
2731 if (TCL_OK != Tcl_GetIntFromObj (interp, objv[1], &trace_level)) {
2735 if (trace_level > 0)
2736 trace_handle = Tcl_CreateObjTrace(interp, trace_level,0,
2737 tcl_tracer,(ClientData)0,
2742 /* following defn's are stolen from tclUnix.h */
2745 * The type of the status returned by wait varies from UNIX system
2746 * to UNIX system. The macro below defines it:
2750 #ifndef NO_UNION_WAIT
2751 # define WAIT_STATUS_TYPE union wait
2753 # define WAIT_STATUS_TYPE int
2758 * following definitions stolen from tclUnix.h
2759 * (should have been made public!)
2761 * Supply definitions for macros to query wait status, if not already
2762 * defined in header files above.
2767 # define WIFEXITED(stat) (((*((int *) &(stat))) & 0xff) == 0)
2771 # define WEXITSTATUS(stat) (((*((int *) &(stat))) >> 8) & 0xff)
2775 # define WIFSIGNALED(stat) (((*((int *) &(stat)))) && ((*((int *) &(stat))) == ((*((int *) &(stat))) & 0x00ff)))
2779 # define WTERMSIG(stat) ((*((int *) &(stat))) & 0x7f)
2783 # define WIFSTOPPED(stat) (((*((int *) &(stat))) & 0xff) == 0177)
2787 # define WSTOPSIG(stat) (((*((int *) &(stat))) >> 8) & 0xff)
2791 /* end of stolen definitions */
2793 /* Describe the processes created with Expect's fork.
2794 This allows us to wait on them later.
2796 This is maintained as a linked list. As additional procs are forked,
2797 new links are added. As procs disappear, links are marked so that we
2798 can reuse them later.
2801 struct forked_proc {
2803 WAIT_STATUS_TYPE wait_status;
2804 enum {not_in_use, wait_done, wait_not_done} link_status;
2805 struct forked_proc *next;
2806 } *forked_proc_base = 0;
2811 struct forked_proc *f;
2813 for (f=forked_proc_base;f;f=f->next) {
2814 f->link_status = not_in_use;
2820 struct forked_proc *f,
2824 f->link_status = wait_not_done;
2827 /* make an entry for a new proc */
2831 struct forked_proc *f;
2833 for (f=forked_proc_base;f;f=f->next) {
2834 if (f->link_status == not_in_use) break;
2837 /* add new entry to the front of the list */
2839 f = (struct forked_proc *)ckalloc(sizeof(struct forked_proc));
2840 f->next = forked_proc_base;
2841 forked_proc_base = f;
2846 /* Provide a last-chance guess for this if not defined already */
2848 #define WNOHANG WNOHANG_BACKUP_VALUE
2851 /* wait returns are a hodgepodge of things
2852 If wait fails, something seriously has gone wrong, for example:
2853 bogus arguments (i.e., incorrect, bogus spawn id)
2854 no children to wait on
2856 If wait succeeeds, something happened on a particular pid
2857 3rd arg is 0 if successfully reaped (if signal, additional fields supplied)
2858 3rd arg is -1 if unsuccessfully reaped (additional fields supplied)
2863 ClientData clientData,
2866 Tcl_Obj *CONST objv[]) /* Argument objects. */
2869 struct ExpState *esPtr;
2870 struct forked_proc *fp = 0; /* handle to a pure forked proc */
2871 struct ExpState esTmp; /* temporary memory for either f or fp */
2875 int result = 0; /* 0 means child was successfully waited on */
2876 /* -1 means an error occurred */
2877 /* -2 means no eligible children to wait on */
2879 static char* options[] = {
2890 #define NO_CHILD (-2)
2892 for (i=1; i<objc; i++) {
2896 name = Tcl_GetString(objv[i]);
2897 if (name[0] != '-') {
2900 if (Tcl_GetIndexFromObj(interp, objv[i], options, "flag", 0,
2901 &index) != TCL_OK) {
2904 switch ((enum options) index) {
2907 if (i >= objc) goto usage;
2908 chanName = Tcl_GetString (objv[i]);
2917 esPtr = expStateCurrent(interp,0,0,1);
2919 esPtr = expStateFromChannelName(interp,chanName,0,0,1,"wait");
2921 if (!esPtr) return TCL_ERROR;
2923 if (!expStateAnyIs(esPtr)) {
2924 /* check if waited on already */
2925 /* things opened by "open" or set with -nowait */
2926 /* are marked sys_waited already */
2927 if (!esPtr->sys_waited) {
2929 Tcl_Pid pid = (Tcl_Pid)(long)esPtr->pid;
2930 /* should probably generate an error */
2931 /* if SIGCHLD is trapped. */
2933 /* pass to Tcl, so it can do wait */
2935 Tcl_DetachPids(1,&pid);
2936 exp_wait_zero(&esPtr->wait);
2939 if (Tcl_AsyncReady()) {
2940 int rc = Tcl_AsyncInvoke(interp,TCL_OK);
2941 if (rc != TCL_OK) return(rc);
2944 result = waitpid(esPtr->pid,(int *)&esPtr->wait,0);
2945 if (result == esPtr->pid) break;
2947 if (errno == EINTR) continue;
2955 * Now have Tcl reap anything we just detached.
2956 * This also allows procs user has created with "exec &"
2957 * and and associated with an "exec &" process to be reaped.
2960 Tcl_ReapDetachedProcs();
2961 exp_rearm_sigchld(interp); /* new */
2963 strcpy(spawn_id,esPtr->name);
2965 /* wait for any of our own spawned processes */
2966 /* we call waitpid rather than wait to avoid running into */
2967 /* someone else's processes. Yes, according to Ousterhout */
2968 /* this is the best way to do it. */
2970 int waited_on_forked_process = 0;
2972 esPtr = expWaitOnAny();
2974 /* if it's not a spawned process, maybe its a forked process */
2975 for (fp=forked_proc_base;fp;fp=fp->next) {
2976 if (fp->link_status == not_in_use) continue;
2978 result = waitpid(fp->pid,(int *)&fp->wait_status,WNOHANG);
2979 if (result == fp->pid) {
2980 waited_on_forked_process = 1;
2983 if (result == 0) continue; /* busy, try next */
2985 if (errno == EINTR) goto restart;
2990 if (waited_on_forked_process) {
2992 * The literal spawn id in the return value from wait appears
2993 * as a -1 to indicate a forked process was waited on.
2995 strcpy(spawn_id,"-1");
2997 result = NO_CHILD; /* no children */
2998 Tcl_ReapDetachedProcs();
3000 exp_rearm_sigchld(interp);
3004 /* sigh, wedge forked_proc into an ExpState structure so we don't
3005 * have to rewrite remaining code (too much)
3009 esPtr->pid = fp->pid;
3010 esPtr->wait = fp->wait_status;
3013 /* non-portable assumption that pid_t can be printed with %d */
3016 Tcl_Obj* d = Tcl_NewListObj (0,NULL);
3018 Tcl_ListObjAppendElement (interp, d, Tcl_NewIntObj (esPtr->pid));
3019 Tcl_ListObjAppendElement (interp, d, Tcl_NewStringObj (spawn_id, -1));
3020 Tcl_ListObjAppendElement (interp, d, Tcl_NewIntObj (-1));
3021 Tcl_ListObjAppendElement (interp, d, Tcl_NewIntObj (errno));
3022 Tcl_ListObjAppendElement (interp, d, LITERAL ("POSIX"));
3023 Tcl_ListObjAppendElement (interp, d, Tcl_NewStringObj (Tcl_ErrnoId(),-1));
3024 Tcl_ListObjAppendElement (interp, d, Tcl_NewStringObj (Tcl_ErrnoMsg(errno),-1));
3026 Tcl_SetObjResult (interp, d);
3028 } else if (result == NO_CHILD) {
3029 exp_error(interp,"no children");
3032 Tcl_Obj* d = Tcl_NewListObj (0,NULL);
3033 Tcl_ListObjAppendElement (interp, d, Tcl_NewIntObj (esPtr->pid));
3034 Tcl_ListObjAppendElement (interp, d, Tcl_NewStringObj (spawn_id,-1));
3035 Tcl_ListObjAppendElement (interp, d, Tcl_NewIntObj (0));
3036 Tcl_ListObjAppendElement (interp, d, Tcl_NewIntObj (WEXITSTATUS(esPtr->wait)));
3038 if (WIFSIGNALED(esPtr->wait)) {
3039 Tcl_ListObjAppendElement (interp, d, LITERAL ("CHILDKILLED"));
3040 Tcl_ListObjAppendElement (interp, d, Tcl_NewStringObj (Tcl_SignalId ((int) (WTERMSIG(esPtr->wait))),-1));
3041 Tcl_ListObjAppendElement (interp, d, Tcl_NewStringObj (Tcl_SignalMsg((int) (WTERMSIG(esPtr->wait))),-1));
3042 } else if (WIFSTOPPED(esPtr->wait)) {
3043 Tcl_ListObjAppendElement (interp, d, LITERAL ("CHILDSUSP"));
3044 Tcl_ListObjAppendElement (interp, d, Tcl_NewStringObj (Tcl_SignalId ((int) (WSTOPSIG(esPtr->wait))),-1));
3045 Tcl_ListObjAppendElement (interp, d, Tcl_NewStringObj (Tcl_SignalMsg((int) (WSTOPSIG(esPtr->wait))),-1));
3048 Tcl_SetObjResult (interp, d);
3052 fp->link_status = not_in_use;
3053 return ((result == -1)?TCL_ERROR:TCL_OK);
3056 esPtr->sys_waited = TRUE;
3057 esPtr->user_waited = TRUE;
3059 /* if user has already called close, forget about this entry entirely */
3061 if (esPtr->registered) {
3062 Tcl_UnregisterChannel(interp,esPtr->channel);
3066 return ((result == -1)?TCL_ERROR:TCL_OK);
3069 exp_error(interp,"usage: -i spawn_id");
3076 ClientData clientData,
3079 Tcl_Obj *CONST objv[]) /* Argument objects. */
3083 exp_error(interp,"usage: fork");
3089 exp_error(interp,"fork: %s",Tcl_PosixError(interp));
3091 } else if (rc == 0) {
3094 exp_getpid = getpid();
3101 /* both child and parent follow remainder of code */
3102 Tcl_SetObjResult (interp, Tcl_NewIntObj (rc));
3103 expDiagLog("fork: returns {%s}\r\n",Tcl_GetStringResult(interp));
3109 Exp_DisconnectObjCmd(
3110 ClientData clientData,
3113 Tcl_Obj *CONST objv[]) /* Argument objects. */
3115 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
3118 /* tell CenterLine to ignore non-use of ttyfd */
3121 #endif /* TIOCNOTTY */
3124 exp_error(interp,"usage: disconnect");
3128 if (exp_disconnected) {
3129 exp_error(interp,"already disconnected");
3133 exp_error(interp,"can only disconnect child process");
3136 exp_disconnected = TRUE;
3138 /* ignore hangup signals generated by testing ptys in getptymaster */
3139 /* and other places */
3140 signal(SIGHUP,SIG_IGN);
3142 /* reopen prevents confusion between send/expect_user */
3143 /* accidentally mapping to a real spawned process after a disconnect */
3145 /* if we're in a child that's about to be disconnected from the
3146 controlling tty, close and reopen 0, 1, and 2 but associated
3147 with /dev/null. This prevents send and expect_user doing
3148 special things if newly spawned processes accidentally
3149 get allocated 0, 1, and 2.
3153 ExpState *stdinout = tsdPtr->stdinout;
3154 if (stdinout->valid) {
3155 exp_close(interp,stdinout);
3156 if (stdinout->registered) {
3157 Tcl_UnregisterChannel(interp,stdinout->channel);
3160 open("/dev/null",0);
3161 open("/dev/null",1);
3162 /* tsdPtr->stdinout = expCreateChannel(interp,0,1,EXP_NOPID);*/
3163 /* tsdPtr->stdinout->keepForever = 1;*/
3166 ExpState *devtty = tsdPtr->devtty;
3168 /* reopen stderr saves error checking in error/log routines. */
3169 if (devtty->valid) {
3170 exp_close(interp,devtty);
3171 if (devtty->registered) {
3172 Tcl_UnregisterChannel(interp,devtty->channel);
3175 open("/dev/null",1);
3176 /* tsdPtr->devtty = expCreateChannel(interp,2,2,EXP_NOPID);*/
3177 /* tsdPtr->devtty->keepForever = 1;*/
3180 Tcl_UnsetVar(interp,"tty_spawn_id",TCL_GLOBAL_ONLY);
3186 /* put process in our own pgrp, and lose controlling terminal */
3188 /* With setpgrp first, child ends up with closed stdio */
3189 /* according to Dave Schmitt <daves@techmpc.csg.gss.mot.com> */
3190 if (fork()) exit(0);
3194 /*signal(SIGHUP,SIG_IGN); moved out to above */
3195 if (fork()) exit(0); /* first child exits (as per Stevens, */
3196 /* UNIX Network Programming, p. 79-80) */
3197 /* second child process continues as daemon */
3202 /* Pyramid lacks this defn */
3204 ttyfd = open("/dev/tty", O_RDWR);
3206 /* zap controlling terminal if we had one */
3207 (void) ioctl(ttyfd, TIOCNOTTY, (char *)0);
3208 (void) close(ttyfd);
3210 #endif /* TIOCNOTTY */
3213 #endif /* DO_SETSID */
3220 ClientData clientData,
3223 Tcl_Obj *CONST objv[]) /* Argument objects. */
3233 for (i=1;i<objc;i++) {
3236 name = Tcl_GetString(objv[i]);
3237 if (name[0] != '-') {
3239 } else if (streq (name,"-")) { /* - by itself */
3244 if (TCL_OK != Tcl_GetIntFromObj (interp, objv[i], &newfd)) {
3247 newfd = - newfd; /* Negation rids us of the effect the '-' prefix had. */
3251 exp_error(interp,"overlay -# requires additional argument");
3254 if (TCL_OK != Tcl_GetIntFromObj (interp, objv[i], &oldfd)) {
3258 expDiagLog("overlay: mapping fd %d to %d\r\n",oldfd,newfd);
3259 if (oldfd != newfd) (void) dup2(oldfd,newfd);
3260 else expDiagLog("warning: overlay: old fd == new fd (%d)\r\n",oldfd);
3264 exp_error(interp,"need program name");
3268 /* convert to string array for execvp.
3269 * Take only the arguments after the command name (i+1 ...). The arguments
3270 * before are arguments of overlay, not of the invoked command. The
3271 * command name is at index.
3274 argv = (char**) ckalloc ((objc+1)*sizeof(char*));
3276 for (k=i+1,j=1;k<objc;k++,j++) {
3277 argv[j] = ckalloc (1+strlen(Tcl_GetString (objv[k])));
3278 strcpy (argv[j],Tcl_GetString (objv[k]));
3282 /* command, handle '-' */
3283 command = Tcl_GetString (objv[i]);
3284 argv[0] = ckalloc (2+strlen(command));
3287 strcpy (argv[0]+1,command);
3289 strcpy (argv[0],command);
3292 signal(SIGINT, SIG_DFL);
3293 signal(SIGQUIT, SIG_DFL);
3295 (void) execvp(command,argv);
3297 for (k=0;k<objc;k++) {
3300 ckfree ((char*)argv);
3302 exp_error(interp,"execvp(%s): %s\r\n",
3303 Tcl_GetString(objv[0]),
3304 Tcl_PosixError(interp));
3310 Exp_InterpreterObjCmd(
3311 ClientData clientData,
3314 Tcl_Obj *CONST objv[]) /* Argument objects. */
3316 Tcl_Obj *eofObj = 0;
3321 static char *options[] = {
3328 for (i = 1; i < objc; i++) {
3329 if (Tcl_GetIndexFromObj(interp, objv[i], options, "flag", 0,
3330 &index) != TCL_OK) {
3333 switch ((enum options) index) {
3337 Tcl_WrongNumArgs(interp, 1, objv,"-eof cmd");
3341 Tcl_IncrRefCount(eofObj);
3346 /* errors and ok, are caught by exp_interpreter() and discarded */
3347 /* to return TCL_OK, type "return" */
3348 rc = exp_interpreter(interp,eofObj);
3350 Tcl_DecrRefCount(eofObj);
3355 /* this command supercede's Tcl's builtin CONTINUE command */
3358 Exp_ExpContinueObjCmd(
3359 ClientData clientData,
3362 Tcl_Obj *CONST objv[]) /* Argument objects. */
3365 return EXP_CONTINUE;
3366 } else if ((objc == 2) &&
3367 (0 == strcmp(Tcl_GetString (objv[1]),"-continue_timer"))) {
3368 return EXP_CONTINUE_TIMER;
3371 exp_error(interp,"usage: exp_continue [-continue_timer]\n");
3375 /* most of this is directly from Tcl's definition for return */
3378 Exp_InterReturnObjCmd(
3379 ClientData clientData,
3382 Tcl_Obj *CONST objv[])
3384 /* let Tcl's return command worry about args */
3385 /* if successful (i.e., TCL_RETURN is returned) */
3386 /* modify the result, so that we will handle it specially */
3388 Tcl_CmdInfo* return_info = (Tcl_CmdInfo*)
3389 Tcl_GetAssocData (interp, EXP_CMDINFO_RETURN, NULL);
3391 int result = return_info->objProc(return_info->objClientData,interp,objc,objv);
3392 if (result == TCL_RETURN)
3393 result = EXP_TCL_RETURN;
3400 ClientData clientData,
3403 Tcl_Obj *CONST objv[]) /* Argument objects. */
3408 int leaveopen = FALSE;
3409 Tcl_Channel channel;
3411 static char* options[] = {
3422 for (i=1; i<objc; i++) {
3426 name = Tcl_GetString(objv[i]);
3427 if (name[0] != '-') {
3430 if (Tcl_GetIndexFromObj(interp, objv[i], options, "flag", 0,
3431 &index) != TCL_OK) {
3434 switch ((enum options) index) {
3437 if (i >= objc) goto usage;
3438 chanName = Tcl_GetString (objv[i]);
3440 case OPEN_LEAVEOPEN:
3447 esPtr = expStateCurrent(interp,1,0,0);
3449 esPtr = expStateFromChannelName(interp,chanName,1,0,0,"exp_open");
3451 if (!esPtr) return TCL_ERROR;
3453 /* make a new copy of file descriptor */
3454 if (-1 == (newfd = dup(esPtr->fdin))) {
3455 exp_error(interp,"dup: %s",Tcl_PosixError(interp));
3460 /* remove from Expect's memory in anticipation of passing to Tcl */
3461 if (esPtr->pid != EXP_NOPID) {
3462 Tcl_Pid pid = (Tcl_Pid)(long)esPtr->pid;
3463 Tcl_DetachPids(1,&pid);
3464 esPtr->pid = EXP_NOPID;
3465 esPtr->sys_waited = esPtr->user_waited = TRUE;
3467 exp_close(interp,esPtr);
3471 * Tcl's MakeFileChannel only allows us to pass a single file descriptor
3472 * but that shouldn't be a problem in practice since all of the channels
3473 * that Expect generates only have one fd. Of course, this code won't
3474 * work if someone creates a pipeline, then passes it to spawn, and then
3475 * again to exp_open. For that to work, Tcl would need a new API.
3476 * Oh, and we're also being rather cavalier with the permissions here,
3477 * but they're likely to be right for the same reasons.
3479 channel = Tcl_MakeFileChannel((ClientData)(long)newfd,TCL_READABLE|TCL_WRITABLE);
3480 Tcl_RegisterChannel(interp, channel);
3481 Tcl_AppendResult(interp, Tcl_GetChannelName(channel), (char *) NULL);
3485 exp_error(interp,"usage: -i spawn_id");
3489 /* return 1 if a string is substring of a flag */
3490 /* this version is the code used by the macro that everyone calls */
3495 int minlen) /* at least this many chars must match */
3497 for (;*flag;flag++,string++,minlen--) {
3498 if (*string == '\0') break;
3499 if (*string != *flag) return 0;
3501 if (*string == '\0' && minlen <= 0) return 1;
3506 exp_create_commands(interp,c)
3508 struct exp_cmd_data *c;
3510 Namespace *globalNsPtr = (Namespace *) Tcl_GetGlobalNamespace(interp);
3511 Namespace *currNsPtr = (Namespace *) Tcl_GetCurrentNamespace(interp);
3512 char cmdnamebuf[80];
3514 for (;c->name;c++) {
3515 /* if already defined, don't redefine */
3516 if ((c->flags & EXP_REDEFINE) ||
3517 !(Tcl_FindHashEntry(&globalNsPtr->cmdTable,c->name) ||
3518 Tcl_FindHashEntry(&currNsPtr->cmdTable,c->name))) {
3520 Tcl_CreateObjCommand(interp,c->name,
3521 c->objproc,c->data,exp_deleteObjProc);
3523 Tcl_CreateCommand(interp,c->name,c->proc,
3524 c->data,exp_deleteProc);
3526 if (!(c->name[0] == 'e' &&
3527 c->name[1] == 'x' &&
3529 && !(c->flags & EXP_NOPREFIX)) {
3530 sprintf(cmdnamebuf,"exp_%s",c->name);
3532 Tcl_CreateObjCommand(interp,cmdnamebuf,c->objproc,c->data,
3535 Tcl_CreateCommand(interp,cmdnamebuf,c->proc,
3536 c->data,exp_deleteProc);
3541 static struct exp_cmd_data cmd_data[] = {
3542 {"close", Exp_CloseObjCmd, 0, (ClientData)0, EXP_REDEFINE},
3544 {"debug", Exp_DebugObjCmd, 0, (ClientData)0, 0},
3546 {"exp_internal", Exp_ExpInternalObjCmd, 0, (ClientData)0, 0},
3547 {"disconnect", Exp_DisconnectObjCmd, 0, (ClientData)0, 0},
3548 {"exit", Exp_ExitObjCmd, 0, (ClientData)0, EXP_REDEFINE},
3549 {"exp_continue", Exp_ExpContinueObjCmd, 0, (ClientData)0, 0},
3550 {"fork", Exp_ForkObjCmd, 0, (ClientData)0, 0},
3551 {"exp_pid", Exp_ExpPidObjCmd, 0, (ClientData)0, 0},
3552 {"getpid", Exp_GetpidDeprecatedObjCmd, 0, (ClientData)0, 0},
3553 {"interpreter", Exp_InterpreterObjCmd, 0, (ClientData)0, 0},
3554 {"log_file", Exp_LogFileObjCmd, 0, (ClientData)0, 0},
3555 {"log_user", Exp_LogUserObjCmd, 0, (ClientData)0, 0},
3556 {"exp_open", Exp_OpenObjCmd, 0, (ClientData)0, 0},
3557 {"overlay", Exp_OverlayObjCmd, 0, (ClientData)0, 0},
3558 {"inter_return", Exp_InterReturnObjCmd, 0, (ClientData)0, 0},
3559 {"send", Exp_SendObjCmd, 0, (ClientData)&sendCD_proc,0},
3560 {"send_error", Exp_SendObjCmd, 0, (ClientData)&sendCD_error,0},
3561 {"send_log", Exp_SendLogObjCmd, 0, (ClientData)0, 0},
3562 {"send_tty", Exp_SendObjCmd, 0, (ClientData)&sendCD_tty,0},
3563 {"send_user", Exp_SendObjCmd, 0, (ClientData)&sendCD_user,0},
3564 {"sleep", Exp_SleepObjCmd, 0, (ClientData)0, 0},
3565 {"spawn", Exp_SpawnObjCmd, 0, (ClientData)0, 0},
3566 {"strace", Exp_StraceObjCmd, 0, (ClientData)0, 0},
3567 {"wait", Exp_WaitObjCmd, 0, (ClientData)0, 0},
3568 {"exp_configure",Exp_ConfigureObjCmd, 0, (ClientData)0, 0},
3572 exp_init_most_cmds(Tcl_Interp *interp)
3574 exp_create_commands(interp,cmd_data);
3577 Tcl_InitHashTable(&slaveNames,TCL_STRING_KEYS);
3578 #endif /* HAVE_PTYTRAP */