I have a program that uses popen()
in order to open and read the output from a shell command. The problem is, as far as I can tell, there is no easy way to get the PID of the running process, and hence, you can't kill it if it gets stuck. So the question is, how can you retrieve the PID from a process opened with popen
?
popen. To run a process and read all of its output, set the stdout value to PIPE and call communicate(). The above script will wait for the process to complete and then it will display the output.
We can get the pid for the current process via the os. getpid() function. We may also get the pid for the parent process via the os. getppid() function.
To start a new process, or in other words, a new subprocess in Python, you need to use the Popen function call. It is possible to pass two parameters in the function call. The first parameter is the program you want to start, and the second is the file argument.
The solution I came up with (and the general consensus) is to create a new popen
function that allows me to retrieve the PID. Since I was unable to find a simple example of this on SO, I wanted to post my implementation in the hopes that it helps somebody else. Feedback and alternate solutions are welcome.
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <sys/wait.h>
#include <errno.h>
#include <string>
#include <sstream>
using namespace std;
#define READ 0
#define WRITE 1
FILE * popen2(string command, string type, int & pid)
{
pid_t child_pid;
int fd[2];
pipe(fd);
if((child_pid = fork()) == -1)
{
perror("fork");
exit(1);
}
/* child process */
if (child_pid == 0)
{
if (type == "r")
{
close(fd[READ]); //Close the READ end of the pipe since the child's fd is write-only
dup2(fd[WRITE], 1); //Redirect stdout to pipe
}
else
{
close(fd[WRITE]); //Close the WRITE end of the pipe since the child's fd is read-only
dup2(fd[READ], 0); //Redirect stdin to pipe
}
setpgid(child_pid, child_pid); //Needed so negative PIDs can kill children of /bin/sh
execl("/bin/sh", "/bin/sh", "-c", command.c_str(), NULL);
exit(0);
}
else
{
if (type == "r")
{
close(fd[WRITE]); //Close the WRITE end of the pipe since parent's fd is read-only
}
else
{
close(fd[READ]); //Close the READ end of the pipe since parent's fd is write-only
}
}
pid = child_pid;
if (type == "r")
{
return fdopen(fd[READ], "r");
}
return fdopen(fd[WRITE], "w");
}
int pclose2(FILE * fp, pid_t pid)
{
int stat;
fclose(fp);
while (waitpid(pid, &stat, 0) == -1)
{
if (errno != EINTR)
{
stat = -1;
break;
}
}
return stat;
}
int main()
{
int pid;
string command = "ping 8.8.8.8";
FILE * fp = popen2(command, "r", pid);
char command_out[100] = {0};
stringstream output;
//Using read() so that I have the option of using select() if I want non-blocking flow
while (read(fileno(fp), command_out, sizeof(command_out)-1) != 0)
{
output << string(command_out);
kill(-pid, 9);
memset(&command_out, 0, sizeof(command_out));
}
string token;
while (getline(output, token, '\n'))
printf("OUT: %s\n", token.c_str());
pclose2(fp, pid);
return 0;
}
CLARIFICATION
I tried to use the defined functions by @Gillespie's answer but found out that the pid
in the C/C++ program was different from the one returned by the terminal command pgrep
and looking at the output of ps -aux | grep myNameProc
it seemed the process of the C program was forked once more.
I think because execl("/bin/sh", "/bin/sh", "-c", command.c_str(), NULL);
is actually equivalent to /bin/sh cmd string
. So basically the child process of your C (or C++) program is creating a new process that does /bin/sh yourRealProcess
where yourRealProcess
is the one specified in the command
string.
I solved doing the following: execl(command.c_str(), command.c_str(), (char*)NULL);
. However, as specified by @Gillespie in the previous comments, in this way you will not be able to pass arguments to your process.
C IMPLEMENTATION
According to my needs I readapted @Gillespie's functions to include the above discussed modification and to work in the C programming language:
FILE * custom_popen(char* command, char type, pid_t* pid)
{
pid_t child_pid;
int fd[2];
pipe(fd);
if((child_pid = fork()) == -1)
{
perror("fork");
exit(1);
}
/* child process */
if (child_pid == 0)
{
if (type == 'r')
{
close(fd[0]); //Close the READ end of the pipe since the child's fd is write-only
dup2(fd[1], 1); //Redirect stdout to pipe
}
else
{
close(fd[1]); //Close the WRITE end of the pipe since the child's fd is read-only
dup2(fd[0], 0); //Redirect stdin to pipe
}
setpgid(child_pid, child_pid); //Needed so negative PIDs can kill children of /bin/sh
execl(command, command, (char*)NULL);
exit(0);
}
else
{
printf("child pid %d\n", child_pid);
if (type == 'r')
{
close(fd[1]); //Close the WRITE end of the pipe since parent's fd is read-only
}
else
{
close(fd[0]); //Close the READ end of the pipe since parent's fd is write-only
}
}
*pid = child_pid;
if (type == 'r')
{
return fdopen(fd[0], "r");
}
return fdopen(fd[1], "w");
}
int custom_pclose(FILE * fp, pid_t pid)
{
int stat;
fclose(fp);
while (waitpid(pid, &stat, 0) == -1)
{
if (errno != EINTR)
{
stat = -1;
break;
}
}
return stat;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With