Compare commits

...

10 Commits

Author SHA1 Message Date
rakshasa 24693fc19e Release 0.15.6. 2025-08-23 08:08:36 +00:00
rakshasa 9661a2aaa9 Added test for xmlrpc string reflection. 2025-08-11 10:18:20 +02:00
rakshasa aaf42c247e Strict checking of d.views push_back arguments. 2025-08-11 10:16:23 +02:00
rakshasa cba48f4a10 Invert color of focus ui element. 2025-08-11 10:14:46 +02:00
rakshasa 4463bf418e Release 0.15.5. 2025-06-22 16:44:05 +00:00
Phil Rosenthal 8bd7c79755 Fix file descriptor leak in session file saving
When system.files.session.fdatasync is set to "no", file descriptors
  were not being closed after writing session files, causing a severe
  resource leak. Each save operation would leak one file descriptor.

  With hundreds of torrents, this leads to tens of thousands of leaked
  file descriptors within hours, mostly pointing to deleted session files.
  This can exhaust the system's file descriptor limit and cause rtorrent
  to fail when opening new files.

  The fix moves the close() call outside the fdatasync conditional block,
  ensuring file descriptors are always properly closed regardless of the
  fdatasync setting.
2025-06-18 19:04:45 +02:00
rakshasa f2b83d50e8 Use libtorrent stable-0.15 branch in workflow. 2025-06-08 13:01:36 +02:00
rakshasa ab40059891 Use libtorrent stable-0.15 branch in workflow. 2025-06-08 13:01:36 +02:00
rakshasa b9d880dea3 Properly handle -1 value passed to curl set_timeout. 2025-06-08 13:01:36 +02:00
rakshasa b4c59d2c7a Fixed corrupted stack in curl stack due to wrong argument type. 2025-06-07 13:17:34 +02:00
11 changed files with 50 additions and 22 deletions
+2
View File
@@ -14,6 +14,8 @@ jobs:
- name: Fetch libtorrent - name: Fetch libtorrent
run: | run: |
git clone https://github.com/rakshasa/libtorrent git clone https://github.com/rakshasa/libtorrent
cd libtorrent
git checkout stable-0.15
- name: Build libtorrent - name: Build libtorrent
run: | run: |
cd libtorrent cd libtorrent
+2
View File
@@ -14,6 +14,8 @@ jobs:
- name: Fetch libtorrent - name: Fetch libtorrent
run: | run: |
git clone https://github.com/rakshasa/libtorrent git clone https://github.com/rakshasa/libtorrent
cd libtorrent
git checkout stable-0.15
- name: Build libtorrent - name: Build libtorrent
run: | run: |
cd libtorrent cd libtorrent
+3 -3
View File
@@ -1,6 +1,6 @@
m4_pattern_allow([PKG_CHECK_EXISTS]) m4_pattern_allow([PKG_CHECK_EXISTS])
AC_INIT([rtorrent],[0.15.4],[sundell.software@gmail.com]) AC_INIT([rtorrent],[0.15.6],[sundell.software@gmail.com])
AC_CONFIG_HEADERS([config.h]) AC_CONFIG_HEADERS([config.h])
AC_CONFIG_MACRO_DIRS([scripts]) AC_CONFIG_MACRO_DIRS([scripts])
@@ -10,7 +10,7 @@ AM_PROG_AR
LT_INIT LT_INIT
AC_PROG_CXX AC_PROG_CXX
AC_DEFINE([API_VERSION], [14], [api version]) AC_DEFINE([API_VERSION], [15], [api version])
# Filter out unwanted flags added by autoconf on some systems, e.g. MacOS. # Filter out unwanted flags added by autoconf on some systems, e.g. MacOS.
TORRENT_REMOVE_UNWANTED(CXX, $CXX, -std=c++11 -std=gnu++11) TORRENT_REMOVE_UNWANTED(CXX, $CXX, -std=c++11 -std=gnu++11)
@@ -54,7 +54,7 @@ fi
PKG_CHECK_MODULES([LIBCURL], [libcurl],, [LIBCURL_CHECK_CONFIG]) PKG_CHECK_MODULES([LIBCURL], [libcurl],, [LIBCURL_CHECK_CONFIG])
PKG_CHECK_MODULES([CPPUNIT], [cppunit],, [no_cppunit="yes"]) PKG_CHECK_MODULES([CPPUNIT], [cppunit],, [no_cppunit="yes"])
PKG_CHECK_MODULES([DEPENDENCIES], [libtorrent >= 0.15.4]) PKG_CHECK_MODULES([DEPENDENCIES], [libtorrent >= 0.15.6])
AC_LANG_PUSH(C++) AC_LANG_PUSH(C++)
TORRENT_WITH_XMLRPC_C TORRENT_WITH_XMLRPC_C
+10 -9
View File
@@ -547,19 +547,20 @@ download_set_variable_string(core::Download* download, const torrent::Object::st
// //
torrent::Object torrent::Object
d_list_push_back(core::Download* download, const torrent::Object& rawArgs, const char* first_key, const char* second_key) { d_list_push_back_string(core::Download* download, const torrent::Object& rawArgs, const char* first_key, const char* second_key) {
download_get_variable(download, first_key, second_key).as_list().push_back(rawArgs); auto& arg = rawArgs.as_string();
download_get_variable(download, first_key, second_key).as_list().push_back(arg);
return torrent::Object(); return torrent::Object();
} }
torrent::Object torrent::Object
d_list_push_back_unique(core::Download* download, const torrent::Object& rawArgs, const char* first_key, const char* second_key) { d_list_push_back_unique_string(core::Download* download, const torrent::Object& rawArgs, const char* first_key, const char* second_key) {
const torrent::Object& args = (rawArgs.is_list() && !rawArgs.as_list().empty()) ? rawArgs.as_list().front() : rawArgs; auto& arg = rawArgs.as_string();
torrent::Object::list_type& list = download_get_variable(download, first_key, second_key).as_list(); torrent::Object::list_type& list = download_get_variable(download, first_key, second_key).as_list();
if (std::find_if(list.begin(), list.end(), [args](const torrent::Object& obj) { return torrent::object_equal(obj, args); }) == list.end()) if (std::none_of(list.begin(), list.end(), [arg](const torrent::Object& obj) { return torrent::object_equal(obj, arg); }))
list.push_back(rawArgs); list.push_back(arg);
return torrent::Object(); return torrent::Object();
} }
@@ -569,7 +570,7 @@ d_list_has(core::Download* download, const torrent::Object& rawArgs, const char*
const torrent::Object& args = (rawArgs.is_list() && !rawArgs.as_list().empty()) ? rawArgs.as_list().front() : rawArgs; const torrent::Object& args = (rawArgs.is_list() && !rawArgs.as_list().empty()) ? rawArgs.as_list().front() : rawArgs;
torrent::Object::list_type& list = download_get_variable(download, first_key, second_key).as_list(); torrent::Object::list_type& list = download_get_variable(download, first_key, second_key).as_list();
return (int64_t)(std::find_if(list.begin(), list.end(), [args](const torrent::Object& obj) { return torrent::object_equal(obj, args); }) != list.end()); return (int64_t)(std::any_of(list.begin(), list.end(), [args](const auto& obj) { return torrent::object_equal(obj, args); }));
} }
torrent::Object torrent::Object
@@ -776,8 +777,8 @@ initialize_command_download() {
CMD2_DL ("d.views", std::bind(&download_get_variable, std::placeholders::_1, "rtorrent", "views")); CMD2_DL ("d.views", std::bind(&download_get_variable, std::placeholders::_1, "rtorrent", "views"));
CMD2_DL ("d.views.has", std::bind(&d_list_has, std::placeholders::_1, std::placeholders::_2, "rtorrent", "views")); CMD2_DL ("d.views.has", std::bind(&d_list_has, std::placeholders::_1, std::placeholders::_2, "rtorrent", "views"));
CMD2_DL ("d.views.remove", std::bind(&d_list_remove, std::placeholders::_1, std::placeholders::_2, "rtorrent", "views")); CMD2_DL ("d.views.remove", std::bind(&d_list_remove, std::placeholders::_1, std::placeholders::_2, "rtorrent", "views"));
CMD2_DL ("d.views.push_back", std::bind(&d_list_push_back, std::placeholders::_1, std::placeholders::_2, "rtorrent", "views")); CMD2_DL ("d.views.push_back", std::bind(&d_list_push_back_string, std::placeholders::_1, std::placeholders::_2, "rtorrent", "views"));
CMD2_DL ("d.views.push_back_unique", std::bind(&d_list_push_back_unique, std::placeholders::_1, std::placeholders::_2, "rtorrent", "views")); CMD2_DL ("d.views.push_back_unique", std::bind(&d_list_push_back_unique_string, std::placeholders::_1, std::placeholders::_2, "rtorrent", "views"));
// This command really needs to be improved, so we have proper // This command really needs to be improved, so we have proper
// logging support. // logging support.
+3
View File
@@ -768,6 +768,7 @@ cmd_status_throttle_names(bool up, const torrent::Object::list_type& args) {
torrent::Object torrent::Object
apply_set_color(int color_id, const torrent::Object::string_type& color_str) { apply_set_color(int color_id, const torrent::Object::string_type& color_str) {
control->object_storage()->set_str_string(display::color_vars[color_id], color_str); control->object_storage()->set_str_string(display::color_vars[color_id], color_str);
display::Canvas::build_colors(); display::Canvas::build_colors();
return torrent::Object(); return torrent::Object();
} }
@@ -875,9 +876,11 @@ initialize_command_ui() {
// Build set/get methods for all color definitions // Build set/get methods for all color definitions
for (int color_id = 1; color_id < display::RCOLOR_MAX; color_id++) { for (int color_id = 1; color_id < display::RCOLOR_MAX; color_id++) {
control->object_storage()->insert_str(display::color_vars[color_id], "", rpc::object_storage::flag_string_type); control->object_storage()->insert_str(display::color_vars[color_id], "", rpc::object_storage::flag_string_type);
CMD2_ANY_STRING(std::string(display::color_vars[color_id]) + ".set", [color_id](const auto&, const auto& arg) { CMD2_ANY_STRING(std::string(display::color_vars[color_id]) + ".set", [color_id](const auto&, const auto& arg) {
return apply_set_color(color_id, arg); return apply_set_color(color_id, arg);
}); });
CMD2_ANY(display::color_vars[color_id], [color_id](const auto&, const auto&) { CMD2_ANY(display::color_vars[color_id], [color_id](const auto&, const auto&) {
return control->object_storage()->get_str(display::color_vars[color_id]); return control->object_storage()->get_str(display::color_vars[color_id]);
}); });
+6 -2
View File
@@ -233,10 +233,14 @@ CurlStack::global_cleanup() {
// TODO: Is this function supposed to set a per-handle timeout, or is // TODO: Is this function supposed to set a per-handle timeout, or is
// it the shortest timeout amongst all handles? // it the shortest timeout amongst all handles?
int int
CurlStack::set_timeout([[maybe_unused]] void* handle, std::chrono::microseconds timeout, void* userp) { CurlStack::set_timeout(void*, long timeout_ms, void* userp) {
CurlStack* stack = (CurlStack*)userp; CurlStack* stack = (CurlStack*)userp;
torrent::this_thread::scheduler()->update_wait_for_ceil_seconds(&stack->m_task_timeout, timeout); if (timeout_ms == -1)
torrent::this_thread::scheduler()->erase(&stack->m_task_timeout);
else
torrent::this_thread::scheduler()->update_wait_for_ceil_seconds(&stack->m_task_timeout, std::chrono::milliseconds(timeout_ms));
return 0; return 0;
} }
+1 -1
View File
@@ -79,7 +79,7 @@ public:
void receive_action(CurlSocket* socket, int type); void receive_action(CurlSocket* socket, int type);
static int set_timeout(void* handle, std::chrono::microseconds timeout, void* userp); static int set_timeout(void*, long timeout_ms, void* userp);
void transfer_done(void* handle, const char* msg); void transfer_done(void* handle, const char* msg);
+2 -1
View File
@@ -100,9 +100,10 @@ DownloadStore::write_bencode(const std::string& filename, const torrent::Object&
#else #else
fdatasync(fd); fdatasync(fd);
#endif #endif
::close(fd);
} }
::close(fd);
return true; return true;
download_store_save_error: download_store_save_error:
+2
View File
@@ -309,6 +309,8 @@ main(int argc, char** argv) {
"schedule2 = prune_file_status,3600,86400,((system.file_status_cache.prune))\n" "schedule2 = prune_file_status,3600,86400,((system.file_status_cache.prune))\n"
"protocol.encryption.set=allow_incoming,prefer_plaintext,enable_retry\n" "protocol.encryption.set=allow_incoming,prefer_plaintext,enable_retry\n"
"ui.color.focus.set=reverse\n"
); );
// Functions that might not get depracted as they are nice for // Functions that might not get depracted as they are nice for
+19 -4
View File
@@ -11,7 +11,15 @@
CPPUNIT_TEST_SUITE_REGISTRATION(TestXmlrpc); CPPUNIT_TEST_SUITE_REGISTRATION(TestXmlrpc);
torrent::Object xmlrpc_cmd_test_reflect([[maybe_unused]] rpc::target_type t, const torrent::Object& obj) { return obj; } torrent::Object
xmlrpc_cmd_test_reflect([[maybe_unused]] rpc::target_type t, const torrent::Object& obj) {
return obj;
}
torrent::Object
xmlrpc_cmd_test_reflect_string([[maybe_unused]] rpc::target_type t, const std::string& obj) {
return obj;
}
void initialize_command_dynamic(); void initialize_command_dynamic();
@@ -96,7 +104,12 @@ std::vector<std::tuple<std::string, std::string, std::string>> basic_requests =
std::make_tuple("Invalid - non-boolean boolean", std::make_tuple("Invalid - non-boolean boolean",
"<?xml version=\"1.0\"?><methodCall><methodName>xmlrpc_reflect</methodName><params><param><value><boolean>string value</boolean></value></param></params></methodCall>", "<?xml version=\"1.0\"?><methodCall><methodName>xmlrpc_reflect</methodName><params><param><value><boolean>string value</boolean></value></param></params></methodCall>",
"<?xml version=\"1.0\"?><methodResponse><fault><value><struct><member><name>faultCode</name><value><i8>-501</i8></value></member><member><name>faultString</name><value><string>unknown boolean value: string value</string></value></member></struct></value></fault></methodResponse>") "<?xml version=\"1.0\"?><methodResponse><fault><value><struct><member><name>faultCode</name><value><i8>-501</i8></value></member><member><name>faultString</name><value><string>unknown boolean value: string value</string></value></member></struct></value></fault></methodResponse>"),
std::make_tuple("CMD2_ANY_STRING",
"<?xml version=\"1.0\"?><methodCall><methodName>xmlrpc_reflect_string</methodName><params><param><value><string></string></value></param><param><value><string>test</string></value></param></params></methodCall>",
"<?xml version=\"1.0\"?><methodResponse><params><param><value><string>test</string></value></param></params></methodResponse>")
}; };
void void
@@ -109,9 +122,11 @@ TestXmlrpc::setUp() {
setlocale(LC_ALL, ""); setlocale(LC_ALL, "");
control = new Control; control = new Control;
if (rpc::commands.find("xmlrpc_reflect") == rpc::commands.end()) { if (rpc::commands.find("xmlrpc_reflect") == rpc::commands.end())
CMD2_ANY("xmlrpc_reflect", &xmlrpc_cmd_test_reflect); CMD2_ANY("xmlrpc_reflect", &xmlrpc_cmd_test_reflect);
}
if (rpc::commands.find("xmlrpc_reflect_string") == rpc::commands.end())
CMD2_ANY_STRING("xmlrpc_reflect_string", &xmlrpc_cmd_test_reflect_string);
} }
void void
-2
View File
@@ -27,6 +27,4 @@ private:
std::unique_ptr<TestMainThread> m_test_main_thread; std::unique_ptr<TestMainThread> m_test_main_thread;
rpc::XmlRpc m_xmlrpc; rpc::XmlRpc m_xmlrpc;
rpc::CommandMap m_map;
rpc::command_base m_commands[cmd_size];
}; };