Compare commits

..

23 Commits

Author SHA1 Message Date
rakshasa 9507ac17b3 Stuff. 2026-08-04 11:08:52 +02:00
rakshasa 49d45c1088 Stuff. 2026-08-04 11:04:20 +02:00
rakshasa 5ab973504a Stuff. 2026-08-04 10:47:23 +02:00
rakshasa ebe28e07b1 Stuff. 2026-08-04 10:40:43 +02:00
rakshasa 06a391f9d8 Stuff. 2026-08-03 09:11:23 +02:00
rakshasa 247ae7621a Tagged release 0.16.19. 2026-08-02 10:44:30 +02:00
xirvik 05831942a7 commands: don't register min_alloc/max_alloc for the generic socket category
SocketManager throws internal_error for category_generic, aborting rtorrent over RPC.
2026-07-30 12:42:24 +02:00
rakshasa ee18a1f3fc Added commit message linter. 2026-07-28 10:04:51 +02:00
Jari Sundell f52f20f1b6 Cleaned up system headers. 2026-07-26 19:53:18 +09:00
xirvik 41eef969ea commands: fix group.<name>.ratio.disable calling a removed command
group_insert() builds the disable method as "schedule_remove=...", but that
command no longer exists: the current name is schedule.remove, and the only
old-style aliases registered are schedule2 and schedule_remove2 (and those only
when deprecated commands are enabled at registration time). Every
group.<name>.ratio.disable therefore faults with

  Command "schedule_remove" does not exist.

including the built-in group.seeding.ratio.disable, so a ratio group can be
enabled but never disabled. The enable half already uses the current name.
2026-07-25 10:18:49 +02:00
fffe 1def5e8b36 Add log.print and enum.log_group methods 2026-07-22 11:08:32 +02:00
simonc56 8355870586 Mark 'd.timestamp.started' as safe in command download initialization 2026-07-20 08:21:32 +02:00
Jari Sundell ba2bc7e64d Various release bug fixes. 2026-07-19 18:53:57 +09:00
rakshasa 0c11deac50 Tagged release 0.16.18. 2026-07-17 10:15:28 +02:00
Jari Sundell f6b3ad0efd Minor cleanup of pex and other commands. 2026-07-15 19:40:13 +09:00
Jari Sundell 5d350f0bcc Use new encryption modes. 2026-07-14 17:34:14 +09:00
Jari Sundell 86fa0d195f Changing listen/dht port changes the listening/dht ports. 2026-07-12 17:19:33 +09:00
Jari Sundell 2354c9cdb5 Moved ChunkManager out of public header directory. 2026-07-10 17:54:37 +09:00
Jari Sundell 1b25bc7f56 Moved all sync/diskspace related methods to MemoryManager. 2026-07-10 02:21:08 +09:00
Jari Sundell c6bde213b4 Added runtime::MemoryManager to split out unrelated features from ChunkManager. 2026-07-09 01:56:30 +09:00
Jari Sundell 959448acda Fixed command_base t_pod align static asserts. 2026-07-08 17:15:56 +09:00
Jari Sundell 3b6da6feac Reordered http queue slots to avoid race conditions. 2026-07-07 16:34:19 +09:00
Jari Sundell ea2d22cb87 Removed unused add/remove error-event code. 2026-07-07 03:05:20 +09:00
39 changed files with 626 additions and 634 deletions
+65
View File
@@ -0,0 +1,65 @@
name: "Lint Commit Message Size"
on:
pull_request:
types: [opened, synchronize, reopened]
jobs:
check-commit-bounds:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Validate Line Count, Width, and Spacing
run: |
# Fetch commit hashes unique to this PR branch
COMMITS=$(git log --no-merges --pretty=format:"%H" origin/${{ github.base_ref }}..HEAD)
MAX_LINES=3
MAX_CHARS=90
FAILED=0
for commit in $COMMITS; do
SUBJECT=$(git log --format="%s" -n 1 $commit)
# Extract clean commit message, trimming trailing blank lines
RAW_MSG=$(git log --format="%B" -n 1 $commit)
CLEAN_MSG=$(echo "$RAW_MSG" | awk '{msg[NR]=$0} END {while(NR>0 && msg[NR]=="") NR--; for(i=1;i<=NR;i++) print msg[i]}')
# 1. Check total line count
LINE_COUNT=$(echo "$CLEAN_MSG" | wc -l)
if [ "$LINE_COUNT" -gt "$MAX_LINES" ]; then
echo "❌ Error: Commit message has too many lines ($LINE_COUNT/$MAX_LINES)."
echo " Commit: '$SUBJECT'"
FAILED=1
fi
# 2. Check for multiple consecutive blank lines
# This regex looks for 2 or more empty lines anywhere in the message
if echo "$CLEAN_MSG" | grep -pz '(\r?\n){3,}'; then
echo "❌ Error: Commit message contains multiple consecutive line breaks."
echo " Commit: '$SUBJECT'"
FAILED=1
fi
# 3. Check maximum width of any individual line
while IFS= read -r line; do
LINE_LENGTH=${#line}
if [ "$LINE_LENGTH" -gt "$MAX_CHARS" ]; then
echo "❌ Error: Line length exceeds limit ($LINE_LENGTH / $MAX_CHARS chars)."
echo " Offending line: '$line'"
FAILED=1
fi
done <<< "$CLEAN_MSG"
done
# Fail the job if any check failed
if [ "$FAILED" -ne 0 ]; then
exit 1
fi
echo "✅ All commit messages passed style rules!"
+3 -3
View File
@@ -1,6 +1,6 @@
m4_pattern_allow([PKG_CHECK_EXISTS])
AC_INIT([rtorrent],[0.16.17],[sundell.software@gmail.com])
AC_INIT([rtorrent],[0.16.19],[sundell.software@gmail.com])
AC_CONFIG_HEADERS([config.h])
AC_CONFIG_MACRO_DIRS([scripts])
@@ -14,7 +14,7 @@ AX_CXX_COMPILE_STDCXX(20, noext, mandatory)
PKG_PROG_PKG_CONFIG
AC_DEFINE([API_VERSION], [23], [api version])
AC_DEFINE([API_VERSION], [24], [api version])
RAK_CHECK_CFLAGS
RAK_CHECK_CXXFLAGS
@@ -47,7 +47,7 @@ fi
PKG_CHECK_MODULES([CPPUNIT], [cppunit],, [no_cppunit="yes"])
PKG_CHECK_MODULES([ZLIB], [zlib])
PKG_CHECK_MODULES([DEPENDENCIES], [libtorrent >= 0.16.17])
PKG_CHECK_MODULES([DEPENDENCIES], [libtorrent >= 0.16.19])
AC_LANG_PUSH(C++)
TORRENT_WITH_XMLRPC_C
+15 -9
View File
@@ -214,15 +214,21 @@ Add a preferred filename encoding to the list. The encodings are
attempted in the order they are inserted, if none match the torrent
default is used.
.TP
\fBprotocol.encryption.handshake.set\fR (deny, allow, prefer, require)
and \fBprotocol.encryption.stream.set\fR (deny, allow, prefer,
require) control how rtorrent deals with encrypted Bittorrent
connections. Handshake \fBallow\fR accepts plain or PE inbound and
tries plain first outbound with one PE retry on failure.
\fBprefer\fR tries PE first with one plain retry. Stream \fBallow\fR
and \fBprefer\fR offer both handshake-only and RC4 on outgoing PE
connections. The default is handshake=allow, stream=allow. Use
\fBprotocol.encryption\fR to inspect the effective policy.
\fBencryption = \fIoption\fB,\fI\&...\fB\fR
Set how rtorrent should deal with encrypted Bittorrent connections. By
default, encryption is disabled, equivalent to specifying the option
\fBnone\fR\&. Alternatively, any number of the following
options may be specified:
\fBallow_incoming\fR (allow incoming encrypted connections),
\fBtry_outgoing\fR (use encryption for outgoing connections),
\fBrequire\fR (disable unencrypted handshakes),
\fBrequire_RC4\fR (also disable plaintext transmission after the
initial encrypted handshake),
\fBenable_retry\fR (if the initial outgoing connection fails, retry
with encryption turned on if it was off or off if it was on),
\fBprefer_plaintext\fR (choose plaintext when peer offers a choice
between plaintext transmission and RC4 encryption, otherwise RC4 will be used).
.TP
\fBpeer_exchange = \fIyes | no\fB\fR
Enable/disable peer exchange for torrents that aren't marked private. Disabled by default.
+17 -11
View File
@@ -484,19 +484,25 @@ default is used.
</varlistentry>
<varlistentry>
<term>protocol.encryption.handshake.set = <replaceable>policy</replaceable></term>
<term>encryption = <replaceable>option</replaceable>,<replaceable>...</replaceable></term>
<listitem><para>
Control how rtorrent deals with encrypted Bittorrent connections.
<emphasis>protocol.encryption.handshake.set</emphasis> (deny, allow, prefer,
require) and <emphasis>protocol.encryption.stream.set</emphasis> (deny, allow,
prefer, require). Handshake <emphasis>allow</emphasis> accepts plain or PE
inbound and tries plain first outbound with one PE retry on failure.
<emphasis>prefer</emphasis> tries PE first with one plain retry. Stream
<emphasis>allow</emphasis> and <emphasis>prefer</emphasis> offer both
handshake-only and RC4 on outgoing PE connections. The default is
handshake=allow, stream=allow. Use <emphasis>protocol.encryption</emphasis> to
inspect the effective policy.
Set how rtorrent should deal with encrypted Bittorrent connections. By
default, encryption is disabled, equivalent to specifying the option
<emphasis>none</emphasis>. Alternatively, any number of the following
options may be specified:
</para><para>
<emphasis>allow_incoming</emphasis> (allow incoming encrypted connections),
<emphasis>try_outgoing</emphasis> (use encryption for outgoing connections),
<emphasis>require</emphasis> (disable unencrypted handshakes),
<emphasis>require_RC4</emphasis> (also disable plaintext transmission after the
initial encrypted handshake),
<emphasis>enable_retry</emphasis> (if the initial outgoing connection fails, retry
with encryption turned on if it was off or off if it was on),
<emphasis>prefer_plaintext</emphasis> (choose plaintext when peer offers a choice
between plaintext transmission and RC4 encryption, otherwise RC4 will be used).
</para></listitem>
</varlistentry>
+6 -32
View File
@@ -95,40 +95,14 @@
#schedule2 = ip_tick,0,1800,ip=rakshasa
#schedule2 = bind_tick,0,1800,bind=rakshasa
# --- Protocol encryption (PE handshake / optional RC4 stream) ---
# Default at startup: handshake=allow stream=allow
# Encryption options, set to none (default) or any combination of the following:
# allow_incoming, try_outgoing, require, require_RC4, enable_retry, prefer_plaintext
#
# Handshake (PE handshake; incoming accepts plain or PE for allow/prefer):
# deny - plain BT handshake only both ways; no retry
# allow - accept plain or PE inbound; plain first outbound with one PE retry
# prefer - accept plain or PE inbound; PE first outbound with one plain retry
# require - PE handshake required both ways; no retry
# The example value allows incoming encrypted connections, starts unencrypted
# outgoing connections but retries with encryption if they fail, preferring
# plain-text to RC4 encryption after the encrypted handshake.
#
# protocol.encryption.handshake.set = allow
#
# Stream (cipher negotiation after PE handshake succeeds):
# deny - offer handshake-only; require handshake-only (no RC4)
# allow - incoming: prefer handshake-only if offered, else RC4
# outgoing: offer both handshake-only and RC4
# prefer - incoming: pick RC4 if offered, else handshake-only
# outgoing: offer both handshake-only and RC4
# require - offer RC4 only; require RC4
#
# Outgoing retry summary with handshake=allow (plain first):
# stream=allow/prefer -> plain BT -> PE (both)
# stream=deny -> plain BT -> PE (handshake-only)
# stream=require -> plain BT -> PE (RC4-only)
#
# Outgoing retry summary with handshake=prefer (PE first):
# stream=allow/prefer -> PE (both) -> plain BT
# stream=deny -> PE (handshake-only) -> plain BT
# stream=require -> PE (RC4-only) -> plain BT
#
# Negotiated stream cipher is logged at connection_handshake level.
#
# protocol.encryption.stream.set = allow
#
# Use protocol.encryption to inspect the effective policy.
# protocol.encryption.set = allow_incoming,enable_retry,prefer_plaintext
# Enable DHT support for trackerless torrents or when all trackers are down.
# May be set to "disable" (completely disable DHT), "off" (do not start DHT),
+1 -2
View File
@@ -42,8 +42,7 @@ throttle.min_peers.seed.set = 30
throttle.max_peers.seed.set = 80
trackers.numwant.set = 80
protocol.encryption.handshake.set = allow
protocol.encryption.stream.set = prefer
protocol.encryption.set = allow_incoming,try_outgoing,enable_retry
# Limits for file handle resources, this is optimized for
# an `ulimit` of 1024 (a common default). You MUST leave
+1 -2
View File
@@ -72,8 +72,7 @@ rc.throttle.min_peers.seed = 30
rc.throttle.max_peers.seed = 80
rc.trackers.numwant = 80
rc.protocol.encryption.handshake.set('allow')
rc.protocol.encryption.stream.set('prefer')
rc.protocol.encryption.set('allow_incoming', 'try_outgoing', 'enable_retry')
-- Limits for file handle resources, this is optimized for
-- an `ulimit` of 1024 (a common default). You MUST leave
+1 -1
View File
@@ -12,7 +12,7 @@ echo "Client version: " `xmlrpc2scgi.py -p scgi://127.0.0.1:${PORT_NUMBER} syste
echo
echo "Generated by 'rtorrent/doc/scripts/print_option_string.sh' on `date -u`."
for i in strings.choke_heuristics strings.choke_heuristics.upload strings.choke_heuristics.download strings.connection_type strings.encryption.handshake strings.encryption.stream strings.ip_filter strings.ip_tos strings.log_group strings.tracker_event strings.tracker_mode; do
for i in strings.choke_heuristics strings.choke_heuristics.upload strings.choke_heuristics.download strings.connection_type strings.encryption strings.ip_filter strings.ip_tos strings.log_group strings.tracker_event strings.tracker_mode; do
echo
echo $i
echo `echo $i | tr 'a-z_.' '-'`
+2 -2
View File
@@ -174,6 +174,8 @@ libsub_root_a_SOURCES = \
utils/list_focus.h \
utils/lockfile.cc \
utils/lockfile.h \
utils/waitpid_queue.cc \
utils/waitpid_queue.h \
utils/watch_ready_queue.cc \
utils/watch_ready_queue.h \
\
@@ -188,8 +190,6 @@ libsub_root_a_SOURCES = \
command_local.cc \
command_logging.cc \
command_network.cc \
encryption_config.cc \
encryption_config.h \
command_peer.cc \
command_throttle.cc \
command_tracker.cc \
+1
View File
@@ -974,6 +974,7 @@ initialize_command_download() {
rpc::rpc.mark_safe("d.size_pex");
rpc::rpc.mark_safe("d.completed_bytes");
rpc::rpc.mark_safe("d.complete");
rpc::rpc.mark_safe("d.timestamp.started");
rpc::rpc.mark_safe("d.timestamp.finished");
rpc::rpc.mark_safe("d.bytes_done");
rpc::rpc.mark_safe("d.peers_accounted");
+17 -13
View File
@@ -444,17 +444,20 @@ initialize_command_dynamic() {
CMD2_ANY ("catch", std::bind(&cmd_catch, std::placeholders::_1, std::placeholders::_2));
CMD2_ANY ("strings.choke_heuristics", std::bind(&torrent::option_list_strings, torrent::OPTION_CHOKE_HEURISTICS));
CMD2_ANY ("strings.choke_heuristics.upload", std::bind(&torrent::option_list_strings, torrent::OPTION_CHOKE_HEURISTICS_UPLOAD));
CMD2_ANY ("strings.choke_heuristics.download", std::bind(&torrent::option_list_strings, torrent::OPTION_CHOKE_HEURISTICS_DOWNLOAD));
CMD2_ANY ("strings.connection_type", std::bind(&torrent::option_list_strings, torrent::OPTION_CONNECTION_TYPE));
CMD2_ANY ("strings.encryption.handshake", std::bind(&torrent::option_list_strings, torrent::OPTION_ENCRYPTION_MODE));
CMD2_ANY ("strings.encryption.stream", std::bind(&torrent::option_list_strings, torrent::OPTION_ENCRYPTION_MODE));
CMD2_ANY ("strings.ip_filter", std::bind(&torrent::option_list_strings, torrent::OPTION_IP_FILTER));
CMD2_ANY ("strings.ip_tos", std::bind(&torrent::option_list_strings, torrent::OPTION_IP_TOS));
CMD2_ANY ("strings.log_group", std::bind(&torrent::option_list_strings, torrent::OPTION_LOG_GROUP));
CMD2_ANY ("strings.tracker_event", std::bind(&torrent::option_list_strings, torrent::OPTION_TRACKER_EVENT));
CMD2_ANY ("strings.tracker_mode", std::bind(&torrent::option_list_strings, torrent::OPTION_TRACKER_MODE));
CMD2_ANY_STRING ("enum.log_group", [](auto, const auto& str) { return torrent::option_find_string_str(torrent::OPTION_LOG_GROUP, str); });
CMD2_ANY ("strings.choke_heuristics", [](auto, auto) { return torrent::option_list_strings(torrent::OPTION_CHOKE_HEURISTICS); });
CMD2_ANY ("strings.choke_heuristics.upload", [](auto, auto) { return torrent::option_list_strings(torrent::OPTION_CHOKE_HEURISTICS_UPLOAD); });
CMD2_ANY ("strings.choke_heuristics.download", [](auto, auto) { return torrent::option_list_strings(torrent::OPTION_CHOKE_HEURISTICS_DOWNLOAD); });
CMD2_ANY ("strings.connection_type", [](auto, auto) { return torrent::option_list_strings(torrent::OPTION_CONNECTION_TYPE); });
CMD2_ANY ("strings.encryption", [](auto, auto) { return torrent::Object::create_list(); });
CMD2_ANY ("strings.encryption.handshake", [](auto, auto) { return torrent::option_list_strings(torrent::OPTION_ENCRYPTION_HANDSHAKE); });
CMD2_ANY ("strings.encryption.stream", [](auto, auto) { return torrent::option_list_strings(torrent::OPTION_ENCRYPTION_STREAM); });
CMD2_ANY ("strings.ip_filter", [](auto, auto) { return torrent::option_list_strings(torrent::OPTION_IP_FILTER); });
CMD2_ANY ("strings.ip_tos", [](auto, auto) { return torrent::option_list_strings(torrent::OPTION_IP_TOS); });
CMD2_ANY ("strings.log_group", [](auto, auto) { return torrent::option_list_strings(torrent::OPTION_LOG_GROUP); });
CMD2_ANY ("strings.tracker_event", [](auto, auto) { return torrent::option_list_strings(torrent::OPTION_TRACKER_EVENT); });
CMD2_ANY ("strings.tracker_mode", [](auto, auto) { return torrent::option_list_strings(torrent::OPTION_TRACKER_MODE); });
// clang-format on
#ifdef HAVE_XMLRPC_TINYXML2
@@ -469,12 +472,13 @@ initialize_command_dynamic() {
rpc::rpc.mark_safe("method.rlookup");
rpc::rpc.mark_safe("catch");
rpc::rpc.mark_safe("enum.log_group");
rpc::rpc.mark_safe("strings.choke_heuristics");
rpc::rpc.mark_safe("strings.choke_heuristics.upload");
rpc::rpc.mark_safe("strings.choke_heuristics.download");
rpc::rpc.mark_safe("strings.connection_type");
rpc::rpc.mark_safe("strings.encryption.handshake");
rpc::rpc.mark_safe("strings.encryption.stream");
rpc::rpc.mark_safe("strings.encryption");
rpc::rpc.mark_safe("strings.ip_filter");
rpc::rpc.mark_safe("strings.ip_tos");
rpc::rpc.mark_safe("strings.log_group");
+7 -7
View File
@@ -117,7 +117,7 @@ group_insert(const torrent::Object::list_type& args) {
rpc::commands.call("method.insert", rpc::create_object_list("group." + name + ".ratio.enable", "simple",
"schedule=group." + name + ".ratio,5,60,on_ratio=" + name));
rpc::commands.call("method.insert", rpc::create_object_list("group." + name + ".ratio.disable", "simple",
"schedule_remove=group." + name + ".ratio"));
"schedule.remove=group." + name + ".ratio"));
rpc::commands.call("method.insert", rpc::create_object_list("group." + name + ".ratio.command", "simple",
"d.try_close= ;d.ignore_commands.set=1"));
rpc::commands.call("method.insert", rpc::create_object_list("group." + name + ".view", "string", view));
@@ -190,10 +190,6 @@ initialize_command_local() {
core::DownloadList* dList = control->core()->download_list();
torrent::FileManager* fileManager = torrent::file_manager();
if (rpc::call_command_value("method.use_deprecated") == 1) {
CMD_ANY_LIST ("file.append", std::bind(&cmd_file_append, std::placeholders::_2));
}
CMD_ANY ("system.hostname", std::bind(&system_hostname));
CMD_ANY ("system.pid", std::bind(&getpid));
@@ -254,12 +250,12 @@ initialize_command_local() {
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); });
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); });
if (i == 0)
continue;
CMD_ANY (category_name + ".min_alloc", [category](auto, auto) { return torrent::runtime::socket_manager()->category_min_allocation(category); });
CMD_ANY (category_name + ".max_alloc", [category](auto, auto) { return torrent::runtime::socket_manager()->category_max_allocation(category); });
CMD_ANY_VALUE_V(category_name + ".min_alloc.set", [category](auto, auto& value) { torrent::runtime::socket_manager()->set_category_min_allocation(category, value); });
CMD_ANY_VALUE_V(category_name + ".max_alloc.set", [category](auto, auto& value) { torrent::runtime::socket_manager()->set_category_max_allocation(category, value); });
}
@@ -358,6 +354,10 @@ initialize_command_local() {
rpc::rpc.mark_safe(category_name + ".size");
rpc::rpc.mark_safe(category_name + ".max_size");
if (i == 0)
continue;
rpc::rpc.mark_safe(category_name + ".min_alloc");
rpc::rpc.mark_safe(category_name + ".max_alloc");
}
+29
View File
@@ -1,6 +1,7 @@
#include "config.h"
#include <fcntl.h>
#include <iterator>
#include <stdio.h>
#include <unistd.h>
#include <torrent/data/chunk_utils.h>
@@ -15,6 +16,7 @@
#include "core/download.h"
#include "core/download_list.h"
#include "core/manager.h"
#include "rpc/parse.h"
#include "rpc/parse_commands.h"
torrent::Object
@@ -38,6 +40,32 @@ apply_log_add_output(const torrent::Object::list_type& args) {
return torrent::Object();
}
torrent::Object
apply_log_print(const torrent::Object::list_type& args) {
if (args.size() < 2)
throw torrent::input_error("Invalid number of arguments.");
torrent::Object::value_type group;
if (args.front().is_value())
group = args.front().as_value();
else if (args.front().is_string())
group = torrent::option_find_string_str(torrent::OPTION_LOG_GROUP, args.front().as_string());
else
throw torrent::input_error("Invalid log group.");
if (group < 0 || group >= torrent::LOG_GROUP_MAX_SIZE)
throw torrent::input_error("Invalid log group.");
std::string message;
for (auto itr = std::next(args.begin()); itr != args.end(); ++itr)
rpc::print_object_std(&message, &*itr, 0);
torrent::log_groups[group].internal_print(message);
return torrent::Object();
}
// TODO: Deprecated.
torrent::Object
apply_log(const torrent::Object::string_type& arg, int logType) {
@@ -112,6 +140,7 @@ initialize_command_logging() {
CMD2_ANY_STRING_V("log.close", std::bind(&torrent::log_close_output_str, std::placeholders::_2));
CMD2_ANY_LIST ("log.add_output", std::bind(&apply_log_add_output, std::placeholders::_2));
CMD2_ANY_LIST ("log.print", std::bind(&apply_log_print, std::placeholders::_2));
CMD2_ANY_STRING ("log.execute", std::bind(&apply_log, std::placeholders::_2, 0));
CMD2_ANY_STRING ("log.vmmap.dump", std::bind(&log_vmmap_dump, std::placeholders::_2));
+122 -5
View File
@@ -19,7 +19,6 @@
#include <torrent/utils/log.h>
#include <torrent/utils/option_strings.h>
#include "encryption_config.h"
#include "globals.h"
#include "control.h"
#include "command_helpers.h"
@@ -35,6 +34,118 @@
#include <systemd/sd-daemon.h>
#endif
torrent::Object
listen_port_range() {
auto port_range = torrent::runtime::client_config()->listen_port_range();
return std::to_string(port_range.first) + "-" + std::to_string(port_range.second);
}
void
set_listen_port_range(const std::string& arg) {
unsigned int port_first{}, port_last{};
if (std::sscanf(arg.c_str(), "%i-%i", &port_first, &port_last) != 2)
throw torrent::input_error("Invalid port_range argument.");
if (port_first >= (1 << 16) || port_last >= (1 << 16))
throw torrent::input_error("Port range out-of-bounds.");
torrent::runtime::client_config()->set_listen_port_range(port_first, port_last);
}
torrent::Object
get_encryption() {
auto encryption_modes = torrent::runtime::network_config()->encryption_modes();
return torrent::option_to_str_or_throw(torrent::OPTION_ENCRYPTION_HANDSHAKE, encryption_modes.first) + "," +
torrent::option_to_str_or_throw(torrent::OPTION_ENCRYPTION_STREAM, encryption_modes.second);
}
torrent::Object
get_handshake_encryption() {
auto encryption_modes = torrent::runtime::network_config()->encryption_modes();
return torrent::option_to_str_or_throw(torrent::OPTION_ENCRYPTION_MODE, encryption_modes.first);
}
torrent::Object
get_stream_encryption() {
auto encryption_modes = torrent::runtime::network_config()->encryption_modes();
return torrent::option_to_str_or_throw(torrent::OPTION_ENCRYPTION_MODE, encryption_modes.second);
}
torrent::Object
apply_obsolete_encryption(const torrent::Object::list_type& args) {
torrent::encryption_mode handshake_mode{torrent::ENCRYPTION_MODE_ALLOW};
torrent::encryption_mode stream_mode{torrent::ENCRYPTION_MODE_ALLOW};
for (auto& itr : args) {
auto arg = itr.as_string();
if (arg == "none") {
handshake_mode = torrent::ENCRYPTION_MODE_DENY;
stream_mode = torrent::ENCRYPTION_MODE_DENY;
break;
} else if (arg == "allow_incoming") {
} else if (arg == "try_outgoing") {
} else if (arg == "require") {
handshake_mode = torrent::ENCRYPTION_MODE_REQUIRE;
} else if (arg == "require_RC4" || arg == "require_rc4") {
handshake_mode = torrent::ENCRYPTION_MODE_REQUIRE;
stream_mode = torrent::ENCRYPTION_MODE_REQUIRE;
break;
} else if (arg == "enable_retry") {
} else if (arg == "prefer_plaintext") {
} else {
throw torrent::input_error("Invalid encryption option: '" + arg + "'");
}
}
lt_log_print(torrent::LOG_WARN, "Obsolete encryption options used, use 'handshake_{deny,allow,prefer,require}, stream_{deny,allow,prefer,require}' instead.");
torrent::runtime::network_config()->set_encryption_modes(handshake_mode, stream_mode);
return {};
}
torrent::Object
apply_encryption(const torrent::Object::list_type& args) {
if (args.empty())
throw torrent::input_error("No encryption options specified.");
torrent::encryption_mode encryption_mode, handshake_mode, stream_mode;
if (args.size() == 1) {
try {
encryption_mode = static_cast<torrent::encryption_mode>(torrent::option_find_string_str(torrent::OPTION_ENCRYPTION_MODE, args.front().as_string()));
} catch (torrent::input_error& e) {
return apply_obsolete_encryption(args);
}
torrent::runtime::network_config()->set_encryption_modes(encryption_mode, encryption_mode);
return {};
}
if (args.size() != 2)
return apply_obsolete_encryption(args);
try {
handshake_mode = static_cast<torrent::encryption_mode>(torrent::option_find_string_str(torrent::OPTION_ENCRYPTION_HANDSHAKE, args.front().as_string()));
stream_mode = static_cast<torrent::encryption_mode>(torrent::option_find_string_str(torrent::OPTION_ENCRYPTION_STREAM, args.back().as_string()));
} catch (torrent::input_error& e) {
return apply_obsolete_encryption(args);
}
torrent::runtime::network_config()->set_encryption_modes(handshake_mode, stream_mode);
return {};
}
torrent::Object
apply_tos(const torrent::Object::string_type& arg) {
rpc::command_base::value_type value;
@@ -214,9 +325,13 @@ initialize_command_network() {
CMD_ANY ("network.listen.backlog", [](auto, auto) { return torrent::runtime::network_config()->listen_backlog(); });
CMD_ANY_VALUE_V ("network.listen.backlog.set", [](auto, auto& value) { return torrent::runtime::network_config()->set_listen_backlog(value); });
CMD_VAR_BOOL ("protocol.pex", true);
CMD_ANY ("protocol.pex", [](auto, auto) { return torrent::runtime::client_config()->is_pex_enabled(); });
CMD_ANY_VALUE_V ("protocol.pex.set", [](auto, auto& value) { return torrent::runtime::client_config()->set_pex_enabled(value); });
encryption_config::initialize_commands();
CMD_ANY_LIST ("protocol.encryption", [](auto, auto) { return get_encryption(); });
CMD_ANY_LIST ("protocol.encryption.set", [](auto, auto& args) { return apply_encryption(args); });
CMD_ANY_LIST ("protocol.encryption.handshake", [](auto, auto) { return get_handshake_encryption(); });
CMD_ANY_LIST ("protocol.encryption.stream", [](auto, auto) { return get_stream_encryption(); });
CMD_VAR_STRING ("protocol.connection.leech", "leech");
CMD_VAR_STRING ("protocol.connection.seed", "seed");
@@ -287,8 +402,10 @@ initialize_command_network() {
CMD_ANY ("network.xmlrpc.size_limit", [](auto, auto) { return rpc::rpc.size_limit(); });
CMD_ANY_VALUE_V ("network.xmlrpc.size_limit.set", [](auto, auto& arg) { return rpc::rpc.set_size_limit(arg); });
CMD_VAR_BOOL ("network.rpc.use_xmlrpc", true);
CMD_VAR_BOOL ("network.rpc.use_jsonrpc", true);
CMD_ANY ("network.rpc.use_xmlrpc", [](auto, auto) { return rpc::rpc.use_xmlrpc(); });
CMD_ANY_VALUE_V ("network.rpc.use_xmlrpc.set", [](auto, auto& arg) { return rpc::rpc.set_use_xmlrpc(arg); });
CMD_ANY ("network.rpc.use_jsonrpc", [](auto, auto) { return rpc::rpc.use_jsonrpc(); });
CMD_ANY_VALUE_V ("network.rpc.use_jsonrpc.set", [](auto, auto& arg) { return rpc::rpc.set_use_jsonrpc(arg); });
CMD_ANY ("network.block.ipv4", [nw_config](auto, auto) { return nw_config->is_block_ipv4(); });
CMD_ANY_VALUE_V ("network.block.ipv4.set", [nw_config](auto, auto& value) { return nw_config->set_block_ipv4(value); });
+2 -1
View File
@@ -15,6 +15,7 @@
#include <torrent/rate.h>
#include <torrent/data/file_utils.h>
#include <torrent/net/http_stack.h>
#include <torrent/runtime/client_config.h>
#include <torrent/utils/string_manip.h>
#include "control.h"
@@ -271,7 +272,7 @@ DownloadFactory::receive_success() {
if (!m_session && m_variables["tied_to_file"].as_value())
rpc::call_command("d.tied_to_file.set", m_uri.empty() ? m_variables["tied_file"] : m_uri, rpc::make_target(download));
rpc::call_command("d.peer_exchange.set", rpc::call_command_value("protocol.pex"), rpc::make_target(download));
rpc::call_command("d.peer_exchange.set", torrent::runtime::client_config()->is_pex_enabled(), rpc::make_target(download));
torrent::resume_load_addresses(*download->download(), resumeObject);
torrent::resume_load_file_priorities(*download->download(), resumeObject);
+1 -17
View File
@@ -16,21 +16,6 @@
namespace display {
namespace {
char
connection_type_char(const torrent::Peer* p) {
if (p->is_encrypted())
return p->is_incoming() ? 'R' : 'L';
if (p->is_obfuscated())
return p->is_incoming() ? 'H' : 'h';
return p->is_incoming() ? 'r' : 'l';
}
} // namespace
WindowPeerList::WindowPeerList(core::Download* d, PList* l, PList::iterator* f) :
Window(new Canvas, 0, 0, 0, extent_full, extent_full),
m_download(d),
@@ -50,7 +35,6 @@ WindowPeerList::redraw() {
m_canvas->print(x, y, "UP"); x += 7;
m_canvas->print(x, y, "DOWN"); x += 7;
m_canvas->print(x, y, "PEER"); x += 7;
// CT: R/H/r or L/h/l (RC4 / handshake-only / plain) + peer type (u/p/ )
m_canvas->print(x, y, "CT/RE/LO"); x += 10;
m_canvas->print(x, y, "QS"); x += 6;
m_canvas->print(x, y, "DONE"); x += 6;
@@ -111,7 +95,7 @@ WindowPeerList::redraw() {
peerType = ' ';
m_canvas->print(x, y, "%c%c/%c%c/%c%c",
connection_type_char(p),
p->is_encrypted() ? (p->is_incoming() ? 'R' : 'L') : (p->is_incoming() ? 'r' : 'l'),
peerType,
p->is_down_choked() ? std::tolower(remoteChoked) : remoteChoked,
-88
View File
@@ -1,88 +0,0 @@
#include "config.h"
#include "encryption_config.h"
#include <torrent/runtime/network_config.h>
#include <torrent/runtime/runtime.h>
#include <torrent/utils/option_strings.h>
#include "command_helpers.h"
#include "globals.h"
namespace encryption_config {
namespace {
Policy
current_policy() {
return torrent::runtime::network_config()->encryption_policy();
}
} // namespace
Policy
default_policy() {
Policy policy;
policy.handshake = Policy::Mode::allow;
policy.stream = Policy::Mode::allow;
return policy;
}
std::string
mode_to_string(Policy::Mode value) {
return torrent::option_to_str_or_throw(torrent::OPTION_ENCRYPTION_MODE,
static_cast<unsigned int>(value));
}
void
apply_mode_value(Policy& policy, Policy::Mode Policy::*field, const std::string& value) {
policy.*field = static_cast<Policy::Mode>(
torrent::option_find_string_str(torrent::OPTION_ENCRYPTION_MODE, value));
}
std::string
summary_string(const Policy& policy) {
return "handshake=" + mode_to_string(policy.handshake)
+ " stream=" + mode_to_string(policy.stream);
}
void
apply_policy(const Policy& policy) {
torrent::runtime::network_config()->set_encryption_policy(policy);
}
torrent::Object
apply_handshake_set(const std::string& value) {
Policy policy = current_policy();
apply_mode_value(policy, &Policy::handshake, value);
apply_policy(policy);
return torrent::Object();
}
torrent::Object
apply_stream_set(const std::string& value) {
Policy policy = current_policy();
apply_mode_value(policy, &Policy::stream, value);
apply_policy(policy);
return torrent::Object();
}
void
initialize_commands() {
apply_policy(default_policy());
CMD2_ANY("protocol.encryption", [](auto, auto) { return summary_string(current_policy()); });
CMD2_ANY("protocol.encryption.handshake", [](auto, auto) { return mode_to_string(current_policy().handshake); });
CMD2_ANY_STRING_V("protocol.encryption.handshake.set", [](auto, auto& str) { return apply_handshake_set(str); });
CMD2_ANY("protocol.encryption.stream", [](auto, auto) { return mode_to_string(current_policy().stream); });
CMD2_ANY_STRING_V("protocol.encryption.stream.set", [](auto, auto& str) { return apply_stream_set(str); });
rpc::rpc.mark_safe("protocol.encryption");
rpc::rpc.mark_safe("protocol.encryption.handshake");
rpc::rpc.mark_safe("protocol.encryption.handshake.set");
rpc::rpc.mark_safe("protocol.encryption.stream");
rpc::rpc.mark_safe("protocol.encryption.stream.set");
}
} // namespace encryption_config
-29
View File
@@ -1,29 +0,0 @@
#ifndef RTORRENT_ENCRYPTION_CONFIG_H
#define RTORRENT_ENCRYPTION_CONFIG_H
#include <cstdint>
#include <string>
#include <torrent/runtime/encryption_policy.h>
#include <torrent/object.h>
namespace encryption_config {
using Policy = torrent::EncryptionPolicy;
Policy default_policy();
std::string mode_to_string(Policy::Mode value);
void apply_mode_value(Policy& policy, Policy::Mode Policy::*field, const std::string& value);
std::string summary_string(const Policy& policy);
void apply_policy(const Policy& policy);
torrent::Object apply_handshake_set(const std::string& value);
torrent::Object apply_stream_set(const std::string& value);
void initialize_commands();
} // namespace encryption_config
#endif
+2 -3
View File
@@ -2,12 +2,11 @@
#define RTORRENT_INPUT_INPUT_EVENT_H
#include <functional>
#include <torrent/event.h>
#include <torrent/system/event.h>
namespace input {
class InputEvent : public torrent::Event {
class InputEvent : public torrent::system::Event {
public:
typedef std::function<void (int)> slot_int;
+5 -7
View File
@@ -301,9 +301,6 @@ main(int argc, char** argv) {
"schedule = low_diskspace,5,60,((close_low_diskspace,500M))\n"
"schedule = prune_file_status,3600,86400,((system.file_status_cache.prune))\n"
"protocol.encryption.handshake.set=allow\n"
"protocol.encryption.stream.set=allow\n"
"ui.color.focus.set=reverse\n"
);
@@ -322,6 +319,8 @@ main(int argc, char** argv) {
CMD_REDIRECT("ratio.max.set", "group.seeding.ratio.max.set");
CMD_REDIRECT("ratio.upload.set", "group.seeding.ratio.upload.set");
CMD_REDIRECT("encryption", "protocol.encryption.set");
CMD_REDIRECT("check_hash", "pieces.hash.on_completion.set");
CMD_REDIRECT("connection_leech", "protocol.connection.leech.set");
@@ -345,8 +344,9 @@ main(int argc, char** argv) {
CMD_REDIRECT("directory", "directory.default.set");
CMD_REDIRECT("session", "session.path.set");
CMD_REDIRECT("scgi_port", "network.scgi.open_port");
CMD_REDIRECT("scgi_local", "network.scgi.open_local");
CMD_REDIRECT_NO_EXPORT("port_range", "network.listen.port.range.set");
CMD_REDIRECT_NO_EXPORT("scgi_port", "network.scgi.open_port");
CMD_REDIRECT_NO_EXPORT("scgi_local", "network.scgi.open_local");
CMD_REDIRECT("to_gm_time", "convert.gm_time");
CMD_REDIRECT("to_gm_date", "convert.gm_date");
@@ -390,8 +390,6 @@ main(int argc, char** argv) {
CMD_REDIRECT("schedule2", "schedule");
CMD_REDIRECT("schedule_remove2", "schedule.remove");
// TODO: Remove file.append when cleaning these up.
CMD_REDIRECT("bind", "network.bind_address.set");
CMD_REDIRECT("ip", "network.local_address.set");
CMD_REDIRECT("port_range", "network.port_range.set");
+43 -204
View File
@@ -1,23 +1,22 @@
#include "config.h"
#include <cassert>
#include <cerrno>
#include <cstring>
#include <fcntl.h>
#include <spawn.h>
#include <string>
#include "rpc/exec_file.h"
// #include <cassert>
// #include <cerrno>
// #include <cstring>
// #include <fcntl.h>
// #include <spawn.h>
// #include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <torrent/net/fd.h>
// #include <sys/types.h>
#include <sys/uio.h>
// #include <torrent/net/fd.h>
#include <torrent/system/thread.h>
#include <torrent/system/spawn_process.h>
#include <torrent/system/types.h>
#include "exec_file.h"
#include "parse.h"
// Standard POSIX environment pointer
extern char** environ;
#include "rpc/parse.h"
namespace rpc {
@@ -25,216 +24,56 @@ namespace rpc {
int
ExecFile::execute(const char* file, char* const* argv, int flags) {
assert(!((flags & flag_capture) && (flags & flag_background)));
torrent::system::SpawnProcess spawn_process;
// Write the executed command and its parameters to the log fd.
[[maybe_unused]] int result;
spawn_process.set_log_fd(m_log_fd);
spawn_process.set_background(flags & flag_background);
spawn_process.set_capture_output(flags & flag_capture);
if (m_log_fd != -1) {
for (char* const* itr = argv; *itr != NULL; itr++) {
if (itr == argv)
result = write(m_log_fd, "\n---\n", sizeof("\n---\n"));
else
result = write(m_log_fd, " ", 1);
std::vector<struct iovec> iovecs;
iovecs.reserve(32);
iovecs.push_back({const_cast<char*>("\n---\n"), 5});
result = write(m_log_fd, *itr, std::strlen(*itr));
for (auto* itr = argv; *itr != nullptr; itr++) {
if (itr != argv)
iovecs.push_back({const_cast<char*>(" "), 1});
iovecs.push_back({*itr, std::strlen(*itr)});
}
result = write(m_log_fd, "\n---\n", sizeof("\n---\n"));
iovecs.push_back({const_cast<char*>("\n---\n"), 5});
[[maybe_unused]] int result = ::writev(m_log_fd, iovecs.data(), iovecs.size());
}
posix_spawn_file_actions_t actions{};
if (posix_spawn_file_actions_init(&actions) != 0)
throw torrent::internal_error("ExecFile::execute(...) posix_spawn_file_actions_init failed.");
posix_spawnattr_t attr;
posix_spawnattr_init(&attr);
// Try to avoid leaking open fds to the spawned process. Prefer POSIX_SPAWN_CLOEXEC_DEFAULT
// (macOS-only) or posix_spawn_file_actions_addclosefrom_np (glibc >= 2.34, FreeBSD >= 13.1).
//
// Other platforms like musl libc, OpenBSD and NetBSD must rely on explicit O_CLOEXEC.
// Handle standard input redirection (/dev/null), posix_spawn_file_actions_addopen handles opening
// and dup2 natively
if (posix_spawn_file_actions_addopen(&actions, 0, "/dev/null", O_RDWR, 0) != 0) {
// Fallback if open fails inside action setup
posix_spawn_file_actions_addclose(&actions, 0);
}
int pipe_0 = -1;
int pipe_1 = -1;
// Handle standard output redirection
if (flags & flag_capture) {
torrent::fd_open_pipe(pipe_0, pipe_1);
posix_spawn_file_actions_adddup2(&actions, pipe_1, 1);
// Ensure the write end of the pipe is closed in the child after duplicating.
posix_spawn_file_actions_addclose(&actions, pipe_0);
posix_spawn_file_actions_addclose(&actions, pipe_1);
} else if (m_log_fd != -1) {
posix_spawn_file_actions_adddup2(&actions, m_log_fd, 1);
} else {
posix_spawn_file_actions_addopen(&actions, 1, "/dev/null", O_RDWR, 0);
}
if (m_log_fd != -1) {
posix_spawn_file_actions_adddup2(&actions, m_log_fd, 2);
} else {
posix_spawn_file_actions_addopen(&actions, 2, "/dev/null", O_RDWR, 0);
}
short spawn_flags = 0;
#if defined(POSIX_SPAWN_CLOEXEC_DEFAULT)
spawn_flags |= POSIX_SPAWN_CLOEXEC_DEFAULT;
#elif defined(HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCLOSEFROM_NP)
posix_spawn_file_actions_addclosefrom_np(&actions, 3);
#endif
if (flags & flag_background) {
#ifdef POSIX_SPAWN_SETSID
spawn_flags |= POSIX_SPAWN_SETSID;
#else
spawn_flags |= POSIX_SPAWN_SETPGROUP;
posix_spawnattr_setpgroup(&attr, 0);
#endif
}
posix_spawnattr_setflags(&attr, spawn_flags);
pid_t child_pid{};
int spawn_status = posix_spawnp(&child_pid, file, &actions, &attr, argv, environ);
posix_spawn_file_actions_destroy(&actions);
posix_spawnattr_destroy(&attr);
int spawn_status = spawn_process.execute(file, argv);
if (spawn_status != 0) {
if (pipe_0 != -1)
torrent::fd_close(pipe_0);
if (m_log_fd != -1) {
auto prefix = "\n--- posix_spawn failed: ";
auto errno_str = torrent::system::errno_enum_str(spawn_status) + " ---\n";
if (pipe_1 != -1)
torrent::fd_close(pipe_1);
struct iovec iovecs[2] = {
{const_cast<char*>(prefix), std::strlen(prefix)},
{const_cast<char*>(errno_str.c_str()), errno_str.size()},
};
[[maybe_unused]] int result = ::writev(m_log_fd, iovecs, 2);
}
throw torrent::input_error("ExecFile::execute() posix_spawn failed: " + torrent::system::errno_enum_str(spawn_status));
}
if (flags & flag_capture) {
m_capture = std::string();
torrent::fd_close(pipe_1);
char buffer[4096];
ssize_t length;
do {
length = read(pipe_0, buffer, sizeof(buffer));
if (length > 0)
m_capture += std::string(buffer, length);
} while (length > 0);
torrent::fd_close(pipe_0);
if (m_log_fd != -1) {
result = write(m_log_fd, "Captured output:\n", sizeof("Captured output:\n"));
result = write(m_log_fd, m_capture.data(), m_capture.length());
}
}
if (flags & flag_background) {
if (m_log_fd != -1)
result = write(m_log_fd, "\n--- Running in Background ---\n", sizeof("\n--- Running in Background ---\n"));
m_waitpid_queue.close_pid(spawn_process.child_pid());
return 0;
}
int status;
while (::waitpid(child_pid, &status, 0) == -1) {
switch (errno) {
case EINTR:
continue;
case ECHILD:
throw torrent::internal_error("ExecFile::execute(...) waitpid failed with ECHILD, child process not found.");
case EINVAL:
throw torrent::internal_error("ExecFile::execute(...) waitpid failed with EINVAL.");
default:
throw torrent::internal_error("ExecFile::execute(...) waitpid failed with unexpected error: " + std::string(std::strerror(errno)));
}
};
// Check return value?
if (m_log_fd != -1) {
if (WIFEXITED(status) && WEXITSTATUS(status) == 0)
result = write(m_log_fd, "\n--- Success ---\n", sizeof("\n--- Success ---\n"));
else
result = write(m_log_fd, "\n--- Error ---\n", sizeof("\n--- Error ---\n"));
}
return status;
}
torrent::Object
ExecFile::execute_object(const torrent::Object& rawArgs, int flags) {
char* argsBuffer[max_args];
char** argsCurrent = argsBuffer;
// Size of value strings are less than 24.
char valueBuffer[buffer_size+1];
char* valueCurrent = valueBuffer;
if (rawArgs.is_list()) {
const torrent::Object::list_type& args = rawArgs.as_list();
if (args.empty())
throw torrent::input_error("Too few arguments.");
for (torrent::Object::list_const_iterator itr = args.begin(), last = args.end(); itr != last; itr++, argsCurrent++) {
if (argsCurrent == argsBuffer + max_args - 1)
throw torrent::input_error("Too many arguments.");
if (itr->is_string() && (!(flags & flag_expand_tilde) || *itr->as_string().c_str() != '~')) {
*argsCurrent = const_cast<char*>(itr->as_string().c_str());
} else {
*argsCurrent = valueCurrent;
valueCurrent = print_object(valueCurrent, valueBuffer + buffer_size, &*itr, flags) + 1;
if (valueCurrent >= valueBuffer + buffer_size)
throw torrent::input_error("Overflowed execute arg buffer.");
}
}
} else {
const torrent::Object::string_type& args = rawArgs.as_string();
if ((flags & flag_expand_tilde) && args.c_str()[0] == '~') {
*argsCurrent = valueCurrent;
valueCurrent = print_object(valueCurrent, valueBuffer + buffer_size, &rawArgs, flags) + 1;
} else {
*argsCurrent = const_cast<char*>(args.c_str());
}
argsCurrent++;
}
*argsCurrent = NULL;
int status = execute(argsBuffer[0], argsBuffer, flags);
if ((flags & flag_throw) && status != 0)
throw torrent::input_error("Bad return code.");
if (flags & flag_capture)
return m_capture;
m_capture = spawn_process.capture_child_output();
return torrent::Object((int64_t)status);
return spawn_process.wait_for_child();
}
}
} // namespace rpc
+4
View File
@@ -3,6 +3,8 @@
#include <torrent/object.h>
#include "utils/waitpid_queue.h"
namespace rpc {
class ExecFile {
@@ -24,6 +26,8 @@ public:
private:
int m_log_fd{-1};
std::string m_capture;
utils::WaitpidQueue m_waitpid_queue;
};
}
+6 -37
View File
@@ -113,25 +113,20 @@ bool
RpcManager::process(RPCType type, const char* in_buffer, uint32_t length, slot_response_callback callback) {
switch (type) {
case RPCType::XML:
// TODO: 'network.rpc.use_xmlrpc' should be a bool in RpcManager, not a command variable.
if (m_xmlrpc.is_valid() && rpc::call_command_value("network.rpc.use_xmlrpc")) {
return m_xmlrpc.process(in_buffer, length, callback);
} else {
if (!m_xmlrpc.is_valid() || !m_use_xmlrpc) {
const std::string response = "<?xml version=\"1.0\"?><methodResponse><fault><value><struct><member><name>faultCode</name><value><i8>-501</i8></value></member><member><name>faultString</name><value><string>XML-RPC not supported</string></value></member></struct></value></fault></methodResponse>";
return callback(response.c_str(), response.size());
}
break;
return m_xmlrpc.process(in_buffer, length, callback);
case RPCType::JSON:
if (rpc::call_command_value("network.rpc.use_jsonrpc")) {
return m_jsonrpc.process(in_buffer, length, callback);
} else {
if (!m_use_jsonrpc) {
const std::string response = "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32601,\"message\":\"JSON-RPC not supported\"},\"id\":null}";
return callback(response.c_str(), response.size());
}
break;
return m_jsonrpc.process(in_buffer, length, callback);
default:
throw torrent::input_error("invalid parameters: unknown RPC type");
@@ -172,32 +167,6 @@ RpcManager::cleanup() {
m_jsonrpc.cleanup();
}
bool
RpcManager::is_type_enabled(RPCType type) const {
switch (type) {
case RPCType::XML:
return m_is_xmlrpc_enabled;
case RPCType::JSON:
return m_is_jsonrpc_enabled;
default:
throw torrent::input_error("invalid parameters: unknown RPC type");
}
}
void
RpcManager::set_type_enabled(RPCType type, bool enabled) {
switch (type) {
case RPCType::XML:
m_is_xmlrpc_enabled = enabled;
break;
case RPCType::JSON:
m_is_jsonrpc_enabled = enabled;
break;
default:
throw torrent::input_error("invalid parameters: unknown RPC type");
}
}
void
RpcManager::insert_command(const char* name, const char* parm, const char* doc) {
m_xmlrpc.insert_command(name, parm, doc);
+13 -4
View File
@@ -67,8 +67,11 @@ public:
int dialect() { return m_xmlrpc.dialect(); }
void set_dialect(int dialect) { m_xmlrpc.set_dialect(dialect); }
bool is_type_enabled(RPCType type) const;
void set_type_enabled(RPCType type, bool enabled);
bool use_xmlrpc() const;
void set_use_xmlrpc(bool v);
bool use_jsonrpc() const;
void set_use_jsonrpc(bool v);
bool process(RPCType type, const char* in_buffer, uint32_t length, slot_response_callback callback);
bool process_untrusted(RPCType type, const char* in_buffer, uint32_t length, slot_response_callback callback);
@@ -102,8 +105,9 @@ private:
JsonRpc m_jsonrpc;
bool m_handlers_initialized{};
bool m_is_jsonrpc_enabled{true};
bool m_is_xmlrpc_enabled{true};
bool m_use_jsonrpc{true};
bool m_use_xmlrpc{true};
std::atomic<bool> m_scgi_allow_compression{true};
std::atomic<unsigned int> m_scgi_min_compress_size{1000};
@@ -116,6 +120,11 @@ private:
extern RpcManager rpc;
inline bool RpcManager::use_xmlrpc() const { return m_use_xmlrpc; }
inline void RpcManager::set_use_xmlrpc(bool v) { m_use_xmlrpc = v; }
inline bool RpcManager::use_jsonrpc() const { return m_use_jsonrpc; }
inline void RpcManager::set_use_jsonrpc(bool v) { m_use_jsonrpc = v; }
} // namespace rpc
#endif
+2 -2
View File
@@ -3,13 +3,13 @@
#include <array>
#include <memory>
#include <torrent/event.h>
#include <torrent/system/event.h>
#include "rpc/scgi_task.h"
namespace rpc {
class SCgi : public torrent::Event {
class SCgi : public torrent::system::Event {
public:
static const int max_tasks = 100;
+2 -2
View File
@@ -4,13 +4,13 @@
#include <memory>
#include <mutex>
#include <vector>
#include <torrent/event.h>
#include <torrent/system/event.h>
namespace rpc {
class SCgi;
class SCgiTask : public torrent::Event {
class SCgiTask : public torrent::system::Event {
public:
static constexpr int default_buffer_size = 8191;
static constexpr int max_header_size = 2000;
+1 -1
View File
@@ -38,7 +38,7 @@ parse_main_options(int argc, char** argv) {
optionParser.insert_option('b', [](auto& arg) { rpc::call_command_set_string("network.bind_address.set", arg); });
optionParser.insert_option('d', [](auto& arg) { rpc::call_command_set_string("directory.default.set", arg); });
optionParser.insert_option('i', [](auto& arg) { rpc::call_command_set_string("ip", arg); });
optionParser.insert_option('p', [](auto& arg) { rpc::call_command_set_string("network.port_range.set", arg); });
optionParser.insert_option('p', [](auto& arg) { rpc::call_command_set_string("network.listen.port.range.set", arg); });
optionParser.insert_option('s', [](auto& arg) { rpc::call_command_set_string("session", arg); });
optionParser.insert_option('O', [](auto& arg) { rpc::parse_command_single_std(arg); });
+1 -1
View File
@@ -111,7 +111,7 @@ ElementPeerList::create_info() {
element->push_column("Client:", te_command("p.client_version="));
element->push_column("Options:", te_command("p.options_str="));
element->push_column("Connected:", te_command("if=$p.is_incoming=,incoming,outgoing"));
element->push_column("Encrypted:", te_command("if=$p.is_encrypted=,yes,$if=$p.is_obfuscated=\\,handshake\\,no"));
element->push_column("Encrypted:", te_command("if=$p.is_encrypted=,yes,$p.is_obfuscated=,handshake,no"));
element->push_back("");
element->push_column("Snubbed:", te_command("if=$p.is_snubbed=,yes,no"));
+115
View File
@@ -0,0 +1,115 @@
#include "config.h"
#include "utils/waitpid_queue.h"
#include <sys/wait.h>
#include <torrent/exceptions.h>
namespace utils {
WaitpidQueue::WaitpidQueue() {
m_worker = std::async(std::launch::async, [this]() {
bool is_running = true;
auto wait_time = 50ms;
while (is_running) {
if (!m_queue.empty()) {
auto start_time = std::chrono::steady_clock::now();
while (!m_wakeup_worker.load(std::memory_order_acquire) && !m_should_shutdown) {
auto elapsed = std::chrono::steady_clock::now() - start_time;
if (elapsed >= wait_time)
break;
std::this_thread::sleep_for(50ms);
}
} else {
m_wakeup_worker.wait(false, std::memory_order_acquire);
std::this_thread::sleep_for(50ms);
}
std::set<pid_t> queue;
{
std::lock_guard<std::mutex> guard(m_mutex);
if (m_should_shutdown) {
if (m_queue.empty())
return;
is_running = false;
}
if (m_queue.empty())
throw torrent::internal_error("WaitpidQueue worker thread woke up but queue is empty.");
queue = m_queue;
m_wakeup_worker.store(false, std::memory_order_release);
}
wait_time = std::min(10 * 1000ms, wait_time * 2);
for (auto pid : queue) {
if (::waitpid(pid, nullptr, WNOHANG) == 0)
continue;
{
std::lock_guard<std::mutex> guard(m_mutex);
if (m_queue.erase(pid) != 1)
throw torrent::internal_error("WaitpidQueue worker thread could not find pid in queue.");
}
wait_time = std::max(50ms, wait_time / 2);
m_remaining.fetch_sub(1, std::memory_order_release);
m_remaining.notify_all();
}
}
});
}
WaitpidQueue::~WaitpidQueue() {
{
std::lock_guard<std::mutex> guard(m_mutex);
m_should_shutdown = true;
}
m_wakeup_worker.store(true, std::memory_order_release);
m_wakeup_worker.notify_all();
// m_worker.wait();
}
void
WaitpidQueue::close_pid(pid_t pid) {
if (pid < 0)
throw torrent::internal_error("WaitpidQueue::close_pid() called with invalid pid.");
m_remaining.fetch_add(1, std::memory_order_acquire);
{
std::lock_guard<std::mutex> guard(m_mutex);
// if (!m_queue.empty()) {
// m_queue.push_back(pid);
// return;
// }
m_queue.insert(pid);
}
m_wakeup_worker.store(true, std::memory_order_release);
m_wakeup_worker.notify_all();
}
void
WaitpidQueue::wait_for(uint32_t max_remaining) {
while (m_remaining.load(std::memory_order_acquire) > max_remaining)
m_remaining.wait(max_remaining, std::memory_order_acquire);
}
} // namespace torrent::utils
+44
View File
@@ -0,0 +1,44 @@
#ifndef RTORRENT_UTILS_WAITPID_QUEUE_H
#define RTORRENT_UTILS_WAITPID_QUEUE_H
#include <future>
#include <set>
#include <torrent/system/common.h>
namespace utils {
class WaitpidQueue {
public:
WaitpidQueue();
~WaitpidQueue();
uint32_t size() const;
void close_pid(int pid);
void wait_for(uint32_t max_remaining);
private:
WaitpidQueue(const WaitpidQueue&) = delete;
WaitpidQueue& operator=(const WaitpidQueue&) = delete;
std::future<void> m_worker;
align_cacheline
std::mutex m_mutex;
std::set<pid_t> m_queue;
bool m_should_shutdown{};
align_cacheline
std::atomic<bool> m_wakeup_worker{};
std::atomic<uint32_t> m_remaining{};
};
inline uint32_t WaitpidQueue::size() const { return m_remaining.load(std::memory_order_acquire); }
} // namespace utils
#endif
+2 -2
View File
@@ -49,8 +49,8 @@ rtorrent_Test_Rpc_SOURCES = $(rtorrent_Test_Common) \
rtorrent_Test_Src_SOURCES = $(rtorrent_Test_Common) \
src/test_command_dynamic.cc \
src/test_command_dynamic.h \
src/test_encryption_config.cc \
src/test_encryption_config.h \
src/test_command_local.cc \
src/test_command_local.h \
src/test_watch_ready_queue.cc \
src/test_watch_ready_queue.h
+22 -8
View File
@@ -2,9 +2,10 @@
#define LIBTORRENT_HELPERS_MOCK_COMPARE_H
#include <algorithm>
#include <map>
#include <type_traits>
#include <torrent/event.h>
#include <torrent/net/socket_address.h>
#include <torrent/system/event.h>
// Compare arguments to mock functions with what is expected. The lhs
// are the expected arguments, rhs are the ones called with.
@@ -13,13 +14,13 @@ template <typename Arg>
inline bool mock_compare_arg(Arg lhs, Arg rhs) { return lhs == rhs; }
template <int I, typename A, typename... Args>
typename std::enable_if<I == 1, int>::type
std::enable_if_t<I == 1, int>
mock_compare_tuple(const std::tuple<A, Args...>& lhs, const std::tuple<Args...>& rhs) {
return mock_compare_arg(std::get<I>(lhs), std::get<I - 1>(rhs)) ? 0 : 1;
}
template <int I, typename A, typename... Args>
typename std::enable_if<1 < I, int>::type
std::enable_if_t<1 < I, int>
mock_compare_tuple(const std::tuple<A, Args...>& lhs, const std::tuple<Args...>& rhs) {
auto res = mock_compare_tuple<I - 1>(lhs, rhs);
@@ -70,24 +71,37 @@ void mock_compare_add(T* v) {
// Specialize:
//
constexpr int mock_compare_gt_two_int = -0xFD30;
template <>
inline bool mock_compare_arg<int>(int lhs, int rhs) {
if (lhs == mock_compare_gt_two_int)
return rhs > 2;
if (rhs == mock_compare_gt_two_int)
return lhs > 2;
return lhs == rhs;
}
template <>
inline bool mock_compare_arg<sockaddr*>(sockaddr* lhs, sockaddr* rhs) {
return lhs != nullptr && rhs != nullptr && torrent::sa_equal(lhs, rhs);
}
template <>
inline bool mock_compare_arg<const sockaddr*>(const sockaddr* lhs, const sockaddr* rhs) {
return lhs != nullptr && rhs != nullptr && torrent::sa_equal(lhs, rhs);
}
template <>
inline bool mock_compare_arg<torrent::Event*>(torrent::Event* lhs, torrent::Event* rhs) {
if (mock_compare_map<torrent::Event>::is_key(lhs)) {
if (!mock_compare_map<torrent::Event>::has_value(rhs)) {
mock_compare_map<torrent::Event>::values[lhs] = rhs;
inline bool mock_compare_arg<torrent::system::Event*>(torrent::system::Event* lhs, torrent::system::Event* rhs) {
if (mock_compare_map<torrent::system::Event>::is_key(lhs)) {
if (!mock_compare_map<torrent::system::Event>::has_value(rhs)) {
mock_compare_map<torrent::system::Event>::values[lhs] = rhs;
return true;
}
return mock_compare_map<torrent::Event>::has_key(lhs) && mock_compare_map<torrent::Event>::get(lhs) == rhs;
return mock_compare_map<torrent::system::Event>::has_key(lhs) && mock_compare_map<torrent::system::Event>::get(lhs) == rhs;
}
return lhs == rhs;
+5 -66
View File
@@ -6,9 +6,9 @@
#include <iostream>
#include <unistd.h>
#include "torrent/event.h"
#include "torrent/net/socket_address.h"
#include "torrent/net/fd.h"
#include "torrent/system/event.h"
#include "torrent/utils/log.h"
#include "torrent/utils/random.h"
@@ -32,7 +32,7 @@ mock_clear(bool ignore_assert) {
MOCK_CLEANUP_MAP(torrent::random_uniform_uint16);
MOCK_CLEANUP_MAP(torrent::random_uniform_uint32);
mock_compare_map<torrent::Event>::values.clear();
mock_compare_map<torrent::system::Event>::values.clear();
}
} // namespace
@@ -55,7 +55,9 @@ mock_redirect_defaults([[maybe_unused]] mock_redirect_flags flags) {
mock_redirect(torrent::fd__close, std::function<int(int fildes)>([](int fildes) { return ::close(fildes); }));
mock_redirect(torrent::fd__fcntl_int, std::function<int(int fildes, int cmd, int arg)>([](int fildes, int cmd, int arg) { return ::fcntl(fildes, cmd, arg); }));
mock_redirect(torrent::fd__setsockopt_int, std::function<int(int socket, int level, int option_name, int option_value)>([](int socket, int level, int option_name, int option_value) { return ::setsockopt(socket, level, option_name, &option_value, sizeof(int)); }));
mock_redirect(torrent::fd__setsockopt_int, std::function<int(int socket, int level, int option_name, int option_value)>([](int socket, int level, int option_name, int option_value) {
return ::setsockopt(socket, level, option_name, &option_value, sizeof(int));
}));
mock_redirect(torrent::fd__socket, std::function<int(int domain, int type, int protocol)>([](int domain, int type, int protocol) { return ::socket(domain, type, protocol); }));
}
@@ -112,69 +114,6 @@ int fd__socket(int domain, int type, int protocol) {
return mock_call<int>(__func__, &torrent::fd__socket, domain, type, protocol);
}
//
// Mock functions for 'torrent/common.h':
//
namespace this_thread {
void event_open(Event* event) {
MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name());
return mock_call<void>(__func__, &torrent::this_thread::event_open, event);
}
void event_open_and_count(Event* event) {
MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name());
return mock_call<void>(__func__, &torrent::this_thread::event_open_and_count, event);
}
void event_close_and_count(Event* event) {
MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name());
return mock_call<void>(__func__, &torrent::this_thread::event_close_and_count, event);
}
void event_closed_and_count(Event* event) {
MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name());
return mock_call<void>(__func__, &torrent::this_thread::event_closed_and_count, event);
}
void event_insert_read(Event* event) {
MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name());
return mock_call<void>(__func__, &torrent::this_thread::event_insert_read, event);
}
void event_insert_write(Event* event) {
MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name());
return mock_call<void>(__func__, &torrent::this_thread::event_insert_write, event);
}
void event_insert_error(Event* event) {
MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name());
return mock_call<void>(__func__, &torrent::this_thread::event_insert_error, event);
}
void event_remove_read(Event* event) {
MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name());
return mock_call<void>(__func__, &torrent::this_thread::event_remove_read, event);
}
void event_remove_write(Event* event) {
MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name());
return mock_call<void>(__func__, &torrent::this_thread::event_remove_write, event);
}
void event_remove_error(Event* event) {
MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name());
return mock_call<void>(__func__, &torrent::this_thread::event_remove_error, event);
}
void event_remove_and_close(Event* event) {
MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name());
return mock_call<void>(__func__, &torrent::this_thread::event_remove_and_close, event);
}
}
//
// Mock functions for 'torrent/utils/random.h':
//
-16
View File
@@ -169,19 +169,3 @@ TestParseOptions::test_flag_libtorrent() {
FLAG_LT_LOG_ASSERT_ERROR("resume_data|rpc_dump");
}
#define FLAGS_LT_IP_TOS_ASSERT(flags, result) \
CPPUNIT_ASSERT(rpc::parse_option_flags(flags, std::bind(&torrent::option_find_string_str, torrent::OPTION_IP_TOS, std::placeholders::_1)) == (result))
#define FLAGS_LT_IP_TOS_ASSERT_ERROR(flags) \
ASSERT_CATCH_INPUT_ERROR(rpc::parse_option_flags(flags, std::bind(&torrent::option_find_string_str, torrent::OPTION_IP_TOS, std::placeholders::_1)))
void
TestParseOptions::test_flags_libtorrent() {
FLAGS_LT_IP_TOS_ASSERT("throughput", torrent::option_find_string(torrent::OPTION_IP_TOS, "throughput"));
FLAGS_LT_IP_TOS_ASSERT("lowdelay | throughput",
torrent::option_find_string(torrent::OPTION_IP_TOS, "lowdelay")
| torrent::option_find_string(torrent::OPTION_IP_TOS, "throughput"));
FLAGS_LT_IP_TOS_ASSERT_ERROR("throughput_");
}
-2
View File
@@ -14,7 +14,6 @@ class TestParseOptions : public test_fixture {
CPPUNIT_TEST(test_flags_print_flags);
CPPUNIT_TEST(test_flag_libtorrent);
CPPUNIT_TEST(test_flags_libtorrent);
CPPUNIT_TEST_SUITE_END();
@@ -30,5 +29,4 @@ public:
void test_flags_print_flags();
void test_flag_libtorrent();
void test_flags_libtorrent();
};
+54
View File
@@ -0,0 +1,54 @@
#include "config.h"
#include "test/src/test_command_local.h"
#include <torrent/torrent.h>
#include <torrent/runtime/socket_manager.h>
#include <torrent/utils/option_strings.h>
#include "control.h"
#include "globals.h"
#include "rpc/parse_commands.h"
CPPUNIT_TEST_SUITE_REGISTRATION(TestCommandLocal);
void initialize_command_local();
void
TestCommandLocal::setUp() {
torrent::initialize_main_thread();
torrent::initialize();
if (control == nullptr)
control = new Control;
if (!rpc::commands.has("system.sockets.size"))
initialize_command_local();
}
void
TestCommandLocal::tearDown() {
torrent::cleanup();
}
void
TestCommandLocal::test_socket_category_commands() {
for (uint32_t i = 0; i < torrent::runtime::SocketManager::category_count; ++i) {
auto category = static_cast<torrent::runtime::socket_manager_category_t>(i);
auto name = "system.sockets." + torrent::option_to_str_or_throw(torrent::OPTION_SOCKET_CATEGORY, i);
for (const auto suffix : {".size", ".max_size", ".min_alloc", ".max_alloc"})
if (rpc::commands.has(name + suffix))
CPPUNIT_ASSERT_NO_THROW(rpc::commands.call(name + suffix));
const bool has_allocation = category != torrent::runtime::category_generic;
CPPUNIT_ASSERT(rpc::commands.has(name + ".size"));
CPPUNIT_ASSERT(rpc::commands.has(name + ".max_size"));
CPPUNIT_ASSERT_EQUAL(has_allocation, rpc::commands.has(name + ".min_alloc"));
CPPUNIT_ASSERT_EQUAL(has_allocation, rpc::commands.has(name + ".max_alloc"));
CPPUNIT_ASSERT_EQUAL(has_allocation, rpc::commands.has(name + ".min_alloc.set"));
CPPUNIT_ASSERT_EQUAL(has_allocation, rpc::commands.has(name + ".max_alloc.set"));
}
}
+15
View File
@@ -0,0 +1,15 @@
#include "test/helpers/test_fixture.h"
class TestCommandLocal : public test_fixture {
CPPUNIT_TEST_SUITE(TestCommandLocal);
CPPUNIT_TEST(test_socket_category_commands);
CPPUNIT_TEST_SUITE_END();
public:
void setUp();
void tearDown();
void test_socket_category_commands();
};
-45
View File
@@ -1,45 +0,0 @@
#include "config.h"
#include "test/src/test_encryption_config.h"
#include <torrent/runtime/encryption_policy.h>
#include "encryption_config.h"
CPPUNIT_TEST_SUITE_REGISTRATION(TestEncryptionConfig);
void
TestEncryptionConfig::test_default_policy() {
const auto policy = encryption_config::default_policy();
CPPUNIT_ASSERT(policy.handshake == encryption_config::Policy::Mode::allow);
CPPUNIT_ASSERT(policy.stream == encryption_config::Policy::Mode::allow);
CPPUNIT_ASSERT_EQUAL(std::string("handshake=allow stream=allow"),
encryption_config::summary_string(policy));
}
void
TestEncryptionConfig::test_granular_round_trip() {
auto round_trip_handshake = [](const std::string& value) {
encryption_config::Policy policy;
encryption_config::apply_mode_value(policy, &encryption_config::Policy::handshake, value);
return encryption_config::mode_to_string(policy.handshake);
};
CPPUNIT_ASSERT_EQUAL(std::string("deny"), round_trip_handshake("deny"));
CPPUNIT_ASSERT_EQUAL(std::string("allow"), round_trip_handshake("allow"));
CPPUNIT_ASSERT_EQUAL(std::string("prefer"), round_trip_handshake("prefer"));
CPPUNIT_ASSERT_EQUAL(std::string("require"), round_trip_handshake("require"));
auto round_trip_stream = [](const std::string& value) {
encryption_config::Policy policy;
encryption_config::apply_mode_value(policy, &encryption_config::Policy::stream, value);
return encryption_config::mode_to_string(policy.stream);
};
CPPUNIT_ASSERT_EQUAL(std::string("deny"), round_trip_stream("deny"));
CPPUNIT_ASSERT_EQUAL(std::string("allow"), round_trip_stream("allow"));
CPPUNIT_ASSERT_EQUAL(std::string("prefer"), round_trip_stream("prefer"));
CPPUNIT_ASSERT_EQUAL(std::string("require"), round_trip_stream("require"));
}
-12
View File
@@ -1,12 +0,0 @@
#include "test/helpers/test_fixture.h"
class TestEncryptionConfig : public test_fixture {
CPPUNIT_TEST_SUITE(TestEncryptionConfig);
CPPUNIT_TEST(test_default_policy);
CPPUNIT_TEST(test_granular_round_trip);
CPPUNIT_TEST_SUITE_END();
public:
void test_default_policy();
void test_granular_round_trip();
};