diff options
Diffstat (limited to 'lib/common/pid.c')
-rw-r--r-- | lib/common/pid.c | 247 |
1 files changed, 247 insertions, 0 deletions
diff --git a/lib/common/pid.c b/lib/common/pid.c new file mode 100644 index 0000000..bb53153 --- /dev/null +++ b/lib/common/pid.c @@ -0,0 +1,247 @@ +/* + * Copyright 2004-2022 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU Lesser General Public License + * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. + */ + +#include <crm_internal.h> + +#ifndef _GNU_SOURCE +# define _GNU_SOURCE +#endif + +#include <stdio.h> +#include <string.h> +#include <sys/stat.h> + +#include <crm/crm.h> + +int +pcmk__pid_active(pid_t pid, const char *daemon) +{ + static pid_t last_asked_pid = 0; /* log spam prevention */ + int rc = 0; + + if (pid <= 0) { + return EINVAL; + } + + rc = kill(pid, 0); + if ((rc < 0) && (errno == ESRCH)) { + return ESRCH; /* no such PID detected */ + + } else if ((daemon == NULL) || !pcmk__procfs_has_pids()) { + // The kill result is all we have, we can't check the name + + if (rc == 0) { + return pcmk_rc_ok; + } + rc = errno; + if (last_asked_pid != pid) { + crm_info("Cannot examine PID %lld: %s", + (long long) pid, pcmk_rc_str(rc)); + last_asked_pid = pid; + } + return rc; /* errno != ESRCH */ + + } else { + /* make sure PID hasn't been reused by another process + XXX: might still be just a zombie, which could confuse decisions */ + bool checked_through_kill = (rc == 0); + char exe_path[PATH_MAX], myexe_path[PATH_MAX]; + + rc = pcmk__procfs_pid2path(pid, exe_path, sizeof(exe_path)); + if (rc != pcmk_rc_ok) { + if (rc != EACCES) { + // Check again to filter out races + if ((kill(pid, 0) < 0) && (errno == ESRCH)) { + return ESRCH; + } + } + if (last_asked_pid != pid) { + if (rc == EACCES) { + crm_info("Could not get executable for PID %lld: %s " + CRM_XS " rc=%d", + (long long) pid, pcmk_rc_str(rc), rc); + } else { + crm_err("Could not get executable for PID %lld: %s " + CRM_XS " rc=%d", + (long long) pid, pcmk_rc_str(rc), rc); + } + last_asked_pid = pid; + } + if (rc == EACCES) { + // Trust kill if it was OK (we can't double-check via path) + return checked_through_kill? pcmk_rc_ok : EACCES; + } else { + return ESRCH; /* most likely errno == ENOENT */ + } + } + + if (daemon[0] != '/') { + rc = snprintf(myexe_path, sizeof(myexe_path), CRM_DAEMON_DIR"/%s", + daemon); + } else { + rc = snprintf(myexe_path, sizeof(myexe_path), "%s", daemon); + } + + if (rc > 0 && rc < sizeof(myexe_path) && !strcmp(exe_path, myexe_path)) { + return pcmk_rc_ok; + } + } + + return ESRCH; +} + +#define LOCKSTRLEN 11 + +/*! + * \internal + * \brief Read a process ID from a file + * + * \param[in] filename Process ID file to read + * \param[out] pid Where to put PID that was read + * + * \return Standard Pacemaker return code + */ +int +pcmk__read_pidfile(const char *filename, pid_t *pid) +{ + int fd; + struct stat sbuf; + int rc = pcmk_rc_ok; + long long pid_read = 0; + char buf[LOCKSTRLEN + 1]; + + CRM_CHECK((filename != NULL) && (pid != NULL), return EINVAL); + + fd = open(filename, O_RDONLY); + if (fd < 0) { + return errno; + } + + if ((fstat(fd, &sbuf) >= 0) && (sbuf.st_size < LOCKSTRLEN)) { + sleep(2); /* if someone was about to create one, + * give'm a sec to do so + */ + } + + if (read(fd, buf, sizeof(buf)) < 1) { + rc = errno; + goto bail; + } + + errno = 0; + rc = sscanf(buf, "%lld", &pid_read); + + if (rc > 0) { + if (pid_read <= 0) { + rc = ESRCH; + } else { + rc = pcmk_rc_ok; + *pid = (pid_t) pid_read; + crm_trace("Read pid %lld from %s", pid_read, filename); + } + } else if (rc == 0) { + rc = ENODATA; + } else { + rc = errno; + } + + bail: + close(fd); + return rc; +} + +/*! + * \internal + * \brief Check whether a process from a PID file matches expected values + * + * \param[in] filename Path of PID file + * \param[in] expected_pid If positive, compare to this PID + * \param[in] expected_name If not NULL, the PID from the PID file is valid + * only if it is active as a process with this name + * \param[out] pid If not NULL, store PID found in PID file here + * + * \return Standard Pacemaker return code + */ +int +pcmk__pidfile_matches(const char *filename, pid_t expected_pid, + const char *expected_name, pid_t *pid) +{ + pid_t pidfile_pid = 0; + int rc = pcmk__read_pidfile(filename, &pidfile_pid); + + if (pid) { + *pid = pidfile_pid; + } + + if (rc != pcmk_rc_ok) { + // Error reading PID file or invalid contents + unlink(filename); + rc = ENOENT; + + } else if ((expected_pid > 0) && (pidfile_pid == expected_pid)) { + // PID in file matches what was expected + rc = pcmk_rc_ok; + + } else if (pcmk__pid_active(pidfile_pid, expected_name) == ESRCH) { + // Contains a stale value + unlink(filename); + rc = ENOENT; + + } else if ((expected_pid > 0) && (pidfile_pid != expected_pid)) { + // Locked by existing process + rc = EEXIST; + } + + return rc; +} + +/*! + * \internal + * \brief Create a PID file for the current process (if not already existent) + * + * \param[in] filename Name of PID file to create + * \param[in] name Name of current process + * + * \return Standard Pacemaker return code + */ +int +pcmk__lock_pidfile(const char *filename, const char *name) +{ + pid_t mypid = getpid(); + int fd = 0; + int rc = 0; + char buf[LOCKSTRLEN + 2]; + + rc = pcmk__pidfile_matches(filename, 0, name, NULL); + if ((rc != pcmk_rc_ok) && (rc != ENOENT)) { + // Locked by existing process + return rc; + } + + fd = open(filename, O_CREAT | O_WRONLY | O_EXCL, 0644); + if (fd < 0) { + return errno; + } + + snprintf(buf, sizeof(buf), "%*lld\n", LOCKSTRLEN - 1, (long long) mypid); + rc = write(fd, buf, LOCKSTRLEN); + close(fd); + + if (rc != LOCKSTRLEN) { + crm_perror(LOG_ERR, "Incomplete write to %s", filename); + return errno; + } + + rc = pcmk__pidfile_matches(filename, mypid, name, NULL); + if (rc != pcmk_rc_ok) { + // Something is really wrong -- maybe I/O error on read back? + unlink(filename); + } + return rc; +} |