summaryrefslogtreecommitdiffstats
path: root/unix/tclUnixChan.c
diff options
context:
space:
mode:
authordkf <donal.k.fellows@manchester.ac.uk>2019-03-24 22:08:26 (GMT)
committerdkf <donal.k.fellows@manchester.ac.uk>2019-03-24 22:08:26 (GMT)
commit4701c749af472143bba603d903ea764623614f94 (patch)
tree45feb9ad3b3da593f5f53402185271c56900d7d6 /unix/tclUnixChan.c
parent8d4ff7c84a4acdc37c023fc436509c263f9455dc (diff)
downloadtcl-4701c749af472143bba603d903ea764623614f94.zip
tcl-4701c749af472143bba603d903ea764623614f94.tar.gz
tcl-4701c749af472143bba603d903ea764623614f94.tar.bz2
Better handling, reset capabilty, and ensure that inherited channels are correct
Diffstat (limited to 'unix/tclUnixChan.c')
-rw-r--r--unix/tclUnixChan.c121
1 files changed, 91 insertions, 30 deletions
diff --git a/unix/tclUnixChan.c b/unix/tclUnixChan.c
index cede011..605e317 100644
--- a/unix/tclUnixChan.c
+++ b/unix/tclUnixChan.c
@@ -82,8 +82,14 @@ typedef struct {
typedef struct {
FileState fileState;
+#ifdef SUPPORTS_TTY
int closeMode; /* One of CLOSE_DEFAULT, CLOSE_DRAIN or
* CLOSE_DISCARD. */
+ int doReset; /* Whether we should do a terminal reset on
+ * close. */
+ struct termios initState; /* The state of the terminal when it was
+ * opened. */
+#endif /* SUPPORTS_TTY */
} TtyState;
#ifdef SUPPORTS_TTY
@@ -100,7 +106,7 @@ typedef struct {
int stop;
} TtyAttrs;
-#endif /* !SUPPORTS_TTY */
+#endif /* SUPPORTS_TTY */
#define UNSUPPORTED_OPTION(detail) \
if (interp) { \
@@ -374,18 +380,18 @@ TtyCloseProc(
ClientData instanceData,
Tcl_Interp *interp)
{
- TtyState *ttyState = instanceData;
+ TtyState *ttyPtr = instanceData;
/*
* If we've been asked by the user to drain or flush, do so now.
*/
- switch (ttyState->closeMode) {
+ switch (ttyPtr->closeMode) {
case CLOSE_DRAIN:
- tcdrain(ttyState->fileState.fd);
+ tcdrain(ttyPtr->fileState.fd);
break;
case CLOSE_DISCARD:
- tcflush(ttyState->fileState.fd, TCIOFLUSH);
+ tcflush(ttyPtr->fileState.fd, TCIOFLUSH);
break;
default:
/* Do nothing */
@@ -393,6 +399,14 @@ TtyCloseProc(
}
/*
+ * If we've had our state changed from the default, reset now.
+ */
+
+ if (ttyPtr->doReset) {
+ tcsetattr(ttyPtr->fileState.fd, TCSANOW, &ttyPtr->initState);
+ }
+
+ /*
* Delegate to close for files.
*/
@@ -860,29 +874,48 @@ TtySetOptionProc(
if (tcgetattr(fsPtr->fileState.fd, &iostate) < 0) {
if (interp != NULL) {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
- "couldn't read current serial state: %s",
+ "couldn't read serial terminal control state: %s",
Tcl_PosixError(interp)));
}
return TCL_ERROR;
}
if (Tcl_UtfNcasecmp(value, "NORMAL", vlen) == 0) {
- iostate.c_iflag |= BRKINT | IGNPAR | ISTRIP | ICRNL | IXON;
- iostate.c_oflag |= OPOST;
- iostate.c_lflag |= ECHO | ICANON | ISIG;
+ SET_BITS(iostate.c_iflag, BRKINT | IGNPAR | ISTRIP | ICRNL | IXON);
+ SET_BITS(iostate.c_oflag, OPOST);
+ SET_BITS(iostate.c_lflag, ECHO | ECHONL | ICANON | ISIG);
} else if (Tcl_UtfNcasecmp(value, "PASSWORD", vlen) == 0) {
- iostate.c_iflag |= BRKINT | IGNPAR | ISTRIP | ICRNL | IXON;
- iostate.c_oflag |= OPOST;
- iostate.c_lflag &= ~(ECHO);
- iostate.c_lflag |= ECHONL | ICANON | ISIG;
+ SET_BITS(iostate.c_iflag, BRKINT | IGNPAR | ISTRIP | ICRNL | IXON);
+ SET_BITS(iostate.c_oflag, OPOST);
+ CLEAR_BITS(iostate.c_lflag, ECHO);
+ /*
+ * Note: password input turns out to be best if you echo the
+ * newline that the user types. Theoretically we could get users
+ * to do the processing of this in their scripts, but it always
+ * feels highly unnatural to do so in practice.
+ */
+ SET_BITS(iostate.c_lflag, ECHONL | ICANON | ISIG);
} else if (Tcl_UtfNcasecmp(value, "RAW", vlen) == 0) {
- iostate.c_iflag = 0;
- iostate.c_oflag &= ~(OPOST);
- iostate.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG);
+#ifdef HAVE_CFMAKERAW
+ cfmakeraw(&iostate);
+#else /* !HAVE_CFMAKERAW */
+ CLEAR_BITS(iostate.c_iflag, IGNBRK | BRKINT | PARMRK | ISTRIP
+ | INLCR | IGNCR | ICRNL | IXON);
+ CLEAR_BITS(iostate.c_oflag, OPOST);
+ CLEAR_BITS(iostate.c_lflag, ECHO | ECHONL | ICANON | ISIG | IEXTEN);
+ CLEAR_BITS(iostate.c_cflag, CSIZE | PARENB);
+ SET_BITS(iostate.c_cflag, CS8);
+#endif /* HAVE_CFMAKERAW */
+ } else if (Tcl_UtfNcasecmp(value, "RESET", vlen) == 0) {
+ /*
+ * Reset to the initial state, whatever that is.
+ */
+
+ memcpy(&iostate, &fsPtr->initState, sizeof(struct termios));
} else {
if (interp) {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"bad mode \"%s\" for -inputmode: must be"
- " normal, password, or raw", value));
+ " normal, password, raw, or reset", value));
Tcl_SetErrorCode(interp, "TCL", "OPERATION", "FCONFIGURE",
"VALUE", NULL);
}
@@ -891,11 +924,25 @@ TtySetOptionProc(
if (tcsetattr(fsPtr->fileState.fd, TCSADRAIN, &iostate) < 0) {
if (interp != NULL) {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
- "couldn't update serial state: %s",
+ "couldn't update serial terminal control state: %s",
Tcl_PosixError(interp)));
}
return TCL_ERROR;
}
+
+ /*
+ * If we've changed the state from default, schedule a reset later.
+ * Note that this specifically does not detect changes made by calling
+ * an external stty program; that is deliberate, as it maintains
+ * compatibility with existing code!
+ *
+ * This mechanism in Tcl is not intended to be a full replacement for
+ * what stty does; it just handles a few common cases and tries not to
+ * leave things in a broken state.
+ */
+
+ fsPtr->doReset = (memcmp(&iostate, &fsPtr->initState,
+ sizeof(struct termios)) != 0);
return TCL_OK;
}
@@ -1598,8 +1645,6 @@ TclpOpenFileChannel(
fcntl(fd, F_SETFD, FD_CLOEXEC);
- sprintf(channelName, "file%d", fd);
-
#ifdef SUPPORTS_TTY
if (strcmp(native, "/dev/tty") != 0 && isatty(fd)) {
/*
@@ -1619,17 +1664,25 @@ TclpOpenFileChannel(
translation = "auto crlf";
channelTypePtr = &ttyChannelType;
TtyInit(fd);
+ sprintf(channelName, "serial%d", fd);
} else
#endif /* SUPPORTS_TTY */
{
translation = NULL;
channelTypePtr = &fileChannelType;
+ sprintf(channelName, "file%d", fd);
}
fsPtr = ckalloc(sizeof(TtyState));
fsPtr->fileState.validMask = channelPermissions | TCL_EXCEPTION;
fsPtr->fileState.fd = fd;
- fsPtr->closeMode = CLOSE_DEFAULT;
+#ifdef SUPPORTS_TTY
+ if (channelTypePtr == &ttyChannelType) {
+ fsPtr->closeMode = CLOSE_DEFAULT;
+ fsPtr->doReset = 0;
+ tcgetattr(fsPtr->fileState.fd, &fsPtr->initState);
+ }
+#endif /* SUPPORTS_TTY */
fsPtr->fileState.channel = Tcl_CreateChannel(channelTypePtr, channelName,
fsPtr, channelPermissions);
@@ -1675,7 +1728,7 @@ Tcl_MakeFileChannel(
int mode) /* ORed combination of TCL_READABLE and
* TCL_WRITABLE to indicate file mode. */
{
- FileState *fsPtr;
+ TtyState *fsPtr;
char channelName[16 + TCL_INTEGER_SPACE];
int fd = PTR2INT(handle);
const Tcl_ChannelType *channelTypePtr;
@@ -1694,22 +1747,30 @@ Tcl_MakeFileChannel(
sprintf(channelName, "serial%d", fd);
} else
#endif /* SUPPORTS_TTY */
- if ((getsockname(fd, (struct sockaddr *)&sockaddr, &sockaddrLen) == 0)
- && (sockaddrLen > 0)
- && (sockaddr.sa_family == AF_INET || sockaddr.sa_family == AF_INET6)) {
+ if ((getsockname(fd, (struct sockaddr *) &sockaddr, &sockaddrLen) == 0)
+ && (sockaddrLen > 0)
+ && (sockaddr.sa_family == AF_INET
+ || sockaddr.sa_family == AF_INET6)) {
return TclpMakeTcpClientChannelMode(INT2PTR(fd), mode);
} else {
channelTypePtr = &fileChannelType;
sprintf(channelName, "file%d", fd);
}
- fsPtr = ckalloc(sizeof(FileState));
- fsPtr->fd = fd;
- fsPtr->validMask = mode | TCL_EXCEPTION;
- fsPtr->channel = Tcl_CreateChannel(channelTypePtr, channelName,
+ fsPtr = ckalloc(sizeof(TtyState));
+ fsPtr->fileState.fd = fd;
+ fsPtr->fileState.validMask = mode | TCL_EXCEPTION;
+ fsPtr->fileState.channel = Tcl_CreateChannel(channelTypePtr, channelName,
fsPtr, mode);
+#ifdef SUPPORTS_TTY
+ if (channelTypePtr == &ttyChannelType) {
+ fsPtr->closeMode = CLOSE_DEFAULT;
+ fsPtr->doReset = 0;
+ tcgetattr(fsPtr->fileState.fd, &fsPtr->initState);
+ }
+#endif /* SUPPORTS_TTY */
- return fsPtr->channel;
+ return fsPtr->fileState.channel;
}
/*