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"
38 #include "exp_command.h"
40 static int is_raw = FALSE;
41 static int is_noecho = FALSE;
43 int exp_ioctled_devtty = FALSE;
45 int exp_stdout_is_tty;
47 /*static*/ extern exp_tty exp_tty_current, exp_tty_cooked;
48 #define tty_current exp_tty_current
49 #define tty_cooked exp_tty_cooked
63 /* 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;
102 #if defined(HAVE_TERMIOS) || defined(HAVE_TERMIO) /* had POSIX too */
103 tty_current.c_lflag |= ECHO;
105 tty_current.c_lflag &= ~ECHO;
107 tty_current.sg_flags |= ECHO;
109 tty_current.sg_flags &= ~ECHO;
116 exp_tty_set_simple(tty)
119 #ifdef HAVE_TCSETATTR
120 return(tcsetattr(exp_dev_tty, TCSADRAIN,tty));
122 return(ioctl (exp_dev_tty, TCSETSW ,tty));
127 exp_tty_get_simple(tty)
130 #ifdef HAVE_TCSETATTR
131 return(tcgetattr(exp_dev_tty, tty));
133 return(ioctl (exp_dev_tty, TCGETS, tty));
137 /* returns 0 if nothing changed */
138 /* if something changed, the out parameters are changed as well */
140 exp_tty_raw_noecho(interp,tty_old,was_raw,was_echo)
143 int *was_raw, *was_echo;
145 if (exp_disconnected) return(0);
146 if (is_raw && is_noecho) return(0);
147 if (exp_dev_tty == -1) return(0);
149 *tty_old = tty_current; /* save old parameters */
151 *was_echo = !is_noecho;
152 debuglog("tty_raw_noecho: was raw = %d echo = %d\r\n",is_raw,!is_noecho);
157 if (exp_tty_set_simple(&tty_current) == -1) {
158 errorlog("ioctl(raw): %s\r\n",Tcl_PosixError(interp));
162 exp_ioctled_devtty = TRUE;
166 /* returns 0 if nothing changed */
167 /* if something changed, the out parameters are changed as well */
169 exp_tty_cooked_echo(interp,tty_old,was_raw,was_echo)
172 int *was_raw, *was_echo;
174 if (exp_disconnected) return(0);
175 if (!is_raw && !is_noecho) return(0);
176 if (exp_dev_tty == -1) return(0);
178 *tty_old = tty_current; /* save old parameters */
180 *was_echo = !is_noecho;
181 debuglog("tty_cooked_echo: was raw = %d echo = %d\r\n",is_raw,!is_noecho);
186 if (exp_tty_set_simple(&tty_current) == -1) {
187 errorlog("ioctl(noraw): %s\r\n",Tcl_PosixError(interp));
190 exp_ioctled_devtty = TRUE;
196 exp_tty_set(interp,tty,raw,echo)
202 if (exp_tty_set_simple(tty) == -1) {
203 errorlog("ioctl(set): %s\r\n",Tcl_PosixError(interp));
209 debuglog("tty_set: raw = %d, echo = %d\r\n",is_raw,!is_noecho);
210 exp_ioctled_devtty = TRUE;
214 /* avoids scoping problems */
216 exp_update_cooked_from_current() {
217 tty_cooked = tty_current;
221 exp_update_real_tty_from_current() {
222 return(exp_tty_set_simple(&tty_current));
226 exp_update_current_from_real_tty() {
227 return(exp_tty_get_simple(&tty_current));
234 exp_stdin_is_tty = isatty(0);
235 exp_stdout_is_tty = isatty(1);
237 setbuf(stdout,(char *)0); /* unbuffer stdout */
242 exp_tty_break(interp,fd)
250 ioctl(fd,TIOCSBRK,0);
251 exp_dsleep(interp,0.25); /* sleep for at least a quarter of a second */
252 ioctl(fd,TIOCCBRK,0);
254 /* dunno how to do this - ignore */
259 /* take strings with newlines and insert carriage-returns. This allows user */
260 /* to write send_user strings without always putting in \r. */
261 /* If len == 0, use strlen to compute it */
262 /* NB: if terminal is not in raw mode, nothing is done. */
266 int *len; /* current and new length of s */
268 static int destlen = 0;
269 static char *dest = 0;
270 char *d; /* ptr into dest */
273 if (s == 0) return("<null>");
275 if (!is_raw) return(s);
277 /* worst case is every character takes 2 to represent */
278 need = 1 + 2*(len?*len:strlen(s));
279 if (need > destlen) {
280 if (dest) ckfree(dest);
281 dest = ckalloc(need);
285 for (d = dest;*s;s++) {
294 if (len) *len = d-dest;
298 /* this stupidity because Tcl needs commands in writable space */
299 static char exec_cmd[] = "exec";
300 static char stty_cmd[] = "/bin/stty";
302 static int /* returns TCL_whatever */
303 exec_stty(interp,argc,argv,devtty)
307 int devtty; /* if true, redirect to /dev/tty */
313 /* insert "system" at front, null at end, */
314 /* and optional redirect in middle, hence "+3" */
315 new_argv = (char **)ckalloc((3+argc)*sizeof(char *));
316 new_argv[0] = exec_cmd;
317 new_argv[1] = stty_cmd;
318 for (i=1;i<argc;i++) {
319 new_argv[i+1] = argv[i];
321 if (devtty) new_argv[++i] =
322 #ifdef STTY_READS_STDOUT
328 new_argv[i+1] = (char *)0;
330 Tcl_ResetResult(interp);
332 /* normally, I wouldn't set one of Tcl's own variables, but in this */
333 /* case, I only only want to see if Tcl resets it to non-NONE, */
334 /* and I don't know any other way of doing it */
335 Tcl_SetVar(interp,"errorCode","NONE",0);
337 #if (TCL_MAJOR_VERSION == 8) && (TCL_MINOR_VERSION < 3)
338 rc = Tcl_ExecCmd((ClientData)0,interp,argc+1+devtty,new_argv);
340 rc = Tcl_ExecObjCmd((ClientData)0,interp,argc+1+devtty,Tcl_NewStringObj(new_argv,-1));
342 ckfree((char *)new_argv);
344 /* if stty-reads-stdout, stty will fail since Exec */
345 /* will detect the stderr. Only by examining errorCode */
346 /* can we tell if a real error occurred. */
348 #ifdef STTY_READS_STDOUT
349 if (rc == TCL_ERROR) {
350 char *ec = Tcl_GetVar(interp,"errorCode",TCL_GLOBAL_ONLY);
351 if (ec && !streq(ec,"NONE")) return TCL_ERROR;
359 Exp_SttyCmd(clientData, interp, argc, argv)
360 ClientData clientData;
365 /* redirection symbol is not counted as a stty arg in terms */
366 /* of recognition. */
367 int saw_unknown_stty_arg = FALSE;
368 int saw_known_stty_arg = FALSE;
373 int was_raw, was_echo;
376 char **redirect; /* location of "<" */
378 int fd; /* (slave) fd of infile */
379 int master = -1; /* master fd of infile */
382 for (argv=argv0+1;*argv;argv++) {
383 if (argv[0][0] == '<') {
387 errorlog("usage: < ttyname");
390 if (streq(infile,"/dev/tty")) {
396 master = exp_trap_off(infile);
397 if (-1 == (fd = open(infile,2))) {
398 errorlog("couldn't open %s: %s",
399 infile,Tcl_PosixError(interp));
407 if (!infile) { /* work on /dev/tty */
408 was_raw = exp_israw();
409 was_echo = exp_isecho();
411 exp_ioctled_devtty = TRUE;
413 for (argv=argv0+1;*argv;argv++) {
414 if (streq(*argv,"raw") ||
415 streq(*argv,"-cooked")) {
417 saw_known_stty_arg = TRUE;
419 } else if (streq(*argv,"-raw") ||
420 streq(*argv,"cooked")) {
423 saw_known_stty_arg = TRUE;
425 } else if (streq(*argv,"echo")) {
427 saw_known_stty_arg = TRUE;
429 } else if (streq(*argv,"-echo")) {
431 saw_known_stty_arg = TRUE;
433 } else if (streq(*argv,"rows")) {
435 exp_win_rows_set(*(argv+1));
439 exp_win_rows_get(interp->result);
442 } else if (streq(*argv,"columns")) {
444 exp_win_columns_set(*(argv+1));
448 exp_win_columns_get(interp->result);
452 saw_unknown_stty_arg = TRUE;
455 /* if any unknown args, let real stty try */
456 if (saw_unknown_stty_arg || no_args) {
457 /* let real stty try */
458 rc = exec_stty(interp,argc,argv0,1);
460 /* find out what weird options user asked for */
461 if (exp_tty_get_simple(&tty_current) == -1) {
462 exp_error(interp,"stty: ioctl(get): %s\r\n",Tcl_PosixError(interp));
466 /* find out user's new defn of 'cooked' */
467 tty_cooked = tty_current;
469 } else if (saw_known_stty_arg) {
470 if (exp_tty_set_simple(&tty_current) == -1) {
471 if (exp_disconnected || (exp_dev_tty == -1) || !isatty(exp_dev_tty)) {
472 errorlog("stty: impossible in this context\n");
473 errorlog("are you disconnected or in a batch, at, or cron script?");
474 /* user could've conceivably closed /dev/tty as well */
476 exp_error(interp,"stty: ioctl(user): %s\r\n",Tcl_PosixError(interp));
481 /* if no result, make a crude one */
482 if (interp->result[0] == '\0') {
483 sprintf(interp->result,"%sraw %secho",
488 /* a different tty */
490 /* temporarily zap redirect */
491 char *redirect_save = *redirect;
494 for (argv=argv0+1;*argv;argv++) {
495 if (streq(*argv,"rows")) {
497 exp_win2_rows_set(fd,*(argv+1));
501 exp_win2_rows_get(fd,interp->result);
504 } else if (streq(*argv,"columns")) {
506 exp_win2_columns_set(fd,*(argv+1));
510 exp_win2_columns_get(fd,interp->result);
513 } else if (streq(*argv,"<")) {
516 saw_unknown_stty_arg = TRUE;
521 /* restore redirect */
522 *redirect = redirect_save;
524 close(fd); /* no more use for this, from now on */
527 if (saw_unknown_stty_arg || no_args) {
528 #ifdef STTY_READS_STDOUT
529 /* switch "<" to ">" */
530 char original_redirect_char = (*redirect)[0];
531 (*redirect)[0] = '>';
532 /* stderr unredirected so we can get it directly! */
534 rc = exec_stty(interp,argc,argv0,0);
535 #ifdef STTY_READS_STDOUT
536 /* restore redirect - don't know if necessary */
537 (*redirect)[0] = original_redirect_char;
549 Exp_SystemCmd(clientData, interp, argc, argv)
550 ClientData clientData;
556 RETSIGTYPE (*old)(); /* save old sigalarm handler */
557 #define MAX_ARGLIST 10240
560 WAIT_STATUS_TYPE waitStatus;
563 int abnormalExit = FALSE;
564 char buf[MAX_ARGLIST];
566 int total_len = 0, arg_len;
568 int stty_args_recognized = TRUE;
569 int cmd_is_stty = FALSE;
571 int was_raw, was_echo;
573 if (argc == 1) return TCL_OK;
575 if (streq(argv[1],"stty")) {
576 debuglog("system stty is deprecated, use stty\r\n");
579 was_raw = exp_israw();
580 was_echo = exp_isecho();
583 if (argc > 2 && cmd_is_stty) {
584 exp_ioctled_devtty = TRUE;
586 for (i=2;i<argc;i++) {
587 if (streq(argv[i],"raw") ||
588 streq(argv[i],"-cooked")) {
590 } else if (streq(argv[i],"-raw") ||
591 streq(argv[i],"cooked")) {
594 } else if (streq(argv[i],"echo")) {
596 } else if (streq(argv[i],"-echo")) {
598 } else stty_args_recognized = FALSE;
601 /* if unknown args, fall thru and let real stty have a go */
602 if (stty_args_recognized) {
603 #ifdef HAVE_TCSETATTR
604 if (tcsetattr(exp_dev_tty,TCSADRAIN, &tty_current) == -1) {
606 if (ioctl(exp_dev_tty, TCSETSW, &tty_current) == -1) {
608 if (exp_disconnected || (exp_dev_tty == -1) || !isatty(exp_dev_tty)) {
609 errorlog("system stty: impossible in this context\n");
610 errorlog("are you disconnected or in a batch, at, or cron script?");
611 /* user could've conceivably closed /dev/tty as well */
613 exp_error(interp,"system stty: ioctl(user): %s\r\n",Tcl_PosixError(interp));
617 sprintf(interp->result,"%sraw %secho",
625 for (i = 1;i<argc;i++) {
626 total_len += (1 + (arg_len = strlen(argv[i])));
627 if (total_len > MAX_ARGLIST) {
628 exp_error(interp,"args too long (>=%d chars)",
632 memcpy(bufp,argv[i],arg_len);
634 /* no need to check bounds, we accted for it earlier */
640 old = signal(SIGCHLD, SIG_DFL);
641 systemStatus = system(buf);
642 signal(SIGCHLD, old); /* restore signal handler */
643 debuglog("system(%s) = %d\r\n",buf,i);
645 if (systemStatus == -1) {
646 exp_error(interp,Tcl_PosixError(interp));
649 *(int *)&waitStatus = systemStatus;
651 if (!stty_args_recognized) {
652 /* find out what weird options user asked for */
653 #ifdef HAVE_TCSETATTR
654 if (tcgetattr(exp_dev_tty, &tty_current) == -1) {
656 if (ioctl(exp_dev_tty, TCGETS, &tty_current) == -1) {
658 errorlog("ioctl(get): %s\r\n",Tcl_PosixError(interp));
662 /* find out user's new defn of 'cooked' */
663 tty_cooked = tty_current;
668 sprintf(interp->result,"%sraw %secho",
673 /* following macros stolen from Tcl's tclUnix.h file */
674 /* we can't include the whole thing because it depends on other macros */
675 /* that come out of Tcl's Makefile, sigh */
681 # define WIFEXITED(stat) (((*((int *) &(stat))) & 0xff) == 0)
686 # define WEXITSTATUS(stat) (((*((int *) &(stat))) >> 8) & 0xff)
691 # define WIFSIGNALED(stat) (((*((int *) &(stat)))) && ((*((int *) &(stat))) == ((*((int *) &(stat))) & 0x00ff)))
696 # define WTERMSIG(stat) ((*((int *) &(stat))) & 0x7f)
701 # define WIFSTOPPED(stat) (((*((int *) &(stat))) & 0xff) == 0177)
706 # define WSTOPSIG(stat) (((*((int *) &(stat))) >> 8) & 0xff)
711 /* stolen from Tcl. Again, this is embedded in another routine */
712 /* (CleanupChildren in tclUnixAZ.c) that we can't use directly. */
714 if (!WIFEXITED(waitStatus) || (WEXITSTATUS(waitStatus) != 0)) {
715 char msg1[20], msg2[20];
716 int pid = 0; /* fake a pid, since system() won't tell us */
719 sprintf(msg1, "%d", pid);
720 if (WIFEXITED(waitStatus)) {
721 sprintf(msg2, "%d", WEXITSTATUS(waitStatus));
722 Tcl_SetErrorCode(interp, "CHILDSTATUS", msg1, msg2,
725 } else if (WIFSIGNALED(waitStatus)) {
728 p = Tcl_SignalMsg((int) (WTERMSIG(waitStatus)));
729 Tcl_SetErrorCode(interp, "CHILDKILLED", msg1,
730 Tcl_SignalId((int) (WTERMSIG(waitStatus))), p,
732 Tcl_AppendResult(interp, "child killed: ", p, "\n",
734 } else if (WIFSTOPPED(waitStatus)) {
737 p = Tcl_SignalMsg((int) (WSTOPSIG(waitStatus)));
738 Tcl_SetErrorCode(interp, "CHILDSUSP", msg1,
739 Tcl_SignalId((int) (WSTOPSIG(waitStatus))), p, (char *) NULL);
740 Tcl_AppendResult(interp, "child suspended: ", p, "\n",
743 Tcl_AppendResult(interp,
744 "child wait status didn't make sense\n",
749 if (abnormalExit && (*interp->result == 0)) {
750 Tcl_AppendResult(interp, "child process exited abnormally",
757 static struct exp_cmd_data
759 {"stty", exp_proc(Exp_SttyCmd), 0, 0},
760 {"system", exp_proc(Exp_SystemCmd), 0, 0},
764 exp_init_tty_cmds(interp)
765 struct Tcl_Interp *interp;
767 exp_create_commands(interp,cmd_data);