Somewhere in my project I use fork and pipe to execute another process and pipe its I/O to communicate with it (I'm writing it in C++). There is no problem when I compile it in Ubuntu 14.04, it will work just fine, but I compiled it in fedora on a WMWare virtual machine and strange things began to happen. If I run the binary in terminal, there is no error but nothing will be written in the pipe (but getting streams of characters will work). I tried to debug my code in fedora, I put a break point in my code, but then a broken pipe signal was given when process tried to read from pipe (there were no signals when executing in terminal).
So, have any of you encountered such problems before? Is there any difference in piping between debian and red hat linux? Or is it because I'm running fedora on a virtual machine?
CODE:
int mFD_p2c [2];
int mFD_c2p [2];
int mEnginePID;
if (pipe(mFD_p2c) != 0 || pipe(mFD_c2p) != 0)
{
cout << "Failed to pipe";
exit(1);
}
mEnginePID = fork();
if (mEnginePID < 0)
{
cout << "Fork failed";
exit(-1);
}
else if (mEnginePID == 0)
{
if (dup2(mFD_p2c[0], 0) != 0 ||
close(mFD_p2c[0]) != 0 ||
close(mFD_p2c[1]) != 0)
{
cout << "Child: failed to set up standard input";
exit(1);
}
if (dup2(mFD_c2p[1], 1) != 1 ||
close(mFD_c2p[1]) != 0 ||
close(mFD_c2p[0]) != 0)
{
cout << "Child: failed to set up standard output";
exit(1);
}
string engine = "stockfish";
execlp(engine.c_str(), (char *) 0);
cout << "Failed to execute " << engine;
exit(1);
}
else
{
close(mFD_p2c[0]);
close(mFD_c2p[1]);
string str = "uci";
int nbytes = str.length();
if (write(mFD_p2c[1], str.c_str(), nbytes) != nbytes)
{
cout << "Parent: short write to child";
exit(1);
}
cout << "The following string has been written to engine:\n"
<< string(1, '\t') << str;
char readBuffer[2];
string output = "";
while (1)
{
int bytes_read = read(mFD_c2p[0], readBuffer, sizeof(char));
if (readBuffer[0] == '\n')
break;
readBuffer[bytes_read] = '\0';
output += readBuffer;
}
cout << "Got: " << output;
}
I see you're using Stockfish. I too have exactly experienced this behavior from Stockfish. The problem lies within how it handles output. Defined in misc.h:
#define sync_cout std::cout << IO_LOCK
And looking at the code again we'll see that IO_LOCK is an enum which is used in an overloaded friend operator for cout:
std::ostream& operator<<(std::ostream& os, SyncCout sc) {
static Mutex m;
if (sc == IO_LOCK)
m.lock();
if (sc == IO_UNLOCK)
m.unlock();
return os;
}
What I see here is that during using cout, a mutex is locked. I don't know how exactly this affects cout's output in a pipe instead of stdout, but I'm positive that this is the cause for the problem. You can check it by removing the lock functionality.
Edit: I forgot to mention that the pipe behavior is not different in linux based systems as mentioned before, but there might be slight differences between distributions handling mutexes used with pipes.
There are no differences in piping between debian and red hat, but the following list of questions may help you:
-Are the Ubuntu and the Fedora using the same architecture (64 bit vs 32) ?
-Are you using the same version of gcc (or any other compiler) ?
(Suggestion: use cerr for your error outputs, and maybe your debug output too -> you dup the standard outputs and inputs, so if something fails you may not see it)
Anyhow, here's how you turn it into a self-contained, compilable example:
stockfish
#cat stockfish
tr a-z A-Z #just so we do something
echo #need to end with a "\n" or else the parent won't break out of the while loop
Run command:
make pipes && PATH=.:$PATH pipes
pipes.cc
//pipes.cc
#include <iostream>
#include <fstream>
#include <string>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
using namespace std;
int mFD_p2c [2];
int mFD_c2p [2];
int mEnginePID;
if (pipe(mFD_p2c) != 0 || pipe(mFD_c2p) != 0)
{
cout << "Failed to pipe";
exit(1);
}
mEnginePID = fork();
if (mEnginePID < 0)
{
cout << "Fork failed";
exit(-1);
}
else if (mEnginePID == 0)
{
if (dup2(mFD_p2c[0], 0) != 0 ||
close(mFD_p2c[0]) != 0 ||
close(mFD_p2c[1]) != 0)
{
cout << "Child: failed to set up standard input";
exit(1);
}
if (dup2(mFD_c2p[1], 1) != 1 ||
close(mFD_c2p[1]) != 0 ||
close(mFD_c2p[0]) != 0)
{
cout << "Child: failed to set up standard output";
exit(1);
}
string engine = "stockfish";
char *const args[]={};
int ret;
execvp(engine.c_str(), args);
//I need the endl here or else it doesn't show for me when the execvp fails; I wasn't able to compile the original exec command so I used a different one from the exec* family
cout << "Failed to execute " << engine << endl;
exit(1);
}
else
{
close(mFD_p2c[0]);
close(mFD_c2p[1]);
string str = "uci";
int nbytes = str.length();
if (write(mFD_p2c[1], str.c_str(), nbytes) != nbytes)
{
cout << "Parent: short write to child";
exit(1);
}
//My particular child process tries to read to the end, so give it the EOF
close(mFD_p2c[1]);
cout << "The following string has been written to engine:\n"
<< string(1, '\t') << str;
char readBuffer[2];
string output = "";
while (1)
{
int bytes_read = read(mFD_c2p[0], readBuffer, sizeof(char));
if (readBuffer[0] == '\n')
break;
readBuffer[bytes_read] = '\0';
output += readBuffer;
}
cout << "Got: " << output;
}
return 0;
}
output:
The following string has been written to engine:
uciGot: UCI
Related
I checked this code several times and cannot understand why does poll() return immediately?
Here file is opened for read and should wait for event. How to make it wait for input?
#include <iostream>
#include <poll.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
using namespace std;
ssize_t read_out_to_the_end(int fd){
char chunk[1024];
ssize_t ret = 0, n;
while((n = ::read(fd, chunk, sizeof chunk)) > 0){
ret += n;
cerr << "read chunk: " << n << " | ";
cerr.write(chunk, n);
cerr << endl;
}
if (n < 0) {
cerr << "err in read" << endl;
}
else if (ret == 0){
cerr << "nothing to read" << endl;
}
return ret;
}
int main() {
int bininfd = open("bin-in", O_RDONLY | O_CREAT);//, 0644/*S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH*/);
if (bininfd < 0) {
perror("err in open(binin)");
return -1;
}
struct pollfd pollfds[] = {
{bininfd, POLLIN, 0},
};
auto&[pfd] = pollfds;
while (1) {
pfd.revents = 0; // cleanup, shouldn't it be redundant
int pollret = poll(pollfds, 1, -1);
if (pollret > 0) {
if (pfd.revents & POLLIN) {
cerr << "(pfd.revents & POLLIN)" << endl;
read_out_to_the_end(pfd.fd);
}
} else if (pollret == 0) {
cerr << "poll timed out" << endl;
continue;
} else {
cerr << "check for error" << endl;
continue;
}
}
}
the output is
(pfd.revents & POLLIN)
nothing to read
(pfd.revents & POLLIN)
nothing to read
(pfd.revents & POLLIN)
nothing to read
(pfd.revents & POLLIN)
nothing to read
(pfd.revents & POLLIN)
............... etc ....................
live example
UPDATE:
read_out_to_the_end() fixed. Thanks to #RemyLebeau
it works (blocks) on fifos as I expect, but not on regular files. Why?
poll() or select() never block on regular files. They always return a regular file as "ready". If you want to use poll() to do what tail -f does, you're on the wrong track.
Quoting from the SUSv4 standard:
The poll() function shall support regular files, terminal and
pseudo-terminal devices, FIFOs, pipes, sockets and [OB XSR] STREAMS-based files. The behavior of poll() on
elements of fds that refer to other types of file is unspecified.
Regular files shall always poll TRUE for reading and writing.
Since using poll() or select() on regular files is pretty much useless, newer interfaces have tried to remedy that. On BSD, you could use kqueue(2) with EVFILT_READ, and on Linux inotify(2) with IN_MODIFY. The newer epoll(7) interface on Linux will simply error out with EPERM if you try to watch a regular file.
Unfortunately, neither of those is standard.
read_out_to_the_end() has several issues:
ret is uninitialized.
The while loop is incrementing n when it should be assigning it instead. But then if the while loop hits the EOF, if( n == 0) will be true even if data was actually read before hitting EOF.
chunk may be null-terminated, but it may also receive nulls too, depending on the input data. So it should not be written to cerr (why not cout?) using operator<<, use cerr.write() instead so that you can pass it the actual number of bytes read.
Try this instead:
ssize_t read_out_to_the_end(int fd){
char chunk[1024];
ssize_t ret = 0, n;
while((n = ::read(fd, chunk, sizeof chunk)) > 0){
ret += n;
cerr << "read chunk: " << n << " | ";
cerr.write(chunk, n);
cerr << endl;
}
if (n < 0) {
cerr << "err in read" << endl;
}
else if (ret == 0){
cerr << "nothing to read" << endl;
}
return ret;
}
int main() {
int bininfd = open("bin-in", O_RDONLY | O_CREAT);//, 0644/*S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH*/);
if (bininfd < 0) {
perror("err in open(binin)");
return -1;
}
pollfd pfd = {};
pfd.fd = bininfd;
pfd.events = POLLIN;
while (true) {
pfd.revents = 0; // cleanup, shouldn't it be redundant
int pollret = poll(&pfd, 1, -1);
if (pollret > 0) {
if (pfd.revents & POLLIN) {
cerr << "(pfd.revents & POLLIN)" << endl;
read_out_to_the_end(pfd.fd);
}
} else if (pollret == 0) {
cerr << "poll timed out" << endl;
continue;
} else {
cerr << "poll error " << errno << endl;
break;
}
}
}
Also, on a side note, the open() documentation says:
The mode argument specifies the file mode bits be applied when a new file is created. This argument must be supplied when O_CREAT or O_TMPFILE is specified in flags; if neither O_CREAT nor O_TMPFILE is specified, then mode is ignored. The effective mode is modified by the process's umask in the usual way: in the absence of a default ACL, the mode of the created file is (mode & ~umask). Note that this mode applies only to future accesses of the newly created file; the open() call that creates a read-only file may well return a read/write file descriptor.
I have a multi-threaded C++03 application that presently uses popen() to invoke itself (same binary) and ssh (different binary) again in a new process and reads the output, however, when porting to Android NDK this is posing some issues such as not not having permissions to access ssh, so I'm linking in Dropbear ssh to my application to try and avoid that issue. Further, my current popen solution requires that stdout and stderr be merged together into a single FD which is a bit messy and I'd like to stop doing that.
I would think the pipe code could be simplified by using fork() instead but wonder how to drop all of the parent's stack/memory which is not needed in the child of the fork? Here is a snippet of the old working code:
#include <iostream>
#include <stdio.h>
#include <string>
#include <errno.h>
using std::endl;
using std::cerr;
using std::cout;
using std::string;
void
doPipe()
{
// Redirect stderr to stdout with '2>&1' so that we see any error messages
// in the pipe output.
const string selfCmd = "/path/to/self/binary arg1 arg2 arg3 2>&1";
FILE *fPtr = ::popen(selfCmd.c_str(), "r");
const int bufSize = 4096;
char buf[bufSize + 1];
if (fPtr == NULL) {
cerr << "Failed attempt to popen '" << selfCmd << "'." << endl;
} else {
cout << "Result of: '" << selfCmd << "':\n";
while (true) {
if (::fgets(buf, bufSize, fPtr) == NULL) {
if (!::feof(fPtr)) {
cerr << "Failed attempt to fgets '" << selfCmd << "'." << endl;
}
break;
} else {
cout << buf;
}
}
if (pclose(fPtr) == -1) {
if (errno != 10) {
cerr << "Failed attempt to pclose '" << selfCmd << "'." << endl;
}
}
cout << "\n";
}
}
So far, this is loosely what I have done to convert to fork(), but fork needlessly duplicates the entire parent process memory space. Further, it does not quite work, because the parent never sees EOF on the outFD it is reading from the pipe(). Where else do I need to close the FDs for this to work? How can I do something like execlp() without supplying a binary path (not easily available on Android) but instead start over with the same binary and a blank image with new args?
#include <iostream>
#include <stdio.h>
#include <string>
#include <errno.h>
using std::endl;
using std::cerr;
using std::cout;
using std::string;
int
selfAction(int argc, char *argv[], int &outFD, int &errFD)
{
pid_t childPid; // Process id used for current process.
// fd[0] is the read end of the pipe and fd[1] is the write end of the pipe.
int fd[2]; // Pipe for normal communication between parent/child.
int fdErr[2]; // Pipe for error communication between parent/child.
// Create a pipe for IPC between child and parent.
const int pipeResult = pipe(fd);
if (pipeResult) {
cerr << "selfAction normal pipe failed: " << errno << ".\n";
return -1;
}
const int errorPipeResult = pipe(fdErr);
if (errorPipeResult) {
cerr << "selfAction error pipe failed: " << errno << ".\n";
return -1;
}
// Fork - error.
if ((childPid = fork()) < 0) {
cerr << "selfAction fork failed: " << errno << ".\n";
return -1;
} else if (childPid == 0) { // Fork -> child.
// Close read end of pipe.
::close(fd[0]);
::close(fdErr[0]);
// Close stdout and set fd[1] to it, this way any stdout of the child is
// piped to the parent.
::dup2(fd[1], STDOUT_FILENO);
::dup2(fdErr[1], STDERR_FILENO);
// Close write end of pipe.
::close(fd[1]);
::close(fdErr[1]);
// Exit child process.
exit(main(argc, argv));
} else { // Fork -> parent.
// Close write end of pipe.
::close(fd[1]);
::close(fdErr[1]);
// Provide fd's to our caller for stdout and stderr:
outFD = fd[0];
errFD = fdErr[0];
return 0;
}
}
void
doFork()
{
int argc = 4;
char *argv[4] = { "/path/to/self/binary", "arg1", "arg2", "arg3" };
int outFD = -1;
int errFD = -1;
int result = selfAction(argc, argv, outFD, errFD);
if (result) {
cerr << "Failed to execute selfAction." << endl;
return;
}
FILE *outFile = fdopen(outFD, "r");
FILE *errFile = fdopen(errFD, "r");
const int bufSize = 4096;
char buf[bufSize + 1];
if (outFile == NULL) {
cerr << "Failed attempt to open fork file." << endl;
return;
} else {
cout << "Result:\n";
while (true) {
if (::fgets(buf, bufSize, outFile) == NULL) {
if (!::feof(outFile)) {
cerr << "Failed attempt to fgets." << endl;
}
break;
} else {
cout << buf;
}
}
if (::close(outFD) == -1) {
if (errno != 10) {
cerr << "Failed attempt to close." << endl;
}
}
cout << "\n";
}
if (errFile == NULL) {
cerr << "Failed attempt to open fork file err." << endl;
return;
} else {
cerr << "Error result:\n";
while (true) {
if (::fgets(buf, bufSize, errFile) == NULL) {
if (!::feof(errFile)) {
cerr << "Failed attempt to fgets err." << endl;
}
break;
} else {
cerr << buf;
}
}
if (::close(errFD) == -1) {
if (errno != 10) {
cerr << "Failed attempt to close err." << endl;
}
}
cerr << "\n";
}
}
There are two kinds of child processes created in this fashion with different tasks in my application:
SSH to another machine and invoke a server that will communicate back to the parent that is acting as a client.
Compute a signature, delta, or merge file using rsync.
First of all, popen is a very thin wrapper on top of fork() followed by exec() [and some call to pipe and dup and so on to manage the ends of a pipe] .
Second, the memory is only duplicated in form of "copy-on-write" memory - meaning that unless one of the processes writes to some page, the actual physical memory is shared between the two processes.
It does mean, of course, the OS has to create a memory map with 4-8 bytes per 4KB [in typical cases] (probably plus some internal OS data to track how many copies there are of that page and stuff - but as long as the page remains the same one as the parent process, the child page uses the parent processes internal data). Compared to everything else involved in creating a new process and loading an executable file into the new process, it's a pretty small part of the time. Since you are almost immediately doing exec, not much of the parent process' memory will be touched, so very little will happen there.
My advice would be that if popen works, keep using popen. If popen doesn't quite do what you want for some reason, then use fork + exec - but make sure you know what the reason for doing so is.
I am working on a code where it will do Linux command piping. Basically in my code, it will parse the user input command, then run it using the execvp function.
However, to do this, I would need to know the command, as well as its parameters. I have been trying to get the parsing to work correctly, however, it seems that when I do a test case, the output from both of the arrays that store their respective programs is the same. The commands/parameters are stored in a char array called prgname1 and prgname2.
For instance, if I were to run my program with the parameter "ps aux | grep [username]", then the output of prgname1[0] and prgname2[0] are both [username]. They are supposed to be ps and grep, respectively.
Can anyone take a look at my code and see where I might be having an error which is causing this?
Thanks!
#include <sys/wait.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <iostream>
#define MAX_PARA_NUM 5
#define MAX_COMMAND_LEN 1024
using namespace std;
int main(int argc, char *argv[]) {
char *prgname1[MAX_PARA_NUM], *prgname2[MAX_PARA_NUM];
char command[MAX_COMMAND_LEN];
int pfd[2];
pipe(pfd);
pid_t cid1, cid2;
char *full = argv[1];
char str[MAX_COMMAND_LEN];
int i = 0;
int j = 0;
int k = 0;
int ind = 0;
while (ind < strlen(full)) {
if (full[ind] == ' ') {
strncpy(command, str, i);
cout << command << endl;
prgname1[j] = command;
j++;
i = 0;
ind++;
}
else {
str[i] = full[ind];
i++;
ind++;
}
if(full[ind] == '|') {
i = 0;
j = 0;
ind+=2;
while (ind < strlen(full)) {
if (full[ind] == ' ') {
strncpy(command, str, i);
cout << command << endl;
prgname2[j] = command;
j++;
i = 0;
ind++;
}
else {
str[i] = full[ind];
i++;
ind++;
}
if (ind == strlen(full)) {
strncpy(command, str, i);
cout << command << endl;
prgname2[j] = command;
break;
}
}
}
}
// test output here not working correctly
cout << prgname1[0] << endl;
cout << prgname2[0] << endl;
// exits if no parameters passed
if (argc != 2) {
cout << "Usage:" << argv[0] << endl;
exit(EXIT_FAILURE);
}
// exits if there is a pipe error
if (pipe(pfd) == -1) {
cerr << "pipe" << endl;
exit(EXIT_FAILURE);
}
cid1 = fork(); // creates child process 1
// exits if there is a fork error
if (cid1 == -1 || cid2 == -1) {
cerr << "fork";
exit(EXIT_FAILURE);
}
// 1st child process executes and writes to the pipe
if (cid1 == 0) {
char **p = prgname1;
close(1); // closes stdout
dup(pfd[1]); // connects pipe output to stdout
close(pfd[0]); // closes pipe input as it is not needed
close(pfd[1]); // closes pipe output as pipe is connected
execvp(prgname1[0], p);
cerr << "execlp 1 failed" << endl;
cid2 = fork();
}
// 2nd child process reads from the pipe and executes
else if (cid2 == 0) {
char **p = prgname2;
close(0); // closes stdin
dup(pfd[0]); // connects pipe input to stdin
close(pfd[0]); // closes pipe input as pipe is connected
close(pfd[1]); // closes pipe output as it is not needed
execvp(prgname2[0], p);
cerr << "execlp 2 failed" << endl;
}
else {
sleep(1);
waitpid(cid1, NULL, 0);
waitpid(cid2, NULL, 0);
cout << "Program successfully completed" << endl;
exit(EXIT_SUCCESS);
}
return 0;
}
argv[1] gives you the first argument on the command line - not the entire command line. If you want the full list of command line arguments passed into the process, you will need to append argv[1], argv[2], ..., argv[argc - 1] together with a space between each.
Additionally, when you process it, you are setting the pointer for your prgname1[index] to command, so every time you set a given character pointer, they are all pointing to the same location (hence, they are all the same value). You need to allocate space for each element in prgname1 and copy command into it (using strncpy). Alternatively, using std::string and std::vector eliminates much of your current code.
This question follows from my attempt to implement the instructions in:
Linux Pipes as Input and Output
How to send a simple string between two programs using pipes?
http://tldp.org/LDP/lpg/node11.html
My question is along the lines of the question in: Linux Pipes as Input and Output, but more specific.
Essentially, I am trying to replace:
/directory/program < input.txt > output.txt
using pipes in C++ in order to avoid using the hard drive. Here's my code:
//LET THE PLUMBING BEGIN
int fd_p2c[2], fd_pFc[2], bytes_read;
// "p2c" = pipe_to_child, "pFc" = pipe_from_child (see above link)
pid_t childpid;
char readbuffer[80];
string program_name;// <---- includes program name + full path
string gulp_command;// <---- includes my line-by-line stdin for program execution
string receive_output = "";
pipe(fd_p2c);//create pipe-to-child
pipe(fd_pFc);//create pipe-from-child
childpid = fork();//create fork
if (childpid < 0)
{
cout << "Fork failed" << endl;
exit(-1);
}
else if (childpid == 0)
{
dup2(0,fd_p2c[0]);//close stdout & make read end of p2c into stdout
close(fd_p2c[0]);//close read end of p2c
close(fd_p2c[1]);//close write end of p2c
dup2(1,fd_pFc[1]);//close stdin & make read end of pFc into stdin
close(fd_pFc[1]);//close write end of pFc
close(fd_pFc[0]);//close read end of pFc
//Execute the required program
execl(program_name.c_str(),program_name.c_str(),(char *) 0);
exit(0);
}
else
{
close(fd_p2c[0]);//close read end of p2c
close(fd_pFc[1]);//close write end of pFc
//"Loop" - send all data to child on write end of p2c
write(fd_p2c[1], gulp_command.c_str(), (strlen(gulp_command.c_str())));
close(fd_p2c[1]);//close write end of p2c
//Loop - receive all data to child on read end of pFc
while (1)
{
bytes_read = read(fd_pFc[0], readbuffer, sizeof(readbuffer));
if (bytes_read <= 0)//if nothing read from buffer...
break;//...break loop
receive_output += readbuffer;//append data to string
}
close(fd_pFc[0]);//close read end of pFc
}
I am absolutely sure that the above strings are initialized properly. However, two things happen that don't make sense to me:
(1) The program I am executing reports that the "input file is empty." Since I am not calling the program with "<" it should not be expecting an input file. Instead, it should be expecting keyboard input. Furthermore, it should be reading the text contained in "gulp_command."
(2) The program's report (provided via standard output) appears in the terminal. This is odd because the purpose of this piping is to transfer stdout to my string "receive_output." But since it is appearing on screen, that indicates to me that the information is not being passed correctly through the pipe to the variable. If I implement the following at the end of the if statement,
cout << receive_output << endl;
I get nothing, as though the string is empty. I appreciate any help you can give me!
EDIT: Clarification
My program currently communicates with another program using text files. My program writes a text file (e.g. input.txt), which is read by the external program. That program then produces output.txt, which is read by my program. So it's something like this:
my code -> input.txt -> program -> output.txt -> my code
Therefore, my code currently uses,
system("program < input.txt > output.txt");
I want to replace this process using pipes. I want to pass my input as standard input to the program, and have my code read the standard output from that program into a string.
Your primary problem is that you have the arguments to dup2() reversed. You need to use:
dup2(fd_p2c[0], 0); // Duplicate read end of pipe to standard input
dup2(fd_pFc[1], 1); // Duplicate write end of pipe to standard output
I got suckered into misreading what you wrote as OK until I put error checking on the set-up code and got unexpected values from the dup2() calls, which told me what the trouble was. When something goes wrong, insert the error checks you skimped on before.
You also did not ensure null termination of the data read from the child; this code does.
Working code (with diagnostics), using cat as the simplest possible 'other command':
#include <unistd.h>
#include <string>
#include <iostream>
using namespace std;
int main()
{
int fd_p2c[2], fd_c2p[2], bytes_read;
pid_t childpid;
char readbuffer[80];
string program_name = "/bin/cat";
string gulp_command = "this is the command data sent to the child cat (kitten?)";
string receive_output = "";
if (pipe(fd_p2c) != 0 || pipe(fd_c2p) != 0)
{
cerr << "Failed to pipe\n";
exit(1);
}
childpid = fork();
if (childpid < 0)
{
cout << "Fork failed" << endl;
exit(-1);
}
else if (childpid == 0)
{
if (dup2(fd_p2c[0], 0) != 0 ||
close(fd_p2c[0]) != 0 ||
close(fd_p2c[1]) != 0)
{
cerr << "Child: failed to set up standard input\n";
exit(1);
}
if (dup2(fd_c2p[1], 1) != 1 ||
close(fd_c2p[1]) != 0 ||
close(fd_c2p[0]) != 0)
{
cerr << "Child: failed to set up standard output\n";
exit(1);
}
execl(program_name.c_str(), program_name.c_str(), (char *) 0);
cerr << "Failed to execute " << program_name << endl;
exit(1);
}
else
{
close(fd_p2c[0]);
close(fd_c2p[1]);
cout << "Writing to child: <<" << gulp_command << ">>" << endl;
int nbytes = gulp_command.length();
if (write(fd_p2c[1], gulp_command.c_str(), nbytes) != nbytes)
{
cerr << "Parent: short write to child\n";
exit(1);
}
close(fd_p2c[1]);
while (1)
{
bytes_read = read(fd_c2p[0], readbuffer, sizeof(readbuffer)-1);
if (bytes_read <= 0)
break;
readbuffer[bytes_read] = '\0';
receive_output += readbuffer;
}
close(fd_c2p[0]);
cout << "From child: <<" << receive_output << ">>" << endl;
}
return 0;
}
Sample output:
Writing to child: <<this is the command data sent to the child cat (kitten?)>>
From child: <<this is the command data sent to the child cat (kitten?)>>
Note that you will need to be careful to ensure you don't get deadlocked with your code. If you have a strictly synchronous protocol (so the parent writes a message and reads a response in lock-step), you should be fine, but if the parent is trying to write a message that's too big to fit in the pipe to the child while the child is trying to write a message that's too big to fit in the pipe back to the parent, then each will be blocked writing while waiting for the other to read.
It sounds like you're looking for coprocesses. You can program them in C/C++ but since they are already available in the (bash) shell, easier to use the shell, right?
First start the external program with the coproc builtin:
coproc external_program
The coproc starts the program in the background and stores the file descriptors to communicate with it in an array shell variable. Now you just need to start your program connecting it to those file descriptors:
your_program <&${COPROC[0]} >&${COPROC[1]}
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <string.h>
#include <iostream>
using namespace std;
int main() {
int i, status, len;
char str[10];
mknod("pipe", S_IFIFO | S_IRUSR | S_IWUSR, 0); //create named pipe
pid_t pid = fork(); // create new process
/* Process A */
if (pid == 0) {
int myPipe = open("pipe", O_WRONLY); // returns a file descriptor for the pipe
cout << "\nThis is process A having PID= " << getpid(); //Get pid of process A
cout << "\nEnter the string: ";
cin >> str;
len = strlen(str);
write(myPipe, str, len); //Process A write to the named pipe
cout << "Process A sent " << str;
close(myPipe); //closes the file descriptor fields.
}
/* Process B */
else {
int myPipe = open("pipe", O_RDONLY); //Open the pipe and returns file descriptor
char buffer[21];
int pid_child;
pid_child = wait(&status); //wait until any one child process terminates
int length = read(myPipe, buffer, 20); //reads up to size bytes from pipe with descriptor fields, store results
// in buffer;
cout<< "\n\nThis is process B having PID= " << getpid();//Get pid of process B
buffer[length] = '\0';
cout << "\nProcess B received " << buffer;
i = 0;
//Reverse the string
for (length = length - 1; length >= 0; length--)
str[i++] = buffer[length];
str[i] = '\0';
cout << "\nRevers of string is " << str;
close(myPipe);
}
unlink("pipe");
return 0;
}
I'm writing a small utility which is supposed to launch several commands in parallel using system() and wait for their results for logging purposes. However, even though I'm calling system() on different threads, by looking at my Activity Monitor I only see one instance of each command at a time. It looks like system is internally synchronized on a mutex, and only one execution is allowed at each time, however this looks like a huge limitation, can someone confirm this behavior? Do you have any ideas on how to solve it?
Update by looking at the threads execution flow, it looks like they're effectively synchronizing on a mutex. Is there an alternative system() which doesn't do that?
I should mention I'm using C++11 (w/ clang and libc++) on Mac OS 10.7.5.
Update code is:
void Batch::run()
{
done.clear();
generator->resetGeneration();
while(generator->hasMoreParameters())
{
// Lock for accessing active
unique_lock<mutex> lock(q_mutex, adopt_lock);
// If we've less experiments than threads
if (active.size() < threads)
{
Configuration conf = generator->generateParameters();
Experiment e(executable, conf);
thread t(&Experiment::run, e, reference_wrapper<Batch>(*this));
thread::id id = t.get_id();
active.insert(id);
t.detach();
}
// Condition variable
q_control.wait(lock, [this] { return active.size() < threads; } );
}
}
void Batch::experimentFinished(std::thread::id pos)
{
unique_lock<mutex> lock(q_mutex, adopt_lock);
active.erase(pos);
lock.unlock();
q_control.notify_all();
}
void Experiment::run(Batch& caller)
{
// Generate run command
stringstream run_command;
run_command << executable + " ";
ParameterExpression::printCommandLine(run_command, config);
if (system(run_command.str().c_str()))
stats["success"] = "true";
else
stats["success"] = "false";
caller.experimentFinished(this_thread::get_id());
}
Just to be clear: the spawning and handling of threads works fine and does what it needs to do, but it looks like you can have just one instance of system() running at a time.
Thanks
POSIX has this to say about system(3):
Using the system() function in more than one thread in a process or when the SIGCHLD signal is being manipulated by more than one thread in a process may produce unexpected results.
Due to the way that SIGCHLD must be blocked during the execution, running system calls concurrently doesn't really work. If you want multiple threads to run external tasks, you'll need to write a bit more code (handling fork/exec/wait yourself).
To whoever comes later, popen did the trick, as it doesn't internally keep a mutex. The code to make it work is
FILE* proc;
char buff[1024];
// Keep track of the success or insuccess of execution
if (!(proc = popen(run_command.str().c_str(), "r")))
stats["success"] = "false";
else
stats["success"] = "true";
// Exhaust output
while(fgets(buff, sizeof(buff), proc) != nullptr);
pclose(proc);
In case this helps, I wrote some fork/exec/wait code in C++ a while back. It captures output into a std::string.
As #Mat points out, fork, exec, and wait aren't really designed to be uses in a multi-threaded process.
So this is more useful if multi-process can be a substitute for multi-threaded in your application.
bool Utility::execAndRedirect(std::string command, std::vector<std::string> args, std::string& output, int& status)
{
int error;
int pipefd[2];
int localStatus;
if (pipe(pipefd) == -1)
{
error = errno;
cerr << "Executing command '" << command << "' failed: " << strerror(error) << endl;
return false;
}
pid_t pid = fork();
if (pid == 0)
{
char** argsC;
argsC = new char*[args.size() + 2];
argsC[0] = new char[command.size() + 1];
strncpy(argsC[0], command.c_str(), command.size());
argsC[0][command.size()] = '\0';
for (size_t count = 0; count < args.size(); count++)
{
argsC[count + 1] = new char[args[count].size() + 1];
strncpy(argsC[count + 1], args[count].c_str(), args[count].size());
argsC[count + 1][args[count].size()] = '\0';
}
argsC[args.size() + 1] = NULL;
close(pipefd[0]);
if (dup2(pipefd[1], STDOUT_FILENO) == -1)
{
error = errno;
cerr << "Executing command '" << command << "' failed: " << strerror(error) << endl;
exit(1);
}
if (dup2(pipefd[1], STDERR_FILENO) == -1)
{
error = errno;
cerr << "Executing command '" << command << "' failed: " << strerror(error) << endl;
exit(1);
}
close(pipefd[1]);
if (execvp(command.c_str(), argsC) == -1)
{
error = errno;
cerr << "Executing command '" << command << "' failed: " << strerror(error) << endl;
exit(1);
}
}
else if (pid > 0)
{
size_t BUFFER_SIZE = 1024;
char buffer[BUFFER_SIZE + 1];
close(pipefd[1]);
ostringstream oss;
ssize_t num_b;
while ((num_b = read(pipefd[0], buffer, BUFFER_SIZE)) != 0)
{
buffer[num_b] = '\0';
oss << buffer;
}
output = oss.str();
waitpid(pid, &localStatus, 0);
close(pipefd[0]);
}
else
{
error = errno;
cerr << "Executing command '" << command << "' failed: " << strerror(error) << endl;
return false;
}
if(WIFEXITED(localStatus))
{
status = WEXITSTATUS(localStatus);
//DateTime current = DateTime::now(); //this is a custom class
if(status == 0)
{
return true;
}
else
{
return false;
}
}
else
{
error = errno;
cerr << "Executing command '" << command << "' failed: child didn't terminate normally" << endl;
return false;
}
}