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