diff options
-rw-r--r-- | build_msvc/bitcoin_config.h | 8 | ||||
-rw-r--r-- | configure.ac | 5 | ||||
-rw-r--r-- | src/bitcoind.cpp | 123 | ||||
-rw-r--r-- | src/init.cpp | 5 | ||||
-rw-r--r-- | src/init.h | 5 |
5 files changed, 128 insertions, 18 deletions
diff --git a/build_msvc/bitcoin_config.h b/build_msvc/bitcoin_config.h index 40a30b9749..dd01cb29eb 100644 --- a/build_msvc/bitcoin_config.h +++ b/build_msvc/bitcoin_config.h @@ -92,9 +92,9 @@ don't. */ #define HAVE_DECL_BSWAP_64 0 -/* Define to 1 if you have the declaration of `daemon', and to 0 if you don't. +/* Define to 1 if you have the declaration of `fork', and to 0 if you don't. */ -#define HAVE_DECL_DAEMON 0 +#define HAVE_DECL_FORK 0 /* Define to 1 if you have the declaration of `htobe16', and to 0 if you don't. */ @@ -132,6 +132,10 @@ don't. */ #define HAVE_DECL_LE64TOH 0 +/* Define to 1 if you have the declaration of `setsid', and to 0 if you don't. + */ +#define HAVE_DECL_SETSID 0 + /* Define to 1 if you have the declaration of `strerror_r', and to 0 if you don't. */ #define HAVE_DECL_STRERROR_R 0 diff --git a/configure.ac b/configure.ac index d99f5dd8ae..3b318cb2df 100644 --- a/configure.ac +++ b/configure.ac @@ -922,8 +922,9 @@ AC_CHECK_DECLS([getifaddrs, freeifaddrs],,, ) AC_CHECK_DECLS([strnlen]) -dnl Check for daemon(3), unrelated to --with-daemon (although used by it) -AC_CHECK_DECLS([daemon]) +dnl These are used for daemonization in bitcoind +AC_CHECK_DECLS([fork]) +AC_CHECK_DECLS([setsid]) AC_CHECK_DECLS([pipe2]) diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp index b7bcb534ef..32f06aec2c 100644 --- a/src/bitcoind.cpp +++ b/src/bitcoind.cpp @@ -20,6 +20,7 @@ #include <util/strencodings.h> #include <util/system.h> #include <util/threadnames.h> +#include <util/tokenpipe.h> #include <util/translation.h> #include <util/url.h> @@ -28,6 +29,79 @@ const std::function<std::string(const char*)> G_TRANSLATION_FUN = nullptr; UrlDecodeFn* const URL_DECODE = urlDecode; +#if HAVE_DECL_FORK + +/** Custom implementation of daemon(). This implements the same order of operations as glibc. + * Opens a pipe to the child process to be able to wait for an event to occur. + * + * @returns 0 if successful, and in child process. + * >0 if successful, and in parent process. + * -1 in case of error (in parent process). + * + * In case of success, endpoint will be one end of a pipe from the child to parent process, + * which can be used with TokenWrite (in the child) or TokenRead (in the parent). + */ +int fork_daemon(bool nochdir, bool noclose, TokenPipeEnd& endpoint) +{ + // communication pipe with child process + std::optional<TokenPipe> umbilical = TokenPipe::Make(); + if (!umbilical) { + return -1; // pipe or pipe2 failed. + } + + int pid = fork(); + if (pid < 0) { + return -1; // fork failed. + } + if (pid != 0) { + // Parent process gets read end, closes write end. + endpoint = umbilical->TakeReadEnd(); + umbilical->TakeWriteEnd().Close(); + + int status = endpoint.TokenRead(); + if (status != 0) { // Something went wrong while setting up child process. + endpoint.Close(); + return -1; + } + + return pid; + } + // Child process gets write end, closes read end. + endpoint = umbilical->TakeWriteEnd(); + umbilical->TakeReadEnd().Close(); + +#if HAVE_DECL_SETSID + if (setsid() < 0) { + exit(1); // setsid failed. + } +#endif + + if (!nochdir) { + if (chdir("/") != 0) { + exit(1); // chdir failed. + } + } + if (!noclose) { + // Open /dev/null, and clone it into STDIN, STDOUT and STDERR to detach + // from terminal. + int fd = open("/dev/null", O_RDWR); + if (fd >= 0) { + bool err = dup2(fd, STDIN_FILENO) < 0 || dup2(fd, STDOUT_FILENO) < 0 || dup2(fd, STDERR_FILENO) < 0; + // Don't close if fd<=2 to try to handle the case where the program was invoked without any file descriptors open. + if (fd > 2) close(fd); + if (err) { + exit(1); // dup2 failed. + } + } else { + exit(1); // open /dev/null failed. + } + } + endpoint.TokenWrite(0); // Success + return 0; +} + +#endif + static bool AppInit(int argc, char* argv[]) { NodeContext node; @@ -59,6 +133,14 @@ static bool AppInit(int argc, char* argv[]) return true; } +#if HAVE_DECL_FORK + // Communication with parent after daemonizing. This is used for signalling in the following ways: + // - a boolean token is sent when the initialization process (all the Init* functions) have finished to indicate + // that the parent process can quit, and whether it was successful/unsuccessful. + // - an unexpected shutdown of the child process creates an unexpected end of stream at the parent + // end, which is interpreted as failure to start. + TokenPipeEnd daemon_ep; +#endif util::Ref context{node}; try { @@ -105,24 +187,34 @@ static bool AppInit(int argc, char* argv[]) // InitError will have been called with detailed error, which ends up on console return false; } - if (args.GetBoolArg("-daemon", false)) { -#if HAVE_DECL_DAEMON -#if defined(MAC_OSX) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" -#endif + if (args.GetBoolArg("-daemon", DEFAULT_DAEMON) || args.GetBoolArg("-daemonwait", DEFAULT_DAEMONWAIT)) { +#if HAVE_DECL_FORK tfm::format(std::cout, PACKAGE_NAME " starting\n"); // Daemonize - if (daemon(1, 0)) { // don't chdir (1), do close FDs (0) - return InitError(Untranslated(strprintf("daemon() failed: %s\n", strerror(errno)))); + switch (fork_daemon(1, 0, daemon_ep)) { // don't chdir (1), do close FDs (0) + case 0: // Child: continue. + // If -daemonwait is not enabled, immediately send a success token the parent. + if (!args.GetBoolArg("-daemonwait", DEFAULT_DAEMONWAIT)) { + daemon_ep.TokenWrite(1); + daemon_ep.Close(); + } + break; + case -1: // Error happened. + return InitError(Untranslated(strprintf("fork_daemon() failed: %s\n", strerror(errno)))); + default: { // Parent: wait and exit. + int token = daemon_ep.TokenRead(); + if (token) { // Success + exit(EXIT_SUCCESS); + } else { // fRet = false or token read error (premature exit). + tfm::format(std::cerr, "Error during initializaton - check debug.log for details\n"); + exit(EXIT_FAILURE); + } + } } -#if defined(MAC_OSX) -#pragma GCC diagnostic pop -#endif #else return InitError(Untranslated("-daemon is not supported on this operating system\n")); -#endif // HAVE_DECL_DAEMON +#endif // HAVE_DECL_FORK } // Lock data directory after daemonization if (!AppInitLockDataDirectory()) @@ -138,6 +230,13 @@ static bool AppInit(int argc, char* argv[]) PrintExceptionContinue(nullptr, "AppInit()"); } +#if HAVE_DECL_FORK + if (daemon_ep.IsOpen()) { + // Signal initialization status to parent, then close pipe. + daemon_ep.TokenWrite(fRet); + daemon_ep.Close(); + } +#endif if (fRet) { WaitForShutdown(); } diff --git a/src/init.cpp b/src/init.cpp index befba2eb2d..8adb637d6e 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -577,8 +577,9 @@ void SetupServerArgs(NodeContext& node) 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); argsman.AddArg("-server", "Accept command line and JSON-RPC commands", ArgsManager::ALLOW_ANY, OptionsCategory::RPC); -#if HAVE_DECL_DAEMON - argsman.AddArg("-daemon", "Run in the background as a daemon and accept commands", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); +#if HAVE_DECL_FORK + argsman.AddArg("-daemon", strprintf("Run in the background as a daemon and accept commands (default: %d)", DEFAULT_DAEMON), ArgsManager::ALLOW_BOOL, OptionsCategory::OPTIONS); + argsman.AddArg("-daemonwait", strprintf("Wait for initialization to be finished before exiting. This implies -daemon (default: %d)", DEFAULT_DAEMONWAIT), ArgsManager::ALLOW_BOOL, OptionsCategory::OPTIONS); #else hidden_args.emplace_back("-daemon"); #endif diff --git a/src/init.h b/src/init.h index c04d966d06..34bca09dd1 100644 --- a/src/init.h +++ b/src/init.h @@ -9,6 +9,11 @@ #include <memory> #include <string> +//! Default value for -daemon option +static constexpr bool DEFAULT_DAEMON = false; +//! Default value for -daemonwait option +static constexpr bool DEFAULT_DAEMONWAIT = false; + class ArgsManager; struct NodeContext; namespace interfaces { |