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