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