Is it possible to start a linux process, getting the PID, but start running later on certain condition?

Viewed 101

I'm thinking about some tool that can pause the program at start.

For example, my_bin starts running at once.

$ ./my_bin

With this tool

$ magic_tool ./my_bin

my_bin will start. I can get the PID. Then I can start the actual running later.

2 Answers

I've just tested my suggestion in the comments and it worked! This is the code in my magic_tool.c:

#include <stdio.h>
#include <unistd.h>
#include <signal.h>

int main (int argc, char *argv[])
{
    pid_t pid; 
        
    printf("Executing %s to wrap %s.\n", argv[0], argv[1]);

    pid = fork();

    if (pid == -1)
        return -1;
        
    if (pid == 0) {
        raise(SIGSTOP);
        execl(argv[1], "", NULL);
    } else {
        printf("PID == %d\n", pid);
    }   

    return 0;
}

I wrote another test program target.c:

#include <stdio.h>

int main ()
{
    puts("It works!\n");
    return 0;
}

Running ./magic_tool ./target printed a PID and returned to shell. Only after running kill -SIGCONT <printed_pid> was It works! printed. You'll probably want to have PID saved somewhere else and also perform some checks in the magic_tool, but I think this is nonetheless a good proof of concept.

EDIT:

I was playing around with this a bit more and for some reason it didn't always work (see why below). The solution is simple - just follow a proper fork off and die pattern a bit more closely in magic_tool.c:

#include <stdio.h>
#include <unistd.h>
#include <signal.h>

int main (int argc, char *argv[])
{
    pid_t pid; 

    printf("Executing %s to wrap %s.\n", argv[0], argv[1]);
    pid = fork();

    if (pid == -1)
        return -1;

    if (pid == 0) {
        setsid();
        pid = fork();

        if (pid == -1)
            return -1;

        if (pid == 0) {
            raise(SIGSTOP);
            if (execl(argv[1], "", NULL))
                return -1;
        }       
        printf("PID == %d\n", pid); 
    }
    return 0;
}

I found an explanation in this answer:

When you start the root process from your shell, it is a process group leader, and its descendants are members of that group. When that leader terminates, the process group is orphaned. When the system detects a newly-orphaned process group in which any member is stopped, then every member of the process group is sent a SIGHUP followed by a SIGCONT.

So, some of your descendant processes are still stopped when the leader terminates, and thus everyone receives a SIGHUP followed by a SIGCONT, which for practical purposes mean they die of SIGHUP.

Exactly which descendants are still stopped (or even just merrily advancing toward exit()) is a timing race.

The answer also links to IEEE Std 1003.1-2017 _Exit entry which contains more details on the matter.

This is mostly a very similar idea as @gst, but done entirely in the shell, you can spawn a subshell (this forks and create a new pid) and have the subshell send itself SIGSTOP signal, when the subshell receives a SIGCONT signal and resumes, the subshell exec the intended program (this replaces the subshell with the intended program without creating a new pid). So that the main shell can continue doing stuff, the subshell should run on background with &.

In a nutshell:

(kill -STOP $BASHPID && exec ./my_bin) &
subpid=$!    # get the pid of above subshell
... do something else ...
kill -CONT $subpid   # resume

Another idea that wouldn't suffer from race condition between the main process sending SIGCONT and the subshell SIGSTOP-ing itself is to use a file descriptor to implement the wait instead:

exec {PIPEFD}<> <(:)   # set PIPEFD to the file descriptor of an anonymous pipe
(read -u $PIPEFD && exec ./my_bin) &
subpid=$!    # get the pid of above subshell
... do something else ...
echo >&$PIPEFD    # resume
Related