1
/*
2
 * daemon.c -- turn a process into a daemon under POSIX, SYSV, BSD.
3
 *
4
 * For license terms, see the file COPYING in this directory.
5
 */
6
7
#include "config.h"
8
9
#include <stdio.h>
10
#include <errno.h>
11
#include <signal.h>
12
#include <string.h>
13
#include <sys/types.h>
14
#ifdef HAVE_SYS_WAIT_H
15
#include <sys/wait.h>
16
#endif
17
#ifdef HAVE_FCNTL_H
18
#include <fcntl.h>
19
#else /* !HAVE_FCNTL_H */
20
#ifdef HAVE_SYS_FCNTL_H
21
#include <sys/fcntl.h>
22
#endif /* HAVE_SYS_FCNTL_H */
23
#endif /* !HAVE_FCNTL_H */
24
#include <sys/stat.h>	/* get umask(2) prototyped */
25
26
#if defined(HAVE_UNISTD_H)
27
#include <unistd.h>
28
#endif
29
30
#if defined(STDC_HEADERS)
31
#include <stdlib.h>
32
#endif
33
34
#if defined(QNX)
35
#include <unix.h>
36
#endif
37
38
#if !defined(HAVE_SETSID) && defined(SIGTSTP)
39
#if defined(HAVE_TERMIOS_H)
40
#  include <termios.h>		/* for TIOCNOTTY under Linux */
41
#endif
42
43
#if !defined(TIOCNOTTY) && defined(HAVE_SGTTY_H)
44
#  include <sgtty.h>		/* for TIOCNOTTY under NEXTSTEP */
45
#endif
46
#endif /* !defined(HAVE_SETSID) && defined(SIGTSTP) */
47
48
/* BSD portability hack */
49
#if !defined(SIGCHLD) && defined(SIGCLD)
50
#define SIGCHLD	SIGCLD
51
#endif
52
53
#include "fetchmail.h"
54
#include "tunable.h"
55
56
static RETSIGTYPE
57
sigchld_handler (int sig)
58
/* process SIGCHLD to obtain the exit code of the terminating process */
59
{
60
#if 	defined(HAVE_WAITPID)				/* the POSIX way */
61
    int status;
62
63
    while (waitpid(-1, &status, WNOHANG) > 0)
64
	continue; /* swallow 'em up. */
65
#elif 	defined(HAVE_WAIT3)				/* the BSD way */
66
    pid_t pid;
67
#if defined(HAVE_UNION_WAIT) && !defined(__FreeBSD__)
68
    union wait status;
69
#else
70
    int status;
71
#endif
72
73
    while ((pid = wait3(&status, WNOHANG, 0)) > 0)
74
	continue; /* swallow 'em up. */
75
#else	/* Zooks! Nothing to do but wait(), and hope we don't block... */
76
    int status;
77
78
    wait(&status);
79
#endif
80
    lastsig = SIGCHLD;
81
    (void)sig;
82
}
83
84
RETSIGTYPE null_signal_handler(int sig) { (void)sig; }
85
86
SIGHANDLERTYPE set_signal_handler(int sig, SIGHANDLERTYPE handler)
87
/* 
88
 * This function is called by other parts of the program to
89
 * setup the signal handler after a change to the signal context.
90
 * This is done to improve robustness of the signal handling code.
91
 * It has the same prototype as signal(2).
92
 */
93
{
94
  SIGHANDLERTYPE rethandler;
95
#ifdef HAVE_SIGACTION
96
  struct sigaction sa_new, sa_old;
97
98
  memset (&sa_new, 0, sizeof sa_new);
99
  sigemptyset (&sa_new.sa_mask);
100
  sa_new.sa_handler = handler;
101
  sa_new.sa_flags = 0;
102
#ifdef SA_RESTART	/* SunOS 4.1 portability hack */
103
  /* system call should restart on all signals except SIGALRM */
104
  if (sig != SIGALRM)
105
      sa_new.sa_flags |= SA_RESTART;
106
#endif
107
#ifdef SA_NOCLDSTOP	/* SunOS 4.1 portability hack */
108
  if (sig == SIGCHLD)
109
      sa_new.sa_flags |= SA_NOCLDSTOP;
110
#endif
111
  sigaction(sig, &sa_new, &sa_old);
112
  rethandler = sa_old.sa_handler;
113
#if defined(SIGPWR)
114
  if (sig == SIGCHLD)
115
     sigaction(SIGPWR, &sa_new, NULL);
116
#endif
117
#else /* HAVE_SIGACTION */
118
  rethandler = signal(sig, handler);
119
#if defined(SIGPWR)
120
  if (sig == SIGCHLD)
121
      signal(SIGPWR, handler);
122
#endif
123
  /* system call should restart on all signals except SIGALRM */
124
  siginterrupt(sig, sig == SIGALRM);
125
#endif /* HAVE_SIGACTION */
126
  return rethandler;
127
}
128
129
void deal_with_sigchld(void)
130
{
131
  set_signal_handler(SIGCHLD, sigchld_handler);
132
}
133
134
int
135
daemonize (const char *logfile)
136
/* detach from control TTY, become process group leader, catch SIGCHLD */
137
{
138
  int fd, logfd;
139
  pid_t childpid;
140
141
  /* if we are started by init (process 1) via /etc/inittab we needn't 
142
     bother to detach from our process group context */
143
144
  if (getppid() == 1) 
145
    goto nottyDetach;
146
147
  /* Ignore BSD terminal stop signals */
148
#ifdef 	SIGTTOU
149
  set_signal_handler(SIGTTOU, SIG_IGN);
150
#endif
151
#ifdef	SIGTTIN
152
  set_signal_handler(SIGTTIN, SIG_IGN);
153
#endif
154
#ifdef	SIGTSTP
155
  set_signal_handler(SIGTSTP, SIG_IGN);
156
#endif
157
158
  /* In case we were not started in the background, fork and let
159
     the parent exit.  Guarantees that the child is not a process
160
     group leader */
161
162
  if ((childpid = fork()) < 0) {
163
    report(stderr, "fork (%s)\n", strerror(errno));
164
    return(PS_IOERR);
165
  }
166
  else if (childpid > 0) 
167
    exit(0);  /* parent */
168
169
  
170
  /* Make ourselves the leader of a new process group with no
171
     controlling terminal */
172
173
#if	defined(HAVE_SETSID)		/* POSIX */
174
  /* POSIX makes this soooo easy to do */
175
  if (setsid() < 0) {
176
    report(stderr, "setsid (%s)\n", strerror(errno));
177
    return(PS_IOERR);
178
  }
179
#elif	defined(SIGTSTP)		/* BSD */
180
  /* change process group */
181
#ifndef __EMX__
182
  setpgrp(0, getpid());
183
#endif
184
  /* lose controlling tty */
185
  if ((fd = open("/dev/tty", O_RDWR)) >= 0) {
186
    ioctl(fd, TIOCNOTTY, (char *) 0);
187
    close(fd);	/* not checking should be safe, there were no writes */
188
  }
189
#else					/* SVR3 and older */
190
  /* change process group */
191
#ifndef __EMX__
192
  setpgrp();
193
#endif
194
  
195
  /* lose controlling tty */
196
  set_signal_handler(SIGHUP, SIG_IGN);
197
  if ((childpid = fork()) < 0) {
198
    report(stderr, "fork (%s)\n", strerror(errno));
199
    return(PS_IOERR);
200
  }
201
  else if (childpid > 0) {
202
    exit(0); 	/* parent */
203
  }
204
#endif
205
206
nottyDetach:
207
208
  (void)close(0);
209
210
  /* Reopen stdin descriptor on /dev/null */
211
  if (open("/dev/null", O_RDWR) < 0) {   /* stdin */
212
    report(stderr, "cannot open /dev/null: %s\n", strerror(errno));
213
    return(PS_IOERR);
214
  }
215
216
  if (logfile)
217
  {
218
      if ((logfd = open(logfile, O_CREAT|O_WRONLY|O_APPEND, 0666)) < 0) {	/* stdout */
219
	  report(stderr, "cannot open %s: %s\n", logfile, strerror(errno));
220
	  return PS_IOERR;
221
      }
222
  } else
223
      logfd = 0;    /* else use /dev/null */
224
225
  /* Close any/all open file descriptors */
226
#if 	defined(HAVE_GETDTABLESIZE)
227
  fd = getdtablesize() - 1;
228
#elif	defined(NOFILE)
229
  fd = NOFILE - 1;
230
#else		/* make an educated guess */
231
  fd = 1023;
232
#endif
233
  while (fd >= 1) {
234
      if (fd != logfd)
235
	  close(fd);	/* not checking this should be safe, no writes */
236
      -- fd;
237
  }
238
239
  if (dup(logfd) < 0						/* stdout */
240
	  || ((logfd == 0 || logfd >= 3) && dup(logfd) < 0)) {	/* stderr */
241
      report(stderr, "dup (%s)\n", strerror(errno));
242
      return(PS_IOERR);
243
  }
244
245
#ifdef HAVE_GETCWD
246
  /* move to root directory, so we don't prevent filesystem unmounts */
247
  chdir("/");
248
#endif
249
250
  /* set our umask to something reasonable (we hope) */
251
#if defined(DEF_UMASK)
252
  umask(DEF_UMASK);
253
#else
254
  umask(022);
255
#endif
256
257
  deal_with_sigchld();
258
259
  return(0);
260
}
261
262
flag is_a_file(int fd)
263
/* is the given fd attached to a file? (used to control logging) */
264
{
265
    struct stat stbuf;
266
267
    /*
268
     * We'd like just to return 1 on (S_IFREG | S_IFBLK),
269
     * but weirdly enough, Linux ptys seem to have S_IFBLK
270
     * so this test would fail when run on an xterm.
271
     */
272
    if (isatty(fd) || fstat(fd, &stbuf))
273
	return(0);
274
    else if (stbuf.st_mode & (S_IFREG))
275
	return(1);
276
    return(0);
277
}
278
279
/* daemon.c ends here */