[comp.unix.wizards] Obscure UNIX question

VAF@score.stanford.edu (Vince Fuller) (03/18/88)

I am attempting to write an application that will talk to a child process via
a PTY. I want to make this communication as completely half duplex as possible,
e.g. my program sends a command string to the PTY then reads the response and
processes it. Because I do not have control over the format of the output from
the child process and it is safe to assume that it is synchronous (i.e. it
reads its input, generates some output, then reads some more input), I want to
be able to read output from the PTY until the child blocks for input. Is there
any way for me to detect that the child process has done this (blocked for
input)? My groveling about in the UNIX manual pages for pty(4), tty(4), et. al.
hasn't gotten me anywhere. Alternatively, if it is not possible to do what I
want, can someone suggest an alternative method to write this application? In
summary, what I want to do is two step loop in the parent process:

    1) Send command to child process by writing on master end of PTY.
    2) Read and process child's output by reading from master end of PTY until
       child is finished processing the command. The child is considered to be
       "finished" when it blocks for input while reading from the slave end of
       PTY - this is the state that I need to be able to detect.

	Thanks,
	Vince Fuller, Stanford Networking Systems
-------
-------

edw@IUS1.CS.CMU.EDU (Eddie Wyatt) (03/18/88)

In article <12472@brl-adm.ARPA>, VAF@score.stanford.edu (Vince Fuller) writes:
> I want to
> be able to read output from the PTY until the child blocks for input. Is there
> any way for me to detect that the child process has done this (blocked for
> input)? 


		man 2 wait

x
x
x
x
x
x
x

-- 

Eddie Wyatt 				e-mail: edw@ius1.cs.cmu.edu

ka@june.cs.washington.edu (Kenneth Almquist) (03/21/88)

> In summary, what I want to do is two step loop in the parent process:
> 
>     1) Send command to child process by writing on master end of PTY.
>     2) Read and process child's output by reading from master end of PTY
>        until child is finished processing the command. The child is
>        considered to be "finished" when it blocks for input while reading
>        from the slave end of PTY - this is the state that I need to be
>        able to detect.
>
> 	Thanks,
> 	Vince Fuller, Stanford Networking Systems

A real challenge, thanks.  Here's what you do.

1)  Set the process group for the pty to the process group of the master
    processes (see getpgrp(2)) using the TIOCSPGRP ioctl (see tty(4)).

2)  Clear the LTOSTOP tty mode in the pty (see the TIOCLBIC ioctl near
    the end of tty(4)).

3)  Place the child process in a separate process group (see setpgrp(2)).
    The pid of the child is a good number to use as the processes group.

4)  Arrange to catch SIGCHLD (see sigvec(2); the purpose will be explained
    below.)

5)  Each time you go through the loop described above:

    a)  Set the process group of the pty to the process group of the
	child process using TIOCSPGRP.

    b)  Send a SIGCONT signal to the child process.

    c)  Write the command to the pty.

    d)  Read the first character of the response.

    e)  Set the process group of the pty to the process group of the master.

    f)  Send the child process a SIGSTOP signal followed by a SIGCONT
	signal (see kill(2)).  The reason for this is explained below.

    g)  Continue reading from the pty until a SIGCHLD signal is received.

    h)  Read any data that is still buffered inside the pty.


This needs a bit of explanation.  The basic idea is to take advantage
of the job control stuff which allows the user to be informed when a
background process tries to read from the terminal.  Step (c) allows
us to wait for the child process to read the input before we put it in
the background again.  Step (f) is necessary because the child might
have finished writing all its output and invoked read before the parent
process performed step (e).  Step (f) uses an undocumented feature of
the tty code to make the child process notice that step (e) has been
performed.  There is no danger that the SIGSTOP signal will be received
before the SIGCONT signal because the number of the SIGSTOP signal is
less than the SIGCONT signal, and another undocumented feature of main-
stream UNIXes is that they deliver lower numbered signals first.

I left out some details of steps (g) and (h).  When the child process
tries to read the pty, it will be stopped, and a SIGCHLD signal will be
sent to the parent process.  (This latter fact is not explicitly docu-
mented.)  If you want the code to be reliable, you have to code it in
a way that avoids race conditions, which is not easy given the Berkeley
signal mechanism.  It requires two flags, one to indicate when the signal
has been received and the other to indicate when a longjmp is safe:

	while (signal_received == 0 && setjmp(sigjump) == 0) {
		jump_ok = 1;
		Call select to wait for input from the pty.
		jump_ok = 0;
		if (input available)
			Read it.
	}
	while (select indicates more data present)
		Read it.

This code assumes that once data is placed in the output queue of the
pty, it will only be removed by the master process.  The code for the
signal handler for SIGCHLD is:

	onsigchld() {
		signal_received = 1;
		if (jump_ok)
			longjmp(sigjump, 1);
	}

Hope this helps (and I hope it's not *too* long winded :-))
				Kenneth Almquist