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