1 /* exp_tty.c - tty support routines */
8 #ifdef HAVE_SYS_FCNTL_H
9 # include <sys/fcntl.h>
16 #ifdef HAVE_INTTYPES_H
17 # include <inttypes.h>
19 #include <sys/types.h>
25 #ifdef HAVE_SYS_WAIT_H
29 #if defined(SIGCLD) && !defined(SIGCHLD)
30 #define SIGCHLD SIGCLD
35 #include "exp_rename.h"
36 #include "exp_tty_in.h"
37 #include "exp_command.h"
41 static int is_raw = FALSE;
42 static int is_noecho = FALSE;
44 int exp_ioctled_devtty = FALSE;
46 int exp_stdout_is_tty;
48 /*static*/ extern exp_tty exp_tty_current, exp_tty_cooked;
49 #define tty_current exp_tty_current
50 #define tty_cooked exp_tty_cooked
64 /* if set == 1, set it to raw, else unset it */
70 #if defined(HAVE_TERMIOS) || defined(HAVE_TERMIO) /* had POSIX too */
71 tty_current.c_iflag = 0;
72 tty_current.c_oflag = 0;
73 tty_current.c_lflag &= ECHO; /* disable everything but echo */
74 tty_current.c_cc[VMIN] = 1;
75 tty_current.c_cc[VTIME] = 0;
77 tty_current.c_iflag = tty_cooked.c_iflag;
78 tty_current.c_oflag = tty_cooked.c_oflag;
79 /* tty_current.c_lflag = tty_cooked.c_lflag;*/
80 /* attempt 2 tty_current.c_lflag = tty_cooked.c_lflag & ~ECHO;*/
81 /* retain current echo setting */
82 tty_current.c_lflag = (tty_cooked.c_lflag & ~ECHO) | (tty_current.c_lflag & ECHO);
83 tty_current.c_cc[VMIN] = tty_cooked.c_cc[VMIN];
84 tty_current.c_cc[VTIME] = tty_cooked.c_cc[VTIME];
86 # if defined(HAVE_SGTTYB)
87 tty_current.sg_flags |= RAW;
89 tty_current.sg_flags = tty_cooked.sg_flags;
101 #if defined(HAVE_TERMIOS) || defined(HAVE_TERMIO) /* had POSIX too */
102 tty_current.c_lflag |= ECHO;
104 tty_current.c_lflag &= ~ECHO;
106 tty_current.sg_flags |= ECHO;
108 tty_current.sg_flags &= ~ECHO;
115 exp_tty_set_simple(exp_tty *tty)
117 #ifdef HAVE_TCSETATTR
118 return(tcsetattr(exp_dev_tty, TCSADRAIN,tty));
120 return(ioctl (exp_dev_tty, TCSETSW ,tty));
125 exp_tty_get_simple(exp_tty *tty)
127 #ifdef HAVE_TCSETATTR
128 return(tcgetattr(exp_dev_tty, tty));
130 return(ioctl (exp_dev_tty, TCGETS, tty));
134 /* returns 0 if nothing changed */
135 /* if something changed, the out parameters are changed as well */
143 if (exp_disconnected) return(0);
144 if (is_raw && is_noecho) return(0);
145 if (exp_dev_tty == -1) return(0);
147 *tty_old = tty_current; /* save old parameters */
149 *was_echo = !is_noecho;
150 expDiagLog("tty_raw_noecho: was raw = %d echo = %d\r\n",is_raw,!is_noecho);
155 if (exp_tty_set_simple(&tty_current) == -1) {
156 expErrorLog("ioctl(raw): %s\r\n",Tcl_PosixError(interp));
158 /* SF #439042 -- Allow overide of "exit" by user / script
161 char buffer [] = "exit 1";
162 Tcl_Eval(interp, buffer);
166 exp_ioctled_devtty = TRUE;
170 /* returns 0 if nothing changed */
171 /* if something changed, the out parameters are changed as well */
179 if (exp_disconnected) return(0);
180 if (!is_raw && !is_noecho) return(0);
181 if (exp_dev_tty == -1) return(0);
183 *tty_old = tty_current; /* save old parameters */
185 *was_echo = !is_noecho;
186 expDiagLog("tty_cooked_echo: was raw = %d echo = %d\r\n",is_raw,!is_noecho);
191 if (exp_tty_set_simple(&tty_current) == -1) {
192 expErrorLog("ioctl(noraw): %s\r\n",Tcl_PosixError(interp));
194 /* SF #439042 -- Allow overide of "exit" by user / script
197 char buffer [] = "exit 1";
198 Tcl_Eval(interp, buffer);
201 exp_ioctled_devtty = TRUE;
213 if (exp_tty_set_simple(tty) == -1) {
214 expErrorLog("ioctl(set): %s\r\n",Tcl_PosixError(interp));
216 /* SF #439042 -- Allow overide of "exit" by user / script
219 char buffer [] = "exit 1";
220 Tcl_Eval(interp, buffer);
226 expDiagLog("tty_set: raw = %d, echo = %d\r\n",is_raw,!is_noecho);
227 exp_ioctled_devtty = TRUE;
231 /* avoids scoping problems */
233 exp_update_cooked_from_current() {
234 tty_cooked = tty_current;
238 exp_update_real_tty_from_current() {
239 return(exp_tty_set_simple(&tty_current));
243 exp_update_current_from_real_tty() {
244 return(exp_tty_get_simple(&tty_current));
251 exp_stdin_is_tty = isatty(0);
252 exp_stdout_is_tty = isatty(1);
254 setbuf(stdout,(char *)0); /* unbuffer stdout */
267 ioctl(fd,TIOCSBRK,0);
268 exp_dsleep(interp,0.25); /* sleep for at least a quarter of a second */
269 ioctl(fd,TIOCCBRK,0);
271 /* dunno how to do this - ignore */
276 /* take strings with newlines and insert carriage-returns. This allows user */
277 /* to write send_user strings without always putting in \r. */
278 /* If len == 0, use strlen to compute it */
279 /* NB: if terminal is not in raw mode, nothing is done. */
283 int *len) /* current and new length of s */
285 static int destlen = 0;
286 static char *dest = 0;
287 char *d; /* ptr into dest */
290 if (s == 0) return("<null>");
292 if (!is_raw) return(s);
294 /* worst case is every character takes 2 to represent */
295 need = 1 + 2*(len?*len:strlen(s));
296 if (need > destlen) {
297 if (dest) ckfree(dest);
298 dest = ckalloc(need);
302 for (d = dest;*s;s++) {
311 if (len) *len = d-dest;
315 static int /* returns TCL_whatever */
320 int devtty) /* if true, redirect to /dev/tty */
325 Tcl_Obj *cmdObj = Tcl_NewStringObj("",0);
326 Tcl_IncrRefCount(cmdObj);
328 Tcl_AppendStringsToObj(cmdObj,"exec ",(char *)0);
329 Tcl_AppendStringsToObj(cmdObj,STTY_BIN,(char *)0);
330 for (i=1;i<argc;i++) {
331 Tcl_AppendStringsToObj(cmdObj," ",argv[i],(char *)0);
333 if (devtty) Tcl_AppendStringsToObj(cmdObj,
334 #ifdef STTY_READS_STDOUT
341 Tcl_ResetResult(interp);
344 * normally, I wouldn't set one of Tcl's own variables, but in this
345 * case, I only want to see if Tcl resets it to non-NONE, and I don't
346 * know any other way of doing it
349 Tcl_SetVar(interp,"errorCode","NONE",0);
350 rc = Tcl_EvalObjEx(interp,cmdObj,TCL_EVAL_DIRECT);
352 Tcl_DecrRefCount(cmdObj);
354 /* if stty-reads-stdout, stty will fail since Exec */
355 /* will detect the stderr. Only by examining errorCode */
356 /* can we tell if a real error occurred. */
358 #ifdef STTY_READS_STDOUT
359 if (rc == TCL_ERROR) {
360 char *ec = Tcl_GetVar(interp,"errorCode",TCL_GLOBAL_ONLY);
361 if (ec && !streq(ec,"NONE")) return TCL_ERROR;
370 ClientData clientData,
375 /* redirection symbol is not counted as a stty arg in terms */
376 /* of recognition. */
377 int saw_unknown_stty_arg = FALSE;
378 int saw_known_stty_arg = FALSE;
383 int was_raw, was_echo;
385 char **redirect; /* location of "<" */
387 int fd; /* (slave) fd of infile */
388 int master = -1; /* master fd of infile */
391 for (argv=argv0+1;*argv;argv++) {
392 if (argv[0][0] == '<') {
396 expErrorLog("usage: < ttyname");
399 if (streq(infile,"/dev/tty")) {
405 master = exp_trap_off(infile);
406 if (-1 == (fd = open(infile,2))) {
407 expErrorLog("couldn't open %s: %s",
408 infile,Tcl_PosixError(interp));
416 if (!infile) { /* work on /dev/tty */
417 was_raw = exp_israw();
418 was_echo = exp_isecho();
420 for (argv=argv0+1;*argv;argv++) {
421 if (streq(*argv,"raw") ||
422 streq(*argv,"-cooked")) {
424 saw_known_stty_arg = TRUE;
426 exp_ioctled_devtty = TRUE;
427 } else if (streq(*argv,"-raw") ||
428 streq(*argv,"cooked")) {
431 saw_known_stty_arg = TRUE;
433 exp_ioctled_devtty = TRUE;
434 } else if (streq(*argv,"echo")) {
436 saw_known_stty_arg = TRUE;
438 exp_ioctled_devtty = TRUE;
439 } else if (streq(*argv,"-echo")) {
441 saw_known_stty_arg = TRUE;
443 exp_ioctled_devtty = TRUE;
444 } else if (streq(*argv,"rows")) {
446 exp_win_rows_set(*(argv+1));
449 exp_ioctled_devtty = TRUE;
451 Tcl_SetResult (interp, exp_win_rows_get(), TCL_VOLATILE);
454 } else if (streq(*argv,"columns")) {
456 exp_win_columns_set(*(argv+1));
459 exp_ioctled_devtty = TRUE;
461 Tcl_SetResult (interp, exp_win_columns_get(), TCL_VOLATILE);
465 saw_unknown_stty_arg = TRUE;
468 /* if any unknown args, let real stty try */
469 if (saw_unknown_stty_arg || no_args) {
470 if (saw_unknown_stty_arg) {
471 exp_ioctled_devtty = TRUE;
474 /* let real stty try */
475 rc = exec_stty(interp,argc,argv0,1);
477 /* find out what weird options user asked for */
478 if (exp_tty_get_simple(&tty_current) == -1) {
479 exp_error(interp,"stty: ioctl(get): %s\r\n",Tcl_PosixError(interp));
483 /* find out user's new defn of 'cooked' */
484 tty_cooked = tty_current;
486 } else if (saw_known_stty_arg) {
487 if (exp_tty_set_simple(&tty_current) == -1) {
488 if (exp_disconnected || (exp_dev_tty == -1) || !isatty(exp_dev_tty)) {
489 expErrorLog("stty: impossible in this context\n");
490 expErrorLog("are you disconnected or in a batch, at, or cron script?");
491 /* user could've conceivably closed /dev/tty as well */
493 exp_error(interp,"stty: ioctl(user): %s\r\n",Tcl_PosixError(interp));
498 /* if no result, make a crude one */
499 if (0 == strcmp(Tcl_GetString(Tcl_GetObjResult(interp)),"")) {
501 sprintf(buf,"%sraw %secho",
504 Tcl_SetResult (interp, buf, TCL_VOLATILE);
507 /* a different tty */
509 /* temporarily zap redirect */
510 char *redirect_save = *redirect;
513 for (argv=argv0+1;*argv;argv++) {
514 if (streq(*argv,"rows")) {
516 exp_win2_rows_set(fd,*(argv+1));
520 Tcl_SetResult (interp, exp_win2_rows_get(fd), TCL_VOLATILE);
523 } else if (streq(*argv,"columns")) {
525 exp_win2_columns_set(fd,*(argv+1));
529 Tcl_SetResult (interp, exp_win2_columns_get(fd), TCL_VOLATILE);
532 } else if (streq(*argv,"<")) {
535 saw_unknown_stty_arg = TRUE;
540 /* restore redirect */
541 *redirect = redirect_save;
543 close(fd); /* no more use for this, from now on */
546 if (saw_unknown_stty_arg || no_args) {
547 #ifdef STTY_READS_STDOUT
548 /* switch "<" to ">" */
549 char original_redirect_char = (*redirect)[0];
550 (*redirect)[0] = '>';
551 /* stderr unredirected so we can get it directly! */
553 rc = exec_stty(interp,argc,argv0,0);
554 #ifdef STTY_READS_STDOUT
555 /* restore redirect - don't know if necessary */
556 (*redirect)[0] = original_redirect_char;
569 ClientData clientData,
575 RETSIGTYPE (*old)(); /* save old sigalarm handler */
576 #define MAX_ARGLIST 10240
579 WAIT_STATUS_TYPE waitStatus;
582 int abnormalExit = FALSE;
583 char buf[MAX_ARGLIST];
585 int total_len = 0, arg_len;
587 int stty_args_recognized = TRUE;
588 int cmd_is_stty = FALSE;
590 int was_raw, was_echo;
592 if (argc == 1) return TCL_OK;
594 if (streq(argv[1],"stty")) {
595 expDiagLogU("system stty is deprecated, use stty\r\n");
598 was_raw = exp_israw();
599 was_echo = exp_isecho();
602 if (argc > 2 && cmd_is_stty) {
603 exp_ioctled_devtty = TRUE;
605 for (i=2;i<argc;i++) {
606 if (streq(argv[i],"raw") ||
607 streq(argv[i],"-cooked")) {
609 } else if (streq(argv[i],"-raw") ||
610 streq(argv[i],"cooked")) {
613 } else if (streq(argv[i],"echo")) {
615 } else if (streq(argv[i],"-echo")) {
617 } else stty_args_recognized = FALSE;
620 /* if unknown args, fall thru and let real stty have a go */
621 if (stty_args_recognized) {
623 #ifdef HAVE_TCSETATTR
624 tcsetattr(exp_dev_tty,TCSADRAIN, &tty_current) == -1
626 ioctl(exp_dev_tty, TCSETSW, &tty_current) == -1
629 if (exp_disconnected || (exp_dev_tty == -1) || !isatty(exp_dev_tty)) {
630 expErrorLog("system stty: impossible in this context\n");
631 expErrorLog("are you disconnected or in a batch, at, or cron script?");
632 /* user could've conceivably closed /dev/tty as well */
634 exp_error(interp,"system stty: ioctl(user): %s\r\n",Tcl_PosixError(interp));
639 sprintf(buf,"%sraw %secho",
642 Tcl_SetResult (interp, buf, TCL_VOLATILE);
648 for (i = 1;i<argc;i++) {
649 total_len += (1 + (arg_len = strlen(argv[i])));
650 if (total_len > MAX_ARGLIST) {
651 exp_error(interp,"args too long (>=%d chars)",
655 memcpy(bufp,argv[i],arg_len);
657 /* no need to check bounds, we accted for it earlier */
664 old = signal(SIGCHLD, SIG_DFL);
665 systemStatus = system(buf);
666 signal(SIGCHLD, old); /* restore signal handler */
667 expDiagLogU("system(");
669 expDiagLog(") = %d\r\n",i);
671 if (systemStatus == -1) {
672 exp_error(interp,Tcl_PosixError(interp));
675 *(int *)&waitStatus = systemStatus;
677 if (!stty_args_recognized) {
678 /* find out what weird options user asked for */
680 #ifdef HAVE_TCSETATTR
681 tcgetattr(exp_dev_tty, &tty_current) == -1
683 ioctl(exp_dev_tty, TCGETS, &tty_current) == -1
686 expErrorLog("ioctl(get): %s\r\n",Tcl_PosixError(interp));
688 /* SF #439042 -- Allow overide of "exit" by user / script
691 char buffer [] = "exit 1";
692 Tcl_Eval(interp, buffer);
696 /* find out user's new defn of 'cooked' */
697 tty_cooked = tty_current;
703 sprintf(buf,"%sraw %secho",
706 Tcl_SetResult (interp, buf, TCL_VOLATILE);
709 /* following macros stolen from Tcl's tclUnix.h file */
710 /* we can't include the whole thing because it depends on other macros */
711 /* that come out of Tcl's Makefile, sigh */
717 # define WIFEXITED(stat) (((*((int *) &(stat))) & 0xff) == 0)
722 # define WEXITSTATUS(stat) (((*((int *) &(stat))) >> 8) & 0xff)
727 # define WIFSIGNALED(stat) (((*((int *) &(stat)))) && ((*((int *) &(stat))) == ((*((int *) &(stat))) & 0x00ff)))
732 # define WTERMSIG(stat) ((*((int *) &(stat))) & 0x7f)
737 # define WIFSTOPPED(stat) (((*((int *) &(stat))) & 0xff) == 0177)
742 # define WSTOPSIG(stat) (((*((int *) &(stat))) >> 8) & 0xff)
747 /* stolen from Tcl. Again, this is embedded in another routine */
748 /* (CleanupChildren in tclUnixAZ.c) that we can't use directly. */
750 if (!WIFEXITED(waitStatus) || (WEXITSTATUS(waitStatus) != 0)) {
751 char msg1[20], msg2[20];
752 int pid = 0; /* fake a pid, since system() won't tell us */
755 sprintf(msg1, "%d", pid);
756 if (WIFEXITED(waitStatus)) {
757 sprintf(msg2, "%d", WEXITSTATUS(waitStatus));
758 Tcl_SetErrorCode(interp, "CHILDSTATUS", msg1, msg2,
761 } else if (WIFSIGNALED(waitStatus)) {
764 p = Tcl_SignalMsg((int) (WTERMSIG(waitStatus)));
765 Tcl_SetErrorCode(interp, "CHILDKILLED", msg1,
766 Tcl_SignalId((int) (WTERMSIG(waitStatus))), p,
768 Tcl_AppendResult(interp, "child killed: ", p, "\n",
770 } else if (WIFSTOPPED(waitStatus)) {
773 p = Tcl_SignalMsg((int) (WSTOPSIG(waitStatus)));
774 Tcl_SetErrorCode(interp, "CHILDSUSP", msg1,
775 Tcl_SignalId((int) (WSTOPSIG(waitStatus))), p, (char *) NULL);
776 Tcl_AppendResult(interp, "child suspended: ", p, "\n",
779 Tcl_AppendResult(interp,
780 "child wait status didn't make sense\n",
785 if (abnormalExit && (Tcl_GetStringResult (interp)[0] == 0)) {
786 Tcl_AppendResult(interp, "child process exited abnormally",
793 static struct exp_cmd_data
795 {"stty", exp_proc(Exp_SttyCmd), 0, 0},
796 {"system", exp_proc(Exp_SystemCmd), 0, 0},
800 exp_init_tty_cmds(struct Tcl_Interp *interp)
802 exp_create_commands(interp,cmd_data);