diff options
Diffstat (limited to 'exp_tty.c')
-rw-r--r-- | exp_tty.c | 811 |
1 files changed, 811 insertions, 0 deletions
diff --git a/exp_tty.c b/exp_tty.c new file mode 100644 index 0000000..3f22c90 --- /dev/null +++ b/exp_tty.c @@ -0,0 +1,811 @@ +/* exp_tty.c - tty support routines */ + +#include "expect_cf.h" +#include <stdio.h> +#include <signal.h> +#include "string.h" + +#ifdef HAVE_SYS_FCNTL_H +# include <sys/fcntl.h> +#else +# include <fcntl.h> +#endif + +#include <sys/stat.h> + +#ifdef HAVE_INTTYPES_H +# include <inttypes.h> +#endif +#include <sys/types.h> + +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif + +#ifdef HAVE_SYS_WAIT_H +#include <sys/wait.h> +#endif + +#if defined(SIGCLD) && !defined(SIGCHLD) +#define SIGCHLD SIGCLD +#endif + +#include "tcl.h" +#include "exp_prog.h" +#include "exp_rename.h" +#include "exp_tty_in.h" +#include "exp_command.h" +#include "exp_log.h" +#include "exp_win.h" + +static int is_raw = FALSE; +static int is_noecho = FALSE; + +int exp_ioctled_devtty = FALSE; +int exp_stdin_is_tty; +int exp_stdout_is_tty; + +/*static*/ extern exp_tty exp_tty_current, exp_tty_cooked; +#define tty_current exp_tty_current +#define tty_cooked exp_tty_cooked + +int +exp_israw(void) +{ + return is_raw; +} + +int +exp_isecho(void) +{ + return !is_noecho; +} + +/* if set == 1, set it to raw, else unset it */ +void +exp_tty_raw(int set) +{ + if (set == 1) { + is_raw = TRUE; +#if defined(HAVE_TERMIOS) || defined(HAVE_TERMIO) /* had POSIX too */ + tty_current.c_iflag = 0; + tty_current.c_oflag = 0; + tty_current.c_lflag &= ECHO; /* disable everything but echo */ + tty_current.c_cc[VMIN] = 1; + tty_current.c_cc[VTIME] = 0; + } else { + tty_current.c_iflag = tty_cooked.c_iflag; + tty_current.c_oflag = tty_cooked.c_oflag; +/* tty_current.c_lflag = tty_cooked.c_lflag;*/ +/* attempt 2 tty_current.c_lflag = tty_cooked.c_lflag & ~ECHO;*/ + /* retain current echo setting */ + tty_current.c_lflag = (tty_cooked.c_lflag & ~ECHO) | (tty_current.c_lflag & ECHO); + tty_current.c_cc[VMIN] = tty_cooked.c_cc[VMIN]; + tty_current.c_cc[VTIME] = tty_cooked.c_cc[VTIME]; +#else +# if defined(HAVE_SGTTYB) + tty_current.sg_flags |= RAW; + } else { + tty_current.sg_flags = tty_cooked.sg_flags; +# endif +#endif + is_raw = FALSE; + } +} + +void +exp_tty_echo(int set) +{ + if (set == 1) { + is_noecho = FALSE; +#if defined(HAVE_TERMIOS) || defined(HAVE_TERMIO) /* had POSIX too */ + tty_current.c_lflag |= ECHO; + } else { + tty_current.c_lflag &= ~ECHO; +#else + tty_current.sg_flags |= ECHO; + } else { + tty_current.sg_flags &= ~ECHO; +#endif + is_noecho = TRUE; + } +} + +int +exp_tty_set_simple(exp_tty *tty) +{ +#ifdef HAVE_TCSETATTR + return(tcsetattr(exp_dev_tty, TCSADRAIN,tty)); +#else + return(ioctl (exp_dev_tty, TCSETSW ,tty)); +#endif +} + +int +exp_tty_get_simple(exp_tty *tty) +{ +#ifdef HAVE_TCSETATTR + return(tcgetattr(exp_dev_tty, tty)); +#else + return(ioctl (exp_dev_tty, TCGETS, tty)); +#endif +} + +/* returns 0 if nothing changed */ +/* if something changed, the out parameters are changed as well */ +int +exp_tty_raw_noecho( + Tcl_Interp *interp, + exp_tty *tty_old, + int *was_raw, + int *was_echo) +{ + if (exp_disconnected) return(0); + if (is_raw && is_noecho) return(0); + if (exp_dev_tty == -1) return(0); + + *tty_old = tty_current; /* save old parameters */ + *was_raw = is_raw; + *was_echo = !is_noecho; + expDiagLog("tty_raw_noecho: was raw = %d echo = %d\r\n",is_raw,!is_noecho); + + exp_tty_raw(1); + exp_tty_echo(-1); + + if (exp_tty_set_simple(&tty_current) == -1) { + expErrorLog("ioctl(raw): %s\r\n",Tcl_PosixError(interp)); + + /* SF #439042 -- Allow overide of "exit" by user / script + */ + { + char buffer [] = "exit 1"; + Tcl_Eval(interp, buffer); + } + } + + exp_ioctled_devtty = TRUE; + return(1); +} + +/* returns 0 if nothing changed */ +/* if something changed, the out parameters are changed as well */ +int +exp_tty_cooked_echo( + Tcl_Interp *interp, + exp_tty *tty_old, + int *was_raw, + int *was_echo) +{ + if (exp_disconnected) return(0); + if (!is_raw && !is_noecho) return(0); + if (exp_dev_tty == -1) return(0); + + *tty_old = tty_current; /* save old parameters */ + *was_raw = is_raw; + *was_echo = !is_noecho; + expDiagLog("tty_cooked_echo: was raw = %d echo = %d\r\n",is_raw,!is_noecho); + + exp_tty_raw(-1); + exp_tty_echo(1); + + if (exp_tty_set_simple(&tty_current) == -1) { + expErrorLog("ioctl(noraw): %s\r\n",Tcl_PosixError(interp)); + + /* SF #439042 -- Allow overide of "exit" by user / script + */ + { + char buffer [] = "exit 1"; + Tcl_Eval(interp, buffer); + } + } + exp_ioctled_devtty = TRUE; + + return(1); +} + +void +exp_tty_set( + Tcl_Interp *interp, + exp_tty *tty, + int raw, + int echo) +{ + if (exp_tty_set_simple(tty) == -1) { + expErrorLog("ioctl(set): %s\r\n",Tcl_PosixError(interp)); + + /* SF #439042 -- Allow overide of "exit" by user / script + */ + { + char buffer [] = "exit 1"; + Tcl_Eval(interp, buffer); + } + } + is_raw = raw; + is_noecho = !echo; + tty_current = *tty; + expDiagLog("tty_set: raw = %d, echo = %d\r\n",is_raw,!is_noecho); + exp_ioctled_devtty = TRUE; +} + +#if 0 +/* avoids scoping problems */ +void +exp_update_cooked_from_current() { + tty_cooked = tty_current; +} + +int +exp_update_real_tty_from_current() { + return(exp_tty_set_simple(&tty_current)); +} + +int +exp_update_current_from_real_tty() { + return(exp_tty_get_simple(&tty_current)); +} +#endif + +void +exp_init_stdio() +{ + exp_stdin_is_tty = isatty(0); + exp_stdout_is_tty = isatty(1); + + setbuf(stdout,(char *)0); /* unbuffer stdout */ +} + +/*ARGSUSED*/ +void +exp_tty_break( + Tcl_Interp *interp, + int fd) +{ +#ifdef POSIX + tcsendbreak(fd,0); +#else +# ifdef TIOCSBRK + ioctl(fd,TIOCSBRK,0); + exp_dsleep(interp,0.25); /* sleep for at least a quarter of a second */ + ioctl(fd,TIOCCBRK,0); +# else + /* dunno how to do this - ignore */ +# endif +#endif +} + +/* take strings with newlines and insert carriage-returns. This allows user */ +/* to write send_user strings without always putting in \r. */ +/* If len == 0, use strlen to compute it */ +/* NB: if terminal is not in raw mode, nothing is done. */ +char * +exp_cook( + char *s, + int *len) /* current and new length of s */ +{ + static int destlen = 0; + static char *dest = 0; + char *d; /* ptr into dest */ + unsigned int need; + + if (s == 0) return("<null>"); + + if (!is_raw) return(s); + + /* worst case is every character takes 2 to represent */ + need = 1 + 2*(len?*len:strlen(s)); + if (need > destlen) { + if (dest) ckfree(dest); + dest = ckalloc(need); + destlen = need; + } + + for (d = dest;*s;s++) { + if (*s == '\n') { + *d++ = '\r'; + *d++ = '\n'; + } else { + *d++ = *s; + } + } + *d = '\0'; + if (len) *len = d-dest; + return(dest); +} + +static int /* returns TCL_whatever */ +exec_stty( + Tcl_Interp *interp, + int argc, + char **argv, + int devtty) /* if true, redirect to /dev/tty */ +{ + int i; + int rc; + + Tcl_Obj *cmdObj = Tcl_NewStringObj("",0); + Tcl_IncrRefCount(cmdObj); + + Tcl_AppendStringsToObj(cmdObj,"exec ",(char *)0); + Tcl_AppendStringsToObj(cmdObj,STTY_BIN,(char *)0); + for (i=1;i<argc;i++) { + Tcl_AppendStringsToObj(cmdObj," ",argv[i],(char *)0); + } + if (devtty) Tcl_AppendStringsToObj(cmdObj, +#ifdef STTY_READS_STDOUT + " >/dev/tty", +#else + " </dev/tty", +#endif + (char *)0); + + Tcl_ResetResult(interp); + + /* + * normally, I wouldn't set one of Tcl's own variables, but in this + * case, I only want to see if Tcl resets it to non-NONE, and I don't + * know any other way of doing it + */ + + Tcl_SetVar(interp,"errorCode","NONE",0); + rc = Tcl_EvalObjEx(interp,cmdObj,TCL_EVAL_DIRECT); + + Tcl_DecrRefCount(cmdObj); + + /* if stty-reads-stdout, stty will fail since Exec */ + /* will detect the stderr. Only by examining errorCode */ + /* can we tell if a real error occurred. */ + +#ifdef STTY_READS_STDOUT + if (rc == TCL_ERROR) { + char *ec = Tcl_GetVar(interp,"errorCode",TCL_GLOBAL_ONLY); + if (ec && !streq(ec,"NONE")) return TCL_ERROR; + } +#endif + return TCL_OK; +} + +/*ARGSUSED*/ +static int +Exp_SttyCmd( + ClientData clientData, + Tcl_Interp *interp, + int argc, + char **argv) +{ + /* redirection symbol is not counted as a stty arg in terms */ + /* of recognition. */ + int saw_unknown_stty_arg = FALSE; + int saw_known_stty_arg = FALSE; + int no_args = TRUE; + + int rc = TCL_OK; + int cooked = FALSE; + int was_raw, was_echo; + + char **redirect; /* location of "<" */ + char *infile = 0; + int fd; /* (slave) fd of infile */ + int master = -1; /* master fd of infile */ + char **argv0 = argv; + + for (argv=argv0+1;*argv;argv++) { + if (argv[0][0] == '<') { + redirect = argv; + infile = *(argv+1); + if (!infile) { + expErrorLog("usage: < ttyname"); + return TCL_ERROR; + } + if (streq(infile,"/dev/tty")) { + infile = 0; + *argv = 0; + *(argv+1) = 0; + argc -= 2; + } else { + master = exp_trap_off(infile); + if (-1 == (fd = open(infile,2))) { + expErrorLog("couldn't open %s: %s", + infile,Tcl_PosixError(interp)); + return TCL_ERROR; + } + } + break; + } + } + + if (!infile) { /* work on /dev/tty */ + was_raw = exp_israw(); + was_echo = exp_isecho(); + + for (argv=argv0+1;*argv;argv++) { + if (streq(*argv,"raw") || + streq(*argv,"-cooked")) { + exp_tty_raw(1); + saw_known_stty_arg = TRUE; + no_args = FALSE; + exp_ioctled_devtty = TRUE; + } else if (streq(*argv,"-raw") || + streq(*argv,"cooked")) { + cooked = TRUE; + exp_tty_raw(-1); + saw_known_stty_arg = TRUE; + no_args = FALSE; + exp_ioctled_devtty = TRUE; + } else if (streq(*argv,"echo")) { + exp_tty_echo(1); + saw_known_stty_arg = TRUE; + no_args = FALSE; + exp_ioctled_devtty = TRUE; + } else if (streq(*argv,"-echo")) { + exp_tty_echo(-1); + saw_known_stty_arg = TRUE; + no_args = FALSE; + exp_ioctled_devtty = TRUE; + } else if (streq(*argv,"rows")) { + if (*(argv+1)) { + exp_win_rows_set(*(argv+1)); + argv++; + no_args = FALSE; + exp_ioctled_devtty = TRUE; + } else { + Tcl_SetResult (interp, exp_win_rows_get(), TCL_VOLATILE); + return TCL_OK; + } + } else if (streq(*argv,"columns")) { + if (*(argv+1)) { + exp_win_columns_set(*(argv+1)); + argv++; + no_args = FALSE; + exp_ioctled_devtty = TRUE; + } else { + Tcl_SetResult (interp, exp_win_columns_get(), TCL_VOLATILE); + return TCL_OK; + } + } else { + saw_unknown_stty_arg = TRUE; + } + } + /* if any unknown args, let real stty try */ + if (saw_unknown_stty_arg || no_args) { + if (saw_unknown_stty_arg) { + exp_ioctled_devtty = TRUE; + } + + /* let real stty try */ + rc = exec_stty(interp,argc,argv0,1); + + /* find out what weird options user asked for */ + if (exp_tty_get_simple(&tty_current) == -1) { + exp_error(interp,"stty: ioctl(get): %s\r\n",Tcl_PosixError(interp)); + rc = TCL_ERROR; + } + if (cooked) { + /* find out user's new defn of 'cooked' */ + tty_cooked = tty_current; + } + } else if (saw_known_stty_arg) { + if (exp_tty_set_simple(&tty_current) == -1) { + if (exp_disconnected || (exp_dev_tty == -1) || !isatty(exp_dev_tty)) { + expErrorLog("stty: impossible in this context\n"); + expErrorLog("are you disconnected or in a batch, at, or cron script?"); + /* user could've conceivably closed /dev/tty as well */ + } + exp_error(interp,"stty: ioctl(user): %s\r\n",Tcl_PosixError(interp)); + rc = TCL_ERROR; + } + } + + /* if no result, make a crude one */ + if (0 == strcmp(Tcl_GetString(Tcl_GetObjResult(interp)),"")) { + char buf [11]; + sprintf(buf,"%sraw %secho", + (was_raw?"":"-"), + (was_echo?"":"-")); + Tcl_SetResult (interp, buf, TCL_VOLATILE); + } + } else { + /* a different tty */ + + /* temporarily zap redirect */ + char *redirect_save = *redirect; + *redirect = 0; + + for (argv=argv0+1;*argv;argv++) { + if (streq(*argv,"rows")) { + if (*(argv+1)) { + exp_win2_rows_set(fd,*(argv+1)); + argv++; + no_args = FALSE; + } else { + Tcl_SetResult (interp, exp_win2_rows_get(fd), TCL_VOLATILE); + goto done; + } + } else if (streq(*argv,"columns")) { + if (*(argv+1)) { + exp_win2_columns_set(fd,*(argv+1)); + argv++; + no_args = FALSE; + } else { + Tcl_SetResult (interp, exp_win2_columns_get(fd), TCL_VOLATILE); + goto done; + } + } else if (streq(*argv,"<")) { + break; + } else { + saw_unknown_stty_arg = TRUE; + break; + } + } + + /* restore redirect */ + *redirect = redirect_save; + + close(fd); /* no more use for this, from now on */ + /* pass by name */ + + if (saw_unknown_stty_arg || no_args) { +#ifdef STTY_READS_STDOUT + /* switch "<" to ">" */ + char original_redirect_char = (*redirect)[0]; + (*redirect)[0] = '>'; + /* stderr unredirected so we can get it directly! */ +#endif + rc = exec_stty(interp,argc,argv0,0); +#ifdef STTY_READS_STDOUT + /* restore redirect - don't know if necessary */ + (*redirect)[0] = original_redirect_char; +#endif + } + } + done: + exp_trap_on(master); + + return rc; +} + +/*ARGSUSED*/ +static int +Exp_SystemCmd( + ClientData clientData, + Tcl_Interp *interp, + int argc, + char **argv) +{ + int result = TCL_OK; + RETSIGTYPE (*old)(); /* save old sigalarm handler */ +#define MAX_ARGLIST 10240 + int i; + + WAIT_STATUS_TYPE waitStatus; + int systemStatus +; + int abnormalExit = FALSE; + char buf[MAX_ARGLIST]; + char *bufp = buf; + int total_len = 0, arg_len; + + int stty_args_recognized = TRUE; + int cmd_is_stty = FALSE; + int cooked = FALSE; + int was_raw, was_echo; + + if (argc == 1) return TCL_OK; + + if (streq(argv[1],"stty")) { + expDiagLogU("system stty is deprecated, use stty\r\n"); + + cmd_is_stty = TRUE; + was_raw = exp_israw(); + was_echo = exp_isecho(); + } + + if (argc > 2 && cmd_is_stty) { + exp_ioctled_devtty = TRUE; + + for (i=2;i<argc;i++) { + if (streq(argv[i],"raw") || + streq(argv[i],"-cooked")) { + exp_tty_raw(1); + } else if (streq(argv[i],"-raw") || + streq(argv[i],"cooked")) { + cooked = TRUE; + exp_tty_raw(-1); + } else if (streq(argv[i],"echo")) { + exp_tty_echo(1); + } else if (streq(argv[i],"-echo")) { + exp_tty_echo(-1); + } else stty_args_recognized = FALSE; + } + + /* if unknown args, fall thru and let real stty have a go */ + if (stty_args_recognized) { + if ( +#ifdef HAVE_TCSETATTR + tcsetattr(exp_dev_tty,TCSADRAIN, &tty_current) == -1 +#else + ioctl(exp_dev_tty, TCSETSW, &tty_current) == -1 +#endif + ) { + if (exp_disconnected || (exp_dev_tty == -1) || !isatty(exp_dev_tty)) { + expErrorLog("system stty: impossible in this context\n"); + expErrorLog("are you disconnected or in a batch, at, or cron script?"); + /* user could've conceivably closed /dev/tty as well */ + } + exp_error(interp,"system stty: ioctl(user): %s\r\n",Tcl_PosixError(interp)); + return(TCL_ERROR); + } + if (cmd_is_stty) { + char buf [11]; + sprintf(buf,"%sraw %secho", + (was_raw?"":"-"), + (was_echo?"":"-")); + Tcl_SetResult (interp, buf, TCL_VOLATILE); + } + return(TCL_OK); + } + } + + for (i = 1;i<argc;i++) { + total_len += (1 + (arg_len = strlen(argv[i]))); + if (total_len > MAX_ARGLIST) { + exp_error(interp,"args too long (>=%d chars)", + total_len); + return(TCL_ERROR); + } + memcpy(bufp,argv[i],arg_len); + bufp += arg_len; + /* no need to check bounds, we accted for it earlier */ + memcpy(bufp," ",1); + bufp += 1; + } + + *(bufp-1) = '\0'; + + old = signal(SIGCHLD, SIG_DFL); + systemStatus = system(buf); + signal(SIGCHLD, old); /* restore signal handler */ + expDiagLogU("system("); + expDiagLogU(buf); + expDiagLog(") = %d\r\n",i); + + if (systemStatus == -1) { + exp_error(interp,Tcl_PosixError(interp)); + return TCL_ERROR; + } + *(int *)&waitStatus = systemStatus; + + if (!stty_args_recognized) { + /* find out what weird options user asked for */ + if ( +#ifdef HAVE_TCSETATTR + tcgetattr(exp_dev_tty, &tty_current) == -1 +#else + ioctl(exp_dev_tty, TCGETS, &tty_current) == -1 +#endif + ) { + expErrorLog("ioctl(get): %s\r\n",Tcl_PosixError(interp)); + + /* SF #439042 -- Allow overide of "exit" by user / script + */ + { + char buffer [] = "exit 1"; + Tcl_Eval(interp, buffer); + } + } + if (cooked) { + /* find out user's new defn of 'cooked' */ + tty_cooked = tty_current; + } + } + + if (cmd_is_stty) { + char buf [11]; + sprintf(buf,"%sraw %secho", + (was_raw?"":"-"), + (was_echo?"":"-")); + Tcl_SetResult (interp, buf, TCL_VOLATILE); + } + +/* following macros stolen from Tcl's tclUnix.h file */ +/* we can't include the whole thing because it depends on other macros */ +/* that come out of Tcl's Makefile, sigh */ + +#if 0 + +#undef WIFEXITED +#ifndef WIFEXITED +# define WIFEXITED(stat) (((*((int *) &(stat))) & 0xff) == 0) +#endif + +#undef WEXITSTATUS +#ifndef WEXITSTATUS +# define WEXITSTATUS(stat) (((*((int *) &(stat))) >> 8) & 0xff) +#endif + +#undef WIFSIGNALED +#ifndef WIFSIGNALED +# define WIFSIGNALED(stat) (((*((int *) &(stat)))) && ((*((int *) &(stat))) == ((*((int *) &(stat))) & 0x00ff))) +#endif + +#undef WTERMSIG +#ifndef WTERMSIG +# define WTERMSIG(stat) ((*((int *) &(stat))) & 0x7f) +#endif + +#undef WIFSTOPPED +#ifndef WIFSTOPPED +# define WIFSTOPPED(stat) (((*((int *) &(stat))) & 0xff) == 0177) +#endif + +#undef WSTOPSIG +#ifndef WSTOPSIG +# define WSTOPSIG(stat) (((*((int *) &(stat))) >> 8) & 0xff) +#endif + +#endif /* 0 */ + +/* stolen from Tcl. Again, this is embedded in another routine */ +/* (CleanupChildren in tclUnixAZ.c) that we can't use directly. */ + + if (!WIFEXITED(waitStatus) || (WEXITSTATUS(waitStatus) != 0)) { + char msg1[20], msg2[20]; + int pid = 0; /* fake a pid, since system() won't tell us */ + + result = TCL_ERROR; + sprintf(msg1, "%d", pid); + if (WIFEXITED(waitStatus)) { + sprintf(msg2, "%d", WEXITSTATUS(waitStatus)); + Tcl_SetErrorCode(interp, "CHILDSTATUS", msg1, msg2, + (char *) NULL); + abnormalExit = TRUE; + } else if (WIFSIGNALED(waitStatus)) { + CONST char *p; + + p = Tcl_SignalMsg((int) (WTERMSIG(waitStatus))); + Tcl_SetErrorCode(interp, "CHILDKILLED", msg1, + Tcl_SignalId((int) (WTERMSIG(waitStatus))), p, + (char *) NULL); + Tcl_AppendResult(interp, "child killed: ", p, "\n", + (char *) NULL); + } else if (WIFSTOPPED(waitStatus)) { + CONST char *p; + + p = Tcl_SignalMsg((int) (WSTOPSIG(waitStatus))); + Tcl_SetErrorCode(interp, "CHILDSUSP", msg1, + Tcl_SignalId((int) (WSTOPSIG(waitStatus))), p, (char *) NULL); + Tcl_AppendResult(interp, "child suspended: ", p, "\n", + (char *) NULL); + } else { + Tcl_AppendResult(interp, + "child wait status didn't make sense\n", + (char *) NULL); + } + } + + if (abnormalExit && (Tcl_GetStringResult (interp)[0] == 0)) { + Tcl_AppendResult(interp, "child process exited abnormally", + (char *) NULL); + } + + return result; +} + +static struct exp_cmd_data +cmd_data[] = { +{"stty", exp_proc(Exp_SttyCmd), 0, 0}, +{"system", exp_proc(Exp_SystemCmd), 0, 0}, +{0}}; + +void +exp_init_tty_cmds(struct Tcl_Interp *interp) +{ + exp_create_commands(interp,cmd_data); +} + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */ |