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;
}
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):
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()
, …).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.