Compare commits

...

35 Commits

Author SHA1 Message Date
rakshasa f9b9629235 Merge branch 'master' into feature/ipc-worker 2026-09-15 14:40:49 +02:00
noctuum 282c86597a Fix a use after free in the xmlrpc-c index path
The string was freed before it was tested, and tmp was never released.
2026-09-14 11:32:11 +02:00
noctuum c41e63248a Check the flush and sync on session file writes
good() was checked before close(), where the data is actually flushed.
2026-09-14 11:10:12 +02:00
noctuum 641626029a Fix a one byte write past address_copy
The index bound was the buffer size, not the last writable index.
2026-09-14 10:53:12 +02:00
noctuum 0c507c581f Release the zlib deflate state in gzip_compress_to_vector.
deflateEnd was never called, leaking 168 kB per compressed response.
2026-09-13 10:43:16 +02:00
noctuum af0b600b54 Add unit tests for ipv4_range_parse.
Covers single addresses, explicit ranges and the cidr mask boundaries.
2026-09-13 10:05:39 +02:00
noctuum c984a69fd1 Fix cidr /32 ip filter entries covering everything above the address.
Shifting end_mask by the full type width is undefined behavior.
2026-09-13 10:05:39 +02:00
Jakob Breivik Grimstveit 20002ce937 Fix false invalid session error on empty URI
Avoid triggering 'Session data is invalid' when loading downloads with an empty URI.
2026-09-11 12:05:59 +02:00
rakshasa 3caa31113d Bumped scgi max content length to 64mb. 2026-09-06 14:50:19 +02:00
Nicolas PARLANT 0373d227c1 add missing headers for libcxx-23
Signed-off-by: Nicolas PARLANT <nicolas.parlant@parhuet.fr>
2026-09-03 09:59:12 +02:00
rakshasa a8be7e443a Tagged release 0.16.22. 2026-09-02 09:39:57 +02:00
aeiouaeiouaeiouaeiouaeiouaeiou f327523a07 gzip.h: fix build with Clang <17
Signed-off-by: aeiouaeiouaeiouaeiouaeiouaeiou <aeioudev@outlook.com>
2026-08-30 11:18:25 +02:00
Jari Sundell 2804be2c9c Optionally sanitize download and file names by replacing slash with underscore. 2026-08-29 18:31:16 +09:00
noctuum 21019fde41 Add the download to the stopped view when a hash check contradicts d.complete.
Setting d.state alone left it in the started view, where d.start cannot restore it.
2026-08-28 09:31:55 +02:00
noctuum e4e858cc40 Stop the download in close_directly() rather than leaving the state behind.
It closed the files while d.state and the started view still called it running.
2026-08-28 09:31:55 +02:00
noctuum 60eb8f0383 Split the file closing in DownloadList::close_directly() into close_files().
Callers that only need the files released should not depend on how a stop behaves.
2026-08-28 09:31:55 +02:00
Jari Sundell 072cbae0c6 Expose system.files.close_idle for FileManager idle FD timeout by @j-c-m 2026-08-28 03:18:25 +09:00
xirvik 16bfaf2694 Reject an XMLRPC size limit above the SCGI content size limit. 2026-08-27 10:48:26 +02:00
xirvik c8da93bd30 Warn that pieces.sync.timeout_safe.set is no longer supported. 2026-08-26 11:21:14 +02:00
xirvik 2cd3ca052d Pass the gz flag to log.append_gz_file. 2026-08-26 10:54:22 +02:00
xirvik 8be1fcb3a4 Return m_use_fsyncdisk from SessionManager::use_fsyncdisk.
system.files.session.fdatasync always reported true even when disabled.
2026-08-26 10:32:12 +02:00
rakshasa 109a20c09c Tagged release 0.16.21. 2026-08-26 08:51:40 +02:00
xirvik ac1b2685d5 Guard the value suffixes against negative overflow.
The k, m and g guards only checked the positive side, so -4611686018427387904K became 0.
2026-08-24 08:57:58 +02:00
xirvik d7b2cfe4f2 Clamp the elapsed time conversion instead of overflowing.
convert.elapsed_time subtracted an unchecked value from the cached seconds.
2026-08-24 08:24:20 +02:00
xirvik dcfbf82a38 Mark view.list and the system.time getters as untrusted safe.
Read-only getters a client needs to list views and make sense of timestamps.
2026-08-24 07:37:23 +02:00
xirvik 3ae91b6536 Check flag_public_rpc when dispatching jsonrpc calls.
Private commands and non-exported redirects were reachable over jsonrpc.
2026-08-19 19:11:53 +02:00
xirvik af6d8a10ad Add a timeout to scgi tasks.
A hundred connections sending a partial header held the whole task pool.
2026-08-18 12:25:58 +02:00
xirvik 9066afc063 Guard the unit multiplication in value commands against overflow.
The kb variants multiply the argument by 1024 without checking the range.
2026-08-18 11:58:47 +02:00
xirvik a2fabb10de Range check the socket allocation and size values.
int64 arguments were truncated into uint32 setters, so -1 became 4294967295.
2026-08-18 11:38:01 +02:00
xirvik 13e073d9ff Reject division overflow in math.div and math.mod.
Dividing the smallest integer by -1 traps and takes the process with it.
2026-08-18 11:12:33 +02:00
xirvik d73f245692 Add a depth limit to the command parser.
A million nested braces in one argument exhausted the main thread's stack.
2026-08-18 10:52:38 +02:00
xirvik 7db356cae4 Add a depth limit to json_to_object.
Deeply nested params recursed until the main thread's stack was exhausted.
2026-08-18 10:34:07 +02:00
Auska 8378c0a83a Fix review: mark ipv4/ipv6 local_port safe, dedup port validation
- mark network.local_port.ipv4/ipv6 safe for untrusted RPC connections (mark_safe matches exact command keys).
- Replace duplicated multi-line port validation with a shared checked_local_port_value helper and single-line lambdas.
2026-08-17 11:29:50 +02:00
Auska 4c6f7afdc7 Add network.local_port commands for tracker port reporting
Mirror the network.local_address.* command family for ports:
- network.local_port            report the best-match local port
- network.local_port.set        set both the ipv4 and ipv6 local ports
- network.local_port.ipv4(.set) get/set the ipv4 local port only
- network.local_port.ipv6(.set) get/set the ipv6 local port only

Values are restricted to 0-65535; 0 (default) means unset, and trackers
then report the listening port.

Why: behind multiple layers of NAT the port peers must connect to can
differ from the port rtorrent is listening on. These commands let the
user manually advertise the forwarded public port per address family,
e.g.:
    network.local_port.ipv4.set = 6881
    network.local_port.ipv6.set = 6882

network.local_port is marked safe for RPC use alongside
network.local_address.
2026-08-17 11:29:50 +02:00
xirvik 385e149ccc Let the download list own its downloads through a shared pointer.
A multicall could then dispatch commands on an erased download.
2026-08-17 10:40:41 +02:00
48 changed files with 667 additions and 326 deletions
+3 -3
View File
@@ -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
+8
View File
@@ -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.
#
+1
View File
@@ -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 \
+6 -18
View File
@@ -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");
+23 -5
View File
@@ -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<core::Download*> 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);
}
+2
View File
@@ -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();
}
+4 -1
View File
@@ -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;
+8 -174
View File
@@ -1,33 +1,16 @@
#include "config.h"
#include <cerrno>
#include <fcntl.h>
#include <functional>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <torrent/torrent.h>
#include <torrent/data/file_manager.h>
#include <torrent/data/chunk_utils.h>
#include <torrent/runtime/runtime.h>
#include <torrent/runtime/memory_manager.h>
#include <torrent/runtime/socket_manager.h>
#include <torrent/utils/chrono.h>
#include <torrent/utils/option_strings.h>
#include <torrent/utils/log.h>
#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<torrent::runtime::socket_manager_category_t>(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");
+1 -1
View File
@@ -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));
+18
View File
@@ -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<uint16_t>(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");
+195
View File
@@ -0,0 +1,195 @@
#include "config.h"
#include <unistd.h>
#include <sys/stat.h>
#include <torrent/data/file_manager.h>
#include <torrent/runtime/client_config.h>
#include <torrent/runtime/memory_manager.h>
#include <torrent/runtime/socket_manager.h>
#include <torrent/runtime/runtime.h>
#include <torrent/utils/chrono.h>
#include <torrent/utils/option_strings.h>
#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<uint32_t>::max())
throw torrent::input_error(std::string("Invalid ") + label + " value.");
return static_cast<uint32_t>(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<torrent::runtime::socket_manager_category_t>(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");
}
}
+2 -2
View File
@@ -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(); });
}
+11 -3
View File
@@ -3,6 +3,7 @@
#include <sys/types.h>
#include <ctime>
#include <limits>
#include <regex>
#include <torrent/utils/log.h>
@@ -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<int64_t>::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");
+2
View File
@@ -118,4 +118,6 @@ private:
std::atomic<bool> m_shutdown_quick{};
};
extern Control* control;
#endif
+13 -3
View File
@@ -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);
+3 -1
View File
@@ -1,6 +1,7 @@
#ifndef RTORRENT_CORE_DOWNLOAD_H
#define RTORRENT_CORE_DOWNLOAD_H
#include <memory>
#include <torrent/common.h>
#include <torrent/download.h>
#include <torrent/download_info.h>
@@ -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);
+18 -4
View File
@@ -7,6 +7,7 @@
#include <functional>
#include <sstream>
#include <stdexcept>
#include <sys/stat.h>
#include <torrent/utils/log.h>
#include <torrent/utils/resume.h>
#include <torrent/object.h>
@@ -39,6 +40,11 @@ static constexpr const char* session_invalid_message = "Session data is invalid,
static std::unique_ptr<torrent::Object>
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<torrent::Object>();
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<torrent::Object> rtorrent_object;
std::unique_ptr<torrent::Object> 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;
+54 -24
View File
@@ -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>(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.");
}
+15 -2
View File
@@ -3,6 +3,7 @@
#include <iosfwd>
#include <list>
#include <memory>
#include <string>
namespace torrent {
@@ -20,9 +21,9 @@ class Download;
//
// Fix apply_on_ratio if the base_type is changed.
class DownloadList : private std::list<Download*> {
class DownloadList : private std::list<std::shared_ptr<Download>> {
public:
typedef std::list<Download*> base_type;
typedef std::list<std::shared_ptr<Download>> 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);
+4 -4
View File
@@ -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
+40 -16
View File
@@ -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<Download>& 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<Download>& d1, const std::shared_ptr<Download>& 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<Download>& 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<Download>& 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));
+6 -5
View File
@@ -15,6 +15,7 @@
#include <functional>
#include <list>
#include <memory>
#include <string>
#include <vector>
#include <torrent/object.h>
@@ -26,9 +27,9 @@ namespace core {
class Download;
class View : private std::vector<Download*> {
class View : private std::vector<std::shared_ptr<Download>> {
public:
typedef std::vector<Download*> base_type;
typedef std::vector<std::shared_ptr<Download>> base_type;
typedef std::function<void()> slot_void;
typedef std::list<slot_void> 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>& 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<Download>& d) { base_type::push_back(d); }
inline void insert_visible(Download* d);
inline void insert_visible(const std::shared_ptr<Download>& d);
inline void erase_internal(iterator itr);
void emit_changed();
+5 -5
View File
@@ -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<int, int>
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);
+6
View File
@@ -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) {
+9 -1
View File
@@ -1,5 +1,7 @@
#include "config.h"
#include <limits>
#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<typename command_value_function<T>::type, T>(command_raw, target, val);
}
return command_base::_call<typename command_value_function<T>::type, T>(command_raw, target, unit * arg.as_value());
auto value = arg.as_value();
if (value > std::numeric_limits<int64_t>::max() / unit ||
value < std::numeric_limits<int64_t>::min() / unit)
throw torrent::input_error("Value out of range.");
return command_base::_call<typename command_value_function<T>::type, T>(command_raw, target, unit * value);
}
template <typename T> const torrent::Object
+5
View File
@@ -1,6 +1,7 @@
#ifndef RTORRENT_RPC_COMMAND_MAP_H
#define RTORRENT_RPC_COMMAND_MAP_H
#include <memory>
#include <map>
#include <string>
#include <cstring>
@@ -104,6 +105,10 @@ inline target_type make_target(T target) {
return target_type((int)target_type_id<T>::value, target);
}
inline target_type make_target(const std::shared_ptr<core::Download>& target) {
return make_target(target.get());
}
template <typename T>
inline target_type make_target_pair(T target1, T target2) {
return target_type((int)target_type_id<T, T>::value, target1, target2);
+12 -4
View File
@@ -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<std::string>());
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);
}
+37 -19
View File
@@ -2,6 +2,7 @@
#include <cstring>
#include <cstdio>
#include <limits>
#include <locale>
#include <torrent/exceptions.h>
@@ -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<int64_t>::max() >> shift) &&
value >= (std::numeric_limits<int64_t>::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<int64_t>::max() / unit ||
*value < std::numeric_limits<int64_t>::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;
+3 -3
View File
@@ -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);
+8
View File
@@ -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);
+1 -1
View File
@@ -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); }
+11
View File
@@ -14,6 +14,7 @@
#include <torrent/runtime/socket_manager.h>
#include <torrent/system/callbacks.h>
#include <torrent/system/poll.h>
#include <torrent/system/scheduler.h>
#include <torrent/utils/log.h>
#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<std::mutex>(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;
+8 -3
View File
@@ -1,10 +1,12 @@
#ifndef RTORRENT_RPC_SCGI_TASK_H
#define RTORRENT_RPC_SCGI_TASK_H
#include <chrono>
#include <memory>
#include <mutex>
#include <vector>
#include <torrent/system/event.h>
#include <torrent/system/scheduler.h>
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;
+3 -4
View File
@@ -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);
}
+13 -3
View File
@@ -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
+1 -1
View File
@@ -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); }
+1
View File
@@ -3,6 +3,7 @@
#include <functional>
#include <string>
#include <vector>
int parse_main_options(int argc, char** argv);
void parse_config_file(int argc, char** argv, std::function<void (const std::string&)> parse_fn);
+5 -2
View File
@@ -1,3 +1,4 @@
#include <memory>
#include "config.h"
#include <cassert>
@@ -30,8 +31,10 @@
namespace ui {
Download::Download(core::Download* d)
: m_download(d) {
Download::Download(const std::shared_ptr<core::Download>& download)
: m_download(download.get()) {
auto* d = download.get();
m_windowDownloadStatus = std::make_unique<WDownloadStatus>(d);
m_windowDownloadStatus->set_bottom(true);
+2 -1
View File
@@ -2,6 +2,7 @@
#define RTORRENT_UI_DOWNLOAD_H
#include <list>
#include <memory>
#include <torrent/peer/peer.h>
#include "display/manager.h"
@@ -36,7 +37,7 @@ public:
DISPLAY_MAX_SIZE
} Display;
Download(core::Download* d);
Download(const std::shared_ptr<core::Download>& d);
~Download();
void activate(display::Frame* frame, bool focus = true);
+1 -1
View File
@@ -89,7 +89,7 @@ DownloadList::unfocus_download(core::Download* d) {
if (m_state == DISPLAY_DOWNLOAD && d == static_cast<Download*>(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();
}
+1 -1
View File
@@ -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;
+4
View File
@@ -5,6 +5,8 @@
#include <zlib.h>
#include <torrent/exceptions.h>
#include "utils/functional.h"
namespace utils {
void
@@ -21,6 +23,8 @@ gzip_compress_to_vector(const char* buffer, unsigned int length, std::vector<cha
if (deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, window_bits | gzip_encoding, gzip_level, Z_DEFAULT_STRATEGY) != Z_OK)
throw torrent::internal_error("gzip_compress_to_vector(...) could not initialize gzip deflate.");
scope_guard guard([&zs]() { deflateEnd(&zs); });
auto max_response_size = deflateBound(&zs, length);
output.resize(offset + max_response_size);
+1
View File
@@ -2,6 +2,7 @@
#define RTORRENT_UTILS_GZIP_H
#include <functional>
#include <vector>
namespace utils {
+4 -2
View File
@@ -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 \
+55
View File
@@ -0,0 +1,55 @@
#include "config.h"
#include "test/src/test_command_ip.h"
#include <cstdint>
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));
}
+25
View File
@@ -0,0 +1,25 @@
#include <cppunit/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>
// 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();
};
@@ -1,6 +1,6 @@
#include "config.h"
#include "test/src/test_command_local.h"
#include "test/src/test_command_system.h"
#include <torrent/torrent.h>
#include <torrent/runtime/socket_manager.h>
@@ -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<torrent::runtime::socket_manager_category_t>(i);
auto name = "system.sockets." + torrent::option_to_str_or_throw(torrent::OPTION_SOCKET_CATEGORY, i);
@@ -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);