From 529c50212bac4bd84711eae630e354f7579b2705 Mon Sep 17 00:00:00 2001 From: Laurence Withers Date: Sat, 26 Jul 2008 16:11:34 +0000 Subject: [PATCH] Add PID file support --- src/daemonitor/000_TopSource.c | 5 + src/daemonitor/100_io.c | 31 ++++++ src/daemonitor/102_pid.c | 194 +++++++++++++++++++++++++++++++++ 3 files changed, 230 insertions(+) create mode 100644 src/daemonitor/102_pid.c diff --git a/src/daemonitor/000_TopSource.c b/src/daemonitor/000_TopSource.c index 13d0e1e..1174719 100644 --- a/src/daemonitor/000_TopSource.c +++ b/src/daemonitor/000_TopSource.c @@ -6,11 +6,16 @@ */ /* Below are all the includes used throughout the application. */ +#include +#include #include #include #include #include +#include +#include #include +#include #include #include #include diff --git a/src/daemonitor/100_io.c b/src/daemonitor/100_io.c index 35a89b6..9e72692 100644 --- a/src/daemonitor/100_io.c +++ b/src/daemonitor/100_io.c @@ -26,11 +26,20 @@ ssize_t safe_write_fixed(int fd, const char* buf, size_t amt); +/* safe_sleep_fixed() + * Sleeps for a fixed period of seconds and nanoseconds. Restarts sleep with correct time if + * interrupted by signal delivery. + */ +void safe_sleep_fixed(int s, int ns); + + + /* IMPLEMENTATION ********************************************************************************/ /* safe_write_fixed() + * Copied from libgslutil (GPL-3). */ ssize_t safe_write_fixed(int fd, const char* buf, size_t amt) __attribute__((nonnull)); ssize_t safe_write_fixed(int fd, const char* buf, size_t amt) @@ -54,6 +63,28 @@ ssize_t safe_write_fixed(int fd, const char* buf, size_t amt) +/* safe_sleep_fixed() + * Adapted from libgslutil (GPL-3). + */ +void safe_sleep_fixed(int s, int ns) +{ + struct timespec req, rem; + + req.tv_sec = s; + req.tv_nsec = ns; + + restart: + if(nanosleep(&req, &rem) == -1) { + if(errno == EINTR) { + memcpy(&req, &rem, sizeof(struct timespec)); + goto restart; + } + return; + } +} + + + /* options for text editors kate: replace-trailing-space-save true; space-indent true; tab-width 4; vim: expandtab:ts=4:sw=4 diff --git a/src/daemonitor/102_pid.c b/src/daemonitor/102_pid.c new file mode 100644 index 0000000..6262d43 --- /dev/null +++ b/src/daemonitor/102_pid.c @@ -0,0 +1,194 @@ +/* daemonitor/src/daemonitor/102_pid.c + * + * (c)2007, Laurence Withers, . + * Released under the GNU GPLv3. See file COPYING or + * http://www.gnu.org/copyleft/gpl.html for details. +*/ + + + +/* PID FILE ROUTINES ****************************************************************************** + * + * This file contains routines for writing and removing PID files. A PID file may be created by + * daemonitor if it is requested. The PID file contains the PID of the daemonitor process, so that + * openrc etc. does not detect crashed services and allow them to be restarted, as daemonitor + * itself will restart them. This also allows the PID file to be used to send a termination signal + * to the daemonitor process. + */ + + + +/* PUBLIC API ************************************************************************************/ + + + +/* pidfile_set_filename() + * Sets the filename of the PID file. A copy of the data is made. + */ +void pidfile_set_filename(const char* filename); + + + +/* pidfile_write() + * Writes a PID file, logging errors to syslog. If an existing PID file containing a valid PID + * of a process whose /proc//exe matches /proc/self/exe is found, then that process is first + * killed with a SIGTERM and then a SIGKILL. On success, 0 is returned. On error, a log message is + * written, and -1 is returned. On success, an atexit() handler which unlinks the PID file is + * registered. + */ +int pidfile_write(void); + + + +/* IMPLEMENTATION ********************************************************************************/ + + + +/* pidfile_filename + * The filename of the PID file. This is copied with strdup(), because the original may have been + * an argument to main(), and may not be valid in the atexit() handler. + */ +char* pidfile_filename; + + + +/* pidfile_set_filename() + * Sets the PID file filename. On out of memory error, logs to syslog and aborts the process. + */ +void pidfile_set_filename(const char* filename) __attribute__((nonnull)); +void pidfile_set_filename(const char* filename) +{ + pidfile_filename = strdup(filename); + if(!pidfile_filename) { + LOG(LOG_CRIT, "Out of memory allocating %lu bytes.", + (unsigned long)strlen(filename)); + exit(1); + } +} + + + +/* pidfile_unlink() + * Unlinks the PID file. Called from atexit(). + */ +void pidfile_unlink(void) +{ + if(unlink(pidfile_filename)) { + LOG(LOG_ERR, "Error removing PID file `%s' (%m).", pidfile_filename); + } +} + + + +/* pidfile_write() + * Kills any existing PID file. Writes a new PID file. Returns 0 on success, -1 on error. + */ +int pidfile_write(void) +{ + char buf[40], * endp, link1[PATH_MAX + 1], link2[PATH_MAX + 1]; + int fd, i; + ssize_t ret; + unsigned long pid = 0; + + /* check for an existing PID file */ + fd = open(pidfile_filename, O_RDONLY); + if(fd != -1) { + ret = read(fd, buf, sizeof(buf) - 20); + if(ret == -1) { + LOG(LOG_WARNING, "Error reading from existing PID file `%s' (%m).", + pidfile_filename); + } else { + buf[ret] = 0; + pid = strtoul(buf, &endp, 10); + if(*endp && !isspace(*endp)) { + LOG(LOG_WARNING, "Invalid value `%s' in existing PID file `%s' (%m).", + buf, pidfile_filename); + pid = 0; + } + } + TEMP_FAILURE_RETRY( close(fd) ); + } + + /* check if there is an existing process matching /proc/self/exe with given PID */ + if(!pid) goto write_pidfile; + + ret = readlink("/proc/self/exe", link1, sizeof(link1) - 1); + if(ret == -1) { + LOG(LOG_WARNING, "Couldn't read /proc/self/exe (%m)."); + goto write_pidfile; + } + link1[ret] = 0; + + snprintf(buf, sizeof(buf), "/proc/%lu/exe", pid); + ret = readlink(buf, link2, sizeof(link2) - 1); + if(ret == -1) { + LOG(LOG_WARNING, "Stale PID file `%s' (PID %lu), removing it.", + pidfile_filename, pid); + goto write_pidfile; + } + link2[ret] = 0; + + if(strcmp(link1, link2)) { + LOG(LOG_WARNING, "Stale PID file `%s' (PID %lu), removing it.", + pidfile_filename, pid); + goto write_pidfile; + } + + /* send it a sigterm */ + LOG(LOG_WARNING, "Found an existing running process with PID %lu, killing it.", pid); + if(kill(pid, SIGTERM)) { + LOG(LOG_ERR, "Couldn't send SIGTERM to PID %lu (%m). Not starting yet.", pid); + return -1; /* come back later */ + } + + /* check it every 50ms until it's gone or 5s have elapsed */ + for(i = 0; i < 100; ++i) { + safe_sleep_fixed(0, 50 * 1000 * 1000); + if(kill(pid, 0) == -1) break; + } + + /* possibly send it a sigkill */ + if(kill(pid, 0) == 0) { + LOG(LOG_WARNING, "Old process with PID %lu still running. Sending it SIGKILL.", pid); + if(kill(pid, SIGKILL) == -1 && errno != ESRCH) { + LOG(LOG_ERR, "Couldn't send SIGKILL to PID %lu (%m). Not starting yet.", pid); + return -1; /* come back later */ + } + } + + LOG(LOG_NOTICE, "Old process gone."); + + /* actually write the PID file */ + write_pidfile: + fd = creat(pidfile_filename, 0666); + if(fd == -1) { + LOG(LOG_ERR, "Couldn't create PID file `%s' (%m).", pidfile_filename); + return -1; /* come back later */ + } + + snprintf(buf, sizeof(buf), "%lu\n", (unsigned long)getpid()); + ret = safe_write_fixed(fd, buf, strlen(buf)); + + if((size_t)ret != strlen(buf)) { + LOG(LOG_ERR, "Couldn't write to PID file `%s' (%m).", pidfile_filename); + TEMP_FAILURE_RETRY( close(fd) ); + return -1; /* come back later */ + } + + if( TEMP_FAILURE_RETRY( close(fd) ) ) { + LOG(LOG_ERR, "Couldn't write to PID file `%s' (%m).", pidfile_filename); + return -1; /* come back later */ + } + + /* success -- log, register handler, return */ + LOG(LOG_NOTICE, "PID %s written to `%s'.", buf, pidfile_filename); + atexit(pidfile_unlink); + return 0; +} + + + +/* options for text editors +kate: replace-trailing-space-save true; space-indent true; tab-width 4; +vim: expandtab:ts=4:sw=4 +*/