[comp.unix.questions] Pipes - child buffers its output

martin@mwtech.UUCP (Martin Weitzel) (05/02/90)

In article <1364@ns-mx.uiowa.edu> rpruess@umaxc.weeg.uiowa.edu (Rex Pruess) writes:
:From a C program, I'd like to be to control an interactive program like
:'ftp' or 'adb'. It should work as follows:
:
: 	write some commands to stdin of 'ftp'
:	read stdout of 'ftp'
:	interpret the results
:	write more commands (depending on the results) to stdin of `ftp'
:	:
:
:I've been successful in setting up the two pipes, but the child (e.g.,
:ftp) buffers all of its output until it exits.  I need the child's output
:returned on a line by line basis.  What's the trick to accomplish this?

I have bad news:
There seems to be no such trick for the "pure mortal user" - the user
without the source. To the benefit of thousands of lazy programmers who
could or should not be obliged to use explicit "setbuf()" or "fflush()"
the decission about buffering an I/O-stream is made automatically in the
library routines.

Unfortunately the one who had to decide about the "automatism" could
not imagine such an advanced scheme as you describe above and hence
decided that only I/O-streams connected to devices work unbuffered,
while pipes and files are buffered.

If your system uses shared libraries, and if you can exactly figure out
some other things about the internal working of the library routines,
you *might* have a small chance to overcome this.
-- 
Martin Weitzel, email: martin@mwtech.UUCP, voice: 49-(0)6151-6 56 83

leo@ehviea.ine.philips.nl (Leo de Wit) (05/04/90)

In article <745@mwtech.UUCP> martin@mwtech.UUCP (Martin Weitzel) writes:
|In article <1364@ns-mx.uiowa.edu> rpruess@umaxc.weeg.uiowa.edu (Rex Pruess) writes:
|:From a C program, I'd like to be to control an interactive program like
|:'ftp' or 'adb'. It should work as follows:
|:
|: 	write some commands to stdin of 'ftp'
|:	read stdout of 'ftp'
|:	interpret the results
|:	write more commands (depending on the results) to stdin of `ftp'
|:	:
|:
|:I've been successful in setting up the two pipes, but the child (e.g.,
|:ftp) buffers all of its output until it exits.  I need the child's output
|:returned on a line by line basis.  What's the trick to accomplish this?
|
|I have bad news:
|There seems to be no such trick for the "pure mortal user" - the user
|without the source. To the benefit of thousands of lazy programmers who
|could or should not be obliged to use explicit "setbuf()" or "fflush()"
|the decission about buffering an I/O-stream is made automatically in the
|library routines.

Actually, it can be done, and it is rather easy too. Provided that your
system supports them, just open a pseudo tty, connect stdin and stdout
of the interactive program to the slave device (by fork, redirections
and exec), and your own application to the master device. Write your
commands to the output side of the master device, they will appear at
the input side of the slave device for the interactive program as its
standard input; read the results from the input side of the master
device, which reflects the output side of the slave device (the
standard output of the interactive program). Since the interactive
program is connected to a tty, buffering will be as you expect.

This method is also exploited by programs like rlogin and script; I
assume in a System V environment similar solutions are possible.

|
|Unfortunately the one who had to decide about the "automatism" could
|not imagine such an advanced scheme as you describe above and hence
|decided that only I/O-streams connected to devices work unbuffered,
|while pipes and files are buffered.
|

Indeed. Perhaps a new fcntl() could solve this: to indicate what kind
of buffering is wanted (e.g. blocked, line-buffered or unbuffered).
The library functions then could make decisions based on the current
flags for that file descriptor; no need for a kludgy 'check for tty'
(though I could imagine some people would object to have low level I/O
routines store/retrieve information about high level I/O ones (stdio
buffering)).  Also no need to provide 'unbuffered' flags to commands
(e.g. cat).  This mechanism would have to get some support by the
shell, e.g. |+ for a pipe, requesting line buffered I/O, or >: for
output, requesting unbuffered I/O.

Just speculating...

    Leo.

guy@auspex.auspex.com (Guy Harris) (05/04/90)

>There seems to be no such trick for the "pure mortal user" - the user
>without the source.

Unless, of course, the "pure mortal user" happens to be working on a
system with pseudo-ttys, and knows how to use them....

richard@aiai.ed.ac.uk (Richard Tobin) (05/04/90)

In article <745@mwtech.UUCP> martin@mwtech.UUCP (Martin Weitzel) writes:
>There seems to be no such trick for the "pure mortal user" - the user
>without the source. To the benefit of thousands of lazy programmers who
>could or should not be obliged to use explicit "setbuf()" or "fflush()"
>the decission about buffering an I/O-stream is made automatically in the
>library routines.

Here's a program I wrote to deal with this situation.  It runs a program
with its input and output connected to a pseudo-terminal, so that it
doesn't get buffered (actually, it gets line-buffered).

Compile it with "cc -o nobuf nobuf.c pseudopipe.c" and run it as
"nobuf program args ...".

Warnings:

(1) It's probably somewhat bug-ridden, expecially since I've just made
    several changes to it.

(2) It crashes my MIPS workstation.

(3) Since it uses pseudo-terminals, it won't work unless you have
    pseudo-terminals.  And maybe not even if you do - I've only
    used it on Suns.

-- Richard

#	This is a shell archive.
#	Remove everything above and including the cut line.
#	Then run the rest of the file through sh.
-----cut here-----cut here-----cut here-----cut here-----
#!/bin/sh
# shar:	Shell Archiver
#	Run the following text with /bin/sh to create:
#	nobuf.c
#	pseudopipe.c
# This archive created: Thu May  3 22:34:55 1990
cat << \SHAR_EOF > nobuf.c
/* A program to defeat buffering by stdio when the output is a pipe.
 *    nobuf program args ...
 * runs the program with its standard input, output, and error connected
 * to a pseudo-terminal.  It transfers data between the pseudo-terminal
 * and the real standard input etc.  Since the program sees its output is
 * a terminal, it won't buffer it.
 *
 * Richard Tobin, 1990.  You may redistribute this program freely if this
 * whole comment remains intact.
 */

#include <errno.h>
#include <signal.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <signal.h>

int dead_baby();		/* called when child exits */
int pty, ifds0;

main(argc, argv)
int argc;
char *argv[];
{
    int ptyfds[2];

    pseudopipe(ptyfds);		/* create pty to talk to child */

    ttymodes(ptyfds[0]);

    signal(SIGCHLD, dead_baby);	/* so we can exit when child does */

    subprocess(&argv[1], ptyfds); /* start up the subshell */

    close(ptyfds[0]);		/* parent doesn't need it */

    pty = ptyfds[1];

    transfer();
}

/* fork and exec the process in argv */

subprocess(argv, ptyfds)
char **argv;
int ptyfds[2];
{
    int tty;

    if(fork() != 0) return;

    /* close files child doesn't want */
    close(ptyfds[1]);
    close(0);
    close(1);
    close(2);

    /* get rid of controlling terminal */
    tty = open("/dev/tty", O_RDWR, 0);
    ioctl(tty, TIOCNOTTY, 0);
    close(tty);

    /* set up pty as std input, output, error */
    dup2(ptyfds[0], 0);
    dup2(ptyfds[0], 1);
    dup2(ptyfds[0], 2);
    close(ptyfds[0]);

    /* run the process */
    execvp(argv[0], argv);

    write(2, "exec failed\n", 12);
    _exit(1);
}

/* set the pseudo terminal to fairly sane modes */

ttymodes(pty)
int pty;
{
    static struct sgttyb sgtty = {B9600, B9600, 255, 255, EVENP|ODDP};
    static struct tchars tchars = {255, 255, 255, 255, 4, 255};

    ioctl(pty, TIOCSETP, &sgtty);
    ioctl(pty, TIOCSETC, &tchars);
}

/* transfer characters between standard input/output and pty */

transfer()
{
    int ifds, nread;
    char buf[256], ctld=4;
    extern int errno;

    ifds0 = (1 << 0) | (1 << pty); /* listen to real and pseudo ttys */

    while(ifds0 != 0)
    {
	ifds = ifds0;

	if(select(pty+1, &ifds, (int *)0, (int *)0, (struct timeval *)0) < 0)
	{
	    if(errno == EINTR)
		continue;
	    else
	    {
		perror("select");
		exit(1);
	    }
	}

	if(ifds & (1<<0))
	{
	    nread = read(0, buf, 256); /* from tty to process */
	    if(nread > 0)
		write(pty, buf, nread);
	    else if(nread == 0)
		write(pty, &ctld, 1); /* writing zero bytes doesn't work */
	    else
		ifds0 &= ~(1 << 0);
	}

	if(ifds & (1 << pty))
	{
	    nread = read(pty, buf, 256); /* from process to tty */
	    if(nread >= 0)
		write(1, buf, nread);
	    else
		ifds0 &= ~(1 << pty);
	}
    }

    while(1)
	sigpause(0);		/* wait for child to exit */
}

/* child process exited - do any remaining i/o and exit */

dead_baby()
{
    char buf[256];
    int nread;

    while(ioctl(pty, FIONREAD, &nread) == 0 && nread > 0)
    {
	nread = read(pty, buf, 256);
	write(1, buf, nread);
    }

    exit(0);
}

	
	
SHAR_EOF
cat << \SHAR_EOF > pseudopipe.c
/* like pipe(2) but uses pseudo-terminals.  consequently, the connection
 * is bi-directional, and will appear as a terminal to any forked process.
 * copyright (C) 1986 Richard M Tobin
 * you may freely distribute/modify this provided this whole comment remains
 * intact.  
 */

#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <strings.h>
#include <stdio.h>

/* pseudopipe puts two file descriptors in its argument array.  fd[0] is the
 * slave side of a pseudo-terminal, fd[1] is the master side.  to talk to a
 * child process, dup fd[0] onto file descriptors 0, 1 and 2.
 * if anything goes wrong (probably no free pty) -1 is returned.
 */

int pseudopipe(fd)
int fd[2];
{
    char pty_master[128], pty_slave[128];
#define masterfd fd[1]
#define slavefd fd[0]
    FILE *cons;
    int start=0;

    masterfd = findpty(pty_master, pty_slave, &start);
    if(masterfd < 0) return -1;
    slavefd = open(pty_slave, O_RDWR, 0);

    if(slavefd >= 0) return 0;

    return -1;

#undef masterfd
#undef slavefd
}

/* findpty find a free pseudo-terminal, and opens the master side of it.
 * the arguments are filled in to be the paths of the master and slave.
 */

#define MASTER  "/dev/ptyXY"
#define SLAVE   "/dev/ttyXY"
    
findpty(master_path, slave_path, ptyno)
char *master_path, *slave_path;
int *ptyno;
{
    struct stat statbuf;
    char c1;
    int i, master;
	
    strcpy(master_path, MASTER);
    strcpy(slave_path,  SLAVE );

/* the pseudo-ttys are /dev/ptyXY, where X is p,q,or r and Y is a
 * hex digit. There are usually 16, 32 or 48 so we check for the
 * of /dev/ptyX0 before trying to open them all.
 */
    
    for(; *ptyno <= ('s'-'p') * 16; ++*ptyno)
    {
	master_path[strlen(MASTER)-2] = *ptyno / 16 + 'p';
	master_path[strlen(MASTER)-1] = "0123456789abcdef"[*ptyno & 15];
	master = open(master_path,O_RDWR);
	if(master < 0) continue;
	slave_path[strlen(SLAVE)-2] = master_path[strlen(MASTER)-2];
	slave_path[strlen(SLAVE)-1] = master_path[strlen(MASTER)-1];
	return master;
    }
    return -1;
}

SHAR_EOF
#	End of shell archive
exit 0
-- 
Richard Tobin,                       JANET: R.Tobin@uk.ac.ed             
AI Applications Institute,           ARPA:  R.Tobin%uk.ac.ed@nsfnet-relay.ac.uk
Edinburgh University.                UUCP:  ...!ukc!ed.ac.uk!R.Tobin