YAZ  5.23.1
daemon.c
Go to the documentation of this file.
1 /* This file is part of the YAZ toolkit.
2  * Copyright (C) Index Data
3  * See the file LICENSE for details.
4  */
5 
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 #include <fcntl.h>
31 
32 #if HAVE_PWD_H
33 #include <pwd.h>
34 #endif
35 
36 #if HAVE_SYS_PRCTL_H
37 #include <sys/prctl.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)) != (int) strlen(buf))
57  {
58  yaz_log(YLOG_FATAL|YLOG_ERRNO, "write");
59  exit(1);
60  }
61  close(pid_fd);
62  }
63 }
64 
65 int child_got_signal_from_us = 0;
66 pid_t child_pid = 0;
67 static void normal_stop_handler(int num)
68 {
69  if (child_pid)
70  {
71  /* relay signal to child */
72  kill(child_pid, num);
73  }
74 }
75 
76 static void log_reopen_handler(int num)
77 {
79  if (child_pid)
80  kill(child_pid, num);
81 }
82 
83 static void sigusr2_handler(int num)
84 {
85  child_got_signal_from_us = 1;
86 }
87 
88 static pid_t keepalive_pid = 0;
89 
90 static void keepalive(void (*work)(void *data), void *data)
91 {
92  int no_sigill = 0;
93  int no_sigabrt = 0;
94  int no_sigsegv = 0;
95  int no_sigbus = 0;
96  int run = 1;
97  int cont = 1;
98  void (*old_sigterm)(int);
99  void (*old_sigusr1)(int);
100  struct sigaction sa2, sa1;
101 
102  keepalive_pid = getpid();
103 
104  /* keep signals in their original state and make sure that some signals
105  to parent process also gets sent to the child.. */
106  old_sigterm = signal(SIGTERM, normal_stop_handler);
107  old_sigusr1 = signal(SIGUSR1, normal_stop_handler);
108 
109  sigemptyset(&sa2.sa_mask);
110  sa2.sa_handler = sigusr2_handler;
111  sa2.sa_flags = 0;
112  sigaction(SIGUSR2, &sa2, &sa1);
113 
114  while (cont && !child_got_signal_from_us)
115  {
116  pid_t p = fork();
117  pid_t p1;
118  int status;
119  if (p == (pid_t) (-1))
120  {
121  yaz_log(YLOG_FATAL|YLOG_ERRNO, "fork");
122  exit(1);
123  }
124  else if (p == 0)
125  {
126  /* child */
127  signal(SIGTERM, old_sigterm);/* restore */
128  signal(SIGUSR1, old_sigusr1);/* restore */
129  sigaction(SIGUSR2, &sa1, NULL);
130 
131  work(data);
132  exit(0);
133  }
134 
135  /* enable signalling in kill_child_handler */
136  child_pid = p;
137 
138  p1 = waitpid(p, &status, 0);
139 
140  /* disable signalling in kill_child_handler */
141  child_pid = 0;
142 
143  if (p1 == (pid_t)(-1))
144  {
145  if (errno != EINTR)
146  {
147  yaz_log(YLOG_FATAL|YLOG_ERRNO, "waitpid");
148  break;
149  }
150  continue;
151  }
152  if (p1 != p)
153  {
154  yaz_log(YLOG_FATAL, "p1=%d != p=%d", p1, p);
155  break;
156  }
157  if (WIFSIGNALED(status))
158  {
159  /* keep the child alive in case of errors, but _log_ */
160  switch (WTERMSIG(status))
161  {
162  case SIGILL:
163  yaz_log(YLOG_WARN, "Received SIGILL from child %ld", (long) p);
164  cont = 1;
165  no_sigill++;
166  break;
167  case SIGABRT:
168  yaz_log(YLOG_WARN, "Received SIGABRT from child %ld", (long) p);
169  cont = 1;
170  no_sigabrt++;
171  break ;
172  case SIGSEGV:
173  yaz_log(YLOG_WARN, "Received SIGSEGV from child %ld", (long) p);
174  cont = 1;
175  ++no_sigsegv;
176  break;
177  case SIGBUS:
178  yaz_log(YLOG_WARN, "Received SIGBUS from child %ld", (long) p);
179  cont = 1;
180  no_sigbus++;
181  break;
182  case SIGTERM:
183  yaz_log(YLOG_LOG, "Received SIGTERM from child %ld",
184  (long) p);
185  cont = 0;
186  break;
187  default:
188  yaz_log(YLOG_WARN, "Received SIG %d from child %ld",
189  WTERMSIG(status), (long) p);
190  cont = 0;
191  }
192  }
193  else if (WIFEXITED(status))
194  {
195  cont = 0;
196  if (WEXITSTATUS(status) != 0)
197  { /* child exited with error */
198  yaz_log(YLOG_LOG, "Exit %d from child %ld",
199  WEXITSTATUS(status), (long) p);
200  }
201  }
202  if (cont) /* respawn slower as we get more errors */
203  sleep(1 + run/5);
204  run++;
205  }
206  if (no_sigill)
207  yaz_log(YLOG_WARN, "keepalive stop. %d SIGILL signal(s)", no_sigill);
208  if (no_sigabrt)
209  yaz_log(YLOG_WARN, "keepalive stop. %d SIGABRT signal(s)", no_sigabrt);
210  if (no_sigsegv)
211  yaz_log(YLOG_WARN, "keepalive stop. %d SIGSEGV signal(s)", no_sigsegv);
212  if (no_sigbus)
213  yaz_log(YLOG_WARN, "keepalive stop. %d SIGBUS signal(s)", no_sigbus);
214  yaz_log(YLOG_LOG, "keepalive stop");
215 }
216 #endif
217 
218 void yaz_daemon_stop(void)
219 {
220 #if HAVE_PWD_H
221  if (keepalive_pid)
222  kill(keepalive_pid, SIGUSR2); /* invoke immediate_exit_handler */
223 #endif
224 }
225 
226 
227 int yaz_daemon(const char *progname,
228  unsigned int flags,
229  void (*work)(void *data), void *data,
230  const char *pidfile, const char *uid)
231 {
232 #if HAVE_PWD_H
233  int pid_fd = -1;
234 
235  /* open pidfile .. defer write until in child and after setuid */
236  if (pidfile)
237  {
238  pid_fd = open(pidfile, O_CREAT|O_RDWR, 0666);
239  if (pid_fd == -1)
240  {
241  yaz_log(YLOG_FATAL|YLOG_ERRNO, "open %s", pidfile);
242  exit(1);
243  }
244  }
245 
246  if (flags & YAZ_DAEMON_DEBUG)
247  {
248  /* in debug mode.. it's quite simple */
249  write_pidfile(pid_fd);
250  work(data);
251  exit(0);
252  }
253 
254  /* running in production mode. */
255  if (uid)
256  {
257  /* OK to use the non-thread version here */
258  struct passwd *pw = getpwnam(uid);
259  if (!pw)
260  {
261  yaz_log(YLOG_FATAL, "%s: Unknown user", uid);
262  exit(1);
263  }
264  if (flags & YAZ_DAEMON_LOG_REOPEN)
265  {
266  FILE *f = yaz_log_file();
267  if (f)
268  {
269  if (fchown(fileno(f), pw->pw_uid, -1))
270  yaz_log(YLOG_WARN|YLOG_ERRNO, "fchown logfile");
271  }
272  }
273  if (setuid(pw->pw_uid) < 0)
274  {
275  yaz_log(YLOG_FATAL|YLOG_ERRNO, "setuid");
276  exit(1);
277  }
278  /* Linux don't produce core dumps evern if the limit is right and
279  files are writable.. This fixes this. See prctl(2) */
280 #if HAVE_SYS_PRCTL_H
281 #ifdef PR_SET_DUMPABLE
282  prctl(PR_SET_DUMPABLE, 1, 0, 0);
283 #endif
284 #endif
285  }
286 
287  if (flags & YAZ_DAEMON_FORK)
288  {
289  /* create pipe so that parent waits until child has created
290  PID (or failed) */
291  static int hand[2]; /* hand shake for child */
292  if (pipe(hand) < 0)
293  {
294  yaz_log(YLOG_FATAL|YLOG_ERRNO, "pipe");
295  return 1;
296  }
297  switch (fork())
298  {
299  case 0:
300  break;
301  case -1:
302  return 1;
303  default:
304  close(hand[1]);
305  while(1)
306  {
307  char dummy[1];
308  int res = read(hand[0], dummy, 1);
309  if (res < 0 && errno != EINTR)
310  {
311  yaz_log(YLOG_FATAL|YLOG_ERRNO, "read fork handshake");
312  break;
313  }
314  else if (res >= 0)
315  break;
316  }
317  close(hand[0]);
318  _exit(0);
319  }
320  /* child */
321  close(hand[0]);
322  if (setsid() < 0)
323  return 1;
324 
325  close(0);
326  close(1);
327  close(2);
328  open("/dev/null", O_RDWR);
329  if (dup(0) == -1)
330  return 1;
331  if (dup(0) == -1)
332  return 1;
333  close(hand[1]);
334  }
335 
336  write_pidfile(pid_fd);
337 
338  if (flags & YAZ_DAEMON_LOG_REOPEN)
339  {
340  signal(SIGHUP, log_reopen_handler);
341  }
342  if (flags & YAZ_DAEMON_KEEPALIVE)
343  {
344  keepalive(work, data);
345  }
346  else
347  {
348  work(data);
349  }
350  return 0;
351 #else
352  work(data);
353  return 0;
354 #endif
355 }
356 
357 /*
358  * Local variables:
359  * c-basic-offset: 4
360  * c-file-style: "Stroustrup"
361  * indent-tabs-mode: nil
362  * End:
363  * vim: shiftwidth=4 tabstop=8 expandtab
364  */
365 
static void normal_stop_handler(int num)
Definition: statserv.c:1260
void yaz_log_reopen()
reopen current log file (unless disabled or stderr)
Definition: log.c:389
Unix daemon management.
Header for errno utilities.
#define YAZ_DAEMON_DEBUG
Definition: daemon.h:42
#define YLOG_LOG
log level: log (regular)
Definition: log.h:48
#define YLOG_FATAL
log level: fatal
Definition: log.h:42
void yaz_daemon_stop(void)
stop daemon - stop parent process
Definition: daemon.c:218
#define YAZ_DAEMON_LOG_REOPEN
Definition: daemon.h:44
#define YAZ_DAEMON_KEEPALIVE
Definition: daemon.h:43
#define YLOG_ERRNO
log level: append system error message
Definition: log.h:50
static const char * progname(const char *argv0)
Definition: test.c:40
FILE * yaz_log_file(void)
returns FILE handle for log or NULL if no file is in use
Definition: log.c:136
#define YLOG_WARN
log level: warning
Definition: log.h:46
void yaz_log(int level, const char *fmt,...)
Writes log message.
Definition: log.c:485
void yaz_snprintf(char *buf, size_t size, const char *fmt,...)
Definition: snprintf.c:31
Logging utility.
#define YAZ_DAEMON_FORK
Definition: daemon.h:41
Header for config file reading utilities.
int yaz_daemon(const char *progname, unsigned int flags, void(*work)(void *data), void *data, const char *pidfile, const char *uid)
daemon utility.
Definition: daemon.c:227