Search code examples
cposix

Function like pipe(3) but returns FILE *


Is there a function like int pipe(int pipefd[2]) but that returns FILE pointers? I know that I can FILE * fdopen(int) both file descriptors and I know I that when I int fclose(FILE *) the FILE pointers it will close the underlying file descriptors so I don't need to keep track of them, but it'd just be nice to stick entirely with either file descriptors or FILE pointers.

int fpipe(FILE * pipes[2]) {
  int result; 
  int pipefd[2];
  FILE * pipe[2];

  if (0 != (result = pipe(pipefd))) {
    return result;
  }

  if (NULL == (pipe[0] = fdopen(pipefd[0], "r"))) {
    close(pipefd[0]);
    close(pipefd[1]);
    return errno;
  }

  if (NULL == (pipe[1] = fdopen(pipefd[1], "w"))) {
    fclose(pipe[0]);
    close(pipefd[1]);
    return errno;
  }

  pipes[1] = pipe[1];
  pipes[0] = pipe[0];
  return 0;
}

Solution

  • Your fpipe() function is close to what's needed, and there isn't a standard function to do the same job, so you will need to write something. The primary reason there isn't such a standard function is that most often you end up forking and then using dup() or dup2() and finally an exec*() function, so there really isn't much benefit to having file streams, most of the time.

    As noted in the comments, you need to decide what to return on success and on error, and manage errno accordingly. There are two plausible designs (and precedents for both):

    1. The function returns 0 on success and -1 on failure with the more detailed error information in the errno variable (classic function call technique: see open(), close(), read(), write(), …).
    2. The function returns 0 on success and an error number on failure (without modifying errno) — this is the technique used by POSIX threads (pthreads) functions.

    Both designs leave the pipes[] array in an indeterminate state on error. This is not unreasonable; the argument array should not be pointing at valuable file streams before the call to fpipe() because if the call succeeded, the values would be lost.

    Remember that no standard C or POSIX library function sets errno to zero. (See POSIX errno).

    Here are the two designs, selected by the presence or absence of -DUSE_PTHREAD_COMPATIBLE_DESIGN on the compiler command line:

    #include <errno.h>
    #include <stdio.h>
    #include <unistd.h>
    
    extern int fpipe(FILE *pipes[2]);  // Should be in a header
    
    #ifndef USE_PTHREAD_COMPATIBLE_DESIGN
    // Design 1 - return -1 on failure and set errno
    int fpipe(FILE *pipes[2])
    {
        int pipefd[2];
    
        if (pipe(pipefd) != 0)
            return -1;
    
        if ((pipes[0] = fdopen(pipefd[0], "r")) == NULL)
        {
            close(pipefd[0]);
            close(pipefd[1]);
            return -1;
        }
    
        if ((pipes[1] = fdopen(pipefd[1], "w")) == NULL)
        {
            fclose(pipes[0]);
            close(pipefd[1]);
            return -1;
        }
    
        return 0;
    }
    
    #else
    
    // Design 2 - return error number on failure and don't modify errno
    int fpipe(FILE *pipes[2])
    {
        int saved_errno = errno;
        int rc = 0;
        int pipefd[2];
    
        if (pipe(pipefd)) != 0)
            rc = errno;
        else if ((pipes[0] = fdopen(pipefd[0], "r")) == NULL)
        {
            rc = errno;
            close(pipefd[0]);
            close(pipefd[1]);
        }
        else if ((pipes[1] = fdopen(pipefd[1], "w")) == NULL)
        {
            rc = errno;
            fclose(pipes[0]);
            close(pipefd[1]);
        }
    
        errno = saved_errno;
        return rc;
    }
    
    #endif /* USE_PTHREAD_COMPATIBLE_DESIGN */
    

    In the first variant of the function, because the bodies of the if blocks always end with return, there's no need to use else if for the next block. With the second variant, the if blocks do not return so the else if is important. C's ability to test the result of assignments is a huge help here.

    If you prefer, you can add if (rc != 0) pipes[0] = pipes[1] = NULL; before the return in the second variant. You have to put those assignments in more places in the other design. In fact, I'd probably set the values to NULL on entry and then only reset pipes[0] to NULL if it was initialized and pipes[1] was not.