Added option -D which puts pazpar2 in background. This improves the
[pazpar2-moved-to-github.git] / src / process.c
1 /* $Id: process.c,v 1.3 2007-06-18 11:10:20 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
40 #include "pazpar2.h"
41
42 static void write_pidfile(const char *pidfile)
43 {
44     if (pidfile)
45     {
46         FILE *f = fopen(pidfile, "w");
47         if (!f)
48         {
49             yaz_log(YLOG_ERRNO|YLOG_FATAL, "Couldn't create %s", pidfile);
50             exit(0);
51         }
52         fprintf(f, "%ld", (long) getpid());
53         fclose(f);
54     }
55 }
56
57 pid_t child_pid = 0;
58 void kill_child_handler(int num)
59 {
60     if (child_pid)
61         kill(child_pid, num);
62 }
63
64 int pazpar2_process(int debug, int daemon,
65                     void (*work)(void *data), void *data,
66                     const char *pidfile, const char *uid /* not yet used */)
67 {
68     int run = 1;
69     int cont = 1;
70     void (*old_sighup)(int);
71     void (*old_sigterm)(int);
72
73     if (debug)
74     {
75         /* in debug mode.. it's quite simple */
76         write_pidfile(pidfile);
77         work(data);
78         exit(0);
79     }
80     
81     write_pidfile(pidfile);
82     /* running in production mode. */
83     if (uid)
84     {
85         /* OK to use the non-thread version here */
86         struct passwd *pw = getpwnam(uid);
87         if (!pw)
88         {
89             yaz_log(YLOG_FATAL, "%s: Unknown user", uid);
90             exit(1);
91         }
92         if (setuid(pw->pw_uid) < 0)
93         {
94             yaz_log(YLOG_FATAL|YLOG_ERRNO, "setuid");
95             exit(1);
96         }
97     }
98
99     if (daemon)
100     {
101         /* create pipe so that parent waits until child has created
102            PID (or failed) */
103         static int hand[2]; /* hand shake for child */
104         if (pipe(hand) < 0)
105         {
106             yaz_log(YLOG_FATAL|YLOG_ERRNO, "pipe");
107             return 1;
108         }
109         switch (fork())
110         {
111         case 0: 
112             break;
113         case -1:
114             return 1;
115         default:
116             close(hand[1]);
117             while(1)
118             {
119                 char dummy[1];
120                 int res = read(hand[0], dummy, 1);
121                 if (res < 0 && errno != EINTR)
122                 {
123                     yaz_log(YLOG_FATAL|YLOG_ERRNO, "read fork handshake");
124                     break;
125                 }
126                 else if (res >= 0)
127                     break;
128             }
129             close(hand[0]);
130             _exit(0);
131         }
132         /* child */
133         close(hand[0]);
134         if (setsid() < 0)
135             return 1;
136         
137         close(0);
138         close(1);
139         close(2);
140         open("/dev/null", O_RDWR);
141         dup(0); dup(0);
142         close(hand[1]);
143     }
144     /* keep signals in their original state and make sure that some signals
145        to parent process also gets sent to the child.. 
146     */
147     old_sighup = signal(SIGHUP, kill_child_handler);
148     old_sigterm = signal(SIGTERM, kill_child_handler);
149     while (cont)
150     {
151         pid_t p = fork();
152         pid_t p1;
153         int status;
154         if (p == (pid_t) (-1))
155         {
156             
157             yaz_log(YLOG_FATAL|YLOG_ERRNO, "fork");
158             exit(1);
159         }
160         else if (p == 0)
161         {
162             /* child */
163             signal(SIGHUP, old_sighup);  /* restore */
164             signal(SIGTERM, old_sigterm);/* restore */
165
166             work(data);
167             exit(0);
168         }
169
170         /* enable signalling in kill_child_handler */
171         child_pid = p;
172         
173         p1 = wait(&status);
174
175         /* disable signalling in kill_child_handler */
176         child_pid = 0;
177
178         if (p1 != p)
179         {
180             yaz_log(YLOG_FATAL, "p1=%d != p=%d", p1, p);
181             exit(1);
182         }
183         
184         if (WIFSIGNALED(status))
185         {
186             /*  keep the child alive in case of errors, but _log_ */
187             switch(WTERMSIG(status)) {
188             case SIGILL:
189                 yaz_log(YLOG_WARN, "Received SIGILL from child %ld", (long) p);
190                 cont = 1;
191                 break;
192             case SIGABRT:
193                 yaz_log(YLOG_WARN, "Received SIGABRT from child %ld", (long) p);
194                 cont = 1;
195                 break ;
196             case SIGSEGV:
197                 yaz_log(YLOG_WARN, "Received SIGSEGV from child %ld", (long) p);
198                 cont = 1;
199                 break;
200             case SIGBUS:        
201                 yaz_log(YLOG_WARN, "Received SIGBUS from child %ld", (long) p);
202                 cont = 1;
203                 break;
204             case SIGTERM:
205                 yaz_log(YLOG_LOG, "Received SIGTERM from child %ld",
206                         (long) p);
207                 cont = 0;
208                 break;
209             default:
210                 yaz_log(YLOG_WARN, "Received SIG %d from child %ld",
211                         WTERMSIG(status), (long) p);
212                 cont = 0;
213             }
214         }
215         else if (status == 0)
216             cont = 0; /* child exited normally */
217         else
218         {   /* child exited with error */
219             yaz_log(YLOG_LOG, "Exit %d from child %ld", status, (long) p);
220             cont = 0;
221         }
222         if (cont) /* respawn slower as we get more errors */
223             sleep(1 + run/5);
224         run++;
225     }
226     return 0;
227 }
228
229 /*
230  * Local variables:
231  * c-basic-offset: 4
232  * indent-tabs-mode: nil
233  * End:
234  * vim: shiftwidth=4 tabstop=8 expandtab
235  */