diff options
Diffstat (limited to 'src/pidfile.c')
-rw-r--r-- | src/pidfile.c | 245 |
1 files changed, 245 insertions, 0 deletions
diff --git a/src/pidfile.c b/src/pidfile.c new file mode 100644 index 0000000..07f7521 --- /dev/null +++ b/src/pidfile.c @@ -0,0 +1,245 @@ +#include "port/stdio.h" /* for snprintf */ +#include "port/string.h" /* for strdup, strcspn */ +#include "port/stdbool.h" /* for bool */ +#include "port/unistd.h" /* for getpid, open, write, read, + * unlink, lockf */ + +#include "pidfile.h" +#include "log.h" +#include "errors.h" + +#include <stdlib.h> /* for strtol */ +#include <errno.h> /* for errno */ +#include <sys/types.h> /* for pid_t, ssize_t, off_t */ +#include <sys/stat.h> /* for umask */ +#include <fcntl.h> /* for O_RDWR */ +#include <signal.h> /* for kill */ + +/* abstraction of the pid file handling in order to unclutter the + * daemon start and stop function + */ + +#define VAR_RUN_DIR "/var/run" + +void pidfile_init( struct pidfile_t *pidfile ) { + pidfile->filename[0] = '\0'; + pidfile->fd = -1; + pidfile->locked = false; + pidfile->running = false; +} + +void pidfile_set_from_daemon_name( struct pidfile_t *pidfile, const char *daemon ) { + pidfile_init( pidfile ); + snprintf( pidfile->filename, PATH_MAX, "%s/%s.pid", VAR_RUN_DIR, daemon ); +} + +void pidfile_set_from_filename( struct pidfile_t *pidfile, const char *filename ) { + pidfile_init( pidfile ); + /* make sure the filename is shorter than the POSIX.1 length */ + snprintf( pidfile->filename, PATH_MAX, "%s", filename ); +} + +bool is_daemon_running( struct pidfile_t *pidfile, pid_t *pid, error_t *error ) { + int res; + ssize_t bytes_read; + char buf[256]; + char *end_ptr; + + /* assume daemon is not running */ + pidfile->running = false; + + /* open pidfile with correct permissions */ + pidfile->fd = open( pidfile->filename, O_RDWR, 0644 ); + if( pidfile->fd < 0 ) { + if( errno == ENOENT ) { + /* this is good, pid file doesn't exist at all */ + LOG( LOG_DEBUG, "No pidfile '%s' found, daemon is not running", pidfile->filename ); + (void)close( pidfile->fd ); + *error = OK; + return pidfile->running; + } else { + LOG( LOG_EMERG, "Unable to open pidfile '%s' for reading: %s", pidfile->filename, strerror( errno ) ); + *error = ERR_INTERNAL; + return pidfile->running; + } + } + + /* try to lock the pid file (non-blocking) */ + res = lockf( pidfile->fd, F_TLOCK, (off_t)0 ); + if( res < 0 ) { + if( errno == EAGAIN ) { + /* another process locks the file already */ + LOG( LOG_DEBUG, "Another process locks the pidfile, daemon already running" ); + *error = OK; + pidfile->locked = true; + pidfile->running = true; + } else { + LOG( LOG_EMERG, "Unable to lock pidfile '%s': %s", pidfile->filename, strerror( errno ) ); + (void)close( pidfile->fd ); + *error = ERR_INTERNAL; + pidfile->running = false; + return pidfile->running; + } + } else { + pidfile->locked = false; + } + + /* try to read the pid from the file */ + bytes_read = read( pidfile->fd, buf, sizeof( buf ) - 1 ); + if( bytes_read < 0 ) { + LOG( LOG_EMERG, "Unable to read pid from pidfile '%s': %s", pidfile->filename, strerror( errno ) ); + (void)close( pidfile->fd ); + *error = ERR_INTERNAL; + return pidfile->running; + } + + /* parse the file and see if we have a valid pid on the first line */ + buf[bytes_read] = 0; + buf[strcspn( buf, "\n" )] = 0; + + errno = 0; + *pid = (pid_t)strtol( buf, &end_ptr, 10 ); + if( ( errno != 0 ) /* ERANGE or valid base */ || + ( end_ptr == NULL ) /* pre-condition for check for '\0'! */ || + ( end_ptr - buf < 1 ) /* too short */ || + ( *pid < 2 ) /* too small value */ ) { + LOG( LOG_EMERG, "pidfile '%s' contains invalid data, can't read PID from it!", pidfile->filename ); + (void)close( pidfile->fd ); + *error = ERR_INTERNAL; + return pidfile->running; + } + LOG( LOG_DEBUG, "Found PID '%lu' in pidfile", *pid ); + + /* the pid is valid, but is there a process with the pid actually running? + * (this handles the case of kill -9!) + */ + res = kill( *pid, 0 ); + if( res < 0 ) { + if( errno == ESRCH ) { + /* this is fine, process doesn't exist with this PID */ + LOG( LOG_EMERG, "Found PID '%lu' in pidfile '%s', but no such process is running. Check and manually delete the pidfile!", *pid, pidfile->filename ); + (void)close( pidfile->fd ); + *error = ERR_INTERNAL; + return pidfile->running; + } else { + LOG( LOG_EMERG, "Can't check if processor with PID '%lu' is alive: %s", *pid, strerror( errno ) ); + (void)close( pidfile->fd ); + *error = ERR_INTERNAL; + return pidfile->running; + } + } + LOG( LOG_DEBUG, "A process with PID '%lu' is already running", *pid ); + + /* process successfuly signaled, so a process with this PID exists + * (worst case, we assume, it's the daemon) + */ + (void)close( pidfile->fd ); + *error = OK; + return pidfile->running; +} + +error_t pidfile_create( struct pidfile_t *pidfile ) { + int res; + char pid_string[20]; + ssize_t bytes_writen; + + /* create or open pid file with correct permissions */ + pidfile->fd = open( pidfile->filename, O_CREAT | O_WRONLY | O_EXCL, 0644 ); + if( pidfile->fd < 0 ) { + LOG( LOG_EMERG, "Unable to open pidfile '%s' for writing: %s", pidfile->filename, strerror( errno ) ); + return ERR_INTERNAL; + } + + /* Try to lock the pid file (non-blocking) */ + res = lockf( pidfile->fd, F_TLOCK, (off_t)0 ); + if( res < 0 ) { + if( errno == EAGAIN ) { + /* another process locks the file already, this should not happen, maybe a + * race between to daemons started in parallel? + */ + LOG( LOG_EMERG, "Unable to lock pidfile '%s' after creation, daemon started in parallel?", pidfile->filename ); + (void)close( pidfile->fd ); + (void)unlink( pidfile->filename ); + return ERR_INVALID_STATE; + } else { + LOG( LOG_EMERG, "Unable to lock pidfile '%s' after creation: %s", pidfile->filename, strerror( errno ) ); + (void)close( pidfile->fd ); + (void)unlink( pidfile->filename ); + return ERR_INTERNAL; + } + } + + /* Truncate the contents of the file, so we don't get funny values + * in the pid file + */ + if( ftruncate( pidfile->fd, (off_t)0 ) < 0 ) { + LOG( LOG_EMERG, "Unable to truncate the pidfile '%s' before writing to it", pidfile->filename, strerror( errno ) ); + (void)close( pidfile->fd ); + (void)unlink( pidfile->filename ); + return ERR_INTERNAL; + } + + /* We remember the pid in the file for init scripts which rely on the pid + * to be stored here. + */ + snprintf( pid_string, 20, "%lu\n", (unsigned long)getpid( ) ); + bytes_writen = write( pidfile->fd, pid_string, strlen( pid_string ) ); + if( bytes_writen < 0 ) { + LOG( LOG_EMERG, "Unable to write PID into the pidfile '%s': %s", pidfile->filename, strerror( errno ) ); + (void)close( pidfile->fd ); + (void)unlink( pidfile->filename ); + return ERR_INTERNAL; + } else if( bytes_writen != (ssize_t)strlen( pid_string ) ) { + /* non-atomic write on files with so little data, strange, should never happen! */ + LOG( LOG_EMERG, "Non-atomic write failed when storing the PID into the pidfile '%s'", pidfile->filename ); + } + LOG( LOG_DEBUG, "Stored '%lu' into the pidfile '%s' and locked it", (unsigned long)getpid( ), pidfile->filename ); + + pidfile->locked = true; + + return OK; +} + +error_t pidfile_release( struct pidfile_t *pidfile ) { + error_t error = OK; + + LOG( LOG_DEBUG, "Releasing (unlocking/closing) pidfile '%s' (fd: %d, locked: %d)", + pidfile->filename, pidfile->fd, pidfile->locked ); + + if( pidfile->locked ) { + if( lockf( pidfile->fd, F_ULOCK, (off_t)0 ) < 0 ) { + LOG( LOG_ALERT, "Unable to unlock the pidfile '%s': %s (%d)", + pidfile->filename, strerror( errno ), errno ); + error = ERR_INTERNAL; + } + } + + if( pidfile->fd >= 0 ) { + if( close( pidfile->fd ) < 0 ) { + LOG( LOG_ALERT, "Unable to close the pidfile '%s': %s (%d)", + pidfile->filename, strerror( errno ), errno ); + error = ERR_INTERNAL; + } + pidfile->fd = -1; + } + + return error; +} + +error_t pidfile_remove( struct pidfile_t *pidfile ) { + error_t error = OK; + + LOG( LOG_DEBUG, "Removing pidfile '%s' (fd: %d, locked: %d, running: %d)", + pidfile->filename, pidfile->fd, pidfile->locked, pidfile->running ); + + + if( !pidfile->running && pidfile->fd != -1 ) { + if( unlink( pidfile->filename ) < 0 ) { + LOG( LOG_ALERT, "Unable to remove the pidfile '%s': %s (%d)", + pidfile->filename, strerror( errno ), errno ); + error = ERR_INTERNAL; + } + } + + return error; +} |