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