23

After opening a pipe to a process with popen, is there a way to kill the process that has been started? (Using pclose is not what I want because that will wait for the process to finish, but I need to kill it.)

Sophie Alpert
  • 139,698
  • 36
  • 220
  • 238

8 Answers8

29

Don't use popen(), write your own wrapper that does what you'd like.

It's fairly straightforward to fork(), and then replace stdin & stdout by using dup2(), and then calling exec() on your child.

That way, your parent will have the exact child PID, and you can use kill() on that.

Google search for "popen2() implementation" for some sample code on how to implement what popen() is doing. It's only a dozen or so lines long. Taken from dzone.com we can see an example that looks like this:

#define READ 0
#define WRITE 1

pid_t
popen2(const char *command, int *infp, int *outfp)
{
    int p_stdin[2], p_stdout[2];
    pid_t pid;

    if (pipe(p_stdin) != 0 || pipe(p_stdout) != 0)
        return -1;

    pid = fork();

    if (pid < 0)
        return pid;
    else if (pid == 0)
    {
        close(p_stdin[WRITE]);
        dup2(p_stdin[READ], READ);
        close(p_stdout[READ]);
        dup2(p_stdout[WRITE], WRITE);

        execl("/bin/sh", "sh", "-c", command, NULL);
        perror("execl");
        exit(1);
    }

    if (infp == NULL)
        close(p_stdin[WRITE]);
    else
        *infp = p_stdin[WRITE];

    if (outfp == NULL)
        close(p_stdout[READ]);
    else
        *outfp = p_stdout[READ];

    return pid;
}

NB: Seems like popen2() is what you want, but my distribution doesn't seem to come with this method.

roschach
  • 8,390
  • 14
  • 74
  • 124
slacy
  • 11,397
  • 8
  • 56
  • 61
  • this doesn't solve the issue of fork's memory duplication. its possible that we don't want a call to fork for this reason – Will Jan 07 '12 at 02:03
  • @phooji what does python have to do with anything? – Mahmoud Al-Qudsi Jun 01 '12 at 20:22
  • @MahmoudAl-Qudsi: Whoops. I must've been loooking at too many python questions. [Earlier python-related comment redacted] – phooji Jun 02 '12 at 18:15
  • 2
    The above needs "close(p_stdin[READ]); close(p_stdout[WRITE]);" to avoid two unwanted open file descriptors lingering (which prevents calls such as fgets() detecting that the forked child has exited). – Chris Oct 15 '12 at 08:41
  • I didn't understand what is the use of exit(1); My understanding is exit will exit whole program then what about the further code? @Slacy – Anurag Daware Aug 09 '19 at 09:57
  • 1
    The code in this answer has a serious problem: it calls `exit()` if `execl()` fails. That will cause any exit handlers registered by the parent process to be called, and flush buffered stream data copied from the parent process to the streams the parent process owns. And if the `popen2()` function is called from a C++ process, static destructors copied from the parent process will also be called. The code should call `_exit()` instead of `exit()` to bypass the parent process's `exit()` handlers. – Andrew Henle Sep 05 '19 at 11:54
  • This is harder than you think on Windows, though. – Jason C May 03 '21 at 16:22
9

Here is an improved version of popen2 (credit is due to Sergey L.). The version posted by slacy does not return the PID of the process created in popen2, but the PID assigned to sh.

pid_t popen2(const char **command, int *infp, int *outfp)
{
    int p_stdin[2], p_stdout[2];
    pid_t pid;

    if (pipe(p_stdin) != 0 || pipe(p_stdout) != 0)
        return -1;

    pid = fork();

    if (pid < 0)
        return pid;
    else if (pid == 0)
    {
        close(p_stdin[WRITE]);
        dup2(p_stdin[READ], READ);
        close(p_stdout[READ]);
        dup2(p_stdout[WRITE], WRITE);

        execvp(*command, command);
        perror("execvp");
        exit(1);
    }

    if (infp == NULL)
        close(p_stdin[WRITE]);
    else
        *infp = p_stdin[WRITE];

    if (outfp == NULL)
        close(p_stdout[READ]);
    else
        *outfp = p_stdout[READ];

    return pid;
}

The new version is to be called with

char *command[] = {"program", "arg1", "arg2", ..., NULL};
chessweb
  • 4,613
  • 5
  • 27
  • 32
4

popen does not actually start a thread, but rather forks a process. As I look at the definition, it doesn't look like there is an easy way to get PID of that process and kill it. There might be difficult ways like examining process tree, but i guess you'd be better off with using pipe, fork and exec functions to mimic behaviour of popen. Then you can use PID you get from fork() to kill the child process.

che
  • 12,097
  • 7
  • 42
  • 71
2

I have follow idea of @slacy that create function popen2.
But found problem when child process is die, parent process that still read file descripter outfp not return from function.

That could fix by add close unuse pipe on parent process.

close(p_stdin[READ]);
close(p_stdout[WRITE]);

We can get correct pid of new process by using bash as shell.

execl("/bin/bash", "bash", "-c", command, NULL);

When child process die the caller of function popen2 should collect status of child process by call pclose2. if we don't collect that status child process could be zombie process when it terminate.

This is the full code that tested and work as expect.

#define READ 0
#define WRITE 1

pid_t
popen2(const char *command, int *infp, int *outfp)
{
    int p_stdin[2], p_stdout[2];
    pid_t pid;

    if (pipe(p_stdin) != 0 || pipe(p_stdout) != 0)
        return -1;

    pid = fork();

    if (pid < 0)
        return pid;
    else if (pid == 0)
    {
        dup2(p_stdin[READ], STDIN_FILENO);
        dup2(p_stdout[WRITE], STDOUT_FILENO);

     //close unuse descriptors on child process.
     close(p_stdin[READ]);
     close(p_stdin[WRITE]);
     close(p_stdout[READ]);
     close(p_stdout[WRITE]);

        //can change to any exec* function family.
        execl("/bin/bash", "bash", "-c", command, NULL);
        perror("execl");
        exit(1);
    }

    // close unused descriptors on parent process.
    close(p_stdin[READ]);
    close(p_stdout[WRITE]);

    if (infp == NULL)
        close(p_stdin[WRITE]);
    else
        *infp = p_stdin[WRITE];

    if (outfp == NULL)
        close(p_stdout[READ]);
    else
        *outfp = p_stdout[READ];

    return pid;
}

int
pclose2(pid_t pid) {
    int internal_stat;
    waitpid(pid, &internal_stat, 0);
    return WEXITSTATUS(internal_stat);
}
unbound
  • 41
  • 1
  • 1
  • 4
  • Should this kill also all forked sub processes. What i am seeing that if i start a SSH session with popen2 the sh call ssh and continues to run the ssh process even when kill is issued. is there a way to catch those processes also ? – Lonko Feb 07 '22 at 09:01
2

Actually if the process is doing I/O (which it should be, otherwise why popen instead of system(3)?), then pclose should whack it with a SIGPIPE the next time it tries to read or write, and it should fall over nicely :-)

Norman Ramsey
  • 198,648
  • 61
  • 360
  • 533
0

I simply put in the (bash) script, in the first line:

echo PID $$

then I read the pid, and use it to do a : kill(pid,9)

Robert
  • 5,278
  • 43
  • 65
  • 115
  • popen is cheesy to begin with, this is a nice cheesy solution to the cheesy problem. – stu May 20 '16 at 16:25
0

The obvious way is system("pkill process_name");

Clearly this is problematic if you have more than one instance of the process running.

Andrew Grant
  • 58,260
  • 22
  • 130
  • 143
  • This does the case where multiple instances are running and one needs to target a precise instance. – jldupont Nov 16 '09 at 20:16
0

I did something similar in python, where you could kill a subprocess and any subprocesses forked for that. It's similar to slacy's solution actually. http://www.pixelbeat.org/libs/subProcess.py

pixelbeat
  • 30,615
  • 9
  • 51
  • 60