1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
|
/**
* Copyright © 2015, 2016 Mattias Andrée <maandree@member.fsf.org>
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
* This file is copied from <http://github.com/maandree/slibc>,
* but with unused stuff removed. It has also been slightly modified
* and changed code style (these "slight" modifications do not change
* the behaviour.)
*/
#define _POSIX_C_SOURCE 200809L
#include "daemonise.h"
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <stdarg.h>
#include <assert.h>
#include <sys/stat.h>
#include <sys/resource.h>
#define t(...) do { if (__VA_ARGS__) goto fail; } while (0)
#define S(...) (saved_errno = errno, __VA_ARGS__, errno = saved_errno)
/**
* The process's environment variables.
*/
extern char** environ;
/**
* The pidfile created by `daemonise`.
*/
static char* __pidfile = NULL;
/**
* Wrapper for `dup` create a new file descriptor
* with a number not less than 3.
*
* @param old The file descriptor to duplicate.
* @return The new file descriptor, -1 on error.
*
* @throws Any error specified for dup(3).
*/
static int
dup_at_least_3(int old)
{
int intermediary[3];
int i = 0, saved_errno;
do {
t (old = dup(old), old == -1);
assert(i < 3);
if (i >= 3)
abort();
intermediary[i++] = old;
} while (old < 3);
i--;
fail:
saved_errno = errno;
while (i--)
close(intermediary[i]);
errno = saved_errno;
return old;
}
/**
* Daemonise the process. This means to:
*
* - close all file descritors except for those to
* stdin, stdout, and stderr,
*
* - remove all custom signal handlers, and apply
* the default handlers.
*
* - unblock all signals,
*
* - remove all malformatted entries in the
* environment (this not containing an '=',)
*
* - set the umask to zero, to be ensure that all
* file permissions are set as specified,
*
* - change directory to '/', to ensure that the
* process does not block any mountpoint from being
* unmounted,
*
* - fork to become a background process,
*
* - temporarily become a session leader to ensure
* that the process does not have a controlling
* terminal.
*
* - fork again to become a child of the daemon
* supervisor, (subreaper could however be in the
* say, so one should not merely rely on this when
* writing a daemon supervisor,) (the first child
* shall exit after this,)
*
* - create, exclusively, a PID file to stop the daemon
* to be being run twice concurrently, and to let
* the daemon supervicer know the process ID of the
* daemon,
*
* - redirect stdin and stdout to /dev/null,
* as well as stderr if it is currently directed
* to a terminal, and
*
* - exit in the original process to let the daemon
* supervisor know that the daemon has been
* initialised.
*
* Before calling this function, you should remove any
* environment variable that could negatively impact
* the runtime of the process.
*
* After calling this function, you should remove
* unnecessary privileges.
*
* Do not try do continue the process in failure unless
* you make sure to only do this in the original process.
* But not that things will not necessarily be as when
* you make the function call. The process can have become
* partially deamonised.
*
* If $XDG_RUNTIME_DIR is set and is not empty, its value
* should be used instead of /run for the runtime data-files
* directory, in which the PID file is stored.
*
* This is a slibc extension.
*
* @etymology (Daemonise) the process!
*
* @param name The name of the daemon. Use a hardcoded value,
* not the process name. Must not be `NULL`.
* @param ... This is a `-1`-terminated list
* of file descritors to keep open.
* All arguments are of type `int`.
* @return Zero on success, -1 on error.
*
* @throws EEXIST The PID file already exists on the system.
* Unless your daemon supervisor removs old
* PID files, this could mean that the daemon
* has exited without removing the PID file.
* @throws Any error specified for signal(3).
* @throws Any error specified for sigemptyset(3).
* @throws Any error specified for sigprocmask(3).
* @throws Any error specified for chdir(3).
* @throws Any error specified for pipe(3).
* @throws Any error specified for dup(3).
* @throws Any error specified for dup2(3).
* @throws Any error specified for fork(3).
* @throws Any error specified for setsid(3).
* @throws Any error specified for open(3).
* @throws Any error specified for malloc(3).
*
* @since Always.
*/
int daemonise(const char* name, ...)
{
struct rlimit rlimit;
int pipe_rw[2] = { -1, -1 };
sigset_t set;
char** r;
char** w;
char* run;
int i, closeerr, fd = -1;
char* keep = NULL;
int keepmax = 0;
pid_t pid;
va_list args;
int saved_errno;
/* Find out which file descriptors not too close. */
va_start(args, name);
while ((fd = va_arg(args, int)) >= 0)
keepmax = fd;
fd = -1;
va_end(args);
keep = calloc((size_t)keepmax + 1, sizeof(char));
t (keep == NULL);
va_start(args, name);
while ((fd = va_arg(args, int)) >= 0)
keep[fd] = 1;
fd = -1;
va_end(args);
/* We assume that the maximum file descriptor is not extremely large.
* We also assume the number of file descriptors too keep is very small,
* but this does not affect us. */
/* Close all files except stdin, stdout, and stderr. */
if (getrlimit(RLIMIT_NOFILE, &rlimit))
rlimit.rlim_cur = 4 << 10;
for (i = 3; (rlim_t)i < rlimit.rlim_cur; i++)
/* File descriptors with numbers above and including
* `rlimit.rlim_cur` cannot be created. They cause EBADF. */
if ((i > keepmax) || (keep[i] == 0))
close(i);
free(keep), keep = NULL;
/* Reset all signal handlers. */
for (i = 1; i < _NSIG; i++)
if (signal(i, SIG_DFL) == SIG_ERR)
t (errno != EINVAL);
/* Set signal mask. */
t (sigemptyset(&set));
t (sigprocmask(SIG_SETMASK, &set, NULL));
/* Remove malformatted environment entires. */
if (environ) {
for (r = w = environ; *r; r++)
if (strchr(*r, '=')) /* It happens that this is not the case! (Thank you PAM!) */
*w++ = *r;
*w = NULL;
}
/* Zero umask. */
umask(0);
/* Change current working directory to '/'. */
t (chdir("/"));
/* Create a channel for letting the original process know when to exit. */
if (pipe(pipe_rw))
t ((pipe_rw[0] = pipe_rw[1] = -1));
t (fd = dup_at_least_3(pipe_rw[0]), fd == -1);
close(pipe_rw[0]);
pipe_rw[0] = fd;
t (fd = dup_at_least_3(pipe_rw[1]), fd == -1);
close(pipe_rw[1]);
pipe_rw[1] = fd;
/* Become a background process. */
t (pid = fork(), pid == -1);
close(pipe_rw[!!pid]), pipe_rw[!!pid] = 1;
if (pid)
exit(read(pipe_rw[0], &fd, (size_t)1) <= 0);
/* Temporarily become session leader. */
t (setsid() == -1);
/* Fork again. */
t (pid = fork(), pid == -1);
if (pid > 0)
exit(0);
/* Create PID file. */
run = getenv("XDG_RUNTIME_DIR");
if (run && *run) {
__pidfile = malloc(sizeof("/.pid") + (strlen(run) + strlen(name)) * sizeof(char));
t (__pidfile == NULL);
stpcpy(stpcpy(stpcpy(stpcpy(__pidfile, run), "/"), name), ".pid");
} else {
__pidfile = malloc(sizeof("/run/.pid") + strlen(name) * sizeof(char));
t (__pidfile == NULL);
stpcpy(stpcpy(stpcpy(__pidfile, "/run/"), name), ".pid");
}
fd = open(__pidfile, O_WRONLY | O_CREAT, 0644);
if (fd == -1) {
S(free(__pidfile), __pidfile = NULL);
goto fail;
}
pid = getpid();
t (dprintf(fd, "%lli\n", (long long int)pid) < 0);
t (close(fd) && (errno != EINTR));
/* Redirect to '/dev/null'. */
closeerr = (isatty(2) || (errno == EBADF));
t (fd = open("/dev/null", O_RDWR), fd == -1);
if (fd != 0) close(0);
if (fd != 1) close(1);
if (closeerr) if (fd != 2) close(2);
t (dup2(fd, 0) == -1);
t (dup2(fd, 1) == -1);
if (closeerr) t (dup2(fd, 2) == -1);
if (fd > 2)
close(fd);
fd = -1;
/* We are done! Let the original process exit. */
if ((write(pipe_rw[1], &fd, (size_t)1) <= 0) ||
(close(pipe_rw[1]) && (errno != EINTR))) {
undaemonise();
abort(); /* Do not overcomplicate things, just abort in this unlikely event. */
}
return 0;
fail:
return S(close(pipe_rw[0]), close(pipe_rw[1]), close(fd), free(keep)), -1;
}
/**
* Remove the PID file created by `daemonise`. This shall
* always be called before exiting after calling `daemonise`,
* even if it failed.
*
* This is a slibc extension.
*
* @etymology (Un)link PID file created by `(daemonise)`!
*
* @return Zero on success, -1 on error.
*
* @throws Any error specified for unlink(3).
*
* @since Always.
*/
int undaemonise(void)
{
int r, saved_errno;
if (__pidfile == NULL)
return 0;
r = unlink(__pidfile);
S(free(__pidfile), __pidfile = NULL);
return r;
}
|