Added daemon utility (yaz_daemon).
authorAdam Dickmeiss <adam@indexdata.dk>
Mon, 18 Feb 2008 17:07:05 +0000 (17:07 +0000)
committerAdam Dickmeiss <adam@indexdata.dk>
Mon, 18 Feb 2008 17:07:05 +0000 (17:07 +0000)
include/yaz/Makefile.am
include/yaz/daemon.h [new file with mode: 0644]
src/Makefile.am
src/daemon.c [new file with mode: 0644]

index 76a8e0a..a5d4fef 100644 (file)
@@ -1,4 +1,4 @@
-## $Id: Makefile.am,v 1.50 2007-11-09 16:46:43 adam Exp $
+## $Id: Makefile.am,v 1.51 2008-02-18 17:07:05 adam Exp $
 
 noinst_HEADERS = icu_I18N.h
 
@@ -11,7 +11,7 @@ pkginclude_HEADERS= backend.h ccl.h ccl_xml.h cql.h comstack.h \
  yaz-ccl.h yaz-iconv.h yaz-util.h yaz-version.h yconfig.h proto.h \
  xmlquery.h libxml2_error.h xmltypes.h snprintf.h query-charset.h \
  mutex.h oid_db.h oid_util.h oid_std.h tokenizer.h copy_types.h \
- icu.h match_glob.h poll.h \
+ icu.h match_glob.h poll.h daemon.h \
  \
  ill.h ill-core.h item-req.h oclc-ill-req-ext.h z-accdes1.h z-accform1.h \
  z-acckrb1.h z-core.h z-date.h z-diag1.h z-espec1.h z-estask.h z-exp.h \
diff --git a/include/yaz/daemon.h b/include/yaz/daemon.h
new file mode 100644 (file)
index 0000000..981327d
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 1995-2008, Index Data
+ * All rights reserved.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of Index Data nor the names of its contributors
+ *       may be used to endorse or promote products derived from this
+ *       software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+/* $Id: daemon.h,v 1.1 2008-02-18 17:07:05 adam Exp $ */
+
+/**
+ * \file daemon.h
+ * \brief Unix daemon management
+ */
+
+#ifndef YAZ_DAEMON_H
+#define YAZ_DAEMON_H
+
+#include <stdio.h>
+#include <yaz/yconfig.h>
+
+YAZ_BEGIN_CDECL
+
+#define YAZ_DAEMON_FORK 1
+#define YAZ_DAEMON_DEBUG 2
+#define YAZ_DAEMON_KEEPALIVE 4
+
+int yaz_daemon(const char *progname,
+               unsigned int flags,
+               void (*work)(void *data), void *data,
+               const char *pidfile, const char *uid);
+
+YAZ_END_CDECL
+
+#endif
+/*
+ * Local variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ * vim: shiftwidth=4 tabstop=8 expandtab
+ */
+
index bab47ee..03dbca2 100644 (file)
@@ -1,6 +1,6 @@
 ## This file is part of the YAZ toolkit.
 ## Copyright (C) 1995-2007, Index Data, All rights reserved.
-## $Id: Makefile.am,v 1.72 2008-01-17 14:44:57 adam Exp $
+## $Id: Makefile.am,v 1.73 2008-02-18 17:07:06 adam Exp $
 
 YAZ_VERSION_INFO=3:0:0
 
@@ -95,7 +95,7 @@ libyaz_la_SOURCES=version.c options.c log.c \
   xmlquery.c http.c \
   mime.c mime.h oid_util.c tokenizer.c \
   record_conv.c retrieval.c elementset.c snprintf.c query-charset.c \
-  copy_types.c match_glob.c poll.c
+  copy_types.c match_glob.c poll.c daemon.c
 
 libyaz_la_LDFLAGS=-version-info $(YAZ_VERSION_INFO)
 
diff --git a/src/daemon.c b/src/daemon.c
new file mode 100644 (file)
index 0000000..a3a6ab1
--- /dev/null
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 1995-2008, Index Data ApS
+ * See the file LICENSE for details.
+ *
+ * $Id: daemon.c,v 1.1 2008-02-18 17:07:06 adam Exp $
+ */
+
+/**
+ * \file
+ * \brief Unix daemon management
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <signal.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <sys/wait.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <pwd.h>
+
+#include <yaz/daemon.h>
+#include <yaz/log.h>
+#include <yaz/snprintf.h>
+
+static void write_pidfile(int pid_fd)
+{
+    if (pid_fd != -1)
+    {
+        char buf[40];
+        yaz_snprintf(buf, sizeof(buf), "%ld", (long) getpid());
+        if (ftruncate(pid_fd, 0))
+        {
+            yaz_log(YLOG_FATAL|YLOG_ERRNO, "ftruncate");
+            exit(1);
+        }
+        if (write(pid_fd, buf, strlen(buf)) != strlen(buf))
+        {
+            yaz_log(YLOG_FATAL|YLOG_ERRNO, "write");
+            exit(1);
+        }
+        close(pid_fd);
+    }
+}
+
+pid_t child_pid = 0;
+static void kill_child_handler(int num)
+{
+    if (child_pid)
+        kill(child_pid, num);
+}
+
+static void keepalive(void (*work)(void *data), void *data)
+{
+    int run = 1;
+    int cont = 1;
+    void (*old_sighup)(int);
+    void (*old_sigterm)(int);
+    
+    /* keep signals in their original state and make sure that some signals
+       to parent process also gets sent to the child.. 
+    */
+    old_sighup = signal(SIGHUP, kill_child_handler);
+    old_sigterm = signal(SIGTERM, kill_child_handler);
+    while (cont)
+    {
+        pid_t p = fork();
+        pid_t p1;
+        int status;
+        if (p == (pid_t) (-1))
+        {
+            
+            yaz_log(YLOG_FATAL|YLOG_ERRNO, "fork");
+            exit(1);
+        }
+        else if (p == 0)
+        {
+                /* child */
+            signal(SIGHUP, old_sighup);  /* restore */
+            signal(SIGTERM, old_sigterm);/* restore */
+            
+            work(data);
+            exit(0);
+        }
+        
+        /* enable signalling in kill_child_handler */
+        child_pid = p;
+        
+        p1 = wait(&status);
+        
+        /* disable signalling in kill_child_handler */
+        child_pid = 0;
+        
+        if (p1 != p)
+        {
+            yaz_log(YLOG_FATAL, "p1=%d != p=%d", p1, p);
+            exit(1);
+        }
+        
+        if (WIFSIGNALED(status))
+        {
+            /*  keep the child alive in case of errors, but _log_ */
+            switch(WTERMSIG(status)) {
+            case SIGILL:
+                yaz_log(YLOG_WARN, "Received SIGILL from child %ld", (long) p);
+                cont = 1;
+                break;
+            case SIGABRT:
+                yaz_log(YLOG_WARN, "Received SIGABRT from child %ld", (long) p);
+                cont = 1;
+                break ;
+            case SIGSEGV:
+                yaz_log(YLOG_WARN, "Received SIGSEGV from child %ld", (long) p);
+                cont = 1;
+                break;
+            case SIGBUS:        
+                yaz_log(YLOG_WARN, "Received SIGBUS from child %ld", (long) p);
+                cont = 1;
+                break;
+            case SIGTERM:
+                yaz_log(YLOG_LOG, "Received SIGTERM from child %ld",
+                        (long) p);
+                cont = 0;
+                break;
+            default:
+                yaz_log(YLOG_WARN, "Received SIG %d from child %ld",
+                        WTERMSIG(status), (long) p);
+                cont = 0;
+                }
+            }
+            else if (status == 0)
+                cont = 0; /* child exited normally */
+            else
+            {   /* child exited with error */
+                yaz_log(YLOG_LOG, "Exit %d from child %ld", status, (long) p);
+                cont = 0;
+            }
+        if (cont) /* respawn slower as we get more errors */
+                sleep(1 + run/5);
+        run++;
+    }
+}
+
+int yaz_daemon(const char *progname,
+               unsigned int flags,
+               void (*work)(void *data), void *data,
+               const char *pidfile, const char *uid)
+{
+    int pid_fd = -1;
+
+    /* open pidfile .. defer write until in child and after setuid */
+    if (pidfile)
+    {
+        pid_fd = open(pidfile, O_CREAT|O_RDWR, 0666);
+        if (pid_fd == -1)
+        {
+            yaz_log(YLOG_FATAL|YLOG_ERRNO, "open %s", pidfile);
+            exit(1);
+        }
+    }
+
+    if (flags & YAZ_DAEMON_DEBUG)
+    {
+        /* in debug mode.. it's quite simple */
+        write_pidfile(pid_fd);
+        work(data);
+        exit(0);
+    }
+
+    /* running in production mode. */
+    if (uid)
+    {
+        /* OK to use the non-thread version here */
+        struct passwd *pw = getpwnam(uid);
+        if (!pw)
+        {
+            yaz_log(YLOG_FATAL, "%s: Unknown user", uid);
+            exit(1);
+        }
+        if (setuid(pw->pw_uid) < 0)
+        {
+            yaz_log(YLOG_FATAL|YLOG_ERRNO, "setuid");
+            exit(1);
+        }
+    }
+
+    if (flags & YAZ_DAEMON_FORK)
+    {
+        /* create pipe so that parent waits until child has created
+           PID (or failed) */
+        static int hand[2]; /* hand shake for child */
+        if (pipe(hand) < 0)
+        {
+            yaz_log(YLOG_FATAL|YLOG_ERRNO, "pipe");
+            return 1;
+        }
+        switch (fork())
+        {
+        case 0: 
+            break;
+        case -1:
+            return 1;
+        default:
+            close(hand[1]);
+            while(1)
+            {
+                char dummy[1];
+                int res = read(hand[0], dummy, 1);
+                if (res < 0 && errno != EINTR)
+                {
+                    yaz_log(YLOG_FATAL|YLOG_ERRNO, "read fork handshake");
+                    break;
+                }
+                else if (res >= 0)
+                    break;
+            }
+            close(hand[0]);
+            _exit(0);
+        }
+        /* child */
+        close(hand[0]);
+        if (setsid() < 0)
+            return 1;
+        
+        close(0);
+        close(1);
+        close(2);
+        open("/dev/null", O_RDWR);
+        dup(0); dup(0);
+        close(hand[1]);
+    }
+
+    write_pidfile(pid_fd);
+
+    if (flags & YAZ_DAEMON_KEEPALIVE)
+    {
+        keepalive(work, data);
+    }
+    else
+    {
+        work(data);
+    }
+    return 0;
+}
+
+/*
+ * Local variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ * vim: shiftwidth=4 tabstop=8 expandtab
+ */