Added daemon utility (yaz_daemon).
[yaz-moved-to-github.git] / src / daemon.c
1 /*
2  * Copyright (C) 1995-2008, Index Data ApS
3  * See the file LICENSE for details.
4  *
5  * $Id: daemon.c,v 1.1 2008-02-18 17:07:06 adam Exp $
6  */
7
8 /**
9  * \file
10  * \brief Unix daemon management
11  */
12
13 #if HAVE_CONFIG_H
14 #include "config.h"
15 #endif
16
17 #include <signal.h>
18 #include <string.h>
19 #include <errno.h>
20 #include <unistd.h>
21 #include <assert.h>
22 #include <stdlib.h>
23 #include <signal.h>
24 #include <sys/wait.h>
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <fcntl.h>
28 #include <pwd.h>
29
30 #include <yaz/daemon.h>
31 #include <yaz/log.h>
32 #include <yaz/snprintf.h>
33
34 static void write_pidfile(int pid_fd)
35 {
36     if (pid_fd != -1)
37     {
38         char buf[40];
39         yaz_snprintf(buf, sizeof(buf), "%ld", (long) getpid());
40         if (ftruncate(pid_fd, 0))
41         {
42             yaz_log(YLOG_FATAL|YLOG_ERRNO, "ftruncate");
43             exit(1);
44         }
45         if (write(pid_fd, buf, strlen(buf)) != strlen(buf))
46         {
47             yaz_log(YLOG_FATAL|YLOG_ERRNO, "write");
48             exit(1);
49         }
50         close(pid_fd);
51     }
52 }
53
54 pid_t child_pid = 0;
55 static void kill_child_handler(int num)
56 {
57     if (child_pid)
58         kill(child_pid, num);
59 }
60
61 static void keepalive(void (*work)(void *data), void *data)
62 {
63     int run = 1;
64     int cont = 1;
65     void (*old_sighup)(int);
66     void (*old_sigterm)(int);
67     
68     /* keep signals in their original state and make sure that some signals
69        to parent process also gets sent to the child.. 
70     */
71     old_sighup = signal(SIGHUP, kill_child_handler);
72     old_sigterm = signal(SIGTERM, kill_child_handler);
73     while (cont)
74     {
75         pid_t p = fork();
76         pid_t p1;
77         int status;
78         if (p == (pid_t) (-1))
79         {
80             
81             yaz_log(YLOG_FATAL|YLOG_ERRNO, "fork");
82             exit(1);
83         }
84         else if (p == 0)
85         {
86                 /* child */
87             signal(SIGHUP, old_sighup);  /* restore */
88             signal(SIGTERM, old_sigterm);/* restore */
89             
90             work(data);
91             exit(0);
92         }
93         
94         /* enable signalling in kill_child_handler */
95         child_pid = p;
96         
97         p1 = wait(&status);
98         
99         /* disable signalling in kill_child_handler */
100         child_pid = 0;
101         
102         if (p1 != p)
103         {
104             yaz_log(YLOG_FATAL, "p1=%d != p=%d", p1, p);
105             exit(1);
106         }
107         
108         if (WIFSIGNALED(status))
109         {
110             /*  keep the child alive in case of errors, but _log_ */
111             switch(WTERMSIG(status)) {
112             case SIGILL:
113                 yaz_log(YLOG_WARN, "Received SIGILL from child %ld", (long) p);
114                 cont = 1;
115                 break;
116             case SIGABRT:
117                 yaz_log(YLOG_WARN, "Received SIGABRT from child %ld", (long) p);
118                 cont = 1;
119                 break ;
120             case SIGSEGV:
121                 yaz_log(YLOG_WARN, "Received SIGSEGV from child %ld", (long) p);
122                 cont = 1;
123                 break;
124             case SIGBUS:        
125                 yaz_log(YLOG_WARN, "Received SIGBUS from child %ld", (long) p);
126                 cont = 1;
127                 break;
128             case SIGTERM:
129                 yaz_log(YLOG_LOG, "Received SIGTERM from child %ld",
130                         (long) p);
131                 cont = 0;
132                 break;
133             default:
134                 yaz_log(YLOG_WARN, "Received SIG %d from child %ld",
135                         WTERMSIG(status), (long) p);
136                 cont = 0;
137                 }
138             }
139             else if (status == 0)
140                 cont = 0; /* child exited normally */
141             else
142             {   /* child exited with error */
143                 yaz_log(YLOG_LOG, "Exit %d from child %ld", status, (long) p);
144                 cont = 0;
145             }
146         if (cont) /* respawn slower as we get more errors */
147                 sleep(1 + run/5);
148         run++;
149     }
150 }
151
152 int yaz_daemon(const char *progname,
153                unsigned int flags,
154                void (*work)(void *data), void *data,
155                const char *pidfile, const char *uid)
156 {
157     int pid_fd = -1;
158
159     /* open pidfile .. defer write until in child and after setuid */
160     if (pidfile)
161     {
162         pid_fd = open(pidfile, O_CREAT|O_RDWR, 0666);
163         if (pid_fd == -1)
164         {
165             yaz_log(YLOG_FATAL|YLOG_ERRNO, "open %s", pidfile);
166             exit(1);
167         }
168     }
169
170     if (flags & YAZ_DAEMON_DEBUG)
171     {
172         /* in debug mode.. it's quite simple */
173         write_pidfile(pid_fd);
174         work(data);
175         exit(0);
176     }
177
178     /* running in production mode. */
179     if (uid)
180     {
181         /* OK to use the non-thread version here */
182         struct passwd *pw = getpwnam(uid);
183         if (!pw)
184         {
185             yaz_log(YLOG_FATAL, "%s: Unknown user", uid);
186             exit(1);
187         }
188         if (setuid(pw->pw_uid) < 0)
189         {
190             yaz_log(YLOG_FATAL|YLOG_ERRNO, "setuid");
191             exit(1);
192         }
193     }
194
195     if (flags & YAZ_DAEMON_FORK)
196     {
197         /* create pipe so that parent waits until child has created
198            PID (or failed) */
199         static int hand[2]; /* hand shake for child */
200         if (pipe(hand) < 0)
201         {
202             yaz_log(YLOG_FATAL|YLOG_ERRNO, "pipe");
203             return 1;
204         }
205         switch (fork())
206         {
207         case 0: 
208             break;
209         case -1:
210             return 1;
211         default:
212             close(hand[1]);
213             while(1)
214             {
215                 char dummy[1];
216                 int res = read(hand[0], dummy, 1);
217                 if (res < 0 && errno != EINTR)
218                 {
219                     yaz_log(YLOG_FATAL|YLOG_ERRNO, "read fork handshake");
220                     break;
221                 }
222                 else if (res >= 0)
223                     break;
224             }
225             close(hand[0]);
226             _exit(0);
227         }
228         /* child */
229         close(hand[0]);
230         if (setsid() < 0)
231             return 1;
232         
233         close(0);
234         close(1);
235         close(2);
236         open("/dev/null", O_RDWR);
237         dup(0); dup(0);
238         close(hand[1]);
239     }
240
241     write_pidfile(pid_fd);
242
243     if (flags & YAZ_DAEMON_KEEPALIVE)
244     {
245         keepalive(work, data);
246     }
247     else
248     {
249         work(data);
250     }
251     return 0;
252 }
253
254 /*
255  * Local variables:
256  * c-basic-offset: 4
257  * indent-tabs-mode: nil
258  * End:
259  * vim: shiftwidth=4 tabstop=8 expandtab
260  */