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