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
|
/*
* Copyright (C) 2013 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Authored by: Michi Henning <michi.henning@canonical.com>
*/
#include <unity/util/internal/DaemonImpl.h>
#include <unity/util/ResourcePtr.h>
#include <dirent.h>
#include <fcntl.h>
#include <signal.h>
#include <stddef.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <cassert>
#include <sstream>
#include <vector>
using namespace std;
namespace unity
{
namespace util
{
namespace internal
{
DaemonImpl::DaemonImpl()
: close_fds_(false), reset_signals_(false), set_umask_(false)
{
}
// LCOV_EXCL_START
// This is tested, but only when coverage is disabled, because closing
// file descriptors interferes with writing the coverage results.
void DaemonImpl::close_fds() noexcept
{
close_fds_ = true;
}
// LCOV_EXCL_STOP
void DaemonImpl::reset_signals() noexcept
{
reset_signals_ = true;
}
void DaemonImpl::set_umask(mode_t mask) noexcept
{
set_umask_ = true;
umask_ = mask;
}
void DaemonImpl::set_working_directory(string const& working_directory)
{
working_directory_ = working_directory;
}
// Turn this process into a proper daemon in its own session and without a control terminal.
// Whether to close open file descriptors, reset signals to their defaults, change the umask,
// or change the working directory is determined by the setters above.
void DaemonImpl::daemonize_me()
{
// Let's start by changing the working directory because that is the most likely thing to fail. If it does
// fail, we have not modified any other properties of the calling process.
// We save the current working dir in case we need to restore it if a fork fails.
ResourcePtr<int, std::function<void(int)>> old_working_dir(
[](int fd)
{
if (fd != -1)
{
int rc __attribute__((unused))
= fchdir(fd);
close(fd);
}
}
);
if (!working_directory_.empty())
{
old_working_dir.reset(open(".", 0)); // Doesn't matter if this fails
if (chdir(working_directory_.c_str()) == -1)
{
ostringstream msg;
msg << "chdir(\"" << working_directory_.c_str() << "\") failed";
throw SyscallException(msg.str(), errno);
}
}
// Fork and let the parent exit.
switch (fork())
{
case -1:
{
// Strong exception guarantee: if working dir was changed, the old_working_dir
// destructor will try to restore it. This will work if at least one spare
// descriptor was avalable to start with. (Otherwise, old_working_dir won't
// be set and and we won't restore the previous working directory.)
throw SyscallException("fork() failed", errno); // LCOV_EXCL_LINE
}
case 0:
{
break; // Child process
}
default:
{
exit(EXIT_SUCCESS); // Parent process, we are done.
}
}
// Make us a process group leader, thereby losing the control terminal.
// No error check needed here: the only possible error is EPERM which means we are a process
// group leader already. But that's impossible because we just forked and are the child.
setsid();
// Set the umask if the caller asked for that. No error checking here because umask() cannot fail.
mode_t old_umask = 0;
if (set_umask_)
{
old_umask = umask(umask_);
}
// We are about to fork a second time, to prevent the process from re-acquiring a control terminal if it
// later opens a terminal device. Because we are a session leader now, we need to ignore SIGHUP, otherwise,
// when the parent exits after the next fork, we'll receive SIGHUP and die. We remember the previous SIGHUP
// disposition so we can restore it to what it was if the caller doesn't want all signals to be
// reset to their default behavior.
struct sigaction old_action;
memset(&old_action, 0, sizeof(old_action)); // To stop valgrind complaints
struct sigaction action;
memset(&action, 0, sizeof(old_action)); // To stop valgrind complaints
action.sa_handler = SIG_IGN;
sigaction(SIGHUP, &action, &old_action);
switch (fork())
{
case -1:
{
// LCOV_EXCL_START
if (set_umask_)
{
umask(old_umask); // Strong exception guarantee
}
sigaction(SIGHUP, &old_action, nullptr); // Strong exception guarantee
// Strong exception guarantee: if working dir was changed, the old_working_dir
// destructor will try to restore it. This will work if at least one spare
// descriptor was avalable to start with. (Otherwise, old_working_dir won't
// be set and and we won't restore the previous working directory.)
throw SyscallException("fork() failed", errno);
// LCOV_EXCL_STOP
}
case 0:
{
if (old_working_dir.has_resource())
{
close(old_working_dir.get()); // Reclaim file descriptor straight away
old_working_dir.release(); // Don't restore previous working dir once we are done
}
break; // Child process
}
default:
{
exit(EXIT_SUCCESS); // Parent process, we are done.
}
}
// From here on, we do as much of the daemonizing as we can, ignoring errors from system calls.
// That's because, now that the second fork has happened, we are committed. Any system call errors below
// are effectively impossible anyway because things like closing open files and changing signal disposition
// never fail unless the OS is seriously ill.
if (!reset_signals_ && old_action.sa_handler != action.sa_handler)
{
sigaction(SIGHUP, &old_action, nullptr); // Restore previous disposition for SIGHUP.
}
// If the caller asked for it, we reset all signals to the default behavior.
if (reset_signals_)
{
action.sa_handler = SIG_DFL;
for (int sig = 1; sig < NSIG; ++sig)
{
sigaction(sig, &action, nullptr);
}
}
// Close standard descriptors plus, if the caller asked for that, all others, and
// connect the standard file descriptors to /dev/null.
close_open_files();
int fd = open("/dev/null", O_RDWR);
assert(fd == 0);
fd = dup(fd);
assert(fd == 1);
fd = dup(fd);
assert(fd == 2);
}
// Close all open file descriptors
void DaemonImpl::close_open_files() noexcept
{
// We close the standard file descriptors first. This allows opendir() to work if we need to close
// other files and are at the descriptor limit already.
close(0);
close(1);
close(2);
// LCOV_EXCL_START // Closing file descriptors interferes with coverage reporting
if (close_fds_)
{
// Close all open files. We use /proc to figure out what files are open.
// That's more efficient than calling close() potentially tens of thousands of times,
// once for each possible descriptor up to the process limit.
char const* proc_self_fd = "/proc/self/fd";
DIR* dirp;
if ((dirp = opendir(proc_self_fd)) == nullptr)
{
return; // This should never happen but, for diligence, we handle it anyway.
}
ResourcePtr<DIR*, decltype(&closedir)> dir(dirp, closedir);
vector<int> descriptors; // We collect the file descriptors to close here
struct dirent* result_p;
while ((result_p = readdir(dir.get())) != nullptr)
{
// Try to treat the file name as a number. If it doesn't look like a number, we are looking at "." or ".." or,
// otherwise, something is seriously wrong because /proc/self/fd is supposed to contain only open file
// descriptor numbers. Rather than giving up in that case, we keep going, closing as many file descriptors as we can.
size_t pos;
int fd = std::stoi(result_p->d_name, &pos);
if (result_p->d_name[pos] == '\0') // The file name did parse as a number
{
// We can't call close() here because that would modify the directory while we are iterating
// over it, which has undefined behavior.
descriptors.push_back(fd);
}
}
for (auto fd : descriptors)
{
close(fd);
}
}
// LCOV_EXCL_STOP
}
} // namespace internal
} // namespace util
} // namespace unity
|