diff options
Diffstat (limited to '')
27 files changed, 3275 insertions, 296 deletions
diff --git a/src/daemon/CMakeLists.txt b/src/daemon/CMakeLists.txt index 5f60857d6..4de8b82b8 100644 --- a/src/daemon/CMakeLists.txt +++ b/src/daemon/CMakeLists.txt @@ -27,12 +27,27 @@ # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. set(daemon_sources - daemon.cpp) + command_parser_executor.cpp + command_server.cpp + daemon.cpp + executor.cpp + main.cpp + rpc_command_executor.cpp +) set(daemon_headers) set(daemon_private_headers + command_parser_executor.h + command_server.h + core.h + daemon.h daemon_commands_handler.h + executor.h + p2p.h + protocol.h + rpc.h + rpc_command_executor.h # cryptonote_protocol ../cryptonote_protocol/blobdatatype.h @@ -62,6 +77,7 @@ target_link_libraries(daemon cryptonote_core crypto common + daemonizer ${Boost_CHRONO_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} diff --git a/src/daemon/command_line_args.h b/src/daemon/command_line_args.h new file mode 100644 index 000000000..bcf599128 --- /dev/null +++ b/src/daemon/command_line_args.h @@ -0,0 +1,76 @@ +// Copyright (c) 2014, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef DAEMON_COMMAND_LINE_ARGS_H +#define DAEMON_COMMAND_LINE_ARGS_H + +#include "common/command_line.h" +#include "cryptonote_config.h" +#include <boost/program_options.hpp> + +namespace daemon_args +{ + std::string const WINDOWS_SERVICE_NAME = "Monero Daemon"; + + const command_line::arg_descriptor<std::string> arg_config_file = { + "config-file" + , "Specify configuration file" + , std::string(CRYPTONOTE_NAME ".conf") + }; + const command_line::arg_descriptor<std::string> arg_log_file = { + "log-file" + , "Specify log file" + , "" + }; + const command_line::arg_descriptor<int> arg_log_level = { + "log-level" + , "" + , LOG_LEVEL_0 + }; + const command_line::arg_descriptor<std::vector<std::string>> arg_command = { + "daemon_command" + , "Hidden" + }; + const command_line::arg_descriptor<bool> arg_os_version = { + "os-version" + , "OS for which this executable was compiled" + }; + const command_line::arg_descriptor<bool> arg_testnet_on = { + "testnet" + , "Run on testnet. The wallet must be launched with --testnet flag." + , false + }; + const command_line::arg_descriptor<bool> arg_dns_checkpoints = { + "enforce-dns-checkpointing" + , "checkpoints from DNS server will be enforced" + , false + }; + +} // namespace daemon_args + +#endif // DAEMON_COMMAND_LINE_ARGS_H diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp new file mode 100644 index 000000000..d3af50b66 --- /dev/null +++ b/src/daemon/command_parser_executor.cpp @@ -0,0 +1,297 @@ +// Copyright (c) 2014, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "cryptonote_core/cryptonote_basic_impl.h" +#include "daemon/command_parser_executor.h" + +namespace daemonize { + +t_command_parser_executor::t_command_parser_executor( + uint32_t ip + , uint16_t port + ) + : m_executor(ip, port) +{} + +bool t_command_parser_executor::print_peer_list(const std::vector<std::string>& args) +{ + if (!args.empty()) return false; + + return m_executor.print_peer_list(); +} + +bool t_command_parser_executor::save_blockchain(const std::vector<std::string>& args) +{ + if (!args.empty()) return false; + + return m_executor.save_blockchain(); +} + +bool t_command_parser_executor::show_hash_rate(const std::vector<std::string>& args) +{ + if (!args.empty()) return false; + + return m_executor.show_hash_rate(); +} + +bool t_command_parser_executor::hide_hash_rate(const std::vector<std::string>& args) +{ + if (!args.empty()) return false; + + return m_executor.hide_hash_rate(); +} + +bool t_command_parser_executor::show_difficulty(const std::vector<std::string>& args) +{ + if (!args.empty()) return false; + + return m_executor.show_difficulty(); +} + +bool t_command_parser_executor::print_connections(const std::vector<std::string>& args) +{ + if (!args.empty()) return false; + + return m_executor.print_connections(); +} + +bool t_command_parser_executor::print_blockchain_info(const std::vector<std::string>& args) +{ + if(!args.size()) + { + std::cout << "need block index parameter" << std::endl; + return false; + } + uint64_t start_index = 0; + uint64_t end_index = 0; + if(!epee::string_tools::get_xtype_from_string(start_index, args[0])) + { + std::cout << "wrong starter block index parameter" << std::endl; + return false; + } + if(args.size() >1 && !epee::string_tools::get_xtype_from_string(end_index, args[1])) + { + std::cout << "wrong end block index parameter" << std::endl; + return false; + } + + return m_executor.print_blockchain_info(start_index, end_index); +} + +bool t_command_parser_executor::set_log_level(const std::vector<std::string>& args) +{ + if(args.size() != 1) + { + std::cout << "use: set_log <log_level_number_0-4>" << std::endl; + return true; + } + + uint16_t l = 0; + if(!epee::string_tools::get_xtype_from_string(l, args[0])) + { + std::cout << "wrong number format, use: set_log <log_level_number_0-4>" << std::endl; + return true; + } + + if(LOG_LEVEL_4 < l) + { + std::cout << "wrong number range, use: set_log <log_level_number_0-4>" << std::endl; + return true; + } + + return m_executor.set_log_level(l); +} + +bool t_command_parser_executor::print_height(const std::vector<std::string>& args) +{ + if (!args.empty()) return false; + + return m_executor.print_height(); +} + +bool t_command_parser_executor::print_block(const std::vector<std::string>& args) +{ + if (args.empty()) + { + std::cout << "expected: print_block (<block_hash> | <block_height>)" << std::endl; + return false; + } + + const std::string& arg = args.front(); + try + { + uint64_t height = boost::lexical_cast<uint64_t>(arg); + return m_executor.print_block_by_height(height); + } + catch (boost::bad_lexical_cast&) + { + crypto::hash block_hash; + if (parse_hash256(arg, block_hash)) + { + return m_executor.print_block_by_hash(block_hash); + } + } + + return false; +} + +bool t_command_parser_executor::print_transaction(const std::vector<std::string>& args) +{ + if (args.empty()) + { + std::cout << "expected: print_tx <transaction hash>" << std::endl; + return true; + } + + const std::string& str_hash = args.front(); + crypto::hash tx_hash; + if (parse_hash256(str_hash, tx_hash)) + { + m_executor.print_transaction(tx_hash); + } + + return true; +} + +bool t_command_parser_executor::print_transaction_pool_long(const std::vector<std::string>& args) +{ + if (!args.empty()) return false; + + return m_executor.print_transaction_pool_long(); +} + +bool t_command_parser_executor::print_transaction_pool_short(const std::vector<std::string>& args) +{ + if (!args.empty()) return false; + + return m_executor.print_transaction_pool_short(); +} + +bool t_command_parser_executor::start_mining(const std::vector<std::string>& args) +{ + if(!args.size()) + { + std::cout << "Please specify a wallet address to mine for: start_mining <addr> [threads=1]" << std::endl; + return true; + } + + cryptonote::account_public_address adr; + if(!cryptonote::get_account_address_from_str(adr, false, args.front())) + { + if(!cryptonote::get_account_address_from_str(adr, true, args.front())) + { + std::cout << "target account address has wrong format" << std::endl; + return true; + } + std::cout << "Mining to a testnet address, make sure this is intentional!" << std::endl; + } + uint64_t threads_count = 1; + if(args.size() > 2) + { + return false; + } + else if(args.size() == 2) + { + bool ok = epee::string_tools::get_xtype_from_string(threads_count, args[1]); + threads_count = (ok && 0 < threads_count) ? threads_count : 1; + } + + m_executor.start_mining(adr, threads_count); + + return true; +} + +bool t_command_parser_executor::stop_mining(const std::vector<std::string>& args) +{ + if (!args.empty()) return false; + + return m_executor.stop_mining(); +} + +bool t_command_parser_executor::stop_daemon(const std::vector<std::string>& args) +{ + if (!args.empty()) return false; + + return m_executor.stop_daemon(); +} + +bool t_command_parser_executor::print_status(const std::vector<std::string>& args) +{ + if (!args.empty()) return false; + + return m_executor.print_status(); +} + +bool t_command_parser_executor::set_limit(const std::vector<std::string>& args) +{ + if(args.size()!=1) return false; + int limit; + try { + limit = std::stoi(args[0]); + } + catch(std::invalid_argument& ex) { + return false; + } + if (limit==-1) limit=128; + limit *= 1024; + + return m_executor.set_limit(limit); +} + +bool t_command_parser_executor::set_limit_up(const std::vector<std::string>& args) +{ + if(args.size()!=1) return false; + int limit; + try { + limit = std::stoi(args[0]); + } + catch(std::invalid_argument& ex) { + return false; + } + if (limit==-1) limit=128; + limit *= 1024; + + return m_executor.set_limit_up(limit); +} + +bool t_command_parser_executor::set_limit_down(const std::vector<std::string>& args) +{ + if(args.size()!=1) return false; + int limit; + try { + limit = std::stoi(args[0]); + } + catch(std::invalid_argument& ex) { + return false; + } + if (limit==-1) limit=128; + limit *= 1024; + + return m_executor.set_limit_down(limit); +} +} // namespace daemonize diff --git a/src/daemon/command_parser_executor.h b/src/daemon/command_parser_executor.h new file mode 100644 index 000000000..19ed8ada7 --- /dev/null +++ b/src/daemon/command_parser_executor.h @@ -0,0 +1,95 @@ +/** +@file +@details + +@image html images/other/runtime-commands.png + +*/ + +// Copyright (c) 2014, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include "daemon/rpc_command_executor.h" + +namespace daemonize { + +class t_command_parser_executor final +{ +private: + t_rpc_command_executor m_executor; +public: + t_command_parser_executor( + uint32_t ip + , uint16_t port + ); + + bool print_peer_list(const std::vector<std::string>& args); + + bool save_blockchain(const std::vector<std::string>& args); + + bool show_hash_rate(const std::vector<std::string>& args); + + bool hide_hash_rate(const std::vector<std::string>& args); + + bool show_difficulty(const std::vector<std::string>& args); + + bool print_connections(const std::vector<std::string>& args); + + bool print_blockchain_info(const std::vector<std::string>& args); + + bool set_log_level(const std::vector<std::string>& args); + + bool print_height(const std::vector<std::string>& args); + + bool print_block(const std::vector<std::string>& args); + + bool print_transaction(const std::vector<std::string>& args); + + bool print_transaction_pool_long(const std::vector<std::string>& args); + + bool print_transaction_pool_short(const std::vector<std::string>& args); + + bool start_mining(const std::vector<std::string>& args); + + bool stop_mining(const std::vector<std::string>& args); + + bool stop_daemon(const std::vector<std::string>& args); + + bool print_status(const std::vector<std::string>& args); + + bool set_limit(const std::vector<std::string>& args); + + bool set_limit_up(const std::vector<std::string>& args); + + bool set_limit_down(const std::vector<std::string>& args); + +}; + +} // namespace daemonize diff --git a/src/daemon/command_server.cpp b/src/daemon/command_server.cpp new file mode 100644 index 000000000..ed92fd348 --- /dev/null +++ b/src/daemon/command_server.cpp @@ -0,0 +1,179 @@ +// Copyright (c) 2014, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "cryptonote_config.h" +#include "version.h" +#include "daemon/command_server.h" + +namespace daemonize { + +namespace p = std::placeholders; + +t_command_server::t_command_server( + uint32_t ip + , uint16_t port + ) + : m_parser(ip, port) + , m_command_lookup() +{ + m_command_lookup.set_handler( + "help" + , std::bind(&t_command_server::help, this, p::_1) + , "Show this help" + ); + m_command_lookup.set_handler( + "print_height" + , std::bind(&t_command_parser_executor::print_height, &m_parser, p::_1) + , "Print local blockchain height" + ); + m_command_lookup.set_handler( + "print_pl" + , std::bind(&t_command_parser_executor::print_peer_list, &m_parser, p::_1) + , "Print peer list" + ); + m_command_lookup.set_handler( + "print_cn" + , std::bind(&t_command_parser_executor::print_connections, &m_parser, p::_1) + , "Print connections" + ); + m_command_lookup.set_handler( + "print_bc" + , std::bind(&t_command_parser_executor::print_blockchain_info, &m_parser, p::_1) + , "Print blockchain info in a given blocks range, print_bc <begin_height> [<end_height>]" + ); + m_command_lookup.set_handler( + "print_block" + , std::bind(&t_command_parser_executor::print_block, &m_parser, p::_1) + , "Print block, print_block <block_hash> | <block_height>" + ); + m_command_lookup.set_handler( + "print_tx" + , std::bind(&t_command_parser_executor::print_transaction, &m_parser, p::_1) + , "Print transaction, print_tx <transaction_hash>" + ); + m_command_lookup.set_handler( + "start_mining" + , std::bind(&t_command_parser_executor::start_mining, &m_parser, p::_1) + , "Start mining for specified address, start_mining <addr> [threads=1]" + ); + m_command_lookup.set_handler( + "stop_mining" + , std::bind(&t_command_parser_executor::stop_mining, &m_parser, p::_1) + , "Stop mining" + ); + m_command_lookup.set_handler( + "print_pool" + , std::bind(&t_command_parser_executor::print_transaction_pool_long, &m_parser, p::_1) + , "Print transaction pool (long format)" + ); + m_command_lookup.set_handler( + "print_pool_sh" + , std::bind(&t_command_parser_executor::print_transaction_pool_short, &m_parser, p::_1) + , "Print transaction pool (short format)" + ); + m_command_lookup.set_handler( + "show_hr" + , std::bind(&t_command_parser_executor::show_hash_rate, &m_parser, p::_1) + , "Start showing hash rate" + ); + m_command_lookup.set_handler( + "hide_hr" + , std::bind(&t_command_parser_executor::hide_hash_rate, &m_parser, p::_1) + , "Stop showing hash rate" + ); + m_command_lookup.set_handler( + "save" + , std::bind(&t_command_parser_executor::save_blockchain, &m_parser, p::_1) + , "Save blockchain" + ); + m_command_lookup.set_handler( + "set_log" + , std::bind(&t_command_parser_executor::set_log_level, &m_parser, p::_1) + , "set_log <level> - Change current log detalization level, <level> is a number 0-4" + ); + m_command_lookup.set_handler( + "diff" + , std::bind(&t_command_parser_executor::show_difficulty, &m_parser, p::_1) + , "Show difficulty" + ); + m_command_lookup.set_handler( + "stop_daemon" + , std::bind(&t_command_parser_executor::stop_daemon, &m_parser, p::_1) + , "Stop the daemon" + ); + m_command_lookup.set_handler( + "print_status" + , std::bind(&t_command_parser_executor::print_status, &m_parser, p::_1) + , "Prints daemon status" + ); + m_command_lookup.set_handler( + "limit" + , std::bind(&t_command_parser_executor::set_limit, &m_parser, p::_1) + , "limit <kB/s> - Set download and upload limit" + ); + m_command_lookup.set_handler( + "limit-up" + , std::bind(&t_command_parser_executor::set_limit_up, &m_parser, p::_1) + , "limit <kB/s> - Set upload limit" + ); + m_command_lookup.set_handler( + "limit-down" + , std::bind(&t_command_parser_executor::set_limit_down, &m_parser, p::_1) + , "limit <kB/s> - Set download limit" + ); +} + +bool t_command_server::process_command_str(const std::string& cmd) +{ + return m_command_lookup.process_command_str(cmd); +} + +bool t_command_server::process_command_vec(const std::vector<std::string>& cmd) +{ + return m_command_lookup.process_command_vec(cmd); +} + +bool t_command_server::help(const std::vector<std::string>& args) +{ + std::cout << get_commands_str() << std::endl; + return true; +} + +std::string t_command_server::get_commands_str() +{ + std::stringstream ss; + ss << CRYPTONOTE_NAME << " v" << MONERO_VERSION_FULL << std::endl; + ss << "Commands: " << std::endl; + std::string usage = m_command_lookup.get_usage(); + boost::replace_all(usage, "\n", "\n "); + usage.insert(0, " "); + ss << usage << std::endl; + return ss.str(); +} + +} // namespace daemonize diff --git a/src/daemon/command_server.h b/src/daemon/command_server.h new file mode 100644 index 000000000..ab2d33a3a --- /dev/null +++ b/src/daemon/command_server.h @@ -0,0 +1,67 @@ +/** +@file +@details + + +Passing RPC commands: + +@image html images/other/runtime-commands.png + +*/ + +// Copyright (c) 2014, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include "console_handler.h" +#include "daemon/command_parser_executor.h" + +namespace daemonize { + +class t_command_server { +private: + t_command_parser_executor m_parser; + epee::command_handler m_command_lookup; +public: + t_command_server( + uint32_t ip + , uint16_t port + ); + + bool process_command_str(const std::string& cmd); + + bool process_command_vec(const std::vector<std::string>& cmd); + +private: + bool help(const std::vector<std::string>& args); + + std::string get_commands_str(); +}; + +} // namespace daemonize diff --git a/src/daemon/core.h b/src/daemon/core.h new file mode 100644 index 000000000..3cea70601 --- /dev/null +++ b/src/daemon/core.h @@ -0,0 +1,98 @@ +// Copyright (c) 2014, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include "cryptonote_core/checkpoints_create.h" +#include "cryptonote_core/cryptonote_core.h" +#include "cryptonote_protocol/cryptonote_protocol_handler.h" +#include "misc_log_ex.h" +#include <stdexcept> +#include <boost/program_options.hpp> + +namespace daemonize +{ + +class t_core final +{ +public: + static void init_options(boost::program_options::options_description & option_spec) + { + cryptonote::core::init_options(option_spec); + cryptonote::miner::init_options(option_spec); + } +private: + typedef cryptonote::t_cryptonote_protocol_handler<cryptonote::core> t_protocol_raw; + cryptonote::core m_core; + // TEMPORARY HACK - Yes, this creates a copy, but otherwise the original + // variable map could go out of scope before the run method is called + boost::program_options::variables_map const m_vm_HACK; +public: + t_core( + boost::program_options::variables_map const & vm + ) + : m_core{nullptr} + , m_vm_HACK{vm} + { + } + + // TODO - get rid of circular dependencies in internals + void set_protocol(t_protocol_raw & protocol) + { + m_core.set_cryptonote_protocol(&protocol); + } + + void run() + { + //initialize core here + LOG_PRINT_L0("Initializing core..."); + if (!m_core.init(m_vm_HACK)) + { + throw std::runtime_error("Failed to initialize core"); + } + LOG_PRINT_L0("Core initialized OK"); + } + + cryptonote::core & get() + { + return m_core; + } + + ~t_core() + { + LOG_PRINT_L0("Deinitializing core..."); + try { + m_core.deinit(); + m_core.set_cryptonote_protocol(nullptr); + } catch (...) { + LOG_PRINT_L0("Failed to deinitialize core..."); + } + } +}; + +} diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 9f2ae3167..728b239a0 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1,21 +1,21 @@ -// Copyright (c) 2014-2015, The Monero Project -// +// Copyright (c) 2014, The Monero Project +// // All rights reserved. -// +// // Redistribution and use in source and binary forms, with or without modification, are // permitted provided that the following conditions are met: -// +// // 1. Redistributions of source code must retain the above copyright notice, this list of // conditions and the following disclaimer. -// +// // 2. Redistributions in binary form must reproduce the above copyright notice, this list // of conditions and the following disclaimer in the documentation and/or other // materials provided with the distribution. -// +// // 3. Neither the name of the copyright holder nor the names of its contributors may be // used to endorse or promote products derived from this software without specific // prior written permission. -// +// // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL @@ -25,278 +25,120 @@ // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// +// // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers -// node.cpp : Defines the entry point for the console application. -// Does this file exist? +#include "daemon/daemon.h" - -#include "include_base_utils.h" +#include "common/util.h" +#include "daemon/core.h" +#include "daemon/p2p.h" +#include "daemon/protocol.h" +#include "daemon/rpc.h" +#include "misc_log_ex.h" #include "version.h" - -using namespace epee; - #include <boost/program_options.hpp> +#include <functional> +#include <memory> + +namespace daemonize { + +struct t_internals { +private: + t_protocol protocol; +public: + t_core core; + t_p2p p2p; + t_rpc rpc; + + t_internals( + boost::program_options::variables_map const & vm + ) + : core{vm} + , protocol{vm, core} + , p2p{vm, protocol} + , rpc{vm, core, p2p} + { + // Handle circular dependencies + protocol.set_p2p_endpoint(p2p.get()); + core.set_protocol(protocol.get()); + } +}; -#include "crypto/hash.h" -#include "console_handler.h" -#include "p2p/net_node.h" -#include "cryptonote_config.h" -#include "cryptonote_core/checkpoints_create.h" -#include "cryptonote_core/checkpoints.h" -#include "cryptonote_core/cryptonote_core.h" -#include "rpc/core_rpc_server.h" -#include "cryptonote_protocol/cryptonote_protocol_handler.h" -#include "daemon_commands_handler.h" -#include "version.h" - -#if defined(WIN32) -#include <crtdbg.h> -#endif - -namespace po = boost::program_options; - -namespace +void t_daemon::init_options(boost::program_options::options_description & option_spec) { - const command_line::arg_descriptor<std::string> arg_config_file = {"config-file", "Specify configuration file", std::string(CRYPTONOTE_NAME ".conf")}; - const command_line::arg_descriptor<bool> arg_os_version = {"os-version", ""}; - const command_line::arg_descriptor<std::string> arg_log_file = {"log-file", "", ""}; - const command_line::arg_descriptor<int> arg_log_level = {"log-level", "", LOG_LEVEL_0}; - const command_line::arg_descriptor<bool> arg_console = {"no-console", "Disable daemon console commands"}; - const command_line::arg_descriptor<bool> arg_testnet_on = { - "testnet" - , "Run on testnet. The wallet must be launched with --testnet flag." - , false - }; - const command_line::arg_descriptor<bool> arg_dns_checkpoints = {"enforce-dns-checkpointing", "checkpoints from DNS server will be enforced", false}; + t_core::init_options(option_spec); + t_p2p::init_options(option_spec); + t_rpc::init_options(option_spec); } -bool command_line_preprocessor(const boost::program_options::variables_map& vm) -{ - bool exit = false; - if (command_line::get_arg(vm, command_line::arg_version)) - { - std::cout << CRYPTONOTE_NAME << " v" << MONERO_VERSION_FULL << ENDL; - exit = true; - } - if (command_line::get_arg(vm, arg_os_version)) - { - std::cout << "OS: " << tools::get_os_version_string() << ENDL; - exit = true; - } +t_daemon::t_daemon( + boost::program_options::variables_map const & vm + ) + : mp_internals{new t_internals{vm}} +{} - if (exit) - { - return true; - } +t_daemon::~t_daemon() = default; - int new_log_level = command_line::get_arg(vm, arg_log_level); - if(new_log_level < LOG_LEVEL_MIN || new_log_level > LOG_LEVEL_MAX) - { - LOG_PRINT_L0("Wrong log level value: "); - } - else if (log_space::get_set_log_detalisation_level(false) != new_log_level) +// MSVC is brain-dead and can't default this... +t_daemon::t_daemon(t_daemon && other) +{ + if (this != &other) { - log_space::get_set_log_detalisation_level(true, new_log_level); - LOG_PRINT_L0("LOG_LEVEL set to " << new_log_level); + mp_internals = std::move(other.mp_internals); + other.mp_internals.reset(nullptr); } - - return false; } -int main(int argc, char* argv[]) +// or this +t_daemon & t_daemon::operator=(t_daemon && other) { - - string_tools::set_module_name_and_folder(argv[0]); -#ifdef WIN32 - _CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF ); -#endif - log_space::get_set_log_detalisation_level(true, LOG_LEVEL_0); - log_space::log_singletone::add_logger(LOGGER_CONSOLE, NULL, NULL); - LOG_PRINT_L0("Starting..."); - - TRY_ENTRY(); - - boost::filesystem::path default_data_path {tools::get_default_data_dir()}; - boost::filesystem::path default_testnet_data_path {default_data_path / "testnet"}; - - po::options_description desc_cmd_only("Command line options"); - po::options_description desc_cmd_sett("Command line options and settings options"); - - command_line::add_arg(desc_cmd_only, command_line::arg_help); - command_line::add_arg(desc_cmd_only, command_line::arg_version); - command_line::add_arg(desc_cmd_only, arg_os_version); - // tools::get_default_data_dir() can't be called during static initialization - command_line::add_arg(desc_cmd_only, command_line::arg_data_dir, default_data_path.string()); - command_line::add_arg(desc_cmd_only, command_line::arg_testnet_data_dir, default_testnet_data_path.string()); - command_line::add_arg(desc_cmd_only, arg_config_file); - - command_line::add_arg(desc_cmd_sett, arg_log_file); - command_line::add_arg(desc_cmd_sett, arg_log_level); - command_line::add_arg(desc_cmd_sett, arg_console); - command_line::add_arg(desc_cmd_sett, arg_testnet_on); - command_line::add_arg(desc_cmd_sett, arg_dns_checkpoints); - - cryptonote::core::init_options(desc_cmd_sett); - cryptonote::core_rpc_server::init_options(desc_cmd_sett); - nodetool::node_server<cryptonote::t_cryptonote_protocol_handler<cryptonote::core> >::init_options(desc_cmd_sett); - cryptonote::miner::init_options(desc_cmd_sett); - - po::options_description desc_options("Allowed options"); - desc_options.add(desc_cmd_only).add(desc_cmd_sett); - - po::variables_map vm; - bool r = command_line::handle_error_helper(desc_options, [&]() - { - po::store(po::parse_command_line(argc, argv, desc_options), vm); - po::notify(vm); - - return true; - }); - if (!r) - return 1; - - if (command_line::get_arg(vm, command_line::arg_help)) + if (this != &other) { - std::cout << CRYPTONOTE_NAME << " v" << MONERO_VERSION_FULL << ENDL << ENDL; - std::cout << desc_options << std::endl; - return false; + mp_internals = std::move(other.mp_internals); + other.mp_internals.reset(nullptr); } + return *this; +} - bool testnet_mode = command_line::get_arg(vm, arg_testnet_on); - - auto data_dir_arg = testnet_mode ? command_line::arg_testnet_data_dir : command_line::arg_data_dir; - - std::string data_dir = command_line::get_arg(vm, data_dir_arg); - tools::create_directories_if_necessary(data_dir); - std::string config = command_line::get_arg(vm, arg_config_file); - - boost::filesystem::path data_dir_path(data_dir); - boost::filesystem::path config_path(config); - if (!config_path.has_parent_path()) +bool t_daemon::run() +{ + if (nullptr == mp_internals) { - config_path = data_dir_path / config_path; + throw std::runtime_error{"Can't run stopped daemon"}; } + tools::signal_handler::install(std::bind(&daemonize::t_daemon::stop, this)); - boost::system::error_code ec; - if (boost::filesystem::exists(config_path, ec)) + try { - po::store(po::parse_config_file<char>(config_path.string<std::string>().c_str(), desc_cmd_sett), vm); + mp_internals->core.run(); + mp_internals->rpc.run(); + mp_internals->p2p.run(); + mp_internals->rpc.stop(); + LOG_PRINT("Node stopped.", LOG_LEVEL_0); + return true; } - - //set up logging options - boost::filesystem::path log_file_path(command_line::get_arg(vm, arg_log_file)); - if (log_file_path.empty()) - log_file_path = log_space::log_singletone::get_default_log_file(); - std::string log_dir; - log_dir = log_file_path.has_parent_path() ? log_file_path.parent_path().string() : log_space::log_singletone::get_default_log_folder(); - - log_space::log_singletone::add_logger(LOGGER_FILE, log_file_path.filename().string().c_str(), log_dir.c_str()); - LOG_PRINT_L0(CRYPTONOTE_NAME << " v" << MONERO_VERSION_FULL); - - if (command_line_preprocessor(vm)) + catch (std::exception const & ex) { - return 0; + LOG_ERROR("Uncaught exception! " << ex.what()); + return false; } - - LOG_PRINT("Module folder: " << argv[0], LOG_LEVEL_0); - - bool res = true; - cryptonote::checkpoints checkpoints; - res = cryptonote::create_checkpoints(checkpoints); - CHECK_AND_ASSERT_MES(res, 1, "Failed to initialize checkpoints"); - boost::filesystem::path json(JSON_HASH_FILE_NAME); - boost::filesystem::path checkpoint_json_hashfile_fullpath = data_dir / json; - - //create objects and link them - cryptonote::core ccore(NULL); - - // tell core if we're enforcing dns checkpoints - bool enforce_dns = command_line::get_arg(vm, arg_dns_checkpoints); - ccore.set_enforce_dns_checkpoints(enforce_dns); - - if (testnet_mode) { - LOG_PRINT_L0("Starting in testnet mode!"); - } else { - ccore.set_checkpoints(std::move(checkpoints)); - ccore.set_checkpoints_file_path(checkpoint_json_hashfile_fullpath.string()); + catch (...) + { + LOG_ERROR("Uncaught exception!"); + return false; } +} - cryptonote::t_cryptonote_protocol_handler<cryptonote::core> cprotocol(ccore, NULL); - nodetool::node_server<cryptonote::t_cryptonote_protocol_handler<cryptonote::core> > p2psrv { - cprotocol - , testnet_mode ? std::move(config::testnet::NETWORK_ID) : std::move(config::NETWORK_ID) - }; - cryptonote::core_rpc_server rpc_server {ccore, p2psrv, testnet_mode}; - cprotocol.set_p2p_endpoint(&p2psrv); - ccore.set_cryptonote_protocol(&cprotocol); - daemon_cmmands_handler dch(p2psrv, testnet_mode); - - //initialize objects - LOG_PRINT_L0("Initializing P2P server..."); - res = p2psrv.init(vm, testnet_mode); - CHECK_AND_ASSERT_MES(res, 1, "Failed to initialize P2P server."); - LOG_PRINT_L0("P2P server initialized OK"); - - LOG_PRINT_L0("Initializing protocol..."); - res = cprotocol.init(vm); - CHECK_AND_ASSERT_MES(res, 1, "Failed to initialize protocol."); - LOG_PRINT_L0("Protocol initialized OK"); - - LOG_PRINT_L0("Initializing core RPC server..."); - res = rpc_server.init(vm); - CHECK_AND_ASSERT_MES(res, 1, "Failed to initialize core RPC server."); - LOG_PRINT_GREEN("Core RPC server initialized OK on port: " << rpc_server.get_binded_port(), LOG_LEVEL_0); - - //initialize core here - LOG_PRINT_L0("Initializing core..."); - res = ccore.init(vm, testnet_mode); - CHECK_AND_ASSERT_MES(res, 1, "Failed to initialize core"); - LOG_PRINT_L0("Core initialized OK"); - - // start components - if(!command_line::has_arg(vm, arg_console)) +void t_daemon::stop() +{ + if (nullptr == mp_internals) { - dch.start_handling(); + throw std::runtime_error{"Can't stop stopped daemon"}; } - - LOG_PRINT_L0("Starting core RPC server..."); - res = rpc_server.run(2, false); - CHECK_AND_ASSERT_MES(res, 1, "Failed to initialize core RPC server."); - LOG_PRINT_L0("Core RPC server started ok"); - - tools::signal_handler::install([&dch, &p2psrv] { - dch.stop_handling(); - p2psrv.send_stop_signal(); - }); - - LOG_PRINT_L0("Starting P2P net loop..."); - p2psrv.run(); - LOG_PRINT_L0("P2P net loop stopped"); - - //stop components - LOG_PRINT_L0("Stopping core rpc server..."); - rpc_server.send_stop_signal(); - rpc_server.timed_wait_server_stop(5000); - - //deinitialize components - LOG_PRINT_L0("Deinitializing core..."); - ccore.deinit(); - LOG_PRINT_L0("Deinitializing RPC server ..."); - rpc_server.deinit(); - LOG_PRINT_L0("Deinitializing protocol..."); - cprotocol.deinit(); - LOG_PRINT_L0("Deinitializing P2P..."); - p2psrv.deinit(); - - - ccore.set_cryptonote_protocol(NULL); - cprotocol.set_p2p_endpoint(NULL); - - LOG_PRINT("Node stopped.", LOG_LEVEL_0); - return 0; - - CATCH_ENTRY_L0("main", 1); + mp_internals->p2p.stop(); + mp_internals->rpc.stop(); + mp_internals.reset(nullptr); // Ensure resources are cleaned up before we return } +} // namespace daemonize diff --git a/src/daemon/daemon.h b/src/daemon/daemon.h new file mode 100644 index 000000000..5c5c27b6b --- /dev/null +++ b/src/daemon/daemon.h @@ -0,0 +1,54 @@ +// Copyright (c) 2014, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include <memory> +#include <boost/program_options.hpp> + +namespace daemonize { + +class t_internals; + +class t_daemon final { +public: + static void init_options(boost::program_options::options_description & option_spec); +private: + std::unique_ptr<t_internals> mp_internals; +public: + t_daemon( + boost::program_options::variables_map const & vm + ); + t_daemon(t_daemon && other); + t_daemon & operator=(t_daemon && other); + ~t_daemon(); + + bool run(); + void stop(); +}; +} diff --git a/src/daemon/daemon_commands_handler.h b/src/daemon/daemon_commands_handler.h index 10a07cf89..7416af9c5 100644 --- a/src/daemon/daemon_commands_handler.h +++ b/src/daemon/daemon_commands_handler.h @@ -1,35 +1,7 @@ -// Copyright (c) 2014-2015, The Monero Project -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers - - -/* This isn't a header file, may want to refactor this... */ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + #pragma once #include <boost/lexical_cast.hpp> @@ -41,21 +13,18 @@ #include "crypto/hash.h" #include "version.h" -/*! - * \brief I don't really know right now - * - * - */ +//#include "net/net_helper.h" +//#include "../p2p/p2p_protocol_defs.h" +//#include "../p2p/net_peerlist_boost_serialization.h" +//#include "net/local_ip.h" +//#include "crypto/crypto.h" +//#include "storages/levin_abstract_invoke2.h" + class daemon_cmmands_handler { nodetool::node_server<cryptonote::t_cryptonote_protocol_handler<cryptonote::core> >& m_srv; public: - daemon_cmmands_handler( - nodetool::node_server<cryptonote::t_cryptonote_protocol_handler<cryptonote::core> >& srv - , bool testnet - ) - : m_srv(srv) - , m_testnet(testnet) + daemon_cmmands_handler(nodetool::node_server<cryptonote::t_cryptonote_protocol_handler<cryptonote::core> >& srv):m_srv(srv) { m_cmd_binder.set_handler("help", boost::bind(&daemon_cmmands_handler::help, this, _1), "Show this help"); m_cmd_binder.set_handler("print_pl", boost::bind(&daemon_cmmands_handler::print_pl, this, _1), "Print peer list"); @@ -74,6 +43,10 @@ public: m_cmd_binder.set_handler("save", boost::bind(&daemon_cmmands_handler::save, this, _1), "Save blockchain"); m_cmd_binder.set_handler("set_log", boost::bind(&daemon_cmmands_handler::set_log, this, _1), "set_log <level> - Change current log detalization level, <level> is a number 0-4"); m_cmd_binder.set_handler("diff", boost::bind(&daemon_cmmands_handler::diff, this, _1), "Show difficulty"); + m_cmd_binder.set_handler("limit-up", boost::bind(&daemon_cmmands_handler::limit_up, this, _1), "Set upload limit"); + m_cmd_binder.set_handler("limit-down", boost::bind(&daemon_cmmands_handler::limit_down, this, _1), "Set download limit"); + m_cmd_binder.set_handler("limit", boost::bind(&daemon_cmmands_handler::limit, this, _1), "Set download and upload limit"); + m_cmd_binder.set_handler("out_peers", boost::bind(&daemon_cmmands_handler::out_peers_limit, this, _1), "Set max limit of out peers"); } bool start_handling() @@ -89,7 +62,7 @@ public: private: epee::srv_console_handlers_binder<nodetool::node_server<cryptonote::t_cryptonote_protocol_handler<cryptonote::core> > > m_cmd_binder; - bool m_testnet; + //-------------------------------------------------------------------------------- std::string get_commands_str() @@ -122,6 +95,122 @@ private: return true; } //-------------------------------------------------------------------------------- + bool limit_up(const std::vector<std::string>& args) + { + if(args.size()!=1) { + std::cout << "Usage: limit_up <speed>" << ENDL; + return false; + } + + int limit; + try { + limit = std::stoi(args[0]); + } + catch(std::invalid_argument& ex) { + return false; + } + + if (limit==-1) { + limit=128; + //this->islimitup=false; + } + + limit *= 1024; + + + //nodetool::epee::net_utils::connection<epee::levin::async_protocol_handler<nodetool::p2p_connection_context> >::set_rate_up_limit( limit ); + epee::net_utils::connection_basic::set_rate_up_limit( limit ); + std::cout << "Set limit-up to " << limit/1024 << " kB/s" << std::endl; + + return true; + } + + //-------------------------------------------------------------------------------- + bool limit_down(const std::vector<std::string>& args) + { + + if(args.size()!=1) { + std::cout << "Usage: limit_down <speed>" << ENDL; + return true; + } + + int limit; + try { + limit = std::stoi(args[0]); + } + + catch(std::invalid_argument& ex) { + return false; + } + + if (limit==-1) { + limit=128; + //this->islimitup=false; + } + + limit *= 1024; + + + //nodetool::epee::net_utils::connection<epee::levin::async_protocol_handler<nodetool::p2p_connection_context> >::set_rate_up_limit( limit ); + epee::net_utils::connection_basic::set_rate_down_limit( limit ); + std::cout << "Set limit-down to " << limit/1024 << " kB/s" << std::endl; + + return true; + } + +//-------------------------------------------------------------------------------- + bool limit(const std::vector<std::string>& args) + { + if(args.size()!=1) { + std::cout << "Usage: limit_down <speed>" << ENDL; + return true; + } + + int limit; + try { + limit = std::stoi(args[0]); + } + catch(std::invalid_argument& ex) { + return false; + } + + if (limit==-1) { + limit=128; + //this->islimitup=false; + } + + limit *= 1024; + + + //nodetool::epee::net_utils::connection<epee::levin::async_protocol_handler<nodetool::p2p_connection_context> >::set_rate_up_limit( limit ); + epee::net_utils::connection_basic::set_rate_down_limit( limit ); + epee::net_utils::connection_basic::set_rate_up_limit( limit ); + std::cout << "Set limit-down to " << limit/1024 << " kB/s" << std::endl; + std::cout << "Set limit-up to " << limit/1024 << " kB/s" << std::endl; + + return true; + } + //-------------------------------------------------------------------------------- + bool out_peers_limit(const std::vector<std::string>& args) { + if(args.size()!=1) { + std::cout << "Usage: limit_down <speed>" << ENDL; + return true; + } + + int limit; + try { + limit = std::stoi(args[0]); + } + + catch(std::invalid_argument& ex) { + return false; + } + + LOG_PRINT_RED_L0("connections_count: " << limit); + m_srv.m_config.m_net_config.connections_count = limit; + return true; + } + //-------------------------------------------------------------------------------- bool show_hr(const std::vector<std::string>& args) { if(!m_srv.get_payload_object().get_core().get_miner().is_mining()) @@ -234,7 +323,11 @@ private: return true; } + // TODO what the hell causes compilation warning in following code line +PUSH_WARNINGS +DISABLE_GCC_WARNING(maybe-uninitialized) log_space::log_singletone::get_set_log_detalisation_level(true, l); +POP_WARNINGS return true; } @@ -374,7 +467,7 @@ private: } cryptonote::account_public_address adr; - if(!cryptonote::get_account_address_from_str(adr, m_testnet, args.front())) + if(!cryptonote::get_account_address_from_str(adr, args.front())) { std::cout << "target account address has wrong format" << std::endl; return true; diff --git a/src/daemon/executor.cpp b/src/daemon/executor.cpp new file mode 100644 index 000000000..f4def4d55 --- /dev/null +++ b/src/daemon/executor.cpp @@ -0,0 +1,71 @@ +// Copyright (c) 2014, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "daemon/executor.h" + +#include "misc_log_ex.h" + +#include "common/command_line.h" +#include "cryptonote_config.h" +#include "version.h" + +#include <string> + +namespace daemonize +{ + std::string const t_executor::NAME = "Monero Daemon"; + + void t_executor::init_options( + boost::program_options::options_description & configurable_options + ) + { + t_daemon::init_options(configurable_options); + } + + std::string const & t_executor::name() + { + return NAME; + } + + t_daemon t_executor::create_daemon( + boost::program_options::variables_map const & vm + ) + { + LOG_PRINT_L0(CRYPTONOTE_NAME << " v" << MONERO_VERSION_FULL); + return t_daemon{vm}; + } + + bool t_executor::run_interactive( + boost::program_options::variables_map const & vm + ) + { + epee::log_space::log_singletone::add_logger(LOGGER_CONSOLE, NULL, NULL); + return t_daemon{vm}.run(); + } +} + diff --git a/src/daemon/executor.h b/src/daemon/executor.h new file mode 100644 index 000000000..553896875 --- /dev/null +++ b/src/daemon/executor.h @@ -0,0 +1,60 @@ +// Copyright (c) 2014, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include "daemon/daemon.h" +#include <boost/program_options/options_description.hpp> +#include <boost/program_options/variables_map.hpp> +#include <string> +#include <vector> + +namespace daemonize +{ + class t_executor final + { + public: + typedef ::daemonize::t_daemon t_daemon; + + static std::string const NAME; + + static void init_options( + boost::program_options::options_description & configurable_options + ); + + std::string const & name(); + + t_daemon create_daemon( + boost::program_options::variables_map const & vm + ); + + bool run_interactive( + boost::program_options::variables_map const & vm + ); + }; +} diff --git a/src/daemon/main.cpp b/src/daemon/main.cpp new file mode 100644 index 000000000..a2b389292 --- /dev/null +++ b/src/daemon/main.cpp @@ -0,0 +1,229 @@ +// Copyright (c) 2014, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#include "common/command_line.h" +#include "common/scoped_message_writer.h" +#include "common/util.h" +#include "cryptonote_core/cryptonote_core.h" +#include "cryptonote_core/miner.h" +#include "daemon/command_server.h" +#include "daemon/daemon.h" +#include "daemon/executor.h" +#include "daemonizer/daemonizer.h" +#include "misc_log_ex.h" +#include "p2p/net_node.h" +#include "rpc/core_rpc_server.h" +#include <boost/program_options.hpp> +#include "daemon/command_line_args.h" + +namespace po = boost::program_options; +namespace bf = boost::filesystem; + +int main(int argc, char const * argv[]) +{ + try { + + epee::string_tools::set_module_name_and_folder(argv[0]); + + // Build argument description + po::options_description all_options("All"); + po::options_description hidden_options("Hidden"); + po::options_description visible_options("Options"); + po::options_description core_settings("Settings"); + po::positional_options_description positional_options; + { + bf::path default_data_dir = daemonizer::get_default_data_dir(); + + // Misc Options + + command_line::add_arg(visible_options, command_line::arg_help); + command_line::add_arg(visible_options, command_line::arg_version); + command_line::add_arg(visible_options, daemon_args::arg_os_version); + command_line::add_arg(visible_options, command_line::arg_data_dir, default_data_dir.string()); + bf::path default_conf = default_data_dir / std::string(CRYPTONOTE_NAME ".conf"); + command_line::add_arg(visible_options, daemon_args::arg_config_file, default_conf.string()); + + // Settings + bf::path default_log = default_data_dir / std::string(CRYPTONOTE_NAME ".log"); + command_line::add_arg(core_settings, daemon_args::arg_log_file, default_log.string()); + command_line::add_arg(core_settings, daemon_args::arg_log_level); + command_line::add_arg(core_settings, daemon_args::arg_testnet_on); + command_line::add_arg(core_settings, daemon_args::arg_dns_checkpoints); + daemonizer::init_options(hidden_options, visible_options); + daemonize::t_executor::init_options(core_settings); + + // Hidden options + command_line::add_arg(hidden_options, daemon_args::arg_command); + + visible_options.add(core_settings); + all_options.add(visible_options); + all_options.add(hidden_options); + + // Positional + positional_options.add(daemon_args::arg_command.name, -1); // -1 for unlimited arguments + } + + // Do command line parsing + po::variables_map vm; + bool ok = command_line::handle_error_helper(visible_options, [&]() + { + boost::program_options::store( + boost::program_options::command_line_parser(argc, argv) + .options(all_options).positional(positional_options).run() + , vm + ); + + return true; + }); + if (!ok) return 1; + + if (command_line::get_arg(vm, command_line::arg_help)) + { + std::cout << CRYPTONOTE_NAME << " v" << MONERO_VERSION_FULL << ENDL << ENDL; + std::cout << "Usage: " + std::string{argv[0]} + " [options|settings] [daemon_command...]" << std::endl << std::endl; + std::cout << visible_options << std::endl; + return 0; + } + + // Monero Version + if (command_line::get_arg(vm, command_line::arg_version)) + { + std::cout << CRYPTONOTE_NAME << " v" << MONERO_VERSION_FULL << ENDL; + return 0; + } + + // OS + if (command_line::get_arg(vm, daemon_args::arg_os_version)) + { + std::cout << "OS: " << tools::get_os_version_string() << ENDL; + return 0; + } + + bool testnet_mode = command_line::get_arg(vm, daemon_args::arg_testnet_on); + + auto data_dir_arg = testnet_mode ? command_line::arg_testnet_data_dir : command_line::arg_data_dir; + + // Create data dir if it doesn't exist + boost::filesystem::path data_dir = boost::filesystem::absolute( + command_line::get_arg(vm, data_dir_arg)); + tools::create_directories_if_necessary(data_dir.string()); + + // FIXME: not sure on windows implementation default, needs further review + //bf::path relative_path_base = daemonizer::get_relative_path_base(vm); + bf::path relative_path_base = data_dir; + + std::string config = command_line::get_arg(vm, daemon_args::arg_config_file); + + boost::filesystem::path data_dir_path(data_dir); + boost::filesystem::path config_path(config); + if (!config_path.has_parent_path()) + { + config_path = data_dir / config_path; + } + + boost::system::error_code ec; + if (bf::exists(config_path, ec)) + { + po::store(po::parse_config_file<char>(config_path.string<std::string>().c_str(), core_settings), vm); + } + po::notify(vm); + + // If there are positional options, we're running a daemon command + if (command_line::has_arg(vm, daemon_args::arg_command)) + { + auto command = command_line::get_arg(vm, daemon_args::arg_command); + auto rpc_ip_str = command_line::get_arg(vm, cryptonote::core_rpc_server::arg_rpc_bind_ip); + auto rpc_port_str = command_line::get_arg(vm, cryptonote::core_rpc_server::arg_rpc_bind_port); + + uint32_t rpc_ip; + uint16_t rpc_port; + if (!epee::string_tools::get_ip_int32_from_string(rpc_ip, rpc_ip_str)) + { + std::cerr << "Invalid IP: " << rpc_ip_str << std::endl; + return 1; + } + if (!epee::string_tools::get_xtype_from_string(rpc_port, rpc_port_str)) + { + std::cerr << "Invalid port: " << rpc_port_str << std::endl; + return 1; + } + + daemonize::t_command_server rpc_commands{rpc_ip, rpc_port}; + if (rpc_commands.process_command_vec(command)) + { + return 0; + } + else + { + std::cerr << "Unknown command" << std::endl; + return 1; + } + } + + // Start with log level 0 + epee::log_space::get_set_log_detalisation_level(true, LOG_LEVEL_0); + + // Set log level + { + int new_log_level = command_line::get_arg(vm, daemon_args::arg_log_level); + if(new_log_level < LOG_LEVEL_MIN || new_log_level > LOG_LEVEL_MAX) + { + LOG_PRINT_L0("Wrong log level value: " << new_log_level); + } + else if (epee::log_space::get_set_log_detalisation_level(false) != new_log_level) + { + epee::log_space::get_set_log_detalisation_level(true, new_log_level); + LOG_PRINT_L0("LOG_LEVEL set to " << new_log_level); + } + } + + // Set log file + { + bf::path log_file_path{bf::absolute(command_line::get_arg(vm, daemon_args::arg_log_file), relative_path_base)}; + + epee::log_space::log_singletone::add_logger( + LOGGER_FILE + , log_file_path.filename().string().c_str() + , log_file_path.parent_path().string().c_str() + ); + } + + return daemonizer::daemonize(argc, argv, daemonize::t_executor{}, vm); + } + catch (std::exception const & ex) + { + LOG_ERROR("Exception in main! " << ex.what()); + } + catch (...) + { + LOG_ERROR("Exception in main!"); + } + return 1; +} diff --git a/src/daemon/p2p.h b/src/daemon/p2p.h new file mode 100644 index 000000000..355a784b1 --- /dev/null +++ b/src/daemon/p2p.h @@ -0,0 +1,99 @@ +// Copyright (c) 2014, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#pragma once + +#include "cryptonote_protocol/cryptonote_protocol_handler.h" +#include "daemon/protocol.h" +#include "misc_log_ex.h" +#include "p2p/net_node.h" +#include <stdexcept> +#include <boost/program_options.hpp> + +namespace daemonize +{ + +class t_p2p final +{ +private: + typedef cryptonote::t_cryptonote_protocol_handler<cryptonote::core> t_protocol_raw; + typedef nodetool::node_server<t_protocol_raw> t_node_server; +public: + static void init_options(boost::program_options::options_description & option_spec) + { + t_node_server::init_options(option_spec); + } +private: + t_node_server m_server; +public: + t_p2p( + boost::program_options::variables_map const & vm + , t_protocol & protocol + ) + : m_server{protocol.get()} + { + //initialize objects + LOG_PRINT_L0("Initializing p2p server..."); + if (!m_server.init(vm)) + { + throw std::runtime_error("Failed to initialize p2p server."); + } + LOG_PRINT_L0("P2p server initialized OK"); + } + + t_node_server & get() + { + return m_server; + } + + void run() + { + LOG_PRINT_L0("Starting p2p net loop..."); + m_server.run(); + LOG_PRINT_L0("p2p net loop stopped"); + } + + void stop() + { + m_server.send_stop_signal(); + } + + ~t_p2p() + { + LOG_PRINT_L0("Deinitializing p2p..."); + try { + m_server.deinit(); + } catch (...) { + LOG_PRINT_L0("Failed to deinitialize p2p..."); + } + } +}; + +} diff --git a/src/daemon/protocol.h b/src/daemon/protocol.h new file mode 100644 index 000000000..2f6a66f49 --- /dev/null +++ b/src/daemon/protocol.h @@ -0,0 +1,88 @@ +// Copyright (c) 2014, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#pragma once + +#include "cryptonote_protocol/cryptonote_protocol_handler.h" +#include "misc_log_ex.h" +#include "p2p/net_node.h" +#include <stdexcept> +#include <boost/program_options.hpp> + +namespace daemonize +{ + +class t_protocol final +{ +private: + typedef cryptonote::t_cryptonote_protocol_handler<cryptonote::core> t_protocol_raw; + typedef nodetool::node_server<t_protocol_raw> t_node_server; + + t_protocol_raw m_protocol; +public: + t_protocol( + boost::program_options::variables_map const & vm + , t_core & core + ) + : m_protocol{core.get(), nullptr} + { + LOG_PRINT_L0("Initializing cryptonote protocol..."); + if (!m_protocol.init(vm)) + { + throw std::runtime_error("Failed to initialize cryptonote protocol."); + } + LOG_PRINT_L0("Cryptonote protocol initialized OK"); + } + + t_protocol_raw & get() + { + return m_protocol; + } + + void set_p2p_endpoint( + t_node_server & server + ) + { + m_protocol.set_p2p_endpoint(&server); + } + + ~t_protocol() + { + LOG_PRINT_L0("Deinitializing cryptonote_protocol..."); + try { + m_protocol.deinit(); + m_protocol.set_p2p_endpoint(nullptr); + } catch (...) { + LOG_PRINT_L0("Failed to deinitialize protocol..."); + } + } +}; + +} diff --git a/src/daemon/rpc.h b/src/daemon/rpc.h new file mode 100644 index 000000000..cf7da51a7 --- /dev/null +++ b/src/daemon/rpc.h @@ -0,0 +1,96 @@ +// Copyright (c) 2014, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#pragma once + +#include "daemon/core.h" +#include "daemon/p2p.h" +#include "misc_log_ex.h" +#include "rpc/core_rpc_server.h" +#include <boost/program_options.hpp> +#include <stdexcept> + +namespace daemonize +{ + +class t_rpc final +{ +public: + static void init_options(boost::program_options::options_description & option_spec) + { + cryptonote::core_rpc_server::init_options(option_spec); + } +private: + cryptonote::core_rpc_server m_server; +public: + t_rpc( + boost::program_options::variables_map const & vm + , t_core & core + , t_p2p & p2p + ) + : m_server{core.get(), p2p.get()} + { + LOG_PRINT_L0("Initializing core rpc server..."); + if (!m_server.init(vm)) + { + throw std::runtime_error("Failed to initialize core rpc server."); + } + LOG_PRINT_GREEN("Core rpc server initialized OK on port: " << m_server.get_binded_port(), LOG_LEVEL_0); + } + + void run() + { + LOG_PRINT_L0("Starting core rpc server..."); + if (!m_server.run(2, false)) + { + throw std::runtime_error("Failed to start core rpc server."); + } + LOG_PRINT_L0("Core rpc server started ok"); + } + + void stop() + { + LOG_PRINT_L0("Stopping core rpc server..."); + m_server.send_stop_signal(); + m_server.timed_wait_server_stop(5000); + } + + ~t_rpc() + { + LOG_PRINT_L0("Deinitializing rpc server..."); + try { + m_server.deinit(); + } catch (...) { + LOG_PRINT_L0("Failed to deinitialize rpc server..."); + } + } +}; + +} diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp new file mode 100644 index 000000000..bdad28f6c --- /dev/null +++ b/src/daemon/rpc_command_executor.cpp @@ -0,0 +1,435 @@ +// Copyright (c) 2014, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#include "string_tools.h" +#include "common/scoped_message_writer.h" +#include "daemon/rpc_command_executor.h" +#include "rpc/core_rpc_server_commands_defs.h" +#include <boost/format.hpp> +#include <ctime> + +namespace daemonize { + +namespace { + /* + void print_peer(std::string const & prefix, cryptonote::peer const & peer) + { + time_t now; + time(&now); + time_t last_seen = static_cast<time_t>(peer.last_seen); + + std::string id_str; + std::string port_str; + std::string elapsed = epee::misc_utils::get_time_interval_string(now - last_seen); + std::string ip_str = epee::string_tools::get_ip_string_from_int32(peer.ip); + epee::string_tools::xtype_to_string(peer.id, id_str); + epee::string_tools::xtype_to_string(peer.port, port_str); + std::string addr_str = ip_str + ":" + port_str; + tools::msg_writer() << boost::format("%-10s %-25s %-25s %s") % prefix % id_str % addr_str % elapsed; + } + */ + + void print_block_header(cryptonote::block_header_responce const & header) + { + tools::success_msg_writer() + << "timestamp: " << boost::lexical_cast<std::string>(header.timestamp) << std::endl + << "previous hash: " << header.prev_hash << std::endl + << "nonce: " << boost::lexical_cast<std::string>(header.nonce) << std::endl + << "is orphan: " << header.orphan_status << std::endl + << "height: " << boost::lexical_cast<std::string>(header.height) << std::endl + << "depth: " << boost::lexical_cast<std::string>(header.depth) << std::endl + << "hash: " << header.hash + << "difficulty: " << boost::lexical_cast<std::string>(header.difficulty) << std::endl + << "reward: " << boost::lexical_cast<std::string>(header.reward); + } +} + +t_rpc_command_executor::t_rpc_command_executor( + uint32_t ip + , uint16_t port + ) + : m_rpc_client{ip, port} +{} + +bool t_rpc_command_executor::print_peer_list() { +/* + cryptonote::COMMAND_RPC_GET_PEER_LIST::request req; + cryptonote::COMMAND_RPC_GET_PEER_LIST::response res; + + bool ok = m_rpc_client.rpc_request(req, res, "/get_peer_list", "Couldn't retrieve peer list"); + + if (!ok) return false; + + for (auto & peer : res.white_list) + { + print_peer("white", peer); + } + + for (auto & peer : res.gray_list) + { + print_peer("gray", peer); + } + +*/ + return true; +} + +bool t_rpc_command_executor::save_blockchain() { + cryptonote::COMMAND_RPC_SAVE_BC::request req; + cryptonote::COMMAND_RPC_SAVE_BC::response res; + + if (m_rpc_client.rpc_request(req, res, "/save_bc", "Couldn't save blockchain")) + { + tools::success_msg_writer() << "Blockchain saved"; + } + + return true; +} + +bool t_rpc_command_executor::show_hash_rate() { +/* + cryptonote::COMMAND_RPC_SET_LOG_HASH_RATE::request req; + cryptonote::COMMAND_RPC_SET_LOG_HASH_RATE::response res; + req.visible = true; + + if (m_rpc_client.rpc_request(req, res, "/set_log_hash_rate", "Unsuccessful")) + { + tools::success_msg_writer() << "Hash rate logging is on"; + } + +*/ + return true; +} + +bool t_rpc_command_executor::hide_hash_rate() { +/* + cryptonote::COMMAND_RPC_SET_LOG_HASH_RATE::request req; + cryptonote::COMMAND_RPC_SET_LOG_HASH_RATE::response res; + req.visible = false; + + if (m_rpc_client.rpc_request(req, res, "/set_log_hash_rate", "Unsuccessful")) + { + tools::success_msg_writer() << "Hash rate logging is off"; + } + +*/ + return true; +} + +bool t_rpc_command_executor::show_difficulty() { + cryptonote::COMMAND_RPC_GET_INFO::request req; + cryptonote::COMMAND_RPC_GET_INFO::response res; + + if (m_rpc_client.rpc_request(req, res, "/getinfo", "Problem fetching info")) + { + tools::success_msg_writer() << "BH: " << res.height + << ", DIFF: " << res.difficulty + << ", HR: " << (int) res.difficulty / 60L << " H/s"; + } + + return true; +} + +bool t_rpc_command_executor::print_connections() { + cryptonote::COMMAND_RPC_GET_CONNECTIONS::request req; + cryptonote::COMMAND_RPC_GET_CONNECTIONS::response res; + + if (m_rpc_client.rpc_request(req, res, "/get_connections", "Unsuccessful")) + { + for (auto & info : res.connections) + { + std::string address = info.ip + ":" + info.port; + std::string in_out = info.incoming ? "INC" : "OUT"; + tools::msg_writer() << boost::format("%-25s peer_id: %-25s %s") % address % info.peer_id % in_out; + } + } + + return true; +} + +bool t_rpc_command_executor::print_blockchain_info(uint64_t start_block_index, uint64_t end_block_index) { +/* + cryptonote::COMMAND_RPC_GET_BLOCK_HEADERS_RANGE::request req; + cryptonote::COMMAND_RPC_GET_BLOCK_HEADERS_RANGE::response res; + + req.start_height = start_block_index; + req.end_height = end_block_index; + + if (m_rpc_client.json_rpc_request(req, res, "getblockheadersrange", "Unsuccessful")) + { + for (auto & header : res.headers) + { + std::cout << "height " << header.height + << ", timestamp " << header.timestamp + << ", cumul_dif " << header.cumulative_difficulty + << ", cumul_size " << header.cumulative_size << std::endl + << "id " << header.hash << std::endl + << "difficulty " << header.difficulty + << ", nonce " << header.nonce + << ", tx_count " << header.tx_count << std::endl; + } + } + +*/ + return true; +} + +bool t_rpc_command_executor::set_log_level(int8_t level) { +/* + cryptonote::COMMAND_RPC_SET_LOG_LEVEL::request req; + cryptonote::COMMAND_RPC_SET_LOG_LEVEL::response res; + req.level = level; + + if (m_rpc_client.rpc_request(req, res, "/set_log_level", "Unsuccessful")) + { + tools::success_msg_writer() << "Log level is now " << boost::lexical_cast<std::string>(level); + } + +*/ + return true; +} + +bool t_rpc_command_executor::print_height() { + cryptonote::COMMAND_RPC_GET_HEIGHT::request req; + cryptonote::COMMAND_RPC_GET_HEIGHT::response res; + + if (m_rpc_client.rpc_request(req, res, "/getheight", "Unsuccessful")) + { + tools::success_msg_writer() << boost::lexical_cast<std::string>(res.height); + } + + return true; +} + +bool t_rpc_command_executor::print_block_by_hash(crypto::hash block_hash) { + cryptonote::COMMAND_RPC_GET_BLOCK_HEADER_BY_HASH::request req; + cryptonote::COMMAND_RPC_GET_BLOCK_HEADER_BY_HASH::response res; + + req.hash = epee::string_tools::pod_to_hex(block_hash); + + if (m_rpc_client.json_rpc_request(req, res, "getblockheaderbyhash", "Unsuccessful")) + { + print_block_header(res.block_header); + } + + return true; +} + +bool t_rpc_command_executor::print_block_by_height(uint64_t height) { + cryptonote::COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT::request req; + cryptonote::COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT::response res; + + req.height = height; + + if (m_rpc_client.json_rpc_request(req, res, "getblockheaderbyheight", "Unsuccessful")) + { + print_block_header(res.block_header); + } + + return true; +} + +bool t_rpc_command_executor::print_transaction(crypto::hash transaction_hash) { + cryptonote::COMMAND_RPC_GET_TRANSACTIONS::request req; + cryptonote::COMMAND_RPC_GET_TRANSACTIONS::response res; + + if (m_rpc_client.rpc_request(req, res, "/gettransactions", "Problem fetching transaction")) + { + if (1 == res.txs_as_hex.size()) + { + tools::success_msg_writer() << res.txs_as_hex.front(); + } + else + { + tools::fail_msg_writer() << "transaction wasn't found: <" << transaction_hash << '>' << std::endl; + } + } + + return true; +} + +bool t_rpc_command_executor::print_transaction_pool_long() { +/* + cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL::request req; + cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL::response res; + + if (m_rpc_client.rpc_request(req, res, "/get_transaction_pool", "Problem fetching transaction pool")) + { + if (res.transactions.empty()) + { + tools::msg_writer() << "Pool is empty" << std::endl; + } + for (auto & tx_info : res.transactions) + { + tools::msg_writer() << "id: " << tx_info.id_hash << std::endl + << "blob_size: " << tx_info.blob_size << std::endl + << "fee: " << tx_info.fee << std::endl + << "kept_by_block: " << tx_info.kept_by_block << std::endl + << "max_used_block_height: " << tx_info.max_used_block_height << std::endl + << "max_used_block_id: " << tx_info.max_used_block_id_hash << std::endl + << "last_failed_height: " << tx_info.last_failed_height << std::endl + << "last_failed_id: " << tx_info.last_failed_id_hash << std::endl; + } + } + +*/ + return true; +} + +bool t_rpc_command_executor::print_transaction_pool_short() { +/* + cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL::request req; + cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL::response res; + + if (m_rpc_client.rpc_request(req, res, "/get_transaction_pool", "Problem fetching transaction pool")) + { + for (auto & tx_info : res.transactions) + { + if (res.transactions.empty()) + { + tools::msg_writer() << "Pool is empty" << std::endl; + } + tools::msg_writer() << "id: " << tx_info.id_hash << std::endl + << tx_info.tx_json << std::endl + << "blob_size: " << tx_info.blob_size << std::endl + << "fee: " << tx_info.fee << std::endl + << "kept_by_block: " << tx_info.kept_by_block << std::endl + << "max_used_block_height: " << tx_info.max_used_block_height << std::endl + << "max_used_block_id: " << tx_info.max_used_block_id_hash << std::endl + << "last_failed_height: " << tx_info.last_failed_height << std::endl + << "last_failed_id: " << tx_info.last_failed_id_hash << std::endl; + } + } + +*/ + return true; +} + +// TODO: update this for testnet +bool t_rpc_command_executor::start_mining(cryptonote::account_public_address address, uint64_t num_threads) { + cryptonote::COMMAND_RPC_START_MINING::request req; + cryptonote::COMMAND_RPC_START_MINING::response res; + req.miner_address = cryptonote::get_account_address_as_str(false, address); + req.threads_count = num_threads; + + if (m_rpc_client.rpc_request(req, res, "/start_mining", "Mining did not start")) + { + tools::success_msg_writer() << "Mining started"; + } + return true; +} + +bool t_rpc_command_executor::stop_mining() { + cryptonote::COMMAND_RPC_STOP_MINING::request req; + cryptonote::COMMAND_RPC_STOP_MINING::response res; + + if (m_rpc_client.rpc_request(req, res, "/stop_mining", "Mining did not stop")) + { + tools::success_msg_writer() << "Mining stopped"; + } + return true; +} + +bool t_rpc_command_executor::stop_daemon() +{ +/* + cryptonote::COMMAND_RPC_STOP_DAEMON::request req; + cryptonote::COMMAND_RPC_STOP_DAEMON::response res; + +//# ifdef WIN32 +// // Stop via service API +// // TODO - this is only temporary! Get rid of hard-coded constants! +// bool ok = windows::stop_service("BitMonero Daemon"); +// ok = windows::uninstall_service("BitMonero Daemon"); +// //bool ok = windows::stop_service(SERVICE_NAME); +// //ok = windows::uninstall_service(SERVICE_NAME); +// if (ok) +// { +// return true; +// } +//# endif + + // Stop via RPC + if(m_rpc_client.rpc_request(req, res, "/stop_daemon", "Daemon did not stop")) + { + tools::success_msg_writer() << "Stop signal sent"; + } +*/ + + return true; +} + +bool t_rpc_command_executor::print_status() +{ + bool daemon_is_alive = m_rpc_client.check_connection(); + + if(daemon_is_alive) { + tools::success_msg_writer() << "bitmonerod is running"; + } + else { + tools::fail_msg_writer() << "bitmonerod is NOT running"; + } + + return true; +} + +bool t_rpc_command_executor::set_limit(int limit) +{ +/* + epee::net_utils::connection_basic::set_rate_down_limit( limit ); + epee::net_utils::connection_basic::set_rate_up_limit( limit ); + std::cout << "Set limit-down to " << limit/1024 << " kB/s" << std::endl; + std::cout << "Set limit-up to " << limit/1024 << " kB/s" << std::endl; +*/ + + return true; +} + +bool t_rpc_command_executor::set_limit_up(int limit) +{ +/* + epee::net_utils::connection_basic::set_rate_up_limit( limit ); + std::cout << "Set limit-up to " << limit/1024 << " kB/s" << std::endl; +*/ + + return true; +} + +bool t_rpc_command_executor::set_limit_down(int limit) +{ +/* + epee::net_utils::connection_basic::set_rate_down_limit( limit ); + std::cout << "Set limit-down to " << limit/1024 << " kB/s" << std::endl; +*/ + + return true; +} + +}// namespace daemonize diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h new file mode 100644 index 000000000..f1dc9cc76 --- /dev/null +++ b/src/daemon/rpc_command_executor.h @@ -0,0 +1,103 @@ +/** +@file +@details + +@image html images/other/runtime-commands.png + +*/ + +// Copyright (c) 2014, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#pragma once + +#include "common/rpc_client.h" +#include "misc_log_ex.h" +#include "cryptonote_core/cryptonote_core.h" +#include "cryptonote_protocol/cryptonote_protocol_handler.h" +#include "p2p/net_node.h" + +namespace daemonize { + +class t_rpc_command_executor final { +private: + tools::t_rpc_client m_rpc_client; +public: + t_rpc_command_executor( + uint32_t ip + , uint16_t port + ); + + bool print_peer_list(); + + bool save_blockchain(); + + bool show_hash_rate(); + + bool hide_hash_rate(); + + bool show_difficulty(); + + bool print_connections(); + + bool print_blockchain_info(uint64_t start_block_index, uint64_t end_block_index); + + bool set_log_level(int8_t level); + + bool print_height(); + + bool print_block_by_hash(crypto::hash block_hash); + + bool print_block_by_height(uint64_t height); + + bool print_transaction(crypto::hash transaction_hash); + + bool print_transaction_pool_long(); + + bool print_transaction_pool_short(); + + bool start_mining(cryptonote::account_public_address address, uint64_t num_threads); + + bool stop_mining(); + + bool stop_daemon(); + + bool print_status(); + + bool set_limit(int limit); + + bool set_limit_up(int limit); + + bool set_limit_down(int limit); + + +}; + +} // namespace daemonize diff --git a/src/daemonizer/CMakeLists.txt b/src/daemonizer/CMakeLists.txt new file mode 100644 index 000000000..d5830111c --- /dev/null +++ b/src/daemonizer/CMakeLists.txt @@ -0,0 +1,74 @@ +# Copyright (c) 2014-2015, The Monero Project +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are +# permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of +# conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list +# of conditions and the following disclaimer in the documentation and/or other +# materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be +# used to endorse or promote products derived from this software without specific +# prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +if(MSVC OR MINGW) + set(daemonizer_sources + windows_service.cpp + windows_daemonizer.inl + ) +else() + set(daemonizer_sources + posix_fork.cpp + posix_daemonizer.inl + ) +endif() + +set(daemonizer_headers +) + +if(MSVC OR MINGW) + set(daemonizer_private_headers + daemonizer.h + windows_service.h + windows_service_runner.h + ) +else() + set(daemonizer_private_headers + daemonizer.h + posix_fork.h + ) +endif() + +bitmonero_private_headers(daemonizer + ${daemonizer_private_headers}) +bitmonero_add_library(daemonizer + ${daemonizer_sources} + ${daemonizer_headers} + ${daemonizer_private_headers}) +target_link_libraries(daemonizer + LINK_PRIVATE + common + ${Boost_CHRONO_LIBRARY} + ${Boost_FILESYSTEM_LIBRARY} + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${Boost_REGEX_LIBRARY} + ${Boost_SYSTEM_LIBRARY} + ${Boost_THREAD_LIBRARY} + ${CMAKE_THREAD_LIBS_INIT} + ${UPNP_LIBRARIES} + ${EXTRA_LIBRARIES}) diff --git a/src/daemonizer/daemonizer.h b/src/daemonizer/daemonizer.h new file mode 100644 index 000000000..6097a58f6 --- /dev/null +++ b/src/daemonizer/daemonizer.h @@ -0,0 +1,38 @@ +#pragma once + +#include <boost/filesystem/path.hpp> +#include <boost/program_options/options_description.hpp> +#include <boost/program_options/variables_map.hpp> + +namespace daemonizer +{ + void init_options( + boost::program_options::options_description & hidden_options + , boost::program_options::options_description & normal_options + ); + + boost::filesystem::path get_default_data_dir(); + + boost::filesystem::path get_relative_path_base( + boost::program_options::variables_map const & vm + ); + + /** + * @arg create_before_detach - this indicates that the daemon should be + * created before the fork, giving it a chance to report initialization + * errors. At the time of this writing, this is not possible in the primary + * daemon (likely due to the size of the blockchain in memory). + */ + template <typename T_executor> + bool daemonize( + int argc, char const * argv[] + , T_executor && executor // universal ref + , boost::program_options::variables_map const & vm + ); +} + +#ifdef WIN32 +# include "daemonizer/windows_daemonizer.inl" +#else +# include "daemonizer/posix_daemonizer.inl" +#endif diff --git a/src/daemonizer/posix_daemonizer.inl b/src/daemonizer/posix_daemonizer.inl new file mode 100644 index 000000000..e06d43d61 --- /dev/null +++ b/src/daemonizer/posix_daemonizer.inl @@ -0,0 +1,60 @@ +#pragma once + +#include "common/scoped_message_writer.h" +#include "common/util.h" +#include "daemonizer/posix_fork.h" + +#include <boost/filesystem/operations.hpp> +#include <boost/filesystem/path.hpp> + +namespace daemonizer +{ + namespace + { + const command_line::arg_descriptor<bool> arg_detach = { + "detach" + , "Run as daemon" + }; + } + + inline void init_options( + boost::program_options::options_description & hidden_options + , boost::program_options::options_description & normal_options + ) + { + command_line::add_arg(normal_options, arg_detach); + } + + inline boost::filesystem::path get_default_data_dir() + { + return boost::filesystem::absolute(tools::get_default_data_dir()); + } + + inline boost::filesystem::path get_relative_path_base( + boost::program_options::variables_map const & vm + ) + { + return boost::filesystem::current_path(); + } + + template <typename T_executor> + inline bool daemonize( + int argc, char const * argv[] + , T_executor && executor // universal ref + , boost::program_options::variables_map const & vm + ) + { + if (command_line::has_arg(vm, arg_detach)) + { + auto daemon = executor.create_daemon(vm); + tools::success_msg_writer() << "Forking to background..."; + posix::fork(); + return daemon.run(); + } + else + { + //LOG_PRINT_L0(CRYPTONOTE_NAME << " v" << MONERO_VERSION_FULL); + return executor.run_interactive(vm); + } + } +} diff --git a/src/daemonizer/posix_fork.cpp b/src/daemonizer/posix_fork.cpp new file mode 100644 index 000000000..c068912ec --- /dev/null +++ b/src/daemonizer/posix_fork.cpp @@ -0,0 +1,108 @@ +// Copyright (c) 2003-2011 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include "daemonizer/posix_fork.h" +#include "misc_log_ex.h" + +#include <cstdlib> +#include <fcntl.h> +#include <unistd.h> +#include <stdexcept> +#include <string> +#include <sys/stat.h> + +namespace posix { + +namespace { + void quit(std::string const & message) + { + LOG_ERROR(message); + throw std::runtime_error(message); + } +} + +void fork() +{ + // Fork the process and have the parent exit. If the process was started + // from a shell, this returns control to the user. Forking a new process is + // also a prerequisite for the subsequent call to setsid(). + if (pid_t pid = ::fork()) + { + if (pid > 0) + { + // We're in the parent process and need to exit. + // + // When the exit() function is used, the program terminates without + // invoking local variables' destructors. Only global variables are + // destroyed. + exit(0); + } + else + { + quit("First fork failed"); + } + } + + // Make the process a new session leader. This detaches it from the + // terminal. + setsid(); + + // A process inherits its working directory from its parent. This could be + // on a mounted filesystem, which means that the running daemon would + // prevent this filesystem from being unmounted. Changing to the root + // directory avoids this problem. + if (chdir("/") < 0) + { + quit("Unable to change working directory to root"); + } + + // The file mode creation mask is also inherited from the parent process. + // We don't want to restrict the permissions on files created by the + // daemon, so the mask is cleared. + umask(0); + + // A second fork ensures the process cannot acquire a controlling terminal. + if (pid_t pid = ::fork()) + { + if (pid > 0) + { + exit(0); + } + else + { + quit("Second fork failed"); + } + } + + // Close the standard streams. This decouples the daemon from the terminal + // that started it. + close(0); + close(1); + close(2); + + // We don't want the daemon to have any standard input. + if (open("/dev/null", O_RDONLY) < 0) + { + quit("Unable to open /dev/null"); + } + + // Send standard output to a log file. + const char* output = "/tmp/bitmonero.daemon.stdout.stderr"; + const int flags = O_WRONLY | O_CREAT | O_APPEND; + const mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; + if (open(output, flags, mode) < 0) + { + quit("Unable to open output file: " + std::string(output)); + } + + // Also send standard error to the same log file. + if (dup(1) < 0) + { + quit("Unable to dup output descriptor"); + } +} + +} // namespace posix diff --git a/src/daemonizer/posix_fork.h b/src/daemonizer/posix_fork.h new file mode 100644 index 000000000..f099685e9 --- /dev/null +++ b/src/daemonizer/posix_fork.h @@ -0,0 +1,11 @@ +#pragma once + +#ifndef WIN32 + +namespace posix { + +void fork(); + +} + +#endif diff --git a/src/daemonizer/windows_daemonizer.inl b/src/daemonizer/windows_daemonizer.inl new file mode 100644 index 000000000..65c1e4195 --- /dev/null +++ b/src/daemonizer/windows_daemonizer.inl @@ -0,0 +1,156 @@ +#pragma once + +#include "common/util.h" +#include "daemonizer/windows_service.h" +#include "daemonizer/windows_service_runner.h" + +#include <shlobj.h> +#include <boost/filesystem/operations.hpp> +#include <boost/filesystem/path.hpp> + +namespace daemonizer +{ + namespace + { + const command_line::arg_descriptor<bool> arg_install_service = { + "install-service" + , "Install Windows service" + }; + const command_line::arg_descriptor<bool> arg_uninstall_service = { + "uninstall-service" + , "Uninstall Windows service" + }; + const command_line::arg_descriptor<bool> arg_start_service = { + "start-service" + , "Start Windows service" + }; + const command_line::arg_descriptor<bool> arg_stop_service = { + "stop-service" + , "Stop Windows service" + }; + const command_line::arg_descriptor<bool> arg_is_service = { + "run-as-service" + , "Hidden -- true if running as windows service" + }; + + std::string get_argument_string(int argc, char const * argv[]) + { + std::string result = ""; + for (int i = 1; i < argc; ++i) + { + result += " " + std::string{argv[i]}; + } + return result; + } + } + + inline void init_options( + boost::program_options::options_description & hidden_options + , boost::program_options::options_description & normal_options + ) + { + command_line::add_arg(normal_options, arg_install_service); + command_line::add_arg(normal_options, arg_uninstall_service); + command_line::add_arg(normal_options, arg_start_service); + command_line::add_arg(normal_options, arg_stop_service); + command_line::add_arg(hidden_options, arg_is_service); + } + + inline boost::filesystem::path get_default_data_dir() + { + bool admin; + if (!windows::check_admin(admin)) + { + admin = false; + } + if (admin) + { + return boost::filesystem::absolute( + tools::get_special_folder_path(CSIDL_COMMON_APPDATA, true) + "\\" + CRYPTONOTE_NAME + ); + } + else + { + return boost::filesystem::absolute( + tools::get_special_folder_path(CSIDL_APPDATA, true) + "\\" + CRYPTONOTE_NAME + ); + } + } + + inline boost::filesystem::path get_relative_path_base( + boost::program_options::variables_map const & vm + ) + { + if (command_line::arg_present(vm, arg_is_service)) + { + if (command_line::arg_present(vm, command_line::arg_data_dir)) + { + return command_line::get_arg(vm, command_line::arg_data_dir); + } + else + { + return tools::get_default_data_dir(); + } + } + else + { + return boost::filesystem::current_path(); + } + } + + template <typename T_executor> + inline bool daemonize( + int argc, char const * argv[] + , T_executor && executor // universal ref + , boost::program_options::variables_map const & vm + ) + { + std::string arguments = get_argument_string(argc, argv); + + if (command_line::arg_present(vm, arg_is_service)) + { + // TODO - Set the service status here for return codes + windows::t_service_runner<typename T_executor::t_daemon>::run( + executor.name() + , executor.create_daemon(vm) + ); + return true; + } + else if (command_line::arg_present(vm, arg_install_service)) + { + if (windows::ensure_admin(arguments)) + { + arguments += " --run-as-service"; + return windows::install_service(executor.name(), arguments); + } + } + else if (command_line::arg_present(vm, arg_uninstall_service)) + { + if (windows::ensure_admin(arguments)) + { + return windows::uninstall_service(executor.name()); + } + } + else if (command_line::arg_present(vm, arg_start_service)) + { + if (windows::ensure_admin(arguments)) + { + return windows::start_service(executor.name()); + } + } + else if (command_line::arg_present(vm, arg_stop_service)) + { + if (windows::ensure_admin(arguments)) + { + return windows::stop_service(executor.name()); + } + } + else // interactive + { + //LOG_PRINT_L0(CRYPTONOTE_NAME << " v" << MONERO_VERSION_FULL); + return executor.run_interactive(vm); + } + + return false; + } +} diff --git a/src/daemonizer/windows_service.cpp b/src/daemonizer/windows_service.cpp new file mode 100644 index 000000000..1b0ee2ef4 --- /dev/null +++ b/src/daemonizer/windows_service.cpp @@ -0,0 +1,341 @@ +#undef UNICODE +#undef _UNICODE + +#include "common/scoped_message_writer.h" +#include "daemonizer/windows_service.h" +#include "string_tools.h" +#include <chrono> +#include <iostream> +#include <utility> +#include <memory> +#include <shellapi.h> +#include <thread> +#include <windows.h> + +namespace windows { + +namespace { + typedef std::unique_ptr<std::remove_pointer<SC_HANDLE>::type, decltype(&::CloseServiceHandle)> service_handle; + + std::string get_last_error() + { + LPSTR p_error_text = nullptr; + + FormatMessage( + FORMAT_MESSAGE_FROM_SYSTEM + | FORMAT_MESSAGE_ALLOCATE_BUFFER + | FORMAT_MESSAGE_IGNORE_INSERTS + , nullptr + , GetLastError() + , MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT) + , reinterpret_cast<LPSTR>(&p_error_text) + , 0 + , nullptr + ); + + if (nullptr == p_error_text) + { + return ""; + } + else + { + return std::string{p_error_text}; + LocalFree(p_error_text); + } + } + + bool relaunch_as_admin( + std::string const & command + , std::string const & arguments + ) + { + SHELLEXECUTEINFO info{}; + info.cbSize = sizeof(info); + info.lpVerb = "runas"; + info.lpFile = command.c_str(); + info.lpParameters = arguments.c_str(); + info.hwnd = nullptr; + info.nShow = SW_SHOWNORMAL; + if (!ShellExecuteEx(&info)) + { + tools::fail_msg_writer() << "Admin relaunch failed: " << get_last_error(); + return false; + } + else + { + return true; + } + } + + // When we relaunch as admin, Windows opens a new window. This just pauses + // to allow the user to read any output. + void pause_to_display_admin_window_messages() + { + std::chrono::milliseconds how_long{1500}; + std::this_thread::sleep_for(how_long); + } +} + +bool check_admin(bool & result) +{ + BOOL is_admin = FALSE; + PSID p_administrators_group = nullptr; + + SID_IDENTIFIER_AUTHORITY nt_authority = SECURITY_NT_AUTHORITY; + + if (!AllocateAndInitializeSid( + &nt_authority + , 2 + , SECURITY_BUILTIN_DOMAIN_RID + , DOMAIN_ALIAS_RID_ADMINS + , 0, 0, 0, 0, 0, 0 + , &p_administrators_group + )) + { + tools::fail_msg_writer() << "Security Identifier creation failed: " << get_last_error(); + return false; + } + + if (!CheckTokenMembership( + nullptr + , p_administrators_group + , &is_admin + )) + { + tools::fail_msg_writer() << "Permissions check failed: " << get_last_error(); + return false; + } + + result = is_admin ? true : false; + + return true; +} + +bool ensure_admin( + std::string const & arguments + ) +{ + bool admin; + + if (!check_admin(admin)) + { + return false; + } + + if (admin) + { + return true; + } + else + { + std::string command = epee::string_tools::get_current_module_path(); + relaunch_as_admin(command, arguments); + return false; + } +} + +bool install_service( + std::string const & service_name + , std::string const & arguments + ) +{ + std::string command = epee::string_tools::get_current_module_path(); + std::string full_command = command + arguments; + + service_handle p_manager{ + OpenSCManager( + nullptr + , nullptr + , SC_MANAGER_CONNECT | SC_MANAGER_CREATE_SERVICE + ) + , &::CloseServiceHandle + }; + if (p_manager == nullptr) + { + tools::fail_msg_writer() << "Couldn't connect to service manager: " << get_last_error(); + return false; + } + + service_handle p_service{ + CreateService( + p_manager.get() + , service_name.c_str() + , service_name.c_str() + , 0 + //, GENERIC_EXECUTE | GENERIC_READ + , SERVICE_WIN32_OWN_PROCESS + , SERVICE_DEMAND_START + , SERVICE_ERROR_NORMAL + , full_command.c_str() + , nullptr + , nullptr + , "" + //, "NT AUTHORITY\\LocalService" + , nullptr // Implies LocalSystem account + , nullptr + ) + , &::CloseServiceHandle + }; + if (p_service == nullptr) + { + tools::fail_msg_writer() << "Couldn't create service: " << get_last_error(); + return false; + } + + tools::success_msg_writer() << "Service installed"; + + pause_to_display_admin_window_messages(); + + return true; +} + +bool start_service( + std::string const & service_name + ) +{ + tools::msg_writer() << "Starting service"; + + SERVICE_STATUS_PROCESS service_status = {}; + DWORD unused = 0; + + service_handle p_manager{ + OpenSCManager( + nullptr + , nullptr + , SC_MANAGER_CONNECT + ) + , &::CloseServiceHandle + }; + if (p_manager == nullptr) + { + tools::fail_msg_writer() << "Couldn't connect to service manager: " << get_last_error(); + return false; + } + + service_handle p_service{ + OpenService( + p_manager.get() + , service_name.c_str() + //, SERVICE_START | SERVICE_QUERY_STATUS + , SERVICE_START + ) + , &::CloseServiceHandle + }; + if (p_service == nullptr) + { + tools::fail_msg_writer() << "Couldn't find service: " << get_last_error(); + return false; + } + + if (!StartService( + p_service.get() + , 0 + , nullptr + )) + { + tools::fail_msg_writer() << "Service start request failed: " << get_last_error(); + return false; + } + + tools::success_msg_writer() << "Service started"; + + pause_to_display_admin_window_messages(); + + return true; +} + +bool stop_service( + std::string const & service_name + ) +{ + tools::msg_writer() << "Stopping service"; + + service_handle p_manager{ + OpenSCManager( + nullptr + , nullptr + , SC_MANAGER_CONNECT + ) + , &::CloseServiceHandle + }; + if (p_manager == nullptr) + { + tools::fail_msg_writer() << "Couldn't connect to service manager: " << get_last_error(); + return false; + } + + service_handle p_service{ + OpenService( + p_manager.get() + , service_name.c_str() + , SERVICE_STOP | SERVICE_QUERY_STATUS + ) + , &::CloseServiceHandle + }; + if (p_service == nullptr) + { + tools::fail_msg_writer() << "Couldn't find service: " << get_last_error(); + return false; + } + + SERVICE_STATUS status = {}; + if (!ControlService(p_service.get(), SERVICE_CONTROL_STOP, &status)) + { + tools::fail_msg_writer() << "Couldn't request service stop: " << get_last_error(); + return false; + } + + tools::success_msg_writer() << "Service stopped"; + + pause_to_display_admin_window_messages(); + + return true; +} + +bool uninstall_service( + std::string const & service_name + ) +{ + service_handle p_manager{ + OpenSCManager( + nullptr + , nullptr + , SC_MANAGER_CONNECT + ) + , &::CloseServiceHandle + }; + if (p_manager == nullptr) + { + tools::fail_msg_writer() << "Couldn't connect to service manager: " << get_last_error(); + return false; + } + + service_handle p_service{ + OpenService( + p_manager.get() + , service_name.c_str() + , SERVICE_QUERY_STATUS | DELETE + ) + , &::CloseServiceHandle + }; + if (p_service == nullptr) + { + tools::fail_msg_writer() << "Couldn't find service: " << get_last_error(); + return false; + } + + SERVICE_STATUS status = {}; + if (!DeleteService(p_service.get())) + { + tools::fail_msg_writer() << "Couldn't uninstall service: " << get_last_error(); + return false; + } + + tools::success_msg_writer() << "Service uninstalled"; + + pause_to_display_admin_window_messages(); + + return true; +} + +} // namespace windows diff --git a/src/daemonizer/windows_service.h b/src/daemonizer/windows_service.h new file mode 100644 index 000000000..11f5fdcdc --- /dev/null +++ b/src/daemonizer/windows_service.h @@ -0,0 +1,36 @@ +#pragma once + +#ifdef WIN32 + +#undef UNICODE +#undef _UNICODE + +#include <string> +#include <windows.h> + +namespace windows +{ + bool check_admin(bool & result); + + bool ensure_admin( + std::string const & arguments + ); + + bool install_service( + std::string const & service_name + , std::string const & arguments + ); + + bool uninstall_service( + std::string const & service_name + ); + + bool start_service( + std::string const & service_name + ); + + bool stop_service( + std::string const & service_name + ); +} +#endif diff --git a/src/daemonizer/windows_service_runner.h b/src/daemonizer/windows_service_runner.h new file mode 100644 index 000000000..79b10b136 --- /dev/null +++ b/src/daemonizer/windows_service_runner.h @@ -0,0 +1,157 @@ +#pragma once + +#ifdef WIN32 + +#undef UNICODE +#undef _UNICODE + +#include "daemonizer/windows_service.h" +#include <memory> +#include <string> +#include <vector> +#include <windows.h> + +namespace windows { + namespace + { + std::vector<char> vecstring(std::string const & str) + { + std::vector<char> result{str.begin(), str.end()}; + result.push_back('\0'); + return result; + } + } + + template <typename T_handler> + class t_service_runner final + { + private: + SERVICE_STATUS_HANDLE m_status_handle{nullptr}; + SERVICE_STATUS m_status{}; + std::mutex m_lock{}; + std::string m_name; + T_handler m_handler; + + static std::unique_ptr<t_service_runner<T_handler>> sp_instance; + public: + t_service_runner( + std::string name + , T_handler handler + ) + : m_name{std::move(name)} + , m_handler{std::move(handler)} + { + m_status.dwServiceType = SERVICE_WIN32; + m_status.dwCurrentState = SERVICE_STOPPED; + m_status.dwControlsAccepted = 0; + m_status.dwWin32ExitCode = NO_ERROR; + m_status.dwServiceSpecificExitCode = NO_ERROR; + m_status.dwCheckPoint = 0; + m_status.dwWaitHint = 0; + } + + t_service_runner & operator=(t_service_runner && other) + { + if (this != &other) + { + m_status_handle = std::move(other.m_status_handle); + m_status = std::move(other.m_status); + m_name = std::move(other.m_name); + m_handler = std::move(other.m_handler); + } + return *this; + } + + static void run( + std::string name + , T_handler handler + ) + { + sp_instance.reset(new t_service_runner<T_handler>{ + std::move(name) + , std::move(handler) + }); + + sp_instance->run_(); + } + + private: + void run_() + { + SERVICE_TABLE_ENTRY table[] = + { + { vecstring(m_name).data(), &service_main } + , { 0, 0 } + }; + + StartServiceCtrlDispatcher(table); + } + + void report_status(DWORD status) + { + m_status.dwCurrentState = status; + if (status == SERVICE_RUNNING) + { + m_status.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN; + } + else if(status == SERVICE_STOP_PENDING) + { + m_status.dwControlsAccepted = 0; + } + SetServiceStatus(m_status_handle, &m_status); + } + + static void WINAPI service_main(DWORD argc, LPSTR * argv) + { + sp_instance->service_main_(argc, argv); + } + + void service_main_(DWORD argc, LPSTR * argv) + { + m_status_handle = RegisterServiceCtrlHandler(m_name.c_str(), &on_state_change_request); + if (m_status_handle == nullptr) return; + + report_status(SERVICE_START_PENDING); + + report_status(SERVICE_RUNNING); + + m_handler.run(); + + on_state_change_request_(SERVICE_CONTROL_STOP); + + // Ensure that the service is uninstalled + uninstall_service(m_name); + } + + static void WINAPI on_state_change_request(DWORD control_code) + { + sp_instance->on_state_change_request_(control_code); + } + + void on_state_change_request_(DWORD control_code) + { + switch (control_code) + { + case SERVICE_CONTROL_INTERROGATE: + break; + case SERVICE_CONTROL_SHUTDOWN: + case SERVICE_CONTROL_STOP: + report_status(SERVICE_STOP_PENDING); + m_handler.stop(); + report_status(SERVICE_STOPPED); + break; + case SERVICE_CONTROL_PAUSE: + break; + case SERVICE_CONTROL_CONTINUE: + break; + default: + break; + } + } + }; + + template <typename T_handler> + std::unique_ptr<t_service_runner<T_handler>> t_service_runner<T_handler>::sp_instance; +} + +#endif |