Simple refactor due to yaz_match_xsd_element 0 check YAZ-822
[yaz-moved-to-github.git] / src / daemon.c
1 /* This file is part of the YAZ toolkit.
2  * Copyright (C) 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         kill(child_pid, num);
73     }
74 }
75
76 static void log_reopen_handler(int num)
77 {
78     yaz_log_reopen();
79     if (child_pid)
80         kill(child_pid, num);
81 }
82
83 static void sigusr2_handler(int num)
84 {
85     child_got_signal_from_us = 1;
86 }
87
88 static pid_t keepalive_pid = 0;
89
90 static void keepalive(void (*work)(void *data), void *data)
91 {
92     int no_sigill = 0;
93     int no_sigabrt = 0;
94     int no_sigsegv = 0;
95     int no_sigbus = 0;
96     int run = 1;
97     int cont = 1;
98     void (*old_sigterm)(int);
99     void (*old_sigusr1)(int);
100     struct sigaction sa2, sa1;
101
102     keepalive_pid = getpid();
103
104     /* keep signals in their original state and make sure that some signals
105        to parent process also gets sent to the child..  */
106     old_sigterm = signal(SIGTERM, normal_stop_handler);
107     old_sigusr1 = signal(SIGUSR1, normal_stop_handler);
108
109     sigemptyset(&sa2.sa_mask);
110     sa2.sa_handler = sigusr2_handler;
111     sa2.sa_flags = 0;
112     sigaction(SIGUSR2, &sa2, &sa1);
113
114     while (cont && !child_got_signal_from_us)
115     {
116         pid_t p = fork();
117         pid_t p1;
118         int status;
119         if (p == (pid_t) (-1))
120         {
121             yaz_log(YLOG_FATAL|YLOG_ERRNO, "fork");
122             exit(1);
123         }
124         else if (p == 0)
125         {
126             /* child */
127             signal(SIGTERM, old_sigterm);/* restore */
128             signal(SIGUSR1, old_sigusr1);/* restore */
129             sigaction(SIGUSR2, &sa1, NULL);
130
131             work(data);
132             exit(0);
133         }
134
135         /* enable signalling in kill_child_handler */
136         child_pid = p;
137
138         p1 = waitpid(p, &status, 0);
139
140         /* disable signalling in kill_child_handler */
141         child_pid = 0;
142
143         if (p1 == (pid_t)(-1))
144         {
145             if (errno != EINTR)
146             {
147                 yaz_log(YLOG_FATAL|YLOG_ERRNO, "waitpid");
148                 break;
149             }
150             continue;
151         }
152         if (p1 != p)
153         {
154             yaz_log(YLOG_FATAL, "p1=%d != p=%d", p1, p);
155             break;
156         }
157         if (WIFSIGNALED(status))
158         {
159             /*  keep the child alive in case of errors, but _log_ */
160             switch (WTERMSIG(status))
161             {
162             case SIGILL:
163                 yaz_log(YLOG_WARN, "Received SIGILL from child %ld", (long) p);
164                 cont = 1;
165                 no_sigill++;
166                 break;
167             case SIGABRT:
168                 yaz_log(YLOG_WARN, "Received SIGABRT from child %ld", (long) p);
169                 cont = 1;
170                 no_sigabrt++;
171                 break ;
172             case SIGSEGV:
173                 yaz_log(YLOG_WARN, "Received SIGSEGV from child %ld", (long) p);
174                 cont = 1;
175                 ++no_sigsegv;
176                 break;
177             case SIGBUS:
178                 yaz_log(YLOG_WARN, "Received SIGBUS from child %ld", (long) p);
179                 cont = 1;
180                 no_sigbus++;
181                 break;
182             case SIGTERM:
183                 yaz_log(YLOG_LOG, "Received SIGTERM from child %ld",
184                         (long) p);
185                 cont = 0;
186                 break;
187             default:
188                 yaz_log(YLOG_WARN, "Received SIG %d from child %ld",
189                         WTERMSIG(status), (long) p);
190                 cont = 0;
191             }
192         }
193         else if (WIFEXITED(status))
194         {
195             cont = 0;
196             if (WEXITSTATUS(status) != 0)
197             {   /* child exited with error */
198                 yaz_log(YLOG_LOG, "Exit %d from child %ld",
199                         WEXITSTATUS(status), (long) p);
200             }
201         }
202         if (cont) /* respawn slower as we get more errors */
203             sleep(1 + run/5);
204         run++;
205     }
206     if (no_sigill)
207         yaz_log(YLOG_WARN, "keepalive stop. %d SIGILL signal(s)", no_sigill);
208     if (no_sigabrt)
209         yaz_log(YLOG_WARN, "keepalive stop. %d SIGABRT signal(s)", no_sigabrt);
210     if (no_sigsegv)
211         yaz_log(YLOG_WARN, "keepalive stop. %d SIGSEGV signal(s)", no_sigsegv);
212     if (no_sigbus)
213         yaz_log(YLOG_WARN, "keepalive stop. %d SIGBUS signal(s)", no_sigbus);
214     yaz_log(YLOG_LOG, "keepalive stop");
215 }
216 #endif
217
218 void yaz_daemon_stop(void)
219 {
220 #if HAVE_PWD_H
221     if (keepalive_pid)
222         kill(keepalive_pid, SIGUSR2); /* invoke immediate_exit_handler */
223 #endif
224 }
225
226
227 int yaz_daemon(const char *progname,
228                unsigned int flags,
229                void (*work)(void *data), void *data,
230                const char *pidfile, const char *uid)
231 {
232 #if HAVE_PWD_H
233     int pid_fd = -1;
234
235     /* open pidfile .. defer write until in child and after setuid */
236     if (pidfile)
237     {
238         pid_fd = open(pidfile, O_CREAT|O_RDWR, 0666);
239         if (pid_fd == -1)
240         {
241             yaz_log(YLOG_FATAL|YLOG_ERRNO, "open %s", pidfile);
242             exit(1);
243         }
244     }
245
246     if (flags & YAZ_DAEMON_DEBUG)
247     {
248         /* in debug mode.. it's quite simple */
249         write_pidfile(pid_fd);
250         work(data);
251         exit(0);
252     }
253
254     /* running in production mode. */
255     if (uid)
256     {
257         /* OK to use the non-thread version here */
258         struct passwd *pw = getpwnam(uid);
259         if (!pw)
260         {
261             yaz_log(YLOG_FATAL, "%s: Unknown user", uid);
262             exit(1);
263         }
264         if (flags & YAZ_DAEMON_LOG_REOPEN)
265         {
266             FILE *f = yaz_log_file();
267             if (f)
268             {
269                 if (fchown(fileno(f), pw->pw_uid, -1))
270                     yaz_log(YLOG_WARN|YLOG_ERRNO, "fchown logfile");
271             }
272         }
273         if (setuid(pw->pw_uid) < 0)
274         {
275             yaz_log(YLOG_FATAL|YLOG_ERRNO, "setuid");
276             exit(1);
277         }
278         /* Linux don't produce core dumps evern if the limit is right and
279            files are writable.. This fixes this. See prctl(2) */
280 #if HAVE_SYS_PRCTL_H
281 #ifdef PR_SET_DUMPABLE
282         prctl(PR_SET_DUMPABLE, 1, 0, 0);
283 #endif
284 #endif
285     }
286
287     if (flags & YAZ_DAEMON_FORK)
288     {
289         /* create pipe so that parent waits until child has created
290            PID (or failed) */
291         static int hand[2]; /* hand shake for child */
292         if (pipe(hand) < 0)
293         {
294             yaz_log(YLOG_FATAL|YLOG_ERRNO, "pipe");
295             return 1;
296         }
297         switch (fork())
298         {
299         case 0:
300             break;
301         case -1:
302             return 1;
303         default:
304             close(hand[1]);
305             while(1)
306             {
307                 char dummy[1];
308                 int res = read(hand[0], dummy, 1);
309                 if (res < 0 && errno != EINTR)
310                 {
311                     yaz_log(YLOG_FATAL|YLOG_ERRNO, "read fork handshake");
312                     break;
313                 }
314                 else if (res >= 0)
315                     break;
316             }
317             close(hand[0]);
318             _exit(0);
319         }
320         /* child */
321         close(hand[0]);
322         if (setsid() < 0)
323             return 1;
324
325         close(0);
326         close(1);
327         close(2);
328         open("/dev/null", O_RDWR);
329         if (dup(0) == -1)
330             return 1;
331         if (dup(0) == -1)
332             return 1;
333         close(hand[1]);
334     }
335
336     write_pidfile(pid_fd);
337
338     if (flags & YAZ_DAEMON_LOG_REOPEN)
339     {
340         signal(SIGHUP, log_reopen_handler);
341     }
342     if (flags & YAZ_DAEMON_KEEPALIVE)
343     {
344         keepalive(work, data);
345     }
346     else
347     {
348         work(data);
349     }
350     return 0;
351 #else
352     work(data);
353     return 0;
354 #endif
355 }
356
357 /*
358  * Local variables:
359  * c-basic-offset: 4
360  * c-file-style: "Stroustrup"
361  * indent-tabs-mode: nil
362  * End:
363  * vim: shiftwidth=4 tabstop=8 expandtab
364  */
365