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:
- Open a pipe and process to run
ls -l
on the host; read its output back - Pipe the output of
ls -l
back to my simulator - Open a pipe and process to run
grep .txt
on the host on the piped output ofls -l
- Pipe the output of this back to the simulator (stuck here)
- Open a pipe and process to run
grep cmds
on the host on the piped output ofgrep .txt
- Pipe the output of this back to the simulator and print it
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.
No need to create two pipes and waste a filedescriptor in each process. Just use a socket instead. https://stackoverflow.com/a/25177958/894520
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...
You can add whatever functionality you need in there.
You seem to have answered your own question. If your code needs to work on an older system that doesn't support
popen
opening bidirectional pipes, then you won't be able to usepopen
(at least not the one that's supplied).The real question would be about the exact capabilities of the older systems in question. In particular, does their
pipe
support creating bidirectional pipes? If they have apipe
that can create a bidirectional pipe, butpopen
that doesn't, then I'd write the main stream of the code to usepopen
with a bidirectional pipe, and supply an implementation ofpopen
that can use a bidirectional pipe that gets compiled in an used where needed.If you need to support systems old enough that
pipe
only supports unidirectional pipes, then you're pretty much stuck with usingpipe
,fork
,dup2
, etc., on your own. I'd probably still wrap this up in a function that works almost like a modern version ofpopen
, but instead of returning one file handle, fills in a small structure with two file handles, one for the child'sstdin
, the other for the child'sstdout
.In one of netresolve backends I'm talking to a script and therefore I need to write to its
stdin
and read from itsstdout
. The following function executes a command with stdin and stdout redirected to a pipe. You can use it and adapt it to your liking.https://github.com/crossdistro/netresolve/blob/master/backends/exec.c#L46
(I used the same code in popen simultaneous read and write)
POSIX stipulates that the
popen()
call is not designed to provide bi-directional communication:Any portable code will make no assumptions beyond that. The BSD
popen()
is similar to what your question describes.Additionally, pipes are different from sockets and each pipe file descriptor is uni-directional. You would have to create two pipes, one configured for each direction.