diff --git a/configure.ac b/configure.ac index 5d75cf3d..f2e3cde5 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ m4_pattern_allow([PKG_CHECK_EXISTS]) -AC_INIT([rtorrent],[0.16.20],[sundell.software@gmail.com]) +AC_INIT([rtorrent],[0.16.22],[sundell.software@gmail.com]) AC_CONFIG_HEADERS([config.h]) AC_CONFIG_MACRO_DIRS([scripts]) @@ -14,7 +14,7 @@ AX_CXX_COMPILE_STDCXX(20, noext, mandatory) PKG_PROG_PKG_CONFIG -AC_DEFINE([API_VERSION], [25], [api version]) +AC_DEFINE([API_VERSION], [26], [api version]) RAK_CHECK_CFLAGS RAK_CHECK_CXXFLAGS @@ -49,7 +49,7 @@ fi PKG_CHECK_MODULES([CPPUNIT], [cppunit],, [no_cppunit="yes"]) PKG_CHECK_MODULES([ZLIB], [zlib]) -PKG_CHECK_MODULES([DEPENDENCIES], [libtorrent >= 0.16.20]) +PKG_CHECK_MODULES([DEPENDENCIES], [libtorrent >= 0.16.22]) AC_LANG_PUSH(C++) TORRENT_WITH_XMLRPC_C diff --git a/doc/rtorrent.rc b/doc/rtorrent.rc index aa2e6fdf..eceb12c4 100644 --- a/doc/rtorrent.rc +++ b/doc/rtorrent.rc @@ -63,6 +63,14 @@ #network.local_address.set = 127.0.0.1 #network.local_address.set = rakshasa.no +# The port reported to the tracker, separate for ipv4/ipv6. A value of 0 +# (the default) falls back to reporting the listening port. network.local_port.set +# sets both the ipv4 and ipv6 ports at once. +# +#network.local_port.set = 6881 +#network.local_port.ipv4.set = 6881 +#network.local_port.ipv6.set = 6882 + # The IP address the listening socket and outgoing connections is # bound to. # diff --git a/src/Makefile.am b/src/Makefile.am index e53c2f96..1c4ccfef 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -195,6 +195,7 @@ libsub_root_a_SOURCES = \ command_tracker.cc \ command_scheduler.cc \ command_string.cc \ + command_system.cc \ command_ui.cc \ control.cc \ control.h \ diff --git a/src/command_download.cc b/src/command_download.cc index cb208307..a910c178 100644 --- a/src/command_download.cc +++ b/src/command_download.cc @@ -142,16 +142,6 @@ apply_d_delete_tied(core::Download* download) { return torrent::Object(); } -void -apply_d_directory(core::Download* download, const std::string& name) { - if (!download->file_list()->is_multi_file()) - download->set_root_directory(name); - else if (name.empty() || *name.rbegin() == '/') - download->set_root_directory(name + download->info()->name().str()); - else - download->set_root_directory(name + "/" + download->info()->name().str()); -} - torrent::Object apply_d_connection_type(core::Download* download, const std::string& name) { torrent::Download::ConnectionType t = @@ -718,7 +708,7 @@ initialize_command_download() { CMD2_DL_V ("d.pause", std::bind(&core::DownloadList::pause_default, control->core()->download_list(), std::placeholders::_1)); CMD2_DL_V ("d.open", std::bind(&core::DownloadList::open_throw, control->core()->download_list(), std::placeholders::_1)); CMD2_DL_V ("d.close", std::bind(&core::DownloadList::close_throw, control->core()->download_list(), std::placeholders::_1)); - CMD2_DL_V ("d.close.directly", std::bind(&core::DownloadList::close_directly, control->core()->download_list(), std::placeholders::_1)); + CMD2_DL_V ("d.close.directly", std::bind(&core::DownloadList::close_files, control->core()->download_list(), std::placeholders::_1)); CMD2_DL_V ("d.erase", std::bind(&core::DownloadList::erase_ptr, control->core()->download_list(), std::placeholders::_1)); CMD2_DL_V ("d.check_hash", std::bind(&core::DownloadList::check_hash, control->core()->download_list(), std::placeholders::_1)); @@ -884,12 +874,11 @@ initialize_command_download() { CMD2_DL_LIST ("d.tracker.insert", std::bind(&download_tracker_insert, std::placeholders::_1, std::placeholders::_2)); CMD2_DL_VALUE_V ("d.tracker.send_scrape", [](auto download, uint64_t arg) { download->tracker_controller().scrape_request(arg); }); - CMD2_DL ("d.directory", CMD2_ON_FL(root_dir)); - CMD2_DL ("d.directory.realpath.or_empty", [](auto* download, auto) { return resolve_path(download->file_list()->root_dir()); }); - CMD2_DL ("d.directory.realpath.or_throw", [](auto* download, auto) { return resolve_path_or_throw(download->file_list()->root_dir()); }); - CMD2_DL_STRING_V("d.directory.set", std::bind(&apply_d_directory, std::placeholders::_1, std::placeholders::_2)); - CMD2_DL ("d.directory_base", CMD2_ON_FL(root_dir)); - CMD2_DL_STRING_V("d.directory_base.set", std::bind(&core::Download::set_root_directory, std::placeholders::_1, std::placeholders::_2)); + CMD2_DL ("d.directory", CMD2_ON_FL(root_dir)); + CMD2_DL ("d.directory.realpath.or_empty", [](auto* download, auto) { return resolve_path(download->file_list()->root_dir()); }); + CMD2_DL ("d.directory.realpath.or_throw", [](auto* download, auto) { return resolve_path_or_throw(download->file_list()->root_dir()); }); + CMD2_DL_STRING_V("d.directory.set", [](auto* download, auto arg) { download->set_directory(arg); }); + CMD2_DL_STRING_V("d.directory.base.set", [](auto* download, auto arg) { download->set_base_directory(arg); }); CMD2_DL ("d.priority", std::bind(&core::Download::priority, std::placeholders::_1)); CMD2_DL ("d.priority_str", std::bind(&retrieve_d_priority_str, std::placeholders::_1)); @@ -950,7 +939,6 @@ initialize_command_download() { rpc::rpc.mark_safe("d.name.or_base64"); rpc::rpc.mark_safe("d.name.or_as_binary"); rpc::rpc.mark_safe("d.directory"); - rpc::rpc.mark_safe("d.directory_base"); rpc::rpc.mark_safe("d.creation_date"); rpc::rpc.mark_safe("d.load_date"); rpc::rpc.mark_safe("d.up.rate"); diff --git a/src/command_events.cc b/src/command_events.cc index 2afd600d..2538944a 100644 --- a/src/command_events.cc +++ b/src/command_events.cc @@ -51,7 +51,7 @@ apply_on_ratio(const torrent::Object& rawArgs) { !(max_ratio > 0 && total_upload * 100 > total_done * max_ratio)) continue; - downloads.push_back(*itr); + downloads.push_back(itr->get()); } auto ratio_command = "group." + group_name + ".ratio.command"; @@ -173,7 +173,7 @@ apply_close_low_diskspace(int64_t arg, uint32_t skip_priority) { torrent::FileList::cache_list cache; - for (auto download : *control->core()->download_list()) { + for (const auto& download : *control->core()->download_list()) { if (!download->is_downloading()) continue; if (download->priority() >= skip_priority) @@ -240,15 +240,25 @@ d_multicall(const torrent::Object::list_type& args) { // Add some pre-parsing of the commands, so we don't spend time // parsing and searching command map for every single call. - std::vector dlist((*view_itr)->begin_visible(), (*view_itr)->end_visible()); + // Hold a reference to each download so a command that erases one does not + // leave the rest of the loop dispatching on freed memory. + core::View::base_type dlist((*view_itr)->begin_visible(), (*view_itr)->end_visible()); torrent::Object resultRaw = torrent::Object::create_list(); torrent::Object::list_type& result = resultRaw.as_list(); - for (auto download : dlist) { + for (const auto& download : dlist) { + if (download.use_count() == 1) + continue; + torrent::Object::list_type& row = result.insert(result.end(), torrent::Object::create_list())->as_list(); for (torrent::Object::list_const_iterator cItr = ++args.begin(); cItr != args.end(); cItr++) { + // A command may erase this download, which destroys the torrent object it + // wraps; the list dropping its reference is what tells us. + if (download.use_count() == 1) + break; + auto& cmd = cItr->as_string(); row.push_back(rpc::parse_command(rpc::make_target(download), cmd.c_str(), cmd.c_str() + cmd.size()).first); } @@ -271,10 +281,12 @@ d_multicall_filtered(const torrent::Object::list_type& args) { if (view_itr == viewManager->end()) throw torrent::input_error("Could not find view '" + arg->as_string() + "'."); - // Make a filtered copy of the current item list + // Make a filtered copy of the current item list, holding a reference to each + // download so a command that erases one cannot free it under us. core::View::base_type dlist; (*view_itr)->filter_by(*++arg, dlist); + // Generate result by iterating over all items auto resultRaw = torrent::Object::create_list(); auto& result = resultRaw.as_list(); @@ -282,11 +294,17 @@ d_multicall_filtered(const torrent::Object::list_type& args) { ++arg; // skip to first command for (const auto& item : dlist) { + if (item.use_count() == 1) + continue; + // Add empty row to result torrent::Object::list_type& row = result.insert(result.end(), torrent::Object::create_list())->as_list(); // Call the provided commands and assemble their results for (torrent::Object::list_const_iterator command = arg; command != args.end(); command++) { + if (item.use_count() == 1) + break; + auto& cmdstr = command->as_string(); row.push_back(rpc::parse_command(rpc::make_target(item), cmdstr.c_str(), cmdstr.c_str() + cmdstr.size()).first); } diff --git a/src/command_helpers.cc b/src/command_helpers.cc index 512d1a6a..fee22018 100644 --- a/src/command_helpers.cc +++ b/src/command_helpers.cc @@ -20,6 +20,7 @@ void initialize_command_throttle(); void initialize_command_tracker(); void initialize_command_scheduler(); void initialize_command_string(); +void initialize_command_system(); void initialize_command_ui(); void @@ -39,4 +40,5 @@ initialize_commands() { initialize_command_tracker(); initialize_command_scheduler(); initialize_command_string(); + initialize_command_system(); } diff --git a/src/command_ip.cc b/src/command_ip.cc index c4194eaf..6988a37c 100644 --- a/src/command_ip.cc +++ b/src/command_ip.cc @@ -131,7 +131,7 @@ ipv4_range_parse(const char* address, uint32_t* address_start, uint32_t* address // copy everything up to '#' to address_copy and work from there while(address[address_start_index] != '#' && address[address_start_index] != '\r' && address[address_start_index] != '\n' && address[address_start_index] != '\0' && - address_start_index < 4096 ) { + address_start_index < 4095 ) { address_copy[address_start_index] = address[address_start_index]; address_start_index++; @@ -206,6 +206,9 @@ ipv4_range_parse(const char* address, uint32_t* address_start, uint32_t* address if (mask_bits == 0) { mask = 0; end_mask = ~(uint32_t)0; + } else if (mask_bits == 32) { + mask = ~(uint32_t)0; + end_mask = 0; } else { mask = (~mask) << (32-mask_bits); end_mask = (~end_mask) >> mask_bits; diff --git a/src/command_local.cc b/src/command_local.cc index 2a7d983b..ab73fe15 100644 --- a/src/command_local.cc +++ b/src/command_local.cc @@ -1,33 +1,16 @@ #include "config.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include -#include +#include +#include "command_helpers.h" +#include "control.h" #include "core/download.h" -#include "core/download_list.h" #include "core/manager.h" #include "rpc/parse_commands.h" +#include "rpc/lua.h" #include "rpc/scgi.h" #include "session/session_manager.h" -#include "utils/file_status_cache.h" - -#include "globals.h" -#include "rpc/lua.h" -#include "control.h" -#include "command_helpers.h" typedef torrent::ChunkManager CM_t; typedef torrent::FileManager FM_t; @@ -43,49 +26,6 @@ apply_pieces_stats_total_size() { return size; } -torrent::Object -system_env(const torrent::Object::string_type& arg) { - if (arg.empty()) - throw torrent::input_error("system.env: Missing variable name."); - - char* val = getenv(arg.c_str()); - return std::string(val ? val : ""); -} - -torrent::Object -system_hostname() { - char buffer[1024]; - - if (gethostname(buffer, 1023) == -1) - throw torrent::input_error("Unable to read hostname."); - -// if (shorten) -// *std::find(buffer, buffer + 1023, '.') = '\0'; - - return std::string(buffer); -} - -torrent::Object -system_get_cwd() { - char* buffer = getcwd(NULL, 0); - - if (buffer == NULL) - throw torrent::input_error("Unable to read cwd."); - - torrent::Object result = torrent::Object(std::string(buffer)); - free(buffer); - - return result; -} - -torrent::Object -system_set_cwd(const torrent::Object::string_type& rawArgs) { - if (::chdir(rawArgs.c_str()) != 0) - throw torrent::input_error("Could not change current working directory."); - - return torrent::Object(); -} - inline torrent::Object::list_const_iterator post_increment(torrent::Object::list_const_iterator& itr, const torrent::Object::list_const_iterator& last) { if (itr == last) @@ -187,94 +127,17 @@ cmd_file_append(const torrent::Object::list_type& args) { void initialize_command_local() { - core::DownloadList* dList = control->core()->download_list(); - torrent::FileManager* fileManager = torrent::file_manager(); - - CMD_ANY ("system.hostname", std::bind(&system_hostname)); - CMD_ANY ("system.pid", std::bind(&getpid)); - - CMD_VAR_C_STRING("system.api_version", (int64_t)API_VERSION); - CMD_VAR_C_STRING("system.client_version", PACKAGE_VERSION); - CMD_VAR_C_STRING("system.library_version", torrent::runtime::version()); - - CMD_VAR_VALUE ("system.file.allocate", 0); - CMD_VAR_VALUE ("system.file.max_size", (int64_t)512 << 30); - CMD_VAR_VALUE ("system.file.split_size", -1); - CMD_VAR_STRING ("system.file.split_suffix", ".part"); - - CMD_ANY ("system.file_status_cache.size", std::bind(&utils::FileStatusCache::size, - (utils::FileStatusCache::base_type*)control->core()->file_status_cache())); - CMD_ANY_V ("system.file_status_cache.prune", std::bind(&utils::FileStatusCache::prune, control->core()->file_status_cache())); - - CMD_VAR_BOOL ("file.prioritize_toc", 0); - CMD_VAR_LIST ("file.prioritize_toc.first"); - CMD_VAR_LIST ("file.prioritize_toc.last"); - - CMD_ANY ("system.files.advise_random", std::bind(&FM_t::advise_random, fileManager)); - CMD_ANY_VALUE_V ("system.files.advise_random.set", std::bind(&FM_t::set_advise_random, fileManager, std::placeholders::_2)); - CMD_ANY ("system.files.advise_random.hashing", std::bind(&FM_t::advise_random_hashing, fileManager)); - CMD_ANY_VALUE_V ("system.files.advise_random.hashing.set", std::bind(&FM_t::set_advise_random_hashing, fileManager, std::placeholders::_2)); - CMD_ANY ("system.files.session.fdatasync", [](auto, auto) { return session_thread::manager()->use_fsyncdisk(); }); - CMD_ANY_VALUE_V ("system.files.session.fdatasync.set", [](auto, auto& value) { return session_thread::manager()->set_use_fsyncdisk(value); }); - - CMD_ANY ("system.files.opened_counter", std::bind(&FM_t::files_opened_counter, fileManager)); - CMD_ANY ("system.files.closed_counter", std::bind(&FM_t::files_closed_counter, fileManager)); - CMD_ANY ("system.files.failed_counter", std::bind(&FM_t::files_failed_counter, fileManager)); - - CMD_ANY_STRING ("system.env", [](auto, auto& str) { return system_env(str); }); - - CMD_ANY ("system.time", [](auto, auto) { return torrent::this_thread::cached_seconds().count(); }); - CMD_ANY ("system.time_seconds", [](auto, auto) { return torrent::utils::cast_seconds(torrent::utils::time_since_epoch()).count(); }); - CMD_ANY ("system.time_usec", [](auto, auto) { return torrent::utils::time_since_epoch().count(); }); - - CMD_ANY_VALUE_V ("system.umask.set", [](auto, auto& value) { return ::umask(value); }); - - CMD_VAR_BOOL ("system.daemon", false); - - CMD_ANY_V ("system.shutdown.normal", [](auto, auto) { control->receive_normal_shutdown(); }); - CMD_ANY_V ("system.shutdown.quick", [](auto, auto) { control->receive_quick_shutdown(); }); - - CMD_REDIRECT_NO_EXPORT("system.shutdown", "system.shutdown.normal"); - - CMD_ANY ("system.cwd", [](auto, auto) { return system_get_cwd(); }); - CMD_ANY_STRING ("system.cwd.set", [](auto, auto& str) { return system_set_cwd(str); }); - - CMD_ANY ("system.sockets.size", [](auto, auto) { return torrent::runtime::socket_manager()->size(); }); - CMD_ANY ("system.sockets.max_size", [](auto, auto) { return torrent::runtime::socket_manager()->max_size(); }); - CMD_ANY_VALUE_V ("system.sockets.max_size.set", [](auto, auto& value) { return torrent::runtime::socket_manager()->set_max_size_and_adjust(value); }); - CMD_ANY_V ("system.sockets.adjust_alloc", [](auto, auto) { torrent::runtime::socket_manager()->adjust_allocation(); }); - CMD_ANY ("system.sockets.reserved_alloc", [](auto, auto) { return torrent::runtime::socket_manager()->reserved_allocation(); }); - CMD_ANY ("system.sockets.available_alloc", [](auto, auto) { return torrent::runtime::socket_manager()->available_allocation(); }); - - for (uint32_t i = 0; i < torrent::runtime::SocketManager::category_count; ++i) { - auto category = static_cast(i); - auto category_name = "system.sockets." + torrent::option_to_str_or_throw(torrent::OPTION_SOCKET_CATEGORY, i); - - CMD_ANY (category_name + ".size", [category](auto, auto) { return torrent::runtime::socket_manager()->category_managed_size(category); }); - CMD_ANY (category_name + ".max_size", [category](auto, auto) { return torrent::runtime::socket_manager()->category_max_size(category); }); - - if (i == 0) { - CMD_ANY (category_name + ".min_alloc", [](auto, auto) { return torrent::runtime::socket_manager()->generic_min_allocation(); }); - continue; - } - - CMD_ANY (category_name + ".max_alloc.limit", [category](auto, auto) { return torrent::runtime::socket_manager()->category_alloc_limit(category); }); - CMD_ANY (category_name + ".min_alloc.limit", [category](auto, auto) { return torrent::runtime::socket_manager()->category_alloc_minimum(category); }); - CMD_ANY (category_name + ".min_alloc", [category](auto, auto) { return torrent::runtime::socket_manager()->category_min_allocation(category); }); - CMD_ANY (category_name + ".max_alloc", [category](auto, auto) { return torrent::runtime::socket_manager()->category_max_allocation(category); }); - CMD_ANY_VALUE_V(category_name + ".min_alloc.set", [category](auto, auto& value) { torrent::runtime::socket_manager()->set_category_min_allocation(category, value); }); - CMD_ANY_VALUE_V(category_name + ".max_alloc.set", [category](auto, auto& value) { torrent::runtime::socket_manager()->set_category_max_allocation(category, value); }); - } + core::DownloadList* dList = control->core()->download_list(); CMD_ANY ("pieces.sync.always_safe", [](auto, auto) { return torrent::runtime::memory_manager()->safe_sync(); }); CMD_ANY_VALUE_V ("pieces.sync.always_safe.set", [](auto, auto& value) { return torrent::runtime::memory_manager()->set_safe_sync(value); }); CMD_ANY ("pieces.sync.safe_free_diskspace", [](auto, auto) { return torrent::runtime::memory_manager()->sync_safe_free_diskspace(); }); CMD_ANY ("pieces.sync.timeout", [](auto, auto) { return torrent::runtime::memory_manager()->timeout_sync().count(); }); CMD_ANY_VALUE_V ("pieces.sync.timeout.set", [](auto, auto& value) { return torrent::runtime::memory_manager()->set_timeout_sync(value); }); - // CMD_ANY ("pieces.sync.timeout_safe", [](auto, auto) { return torrent::runtime::memory_manager()->timeout_safe_sync(); }); - // CMD_ANY_VALUE_V ("pieces.sync.timeout_safe.set", [](auto, auto& value) { return torrent::runtime::memory_manager()->set_timeout_safe_sync(value); }); CMD_ANY ("pieces.sync.timeout_safe", [](auto, auto) { return 0; }); - CMD_ANY_VALUE_V ("pieces.sync.timeout_safe.set", [](auto, auto) { }); + CMD_ANY_VALUE_V ("pieces.sync.timeout_safe.set", [](auto, auto) { + lt_log_print(torrent::LOG_WARN, "pieces.sync.timeout_safe.set is no longer supported and does nothing."); + }); CMD_ANY ("pieces.sync.queue_size", [](auto, auto) { return torrent::runtime::memory_manager()->sync_queue_block_count(); }); CMD_ANY ("pieces.preload.type", [](auto, auto) { return torrent::runtime::memory_manager()->preload_type(); }); @@ -349,35 +212,6 @@ initialize_command_local() { CMD_ANY_LIST ("group.insert", std::bind(&group_insert, std::placeholders::_2)); - rpc::rpc.mark_safe("system.api_version"); - rpc::rpc.mark_safe("system.client_version"); - rpc::rpc.mark_safe("system.library_version"); - rpc::rpc.mark_safe("system.file.max_size"); - rpc::rpc.mark_safe("system.file.split_size"); - rpc::rpc.mark_safe("system.file.split_suffix"); - - rpc::rpc.mark_safe("system.sockets.size"); - rpc::rpc.mark_safe("system.sockets.max_size"); - rpc::rpc.mark_safe("system.sockets.reserved_alloc"); - rpc::rpc.mark_safe("system.sockets.available_alloc"); - - for (uint32_t i = 0; i < torrent::runtime::SocketManager::category_count; ++i) { - auto category_name = "system.sockets." + torrent::option_to_str_or_throw(torrent::OPTION_SOCKET_CATEGORY, i); - - rpc::rpc.mark_safe(category_name + ".size"); - rpc::rpc.mark_safe(category_name + ".max_size"); - - if (i == 0) { - rpc::rpc.mark_safe(category_name + ".min_alloc"); - continue; - } - - rpc::rpc.mark_safe(category_name + ".max_alloc.limit"); - rpc::rpc.mark_safe(category_name + ".min_alloc.limit"); - rpc::rpc.mark_safe(category_name + ".min_alloc"); - rpc::rpc.mark_safe(category_name + ".max_alloc"); - } - rpc::rpc.mark_safe("directory.default"); rpc::rpc.mark_safe("session.path"); rpc::rpc.mark_safe("session.path.realpath.or_empty"); diff --git a/src/command_logging.cc b/src/command_logging.cc index 3ee952c8..8dd47cd3 100644 --- a/src/command_logging.cc +++ b/src/command_logging.cc @@ -135,7 +135,7 @@ initialize_command_logging() { CMD2_ANY_LIST ("log.open_gz_file_pid", std::bind(&apply_log_open, log_flag_append_pid | log_flag_use_gz, std::placeholders::_2)); CMD2_ANY_LIST ("log.append_file", std::bind(&apply_log_open, log_flag_append_file, std::placeholders::_2)); CMD2_ANY_LIST ("log.append_file.flush", std::bind(&apply_log_open, log_flag_append_file | log_flag_flush, std::placeholders::_2)); - CMD2_ANY_LIST ("log.append_gz_file", std::bind(&apply_log_open, log_flag_append_file, std::placeholders::_2)); + CMD2_ANY_LIST ("log.append_gz_file", std::bind(&apply_log_open, log_flag_append_file | log_flag_use_gz, std::placeholders::_2)); CMD2_ANY_STRING_V("log.close", std::bind(&torrent::log_close_output_str, std::placeholders::_2)); diff --git a/src/command_network.cc b/src/command_network.cc index a8c6b027..660d238d 100644 --- a/src/command_network.cc +++ b/src/command_network.cc @@ -54,6 +54,14 @@ set_listen_port_range(const std::string& arg) { torrent::runtime::client_config()->set_listen_port_range(port_first, port_last); } +uint16_t +checked_local_port_value(int64_t value, const char* label) { + if (value < 0 || value > 65535) + throw torrent::input_error(std::string("Invalid ") + label + " port number."); + + return static_cast(value); +} + torrent::Object get_encryption() { auto encryption_modes = torrent::runtime::network_config()->encryption_modes(); @@ -379,6 +387,13 @@ initialize_command_network() { CMD_ANY ("network.local_address.ipv6", [nw_config](auto, auto) { return nw_config->local_inet6_address_str(); }); CMD_ANY_STRING_V("network.local_address.ipv6.set", [nw_config](auto, auto& str) { return nw_config->set_local_inet6_address_str(str); }); + CMD_ANY ("network.local_port", [nw_config](auto, auto) { return nw_config->local_port_best_match(); }); + CMD_ANY_VALUE_V ("network.local_port.set", [nw_config](auto, auto& value) { return nw_config->set_local_port(checked_local_port_value(value, "local")); }); + CMD_ANY ("network.local_port.ipv4", [nw_config](auto, auto) { return nw_config->local_inet_port(); }); + CMD_ANY_VALUE_V ("network.local_port.ipv4.set", [nw_config](auto, auto& value) { return nw_config->set_local_inet_port(checked_local_port_value(value, "local ipv4")); }); + CMD_ANY ("network.local_port.ipv6", [nw_config](auto, auto) { return nw_config->local_inet6_port(); }); + CMD_ANY_VALUE_V ("network.local_port.ipv6.set", [nw_config](auto, auto& value) { return nw_config->set_local_inet6_port(checked_local_port_value(value, "local ipv6")); }); + CMD_ANY ("network.proxy.global", [](auto, auto) { return torrent::runtime::proxy_manager()->proxy_url(); }); CMD_ANY_STRING_V("network.proxy.global.set", [](auto, auto& str) { return torrent::runtime::proxy_manager()->set_proxy_url(str); }); CMD_ANY ("network.proxy.http", [](auto, auto) { return torrent::runtime::proxy_manager()->http_proxy_url(); }); @@ -437,6 +452,9 @@ initialize_command_network() { rpc::rpc.mark_safe("network.receive_buffer.size"); rpc::rpc.mark_safe("network.bind_address"); rpc::rpc.mark_safe("network.local_address"); + rpc::rpc.mark_safe("network.local_port"); + rpc::rpc.mark_safe("network.local_port.ipv4"); + rpc::rpc.mark_safe("network.local_port.ipv6"); rpc::rpc.mark_safe("network.xmlrpc.size_limit"); rpc::rpc.mark_safe("network.open_sockets"); diff --git a/src/command_system.cc b/src/command_system.cc new file mode 100644 index 00000000..4615acd2 --- /dev/null +++ b/src/command_system.cc @@ -0,0 +1,195 @@ +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "command_helpers.h" +#include "control.h" +#include "globals.h" +#include "core/manager.h" +#include "session/session_manager.h" +#include "utils/file_status_cache.h" + + +torrent::Object +system_env(const torrent::Object::string_type& arg) { + if (arg.empty()) + throw torrent::input_error("system.env: Missing variable name."); + + char* val = getenv(arg.c_str()); + return std::string(val ? val : ""); +} + +torrent::Object +system_hostname() { + char buffer[1024]; + + if (gethostname(buffer, 1023) == -1) + throw torrent::input_error("Unable to read hostname."); + +// if (shorten) +// *std::find(buffer, buffer + 1023, '.') = '\0'; + + return std::string(buffer); +} + +torrent::Object +system_get_cwd() { + char* buffer = getcwd(NULL, 0); + + if (buffer == NULL) + throw torrent::input_error("Unable to read cwd."); + + torrent::Object result = torrent::Object(std::string(buffer)); + free(buffer); + + return result; +} + +torrent::Object +system_set_cwd(const torrent::Object::string_type& rawArgs) { + if (::chdir(rawArgs.c_str()) != 0) + throw torrent::input_error("Could not change current working directory."); + + return torrent::Object(); +} + +uint32_t +checked_socket_value(int64_t value, const char* label) { + if (value < 0 || value > std::numeric_limits::max()) + throw torrent::input_error(std::string("Invalid ") + label + " value."); + + return static_cast(value); +} + +void +initialize_command_system() { + CMD_ANY ("system.hostname", std::bind(&system_hostname)); + CMD_ANY ("system.pid", std::bind(&getpid)); + + CMD_VAR_C_STRING("system.api_version", (int64_t)API_VERSION); + CMD_VAR_C_STRING("system.client_version", PACKAGE_VERSION); + CMD_VAR_C_STRING("system.library_version", torrent::runtime::version()); + + CMD_ANY ("system.torrent_name.use_sanitized", [](auto, auto) { return torrent::runtime::client_config()->torrent_name_use_sanitized(); }); + CMD_ANY_VALUE_V ("system.torrent_name.use_sanitized.set", [](auto, auto& value) { return torrent::runtime::client_config()->set_torrent_name_use_sanitized(value); }); + + CMD_VAR_VALUE ("system.file.allocate", 0); + CMD_VAR_VALUE ("system.file.max_size", (int64_t)512 << 30); + CMD_VAR_VALUE ("system.file.split_size", -1); + CMD_VAR_STRING ("system.file.split_suffix", ".part"); + + CMD_ANY ("system.file_name.replace_slash", [](auto, auto) { return torrent::runtime::client_config()->file_name_replace_slash(); }); + CMD_ANY_STRING_V("system.file_name.replace_slash.set", [](auto, auto& str) { return torrent::runtime::client_config()->set_file_name_replace_slash(str); }); + + CMD_ANY ("system.file_status_cache.size", [](auto, auto) { return control->core()->file_status_cache()->size(); }); + CMD_ANY_V ("system.file_status_cache.prune", [](auto, auto) { return control->core()->file_status_cache()->prune(); }); + + CMD_VAR_BOOL ("file.prioritize_toc", 0); + CMD_VAR_LIST ("file.prioritize_toc.first"); + CMD_VAR_LIST ("file.prioritize_toc.last"); + + CMD_ANY ("system.files.advise_random", [](auto, auto) { return torrent::file_manager()->advise_random(); }); + CMD_ANY_VALUE_V ("system.files.advise_random.set", [](auto, auto& value) { return torrent::file_manager()->set_advise_random(value); }); + CMD_ANY ("system.files.advise_random.hashing", [](auto, auto) { return torrent::file_manager()->advise_random_hashing(); }); + CMD_ANY_VALUE_V ("system.files.advise_random.hashing.set", [](auto, auto& value) { return torrent::file_manager()->set_advise_random_hashing(value); }); + CMD_ANY ("system.files.session.fdatasync", [](auto, auto) { return session_thread::manager()->use_fsyncdisk(); }); + CMD_ANY_VALUE_V ("system.files.session.fdatasync.set", [](auto, auto& value) { return session_thread::manager()->set_use_fsyncdisk(value); }); + + CMD_ANY ("system.files.close_idle", [](auto, auto) { return torrent::file_manager()->close_idle_timeout().count(); }); + CMD_ANY_VALUE_V ("system.files.close_idle.set", [](auto, auto& value) { return torrent::file_manager()->set_close_idle_timeout(value * 1s); }); + + CMD_ANY ("system.files.opened_counter", [](auto, auto) { return torrent::file_manager()->files_opened_counter(); }); + CMD_ANY ("system.files.closed_counter", [](auto, auto) { return torrent::file_manager()->files_closed_counter(); }); + CMD_ANY ("system.files.failed_counter", [](auto, auto) { return torrent::file_manager()->files_failed_counter(); }); + + CMD_ANY_STRING ("system.env", [](auto, auto& str) { return system_env(str); }); + + CMD_ANY ("system.time", [](auto, auto) { return torrent::this_thread::cached_seconds().count(); }); + CMD_ANY ("system.time_seconds", [](auto, auto) { return torrent::utils::cast_seconds(torrent::utils::time_since_epoch()).count(); }); + CMD_ANY ("system.time_usec", [](auto, auto) { return torrent::utils::time_since_epoch().count(); }); + + CMD_ANY_VALUE_V ("system.umask.set", [](auto, auto& value) { return ::umask(value); }); + + CMD_VAR_BOOL ("system.daemon", false); + + CMD_ANY_V ("system.shutdown.normal", [](auto, auto) { control->receive_normal_shutdown(); }); + CMD_ANY_V ("system.shutdown.quick", [](auto, auto) { control->receive_quick_shutdown(); }); + + CMD_REDIRECT_NO_EXPORT("system.shutdown", "system.shutdown.normal"); + + CMD_ANY ("system.cwd", [](auto, auto) { return system_get_cwd(); }); + CMD_ANY_STRING ("system.cwd.set", [](auto, auto& str) { return system_set_cwd(str); }); + + CMD_ANY ("system.sockets.size", [](auto, auto) { return torrent::runtime::socket_manager()->size(); }); + CMD_ANY ("system.sockets.max_size", [](auto, auto) { return torrent::runtime::socket_manager()->max_size(); }); + CMD_ANY_VALUE_V ("system.sockets.max_size.set", [](auto, auto& value) { + return torrent::runtime::socket_manager()->set_max_size_and_adjust(checked_socket_value(value, "socket max size")); + }); + CMD_ANY_V ("system.sockets.adjust_alloc", [](auto, auto) { torrent::runtime::socket_manager()->adjust_allocation(); }); + CMD_ANY ("system.sockets.reserved_alloc", [](auto, auto) { return torrent::runtime::socket_manager()->reserved_allocation(); }); + CMD_ANY ("system.sockets.available_alloc", [](auto, auto) { return torrent::runtime::socket_manager()->available_allocation(); }); + + for (uint32_t i = 0; i < torrent::runtime::SocketManager::category_count; ++i) { + auto category = static_cast(i); + auto category_name = "system.sockets." + torrent::option_to_str_or_throw(torrent::OPTION_SOCKET_CATEGORY, i); + + CMD_ANY (category_name + ".size", [category](auto, auto) { return torrent::runtime::socket_manager()->category_managed_size(category); }); + CMD_ANY (category_name + ".max_size", [category](auto, auto) { return torrent::runtime::socket_manager()->category_max_size(category); }); + + if (i == 0) { + CMD_ANY (category_name + ".min_alloc", [](auto, auto) { return torrent::runtime::socket_manager()->generic_min_allocation(); }); + continue; + } + + CMD_ANY (category_name + ".max_alloc.limit", [category](auto, auto) { return torrent::runtime::socket_manager()->category_alloc_limit(category); }); + CMD_ANY (category_name + ".min_alloc.limit", [category](auto, auto) { return torrent::runtime::socket_manager()->category_alloc_minimum(category); }); + CMD_ANY (category_name + ".min_alloc", [category](auto, auto) { return torrent::runtime::socket_manager()->category_min_allocation(category); }); + CMD_ANY (category_name + ".max_alloc", [category](auto, auto) { return torrent::runtime::socket_manager()->category_max_allocation(category); }); + CMD_ANY_VALUE_V(category_name + ".min_alloc.set", [category](auto, auto& value) { + torrent::runtime::socket_manager()->set_category_min_allocation(category, checked_socket_value(value, "socket min alloc")); + }); + CMD_ANY_VALUE_V(category_name + ".max_alloc.set", [category](auto, auto& value) { + torrent::runtime::socket_manager()->set_category_max_allocation(category, checked_socket_value(value, "socket max alloc")); + }); + } + + rpc::rpc.mark_safe("system.api_version"); + rpc::rpc.mark_safe("system.client_version"); + rpc::rpc.mark_safe("system.library_version"); + rpc::rpc.mark_safe("system.time"); + rpc::rpc.mark_safe("system.time_seconds"); + rpc::rpc.mark_safe("system.time_usec"); + rpc::rpc.mark_safe("system.file.max_size"); + rpc::rpc.mark_safe("system.file.split_size"); + rpc::rpc.mark_safe("system.file.split_suffix"); + + rpc::rpc.mark_safe("system.sockets.size"); + rpc::rpc.mark_safe("system.sockets.max_size"); + rpc::rpc.mark_safe("system.sockets.reserved_alloc"); + rpc::rpc.mark_safe("system.sockets.available_alloc"); + + for (uint32_t i = 0; i < torrent::runtime::SocketManager::category_count; ++i) { + auto category_name = "system.sockets." + torrent::option_to_str_or_throw(torrent::OPTION_SOCKET_CATEGORY, i); + + rpc::rpc.mark_safe(category_name + ".size"); + rpc::rpc.mark_safe(category_name + ".max_size"); + + if (i == 0) { + rpc::rpc.mark_safe(category_name + ".min_alloc"); + continue; + } + + rpc::rpc.mark_safe(category_name + ".max_alloc.limit"); + rpc::rpc.mark_safe(category_name + ".min_alloc.limit"); + rpc::rpc.mark_safe(category_name + ".min_alloc"); + rpc::rpc.mark_safe(category_name + ".max_alloc"); + } +} diff --git a/src/command_tracker.cc b/src/command_tracker.cc index f600143a..49f851b1 100644 --- a/src/command_tracker.cc +++ b/src/command_tracker.cc @@ -68,11 +68,11 @@ apply_dht_add_node(const std::string& arg) { torrent::Object apply_enable_trackers(int64_t arg) { if (arg == 0) { - for (auto download : *control->core()->download_list()) + for (const auto& download : *control->core()->download_list()) download->tracker_controller().for_each([](auto& tracker) { tracker.disable(); }); } else { - for (auto download : *control->core()->download_list()) + for (const auto& download : *control->core()->download_list()) download->tracker_controller().for_each([](auto& tracker) { tracker.enable(); }); } diff --git a/src/command_ui.cc b/src/command_ui.cc index f6222ac2..1d53b1e6 100644 --- a/src/command_ui.cc +++ b/src/command_ui.cc @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -407,8 +408,9 @@ apply_to_time(const torrent::Object& rawArgs, int flags) { torrent::Object apply_to_elapsed_time(const torrent::Object& rawArgs) { auto cached_seconds = torrent::this_thread::cached_seconds().count(); + auto value = rawArgs.as_value(); - uint64_t arg = cached_seconds - rawArgs.as_value(); + uint64_t arg = value >= 0 && value <= cached_seconds ? cached_seconds - value : 0; char buffer[48]; snprintf(buffer, 48, "%2d:%02d:%02d", (int)(arg / 3600), (int)((arg / 60) % 60), (int)(arg % 60)); @@ -674,8 +676,13 @@ apply_math_basic(const char* name, Comp op, const torrent::Object::list_type& ar throw torrent::input_error(std::string(name) + ": Wrong argument type"); } - if (divides && !rhs && itr != args.begin()) - throw torrent::input_error(std::string(name) + ": Division by zero!"); + if (divides && itr != args.begin()) { + if (rhs == 0) + throw torrent::input_error(std::string(name) + ": Division by zero!"); + + if (val == std::numeric_limits::min() && rhs == -1) + throw torrent::input_error(std::string(name) + ": Division overflow!"); + } val = itr == args.begin() ? rhs : op(val, rhs); @@ -916,6 +923,7 @@ initialize_command_ui() { rpc::rpc.mark_safe("view.set_visible"); rpc::rpc.mark_safe("view.set_not_visible"); + rpc::rpc.mark_safe("view.list"); rpc::rpc.mark_safe("cat"); rpc::rpc.mark_safe("if"); diff --git a/src/control.h b/src/control.h index dfc287d4..ceb3656e 100644 --- a/src/control.h +++ b/src/control.h @@ -118,4 +118,6 @@ private: std::atomic m_shutdown_quick{}; }; +extern Control* control; + #endif diff --git a/src/core/download.cc b/src/core/download.cc index c28badc8..4167cbaa 100644 --- a/src/core/download.cc +++ b/src/core/download.cc @@ -101,7 +101,18 @@ Download::set_throttle_name(const std::string& name) { } void -Download::set_root_directory(const std::string& path) { +Download::set_directory(const std::string& path) { + if (!m_download.file_list()->is_multi_file()) + return set_base_directory(path); + + if (path.empty() || *path.rbegin() == '/') + return set_base_directory(path + m_download.info()->name_sanitized()); + + set_base_directory(path + "/" + m_download.info()->name_sanitized()); +} + +void +Download::set_base_directory(const std::string& path) { // If the download is open, hashed and has completed chunks make // sure to verify that the download files are still present. // @@ -120,13 +131,12 @@ Download::set_root_directory(const std::string& path) { !file_stat.update(file_list->front()->frozen_path().str()))) { set_message("Cannot change the directory of an open download after the files have been moved."); - rpc::call_command("d.state.set", (int64_t)0, rpc::make_target(this)); control->core()->download_list()->close_directly(this); throw torrent::input_error("Cannot change the directory of an open download after the files have been moved."); } - control->core()->download_list()->close_directly(this); + control->core()->download_list()->close_files(this); file_list->set_root_dir(expand_path(path)); bencode()->get_key("rtorrent").insert_key("directory", path); diff --git a/src/core/download.h b/src/core/download.h index 67049c36..046034d8 100644 --- a/src/core/download.h +++ b/src/core/download.h @@ -1,6 +1,7 @@ #ifndef RTORRENT_CORE_DOWNLOAD_H #define RTORRENT_CORE_DOWNLOAD_H +#include #include #include #include @@ -78,7 +79,8 @@ public: uint32_t resume_flags() { return m_resumeFlags; } void set_resume_flags(uint32_t flags) { m_resumeFlags = flags; } - void set_root_directory(const std::string& path); + void set_directory(const std::string& path); + void set_base_directory(const std::string& path); void set_throttle_name(const std::string& name); diff --git a/src/core/download_factory.cc b/src/core/download_factory.cc index a243066c..74c548dc 100644 --- a/src/core/download_factory.cc +++ b/src/core/download_factory.cc @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -39,6 +40,11 @@ static constexpr const char* session_invalid_message = "Session data is invalid, static std::unique_ptr download_factory_load_stream(const char* filename, bool* is_invalid) { + struct stat sb; + + if (stat(filename, &sb) != 0 || !S_ISREG(sb.st_mode)) + return std::unique_ptr(); + std::fstream stream(filename, std::ios::in | std::ios::binary); if (!stream.is_open()) @@ -167,11 +173,19 @@ void DownloadFactory::receive_success() { bool session_invalid = false; - auto rtorrent_object = download_factory_load_stream((expand_path(m_uri) + ".rtorrent").c_str(), &session_invalid); - auto libtorrent_resume_object = download_factory_load_stream((expand_path(m_uri) + ".libtorrent_resume").c_str(), &session_invalid); + std::unique_ptr rtorrent_object; + std::unique_ptr libtorrent_resume_object; - if (session_invalid) - lt_log_print(torrent::LOG_ERROR, "%s: %s", session_invalid_message, m_uri.c_str()); + if (m_session) { + if (m_uri.empty()) + throw torrent::input_error("Session torrent URI is empty."); + + rtorrent_object = download_factory_load_stream((expand_path(m_uri) + ".rtorrent").c_str(), &session_invalid); + libtorrent_resume_object = download_factory_load_stream((expand_path(m_uri) + ".libtorrent_resume").c_str(), &session_invalid); + + if (session_invalid) + lt_log_print(torrent::LOG_ERROR, "%s: %s", session_invalid_message, m_uri.c_str()); + } uint32_t tracker_key; diff --git a/src/core/download_list.cc b/src/core/download_list.cc index 79854c6a..b67f590a 100644 --- a/src/core/download_list.cc +++ b/src/core/download_list.cc @@ -37,7 +37,7 @@ namespace core { inline void DownloadList::check_contains([[maybe_unused]] Download* d) { #ifdef USE_EXTRA_DEBUG - if (std::find(begin(), end(), d) == end()) + if (std::find_if(begin(), end(), [d](const auto& entry) { return entry.get() == d; }) == end()) throw torrent::internal_error("DownloadList::check_contains(...) failed."); #endif } @@ -54,7 +54,6 @@ DownloadList::clear() { base_type::pop_back(); torrent::download_remove(*download->download()); - delete download; } catch (torrent::internal_error& e) { lt_log_print(torrent::LOG_ERROR, "DownloadList::clear() failed to close or remove download: %s", e.what()); @@ -70,7 +69,7 @@ DownloadList::clear() { void DownloadList::session_save() { for (auto& download : *this) - session_thread::manager()->save_resume_download(download); + session_thread::manager()->save_resume_download(download.get()); control->dht_manager()->save_dht_cache(); control->ui()->save_input_history(); @@ -78,7 +77,7 @@ DownloadList::session_save() { DownloadList::iterator DownloadList::find(const torrent::HashString& hash) { - return std::find_if(begin(), end(), [hash](Download* d) { return hash == d->info()->hash(); }); + return std::find_if(begin(), end(), [hash](const auto& d) { return hash == d->info()->hash(); }); } DownloadList::iterator @@ -91,14 +90,14 @@ DownloadList::find_hex(const char* hash) { if (torrent::utils::transform_from_hex(hash, hash + 40, key) != key.end()) return end(); - return std::find_if(begin(), end(), [key](Download* d) { return key == d->info()->hash(); }); + return std::find_if(begin(), end(), [key](const auto& d) { return key == d->info()->hash(); }); } Download* DownloadList::find_hex_ptr(const char* hash) { iterator itr = find_hex(hash); - return itr != end() ? *itr : NULL; + return itr != end() ? itr->get() : NULL; } Download* @@ -159,7 +158,7 @@ DownloadList::create(std::istream* str, uint32_t tracker_key, bool printLog) { DownloadList::iterator DownloadList::insert(Download* download) { - iterator itr = base_type::insert(end(), download); + iterator itr = base_type::insert(end(), std::shared_ptr(download)); lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Inserting download."); @@ -170,7 +169,7 @@ DownloadList::insert(Download* download) { // This needs to be separated into two different calls to ensure // the download remains in the view. for (auto v : *control->view_manager()) - v->insert(download); + v->insert(*itr); for (auto v : *control->view_manager()) v->filter_download(download); @@ -187,7 +186,7 @@ DownloadList::insert(Download* download) { void DownloadList::erase_ptr(Download* download) { - erase(std::find(begin(), end(), download)); + erase(std::find_if(begin(), end(), [download](const auto& entry) { return entry.get() == download; })); } DownloadList::iterator @@ -201,15 +200,14 @@ DownloadList::erase(iterator itr) { (*itr)->set_hash_failed(true); close(*itr); - session_thread::manager()->remove_download(*itr); + session_thread::manager()->remove_download(itr->get()); DL_TRIGGER_EVENT(*itr, "event.download.erased"); for (auto v : *control->view_manager()) - v->erase(*itr); + v->erase(itr->get()); torrent::download_remove(*(*itr)->download()); - delete *itr; return base_type::erase(itr); } @@ -256,9 +254,11 @@ DownloadList::close(Download* download) { } } +// Releases the files without changing the download's state, for callers that +// need the files closed and will keep using the download. void -DownloadList::close_directly(Download* download) { - lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Closing download directly."); +DownloadList::close_files(Download* download) { + lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Closing download files."); if (download->download()->info()->is_active()) { download->download()->stop(torrent::Download::stop_skip_tracker); @@ -271,6 +271,44 @@ DownloadList::close_directly(Download* download) { download->download()->close(); } +void +DownloadList::close_directly(Download* download) { + lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Closing download directly."); + + bool was_active = download->download()->info()->is_active(); + bool was_open = download->download()->info()->is_open(); + + close_files(download); + set_state_stopped(download); + + if (was_active) { + DL_TRIGGER_EVENT(download, "event.download.paused"); + update_paused_state(download); + } + + if (was_open) { + DL_TRIGGER_EVENT(download, "event.download.hash_removed"); + DL_TRIGGER_EVENT(download, "event.download.closed"); + } +} + +void +DownloadList::set_state_stopped(Download* download) { + control->view_manager()->find_ptr_throw("stopped")->set_visible(download); + rpc::call_command("d.state.set", (int64_t)0, rpc::make_target(download)); +} + +void +DownloadList::update_paused_state(Download* download) { + rpc::call_command("d.state_changed.set", torrent::this_thread::cached_seconds().count(), rpc::make_target(download)); + rpc::call_command("d.state_counter.set", rpc::call_command_value("d.state_counter", rpc::make_target(download)), rpc::make_target(download)); + + // If initial seeding is complete, don't try it again when restarting. + if (download->is_done() && + rpc::call_command("d.connection_current", torrent::Object(), rpc::make_target(download)).as_string() == "initial_seed") + rpc::call_command("d.connection_seed.set", rpc::call_command("d.connection_current", torrent::Object(), rpc::make_target(download)), rpc::make_target(download)); +} + void DownloadList::close_quick(Download* download) { lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Closing download quickly."); @@ -445,15 +483,7 @@ DownloadList::pause(Download* download, int flags) { // view. DL_TRIGGER_EVENT(download, "event.download.paused"); - auto cached_seconds = torrent::this_thread::cached_seconds().count(); - - rpc::call_command("d.state_changed.set", cached_seconds, rpc::make_target(download)); - rpc::call_command("d.state_counter.set", rpc::call_command_value("d.state_counter", rpc::make_target(download)), rpc::make_target(download)); - - // If initial seeding is complete, don't try it again when restarting. - if (download->is_done() && - rpc::call_command("d.connection_current", torrent::Object(), rpc::make_target(download)).as_string() == "initial_seed") - rpc::call_command("d.connection_seed.set", rpc::call_command("d.connection_current", torrent::Object(), rpc::make_target(download)), rpc::make_target(download)); + update_paused_state(download); // Save the state after all the slots, etc have been called so we // include the modifications they may make. @@ -524,7 +554,7 @@ DownloadList::hash_done(Download* download) { // If the download was previously completed but the files were // f.ex deleted, then we clear the state and complete. if (rpc::call_command_value("d.complete", rpc::make_target(download)) && !download->is_done()) { - rpc::call_command("d.state.set", (int64_t)0, rpc::make_target(download)); + set_state_stopped(download); download->set_message("Download registered as completed, but hash check returned unfinished chunks."); } diff --git a/src/core/download_list.h b/src/core/download_list.h index 40d24d83..c79bbbae 100644 --- a/src/core/download_list.h +++ b/src/core/download_list.h @@ -3,6 +3,7 @@ #include #include +#include #include namespace torrent { @@ -20,9 +21,9 @@ class Download; // // Fix apply_on_ratio if the base_type is changed. -class DownloadList : private std::list { +class DownloadList : private std::list> { public: - typedef std::list base_type; + typedef std::list> base_type; using base_type::iterator; using base_type::const_iterator; @@ -64,8 +65,17 @@ public: bool open(Download* d); void open_throw(Download* d); + // Overloads for the list's own entries, so callers iterating it do not need + // to unwrap. + bool open(const value_type& d) { return open(d.get()); } + void close(const value_type& d) { close(d.get()); } + void close_quick(const value_type& d) { close_quick(d.get()); } + void pause(const value_type& d) { pause(d.get()); } + void resume(const value_type& d) { resume(d.get()); } + void close(Download* d); void close_directly(Download* d); + void close_files(Download* d); void close_quick(Download* d); void close_throw(Download* d); @@ -123,6 +133,9 @@ private: void hash_done(Download* d); void hash_queue(Download* d, int type); + void set_state_stopped(Download* d); + void update_paused_state(Download* d); + inline void check_contains(Download* d); void received_finished(Download* d); diff --git a/src/core/manager.cc b/src/core/manager.cc index 340c0557..2e4d5f67 100644 --- a/src/core/manager.cc +++ b/src/core/manager.cc @@ -154,10 +154,10 @@ Manager::cleanup() { void Manager::shutdown(bool force) { if (!force) { - for (auto d : *m_download_list) - m_download_list->pause_default(d); + for (const auto& d : *m_download_list) + m_download_list->pause_default(d.get()); } else { - for (auto d : *m_download_list) + for (const auto& d : *m_download_list) m_download_list->close_quick(d); } } @@ -405,7 +405,7 @@ Manager::receive_hashing_changed() { continue; try { - m_download_list->open_throw(*itr); + m_download_list->open_throw(itr->get()); // Since the bitfield is allocated on loading of resume load or // hash start, and unallocated on close, we know that if it it diff --git a/src/core/view.cc b/src/core/view.cc index 4f5310ee..b5093867 100644 --- a/src/core/view.cc +++ b/src/core/view.cc @@ -16,10 +16,20 @@ namespace core { // Also add focus thingie here? +// Matches the list entry that owns a given download. +inline auto +entry_is(Download* download) { + return [download](const std::shared_ptr& entry) { return entry.get() == download; }; +} + struct view_downloads_compare { view_downloads_compare(const torrent::Object& cmd) : m_command(cmd) {} + bool operator()(const std::shared_ptr& d1, const std::shared_ptr& d2) const { + return (*this)(d1.get(), d2.get()); + } + bool operator()(Download* d1, Download* d2) const { try { if (m_command.is_empty()) @@ -54,6 +64,10 @@ struct view_downloads_filter { view_downloads_filter(const torrent::Object& cmd, const torrent::Object& cmd2) : m_command(cmd), m_command2(cmd2) {} + bool operator()(const std::shared_ptr& d1) const { + return (*this)(d1.get()); + } + bool operator()(Download* d1) const { return this->evalCmd(m_command, d1) && this->evalCmd(m_command2, d1); } @@ -152,7 +166,7 @@ View::initialize(const std::string& name) { void View::erase(Download* download) { - iterator itr = std::find(base_type::begin(), base_type::end(), download); + iterator itr = std::find_if(base_type::begin(), base_type::end(), entry_is(download)); if (itr >= end_visible()) { erase_internal(itr); @@ -165,22 +179,24 @@ View::erase(Download* download) { void View::set_visible(Download* download) { - iterator itr = std::find(begin_filtered(), end_filtered(), download); + iterator itr = std::find_if(begin_filtered(), end_filtered(), entry_is(download)); if (itr == end_filtered()) return; // Don't optimize erase since we want to keep the order of the // non-visible elements. + auto entry = *itr; + base_type::erase(itr); - insert_visible(download); + insert_visible(entry); rpc::call_object_nothrow(m_event_added, rpc::make_target(download)); } void View::set_not_visible(Download* download) { - iterator itr = std::find(begin_visible(), end_visible(), download); + iterator itr = std::find_if(begin_visible(), end_visible(), entry_is(download)); if (itr == end_visible()) return; @@ -190,8 +206,10 @@ View::set_not_visible(Download* download) { // Don't optimize erase since we want to keep the order of the // non-visible elements. + auto entry = *itr; + base_type::erase(itr); - base_type::push_back(download); + base_type::push_back(entry); rpc::call_object_nothrow(m_event_removed, rpc::make_target(download)); } @@ -241,12 +259,12 @@ View::prev_focus(unsigned int i) { void View::sort() { - Download* curFocus = focus() != end_visible() ? *focus() : NULL; + Download* curFocus = focus() != end_visible() ? focus()->get() : NULL; // Don't go randomly switching around equivalent elements. std::stable_sort(begin(), end_visible(), view_downloads_compare(m_sortCurrent)); - m_focus = position(std::find(begin(), end_visible(), curFocus)); + m_focus = position(std::find_if(begin(), end_visible(), entry_is(curFocus))); emit_changed(); } @@ -280,10 +298,10 @@ View::filter() { // set the elements to NULL as we trigger commands on them. Or // perhaps always clear them, thus not throwing anything. if (!m_event_removed.is_empty()) - std::for_each(changed.begin(), splitChanged, std::bind(&rpc::call_object_d_nothrow, m_event_removed, std::placeholders::_1)); + std::for_each(changed.begin(), splitChanged, [this](const auto& d) { rpc::call_object_d_nothrow(m_event_removed, d.get()); }); if (!m_event_added.is_empty()) - std::for_each(changed.begin(), splitChanged, std::bind(&rpc::call_object_d_nothrow, m_event_added, std::placeholders::_1)); + std::for_each(changed.begin(), splitChanged, [this](const auto& d) { rpc::call_object_d_nothrow(m_event_added, d.get()); }); emit_changed(); } @@ -294,21 +312,23 @@ View::filter_by(const torrent::Object& condition, View::base_type& result) { view_downloads_filter matches = view_downloads_filter(condition, m_temp_filter); for (iterator itr = begin_visible(); itr != end_visible(); ++itr) - if (matches(*itr)) + if (matches(itr->get())) result.push_back(*itr); } void View::filter_download(core::Download* download) { - iterator itr = std::find(base_type::begin(), base_type::end(), download); + iterator itr = std::find_if(base_type::begin(), base_type::end(), entry_is(download)); if (itr == base_type::end()) throw torrent::internal_error("View::filter_download(...) could not find download."); if (view_downloads_filter(m_filter, m_temp_filter)(download)) { if (itr >= end_visible()) { + auto entry = *itr; + erase_internal(itr); - insert_visible(download); + insert_visible(entry); rpc::call_object_nothrow(m_event_added, rpc::make_target(download)); @@ -317,16 +337,20 @@ View::filter_download(core::Download* download) { // already visible. // // Consider removing this. + auto entry = *itr; + erase_internal(itr); - insert_visible(download); + insert_visible(entry); } } else { if (itr >= end_visible()) return; + auto entry = *itr; + erase_internal(itr); - base_type::push_back(download); + base_type::push_back(entry); rpc::call_object_nothrow(m_event_removed, rpc::make_target(download)); } @@ -345,8 +369,8 @@ View::clear_filter_on() { } inline void -View::insert_visible(Download* d) { - auto itr = std::find_if(begin_visible(), end_visible(), [this, d](auto d2) { return view_downloads_compare(m_sortNew)(d, d2); }); +View::insert_visible(const std::shared_ptr& d) { + auto itr = std::find_if(begin_visible(), end_visible(), [this, &d](const auto& d2) { return view_downloads_compare(m_sortNew)(d.get(), d2.get()); }); m_size++; m_focus += (m_focus >= position(itr)); diff --git a/src/core/view.h b/src/core/view.h index abbf0bef..3ddd01f2 100644 --- a/src/core/view.h +++ b/src/core/view.h @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -26,9 +27,9 @@ namespace core { class Download; -class View : private std::vector { +class View : private std::vector> { public: - typedef std::vector base_type; + typedef std::vector> base_type; typedef std::function slot_void; typedef std::list signal_void; @@ -72,7 +73,7 @@ public: emit_changed(); } - void insert(Download* download) { base_type::push_back(download); } + void insert(const std::shared_ptr& download) { base_type::push_back(download); } void erase(Download* download); void set_visible(Download* download); @@ -124,9 +125,9 @@ private: View(const View&); void operator=(const View&); - void push_back(Download* d) { base_type::push_back(d); } + void push_back(const std::shared_ptr& d) { base_type::push_back(d); } - inline void insert_visible(Download* d); + inline void insert_visible(const std::shared_ptr& d); inline void erase_internal(iterator itr); void emit_changed(); diff --git a/src/display/window_download_list.cc b/src/display/window_download_list.cc index 39b5e067..9744e048 100644 --- a/src/display/window_download_list.cc +++ b/src/display/window_download_list.cc @@ -37,7 +37,7 @@ WindowDownloadList::set_view(core::View* l) { // Return a pair of ints, representing a) the ncurses attributes and b) the ncurses color pair ID to use std::pair WindowDownloadList::get_attr_color(core::View::iterator selected) { - core::Download* item = *selected; + core::Download* item = selected->get(); unsigned long focus_attr = selected == m_view->focus() ? m_canvas->attr_map().at(RCOLOR_FOCUS) : 0; int offset = (((selected - m_view->begin_visible()) & 1) + 1) * RCOLOR_MAX; // Determine the even/odd offset for the color pair bool active = item->is_open() && item->is_active(); @@ -132,15 +132,15 @@ WindowDownloadList::redraw() { ColorKind focus_color = is_focused ? RCOLOR_FOCUS : RCOLOR_LABEL; auto attr_color = get_attr_color(range.first); - print_download_title(buffer.data(), last, *range.first); + print_download_title(buffer.data(), last, range.first->get()); m_canvas->print(0, pos, "%c %s", focus_char, buffer.data()); m_canvas->set_attr(2, pos++, -1, attr_color.first, attr_color.second); - print_download_info_full(buffer.data(), last, *range.first); + print_download_info_full(buffer.data(), last, range.first->get()); m_canvas->print(0, pos, "%c %s", focus_char, buffer.data()); m_canvas->set_attr(2, pos++, -1, focus_color); - print_download_status(buffer.data(), last, *range.first); + print_download_status(buffer.data(), last, range.first->get()); m_canvas->print(0, pos, "%c %s", focus_char, buffer.data()); m_canvas->set_attr(2, pos++, -1, focus_color); @@ -152,7 +152,7 @@ WindowDownloadList::redraw() { char focus_char = range.first == m_view->focus() ? '*' : ' '; auto attr_color = get_attr_color(range.first); - print_download_info_compact(buffer.data(), last, *range.first); + print_download_info_compact(buffer.data(), last, range.first->get()); m_canvas->print(0, pos, "%c %s", focus_char, buffer.data()); m_canvas->set_attr(2, pos++, -1, attr_color.first, attr_color.second); diff --git a/src/main.cc b/src/main.cc index 4c06ba95..f5269217 100644 --- a/src/main.cc +++ b/src/main.cc @@ -378,6 +378,12 @@ main(int argc, char** argv) { lt_log_print(torrent::LOG_WARN, "network.max_open_files.set is deprecated, use system.sockets.files.min_alloc.set instead."); }); + // TODO: Keep d.directory_base for a while as it is widely used. + CMD_REDIRECT("d.directory_base", "d.directory"); + CMD_REDIRECT("d.directory_base.set", "d.directory.base.set"); + + rpc::rpc.mark_safe("d.directory_base"); + // if (rpc::call_command_value("method.use_intermediate") == 1) { // } else if (rpc::call_command_value("method.use_intermediate") == 2) { diff --git a/src/rpc/command.cc b/src/rpc/command.cc index 0bd7da79..26c45b3c 100644 --- a/src/rpc/command.cc +++ b/src/rpc/command.cc @@ -1,5 +1,7 @@ #include "config.h" +#include + #include "core/download.h" #include "parse.h" @@ -41,7 +43,13 @@ command_base_call_value_base(command_base* command_raw, target_type target, cons return command_base::_call::type, T>(command_raw, target, val); } - return command_base::_call::type, T>(command_raw, target, unit * arg.as_value()); + auto value = arg.as_value(); + + if (value > std::numeric_limits::max() / unit || + value < std::numeric_limits::min() / unit) + throw torrent::input_error("Value out of range."); + + return command_base::_call::type, T>(command_raw, target, unit * value); } template const torrent::Object diff --git a/src/rpc/command_map.h b/src/rpc/command_map.h index c67cb1fd..50472333 100644 --- a/src/rpc/command_map.h +++ b/src/rpc/command_map.h @@ -1,6 +1,7 @@ #ifndef RTORRENT_RPC_COMMAND_MAP_H #define RTORRENT_RPC_COMMAND_MAP_H +#include #include #include #include @@ -104,6 +105,10 @@ inline target_type make_target(T target) { return target_type((int)target_type_id::value, target); } +inline target_type make_target(const std::shared_ptr& target) { + return make_target(target.get()); +} + template inline target_type make_target_pair(T target1, T target2) { return target_type((int)target_type_id::value, target1, target2); diff --git a/src/rpc/jsonrpc.cc b/src/rpc/jsonrpc.cc index 77e68479..611c35aa 100644 --- a/src/rpc/jsonrpc.cc +++ b/src/rpc/jsonrpc.cc @@ -26,10 +26,12 @@ constexpr int JSONRPC_METHOD_NOT_FOUND_ERROR = -32601; constexpr int JSONRPC_INVALID_PARAMS_ERROR = -32602; constexpr int JSONRPC_INTERNAL_ERROR = -32000; +constexpr uint32_t max_json_depth = 1024; + using json = nlohmann::json; torrent::Object -json_to_object(const json& value) { +json_to_object(const json& value, uint32_t depth = 0) { switch (value.type()) { case json::value_t::number_unsigned: case json::value_t::number_integer: @@ -42,20 +44,26 @@ json_to_object(const json& value) { return torrent::Object(value.get()); case json::value_t::array: { + if (++depth >= max_json_depth) + throw torrent::input_error("invalid parameters: max depth reached"); + auto array_raw = torrent::Object::create_list(); auto& array = array_raw.as_list(); for (const auto& entry : value) - array.push_back(json_to_object(entry)); + array.push_back(json_to_object(entry, depth)); return array_raw; } case json::value_t::object: { + if (++depth >= max_json_depth) + throw torrent::input_error("invalid parameters: max depth reached"); + auto map_raw = torrent::Object::create_map(); auto& map = map_raw.as_map(); for (const auto& entry : value.items()) - map[entry.key()] = json_to_object(entry.value()); + map[entry.key()] = json_to_object(entry.value(), depth); return map_raw; } @@ -138,7 +146,7 @@ jsonrpc_call_command(const std::string& method, const json& params) { CommandMap::iterator itr = commands.find(method.c_str()); - if (itr == commands.end()) { + if (itr == commands.end() || !(itr->second.m_flags & CommandMap::flag_public_rpc)) { throw rpc_error(JSONRPC_METHOD_NOT_FOUND_ERROR, "method not found: " + method); } diff --git a/src/rpc/parse.cc b/src/rpc/parse.cc index 3953fa84..e49dde66 100644 --- a/src/rpc/parse.cc +++ b/src/rpc/parse.cc @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -10,6 +11,8 @@ namespace rpc { +constexpr uint32_t max_parse_depth = 1024; + const char* parse_skip_wspace(const char* first, const char* last) { while (first != last && parse_is_space(*first)) @@ -99,6 +102,12 @@ parse_whole_value_nothrow(const char* src, int64_t* value, int base, int unit) { return true; } +static bool +value_fits_shifted(int64_t value, int shift) { + return value <= (std::numeric_limits::max() >> shift) && + value >= (std::numeric_limits::min() >> shift); +} + const char* parse_value_nothrow(const char* src, int64_t* value, int base, int unit) { if (unit <= 0) @@ -121,20 +130,26 @@ parse_value_nothrow(const char* src, int64_t* value, int base, int unit) { case 'B': ++last; break; case 'k': case 'K': - if (*value > (int64_t)0x1FFFFFFFFFFFFF) return src; // overflow guard + if (!value_fits_shifted(*value, 10)) return src; // overflow guard *value = *value << 10; ++last; break; case 'm': case 'M': - if (*value > (int64_t)0x7FFFFFFFFFF) return src; // overflow guard + if (!value_fits_shifted(*value, 20)) return src; // overflow guard *value = *value << 20; ++last; break; case 'g': case 'G': - if (*value > (int64_t)0x1FFFFFFFF) return src; // overflow guard + if (!value_fits_shifted(*value, 30)) return src; // overflow guard *value = *value << 30; ++last; break; // case ' ': // case '\0': *value = *value * unit; break; // default: throw torrent::input_error("Could not parse value."); - default: *value = *value * unit; break; + default: + if (*value > std::numeric_limits::max() / unit || + *value < std::numeric_limits::min() / unit) + return src; // overflow guard + + *value = *value * unit; + break; } return last; @@ -142,10 +157,13 @@ parse_value_nothrow(const char* src, int64_t* value, int base, int unit) { // Somewhat ugly... const char* -parse_object(const char* first, const char* last, torrent::Object* dest, bool (*delim)(const char)) { +parse_object(const char* first, const char* last, torrent::Object* dest, bool (*delim)(const char), uint32_t depth) { + if (++depth >= max_parse_depth) + throw torrent::input_error("Max parse depth reached."); + if (*first == '{') { *dest = torrent::Object::create_list(); - first = parse_list(first + 1, last, dest, &parse_is_delim_block); + first = parse_list(first + 1, last, dest, &parse_is_delim_block, depth); first = parse_skip_wspace(first, last); if (first == last || *first != '}') @@ -154,18 +172,18 @@ parse_object(const char* first, const char* last, torrent::Object* dest, bool (* return ++first; } else if (*first == '(') { - int32_t depth = 1; + int32_t parentheses = 1; while (first + 1 != last && *(first + 1) == '(') { first++; - depth++; + parentheses++; } - if (depth > 3) + if (parentheses > 3) throw torrent::input_error("Max 3 parentheses per object allowed."); *dest = torrent::Object::create_dict_key(); - dest->set_flags(torrent::Object::flag_function << (depth - 1)); + dest->set_flags(torrent::Object::flag_function << (parentheses - 1)); first = parse_string(first + 1, last, &dest->as_dict_key(), &parse_is_delim_func); first = parse_skip_wspace(first, last); @@ -176,16 +194,16 @@ parse_object(const char* first, const char* last, torrent::Object* dest, bool (* if (*first == ',') { // This will always create a list even for single argument functions... dest->as_dict_obj() = torrent::Object::create_list(); - first = parse_list(first + 1, last, &dest->as_dict_obj(), &parse_is_delim_func); + first = parse_list(first + 1, last, &dest->as_dict_obj(), &parse_is_delim_func, depth); first = parse_skip_wspace(first, last); } - while (depth != 0 && first != last && *first == ')') { + while (parentheses != 0 && first != last && *first == ')') { first++; - depth--; + parentheses--; } - if (depth != 0) + if (parentheses != 0) throw torrent::input_error("Parentheses mismatch."); return first; @@ -198,7 +216,7 @@ parse_object(const char* first, const char* last, torrent::Object* dest, bool (* } const char* -parse_list(const char* first, const char* last, torrent::Object* dest, bool (*delim)(const char)) { +parse_list(const char* first, const char* last, torrent::Object* dest, bool (*delim)(const char), uint32_t depth) { if (!dest->is_list()) throw torrent::internal_error("parse_list(...) !dest->is_list()."); @@ -206,7 +224,7 @@ parse_list(const char* first, const char* last, torrent::Object* dest, bool (*de torrent::Object tmp; first = parse_skip_wspace(first, last); - first = parse_object(first, last, &tmp, delim); + first = parse_object(first, last, &tmp, delim, depth); first = parse_skip_wspace(first, last); dest->as_list().push_back(tmp); @@ -221,9 +239,9 @@ parse_list(const char* first, const char* last, torrent::Object* dest, bool (*de } const char* -parse_whole_list(const char* first, const char* last, torrent::Object* dest, bool (*delim)(const char)) { +parse_whole_list(const char* first, const char* last, torrent::Object* dest, bool (*delim)(const char), uint32_t depth) { first = parse_skip_wspace(first, last); - first = parse_object(first, last, dest, delim); + first = parse_object(first, last, dest, delim, depth); first = parse_skip_wspace(first, last); if (first != last && parse_is_seperator(*first)) { @@ -231,7 +249,7 @@ parse_whole_list(const char* first, const char* last, torrent::Object* dest, boo tmp.swap(*dest); dest->as_list().push_back(tmp); - first = parse_list(++first, last, dest, delim); + first = parse_list(++first, last, dest, delim, depth); } return first; diff --git a/src/rpc/parse.h b/src/rpc/parse.h index 4af4b7c1..a7b8f422 100644 --- a/src/rpc/parse.h +++ b/src/rpc/parse.h @@ -77,9 +77,9 @@ const char* parse_value_nothrow(const char* first, const char* last, int64_t* va void parse_whole_value(const char* src, int64_t* value, int base = 0, int unit = 1); bool parse_whole_value_nothrow(const char* src, int64_t* value, int base = 0, int unit = 1); -const char* parse_object (const char* first, const char* last, torrent::Object* dest, bool (*delim)(const char) = &parse_is_delim_default); -const char* parse_list (const char* first, const char* last, torrent::Object* dest, bool (*delim)(const char) = &parse_is_delim_default); -const char* parse_whole_list(const char* first, const char* last, torrent::Object* dest, bool (*delim)(const char) = &parse_is_delim_default); +const char* parse_object (const char* first, const char* last, torrent::Object* dest, bool (*delim)(const char) = &parse_is_delim_default, uint32_t depth = 0); +const char* parse_list (const char* first, const char* last, torrent::Object* dest, bool (*delim)(const char) = &parse_is_delim_default, uint32_t depth = 0); +const char* parse_whole_list(const char* first, const char* last, torrent::Object* dest, bool (*delim)(const char) = &parse_is_delim_default, uint32_t depth = 0); std::string convert_to_string(const torrent::Object& src); diff --git a/src/rpc/rpc_manager.cc b/src/rpc/rpc_manager.cc index 6e9cd5c5..39faf7a1 100644 --- a/src/rpc/rpc_manager.cc +++ b/src/rpc/rpc_manager.cc @@ -167,6 +167,14 @@ RpcManager::cleanup() { m_jsonrpc.cleanup(); } +void +RpcManager::set_size_limit(uint64_t size) { + if (size > SCgiTask::max_content_size) + throw torrent::input_error("XMLRPC size limit cannot exceed the SCGI content size limit."); + + m_xmlrpc.set_size_limit(size); +} + void RpcManager::insert_command(const char* name, const char* parm, const char* doc) { m_xmlrpc.insert_command(name, parm, doc); diff --git a/src/rpc/rpc_manager.h b/src/rpc/rpc_manager.h index beb99647..e9b84b8c 100644 --- a/src/rpc/rpc_manager.h +++ b/src/rpc/rpc_manager.h @@ -62,7 +62,7 @@ public: void cleanup(); int64_t size_limit() { return m_xmlrpc.size_limit(); }; - void set_size_limit(uint64_t size) { m_xmlrpc.set_size_limit(size); }; + void set_size_limit(uint64_t size); int dialect() { return m_xmlrpc.dialect(); } void set_dialect(int dialect) { m_xmlrpc.set_dialect(dialect); } diff --git a/src/rpc/scgi_task.cc b/src/rpc/scgi_task.cc index 1f2a48dd..89633aff 100644 --- a/src/rpc/scgi_task.cc +++ b/src/rpc/scgi_task.cc @@ -14,6 +14,7 @@ #include #include #include +#include #include #include "control.h" @@ -27,6 +28,8 @@ namespace rpc { SCgiTask::SCgiTask() : m_callback_id(torrent::system::make_callback_id()) { + m_task_timeout.slot() = [this]() { close(); }; + reset_file_descriptor(); } @@ -49,6 +52,8 @@ SCgiTask::open(SCgi* parent, int fd) { torrent::this_thread::poll()->open_and_insert_read(this); + torrent::this_thread::scheduler()->update_wait_for_ceil_seconds(&m_task_timeout, timeout_request); + auto lock = std::lock_guard(m_result_mutex); // Leave room for terminating nul byte for parsing the header. @@ -60,6 +65,8 @@ SCgiTask::cancel_open() { if (!is_open()) return; + torrent::this_thread::scheduler()->erase(&m_task_timeout); + torrent::this_thread::poll()->remove_and_close(this); torrent::fd_close(file_descriptor()); @@ -71,6 +78,8 @@ SCgiTask::close() { if (!is_open()) return; + torrent::this_thread::scheduler()->erase(&m_task_timeout); + torrent::system::cancel_callback_and_wait(m_callback_id, scgi_thread::thread(), torrent::main_thread::thread()); torrent::runtime::socket_manager()->close_event_or_throw(this, [this]() { @@ -157,6 +166,8 @@ SCgiTask::event_read() { torrent::this_thread::poll()->remove_read(this); + torrent::this_thread::scheduler()->update_wait_for_ceil_seconds(&m_task_timeout, timeout_request); + if (m_parent->log_fd() >= 0) { [[maybe_unused]] int result; diff --git a/src/rpc/scgi_task.h b/src/rpc/scgi_task.h index bffe8101..22f93787 100644 --- a/src/rpc/scgi_task.h +++ b/src/rpc/scgi_task.h @@ -1,10 +1,12 @@ #ifndef RTORRENT_RPC_SCGI_TASK_H #define RTORRENT_RPC_SCGI_TASK_H +#include #include #include #include #include +#include namespace rpc { @@ -14,7 +16,9 @@ class SCgiTask : public torrent::system::Event { public: static constexpr int default_buffer_size = 8191; static constexpr int max_header_size = 2000; - static constexpr int max_content_size = (2 << 23); + static constexpr int max_content_size = (1 << 26); + + static constexpr auto timeout_request = std::chrono::seconds(60); enum ContentType { XML, JSON }; @@ -54,8 +58,9 @@ private: void plaintext_response(const char* buffer, uint32_t content_length); void gzip_response(const char* buffer, uint32_t content_length); - SCgi* m_parent{}; - torrent::system::callback_id m_callback_id; + SCgi* m_parent{}; + torrent::system::callback_id m_callback_id; + torrent::system::SchedulerEntry m_task_timeout; std::mutex m_result_mutex; diff --git a/src/rpc/xmlrpc_c.cc b/src/rpc/xmlrpc_c.cc index ab04166d..48b05ad9 100644 --- a/src/rpc/xmlrpc_c.cc +++ b/src/rpc/xmlrpc_c.cc @@ -77,16 +77,18 @@ xmlrpc_list_entry_to_value(xmlrpc_env* env, xmlrpc_value* src, int index) { { const char* str; xmlrpc_read_string(env, tmp, &str); + xmlrpc_DECREF(tmp); if (env->fault_occurred) throw xmlrpc_error_c(env); const char* end = str; int64_t v3 = ::strtoll(str, (char**)&end, 0); + bool invalid = *str == '\0' || *end != '\0'; ::free((void*)str); - if (*str == '\0' || *end != '\0') + if (invalid) throw xmlrpc_error_c(XMLRPC_TYPE_ERROR, "Invalid index."); return v3; @@ -529,9 +531,6 @@ XmlRpc::size_limit() { void XmlRpc::set_size_limit(uint64_t size) { - if (size >= (64 << 20)) - throw torrent::input_error("Invalid XMLRPC limit size."); - xmlrpc_limit_set(XMLRPC_XML_SIZE_LIMIT_ID, size); } diff --git a/src/session/download_storer.cc b/src/session/download_storer.cc index 1af2f1cd..1464a906 100644 --- a/src/session/download_storer.cc +++ b/src/session/download_storer.cc @@ -127,8 +127,12 @@ save_stream(const std::string& path, bool use_fsyncdisk, const std::stringstream if (!output.good()) throw torrent::storage_error("failed to write stream to file : " + path); + // The data only reaches the kernel here, so this is where a full disk is seen. output.close(); + if (!output.good()) + throw torrent::storage_error("failed to flush stream to file : " + path); + // Ensure that the new file is actually written to the disk int fd = ::open(path.c_str(), O_WRONLY); @@ -137,13 +141,19 @@ save_stream(const std::string& path, bool use_fsyncdisk, const std::stringstream if (use_fsyncdisk) { #ifdef __APPLE__ - ::fsync(fd); + int sync_result = ::fsync(fd); #else - ::fdatasync(fd); + int sync_result = ::fdatasync(fd); #endif + + if (sync_result == -1) { + ::close(fd); + throw torrent::storage_error("failed to sync file to disk : " + path); + } } - ::close(fd); + if (::close(fd) == -1) + throw torrent::storage_error("failed to close file descriptor : " + path); } } // namespace anonymous diff --git a/src/session/session_manager.h b/src/session/session_manager.h index eff135ac..5a135552 100644 --- a/src/session/session_manager.h +++ b/src/session/session_manager.h @@ -126,7 +126,7 @@ private: inline bool SessionManager::is_used() const { return !m_path.empty(); } inline std::string SessionManager::path() const { return m_path; } -inline bool SessionManager::use_fsyncdisk() const { return true; } +inline bool SessionManager::use_fsyncdisk() const { return m_use_fsyncdisk; } inline bool SessionManager::use_lock() const { return m_use_lock; } inline void SessionManager::flush_all_pending_builds() { process_pending_builds(true); } diff --git a/src/setup.h b/src/setup.h index bd009135..3eb5df8f 100644 --- a/src/setup.h +++ b/src/setup.h @@ -3,6 +3,7 @@ #include #include +#include int parse_main_options(int argc, char** argv); void parse_config_file(int argc, char** argv, std::function parse_fn); diff --git a/src/ui/download.cc b/src/ui/download.cc index 9f8de921..de935da2 100644 --- a/src/ui/download.cc +++ b/src/ui/download.cc @@ -1,3 +1,4 @@ +#include #include "config.h" #include @@ -30,8 +31,10 @@ namespace ui { -Download::Download(core::Download* d) - : m_download(d) { +Download::Download(const std::shared_ptr& download) + : m_download(download.get()) { + + auto* d = download.get(); m_windowDownloadStatus = std::make_unique(d); m_windowDownloadStatus->set_bottom(true); diff --git a/src/ui/download.h b/src/ui/download.h index 2654a410..bb7a39ed 100644 --- a/src/ui/download.h +++ b/src/ui/download.h @@ -2,6 +2,7 @@ #define RTORRENT_UI_DOWNLOAD_H #include +#include #include #include "display/manager.h" @@ -36,7 +37,7 @@ public: DISPLAY_MAX_SIZE } Display; - Download(core::Download* d); + Download(const std::shared_ptr& d); ~Download(); void activate(display::Frame* frame, bool focus = true); diff --git a/src/ui/download_list.cc b/src/ui/download_list.cc index c9aca0ff..17c9eb97 100644 --- a/src/ui/download_list.cc +++ b/src/ui/download_list.cc @@ -89,7 +89,7 @@ DownloadList::unfocus_download(core::Download* d) { if (m_state == DISPLAY_DOWNLOAD && d == static_cast(m_uiArray[DISPLAY_DOWNLOAD])->download()) activate_display(DISPLAY_DOWNLOAD_LIST); - if (current_view()->focus() < current_view()->end_visible() && *current_view()->focus() == d) + if (current_view()->focus() < current_view()->end_visible() && current_view()->focus()->get() == d) current_view()->next_focus(); } diff --git a/src/ui/element_download_list.cc b/src/ui/element_download_list.cc index 11ecbc71..9f651fd6 100644 --- a/src/ui/element_download_list.cc +++ b/src/ui/element_download_list.cc @@ -196,7 +196,7 @@ ElementDownloadList::receive_cycle_throttle() { if (m_view->focus() == m_view->end_visible()) return; - core::Download* download = *m_view->focus(); + core::Download* download = m_view->focus()->get(); if (download->is_active()) { lt_log_print(torrent::LOG_TORRENT_WARN, "Cannot change throttle on active download."); return; diff --git a/src/utils/gzip.cc b/src/utils/gzip.cc index b0a416f0..3c01ad2b 100644 --- a/src/utils/gzip.cc +++ b/src/utils/gzip.cc @@ -5,6 +5,8 @@ #include #include +#include "utils/functional.h" + namespace utils { void @@ -21,6 +23,8 @@ gzip_compress_to_vector(const char* buffer, unsigned int length, std::vector +#include namespace utils { diff --git a/test/Makefile.am b/test/Makefile.am index 550051f8..5432dcb5 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -51,8 +51,10 @@ rtorrent_Test_Rpc_SOURCES = $(rtorrent_Test_Common) \ rtorrent_Test_Src_SOURCES = $(rtorrent_Test_Common) \ src/test_command_dynamic.cc \ src/test_command_dynamic.h \ - src/test_command_local.cc \ - src/test_command_local.h \ + src/test_command_ip.cc \ + src/test_command_ip.h \ + src/test_command_system.cc \ + src/test_command_system.h \ src/test_command_path.cc \ src/test_command_path.h \ src/test_command_string.cc \ diff --git a/test/src/test_command_ip.cc b/test/src/test_command_ip.cc new file mode 100644 index 00000000..9b34f05f --- /dev/null +++ b/test/src/test_command_ip.cc @@ -0,0 +1,55 @@ +#include "config.h" + +#include "test/src/test_command_ip.h" + +#include + +CPPUNIT_TEST_SUITE_REGISTRATION(TestCommandIp); + +bool ipv4_range_parse(const char* address, uint32_t* address_start, uint32_t* address_end); + +static uint32_t +ipv4(uint32_t a, uint32_t b, uint32_t c, uint32_t d) { + return (a << 24) | (b << 16) | (c << 8) | d; +} + +#define RANGE_ASSERT(address, expected_start, expected_end) \ + { \ + uint32_t start = 0; \ + uint32_t end = 0; \ + \ + CPPUNIT_ASSERT(ipv4_range_parse(address, &start, &end)); \ + CPPUNIT_ASSERT_EQUAL(expected_start, start); \ + CPPUNIT_ASSERT_EQUAL(expected_end, end); \ + } + +void +TestCommandIp::test_single_address() { + RANGE_ASSERT("10.1.2.3", ipv4(10, 1, 2, 3), ipv4(10, 1, 2, 3)); +} + +void +TestCommandIp::test_explicit_range() { + RANGE_ASSERT("10.1.2.3-10.1.2.9", ipv4(10, 1, 2, 3), ipv4(10, 1, 2, 9)); +} + +void +TestCommandIp::test_cidr() { + RANGE_ASSERT("10.0.0.0/8", ipv4(10, 0, 0, 0), ipv4(10, 255, 255, 255)); + RANGE_ASSERT("10.1.2.0/24", ipv4(10, 1, 2, 0), ipv4(10, 1, 2, 255)); + RANGE_ASSERT("10.1.2.128/25", ipv4(10, 1, 2, 128), ipv4(10, 1, 2, 255)); + RANGE_ASSERT("10.1.2.3/31", ipv4(10, 1, 2, 2), ipv4(10, 1, 2, 3)); +} + +void +TestCommandIp::test_cidr_zero_mask() { + RANGE_ASSERT("0.0.0.0/0", ipv4(0, 0, 0, 0), ipv4(255, 255, 255, 255)); + RANGE_ASSERT("10.1.2.3/0", ipv4(0, 0, 0, 0), ipv4(255, 255, 255, 255)); +} + +void +TestCommandIp::test_cidr_full_mask() { + RANGE_ASSERT("10.1.2.3/32", ipv4(10, 1, 2, 3), ipv4(10, 1, 2, 3)); + RANGE_ASSERT("0.0.0.0/32", ipv4(0, 0, 0, 0), ipv4(0, 0, 0, 0)); + RANGE_ASSERT("255.255.255.255/32", ipv4(255, 255, 255, 255), ipv4(255, 255, 255, 255)); +} diff --git a/test/src/test_command_ip.h b/test/src/test_command_ip.h new file mode 100644 index 00000000..9d45a71b --- /dev/null +++ b/test/src/test_command_ip.h @@ -0,0 +1,25 @@ +#include +#include + +// ipv4_range_parse is a pure function, so this does not use test_fixture and +// the mock and logging setup that comes with it. +class TestCommandIp : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(TestCommandIp); + + CPPUNIT_TEST(test_single_address); + CPPUNIT_TEST(test_explicit_range); + + CPPUNIT_TEST(test_cidr); + CPPUNIT_TEST(test_cidr_zero_mask); + CPPUNIT_TEST(test_cidr_full_mask); + + CPPUNIT_TEST_SUITE_END(); + +public: + void test_single_address(); + void test_explicit_range(); + + void test_cidr(); + void test_cidr_zero_mask(); + void test_cidr_full_mask(); +}; diff --git a/test/src/test_command_local.cc b/test/src/test_command_system.cc similarity index 83% rename from test/src/test_command_local.cc rename to test/src/test_command_system.cc index bde49046..2ddf937f 100644 --- a/test/src/test_command_local.cc +++ b/test/src/test_command_system.cc @@ -1,6 +1,6 @@ #include "config.h" -#include "test/src/test_command_local.h" +#include "test/src/test_command_system.h" #include #include @@ -10,12 +10,12 @@ #include "globals.h" #include "rpc/parse_commands.h" -CPPUNIT_TEST_SUITE_REGISTRATION(TestCommandLocal); +CPPUNIT_TEST_SUITE_REGISTRATION(TestCommandSystem); -void initialize_command_local(); +void initialize_command_system(); void -TestCommandLocal::setUp() { +TestCommandSystem::setUp() { torrent::initialize_main_thread(); torrent::initialize(); @@ -23,16 +23,16 @@ TestCommandLocal::setUp() { control = new Control; if (!rpc::commands.has("system.sockets.size")) - initialize_command_local(); + initialize_command_system(); } void -TestCommandLocal::tearDown() { +TestCommandSystem::tearDown() { torrent::cleanup(); } void -TestCommandLocal::test_socket_category_commands() { +TestCommandSystem::test_socket_category_commands() { for (uint32_t i = 0; i < torrent::runtime::SocketManager::category_count; ++i) { auto category = static_cast(i); auto name = "system.sockets." + torrent::option_to_str_or_throw(torrent::OPTION_SOCKET_CATEGORY, i); diff --git a/test/src/test_command_local.h b/test/src/test_command_system.h similarity index 69% rename from test/src/test_command_local.h rename to test/src/test_command_system.h index 652febfc..7dfd7d2b 100644 --- a/test/src/test_command_local.h +++ b/test/src/test_command_system.h @@ -1,7 +1,7 @@ #include "test/helpers/test_fixture.h" -class TestCommandLocal : public test_fixture { - CPPUNIT_TEST_SUITE(TestCommandLocal); +class TestCommandSystem : public test_fixture { + CPPUNIT_TEST_SUITE(TestCommandSystem); CPPUNIT_TEST(test_socket_category_commands);