oxen-core/src/daemon/main.cpp
Jason Rhinelander f287d3d614 Remove STACK_TRACE option
This option is incredibly misguided: exceptions are a normal part of C++
error handling that are used *as intended* in lots of places in the
code.  Spewing massive amounts of output every time any exception is
thrown anywhere (even when caught!) is terrible.

More than that, we don't ever build with it enabled (for the above
reasons) so this is all just unused code.
2021-05-12 11:59:22 -03:00

373 lines
16 KiB
C++

// Copyright (c) 2014-2020, The Monero Project
// Copyright (c) 2018, The Loki 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 <cstdlib>
#include "common/command_line.h"
#include "common/scoped_message_writer.h"
#include "common/password.h"
#include "common/util.h"
#include "common/fs.h"
#include "cryptonote_core/cryptonote_core.h"
#include "daemonizer/daemonizer.h"
#include "epee/misc_log_ex.h"
#include "p2p/net_node.h"
#include "rpc/rpc_args.h"
#include "rpc/core_rpc_server.h"
#include "daemon/command_line_args.h"
#include "version.h"
#include "command_server.h"
#include "daemon.h"
#undef OXEN_DEFAULT_LOG_CATEGORY
#define OXEN_DEFAULT_LOG_CATEGORY "daemon"
namespace po = boost::program_options;
using namespace std::literals;
namespace {
// Some ANSI color sequences that we use here (before the log system is initialized):
constexpr auto RESET = "\033[0m";
constexpr auto RED = "\033[31;1m";
constexpr auto YELLOW = "\033[33;1m";
constexpr auto CYAN = "\033[36;1m";
}
int main(int argc, char const * argv[])
{
bool logs_initialized = false;
try {
// TODO parse the debug options like set log level right here at start
tools::on_startup();
epee::string_tools::set_module_name_and_folder(argv[0]);
auto opt_size = command_line::boost_option_sizes();
// Build argument description
po::options_description all_options("All", opt_size.first, opt_size.second);
po::options_description hidden_options("Hidden");
po::options_description visible_options("Options", opt_size.first, opt_size.second);
po::options_description core_settings("Settings", opt_size.first, opt_size.second);
po::positional_options_description positional_options;
{
// 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_config_file);
// Settings
command_line::add_arg(core_settings, daemon_args::arg_log_file);
command_line::add_arg(core_settings, daemon_args::arg_log_level);
command_line::add_arg(core_settings, daemon_args::arg_max_log_file_size);
command_line::add_arg(core_settings, daemon_args::arg_max_log_files);
command_line::add_arg(core_settings, daemon_args::arg_max_concurrency);
daemonizer::init_options(hidden_options, visible_options);
daemonize::daemon::init_options(core_settings, hidden_options);
// 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 << CYAN << "Oxen '" << OXEN_RELEASE_NAME << "' (v" << OXEN_VERSION_FULL << ")" << RESET << "\n\n";
std::cout << "Usage: " + std::string{argv[0]} + " [options|settings] [daemon_command...]" << std::endl << std::endl;
std::cout << visible_options << std::endl;
return 0;
}
// Oxen Version
if (command_line::get_arg(vm, command_line::arg_version))
{
std::cout << CYAN << "Oxen '" << OXEN_RELEASE_NAME << "' (v" << OXEN_VERSION_FULL << ")" << RESET << "\n\n";
return 0;
}
std::optional<fs::path> load_config;
if (command_line::is_arg_defaulted(vm, daemon_args::arg_config_file)) {
// We are using the default config file, which will be in the data directory, as determined
// *only* by the command-line arguments but *not* config file arguments, unlike pretty much
// all other command line options (where we load from both, with cli options taking
// precendence). Thus it's possible that the data-dir isn't specified on the command-line
// which means *for the purpose of loading the config file* that we use `~/.oxen`, but that
// after we load the config file it could be something else. (In such an edge case, we simply
// ignore a <final-data-dir>/oxen.conf).
auto data_dir = fs::absolute(fs::u8path(command_line::get_arg(vm, cryptonote::arg_data_dir)));
// --regtest should append a /regtest to the data-dir, but this is done here rather than in the
// defaults because this is a dev-only option that we don't really want the user to need to
// worry about.
if (command_line::get_arg(vm, cryptonote::arg_regtest_on))
data_dir /= "regtest";
// We also have to worry about migrating loki.conf -> oxen.conf *and* about a potential
// ~/.loki -> ~/.oxen migration, so build a list of possible options along with whether we
// want to rename if we find one (the data-dir migration happens later):
std::list<std::pair<fs::path, bool>> potential;
if (std::error_code ec; fs::exists(data_dir, ec)) {
potential.emplace_back(data_dir / CRYPTONOTE_NAME ".conf", false);
potential.emplace_back(data_dir / "loki.conf", true);
} else if (command_line::is_arg_defaulted(vm, cryptonote::arg_data_dir)) {
// If we weren't given an explict command-line data-dir then we also need to check the
// legacy data directory. (We will rename it, later, but we have to check it *first*
// because it might have a data-dir inside it that affects the data dir rename logic).
auto old_data_dir = tools::get_depreciated_default_data_dir();
// If we *have* a --testnet or --devnet arg then we can use it, but it's possible that the
// config file itself will set and change that, which is why we retrieve those arguments
// again later, after parsing the config.
if (command_line::get_arg(vm, cryptonote::arg_testnet_on)) old_data_dir /= "testnet";
else if (command_line::get_arg(vm, cryptonote::arg_devnet_on)) old_data_dir /= "devnet";
else if (command_line::get_arg(vm, cryptonote::arg_regtest_on)) old_data_dir /= "regtest";
potential.emplace_back(old_data_dir / CRYPTONOTE_NAME ".conf", false);
potential.emplace_back(old_data_dir / "loki.conf", true);
}
for (auto& [conf, rename] : potential) {
if (std::error_code ec; fs::exists(conf, ec)) {
if (rename) {
fs::path renamed = conf;
renamed.replace_filename(CRYPTONOTE_NAME ".conf");
assert(renamed != conf);
if (fs::rename(conf, renamed, ec); ec) {
std::cerr << RED << "Failed to migrate " << conf << " -> " << renamed <<
": " << ec.message() << RESET << "\n";
return 1;
}
if (fs::create_symlink(renamed.filename(), conf, ec); ec) {
std::cerr << YELLOW << "Failed to create post-migration " << conf << " -> " << renamed <<
" symlink: " << ec.message() << RESET << "\n";
// Continue anyway as this isn't fatal
}
std::cerr << CYAN << "Renamed " << conf << " -> " << renamed << RESET << "\n";
load_config = std::move(renamed);
} else {
load_config = std::move(conf);
}
break;
}
}
} else {
// config file explicitly given, no migration
load_config = fs::u8path(command_line::get_arg(vm, daemon_args::arg_config_file));
if (std::error_code ec; !fs::exists(*load_config, ec)) {
std::cerr << RED << "Can't find config file " << *load_config << RESET << "\n";
return 1;
}
}
if (load_config)
{
try
{
fs::ifstream cfg{*load_config};
if (!cfg.is_open())
throw std::runtime_error{"Unable to open file"};
po::store(po::parse_config_file<char>(
cfg,
po::options_description{}.add(core_settings).add(hidden_options)),
vm);
}
catch (const std::exception &e)
{
std::cerr << RED << "Error parsing config file: " << e.what() << RESET << "\n";
return 1;
}
}
const bool testnet = command_line::get_arg(vm, cryptonote::arg_testnet_on);
const bool devnet = command_line::get_arg(vm, cryptonote::arg_devnet_on);
const bool regtest = command_line::get_arg(vm, cryptonote::arg_regtest_on);
if (testnet + devnet + regtest > 1)
{
std::cerr << RED << "Can't specify more than one of --testnet and --devnet and --regtest" << RESET << "\n";
return 1;
}
// data_dir
// default: e.g. ~/.oxen/ or ~/.oxen/testnet
// if data-dir argument given:
// absolute path
// relative path: relative to cwd
// Create data dir if it doesn't exist
auto data_dir = fs::absolute(fs::u8path(command_line::get_arg(vm, cryptonote::arg_data_dir)));
// --regtest should append a /regtest to the data-dir, but this is done here rather than in the
// defaults because this is a dev-only option that we don't really want the user to need to
// worry about.
if (command_line::get_arg(vm, cryptonote::arg_regtest_on))
data_dir /= "regtest";
// Will check if the default data directory is used and if it exists.
// Then will ensure that migration from the old data directory (.loki) has occurred if it exists.
if (command_line::is_arg_defaulted(vm, cryptonote::arg_data_dir) && !fs::exists(data_dir)) {
auto old_data_dir = tools::get_depreciated_default_data_dir();
if (testnet) old_data_dir /= "testnet";
else if (devnet) old_data_dir /= "devnet";
else if (regtest) old_data_dir /= "regtest";
if (fs::is_directory(old_data_dir))
{
std::error_code ec;
if (fs::create_directories(data_dir.parent_path(), ec); ec) {
std::cerr << RED << "Data directory migration failed: cannot create " << data_dir.parent_path()
<< ": " << ec.message() << RESET << "\n";
return 1;
}
if (fs::rename(old_data_dir, data_dir, ec); ec) {
std::cerr << RED << "Data directory migrate failed: could not rename " << old_data_dir << " to " << data_dir
<< ": " << ec.message() << RESET << "\n";
return 1;
}
if (fs::create_directory_symlink(data_dir, old_data_dir, ec); ec)
std::cerr << YELLOW << "Failed to create " << old_data_dir << " -> " << data_dir << " symlink" << RESET << "\n";
std::cerr << CYAN << "Migrated data directory from " << old_data_dir << " to " << data_dir << RESET << "\n";
}
}
// Create the data directory; we have to do this before initializing the logs because the log
// likely goes inside the data dir.
if (std::error_code ec; !fs::create_directories(data_dir, ec) && ec)
std::cerr << YELLOW << "Failed to create data directory " << data_dir << ": " << ec.message() << RESET << "\n";
po::notify(vm);
// log_file_path
// default: <data_dir>/<CRYPTONOTE_NAME>.log
// if log-file argument given:
// absolute path
// relative path: relative to data_dir
auto log_file_path = data_dir / CRYPTONOTE_NAME ".log";
if (!command_line::is_arg_defaulted(vm, daemon_args::arg_log_file))
log_file_path = command_line::get_arg(vm, daemon_args::arg_log_file);
if (log_file_path.is_relative())
log_file_path = fs::absolute(data_dir / log_file_path);
mlog_configure(log_file_path.string(), true, command_line::get_arg(vm, daemon_args::arg_max_log_file_size), command_line::get_arg(vm, daemon_args::arg_max_log_files));
// Set log level
if (!command_line::is_arg_defaulted(vm, daemon_args::arg_log_level))
{
mlog_set_log(command_line::get_arg(vm, daemon_args::arg_log_level).c_str());
}
logs_initialized = true;
if (!command_line::is_arg_defaulted(vm, daemon_args::arg_max_concurrency))
tools::set_max_concurrency(command_line::get_arg(vm, daemon_args::arg_max_concurrency));
// logging is now set up
// FIXME: only print this when starting up as a daemon but not when running rpc commands
MGINFO_CYAN("Oxen '" << OXEN_RELEASE_NAME << "' (v" << OXEN_VERSION_FULL << ")");
// If there are positional options, we're running a daemon command
{
auto command = command_line::get_arg(vm, daemon_args::arg_command);
if (command.size())
{
auto rpc_config = cryptonote::rpc_args::process(vm);
std::string rpc_addr;
// TODO: remove this in oxen 9.x and only use rpc-admin
if (!is_arg_defaulted(vm, cryptonote::rpc::http_server::arg_rpc_bind_port) ||
rpc_config.bind_ip.has_value()) {
auto rpc_port = command_line::get_arg(vm, cryptonote::rpc::http_server::arg_rpc_bind_port);
if (rpc_port == 0)
rpc_port =
command_line::get_arg(vm, cryptonote::arg_testnet_on) ? config::testnet::RPC_DEFAULT_PORT :
command_line::get_arg(vm, cryptonote::arg_devnet_on) ? config::devnet::RPC_DEFAULT_PORT :
config::RPC_DEFAULT_PORT;
rpc_addr = rpc_config.bind_ip.value_or("127.0.0.1") + ":" + std::to_string(rpc_port);
} else {
rpc_addr = command_line::get_arg(vm, cryptonote::rpc::http_server::arg_rpc_admin)[0];
if (rpc_addr == "none")
throw std::runtime_error{"Cannot invoke oxend command: --rpc-admin is disabled"};
}
{
// Throws if invalid:
auto [ip, port] = daemonize::parse_ip_port(rpc_addr, "--rpc-admin");
rpc_addr = "http://"s + (ip.find(':') != std::string::npos ? "[" + ip + "]" : ip) + ":" + std::to_string(port);
}
daemonize::command_server rpc_commands{rpc_addr, rpc_config.login};
return rpc_commands.process_command_and_log(command) ? 0 : 1;
}
}
MINFO("Moving from main() into the daemonize now.");
return daemonizer::daemonize<daemonize::daemon>("Oxen Daemon", argc, argv, std::move(vm))
? 0 : 1;
}
catch (std::exception const & ex)
{
if (logs_initialized)
LOG_ERROR("Exception in main! " << ex.what());
else
std::cerr << RED << "Exception in main! " << ex.what() << RESET << "\n";
}
catch (...)
{
if (logs_initialized)
LOG_ERROR("Exception in main! (unknown exception type)");
else
std::cerr << RED << "Exception in main! (unknown exception type)" << RESET << "\n";
}
return 1;
}