From 45599fc9ee80e591dcd86bbc353005da53fd4de5 Mon Sep 17 00:00:00 2001 From: div72 <60045611+div72@users.noreply.github.com> Date: Tue, 31 May 2022 17:19:44 +0300 Subject: [PATCH 1/6] util: port syserror --- src/Makefile.am | 2 ++ src/fs.cpp | 7 ++++++- src/util/syserror.cpp | 34 ++++++++++++++++++++++++++++++++++ src/util/syserror.h | 16 ++++++++++++++++ 4 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 src/util/syserror.cpp create mode 100644 src/util/syserror.h diff --git a/src/Makefile.am b/src/Makefile.am index 0139eb6324..6c0d97c389 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -194,6 +194,7 @@ GRIDCOIN_CORE_H = \ util/settings.h \ util/strencodings.h \ util/string.h \ + util/syserror.h \ util/system.h \ util/threadnames.h \ util/time.h \ @@ -296,6 +297,7 @@ GRIDCOIN_CORE_CPP = addrdb.cpp \ util/settings.cpp \ util/strencodings.cpp \ util/string.cpp \ + util/syserror.cpp \ util/system.cpp \ util/threadnames.cpp \ util/time.cpp \ diff --git a/src/fs.cpp b/src/fs.cpp index a9ccd89d8f..e592e3aef4 100644 --- a/src/fs.cpp +++ b/src/fs.cpp @@ -1,4 +1,9 @@ +// Copyright (c) 2017-2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://opensource.org/licenses/mit-license.php. + #include +#include #ifndef WIN32 #include @@ -34,7 +39,7 @@ fs::path AbsPathJoin(const fs::path& base, const fs::path& path) #ifndef WIN32 static std::string GetErrorReason() { - return std::strerror(errno); + return SysErrorString(errno); } FileLock::FileLock(const fs::path& file) diff --git a/src/util/syserror.cpp b/src/util/syserror.cpp new file mode 100644 index 0000000000..b48d920671 --- /dev/null +++ b/src/util/syserror.cpp @@ -0,0 +1,34 @@ +// Copyright (c) 2020-2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://opensource.org/licenses/mit-license.php. + +#if defined(HAVE_CONFIG_H) +#include +#endif + +#include +#include + +#include + +std::string SysErrorString(int err) +{ + char buf[1024]; + /* Too bad there are three incompatible implementations of the + * thread-safe strerror. */ + const char *s = nullptr; +#ifdef WIN32 + if (strerror_s(buf, sizeof(buf), err) == 0) s = buf; +#else +#ifdef STRERROR_R_CHAR_P /* GNU variant can return a pointer outside the passed buffer */ + s = strerror_r(err, buf, sizeof(buf)); +#else /* POSIX variant always returns message in buffer */ + if (strerror_r(err, buf, sizeof(buf)) == 0) s = buf; +#endif +#endif + if (s != nullptr) { + return strprintf("%s (%d)", s, err); + } else { + return strprintf("Unknown error (%d)", err); + } +} diff --git a/src/util/syserror.h b/src/util/syserror.h new file mode 100644 index 0000000000..1381d80822 --- /dev/null +++ b/src/util/syserror.h @@ -0,0 +1,16 @@ +// Copyright (c) 2010-2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_UTIL_SYSERROR_H +#define BITCOIN_UTIL_SYSERROR_H + +#include + +/** Return system error string from errno value. Use this instead of + * std::strerror, which is not thread-safe. For network errors use + * NetworkErrorString from sock.h instead. + */ +std::string SysErrorString(int err); + +#endif // BITCOIN_UTIL_SYSERROR_H From a34ed24d389d746373daab24582173e7178f087a Mon Sep 17 00:00:00 2001 From: div72 <60045611+div72@users.noreply.github.com> Date: Tue, 31 May 2022 15:50:45 +0300 Subject: [PATCH 2/6] util: port tokenpipe --- src/Makefile.am | 2 + src/util/tokenpipe.cpp | 111 +++++++++++++++++++++++++++++++++++ src/util/tokenpipe.h | 127 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 240 insertions(+) create mode 100644 src/util/tokenpipe.cpp create mode 100644 src/util/tokenpipe.h diff --git a/src/Makefile.am b/src/Makefile.am index 6c0d97c389..dbf5977f90 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -198,6 +198,7 @@ GRIDCOIN_CORE_H = \ util/system.h \ util/threadnames.h \ util/time.h \ + util/tokenpipe.h \ util.h \ validation.h \ version.h \ @@ -301,6 +302,7 @@ GRIDCOIN_CORE_CPP = addrdb.cpp \ util/system.cpp \ util/threadnames.cpp \ util/time.cpp \ + util/tokenpipe.cpp \ util.cpp \ validation.cpp \ wallet/db.cpp \ diff --git a/src/util/tokenpipe.cpp b/src/util/tokenpipe.cpp new file mode 100644 index 0000000000..82c7328c77 --- /dev/null +++ b/src/util/tokenpipe.cpp @@ -0,0 +1,111 @@ +// Copyright (c) 2021-2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://opensource.org/licenses/mit-license.php. +#include + +#if defined(HAVE_CONFIG_H) +#include +#endif + +#ifndef WIN32 + +#include +#include +#include +#include + +TokenPipeEnd TokenPipe::TakeReadEnd() +{ + TokenPipeEnd res(m_fds[0]); + m_fds[0] = -1; + return res; +} + +TokenPipeEnd TokenPipe::TakeWriteEnd() +{ + TokenPipeEnd res(m_fds[1]); + m_fds[1] = -1; + return res; +} + +TokenPipeEnd::TokenPipeEnd(int fd) : m_fd(fd) +{ +} + +TokenPipeEnd::~TokenPipeEnd() +{ + Close(); +} + +int TokenPipeEnd::TokenWrite(uint8_t token) +{ + while (true) { + ssize_t result = write(m_fd, &token, 1); + if (result < 0) { + // Failure. It's possible that the write was interrupted by a signal, + // in that case retry. + if (errno != EINTR) { + return TS_ERR; + } + } else if (result == 0) { + return TS_EOS; + } else { // ==1 + return 0; + } + } +} + +int TokenPipeEnd::TokenRead() +{ + uint8_t token; + while (true) { + ssize_t result = read(m_fd, &token, 1); + if (result < 0) { + // Failure. Check if the read was interrupted by a signal, + // in that case retry. + if (errno != EINTR) { + return TS_ERR; + } + } else if (result == 0) { + return TS_EOS; + } else { // ==1 + return token; + } + } + return token; +} + +void TokenPipeEnd::Close() +{ + if (m_fd != -1) close(m_fd); + m_fd = -1; +} + +std::optional TokenPipe::Make() +{ + int fds[2] = {-1, -1}; +#if HAVE_O_CLOEXEC && HAVE_DECL_PIPE2 + if (pipe2(fds, O_CLOEXEC) != 0) { + return std::nullopt; + } +#else + if (pipe(fds) != 0) { + return std::nullopt; + } +#endif + return TokenPipe(fds); +} + +TokenPipe::~TokenPipe() +{ + Close(); +} + +void TokenPipe::Close() +{ + if (m_fds[0] != -1) close(m_fds[0]); + if (m_fds[1] != -1) close(m_fds[1]); + m_fds[0] = m_fds[1] = -1; +} + +#endif // WIN32 diff --git a/src/util/tokenpipe.h b/src/util/tokenpipe.h new file mode 100644 index 0000000000..3ce8c7b6aa --- /dev/null +++ b/src/util/tokenpipe.h @@ -0,0 +1,127 @@ +// Copyright (c) 2021 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_UTIL_TOKENPIPE_H +#define BITCOIN_UTIL_TOKENPIPE_H + +#ifndef WIN32 + +#include +#include + +/** One end of a token pipe. */ +class TokenPipeEnd +{ +private: + int m_fd = -1; + +public: + TokenPipeEnd(int fd = -1); + ~TokenPipeEnd(); + + /** Return value constants for TokenWrite and TokenRead. */ + enum Status { + TS_ERR = -1, //!< I/O error + TS_EOS = -2, //!< Unexpected end of stream + }; + + /** Write token to endpoint. + * + * @returns 0 If successful. + * <0 if error: + * TS_ERR If an error happened. + * TS_EOS If end of stream happened. + */ + int TokenWrite(uint8_t token); + + /** Read token from endpoint. + * + * @returns >=0 Token value, if successful. + * <0 if error: + * TS_ERR If an error happened. + * TS_EOS If end of stream happened. + */ + int TokenRead(); + + /** Explicit close function. + */ + void Close(); + + /** Return whether endpoint is open. + */ + bool IsOpen() { return m_fd != -1; } + + // Move-only class. + TokenPipeEnd(TokenPipeEnd&& other) + { + m_fd = other.m_fd; + other.m_fd = -1; + } + TokenPipeEnd& operator=(TokenPipeEnd&& other) + { + Close(); + m_fd = other.m_fd; + other.m_fd = -1; + return *this; + } + TokenPipeEnd(const TokenPipeEnd&) = delete; + TokenPipeEnd& operator=(const TokenPipeEnd&) = delete; +}; + +/** An interprocess or interthread pipe for sending tokens (one-byte values) + * over. + */ +class TokenPipe +{ +private: + int m_fds[2] = {-1, -1}; + + TokenPipe(int fds[2]) : m_fds{fds[0], fds[1]} {} + +public: + ~TokenPipe(); + + /** Create a new pipe. + * @returns The created TokenPipe, or an empty std::nullopt in case of error. + */ + static std::optional Make(); + + /** Take the read end of this pipe. This can only be called once, + * as the object will be moved out. + */ + TokenPipeEnd TakeReadEnd(); + + /** Take the write end of this pipe. This should only be called once, + * as the object will be moved out. + */ + TokenPipeEnd TakeWriteEnd(); + + /** Close and end of the pipe that hasn't been moved out. + */ + void Close(); + + // Move-only class. + TokenPipe(TokenPipe&& other) + { + for (int i = 0; i < 2; ++i) { + m_fds[i] = other.m_fds[i]; + other.m_fds[i] = -1; + } + } + TokenPipe& operator=(TokenPipe&& other) + { + Close(); + for (int i = 0; i < 2; ++i) { + m_fds[i] = other.m_fds[i]; + other.m_fds[i] = -1; + } + return *this; + } + TokenPipe(const TokenPipe&) = delete; + TokenPipe& operator=(const TokenPipe&) = delete; +}; + +#endif // WIN32 + +#endif // BITCOIN_UTIL_TOKENPIPE_H From 5ffd332d472a6e63ceb0b5eaef6f4c2524f06a9d Mon Sep 17 00:00:00 2001 From: div72 <60045611+div72@users.noreply.github.com> Date: Tue, 31 May 2022 20:02:35 +0300 Subject: [PATCH 3/6] build: check for daemon related functions --- configure.ac | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index b73dbddeb6..741b1bd618 100755 --- a/configure.ac +++ b/configure.ac @@ -685,8 +685,11 @@ AC_CHECK_HEADERS([endian.h sys/endian.h byteswap.h stdio.h stdlib.h unistd.h str AC_CHECK_DECLS([strnlen]) -# 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]) AC_CHECK_DECLS([le16toh, le32toh, le64toh, htole16, htole32, htole64, be16toh, be32toh, be64toh, htobe16, htobe32, htobe64],,, [#if HAVE_ENDIAN_H From 415ae8edf2e69dcf05ff92e1f9392c9ca99b0131 Mon Sep 17 00:00:00 2001 From: div72 <60045611+div72@users.noreply.github.com> Date: Tue, 31 May 2022 16:33:01 +0300 Subject: [PATCH 4/6] init: small cleanup --- src/gridcoinresearchd.cpp | 59 ++++++++++++--------------------------- src/init.cpp | 35 +++++++---------------- src/init.h | 8 +++++- src/qt/bitcoin.cpp | 2 +- src/util.cpp | 2 -- src/util.h | 2 -- 6 files changed, 36 insertions(+), 72 deletions(-) diff --git a/src/gridcoinresearchd.cpp b/src/gridcoinresearchd.cpp index ff174a6655..b7b6c8d03e 100644 --- a/src/gridcoinresearchd.cpp +++ b/src/gridcoinresearchd.cpp @@ -55,8 +55,7 @@ bool AppInit(int argc, char* argv[]) // If Qt is used, parameters/gridcoinresearch.conf are parsed in qt/bitcoin.cpp's main() std::string error; if (!gArgs.ParseParameters(argc, argv, error)) { - tfm::format(std::cerr, "Error parsing command line arguments: %s\n", error); - return EXIT_FAILURE; + return InitError(strprintf("Error parsing command line arguments: %s\n", error)); } if (HelpRequested(gArgs)) { @@ -70,20 +69,18 @@ bool AppInit(int argc, char* argv[]) strUsage += "\n" + gArgs.GetHelpMessage(); tfm::format(std::cout, "%s", strUsage); - - return EXIT_SUCCESS; + return true; } if (gArgs.IsArgSet("-version")) { tfm::format(std::cout, "%s", VersionMessage().c_str()); - return false; + return true; } if (!CheckDataDirOption()) { - tfm::format(std::cerr, "Error: Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", "")); - return EXIT_FAILURE; + return InitError(strprintf("Error: Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", ""))); } /** Check mainnet config file first in case testnet is set there and not in command line args **/ @@ -94,8 +91,7 @@ bool AppInit(int argc, char* argv[]) if (!gArgs.ReadConfigFiles(error_msg, true)) { - tfm::format(std::cerr, "Config file cannot be parsed. Cannot continue.\n"); - exit(1); + return InitError("Config file cannot be parsed. Cannot continue.\n"); } SelectParams(gArgs.IsArgSet("-testnet") ? CBaseChainParams::TESTNET : CBaseChainParams::MAIN); @@ -103,13 +99,11 @@ bool AppInit(int argc, char* argv[]) // reread config file after correct chain is selected if (!gArgs.ReadConfigFiles(error_msg, true)) { - tfm::format(std::cerr, "Config file cannot be parsed. Cannot continue.\n"); - exit(1); + return InitError("Config file cannot be parsed. Cannot continue.\n"); } if (!gArgs.InitSettings(error)) { - tfm::format(std::cerr, "Error initializing settings.\n"); - exit(1); + return InitError("Error initializing settings.\n"); } // Command-line RPC - single commands execute and exit. @@ -119,19 +113,18 @@ bool AppInit(int argc, char* argv[]) if (fCommandLine) { - int ret = CommandLineRPC(argc, argv); - exit(ret); + return !CommandLineRPC(argc, argv); } + // -server defaults to true for gridcoinresearchd but not for the GUI so do this here + gArgs.SoftSetBoolArg("-server", true); // Initialize logging as early as possible. InitLogging(); // Make sure a user does not request snapshotdownload and resetblockchaindata at same time! if (gArgs.IsArgSet("-snapshotdownload") && gArgs.IsArgSet("-resetblockchaindata")) { - tfm::format(std::cerr, "-snapshotdownload and -resetblockchaindata cannot be used in conjunction"); - - exit(1); + return InitError("-snapshotdownload and -resetblockchaindata cannot be used in conjunction"); } // Check to see if the user requested a snapshot and we are not running TestNet! @@ -143,10 +136,8 @@ bool AppInit(int argc, char* argv[]) // Use new probe feature if (!LockDirectory(GetDataDir(), ".lock", false)) { - tfm::format(std::cerr, "Cannot obtain a lock on data directory %s. Gridcoin is probably already running.", - GetDataDir().string().c_str()); - - exit(1); + return InitError(strprintf("Cannot obtain a lock on data directory %s. Gridcoin is probably already running.", + GetDataDir().string())); } else @@ -162,7 +153,7 @@ bool AppInit(int argc, char* argv[]) snapshot.DeleteSnapshot(); - exit(1); + return false; } } @@ -178,9 +169,8 @@ bool AppInit(int argc, char* argv[]) // Let's check make sure Gridcoin is not already running in the data directory. if (!LockDirectory(GetDataDir(), ".lock", false)) { - tfm::format(std::cerr, "Cannot obtain a lock on data directory %s. Gridcoin is probably already running.", GetDataDir().string().c_str()); - - exit(1); + return InitError(strprintf("Cannot obtain a lock on data directory %s. Gridcoin is probably already running.", + GetDataDir().string())); } else @@ -192,17 +182,11 @@ bool AppInit(int argc, char* argv[]) { LogPrintf("ResetBlockchainData: failed to clean up blockchain data"); - std::string inftext = resetblockchain.ResetBlockchainMessages(resetblockchain.CleanUp); - - tfm::format(std::cerr, "%s", inftext.c_str()); - - exit(1); + return InitError(resetblockchain.ResetBlockchainMessages(resetblockchain.CleanUp)); } } } - LogPrintf("AppInit"); - fRet = AppInit2(threads); } catch (std::exception& e) { @@ -238,18 +222,11 @@ int main(int argc, char* argv[]) // Reinit default timer to ensure it is zeroed out at the start of main. g_timer.InitTimer("default", false); - bool fRet = false; - // Set global boolean to indicate intended absence of GUI to core... fQtActive = false; // Connect bitcoind signal handlers noui_connect(); - fRet = AppInit(argc, argv); - - if (fRet && fDaemon) - return 0; - - return 1; + return (AppInit(argc, argv) ? EXIT_SUCCESS : EXIT_FAILURE); } diff --git a/src/init.cpp b/src/init.cpp index a1f35d740d..d4ddc5e2ed 100755 --- a/src/init.cpp +++ b/src/init.cpp @@ -320,8 +320,6 @@ void SetupServerArgs() ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-mininput=", "When creating transactions, ignore inputs with value less than this (default: 0.01)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); - argsman.AddArg("-daemon", "Run in the background as a daemon and accept commands", - ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-testnet", "Use the test network", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-blocknotify=", "Execute command when the best block changes (%s in cmd is replaced by block hash)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); @@ -560,6 +558,14 @@ void SetupServerArgs() "Acceptable ciphers (default: TLSv1.2+HIGH:TLSv1+HIGH:!SSLv2:!aNULL:!eNULL:!3DES:@STRENGTH)", ArgsManager::ALLOW_ANY, OptionsCategory::RPC); +#if HAVE_DECL_FORK + argsman.AddArg("-daemon", strprintf("Run in the background as a daemon and accept commands (default: %d)", DEFAULT_DAEMON), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + argsman.AddArg("-daemonwait", strprintf("Wait for initialization to be finished before exiting. This implies -daemon (default: %d)", DEFAULT_DAEMONWAIT), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); +#else + hidden_args.emplace_back("-daemon"); + hidden_args.emplace_back("-daemonwait"); +#endif + // Additional hidden options hidden_args.emplace_back("-devbuild"); hidden_args.emplace_back("-scrapersleep"); @@ -892,28 +898,10 @@ bool AppInit2(ThreadHandlerPtr threads) LogInstance().EnableCategory(BCLog::LogFlags::VERBOSE); } -#if defined(WIN32) - fDaemon = false; -#else - if(fQtActive) - fDaemon = false; - else - fDaemon = gArgs.GetBoolArg("-daemon"); -#endif - // Check for -socks - as this is a privacy risk to continue, exit here if (gArgs.IsArgSet("-socks")) return InitError(_("Error: Unsupported argument -socks found. Setting SOCKS version isn't possible anymore, only SOCKS5 proxies are supported.")); - if (fDaemon) - fServer = true; - else - fServer = gArgs.GetBoolArg("-server"); - - /* force fServer when running without GUI */ - if(!fQtActive) - fServer = true; - if (gArgs.IsArgSet("-timeout")) { int nNewTimeout = gArgs.GetArg("-timeout", 5000); @@ -983,7 +971,7 @@ bool AppInit2(ThreadHandlerPtr threads) #if !defined(WIN32) - if (fDaemon) + if (gArgs.GetBoolArg("-daemon", DEFAULT_DAEMON)) { // Daemonize pid_t pid = fork(); @@ -1036,9 +1024,6 @@ bool AppInit2(ThreadHandlerPtr threads) } } - if (fDaemon) - tfm::format(std::cout, "Gridcoin server starting\n"); - // ********************************************************* Step 5: verify database integrity uiInterface.InitMessage(_("Verifying database integrity...")); @@ -1485,7 +1470,7 @@ bool AppInit2(ThreadHandlerPtr threads) if (!threads->createThread(StartNode, nullptr, "Start Thread")) InitError(_("Error: could not start node")); - if (fServer) StartRPCThreads(); + if (gArgs.GetBoolArg("-server", false)) StartRPCThreads(); // ********************************************************* Step 13: finished diff --git a/src/init.h b/src/init.h index a40e5f6fc2..db12638805 100644 --- a/src/init.h +++ b/src/init.h @@ -1,13 +1,19 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto -// Copyright (c) 2009-2012 The Bitcoin developers +// Copyright (c) 2009-2021 The Bitcoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or https://opensource.org/licenses/mit-license.php. + #ifndef BITCOIN_INIT_H #define BITCOIN_INIT_H #include "wallet/wallet.h" #include +//! Default value for -daemon option +static constexpr bool DEFAULT_DAEMON = false; +//! Default value for -daemonwait option +static constexpr bool DEFAULT_DAEMONWAIT = false; + extern CWallet* pwalletMain; void InitLogging(); diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 3ab0d855ba..2866b4bb1b 100755 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -145,7 +145,7 @@ static bool ThreadSafeAskFee(int64_t nFeeRequired, const std::string& strCaption nMinFee = GetBaseFee(txDummy, GMF_SEND); } - if(nFeeRequired < nMinFee || nFeeRequired <= nTransactionFee || fDaemon) + if (nFeeRequired < nMinFee || nFeeRequired <= nTransactionFee) return true; bool payFee = false; diff --git a/src/util.cpp b/src/util.cpp index 876f9b7ef0..ae8d6e28e8 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -29,8 +29,6 @@ using namespace std; bool fPrintToConsole = false; bool fRequestShutdown = false; std::atomic fShutdown = false; -bool fDaemon = false; -bool fServer = false; bool fCommandLine = false; bool fTestNet = false; bool fNoListen = false; diff --git a/src/util.h b/src/util.h index 3c043b7f1a..ccc531ef7c 100644 --- a/src/util.h +++ b/src/util.h @@ -79,8 +79,6 @@ extern int GetDayOfYear(int64_t timestamp); extern bool fPrintToConsole; extern bool fRequestShutdown; extern std::atomic fShutdown; -extern bool fDaemon; -extern bool fServer; extern bool fCommandLine; extern bool fTestNet; extern bool fNoListen; From e4679fff66ce4f7acfa17cd5c0dd8f8bcf89c248 Mon Sep 17 00:00:00 2001 From: div72 <60045611+div72@users.noreply.github.com> Date: Tue, 31 May 2022 17:10:30 +0300 Subject: [PATCH 5/6] init: proper forking --- src/gridcoinresearchd.cpp | 125 ++++++++++++++++++++++++++++++++++++-- src/init.cpp | 47 ++++++-------- src/util.cpp | 11 ---- src/util.h | 3 - 4 files changed, 141 insertions(+), 45 deletions(-) diff --git a/src/gridcoinresearchd.cpp b/src/gridcoinresearchd.cpp index b7b6c8d03e..65ef60ce4f 100644 --- a/src/gridcoinresearchd.cpp +++ b/src/gridcoinresearchd.cpp @@ -10,7 +10,9 @@ #include "chainparams.h" #include "chainparamsbase.h" #include "util.h" +#include #include "util/threadnames.h" +#include #include "net.h" #include "txdb.h" #include "wallet/walletdb.h" @@ -26,10 +28,79 @@ extern bool fQtActive; -////////////////////////////////////////////////////////////////////////////// -// -// Start -// +#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 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 + bool AppInit(int argc, char* argv[]) { #ifdef WIN32 @@ -79,6 +150,15 @@ 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 + if (!CheckDataDirOption()) { return InitError(strprintf("Error: Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", ""))); } @@ -187,7 +267,44 @@ bool AppInit(int argc, char* argv[]) } } + if (gArgs.GetBoolArg("-daemon", DEFAULT_DAEMON) || gArgs.GetBoolArg("-daemonwait", DEFAULT_DAEMONWAIT)) { +#if HAVE_DECL_FORK + tfm::format(std::cout, PACKAGE_NAME " starting\n"); + + // Daemonize + 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 (!gArgs.GetBoolArg("-daemonwait", DEFAULT_DAEMONWAIT)) { + daemon_ep.TokenWrite(1); + daemon_ep.Close(); + } + break; + case -1: // Error happened. + return InitError(strprintf("fork_daemon() failed: %s\n", SysErrorString(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); + } + } + } +#else + return InitError("-daemon is not supported on this operating system\n"); +#endif // HAVE_DECL_FORK + } + fRet = AppInit2(threads); +#if HAVE_DECL_FORK + if (daemon_ep.IsOpen()) { + // Signal initialization status to parent, then close pipe. + daemon_ep.TokenWrite(fRet); + daemon_ep.Close(); + } +#endif } catch (std::exception& e) { LogPrintf("AppInit()Exception1"); diff --git a/src/init.cpp b/src/init.cpp index d4ddc5e2ed..5d1b117799 100755 --- a/src/init.cpp +++ b/src/init.cpp @@ -20,6 +20,7 @@ #include "gridcoin/upgrade.h" #include "miner.h" #include "node/blockstorage.h" +#include #include #include @@ -67,6 +68,21 @@ static fs::path GetPidFile(const ArgsManager& args) return AbsPathForConfigVal(fs::path(args.GetArg("-pid", GRIDCOIN_PID_FILENAME))); } +[[nodiscard]] static bool CreatePidFile(const ArgsManager& args) +{ + fsbridge::ofstream file{GetPidFile(args)}; + if (file) { +#ifdef WIN32 + tfm::format(file, "%d\n", GetCurrentProcessId()); +#else + tfm::format(file, "%d\n", getpid()); +#endif + return true; + } else { + return InitError(strprintf(_("Unable to create the PID file '%s': %s"), GetPidFile(args).string(), SysErrorString(errno))); + } +} + ////////////////////////////////////////////////////////////////////////////// // // Shutdown @@ -937,6 +953,10 @@ bool AppInit2(ThreadHandlerPtr threads) } // ********************************************************* Step 4: application initialization: dir lock, daemonize, pidfile, debug log + if (!CreatePidFile(gArgs)) { + // Detailed error printed inside CreatePidFile(). + return false; + } // Initialize internal hashing code with SSE/AVX2 optimizations. In the future we will also have ARM/NEON optimizations. std::string sha256_algo = SHA256AutoDetect(); LogPrintf("Using the '%s' SHA256 implementation\n", sha256_algo); @@ -969,33 +989,6 @@ bool AppInit2(ThreadHandlerPtr threads) return InitError(strprintf(_("Cannot obtain a lock on data directory %s. %s is probably already running."), datadir.string(), PACKAGE_NAME)); } - -#if !defined(WIN32) - if (gArgs.GetBoolArg("-daemon", DEFAULT_DAEMON)) - { - // Daemonize - pid_t pid = fork(); - if (pid < 0) - { - tfm::format(std::cerr, "Error: fork() returned %d errno %d\n", pid, errno); - return false; - } - if (pid > 0) - { - CreatePidFile(GetPidFile(gArgs), pid); - - // Now that we are forked we can request a shutdown so the parent - // exits while the child lives on. - StartShutdown(); - return true; - } - - pid_t sid = setsid(); - if (sid < 0) - tfm::format(std::cerr, "Error: setsid() returned %d errno %d\n", sid, errno); - } -#endif - #if (OPENSSL_VERSION_NUMBER < 0x10100000L) LogPrintf("Using OpenSSL version %s\n", SSLeay_version(SSLEAY_VERSION)); #elif defined OPENSSL_VERSION diff --git a/src/util.cpp b/src/util.cpp index ae8d6e28e8..d48c2f6709 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -168,17 +168,6 @@ bool WildcardMatch(const string& str, const string& mask) return WildcardMatch(str.c_str(), mask.c_str()); } -#ifndef WIN32 -void CreatePidFile(const fs::path &path, pid_t pid) -{ - fsbridge::ofstream file{path}; - if (file) - { - tfm::format(file, "%d\n", pid); - } -} -#endif - /** * Ignores exceptions thrown by Boost's create_directories if the requested directory exists. * Specifically handles case where path p exists, but it wasn't possible for the user to diff --git a/src/util.h b/src/util.h index ccc531ef7c..58524a5eb7 100644 --- a/src/util.h +++ b/src/util.h @@ -98,9 +98,6 @@ bool TryCreateDirectories(const fs::path& p); std::string TimestampToHRDate(double dtm); -#ifndef WIN32 -void CreatePidFile(const fs::path &path, pid_t pid); -#endif bool DirIsWritable(const fs::path& directory); bool LockDirectory(const fs::path& directory, const std::string lockfile_name, bool probe_only=false); bool TryCreateDirectories(const fs::path& p); From 6500b649ba04968c76a7436c5c45ef46229f997b Mon Sep 17 00:00:00 2001 From: div72 <60045611+div72@users.noreply.github.com> Date: Sat, 11 Jun 2022 23:40:44 +0300 Subject: [PATCH 6/6] contrib: add example systemd service file --- contrib/init/gridcoinresearchd.service | 82 ++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 contrib/init/gridcoinresearchd.service diff --git a/contrib/init/gridcoinresearchd.service b/contrib/init/gridcoinresearchd.service new file mode 100644 index 0000000000..5077f85301 --- /dev/null +++ b/contrib/init/gridcoinresearchd.service @@ -0,0 +1,82 @@ +# It is not recommended to modify this file in-place, because it will +# be overwritten during package upgrades. If you want to add further +# options or overwrite existing ones then use +# $ systemctl edit gridcoinresearchd.service +# See "man systemd.service" for details. + +# Note that almost all daemon options could be specified in +# /etc/gridcoin/gridcoin.conf, but keep in mind those explicitly +# specified as arguments in ExecStart= will override those in the +# config file. + +[Unit] +Description=Gridcoin daemon +Documentation=https://github.com/gridcoin-community/Gridcoin-Research/blob/development/doc/gridcoinresearch.conf.md + +# https://www.freedesktop.org/wiki/Software/systemd/NetworkTarget/ +After=network-online.target +Wants=network-online.target + +[Service] +ExecStart=/usr/bin/gridcoinresearchd -daemonwait \ + -pid=/run/gridcoinresearchd/gridcoinresearchd.pid \ + -conf=/etc/gridcoin/gridcoin.conf \ + -datadir=/var/lib/gridcoinresearchd + +# Make sure the config directory is readable by the service user +PermissionsStartOnly=true +ExecStartPre=/bin/chgrp gridcoin /etc/gridcoin + +# Process management +#################### + +Type=forking +PIDFile=/run/gridcoinresearchd/gridcoinresearchd.pid +Restart=on-failure +TimeoutStartSec=infinity +TimeoutStopSec=600 + +# Directory creation and permissions +#################################### + +# Run as gridcoin:gridcoin +User=gridcoin +Group=gridcoin + +# /run/gridcoinresearchd +RuntimeDirectory=gridcoinresearchd +RuntimeDirectoryMode=0710 + +# /etc/gridcoin +ConfigurationDirectory=gridcoin +ConfigurationDirectoryMode=0710 + +# /var/lib/gridcoinresearchd +StateDirectory=gridcoinresearchd +StateDirectoryMode=0710 + +# Hardening measures +#################### + +# Provide a private /tmp and /var/tmp. +PrivateTmp=true + +# Mount /usr, /boot/ and /etc read-only for the process. +ProtectSystem=full + +# Deny access to /home, /root and /run/user +ProtectHome=true + +# Disallow the process and all of its children to gain +# new privileges through execve(). +NoNewPrivileges=true + +# Use a new /dev namespace only populated with API pseudo devices +# such as /dev/null, /dev/zero and /dev/random. +PrivateDevices=true + +# Deny the creation of writable and executable memory mappings. +MemoryDenyWriteExecute=true + +[Install] +WantedBy=multi-user.target