I'm implementing piping on a simulated file system in C++ (with mostly C). It needs to run commands in the host shell but perform the piping itself on the simulated file system.
I could achieve this with the pipe()
, fork()
, and system()
system calls, but I'd prefer to use popen()
(which handles creating a pipe, forking a process, and passing a command to the shell). This may not be possible because (I think) I need to be able to write from the parent process of the pipe, read on the child process end, write the output back from the child, and finally read that output from the parent. The man page for popen()
on my system says a bidirectional pipe is possible, but my code needs to run on a system with an older version supporting only unidirectional pipes.
With the separate calls above, I can open/close pipes to achieve this. Is that possible with popen()
?
For a trivial example, to run ls -l | grep .txt | grep cmds
I need to:
ls -l
on the host; read its output backls -l
back to my simulator grep .txt
on the host on the piped output of ls -l
grep cmds
on the host on the piped output of grep .txt
man popen
From Mac OS X:
The
popen()
function 'opens' a process by creating a bidirectional pipe, forking, and invoking the shell. Any streams opened by previouspopen()
calls in the parent process are closed in the new child process. Historically,popen()
was implemented with a unidirectional pipe; hence, many implementations ofpopen()
only allow the mode argument to specify reading or writing, not both. Becausepopen()
is now implemented using a bidirectional pipe, the mode argument may request a bidirectional data flow. The mode argument is a pointer to a null-terminated string which must be 'r' for reading, 'w' for writing, or 'r+' for reading and writing.
I'd suggest writing your own function to do the piping/forking/system-ing for you. You could have the function spawn a process and return read/write file descriptors, as in...
typedef void pfunc_t (int rfd, int wfd);
pid_t pcreate(int fds[2], pfunc_t pfunc) {
/* Spawn a process from pfunc, returning it's pid. The fds array passed will
* be filled with two descriptors: fds[0] will read from the child process,
* and fds[1] will write to it.
* Similarly, the child process will receive a reading/writing fd set (in
* that same order) as arguments.
*/
pid_t pid;
int pipes[4];
/* Warning: I'm not handling possible errors in pipe/fork */
pipe(&pipes[0]); /* Parent read/child write pipe */
pipe(&pipes[2]); /* Child read/parent write pipe */
if ((pid = fork()) > 0) {
/* Parent process */
fds[0] = pipes[0];
fds[1] = pipes[3];
close(pipes[1]);
close(pipes[2]);
return pid;
} else {
close(pipes[0]);
close(pipes[3]);
pfunc(pipes[2], pipes[1]);
exit(0);
}
return -1; /* ? */
}
You can add whatever functionality you need in there.