daemon: exit monitor process for SIG{HUP,TERM,USR1}
[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 pid_t child_pid = 0;
66 static void kill_child_handler(int num)
67 {
68     if (child_pid)
69     {
70         kill(child_pid, num);
71         _exit(0);
72     }
73 }
74
75 static void keepalive(void (*work)(void *data), void *data)
76 {
77     int run = 1;
78     int cont = 1;
79     void (*old_sighup)(int);
80     void (*old_sigterm)(int);
81     void (*old_sigusr1)(int);
82     
83     /* keep signals in their original state and make sure that some signals
84        to parent process also gets sent to the child.. 
85     */
86     old_sighup = signal(SIGHUP, kill_child_handler);
87     old_sigterm = signal(SIGTERM, kill_child_handler);
88     old_sigusr1 = signal(SIGUSR1, kill_child_handler);
89     while (cont)
90     {
91         pid_t p = fork();
92         pid_t p1;
93         int status;
94         if (p == (pid_t) (-1))
95         {
96             
97             yaz_log(YLOG_FATAL|YLOG_ERRNO, "fork");
98             exit(1);
99         }
100         else if (p == 0)
101         {
102                 /* child */
103             signal(SIGHUP, old_sighup);  /* restore */
104             signal(SIGTERM, old_sigterm);/* restore */
105             signal(SIGUSR1, old_sigusr1);/* restore */
106             
107             work(data);
108             exit(0);
109         }
110         
111         /* enable signalling in kill_child_handler */
112         child_pid = p;
113         
114         p1 = wait(&status);
115         
116         /* disable signalling in kill_child_handler */
117         child_pid = 0;
118         
119         if (p1 != p)
120         {
121             yaz_log(YLOG_FATAL, "p1=%d != p=%d", p1, p);
122             exit(1);
123         }
124         
125         if (WIFSIGNALED(status))
126         {
127             /*  keep the child alive in case of errors, but _log_ */
128             switch(WTERMSIG(status)) {
129             case SIGILL:
130                 yaz_log(YLOG_WARN, "Received SIGILL from child %ld", (long) p);
131                 cont = 1;
132                 break;
133             case SIGABRT:
134                 yaz_log(YLOG_WARN, "Received SIGABRT from child %ld", (long) p);
135                 cont = 1;
136                 break ;
137             case SIGSEGV:
138                 yaz_log(YLOG_WARN, "Received SIGSEGV from child %ld", (long) p);
139                 cont = 1;
140                 break;
141             case SIGBUS:        
142                 yaz_log(YLOG_WARN, "Received SIGBUS from child %ld", (long) p);
143                 cont = 1;
144                 break;
145             case SIGTERM:
146                 yaz_log(YLOG_LOG, "Received SIGTERM from child %ld",
147                         (long) p);
148                 cont = 0;
149                 break;
150             default:
151                 yaz_log(YLOG_WARN, "Received SIG %d from child %ld",
152                         WTERMSIG(status), (long) p);
153                 cont = 0;
154             }
155         }
156         else if (status == 0)
157             cont = 0; /* child exited normally */
158         else
159         {   /* child exited with error */
160             yaz_log(YLOG_LOG, "Exit %d from child %ld", status, (long) p);
161             cont = 0;
162         }
163         if (cont) /* respawn slower as we get more errors */
164             sleep(1 + run/5);
165         run++;
166     }
167 }
168 #endif
169
170 int yaz_daemon(const char *progname,
171                unsigned int flags,
172                void (*work)(void *data), void *data,
173                const char *pidfile, const char *uid)
174 {
175 #if HAVE_PWD_H
176     int pid_fd = -1;
177
178     /* open pidfile .. defer write until in child and after setuid */
179     if (pidfile)
180     {
181         pid_fd = open(pidfile, O_CREAT|O_RDWR, 0666);
182         if (pid_fd == -1)
183         {
184             yaz_log(YLOG_FATAL|YLOG_ERRNO, "open %s", pidfile);
185             exit(1);
186         }
187     }
188
189     if (flags & YAZ_DAEMON_DEBUG)
190     {
191         /* in debug mode.. it's quite simple */
192         write_pidfile(pid_fd);
193         work(data);
194         exit(0);
195     }
196
197     /* running in production mode. */
198     if (uid)
199     {
200         /* OK to use the non-thread version here */
201         struct passwd *pw = getpwnam(uid);
202         if (!pw)
203         {
204             yaz_log(YLOG_FATAL, "%s: Unknown user", uid);
205             exit(1);
206         }
207         if (setuid(pw->pw_uid) < 0)
208         {
209             yaz_log(YLOG_FATAL|YLOG_ERRNO, "setuid");
210             exit(1);
211         }
212         /* Linux don't produce core dumps evern if the limit is right and
213            files are writable.. This fixes this. See prctl(2) */
214 #if HAVE_SYS_PRCTL_H
215 #ifdef PR_SET_DUMPABLE
216         prctl(PR_SET_DUMPABLE, 1, 0, 0);
217 #endif
218 #endif
219     }
220
221     if (flags & YAZ_DAEMON_FORK)
222     {
223         /* create pipe so that parent waits until child has created
224            PID (or failed) */
225         static int hand[2]; /* hand shake for child */
226         if (pipe(hand) < 0)
227         {
228             yaz_log(YLOG_FATAL|YLOG_ERRNO, "pipe");
229             return 1;
230         }
231         switch (fork())
232         {
233         case 0: 
234             break;
235         case -1:
236             return 1;
237         default:
238             close(hand[1]);
239             while(1)
240             {
241                 char dummy[1];
242                 int res = read(hand[0], dummy, 1);
243                 if (res < 0 && errno != EINTR)
244                 {
245                     yaz_log(YLOG_FATAL|YLOG_ERRNO, "read fork handshake");
246                     break;
247                 }
248                 else if (res >= 0)
249                     break;
250             }
251             close(hand[0]);
252             _exit(0);
253         }
254         /* child */
255         close(hand[0]);
256         if (setsid() < 0)
257             return 1;
258         
259         close(0);
260         close(1);
261         close(2);
262         open("/dev/null", O_RDWR);
263         if (dup(0) == -1)
264             return 1;
265         if (dup(0) == -1)
266             return 1;
267         close(hand[1]);
268     }
269
270     write_pidfile(pid_fd);
271
272     if (flags & YAZ_DAEMON_KEEPALIVE)
273     {
274         keepalive(work, data);
275     }
276     else
277     {
278         work(data);
279     }
280     return 0;
281 #else
282     work(data);
283     return 0;
284 #endif
285 }
286
287 /*
288  * Local variables:
289  * c-basic-offset: 4
290  * c-file-style: "Stroustrup"
291  * indent-tabs-mode: nil
292  * End:
293  * vim: shiftwidth=4 tabstop=8 expandtab
294  */
295