3

I don't need to wait for them, I just need a yes or no.

I'm executing a program that I know is going to fork (and the children are likely to fork too). If run correctly, it should take care of all the its children and exit them when it finishes. I need to write a test that checks if this is true. Orphaned processes get assigned to the init process, so I cannot see them in pstree anymore.

Bonus points if I can get a list of their PIDs so I can send SIGKILL to them. Ideally if it's something POSIX-y, so it would work on *BSD.

2 Answers 2

5

One way to find these would be to use ps -ef, looking for rows where the parent-id is "1", e.g.,

#!/bin/sh
orphans="$(ps -ef | awk '$3 == 1{ print $2; }')"
echo "Processes which might be orphans: $orphans"

However, many processes have "1" as their parent. Determining which are interesting to you can best be done by remembering which child processes your program created.

If you happen to know the login name (and/or user-id) under which these processes are created, you can eliminate some of the possibilities. The first column of ps can show (depending on the type of system) either the login name or corresponding user-id. POSIX offers some help here, but it's easy to find systems which differ — and the documentation reflects this:

  • FreeBSD 10, for instance, does not show the login name for the -f option. With just ps -ef, it shows the process-id in the first column, and does not show the parent's process-id. It requires the -l option to show (instead) the user-id.
  • OSX gives the user-id in either case (ps -ef or ps -efl).
  • Given the -l option, Solaris 10 shows process flags in the first and second columns. That's mentioned in POSIX, although the content of the flags is unspecified (because the content differs across Unix platforms).
  • Linux gives the login-name and process flags as per POSIX.

As you can see, for some subset of the available systems, ps -efl would give the "same" result for the first three columns. For anything more general, you'd have to look at the header (first line) and determine which column contains the information corresponding to the process owner (login name or user-id) and the process id and its parent's process-id.

For a given system (knowing the usable options for ps, and knowing whether to match the logon name or user-id), you could use awk to match that field as well, e.g.,

#!/bin/sh
orphans="$(ps -ef | awk -v user=$LOGNAME '($1 == user && $3 == 1){ print $2; }')"
echo "Processes which might be orphans: $orphans"

Here I used $LOGNAME, to account for POSIX's use of the term login name, which could be misleading (since in principle the processes could be via sudo, while POSIX's use of the term implies that they came via a "login").

Further reading:

2
  • from my recent experience with systemd on ubuntu orphaned processes (e.g. disown'ed) don't necessarily get "adopted" (to continue the metaphor) by PID 1. When an interactive shell is launched in an emulator like gnome terminal, some process descendent from within the gui process branch seems to pick up the orphans. Commented May 16, 2016 at 9:17
  • It would be pretty impossible for me to determine if one of the processes belonging to PID 1 were one of my own - I don't know what their names will be and this test doesn't have the internal information about what is being forked. In fact, I'm testing that internal mechanism as a black box - if I relied on its internal info, I would be mostly just testing a tautology.
    – M.K.
    Commented May 16, 2016 at 10:56
3

When a process terminates, the PPID of its children is set to 1 (adoption by init), but the PGID (process group identifier) and the SID (session identifier) don't change.

The process's children probably don't change their process group, unless they're intended to be daemons. Assuming they don't, start the process to be tested in its own process group. Call setpgid(getpid(), getpid()) from your test framework, after forking and before calling execve to execute the program being tested. Call kill(-test_program_pid, 0) (kill with a negative pid argument and the signal value 0) to test whether there is a running process with the PGID test_program_pid. Pass SIGKILL as the signal argument to kill them all.

test_program_pid = fork();
if (test_program_pid) {
    waitpid(test_program_pid, &status, 0);
    if (kill(-test_program_pid, 0)) {
        record_failue("some child processes were not terminated properly");
    }
    kill(-test_program_pid, SIGKILL);
} else {
    setpgid(getpid(), getpid());
    execve("/program/to/test", …);
}

An alternative method would be to create a temporary file and open it in the program that you're testing and nowhere else. If the program calls execve, make sure that the file descriptor is opened without the O_CLOEXEC flag (or call fcntl(fd, FD_CLOEXEC, 0)). This method assumes that the program doesn't go and close the file descriptors that it doesn't use explicitly. You can then run fuser /temp/file to list the processes that have this file open, and fuser -k /temp/file to kill them. A variant of this approach that works even with programs that close file descriptors they don't use, but assumes that the program doesn't change its current directory, is to create a temporary directory and change to that directory to run the program.

2
  • Thanks, this is exactly what I wanted. Using setsid() would probably work too.
    – M.K.
    Commented May 17, 2016 at 10:39
  • How might I manage this from a bash script?
    – Steven Lu
    Commented Jan 31, 2020 at 16:52

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .