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