Skip to content

Commit e017a91

Browse files
committed
bitcoind: Add -daemonwait option to wait for initialization
This adds a `-daemonwait` flag that does the same as `-daemon` except it, from a user perspective, backgrounds the process only after initialization is complete. This can be useful when the process launching bitcoind wants to guarantee that either the RPC server is running, or that initialization failed, before continuing. The exit code indicates the initialization result. This replaces the use of the libc function `daemon()` by a custom implementation which is inspired by the glibc implementation, but also creates a pipe from the child to the parent process for communication. An additional advantage of having our own `daemon()` implementation is that no MACOS-specific pragmas are needed anymore to silence a deprecation warning.
1 parent c3e6fde commit e017a91

File tree

5 files changed

+128
-18
lines changed

5 files changed

+128
-18
lines changed

build_msvc/bitcoin_config.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,9 @@
9292
don't. */
9393
#define HAVE_DECL_BSWAP_64 0
9494

95-
/* Define to 1 if you have the declaration of `daemon', and to 0 if you don't.
95+
/* Define to 1 if you have the declaration of `fork', and to 0 if you don't.
9696
*/
97-
#define HAVE_DECL_DAEMON 0
97+
#define HAVE_DECL_FORK 0
9898

9999
/* Define to 1 if you have the declaration of `htobe16', and to 0 if you
100100
don't. */
@@ -132,6 +132,10 @@
132132
don't. */
133133
#define HAVE_DECL_LE64TOH 0
134134

135+
/* Define to 1 if you have the declaration of `setsid', and to 0 if you don't.
136+
*/
137+
#define HAVE_DECL_SETSID 0
138+
135139
/* Define to 1 if you have the declaration of `strerror_r', and to 0 if you
136140
don't. */
137141
#define HAVE_DECL_STRERROR_R 0

configure.ac

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -922,8 +922,9 @@ AC_CHECK_DECLS([getifaddrs, freeifaddrs],,,
922922
)
923923
AC_CHECK_DECLS([strnlen])
924924

925-
dnl Check for daemon(3), unrelated to --with-daemon (although used by it)
926-
AC_CHECK_DECLS([daemon])
925+
dnl These are used for daemonization in bitcoind
926+
AC_CHECK_DECLS([fork])
927+
AC_CHECK_DECLS([setsid])
927928

928929
AC_CHECK_DECLS([pipe2])
929930

src/bitcoind.cpp

Lines changed: 111 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <util/strencodings.h>
2121
#include <util/system.h>
2222
#include <util/threadnames.h>
23+
#include <util/tokenpipe.h>
2324
#include <util/translation.h>
2425
#include <util/url.h>
2526

@@ -28,6 +29,79 @@
2829
const std::function<std::string(const char*)> G_TRANSLATION_FUN = nullptr;
2930
UrlDecodeFn* const URL_DECODE = urlDecode;
3031

32+
#if HAVE_DECL_FORK
33+
34+
/** Custom implementation of daemon(). This implements the same order of operations as glibc.
35+
* Opens a pipe to the child process to be able to wait for an event to occur.
36+
*
37+
* @returns 0 if successful, and in child process.
38+
* >0 if successful, and in parent process.
39+
* -1 in case of error (in parent process).
40+
*
41+
* In case of success, endpoint will be one end of a pipe from the child to parent process,
42+
* which can be used with TokenWrite (in the child) or TokenRead (in the parent).
43+
*/
44+
int fork_daemon(bool nochdir, bool noclose, TokenPipeEnd& endpoint)
45+
{
46+
// communication pipe with child process
47+
std::optional<TokenPipe> umbilical = TokenPipe::Make();
48+
if (!umbilical) {
49+
return -1; // pipe or pipe2 failed.
50+
}
51+
52+
int pid = fork();
53+
if (pid < 0) {
54+
return -1; // fork failed.
55+
}
56+
if (pid != 0) {
57+
// Parent process gets read end, closes write end.
58+
endpoint = umbilical->TakeReadEnd();
59+
umbilical->TakeWriteEnd().Close();
60+
61+
int status = endpoint.TokenRead();
62+
if (status != 0) { // Something went wrong while setting up child process.
63+
endpoint.Close();
64+
return -1;
65+
}
66+
67+
return pid;
68+
}
69+
// Child process gets write end, closes read end.
70+
endpoint = umbilical->TakeWriteEnd();
71+
umbilical->TakeReadEnd().Close();
72+
73+
#if HAVE_DECL_SETSID
74+
if (setsid() < 0) {
75+
exit(1); // setsid failed.
76+
}
77+
#endif
78+
79+
if (!nochdir) {
80+
if (chdir("/") != 0) {
81+
exit(1); // chdir failed.
82+
}
83+
}
84+
if (!noclose) {
85+
// Open /dev/null, and clone it into STDIN, STDOUT and STDERR to detach
86+
// from terminal.
87+
int fd = open("/dev/null", O_RDWR);
88+
if (fd >= 0) {
89+
bool err = dup2(fd, STDIN_FILENO) < 0 || dup2(fd, STDOUT_FILENO) < 0 || dup2(fd, STDERR_FILENO) < 0;
90+
// Don't close if fd<=2 to try to handle the case where the program was invoked without any file descriptors open.
91+
if (fd > 2) close(fd);
92+
if (err) {
93+
exit(1); // dup2 failed.
94+
}
95+
} else {
96+
exit(1); // open /dev/null failed.
97+
}
98+
}
99+
endpoint.TokenWrite(0); // Success
100+
return 0;
101+
}
102+
103+
#endif
104+
31105
static bool AppInit(int argc, char* argv[])
32106
{
33107
NodeContext node;
@@ -59,6 +133,14 @@ static bool AppInit(int argc, char* argv[])
59133
return true;
60134
}
61135

136+
#if HAVE_DECL_FORK
137+
// Communication with parent after daemonizing. This is used for signalling in the following ways:
138+
// - a boolean token is sent when the initialization process (all the Init* functions) have finished to indicate
139+
// that the parent process can quit, and whether it was successful/unsuccessful.
140+
// - an unexpected shutdown of the child process creates an unexpected end of stream at the parent
141+
// end, which is interpreted as failure to start.
142+
TokenPipeEnd daemon_ep;
143+
#endif
62144
util::Ref context{node};
63145
try
64146
{
@@ -105,24 +187,34 @@ static bool AppInit(int argc, char* argv[])
105187
// InitError will have been called with detailed error, which ends up on console
106188
return false;
107189
}
108-
if (args.GetBoolArg("-daemon", false)) {
109-
#if HAVE_DECL_DAEMON
110-
#if defined(MAC_OSX)
111-
#pragma GCC diagnostic push
112-
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
113-
#endif
190+
if (args.GetBoolArg("-daemon", DEFAULT_DAEMON) || args.GetBoolArg("-daemonwait", DEFAULT_DAEMONWAIT)) {
191+
#if HAVE_DECL_FORK
114192
tfm::format(std::cout, PACKAGE_NAME " starting\n");
115193

116194
// Daemonize
117-
if (daemon(1, 0)) { // don't chdir (1), do close FDs (0)
118-
return InitError(Untranslated(strprintf("daemon() failed: %s\n", strerror(errno))));
195+
switch (fork_daemon(1, 0, daemon_ep)) { // don't chdir (1), do close FDs (0)
196+
case 0: // Child: continue.
197+
// If -daemonwait is not enabled, immediately send a success token the parent.
198+
if (!args.GetBoolArg("-daemonwait", DEFAULT_DAEMONWAIT)) {
199+
daemon_ep.TokenWrite(1);
200+
daemon_ep.Close();
201+
}
202+
break;
203+
case -1: // Error happened.
204+
return InitError(Untranslated(strprintf("fork_daemon() failed: %s\n", strerror(errno))));
205+
default: { // Parent: wait and exit.
206+
int token = daemon_ep.TokenRead();
207+
if (token) { // Success
208+
exit(EXIT_SUCCESS);
209+
} else { // fRet = false or token read error (premature exit).
210+
tfm::format(std::cerr, "Error during initializaton - check debug.log for details\n");
211+
exit(EXIT_FAILURE);
212+
}
213+
}
119214
}
120-
#if defined(MAC_OSX)
121-
#pragma GCC diagnostic pop
122-
#endif
123215
#else
124216
return InitError(Untranslated("-daemon is not supported on this operating system\n"));
125-
#endif // HAVE_DECL_DAEMON
217+
#endif // HAVE_DECL_FORK
126218
}
127219
// Lock data directory after daemonization
128220
if (!AppInitLockDataDirectory())
@@ -138,6 +230,13 @@ static bool AppInit(int argc, char* argv[])
138230
PrintExceptionContinue(nullptr, "AppInit()");
139231
}
140232

233+
#if HAVE_DECL_FORK
234+
if (daemon_ep.IsOpen()) {
235+
// Signal initialization status to parent, then close pipe.
236+
daemon_ep.TokenWrite(fRet);
237+
daemon_ep.Close();
238+
}
239+
#endif
141240
if (fRet) {
142241
WaitForShutdown();
143242
}

src/init.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -577,8 +577,9 @@ void SetupServerArgs(NodeContext& node)
577577
argsman.AddArg("-rpcworkqueue=<n>", strprintf("Set the depth of the work queue to service RPC calls (default: %d)", DEFAULT_HTTP_WORKQUEUE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::RPC);
578578
argsman.AddArg("-server", "Accept command line and JSON-RPC commands", ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
579579

580-
#if HAVE_DECL_DAEMON
581-
argsman.AddArg("-daemon", "Run in the background as a daemon and accept commands", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
580+
#if HAVE_DECL_FORK
581+
argsman.AddArg("-daemon", strprintf("Run in the background as a daemon and accept commands (default: %d)", DEFAULT_DAEMON), ArgsManager::ALLOW_BOOL, OptionsCategory::OPTIONS);
582+
argsman.AddArg("-daemonwait", strprintf("Wait for initialization to be finished before exiting. This implies -daemon (default: %d)", DEFAULT_DAEMONWAIT), ArgsManager::ALLOW_BOOL, OptionsCategory::OPTIONS);
582583
#else
583584
hidden_args.emplace_back("-daemon");
584585
#endif

src/init.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99
#include <memory>
1010
#include <string>
1111

12+
//! Default value for -daemon option
13+
static constexpr bool DEFAULT_DAEMON = false;
14+
//! Default value for -daemonwait option
15+
static constexpr bool DEFAULT_DAEMONWAIT = false;
16+
1217
class ArgsManager;
1318
struct NodeContext;
1419
namespace interfaces {

0 commit comments

Comments
 (0)