Compare commits

...

77 Commits

Author SHA1 Message Date
rakshasa 184a6cb796 Merge branch 'master' into silasmariusz-untrusted-safe-file-allocate 2026-09-30 10:05:01 +02:00
Silas Mariusz 11885cbb59 Mark the system.file.allocate getter untrusted-safe, because d.open reads it. 2026-09-30 10:04:42 +02:00
noctuum dc916278c2 Do not switch an active download to initial_seed
`confirm_finished` did, and the `input_error` reached `main`.
2026-09-29 10:51:58 +02:00
noctuum 366b936ded Check m_entry against size() in set_entry 2026-09-29 10:18:35 +02:00
noctuum 8bcc4c6e92 Read the unpacked object in the raw string case
A list holding one raw string reached this case and threw on `src`.
2026-09-29 10:00:30 +02:00
noctuum 5421a35349 Remove code that has no remaining user
Three declarations had no definition and the rest had no user left.
2026-09-28 18:40:17 +02:00
noctuum d5ce0984fc Reset the freed pointers in XmlRpc::cleanup
`is_valid()` tests `m_env`, so it stayed true after `cleanup()`.
2026-09-28 18:01:05 +02:00
noctuum a89ce3cd7b Check first against last in parse_object
A command ending in `=` reaches it with an empty range.
2026-09-28 17:34:06 +02:00
noctuum d98a441dc4 Sort the transfer chunks by index
The default comparison ordered the pointers, not the chunk indexes.
2026-09-28 17:11:08 +02:00
noctuum fc6e57c3fa Check pthread_sigmask by its return value
`pthread_sigmask` returns the error number and does not set `errno`.
2026-09-28 16:46:02 +02:00
noctuum 8e23ba8b17 Reject an empty argument list in execute_lua
`lua.execute` with no arguments dereferenced `args.begin()`.
2026-09-28 11:36:53 +02:00
noctuum e656801cfa Limit tab completion to text before the cursor
`substr` takes a count, so the text after the cursor joined the prefix.
2026-09-28 11:00:25 +02:00
noctuum 9f6fd7994e Guard control in the main catch handlers
A start without HOME throws before control is constructed.
2026-09-28 10:46:13 +02:00
noctuum bb77aa644c Stop at max_active in scheduler.simple.removed
The limit was checked once, then every download was resumed.
2026-09-28 10:28:46 +02:00
xirvik 83a5e7687b Sync the session directory after committing the renames
Syncing the files alone does not make the new names survive a crash.
2026-09-25 09:31:08 +02:00
Jari Sundell 910b9b35fc Reorder mutex and atomic variable declarations 2026-09-25 08:52:56 +02:00
xirvik e3051df78c Give SessionManager::m_active its own cache line
Prevents false sharing with the adjacent m_mutex under concurrent access.
2026-09-25 08:52:56 +02:00
xirvik 21287a1355 Make SessionManager::m_active atomic
It is written under m_mutex but read under m_pending_builds_mutex in one place.
2026-09-25 08:52:56 +02:00
xirvik 6471dc181e Guard the download against handlers that erase it mid-close
Track a weak_ptr lifetime handle and make erase ignore re-entrant erase.
2026-09-24 10:22:40 +02:00
xirvik a5c39566c8 Write session files with O_NOFOLLOW and mode 0600
Also make directory_entry::is_file() report the real type so symlinks are skipped.
2026-09-24 09:52:03 +02:00
xirvik 019c16a077 rpc: multicall requires methodName as the struct's first member
Faults clearly instead of the confusing positional parse error it replaces.
2026-09-24 09:20:25 +02:00
xirvik 439d23ce62 Bound JSON-RPC input by size and nesting depth
Enforce the nesting bound in one parse, capping allocation by depth, not input size.
2026-09-23 09:25:25 +02:00
xirvik 83b03c2c1e Range-check global throttle rates before narrowing them to kB.
Multiplying kB back to bytes in unsigned int wrapped 2^32 to 0, which means unlimited.
2026-09-23 09:01:02 +02:00
rakshasa 05cdd03cd2 Tagged release 0.16.24. 2026-09-22 15:24:24 +02:00
xirvik d1a6942ea7 Keep the object_storage key terminator inside the key buffer
find_raw_string now honours the raw_string size instead of scanning for a nul.
2026-09-21 09:22:26 +02:00
xirvik 3652329e2d Pass the typed download filter as an argument, not command text
A pattern containing '}' closed the match command early and ran the rest as commands.
2026-09-20 16:39:14 +02:00
xirvik 7916e6022f Stop the value command on the untrusted-safe allowlist crashing the daemon
glibc leaves endptr unset for a small invalid base; ERANGE throws instead of clamping.
2026-09-20 15:58:47 +02:00
xirvik c59da0848e Dump the ipv4 filter by reference instead of copying the whole map
A static table owns the map; the loop only reads it on the main thread. No test needed.
2026-09-20 15:33:09 +02:00
xirvik e2f66ff08c Drop the address range parser and its includes, dead since 29edda2f
External linkage kept -Wunused-function quiet; a deletion of dead code needs no test.
2026-09-20 15:01:44 +02:00
xirvik f0b6f2cb47 Fail p.multicall when a command changes the connection list
Disconnecting a peer frees it and reorders the list, so the iteration cannot continue.
2026-09-20 14:05:49 +02:00
xirvik d334b8d339 Stop printing a strerror of si_errno in the SIGBUS dump
Linux leaves si_errno zero there, so the line always read Error: Success.
2026-09-20 10:05:55 +02:00
xirvik 19b71a98b7 Fix data race reading the log buffer from the display thread
Copy entries under the buffer lock before rendering; other threads mutate the deque.
2026-09-20 09:42:59 +02:00
xirvik 8ee3b984dd Give network.xmlrpc.size_limit.set a floor
A limit below any valid request rejects the request that would raise it again.
2026-09-20 09:23:12 +02:00
xirvik 4e7129c71c Keep starting when the DHT cache file is rejected
The router could throw while loading it, stopping rtorrent from starting at all.
2026-09-20 08:53:34 +02:00
xirvik 80ae6a4590 Answer an oversized RPC response with a fault
Cap XML-RPC and JSON-RPC output at the SCGI response limit before handing it over.
2026-09-19 18:04:09 +02:00
xirvik 2086e8e780 Check the argument count before indexing config comment log args
A '# do:log.add_output=debug' line read args[1] past the end of the vector.
2026-09-19 12:13:43 +02:00
xirvik d9439d7a95 Dispatch view.event_added over the added half of the changed range
fb75510 pointed it at the removed half, so it fired on departing downloads instead.
2026-09-19 11:41:58 +02:00
noctuum 0d3404576a Reject a throttle rate that overflows
rate * 1024 wraps to zero at 2^54, which means no limit.
2026-09-19 11:11:00 +02:00
noctuum 84a0d86f2c Own the fault string in xmlrpc_error_c
xmlrpc_env_set_fault frees the buffer the pointer came from.
2026-09-19 10:34:46 +02:00
noctuum 1b15b7c518 Report a failed write in log.vmmap.dump
The dump is only flushed at fclose, whose result was discarded.
2026-09-19 10:11:23 +02:00
noctuum b1be898ba1 Reject an over-long or empty object_storage key
key_type stores a key of key_size or longer as the empty key.
2026-09-18 16:54:48 +02:00
Jari Sundell bd00b5f6fe Use explicit device name binding rather than a custom syntax. 2026-09-18 23:19:43 +09:00
noctuum b34eb1e895 Own the download factory until commit
A malformed data uri threw before commit, and nothing deleted it.
2026-09-18 11:24:30 +02:00
noctuum b857acd081 Increment d.state_counter on pause
The start path increments it; the pause path assigned it to itself.
2026-09-18 11:05:31 +02:00
noctuum 988aca1281 Release the scgi task buffer on close
clear() keeps the capacity, and the task is pooled until exit.
2026-09-18 10:53:58 +02:00
noctuum 378c7ee854 Reject a throttle rate that does not parse
A discarded parse result left the rate at zero, which is unlimited.
2026-09-18 10:37:14 +02:00
noctuum da9606bfae Take ownership of the magnet metadata object
Nothing freed the bencode map the meta download built.
2026-09-18 10:17:28 +02:00
noctuum 8423803b6e Check the flush on the dht cache and history
good() ran before close(), so a failed write replaced the file.
2026-09-17 16:13:55 +02:00
noctuum c1ce2febf0 Reject an over-long line instead of truncating
getline sets failbit, which both loops read as end of input.
2026-09-17 11:33:44 +02:00
noctuum 5ebb7bd151 Call the target deleter in the tinyxml2 backend
A tracker target allocates, and the deleter went out of scope unused.
2026-09-17 10:33:45 +02:00
noctuum 50f0cad0c6 Release the xmlrpc array item on a throw
xmlrpc_to_target throws for an invalid target, skipping the DECREF.
2026-09-17 10:14:21 +02:00
noctuum 3717649bb3 Guard the value iterator in method.insert.list
The list branch dereferenced end() when no value was given.
2026-09-17 09:54:14 +02:00
noctuum c32b56e9d8 Do not reschedule a deleted scheduler item
schedule.remove from the scheduled command deletes the item.
2026-09-16 11:37:19 +02:00
noctuum de54a4c2ad Clamp the negative width in wstring_width
A narrow terminal makes the file list width go below zero.
2026-09-16 11:18:54 +02:00
noctuum 26fdca8f4d Use empty_visible in the view focus movers
empty() counts filtered entries, so size() - 1 underflows.
2026-09-16 10:57:25 +02:00
noctuum 00bcdaee23 Check the info-hash target in p.call_target
Without it an unknown info-hash segfaults the process.
2026-09-16 10:29:52 +02:00
rakshasa 3787dbe7f3 Tagged release 0.16.23. 2026-09-15 14:49:21 +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
91 changed files with 2131 additions and 861 deletions
+3 -3
View File
@@ -1,6 +1,6 @@
m4_pattern_allow([PKG_CHECK_EXISTS]) m4_pattern_allow([PKG_CHECK_EXISTS])
AC_INIT([rtorrent],[0.16.21],[sundell.software@gmail.com]) AC_INIT([rtorrent],[0.16.24],[sundell.software@gmail.com])
AC_CONFIG_HEADERS([config.h]) AC_CONFIG_HEADERS([config.h])
AC_CONFIG_MACRO_DIRS([scripts]) AC_CONFIG_MACRO_DIRS([scripts])
@@ -14,7 +14,7 @@ AX_CXX_COMPILE_STDCXX(20, noext, mandatory)
PKG_PROG_PKG_CONFIG PKG_PROG_PKG_CONFIG
AC_DEFINE([API_VERSION], [26], [api version]) AC_DEFINE([API_VERSION], [28], [api version])
RAK_CHECK_CFLAGS RAK_CHECK_CFLAGS
RAK_CHECK_CXXFLAGS RAK_CHECK_CXXFLAGS
@@ -49,7 +49,7 @@ fi
PKG_CHECK_MODULES([CPPUNIT], [cppunit],, [no_cppunit="yes"]) PKG_CHECK_MODULES([CPPUNIT], [cppunit],, [no_cppunit="yes"])
PKG_CHECK_MODULES([ZLIB], [zlib]) PKG_CHECK_MODULES([ZLIB], [zlib])
PKG_CHECK_MODULES([DEPENDENCIES], [libtorrent >= 0.16.21]) PKG_CHECK_MODULES([DEPENDENCIES], [libtorrent >= 0.16.24])
AC_LANG_PUSH(C++) AC_LANG_PUSH(C++)
TORRENT_WITH_XMLRPC_C TORRENT_WITH_XMLRPC_C
+1 -2
View File
@@ -17,7 +17,6 @@ libsub_root_a_SOURCES = \
core/http_queue.h \ core/http_queue.h \
core/manager.cc \ core/manager.cc \
core/manager.h \ core/manager.h \
core/range_map.h \
core/view.cc \ core/view.cc \
core/view.h \ core/view.h \
core/view_manager.cc \ core/view_manager.cc \
@@ -171,7 +170,6 @@ libsub_root_a_SOURCES = \
utils/functional.h \ utils/functional.h \
utils/gzip.cc \ utils/gzip.cc \
utils/gzip.h \ utils/gzip.h \
utils/list_focus.h \
utils/lockfile.cc \ utils/lockfile.cc \
utils/lockfile.h \ utils/lockfile.h \
utils/waitpid_queue.cc \ utils/waitpid_queue.cc \
@@ -195,6 +193,7 @@ libsub_root_a_SOURCES = \
command_tracker.cc \ command_tracker.cc \
command_scheduler.cc \ command_scheduler.cc \
command_string.cc \ command_string.cc \
command_system.cc \
command_ui.cc \ command_ui.cc \
control.cc \ control.cc \
control.h \ control.h \
+18 -19
View File
@@ -142,16 +142,6 @@ apply_d_delete_tied(core::Download* download) {
return torrent::Object(); 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 torrent::Object
apply_d_connection_type(core::Download* download, const std::string& name) { apply_d_connection_type(core::Download* download, const std::string& name) {
torrent::Download::ConnectionType t = torrent::Download::ConnectionType t =
@@ -412,13 +402,21 @@ p_multicall(core::Download* download, const torrent::Object::list_type& args) {
auto resultRaw = torrent::Object::create_list(); auto resultRaw = torrent::Object::create_list();
auto& result = resultRaw.as_list(); auto& result = resultRaw.as_list();
for (const auto& connection : *download->connection_list()) { auto* connection_list = download->connection_list();
const auto change_counter = connection_list->change_counter();
for (const auto& connection : *connection_list) {
torrent::Object::list_type& row = result.insert(result.end(), torrent::Object::create_list())->as_list(); torrent::Object::list_type& row = result.insert(result.end(), torrent::Object::create_list())->as_list();
for (auto cItr = ++args.begin(); cItr != args.end(); cItr++) { for (auto cItr = ++args.begin(); cItr != args.end(); cItr++) {
const std::string& cmd = cItr->as_string(); const std::string& cmd = cItr->as_string();
row.push_back(rpc::parse_command(rpc::make_target(connection), cmd.c_str(), cmd.c_str() + cmd.size()).first); row.push_back(rpc::parse_command(rpc::make_target(connection), cmd.c_str(), cmd.c_str() + cmd.size()).first);
// Erasing a peer frees it and swaps the last element into its place, so
// neither this peer nor the iteration survives a change to the list.
if (connection_list->change_counter() != change_counter)
throw torrent::input_error("Command changed the connection list during p.multicall.");
} }
} }
@@ -441,6 +439,9 @@ p_call_target(const torrent::Object::list_type& args) {
const std::string& peer_id = itr++->as_string(); const std::string& peer_id = itr++->as_string();
const std::string& command_key = itr++->as_string(); const std::string& command_key = itr++->as_string();
if (download == nullptr)
throw torrent::input_error("invalid parameters: info-hash not found");
torrent::HashString hash; torrent::HashString hash;
if (peer_id.size() != 40) if (peer_id.size() != 40)
@@ -718,7 +719,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.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.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", 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.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)); CMD2_DL_V ("d.check_hash", std::bind(&core::DownloadList::check_hash, control->core()->download_list(), std::placeholders::_1));
@@ -884,12 +885,11 @@ initialize_command_download() {
CMD2_DL_LIST ("d.tracker.insert", std::bind(&download_tracker_insert, std::placeholders::_1, std::placeholders::_2)); 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_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", 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_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 ("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_STRING_V("d.directory.set", [](auto* download, auto arg) { download->set_directory(arg); });
CMD2_DL ("d.directory_base", CMD2_ON_FL(root_dir)); CMD2_DL_STRING_V("d.directory.base.set", [](auto* download, auto arg) { download->set_base_directory(arg); });
CMD2_DL_STRING_V("d.directory_base.set", std::bind(&core::Download::set_root_directory, std::placeholders::_1, std::placeholders::_2));
CMD2_DL ("d.priority", std::bind(&core::Download::priority, std::placeholders::_1)); 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)); CMD2_DL ("d.priority_str", std::bind(&retrieve_d_priority_str, std::placeholders::_1));
@@ -950,7 +950,6 @@ initialize_command_download() {
rpc::rpc.mark_safe("d.name.or_base64"); rpc::rpc.mark_safe("d.name.or_base64");
rpc::rpc.mark_safe("d.name.or_as_binary"); rpc::rpc.mark_safe("d.name.or_as_binary");
rpc::rpc.mark_safe("d.directory"); 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.creation_date");
rpc::rpc.mark_safe("d.load_date"); rpc::rpc.mark_safe("d.load_date");
rpc::rpc.mark_safe("d.up.rate"); rpc::rpc.mark_safe("d.up.rate");
+1 -1
View File
@@ -157,7 +157,7 @@ system_method_insert_object(const torrent::Object::list_type& args, int flags) {
torrent::Object valueList = torrent::Object::create_list(); torrent::Object valueList = torrent::Object::create_list();
torrent::Object::list_type& valueListType = valueList.as_list(); torrent::Object::list_type& valueListType = valueList.as_list();
if ((itrArgs)->is_list()) if (itrArgs != args.end() && (itrArgs)->is_list())
valueListType = (itrArgs)->as_list(); valueListType = (itrArgs)->as_list();
control->object_storage()->insert_str(raw_key, valueList, flags); control->object_storage()->insert_str(raw_key, valueList, flags);
+2
View File
@@ -20,6 +20,7 @@ void initialize_command_throttle();
void initialize_command_tracker(); void initialize_command_tracker();
void initialize_command_scheduler(); void initialize_command_scheduler();
void initialize_command_string(); void initialize_command_string();
void initialize_command_system();
void initialize_command_ui(); void initialize_command_ui();
void void
@@ -39,4 +40,5 @@ initialize_commands() {
initialize_command_tracker(); initialize_command_tracker();
initialize_command_scheduler(); initialize_command_scheduler();
initialize_command_string(); initialize_command_string();
initialize_command_system();
} }
+11 -3
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 // copy everything up to '#' to address_copy and work from there
while(address[address_start_index] != '#' && address[address_start_index] != '\r' && while(address[address_start_index] != '#' && address[address_start_index] != '\r' &&
address[address_start_index] != '\n' && address[address_start_index] != '\0' && 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_copy[address_start_index] = address[address_start_index];
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) { if (mask_bits == 0) {
mask = 0; mask = 0;
end_mask = ~(uint32_t)0; end_mask = ~(uint32_t)0;
} else if (mask_bits == 32) {
mask = ~(uint32_t)0;
end_mask = 0;
} else { } else {
mask = (~mask) << (32-mask_bits); mask = (~mask) << (32-mask_bits);
end_mask = (~end_mask) >> mask_bits; end_mask = (~end_mask) >> mask_bits;
@@ -316,6 +319,11 @@ apply_ipv4_filter_load(const torrent::Object::list_type& args) {
ipv4_filter_parse(buffer, value); ipv4_filter_parse(buffer, value);
} }
if (file.fail() && !file.eof()) {
lineNumber++;
throw torrent::input_error("Exceeded max line length.");
}
} catch (torrent::input_error& e) { } catch (torrent::input_error& e) {
snprintf(buffer, 2048, "Error in ip filter file: %s:%u: %s", filename.c_str(), lineNumber, e.what()); snprintf(buffer, 2048, "Error in ip filter file: %s:%u: %s", filename.c_str(), lineNumber, e.what());
@@ -336,8 +344,8 @@ apply_ipv4_filter_dump() {
torrent::Object raw_result = torrent::Object::create_list(); torrent::Object raw_result = torrent::Object::create_list();
torrent::Object::list_type& result = raw_result.as_list(); torrent::Object::list_type& result = raw_result.as_list();
torrent::ipv4_table::range_map_type range_map = torrent::PeerList::ipv4_filter()->range_map; const torrent::ipv4_table::range_map_type& range_map = torrent::PeerList::ipv4_filter()->range_map;
torrent::ipv4_table::range_map_type::iterator iter = range_map.begin(); torrent::ipv4_table::range_map_type::const_iterator iter = range_map.begin();
while(iter != range_map.end()) { while(iter != range_map.end()) {
char buffer[64]; char buffer[64];
+8 -234
View File
@@ -1,34 +1,16 @@
#include "config.h" #include "config.h"
#include <cerrno>
#include <fcntl.h>
#include <functional>
#include <limits>
#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/memory_manager.h>
#include <torrent/runtime/socket_manager.h> #include <torrent/utils/log.h>
#include <torrent/utils/chrono.h>
#include <torrent/utils/option_strings.h>
#include "command_helpers.h"
#include "control.h"
#include "core/download.h" #include "core/download.h"
#include "core/download_list.h"
#include "core/manager.h" #include "core/manager.h"
#include "rpc/parse_commands.h" #include "rpc/parse_commands.h"
#include "rpc/lua.h"
#include "rpc/scgi.h" #include "rpc/scgi.h"
#include "session/session_manager.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::ChunkManager CM_t;
typedef torrent::FileManager FM_t; typedef torrent::FileManager FM_t;
@@ -44,49 +26,6 @@ apply_pieces_stats_total_size() {
return 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 inline torrent::Object::list_const_iterator
post_increment(torrent::Object::list_const_iterator& itr, const torrent::Object::list_const_iterator& last) { post_increment(torrent::Object::list_const_iterator& itr, const torrent::Object::list_const_iterator& last) {
if (itr == last) if (itr == last)
@@ -138,152 +77,19 @@ group_insert(const torrent::Object::list_type& args) {
return name; return name;
} }
static const int file_print_use_space = 0x1;
static const int file_print_delim_space = 0x2;
void
file_print_list(torrent::Object::list_const_iterator first, torrent::Object::list_const_iterator last, FILE* output, int flags) {
while (first != last) {
switch (first->type()) {
case torrent::Object::TYPE_STRING:
fprintf(output, (const char*)" %s" + !(flags & file_print_use_space), first->as_string().c_str());
break;
case torrent::Object::TYPE_VALUE:
fprintf(output, (const char*)" %" PRIi64 + !(flags & file_print_use_space), first->as_value());
break;
case torrent::Object::TYPE_LIST:
file_print_list(first->as_list().begin(), first->as_list().end(), output, 0);
break;
case torrent::Object::TYPE_NONE:
break;
default:
throw torrent::input_error("Invalid type.");
}
flags |= (flags & file_print_delim_space) >> 1;
first++;
}
}
torrent::Object
cmd_file_append(const torrent::Object::list_type& args) {
if (args.empty())
throw torrent::input_error("Invalid number of arguments.");
FILE* output = fopen(args.front().as_string().c_str(), "a");
if (output == nullptr)
throw torrent::input_error("Could not append to file '" + args.front().as_string() + "': " + std::strerror(errno));
try {
file_print_list(++args.begin(), args.end(), output, file_print_delim_space);
fprintf(output, "\n");
} catch (...) {
fclose(output);
throw;
}
fclose(output);
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 void
initialize_command_local() { initialize_command_local() {
core::DownloadList* dList = control->core()->download_list(); 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(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")); });
}
CMD_ANY ("pieces.sync.always_safe", [](auto, auto) { return torrent::runtime::memory_manager()->safe_sync(); }); 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_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.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 ("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_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 ("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.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(); }); CMD_ANY ("pieces.preload.type", [](auto, auto) { return torrent::runtime::memory_manager()->preload_type(); });
@@ -358,38 +164,6 @@ initialize_command_local() {
CMD_ANY_LIST ("group.insert", std::bind(&group_insert, std::placeholders::_2)); 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.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");
}
rpc::rpc.mark_safe("directory.default"); rpc::rpc.mark_safe("directory.default");
rpc::rpc.mark_safe("session.path"); rpc::rpc.mark_safe("session.path");
rpc::rpc.mark_safe("session.path.realpath.or_empty"); rpc::rpc.mark_safe("session.path.realpath.or_empty");
+5 -2
View File
@@ -122,7 +122,10 @@ log_vmmap_dump(const std::string& str) {
fprintf(log_file, "%8p-%8p [%5llxk]\n", all_mapping.ptr, (char*)all_mapping.ptr + all_mapping.length, (long long unsigned int)(all_mapping.length / 1024)); fprintf(log_file, "%8p-%8p [%5llxk]\n", all_mapping.ptr, (char*)all_mapping.ptr + all_mapping.length, (long long unsigned int)(all_mapping.length / 1024));
} }
fclose(log_file); // The buffered output only reaches the kernel here.
if (fclose(log_file) != 0)
throw torrent::input_error("Could not write log file: " + str);
return torrent::Object(); return torrent::Object();
} }
@@ -135,7 +138,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.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", 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_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)); CMD2_ANY_STRING_V("log.close", std::bind(&torrent::log_close_output_str, std::placeholders::_2));
+15 -9
View File
@@ -374,25 +374,31 @@ initialize_command_network() {
CMD_ANY_STRING ("network.tos.set", [](auto, auto& str) { return apply_tos(str); }); CMD_ANY_STRING ("network.tos.set", [](auto, auto& str) { return apply_tos(str); });
CMD_ANY ("network.bind_address", [nw_config](auto, auto) { return nw_config->bind_address_best_match_str(); }); CMD_ANY ("network.bind_address", [nw_config](auto, auto) { return nw_config->bind_address_best_match_str(); });
CMD_ANY_STRING_V("network.bind_address.set", [nw_config](auto, auto& str) { return nw_config->set_bind_address_str(str); }); CMD_ANY_STRING_V("network.bind_address.set", [nw_config](auto, auto& str) { return nw_config->set_bind_address(str); });
CMD_ANY ("network.bind_address.ipv4", [nw_config](auto, auto) { return nw_config->bind_inet_address_str(); }); CMD_ANY ("network.bind_address.ipv4", [nw_config](auto, auto) { return nw_config->bind_inet_address_str(); });
CMD_ANY_STRING_V("network.bind_address.ipv4.set", [nw_config](auto, auto& str) { return nw_config->set_bind_inet_address_str(str); }); CMD_ANY_STRING_V("network.bind_address.ipv4.set", [nw_config](auto, auto& str) { return nw_config->set_bind_inet_address(str); });
CMD_ANY ("network.bind_address.ipv6", [nw_config](auto, auto) { return nw_config->bind_inet6_address_str(); }); CMD_ANY ("network.bind_address.ipv6", [nw_config](auto, auto) { return nw_config->bind_inet6_address_str(); });
CMD_ANY_STRING_V("network.bind_address.ipv6.set", [nw_config](auto, auto& str) { return nw_config->set_bind_inet6_address_str(str); }); CMD_ANY_STRING_V("network.bind_address.ipv6.set", [nw_config](auto, auto& str) { return nw_config->set_bind_inet6_address(str); });
CMD_ANY_STRING_V("network.bind_device.set", [nw_config](auto, auto& str) { return nw_config->set_bind_device_name(str); });
CMD_ANY ("network.bind_device.ipv4", [nw_config](auto, auto) { return nw_config->bind_inet_device_name(); });
CMD_ANY_STRING_V("network.bind_device.ipv4.set", [nw_config](auto, auto& str) { return nw_config->set_bind_inet_device_name(str); });
CMD_ANY ("network.bind_device.ipv6", [nw_config](auto, auto) { return nw_config->bind_inet6_device_name(); });
CMD_ANY_STRING_V("network.bind_device.ipv6.set", [nw_config](auto, auto& str) { return nw_config->set_bind_inet6_device_name(str); });
CMD_ANY ("network.local_address", [nw_config](auto, auto) { return nw_config->local_address_best_match_str(); }); CMD_ANY ("network.local_address", [nw_config](auto, auto) { return nw_config->local_address_best_match_str(); });
CMD_ANY_STRING_V("network.local_address.set", [nw_config](auto, auto& str) { return nw_config->set_local_address_str(str); }); CMD_ANY_STRING_V("network.local_address.set", [nw_config](auto, auto& str) { return nw_config->set_local_address(str); });
CMD_ANY ("network.local_address.ipv4", [nw_config](auto, auto) { return nw_config->local_inet_address_str(); }); CMD_ANY ("network.local_address.ipv4", [nw_config](auto, auto) { return nw_config->local_inet_address_str(); });
CMD_ANY_STRING_V("network.local_address.ipv4.set", [nw_config](auto, auto& str) { return nw_config->set_local_inet_address_str(str); }); CMD_ANY_STRING_V("network.local_address.ipv4.set", [nw_config](auto, auto& str) { return nw_config->set_local_inet_address(str); });
CMD_ANY ("network.local_address.ipv6", [nw_config](auto, auto) { return nw_config->local_inet6_address_str(); }); 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_STRING_V("network.local_address.ipv6.set", [nw_config](auto, auto& str) { return nw_config->set_local_inet6_address(str); });
CMD_ANY ("network.local_port", [nw_config](auto, auto) { return nw_config->local_port_best_match(); }); 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_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 ("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_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 ("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_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 ("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_STRING_V("network.proxy.global.set", [](auto, auto& str) { return torrent::runtime::proxy_manager()->set_proxy_url(str); });
+5 -1
View File
@@ -28,9 +28,10 @@ cmd_scheduler_simple_removed(core::Download* download) {
control->core()->download_list()->pause(download); control->core()->download_list()->pause(download);
core::View* viewActive = *control->view_manager()->find("active"); core::View* viewActive = *control->view_manager()->find("active");
unsigned int numActive = viewActive->size_visible();
int64_t maxActive = rpc::call_command("scheduler.max_active", torrent::Object()).as_value(); int64_t maxActive = rpc::call_command("scheduler.max_active", torrent::Object()).as_value();
if ((int64_t)viewActive->size_visible() >= maxActive) if ((int64_t)numActive >= maxActive)
return torrent::Object(); return torrent::Object();
// The 'started' view contains all the views we may choose amongst. // The 'started' view contains all the views we may choose amongst.
@@ -41,6 +42,9 @@ cmd_scheduler_simple_removed(core::Download* download) {
continue; continue;
control->core()->download_list()->resume(*itr); control->core()->download_list()->resume(*itr);
if ((int64_t)++numActive >= maxActive)
break;
} }
return torrent::Object(); return torrent::Object();
+196
View File
@@ -0,0 +1,196 @@
#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.allocate");
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");
}
}
+29 -60
View File
@@ -1,10 +1,9 @@
#include "config.h" #include "config.h"
#include <cstdio> #include <limits>
#include <torrent/throttle.h> #include <torrent/throttle.h>
#include <torrent/rate.h> #include <torrent/rate.h>
#include <torrent/download/resource_manager.h> #include <torrent/download/resource_manager.h>
#include <torrent/net/socket_address.h>
#include "core/manager.h" #include "core/manager.h"
#include "ui/root.h" #include "ui/root.h"
@@ -15,59 +14,6 @@
#include "control.h" #include "control.h"
#include "command_helpers.h" #include "command_helpers.h"
std::pair<uint32_t, uint32_t>
parse_address_range(const torrent::Object::list_type& args, torrent::Object::list_type::const_iterator itr) {
unsigned int prefixWidth, ret;
char dummy;
char host[1024];
torrent::sa_unique_ptr sa;
ret = std::sscanf(itr->as_string().c_str(), "%1023[^/]/%d%c", host, &prefixWidth, &dummy);
if (ret < 1)
throw torrent::input_error("Invalid address/prefix.");
try {
sa = torrent::sa_copy(torrent::sa_lookup_address(host, AF_INET).get());
} catch (torrent::input_error& e) {
throw torrent::input_error("Could not resolve host: " + std::string(e.what()));
}
uint32_t begin, end;
auto sa_addr = htonl(reinterpret_cast<sockaddr_in*>(sa.get())->sin_addr.s_addr);
begin = end = sa_addr;
if (ret == 2) {
if (++itr != args.end())
throw torrent::input_error("Cannot specify both network and range end.");
uint32_t netmask = std::numeric_limits<uint32_t>::max() << (32 - prefixWidth);
if (prefixWidth >= 32 || sa_addr & ~netmask)
throw torrent::input_error("Invalid address/prefix.");
end = sa_addr | ~netmask;
} else if (++itr != args.end()) {
try {
sa = torrent::sa_copy(torrent::sa_lookup_address(itr->as_string(), AF_INET).get());
} catch (torrent::input_error& e) {
throw torrent::input_error("Could not resolve host: " + std::string(e.what()));
}
sa_addr = htonl(reinterpret_cast<sockaddr_in*>(sa.get())->sin_addr.s_addr);
end = sa_addr;
}
// convert to [begin, end) making sure the end doesn't overflow
// (this precludes 255.255.255.255 from ever matching, but that's not a real IP anyway)
return std::make_pair((uint32_t)begin, (uint32_t)std::max(end, end + 1));
}
torrent::Object torrent::Object
apply_throttle(const torrent::Object::list_type& args, bool up) { apply_throttle(const torrent::Object::list_type& args, bool up) {
auto arg_itr = args.begin(); auto arg_itr = args.begin();
@@ -83,11 +29,16 @@ apply_throttle(const torrent::Object::list_type& args, bool up) {
throw torrent::input_error("Missing throttle rate for '" + name + "'."); throw torrent::input_error("Missing throttle rate for '" + name + "'.");
int64_t rate; int64_t rate;
rpc::parse_whole_value_nothrow(arg_itr->as_string().c_str(), &rate);
if (!rpc::parse_whole_value_nothrow(arg_itr->as_string().c_str(), &rate))
throw torrent::input_error("Invalid throttle rate for '" + name + "'.");
if (rate < 0) if (rate < 0)
throw torrent::input_error("Throttle rate must be non-negative."); throw torrent::input_error("Throttle rate must be non-negative.");
if (rate > (std::numeric_limits<int64_t>::max() >> 10))
throw torrent::input_error("Throttle rate is too large.");
auto itr = control->core()->throttles().find(name); auto itr = control->core()->throttles().find(name);
if (itr == control->core()->throttles().end()) if (itr == control->core()->throttles().end())
@@ -138,6 +89,24 @@ throttle_update(const char* variable, int64_t value) {
return torrent::Object(); return torrent::Object();
} }
static unsigned int
throttle_rate_to_kb(int64_t rate) {
if (rate < 0 || rate > std::numeric_limits<unsigned int>::max() - 1)
throw torrent::input_error("Throttle rate must be between 0 and 4294967294.");
return static_cast<unsigned int>(rate >> 10);
}
static void
set_up_throttle_i64(ui::Root* root, int64_t rate) {
root->set_up_throttle(throttle_rate_to_kb(rate));
}
static void
set_down_throttle_i64(ui::Root* root, int64_t rate) {
root->set_down_throttle(throttle_rate_to_kb(rate));
}
void void
initialize_command_throttle() { initialize_command_throttle() {
CMD2_ANY ("throttle.unchoked_uploads", std::bind(&torrent::ResourceManager::currently_upload_unchoked, torrent::resource_manager())); CMD2_ANY ("throttle.unchoked_uploads", std::bind(&torrent::ResourceManager::currently_upload_unchoked, torrent::resource_manager()));
@@ -174,13 +143,13 @@ initialize_command_throttle() {
CMD2_ANY ("throttle.global_up.rate", std::bind(&torrent::Rate::rate, torrent::up_rate())); CMD2_ANY ("throttle.global_up.rate", std::bind(&torrent::Rate::rate, torrent::up_rate()));
CMD2_ANY ("throttle.global_up.total", std::bind(&torrent::Rate::total, torrent::up_rate())); CMD2_ANY ("throttle.global_up.total", std::bind(&torrent::Rate::total, torrent::up_rate()));
CMD2_ANY ("throttle.global_up.max_rate", std::bind(&torrent::Throttle::max_rate, torrent::up_throttle_global())); CMD2_ANY ("throttle.global_up.max_rate", std::bind(&torrent::Throttle::max_rate, torrent::up_throttle_global()));
CMD2_ANY_VALUE_V ("throttle.global_up.max_rate.set", std::bind(&ui::Root::set_up_throttle_i64, control->ui(), std::placeholders::_2)); CMD2_ANY_VALUE_V ("throttle.global_up.max_rate.set", std::bind(&set_up_throttle_i64, control->ui(), std::placeholders::_2));
CMD2_ANY_VALUE_KB("throttle.global_up.max_rate.set_kb", std::bind(&ui::Root::set_up_throttle_i64, control->ui(), std::placeholders::_2)); CMD2_ANY_VALUE_KB("throttle.global_up.max_rate.set_kb", std::bind(&set_up_throttle_i64, control->ui(), std::placeholders::_2));
CMD2_ANY ("throttle.global_down.rate", std::bind(&torrent::Rate::rate, torrent::down_rate())); CMD2_ANY ("throttle.global_down.rate", std::bind(&torrent::Rate::rate, torrent::down_rate()));
CMD2_ANY ("throttle.global_down.total", std::bind(&torrent::Rate::total, torrent::down_rate())); CMD2_ANY ("throttle.global_down.total", std::bind(&torrent::Rate::total, torrent::down_rate()));
CMD2_ANY ("throttle.global_down.max_rate", std::bind(&torrent::Throttle::max_rate, torrent::down_throttle_global())); CMD2_ANY ("throttle.global_down.max_rate", std::bind(&torrent::Throttle::max_rate, torrent::down_throttle_global()));
CMD2_ANY_VALUE_V ("throttle.global_down.max_rate.set", std::bind(&ui::Root::set_down_throttle_i64, control->ui(), std::placeholders::_2)); CMD2_ANY_VALUE_V ("throttle.global_down.max_rate.set", std::bind(&set_down_throttle_i64, control->ui(), std::placeholders::_2));
CMD2_ANY_VALUE_KB("throttle.global_down.max_rate.set_kb", std::bind(&ui::Root::set_down_throttle_i64, control->ui(), std::placeholders::_2)); CMD2_ANY_VALUE_KB("throttle.global_down.max_rate.set_kb", std::bind(&set_down_throttle_i64, control->ui(), std::placeholders::_2));
// Temporary names, need to change this to accept real rates rather // Temporary names, need to change this to accept real rates rather
// than kB. // than kB.
+12 -2
View File
@@ -2,6 +2,7 @@
#include <sys/types.h> #include <sys/types.h>
#include <cerrno>
#include <ctime> #include <ctime>
#include <limits> #include <limits>
#include <regex> #include <regex>
@@ -126,11 +127,20 @@ apply_value([[maybe_unused]] rpc::target_type target, const torrent::Object::lis
if (args.front().is_value()) { if (args.front().is_value()) {
val = args.front().as_value(); val = args.front().as_value();
} else { } else {
int base = args.size() > 1 ? args.back().is_value() ? int64_t base = args.size() > 1 ? args.back().is_value() ?
args.back().as_value() : strtol(args.back().as_string().c_str(), NULL, 10) : 10; args.back().as_value() : strtoll(args.back().as_string().c_str(), NULL, 10) : 10;
if (base != 0 && (base < 2 || base > 36))
throw torrent::input_error("'value' base must be 0 or between 2 and 36!");
char* endptr = 0; char* endptr = 0;
errno = 0;
val = strtoll(args.front().as_string().c_str(), &endptr, base); val = strtoll(args.front().as_string().c_str(), &endptr, base);
if (errno == ERANGE)
throw torrent::input_error("Number out of range: " + args.front().as_string());
while (*endptr == ' ' || *endptr == '\n') ++endptr; while (*endptr == ' ' || *endptr == '\n') ++endptr;
if (*endptr) if (*endptr)
throw torrent::input_error("Junk at end of number: " + args.front().as_string()); throw torrent::input_error("Junk at end of number: " + args.front().as_string());
+2
View File
@@ -118,4 +118,6 @@ private:
std::atomic<bool> m_shutdown_quick{}; std::atomic<bool> m_shutdown_quick{};
}; };
extern Control* control;
#endif #endif
+12 -3
View File
@@ -4,6 +4,7 @@
#include <fstream> #include <fstream>
#include <sstream> #include <sstream>
#include <torrent/exceptions.h>
#include <torrent/object.h> #include <torrent/object.h>
#include <torrent/object_stream.h> #include <torrent/object_stream.h>
#include <torrent/rate.h> #include <torrent/rate.h>
@@ -62,7 +63,14 @@ DhtManager::load_dht_cache() {
LT_LOG("could not open cache file (path:%s)", cache_filename.c_str()); LT_LOG("could not open cache file (path:%s)", cache_filename.c_str());
} }
torrent::runtime::network_manager()->dht_controller()->initialize(cache); try {
torrent::runtime::network_manager()->dht_controller()->initialize(cache);
} catch (const torrent::base_error& e) {
LT_LOG_ERROR("cache file rejected, discarding (path:%s errmsg:%s)", cache_filename.c_str(), e.what());
torrent::runtime::network_manager()->dht_controller()->initialize(torrent::Object::create_map());
}
if (m_start == dht_on) if (m_start == dht_on)
start_dht(); start_dht();
@@ -132,11 +140,12 @@ DhtManager::save_dht_cache() {
torrent::Object cache = torrent::Object::create_map(); torrent::Object cache = torrent::Object::create_map();
cache_file << *torrent::runtime::network_manager()->dht_controller()->store_cache(&cache); cache_file << *torrent::runtime::network_manager()->dht_controller()->store_cache(&cache);
// The data only reaches the kernel here, so this is where a full disk is seen.
cache_file.close();
if (!cache_file.good()) if (!cache_file.good())
return; return;
cache_file.close();
::rename(filename_tmp.c_str(), filename.c_str()); ::rename(filename_tmp.c_str(), filename.c_str());
} }
+13 -3
View File
@@ -101,7 +101,18 @@ Download::set_throttle_name(const std::string& name) {
} }
void 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 // If the download is open, hashed and has completed chunks make
// sure to verify that the download files are still present. // 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()))) { !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."); 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); 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."); 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)); file_list->set_root_dir(expand_path(path));
bencode()->get_key("rtorrent").insert_key("directory", path); bencode()->get_key("rtorrent").insert_key("directory", path);
+12 -1
View File
@@ -51,6 +51,14 @@ public:
bool is_hash_checking() const { return m_download.is_hash_checking(); } bool is_hash_checking() const { return m_download.is_hash_checking(); }
bool is_hash_failed() const { return m_hashFailed; } bool is_hash_failed() const { return m_hashFailed; }
// Expires once the download is erased, even if other owners keep the
// object alive. Take it before triggering events that may erase.
std::weak_ptr<void> lifetime() const { return m_lifetime; }
void release_lifetime() { m_lifetime.reset(); }
bool is_erasing() const { return m_erasing; }
void set_erasing() { m_erasing = true; }
void set_hash_failed(bool v) { m_hashFailed = v; } void set_hash_failed(bool v) { m_hashFailed = v; }
download_type* download() { return &m_download; } download_type* download() { return &m_download; }
@@ -79,7 +87,8 @@ public:
uint32_t resume_flags() { return m_resumeFlags; } uint32_t resume_flags() { return m_resumeFlags; }
void set_resume_flags(uint32_t flags) { m_resumeFlags = flags; } 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); void set_throttle_name(const std::string& name);
@@ -102,6 +111,8 @@ private:
// Store the FileList instance so we can use slots etc on it. // Store the FileList instance so we can use slots etc on it.
download_type m_download; download_type m_download;
bool m_hashFailed{}; bool m_hashFailed{};
bool m_erasing{};
std::shared_ptr<void> m_lifetime{std::make_shared<char>()};
std::string m_message; std::string m_message;
uint32_t m_resumeFlags{default_resume_flags}; uint32_t m_resumeFlags{default_resume_flags};
unsigned int m_group{}; unsigned int m_group{};
+18 -4
View File
@@ -7,6 +7,7 @@
#include <functional> #include <functional>
#include <sstream> #include <sstream>
#include <stdexcept> #include <stdexcept>
#include <sys/stat.h>
#include <torrent/utils/log.h> #include <torrent/utils/log.h>
#include <torrent/utils/resume.h> #include <torrent/utils/resume.h>
#include <torrent/object.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> static std::unique_ptr<torrent::Object>
download_factory_load_stream(const char* filename, bool* is_invalid) { 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); std::fstream stream(filename, std::ios::in | std::ios::binary);
if (!stream.is_open()) if (!stream.is_open())
@@ -167,11 +173,19 @@ void
DownloadFactory::receive_success() { DownloadFactory::receive_success() {
bool session_invalid = false; bool session_invalid = false;
auto rtorrent_object = download_factory_load_stream((expand_path(m_uri) + ".rtorrent").c_str(), &session_invalid); std::unique_ptr<torrent::Object> rtorrent_object;
auto libtorrent_resume_object = download_factory_load_stream((expand_path(m_uri) + ".libtorrent_resume").c_str(), &session_invalid); std::unique_ptr<torrent::Object> libtorrent_resume_object;
if (session_invalid) if (m_session) {
lt_log_print(torrent::LOG_ERROR, "%s: %s", session_invalid_message, m_uri.c_str()); 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; uint32_t tracker_key;
+105 -18
View File
@@ -194,6 +194,12 @@ DownloadList::erase(iterator itr) {
if (itr == end()) if (itr == end())
throw torrent::internal_error("DownloadList::erase(...) could not find download."); throw torrent::internal_error("DownloadList::erase(...) could not find download.");
// An event handler below may erase the same download again.
if ((*itr)->is_erasing())
return std::next(itr);
(*itr)->set_erasing();
lt_log_print_info(torrent::LOG_TORRENT_INFO, (*itr)->info(), "download_list", "Erasing download."); lt_log_print_info(torrent::LOG_TORRENT_INFO, (*itr)->info(), "download_list", "Erasing download.");
// Makes sure close doesn't restart hashing of this download. // Makes sure close doesn't restart hashing of this download.
@@ -207,6 +213,7 @@ DownloadList::erase(iterator itr) {
for (auto v : *control->view_manager()) for (auto v : *control->view_manager())
v->erase(itr->get()); v->erase(itr->get());
(*itr)->release_lifetime();
torrent::download_remove(*(*itr)->download()); torrent::download_remove(*(*itr)->download());
return base_type::erase(itr); return base_type::erase(itr);
@@ -254,9 +261,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 void
DownloadList::close_directly(Download* download) { DownloadList::close_files(Download* download) {
lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Closing download directly."); lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Closing download files.");
if (download->download()->info()->is_active()) { if (download->download()->info()->is_active()) {
download->download()->stop(torrent::Download::stop_skip_tracker); download->download()->stop(torrent::Download::stop_skip_tracker);
@@ -269,6 +278,53 @@ DownloadList::close_directly(Download* download) {
download->download()->close(); download->download()->close();
} }
void
DownloadList::close_directly(Download* download) {
lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Closing download directly.");
auto lifetime = download->lifetime();
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");
if (lifetime.expired())
return;
update_paused_state(download);
}
if (was_open) {
DL_TRIGGER_EVENT(download, "event.download.hash_removed");
if (lifetime.expired())
return;
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)) + 1, 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 void
DownloadList::close_quick(Download* download) { DownloadList::close_quick(Download* download) {
lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Closing download quickly."); lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Closing download quickly.");
@@ -290,8 +346,13 @@ DownloadList::close_throw(Download* download) {
// When pause gets called it will clear the initial hash check state // When pause gets called it will clear the initial hash check state
// and set hash failed. This should ensure hashing doesn't restart // and set hash failed. This should ensure hashing doesn't restart
// until resume gets called. // until resume gets called.
auto lifetime = download->lifetime();
pause(download); pause(download);
if (lifetime.expired())
return;
// Check for is_open after pause due to hashing. // Check for is_open after pause due to hashing.
if (!download->is_open()) if (!download->is_open())
return; return;
@@ -311,6 +372,10 @@ DownloadList::close_throw(Download* download) {
throw torrent::internal_error("DownloadList::close_throw(...) called but we're going into a hashing loop."); throw torrent::internal_error("DownloadList::close_throw(...) called but we're going into a hashing loop.");
DL_TRIGGER_EVENT(download, "event.download.hash_removed"); DL_TRIGGER_EVENT(download, "event.download.hash_removed");
if (lifetime.expired())
return;
DL_TRIGGER_EVENT(download, "event.download.closed"); DL_TRIGGER_EVENT(download, "event.download.closed");
} }
@@ -417,6 +482,8 @@ DownloadList::pause(Download* download, int flags) {
lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Pausing download: flags:%0x.", flags); lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Pausing download: flags:%0x.", flags);
auto lifetime = download->lifetime();
try { try {
download->set_resume_flags(Download::default_resume_flags); download->set_resume_flags(Download::default_resume_flags);
@@ -430,6 +497,9 @@ DownloadList::pause(Download* download, int flags) {
rpc::call_command_set_value("d.hashing.set", Download::variable_hashing_stopped, rpc::make_target(download)); rpc::call_command_set_value("d.hashing.set", Download::variable_hashing_stopped, rpc::make_target(download));
DL_TRIGGER_EVENT(download, "event.download.hash_removed"); DL_TRIGGER_EVENT(download, "event.download.hash_removed");
if (lifetime.expired())
return;
} }
if (!download->download()->info()->is_active()) if (!download->download()->info()->is_active())
@@ -443,15 +513,10 @@ DownloadList::pause(Download* download, int flags) {
// view. // view.
DL_TRIGGER_EVENT(download, "event.download.paused"); DL_TRIGGER_EVENT(download, "event.download.paused");
auto cached_seconds = torrent::this_thread::cached_seconds().count(); if (lifetime.expired())
return;
rpc::call_command("d.state_changed.set", cached_seconds, rpc::make_target(download)); update_paused_state(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));
// Save the state after all the slots, etc have been called so we // Save the state after all the slots, etc have been called so we
// include the modifications they may make. // include the modifications they may make.
@@ -522,7 +587,7 @@ DownloadList::hash_done(Download* download) {
// If the download was previously completed but the files were // If the download was previously completed but the files were
// f.ex deleted, then we clear the state and complete. // f.ex deleted, then we clear the state and complete.
if (rpc::call_command_value("d.complete", rpc::make_target(download)) && !download->is_done()) { 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."); download->set_message("Download registered as completed, but hash check returned unfinished chunks.");
} }
@@ -531,9 +596,15 @@ DownloadList::hash_done(Download* download) {
rpc::call_command("d.complete.set", (int64_t)download->is_done(), rpc::make_target(download)); rpc::call_command("d.complete.set", (int64_t)download->is_done(), rpc::make_target(download));
torrent::resume_save_progress(*download->download(), download->download()->bencode()->get_key("libtorrent_resume")); torrent::resume_save_progress(*download->download(), download->download()->bencode()->get_key("libtorrent_resume"));
if (rpc::call_command_value("d.state", rpc::make_target(download)) == 1) if (rpc::call_command_value("d.state", rpc::make_target(download)) == 1) {
auto lifetime = download->lifetime();
resume(download, download->resume_flags()); resume(download, download->resume_flags());
if (lifetime.expired())
return;
}
break; break;
case Download::variable_hashing_last: case Download::variable_hashing_last:
@@ -570,11 +641,24 @@ DownloadList::hash_queue(Download* download, int type) {
// HACK // HACK
if (download->is_open()) { if (download->is_open()) {
auto lifetime = download->lifetime();
pause(download, torrent::Download::stop_skip_tracker); pause(download, torrent::Download::stop_skip_tracker);
if (lifetime.expired())
return;
download->download()->close(); download->download()->close();
DL_TRIGGER_EVENT(download, "event.download.hash_removed"); DL_TRIGGER_EVENT(download, "event.download.hash_removed");
if (lifetime.expired())
return;
DL_TRIGGER_EVENT(download, "event.download.closed"); DL_TRIGGER_EVENT(download, "event.download.closed");
if (lifetime.expired())
return;
} }
torrent::resume_clear_progress(*download->download(), download->download()->bencode()->get_key("libtorrent_resume")); torrent::resume_clear_progress(*download->download(), download->download()->bencode()->get_key("libtorrent_resume"));
@@ -625,6 +709,10 @@ DownloadList::confirm_finished(Download* download) {
if (choke_up.is_string_empty()) choke_up = rpc::call_command("protocol.choke_heuristics.up.seed", torrent::Object(), rpc::make_target(download)); if (choke_up.is_string_empty()) choke_up = rpc::call_command("protocol.choke_heuristics.up.seed", torrent::Object(), rpc::make_target(download));
if (choke_down.is_string_empty()) choke_down = rpc::call_command("protocol.choke_heuristics.down.seed", torrent::Object(), rpc::make_target(download)); if (choke_down.is_string_empty()) choke_down = rpc::call_command("protocol.choke_heuristics.down.seed", torrent::Object(), rpc::make_target(download));
// libtorrent refuses initial seeding on an active download.
if (download->is_active() && conn_current.as_string() == "initial_seed")
conn_current = "seed";
rpc::call_command("d.connection_current.set", conn_current, rpc::make_target(download)); rpc::call_command("d.connection_current.set", conn_current, rpc::make_target(download));
rpc::call_command("d.up.choke_heuristics.set", choke_up, rpc::make_target(download)); rpc::call_command("d.up.choke_heuristics.set", choke_up, rpc::make_target(download));
rpc::call_command("d.down.choke_heuristics.set", choke_down, rpc::make_target(download)); rpc::call_command("d.down.choke_heuristics.set", choke_down, rpc::make_target(download));
@@ -650,12 +738,12 @@ DownloadList::confirm_finished(Download* download) {
// up/downloaded baseline. // up/downloaded baseline.
download->download()->send_completed(); download->download()->send_completed();
// Save the hash in case the finished event erases it. // The finished event may erase the download.
torrent::HashString infohash = download->info()->hash(); auto lifetime = download->lifetime();
DL_TRIGGER_EVENT(download, "event.download.finished"); DL_TRIGGER_EVENT(download, "event.download.finished");
if (find(infohash) == end()) if (lifetime.expired())
return; return;
// if (download->resume_flags() != Download::default_resume_flags) // if (download->resume_flags() != Download::default_resume_flags)
@@ -694,11 +782,10 @@ DownloadList::process_meta_download(Download* download) {
return; return;
} }
torrent::Object* bencode = new torrent::Object(torrent::Object::create_map()); auto bencode = std::make_unique<torrent::Object>(torrent::Object::create_map());
file >> bencode->insert_key("info", torrent::Object()); file >> bencode->insert_key("info", torrent::Object());
if (file.fail()) { if (file.fail()) {
delete bencode;
lt_log_print(torrent::LOG_TORRENT_ERROR, "Could not create download, the input is not a valid torrent."); lt_log_print(torrent::LOG_TORRENT_ERROR, "Could not create download, the input is not a valid torrent.");
return; return;
} }
@@ -714,7 +801,7 @@ DownloadList::process_meta_download(Download* download) {
erase_ptr(download); erase_ptr(download);
control->core()->try_create_download_from_meta_download(bencode, metafile); control->core()->try_create_download_from_meta_download(std::move(bencode), metafile);
} }
} }
+4
View File
@@ -75,6 +75,7 @@ public:
void close(Download* d); void close(Download* d);
void close_directly(Download* d); void close_directly(Download* d);
void close_files(Download* d);
void close_quick(Download* d); void close_quick(Download* d);
void close_throw(Download* d); void close_throw(Download* d);
@@ -132,6 +133,9 @@ private:
void hash_done(Download* d); void hash_done(Download* d);
void hash_queue(Download* d, int type); 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); inline void check_contains(Download* d);
void received_finished(Download* d); void received_finished(Download* d);
+6 -3
View File
@@ -195,14 +195,14 @@ Manager::try_create_download(const std::string& uri, int flags, const command_li
return; return;
// Adding download. // Adding download.
DownloadFactory* f = new DownloadFactory(this); auto f = std::make_unique<DownloadFactory>(this);
f->variables()["tied_to_file"] = (int64_t)(bool)(flags & create_tied); f->variables()["tied_to_file"] = (int64_t)(bool)(flags & create_tied);
f->commands().insert(f->commands().end(), commands.begin(), commands.end()); f->commands().insert(f->commands().end(), commands.begin(), commands.end());
f->set_start(flags & create_start); f->set_start(flags & create_start);
f->set_print_log(!(flags & create_quiet)); f->set_print_log(!(flags & create_quiet));
f->slot_finished([f]() { delete f; }); f->slot_finished([factory = f.get()]() { delete factory; });
if (is_data_uri(uri)) { if (is_data_uri(uri)) {
// Allow the use of data URIs, primarily for JSON-RPC which // Allow the use of data URIs, primarily for JSON-RPC which
@@ -216,10 +216,13 @@ Manager::try_create_download(const std::string& uri, int flags, const command_li
} }
f->commit(); f->commit();
// From here the finished slot deletes it.
f.release();
} }
void void
Manager::try_create_download_from_meta_download(torrent::Object* bencode, const std::string& metafile) { Manager::try_create_download_from_meta_download(std::unique_ptr<torrent::Object> bencode, const std::string& metafile) {
DownloadFactory* f = new DownloadFactory(this); DownloadFactory* f = new DownloadFactory(this);
f->variables()["tied_to_file"] = (int64_t)true; f->variables()["tied_to_file"] = (int64_t)true;
+1 -2
View File
@@ -8,7 +8,6 @@
#include <torrent/object.h> #include <torrent/object.h>
#include "download_list.h" #include "download_list.h"
#include "range_map.h"
namespace torrent { namespace torrent {
class Bencode; class Bencode;
@@ -76,7 +75,7 @@ public:
// Temporary, find a better place for this. // Temporary, find a better place for this.
void try_create_download(const std::string& uri, int flags, const command_list_type& commands); void try_create_download(const std::string& uri, int flags, const command_list_type& commands);
void try_create_download_expand(const std::string& uri, int flags, command_list_type commands = command_list_type()); void try_create_download_expand(const std::string& uri, int flags, command_list_type commands = command_list_type());
void try_create_download_from_meta_download(torrent::Object* bencode, const std::string& metafile); void try_create_download_from_meta_download(std::unique_ptr<torrent::Object> bencode, const std::string& metafile);
private: private:
void create_http(const std::string& uri); void create_http(const std::string& uri);
-199
View File
@@ -1,199 +0,0 @@
// rTorrent - BitTorrent client
// Copyright (C) 2005-2008, Jari Sundell
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// In addition, as a special exception, the copyright holders give
// permission to link the code of portions of this program with the
// OpenSSL library under certain conditions as described in each
// individual source file, and distribute linked combinations
// including the two.
//
// You must obey the GNU General Public License in all respects for
// all of the code used other than OpenSSL. If you modify file(s)
// with this exception, you may extend this exception to your version
// of the file(s), but you are not obligated to do so. If you do not
// wish to do so, delete this exception statement from your version.
// If you delete this exception statement from all source files in the
// program, then also delete it here.
//
// Contact: Jari Sundell <sundell.software@gmail.com>
#ifndef RTORRENT_CORE_RANGE_MAP_H
#define RTORRENT_CORE_RANGE_MAP_H
#include <map>
#include <stdexcept>
namespace core {
// Associate values with a range of keys, and retrieve for any key in the range.
// The template arguments have the same semantics as std::map.
// Exception: if set_merge is used, the value type must have a defined operator ==.
template<typename Key, typename T, typename Compare = std::less<Key>,
typename Alloc = std::allocator<std::pair<const Key, T> > >
class RangeMap : private std::map<Key, std::pair<Key, T>, Compare,
typename std::allocator_traits<Alloc>::template rebind_alloc<std::pair<const Key, std::pair<Key, T>>>> {
typedef std::map<Key, std::pair<Key, T>, Compare,
typename std::allocator_traits<Alloc>::template rebind_alloc<std::pair<const Key, std::pair<Key, T>>>> base_type;
//std::allocator_traits<Alloc>::template rebind_alloc<std::pair<const Key, std::pair<Key, T>>>
public:
RangeMap() = default;
RangeMap(const Compare& c) : base_type(c) {}
typedef typename base_type::iterator iterator;
typedef typename base_type::reverse_iterator reverse_iterator;
typedef typename base_type::const_iterator const_iterator;
typedef typename base_type::const_reverse_iterator const_reverse_iterator;
// using typename base_type::const_iterator;
// using typename base_type::const_reverse_iterator;
using base_type::clear;
using base_type::swap;
using base_type::size;
using base_type::empty;
using base_type::begin;
using base_type::end;
using base_type::rbegin;
using base_type::rend;
using base_type::key_comp;
using base_type::value_comp;
// Store a value for the range [begin, end). Returns iterator for the range.
const_iterator set_range(const Key& begin, const Key& end, const T& value);
// Same, but merge adjacent ranges having the same value. Returns iterator for the merged range.
const_iterator set_merge(Key begin, const Key& end, const T& value);
// Find range containing the given key, or end().
const_iterator find(const Key& key) const;
// Retrieve value for key in a range, throw std::out_of_range if range does not exist.
const T& get(const Key& key) const;
// Retrieve value for key in a range, return def if range does not exist.
T get(const Key& key, T def) const;
private:
iterator crop_overlap(const Key& begin, const Key& end);
};
// Semantics of an entry:
// .first End of range (exclusive), map key.
// .second.first Beginning of range.
// .second.second Value.
template<typename Key, typename T, typename C, typename A>
inline typename RangeMap<Key,T,C,A>::iterator
RangeMap<Key,T,C,A>::crop_overlap(const Key& _begin, const Key& _end) {
typename RangeMap::iterator itr = base_type::upper_bound(_begin);
while (itr != end() && key_comp()(itr->second.first, _end)) {
// There's a subrange before the new begin: need new entry (new range end means new key).
if (key_comp()(itr->second.first, _begin))
base_type::insert(itr, typename RangeMap::value_type(_begin, itr->second));
// Old end is within our range: erase entry.
if (!key_comp()(_end, itr->first)) {
base_type::erase(itr++);
// Otherwise simply set the new begin of the old range.
} else {
itr->second.first = _end;
++itr;
}
}
return itr;
}
template<typename Key, typename T, typename C, typename A>
inline typename RangeMap<Key,T,C,A>::const_iterator
RangeMap<Key,T,C,A>::set_merge(Key _begin, const Key& _end, const T& value) {
if (!key_comp()(_begin, _end))
return end();
// Crop overlapping ranges and return iterator to first range after the one we're inserting.
typename RangeMap::iterator itr = crop_overlap(_begin, _end);
// Check if range before new one is adjacent and has same value: if so erase it and use its beginning.
if (itr != begin()) {
typename RangeMap::iterator prev = itr;
if (!key_comp()((--prev)->first, _begin) && prev->second.second == value) {
_begin = prev->second.first;
base_type::erase(prev);
}
}
// Range after new one is adjacent and has same value: set new beginning.
if (itr != end() && !key_comp()(_end, itr->second.first) && itr->second.second == value) {
itr->second.first = _begin;
return itr;
}
// Otherwise, this range isn't mergeable, make new entry.
return base_type::insert(itr, typename RangeMap::value_type(_end, typename RangeMap::mapped_type(_begin, value)));
}
template<typename Key, typename T, typename C, typename A>
inline typename RangeMap<Key,T,C,A>::const_iterator
RangeMap<Key,T,C,A>::set_range(const Key& _begin, const Key& _end, const T& value) {
if (!key_comp()(_begin, _end))
return end();
return base_type::insert(crop_overlap(_begin, _end), typename RangeMap::value_type(_end, typename RangeMap::mapped_type(_begin, value)));
}
template<typename Key, typename T, typename C, typename A>
inline typename RangeMap<Key,T,C,A>::const_iterator
RangeMap<Key,T,C,A>::find(const Key& key) const {
typename RangeMap::const_iterator itr = base_type::upper_bound(key);
if (itr != end() && key_comp()(key, itr->second.first))
itr = end();
return itr;
}
template<typename Key, typename T, typename C, typename A>
inline const T&
RangeMap<Key,T,C,A>::get(const Key& key) const {
typename RangeMap::const_iterator itr = find(key);
if (itr == end())
throw std::out_of_range("RangeMap::get");
return itr->second.second;
}
template<typename Key, typename T, typename C, typename A>
inline T
RangeMap<Key,T,C,A>::get(const Key& key, T def) const {
typename RangeMap::const_iterator itr = find(key);
return (itr == end() ? def : itr->second.second);
}
}
#endif
+3 -3
View File
@@ -216,7 +216,7 @@ View::set_not_visible(Download* download) {
void void
View::next_focus(unsigned int i) { View::next_focus(unsigned int i) {
if (empty()) if (empty_visible())
return; return;
// If at the boundary, roll over // If at the boundary, roll over
@@ -239,7 +239,7 @@ View::next_focus(unsigned int i) {
void void
View::prev_focus(unsigned int i) { View::prev_focus(unsigned int i) {
if (empty()) if (empty_visible())
return; return;
// If at the boundary, roll over // If at the boundary, roll over
@@ -301,7 +301,7 @@ View::filter() {
std::for_each(changed.begin(), splitChanged, [this](const auto& d) { rpc::call_object_d_nothrow(m_event_removed, d.get()); }); 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()) if (!m_event_added.is_empty())
std::for_each(changed.begin(), splitChanged, [this](const auto& d) { rpc::call_object_d_nothrow(m_event_added, d.get()); }); std::for_each(splitChanged, changed.end(), [this](const auto& d) { rpc::call_object_d_nothrow(m_event_added, d.get()); });
emit_changed(); emit_changed();
} }
-6
View File
@@ -16,15 +16,12 @@ namespace utils {
namespace torrent { namespace torrent {
class ClientInfo; class ClientInfo;
class Entry;
} }
class Control; class Control;
namespace display { namespace display {
char* print_string(char* first, char* last, char* str);
char* print_hhmmss(char* first, char* last, time_t t); char* print_hhmmss(char* first, char* last, time_t t);
char* print_hhmmss_local(char* first, char* last, time_t t); char* print_hhmmss_local(char* first, char* last, time_t t);
char* print_ddhhmm(char* first, char* last, time_t t); char* print_ddhhmm(char* first, char* last, time_t t);
@@ -42,9 +39,6 @@ char* print_download_percentage_done(char* first, char* last, core::Downlo
char* print_client_version(char* first, char* last, const torrent::ClientInfo& clientInfo); char* print_client_version(char* first, char* last, const torrent::ClientInfo& clientInfo);
char* print_entry_tags(char* first, char* last);
char* print_entry_file(char* first, char* last, const torrent::Entry& entry);
char* print_status_throttle_limit(char* first, char* last, bool up, const std::vector<std::string>& throttle_names); char* print_status_throttle_limit(char* first, char* last, bool up, const std::vector<std::string>& throttle_names);
char* print_status_throttle_rate(char* first, char* last, bool up, const std::vector<std::string>& throttle_names, const double& global_rate); char* print_status_throttle_rate(char* first, char* last, bool up, const std::vector<std::string>& throttle_names, const double& global_rate);
+2 -1
View File
@@ -57,7 +57,8 @@ WindowDownloadChunksSeen::redraw() {
const torrent::TransferList* transfers = m_download->download()->transfer_list(); const torrent::TransferList* transfers = m_download->download()->transfer_list();
std::vector<torrent::BlockList*> transferChunks(transfers->begin(), transfers->end()); std::vector<torrent::BlockList*> transferChunks(transfers->begin(), transfers->end());
std::sort(transferChunks.begin(), transferChunks.end()); std::sort(transferChunks.begin(), transferChunks.end(),
[](const auto& left, const auto& right) { return left->index() < right->index(); });
std::vector<torrent::BlockList*>::const_iterator itrTransfer = transferChunks.begin(); std::vector<torrent::BlockList*>::const_iterator itrTransfer = transferChunks.begin();
+4 -1
View File
@@ -25,7 +25,10 @@ WindowFileList::WindowFileList(const ui::ElementFileList* element) :
// Convert std::string to std::wstring of given width (in screen positions), // Convert std::string to std::wstring of given width (in screen positions),
// taking into account that some characters may be occupying two screen positions. // taking into account that some characters may be occupying two screen positions.
std::wstring std::wstring
wstring_width(const std::string& i_str, [[maybe_unused]] int width) { wstring_width(const std::string& i_str, int width) {
if (width < 0)
width = 0;
std::wstring result(width + 1, L' '); std::wstring result(width + 1, L' ');
size_t length = std::mbstowcs(result.data(), i_str.c_str(), width); size_t length = std::mbstowcs(result.data(), i_str.c_str(), width);
+20 -6
View File
@@ -1,6 +1,8 @@
#include "config.h" #include "config.h"
#include <ctime> #include <ctime>
#include <mutex>
#include <vector>
#include <torrent/torrent.h> #include <torrent/torrent.h>
#include <torrent/system/callbacks.h> #include <torrent/system/callbacks.h>
#include <torrent/system/thread.h> #include <torrent/system/thread.h>
@@ -41,13 +43,20 @@ WindowLog::redraw() {
int pos = m_canvas->height(); int pos = m_canvas->height();
for (iterator itr = m_log->end(), last = find_older(); itr != last && pos > 0; --pos) { std::vector<torrent::log_entry> entries;
itr--;
{
std::lock_guard<torrent::log_buffer> guard(*m_log);
for (iterator itr = m_log->end(), last = find_older(); itr != last && entries.size() < static_cast<size_t>(pos); )
entries.push_back(*--itr);
}
for (const auto& entry : entries) {
char buffer[16]; char buffer[16];
print_hhmmss_local(buffer, buffer + 16, static_cast<time_t>(itr->timestamp)); print_hhmmss_local(buffer, buffer + 16, static_cast<time_t>(entry.timestamp));
m_canvas->print(0, pos - 1, "(%s) %s", buffer, itr->message.c_str()); m_canvas->print(0, --pos, "(%s) %s", buffer, entry.message.c_str());
} }
} }
@@ -60,8 +69,13 @@ WindowLog::receive_update() {
if (!is_active()) if (!is_active())
return; return;
auto itr = find_older(); std::iterator_traits<iterator>::difference_type height;
auto height = std::min(std::distance(itr, (iterator)m_log->end()), (std::iterator_traits<iterator>::difference_type)10);
{
std::lock_guard<torrent::log_buffer> guard(*m_log);
height = std::min(std::distance(find_older(), (iterator)m_log->end()), (std::iterator_traits<iterator>::difference_type)10);
}
if (height != m_max_height) { if (height != m_max_height) {
m_min_height = height != 0 ? 1 : 0; m_min_height = height != 0 ? 1 : 0;
+18 -6
View File
@@ -1,6 +1,8 @@
#include "config.h" #include "config.h"
#include <ctime> #include <ctime>
#include <mutex>
#include <vector>
#include "canvas.h" #include "canvas.h"
#include "utils.h" #include "utils.h"
@@ -29,24 +31,34 @@ WindowLogComplete::redraw() {
int pos = m_canvas->height(); int pos = m_canvas->height();
for (iterator itr = m_log->end(), last = m_log->begin(); itr != last && pos > 0; ) { std::vector<torrent::log_entry> entries;
itr--;
{
std::lock_guard<torrent::log_buffer> guard(*m_log);
for (iterator itr = m_log->end(), last = m_log->begin(); itr != last && entries.size() < static_cast<size_t>(pos); )
entries.push_back(*--itr);
}
for (const auto& entry : entries) {
if (pos <= 0)
break;
char buffer[16]; char buffer[16];
// Use an arbitrary min width of 60 for allowing multiple // Use an arbitrary min width of 60 for allowing multiple
// lines. This should ensure we don't mess up the display when the // lines. This should ensure we don't mess up the display when the
// screen is shrunk too much. // screen is shrunk too much.
unsigned int timeWidth = 3 + print_hhmmss_local(buffer, buffer + 16, static_cast<time_t>(itr->timestamp)) - buffer; unsigned int timeWidth = 3 + print_hhmmss_local(buffer, buffer + 16, static_cast<time_t>(entry.timestamp)) - buffer;
unsigned int logWidth = m_canvas->width() > 60 ? (m_canvas->width() - timeWidth) : (60 - timeWidth); unsigned int logWidth = m_canvas->width() > 60 ? (m_canvas->width() - timeWidth) : (60 - timeWidth);
unsigned int logHeight = (itr->message.size() + logWidth - 1) / logWidth; unsigned int logHeight = (entry.message.size() + logWidth - 1) / logWidth;
for (unsigned int j = logHeight; j > 0 && pos > 0; --j, --pos) for (unsigned int j = logHeight; j > 0 && pos > 0; --j, --pos)
if (j == 1) if (j == 1)
m_canvas->print(0, pos - 1, "(%s) %s", buffer, itr->message.substr(0, m_canvas->width() - timeWidth).c_str()); m_canvas->print(0, pos - 1, "(%s) %s", buffer, entry.message.substr(0, m_canvas->width() - timeWidth).c_str());
else else
m_canvas->print(timeWidth, pos - 1, "%s", itr->message.substr(logWidth * (j - 1), m_canvas->width() - timeWidth).c_str()); m_canvas->print(timeWidth, pos - 1, "%s", entry.message.substr(logWidth * (j - 1), m_canvas->width() - timeWidth).c_str());
} }
} }
-2
View File
@@ -69,8 +69,6 @@ public:
bool pressed(int key); bool pressed(int key);
void ignore(int key) { (*this)[key] = slot_void(); }
private: private:
bool m_enabled{true}; bool m_enabled{true};
}; };
+1 -1
View File
@@ -100,7 +100,7 @@ PathInput::receive_do_complete() {
entry.s_name += '/'; entry.s_name += '/';
} }
range_type r = find_incomplete(dir, str().substr(dirEnd, get_pos())); range_type r = find_incomplete(dir, str().substr(dirEnd, get_pos() - dirEnd));
if (r.first == r.second) if (r.first == r.second)
return; // Show some nice colors here. return; // Show some nice colors here.
+10 -4
View File
@@ -379,6 +379,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."); 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) { // if (rpc::call_command_value("method.use_intermediate") == 1) {
// } else if (rpc::call_command_value("method.use_intermediate") == 2) { // } else if (rpc::call_command_value("method.use_intermediate") == 2) {
@@ -486,7 +492,8 @@ main(int argc, char** argv) {
control->cleanup(); control->cleanup();
} catch (torrent::internal_error& e) { } catch (torrent::internal_error& e) {
control->cleanup_exception(); if (control != nullptr)
control->cleanup_exception();
std::cout << "rtorrent: caught torrent::internal_error: " std::cout << "rtorrent: caught torrent::internal_error: "
<< e.what() << std::endl << e.what() << std::endl
@@ -499,7 +506,8 @@ main(int argc, char** argv) {
return -1; return -1;
} catch (std::exception& e) { } catch (std::exception& e) {
control->cleanup_exception(); if (control != nullptr)
control->cleanup_exception();
std::cout << "rtorrent: caught" << typeid(e).name() << " : " << e.what() << std::endl; std::cout << "rtorrent: caught" << typeid(e).name() << " : " << e.what() << std::endl;
@@ -544,8 +552,6 @@ handle_sigbus(int signum, siginfo_t* sa, [[maybe_unused]] void* ptr) {
#else #else
output << "Stack dump not enabled." << std::endl; output << "Stack dump not enabled." << std::endl;
#endif #endif
output << std::endl << "Error: " << std::strerror(sa->si_errno) << std::endl;
const char* signal_reason; const char* signal_reason;
switch (sa->si_code) { switch (sa->si_code) {
+7 -1
View File
@@ -61,14 +61,20 @@ CommandScheduler::call_item(value_type item) {
// Remove the item before calling the command if it should be // Remove the item before calling the command if it should be
// removed. // removed.
std::string key = item->key();
try { try {
rpc::call_object(item->command()); rpc::call_object(item->command());
} catch (torrent::input_error& e) { } catch (torrent::input_error& e) {
if (m_slotErrorMessage) if (m_slotErrorMessage)
m_slotErrorMessage("Scheduled command failed: " + item->key() + ": " + e.what()); m_slotErrorMessage("Scheduled command failed: " + key + ": " + e.what());
} }
// The command is allowed to erase or replace this item, which deletes it.
if (std::find(begin(), end(), item) == end())
return;
// Still schedule if we caught a torrrent::input_error? // Still schedule if we caught a torrrent::input_error?
auto next = item->next_time_scheduled(); auto next = item->next_time_scheduled();
+47 -1
View File
@@ -2,8 +2,10 @@
#include "rpc/jsonrpc.h" #include "rpc/jsonrpc.h"
#include <cstddef>
#include <cstdint> #include <cstdint>
#include <string> #include <string>
#include <utility>
#include <torrent/common.h> #include <torrent/common.h>
#include <torrent/torrent.h> #include <torrent/torrent.h>
#include <torrent/utils/string_manip.h> #include <torrent/utils/string_manip.h>
@@ -13,6 +15,7 @@
#include "rpc/command_map.h" #include "rpc/command_map.h"
#include "rpc/nlohmann/json.h" #include "rpc/nlohmann/json.h"
#include "rpc/parse_commands.h" #include "rpc/parse_commands.h"
#include "rpc/scgi_task.h"
#include "torrent/exceptions.h" #include "torrent/exceptions.h"
#include "torrent/object.h" #include "torrent/object.h"
#include "utils/functional.h" #include "utils/functional.h"
@@ -222,13 +225,49 @@ handle_notification(const json& request) noexcept {
} }
} }
namespace {
using json_input_adapter = decltype(nlohmann::detail::input_adapter(std::declval<const char*>(), std::declval<const char*>()));
using json_dom_parser = nlohmann::detail::json_sax_dom_parser<json, json_input_adapter>;
class json_depth_limited_parser : public json_dom_parser {
public:
explicit json_depth_limited_parser(json& root) : json_dom_parser(root) {}
bool start_object(std::size_t length) { return enter() && json_dom_parser::start_object(length); }
bool start_array(std::size_t length) { return enter() && json_dom_parser::start_array(length); }
bool end_object() { m_depth--; return json_dom_parser::end_object(); }
bool end_array() { m_depth--; return json_dom_parser::end_array(); }
private:
bool enter() { return ++m_depth <= max_json_depth; }
uint32_t m_depth{0};
};
} // namespace
bool bool
JsonRpc::process(const char* in_buffer, uint32_t length, slot_write callback) { JsonRpc::process(const char* in_buffer, uint32_t length, slot_write callback) {
json response; json response;
json body; json body;
if (length > m_size_limit) {
auto err_str = json_error(JSONRPC_INVALID_REQUEST_ERROR, "content size exceeds maximum RPC limit", nullptr).dump();
return callback(err_str.c_str(), err_str.size());
}
try { try {
body = json::parse(in_buffer, in_buffer + length); json_depth_limited_parser handler(body);
if (!json::sax_parse(in_buffer, in_buffer + length, &handler)) {
auto err_str = json_error(JSONRPC_INVALID_REQUEST_ERROR, "maximum nesting depth exceeded", nullptr).dump();
return callback(err_str.c_str(), err_str.size());
}
switch (body.type()) { switch (body.type()) {
case json::value_t::object: { case json::value_t::object: {
if (!body.contains("id")) { if (!body.contains("id")) {
@@ -264,6 +303,13 @@ JsonRpc::process(const char* in_buffer, uint32_t length, slot_write callback) {
std::string response_str = response.dump(); std::string response_str = response.dump();
if (response_str.size() > SCgiTask::max_response_size) {
const auto& id = response.is_object() && response.contains("id") ? response["id"] : json(nullptr);
auto err_str = json_error(JSONRPC_INTERNAL_ERROR, "response size exceeds maximum RPC limit", id).dump();
return callback(err_str.c_str(), err_str.size());
}
return callback(response_str.c_str(), response_str.size()); return callback(response_str.c_str(), response_str.size());
} catch (json::exception& e) { } catch (json::exception& e) {
+7
View File
@@ -5,6 +5,8 @@
#include <cstdint> #include <cstdint>
#include "rpc/scgi_task.h"
namespace rpc { namespace rpc {
class JsonRpc { class JsonRpc {
@@ -17,6 +19,11 @@ public:
bool process(const char* in_buffer, uint32_t length, slot_write callback); bool process(const char* in_buffer, uint32_t length, slot_write callback);
void insert_command(const char* name, const char* parm, const char* doc) {}; void insert_command(const char* name, const char* parm, const char* doc) {};
void set_size_limit(uint64_t size) { m_size_limit = size; }
private:
uint64_t m_size_limit{SCgiTask::max_content_size};
}; };
} // namespace rpc } // namespace rpc
+4
View File
@@ -394,6 +394,10 @@ execute_lua(LuaEngine* engine, rpc::target_type target_type, torrent::Object con
switch (raw_args.type()) { switch (raw_args.type()) {
case torrent::Object::TYPE_LIST: { case torrent::Object::TYPE_LIST: {
const torrent::Object::list_type& args = raw_args.as_list(); const torrent::Object::list_type& args = raw_args.as_list();
if (args.empty())
throw torrent::input_error("Too few arguments.");
if (flags & LuaEngine::flag_string) { if (flags & LuaEngine::flag_string) {
check_lua_status(l_state, luaL_loadstring(l_state, args.begin()->as_string().c_str())); check_lua_status(l_state, luaL_loadstring(l_state, args.begin()->as_string().c_str()));
} else { } else {
+7 -6
View File
@@ -26,10 +26,7 @@ const size_t object_storage::key_size;
object_storage::iterator object_storage::iterator
object_storage::find_raw_string(const torrent::raw_string& key) { object_storage::find_raw_string(const torrent::raw_string& key) {
fixed_key_type<64> k; return find(key_type::from_raw_string(key));
k.set_c_str(key.data());
return find(k);
} }
object_storage::iterator object_storage::iterator
@@ -64,8 +61,12 @@ object_storage::insert(const char* key_data, uint32_t key_size, const torrent::O
if (std::find(key_data, key_data + key_size, '\0') != key_data + key_size) if (std::find(key_data, key_data + key_size, '\0') != key_data + key_size)
throw torrent::input_error("Found nul-char in string."); throw torrent::input_error("Found nul-char in string.");
// Check for size > key_size. // key_type turns a key this long into the empty key.
// Check for empty string. if (key_size >= object_storage::key_size)
throw torrent::input_error("Key is too long.");
if (key_size == 0)
throw torrent::input_error("Key is empty.");
bool use_raw = false; bool use_raw = false;
torrent::Object object; torrent::Object object;
+1 -8
View File
@@ -158,14 +158,7 @@ fixed_key_type<MaxSize>::set_data(const value_type* src_data, size_type src_size
template <size_t MaxSize> inline void template <size_t MaxSize> inline void
fixed_key_type<MaxSize>::set_c_str(const value_type* src_data) { fixed_key_type<MaxSize>::set_c_str(const value_type* src_data) {
value_type* itr = m_data; set_c_str(src_data, std::strlen(src_data));
const value_type* last = m_data + max_size;
while (itr != last && *src_data != '\0')
*itr++ = *src_data++;
*itr = '\0';
m_size = std::distance(m_data, itr);
} }
template <size_t MaxSize> inline void template <size_t MaxSize> inline void
+3 -3
View File
@@ -161,7 +161,7 @@ parse_object(const char* first, const char* last, torrent::Object* dest, bool (*
if (++depth >= max_parse_depth) if (++depth >= max_parse_depth)
throw torrent::input_error("Max parse depth reached."); throw torrent::input_error("Max parse depth reached.");
if (*first == '{') { if (first != last && *first == '{') {
*dest = torrent::Object::create_list(); *dest = torrent::Object::create_list();
first = parse_list(first + 1, last, dest, &parse_is_delim_block, depth); first = parse_list(first + 1, last, dest, &parse_is_delim_block, depth);
first = parse_skip_wspace(first, last); first = parse_skip_wspace(first, last);
@@ -171,7 +171,7 @@ parse_object(const char* first, const char* last, torrent::Object* dest, bool (*
return ++first; return ++first;
} else if (*first == '(') { } else if (first != last && *first == '(') {
int32_t parentheses = 1; int32_t parentheses = 1;
while (first + 1 != last && *(first + 1) == '(') { while (first + 1 != last && *(first + 1) == '(') {
@@ -393,7 +393,7 @@ convert_to_value_nothrow(const torrent::Object& src, int64_t* value, int base, i
== unpacked.as_string().c_str() + unpacked.as_string().size(); == unpacked.as_string().c_str() + unpacked.as_string().size();
case torrent::Object::TYPE_RAW_STRING: { case torrent::Object::TYPE_RAW_STRING: {
const torrent::raw_string& str = src.as_raw_string(); const torrent::raw_string& str = unpacked.as_raw_string();
auto buffer = std::make_unique<char[]>(str.size() + 1); auto buffer = std::make_unique<char[]>(str.size() + 1);
std::memcpy(buffer.get(), str.data(), str.size()); std::memcpy(buffer.get(), str.data(), str.size());
+5
View File
@@ -181,6 +181,11 @@ parse_command_file(const std::string& path) {
getCount = 0; getCount = 0;
} }
if (file.fail() && !file.eof()) {
lineNumber++;
throw torrent::input_error("Exceeded max line length.");
}
} catch (torrent::input_error& e) { } catch (torrent::input_error& e) {
snprintf(buffer, 2048, "Error in option file: %s:%u: %s", path.c_str(), lineNumber, e.what()); snprintf(buffer, 2048, "Error in option file: %s:%u: %s", path.c_str(), lineNumber, e.what());
+12
View File
@@ -167,6 +167,18 @@ RpcManager::cleanup() {
m_jsonrpc.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.");
if (size < min_size_limit)
throw torrent::input_error("XMLRPC size limit is too small to hold a request.");
m_xmlrpc.set_size_limit(size);
m_jsonrpc.set_size_limit(size);
}
void void
RpcManager::insert_command(const char* name, const char* parm, const char* doc) { RpcManager::insert_command(const char* name, const char* parm, const char* doc) {
m_xmlrpc.insert_command(name, parm, doc); m_xmlrpc.insert_command(name, parm, doc);
+5 -1
View File
@@ -53,6 +53,10 @@ public:
enum RPCType { XML, enum RPCType { XML,
JSON }; JSON };
// A limit below the shortest useful request rejects every request, the one
// that would raise it again included.
static constexpr uint64_t min_size_limit = 1024;
RpcManager() = default; RpcManager() = default;
~RpcManager() = default; ~RpcManager() = default;
@@ -62,7 +66,7 @@ public:
void cleanup(); void cleanup();
int64_t size_limit() { return m_xmlrpc.size_limit(); }; 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(); } int dialect() { return m_xmlrpc.dialect(); }
void set_dialect(int dialect) { m_xmlrpc.set_dialect(dialect); } void set_dialect(int dialect) { m_xmlrpc.set_dialect(dialect); }
+3 -2
View File
@@ -93,7 +93,8 @@ SCgiTask::close() {
// The callbacks are guaranteed to be finished/canceled at this point. // The callbacks are guaranteed to be finished/canceled at this point.
auto lock = std::lock_guard<std::mutex>(m_result_mutex); auto lock = std::lock_guard<std::mutex>(m_result_mutex);
m_buffer.clear(); // clear() would keep the capacity, and the task is pooled for the lifetime of the process.
std::vector<char>().swap(m_buffer);
} }
void void
@@ -404,7 +405,7 @@ void
SCgiTask::receive_write(const char* buffer, uint32_t length) { SCgiTask::receive_write(const char* buffer, uint32_t length) {
assert(torrent::this_thread::thread() == torrent::main_thread::thread()); assert(torrent::this_thread::thread() == torrent::main_thread::thread());
if (buffer == nullptr || length > (100 << 20)) if (buffer == nullptr || length > max_response_size)
throw torrent::internal_error("SCgiTask::receive_write(...) received bad input."); throw torrent::internal_error("SCgiTask::receive_write(...) received bad input.");
// Main thread callback already locked this mutex. // Main thread callback already locked this mutex.
+4 -1
View File
@@ -2,6 +2,7 @@
#define RTORRENT_RPC_SCGI_TASK_H #define RTORRENT_RPC_SCGI_TASK_H
#include <chrono> #include <chrono>
#include <cstdint>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <vector> #include <vector>
@@ -16,7 +17,9 @@ class SCgiTask : public torrent::system::Event {
public: public:
static constexpr int default_buffer_size = 8191; static constexpr int default_buffer_size = 8191;
static constexpr int max_header_size = 2000; 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 uint32_t max_response_size = (100 << 20);
static constexpr auto timeout_request = std::chrono::seconds(60); static constexpr auto timeout_request = std::chrono::seconds(60);
+11 -11
View File
@@ -27,11 +27,11 @@ public:
virtual ~xmlrpc_error_c() throw() {} virtual ~xmlrpc_error_c() throw() {}
virtual int type() const throw() { return m_type; } virtual int type() const throw() { return m_type; }
virtual const char* what() const throw() { return m_msg; } virtual const char* what() const throw() { return m_msg.c_str(); }
private: private:
int m_type; int m_type;
const char* m_msg; std::string m_msg;
}; };
torrent::Object xmlrpc_to_object(xmlrpc_env* env, xmlrpc_value* value, int call_type = 0, rpc::target_type* target = NULL, std::function<void()>* deleter = NULL); torrent::Object xmlrpc_to_object(xmlrpc_env* env, xmlrpc_value* value, int call_type = 0, rpc::target_type* target = NULL, std::function<void()>* deleter = NULL);
@@ -44,10 +44,9 @@ xmlrpc_list_entry_to_object(xmlrpc_env* env, xmlrpc_value* src, int index) {
if (env->fault_occurred) if (env->fault_occurred)
throw xmlrpc_error_c(env); throw xmlrpc_error_c(env);
torrent::Object obj = xmlrpc_to_object(env, tmp); utils::scope_guard guard([tmp]() { xmlrpc_DECREF(tmp); });
xmlrpc_DECREF(tmp);
return obj; return xmlrpc_to_object(env, tmp);
} }
int64_t int64_t
@@ -77,16 +76,18 @@ xmlrpc_list_entry_to_value(xmlrpc_env* env, xmlrpc_value* src, int index) {
{ {
const char* str; const char* str;
xmlrpc_read_string(env, tmp, &str); xmlrpc_read_string(env, tmp, &str);
xmlrpc_DECREF(tmp);
if (env->fault_occurred) if (env->fault_occurred)
throw xmlrpc_error_c(env); throw xmlrpc_error_c(env);
const char* end = str; const char* end = str;
int64_t v3 = ::strtoll(str, (char**)&end, 0); int64_t v3 = ::strtoll(str, (char**)&end, 0);
bool invalid = *str == '\0' || *end != '\0';
::free((void*)str); ::free((void*)str);
if (*str == '\0' || *end != '\0') if (invalid)
throw xmlrpc_error_c(XMLRPC_TYPE_ERROR, "Invalid index."); throw xmlrpc_error_c(XMLRPC_TYPE_ERROR, "Invalid index.");
return v3; return v3;
@@ -211,11 +212,11 @@ xmlrpc_to_object(xmlrpc_env* env, xmlrpc_value* value, int call_type, rpc::targe
if (env->fault_occurred) if (env->fault_occurred)
throw xmlrpc_error_c(env); throw xmlrpc_error_c(env);
utils::scope_guard guard([tmp]() { xmlrpc_DECREF(tmp); });
if (target != nullptr) if (target != nullptr)
std::tie(*target, *deleter) = xmlrpc_to_target(env, tmp, call_type); std::tie(*target, *deleter) = xmlrpc_to_target(env, tmp, call_type);
xmlrpc_DECREF(tmp);
if (env->fault_occurred) if (env->fault_occurred)
throw xmlrpc_error_c(env); throw xmlrpc_error_c(env);
@@ -439,6 +440,8 @@ XmlRpc::cleanup() {
xmlrpc_registry_free((xmlrpc_registry*)m_registry); xmlrpc_registry_free((xmlrpc_registry*)m_registry);
xmlrpc_env_clean((xmlrpc_env*)m_env); xmlrpc_env_clean((xmlrpc_env*)m_env);
delete (xmlrpc_env*)m_env; delete (xmlrpc_env*)m_env;
m_env = nullptr;
m_registry = nullptr;
} }
bool bool
@@ -529,9 +532,6 @@ XmlRpc::size_limit() {
void void
XmlRpc::set_size_limit(uint64_t size) { 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); xmlrpc_limit_set(XMLRPC_XML_SIZE_LIMIT_ID, size);
} }
+20 -8
View File
@@ -19,6 +19,7 @@
#include "rpc/tinyxml2/tinyxml2.h" #include "rpc/tinyxml2/tinyxml2.h"
#include "rpc/rpc_manager.h" #include "rpc/rpc_manager.h"
#include "utils/base64.h" #include "utils/base64.h"
#include "utils/functional.h"
#include "xmlrpc.h" #include "xmlrpc.h"
namespace rpc { namespace rpc {
@@ -258,6 +259,8 @@ execute_command(std::string method_name, const tinyxml2::XMLElement* params_elem
torrent::Object params_raw = torrent::Object::create_list(); torrent::Object params_raw = torrent::Object::create_list();
torrent::Object::list_type& params = params_raw.as_list(); torrent::Object::list_type& params = params_raw.as_list();
rpc::target_type target = rpc::make_target(); rpc::target_type target = rpc::make_target();
std::function<void()> deleter = []() {};
utils::scope_guard guard([&deleter]() { deleter(); });
if (params_element != nullptr) { if (params_element != nullptr) {
if (std::strncmp(params_element->Name(), "params", sizeof("params")) == 0) { if (std::strncmp(params_element->Name(), "params", sizeof("params")) == 0) {
@@ -265,8 +268,6 @@ execute_command(std::string method_name, const tinyxml2::XMLElement* params_elem
const auto* child = params_element->FirstChildElement("param"); const auto* child = params_element->FirstChildElement("param");
if (child != nullptr) { if (child != nullptr) {
std::function<void()> deleter = []() {};
RpcManager::object_to_target(xml_value_to_object(child->FirstChildElement("value")), cmd_itr->second.m_flags, &target, &deleter); RpcManager::object_to_target(xml_value_to_object(child->FirstChildElement("value")), cmd_itr->second.m_flags, &target, &deleter);
child = child->NextSiblingElement("param"); child = child->NextSiblingElement("param");
@@ -282,8 +283,6 @@ execute_command(std::string method_name, const tinyxml2::XMLElement* params_elem
const auto* child = params_element->FirstChildElement("data")->FirstChildElement("value"); const auto* child = params_element->FirstChildElement("data")->FirstChildElement("value");
if (child != nullptr) { if (child != nullptr) {
std::function<void()> deleter = []() {};
RpcManager::object_to_target(xml_value_to_object(child), cmd_itr->second.m_flags, &target, &deleter); RpcManager::object_to_target(xml_value_to_object(child), cmd_itr->second.m_flags, &target, &deleter);
child = child->NextSiblingElement("value"); child = child->NextSiblingElement("value");
@@ -327,15 +326,20 @@ process_document(const tinyxml2::XMLDocument* doc, tinyxml2::XMLPrinter* printer
auto& result_list = result.as_list(); auto& result_list = result.as_list();
auto parent_elements = element_access(doc->RootElement(), {"params", "param", "value", "array", "data"}); auto parent_elements = element_access(doc->RootElement(), {"params", "param", "value", "array", "data"});
for (auto child = parent_elements->FirstChildElement("value"); child; child = child->NextSiblingElement("value")) { for (auto child = parent_elements->FirstChildElement("value"); child; child = child->NextSiblingElement("value")) {
auto sub_method_name = element_access(child, {"struct", "member", "value", "string"})->GetText(); auto method_name_member = element_access(child, {"struct", "member"});
auto member_name = method_name_member->FirstChildElement("name");
if (member_name == nullptr || member_name->GetText() == nullptr ||
std::strncmp(member_name->GetText(), "methodName", sizeof("methodName")) != 0)
throw rpc_error(XMLRPC_PARSE_ERROR, "multicall struct's first member must be methodName");
auto sub_method_name = element_access(method_name_member, {"value", "string"})->GetText();
if (sub_method_name == nullptr) if (sub_method_name == nullptr)
throw rpc_error(XMLRPC_PARSE_ERROR, "multicall methodName element is empty"); throw rpc_error(XMLRPC_PARSE_ERROR, "multicall methodName element is empty");
// If sub_params ends up a nullptr at the end of this if-chian, // If sub_params ends up a nullptr at the end of this if-chian,
// execute_command will turn it into an empty list // execute_command will turn it into an empty list
auto sub_params = element_access(child, {"struct", "member"}); auto sub_params = method_name_member->NextSiblingElement("member");
if (sub_params != nullptr)
sub_params = sub_params->NextSiblingElement("member");
if (sub_params != nullptr) if (sub_params != nullptr)
sub_params = sub_params->FirstChildElement("value"); sub_params = sub_params->FirstChildElement("value");
if (sub_params != nullptr) if (sub_params != nullptr)
@@ -426,6 +430,14 @@ XmlRpc::process(const char* inBuffer, uint32_t length, slot_write slotWrite) {
// remains. // remains.
tinyxml2::XMLPrinter printer(nullptr, true, 0); tinyxml2::XMLPrinter printer(nullptr, true, 0);
process_document(&doc, &printer); process_document(&doc, &printer);
if (printer.CStrSize() - 1 > static_cast<int>(SCgiTask::max_response_size)) {
tinyxml2::XMLPrinter fault_printer(nullptr, true, 0);
print_xmlrpc_fault(XMLRPC_LIMIT_EXCEEDED_ERROR, "Response size exceeds maximum XML-RPC limit", &fault_printer);
return slotWrite(fault_printer.CStr(), fault_printer.CStrSize() - 1);
}
return slotWrite(printer.CStr(), printer.CStrSize() - 1); return slotWrite(printer.CStr(), printer.CStrSize() - 1);
} catch (rpc_error& e) { } catch (rpc_error& e) {
tinyxml2::XMLPrinter printer(nullptr, true, 0); tinyxml2::XMLPrinter printer(nullptr, true, 0);
+69 -17
View File
@@ -2,6 +2,7 @@
#include "download_storer.h" #include "download_storer.h"
#include <cerrno>
#include <fcntl.h> #include <fcntl.h>
#include <fstream> #include <fstream>
#include <unistd.h> #include <unistd.h>
@@ -16,6 +17,13 @@
#include "core/download.h" #include "core/download.h"
#include "utils/directory.h" #include "utils/directory.h"
// O_DIRECTORY makes the open refuse anything that is not a directory. A
// platform that does not define it still gets the read-only directory
// handle fsync needs, so fall back to no extra flag.
#ifndef O_DIRECTORY
#define O_DIRECTORY 0
#endif
namespace session { namespace session {
DownloadStorer::DownloadStorer(core::Download* download) DownloadStorer::DownloadStorer(core::Download* download)
@@ -116,34 +124,73 @@ is_correct_format(const std::string& f) {
void void
save_stream(const std::string& path, bool use_fsyncdisk, const std::stringstream& stream) { save_stream(const std::string& path, bool use_fsyncdisk, const std::stringstream& stream) {
std::fstream output(path.c_str(), std::ios::out | std::ios::trunc); // Remove any leftover temporary file first so that O_EXCL only ever fails on
// an entry that appeared after the unlink, and O_NOFOLLOW keeps a symlink
// planted in the session directory from redirecting the write.
if (::unlink(path.c_str()) == -1 && errno != ENOENT)
throw torrent::storage_error("failed to remove stale file : " + path);
// TODO: If we cannot open more files, wait for some to finish and try again. // TODO: If we cannot open more files, wait for some to finish and try again.
if (!output.is_open()) int fd = ::open(path.c_str(), O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW, 0600);
throw torrent::storage_error("failed to open file for writing : " + path);
output << stream.rdbuf();
if (!output.good())
throw torrent::storage_error("failed to write stream to file : " + path);
output.close();
// Ensure that the new file is actually written to the disk
int fd = ::open(path.c_str(), O_WRONLY);
if (fd < 0) if (fd < 0)
throw torrent::storage_error("failed to open file descriptor for fsync : " + path); throw torrent::storage_error("failed to open file for writing : " + path);
const auto data = stream.view();
std::size_t remaining = data.size();
const char* cursor = data.data();
while (remaining != 0) {
ssize_t result = ::write(fd, cursor, remaining);
if (result == -1) {
if (errno == EINTR)
continue;
::close(fd);
throw torrent::storage_error("failed to write stream to file : " + path);
}
cursor += result;
remaining -= result;
}
if (use_fsyncdisk) { if (use_fsyncdisk) {
#ifdef __APPLE__ #ifdef __APPLE__
::fsync(fd); int sync_result = ::fsync(fd);
#else #else
::fdatasync(fd); int sync_result = ::fdatasync(fd);
#endif #endif
if (sync_result == -1) {
::close(fd);
throw torrent::storage_error("failed to sync file to disk : " + path);
}
} }
::close(fd); // A full disk may only be seen when the descriptor is closed.
if (::close(fd) == -1)
throw torrent::storage_error("failed to close file descriptor : " + path);
}
void
sync_directory_of(const std::string& path) {
auto separator = path.rfind('/');
auto directory = separator == std::string::npos ? std::string(".") :
separator == 0 ? std::string("/") : path.substr(0, separator);
int fd = ::open(directory.c_str(), O_RDONLY | O_DIRECTORY);
if (fd < 0)
throw torrent::storage_error("failed to open session directory for sync : " + directory);
if (::fsync(fd) == -1) {
::close(fd);
throw torrent::storage_error("failed to sync session directory : " + directory);
}
if (::close(fd) == -1)
throw torrent::storage_error("failed to close session directory : " + directory);
} }
} // namespace anonymous } // namespace anonymous
@@ -173,6 +220,11 @@ DownloadStorer::save_and_move_streams(const std::string& path, bool use_fsyncdis
if (::rename((rtorrent_path + ".new").c_str(), rtorrent_path.c_str()) == -1) if (::rename((rtorrent_path + ".new").c_str(), rtorrent_path.c_str()) == -1)
throw torrent::storage_error("failed to rename rtorrent resume file : " + rtorrent_path); throw torrent::storage_error("failed to rename rtorrent resume file : " + rtorrent_path);
// Syncing the files themselves does not persist the renames; the directory
// holding them has to be synced for the new names to survive a crash.
if (use_fsyncdisk)
sync_directory_of(path);
} }
utils::Directory utils::Directory
-1
View File
@@ -75,7 +75,6 @@ SessionManager::save_resume_download(core::Download* download) {
{ {
std::unique_lock<std::mutex> lock(m_pending_builds_mutex); std::unique_lock<std::mutex> lock(m_pending_builds_mutex);
// TODO: This is under the wrong lock.
if (!m_active) if (!m_active)
throw torrent::internal_error("SessionManager::save_resume_download() called while not active."); throw torrent::internal_error("SessionManager::save_resume_download() called while not active.");
+8 -3
View File
@@ -1,6 +1,7 @@
#ifndef RTORRENT_SESSION_SESSION_MANAGER_H #ifndef RTORRENT_SESSION_SESSION_MANAGER_H
#define RTORRENT_SESSION_SESSION_MANAGER_H #define RTORRENT_SESSION_SESSION_MANAGER_H
#include <atomic>
#include <condition_variable> #include <condition_variable>
#include <deque> #include <deque>
#include <future> #include <future>
@@ -99,9 +100,13 @@ private:
bool m_use_fsyncdisk{true}; bool m_use_fsyncdisk{true};
bool m_use_lock{true}; bool m_use_lock{true};
align_cacheline std::mutex m_mutex; align_cacheline
bool m_active{}; std::atomic<bool> m_active{};
align_cacheline
std::mutex m_mutex;
std::deque<SaveRequest> m_save_requests; std::deque<SaveRequest> m_save_requests;
std::atomic<size_t> m_save_request_counter{}; std::atomic<size_t> m_save_request_counter{};
@@ -126,7 +131,7 @@ private:
inline bool SessionManager::is_used() const { return !m_path.empty(); } inline bool SessionManager::is_used() const { return !m_path.empty(); }
inline std::string SessionManager::path() const { return m_path; } 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 bool SessionManager::use_lock() const { return m_use_lock; }
inline void SessionManager::flush_all_pending_builds() { process_pending_builds(true); } inline void SessionManager::flush_all_pending_builds() { process_pending_builds(true); }
+6 -2
View File
@@ -86,9 +86,13 @@ config_comment_log(const std::string& command, const std::string& raw_args) {
pos = next_pos + 1; pos = next_pos + 1;
} }
if (command == "log.add_output") if (command == "log.add_output") {
if (args.size() != 2)
throw torrent::input_error("Invalid number of arguments.");
log_add_group_output_str(args[0], args[1]); log_add_group_output_str(args[0], args[1]);
else if (command == "log.open_file")
} else if (command == "log.open_file")
apply_log_open_str(0, args); apply_log_open_str(0, args);
else if (command == "log.open_file.flush") else if (command == "log.open_file.flush")
apply_log_open_str(log_flag_flush, args); apply_log_open_str(log_flag_flush, args);
+1
View File
@@ -3,6 +3,7 @@
#include <functional> #include <functional>
#include <string> #include <string>
#include <vector>
int parse_main_options(int argc, char** argv); int parse_main_options(int argc, char** argv);
void parse_config_file(int argc, char** argv, std::function<void (const std::string&)> parse_fn); void parse_config_file(int argc, char** argv, std::function<void (const std::string&)> parse_fn);
+8 -4
View File
@@ -63,8 +63,10 @@ SignalHandler::set_block(unsigned int signum) {
sigemptyset(&mask); sigemptyset(&mask);
sigaddset(&mask, signum); sigaddset(&mask, signum);
if (pthread_sigmask(SIG_BLOCK, &mask, NULL) == -1) int result = pthread_sigmask(SIG_BLOCK, &mask, NULL);
throw std::logic_error("Could not block signal: " + std::string(std::strerror(errno)));
if (result != 0)
throw std::logic_error("Could not block signal: " + std::string(std::strerror(result)));
} }
void void
@@ -77,8 +79,10 @@ SignalHandler::set_unblock(unsigned int signum) {
sigemptyset(&mask); sigemptyset(&mask);
sigaddset(&mask, signum); sigaddset(&mask, signum);
if (pthread_sigmask(SIG_UNBLOCK, &mask, NULL) == -1) int result = pthread_sigmask(SIG_UNBLOCK, &mask, NULL);
throw std::logic_error("Could not unblock signal: " + std::string(std::strerror(errno)));
if (result != 0)
throw std::logic_error("Could not unblock signal: " + std::string(std::strerror(result)));
} }
void void
-1
View File
@@ -6,7 +6,6 @@
#include <torrent/peer/peer.h> #include <torrent/peer/peer.h>
#include "display/manager.h" #include "display/manager.h"
#include "utils/list_focus.h"
#include "element_base.h" #include "element_base.h"
+36 -8
View File
@@ -30,6 +30,38 @@
namespace ui { namespace ui {
std::string
create_filter_pattern(const std::string& input) {
std::string pattern = input;
if (pattern.empty())
return pattern;
if (pattern.back() != '$')
pattern = pattern + ".*";
if (pattern.front() != '^')
pattern = ".*" + pattern;
std::transform(pattern.begin(), pattern.end(), pattern.begin(), ::tolower);
return pattern;
}
torrent::Object
create_filter_command(const std::string& pattern) {
torrent::Object name = torrent::Object::create_dict_key();
name.as_dict_key() = "d.name";
torrent::Object args = torrent::Object::create_list();
args.as_list().push_back(name);
args.as_list().push_back(torrent::Object(pattern));
torrent::Object command = torrent::Object::create_dict_key();
command.as_dict_key() = "match";
command.as_dict_obj() = args;
return command;
}
DownloadList::DownloadList() { DownloadList::DownloadList() {
m_uiArray[DISPLAY_DOWNLOAD] = NULL; m_uiArray[DISPLAY_DOWNLOAD] = NULL;
m_uiArray[DISPLAY_DOWNLOAD_LIST] = new ElementDownloadList(); m_uiArray[DISPLAY_DOWNLOAD_LIST] = new ElementDownloadList();
@@ -319,16 +351,12 @@ DownloadList::receive_exit_input(Input type) {
current_view()->filter(); current_view()->filter();
current_view()->sort(); current_view()->sort();
} else { } else {
std::string pattern = input->str(); std::string pattern = create_filter_pattern(input->str());
if (pattern.back() != '$')
pattern = pattern + ".*";
if (pattern.front() != '^')
pattern = ".*" + pattern;
std::transform(pattern.begin(), pattern.end(), pattern.begin(), ::tolower);
std::string temp_filter = "match={d.name=," + pattern + "}";
if (rpc::call_command_value("view.filter.temp.log")) if (rpc::call_command_value("view.filter.temp.log"))
control->core()->push_log_std("Temporary filter on '" + current_view()->name() + "' view: " + pattern); control->core()->push_log_std("Temporary filter on '" + current_view()->name() + "' view: " + pattern);
current_view()->set_filter_temp(temp_filter);
current_view()->set_filter_temp(create_filter_command(pattern));
current_view()->filter(); current_view()->filter();
} }
break; break;
+9
View File
@@ -1,6 +1,9 @@
#ifndef RTORRENT_UI_DOWNLOAD_LIST_H #ifndef RTORRENT_UI_DOWNLOAD_LIST_H
#define RTORRENT_UI_DOWNLOAD_LIST_H #define RTORRENT_UI_DOWNLOAD_LIST_H
#include <string>
#include <torrent/object.h>
#include "element_base.h" #include "element_base.h"
#include "globals.h" #include "globals.h"
#include "display/manager.h" #include "display/manager.h"
@@ -29,6 +32,12 @@ namespace ui {
class Download; class Download;
// Wrap a user-typed download name pattern in the anchors 'match' expects.
std::string create_filter_pattern(const std::string& input);
// Build the 'match' command the temporary view filter runs against d.name.
torrent::Object create_filter_command(const std::string& pattern);
class DownloadList : public ElementBase { class DownloadList : public ElementBase {
public: public:
typedef display::WindowDownloadList WList; typedef display::WindowDownloadList WList;
+1 -1
View File
@@ -179,7 +179,7 @@ ElementMenu::set_entry(size_type idx, bool triggerSlot) {
m_entry = idx; m_entry = idx;
focus_entry(m_entry); focus_entry(m_entry);
if (triggerSlot) if (triggerSlot && m_entry < size())
base_type::operator[](m_entry).m_slotFocus(); base_type::operator[](m_entry).m_slotFocus();
m_window->mark_dirty(); m_window->mark_dirty();
+3 -2
View File
@@ -467,6 +467,9 @@ Root::save_input_history() {
history_file << entry << "|" + category.at((pitr->second + i) % m_input_history_length) + "\n"; history_file << entry << "|" + category.at((pitr->second + i) % m_input_history_length) + "\n";
} }
// The data only reaches the kernel here, so this is where a full disk is seen.
history_file.close();
if (!history_file.good()) { if (!history_file.good()) {
lt_log_print(torrent::LOG_DEBUG, "input history file corrupted during writing, discarding (path:%s)", history_filename.c_str()); lt_log_print(torrent::LOG_DEBUG, "input history file corrupted during writing, discarding (path:%s)", history_filename.c_str());
return; return;
@@ -474,8 +477,6 @@ Root::save_input_history() {
lt_log_print(torrent::LOG_DEBUG, "input history file written (path:%s)", history_filename.c_str()); lt_log_print(torrent::LOG_DEBUG, "input history file written (path:%s)", history_filename.c_str());
} }
history_file.close();
std::rename(history_filename_tmp.c_str(), history_filename.c_str()); std::rename(history_filename_tmp.c_str(), history_filename.c_str());
} }
-4
View File
@@ -70,10 +70,6 @@ public:
void set_down_throttle(unsigned int throttle); void set_down_throttle(unsigned int throttle);
void set_up_throttle(unsigned int throttle); void set_up_throttle(unsigned int throttle);
// Rename to raw or something, make base function.
void set_down_throttle_i64(int64_t throttle) { set_down_throttle(throttle >> 10); }
void set_up_throttle_i64(int64_t throttle) { set_up_throttle(throttle >> 10); }
void adjust_down_throttle(int throttle); void adjust_down_throttle(int throttle);
void adjust_up_throttle(int throttle); void adjust_up_throttle(int throttle);
+27 -5
View File
@@ -5,6 +5,7 @@
#include <algorithm> #include <algorithm>
#include <cstdlib> #include <cstdlib>
#include <dirent.h> #include <dirent.h>
#include <fcntl.h>
#include <functional> #include <functional>
#include <sys/stat.h> #include <sys/stat.h>
#include <torrent/exceptions.h> #include <torrent/exceptions.h>
@@ -13,6 +14,24 @@
namespace utils { namespace utils {
namespace {
uint8_t
entry_type_from_mode(mode_t mode) {
if (S_ISREG(mode))
return DT_REG;
if (S_ISDIR(mode))
return DT_DIR;
if (S_ISLNK(mode))
return DT_LNK;
return DT_UNKNOWN;
}
} // namespace
// Keep this? // Keep this?
bool bool
Directory::is_valid() const { Directory::is_valid() const {
@@ -38,9 +57,6 @@ Directory::update(int flags) {
return false; return false;
struct dirent* entry; struct dirent* entry;
#ifdef __sun__
struct stat s;
#endif
while ((entry = readdir(d)) != NULL) { while ((entry = readdir(d)) != NULL) {
if ((flags & update_hide_dot) && entry->d_name[0] == '.') if ((flags & update_hide_dot) && entry->d_name[0] == '.')
@@ -49,16 +65,22 @@ Directory::update(int flags) {
iterator itr = base_type::insert(end(), value_type()); iterator itr = base_type::insert(end(), value_type());
#ifdef __sun__ #ifdef __sun__
stat(entry->d_name, &s);
itr->s_fileno = entry->d_ino; itr->s_fileno = entry->d_ino;
itr->s_reclen = 0; itr->s_reclen = 0;
itr->s_type = s.st_mode; itr->s_type = DT_UNKNOWN;
#else #else
itr->s_fileno = entry->d_fileno; itr->s_fileno = entry->d_fileno;
itr->s_reclen = entry->d_reclen; itr->s_reclen = entry->d_reclen;
itr->s_type = entry->d_type; itr->s_type = entry->d_type;
#endif #endif
if (itr->s_type == DT_UNKNOWN) {
struct stat st;
if (fstatat(dirfd(d), entry->d_name, &st, AT_SYMLINK_NOFOLLOW) == 0)
itr->s_type = entry_type_from_mode(st.st_mode);
}
#ifdef DIRENT_NAMLEN_EXISTS_FOOBAR #ifdef DIRENT_NAMLEN_EXISTS_FOOBAR
itr->s_name = std::string(entry->d_name, entry->d_name + entry->d_namlen); itr->s_name = std::string(entry->d_name, entry->d_name + entry->d_namlen);
#else #else
+2 -2
View File
@@ -2,14 +2,14 @@
#define RTORRENT_UTILS_DIRECTORY_H #define RTORRENT_UTILS_DIRECTORY_H
#include <cstdint> #include <cstdint>
#include <dirent.h>
#include <string> #include <string>
#include <vector> #include <vector>
namespace utils { namespace utils {
struct directory_entry { struct directory_entry {
// Fix. bool is_file() const { return s_type == DT_REG; }
bool is_file() const { return true; }
// The name and types should match POSIX. // The name and types should match POSIX.
uint32_t s_fileno; uint32_t s_fileno;
+4
View File
@@ -5,6 +5,8 @@
#include <zlib.h> #include <zlib.h>
#include <torrent/exceptions.h> #include <torrent/exceptions.h>
#include "utils/functional.h"
namespace utils { namespace utils {
void 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) 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."); 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); auto max_response_size = deflateBound(&zs, length);
output.resize(offset + max_response_size); output.resize(offset + max_response_size);
+1
View File
@@ -2,6 +2,7 @@
#define RTORRENT_UTILS_GZIP_H #define RTORRENT_UTILS_GZIP_H
#include <functional> #include <functional>
#include <vector>
namespace utils { namespace utils {
-158
View File
@@ -1,158 +0,0 @@
// rTorrent - BitTorrent client
// Copyright (C) 2005-2011, Jari Sundell
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// In addition, as a special exception, the copyright holders give
// permission to link the code of portions of this program with the
// OpenSSL library under certain conditions as described in each
// individual source file, and distribute linked combinations
// including the two.
//
// You must obey the GNU General Public License in all respects for
// all of the code used other than OpenSSL. If you modify file(s)
// with this exception, you may extend this exception to your version
// of the file(s), but you are not obligated to do so. If you do not
// wish to do so, delete this exception statement from your version.
// If you delete this exception statement from all source files in the
// program, then also delete it here.
//
// Contact: Jari Sundell <sundell.software@gmail.com>
#ifndef RTORRENT_UTILS_LIST_FOCUS_H
#define RTORRENT_UTILS_LIST_FOCUS_H
#include <functional>
#include <list>
namespace utils {
// Can't make this class inherit privately due to gcc PR 14258.
template <typename Base>
class ListFocus {
public:
typedef Base base_type;
typedef std::function<void ()> slot_void;
typedef std::list<slot_void> signal_void;
typedef typename base_type::iterator iterator;
typedef typename base_type::const_iterator const_iterator;
typedef typename base_type::reverse_iterator reverse_iterator;
typedef typename base_type::const_reverse_iterator const_reverse_iterator;
typedef typename base_type::value_type value_type;
ListFocus(base_type* b = nullptr) : m_base(b) { if (b) m_focus = b->end(); }
// Convinience functions, would have added more through using, but
// can't.
iterator begin() { return m_base->begin(); }
iterator end() { return m_base->end(); }
reverse_iterator rbegin() { return m_base->rbegin(); }
reverse_iterator rend() { return m_base->rend(); }
// Don't do erase on this object without making sure focus is right.
base_type& base() { return *m_base; }
iterator get_focus() { return m_focus; }
void set_focus(iterator itr);
// These are looping increment/decrements.
iterator inc_focus();
iterator dec_focus();
iterator erase(iterator itr);
void remove(const value_type& v);
// Be careful with copying signals.
signal_void& signal_changed() { return m_signal_changed; }
private:
void emit_changed();
base_type* m_base;
iterator m_focus;
signal_void m_signal_changed;
};
template <typename Base>
void
ListFocus<Base>::set_focus(iterator itr) {
m_focus = itr;
emit_changed();
}
template <typename Base>
typename ListFocus<Base>::iterator
ListFocus<Base>::inc_focus() {
if (m_focus != end())
++m_focus;
else
m_focus = begin();
emit_changed();
return m_focus;
}
template <typename Base>
typename ListFocus<Base>::iterator
ListFocus<Base>::dec_focus() {
if (m_focus != begin())
--m_focus;
else
m_focus = end();
emit_changed();
return m_focus;
}
template <typename Base>
typename ListFocus<Base>::iterator
ListFocus<Base>::erase(iterator itr) {
if (itr == m_focus) {
m_focus = m_base->erase(itr);
emit_changed();
return m_focus;
}
return m_base->erase(itr);
}
template <typename Base>
void
ListFocus<Base>::remove(const value_type& v) {
iterator first = begin();
iterator last = end();
while (first != last)
if (*first == v)
first = erase(first);
else
++first;
}
template <typename Base>
void
ListFocus<Base>::emit_changed() {
for (const auto& itr : m_signal_changed)
itr();
}
}
#endif
+27 -3
View File
@@ -1,4 +1,5 @@
TESTS = \ TESTS = \
rtorrent_Test_Core \
rtorrent_Test_Rpc \ rtorrent_Test_Rpc \
rtorrent_Test_Src rtorrent_Test_Src
@@ -7,6 +8,7 @@ check_PROGRAMS = $(TESTS)
rtorrent_Test_LDADD = \ rtorrent_Test_LDADD = \
../src/libsub_root.a ../src/libsub_root.a
rtorrent_Test_Core_LDADD = $(rtorrent_Test_LDADD)
rtorrent_Test_Rpc_LDADD = $(rtorrent_Test_LDADD) rtorrent_Test_Rpc_LDADD = $(rtorrent_Test_LDADD)
rtorrent_Test_Src_LDADD = $(rtorrent_Test_LDADD) rtorrent_Test_Src_LDADD = $(rtorrent_Test_LDADD)
@@ -30,6 +32,10 @@ rtorrent_Test_Common = \
helpers/test_utils.h \ helpers/test_utils.h \
helpers/utils.h helpers/utils.h
rtorrent_Test_Core_SOURCES = $(rtorrent_Test_Common) \
core/test_view.cc \
core/test_view.h
rtorrent_Test_Rpc_SOURCES = $(rtorrent_Test_Common) \ rtorrent_Test_Rpc_SOURCES = $(rtorrent_Test_Common) \
rpc/test_command.cc \ rpc/test_command.cc \
rpc/test_command.h \ rpc/test_command.h \
@@ -46,20 +52,38 @@ rtorrent_Test_Rpc_SOURCES = $(rtorrent_Test_Common) \
rpc/test_object_storage.cc \ rpc/test_object_storage.cc \
rpc/test_object_storage.h \ rpc/test_object_storage.h \
rpc/test_parse_options.cc \ rpc/test_parse_options.cc \
rpc/test_parse_options.h rpc/test_parse_options.h \
rpc/test_rpc_manager.cc \
rpc/test_rpc_manager.h
rtorrent_Test_Src_SOURCES = $(rtorrent_Test_Common) \ rtorrent_Test_Src_SOURCES = $(rtorrent_Test_Common) \
src/test_command_dynamic.cc \ src/test_command_dynamic.cc \
src/test_command_dynamic.h \ src/test_command_dynamic.h \
src/test_command_local.cc \ src/test_command_ip.cc \
src/test_command_local.h \ src/test_command_ip.h \
src/test_command_system.cc \
src/test_command_system.h \
src/test_command_path.cc \ src/test_command_path.cc \
src/test_command_path.h \ src/test_command_path.h \
src/test_command_string.cc \ src/test_command_string.cc \
src/test_command_string.h \ src/test_command_string.h \
src/test_command_throttle.cc \
src/test_command_throttle.h \
src/test_input_path_input.cc \
src/test_input_path_input.h \
src/test_session_commit.cc \
src/test_session_commit.h \
src/test_session_storer.cc \
src/test_session_storer.h \
src/test_setup.cc \
src/test_setup.h \
src/test_ui_download_list.cc \
src/test_ui_download_list.h \
src/test_watch_ready_queue.cc \ src/test_watch_ready_queue.cc \
src/test_watch_ready_queue.h src/test_watch_ready_queue.h
rtorrent_Test_Core_CXXFLAGS = $(CPPUNIT_CFLAGS)
rtorrent_Test_Core_LDFLAGS = $(CPPUNIT_LIBS) -ldl
rtorrent_Test_Rpc_CXXFLAGS = $(CPPUNIT_CFLAGS) rtorrent_Test_Rpc_CXXFLAGS = $(CPPUNIT_CFLAGS)
rtorrent_Test_Rpc_LDFLAGS = $(CPPUNIT_LIBS) -ldl rtorrent_Test_Rpc_LDFLAGS = $(CPPUNIT_LIBS) -ldl
rtorrent_Test_Src_CXXFLAGS = $(CPPUNIT_CFLAGS) rtorrent_Test_Src_CXXFLAGS = $(CPPUNIT_CFLAGS)
+158
View File
@@ -0,0 +1,158 @@
#include "config.h"
#include "test/core/test_view.h"
#include <algorithm>
#include <cstddef>
#include <deque>
#include <memory>
#include <set>
#include <string>
#include <vector>
#include "command_helpers.h"
#include "control.h"
#include "core/download.h"
#include "core/view.h"
#include "globals.h"
#include "rpc/parse_commands.h"
CPPUNIT_TEST_SUITE_REGISTRATION(TestView);
namespace {
const char* filter_command = "test.view.filter";
const char* added_command = "test.view.event_added";
const char* removed_command = "test.view.event_removed";
// View only stores these and hands them to the command layer as RPC targets,
// which casts the pointer without reading through it.
struct download_stub {
alignas(core::Download) std::byte storage[sizeof(core::Download)];
};
std::deque<download_stub> stub_storage;
std::vector<core::Download*> stub_downloads;
std::set<core::Download*> matches_filter;
std::vector<core::Download*> added_events;
std::vector<core::Download*> removed_events;
std::shared_ptr<core::Download>
make_stub_download() {
auto download = reinterpret_cast<core::Download*>(&stub_storage.emplace_back());
stub_downloads.push_back(download);
return std::shared_ptr<core::Download>(download, [](core::Download*) {});
}
// Renders a dispatch list as "d0,d2" so a failure names the downloads the
// handler saw, not just how many there were.
std::string
describe(const std::vector<core::Download*>& downloads) {
std::string result;
for (const auto& download : downloads) {
auto itr = std::find(stub_downloads.begin(), stub_downloads.end(), download);
if (!result.empty())
result += ',';
result += 'd' + std::to_string(std::distance(stub_downloads.begin(), itr));
}
return result;
}
torrent::Object
cmd_filter(core::Download* download, const torrent::Object&) {
return torrent::Object(static_cast<int64_t>(matches_filter.count(download)));
}
torrent::Object
cmd_event_added(core::Download* download, const torrent::Object&) {
added_events.push_back(download);
return torrent::Object();
}
torrent::Object
cmd_event_removed(core::Download* download, const torrent::Object&) {
removed_events.push_back(download);
return torrent::Object();
}
torrent::Object
command_object(const char* command) {
return torrent::Object(std::string(command) + "=");
}
} // namespace
void
TestView::setUp() {
TestFixtureWithMainThread::setUp();
if (control == nullptr)
control = new Control;
if (!rpc::commands.has(filter_command)) {
CMD2_DL(filter_command, &cmd_filter);
CMD2_DL(added_command, &cmd_event_added);
CMD2_DL(removed_command, &cmd_event_removed);
}
stub_storage.clear();
stub_downloads.clear();
matches_filter.clear();
added_events.clear();
removed_events.clear();
}
void
TestView::tearDown() {
TestFixtureWithMainThread::tearDown();
}
// d0 and d1 start visible and stop matching, d2 and d3 start filtered out and
// start matching. The two halves are the same size on purpose: a test that
// only counted the dispatches would pass even if both events were sent over
// the same half of the changed range.
void
TestView::test_filter_dispatches_events_to_the_right_downloads() {
core::View view;
view.initialize("test_view");
auto d0 = make_stub_download();
auto d1 = make_stub_download();
auto d2 = make_stub_download();
auto d3 = make_stub_download();
view.insert(d0);
view.insert(d1);
view.insert(d2);
view.insert(d3);
view.set_visible(d0.get());
view.set_visible(d1.get());
CPPUNIT_ASSERT_EQUAL(core::View::size_type(2), view.size_visible());
matches_filter = {d2.get(), d3.get()};
view.set_filter(command_object(filter_command));
view.set_event_added(command_object(added_command));
view.set_event_removed(command_object(removed_command));
added_events.clear();
removed_events.clear();
view.filter();
CPPUNIT_ASSERT_EQUAL(std::string("d2,d3"), describe(added_events));
CPPUNIT_ASSERT_EQUAL(std::string("d0,d1"), describe(removed_events));
CPPUNIT_ASSERT_EQUAL(core::View::size_type(2), view.size_visible());
CPPUNIT_ASSERT(*view.begin_visible() == d2);
CPPUNIT_ASSERT(*(view.begin_visible() + 1) == d3);
}
+15
View File
@@ -0,0 +1,15 @@
#include "test/helpers/test_main_thread.h"
class TestView : public TestFixtureWithMainThread {
CPPUNIT_TEST_SUITE(TestView);
CPPUNIT_TEST(test_filter_dispatches_events_to_the_right_downloads);
CPPUNIT_TEST_SUITE_END();
public:
void setUp();
void tearDown();
void test_filter_dispatches_events_to_the_right_downloads();
};
+107
View File
@@ -8,12 +8,18 @@
#include "globals.h" #include "globals.h"
#include "command_helpers.h" #include "command_helpers.h"
#include "rpc/command_map.h" #include "rpc/command_map.h"
#include "rpc/scgi_task.h"
CPPUNIT_TEST_SUITE_REGISTRATION(TestJsonrpc); CPPUNIT_TEST_SUITE_REGISTRATION(TestJsonrpc);
torrent::Object torrent::Object
jsonrpc_cmd_test_reflect([[maybe_unused]] rpc::target_type t, const torrent::Object& obj) { return obj; } jsonrpc_cmd_test_reflect([[maybe_unused]] rpc::target_type t, const torrent::Object& obj) { return obj; }
torrent::Object
jsonrpc_cmd_test_oversized([[maybe_unused]] rpc::target_type t, [[maybe_unused]] const torrent::Object& obj) {
return torrent::Object(std::string(rpc::SCgiTask::max_response_size + (1 << 20), 'a'));
}
void initialize_command_dynamic(); void initialize_command_dynamic();
// Name, Request, Expected response // Name, Request, Expected response
@@ -130,6 +136,10 @@ TestJsonrpc::setUp() {
if (rpc::commands.find("jsonrpc_reflect") == rpc::commands.end()) { if (rpc::commands.find("jsonrpc_reflect") == rpc::commands.end()) {
CMD2_ANY("jsonrpc_reflect", &jsonrpc_cmd_test_reflect); CMD2_ANY("jsonrpc_reflect", &jsonrpc_cmd_test_reflect);
} }
if (rpc::commands.find("jsonrpc_oversized") == rpc::commands.end()) {
CMD2_ANY("jsonrpc_oversized", &jsonrpc_cmd_test_oversized);
}
} }
void void
@@ -145,3 +155,100 @@ TestJsonrpc::test_basics() {
CPPUNIT_ASSERT_EQUAL_MESSAGE(std::get<0>(test), std::get<2>(test), output); CPPUNIT_ASSERT_EQUAL_MESSAGE(std::get<0>(test), std::get<2>(test), output);
} }
} }
// A command whose result does not fit in the SCGI response buffer must be
// answered with a fault, not handed to the writer. SCgiTask::receive_write
// treats an oversized body as an internal_error, which is not caught by any
// RPC handler and terminates the process.
void
TestJsonrpc::test_response_size_limit() {
const std::string request = R"({"jsonrpc": "2.0", "method": "jsonrpc_oversized", "params": [""], "id": 1})";
const std::string expected = R"({"error":{"code":-32000,"message":"response size exceeds maximum RPC limit"},"id":1,"jsonrpc":"2.0"})";
std::string output;
m_jsonrpc.process(request.c_str(), request.size(), [&output](const char* c, uint32_t l) { output.append(c, l); return true; });
CPPUNIT_ASSERT_MESSAGE("response handed to the writer is " + std::to_string(output.size()) +
" bytes, over the " + std::to_string(rpc::SCgiTask::max_response_size) + " byte SCGI limit",
output.size() <= rpc::SCgiTask::max_response_size);
CPPUNIT_ASSERT_EQUAL(expected, output);
}
// The bound is applied while the document is parsed: json_to_object only ever
// walks "params", and by the time it runs the whole tree already exists.
void
TestJsonrpc::test_depth_limit() {
// A JSON string holding a quote and brackets that must not be counted.
const std::string tricky = R"("\"[[[")";
std::vector<std::tuple<std::string, std::string, std::string>> requests = {
std::make_tuple("Nesting under the limit is accepted",
R"({"jsonrpc": "2.0", "method": "jsonrpc_reflect", "params": ["", )" +
std::string(1000, '[') + std::string(1000, ']') + R"(], "id": 1})",
R"({"id":1,"jsonrpc":"2.0","result":[)" +
std::string(1000, '[') + std::string(1000, ']') + R"(]})"),
// Nesting outside "params" is never converted, so json_to_object's own
// bound never sees it.
std::make_tuple("Nesting outside params is rejected",
R"({"jsonrpc": "2.0", "method": "jsonrpc_reflect", "params": [""], "id": 1, "x": )" +
std::string(2000, '[') + std::string(2000, ']') + R"(})",
R"({"error":{"code":-32600,"message":"maximum nesting depth exceeded"},"id":null,"jsonrpc":"2.0"})"),
std::make_tuple("Nesting over the limit is rejected",
R"({"jsonrpc": "2.0", "method": "jsonrpc_reflect", "params": ["", )" +
std::string(2000, '[') + std::string(2000, ']') + R"(], "id": 1})",
R"({"error":{"code":-32600,"message":"maximum nesting depth exceeded"},"id":null,"jsonrpc":"2.0"})"),
std::make_tuple("Brackets inside a string are not nesting",
R"({"jsonrpc": "2.0", "method": "jsonrpc_reflect", "params": ["", ")" +
std::string(2000, '[') + R"("], "id": 1})",
R"({"id":1,"jsonrpc":"2.0","result":[")" +
std::string(2000, '[') + R"("]})"),
std::make_tuple("An escaped quote does not end a string",
R"({"jsonrpc": "2.0", "method": "jsonrpc_reflect", "params": ["", )" + tricky + R"(, ")" +
std::string(2000, '[') + R"("], "id": 1})",
R"({"id":1,"jsonrpc":"2.0","result":[)" + tricky + R"(,")" +
std::string(2000, '[') + R"("]})"),
// The bound is on the whole document, so the outer object and the params
// array are two of the 1024 containers and 1022 are left for the payload.
std::make_tuple("Nesting one below the limit is accepted",
R"({"jsonrpc": "2.0", "method": "jsonrpc_reflect", "params": ["", )" +
std::string(1021, '[') + std::string(1021, ']') + R"(], "id": 1})",
R"({"id":1,"jsonrpc":"2.0","result":[)" +
std::string(1021, '[') + std::string(1021, ']') + R"(]})"),
std::make_tuple("Nesting at the limit is accepted",
R"({"jsonrpc": "2.0", "method": "jsonrpc_reflect", "params": ["", )" +
std::string(1022, '[') + std::string(1022, ']') + R"(], "id": 1})",
R"({"id":1,"jsonrpc":"2.0","result":[)" +
std::string(1022, '[') + std::string(1022, ']') + R"(]})"),
std::make_tuple("Nesting one over the limit is rejected",
R"({"jsonrpc": "2.0", "method": "jsonrpc_reflect", "params": ["", )" +
std::string(1023, '[') + std::string(1023, ']') + R"(], "id": 1})",
R"({"error":{"code":-32600,"message":"maximum nesting depth exceeded"},"id":null,"jsonrpc":"2.0"})"),
};
for (auto& test : requests) {
std::string output;
m_jsonrpc.process(std::get<1>(test).c_str(), std::get<1>(test).size(), [&output](const char* c, uint32_t l) { output.append(c, l); return true; });
CPPUNIT_ASSERT_EQUAL_MESSAGE(std::get<0>(test), std::get<2>(test), output);
}
}
// network.xmlrpc.size_limit is the only knob bounding how much input a single
// request may spend memory on, and it has to bound the JSON path too.
void
TestJsonrpc::test_size_limit() {
const std::string request = R"({"jsonrpc": "2.0", "method": "jsonrpc_reflect", "params": [""], "id": 1})";
const std::string expected = R"({"error":{"code":-32600,"message":"content size exceeds maximum RPC limit"},"id":null,"jsonrpc":"2.0"})";
std::string output;
m_jsonrpc.set_size_limit(1);
m_jsonrpc.process(request.c_str(), request.size(), [&output](const char* c, uint32_t l) { output.append(c, l); return true; });
CPPUNIT_ASSERT_EQUAL(expected, output);
}
+6
View File
@@ -8,6 +8,9 @@ class TestJsonrpc : public test_fixture {
CPPUNIT_TEST_SUITE(TestJsonrpc); CPPUNIT_TEST_SUITE(TestJsonrpc);
CPPUNIT_TEST(test_basics); CPPUNIT_TEST(test_basics);
CPPUNIT_TEST(test_response_size_limit);
CPPUNIT_TEST(test_depth_limit);
CPPUNIT_TEST(test_size_limit);
CPPUNIT_TEST_SUITE_END(); CPPUNIT_TEST_SUITE_END();
@@ -16,6 +19,9 @@ public:
void tearDown(); void tearDown();
void test_basics(); void test_basics();
void test_response_size_limit();
void test_depth_limit();
void test_size_limit();
private: private:
std::unique_ptr<TestMainThread> m_test_main_thread; std::unique_ptr<TestMainThread> m_test_main_thread;
+28
View File
@@ -49,6 +49,34 @@ TestObjectStorage::test_validate_keys() {
torrent::raw_string raw_string_4("test_4\0foo", 10); torrent::raw_string raw_string_4("test_4\0foo", 10);
ASSERT_CATCH_INPUT_ERROR( { m_storage.insert(raw_string_4, torrent::Object("a"), rpc::object_storage::flag_string_type); } ); ASSERT_CATCH_INPUT_ERROR( { m_storage.insert(raw_string_4, torrent::Object("a"), rpc::object_storage::flag_string_type); } );
ASSERT_CATCH_INPUT_ERROR( { m_storage.insert_str("", torrent::Object("a"), rpc::object_storage::flag_string_type); } );
std::string key_max(rpc::object_storage::key_size - 1, 'k');
CPPUNIT_ASSERT(m_storage.insert_str(key_max, torrent::Object("a"), rpc::object_storage::flag_string_type)->first == key_max);
ASSERT_CATCH_INPUT_ERROR( { m_storage.insert_str(key_max + 'k', torrent::Object("a"), rpc::object_storage::flag_string_type); } );
// The over-long key must not have been stored as the empty key.
CPPUNIT_ASSERT(m_storage.find_raw_string(torrent::raw_string::from_c_str("")) == m_storage.end());
// An over-long key must not be truncated into a match either.
CPPUNIT_ASSERT(m_storage.find_raw_string(torrent::raw_string::from_string(key_max + 'k')) == m_storage.end());
// set_c_str puts the terminating nul at m_data[size()], so size() has to
// stay below max_size for that write to land inside the array.
rpc::object_storage::key_type key_long;
key_long.set_c_str(std::string(rpc::object_storage::key_size + 1, 'k').c_str());
CPPUNIT_ASSERT(key_long.size() < rpc::object_storage::key_size);
m_storage.clear();
m_storage.insert_str("test_5", torrent::Object("a"), rpc::object_storage::flag_string_type);
// insert rejects a key holding a nul-char, so a lookup must not match past one.
CPPUNIT_ASSERT(m_storage.find_raw_string(torrent::raw_string("test_5\0x", 8)) == m_storage.end());
CPPUNIT_ASSERT(m_storage.find_raw_string(torrent::raw_string::from_c_str("test_5")) != m_storage.end());
m_storage.clear();
} }
// And test many other bad/good string combos. // And test many other bad/good string combos.
+21
View File
@@ -0,0 +1,21 @@
#include "config.h"
#include "test/rpc/test_rpc_manager.h"
#include <torrent/exceptions.h>
#include "rpc/scgi_task.h"
CPPUNIT_TEST_SUITE_REGISTRATION(TestRpcManager);
// A size limit too small to hold any request rejects every request, including
// the one that would put it back, so it can only be undone by a restart.
void
TestRpcManager::test_size_limit_bounds() {
CPPUNIT_ASSERT_THROW(m_rpc_manager.set_size_limit(0), torrent::input_error);
CPPUNIT_ASSERT_THROW(m_rpc_manager.set_size_limit(rpc::RpcManager::min_size_limit - 1), torrent::input_error);
CPPUNIT_ASSERT_THROW(m_rpc_manager.set_size_limit(rpc::SCgiTask::max_content_size + 1), torrent::input_error);
CPPUNIT_ASSERT_NO_THROW(m_rpc_manager.set_size_limit(rpc::RpcManager::min_size_limit));
CPPUNIT_ASSERT_NO_THROW(m_rpc_manager.set_size_limit(rpc::SCgiTask::max_content_size));
}
+17
View File
@@ -0,0 +1,17 @@
#include "test/helpers/test_fixture.h"
#include "rpc/rpc_manager.h"
class TestRpcManager : public test_fixture {
CPPUNIT_TEST_SUITE(TestRpcManager);
CPPUNIT_TEST(test_size_limit_bounds);
CPPUNIT_TEST_SUITE_END();
public:
void test_size_limit_bounds();
private:
rpc::RpcManager m_rpc_manager;
};
+70
View File
@@ -8,6 +8,7 @@
#include "globals.h" #include "globals.h"
#include "command_helpers.h" #include "command_helpers.h"
#include "rpc/command_map.h" #include "rpc/command_map.h"
#include "rpc/scgi_task.h"
CPPUNIT_TEST_SUITE_REGISTRATION(TestXmlrpc); CPPUNIT_TEST_SUITE_REGISTRATION(TestXmlrpc);
@@ -21,6 +22,11 @@ xmlrpc_cmd_test_reflect_string([[maybe_unused]] rpc::target_type t, const std::s
return obj; return obj;
} }
torrent::Object
xmlrpc_cmd_test_oversized([[maybe_unused]] rpc::target_type t, [[maybe_unused]] const torrent::Object& obj) {
return torrent::Object(std::string(rpc::SCgiTask::max_response_size + (1 << 20), 'a'));
}
void initialize_command_dynamic(); void initialize_command_dynamic();
#if defined(HAVE_XMLRPC_TINYXML2) && !defined(HAVE_XMLRPC_C) #if defined(HAVE_XMLRPC_TINYXML2) && !defined(HAVE_XMLRPC_C)
@@ -127,6 +133,9 @@ TestXmlrpc::setUp() {
if (rpc::commands.find("xmlrpc_reflect_string") == rpc::commands.end()) if (rpc::commands.find("xmlrpc_reflect_string") == rpc::commands.end())
CMD2_ANY_STRING("xmlrpc_reflect_string", &xmlrpc_cmd_test_reflect_string); CMD2_ANY_STRING("xmlrpc_reflect_string", &xmlrpc_cmd_test_reflect_string);
if (rpc::commands.find("xmlrpc_oversized") == rpc::commands.end())
CMD2_ANY("xmlrpc_oversized", &xmlrpc_cmd_test_oversized);
} }
void void
@@ -165,11 +174,72 @@ TestXmlrpc::test_size_limit() {
CPPUNIT_ASSERT_EQUAL(expected, output); CPPUNIT_ASSERT_EQUAL(expected, output);
} }
// A command whose result does not fit in the SCGI response buffer must be
// answered with a fault, not handed to the writer. SCgiTask::receive_write
// treats an oversized body as an internal_error, which is not caught by any
// RPC handler and terminates the process.
void
TestXmlrpc::test_response_size_limit() {
std::string input = "<?xml version=\"1.0\"?><methodCall><methodName>xmlrpc_oversized</methodName><params><param><value><string></string></value></param></params></methodCall>";
std::string expected = "<?xml version=\"1.0\"?><methodResponse><fault><value><struct><member><name>faultCode</name><value><i8>-509</i8></value></member><member><name>faultString</name><value><string>Response size exceeds maximum XML-RPC limit</string></value></member></struct></value></fault></methodResponse>";
std::string output;
m_xmlrpc.process(input.c_str(), input.size(), [&output](const char* c, uint32_t l){ output.append(c, l); return true;});
CPPUNIT_ASSERT_MESSAGE("response handed to the writer is " + std::to_string(output.size()) +
" bytes, over the " + std::to_string(rpc::SCgiTask::max_response_size) + " byte SCGI limit",
output.size() <= rpc::SCgiTask::max_response_size);
CPPUNIT_ASSERT_EQUAL(expected, output);
}
namespace {
const std::string multicall_method_name =
"<member><name>methodName</name><value><string>xmlrpc_reflect</string></value></member>";
const std::string multicall_params =
"<member><name>params</name><value><array><data>"
"<value><string></string></value><value><string>a</string></value>"
"</data></array></value></member>";
std::string
multicall_request(const std::string& members) {
return "<?xml version=\"1.0\"?><methodCall><methodName>system.multicall</methodName>"
"<params><param><value><array><data><value><struct>" + members +
"</struct></value></data></array></value></param></params></methodCall>";
}
}
void
TestXmlrpc::test_multicall_member_order() {
auto call = [this](const std::string& input) {
std::string output;
m_xmlrpc.process(input.c_str(), input.size(), [&output](const char* c, uint32_t l){ output.append(c, l); return true;});
return output;
};
// methodName first is the only order accepted; the positive control proves
// the harness drives the real code path rather than a stub.
std::string ordered = call(multicall_request(multicall_method_name + multicall_params));
CPPUNIT_ASSERT(ordered.find("faultCode") == std::string::npos);
// params before methodName is rejected with a clear top-level fault,
// instead of the "could not find expected element string" of a positional read.
std::string expected_fault =
"<?xml version=\"1.0\"?><methodResponse><fault><value><struct>"
"<member><name>faultCode</name><value><i8>-503</i8></value></member>"
"<member><name>faultString</name><value><string>multicall struct's first member must be methodName</string></value></member>"
"</struct></value></fault></methodResponse>";
CPPUNIT_ASSERT_EQUAL(expected_fault, call(multicall_request(multicall_params + multicall_method_name)));
}
#else #else
void TestXmlrpc::test_invalid_utf8() {} void TestXmlrpc::test_invalid_utf8() {}
void TestXmlrpc::test_basics() {} void TestXmlrpc::test_basics() {}
void TestXmlrpc::test_size_limit() {} void TestXmlrpc::test_size_limit() {}
void TestXmlrpc::test_response_size_limit() {}
void TestXmlrpc::test_multicall_member_order() {}
void TestXmlrpc::setUp() {} void TestXmlrpc::setUp() {}
void TestXmlrpc::tearDown() {} void TestXmlrpc::tearDown() {}
+4
View File
@@ -10,6 +10,8 @@ class TestXmlrpc : public test_fixture {
CPPUNIT_TEST(test_basics); CPPUNIT_TEST(test_basics);
CPPUNIT_TEST(test_invalid_utf8); CPPUNIT_TEST(test_invalid_utf8);
CPPUNIT_TEST(test_size_limit); CPPUNIT_TEST(test_size_limit);
CPPUNIT_TEST(test_response_size_limit);
CPPUNIT_TEST(test_multicall_member_order);
CPPUNIT_TEST_SUITE_END(); CPPUNIT_TEST_SUITE_END();
@@ -22,6 +24,8 @@ public:
void test_basics(); void test_basics();
void test_invalid_utf8(); void test_invalid_utf8();
void test_size_limit(); void test_size_limit();
void test_response_size_limit();
void test_multicall_member_order();
private: private:
std::unique_ptr<TestMainThread> m_test_main_thread; std::unique_ptr<TestMainThread> m_test_main_thread;
+50
View File
@@ -2,6 +2,8 @@
#include "test/src/test_command_dynamic.h" #include "test/src/test_command_dynamic.h"
#include "helpers/assert.h"
#include "control.h" #include "control.h"
#include "globals.h" #include "globals.h"
#include "rpc/parse_commands.h" #include "rpc/parse_commands.h"
@@ -61,3 +63,51 @@ TestCommandDynamic::test_old_style() {
rpc::commands.call_command("method.insert", rpc::create_object_list("test_old_style.4", "simple", "cat=test.3")); rpc::commands.call_command("method.insert", rpc::create_object_list("test_old_style.4", "simple", "cat=test.3"));
CPPUNIT_ASSERT(rpc::commands.call_command("test_old_style.4", torrent::Object()).as_string() == "test.3"); CPPUNIT_ASSERT(rpc::commands.call_command("test_old_style.4", torrent::Object()).as_string() == "test.3");
} }
void
TestCommandDynamic::test_insert_list() {
torrent::Object key_only = torrent::Object::create_list();
key_only.as_list().push_back("test_insert_list.1");
rpc::commands.call_command("method.insert.list", key_only);
torrent::Object result = rpc::commands.call_command("test_insert_list.1", torrent::Object());
CPPUNIT_ASSERT(result.is_list());
CPPUNIT_ASSERT(result.as_list().empty());
rpc::commands.call_command("method.insert.list",
rpc::create_object_list("test_insert_list.2", rpc::create_object_list("a", "b")));
torrent::Object filled = rpc::commands.call_command("test_insert_list.2", torrent::Object());
CPPUNIT_ASSERT(filled.is_list());
CPPUNIT_ASSERT_EQUAL((size_t)2, filled.as_list().size());
}
void
TestCommandDynamic::test_value_base() {
auto value = [](std::initializer_list<torrent::Object> objects) {
auto args = torrent::Object::create_list();
for (const auto& object : objects)
args.as_list().push_back(object);
return rpc::commands.call_command("value", args).as_value();
};
CPPUNIT_ASSERT_EQUAL(int64_t(10), value({"10"}));
CPPUNIT_ASSERT_EQUAL(int64_t(255), value({"ff", int64_t(16)}));
// strtoll only defines base 0 and base 2 through 36.
ASSERT_CATCH_INPUT_ERROR( { value({"10", int64_t(1)}); } );
ASSERT_CATCH_INPUT_ERROR( { value({"10", int64_t(37)}); } );
ASSERT_CATCH_INPUT_ERROR( { value({"10", int64_t(-1)}); } );
// An out-of-range base must not be narrowed into a valid one.
ASSERT_CATCH_INPUT_ERROR( { value({"10", int64_t(1) << 40}); } );
ASSERT_CATCH_INPUT_ERROR( { value({"ff", (int64_t(1) << 32) + 16}); } );
// A number too large for the result must be rejected, not clamped.
ASSERT_CATCH_INPUT_ERROR( { value({"99999999999999999999999"}); } );
}
+4
View File
@@ -7,6 +7,8 @@ class TestCommandDynamic : public test_fixture {
CPPUNIT_TEST(test_basics); CPPUNIT_TEST(test_basics);
CPPUNIT_TEST(test_get_set); CPPUNIT_TEST(test_get_set);
CPPUNIT_TEST(test_old_style); CPPUNIT_TEST(test_old_style);
CPPUNIT_TEST(test_insert_list);
CPPUNIT_TEST(test_value_base);
CPPUNIT_TEST_SUITE_END(); CPPUNIT_TEST_SUITE_END();
@@ -18,6 +20,8 @@ public:
void test_get_set(); void test_get_set();
void test_old_style(); void test_old_style();
void test_insert_list();
void test_value_base();
private: private:
std::unique_ptr<TestMainThread> m_test_main_thread; std::unique_ptr<TestMainThread> m_test_main_thread;
+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 "config.h"
#include "test/src/test_command_local.h" #include "test/src/test_command_system.h"
#include <torrent/torrent.h> #include <torrent/torrent.h>
#include <torrent/runtime/socket_manager.h> #include <torrent/runtime/socket_manager.h>
@@ -10,12 +10,12 @@
#include "globals.h" #include "globals.h"
#include "rpc/parse_commands.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 void
TestCommandLocal::setUp() { TestCommandSystem::setUp() {
torrent::initialize_main_thread(); torrent::initialize_main_thread();
torrent::initialize(); torrent::initialize();
@@ -23,16 +23,16 @@ TestCommandLocal::setUp() {
control = new Control; control = new Control;
if (!rpc::commands.has("system.sockets.size")) if (!rpc::commands.has("system.sockets.size"))
initialize_command_local(); initialize_command_system();
} }
void void
TestCommandLocal::tearDown() { TestCommandSystem::tearDown() {
torrent::cleanup(); torrent::cleanup();
} }
void void
TestCommandLocal::test_socket_category_commands() { TestCommandSystem::test_socket_category_commands() {
for (uint32_t i = 0; i < torrent::runtime::SocketManager::category_count; ++i) { 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 = static_cast<torrent::runtime::socket_manager_category_t>(i);
auto name = "system.sockets." + torrent::option_to_str_or_throw(torrent::OPTION_SOCKET_CATEGORY, 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" #include "test/helpers/test_fixture.h"
class TestCommandLocal : public test_fixture { class TestCommandSystem : public test_fixture {
CPPUNIT_TEST_SUITE(TestCommandLocal); CPPUNIT_TEST_SUITE(TestCommandSystem);
CPPUNIT_TEST(test_socket_category_commands); CPPUNIT_TEST(test_socket_category_commands);
+102
View File
@@ -0,0 +1,102 @@
#include "config.h"
#include "test/src/test_command_throttle.h"
#include <torrent/throttle.h>
#include <torrent/torrent.h>
#include "core/manager.h"
#include "control.h"
#include "globals.h"
#include "rpc/parse_commands.h"
CPPUNIT_TEST_SUITE_REGISTRATION(TestCommandThrottle);
void initialize_command_throttle();
static void
call_set(const char* key, const char* value) {
rpc::commands.call_command(key, torrent::Object(std::string(value)));
}
static void
call_named(const char* key, const char* name, const char* value) {
torrent::Object::list_type args;
args.push_back(torrent::Object(std::string(name)));
args.push_back(torrent::Object(std::string(value)));
rpc::commands.call_command(key, torrent::Object::create_list_range(args.begin(), args.end()));
}
static uint64_t
down_rate() {
return torrent::down_throttle_global()->max_rate();
}
void
TestCommandThrottle::setUp() {
torrent::initialize_main_thread();
torrent::initialize();
if (control == nullptr)
control = new Control;
if (!rpc::commands.has("throttle.global_down.max_rate.set_kb"))
initialize_command_throttle();
}
void
TestCommandThrottle::tearDown() {
torrent::cleanup();
}
void
TestCommandThrottle::test_global_rate_in_range() {
call_set("throttle.global_down.max_rate.set_kb", "1024");
CPPUNIT_ASSERT_EQUAL(uint64_t{1048576}, down_rate());
call_set("throttle.global_down.max_rate.set_kb", "4194303");
CPPUNIT_ASSERT_EQUAL(uint64_t{4294966272}, down_rate());
call_set("throttle.global_down.max_rate.set", "4294966272");
CPPUNIT_ASSERT_EQUAL(uint64_t{4294966272}, down_rate());
}
void
TestCommandThrottle::test_global_rate_kb_out_of_range() {
call_set("throttle.global_down.max_rate.set_kb", "1024");
CPPUNIT_ASSERT_THROW(call_set("throttle.global_down.max_rate.set_kb", "4194304"), torrent::input_error);
CPPUNIT_ASSERT_EQUAL(uint64_t{1048576}, down_rate());
}
void
TestCommandThrottle::test_global_rate_bytes_out_of_range() {
call_set("throttle.global_down.max_rate.set", "1048576");
CPPUNIT_ASSERT_THROW(call_set("throttle.global_down.max_rate.set", "4294967296"), torrent::input_error);
CPPUNIT_ASSERT_EQUAL(uint64_t{1048576}, down_rate());
}
void
TestCommandThrottle::test_global_rate_negative() {
call_set("throttle.global_down.max_rate.set", "1048576");
CPPUNIT_ASSERT_THROW(call_set("throttle.global_down.max_rate.set", "-1"), torrent::input_error);
CPPUNIT_ASSERT_EQUAL(uint64_t{1048576}, down_rate());
}
void
TestCommandThrottle::test_named_rate_in_range() {
call_named("throttle.down", "test_named_in_range", "1024");
auto itr = control->core()->throttles().find("test_named_in_range");
CPPUNIT_ASSERT(itr != control->core()->throttles().end());
CPPUNIT_ASSERT_EQUAL(uint64_t{1048576}, itr->second.second->max_rate());
}
void
TestCommandThrottle::test_named_rate_out_of_range() {
CPPUNIT_ASSERT_THROW(call_named("throttle.down", "test_named_out_of_range", "18014398509481984"), torrent::input_error);
}
+25
View File
@@ -0,0 +1,25 @@
#include "test/helpers/test_fixture.h"
class TestCommandThrottle : public test_fixture {
CPPUNIT_TEST_SUITE(TestCommandThrottle);
CPPUNIT_TEST(test_global_rate_in_range);
CPPUNIT_TEST(test_global_rate_kb_out_of_range);
CPPUNIT_TEST(test_global_rate_bytes_out_of_range);
CPPUNIT_TEST(test_global_rate_negative);
CPPUNIT_TEST(test_named_rate_in_range);
CPPUNIT_TEST(test_named_rate_out_of_range);
CPPUNIT_TEST_SUITE_END();
public:
void setUp();
void tearDown();
void test_global_rate_in_range();
void test_global_rate_kb_out_of_range();
void test_global_rate_bytes_out_of_range();
void test_global_rate_negative();
void test_named_rate_in_range();
void test_named_rate_out_of_range();
};
+77
View File
@@ -0,0 +1,77 @@
#include "config.h"
#include "test/src/test_input_path_input.h"
#include <cstdlib>
#include <sys/stat.h>
#include <unistd.h>
#include "input/path_input.h"
CPPUNIT_TEST_SUITE_REGISTRATION(TestInputPathInput);
void
TestInputPathInput::setUp() {
test_fixture::setUp();
char temp_dir[] = "/tmp/rtorrent_test_path_input_XXXXXX";
CPPUNIT_ASSERT(mkdtemp(temp_dir) != nullptr);
m_temp_dir = temp_dir;
CPPUNIT_ASSERT_EQUAL(0, mkdir((m_temp_dir + "/alpha").c_str(), 0755));
CPPUNIT_ASSERT_EQUAL(0, mkdir((m_temp_dir + "/alpha/beta").c_str(), 0755));
}
void
TestInputPathInput::tearDown() {
rmdir((m_temp_dir + "/alpha/beta").c_str());
rmdir((m_temp_dir + "/alpha").c_str());
rmdir(m_temp_dir.c_str());
test_fixture::tearDown();
}
// Tab at the end of the line completes the trailing component.
void
TestInputPathInput::test_complete_at_end() {
input::PathInput input;
input.str() = m_temp_dir + "/al";
input.set_pos(input.str().size());
CPPUNIT_ASSERT(input.pressed('\t'));
CPPUNIT_ASSERT_EQUAL(m_temp_dir + "/alpha/", input.str());
CPPUNIT_ASSERT_EQUAL(input.str().size(), input.get_pos());
}
// The same completion with the cursor left inside the line. Only the text up to
// the cursor selects the entry, and the rest of the line is discarded.
void
TestInputPathInput::test_complete_mid_line() {
input::PathInput input;
input.str() = m_temp_dir + "/alZZZ";
input.set_pos(m_temp_dir.size() + 3);
CPPUNIT_ASSERT(input.pressed('\t'));
CPPUNIT_ASSERT_EQUAL(m_temp_dir + "/alpha/", input.str());
CPPUNIT_ASSERT_EQUAL(input.str().size(), input.get_pos());
}
// A cursor resting on a separator keeps completing the component after it.
void
TestInputPathInput::test_cursor_on_separator() {
input::PathInput input;
input.str() = m_temp_dir + "/alpha/be";
input.set_pos(m_temp_dir.size() + 6);
CPPUNIT_ASSERT(input.pressed('\t'));
CPPUNIT_ASSERT_EQUAL(m_temp_dir + "/alpha/beta/", input.str());
CPPUNIT_ASSERT_EQUAL(input.str().size(), input.get_pos());
}
+24
View File
@@ -0,0 +1,24 @@
#include "test/helpers/test_fixture.h"
#include <string>
class TestInputPathInput : public test_fixture {
CPPUNIT_TEST_SUITE(TestInputPathInput);
CPPUNIT_TEST(test_complete_at_end);
CPPUNIT_TEST(test_complete_mid_line);
CPPUNIT_TEST(test_cursor_on_separator);
CPPUNIT_TEST_SUITE_END();
public:
void setUp();
void tearDown();
void test_complete_at_end();
void test_complete_mid_line();
void test_cursor_on_separator();
private:
std::string m_temp_dir;
};
+94
View File
@@ -0,0 +1,94 @@
#include "config.h"
#include "test/src/test_session_commit.h"
#include <cstdlib>
#include <dirent.h>
#include <fstream>
#include <sstream>
#include <sys/stat.h>
#include <unistd.h>
#include "session/download_storer.h"
CPPUNIT_TEST_SUITE_REGISTRATION(TestSessionCommit);
namespace {
const char* entry_name = "0123456789ABCDEF0123456789ABCDEF01234567.torrent";
std::string
read_file(const std::string& path) {
std::ifstream file(path.c_str());
std::stringstream buffer;
buffer << file.rdbuf();
return buffer.str();
}
void
remove_directory(const std::string& path) {
DIR* d = ::opendir(path.c_str());
if (d == NULL)
return;
struct dirent* entry;
while ((entry = ::readdir(d)) != NULL) {
if (entry->d_name[0] == '.' && (entry->d_name[1] == '\0' || (entry->d_name[1] == '.' && entry->d_name[2] == '\0')))
continue;
::unlink((path + "/" + entry->d_name).c_str());
}
::closedir(d);
::rmdir(path.c_str());
}
} // namespace
void
TestSessionCommit::setUp() {
test_fixture::setUp();
char temp_dir[] = "/tmp/rtorrent_test_commit_XXXXXX";
CPPUNIT_ASSERT(mkdtemp(temp_dir) != nullptr);
m_session_dir = temp_dir;
}
void
TestSessionCommit::tearDown() {
remove_directory(m_session_dir);
test_fixture::tearDown();
}
void
TestSessionCommit::commit_and_verify(bool use_fsyncdisk) {
auto path = m_session_dir + "/" + entry_name;
std::stringstream torrent_stream("torrent-data");
std::stringstream rtorrent_stream("rtorrent-data");
std::stringstream libtorrent_stream("libtorrent-data");
session::DownloadStorer::save_and_move_streams(path, use_fsyncdisk, &torrent_stream, &rtorrent_stream, &libtorrent_stream);
CPPUNIT_ASSERT_EQUAL(std::string("torrent-data"), read_file(path));
CPPUNIT_ASSERT_EQUAL(std::string("rtorrent-data"), read_file(path + ".rtorrent"));
CPPUNIT_ASSERT_EQUAL(std::string("libtorrent-data"), read_file(path + ".libtorrent_resume"));
struct stat st;
CPPUNIT_ASSERT(::stat((path + ".new").c_str(), &st) == -1);
}
void
TestSessionCommit::test_commit_publishes_all_three_files() {
commit_and_verify(false);
}
void
TestSessionCommit::test_commit_with_fsync_publishes_all_three_files() {
commit_and_verify(true);
}
+24
View File
@@ -0,0 +1,24 @@
#include "test/helpers/test_fixture.h"
#include <string>
class TestSessionCommit : public test_fixture {
CPPUNIT_TEST_SUITE(TestSessionCommit);
CPPUNIT_TEST(test_commit_publishes_all_three_files);
CPPUNIT_TEST(test_commit_with_fsync_publishes_all_three_files);
CPPUNIT_TEST_SUITE_END();
public:
void setUp();
void tearDown();
void test_commit_publishes_all_three_files();
void test_commit_with_fsync_publishes_all_three_files();
private:
void commit_and_verify(bool use_fsyncdisk);
std::string m_session_dir;
};
+152
View File
@@ -0,0 +1,152 @@
#include "config.h"
#include "test/src/test_session_storer.h"
#include <cstdlib>
#include <dirent.h>
#include <fstream>
#include <sstream>
#include <sys/stat.h>
#include <unistd.h>
#include "session/download_storer.h"
#include "utils/directory.h"
CPPUNIT_TEST_SUITE_REGISTRATION(TestSessionStorer);
namespace {
const char* entry_name = "0123456789ABCDEF0123456789ABCDEF01234567.torrent";
const char* link_name = "FEDCBA9876543210FEDCBA9876543210FEDCBA98.torrent";
void
write_file(const std::string& path, const std::string& content) {
std::ofstream file(path.c_str());
file << content;
file.close();
CPPUNIT_ASSERT(file.good());
}
std::string
read_file(const std::string& path) {
std::ifstream file(path.c_str());
std::stringstream buffer;
buffer << file.rdbuf();
return buffer.str();
}
void
save_session_files(const std::string& path) {
std::stringstream torrent_stream("torrent-data");
std::stringstream rtorrent_stream("rtorrent-data");
std::stringstream libtorrent_stream("libtorrent-data");
session::DownloadStorer::save_and_move_streams(path, false, &torrent_stream, &rtorrent_stream, &libtorrent_stream);
}
unsigned int
permissions_of(const std::string& path) {
struct stat st;
CPPUNIT_ASSERT_EQUAL(0, ::stat(path.c_str(), &st));
return st.st_mode & 07777;
}
void
remove_directory(const std::string& path) {
DIR* d = ::opendir(path.c_str());
if (d == NULL)
return;
struct dirent* entry;
while ((entry = ::readdir(d)) != NULL) {
if (entry->d_name[0] == '.' && (entry->d_name[1] == '\0' || (entry->d_name[1] == '.' && entry->d_name[2] == '\0')))
continue;
::unlink((path + "/" + entry->d_name).c_str());
}
::closedir(d);
::rmdir(path.c_str());
}
} // namespace
void
TestSessionStorer::setUp() {
test_fixture::setUp();
char temp_dir[] = "/tmp/rtorrent_test_session_XXXXXX";
CPPUNIT_ASSERT(mkdtemp(temp_dir) != nullptr);
m_temp_dir = temp_dir;
m_session_dir = m_temp_dir + "/session";
CPPUNIT_ASSERT_EQUAL(0, ::mkdir(m_session_dir.c_str(), 0755));
}
void
TestSessionStorer::tearDown() {
remove_directory(m_session_dir);
remove_directory(m_temp_dir);
test_fixture::tearDown();
}
// A symlink planted where the next temporary session file will be written must
// not redirect the write to the file it points at.
void
TestSessionStorer::test_temp_file_symlink_is_not_followed() {
auto outside = m_temp_dir + "/outside.txt";
auto path = m_session_dir + "/" + entry_name;
write_file(outside, "original");
CPPUNIT_ASSERT_EQUAL(0, ::symlink(outside.c_str(), (path + ".new").c_str()));
save_session_files(path);
CPPUNIT_ASSERT_EQUAL(std::string("original"), read_file(outside));
CPPUNIT_ASSERT_EQUAL(std::string("torrent-data"), read_file(path));
}
// Session files carry tracker announce urls, so they must not be readable by
// other users regardless of the umask rtorrent was started with.
void
TestSessionStorer::test_saved_files_are_owner_only() {
auto path = m_session_dir + "/" + entry_name;
auto prev_umask = ::umask(0);
save_session_files(path);
::umask(prev_umask);
CPPUNIT_ASSERT_EQUAL(0600u, permissions_of(path));
CPPUNIT_ASSERT_EQUAL(0600u, permissions_of(path + ".rtorrent"));
CPPUNIT_ASSERT_EQUAL(0600u, permissions_of(path + ".libtorrent_resume"));
}
// Entries listed for loading must report their real type so that a symlink in
// the session directory is skipped instead of loaded.
void
TestSessionStorer::test_symlinked_entry_is_not_a_file() {
write_file(m_temp_dir + "/outside.txt", "d0:e");
write_file(m_session_dir + "/" + entry_name, "d0:e");
CPPUNIT_ASSERT_EQUAL(0, ::symlink((m_temp_dir + "/outside.txt").c_str(), (m_session_dir + "/" + link_name).c_str()));
auto entries = session::DownloadStorer::get_formated_entries(m_session_dir + "/");
CPPUNIT_ASSERT_EQUAL(size_t{2}, size_t{entries.size()});
for (const auto& entry : entries) {
if (entry.s_name == entry_name)
CPPUNIT_ASSERT(entry.is_file());
else
CPPUNIT_ASSERT(!entry.is_file());
}
}
+25
View File
@@ -0,0 +1,25 @@
#include "test/helpers/test_fixture.h"
#include <string>
class TestSessionStorer : public test_fixture {
CPPUNIT_TEST_SUITE(TestSessionStorer);
CPPUNIT_TEST(test_temp_file_symlink_is_not_followed);
CPPUNIT_TEST(test_saved_files_are_owner_only);
CPPUNIT_TEST(test_symlinked_entry_is_not_a_file);
CPPUNIT_TEST_SUITE_END();
public:
void setUp();
void tearDown();
void test_temp_file_symlink_is_not_followed();
void test_saved_files_are_owner_only();
void test_symlinked_entry_is_not_a_file();
private:
std::string m_temp_dir;
std::string m_session_dir;
};
+78
View File
@@ -0,0 +1,78 @@
#include "config.h"
#include "test/src/test_setup.h"
#include <fstream>
#include <string>
#include <unistd.h>
#include <torrent/exceptions.h>
#include "setup.h"
CPPUNIT_TEST_SUITE_REGISTRATION(TestSetup);
// Linking setup.o pulls in the help printer, which lives in src/main.cc.
void
print_help() {}
namespace {
class temp_config_file {
public:
temp_config_file(const std::string& contents) {
char path[] = "/tmp/rtorrent_test_setup_XXXXXX";
CPPUNIT_ASSERT(::mkstemp(path) != -1);
m_path = path;
std::ofstream file(m_path);
file << contents << '\n';
}
~temp_config_file() { ::unlink(m_path.c_str()); }
const std::string& path() const { return m_path; }
private:
std::string m_path;
};
// The group name must be a valid one, else option_find_string throws before
// the output argument is ever touched.
void
assert_arg_count_error(const std::string& line) {
temp_config_file file(line);
try {
parse_config_file_comments(file.path());
} catch (torrent::input_error& e) {
CPPUNIT_ASSERT_EQUAL(std::string("Invalid number of arguments."), std::string(e.what()));
return;
}
CPPUNIT_FAIL("no torrent::input_error thrown for: " + line);
}
} // namespace
void
TestSetup::test_config_comment_log_add_output() {
temp_config_file file("# do:log.add_output=debug,test_output");
CPPUNIT_ASSERT_NO_THROW(parse_config_file_comments(file.path()));
}
void
TestSetup::test_config_comment_log_add_output_no_args() {
assert_arg_count_error("# do:log.add_output=");
}
void
TestSetup::test_config_comment_log_add_output_one_arg() {
assert_arg_count_error("# do:log.add_output=debug");
}
void
TestSetup::test_config_comment_log_add_output_too_many_args() {
assert_arg_count_error("# do:log.add_output=debug,test_output,extra");
}
+18
View File
@@ -0,0 +1,18 @@
#include "test/helpers/test_fixture.h"
class TestSetup : public test_fixture {
CPPUNIT_TEST_SUITE(TestSetup);
CPPUNIT_TEST(test_config_comment_log_add_output);
CPPUNIT_TEST(test_config_comment_log_add_output_no_args);
CPPUNIT_TEST(test_config_comment_log_add_output_one_arg);
CPPUNIT_TEST(test_config_comment_log_add_output_too_many_args);
CPPUNIT_TEST_SUITE_END();
public:
void test_config_comment_log_add_output();
void test_config_comment_log_add_output_no_args();
void test_config_comment_log_add_output_one_arg();
void test_config_comment_log_add_output_too_many_args();
};
+52
View File
@@ -0,0 +1,52 @@
#include "config.h"
#include "test/src/test_ui_download_list.h"
#include "ui/download_list.h"
CPPUNIT_TEST_SUITE_REGISTRATION(TestUiDownloadList);
void
TestUiDownloadList::test_filter_pattern() {
CPPUNIT_ASSERT_EQUAL(std::string(""), ui::create_filter_pattern(""));
CPPUNIT_ASSERT_EQUAL(std::string(".*linux.*"), ui::create_filter_pattern("linux"));
CPPUNIT_ASSERT_EQUAL(std::string("^linux.*"), ui::create_filter_pattern("^linux"));
CPPUNIT_ASSERT_EQUAL(std::string(".*linux$"), ui::create_filter_pattern("linux$"));
CPPUNIT_ASSERT_EQUAL(std::string(".*linux.*"), ui::create_filter_pattern("LiNuX"));
}
void
TestUiDownloadList::test_filter_command() {
torrent::Object command = ui::create_filter_command(".*linux.*");
CPPUNIT_ASSERT(command.is_dict_key());
CPPUNIT_ASSERT_EQUAL(std::string("match"), command.as_dict_key());
const torrent::Object::list_type& args = command.as_dict_obj().as_list();
CPPUNIT_ASSERT_EQUAL((size_t)2, args.size());
CPPUNIT_ASSERT(args.front().is_dict_key());
CPPUNIT_ASSERT_EQUAL(std::string("d.name"), args.front().as_dict_key());
CPPUNIT_ASSERT(args.back().is_string());
CPPUNIT_ASSERT_EQUAL(std::string(".*linux.*"), args.back().as_string());
}
// A pattern that closes the 'match' argument early and appends a second
// command must stay a single inert string argument.
void
TestUiDownloadList::test_filter_command_does_not_inject() {
const std::string pattern = ui::create_filter_pattern("zzz},$d.custom1.set={pwned");
CPPUNIT_ASSERT_EQUAL(std::string(".*zzz},$d.custom1.set={pwned.*"), pattern);
torrent::Object command = ui::create_filter_command(pattern);
CPPUNIT_ASSERT(command.is_dict_key());
CPPUNIT_ASSERT_EQUAL(std::string("match"), command.as_dict_key());
const torrent::Object::list_type& args = command.as_dict_obj().as_list();
CPPUNIT_ASSERT_EQUAL((size_t)2, args.size());
CPPUNIT_ASSERT(args.back().is_string());
CPPUNIT_ASSERT_EQUAL(pattern, args.back().as_string());
}
+16
View File
@@ -0,0 +1,16 @@
#include "test/helpers/test_fixture.h"
class TestUiDownloadList : public test_fixture {
CPPUNIT_TEST_SUITE(TestUiDownloadList);
CPPUNIT_TEST(test_filter_pattern);
CPPUNIT_TEST(test_filter_command);
CPPUNIT_TEST(test_filter_command_does_not_inject);
CPPUNIT_TEST_SUITE_END();
public:
void test_filter_pattern();
void test_filter_command();
void test_filter_command_does_not_inject();
};