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