home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Source Code 1993 July / THE_SOURCE_CODE_CD_ROM.iso / languages / tcl / expect / expect-4.7 / exp_command.c < prev    next >
Encoding:
C/C++ Source or Header  |  1993-05-27  |  40.0 KB  |  1,688 lines

  1. /* command.c - expect commands, except for interact and expect
  2.  
  3. Written by: Don Libes, NIST, 2/6/90
  4.  
  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.
  8.  
  9. expect commands are as follows.  They are discussed further in the man page
  10. and the paper "expect: Curing Those Uncontrollable Fits of Interaction",
  11. Proceedings of the Summer 1990 USENIX Conference, Anaheim, California.
  12.  
  13. Command        Arguments    Returns            Sets
  14. -------        ---------    -------            ----
  15. close        [-i spawn_id]
  16. debug        [-f file] expr
  17. disconnect    [status]
  18. exit        [status]
  19. expect_after    patlst body ...
  20. expect_before    patlst body ...
  21. expect[_user]    patlst body ...    string matched        expect_status
  22. getpid                pid
  23. interact    str-body pairs    body return
  24. interpreter            TCL status
  25. log_file    [[-a] file]
  26. log_user    expr
  27. match_max    max match size
  28. overlay        [-] fd-spawn_id pairs
  29. ready        spawn_id set    spawn_ids ready
  30. send        [...]
  31. send_error    [...]
  32. send_user    [...]
  33. spawn        program [...]    pid            spawn_id
  34. strace        level
  35. system        shell command    TCL status
  36. trap        [[arg] siglist]
  37. wait                {pid status} or {-1 errno}
  38.  
  39. Variable of interest are:
  40.  
  41. Name        Type    Value            Set by        Default
  42. ----        ----    -----            ------        -------
  43. expect_out    str    string matched        expect cmds
  44. spawn_id    int    currently spawned proc    user/spawn cmd
  45. timeout        int    seconds            user        10
  46. tty_spawn_id    int    spawn_id of /dev/tty    expect itself
  47. user_spawn_id    int    spawn_id of user    expect itself    1
  48. send_slow    int/flt    send -s size/time    user
  49. send_human    5 flts    send -h timing        user
  50.  
  51. */
  52.  
  53. #include "exp_conf.h"
  54.  
  55. #include <stdio.h>
  56. #include <sys/types.h>
  57. #include <sys/time.h>
  58. /*#include <sys/ioctl.h> Rob says no longer needed? */
  59. #ifdef HAVE_SYS_FCNTL_H
  60. #  include <sys/fcntl.h>
  61. #else
  62. #  include <fcntl.h>
  63. #endif
  64. #include <sys/file.h>
  65. #include "exp_tty.h"
  66. #ifdef HAVE_STROPTS_H
  67. #  include <sys/stropts.h>
  68. #endif
  69.  
  70. #ifdef HAVE_SYS_WAIT_H
  71. #include <sys/wait.h>
  72. #endif
  73.  
  74. #include <varargs.h>
  75. #include <errno.h>
  76.  
  77. #include <signal.h>
  78. #include <math.h>        /* for log/pow computation in send -h */
  79. #include <ctype.h>        /* all this for ispunct! */
  80.  
  81. #include "tcl.h"
  82. #include "string.h"
  83. #include "exp_rename.h"
  84. #include "exp_global.h"
  85. #include "exp_command.h"
  86. #include "exp_log.h"
  87. #include "exp_event.h"
  88. #include "exp_main.h"
  89.  
  90. #define SPAWN_ID_VARNAME "spawn_id"
  91.  
  92. int getptymaster();
  93. int getptyslave();
  94. extern exp_tty tty_current, tty_cooked;
  95.  
  96. int exp_disconnected = FALSE;    /* whether we are a disconnected process */
  97. int exp_forked = FALSE;        /* whether we are child process */
  98.  
  99. /* the following are just reserved addresses, to be used as ClientData */
  100. /* args to be used to tell commands how they were called. */
  101. /* The actual values won't be used, only the addresses, but I give them */
  102. /* values out of my irrational fear the compiler might collapse them all. */
  103. static int sendCD_error = 2;    /* called as send_error */
  104. static int sendCD_user = 3;    /* called as send_user */
  105. static int sendCD_proc = 4;    /* called as send or send_spawn */
  106. static int sendCD_log = 5;    /* called as send_log */
  107. static int sendCD_tty = 6;    /* called as send_tty */
  108.  
  109. struct f *fs = 0;        /* process array (indexed by spawn_id's) */
  110. int fd_max = -1;        /* highest fd */
  111.  
  112. int expect_key;            /* no need to initialize */
  113.  
  114. /* Do not terminate format strings with \n!!! */
  115. /*VARARGS*/
  116. void
  117. exp_error(va_alist)
  118. va_dcl
  119. {
  120.     Tcl_Interp *interp;
  121.     char *fmt;
  122.     va_list args;
  123.  
  124.     va_start(args);
  125.     interp = va_arg(args,Tcl_Interp *);
  126.     fmt = va_arg(args,char *);
  127.     vsprintf(interp->result,fmt,args);
  128.     va_end(args);
  129. }
  130.  
  131. /* returns handle if fd is usable, 0 if not */
  132. struct f *
  133. exp_fd2f(interp,fd,opened,adjust,msg)
  134. Tcl_Interp *interp;
  135. int fd;
  136. int opened;        /* check not closed */
  137. int adjust;        /* adjust buffer sizes */
  138. char *msg;
  139. {
  140.     if (fd >= 0 && fd <= fd_max && (fs[fd].flags & FD_VALID)) {
  141.         struct f *f = fs + fd;
  142.  
  143.         /* following is a little tricky, do not be tempted do the */
  144.         /* 'usual' boolean simplification */
  145.         if ((!opened) || !(f->flags & FD_CLOSED)) {
  146.             if ((!adjust) || (TCL_OK == exp_adjust(interp,f)))
  147.                 return f;
  148.         }
  149.     }
  150.  
  151.     exp_error(interp,"%s: invalid spawn id (%d)",msg,fd);
  152.     return(0);
  153. }
  154.  
  155. #if 0
  156. /* following routine is not current used, but might be later */
  157. /* returns fd or -1 if no such entry */
  158. static int
  159. pid_to_fd(pid)
  160. int pid;
  161. {
  162.     int fd;
  163.  
  164.     for (fd=0;fd<=fd_max;fd++) {
  165.         if (fs[fd].pid == pid) return(fd);
  166.     }
  167.     return 0;
  168. }
  169. #endif
  170.  
  171. int
  172. exp_close(interp,fd)
  173. Tcl_Interp *interp;
  174. int fd;
  175. {
  176.     struct f *f = exp_fd2f(interp,fd,1,0,"close");
  177.     if (!f) return(TCL_ERROR);
  178.  
  179.     /* Ignore close errors.  Some systems are really braindamaged and */
  180.     /* return errors for no evident reason.  Anyway, receiving an error */
  181.     /* upon pty-close doesn't mean anything anyway as far as I know. */
  182.     close(fd);
  183.     if (f->buffer) {
  184.         free(f->buffer);
  185.         f->buffer = 0;
  186.         f->msize = 0;
  187.         f->size = 0;
  188.         f->printed = 0;
  189.         f->echoed = 0;
  190.         if (f->armed) {
  191.             exp_event_disarm(f-fs);
  192.             f->armed = FALSE;
  193.         }
  194.         free(f->lower);
  195.     }
  196.     f->armed = FALSE;
  197.  
  198.     if (f->flags & FD_USERWAITED) f->flags = 0;
  199.     else f->flags |= FD_CLOSED;
  200.     return(TCL_OK);
  201. }
  202.  
  203. static int
  204. fd_new(fd,pid)
  205. int fd;
  206. int pid;
  207. {
  208.     int i, low;
  209.     struct f *newfs;    /* temporary, so we don't lose old fs */
  210.  
  211.     /* resize table if nec */
  212.     if (fd > fd_max) {
  213.         if (!fs) {    /* no fd's yet allocated */
  214.             if (!(newfs = (struct f *)
  215.                 malloc(sizeof(struct f)*(fd+1)))) {
  216.                 /* malloc failed */
  217.                 return(TCL_ERROR);
  218.             }
  219.             low = 0;
  220.         } else {        /* enlarge fd table */
  221.             if (!(newfs = (struct f *)realloc((char *)fs,
  222.                 sizeof(struct f)*(fd+1)))) {
  223.                 /* malloc failed */
  224.                 return(TCL_ERROR);
  225.             }
  226.             low = fd_max+1;
  227.         }
  228.         fs = newfs;
  229.         fd_max = fd;
  230.         for (i = low; i <= fd_max; i++) { /* init new fd entries */
  231.             struct f *f;
  232.  
  233.             f = fs+i;
  234.             f->pid = 0;
  235.             f->buffer = 0;
  236.             f->lower = 0;
  237.             f->flags = 0;    /* not FD_VALID */
  238.             f->msize = f->size = 0;
  239.             f->armed = FALSE;
  240.         }
  241.     }
  242.     /* close down old table entry if nec */
  243.     fs[fd].pid = pid;        /* overwrite pid */
  244.     fs[fd].size = 0;        /* forget old data */
  245.                     /* but leave msize and buffer as is! */
  246.     if (fs[fd].buffer) fs[fd].buffer[0] = '\0';
  247.  
  248.     fs[fd].printed = 0;
  249.     fs[fd].echoed = 0;
  250.     fs[fd].parity = exp_default_parity;
  251.     fs[fd].key = expect_key++;
  252.     fs[fd].force_read = FALSE;
  253.     fs[fd].armed = FALSE;
  254.     fs[fd].umsize = exp_default_match_max;
  255.     fs[fd].flags = FD_VALID;    /* reset all flags */
  256.     return(TCL_OK);
  257. }
  258.  
  259. void
  260. exp_init_spawn_id_vars(interp)
  261. Tcl_Interp *interp;
  262. {
  263.     Tcl_SetVar(interp,"user_spawn_id",USER_SPAWN_ID_LIT,0);
  264.  
  265.     /* note that the user_spawn_id is NOT /dev/tty which could */
  266.     /* (at least in theory anyway) be later re-opened on a different */
  267.     /* fd, while stdin might have been redirected away from /dev/tty */
  268.  
  269.     if (dev_tty != -1) {
  270.         char dev_tty_str[10];
  271.         sprintf(dev_tty_str,"%d",dev_tty);
  272.         Tcl_SetVar(interp,"tty_spawn_id",dev_tty_str,0);
  273.     }
  274. }
  275.  
  276. void
  277. exp_init_spawn_ids(interp)
  278. Tcl_Interp *interp;
  279. {
  280.     fd_new(0,getpid());
  281.     fs[0].flags |= FD_USER;
  282.  
  283.     if (dev_tty != -1) {
  284.         fd_new(dev_tty,getpid());
  285.     }
  286.  
  287.     /* really should be in interpreter() but silly to do on every call */
  288.     exp_adjust(interp,&fs[0]);
  289. }
  290.  
  291. /* arguments are passed verbatim to execvp() */
  292. /*ARGSUSED*/
  293. static int
  294. cmdSpawn(clientData,interp,argc,argv)
  295. ClientData clientData;
  296. Tcl_Interp *interp;
  297. int argc;
  298. char **argv;
  299. {
  300.     int slave;
  301.     int pid;
  302.     char **a;
  303.     int ttyfd;
  304.     int master;
  305.     int ttyinit = TRUE;
  306.     int ttycopy = TRUE;
  307.     int echo = TRUE;
  308.     int console = FALSE;
  309.     char *argv0 = argv[0];
  310.  
  311.     argc--; argv++;
  312.  
  313.     /* new */
  314.     for (;argc>0;argc--,argv++) {
  315.         if (streq(*argv,"-nottyinit")) {
  316.             ttyinit = FALSE;
  317.         } else if (streq(*argv,"-nottycopy")) {
  318.             ttycopy = FALSE;
  319.         } else if (streq(*argv,"-noecho")) {
  320.             echo = FALSE;
  321.         } else if (streq(*argv,"-console")) {
  322.             console = TRUE;
  323.         } else break;
  324.     }
  325.  
  326.     if (argc == 0) {
  327.         exp_error(interp,"usage: spawn program [args]");
  328.         return(TCL_ERROR);
  329.     }
  330.  
  331.     if (echo) {
  332.         Log(0,"%s ",argv0);
  333.         for (a = argv;*a;a++) {
  334.             Log(0,"%s ",*a);
  335.         }
  336.         nflog("\r\n",0);
  337.     }
  338.  
  339.     if (0 > (master = getptymaster())) {
  340.         exp_error(interp,"too many programs spawned? - out of ptys");
  341.         return(TCL_ERROR);
  342.     }
  343.  
  344.     /* much easier to set this, than remember all masters */
  345.     fcntl(master,F_SETFD,1);    /* close on exec */
  346.  
  347.     if (NULL == (argv[0] = Tcl_TildeSubst(interp,argv[0])))
  348.         return TCL_ERROR;
  349.  
  350.     if ((pid = fork()) == -1) {
  351.         exp_error(interp,"fork: %s",sys_errlist[errno]);
  352.         return(TCL_ERROR);
  353.     }
  354.  
  355.     if (pid) {
  356.         /* parent */
  357.         char buf[3];
  358.  
  359.         if (TCL_ERROR == fd_new(master,pid)) return(TCL_ERROR);
  360.  
  361. #ifdef CRAY
  362.         setptypid(pid);
  363. #endif
  364. #ifdef HAVE_PTYTRAP
  365.         /* trap initial opens in a feeble attempt to not block */
  366.         /* the initially.  If the process itself opens */
  367.         /* /dev/tty, such blocks will be trapped later */
  368.         /* during normal event processing */
  369.  
  370.         /* initial slave open */
  371.         if (exp_wait_for_slave_open(master)) {
  372.             exp_error(interp,"failed to trap slave pty open");
  373.             return(TCL_ERROR);
  374.         }
  375.         /* open("/dev/tty"); */
  376.         if (exp_wait_for_slave_open(master)) {
  377.             exp_error(interp,"failed to trap slave pty open");
  378.             return(TCL_ERROR);
  379.         }
  380. #endif
  381.  
  382.         /* tell user id of new process */
  383.         sprintf(buf,"%d",master);
  384.         Tcl_SetVar(interp,SPAWN_ID_VARNAME,buf,0);
  385.  
  386.         sprintf(interp->result,"%d",pid);
  387.         debuglog("spawn: returns {%s}\r\n",interp->result);
  388.  
  389.         return(TCL_OK);
  390.     }
  391.  
  392.     /* child process - do not return from here!  all errors must exit() */
  393.  
  394.     if (dev_tty != -1) {
  395.         close(dev_tty);
  396.         dev_tty = -1;
  397.     }
  398.  
  399. #ifdef CRAY
  400.     (void) close(master);
  401. #endif
  402.  
  403. #ifdef POSIX
  404. #define DO_SETSID
  405. #endif
  406. #ifdef __convex__
  407. #define DO_SETSID
  408. #endif
  409.  
  410. #ifdef DO_SETSID
  411.     setsid();
  412. #else
  413. #ifdef SYSV3
  414. #ifndef CRAY
  415.     setpgrp();
  416. #endif /* CRAY */
  417. #else /* !SYSV3 */
  418. #ifdef MIPS_BSD
  419.     /* required on BSD side of MIPS OS <jmsellen@watdragon.waterloo.edu> */
  420. #    include <sysv/sys.s>
  421.     syscall(SYS_setpgrp);
  422. #endif
  423.     setpgrp(0,0);
  424. /*    setpgrp(0,getpid());*/    /* make a new pgrp leader */
  425.     ttyfd = open("/dev/tty", O_RDWR);
  426.     if (ttyfd >= 0) {
  427.         (void) ioctl(ttyfd, TIOCNOTTY, (char *)0);
  428.         (void) close(ttyfd);
  429.     }
  430. #endif /* SYSV3 */
  431. #endif /* DO_SETSID */
  432.     close(0);
  433.     close(1);
  434.     /* leave 2 around awhile for stderr-related stuff */
  435.  
  436.     /* since we closed fd 0, open of pty slave must return fd 0 */
  437.  
  438.     /* since getptyslave may have to run stty, (some of which work on fd */
  439.     /* 0 and some of which work on 1) do the dup's inside getptyslave. */
  440.  
  441. #define STTY_INIT    "stty_init"
  442.     if (0 > (slave = getptyslave(ttycopy,ttyinit,get_var(STTY_INIT)))) {
  443.         errorlog("open(slave pty): %s\r\n",sys_errlist[errno]);
  444.         exit(-1);
  445.     }
  446.     /* sanity check */
  447.     if (slave != 0) {
  448.         errorlog("getptyslave: slave = %d but expected 0\n",slave);
  449.         exit(-1);
  450.     }
  451.  
  452. #if defined(TIOCSCTTY) && !defined(CIBAUD) && !defined(sun)
  453.     /* 4.3+BSD way to acquire controlling terminal */
  454.     /* according to Stevens - Adv. Prog..., p 642 */
  455.     if (ioctl(0,TIOCSCTTY,(char *)0) < 0) {
  456.         errorlog("failed to get controlling terminal using TIOCSCTTY");
  457.         exit(-1);
  458.     }
  459. #endif
  460.  
  461. #ifdef CRAY
  462.      (void) setsid();
  463.      (void) ioctl(0,TCSETCTTY,0);
  464.      (void) close(0);
  465.      if (open("/dev/tty", O_RDWR) < 0) {
  466.          errorlog("open(/dev/tty): %s\r\n",sys_errlist[errno]);
  467.          exit(-1);
  468.      }
  469.      (void) close(1);
  470.      (void) close(2);
  471.      (void) dup(0);
  472.      (void) dup(0);
  473.     setptyutmp();    /* create a utmp entry */
  474.  
  475.     /* _CRAY2 code from Hal Peterson <hrp@cray.com>, Cray Research, Inc. */
  476. #ifdef _CRAY2
  477.     /*
  478.      * Interpose a process between expect and the spawned child to
  479.      * keep the slave side of the pty open to allow time for expect
  480.      * to read the last output.  This is a workaround for an apparent
  481.      * bug in the Unicos pty driver on Cray-2's under Unicos 6.0 (at
  482.      * least).
  483.      */
  484.     if ((pid = fork()) == -1) {
  485.         errorlog("second fork: %s\r\n",sys_errlist[errno]);
  486.         exit(-1);
  487.     }
  488.  
  489.     if (pid) {
  490.          /* Intermediate process. */
  491.         int status;
  492.         int timeout;
  493.         char *t;
  494.  
  495.         /* How long should we wait? */
  496.         if (t = get_var("pty_timeout"))
  497.             timeout = atoi(t);
  498.         else if (t = get_var("timeout"))
  499.             timeout = atoi(t)/2;
  500.         else
  501.             timeout = 5;
  502.  
  503.         /* Let the spawned process run to completion. */
  504.          while (wait(&status) < 0 && errno == EINTR)
  505.             /* empty body */;
  506.  
  507.         /* Wait for the pty to clear. */
  508.         sleep(timeout);
  509.  
  510.         /* Duplicate the spawned process's status. */
  511.         if (WIFSIGNALED(status))
  512.             kill(getpid(), WTERMSIG(status));
  513.  
  514.         /* The kill may not have worked, but this will. */
  515.          exit(WEXITSTATUS(status));
  516.     }
  517. #endif /* _CRAY2 */
  518. #endif /* CRAY */
  519.  
  520. #ifdef TIOCCONS
  521.     if (console) {
  522.         int on = 1;
  523.         if (ioctl(0,TIOCCONS,(char *)&on) == -1) {
  524.             errorlog(stderr, "spawn %s: cannot open console, check permissions of /dev/console\n",argv[0]);
  525.             exit(-1);
  526.         }
  527.     }
  528. #endif /* TIOCCONS */
  529.  
  530.     /* by now, fd 0 and 1 point to slave pty, so fix 2 */
  531.     close(2);
  532.     fcntl(0,F_DUPFD,2);    /* duplicate 0 onto 2 */
  533.  
  534.     /* avoid fflush of cmdfile since this screws up the parents seek ptr */
  535.     /* There is no portable way to fclose a shared read-stream!!!! */
  536.     if (exp_cmdfile && (exp_cmdfile != stdin))
  537.         (void) close(fileno(exp_cmdfile));
  538.     if (logfile) (void) fclose(logfile);
  539.     if (debugfile) (void) fclose(debugfile);
  540.     /* (possibly multiple) masters are closed automatically due to */
  541.     /* earlier fcntl(,,CLOSE_ON_EXEC); */
  542.  
  543.         (void) execvp(argv[0],argv);
  544.     /* Unfortunately, by now we've closed fd's to stderr, logfile and
  545.         debugfile.
  546.        The only reasonable thing to do is to send back the error as
  547.        part of the program output.  This will be picked up in an
  548.        expect or interact command.
  549.     */
  550.     errorlog("execvp(%s): %s\r\n",argv[0],sys_errlist[errno]);
  551.     exit(-1);
  552.     /*NOTREACHED*/
  553. }
  554.  
  555. /*ARGSUSED*/
  556. static int
  557. cmdGetpid(clientData, interp, argc, argv)
  558. ClientData clientData;
  559. Tcl_Interp *interp;
  560. int argc;
  561. char **argv;
  562. {
  563.     sprintf(interp->result,"%d",getpid());
  564.     return(TCL_OK);
  565. }
  566.  
  567. /* returns current master (via out-parameter) */
  568. /* returns f or 0, but note that since exp_fd2f calls tcl_error, this */
  569. /* may be immediately followed by a "return(TCL_ERROR)"!!! */
  570. struct f *
  571. exp_update_master(interp,m,opened,adjust)
  572. Tcl_Interp *interp;
  573. int *m;
  574. int opened;
  575. int adjust;
  576. {
  577.     char *s = exp_get_var(interp,SPAWN_ID_VARNAME);
  578.     *m = (s?atoi(s):USER_SPAWN_ID);
  579.     return(exp_fd2f(interp,*m,opened,adjust,(s?s:USER_SPAWN_ID_LIT)));
  580. }
  581.  
  582. /*ARGSUSED*/
  583. static int
  584. cmdSystem(clientData, interp, argc, argv)
  585. ClientData clientData;
  586. Tcl_Interp *interp;
  587. int argc;
  588. char **argv;
  589. {
  590. #define MAX_ARGLIST 10240
  591.     int i;
  592.     char buf[MAX_ARGLIST];
  593.     char *bufp = buf;
  594.     int total_len = 0, arg_len;
  595.  
  596.     int stty_args_recognized = TRUE;
  597.     int cooked = FALSE;
  598.  
  599.     if (argc > 2 && streq(argv[1],"stty")) {
  600.         ioctled_devtty = TRUE;
  601.  
  602.         for (i=2;i<argc;i++) {
  603.             if (streq(argv[i],"raw") ||
  604.                 streq(argv[i],"-cooked")) {
  605.                 tty_raw(1);
  606.             } else if (streq(argv[i],"-raw") ||
  607.                    streq(argv[i],"cooked")) {
  608.                 cooked = TRUE;
  609.                 tty_raw(-1);
  610.             } else if (streq(argv[i],"echo")) {
  611.                 tty_echo(1);
  612.             } else if (streq(argv[i],"-echo")) {
  613.                 tty_echo(-1);
  614.             } else stty_args_recognized = FALSE;
  615.         }
  616.         /* if unknown args, fall thru and let real stty have a go */
  617.         if (stty_args_recognized) {
  618. #ifdef POSIX
  619.              if (tcsetattr(dev_tty,TCSADRAIN, &tty_current) == -1) {
  620. #else
  621.                 if (ioctl(dev_tty, TCSETSW, &tty_current) == -1) {
  622. #endif
  623.                 if (exp_disconnected || (dev_tty == -1) || !isatty(dev_tty)) {
  624.                 errorlog("system stty: impossible in this context\n");
  625.                 errorlog("are you disconnected or in a batch, at, or cron script?");
  626.                 /* user could've conceivably closed /dev/tty as well */
  627.                 }
  628.                 exp_error("system stty: ioctl(user): %s\r\n",sys_errlist[errno]);
  629.                 return(TCL_ERROR);
  630.             }
  631.             return(TCL_OK);
  632.         }
  633.     }
  634.  
  635.     for (i = 1;i<argc;i++) {
  636.         total_len += (1 + (arg_len = strlen(argv[i])));
  637.         if (total_len > MAX_ARGLIST) {
  638.             exp_error(interp,"args too long (>=%d chars)",
  639.                 total_len);
  640.             return(TCL_ERROR);
  641.         }
  642.         memcpy(bufp,argv[i],arg_len);
  643.         bufp += arg_len;
  644.         /* no need to check bounds, we accted for it earlier */
  645.         memcpy(bufp," ",1);
  646.         bufp += 1;
  647.     }
  648.  
  649.     *(bufp-1) = '\0';
  650.     i = system(buf);
  651.     debuglog("system(%s) = %d\r\n",buf,i);
  652.  
  653.     if (!stty_args_recognized) {
  654.         /* find out what weird options user asked for */
  655. #ifdef POSIX
  656.         if (tcgetattr(dev_tty, &tty_current) == -1) {
  657. #else
  658.             if (ioctl(dev_tty, TCGETS, &tty_current) == -1) {
  659. #endif
  660.             errorlog("ioctl(get): %s\r\n",sys_errlist[errno]);
  661.             exp_exit(interp,-1);
  662.         }
  663.         if (cooked) {
  664.             /* find out user's new defn of 'cooked' */
  665.             tty_cooked = tty_current;
  666.         }
  667.     }
  668.  
  669.     /* Following the style of Tcl_ExecCmd, we can just return the */
  670.     /* raw result (appropriately shifted and masked) to Tcl */
  671.     return(0xff & (i>>8));
  672. }
  673.  
  674. /* write exactly this many bytes, i.e. retry partial writes */
  675. /* I don't know if this is necessary, but large sends might have this */
  676. /* problem */
  677. /* returns 0 for success, -1 for failure */
  678. static int
  679. exact_write(fd,buffer,rembytes)
  680. int fd;
  681. char *buffer;
  682. int rembytes;
  683. {
  684.     int cc;
  685.  
  686.     while (rembytes) {
  687.         if (-1 == (cc = write(fd,buffer,rembytes))) return(-1);
  688.         /* if (0 == cc) return(-1); can't happen! */
  689.  
  690.         buffer += cc;
  691.         rembytes -= cc;
  692.     }
  693.     return(0);
  694. }
  695.  
  696. struct slow_arg {
  697.     int size;
  698.     long time;        /* microseconds */
  699. };
  700.  
  701. /* returns -1 failure, 0 if successful */
  702. static int
  703. get_slow_args(interp,x)
  704. Tcl_Interp *interp;
  705. struct slow_arg *x;
  706. {
  707.     float ftime;
  708.  
  709.     int sc;        /* return from scanf */
  710.     char *s = exp_get_var(interp,"send_slow");
  711.     if (!s) {
  712.         exp_error(interp,"send -s: send_slow has no value");
  713.         return(-1);
  714.     }
  715.     if (2 != (sc = sscanf(s,"%d %f",&x->size,&ftime))) {
  716.         exp_error(interp,"send -s: found %d value(s) in send_slow but need 2",sc);
  717.         return(-1);
  718.     }
  719.     if (x->size <= 0) {
  720.         exp_error(interp,"send -s: size (%d) in send_slow must be positive", x->size);
  721.         return(-1);
  722.     }
  723.     x->time = ftime*1000000L;
  724.     if (x->time <= 0) {
  725.         exp_error(interp,"send -s: time (%f) in send_slow must be larger",ftime);
  726.         return(-1);
  727.     }
  728.     return(0);
  729. }
  730.  
  731. /* returns 0 for success, -1 for failure */
  732. static int
  733. slow_write(fd,buffer,rembytes,arg)
  734. int fd;
  735. char *buffer;
  736. int rembytes;
  737. struct slow_arg *arg;
  738. {
  739.     while (rembytes > 0) {
  740.         int len;
  741.  
  742.         len = (arg->size<rembytes?arg->size:rembytes);
  743.         if (0 > exact_write(fd,buffer,len)) return(-1);
  744.         rembytes -= arg->size;
  745.         buffer += arg->size;
  746.  
  747.         /* skip sleep after last write */
  748.         if (rembytes > 0) exp_usleep(arg->time);
  749.     }
  750.     return(0);
  751. }
  752.  
  753. struct human_arg {
  754.     float alpha;        /* average interarrival time in seconds */
  755.     float alpha_eow;    /* as above but for eow transitions */
  756.     float c;        /* shape */
  757.     float min, max;
  758. };
  759.  
  760. /* returns -1 if error, 0 if success */
  761. static int
  762. get_human_args(interp,x)
  763. Tcl_Interp *interp;
  764. struct human_arg *x;
  765. {
  766.     int sc;        /* return from scanf */
  767.     char *s = exp_get_var(interp,"send_human");
  768.  
  769.     if (!s) {
  770.         exp_error(interp,"send -h: send_human has no value");
  771.         return(-1);
  772.     }
  773.     if (5 != (sc = sscanf(s,"%f %f %f %f %f",
  774.             &x->alpha,&x->alpha_eow,&x->c,&x->min,&x->max))) {
  775.         exp_error(interp,"send -h: found %d value(s) in send_human but need 5",sc);
  776.         return(-1);
  777.     }
  778.     if (x->alpha < 0 || x->alpha_eow < 0) {
  779.         exp_error(interp,"send -h: average interarrival times (%f %f) must be non-negative in send_human", x->alpha,x->alpha_eow);
  780.         return(-1);
  781.     }
  782.     if (x->c <= 0) {
  783.         exp_error(interp,"send -h: variability (%f) in send_human must be positive",x->c);
  784.         return(-1);
  785.     }
  786.     x->c = 1/x->c;
  787.  
  788.     if (x->min < 0) {
  789.         exp_error(interp,"send -h: minimum (%f) in send_human must be non-negative",x->min);
  790.         return(-1);
  791.     }
  792.     if (x->max < 0) {
  793.         exp_error(interp,"send -h: maximum (%f) in send_human must be non-negative",x->max);
  794.         return(-1);
  795.     }
  796.     if (x->max < x->min) {
  797.         exp_error(interp,"send -h: maximum (%f) must be >= minimum (%f) in send_human",x->max,x->min);
  798.         return(-1);
  799.     }
  800.     return(0);
  801. }
  802.  
  803. /* Compute random numbers from 0 to 1, for expect's send -h */
  804. /* This implementation sacrifices beauty for portability */
  805. static float
  806. unit_random()
  807. {
  808.     /* current implementation is pathetic but works */
  809.     /* 99991 is largest prime in my CRC - can't hurt, eh? */
  810.     return((float)(1+(rand()%99991))/99991.0);
  811. }
  812.  
  813. void
  814. exp_init_unit_random()
  815. {
  816.     srand(getpid());
  817. }
  818.  
  819. /* This function is my implementation of the Weibull distribution. */
  820. /* I've added a max time and an "alpha_eow" that captures the slight */
  821. /* but noticable change in human typists when hitting end-of-word */
  822. /* transitions. */
  823. /* returns 0 for success, -1 for failure */
  824. static int
  825. human_write(fd,buffer,arg)
  826. int fd;
  827. char *buffer;
  828. struct human_arg *arg;
  829. {
  830.     char *sp;
  831.     float t;
  832.     float alpha;
  833.     int wc;
  834.     int in_word = TRUE;
  835.  
  836.     debuglog("human_write: avg_arr=%f/%f  1/shape=%f  min=%f  max=%f\r\n",
  837.         arg->alpha,arg->alpha_eow,arg->c,arg->min,arg->max);
  838.  
  839.     for (sp = buffer;*sp;sp++) {
  840.         /* use the end-of-word alpha at eow transitions */
  841.         if (in_word && (ispunct(*sp) || isspace(*sp)))
  842.             alpha = arg->alpha_eow;
  843.         else alpha = arg->alpha;
  844.         in_word = !(ispunct(*sp) || isspace(*sp));
  845.  
  846.         t = (pow(-log((double)unit_random()),arg->c)*alpha)+arg->min;
  847.         if (t>arg->max) t = arg->max;
  848.  
  849.         /* skip sleep before writing first character */
  850.         if (sp != buffer) exp_usleep((long)(t*1000000));
  851.  
  852.         wc = write(fd,sp,1);
  853.         if (0 > wc) return(wc);
  854.     }
  855.     return(0);
  856. }
  857.  
  858.  
  859. /* I've rewritten this to be unbuffered.  I did this so you could shove */
  860. /* large files through "send".  If you are concerned about efficiency */
  861. /* you should quote all your send args to make them one single argument. */
  862. /*ARGSUSED*/
  863. static int
  864. cmdSend(clientData, interp, argc, argv)
  865. ClientData clientData;
  866. Tcl_Interp *interp;
  867. int argc;
  868. char **argv;
  869. {
  870.     int master = -1;
  871.     int master_flag = FALSE;
  872.     int i;
  873.     int wc = 0;    /* if negative, a write has failed */
  874.     extern int is_debugging;
  875.     struct human_arg human_args;
  876.     struct slow_arg slow_args;
  877. #define SEND_STYLE_PLAIN    0
  878. #define SEND_STYLE_HUMAN    1
  879. #define SEND_STYLE_SLOW        2
  880. #define SEND_STYLE_NONE        3
  881.     int send_style = SEND_STYLE_PLAIN;
  882.     int want_cooked = TRUE;
  883.  
  884.     argv++;
  885.     argc--;
  886.     while (argc) {
  887.         if (streq(*argv,"--")) {
  888.             argc--; argv++;
  889.             break;
  890.         } else if (streq(*argv,"-i")) {
  891.             argc--; argv++;
  892.             if (argc==0) {
  893.                 exp_error(interp,"-i requires following spawn_id");
  894.                 return(TCL_ERROR);
  895.             }
  896.             master = atoi(*argv);
  897.             master_flag = TRUE;
  898.             argc--; argv++;
  899.             continue;
  900.         } else if (streq(*argv,"-h")) {
  901.             argc--; argv++;
  902.             if (-1 == get_human_args(interp,&human_args))
  903.                 return(TCL_ERROR);
  904.             send_style = SEND_STYLE_HUMAN;
  905.         } else if (streq(*argv,"-s")) {
  906.             argc--; argv++;
  907.             if (-1 == get_slow_args(interp,&slow_args))
  908.                 return(TCL_ERROR);
  909.             send_style = SEND_STYLE_SLOW;
  910.         } else if (streq(*argv,"-raw")) {
  911.             argc--; argv++;
  912.             want_cooked = FALSE;
  913.         } else break;
  914.     }
  915.  
  916.     if (clientData == &sendCD_user) master = 1;
  917.     else if (clientData == &sendCD_error) master = 2;
  918.     else if (clientData == &sendCD_tty) master = dev_tty;
  919.     else if ((clientData != &sendCD_log) && !master_flag) {
  920.         /* we really do want to check if it is open */
  921.         /* but since stdin could be closed, we have to first */
  922.         /* get the fd and then convert it from 0 to 1 if necessary */
  923.         if (0 == exp_update_master(interp,&master,0,0))
  924.             return(TCL_ERROR);
  925.         /* true if called as Send with user_spawn_id */
  926.         if (is_user(master)) {
  927.             clientData = &sendCD_user;
  928.             master = 1;
  929.         }
  930.     }
  931.  
  932.     /* now do the close check */
  933.     if ((master != -1) && (fs[master].flags & FD_CLOSED)) {
  934.         exp_error(interp,"%d: invalid spawn id",master);
  935.         return(TCL_ERROR);
  936.     }
  937.  
  938. #define send_to_stderr    (clientData == &sendCD_error)
  939. #define send_to_proc    (clientData == &sendCD_proc)
  940. #define send_to_log    (clientData == &sendCD_log)
  941. #define send_to_user    ((clientData == &sendCD_user) || \
  942.              (clientData == &sendCD_tty))
  943. /*debuglog("master = %d  send_to_proc = %d\r\n",master,send_to_proc);*/
  944.  
  945.     if (send_to_log) send_style = SEND_STYLE_NONE;
  946.     if (send_to_proc) want_cooked = FALSE;
  947.     if (send_to_proc) debuglog("send: sent {");
  948.     /* if the closing brace doesn't appear, that's because an error was */
  949.     /* encountered before we could send it */
  950.  
  951.     for (i = 0;i<argc;i++) {
  952.         if (i != 0) {
  953.             /* wedge ' ' between other args.  If you don't want */
  954.             /* this, make it all into one arg! */
  955.             if (send_to_proc) {
  956.                 debuglog(" ");
  957.             } else {
  958.                 if (debugfile) fwrite(" ",1,1,debugfile);
  959.                 if ((send_to_user && logfile_all) || logfile)
  960.                     fwrite(" ",1,1,logfile);
  961.             }
  962.             switch (send_style) {
  963.             case SEND_STYLE_SLOW:
  964.                 wc = slow_write(master," ",1,&slow_args);
  965.                 break;
  966.             case SEND_STYLE_HUMAN:
  967.                 wc = human_write(master," ",&human_args);
  968.                 break;
  969.             case SEND_STYLE_NONE:
  970.                 break;
  971.             default:
  972.                 wc = exact_write(master," ",1);
  973.                 break;
  974.             }
  975.         }
  976.         if (wc >= 0) {
  977.             int len = strlen(argv[i]);
  978.             if (send_to_proc) {
  979.                 debuglog("%s",dprintify(argv[i]));
  980.             } else {
  981.                 if (debugfile)
  982.                     fwrite(argv[i],1,len,debugfile);
  983.                 if ((send_to_user && logfile_all) || logfile)
  984.                     fwrite(argv[i],1,len,logfile);
  985.             }
  986.  
  987.             if (want_cooked) argv[i] = exp_cook(argv[i],&len);
  988.  
  989.             switch (send_style) {
  990.             case SEND_STYLE_SLOW:
  991.                 wc = slow_write(master,argv[i],len,&slow_args);
  992.                 break;
  993.             case SEND_STYLE_HUMAN:
  994.                 wc = human_write(master,argv[i],&human_args);
  995.                 break;
  996.             case SEND_STYLE_NONE:
  997.                 break;
  998.             default:
  999.                 wc = exact_write(master,argv[i],len);
  1000.                 break;
  1001.             }
  1002.         }
  1003.         if (wc < 0) {
  1004.  
  1005. /* actually, I think close is the wrong thing to do here */
  1006. #if 0
  1007.             int save_errno = errno;
  1008.  
  1009.             /* write failed, so close fd.  But if close fails */
  1010.             /* describe write error rather than close */
  1011.             if (send_to_proc) exp_close(interp,master);
  1012. #endif
  1013.             exp_error(interp,"write(spawn_id=%d): %s",
  1014.                     master,sys_errlist[errno]);
  1015.             return(TCL_ERROR);
  1016.         }
  1017.     }
  1018.     if (send_to_proc) debuglog("} to spawn id %d\r\n",master);
  1019.  
  1020.     return(TCL_OK);
  1021. }
  1022.  
  1023. void
  1024. cmdLogFile_usage(interp)
  1025. Tcl_Interp *interp;
  1026. {
  1027.     exp_error(interp,"usage: log_file [[-a] file]");
  1028. }
  1029.  
  1030. /*ARGSUSED*/
  1031. static int
  1032. cmdLogFile(clientData, interp, argc, argv)
  1033. ClientData clientData;
  1034. Tcl_Interp *interp;
  1035. int argc;
  1036. char **argv;
  1037. {
  1038.     /* when this function returns, we guarantee that if logfile_all */
  1039.     /* is TRUE, then logfile is non-zero */
  1040.  
  1041.     logfile_all = FALSE;
  1042.  
  1043.     argv++; argc--;
  1044.     while (argc) {
  1045.         if (0 != strcmp(*argv,"-a")) break;
  1046.         argc--;argv++;
  1047.         logfile_all = TRUE;
  1048.     }
  1049.  
  1050.     /* note that logfile_all may be TRUE here, even if logfile is zero */
  1051.  
  1052.     if (argc > 1) {
  1053.         /* too many arguments */
  1054.         cmdLogFile_usage(interp);
  1055.         if (!logfile) logfile_all = FALSE;
  1056.         return(TCL_ERROR);
  1057.     }
  1058.  
  1059.     if (argc == 0) {
  1060.         if (logfile_all) {
  1061.             cmdLogFile_usage(interp);
  1062.             if (!logfile) logfile_all = FALSE;
  1063.             return(TCL_ERROR);
  1064.         } else if (logfile) {
  1065.             fclose(logfile);
  1066.             logfile = 0;
  1067.         /*SUPPRESS 530*/
  1068.         } else {
  1069.             /* asked to close file but not open, ignore */
  1070.             /* exp_error(interp,"log not open"); */
  1071.             /* return(TCL_ERROR); */
  1072.         }
  1073.     } else {
  1074.         if (logfile) fclose(logfile);
  1075.         if (*argv[0] == '~') {
  1076.             argv[0] = Tcl_TildeSubst(interp, argv[0]);
  1077.             if (argv[0] == NULL) return(TCL_ERROR);
  1078.         }
  1079.  
  1080.         if (NULL == (logfile = fopen(argv[0],"a"))) {
  1081.             exp_error(interp,"%s: %s",argv[0],sys_errlist[errno]);
  1082.             logfile_all = FALSE;
  1083.             return(TCL_ERROR);
  1084.         }
  1085. /*new*/        setbuf(logfile,(char *)0);
  1086.     }
  1087.     return(TCL_OK);
  1088. }
  1089.  
  1090. /*ARGSUSED*/
  1091. static int
  1092. cmdLogUser(clientData, interp, argc, argv)
  1093. ClientData clientData;
  1094. Tcl_Interp *interp;
  1095. int argc;
  1096. char **argv;
  1097. {
  1098.     if (argc != 2) {
  1099.         exp_error(interp,"usage: log_user expr");
  1100.         return(TCL_ERROR);
  1101.     }
  1102.  
  1103.     if (0 == atoi(argv[1])) loguser = FALSE;
  1104.     else loguser = TRUE;
  1105.     return(TCL_OK);
  1106. }
  1107.  
  1108. void
  1109. cmdDebug_usage(interp)
  1110. Tcl_Interp *interp;
  1111. {
  1112.     exp_error(interp,"usage: debug [-f file] expr");
  1113. }
  1114.  
  1115. /*ARGSUSED*/
  1116. static int
  1117. cmdDebug(clientData, interp, argc, argv)
  1118. ClientData clientData;
  1119. Tcl_Interp *interp;
  1120. int argc;
  1121. char **argv;
  1122. {
  1123.     int fopened = FALSE;
  1124.  
  1125.     argv++;
  1126.     argc--;
  1127.     while (argc) {
  1128.         if (!streq(*argv,"-f")) break;
  1129.         argc--;argv++;
  1130.         if (argc < 1) {
  1131.             cmdDebug_usage(interp);
  1132.             return(TCL_ERROR);
  1133.         }
  1134.         if (debugfile) fclose(debugfile);
  1135.         if (*argv[0] == '~') {
  1136.             argv[0] = Tcl_TildeSubst(interp, argv[0]);
  1137.             if (argv[0] == NULL) return(TCL_ERROR);
  1138.         }
  1139.         if (NULL == (debugfile = fopen(*argv,"a"))) {
  1140.             exp_error(interp,"%s: %s",*argv,sys_errlist[errno]);
  1141.             return(TCL_ERROR);
  1142.         }
  1143.         setbuf(debugfile,(char *)0);
  1144.         fopened = TRUE;
  1145.         argc--;argv++;
  1146.     }
  1147.  
  1148.     if (argc != 1) {
  1149.         cmdDebug_usage(interp);
  1150.         return(TCL_ERROR);
  1151.     }
  1152.  
  1153.     /* if no -f given, close file */
  1154.     if (fopened == FALSE && debugfile) {
  1155.         fclose(debugfile);
  1156.         debugfile = 0;
  1157.     }
  1158.  
  1159.     exp_is_debugging = atoi(*argv);
  1160.     return(TCL_OK);
  1161. }
  1162.  
  1163. /*ARGSUSED*/
  1164. static int
  1165. cmdExit(clientData, interp, argc, argv)
  1166. ClientData clientData;
  1167. Tcl_Interp *interp;
  1168. int argc;
  1169. char **argv;
  1170. {
  1171.     int value = 0;
  1172.  
  1173.     if (argc > 2) {
  1174.         exp_error(interp,"usage: exit [status]");
  1175.         return(TCL_ERROR);
  1176.     }
  1177.  
  1178.     if (argc == 2) {
  1179.         if (Tcl_GetInt(interp, argv[1], &value) != TCL_OK) {
  1180.             return TCL_ERROR;
  1181.         }
  1182.     }
  1183.  
  1184.     exp_exit(interp,value);
  1185.     /*NOTREACHED*/
  1186. }
  1187.  
  1188. /*ARGSUSED*/
  1189. static int
  1190. cmdClose(clientData, interp, argc, argv)
  1191. ClientData clientData;
  1192. Tcl_Interp *interp;
  1193. int argc;
  1194. char **argv;
  1195. {
  1196.     int m = -1;
  1197.  
  1198.     if (argc == 1) {
  1199.         if (exp_update_master(interp,&m,1,0) == 0) return(TCL_ERROR);
  1200.     } else if (streq(argv[1],"-i")) {
  1201.         if (argc != 3) {
  1202.             exp_error(interp,"usage: close [-i spawn_id]");
  1203.             return(TCL_ERROR);
  1204.         }
  1205.         m = atoi(argv[2]);
  1206.     }
  1207.  
  1208.     if (m != -1) return(exp_close(interp,m));
  1209.  
  1210.     /* doesn't look like our format, it must be a Tcl-style file handle */
  1211.     /* we're so lucky that the formats are easily distinguishable */
  1212.     /* Historical note: we used "close" in Tcl long before there was a */
  1213.     /* builtin by the same name. */
  1214.  
  1215.     /* don't know what is formally correct as 1st arg, but I see the */
  1216.     /* code doesn't use it anyway */
  1217.     return(Tcl_CloseCmd(clientData,interp,argc,argv));
  1218. }
  1219.  
  1220. /*ARGSUSED*/
  1221. static void
  1222. tcl_tracer(clientData,interp,level,command,cmdProc,cmdClientData,argc,argv)
  1223. ClientData clientData;
  1224. Tcl_Interp *interp;
  1225. int level;
  1226. char *command;
  1227. int (*cmdProc)();
  1228. ClientData cmdClientData;
  1229. int argc;
  1230. char *argv[];
  1231. {
  1232.     int i;
  1233.  
  1234.     /* come out on stderr, by using errorlog */
  1235.     errorlog("%2d",level);
  1236.     for (i = 0;i<level;i++) nferrorlog("  ",0/*ignored - satisfy lint*/);
  1237.     errorlog("%s\r\n",command);
  1238. }
  1239.  
  1240. /*ARGSUSED*/
  1241. static int
  1242. cmdTrace(clientData, interp, argc, argv)
  1243. ClientData clientData;
  1244. Tcl_Interp *interp;
  1245. int argc;
  1246. char **argv;
  1247. {
  1248.     static int trace_level = 0;
  1249.     static Tcl_Trace trace_handle;
  1250.  
  1251.     if (argc != 2) {
  1252.         exp_error(interp,"usage: trace level");
  1253.         return(TCL_ERROR);
  1254.     }
  1255.     /* tracing already in effect, undo it */
  1256.     if (trace_level > 0) Tcl_DeleteTrace(interp,trace_handle);
  1257.  
  1258.     /* get and save new trace level */
  1259.     trace_level = atoi(argv[1]);
  1260.     if (trace_level > 0)
  1261.         trace_handle = Tcl_CreateTrace(interp,
  1262.                 trace_level,tcl_tracer,(ClientData)0);
  1263.     return(TCL_OK);
  1264. }
  1265.  
  1266. static char *wait_usage = "usage: wait [-i spawn_id]";
  1267.  
  1268. /*ARGSUSED*/
  1269. static int
  1270. cmdWait(clientData, interp, argc, argv)
  1271. ClientData clientData;
  1272. Tcl_Interp *interp;
  1273. int argc;
  1274. char **argv;
  1275. {
  1276.     int master;
  1277.     struct f *f;
  1278.     /* if your C compiler bombs here, define NO_PID_T in Makefile */
  1279. #ifdef NO_PID_T
  1280.     int pid = 0;    /* 0 indicates no error occurred (yet) */
  1281. #else
  1282.     pid_t pid = 0;    /* ditto */
  1283. #endif
  1284.  
  1285.     argc--; argv++;
  1286.  
  1287.     if (argc == 0) {
  1288.         if (0 == exp_update_master(interp,&master,0,0))
  1289.             return(TCL_ERROR);
  1290.     } /* else if (streq(argv,"-pid")) {
  1291.         argv++;
  1292.         pid = atoi(argv++);
  1293.         */ /* search through fs for one with right pid */ /*
  1294.         master = pid_to_fd(pid);
  1295.     } */ else if (streq(argv[0],"-i")) {
  1296.         if (argc != 2) {
  1297.             exp_error(interp,wait_usage);
  1298.             return(TCL_ERROR);
  1299.         }
  1300.         master = atoi(argv[1]);
  1301.     } else {
  1302.         exp_error(interp,wait_usage);
  1303.         return(TCL_ERROR);
  1304.     }
  1305.  
  1306.     if (!(f = exp_fd2f(interp,master,0,0,"wait"))) return(TCL_ERROR);
  1307.  
  1308.     if (!(f->flags & FD_SYSWAITED)) {
  1309. #if defined(NOWAITPID) && defined(HAVE_WAIT4)
  1310. # undef NOWAITPID
  1311. # define waitpid(pid,stat,opt) wait4(pid,stat,opt,NULL)
  1312. #endif
  1313.  
  1314. #ifdef NOWAITPID
  1315.         int status;
  1316.         for (;;) {
  1317.             int i;
  1318.  
  1319.             pid = wait(&status);
  1320.             if (pid == f->pid) break;
  1321.             /* oops, wrong pid */
  1322.             for (i=0;i<=fd_max;i++) {
  1323.                 if (fs[i].pid == pid) break;
  1324.             }
  1325.             if (i>fd_max) {
  1326.                 debuglog("wait found unknown pid %d\r\n",pid);
  1327.                 continue;    /* drop on floor */
  1328.             }
  1329.             fs[i].flags |= FD_SYSWAITED;
  1330.             fs[i].wait = status;
  1331.         }
  1332.         f->wait = status;
  1333. #else
  1334.         pid = waitpid(f->pid,&f->wait,0);
  1335. #endif
  1336.         f->flags |= FD_SYSWAITED;
  1337.     }
  1338.     if (f->flags & FD_CLOSED) f->flags = 0;
  1339.     f->flags |= FD_USERWAITED;
  1340. #ifndef WEXITSTATUS
  1341. #define WEXITSTATUS(x) x
  1342. #endif
  1343.     /* return {pid status} or {-1 errno} */
  1344.     /* non-portable assumption that pid_t can be printed with %d */
  1345.     sprintf(interp->result,"%d %d",f->pid,
  1346.         (pid == -1)?errno:WEXITSTATUS(f->wait));
  1347.     return(TCL_OK);
  1348. }
  1349.  
  1350. /*ARGSUSED*/
  1351. static int
  1352. cmdFork(clientData, interp, argc, argv)
  1353. ClientData clientData;
  1354. Tcl_Interp *interp;
  1355. int argc;
  1356. char **argv;
  1357. {
  1358.     int pid;
  1359.  
  1360.     if (argc > 1) {
  1361.         exp_error(interp,"usage: fork");
  1362.         return(TCL_ERROR);
  1363.     }
  1364.  
  1365.     if (0 == (pid = fork())) exp_forked = TRUE;
  1366.  
  1367.     sprintf(interp->result,"%d",pid);
  1368.     debuglog("fork: returns {%s}\r\n",interp->result);
  1369.     return(TCL_OK);
  1370. }
  1371.  
  1372. /*ARGSUSED*/
  1373. static int
  1374. cmdDisconnect(clientData, interp, argc, argv)
  1375. ClientData clientData;
  1376. Tcl_Interp *interp;
  1377. int argc;
  1378. char **argv;
  1379. {
  1380.     int ttyfd;
  1381.  
  1382.     if (argc > 1) {
  1383.         exp_error(interp,"usage: disconnect");
  1384.         return(TCL_ERROR);
  1385.     }
  1386.  
  1387.     if (exp_disconnected) {
  1388.         exp_error(interp,"already disconnected");
  1389.         return(TCL_ERROR);
  1390.     }
  1391.     if (!exp_forked) {
  1392.         exp_error(interp,"can only disconnect child process");
  1393.         return(TCL_ERROR);
  1394.     }
  1395.     exp_disconnected = TRUE;
  1396.  
  1397.     /* ignore hangup signals generated by testing ptys in getptymaster */
  1398.     /* and other places */
  1399.     signal(SIGHUP,SIG_IGN);
  1400.  
  1401.     /* freopen(,,stdin) prevents confusion between send/expect_user */
  1402.     /* accidentally mapping to a real spawned process after a disconnect */
  1403.     /* A better solution might be to put code in the _user routines to */
  1404.     /* check that a disconnect has occured. */
  1405.     /* freopen(,,stderr) saves error checking in error/log routines. */
  1406.     freopen("/dev/null","r",stdin);
  1407.     freopen("/dev/null","w",stdout);
  1408.     freopen("/dev/null","w",stderr);
  1409.  
  1410. #ifdef DO_SETSID
  1411.     setsid();
  1412. #else
  1413. #ifdef SYSV3
  1414.     /* put process in our own pgrp, and lose controlling terminal */
  1415. #ifdef sysV88
  1416.     /* With setpgrp first, child ends up with closed stdio */
  1417.     /* according to Dave Schmitt <daves@techmpc.csg.gss.mot.com> */
  1418.     if (fork()) exit(0);
  1419.     setpgrp();
  1420. #else
  1421.     setpgrp();
  1422.     /*signal(SIGHUP,SIG_IGN); moved out to above */
  1423.     if (fork()) exit(0);    /* first child exits (as per Stevens, */
  1424.     /* UNIX Network Programming, p. 79-80) */
  1425.     /* second child process continues as daemon */
  1426. #endif
  1427. #else /* !SYSV3 */
  1428. #ifdef MIPS_BSD
  1429.     /* required on BSD side of MIPS OS <jmsellen@watdragon.waterloo.edu> */
  1430. #    include <sysv/sys.s>
  1431.     syscall(SYS_setpgrp);
  1432. #endif
  1433.     setpgrp(0,0);
  1434. /*    setpgrp(0,getpid());*/    /* put process in our own pgrp */
  1435.     ttyfd = open("/dev/tty", O_RDWR);
  1436.     if (ttyfd >= 0) {
  1437.         /* zap controlling terminal if we had one */
  1438.         (void) ioctl(ttyfd, TIOCNOTTY, (char *)0);
  1439.         (void) close(ttyfd);
  1440.     }
  1441. #endif /* SYSV3 */
  1442. #endif /* DO_SETSID */
  1443.     return(TCL_OK);
  1444. }
  1445.  
  1446. /*ARGSUSED*/
  1447. static int
  1448. cmdOverlay(clientData, interp, argc, argv)
  1449. ClientData clientData;
  1450. Tcl_Interp *interp;
  1451. int argc;
  1452. char **argv;
  1453. {
  1454.     int newfd, oldfd;
  1455.     int dash_name = 0;
  1456.     char *command;
  1457.  
  1458.     argc--; argv++;
  1459.     while (argc) {
  1460.         if (*argv[0] != '-') break;    /* not a flag */
  1461.         if (streq(*argv,"-")) {        /* - by itself */
  1462.             argc--; argv++;
  1463.             dash_name = 1;
  1464.             continue;
  1465.         }
  1466.         newfd = atoi(argv[0]+1);
  1467.         argc--; argv++;
  1468.         if (argc == 0) {
  1469.             exp_error(interp,"overlay -# requires additional argument");
  1470.             return(TCL_ERROR);
  1471.         }
  1472.         oldfd = atoi(argv[0]);
  1473.         argc--; argv++;
  1474.         debuglog("overlay: mapping fd %d to %d\r\n",oldfd,newfd);
  1475.         if (oldfd != newfd) (void) dup2(oldfd,newfd);
  1476.         else debuglog("warning: overlay: old fd == new fd (%d)\r\n",oldfd);
  1477.     }
  1478.     if (argc == 0) {
  1479.         exp_error(interp,"need program name");
  1480.         return(TCL_ERROR);
  1481.     }
  1482.     command = argv[0];
  1483.     if (dash_name) {
  1484.         argv[0] = malloc(1+strlen(command));
  1485.         /* check for malloc failure */
  1486.         sprintf(argv[0],"-%s",command);
  1487.     }
  1488.  
  1489.     signal(SIGINT, SIG_DFL);
  1490.     signal(SIGQUIT, SIG_DFL);
  1491.         (void) execvp(command,argv);
  1492.     exp_error(interp,"execvp(%s): %s\r\n",argv[0],sys_errlist[errno]);
  1493.     return(TCL_ERROR);
  1494. }
  1495.  
  1496. #if 0
  1497. /*ARGSUSED*/
  1498. int
  1499. cmdReady(clientData, interp, argc, argv)
  1500. ClientData clientData;
  1501. Tcl_Interp *interp;
  1502. int argc;
  1503. char **argv;
  1504. {
  1505.     char num[4];    /* can hold up to "999 " */
  1506.     char buf[1024];    /* can easily hold 256 spawn_ids! */
  1507.     int i, j;
  1508.     int *masters, *masters2;
  1509.     int timeout = get_timeout();
  1510.  
  1511.     if (argc < 2) {
  1512.         exp_error(interp,"usage: ready spawn_id1 [spawn_id2 ...]");
  1513.         return(TCL_ERROR);
  1514.     }
  1515.  
  1516.     if (0 == (masters = (int *)malloc((argc-1)*sizeof(int)))) {
  1517.         exp_error(interp,"malloc(%d spawn_id's)",(argc-1));
  1518.         return(TCL_ERROR);
  1519.     }
  1520.     if (0 == (masters2 = (int *)malloc((argc-1)*sizeof(int)))) {
  1521.         exp_error(interp,"malloc(%d spawn_id's)",(argc-1));
  1522.         return(TCL_ERROR);
  1523.     }
  1524.  
  1525.     for (i=1;i<argc;i++) {
  1526.         j = atoi(argv[i]);
  1527.         if (!exp_fd2f(interp,j,1,"ready")) {
  1528.             free(masters);
  1529.             return(TCL_ERROR);
  1530.         }
  1531.         masters[i-1] = j;
  1532.     }
  1533.     j = i-1;
  1534.     if (TCL_ERROR == ready(masters,i-1,masters2,&j,&timeout))
  1535.         return(TCL_ERROR);
  1536.  
  1537.     /* pack result back into out-array */
  1538.     buf[0] = '\0';
  1539.     for (i=0;i<j;i++) {
  1540.         sprintf(num,"%d ",masters2[i]); /* note extra blank */
  1541.         strcat(buf,num);
  1542.     }
  1543.     free(masters); free(masters2);
  1544.     Tcl_Return(interp,buf,TCL_VOLATILE);
  1545.     return(TCL_OK);
  1546. }
  1547. #endif
  1548.  
  1549. /*ARGSUSED*/
  1550. int
  1551. cmdInterpreter(clientData, interp, argc, argv)
  1552. ClientData clientData;
  1553. Tcl_Interp *interp;
  1554. int argc;
  1555. char **argv;
  1556. {
  1557.     if (argc != 1) {
  1558.         exp_error(interp,"no arguments allowed");
  1559.         return(TCL_ERROR);
  1560.     }
  1561.  
  1562.     exp_interpreter(interp);
  1563.     /* errors and ok, are caught by escape() and discarded */
  1564.     /* the only thing that actually comes out of escape are */
  1565.     /* RETURN, BREAK, and CONTINUE which we all translate to OK */
  1566.     return(TCL_OK);
  1567. }
  1568.  
  1569. /* this command supercede's Tcl's builtin CONTINUE command */
  1570. /*ARGSUSED*/
  1571. int
  1572. cmdContinueExpect(clientData, interp, argc, argv)
  1573. ClientData clientData;
  1574. Tcl_Interp *interp;
  1575. int argc;
  1576. char **argv;
  1577. {
  1578.     if (argc == 1) return(TCL_CONTINUE);
  1579.     else if (argc == 2) {
  1580.         if (streq(argv[1],"-expect")) {
  1581.             return(TCL_CONTINUE_EXPECT);
  1582.         }
  1583.     }
  1584.     exp_error(interp,"usage: continue [-expect]\n");
  1585.     return(TCL_ERROR);
  1586. }
  1587.  
  1588. /* this command supercede's Tcl's builtin RETURN command */
  1589. /*ARGSUSED*/
  1590. int
  1591. cmdReturnInter(clientData, interp, argc, argv)
  1592. ClientData clientData;
  1593. Tcl_Interp *interp;
  1594. int argc;
  1595. char **argv;
  1596. {
  1597.     int rc = TCL_RETURN;
  1598.  
  1599.     if (argc == 1) return(TCL_RETURN);
  1600.     if (argc > 3) {
  1601.         exp_error(interp,"usage: return [-tcl] [value]\n");
  1602.         return(TCL_ERROR);
  1603.     }
  1604.  
  1605.     if (streq(argv[1],"-tcl")) rc = TCL_RETURN_TCL;
  1606.  
  1607.     if (!(rc == TCL_RETURN_TCL && argc == 2)) {
  1608.         /* if either return "-tcl expr" or "expr", get the expr */
  1609.         Tcl_SetResult(interp,argv[1],TCL_VOLATILE);
  1610.     }
  1611.  
  1612.     return(rc);
  1613. }
  1614.  
  1615. void
  1616. exp_create_commands(interp)
  1617. Tcl_Interp *interp;
  1618. {
  1619.     extern int cmdInteract();
  1620.  
  1621.     extern int cmdExpectVersion();
  1622.  
  1623.     extern int cmdPrompt1();
  1624.     extern int cmdPrompt2();
  1625.  
  1626.     Tcl_CreateCommand(interp,"close",
  1627.         cmdClose,(ClientData)0,exp_deleteProc);
  1628.     Tcl_CreateCommand(interp,"debug",
  1629.         cmdDebug,(ClientData)0,exp_deleteProc);
  1630.     Tcl_CreateCommand(interp,"disconnect",
  1631.         cmdDisconnect,(ClientData)0,exp_deleteProc);
  1632.     Tcl_CreateCommand(interp,"exit",
  1633.         cmdExit,(ClientData)0,exp_deleteProc);
  1634.  
  1635.     /* following commands are created in exp_init_expect in expect.c */
  1636.     /* to prevent unnecessary name space pollution by the ClientData */
  1637.     /* argument to Tcl_CreateCommand */
  1638.     /* expect, expect_after, expect_before, expect_user */
  1639.     /* trap */
  1640.     /* matchmax */
  1641.  
  1642.     Tcl_CreateCommand(interp,"continue",
  1643.         cmdContinueExpect,(ClientData)0,exp_deleteProc);
  1644.     Tcl_CreateCommand(interp,"expect_version",
  1645.         cmdExpectVersion,(ClientData)0,exp_deleteProc);
  1646.     Tcl_CreateCommand(interp,"fork",
  1647.         cmdFork,(ClientData)0,exp_deleteProc);
  1648.     Tcl_CreateCommand(interp,"getpid",
  1649.         cmdGetpid,(ClientData)0,exp_deleteProc);
  1650.     Tcl_CreateCommand(interp,"interact",
  1651.         cmdInteract,(ClientData)0,exp_deleteProc);
  1652.     Tcl_CreateCommand(interp,"interpreter",
  1653.         cmdInterpreter,(ClientData)0,exp_deleteProc);
  1654.     Tcl_CreateCommand(interp,"log_file",
  1655.         cmdLogFile,(ClientData)0,exp_deleteProc);
  1656.     Tcl_CreateCommand(interp,"log_user",
  1657.         cmdLogUser,(ClientData)0,exp_deleteProc);
  1658.     Tcl_CreateCommand(interp,"overlay",
  1659.         cmdOverlay,(ClientData)0,exp_deleteProc);
  1660.     Tcl_CreateCommand(interp,"prompt1",
  1661.         cmdPrompt1,(ClientData)0,exp_deleteProc);
  1662.     Tcl_CreateCommand(interp,"prompt2",
  1663.         cmdPrompt2,(ClientData)0,exp_deleteProc);
  1664.     Tcl_CreateCommand(interp,"return",
  1665.         cmdReturnInter,(ClientData)0,exp_deleteProc);
  1666.     Tcl_CreateCommand(interp,"send",
  1667.         cmdSend,(ClientData)&sendCD_proc,exp_deleteProc);
  1668.     /* Tk may wipe out "send" so provide an alias: "send_spawn" */
  1669.     Tcl_CreateCommand(interp,"send_spawn",
  1670.         cmdSend,(ClientData)&sendCD_proc,exp_deleteProc);
  1671.     Tcl_CreateCommand(interp,"send_error",
  1672.         cmdSend,(ClientData)&sendCD_error,exp_deleteProc);
  1673.     Tcl_CreateCommand(interp,"send_log",
  1674.         cmdSend,(ClientData)&sendCD_log,exp_deleteProc);
  1675.     Tcl_CreateCommand(interp,"send_tty",
  1676.         cmdSend,(ClientData)&sendCD_tty,exp_deleteProc);
  1677.     Tcl_CreateCommand(interp,"send_user",
  1678.         cmdSend,(ClientData)&sendCD_user,exp_deleteProc);
  1679.     Tcl_CreateCommand(interp,"spawn",
  1680.         cmdSpawn,(ClientData)0,exp_deleteProc);
  1681.     Tcl_CreateCommand(interp,"strace",
  1682.         cmdTrace,(ClientData)0,exp_deleteProc);
  1683.     Tcl_CreateCommand(interp,"system",
  1684.         cmdSystem,(ClientData)0,exp_deleteProc);
  1685.     Tcl_CreateCommand(interp,"wait",
  1686.         cmdWait,(ClientData)0,exp_deleteProc);
  1687. }
  1688.