[comp.unix.questions] How do I unbuffer the standard output of a child process?

lemieux@ireq.hydro.qc.ca (Lemieux) (03/20/91)

	My program forks a process and redirect the stdin and
	stdout of the child to a set of named pipes.

	Thus, I can send a command to the child process through
	one pipe and read the result from the other.

	One of the program's constraint is that I have to get the
	result immediately (I can't wait for the output buffer
	of the child process to be flushed).

	Unfortunately, I did not find a way to unbuffer the standard
	output of the child process (even with setbuf). How can I do that?
	Can someone help me, please?

	Thanks in advance.

	Here's what I tried (no validation is done for clarity!).

	#include <stdio.h>
	#include <sys/stat.h>
	
	#define	PIPE_IN	 "/tmp/pipe_in"
	#define		PIPE_OUT "/tmp/pipe_out"

	main(argc,argv)
	int	argc;
	char	*argv[];
	{
	int	pid;
	int	fd_in,fd_out;
	FILE	*fp,*fp_in,*fp_out;
	
		/* create pipes */
		mknod(PIPE_IN ,S_IFIFO | 0777,0);
		mknod(PIPE_OUT,S_IFIFO | 0777,0);

		/* create child process */
		if ((pid = fork()) == -1)
		{
			fprintf(stderr,"Can't fork child\n");
			exit(1);
		}

		/* server */
		if (pid > 0)
		{
			/* establish the communication medium */
			fd_in  = open(PIPE_IN ,O_WRONLY);
			fd_out = open(PIPE_OUT,O_RDONLY);
			fp_in  = fdopen(fd_in ,"w");
			fp_out = fdopen(fd_out,"r");
	
			/* unbuffer the streams */
			setbuf(fp_in,NULL);
			setbuf(fp_out,NULL);
	
			/* lots of stuff deleted for clarity */
			...
		}
		/* client */
		else
		{
			/* redirect stdin and stdout */
			freopen(PIPE_IN,"r",stdin);
			fp = freopen(PIPE_OUT,"w",stdout);
	
			/*
			 * THIS COMMAND IS SUPPOSE TO UNBUFFER
			 * THE CHILD OUTPUT
			 */
			setbuf(fp,NULL);

			/* execute the client program */
			execl(argv[1],argv[2],0);
		}
	}

-----------------------------------------------------------------------------
Eric LEMIEUX				| Internet: lemieux@ireq.hydro.qc.ca
Institut de Recherche d'Hydro-Quebec	|
1800 Montee Sainte-Julie		| TEL: (514) 652-8139 
Varennes, Quebec, Canada		| FAX: (514) 652-8309
-----------------------------------------------------------------------------

jik@athena.mit.edu (Jonathan I. Kamens) (03/20/91)

  (Hey, Steve, isn't it about time this got added to the FAQ posting? 
Something like this (corrections welcomed)....)

Q) How do I unbuffer the output of a subprocess running on the other
   end of a pipe in my C program?

   Most implementations of the stdio library decide how to buffer the
   stdout stream based on whether or not it is a terminal.  If stdout
   is a terminal, then output is line-buffered, which means that
   output is actually sent to the terminal after each newline is
   printed by the program.  However, if stdout is not a terminal, then
   a larger buffer is used (usually, it is BUFSIZ characters large;
   BUFSIZ is defined in <stdio.h>).

   It is sometimes necessary, however, to force a process to write its
   output after every line, even when it isn't running on a terminal.
   For example, if you are using a standard filter such as "tr" in a
   subprocess, with both the input and output of the filter connected
   to pipes you control, then you might want each line passed to the
   filter to be output by it immediately so that you can use the
   result.

   Unfortunately, this is not easy.  Here are some proposed solutions
   that *will not* work, just to give you an idea of what the problem
   is:

   1. Use setbuf() to disable buffering in the subprocess before
      exec()ing the filter.

      Won't work because all of the stdio initialization takes place
      again in the filter when it starts up, and at that point stdio
      will simply put back the buffering when it realizes that it
      isn't running on a terminal.

   2. Use setbuf() in the parent process on the input pipe to the
      filter, or fflush() in the parent after each chunk of output is
      sent to the printer.

      Doesn't address the problem, which is buffering in the
      subprocess, not buffering in the parent.

   3. Use setbuf() in the parent process on the output pipe from the
      filter.

      Once again, doesn't address the problem.

   If you have access to the source code of the subprocess, then the
   easiest solution to the problem is to modify it to use setbuf() to
   turn off buffering when it starts up, or to fflush() its output
   where you want to be able to read output immediately in the parent
   process.

   If not, then what you have to do is *convince* the subprocess that
   it's talking to a terminal, even though it isn't.  To do this, you
   need to run the subprocess on a pty ("pseudo-tty") rather than
   running talking to it.

   The subject of programming with pty's is too complicated to
   describe in detail here, but the basic idea is:

   1. Scan through the available pty's (usually having names in the
      form "/dev/pty[p-r][0-9a-f]") until you find one you can open.

   2. Fork.

   3. In the child, open the tty corresponding to the pty opened in
      the parent.  For example, if the parent opened "/dev/ptyp5",
      then the child would open "/dev/ttyp5", and replace file
      descriptors 0, 1, and 2 with the file descriptor for the tty.

   4. exec() the filter.

   The man page pty(4), if it exists on your system, may go into more
   detail about pty's.  You might also want to read tty(4).

   If you don't want to learn to work with pty's in C code, then you
   can use Dan Bernstein's "pty" package to do the hard work for you.
   If, for example, your subprocess was originally "tr A-Z a-z", then
   you would use "pty -0 tr A-Z a-z" (assuming that you have installed
   "pty" in your search path).

   For information about obtaining the source code for the pty
   package, see Question 27.

-- 
Jonathan Kamens			              USnail:
MIT Project Athena				11 Ashford Terrace
jik@Athena.MIT.EDU				Allston, MA  02134
Office: 617-253-8085			      Home: 617-782-0710