[net.bugs.4bsd] rlogin drops output when stop/start characters change

jdb@mordor.UUCP (John Bruner) (08/30/85)

Index:	ucb/rlogin.c 4.2 Fix

Description:
	"rlogin" throws away output whenever the stop/start
	characters are changed to/from ^S/^Q.  This is most
	noticeable when using a shell which provides its
	own input line editing (e.g. "tcsh"), and often
	causes the last portion of the output from the
	previous program to be lost.

Repeat-By:
	Use "rlogin" to connect to a machine which has such
	a shell and enable that shell's line editor.  Then
	run some commands which produce quite a bit of
	output (e.g. "cat /etc/passwd").  The problem is
	timing dependent, but it will probably show up after
	a couple of attempts.

Fix:
	"rlogind", the remote daemon for "rlogin", operates
	the remote pseudo-terminal in packet mode.  Each time
	that the stop/start characters are changed to/from
	^S/^Q it receives a TIOCPKT_DOSTOP or TIOCPKT_NOSTOP
	packet from the master side of the pty.  It sends this
	as an out-of-band message to the local "rlogin".
	"rlogind" also sends an out-of-band message if it
	receives a TIOCPKT_FLUSHWRITE packet from the master
	side of the pty.  (I haven't look at the code myself,
	but I'm told that the kernel never produces this
	packet type.)

	The local "rlogin" process always flushes pending
	output when it receives an out-of-band message.
	After flushing output it examines the message to
	determine if it was TIOCPKT_[DN]OSTOP; if so, then
	it sets or resets the stop/start characters on the
	local tty.  Because it unconditionally flushes output,
	a TIOCPKT_[DN]OSTOP message causes output to be lost.

	"rlogin" cannot simply recv() the out-of-band message
	because it uses the position of this message among
	the in-band data to determine how much output must
	be flushed.  (It does this with the undocumented
	SIOCATMARK ioctl.)  In order to handle this properly,
	it must "peek" at the message first to determine
	its contents, flush output if necessary, and then
	recv() the message and take any other appropriate
	actions.

	Guy Harris at Sun suggested that I include code to
	handle TIOCKPT_FLUSHREAD messages for completeness.
	The following includes that change as well:

***************
*** 20,25
  #include <sys/types.h>
  #include <sys/socket.h>
  #include <sys/wait.h>
  
  #include <netinet/in.h>
  

--- 20,26 -----
  #include <sys/types.h>
  #include <sys/socket.h>
  #include <sys/wait.h>
+ #include <sys/file.h>
  
  #include <netinet/in.h>
  
***************
*** 362,368
  
  oob()
  {
! 	int out = 1+1, atmark;
  	char waste[BUFSIZ], mark;
  
  	ioctl(1, TIOCFLUSH, (char *)&out);

--- 363,369 -----
  
  oob()
  {
! 	int out = FWRITE, in = FREAD, atmark;
  	char waste[BUFSIZ], mark;
  
  	recv(rem, &mark, 1, MSG_OOB|MSG_PEEK);
***************
*** 365,375
  	int out = 1+1, atmark;
  	char waste[BUFSIZ], mark;
  
! 	ioctl(1, TIOCFLUSH, (char *)&out);
! 	for (;;) {
! 		if (ioctl(rem, SIOCATMARK, &atmark) < 0) {
! 			perror("ioctl");
! 			break;
  		}
  		if (atmark)
  			break;

--- 366,383 -----
  	int out = FWRITE, in = FREAD, atmark;
  	char waste[BUFSIZ], mark;
  
! 	recv(rem, &mark, 1, MSG_OOB|MSG_PEEK);
! 	if (mark & TIOCPKT_FLUSHWRITE) {
! 		ioctl(1, TIOCFLUSH, (char *)&out);
! 		for (;;) {
! 			if (ioctl(rem, SIOCATMARK, &atmark) < 0) {
! 				perror("ioctl");
! 				break;
! 			}
! 			if (atmark)
! 				break;
! 			if (read(rem, waste, sizeof (waste)) <= 0)
! 				break;
  		}
  	}
  	recv(rem, &mark, 1, MSG_OOB);
***************
*** 371,380
  			perror("ioctl");
  			break;
  		}
- 		if (atmark)
- 			break;
- 		if (read(rem, waste, sizeof (waste)) <= 0)
- 			break;
  	}
  	recv(rem, &mark, 1, MSG_OOB);
  	if (mark & TIOCPKT_NOSTOP) {

--- 379,384 -----
  			if (read(rem, waste, sizeof (waste)) <= 0)
  				break;
  		}
  	}
  	recv(rem, &mark, 1, MSG_OOB);
  	if (mark & TIOCPKT_FLUSHREAD)
***************
*** 377,382
  			break;
  	}
  	recv(rem, &mark, 1, MSG_OOB);
  	if (mark & TIOCPKT_NOSTOP) {
  		notc.t_stopc = -1;
  		notc.t_startc = -1;

--- 381,388 -----
  		}
  	}
  	recv(rem, &mark, 1, MSG_OOB);
+ 	if (mark & TIOCPKT_FLUSHREAD)
+ 		ioctl(0, TIOCFLUSH, (char *)&in);
  	if (mark & TIOCPKT_NOSTOP) {
  		notc.t_stopc = -1;
  		notc.t_startc = -1;
-- 
  John Bruner (S-1 Project, Lawrence Livermore National Laboratory)
  MILNET: jdb@mordor [jdb@s1-c.ARPA]	(415) 422-0758
  UUCP: ...!ucbvax!dual!mordor!jdb 	...!seismo!mordor!jdb

chris@umcp-cs.UUCP (Chris Torek) (08/31/85)

> "rlogin" cannot simply recv() the out-of-band message because it
> uses the position of this message among the in-band data to determine
> how much output must be flushed.  (It does this with the undocumented
> SIOCATMARK ioctl.)

Undocumented it is not: the 4.2BSD IPC primer explains SIOCATMARK.
Would that this were not the only place.

> In order to handle this properly, it must "peek" at the message
> first to determine its contents, flush output if necessary, and
> then recv() the message and take any other appropriate actions.

It is interesting to note that recv(fd, buf, len, MSG_OOB) and
recv(fd, buf, len, MSG_OOB|MSG_PEEK) perform the same operation.
OOB data is only consumed in the kernel by reading the in-band data
past the out-of-band mark.  This is odd at best.  In addition some
of the kernel code that implements out of band data is very strange.

I would suggest that you check the return values from the two recv
calls.  I'm not yet convinced that this fix actually does the right
things, if only because the OOB kernel code is so odd.
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 4251)
UUCP:	seismo!umcp-cs!chris
CSNet:	chris@umcp-cs		ARPA:	chris@maryland

chris@umcp-cs.UUCP (Chris Torek) (08/31/85)

(Oops, one more.  I do not wish to seem wroth with your fix: rlogin's
handling of out of band data has long been a sore point with me,
and you have finally prodded me enough that I am investigating it.)

> "rlogind" also sends an out-of-band message if it receives a
> TIOCPKT_FLUSHWRITE packet from the master side of the pty.  (I
> haven't look at the code myself, but I'm told that the kernel never
> produces this packet type.)

Ah, but it does!  Look at the code for ptsstop and note the comment;
and compare the values of TIOCPKT_FLUSHREAD and FREAD, and those of
TIOCPKT_FLUSHWRITE and FWRITE.  Recall that a TTY stop routine is
called both for flow control (^S/^Q) and for I/O flushes (TIOCFLUSH
and signals).

The new code is certainly correct; I hope the kernel is as well.
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 4251)
UUCP:	seismo!umcp-cs!chris
CSNet:	chris@umcp-cs		ARPA:	chris@maryland

guy@sun.uucp (Guy Harris) (09/02/85)

> > "rlogind" also sends an out-of-band message if it receives a
> > TIOCPKT_FLUSHWRITE packet from the master side of the pty.  (I
> > haven't look at the code myself, but I'm told that the kernel never
> > produces this packet type.)
> 
> Ah, but it does!  Look at the code for ptsstop and note the comment;
> and compare the values of TIOCPKT_FLUSHREAD and FREAD, and those of
> TIOCPKT_FLUSHWRITE and FWRITE.  Recall that a TTY stop routine is
> called both for flow control (^S/^Q) and for I/O flushes (TIOCFLUSH
> and signals).

Interestingly (or grossly, take your pick) enough, when ttyflush is called
with FREAD but not FWRITE, not TIOCPKT_FLUSHREAD will be generated.  The
d_stop routine is called only if output is being flushed.  (The S5 generic
tty driver/unit driver interface is much cleaner; there's a general "special
function" call and it has separate commands for "block input", "unblock
input", "restart output", "suspend output", "resume suspended output",
"transmit break", "flush output", and "flush input".  Overloading "freeze
output" with "flush output and maybe input too" is asking for trouble.)

	Guy Harris