diff --git a/src/Makefile.am b/src/Makefile.am index 8b1d103d..af4b0d1d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -197,6 +197,8 @@ libsub_root_a_SOURCES = \ globals.h \ option_parser.cc \ option_parser.h \ + setup.cc \ + setup.h \ signal_handler.cc \ signal_handler.h diff --git a/src/command_logging.cc b/src/command_logging.cc index 7178c862..15500382 100644 --- a/src/command_logging.cc +++ b/src/command_logging.cc @@ -8,53 +8,23 @@ #include #include -#include "globals.h" #include "control.h" #include "command_helpers.h" +#include "globals.h" +#include "setup.h" #include "core/download.h" #include "core/download_list.h" #include "core/manager.h" #include "rpc/parse_commands.h" -static const int log_flag_use_gz = 0x1; -static const int log_flag_append_pid = 0x2; -static const int log_flag_append_file = 0x4; -static const int log_flag_flush = 0x8; - -void -log_add_group_output_str(const char* group_name, const char* output_id) { - int log_group = torrent::option_find_string(torrent::OPTION_LOG_GROUP, group_name); - torrent::log_add_group_output(log_group, output_id); -} - torrent::Object -apply_log_open(int output_flags, const torrent::Object::list_type& args) { - if (args.size() < 2) - throw torrent::input_error("Invalid number of arguments."); +apply_log_open(int output_flags, const torrent::Object::list_type& raw_args) { + std::vector args; - torrent::Object::list_const_iterator itr = args.begin(); - - auto output_id = (itr++)->as_string(); - auto file_name = expand_path((itr++)->as_string()); - - if ((output_flags & log_flag_append_pid)) { - char buffer[32]; - snprintf(buffer, 32, ".%li", (long)getpid()); - - file_name += buffer; - } - - bool append = (output_flags & log_flag_append_file); - bool flush = (output_flags & log_flag_flush); - - if ((output_flags & log_flag_use_gz)) - torrent::log_open_gz_file_output(output_id.c_str(), file_name.c_str(), append); - else - torrent::log_open_file_output(output_id.c_str(), file_name.c_str(), append, flush); - - while (itr != args.end()) - log_add_group_output_str((itr++)->as_string().c_str(), output_id.c_str()); + for (const auto& arg : raw_args) + args.push_back(arg.as_string()); + apply_log_open_str(output_flags, args); return torrent::Object(); } @@ -63,8 +33,7 @@ apply_log_add_output(const torrent::Object::list_type& args) { if (args.size() != 2) throw torrent::input_error("Invalid number of arguments."); - log_add_group_output_str(args.front().as_string().c_str(), - args.back().as_string().c_str()); + log_add_group_output_str(args.front().as_string(), args.back().as_string()); return torrent::Object(); } diff --git a/src/globals.cc b/src/globals.cc index 505f8f8f..5cb62635 100644 --- a/src/globals.cc +++ b/src/globals.cc @@ -16,12 +16,12 @@ expand_path(const std::string& path) { if (path[0] == '~') { if (path.size() >= 2 && path[1] != '/') - throw torrent::input_error("Could not expand ~ in session path, only '~' or '~/...' is supported."); + throw torrent::input_error("Could not expand ~ in path, only '~' or '~/...' is supported."); const char* home = std::getenv("HOME"); if (home == nullptr || *home == '\0') - throw torrent::input_error("Could not expand ~ in session path, HOME environment variable not set."); + throw torrent::input_error("Could not expand ~ in path, HOME environment variable not set."); return home + path.substr(1); } diff --git a/src/main.cc b/src/main.cc index 737d94d2..c770936a 100644 --- a/src/main.cc +++ b/src/main.cc @@ -20,7 +20,6 @@ #include "core/dht_manager.h" #include "core/download.h" -#include "core/download_factory.h" #include "core/manager.h" #include "display/canvas.h" #include "display/window.h" @@ -30,15 +29,15 @@ #include "rpc/command_scheduler_item.h" #include "rpc/parse_commands.h" #include "scgi/thread_scgi.h" -#include "session/download_storer.h" -#include "session/thread_session.h" #include "session/session_manager.h" +#include "session/thread_session.h" #include "ui/root.h" #include "utils/directory.h" #include "control.h" #include "command_helpers.h" #include "globals.h" +#include "setup.h" #include "signal_handler.h" #include "option_parser.h" @@ -50,37 +49,6 @@ void do_panic(int signum); void print_help(); void initialize_commands(); -void do_nothing() {} -void do_nothing_str(const std::string&) {} - -int -parse_options(int argc, char** argv) { - try { - OptionParser optionParser; - - // Converted. - optionParser.insert_flag('h', std::bind(&print_help)); - optionParser.insert_flag('n', std::bind(&do_nothing_str, std::placeholders::_1)); - optionParser.insert_flag('D', std::bind(&do_nothing_str, std::placeholders::_1)); - optionParser.insert_flag('I', std::bind(&do_nothing_str, std::placeholders::_1)); - optionParser.insert_flag('K', std::bind(&do_nothing_str, std::placeholders::_1)); - - optionParser.insert_option('b', std::bind(&rpc::call_command_set_string, "network.bind_address.set", std::placeholders::_1)); - optionParser.insert_option('d', std::bind(&rpc::call_command_set_string, "directory.default.set", std::placeholders::_1)); - optionParser.insert_option('i', std::bind(&rpc::call_command_set_string, "ip", std::placeholders::_1)); - optionParser.insert_option('p', std::bind(&rpc::call_command_set_string, "network.port_range.set", std::placeholders::_1)); - optionParser.insert_option('s', std::bind(&rpc::call_command_set_string, "session", std::placeholders::_1)); - - optionParser.insert_option('O', std::bind(&rpc::parse_command_single_std, std::placeholders::_1)); - optionParser.insert_option_list('o', std::bind(&rpc::call_command_set_std_string, std::placeholders::_1, std::placeholders::_2)); - - return optionParser.process(argc, argv); - - } catch (torrent::input_error& e) { - throw torrent::input_error("Failed to parse command line option: " + std::string(e.what())); - } -} - void initialize_rpc_slots() { rpc::rpc.slot_find_download() = [](const char* hash) { @@ -110,43 +78,6 @@ initialize_rpc_slots() { }; } -void -load_session_torrents() { - utils::Directory entries = session::DownloadStorer::get_formated_entries(session_thread::manager()->path()); - - for (const auto& entry : entries) { - // We don't really support session torrents that are links. These - // would be overwritten anyway on exit, and thus not really be - // useful. - if (!entry.is_file()) - continue; - - core::DownloadFactory* f = new core::DownloadFactory(control->core()); - - // Replace with session torrent flag. - f->set_session(true); - f->set_init_load(true); - f->slot_finished([f](){ delete f; }); - f->load(entries.path() + entry.s_name); - f->commit(); - } -} - -void -load_arg_torrents(char** first, char** last) { - //std::for_each(begin, end, std::bind1st(std::mem_fun(&core::Manager::insert), &control->get_core())); - for (; first != last; ++first) { - core::DownloadFactory* f = new core::DownloadFactory(control->core()); - - // Replace with session torrent flag. - f->set_start(true); - f->set_init_load(true); - f->slot_finished([f](){ delete f; }); - f->load(*first); - f->commit(); - } -} - static void client_perform() { // Use throw exclusively. @@ -176,19 +107,36 @@ main(int argc, char** argv) { srandom(random_seed); srand48(random_seed); - torrent::initialize_main_thread(); torrent::log_initialize(); + // TODO: Create a fake thread object for initializing other processes and enabling logging. + torrent::initialize_main_thread(); + + SignalHandler::set_ignore(SIGPIPE); + SignalHandler::set_handler(SIGSEGV, std::bind(&do_panic, SIGSEGV)); + SignalHandler::set_handler(SIGILL, std::bind(&do_panic, SIGILL)); + SignalHandler::set_handler(SIGFPE, std::bind(&do_panic, SIGFPE)); + + // Limited list of commands with the following format: + // + // cat ~/.rtorrent.rc: + // + // # do:log.open_file=system,/usr/rakshasa/system.log + // # do:log.add_output=system,system + // + parse_config_file(argc, argv, [](auto& path) { + if (path.empty()) + return; + + parse_config_file_comments(path); + }); + control = new Control; - SignalHandler::set_ignore(SIGPIPE); SignalHandler::set_handler(SIGINT, std::bind(&Control::receive_normal_shutdown, control)); SignalHandler::set_handler(SIGHUP, std::bind(&Control::receive_normal_shutdown, control)); SignalHandler::set_handler(SIGTERM, std::bind(&Control::receive_quick_shutdown, control)); SignalHandler::set_handler(SIGWINCH, std::bind(&display::Manager::force_redraw, control->display())); - SignalHandler::set_handler(SIGSEGV, std::bind(&do_panic, SIGSEGV)); - SignalHandler::set_handler(SIGILL, std::bind(&do_panic, SIGILL)); - SignalHandler::set_handler(SIGFPE, std::bind(&do_panic, SIGFPE)); SignalHandler::set_sigaction_handler(SIGBUS, &handle_sigbus); @@ -200,7 +148,7 @@ main(int argc, char** argv) { // threads. Use '--enable-interrupt-socket' when configuring // LibTorrent to enable this workaround. if (torrent::utils::Thread::should_handle_sigusr1()) - SignalHandler::set_handler(SIGUSR1, std::bind(&do_nothing)); + SignalHandler::set_handler(SIGUSR1, []() {}); torrent::log_add_group_output(torrent::LOG_NOTICE, "important"); torrent::log_add_group_output(torrent::LOG_DHT_ERROR, "important"); @@ -459,23 +407,16 @@ main(int argc, char** argv) { } } - int firstArg = parse_options(argc, argv); + int firstArg = parse_main_options(argc, argv); - if (OptionParser::has_flag('n', argc, argv)) { - lt_log_print(torrent::LOG_WARN, "Ignoring rtorrent.rc."); - } else { - char* config_dir = std::getenv("XDG_CONFIG_HOME"); - char* home_dir = std::getenv("HOME"); + parse_config_file(argc, argv, [](auto& path) { + if (path.empty()) { + lt_log_print(torrent::LOG_WARN, "Ignoring rtorrent.rc."); + return; + } - if (config_dir != NULL && config_dir[0] == '/' && - access((std::string(config_dir) + "/rtorrent/rtorrent.rc").c_str(), F_OK) != -1) { - rpc::parse_command_single(rpc::make_target(), "try_import = " + std::string(config_dir) + "/rtorrent/rtorrent.rc"); - } else if (home_dir != NULL && access((std::string(home_dir) + "/.config/rtorrent/rtorrent.rc").c_str(), F_OK) != -1) { - rpc::parse_command_single(rpc::make_target(), "try_import = ~/.config/rtorrent/rtorrent.rc"); - } else { - rpc::parse_command_single(rpc::make_target(), "try_import = ~/.rtorrent.rc"); - } - } + rpc::parse_command_single(rpc::make_target(), "try_import=" + path); + }); LT_LOG("seeded srandom and srand48 (seed:%u)", random_seed); @@ -485,11 +426,8 @@ main(int argc, char** argv) { // Load session torrents and perform scheduled tasks to ensure // session torrents are loaded before arg torrents. control->dht_manager()->load_dht_cache(); - load_session_torrents(); - - // TODO: Check if this is required. - // rak::priority_queue_perform(&taskScheduler, cachedTime); + load_session_torrents(session_thread::manager()->path()); load_arg_torrents(argv + firstArg, argv + argc); // Make sure we update the display before any scheduled tasks can @@ -616,7 +554,9 @@ do_panic(int signum) { // Use the default signal handler in the future to avoid infinit // loops. SignalHandler::set_default(signum); - display::Canvas::cleanup(); + + if (control != nullptr) + display::Canvas::cleanup(); std::stringstream output; diff --git a/src/option_parser.cc b/src/option_parser.cc index 630627d9..d8bc1c59 100644 --- a/src/option_parser.cc +++ b/src/option_parser.cc @@ -1,5 +1,7 @@ #include "config.h" +#include "option_parser.h" + #include #include #include @@ -8,8 +10,6 @@ #include #include -#include "option_parser.h" - void OptionParser::insert_flag(char c, slot_string s) { m_container[c].m_slot = s; diff --git a/src/option_parser.h b/src/option_parser.h index 65e615ff..43946787 100644 --- a/src/option_parser.h +++ b/src/option_parser.h @@ -1,37 +1,3 @@ -// rTorrent - BitTorrent client -// Copyright (C) 2005-2011, Jari Sundell -// -// This program is free software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation; either version 2 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// In addition, as a special exception, the copyright holders give -// permission to link the code of portions of this program with the -// OpenSSL library under certain conditions as described in each -// individual source file, and distribute linked combinations -// including the two. -// -// You must obey the GNU General Public License in all respects for -// all of the code used other than OpenSSL. If you modify file(s) -// with this exception, you may extend this exception to your version -// of the file(s), but you are not obligated to do so. If you do not -// wish to do so, delete this exception statement from your version. -// If you delete this exception statement from all source files in the -// program, then also delete it here. -// -// Contact: Jari Sundell - - #ifndef RTORRENT_OPTION_PARSER_H #define RTORRENT_OPTION_PARSER_H diff --git a/src/rpc/parse_commands.cc b/src/rpc/parse_commands.cc index a39303f8..34cb4c9c 100644 --- a/src/rpc/parse_commands.cc +++ b/src/rpc/parse_commands.cc @@ -151,23 +151,21 @@ parse_command_file(const std::string& path) { try { unsigned int getCount = 0; - while (file.good() - && !file.getline(buffer + getCount, 4096 - getCount).fail()) { - + while (file.good() && !file.getline(buffer + getCount, 4096 - getCount).fail()) { if (file.gcount() == 0) throw torrent::internal_error("parse_command_file(...) file.gcount() == 0."); - int lineLength = file.gcount() - 1; + int line_length = file.gcount() - 1; // In case we are at the end of the file and the last character is // not a line feed, we'll just increase the read character count so // that the last would also be included in option line. if (file.eof() && file.get() != '\n') - lineLength++; + line_length++; - int escaped = parse_count_escaped(buffer + getCount, buffer + getCount + lineLength); + int escaped = parse_count_escaped(buffer + getCount, buffer + getCount + line_length); lineNumber++; - getCount += lineLength; + getCount += line_length; if (getCount == 4096 - 1) throw torrent::input_error("Exceeded max line length."); diff --git a/src/setup.cc b/src/setup.cc new file mode 100644 index 00000000..4dc449f5 --- /dev/null +++ b/src/setup.cc @@ -0,0 +1,220 @@ +#include "config.h" + +#include "setup.h" + +#include +#include +#include +#include +#include + +#include "control.h" +#include "globals.h" +#include "option_parser.h" +#include "core/download_factory.h" +#include "rpc/parse_commands.h" +#include "session/download_storer.h" +#include "utils/directory.h" + +void do_panic(int signum); +void print_help(); + +// +// Parse command line args and config files: +// + +int +parse_main_options(int argc, char** argv) { + try { + OptionParser optionParser; + + // Converted. + optionParser.insert_flag('h', [](auto&) { print_help(); }); + optionParser.insert_flag('n', [](auto&) { }); + optionParser.insert_flag('D', [](auto&) { }); + optionParser.insert_flag('I', [](auto&) { }); + optionParser.insert_flag('K', [](auto&) { }); + + optionParser.insert_option('b', [](auto& arg) { rpc::call_command_set_string("network.bind_address.set", arg); }); + optionParser.insert_option('d', [](auto& arg) { rpc::call_command_set_string("directory.default.set", arg); }); + optionParser.insert_option('i', [](auto& arg) { rpc::call_command_set_string("ip", arg); }); + optionParser.insert_option('p', [](auto& arg) { rpc::call_command_set_string("network.port_range.set", arg); }); + optionParser.insert_option('s', [](auto& arg) { rpc::call_command_set_string("session", arg); }); + + optionParser.insert_option('O', [](auto& arg) { rpc::parse_command_single_std(arg); }); + optionParser.insert_option_list('o', [](auto& arg1, auto& arg2) { rpc::call_command_set_std_string(arg1, arg2); }); + + return optionParser.process(argc, argv); + + } catch (torrent::input_error& e) { + throw torrent::input_error("Failed to parse command line option: " + std::string(e.what())); + } +} + +void +parse_config_file(int argc, char** argv, std::function parse_fn) { + if (OptionParser::has_flag('n', argc, argv)) + return parse_fn(""); + + char* config_dir = std::getenv("XDG_CONFIG_HOME"); + char* home_dir = std::getenv("HOME"); + + if (config_dir != nullptr && config_dir[0] == '/' && access((std::string(config_dir) + "/rtorrent/rtorrent.rc").c_str(), F_OK) != -1) + return parse_fn(std::string(config_dir) + "/rtorrent/rtorrent.rc"); + + if (home_dir == nullptr) + throw torrent::input_error("Could not find home directory, HOME environment variable not set."); + + if (access((std::string(home_dir) + "/.config/rtorrent/rtorrent.rc").c_str(), F_OK) != -1) + return parse_fn(std::string(home_dir) + "/.config/rtorrent/rtorrent.rc"); + + return parse_fn(std::string(home_dir) + "/.rtorrent.rc"); +} + +void +config_comment_log(const std::string& command, const std::string& raw_args) { + std::vector args; + size_t pos{}; + + while (pos < raw_args.size()) { + size_t next_pos = raw_args.find(',', pos); + + if (next_pos == std::string::npos) + next_pos = raw_args.size(); + + args.push_back(raw_args.substr(pos, next_pos - pos)); + pos = next_pos + 1; + } + + if (command == "log.add_output") + log_add_group_output_str(args[0], args[1]); + else if (command == "log.open_file") + apply_log_open_str(0, args); + else if (command == "log.open_file.flush") + apply_log_open_str(log_flag_flush, args); + else if (command == "log.open_gz_file") + apply_log_open_str(log_flag_use_gz, args); + else if (command == "log.open_file_pid") + apply_log_open_str(log_flag_append_pid, args); + else if (command == "log.open_gz_file_pid") + apply_log_open_str(log_flag_append_pid | log_flag_use_gz, args); + else if (command == "log.append_file") + apply_log_open_str(log_flag_append_file, args); + else if (command == "log.append_file.flush") + apply_log_open_str(log_flag_append_file | log_flag_flush, args); + else if (command == "log.append_gz_file") + apply_log_open_str(log_flag_append_file | log_flag_use_gz, args); + else + throw torrent::input_error("Unknown log command: " + command); +} + +// Call special commands in the format "# do:command=args" in the config file. +void +parse_config_file_comments(const std::string& path) { + std::fstream file(path, std::ios::in); + + if (!file.is_open()) + return; + + std::string line; + + while (std::getline(file, line)) { + if (line.size() <= 5 || line.compare(0, 5, "# do:") != 0) + continue; + + auto equal_pos = line.find('='); + + if (equal_pos == std::string::npos) + throw torrent::input_error("Invalid command in config file comment: " + line); + + std::string command = line.substr(5, equal_pos - 5); + std::string args = line.substr(equal_pos + 1); + + if (command.empty()) + throw torrent::input_error("Invalid command in config file comment: " + line); + + if (command.compare(0, 4, "log.") == 0) + config_comment_log(command, args); + else + throw torrent::input_error("Unknown command in config file comment: " + line); + + lt_log_print(torrent::LOG_NOTICE, "Pre-config command: %s=%s", command.c_str(), args.c_str()); + } +} + +// +// Torrents: +// + +void +load_session_torrents(const std::string& path) { + auto entries = session::DownloadStorer::get_formated_entries(path); + + for (const auto& entry : entries) { + // We don't really support session torrents that are links. These + // would be overwritten anyway on exit, and thus not really be + // useful. + if (!entry.is_file()) + continue; + + auto* f = new core::DownloadFactory(control->core()); + + f->set_session(true); + f->set_init_load(true); + f->slot_finished([f](){ delete f; }); + f->load(entries.path() + entry.s_name); + f->commit(); + } +} + +void +load_arg_torrents(char** first, char** last) { + for (; first != last; ++first) { + auto* f = new core::DownloadFactory(control->core()); + + f->set_start(true); + f->set_init_load(true); + f->slot_finished([f](){ delete f; }); + f->load(*first); + f->commit(); + } +} + +// +// Logging: +// + +void +log_add_group_output_str(const std::string& group_name, const std::string& output_id) { + int log_group = torrent::option_find_string(torrent::OPTION_LOG_GROUP, group_name.c_str()); + + torrent::log_add_group_output(log_group, output_id.c_str()); +} + +void +apply_log_open_str(int output_flags, const std::vector& args) { + if (args.size() < 2) + throw torrent::input_error("Invalid number of arguments."); + + auto output_id = args[0]; + auto file_name = expand_path(args[1]); + + if ((output_flags & log_flag_append_pid)) { + char buffer[32]; + snprintf(buffer, 32, ".%li", (long)getpid()); + + file_name += buffer; + } + + bool append = (output_flags & log_flag_append_file); + bool flush = (output_flags & log_flag_flush); + + if ((output_flags & log_flag_use_gz)) + torrent::log_open_gz_file_output(output_id.c_str(), file_name.c_str(), append); + else + torrent::log_open_file_output(output_id.c_str(), file_name.c_str(), append, flush); + + for (size_t i = 2; i < args.size(); i++) + log_add_group_output_str(args[i], output_id); +} + diff --git a/src/setup.h b/src/setup.h new file mode 100644 index 00000000..fe105c3b --- /dev/null +++ b/src/setup.h @@ -0,0 +1,22 @@ +#ifndef RTORRENT_SETUP_H +#define RTORRENT_SETUP_H + +#include +#include + +int parse_main_options(int argc, char** argv); +void parse_config_file(int argc, char** argv, std::function parse_fn); +void parse_config_file_comments(const std::string& path); + +void load_session_torrents(const std::string& path); +void load_arg_torrents(char** first, char** last); + +static constexpr int log_flag_use_gz = 0x1; +static constexpr int log_flag_append_pid = 0x2; +static constexpr int log_flag_append_file = 0x4; +static constexpr int log_flag_flush = 0x8; + +void log_add_group_output_str(const std::string& group_name, const std::string& output_id); +void apply_log_open_str(int output_flags, const std::vector& args); + +#endif