From 3119fb1ec2aa6ce7aca87844ca0581cbf3c0d193 Mon Sep 17 00:00:00 2001
From: Mattias Andrée <maandree@kth.se>
Date: Mon, 11 Jul 2016 13:35:11 +0200
Subject: Implement use of PID file
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Mattias Andrée <maandree@kth.se>
---
 src/gammad.c | 176 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 src/util.c   |  98 +++++++++++++++++++++++++++++++++
 src/util.h   |  23 ++++----
 3 files changed, 286 insertions(+), 11 deletions(-)
 create mode 100644 src/util.c

(limited to 'src')

diff --git a/src/gammad.c b/src/gammad.c
index 28411cf..ad1882e 100644
--- a/src/gammad.c
+++ b/src/gammad.c
@@ -17,7 +17,9 @@
  */
 #include <libgamma.h>
 
+#include <sys/stat.h>
 #include <errno.h>
+#include <fcntl.h>
 #include <pwd.h>
 #include <stdlib.h>
 #include <stdio.h>
@@ -63,6 +65,7 @@ static char* get_pathname(libgamma_site_state_t* site, const char* suffix)
   char* rc;
   struct passwd* pw;
   size_t n;
+  int saved_errno;
   
   if (site->site)
     {
@@ -104,7 +107,9 @@ static char* get_pathname(libgamma_site_state_t* site, const char* suffix)
   return rc;
   
  fail:
+  saved_errno = errno;
   free(name);
+  errno = saved_errno;
   return NULL;
 }
 
@@ -197,6 +202,156 @@ static char* get_crtc_name(libgamma_crtc_information_t* info, libgamma_crtc_stat
 }
 
 
+/**
+ * Check whether a PID file is outdated
+ * 
+ * @param   pidfile  The PID file
+ * @param   token    An environment variable (including both key and value)
+ *                   that must exist in the process if it is a gammad process
+ * @return           -1: An error occurred
+ *                    0: The service is already running
+ *                    1: The PID file is outdated
+ */
+static int is_pidfile_reusable(const char* pidfile, const char* token)
+{
+  /* PORTERS: /proc/$PID/environ is Linux specific */
+  
+  char temp[sizeof("/proc//environ") + 3 * sizeof(pid_t)];
+  int fd = -1, saved_errno;
+  char* content = NULL;
+  char* p;
+  char* end;
+  pid_t pid = 0;
+  size_t n;
+  
+  /* Get PID */
+  fd = open(pidfile, O_RDONLY);
+  if (fd < 0)
+    return -1;
+  content = nread(fd, &n);
+  if (content == NULL)
+    goto fail;
+  close(fd), fd = -1;
+  
+  if (('0' > content[0]) || (content[0] > '9'))
+    goto bad;
+  if ((content[0] == '0') && ('0' <= content[1]) && (content[1] <= '9'))
+    goto bad;
+  for (p = content; *p; p++)
+    if (('0' <= *p) && (*p <= '9'))
+      pid = pid * 10 + (*p & 15);
+    else
+      break;
+  if (*p++ != '\n')
+    goto bad;
+  if (*p)
+    goto bad;
+  if ((size_t)(content - p) != n)
+    goto bad;
+  sprintf(temp, "%llu", (unsigned long long)pid);
+  if (strcmp(content, temp))
+    goto bad;
+  
+  /* Validate PID */
+  sprintf(temp, "/proc/%llu/environ", (unsigned long long)pid);
+  fd = open(temp, O_RDONLY);
+  if (fd < 0)
+    return ((errno == ENOENT) || (errno == EACCES)) ? 1 : -1;
+  content = nread(fd, &n);
+  if (content == NULL)
+    goto fail;
+  close(fd), fd = -1;
+  
+  for (end = (p = content) + n; p != end; p = strchr(p, '\0') + 1)
+    if (!strcmp(p, token))
+      return 0;
+  
+  return 1;
+ bad:
+  fprintf(stderr, "%s: pid file contain invalid content: %s\n", argv0, pidfile);
+  errno = 0;
+  return -1;
+ fail:
+  saved_errno = errno;
+  free(content);
+  if (fd >= 0)
+    close(fd);
+  errno = saved_errno;
+  return -1;
+}
+
+
+/**
+ * Create PID file
+ * 
+ * @param   pidfile  The pathname of the PID file
+ * @return           Zero on success, -1 on error
+ */
+static int create_pidfile(char* pidfile)
+{
+  int fd, r, saved_errno;
+  char* p;
+  char* token;
+  
+  /* Create token used to validate the service. */
+  token = malloc(sizeof("GAMMAD_PIDFILE_TOKEN=") + strlen(pidfile));
+  if (token == NULL)
+    return -1;
+  sprintf(token, "GAMMAD_PIDFILE_TOKEN=%s", pidfile);
+  if (putenv(token))
+    return -1;
+  
+  /* Create PID file's directory. */
+  for (p = pidfile; *p == '/'; p++);
+  while ((p = strchr(p, '/')))
+    {
+      *p = '\0';
+      if (mkdir(pidfile, 0644) < 0)
+	if (errno != EEXIST)
+	  return -1;
+      *p++ = '/';
+    }
+  
+  /* Create PID file. */
+ retry:
+  fd = open(pidfile, O_CREAT | O_EXCL, 0644);
+  if (fd < 0)
+    {
+      if (errno == EINTR)
+	goto retry;
+      if (errno != EEXIST)
+	return -1;
+      r = is_pidfile_reusable(pidfile, token);
+      if (r > 0)
+	{
+	  unlink(pidfile);
+	  goto retry;
+	}
+      else if (r < 0)
+	goto fail;
+      fprintf(stderr, "%s: service is already running\n", argv0);
+      errno = 0;
+      return -1;
+    }
+  
+  /* Write PID to PID file. */
+  if (dprintf(fd, "%llu\n", (unsigned long long)getpid()) < 0)
+    goto fail;
+  
+  /* Done */
+  if (close(fd) < 0)
+    if (errno != EINTR)
+      return -1;
+  return 0;
+ fail:
+  saved_errno = errno;
+  close(fd);
+  unlink(pidfile);
+  errno = saved_errno;
+  return -1;
+}
+
+
 /**
  * Print usage information and exit
  */
@@ -210,11 +365,13 @@ static void usage(void)
 int main(int argc, char** argv)
 {
   int method = -1, gerror, rc = 1, preserve = 0;
-  char *sitename = NULL;
+  char* sitename = NULL;
   libgamma_site_state_t site;
   libgamma_partition_state_t* partitions = NULL;
   libgamma_crtc_state_t* crtcs = NULL;
   size_t i, j, n, n0;
+  char* pidpath = NULL;
+  char* socketpath = NULL;
   
   memset(&site, 0, sizeof(site));
   
@@ -246,6 +403,19 @@ int main(int argc, char** argv)
   if ((gerror = libgamma_site_initialise(&site, method, sitename)))
     goto fail_libgamma;
   
+  /* Get PID file and socket pathname */
+  if (!(pidpath = get_pidfile_pathname(&site)))
+    goto fail;
+  if (!(socketpath = get_socket_pathname(&site)))
+    goto fail;
+  
+  /* Create PID file */
+  if (create_pidfile(pidpath) < 0)
+    {
+      free(pidpath), pidpath = NULL;
+      goto fail;
+    }
+  
   /* Get partitions */
   if (site.partitions_available)
     if (!(partitions = calloc(site.partitions_available, sizeof(*partitions))))
@@ -426,6 +596,10 @@ int main(int argc, char** argv)
       libgamma_partition_destroy(partitions + i);
   free(partitions);
   libgamma_site_destroy(&site);
+  free(socketpath);
+  if (pidpath)
+    unlink(pidpath);
+  free(pidpath);
   return rc;
   /* Fail */
  fail:
diff --git a/src/util.c b/src/util.c
new file mode 100644
index 0000000..cb8c155
--- /dev/null
+++ b/src/util.c
@@ -0,0 +1,98 @@
+/**
+ * gammad -- Cooperative gamma server
+ * Copyright (C) 2016  Mattias Andrée (maandree@kth.se)
+ * 
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#include "util.h"
+
+#include <sys/stat.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+
+
+/**
+ * Duplicate a memory segment
+ * 
+ * @param   src  The memory segment, must not be `NULL`
+ * @param   n    The size of the memory segment, must not be zero
+ * @return       The duplicate of the memory segment,
+ *               `NULL` on error
+ */
+void* memdup(const void* src, size_t n)
+{
+  void* dest = malloc(n);
+  if (dest == NULL)
+    return NULL;
+  memcpy(dest, src, n);
+  return dest;
+}
+
+
+/**
+ * Read an entire file
+ * 
+ * @param   fd  The file descriptor
+ * @param   n   Output for the size of the file
+ * @return      The read content, plus a NUL byte at
+ *              the end (not counted in `*n`)
+ */
+void* nread(int fd, size_t* n)
+{
+  size_t size = 32;
+  ssize_t got;
+  struct stat st;
+  char* buf = NULL;
+  char* new;
+  int saved_errno;
+  
+  *n = 0;
+  
+  if (!fstat(fd, &st))
+    size = st.st_size <= 0 ? 32 : (size_t)(st.st_size);
+  
+  buf = malloc(size + 1);
+  if (buf == NULL)
+    return NULL;
+  
+  for (;;)
+    {
+      if (*n == size)
+	{
+	  new = realloc(buf, (size <<= 1) + 1);
+	  if (new == NULL)
+	    goto fail;
+	  buf = new;
+	}
+      
+      got = read(fd, buf + *n, size - *n);
+      if (got < 0)
+	goto fail;
+      if (got == 0)
+	break;
+      *n += (size_t)got;
+    }
+  
+  buf[*n] = '\0';
+  return buf;
+ fail:
+  saved_errno = errno;
+  free(buf);
+  errno = saved_errno;
+  return NULL;
+}
+
diff --git a/src/util.h b/src/util.h
index 4f9cbde..c44164b 100644
--- a/src/util.h
+++ b/src/util.h
@@ -15,8 +15,7 @@
  * You should have received a copy of the GNU General Public License
  * along with this library.  If not, see <http://www.gnu.org/licenses/>.
  */
-#include <stdlib.h>
-#include <string.h>
+#include <stddef.h>
 
 
 
@@ -28,12 +27,16 @@
  * @return       The duplicate of the memory segment,
  *               `NULL` on error
  */
-static inline void* memdup(const void* src, size_t n)
-{
-  void* dest = malloc(n);
-  if (dest == NULL)
-    return NULL;
-  memcpy(dest, src, n);
-  return dest;
-}
+void* memdup(const void* src, size_t n);
+
+
+/**
+ * Read an entire file
+ * 
+ * @param   fd  The file descriptor
+ * @param   n   Output for the size of the file
+ * @return      The read content, plus a NUL byte at
+ *              the end (not counted in `*n`)
+ */
+void* nread(int fd, size_t* n);
 
-- 
cgit v1.2.3-70-g09d2