Pipes - child buffers its output

Richard Tobin richard at aiai.ed.ac.uk
Fri May 4 07:42:03 AEST 1990


In article <745 at mwtech.UUCP> martin at 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 at uk.ac.ed             
AI Applications Institute,           ARPA:  R.Tobin%uk.ac.ed at nsfnet-relay.ac.uk
Edinburgh University.                UUCP:  ...!ukc!ed.ac.uk!R.Tobin



More information about the Comp.unix.questions mailing list