Removing mk related classes.
[pazpar2-moved-to-github.git] / src / process.c
1 /* $Id: process.c,v 1.4 2007-06-18 11:44:43 adam Exp $
2    Copyright (c) 2006-2007, Index Data.
3
4 This file is part of Pazpar2.
5
6 Pazpar2 is free software; you can redistribute it and/or modify it under
7 the terms of the GNU General Public License as published by the Free
8 Software Foundation; either version 2, or (at your option) any later
9 version.
10
11 Pazpar2 is distributed in the hope that it will be useful, but WITHOUT ANY
12 WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Pazpar2; see the file LICENSE.  If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19 02111-1307, USA.
20  */
21
22 #if HAVE_CONFIG_H
23 #include "cconfig.h"
24 #endif
25
26 #include <signal.h>
27 #include <errno.h>
28 #include <unistd.h>
29 #include <assert.h>
30 #include <stdlib.h>
31 #include <signal.h>
32 #include <sys/wait.h>
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 #include <fcntl.h>
36 #include <pwd.h>
37
38 #include <yaz/log.h>
39 #include <yaz/snprintf.h>
40 #include "pazpar2.h"
41
42 static void write_pidfile(int pid_fd)
43 {
44     if (pid_fd != -1)
45     {
46         char buf[40];
47         yaz_snprintf(buf, sizeof(buf), "%ld", (long) getpid());
48         if (ftruncate(pid_fd, 0))
49         {
50             yaz_log(YLOG_FATAL|YLOG_ERRNO, "ftruncate");
51             exit(1);
52         }
53         if (write(pid_fd, buf, strlen(buf)) != strlen(buf))
54         {
55             yaz_log(YLOG_FATAL|YLOG_ERRNO, "write");
56             exit(1);
57         }
58         close(pid_fd);
59     }
60 }
61
62 pid_t child_pid = 0;
63 void kill_child_handler(int num)
64 {
65     if (child_pid)
66         kill(child_pid, num);
67 }
68
69 int pazpar2_process(int debug, int daemon,
70                     void (*work)(void *data), void *data,
71                     const char *pidfile, const char *uid /* not yet used */)
72 {
73     int run = 1;
74     int cont = 1;
75     void (*old_sighup)(int);
76     void (*old_sigterm)(int);
77     int pid_fd = -1;
78
79     /* open pidfile .. defer write until in child and after setuid */
80     if (pidfile)
81     {
82         pid_fd = open(pidfile, O_CREAT|O_RDWR, 0666);
83         if (pid_fd == -1)
84         {
85             yaz_log(YLOG_FATAL|YLOG_ERRNO, "open %s", pidfile);
86             exit(1);
87         }
88     }
89
90     if (debug)
91     {
92         /* in debug mode.. it's quite simple */
93         write_pidfile(pid_fd);
94         work(data);
95         exit(0);
96     }
97
98     /* running in production mode. */
99     if (uid)
100     {
101         /* OK to use the non-thread version here */
102         struct passwd *pw = getpwnam(uid);
103         if (!pw)
104         {
105             yaz_log(YLOG_FATAL, "%s: Unknown user", uid);
106             exit(1);
107         }
108         if (setuid(pw->pw_uid) < 0)
109         {
110             yaz_log(YLOG_FATAL|YLOG_ERRNO, "setuid");
111             exit(1);
112         }
113     }
114
115     if (daemon)
116     {
117         /* create pipe so that parent waits until child has created
118            PID (or failed) */
119         static int hand[2]; /* hand shake for child */
120         if (pipe(hand) < 0)
121         {
122             yaz_log(YLOG_FATAL|YLOG_ERRNO, "pipe");
123             return 1;
124         }
125         switch (fork())
126         {
127         case 0: 
128             break;
129         case -1:
130             return 1;
131         default:
132             close(hand[1]);
133             while(1)
134             {
135                 char dummy[1];
136                 int res = read(hand[0], dummy, 1);
137                 if (res < 0 && errno != EINTR)
138                 {
139                     yaz_log(YLOG_FATAL|YLOG_ERRNO, "read fork handshake");
140                     break;
141                 }
142                 else if (res >= 0)
143                     break;
144             }
145             close(hand[0]);
146             _exit(0);
147         }
148         /* child */
149         close(hand[0]);
150         if (setsid() < 0)
151             return 1;
152         
153         close(0);
154         close(1);
155         close(2);
156         open("/dev/null", O_RDWR);
157         dup(0); dup(0);
158         close(hand[1]);
159     }
160
161     write_pidfile(pid_fd);
162
163     /* keep signals in their original state and make sure that some signals
164        to parent process also gets sent to the child.. 
165     */
166     old_sighup = signal(SIGHUP, kill_child_handler);
167     old_sigterm = signal(SIGTERM, kill_child_handler);
168     while (cont)
169     {
170         pid_t p = fork();
171         pid_t p1;
172         int status;
173         if (p == (pid_t) (-1))
174         {
175             
176             yaz_log(YLOG_FATAL|YLOG_ERRNO, "fork");
177             exit(1);
178         }
179         else if (p == 0)
180         {
181             /* child */
182             signal(SIGHUP, old_sighup);  /* restore */
183             signal(SIGTERM, old_sigterm);/* restore */
184
185             work(data);
186             exit(0);
187         }
188
189         /* enable signalling in kill_child_handler */
190         child_pid = p;
191         
192         p1 = wait(&status);
193
194         /* disable signalling in kill_child_handler */
195         child_pid = 0;
196
197         if (p1 != p)
198         {
199             yaz_log(YLOG_FATAL, "p1=%d != p=%d", p1, p);
200             exit(1);
201         }
202         
203         if (WIFSIGNALED(status))
204         {
205             /*  keep the child alive in case of errors, but _log_ */
206             switch(WTERMSIG(status)) {
207             case SIGILL:
208                 yaz_log(YLOG_WARN, "Received SIGILL from child %ld", (long) p);
209                 cont = 1;
210                 break;
211             case SIGABRT:
212                 yaz_log(YLOG_WARN, "Received SIGABRT from child %ld", (long) p);
213                 cont = 1;
214                 break ;
215             case SIGSEGV:
216                 yaz_log(YLOG_WARN, "Received SIGSEGV from child %ld", (long) p);
217                 cont = 1;
218                 break;
219             case SIGBUS:        
220                 yaz_log(YLOG_WARN, "Received SIGBUS from child %ld", (long) p);
221                 cont = 1;
222                 break;
223             case SIGTERM:
224                 yaz_log(YLOG_LOG, "Received SIGTERM from child %ld",
225                         (long) p);
226                 cont = 0;
227                 break;
228             default:
229                 yaz_log(YLOG_WARN, "Received SIG %d from child %ld",
230                         WTERMSIG(status), (long) p);
231                 cont = 0;
232             }
233         }
234         else if (status == 0)
235             cont = 0; /* child exited normally */
236         else
237         {   /* child exited with error */
238             yaz_log(YLOG_LOG, "Exit %d from child %ld", status, (long) p);
239             cont = 0;
240         }
241         if (cont) /* respawn slower as we get more errors */
242             sleep(1 + run/5);
243         run++;
244     }
245     return 0;
246 }
247
248 /*
249  * Local variables:
250  * c-basic-offset: 4
251  * indent-tabs-mode: nil
252  * End:
253  * vim: shiftwidth=4 tabstop=8 expandtab
254  */