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