Subject: telnet loop while trying to flush revoked tty FD
To: None <tech-net@netbsd.org>
From: john heasley <heas@shrubbery.net>
List: tech-net
Date: 05/31/2003 14:13:12
I ran this patch by Matthias and Martin.  They did not notice anything
wrong with the patch described below, but were concerned that it might
be masking a bug that is the root cause and since I am not certain how
telnet reached the described state, they thought it best to solicit
further comment here.

so, here goes....

I am not positive how telnet gets into this situation; undoubtedly it
is expect or me doing something dumb with expect.  However, it appears
that while telnet is trying to exit, it can get stuck in it's final
attempt to flush it's tty when the tty is already dead.

In the instance that I caught, lsof indicated that the filedescriptor
had been revoked, like so:

telnet  22519 heas    0u  VBAD                            (revoked)
telnet  22519 heas    1u  VBAD                            (revoked)
telnet  22519 heas    2u  VBAD                            (revoked)

causing it to loop in SetForExit->EmptyTerminal.

Besides updating this comment that i stumbled upon, I changed changed
EmptyTerminal to return when ttyflush returns "permanent failure"
(PR/18984) and ttyflush to mark the ring buffer as empty so that 
anything which tested for a non-empty ring would think it was empty,
and not try again.

This seems to work fine.  since I do not know how it got into this
state, i can't reproduce it.  I wrote a quick program to revoke()
the ttys of a stuck telnet (stuck for other legitimate reasons).
but, I think there was more to this scenario than i know because
telnet never made a call to EmptyTerminal() before exiting.

the change to tn3270 is a bit gratuitous.  i do not have anything to
test that.  but it seems legit.

Index: sys_bsd.c
===================================================================
RCS file: /cvsroot/src/usr.bin/telnet/sys_bsd.c,v
retrieving revision 1.23
diff -d -u -r1.23 sys_bsd.c
--- sys_bsd.c	2003/03/15 04:48:22	1.23
+++ sys_bsd.c	2003/05/30 17:22:21
@@ -420,7 +420,8 @@
      * Write any outstanding data before switching modes
      * ttyflush() returns 0 only when there is no more data
      * left to write out, it returns -1 if it couldn't do
-     * anything at all, otherwise it returns 1 + the number
+     * anything at all, it returns -2 if writing returns a
+     * permanent failure, otherwise it returns 1 + the number
      * of characters left to write.
 #ifndef	USE_TERMIO
      * We would really like to ask the kernel to wait for the output
Index: terminal.c
===================================================================
RCS file: /cvsroot/src/usr.bin/telnet/terminal.c,v
retrieving revision 1.9
diff -d -u -r1.9 terminal.c
--- terminal.c	2003/03/15 04:48:22	1.9
+++ terminal.c	2003/05/30 17:22:21
@@ -158,10 +158,12 @@
 	ring_consumed(&ttyoring, n);
     }
     if (n < 0) {
-	if (errno == EAGAIN)
+	if (errno == EAGAIN) {
 	    return -1;
-	else
+	} else {
+	    ring_consumed(&ttyoring, n0);
 	    return -2;
+	}
     }
     if (n == n0) {
 	if (n0)
Index: tn3270.c
===================================================================
RCS file: /cvsroot/src/usr.bin/telnet/tn3270.c,v
retrieving revision 1.14
diff -d -u -r1.14 tn3270.c
--- tn3270.c	2002/09/23 12:48:04	1.14
+++ tn3270.c	2003/05/30 17:22:21
@@ -221,12 +221,14 @@
 	    set[0].fd = tout;
 	    set[0].events = POLLOUT;
 #endif	/* defined(unix) */
-	    (void) ttyflush(0);
+	    if (ttyflush(0) == -2)
+		return(origCount);
 	    while (TTYROOM() == 0) {
 #if	defined(unix)
 		(void) poll(set, 1, INFTIM);
 #endif	/* defined(unix) */
-		(void) ttyflush(0);
+		if (ttyflush(0) == -2)
+		    return(origCount);
 	    }
 	}
 	c = TTYROOM();
Index: utilities.c
===================================================================
RCS file: /cvsroot/src/usr.bin/telnet/utilities.c,v
retrieving revision 1.14
diff -d -u -r1.14 utilities.c
--- utilities.c	2002/09/23 12:48:05	1.14
+++ utilities.c	2003/05/30 17:22:21
@@ -908,7 +908,8 @@
 #endif	/* defined(unix) */
     } else {
 	while (TTYBYTES()) {
-	    (void) ttyflush(0);
+	    if (ttyflush(0) == -2)
+		return;
 #if	defined(unix)
 	    (void) poll(set, 1, INFTIM);
 #endif	/* defined(unix) */