Sockets, Stdio and Buffering

Sockets, Stdio and Buffering

Post by Greg Bon » Sat, 06 Jul 1996 04:00:00



I've checked the socket FAQ and old usenet articles related to the
subject but I haven't found a solution to my specific problem. I'm
attempting to exec() a tty-oriented interactive program that uses the
stdio library. I go through the usual redirection of stdin, stdout to a
socket before exec()'ing the program, but I'm finding that due to the
buffering of stdout in the exec()'d child, I can't access all the
child's output from the parent using a socket read().

I don't have the source to the exec()'d program so explicitly
fflush()'ing stdout is not an option. The only partial solution I've
been able to come up with (see snippet below) is to set the child's
stdout socket buffer size to one byte using setsockopt(). However, this
is only a partial solution since one byte of data is always inaccessible
in a stdio buffer until another byte is sent to stdout - this leaves the
parent always one byte short! I also tried disabling buffering on the
redirected stdout stream using setvbuff() just before exec()'ing the
child program (again, see snippet below) but this attribute did not get
inherited by the child.

So is my partial solution the only solution? Or is there some other
approach I have overlooked. Any help would be greatly appreciated!

Code snippet follows my .sig...

--
* Greg Bond                         * Dept. of Electrical Eng.  

* voice: (604) 822 0899             * 2356 Main Mall                
* fax:   (604) 822 5949             * Vancouver, BC              
* web: http://www.ee.ubc.ca/~bond   * Canada, V6T 1Z4

/* parent.c */

/* Copyright 1996, GWBond, Dept. of Electrical Eng., UBC */

/* GWBond  July 1996 */

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>

main(int argc, char **argv)  {

  int sockets[2];
  char *child = argv[1];
  pid_t child_pid;
  int bufsz = 1;

  if (socketpair (AF_UNIX, SOCK_STREAM, 0, sockets) != 0 ) {
    fprintf (stderr,  "Socket creation failure");
    exit(0);
  }

  if ((child_pid = fork ()) == -1) {
      fprintf (stderr, "Error forking child");
      exit(0);
    }

  if (child_pid == 0)  {

    /* child */

    char *argv[] = {NULL, NULL};
    argv[0] = child;

    close (sockets[0]);

    if (dup2 (sockets[1], 0) != 0) {
      fprintf (stderr, "Error redirecting stdin");
      exit(0);
    }

    if (dup2 (sockets[1], 1) != 1) {
      fprintf (stderr, "Error redirecting stdout");
      exit(0);
    }

    /* currently using stderr for debugging */

/*     dup2 (sockets[1], 2);*/

    close (sockets[1]);

    /* Set buffer size on stdout socket to be one byte. */
    /* Still means that last byte in buff can't be read by parent! */

    setsockopt (1, SOL_SOCKET, SO_SNDBUF, &bufsz, sizeof (bufsz));

    /* Alternative to above is to disable buffering on stdout stream. */
    /* But this attribute doesn't get inherited by exec'd child! */

/*     if (setvbuf (stdout, NULL, _IONBF, 0) != 0) {*/
/*       fprintf (stderr, "Error modifying stdout buffering"); */
/*       exit(0); */
/*     } */

    if (execv (child, argv) == -1) {
      fprintf (stderr,  "Child creation failure");
      exit(0);
    }
  }
  else {

    /* parent */

    /* rest of code deleted .... */

 
 
 

Sockets, Stdio and Buffering

Post by Andrew Gier » Sun, 07 Jul 1996 04:00:00



Quote:>I've checked the socket FAQ and old usenet articles related to the
>subject but I haven't found a solution to my specific problem. I'm
>attempting to exec() a tty-oriented interactive program that uses the
>stdio library. I go through the usual redirection of stdin, stdout to a
>socket before exec()'ing the program, but I'm finding that due to the
>buffering of stdout in the exec()'d child, I can't access all the
>child's output from the parent using a socket read().

>I don't have the source to the exec()'d program so explicitly
>fflush()'ing stdout is not an option. The only partial solution I've
>been able to come up with (see snippet below) is to set the child's
>stdout socket buffer size to one byte using setsockopt(). However, this
>is only a partial solution since one byte of data is always inaccessible
>in a stdio buffer until another byte is sent to stdout - this leaves the
>parent always one byte short! I also tried disabling buffering on the
>redirected stdout stream using setvbuff() just before exec()'ing the
>child program (again, see snippet below) but this attribute did not get
>inherited by the child.

>So is my partial solution the only solution? Or is there some other
>approach I have overlooked. Any help would be greatly appreciated!

[The short answer to this question is that you want to use a pty
rather than a socket; the remainder of this article is an attempt
to explain why.

I think we need to put some stuff in the FAQ about this, because this
question (or variations of it) come up fairly regularly. Vic, as usual,
feel free to extract any useful info you can from this.]

Firstly, the socket buffer controlled by setsockopt() has *absolutly*
*nothing* to do with stdio buffering. Setting it to 1 is guaranteed to
be the Wrong Thing(tm).

Perhaps the following diagram might make things a little clearer:

        Process A                   Process B
    +---------------------+     +---------------------+
    |                     |     |                     |
    |    mainline code    |     |    mainline code    |
    |         |           |     |         ^           |
    |         v           |     |         |           |
    |      fputc()        |     |      fgetc()        |
    |         |           |     |         ^           |
    |         v           |     |         |           |
    |    +-----------+    |     |    +-----------+    |
    |    | stdio     |    |     |    | stdio     |    |
    |    | buffer    |    |     |    | buffer    |    |
    |    +-----------+    |     |    +-----------+    |
    |         |           |     |         ^           |
    |         |           |     |         |           |
    |      write()        |     |       read()        |
    |         |           |     |         |           |
    +-------- | ----------+     +-------- | ----------+
              |                           |                  User space
  ------------|-------------------------- | ---------------------------
              |                           |                Kernel space
              v                           |
         +-----------+               +-----------+  
         | socket    |               | socket    |  
         | buffer    |               | buffer    |  
         +-----------+               +-----------+  
              |                           ^
              v                           |
      (AF- and protocol-          (AF- and protocol-
       dependent code)             dependent code)  

Assuming these two processes are communicating with each other (I've
deliberately omitted the actual comms mechanisms, which aren't really
relevent), you can see that data written by process A to it's stdio
buffer is completely inaccessible to process B. Only once the decision
is made to flush that buffer to the kernel [via write()] can the data
actually be delivered to the other process.

If you are using AF_UNIX sockets [which should be the case if you are
using socketpair()] then you can assume that data is available to read()
as soon as it has been passed to write(). If you are using AF_INET, then
there is discussion in the FAQ about delivery of data.

The only guaranteed way to affect the buffering within process A is to
change the code. However, the default buffering for stdout is controlled
by whether the underlying FD refers to a terminal or not; generally,
output to terminals is line-buffered, and output to non-terminals
(including but not limited to files, pipes, sockets, non-tty devices,
etc.) is fully buffered. So the desired effect can usually be achieved
by using a pty device; this, for example, is what the 'expect' program
does.

Since the stdio buffer (and the FILE structure, and everything else
related to stdio) is user-level data, it is not preserved across an
exec() call, hence trying to use setvbuf() before the exec is
ineffective, as you discovered.


"Usenet is like a herd of performing elephants with diarrhea; massive,
difficult to redirect, awe-inspiring, entertaining, and a source of
mind-boggling amounts of excrement when you least expect it." [Gene Spafford]

 
 
 

Sockets, Stdio and Buffering

Post by Roger Espel Lli » Sun, 07 Jul 1996 04:00:00



Quote:>I've checked the socket FAQ and old usenet articles related to the
>subject but I haven't found a solution to my specific problem. I'm
>attempting to exec() a tty-oriented interactive program that uses the
>stdio library. I go through the usual redirection of stdin, stdout to a
>socket before exec()'ing the program, but I'm finding that due to the
>buffering of stdout in the exec()'d child, I can't access all the
>child's output from the parent using a socket read().

>I don't have the source to the exec()'d program so explicitly
>fflush()'ing stdout is not an option. The only partial solution I've
>been able to come up with (see snippet below) is to set the child's
>stdout socket buffer size to one byte using setsockopt(). However, this
>is only a partial solution since one byte of data is always inaccessible
>in a stdio buffer until another byte is sent to stdout - this leaves the
>parent always one byte short! I also tried disabling buffering on the
>redirected stdout stream using setvbuff() just before exec()'ing the
>child program (again, see snippet below) but this attribute did not get
>inherited by the child.

>So is my partial solution the only solution? Or is there some other
>approach I have overlooked. Any help would be greatly appreciated!

The standard solution to this is to use a pty/tty pair instead of a
pipe;  this works fine, other than the fact that it uses system
resources (there's often a fixed max number of ptys on the system) and
the whole tty/pty thing can be hard to get right.  If it's an option,
you can use some standalone program that will just run something inside
a pty and buffer its input/output.  I've seen a package by the name
pty.tar.gz that did that; you could search around for it with archie or
AltaVista.

Another option [ **warning, evil hack** ] , if you're on a system that
supports this (SunOS, Solaris, Linux ELF do; I don't know about others)
is to, on your main program, putenv() the name of a shared executable
(*.so)  in LD_PRELOAD, and then in that .so redefine some commonly used
libc function that the program you're exec'ing is known to use early.  
There you can 'get control' on the running program, and the first time
you get it, do a setbuf(stdout, NULL) on the program's behalf, and then
call the original libc function with a dlopen() + dlsym().  And you keep
the dlsym() value on a static var, so you can just call that the
following times.

Email me for example code if this is not detailed enough...

        -Roger
--

WWW & PGP key: http://www.eleves.ens.fr:8080/home/espel/index.html

 
 
 

1. Non-blocking socket and stdio line buffering

This is just a guess, but fgets() reads input until until "n-1
characters are read, a new-line character is read, or an end-of-file
condition is encountered.". Then it nul terminates the buffer.

With non-blocking input it might run out of input with none of those
conditions satisfied, and therefore not null terminate the buffer. I
can imagine that would result in wierd behavior like you describe.

Joe

2. DigitalAudio cards supported under Linux?

3. Sockets and stdio: fread and fwrite on the same socket.

4. OpenOffice antialiasing?

5. HELP: buffered copy using stdio doesn't work

6. xwindows resolutions, fonts questions

7. C library change for stdio buffering and pipes?

8. Linux on 31 disks $45

9. amusing stdio buffering bug in Solaris 2.3

10. Why does STDIO buffer to a pipe, but not to a TTY?

11. using stdio with sockets

12. Using stdio with fdopen() on sockets.

13. select, sockets, and stdio