I'm trying to write a simple console program in C that forks, runs a child process and, with pipe(), reads all stdin of the parent program and sends it to the stdin of the child program and reads all stdout of the child program and sends it to the stdout of the parent program. Eventually, I can make the parent do something other than just passing this data through—for now, it would suffice if the parent program behaves as if the child program were run directly.
Seems to work fine, except that when I read() from the stream that is piped from the child's output it won't return anything until a newline is encountered. This means that if the child process asks a question in middle of a line, the question will never appear until after the user types the answer, which is unacceptable.
I also have the same problem with the parent's stdin. Even if the user should be able to answer the question with a simple "y", the program cannot read the "y" until the user pushes enter, which is also unacceptable.
I'm setting the input stream to nonblocking with:
fcntl(stream, F_SETFL, fcntl(stream, F_GETFL) | O_NONBLOCK);
It works fine, but read() returns -1 until a newline is encountered.
Is there something I can do to read the actual data from the streams without being "protected" from partial lines? Or is there some totally different approach I should take? Is there some open source program that does something similar that I can examine?
Code is here:
#include <unistd.h>
#include <sched.h>
#include <fcntl.h>
int main(int argc, const char * const argv[])
{
int outfd[2];
int infd[2];
int oldstdin, oldstdout;
pipe2(outfd, O_NONBLOCK); // Where the parent is going to write to
pipe2(infd, O_NONBLOCK); // From where parent is going to read
oldstdin = dup(0); // Save current stdin
oldstdout = dup(1); // Save stdout
close(0);
close(1);
dup2(outfd[0], 0); // Make the read end of outfd pipe as stdin
dup2(infd[1],1); // Make the write end of infd as stdout
if(!fork())
{
const char * pChildArguments[] = { "/usr/bin/php", "test.php", 0 };
close(outfd[0]); // Not required for the child
close(outfd[1]);
close(infd[0]);
close(infd[1]);
execv(pChildArguments[0], (char * const *)pChildArguments);
}
else
{
char input[100];
close(0); // Restore the original std fds of parent
close(1);
dup2(oldstdin, 0);
dup2(oldstdout, 1);
close(outfd[0]); // These are being used by the child
close(infd[1]);
fcntl(infd[0], F_SETFL, fcntl(infd[0], F_GETFL) | O_NONBLOCK);
fcntl(0, F_SETFL, fcntl(0, F_GETFL) | O_NONBLOCK);
for (;;) {
ssize_t readReturnValue;
readReturnValue = read(infd[0], input, 100);
if (readReturnValue == 0) { break; }
if (readReturnValue > 0) {
write(1, input, readReturnValue);
fsync(1);
}
readReturnValue = read(0, input, 100);
if (readReturnValue > 0) {
write(outfd[1], input, readReturnValue);
fsync(outfd[1]);
}
sched_yield();
}
}
}
It was adapted from this blog post.
The test.php (used as a child process) is this:
<?php
echo "This lines goes through.\n";
$a = readline("Say something: ");
echo "You said " . $a . "\n";