PID file safety
There are some instances where we need to use the old-style PID files in order
to signal a daemon process (typically sending it a SIGHUP
to ask it to
re-read its configuration files or a SIGTERM
to ask the daemon to exit). We
can get our of the business of managing daemons with things like systemd but in
the cases when we still have to deal with PID files, we should be careful.
Typically we have opened a PID file for a hypothetical daemon, food
:
FILE *f = fopen("/var/run/food.pid", "r");
if (f) {
/* Daemon may be running. */
We then need to read in the PID (we expect the file to contain just a number) so we can send that daemon a signal. This can be as simple as:
pid_t pid;
if (fscanf(f, "%" SCNiMAX, &pid) == 1) {
/* We probably read a PID */
It’s very important that we check the result of fscanf(3) as we only want to
proceed if we read something that looks like a number. kill(2) is very
dangerous depending on what we do with it and what our privileges are on the
system. For instance, the pid
argument to kill implies:
- Send the signal to every process that we have permission to if
pid
is -1, certainly this would be bad. Ifpid
is less than -1, the signal will be sent to a particular process group. - Send the signal to every process in the process group of the calling process if
pid
is 0, this is also bad. - If
pid
is positive, send the signal to that process. However ifpid
is 1, we are going to send that process to the init system. Ifpid
happens to be our own PID, we send the signal to ourselves.
The data type for a PID is pid_t
which in the GNU standard C library is an
int
. We should probably check that the PID at least looks like something
sane:
pid_t pid;
if (fscanf(f, "%" SCNiMAX, &pid) == 1 && pid > 1 && pid != getpid()) {
/* We probably read a PID and it seems sane */
Depending on the application, we may also use getppid(2) to make sure the PID is not the one for our own parent process.
We can then try to check if someone is running with that PID. Traditionally that is done by sending a 0 signal (a 0 signal means that no signal is actually sent but error checking is still performed).
fclose(f);
if (kill(pid, 0) == -1) {
if (errno == ESRCH) {
/* Nothing with that PID seems to exist
(or the process is a zombie) */
} else if (errno == EPERM) {
/* We don't have permissions to send a signal
to this process */
}
} else {
/* We can send signals to this PID, proceed,
for example: */
if (kill(pid, SIGHUP)) {
fprintf(stderr, "Couldn't signal %d: %m", pid);
}
}
Finally, if we need to implement the daemon side (double-forking, closing file descriptors, optionally creating PID files, and so on) please consider linking against libdaemon rather than writing the code yourself (see the testd.c example for usage).