Compare commits

..

23 Commits

Author SHA1 Message Date
Ivan 29f705c22a Build out of tree so clang-tidy sees changed src lines
In-tree builds pass relative names that clang-tidy-diff's line filter cannot match.
2026-10-21 18:15:02 +02:00
Ivan 3046557eaa Install libcppunit-dev in the clang-tidy job
Without CppUnit headers, CPPUNIT_TEST() lines parse as misnamed members and functions.
2026-10-21 18:12:54 +02:00
Ivan b46c71b98d Use the string_utf8 text objects in the *.or_base64 and *.or_as_binary commands
Names and paths with C0 controls other than tab and line feed are now sent as binary.
2026-10-21 18:11:47 +02:00
Ivan 19ef2d1830 Let the clang-tidy comment workflow check out fork pull requests
actions/checkout@v4 now needs allow-unsafe-pr-checkout for fork PR code in workflow_run.
2026-10-21 12:28:12 +02:00
Ivan 5a3b4e50cd Save session data once on shutdown, before waiting for stopped announces
Write it after downloads stop, or after the loop if skipped; one error policy for saves.
2026-10-21 12:27:57 +02:00
Ivan 9dd54038b4 Abort loading a torrent when file_split_all() fails
A zero split size, or a file needing over 1000 parts, made the next load exit rtorrent.
2026-10-21 11:58:49 +02:00
Silas Mariusz 8df6f23e77 Pause the SCGI listener for a second when accept() runs out of descriptors or memory, instead of throwing. 2026-10-21 11:33:33 +02:00
noctuum 40658e20d9 Report open errors from the quick hash check
With no other hash check running, nothing retries it later.
2026-10-21 11:07:42 +02:00
Ivan 0038868a0b Clear a stale hash check message after a complete hash check 2026-10-21 10:48:33 +02:00
Copilot d316b21cf0 Add session.umask.set to configure session file permissions. 2026-10-20 18:32:32 +09:00
LastElaphine 8cab343a82 Update example configuration files to align with wiki template 2026-10-18 13:22:33 +02:00
Copilot 3c7bb87f90 Align logging calls with libtorrent API. 2026-10-10 21:08:34 +09:00
Copilot 7ee4b147d9 Collapse cascading log levels into LOG_ERRORS / LOG_EVENTS / LOG_DEBUG_TRACE. 2026-10-10 18:48:47 +09:00
rakshasa 5aa45bbaf4 Removed old LOG_TORRENT_* categories and replaced with LOG_TORRENT_{DEBUG,EVENTS}. 2026-10-08 15:31:52 +02:00
Scott Pope 728790a2b4 Parse multicall commands once per call
Keep the change to d, f, p and t multicalls. Parse each column lazily on first use and evaluate it through rpc::call_object, preserving per-target argument expansion.

Compared unpatched and patched builds on macOS: output was byte-identical across the test harness, including an erase-mid-call check.
2026-10-06 11:19:48 +02:00
Nicolas PARLANT e652a3cbab add missing header
cstdint for uint32_t
2026-10-05 16:59:00 +02:00
rakshasa 15ccae27de Tagged release 0.16.25. 2026-10-05 08:42:50 +02:00
Silas Mariusz 4c0535b1fa Align the XML-RPC command stack so torrent::Object is not constructed on a 4-byte boundary. 2026-10-04 13:37:01 +02:00
Jari Sundell d59765fda4 Added legacy UTF-8 torrent name and path handling. 2026-10-04 20:36:29 +09:00
Silas Mariusz 66c49d24e5 Use heap allocated buffer for rpc::ExecFile::execute_object() and increase size to 128Kb. 2026-10-04 10:18:58 +02:00
rakshasa 3af0410a6e Restrict parse value to strtoll with restrictions on input. 2026-10-01 10:32:43 +02:00
rakshasa 3c94b03323 Restrict parse value to strtoll with restrictions on input. 2026-10-01 10:32:43 +02:00
rakshasa c5d54fbb97 Restrict parse value to strtoll with restrictions on input. 2026-10-01 10:32:43 +02:00
60 changed files with 1122 additions and 276 deletions
@@ -64,6 +64,8 @@ jobs:
repository: ${{ env.PR_HEAD_REPO }} repository: ${{ env.PR_HEAD_REPO }}
ref: ${{ env.PR_HEAD_SHA }} ref: ${{ env.PR_HEAD_SHA }}
persist-credentials: false persist-credentials: false
# Only read by the comment action, never built or run.
allow-unsafe-pr-checkout: true
- name: Redownload analysis results - name: Redownload analysis results
uses: actions/github-script@v7 uses: actions/github-script@v7
with: with:
+7 -3
View File
@@ -16,6 +16,7 @@ jobs:
sudo apt-get install -y \ sudo apt-get install -y \
bear \ bear \
clang-tidy \ clang-tidy \
libcppunit-dev \
libcurl4-openssl-dev \ libcurl4-openssl-dev \
zlib1g-dev zlib1g-dev
- name: Fetch libtorrent - name: Fetch libtorrent
@@ -49,16 +50,19 @@ jobs:
autoconf -i autoconf -i
autoheader autoheader
automake --add-missing automake --add-missing
./configure mkdir build
cd build
"${GITHUB_WORKSPACE}/configure"
- name: Prepare compile_commands.json - name: Prepare compile_commands.json
run: | run: |
bear -- make cd build
bear -- make check TESTS=
- name: Create results directory - name: Create results directory
run: | run: |
mkdir clang-tidy-result mkdir clang-tidy-result
- name: Analyze - name: Analyze
run: | run: |
git diff -U0 "$(git merge-base HEAD "upstream/${{ github.event.pull_request.base.ref }}")" | clang-tidy-diff -p1 -path build -export-fixes clang-tidy-result/fixes.yml "-extra-arg=-include/${PWD}/config.h" git diff -U0 "$(git merge-base HEAD "upstream/${{ github.event.pull_request.base.ref }}")" | clang-tidy-diff -p1 -path build -export-fixes clang-tidy-result/fixes.yml "-extra-arg=-include/${PWD}/build/config.h"
- name: Save PR metadata - name: Save PR metadata
run: | run: |
echo "${{ github.event.number }}" > clang-tidy-result/pr-id.txt echo "${{ github.event.number }}" > clang-tidy-result/pr-id.txt
+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.24],[sundell.software@gmail.com]) AC_INIT([rtorrent],[0.16.25],[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], [28], [api version]) AC_DEFINE([API_VERSION], [29], [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.24]) PKG_CHECK_MODULES([DEPENDENCIES], [libtorrent >= 0.16.25])
AC_LANG_PUSH(C++) AC_LANG_PUSH(C++)
TORRENT_WITH_XMLRPC_C TORRENT_WITH_XMLRPC_C
+9 -13
View File
@@ -18,7 +18,8 @@ Some control over formatting will be provided at a later date.
\begin{verbatim} \begin{verbatim}
# log.add_output = "logging event", "log name" # log.add_output = "logging event", "log name"
log.add_output = "info", "rtorrent.log" log.add_output = "errors", "rtorrent.log"
log.add_output = "events", "rtorrent.log"
log.add_output = "dht_all", "tracker.log" log.add_output = "dht_all", "tracker.log"
log.add_output = "tracker_events", "tracker.log" log.add_output = "tracker_events", "tracker.log"
@@ -31,21 +32,16 @@ Each log handle can be added to multiple different logging events.
\subsection{Logging events} \subsection{Logging events}
\begin{verbatim} \begin{verbatim}
"critical" "errors"
"error" "events"
"warn" "debug_trace"
"notice"
"info"
"debug"
\end{verbatim} \end{verbatim}
The above events receive logging events from all the sub-groups The above events are not cascading, so add both ``errors'' and
displayed below, and each event also reciving events from the event ``events'' to an output to receive both.
above in importance.
Thus some high-volume sub-group events such as ``tracker\_debug'' are High-volume sub-group events such as ``tracker\_debug'' are not part
not part of ``debug'' and every ``warn'' event will receive events of ``debug\_trace''.
from ``error'', ``critical''.
\begin{verbatim} \begin{verbatim}
"connection_*" "connection_*"
+5 -5
View File
@@ -52,11 +52,11 @@
# watch directory. # watch directory.
# #
#directory.watch.ready = ./watch, load.start #directory.watch.ready = ./watch, load.start
#schedule2 = watch_directory,5,5,load.start=./watch/*.torrent #schedule = watch_directory,5,5,load.start=./watch/*.torrent
# Close torrents when disk-space is low. # Close torrents when disk-space is low.
# #
#schedule2 = low_diskspace,5,60,close_low_diskspace=100M #schedule = low_diskspace,5,60,close_low_diskspace=100M
# The IP address reported to the tracker. # The IP address reported to the tracker.
# #
@@ -100,8 +100,8 @@
# Alternative calls to bind and IP that should handle dynamic IP's. # Alternative calls to bind and IP that should handle dynamic IP's.
# #
#schedule2 = ip_tick,0,1800,ip=rakshasa #schedule = ip_tick,0,1800,ip=rakshasa
#schedule2 = bind_tick,0,1800,bind=rakshasa #schedule = bind_tick,0,1800,bind=rakshasa
# Encryption options, set to none (default) or any combination of the following: # Encryption options, set to none (default) or any combination of the following:
# allow_incoming, try_outgoing, require, require_RC4, enable_retry, prefer_plaintext # allow_incoming, try_outgoing, require, require_RC4, enable_retry, prefer_plaintext
@@ -145,4 +145,4 @@
# #
#network.scgi.open_port = "127.0.0.1:5000" #network.scgi.open_port = "127.0.0.1:5000"
#network.scgi.open_local = (cat,(session.path),/rpc.sock) #network.scgi.open_local = (cat,(session.path),/rpc.sock)
#schedule2 = socket_chmod, 0, 0, "execute.nothrow=chmod,770,(cat,(session.path),/rpc.sock)" #schedule = socket_chmod, 0, 0, "execute.nothrow=chmod,770,(cat,(session.path),/rpc.sock)"
+17 -10
View File
@@ -30,7 +30,6 @@ network.listen.port.random.set = no
# (conservative settings for 'private' trackers, change for 'public') # (conservative settings for 'private' trackers, change for 'public')
dht.mode.set = disable dht.mode.set = disable
protocol.pex.set = no protocol.pex.set = no
trackers.use_udp.set = no
# Peer settings # Peer settings
throttle.max_uploads.set = 100 throttle.max_uploads.set = 100
@@ -42,14 +41,22 @@ throttle.min_peers.seed.set = 30
throttle.max_peers.seed.set = 80 throttle.max_peers.seed.set = 80
trackers.numwant.set = 80 trackers.numwant.set = 80
protocol.encryption.set = allow_incoming,try_outgoing,enable_retry # Select encryption for handshake and connection:
#
# protocol.encryption.set = {deny,allow,prefer,require}
# protocol.encryption.set = handshake_{deny,allow,prefer,require}, stream_{deny,allow,prefer,require}
#
#protocol.encryption.set = deny
#protocol.encryption.set = require
#protocol.encryption.set = handshake_prefer, stream_deny
#protocol.encryption.set = handshake_require, stream_prefer
# Limits for file handle resources, this is optimized for # Limits for file handle resources, this is optimized for
# an `ulimit` of 1024 (a common default). You MUST leave # an `ulimit` of 1024 (a common default). You MUST leave
# a ceiling of handles reserved for rTorrent's internal needs! # a ceiling of handles reserved for rTorrent's internal needs!
network.http.max_open.set = 50
network.max_open_files.set = 600 # Deprecated, see: https://github.com/rakshasa/rtorrent/wiki/Socket-Manager-and-Resource-Allocation
network.max_open_sockets.set = 300
# Memory resource usage (increase if you have a large number of items loaded, # Memory resource usage (increase if you have a large number of items loaded,
# and/or the available resources to spend) # and/or the available resources to spend)
@@ -65,11 +72,10 @@ execute.nothrow = sh, -c, (cat, "echo >",\
(session.path), "rtorrent.pid", " ", (system.pid)) (session.path), "rtorrent.pid", " ", (system.pid))
# Other operational settings (check & adapt) # Other operational settings (check & adapt)
encoding.add = utf8
system.umask.set = 0027 system.umask.set = 0027
system.cwd.set = (directory.default) system.cwd.set = (directory.default)
network.http.dns_cache_timeout.set = 25 network.http.dns_cache_timeout.set = 25
schedule2 = monitor_diskspace, 15, 60, ((close_low_diskspace, 1000M)) schedule = monitor_diskspace, 15, 60, ((close_low_diskspace, 1000M))
##pieces.hash.on_completion.set = no ##pieces.hash.on_completion.set = no
##view.sort_current = seeding, greater=d.ratio= ##view.sort_current = seeding, greater=d.ratio=
##keys.layout.set = qwerty ##keys.layout.set = qwerty
@@ -88,8 +94,8 @@ method.insert = d.data_path, simple,\
method.insert = d.session_file, simple, "cat=(session.path), (d.hash), .torrent" method.insert = d.session_file, simple, "cat=(session.path), (d.hash), .torrent"
# Watch directories (add more as you like, but use unique schedule names) # Watch directories (add more as you like, but use unique schedule names)
schedule2 = watch_start, 10, 10, ((load.start_verbose, (cat, (cfg.watch), "start/*.torrent"))) schedule = watch_start, 10, 10, ((load.start_verbose, (cat, (cfg.watch), "start/*.torrent")))
schedule2 = watch_load, 11, 10, ((load.verbose, (cat, (cfg.watch), "load/*.torrent"))) schedule = watch_load, 11, 10, ((load.verbose, (cat, (cfg.watch), "load/*.torrent")))
# Run the rTorrent process as a daemon in the background # Run the rTorrent process as a daemon in the background
# (and control via XMLRPC sockets) # (and control via XMLRPC sockets)
@@ -104,7 +110,8 @@ print = (cat, "Logging to ", (cfg.logfile))
log.open_file = "log", (cfg.logfile) log.open_file = "log", (cfg.logfile)
log.add_output = "info", "log" log.add_output = "errors", "log"
log.add_output = "events", "log"
#log.add_output = "tracker_events", "log" #log.add_output = "tracker_events", "log"
#log.add_output = "tracker_requests", "log" #log.add_output = "tracker_requests", "log"
+7 -11
View File
@@ -60,7 +60,6 @@ rc.network.listen.port.random = false
-- (conservative settings for 'private' trackers, change for 'public') -- (conservative settings for 'private' trackers, change for 'public')
rc.dht.mode = 'disable' rc.dht.mode = 'disable'
rc.protocol.pex = false rc.protocol.pex = false
rc.trackers.use_udp = false
-- Peer settings -- Peer settings
rc.throttle.max_uploads = 100 rc.throttle.max_uploads = 100
@@ -72,14 +71,11 @@ rc.throttle.min_peers.seed = 30
rc.throttle.max_peers.seed = 80 rc.throttle.max_peers.seed = 80
rc.trackers.numwant = 80 rc.trackers.numwant = 80
rc.protocol.encryption.set('allow_incoming', 'try_outgoing', 'enable_retry')
-- Limits for file handle resources, this is optimized for -- Limits for file handle resources, this is optimized for
-- an `ulimit` of 1024 (a common default). You MUST leave -- an `ulimit` of 1024 (a common default). You MUST leave
-- a ceiling of handles reserved for rTorrent's internal needs! -- a ceiling of handles reserved for rTorrent's internal needs!
rc.network.http.max_open = 50
rc.network.max_open_files = 600 -- Deprecated, see: https://github.com/rakshasa/rtorrent/wiki/Socket-Manager-and-Resource-Allocation
rc.network.max_open_sockets = 300
-- Memory resource usage (increase if you have a large number of items loaded, -- Memory resource usage (increase if you have a large number of items loaded,
-- and/or the available resources to spend) -- and/or the available resources to spend)
@@ -97,11 +93,10 @@ rc.execute.nothrow(
})) }))
-- Other operational settings (check & adapt) -- Other operational settings (check & adapt)
rc.encoding.add('utf8')
rc.system.umask = 0027 rc.system.umask = 0027
rc.system.cwd = rc.directory.default() rc.system.cwd = rc.directory.default()
rc.network.http.dns_cache_timeout = 25 rc.network.http.dns_cache_timeout = 25
rc.schedule2('monitor_diskspace', '15', '60', 'close_low_diskspace=1000M') rc.schedule('monitor_diskspace', '15', '60', 'close_low_diskspace=1000M')
--rc.pieces.hash.on_completion = false --rc.pieces.hash.on_completion = false
--rc.view.sort_current('seeding', 'greater=d.ratio=') --rc.view.sort_current('seeding', 'greater=d.ratio=')
--rc.keys.layout = 'qwerty' --rc.keys.layout = 'qwerty'
@@ -121,8 +116,8 @@ rc.method.insert('d.data_path', 'simple',
rc.method.insert('d.session_file', 'simple', 'cat=(session.path), (d.hash), .torrent') rc.method.insert('d.session_file', 'simple', 'cat=(session.path), (d.hash), .torrent')
-- Watch directories (add more as you like, but use unique schedule names) -- Watch directories (add more as you like, but use unique schedule names)
rc.schedule2('watch_start', '10', '10', 'load.start_verbose=(cat, (cfg.watch), "start/*.torrent")') rc.schedule('watch_start', '10', '10', 'load.start_verbose=(cat, (cfg.watch), "start/*.torrent")')
rc.schedule2('watch_load', '11', '10', 'load.verbose=(cat, (cfg.watch), "load/*.torrent")') rc.schedule('watch_load', '11', '10', 'load.verbose=(cat, (cfg.watch), "load/*.torrent")')
-- Run the rTorrent process as a daemon in the background -- Run the rTorrent process as a daemon in the background
-- (and control via XMLRPC sockets) -- (and control via XMLRPC sockets)
@@ -135,7 +130,8 @@ rc.schedule2('watch_load', '11', '10', 'load.verbose=(cat, (cfg.watch), "load/*.
-- Groups = connection_* dht_* peer_* rpc_* storage_* thread_* tracker_* torrent_* -- Groups = connection_* dht_* peer_* rpc_* storage_* thread_* tracker_* torrent_*
rc.print('Logging to '..rc.cfg.logfile()) rc.print('Logging to '..rc.cfg.logfile())
rc.log.open_file('log', rc.cfg.logfile()) rc.log.open_file('log', rc.cfg.logfile())
rc.log.add_output('info', 'log') rc.log.add_output('errors', 'log')
rc.log.add_output('events', 'log')
--rc.log.add_output('tracker_events', 'log') --rc.log.add_output('tracker_events', 'log')
--rc.log.add_output('tracker_requests', 'log') --rc.log.add_output('tracker_requests', 'log')
+37 -23
View File
@@ -107,7 +107,7 @@ apply_d_change_link(core::Download* download, const torrent::Object::list_type&
switch (changeType) { switch (changeType) {
case 0: case 0:
if (symlink(target.c_str(), link.c_str()) == -1) if (symlink(target.c_str(), link.c_str()) == -1)
lt_log_print(torrent::LOG_TORRENT_WARN, "create_link failed: %s", std::strerror(errno)); lt_log_print(torrent::LOG_TORRENT_DEBUG, "create_link failed: %s", std::strerror(errno));
break; break;
@@ -117,7 +117,7 @@ apply_d_change_link(core::Download* download, const torrent::Object::list_type&
errno = 0; errno = 0;
if (!fileStat.update_link(link) || !fileStat.is_link() || unlink(link.c_str()) == -1) if (!fileStat.update_link(link) || !fileStat.is_link() || unlink(link.c_str()) == -1)
lt_log_print(torrent::LOG_TORRENT_WARN, "delete_link failed: %s", std::strerror(errno)); lt_log_print(torrent::LOG_TORRENT_DEBUG, "delete_link failed: %s", std::strerror(errno));
break; break;
} }
@@ -135,7 +135,7 @@ apply_d_delete_tied(core::Download* download) {
if (tie.empty()) if (tie.empty())
return torrent::Object(); return torrent::Object();
if (::unlink(expand_path(tie).c_str()) == -1) if (unlink(expand_path(tie).c_str()) == -1)
control->core()->push_log_std("Could not unlink tied file: " + std::string(std::strerror(errno))); control->core()->push_log_std("Could not unlink tied file: " + std::string(std::strerror(errno)));
rpc::call_command("d.tied_to_file.set", std::string(), rpc::make_target(download)); rpc::call_command("d.tied_to_file.set", std::string(), rpc::make_target(download));
@@ -293,7 +293,7 @@ apply_d_add_peer(core::Download* download, const std::string& arg) {
// Currently discarding SOCK_STREAM. // Currently discarding SOCK_STREAM.
torrent::this_thread::resolver()->resolve_preferred(callback_id, host, AF_UNSPEC, AF_INET, [download, port](torrent::c_sa_shared_ptr sa, int err) { torrent::this_thread::resolver()->resolve_preferred(callback_id, host, AF_UNSPEC, AF_INET, [download, port](torrent::c_sa_shared_ptr sa, int err) {
if (sa == nullptr) { if (sa == nullptr) {
lt_log_print(torrent::LOG_TORRENT_WARN, "could not resolve hostname for added peer: %s", gai_strerror(err)); lt_log_print(torrent::LOG_TORRENT_DEBUG, "could not resolve hostname for added peer: %s", gai_strerror(err));
return; return;
} }
@@ -334,6 +334,11 @@ f_multicall(core::Download* download, const torrent::Object::list_type& args) {
bool use_regex = true; bool use_regex = true;
rpc::preparsed_commands commands([&args](auto& cmds) {
for (auto cItr = ++args.begin(); cItr != args.end(); ++cItr)
cmds.push_back(rpc::parse_command_object(cItr->as_string()));
});
if (args.front().is_list()) if (args.front().is_list())
for (const auto& o : args.front().as_list()) for (const auto& o : args.front().as_list())
regex_list.push_back(o.as_string_c()); regex_list.push_back(o.as_string_c());
@@ -349,10 +354,10 @@ f_multicall(core::Download* download, const torrent::Object::list_type& args) {
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 (torrent::Object::list_const_iterator cItr = ++args.begin(); cItr != args.end(); cItr++) { // Defer parsing until a file actually matches the multicall selection.
const std::string& cmd = cItr->as_string(); commands.prepare_if_needed();
row.push_back(rpc::parse_command(rpc::make_target(file.get()), cmd.c_str(), cmd.c_str() + cmd.size()).first); for (auto& itr : commands)
} row.push_back(rpc::call_object(itr, rpc::make_target(file.get())));
} }
return resultRaw; return resultRaw;
@@ -372,6 +377,11 @@ t_multicall(core::Download* download, const torrent::Object::list_type& args) {
auto result_raw = torrent::Object::create_list(); auto result_raw = torrent::Object::create_list();
auto& result = result_raw.as_list(); auto& result = result_raw.as_list();
rpc::preparsed_commands commands([&args](auto& cmds) {
for (auto cItr = ++args.begin(); cItr != args.end(); ++cItr)
cmds.push_back(rpc::parse_command_object(cItr->as_string()));
});
for (uint32_t idx = 0, last = download->tracker_list_size(); idx < last; idx++) { for (uint32_t idx = 0, last = download->tracker_list_size(); idx < last; idx++) {
auto& row = result.insert(result.end(), torrent::Object::create_list())->as_list(); auto& row = result.insert(result.end(), torrent::Object::create_list())->as_list();
auto tracker = download->tracker_controller().at(idx); auto tracker = download->tracker_controller().at(idx);
@@ -379,11 +389,10 @@ t_multicall(core::Download* download, const torrent::Object::list_type& args) {
if (!tracker.is_valid()) if (!tracker.is_valid())
continue; continue;
for (auto cItr = ++args.begin(); cItr != args.end(); cItr++) { // Do not parse columns when there are no valid tracker targets.
auto& cmd = cItr->as_string(); commands.prepare_if_needed();
for (auto& itr : commands)
row.push_back(rpc::parse_command(rpc::make_target(&tracker), cmd.c_str(), cmd.c_str() + cmd.size()).first); row.push_back(rpc::call_object(itr, rpc::make_target(&tracker)));
}
} }
return result_raw; return result_raw;
@@ -405,13 +414,18 @@ p_multicall(core::Download* download, const torrent::Object::list_type& args) {
auto* connection_list = download->connection_list(); auto* connection_list = download->connection_list();
const auto change_counter = connection_list->change_counter(); const auto change_counter = connection_list->change_counter();
rpc::preparsed_commands commands([&args](auto& cmds) {
for (auto cItr = ++args.begin(); cItr != args.end(); ++cItr)
cmds.push_back(rpc::parse_command_object(cItr->as_string()));
});
for (const auto& connection : *connection_list) { 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++) { // Prepare only after a peer exists, preserving empty-list laziness.
const std::string& cmd = cItr->as_string(); commands.prepare_if_needed();
for (auto& itr : commands) {
row.push_back(rpc::parse_command(rpc::make_target(connection), cmd.c_str(), cmd.c_str() + cmd.size()).first); row.push_back(rpc::call_object(itr, rpc::make_target(connection)));
// Erasing a peer frees it and swaps the last element into its place, so // 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. // neither this peer nor the iteration survives a change to the list.
@@ -654,8 +668,8 @@ initialize_command_download() {
CMD2_DL("d.base_path.base64", [](auto* download, auto) { return retrieve_d_base_path(download).object_base64(); }); CMD2_DL("d.base_path.base64", [](auto* download, auto) { return retrieve_d_base_path(download).object_base64(); });
CMD2_DL("d.base_path.base64_as_binary", [](auto* download, auto) { return retrieve_d_base_path(download).object_base64_as_binary(); }); CMD2_DL("d.base_path.base64_as_binary", [](auto* download, auto) { return retrieve_d_base_path(download).object_base64_as_binary(); });
CMD2_DL("d.base_path.as_binary", [](auto* download, auto) { return retrieve_d_base_path(download).object_as_binary(); }); CMD2_DL("d.base_path.as_binary", [](auto* download, auto) { return retrieve_d_base_path(download).object_as_binary(); });
CMD2_DL("d.base_path.or_base64", [](auto* download, auto) { return retrieve_d_base_path(download).object_utf8_or_base64(); }); CMD2_DL("d.base_path.or_base64", [](auto* download, auto) { return retrieve_d_base_path(download).object_utf8_text_or_base64(); });
CMD2_DL("d.base_path.or_as_binary", [](auto* download, auto) { return retrieve_d_base_path(download).object_utf8_or_as_binary(); }); CMD2_DL("d.base_path.or_as_binary", [](auto* download, auto) { return retrieve_d_base_path(download).object_utf8_text_or_as_binary(); });
CMD2_DL("d.base_path.realpath.or_empty", [](auto* download, auto) { return resolve_path(retrieve_d_base_path(download).str()); }); CMD2_DL("d.base_path.realpath.or_empty", [](auto* download, auto) { return resolve_path(retrieve_d_base_path(download).str()); });
CMD2_DL("d.base_path.realpath.or_throw", [](auto* download, auto) { return resolve_path_or_throw(retrieve_d_base_path(download).str()); }); CMD2_DL("d.base_path.realpath.or_throw", [](auto* download, auto) { return resolve_path_or_throw(retrieve_d_base_path(download).str()); });
CMD2_DL("d.base_filename", [](auto* download, auto) { return retrieve_d_base_filename(download).str(); }); CMD2_DL("d.base_filename", [](auto* download, auto) { return retrieve_d_base_filename(download).str(); });
@@ -663,16 +677,16 @@ initialize_command_download() {
CMD2_DL("d.base_filename.base64", [](auto* download, auto) { return retrieve_d_base_filename(download).object_base64(); }); CMD2_DL("d.base_filename.base64", [](auto* download, auto) { return retrieve_d_base_filename(download).object_base64(); });
CMD2_DL("d.base_filename.base64_as_binary", [](auto* download, auto) { return retrieve_d_base_filename(download).object_base64_as_binary(); }); CMD2_DL("d.base_filename.base64_as_binary", [](auto* download, auto) { return retrieve_d_base_filename(download).object_base64_as_binary(); });
CMD2_DL("d.base_filename.as_binary", [](auto* download, auto) { return retrieve_d_base_filename(download).object_as_binary(); }); CMD2_DL("d.base_filename.as_binary", [](auto* download, auto) { return retrieve_d_base_filename(download).object_as_binary(); });
CMD2_DL("d.base_filename.or_base64", [](auto* download, auto) { return retrieve_d_base_filename(download).object_utf8_or_base64(); }); CMD2_DL("d.base_filename.or_base64", [](auto* download, auto) { return retrieve_d_base_filename(download).object_utf8_text_or_base64(); });
CMD2_DL("d.base_filename.or_as_binary", [](auto* download, auto) { return retrieve_d_base_filename(download).object_utf8_or_as_binary(); }); CMD2_DL("d.base_filename.or_as_binary", [](auto* download, auto) { return retrieve_d_base_filename(download).object_utf8_text_or_as_binary(); });
CMD2_DL("d.name", [](auto* download, auto) { return download->info()->name().str(); }); CMD2_DL("d.name", [](auto* download, auto) { return download->info()->name().str(); });
CMD2_DL("d.name.hex", [](auto* download, auto) { return download->info()->name().object_hex(); }); CMD2_DL("d.name.hex", [](auto* download, auto) { return download->info()->name().object_hex(); });
CMD2_DL("d.name.base64", [](auto* download, auto) { return download->info()->name().object_base64(); }); CMD2_DL("d.name.base64", [](auto* download, auto) { return download->info()->name().object_base64(); });
CMD2_DL("d.name.base64_as_binary", [](auto* download, auto) { return download->info()->name().object_base64_as_binary(); }); CMD2_DL("d.name.base64_as_binary", [](auto* download, auto) { return download->info()->name().object_base64_as_binary(); });
CMD2_DL("d.name.as_binary", [](auto* download, auto) { return download->info()->name().object_as_binary(); }); CMD2_DL("d.name.as_binary", [](auto* download, auto) { return download->info()->name().object_as_binary(); });
CMD2_DL("d.name.or_base64", [](auto* download, auto) { return download->info()->name().object_utf8_or_base64(); }); CMD2_DL("d.name.or_base64", [](auto* download, auto) { return download->info()->name().object_utf8_text_or_base64(); });
CMD2_DL("d.name.or_as_binary", [](auto* download, auto) { return download->info()->name().object_utf8_or_as_binary(); }); CMD2_DL("d.name.or_as_binary", [](auto* download, auto) { return download->info()->name().object_utf8_text_or_as_binary(); });
CMD2_DL("d.creation_date", [](auto* download, auto) { return download->info()->creation_date(); }); CMD2_DL("d.creation_date", [](auto* download, auto) { return download->info()->creation_date(); });
CMD2_DL("d.load_date", [](auto* download, auto) { return download->info()->load_date(); }); CMD2_DL("d.load_date", [](auto* download, auto) { return download->info()->load_date(); });
+1 -1
View File
@@ -394,7 +394,7 @@ cmd_catch(rpc::target_type target, const torrent::Object& args) {
try { try {
return rpc::call_object(args, target); return rpc::call_object(args, target);
} catch (torrent::input_error& e) { } catch (torrent::input_error& e) {
lt_log_print(torrent::LOG_WARN, "Caught exception: '%s'.", e.what()); lt_log_print(torrent::LOG_ERRORS, "Caught exception: '%s'.", e.what());
return torrent::Object(); return torrent::Object();
} }
} }
+13 -7
View File
@@ -169,7 +169,7 @@ void apply_try_import(const std::string& path) { if (!rpc::parse_command_file(pa
torrent::Object torrent::Object
apply_close_low_diskspace(int64_t arg, uint32_t skip_priority) { apply_close_low_diskspace(int64_t arg, uint32_t skip_priority) {
bool closed = false; int closed{};
torrent::FileList::cache_list cache; torrent::FileList::cache_list cache;
@@ -186,11 +186,11 @@ apply_close_low_diskspace(int64_t arg, uint32_t skip_priority) {
download->set_hash_failed(true); download->set_hash_failed(true);
download->set_message(std::string("Low diskspace.")); download->set_message(std::string("Low diskspace."));
closed = true; closed++;
} }
if (closed) if (closed != 0)
lt_log_print(torrent::LOG_TORRENT_ERROR, "Closed torrents due to low diskspace."); lt_log_print(torrent::LOG_TORRENT_EVENTS, "closed torrents due to low diskspace : count:%i", closed);
return torrent::Object(); return torrent::Object();
} }
@@ -247,20 +247,26 @@ d_multicall(const torrent::Object::list_type& args) {
torrent::Object resultRaw = torrent::Object::create_list(); torrent::Object resultRaw = torrent::Object::create_list();
torrent::Object::list_type& result = resultRaw.as_list(); torrent::Object::list_type& result = resultRaw.as_list();
rpc::preparsed_commands commands([&args](auto& cmds) {
for (auto cItr = ++args.begin(); cItr != args.end(); ++cItr)
cmds.push_back(rpc::parse_command_object(cItr->as_string()));
});
for (const auto& download : dlist) { for (const auto& download : dlist) {
if (download.use_count() == 1) if (download.use_count() == 1)
continue; continue;
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 (torrent::Object::list_const_iterator cItr = ++args.begin(); cItr != args.end(); cItr++) { // Skip parsing if there are no usable download targets in the view.
commands.prepare_if_needed();
for (auto& itr : commands) {
// A command may erase this download, which destroys the torrent object it // A command may erase this download, which destroys the torrent object it
// wraps; the list dropping its reference is what tells us. // wraps; the list dropping its reference is what tells us.
if (download.use_count() == 1) if (download.use_count() == 1)
break; break;
auto& cmd = cItr->as_string(); row.push_back(rpc::call_object(itr, rpc::make_target(download)));
row.push_back(rpc::parse_command(rpc::make_target(download), cmd.c_str(), cmd.c_str() + cmd.size()).first);
} }
} }
+4 -4
View File
@@ -45,8 +45,8 @@ apply_f_path_components(torrent::File* file, int output_type) {
case 2: result.push_back(itr.object_base64()); break; case 2: result.push_back(itr.object_base64()); break;
case 3: result.push_back(itr.object_base64_as_binary()); break; case 3: result.push_back(itr.object_base64_as_binary()); break;
case 4: result.push_back(itr.object_as_binary()); break; case 4: result.push_back(itr.object_as_binary()); break;
case 5: result.push_back(itr.object_utf8_or_base64()); break; case 5: result.push_back(itr.object_utf8_text_or_base64()); break;
case 6: result.push_back(itr.object_utf8_or_as_binary()); break; case 6: result.push_back(itr.object_utf8_text_or_as_binary()); break;
default: throw torrent::input_error("apply_f_path_components(): invalid output type"); default: throw torrent::input_error("apply_f_path_components(): invalid output type");
}; };
} }
@@ -114,8 +114,8 @@ initialize_command_file() {
CMD2_FILE("f.frozen_path.hex", [](auto* file, auto) { return file->frozen_path().object_hex(); }); CMD2_FILE("f.frozen_path.hex", [](auto* file, auto) { return file->frozen_path().object_hex(); });
CMD2_FILE("f.frozen_path.base64", [](auto* file, auto) { return file->frozen_path().object_base64(); }); CMD2_FILE("f.frozen_path.base64", [](auto* file, auto) { return file->frozen_path().object_base64(); });
CMD2_FILE("f.frozen_path.as_binary", [](auto* file, auto) { return file->frozen_path().object_as_binary(); }); CMD2_FILE("f.frozen_path.as_binary", [](auto* file, auto) { return file->frozen_path().object_as_binary(); });
CMD2_FILE("f.frozen_path.or_base64", [](auto* file, auto) { return file->frozen_path().object_utf8_or_base64(); }); CMD2_FILE("f.frozen_path.or_base64", [](auto* file, auto) { return file->frozen_path().object_utf8_text_or_base64(); });
CMD2_FILE("f.frozen_path.or_as_binary", [](auto* file, auto) { return file->frozen_path().object_utf8_or_as_binary(); }); CMD2_FILE("f.frozen_path.or_as_binary", [](auto* file, auto) { return file->frozen_path().object_utf8_text_or_as_binary(); });
CMD2_FILE("f.frozen_path.realpath.or_empty", [](auto* file, auto) { return resolve_path(file->frozen_path().str()); }); CMD2_FILE("f.frozen_path.realpath.or_empty", [](auto* file, auto) { return resolve_path(file->frozen_path().str()); });
CMD2_FILE("f.frozen_path.realpath.or_throw", [](auto* file, auto) { return resolve_path_or_throw(file->frozen_path().str()); }); CMD2_FILE("f.frozen_path.realpath.or_throw", [](auto* file, auto) { return resolve_path_or_throw(file->frozen_path().str()); });
+5 -4
View File
@@ -1,5 +1,6 @@
#include "config.h" #include "config.h"
#include <limits>
#include <memory> #include <memory>
#include <torrent/download/resource_manager.h> #include <torrent/download/resource_manager.h>
@@ -19,9 +20,6 @@
// For cg_d_group. // For cg_d_group.
#include "core/download.h" #include "core/download.h"
#define LT_LOG_SUBSYSTEM(log_fmt, ...) \
lt_log_print_subsystem(torrent::LOG_TORRENT_INFO, "choke_queue", log_fmt, __VA_ARGS__);
// A hack to allow testing of the new choke_group API without the // A hack to allow testing of the new choke_group API without the
// working parts present. // working parts present.
#define USE_CHOKE_GROUP 0 #define USE_CHOKE_GROUP 0
@@ -206,7 +204,7 @@ apply_cg_index_of(const std::string& arg) {
torrent::Object torrent::Object
apply_cg_all_update_balance(bool is_up) { apply_cg_all_update_balance(bool is_up) {
LT_LOG_SUBSYSTEM("apply update balance: hack is_up:%i", (int)is_up); lt_log_print(torrent::LOG_TORRENT_DEBUG, "choke_queue : applying update balance : is_up:%i", (int)is_up);
for (const auto& itr : cg_list_hack) { for (const auto& itr : cg_list_hack) {
if (is_up) if (is_up)
@@ -232,6 +230,9 @@ apply_cg_max_set(const torrent::Object::list_type& args, bool is_up) {
int64_t second_arg = 0; int64_t second_arg = 0;
rpc::parse_whole_value(args.back().as_string().c_str(), &second_arg); rpc::parse_whole_value(args.back().as_string().c_str(), &second_arg);
if (second_arg < -1 || second_arg > std::numeric_limits<uint32_t>::max())
throw torrent::input_error("Max unchoked must be between -1 and 4294967295.");
if (is_up) if (is_up)
cg_get_group(args.front())->up_queue()->set_max_unchoked(second_arg); cg_get_group(args.front())->up_queue()->set_max_unchoked(second_arg);
else else
+13
View File
@@ -1,12 +1,25 @@
#ifndef RTORRENT_UTILS_COMMAND_HELPERS_H #ifndef RTORRENT_UTILS_COMMAND_HELPERS_H
#define RTORRENT_UTILS_COMMAND_HELPERS_H #define RTORRENT_UTILS_COMMAND_HELPERS_H
#include <cstdint>
#include <limits>
#include <string>
#include <torrent/exceptions.h>
#include "rpc/command.h" #include "rpc/command.h"
#include "rpc/parse_commands.h" #include "rpc/parse_commands.h"
#include "rpc/object_storage.h" #include "rpc/object_storage.h"
void initialize_commands(); void initialize_commands();
inline uint16_t
checked_port_value(int64_t value, const char* label) {
if (value < 0 || value > std::numeric_limits<uint16_t>::max())
throw torrent::input_error(std::string("Invalid ") + label + " port number.");
return static_cast<uint16_t>(value);
}
// //
// Aliases with CMD_* for the below // Aliases with CMD_* for the below
// //
+4 -1
View File
@@ -88,7 +88,7 @@ initialize_command_local() {
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 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."); lt_log_print(torrent::LOG_ERRORS, "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(); });
@@ -123,6 +123,8 @@ initialize_command_local() {
CMD_ANY_STRING_V("session.path.set", [](auto, auto& str) { return session_thread::manager()->set_path(str); }); CMD_ANY_STRING_V("session.path.set", [](auto, auto& str) { return session_thread::manager()->set_path(str); });
CMD_ANY ("session.use_lock", [](auto, auto) { return session_thread::manager()->use_lock(); }); CMD_ANY ("session.use_lock", [](auto, auto) { return session_thread::manager()->use_lock(); });
CMD_ANY_VALUE_V ("session.use_lock.set", [](auto, auto& value) { return session_thread::manager()->set_use_lock(value); }); CMD_ANY_VALUE_V ("session.use_lock.set", [](auto, auto& value) { return session_thread::manager()->set_use_lock(value); });
CMD_ANY ("session.umask", [](auto, auto) { return session_thread::manager()->umask(); });
CMD_ANY_VALUE_V ("session.umask.set", [](auto, auto& value) { return session_thread::manager()->set_umask(value); });
CMD_VAR_BOOL ("session.on_completion", true); CMD_VAR_BOOL ("session.on_completion", true);
CMD_ANY_V ("session.save", [dList](auto, auto) { return dList->session_save(); }); CMD_ANY_V ("session.save", [dList](auto, auto) { return dList->session_save(); });
@@ -171,6 +173,7 @@ initialize_command_local() {
rpc::rpc.mark_safe("directory.default.realpath.or_empty"); rpc::rpc.mark_safe("directory.default.realpath.or_empty");
rpc::rpc.mark_safe("directory.default.realpath.or_throw"); rpc::rpc.mark_safe("directory.default.realpath.or_throw");
rpc::rpc.mark_safe("session.use_lock"); rpc::rpc.mark_safe("session.use_lock");
rpc::rpc.mark_safe("session.umask");
rpc::rpc.mark_safe("session.on_completion"); rpc::rpc.mark_safe("session.on_completion");
rpc::rpc.mark_safe("pieces.sync.always_safe"); rpc::rpc.mark_safe("pieces.sync.always_safe");
+5 -13
View File
@@ -54,14 +54,6 @@ set_listen_port_range(const std::string& arg) {
torrent::runtime::client_config()->set_listen_port_range(port_first, port_last); torrent::runtime::client_config()->set_listen_port_range(port_first, port_last);
} }
uint16_t
checked_local_port_value(int64_t value, const char* label) {
if (value < 0 || value > 65535)
throw torrent::input_error(std::string("Invalid ") + label + " port number.");
return static_cast<uint16_t>(value);
}
torrent::Object torrent::Object
get_encryption() { get_encryption() {
auto encryption_modes = torrent::runtime::network_config()->encryption_modes(); auto encryption_modes = torrent::runtime::network_config()->encryption_modes();
@@ -114,7 +106,7 @@ apply_obsolete_encryption(const torrent::Object::list_type& args) {
} }
} }
lt_log_print(torrent::LOG_WARN, "Obsolete encryption options used, use 'handshake_{deny,allow,prefer,require}, stream_{deny,allow,prefer,require}' instead."); lt_log_print(torrent::LOG_ERRORS, "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); torrent::runtime::network_config()->set_encryption_modes(handshake_mode, stream_mode);
return {}; return {};
@@ -325,7 +317,7 @@ initialize_command_network() {
auto nw_config = torrent::runtime::network_config(); auto nw_config = torrent::runtime::network_config();
CMD_ANY ("network.listen.port", [](auto, auto) { return torrent::runtime::network_manager()->listen_port(); }); CMD_ANY ("network.listen.port", [](auto, auto) { return torrent::runtime::network_manager()->listen_port(); });
CMD_ANY_VALUE_V ("network.listen.port.set", [](auto, auto& value) { return torrent::runtime::network_manager()->set_listen_port(value); }); CMD_ANY_VALUE_V ("network.listen.port.set", [](auto, auto& value) { return torrent::runtime::network_manager()->set_listen_port(checked_port_value(value, "listen")); });
CMD_ANY ("network.listen.port.random", [](auto, auto) { return torrent::runtime::client_config()->listen_port_random(); }); CMD_ANY ("network.listen.port.random", [](auto, auto) { return torrent::runtime::client_config()->listen_port_random(); });
CMD_ANY_VALUE_V ("network.listen.port.random.set", [](auto, auto& value) { return torrent::runtime::client_config()->set_listen_port_random(value); }); CMD_ANY_VALUE_V ("network.listen.port.random.set", [](auto, auto& value) { return torrent::runtime::client_config()->set_listen_port_random(value); });
CMD_ANY ("network.listen.port.range", [](auto, auto) { return listen_port_range(); }); CMD_ANY ("network.listen.port.range", [](auto, auto) { return listen_port_range(); });
@@ -394,11 +386,11 @@ initialize_command_network() {
CMD_ANY_STRING_V("network.local_address.ipv6.set", [nw_config](auto, auto& str) { return nw_config->set_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); });
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_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_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_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
View File
@@ -88,6 +88,8 @@ initialize_command_system() {
CMD_ANY ("system.file_name.replace_slash", [](auto, auto) { return torrent::runtime::client_config()->file_name_replace_slash(); }); 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_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_name.allow_legacy_utf8", [](auto, auto) { return torrent::runtime::client_config()->file_name_allow_legacy_utf8(); });
CMD_ANY_VALUE_V ("system.file_name.allow_legacy_utf8.set", [](auto, auto& value) { return torrent::runtime::client_config()->set_file_name_allow_legacy_utf8(value); });
CMD_ANY ("system.file_status_cache.size", [](auto, auto) { return control->core()->file_status_cache()->size(); }); 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_ANY_V ("system.file_status_cache.prune", [](auto, auto) { return control->core()->file_status_cache()->prune(); });
@@ -167,10 +169,13 @@ initialize_command_system() {
rpc::rpc.mark_safe("system.time"); rpc::rpc.mark_safe("system.time");
rpc::rpc.mark_safe("system.time_seconds"); rpc::rpc.mark_safe("system.time_seconds");
rpc::rpc.mark_safe("system.time_usec"); rpc::rpc.mark_safe("system.time_usec");
rpc::rpc.mark_safe("system.torrent_name.use_sanitized");
rpc::rpc.mark_safe("system.file.allocate"); rpc::rpc.mark_safe("system.file.allocate");
rpc::rpc.mark_safe("system.file.max_size"); rpc::rpc.mark_safe("system.file.max_size");
rpc::rpc.mark_safe("system.file.split_size"); rpc::rpc.mark_safe("system.file.split_size");
rpc::rpc.mark_safe("system.file.split_suffix"); rpc::rpc.mark_safe("system.file.split_suffix");
rpc::rpc.mark_safe("system.file_name.replace_slash");
rpc::rpc.mark_safe("system.file_name.allow_legacy_utf8");
rpc::rpc.mark_safe("system.sockets.size"); rpc::rpc.mark_safe("system.sockets.size");
rpc::rpc.mark_safe("system.sockets.max_size"); rpc::rpc.mark_safe("system.sockets.max_size");
+2 -2
View File
@@ -133,7 +133,7 @@ initialize_command_tracker() {
CMD2_ANY ("trackers.use_udp", [](auto, auto) { return true; }); CMD2_ANY ("trackers.use_udp", [](auto, auto) { return true; });
CMD2_ANY_VALUE_V ("trackers.use_udp.set", [](auto, auto) { CMD2_ANY_VALUE_V ("trackers.use_udp.set", [](auto, auto) {
lt_log_print(torrent::LOG_ERROR, "trackers.use_udp.set is no longer supported", 0); lt_log_print(torrent::LOG_ERRORS, "trackers.use_udp.set is no longer supported", 0);
}) })
CMD2_ANY_STRING_V ("dht.mode.set", [](auto, auto& str) { return control->dht_manager()->set_mode_by_user(str); }); CMD2_ANY_STRING_V ("dht.mode.set", [](auto, auto& str) { return control->dht_manager()->set_mode_by_user(str); });
@@ -142,7 +142,7 @@ initialize_command_tracker() {
lt_log_print(torrent::LOG_DHT_ERROR, "dht.port.set is no longer supported, use dht.override_port.set", 0); lt_log_print(torrent::LOG_DHT_ERROR, "dht.port.set is no longer supported, use dht.override_port.set", 0);
}); });
CMD2_ANY ("dht.override_port", [](auto, auto) { return torrent::runtime::network_config()->override_dht_port(); }); CMD2_ANY ("dht.override_port", [](auto, auto) { return torrent::runtime::network_config()->override_dht_port(); });
CMD2_ANY_VALUE_V ("dht.override_port.set", [](auto, auto& value) { return torrent::runtime::network_manager()->set_dht_port(value); }); CMD2_ANY_VALUE_V ("dht.override_port.set", [](auto, auto& value) { return torrent::runtime::network_manager()->set_dht_port(checked_port_value(value, "DHT override")); });
CMD2_ANY_STRING ("dht.add_node", [](auto, auto& str) { return apply_dht_add_node(str); }); CMD2_ANY_STRING ("dht.add_node", [](auto, auto& str) { return apply_dht_add_node(str); });
CMD2_ANY ("dht.statistics", [](auto, auto) { return control->dht_manager()->dht_statistics(); }); CMD2_ANY ("dht.statistics", [](auto, auto) { return control->dht_manager()->dht_statistics(); });
+10
View File
@@ -10,6 +10,7 @@
#include <torrent/utils/directory_events.h> #include <torrent/utils/directory_events.h>
#include "core/dht_manager.h" #include "core/dht_manager.h"
#include "core/download_list.h"
#include "core/http_queue.h" #include "core/http_queue.h"
#include "core/manager.h" #include "core/manager.h"
#include "core/view_manager.h" #include "core/view_manager.h"
@@ -152,6 +153,15 @@ Control::handle_shutdown() {
m_core->shutdown(true); m_core->shutdown(true);
} }
if (!m_shutdown_session_saved) {
// Transfers are stopped, so this is the final state; write it before the
// stopped announces can hold the shutdown. A shutdown hook may already
// have queued these resume builds.
m_core->download_list()->session_save();
session_thread::manager()->flush_shutdown_saves();
m_shutdown_session_saved = true;
}
if (!m_task_shutdown_clear_requests.is_scheduled()) if (!m_task_shutdown_clear_requests.is_scheduled())
torrent::this_thread::scheduler()->wait_for_ceil_seconds(&m_task_shutdown_clear_requests, 10s); torrent::this_thread::scheduler()->wait_for_ceil_seconds(&m_task_shutdown_clear_requests, 10s);
+2
View File
@@ -50,6 +50,7 @@ public:
bool is_shutdown_completed(); bool is_shutdown_completed();
bool is_shutdown_received() { return m_shutdown_received; } bool is_shutdown_received() { return m_shutdown_received; }
bool is_shutdown_started() { return m_shutdown_quick; } bool is_shutdown_started() { return m_shutdown_quick; }
bool is_shutdown_session_saved() { return m_shutdown_session_saved; }
void initialize(); void initialize();
void cleanup(); void cleanup();
@@ -114,6 +115,7 @@ private:
align_cacheline align_cacheline
bool m_shutdown_session_saved{};
std::atomic<bool> m_shutdown_received{}; std::atomic<bool> m_shutdown_received{};
std::atomic<bool> m_shutdown_quick{}; std::atomic<bool> m_shutdown_quick{};
}; };
+2 -2
View File
@@ -21,10 +21,10 @@
#include "session/session_manager.h" #include "session/session_manager.h"
#define LT_LOG(log_fmt, ...) \ #define LT_LOG(log_fmt, ...) \
lt_log_print_subsystem(torrent::LOG_DHT_CONTROLLER, "dht_manager", log_fmt, __VA_ARGS__); lt_log_print(torrent::LOG_DHT_CONTROLLER, "dht_manager : " log_fmt, __VA_ARGS__);
#define LT_LOG_ERROR(log_fmt, ...) \ #define LT_LOG_ERROR(log_fmt, ...) \
lt_log_print_subsystem(torrent::LOG_DHT_ERROR, "dht_manager", log_fmt, __VA_ARGS__); lt_log_print(torrent::LOG_DHT_ERROR, "dht_manager : " log_fmt, __VA_ARGS__);
namespace core { namespace core {
+13 -4
View File
@@ -184,7 +184,7 @@ DownloadFactory::receive_success() {
libtorrent_resume_object = download_factory_load_stream((expand_path(m_uri) + ".libtorrent_resume").c_str(), &session_invalid); libtorrent_resume_object = download_factory_load_stream((expand_path(m_uri) + ".libtorrent_resume").c_str(), &session_invalid);
if (session_invalid) if (session_invalid)
lt_log_print(torrent::LOG_ERROR, "%s: %s", session_invalid_message, m_uri.c_str()); lt_log_print(torrent::LOG_ERRORS, "%s: %s", session_invalid_message, m_uri.c_str());
} }
uint32_t tracker_key; uint32_t tracker_key;
@@ -278,12 +278,21 @@ DownloadFactory::receive_success() {
// Check first if we already have these values set in the session // Check first if we already have these values set in the session
// torrent, so that it is safe to change the values. // torrent, so that it is safe to change the values.
// try {
// Need to also catch the exceptions.
if (rpc::call_command_value("system.file.split_size") >= 0) if (rpc::call_command_value("system.file.split_size") >= 0)
torrent::file_split_all(download->download()->file_list(), torrent::file_split_all(download->download()->file_list(),
rpc::call_command_value("system.file.split_size"), rpc::call_command_value("system.file.split_size"),
rpc::call_command_string("system.file.split_suffix")); rpc::call_command_string("system.file.split_suffix"));
} catch (const torrent::input_error& e) {
std::string msg = "Could not split files: " + std::string(e.what());
lt_log_print(torrent::LOG_ERRORS, "%s: %s", msg.c_str(), m_uri.c_str());
torrent::download_remove(*download->download());
delete download;
return receive_failed(msg);
}
if (rtorrent->has_key_string("directory")) { if (rtorrent->has_key_string("directory")) {
rpc::call_command("d.directory_base.set", rtorrent->get_key("directory"), rpc::make_target(download)); rpc::call_command("d.directory_base.set", rtorrent->get_key("directory"), rpc::make_target(download));
@@ -313,7 +322,7 @@ DownloadFactory::receive_success() {
} catch (const torrent::input_error& e) { } catch (const torrent::input_error& e) {
std::string msg = std::string(session_invalid_message) + ": " + e.what(); std::string msg = std::string(session_invalid_message) + ": " + e.what();
lt_log_print(torrent::LOG_ERROR, "%s: %s", msg.c_str(), m_uri.c_str()); lt_log_print(torrent::LOG_ERRORS, "%s: %s", msg.c_str(), m_uri.c_str());
if (m_printLog) if (m_printLog)
m_manager->push_log_std(msg + ": \"" + m_uri + "\""); m_manager->push_log_std(msg + ": \"" + m_uri + "\"");
+58 -31
View File
@@ -29,6 +29,11 @@
#include "session/session_manager.h" #include "session/session_manager.h"
#include "ui/root.h" #include "ui/root.h"
#define LT_LOG_DOWNLOAD_DEBUG(log_fmt, ...) \
lt_log_print_hash(torrent::LOG_TORRENT_DEBUG, download->info()->hash(), "download_list : " log_fmt, __VA_ARGS__);
#define LT_LOG_DOWNLOAD_EVENTS(log_fmt, ...) \
lt_log_print_info(torrent::LOG_TORRENT_EVENTS, download->info(), "download_list : " log_fmt, __VA_ARGS__);
#define DL_TRIGGER_EVENT(download, event_name) \ #define DL_TRIGGER_EVENT(download, event_name) \
rpc::commands.call_catch(event_name, rpc::make_target(download), torrent::Object(), "Event '" event_name "' failed: "); rpc::commands.call_catch(event_name, rpc::make_target(download), torrent::Object(), "Event '" event_name "' failed: ");
@@ -56,7 +61,7 @@ DownloadList::clear() {
torrent::download_remove(*download->download()); torrent::download_remove(*download->download());
} catch (torrent::internal_error& e) { } catch (torrent::internal_error& e) {
lt_log_print(torrent::LOG_ERROR, "DownloadList::clear() failed to close or remove download: %s", e.what()); lt_log_print(torrent::LOG_ERRORS, "DownloadList::clear() failed to close or remove download: %s", e.what());
error_count++; error_count++;
continue; continue;
} }
@@ -82,7 +87,7 @@ DownloadList::find(const torrent::HashString& hash) {
DownloadList::iterator DownloadList::iterator
DownloadList::find_hex(const char* hash) { DownloadList::find_hex(const char* hash) {
if (strlen(hash) < 40) if (strlen(hash) != 40)
return end(); return end();
torrent::HashString key; torrent::HashString key;
@@ -109,7 +114,7 @@ DownloadList::create(torrent::Object* obj, uint32_t tracker_key, bool printLog)
} catch (torrent::local_error& e) { } catch (torrent::local_error& e) {
if (printLog) if (printLog)
lt_log_print(torrent::LOG_TORRENT_ERROR, "Could not create download: %s", e.what()); lt_log_print(torrent::LOG_TORRENT_EVENTS, "download_list : could not create download : %s", e.what());
delete obj; delete obj;
@@ -135,7 +140,7 @@ DownloadList::create(std::istream* str, uint32_t tracker_key, bool printLog) {
delete object; delete object;
if (printLog) if (printLog)
lt_log_print(torrent::LOG_TORRENT_ERROR, "Could not create download, the input is not a valid torrent."); lt_log_print(torrent::LOG_TORRENT_EVENTS, "download_list : could not create download : invalid or incomplete bencode data");
return NULL; return NULL;
} }
@@ -146,7 +151,7 @@ DownloadList::create(std::istream* str, uint32_t tracker_key, bool printLog) {
delete object; delete object;
if (printLog) if (printLog)
lt_log_print(torrent::LOG_TORRENT_ERROR, "Could not create download: %s", e.what()); lt_log_print(torrent::LOG_TORRENT_EVENTS, "download_list : could not create download : %s", e.what());
return NULL; return NULL;
} }
@@ -160,7 +165,7 @@ DownloadList::iterator
DownloadList::insert(Download* download) { DownloadList::insert(Download* download) {
iterator itr = base_type::insert(end(), std::shared_ptr<Download>(download)); iterator itr = base_type::insert(end(), std::shared_ptr<Download>(download));
lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Inserting download."); LT_LOG_DOWNLOAD_DEBUG("inserting download", 0);
try { try {
(*itr)->data()->slot_initial_hash() = std::bind(&DownloadList::hash_done, this, download); (*itr)->data()->slot_initial_hash() = std::bind(&DownloadList::hash_done, this, download);
@@ -200,7 +205,7 @@ DownloadList::erase(iterator itr) {
(*itr)->set_erasing(); (*itr)->set_erasing();
lt_log_print_info(torrent::LOG_TORRENT_INFO, (*itr)->info(), "download_list", "Erasing download."); lt_log_print_hash(torrent::LOG_TORRENT_DEBUG, (*itr)->info()->hash(), "download_list : removing download");
// Makes sure close doesn't restart hashing of this download. // Makes sure close doesn't restart hashing of this download.
(*itr)->set_hash_failed(true); (*itr)->set_hash_failed(true);
@@ -228,7 +233,7 @@ DownloadList::open(Download* download) {
return true; return true;
} catch (torrent::local_error& e) { } catch (torrent::local_error& e) {
lt_log_print(torrent::LOG_TORRENT_ERROR, "Could not open download: %s", e.what()); lt_log_print(torrent::LOG_TORRENT_EVENTS, "download_list : could not open download : %s", e.what());
return false; return false;
} }
} }
@@ -237,7 +242,7 @@ void
DownloadList::open_throw(Download* download) { DownloadList::open_throw(Download* download) {
check_contains(download); check_contains(download);
lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Opening download."); lt_log_print_hash(torrent::LOG_TORRENT_DEBUG, download->info()->hash(), "download_list : opening download");
if (download->download()->info()->is_open()) if (download->download()->info()->is_open())
return; return;
@@ -248,6 +253,7 @@ DownloadList::open_throw(Download* download) {
openFlags |= torrent::Download::open_enable_fallocate; openFlags |= torrent::Download::open_enable_fallocate;
download->download()->open(openFlags); download->download()->open(openFlags);
DL_TRIGGER_EVENT(download, "event.download.opened"); DL_TRIGGER_EVENT(download, "event.download.opened");
} }
@@ -257,7 +263,7 @@ DownloadList::close(Download* download) {
close_throw(download); close_throw(download);
} catch (torrent::local_error& e) { } catch (torrent::local_error& e) {
lt_log_print(torrent::LOG_TORRENT_ERROR, "Could not close download: %s", e.what()); lt_log_print(torrent::LOG_TORRENT_EVENTS, "download_list : could not close download : %s", e.what());
} }
} }
@@ -265,7 +271,7 @@ DownloadList::close(Download* download) {
// need the files closed and will keep using the download. // need the files closed and will keep using the download.
void void
DownloadList::close_files(Download* download) { DownloadList::close_files(Download* download) {
lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Closing download files."); lt_log_print_hash(torrent::LOG_TORRENT_DEBUG, download->info()->hash(), "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);
@@ -280,7 +286,7 @@ DownloadList::close_files(Download* download) {
void void
DownloadList::close_directly(Download* download) { DownloadList::close_directly(Download* download) {
lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Closing download directly."); lt_log_print_hash(torrent::LOG_TORRENT_DEBUG, download->info()->hash(), "download_list : closing download directly");
auto lifetime = download->lifetime(); auto lifetime = download->lifetime();
bool was_active = download->download()->info()->is_active(); bool was_active = download->download()->info()->is_active();
@@ -327,7 +333,8 @@ DownloadList::update_paused_state(Download* 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_hash(torrent::LOG_TORRENT_DEBUG, download->info()->hash(), "download_list : closing download quickly.");
close(download); close(download);
// Make sure we cancel any tracker requests. This should rather be // Make sure we cancel any tracker requests. This should rather be
@@ -341,7 +348,7 @@ void
DownloadList::close_throw(Download* download) { DownloadList::close_throw(Download* download) {
check_contains(download); check_contains(download);
lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Closing download with throw."); LT_LOG_DOWNLOAD_DEBUG("closing download with throw", 0);
// 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
@@ -383,7 +390,7 @@ void
DownloadList::resume(Download* download, int flags) { DownloadList::resume(Download* download, int flags) {
check_contains(download); check_contains(download);
lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Resuming download: flags:%0x.", flags); LT_LOG_DOWNLOAD_DEBUG("resuming download : flags:%0x.", flags);
try { try {
@@ -472,7 +479,7 @@ DownloadList::resume(Download* download, int flags) {
DL_TRIGGER_EVENT(download, "event.download.resumed"); DL_TRIGGER_EVENT(download, "event.download.resumed");
} catch (torrent::local_error& e) { } catch (torrent::local_error& e) {
lt_log_print(torrent::LOG_TORRENT_ERROR, "Could not resume download: %s", e.what()); LT_LOG_DOWNLOAD_EVENTS("could not resume download : %s", e.what());
} }
} }
@@ -480,7 +487,7 @@ void
DownloadList::pause(Download* download, int flags) { DownloadList::pause(Download* download, int flags) {
check_contains(download); check_contains(download);
lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Pausing download: flags:%0x.", flags); LT_LOG_DOWNLOAD_DEBUG("pausing download : flags:%0x.", flags);
auto lifetime = download->lifetime(); auto lifetime = download->lifetime();
@@ -523,7 +530,7 @@ DownloadList::pause(Download* download, int flags) {
//control->core()->download_store()->save(download); //control->core()->download_store()->save(download);
} catch (torrent::local_error& e) { } catch (torrent::local_error& e) {
lt_log_print(torrent::LOG_TORRENT_ERROR, "Could not pause download: %s", e.what()); LT_LOG_DOWNLOAD_EVENTS("could not pause download : %s", e.what());
} }
} }
@@ -531,7 +538,7 @@ void
DownloadList::check_hash(Download* download) { DownloadList::check_hash(Download* download) {
check_contains(download); check_contains(download);
lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Checking hash."); LT_LOG_DOWNLOAD_DEBUG("checking hash", 0);
try { try {
if (rpc::call_command_value("d.hashing", rpc::make_target(download)) != Download::variable_hashing_stopped) if (rpc::call_command_value("d.hashing", rpc::make_target(download)) != Download::variable_hashing_stopped)
@@ -540,7 +547,7 @@ DownloadList::check_hash(Download* download) {
hash_queue(download, Download::variable_hashing_rehash); hash_queue(download, Download::variable_hashing_rehash);
} catch (torrent::local_error& e) { } catch (torrent::local_error& e) {
lt_log_print(torrent::LOG_TORRENT_ERROR, "Could not check hash: %s", e.what()); LT_LOG_DOWNLOAD_EVENTS("could not check hash : %s", e.what());
} }
} }
@@ -548,7 +555,7 @@ void
DownloadList::hash_done(Download* download) { DownloadList::hash_done(Download* download) {
check_contains(download); check_contains(download);
lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Hash done."); LT_LOG_DOWNLOAD_DEBUG("hash done", 0);
if (download->is_hash_checking() || download->is_active()) if (download->is_hash_checking() || download->is_active())
throw torrent::internal_error("DownloadList::hash_done(...) download in invalid state."); throw torrent::internal_error("DownloadList::hash_done(...) download in invalid state.");
@@ -579,6 +586,23 @@ DownloadList::hash_done(Download* download) {
if (download->is_done() && download->download()->info()->is_meta_download()) if (download->is_done() && download->download()->info()->is_meta_download())
return process_meta_download(download); return process_meta_download(download);
constexpr auto completed_unfinished_message =
"Download registered as completed, but hash check returned unfinished chunks.";
constexpr auto final_bad_chunks_message = "Hash check on download completion found bad chunks.";
constexpr auto invalid_hashing_message =
"Hash check completed but the \"hashing\" variable is in an invalid state.";
// A pass that found every chunk retires the diagnoses an earlier pass set
// below. Any other message is left alone.
if (download->is_done() &&
(hashing == Download::variable_hashing_initial ||
hashing == Download::variable_hashing_rehash ||
hashing == Download::variable_hashing_last) &&
(download->message() == completed_unfinished_message ||
download->message() == final_bad_chunks_message ||
download->message() == invalid_hashing_message))
download->set_message("");
switch (hashing) { switch (hashing) {
case Download::variable_hashing_initial: case Download::variable_hashing_initial:
case Download::variable_hashing_rehash: case Download::variable_hashing_rehash:
@@ -588,7 +612,7 @@ DownloadList::hash_done(Download* download) {
// 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()) {
set_state_stopped(download); set_state_stopped(download);
download->set_message("Download registered as completed, but hash check returned unfinished chunks."); download->set_message(completed_unfinished_message);
} }
// Save resume data so we update time-stamps and priorities if // Save resume data so we update time-stamps and priorities if
@@ -612,8 +636,9 @@ DownloadList::hash_done(Download* download) {
if (download->is_done()) { if (download->is_done()) {
confirm_finished(download); confirm_finished(download);
} else { } else {
download->set_message("Hash check on download completion found bad chunks."); download->set_message(final_bad_chunks_message);
lt_log_print(torrent::LOG_TORRENT_ERROR, "Hash check on download completion found bad chunks."); LT_LOG_DOWNLOAD_EVENTS("hash check on download completion found bad chunks", 0);
DL_TRIGGER_EVENT(download, "event.download.hash_final_failed"); DL_TRIGGER_EVENT(download, "event.download.hash_final_failed");
} }
@@ -623,7 +648,7 @@ DownloadList::hash_done(Download* download) {
case Download::variable_hashing_stopped: case Download::variable_hashing_stopped:
default: default:
// Either an error or someone wrote to the hashing variable... // Either an error or someone wrote to the hashing variable...
download->set_message("Hash check completed but the \"hashing\" variable is in an invalid state."); download->set_message(invalid_hashing_message);
return; return;
} }
@@ -634,7 +659,7 @@ void
DownloadList::hash_queue(Download* download, int type) { DownloadList::hash_queue(Download* download, int type) {
check_contains(download); check_contains(download);
lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Hash queue."); LT_LOG_DOWNLOAD_DEBUG("hash queue : type:%d", type);
if (rpc::call_command_value("d.hashing", rpc::make_target(download)) != Download::variable_hashing_stopped) if (rpc::call_command_value("d.hashing", rpc::make_target(download)) != Download::variable_hashing_stopped)
throw torrent::internal_error("DownloadList::hash_queue(...) hashing already queued."); throw torrent::internal_error("DownloadList::hash_queue(...) hashing already queued.");
@@ -678,7 +703,7 @@ void
DownloadList::received_finished(Download* download) { DownloadList::received_finished(Download* download) {
check_contains(download); check_contains(download);
lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Received finished."); LT_LOG_DOWNLOAD_DEBUG("received finished", 0);
if (rpc::call_command_value("pieces.hash.on_completion")) if (rpc::call_command_value("pieces.hash.on_completion"))
// Set some 'checking_finished_thingie' variable to make hash_done // Set some 'checking_finished_thingie' variable to make hash_done
@@ -693,7 +718,7 @@ void
DownloadList::confirm_finished(Download* download) { DownloadList::confirm_finished(Download* download) {
check_contains(download); check_contains(download);
lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Confirming finished."); LT_LOG_DOWNLOAD_DEBUG("confirming finished", 0);
if (download->download()->info()->is_meta_download()) if (download->download()->info()->is_meta_download())
return process_meta_download(download); return process_meta_download(download);
@@ -768,7 +793,7 @@ DownloadList::confirm_finished(Download* download) {
void void
DownloadList::process_meta_download(Download* download) { DownloadList::process_meta_download(Download* download) {
lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Processing meta download."); LT_LOG_DOWNLOAD_DEBUG("processing meta download", 0);
rpc::call_command("d.stop", torrent::Object(), rpc::make_target(download)); rpc::call_command("d.stop", torrent::Object(), rpc::make_target(download));
rpc::call_command("d.close", torrent::Object(), rpc::make_target(download)); rpc::call_command("d.close", torrent::Object(), rpc::make_target(download));
@@ -778,7 +803,7 @@ DownloadList::process_meta_download(Download* download) {
std::fstream file(metafile.c_str(), std::ios::in | std::ios::binary); std::fstream file(metafile.c_str(), std::ios::in | std::ios::binary);
if (!file.is_open()) { if (!file.is_open()) {
lt_log_print(torrent::LOG_TORRENT_ERROR, "Could not read download metadata."); LT_LOG_DOWNLOAD_EVENTS("could not read download metadata : %s", metafile.c_str());
return; return;
} }
@@ -786,7 +811,7 @@ DownloadList::process_meta_download(Download* download) {
file >> bencode->insert_key("info", torrent::Object()); file >> bencode->insert_key("info", torrent::Object());
if (file.fail()) { if (file.fail()) {
lt_log_print(torrent::LOG_TORRENT_ERROR, "Could not create download, the input is not a valid torrent."); LT_LOG_DOWNLOAD_EVENTS("could not create download, the input is not a valid torrent : %s", metafile.c_str());
return; return;
} }
@@ -794,8 +819,10 @@ DownloadList::process_meta_download(Download* download) {
// Steal the keys we still need. The old download has no use for them. // Steal the keys we still need. The old download has no use for them.
bencode->insert_key("rtorrent_meta_download", torrent::Object()).swap(download->bencode()->get_key("rtorrent_meta_download")); bencode->insert_key("rtorrent_meta_download", torrent::Object()).swap(download->bencode()->get_key("rtorrent_meta_download"));
if (download->bencode()->has_key("announce")) if (download->bencode()->has_key("announce"))
bencode->insert_key("announce", torrent::Object()).swap(download->bencode()->get_key("announce")); bencode->insert_key("announce", torrent::Object()).swap(download->bencode()->get_key("announce"));
if (download->bencode()->has_key("announce-list")) if (download->bencode()->has_key("announce-list"))
bencode->insert_key("announce-list", torrent::Object()).swap(download->bencode()->get_key("announce-list")); bencode->insert_key("announce-list", torrent::Object()).swap(download->bencode()->get_key("announce-list"));
+1
View File
@@ -1,6 +1,7 @@
#ifndef RTORRENT_CORE_DOWNLOAD_LIST_H #ifndef RTORRENT_CORE_DOWNLOAD_LIST_H
#define RTORRENT_CORE_DOWNLOAD_LIST_H #define RTORRENT_CORE_DOWNLOAD_LIST_H
#include <cstdint>
#include <iosfwd> #include <iosfwd>
#include <list> #include <list>
#include <memory> #include <memory>
+4 -2
View File
@@ -436,11 +436,13 @@ Manager::receive_hashing_changed() {
if (tryQuick) { if (tryQuick) {
// Make sure we don't repeat the quick hashing. // Make sure we don't repeat the quick hashing.
rpc::call_command_set_value("d.hashing.set", Download::variable_hashing_rehash, rpc::make_target(*itr)); rpc::call_command_set_value("d.hashing.set", Download::variable_hashing_rehash, rpc::make_target(*itr));
}
} else { if (!tryQuick || !foundHashing) {
(*itr)->set_hash_failed(true); (*itr)->set_hash_failed(true);
(*itr)->set_message("Hashing failed: " + std::string(e.what())); (*itr)->set_message("Hashing failed: " + std::string(e.what()));
lt_log_print(torrent::LOG_TORRENT_ERROR, "Hashing failed: %s", e.what());
lt_log_print_hash(torrent::LOG_TORRENT_EVENTS, (*itr)->info()->hash(), "hashing failed : %s", e.what());
} }
} }
} }
+23 -18
View File
@@ -152,10 +152,11 @@ main(int argc, char** argv) {
SignalHandler::set_sigaction_handler(SIGBUS, &handle_sigbus); SignalHandler::set_sigaction_handler(SIGBUS, &handle_sigbus);
torrent::log_add_group_output(torrent::LOG_NOTICE, "important"); torrent::log_add_group_output(torrent::LOG_ERRORS, "important");
torrent::log_add_group_output(torrent::LOG_DHT_ERROR, "important"); torrent::log_add_group_output(torrent::LOG_DHT_ERROR, "important");
torrent::log_add_group_output(torrent::LOG_INFO, "complete"); torrent::log_add_group_output(torrent::LOG_ERRORS, "complete");
torrent::log_add_group_output(torrent::LOG_EVENTS, "complete");
torrent::log_add_group_output(torrent::LOG_DHT_ERROR, "complete"); torrent::log_add_group_output(torrent::LOG_DHT_ERROR, "complete");
torrent::log_add_group_output(torrent::LOG_DHT_CONTROLLER, "complete"); torrent::log_add_group_output(torrent::LOG_DHT_CONTROLLER, "complete");
@@ -175,17 +176,17 @@ main(int argc, char** argv) {
if (OptionParser::has_flag('D', argc, argv)) { if (OptionParser::has_flag('D', argc, argv)) {
rpc::call_command_set_value("method.use_deprecated.set", true); rpc::call_command_set_value("method.use_deprecated.set", true);
lt_log_print(torrent::LOG_WARN, "Enabled deprecated commands."); lt_log_print(torrent::LOG_ERRORS, "Enabled deprecated commands.");
} }
if (OptionParser::has_flag('I', argc, argv)) { if (OptionParser::has_flag('I', argc, argv)) {
rpc::call_command_set_value("method.use_intermediate.set", 0); rpc::call_command_set_value("method.use_intermediate.set", 0);
lt_log_print(torrent::LOG_WARN, "Disabled intermediate commands."); lt_log_print(torrent::LOG_ERRORS, "Disabled intermediate commands.");
} }
if (OptionParser::has_flag('K', argc, argv)) { if (OptionParser::has_flag('K', argc, argv)) {
rpc::call_command_set_value("method.use_intermediate.set", 2); rpc::call_command_set_value("method.use_intermediate.set", 2);
lt_log_print(torrent::LOG_WARN, "Allowing intermediate commands without xmlrpc."); lt_log_print(torrent::LOG_ERRORS, "Allowing intermediate commands without xmlrpc.");
} }
rpc::parse_command_multiple rpc::parse_command_multiple
@@ -372,11 +373,11 @@ main(int argc, char** argv) {
rpc::rpc.mark_safe("network.http.proxy_address"); rpc::rpc.mark_safe("network.http.proxy_address");
CMD2_ANY_VALUE_V("network.http.max_total_connections.set", [](auto, auto) { CMD2_ANY_VALUE_V("network.http.max_total_connections.set", [](auto, auto) {
lt_log_print(torrent::LOG_WARN, "network.http.max_total_connections.set is deprecated, use system.sockets.http.min_alloc.set instead."); lt_log_print(torrent::LOG_ERRORS, "network.http.max_total_connections.set is deprecated, use system.sockets.http.min_alloc.set instead.");
}); });
CMD2_ANY_VALUE_V("network.max_open_files.set", [](auto, auto) { CMD2_ANY_VALUE_V("network.max_open_files.set", [](auto, auto) {
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_ERRORS, "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. // TODO: Keep d.directory_base for a while as it is widely used.
@@ -419,16 +420,16 @@ main(int argc, char** argv) {
CMD_REDIRECT("max_memory_usage", "pieces.memory.max.set"); CMD_REDIRECT("max_memory_usage", "pieces.memory.max.set");
CMD_ANY_LIST("throttle.ip", []( auto, auto) { CMD_ANY_LIST("throttle.ip", []( auto, auto) {
lt_log_print(torrent::LOG_WARN, "The 'throttle.ip' command is deprecated and does nothing."); lt_log_print(torrent::LOG_ERRORS, "The 'throttle.ip' command is deprecated and does nothing.");
return torrent::Object(); return torrent::Object();
}); });
CMD_ANY("network.port_open", [](auto, auto) { CMD_ANY("network.port_open", [](auto, auto) {
lt_log_print(torrent::LOG_WARN, "The 'network.port_open' command is deprecated and does nothing."); lt_log_print(torrent::LOG_ERRORS, "The 'network.port_open' command is deprecated and does nothing.");
return torrent::Object(); return torrent::Object();
}); });
CMD_ANY("network.port_open.set", [](auto, auto) { CMD_ANY("network.port_open.set", [](auto, auto) {
lt_log_print(torrent::LOG_WARN, "The 'network.port_open.set' command is deprecated and does nothing."); lt_log_print(torrent::LOG_ERRORS, "The 'network.port_open.set' command is deprecated and does nothing.");
return torrent::Object(); return torrent::Object();
}); });
@@ -443,7 +444,7 @@ main(int argc, char** argv) {
if (fd == -1) { if (fd == -1) {
if (errno == EAFNOSUPPORT) { if (errno == EAFNOSUPPORT) {
lt_log_print(torrent::LOG_WARN, "disabling ipv6 support, not available on system"); lt_log_print(torrent::LOG_ERRORS, "disabling ipv6 support, not available on system");
rpc::call_command_set_value("network.block.ipv6.set", true); rpc::call_command_set_value("network.block.ipv6.set", true);
} }
} else { } else {
@@ -455,7 +456,7 @@ main(int argc, char** argv) {
parse_config_file(argc, argv, [](auto& path) { parse_config_file(argc, argv, [](auto& path) {
if (path.empty()) { if (path.empty()) {
lt_log_print(torrent::LOG_WARN, "Ignoring rtorrent.rc."); lt_log_print(torrent::LOG_ERRORS, "Ignoring rtorrent.rc.");
return; return;
} }
@@ -488,7 +489,11 @@ main(int argc, char** argv) {
torrent::system::Thread::self()->event_loop(); torrent::system::Thread::self()->event_loop();
// A quick shutdown with no tracker request pending leaves the loop before
// handle_shutdown() could save.
if (!control->is_shutdown_session_saved())
control->core()->download_list()->session_save(); control->core()->download_list()->session_save();
control->cleanup(); control->cleanup();
} catch (torrent::internal_error& e) { } catch (torrent::internal_error& e) {
@@ -499,7 +504,7 @@ main(int argc, char** argv) {
<< e.what() << std::endl << e.what() << std::endl
<< e.backtrace(); << e.backtrace();
lt_log_print_dump(torrent::LOG_CRITICAL, e.backtrace().c_str(), e.backtrace().size(), lt_log_print_dump(torrent::LOG_ERRORS, e.backtrace().c_str(), e.backtrace().size(),
"Caught internal_error: '%s'.", e.what()); "Caught internal_error: '%s'.", e.what());
torrent::log_cleanup(); torrent::log_cleanup();
@@ -511,7 +516,7 @@ main(int argc, char** argv) {
std::cout << "rtorrent: caught" << typeid(e).name() << " : " << e.what() << std::endl; std::cout << "rtorrent: caught" << typeid(e).name() << " : " << e.what() << std::endl;
lt_log_print(torrent::LOG_CRITICAL, "Caught exception: '%s'.", e.what()); lt_log_print(torrent::LOG_ERRORS, "Caught exception: '%s'.", e.what());
torrent::log_cleanup(); torrent::log_cleanup();
return -1; return -1;
@@ -588,9 +593,9 @@ handle_sigbus(int signum, siginfo_t* sa, [[maybe_unused]] void* ptr) {
handle_sigbus_exit: handle_sigbus_exit:
std::cout << output.rdbuf(); std::cout << output.rdbuf();
if (lt_log_is_valid(torrent::LOG_CRITICAL)) { if (lt_log_is_valid(torrent::LOG_ERRORS)) {
std::string dump = output.str(); std::string dump = output.str();
lt_log_print_dump(torrent::LOG_CRITICAL, dump.c_str(), dump.size(), "Caught signal: '%s'.", signal_reason); lt_log_print_dump(torrent::LOG_ERRORS, dump.c_str(), dump.size(), "Caught signal: '%s'.", signal_reason);
} }
torrent::log_cleanup(); torrent::log_cleanup();
@@ -629,9 +634,9 @@ do_panic(int signum) {
std::cout << output.rdbuf(); std::cout << output.rdbuf();
if (lt_log_is_valid(torrent::LOG_CRITICAL)) { if (lt_log_is_valid(torrent::LOG_ERRORS)) {
std::string dump = output.str(); std::string dump = output.str();
lt_log_print_dump(torrent::LOG_CRITICAL, dump.c_str(), dump.size(), "Caught signal: '%s.", strsignal(signum)); lt_log_print_dump(torrent::LOG_ERRORS, dump.c_str(), dump.size(), "Caught signal: '%s.", strsignal(signum));
} }
torrent::log_cleanup(); torrent::log_cleanup();
+1 -1
View File
@@ -108,7 +108,7 @@ public:
static stack_type* from_data(char* data) { return reinterpret_cast<stack_type*>(data); } static stack_type* from_data(char* data) { return reinterpret_cast<stack_type*>(data); }
char buffer[sizeof(torrent::Object) * max_arguments]; alignas(optimal_alignment) char buffer[sizeof(torrent::Object) * max_arguments];
}; };
command_base() : m_copy_helper(nullptr), m_dest_helper(nullptr) {} command_base() : m_copy_helper(nullptr), m_dest_helper(nullptr) {}
+5 -2
View File
@@ -1,5 +1,7 @@
#include "config.h" #include "config.h"
#include <memory>
#include "rpc/exec_file.h" #include "rpc/exec_file.h"
// #include <cassert> // #include <cassert>
@@ -81,8 +83,9 @@ ExecFile::execute_object(const torrent::Object& rawArgs, int flags) {
char* argsBuffer[max_args]; char* argsBuffer[max_args];
char** argsCurrent = argsBuffer; char** argsCurrent = argsBuffer;
// Size of value strings are less than 24. // On the heap: a buffer of buffer_size does not belong on the stack of whichever thread runs the command.
char valueBuffer[buffer_size+1]; auto valueStorage = std::make_unique<char[]>(buffer_size + 1);
char* valueBuffer = valueStorage.get();
char* valueCurrent = valueBuffer; char* valueCurrent = valueBuffer;
if (rawArgs.is_list()) { if (rawArgs.is_list()) {
+1 -1
View File
@@ -10,7 +10,7 @@ namespace rpc {
class ExecFile { class ExecFile {
public: public:
static constexpr unsigned int max_args = 128; static constexpr unsigned int max_args = 128;
static constexpr unsigned int buffer_size = 4096; static constexpr unsigned int buffer_size = 128 * 1024;
static constexpr int flag_expand_tilde = 0x1; static constexpr int flag_expand_tilde = 0x1;
static constexpr int flag_throw = 0x2; static constexpr int flag_throw = 0x2;
+37 -6
View File
@@ -1,5 +1,7 @@
#include "config.h" #include "config.h"
#include <cctype>
#include <charconv>
#include <cstring> #include <cstring>
#include <cstdio> #include <cstdio>
#include <limits> #include <limits>
@@ -113,16 +115,45 @@ parse_value_nothrow(const char* src, int64_t* value, int base, int unit) {
if (unit <= 0) if (unit <= 0)
throw torrent::input_error("Command::string_to_value_unit(...) received unit <= 0."); throw torrent::input_error("Command::string_to_value_unit(...) received unit <= 0.");
char* last; if (base != 0 && base != 8 && base != 10 && base != 16)
throw torrent::input_error("Command::string_to_value_unit(...) received invalid base.");
const char* first = src;
while (parse_is_space(*src))
src++;
if (src[0] == '+')
return first;
if (src[0] == '-') {
if (base == 8 || base == 16)
return first;
if (src[1] == '0')
return first;
}
if (base == 10 && src[0] == '0' && (src[1] >= '0' && src[1] <= '9'))
return first;
char* last{};
errno = 0;
*value = strtoll(src, &last, base); *value = strtoll(src, &last, base);
if (errno == ERANGE)
return first;
if (last == src) { if (last == src) {
*value = 0;
if (strcasecmp(src, "no") == 0) { *value = 0; return src + strlen("no"); } if (strcasecmp(src, "no") == 0) { *value = 0; return src + strlen("no"); }
if (strcasecmp(src, "yes") == 0) { *value = 1; return src + strlen("yes"); } if (strcasecmp(src, "yes") == 0) { *value = 1; return src + strlen("yes"); }
if (strcasecmp(src, "true") == 0) { *value = 1; return src + strlen("true"); } if (strcasecmp(src, "true") == 0) { *value = 1; return src + strlen("true"); }
if (strcasecmp(src, "false") == 0) { *value = 0; return src + strlen("false"); } if (strcasecmp(src, "false") == 0) { *value = 0; return src + strlen("false"); }
return src; return first;
} }
switch (*last) { switch (*last) {
@@ -130,15 +161,15 @@ parse_value_nothrow(const char* src, int64_t* value, int base, int unit) {
case 'B': ++last; break; case 'B': ++last; break;
case 'k': case 'k':
case 'K': case 'K':
if (!value_fits_shifted(*value, 10)) return src; // overflow guard if (!value_fits_shifted(*value, 10)) return first; // overflow guard
*value = *value << 10; ++last; break; *value = *value << 10; ++last; break;
case 'm': case 'm':
case 'M': case 'M':
if (!value_fits_shifted(*value, 20)) return src; // overflow guard if (!value_fits_shifted(*value, 20)) return first; // overflow guard
*value = *value << 20; ++last; break; *value = *value << 20; ++last; break;
case 'g': case 'g':
case 'G': case 'G':
if (!value_fits_shifted(*value, 30)) return src; // overflow guard if (!value_fits_shifted(*value, 30)) return first; // overflow guard
*value = *value << 30; ++last; break; *value = *value << 30; ++last; break;
// case ' ': // case ' ':
// case '\0': *value = *value * unit; break; // case '\0': *value = *value * unit; break;
@@ -146,7 +177,7 @@ parse_value_nothrow(const char* src, int64_t* value, int base, int unit) {
default: default:
if (*value > std::numeric_limits<int64_t>::max() / unit || if (*value > std::numeric_limits<int64_t>::max() / unit ||
*value < std::numeric_limits<int64_t>::min() / unit) *value < std::numeric_limits<int64_t>::min() / unit)
return src; // overflow guard return first; // overflow guard
*value = *value * unit; *value = *value * unit;
break; break;
-34
View File
@@ -1,37 +1,3 @@
// 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_RPC_PARSE_H #ifndef RTORRENT_RPC_PARSE_H
#define RTORRENT_RPC_PARSE_H #define RTORRENT_RPC_PARSE_H
+33
View File
@@ -123,6 +123,39 @@ parse_command(target_type target, const char* first, const char* last) {
return std::make_pair(commands.call_command(key, args, target), first); return std::make_pair(commands.call_command(key, args, target), first);
} }
torrent::Object
parse_command_object(const char* first, const char* last) {
first = std::find_if(first, last, [&](char c) { return !command_map_is_space(c); });
if (first == last || *first == '#')
return torrent::Object();
char key[128];
first = parse_command_name(first, last, key, key + 128);
first = std::find_if(first, last, [&](char c) { return !command_map_is_space(c); });
if (first == last || *first != '=')
throw torrent::input_error("Could not find '=' in command '" + std::string(key) + "'.");
torrent::Object result = torrent::Object::create_dict_key();
result.as_dict_key() = key;
first = parse_whole_list(first + 1, last, &result.as_dict_obj(), &parse_is_delim_command);
// Find the last character that is part of this command, skipping
// the whitespace at the end.
first = std::find_if(first, last, [&](char c) { return !command_map_is_space(c); });
// This helper accepts exactly one command and cannot return where a next
// command begins, so reject every non-whitespace suffix, including ';'.
if (first != last && *first != '\0')
throw torrent::input_error("Junk at end of input.");
return result;
}
torrent::Object torrent::Object
parse_command_multiple(target_type target, const char* first, const char* last) { parse_command_multiple(target_type target, const char* first, const char* last) {
parse_command_type result; parse_command_type result;
+31
View File
@@ -37,6 +37,9 @@
#include <string> #include <string>
#include <cstring> #include <cstring>
#include <functional>
#include <utility>
#include <vector>
#include "xmlrpc.h" #include "xmlrpc.h"
#include "rpc_manager.h" #include "rpc_manager.h"
@@ -67,6 +70,34 @@ parse_command_single(target_type target, const std::string& cmd) {
return parse_command(target, cmd.c_str(), cmd.c_str() + cmd.size()).first; return parse_command(target, cmd.c_str(), cmd.c_str() + cmd.size()).first;
} }
// Parse one RPC command without executing it. Repeated evaluations can use
// call_object on the result, which handles per-target argument expansion.
torrent::Object parse_command_object(const char* first, const char* last);
inline torrent::Object parse_command_object(const std::string& cmd) {
return parse_command_object(cmd.c_str(), cmd.c_str() + cmd.size());
}
// Prepare a multicall's commands once, on the first target that uses them. This
// keeps empty target lists from parsing commands that would never be evaluated.
struct preparsed_commands : public std::vector<torrent::Object> {
explicit preparsed_commands(std::function<void(preparsed_commands&)> prepare)
: m_prepare(std::move(prepare)) {}
void prepare_if_needed() {
if (m_prepare) {
// Clear before invoking: the callback may inspect this vector, and a
// throwing callback must not be run again against partially added items.
auto prepare = std::move(m_prepare);
m_prepare = {};
prepare(*this);
}
}
private:
std::function<void(preparsed_commands&)> m_prepare;
};
inline torrent::Object inline torrent::Object
parse_command_multiple_std(const std::string& cmd, target_type target = rpc::make_target()) { parse_command_multiple_std(const std::string& cmd, target_type target = rpc::make_target()) {
return parse_command_multiple(target, cmd.c_str(), cmd.c_str() + cmd.size()); return parse_command_multiple(target, cmd.c_str(), cmd.c_str() + cmd.size());
+31 -1
View File
@@ -10,6 +10,8 @@
#include <torrent/net/fd.h> #include <torrent/net/fd.h>
#include <torrent/net/socket_address.h> #include <torrent/net/socket_address.h>
#include <torrent/system/poll.h> #include <torrent/system/poll.h>
#include <torrent/system/scheduler.h>
#include <torrent/utils/log.h>
#include <torrent/runtime/socket_manager.h> #include <torrent/runtime/socket_manager.h>
#include "control.h" #include "control.h"
@@ -25,6 +27,8 @@ SCgi::SCgi() {
std::generate(m_tasks.begin(), m_tasks.end(), []() { return std::make_unique<SCgiTask>(); }); std::generate(m_tasks.begin(), m_tasks.end(), []() { return std::make_unique<SCgiTask>(); });
m_current = m_tasks.begin(); m_current = m_tasks.begin();
m_resume_accept.slot() = [this] { resume_accept(); };
} }
SCgi::~SCgi() { SCgi::~SCgi() {
@@ -121,6 +125,9 @@ SCgi::stop() {
if (!is_open()) if (!is_open())
return; return;
if (m_resume_accept.is_scheduled())
torrent::this_thread::scheduler()->erase(&m_resume_accept);
for (auto& itr : m_tasks) { for (auto& itr : m_tasks) {
if (itr->is_open()) if (itr->is_open())
itr->close(); itr->close();
@@ -158,9 +165,16 @@ SCgi::event_read() {
return; return;
// Force a new event_read() call just to be sure we don't enter an infinite loop. // Force a new event_read() call just to be sure we don't enter an infinite loop.
if (errno == ECONNABORTED) if (errno == ECONNABORTED || errno == EPROTO || errno == EPERM)
return; return;
// Out of file descriptors or memory: the connection stays queued and the socket stays readable,
// so stop reading it for a while instead of spinning.
if (errno == EMFILE || errno == ENFILE || errno == ENOBUFS || errno == ENOMEM) {
pause_accept(errno);
return;
}
throw torrent::resource_error("Listener port accept() failed: " + std::string(std::strerror(errno))); throw torrent::resource_error("Listener port accept() failed: " + std::string(std::strerror(errno)));
} }
@@ -189,6 +203,22 @@ SCgi::event_read() {
} }
} }
void
SCgi::pause_accept(int error) {
lt_log_print(torrent::LOG_ERRORS, "scgi: accept() failed: %s : not accepting connections for a second", std::strerror(error));
torrent::this_thread::poll()->remove_read(this);
torrent::this_thread::scheduler()->update_wait_for(&m_resume_accept, std::chrono::seconds(1));
}
void
SCgi::resume_accept() {
if (!is_open())
return;
torrent::this_thread::poll()->insert_read(this);
}
void void
SCgi::event_write() { SCgi::event_write() {
throw torrent::internal_error("Listener does not support write()."); throw torrent::internal_error("Listener does not support write().");
+5
View File
@@ -4,6 +4,7 @@
#include <array> #include <array>
#include <memory> #include <memory>
#include <torrent/system/event.h> #include <torrent/system/event.h>
#include <torrent/system/scheduler.h>
#include "rpc/scgi_task.h" #include "rpc/scgi_task.h"
@@ -39,12 +40,16 @@ private:
using task_list = std::array<std::unique_ptr<SCgiTask>, max_tasks>; using task_list = std::array<std::unique_ptr<SCgiTask>, max_tasks>;
void open(sockaddr* sa, unsigned int length); void open(sockaddr* sa, unsigned int length);
void pause_accept(int error);
void resume_accept();
std::string m_path; std::string m_path;
int m_logFd{-1}; int m_logFd{-1};
task_list m_tasks; task_list m_tasks;
task_list::iterator m_current; task_list::iterator m_current;
torrent::system::SchedulerEntry m_resume_accept;
}; };
} }
+3 -3
View File
@@ -82,7 +82,7 @@ ThreadScgi::change_rpc_log() {
::close(scgi()->log_fd()); ::close(scgi()->log_fd());
scgi()->set_log_fd(-1); scgi()->set_log_fd(-1);
lt_log_print(torrent::LOG_NOTICE, "Closed RPC log.", 0); lt_log_print(torrent::LOG_EVENTS, "Closed RPC log.", 0);
} }
if (m_rpc_log_filename.empty()) if (m_rpc_log_filename.empty())
@@ -91,11 +91,11 @@ ThreadScgi::change_rpc_log() {
scgi()->set_log_fd(open(expand_path(m_rpc_log_filename).c_str(), O_WRONLY | O_APPEND | O_CREAT, 0644)); scgi()->set_log_fd(open(expand_path(m_rpc_log_filename).c_str(), O_WRONLY | O_APPEND | O_CREAT, 0644));
if (scgi()->log_fd() == -1) { if (scgi()->log_fd() == -1) {
lt_log_print(torrent::LOG_NOTICE, "Could not open RPC log file '%s'.", m_rpc_log_filename.c_str()); lt_log_print(torrent::LOG_EVENTS, "Could not open RPC log file '%s'.", m_rpc_log_filename.c_str());
return; return;
} }
lt_log_print(torrent::LOG_NOTICE, "Logging RPC events to '%s'.", m_rpc_log_filename.c_str()); lt_log_print(torrent::LOG_EVENTS, "Logging RPC events to '%s'.", m_rpc_log_filename.c_str());
} }
void void
+12 -5
View File
@@ -5,6 +5,7 @@
#include <cerrno> #include <cerrno>
#include <fcntl.h> #include <fcntl.h>
#include <fstream> #include <fstream>
#include <sys/stat.h>
#include <unistd.h> #include <unistd.h>
#include <torrent/exceptions.h> #include <torrent/exceptions.h>
#include <torrent/object.h> #include <torrent/object.h>
@@ -123,7 +124,7 @@ 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, mode_t file_mode, const std::stringstream& stream) {
// Remove any leftover temporary file first so that O_EXCL only ever fails on // 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 // an entry that appeared after the unlink, and O_NOFOLLOW keeps a symlink
// planted in the session directory from redirecting the write. // planted in the session directory from redirecting the write.
@@ -136,6 +137,12 @@ save_stream(const std::string& path, bool use_fsyncdisk, const std::stringstream
if (fd < 0) if (fd < 0)
throw torrent::storage_error("failed to open file for writing : " + path); throw torrent::storage_error("failed to open file for writing : " + path);
// open() applies the process umask, so set the configured mode explicitly.
if (file_mode != 0 && ::fchmod(fd, file_mode) == -1) {
::close(fd);
throw torrent::storage_error("failed to set file permissions : " + path);
}
const auto data = stream.view(); const auto data = stream.view();
std::size_t remaining = data.size(); std::size_t remaining = data.size();
const char* cursor = data.data(); const char* cursor = data.data();
@@ -196,7 +203,7 @@ sync_directory_of(const std::string& path) {
} // namespace anonymous } // namespace anonymous
void void
DownloadStorer::save_and_move_streams(const std::string& path, bool use_fsyncdisk, DownloadStorer::save_and_move_streams(const std::string& path, bool use_fsyncdisk, mode_t file_mode,
const std::stringstream* torrent_stream, const std::stringstream* torrent_stream,
const std::stringstream* rtorrent_stream, const std::stringstream* rtorrent_stream,
const std::stringstream* libtorrent_stream) { const std::stringstream* libtorrent_stream) {
@@ -205,10 +212,10 @@ DownloadStorer::save_and_move_streams(const std::string& path, bool use_fsyncdis
auto rtorrent_path = path + ".rtorrent"; auto rtorrent_path = path + ".rtorrent";
if (torrent_stream) if (torrent_stream)
save_stream(torrent_path + ".new", use_fsyncdisk, *torrent_stream); save_stream(torrent_path + ".new", use_fsyncdisk, file_mode, *torrent_stream);
save_stream(libtorrent_path + ".new", use_fsyncdisk, *libtorrent_stream); save_stream(libtorrent_path + ".new", use_fsyncdisk, file_mode, *libtorrent_stream);
save_stream(rtorrent_path + ".new", use_fsyncdisk, *rtorrent_stream); save_stream(rtorrent_path + ".new", use_fsyncdisk, file_mode, *rtorrent_stream);
if (torrent_stream) { if (torrent_stream) {
if (::rename((torrent_path + ".new").c_str(), torrent_path.c_str()) == -1) if (::rename((torrent_path + ".new").c_str(), torrent_path.c_str()) == -1)
+2 -1
View File
@@ -3,6 +3,7 @@
#include <memory> #include <memory>
#include <sstream> #include <sstream>
#include <sys/types.h>
#include <string> #include <string>
#include <torrent/common.h> #include <torrent/common.h>
@@ -33,7 +34,7 @@ public:
auto rtorrent_stream() { return std::move(m_rtorrent_stream); } auto rtorrent_stream() { return std::move(m_rtorrent_stream); }
auto libtorrent_stream() { return std::move(m_libtorrent_stream); } auto libtorrent_stream() { return std::move(m_libtorrent_stream); }
static void save_and_move_streams(const std::string& path, bool use_fsyncdisk, static void save_and_move_streams(const std::string& path, bool use_fsyncdisk, mode_t file_mode,
const std::stringstream* torrent_stream, const std::stringstream* torrent_stream,
const std::stringstream* rtorrent_stream, const std::stringstream* rtorrent_stream,
const std::stringstream* libtorrent_stream); const std::stringstream* libtorrent_stream);
+69 -18
View File
@@ -65,6 +65,30 @@ SessionManager::set_use_lock(bool use_lock) {
m_use_lock = use_lock; m_use_lock = use_lock;
} }
mode_t
SessionManager::file_mode() const {
if (m_umask == 0)
return 0;
return 0666 & ~m_umask;
}
void
SessionManager::set_umask(int64_t umask) {
assert(torrent::this_thread::thread() == torrent::main_thread::thread());
if (m_freeze_info)
throw torrent::input_error("Session umask option cannot be changed after startup.");
if (umask < 0 || umask > 0777)
throw torrent::input_error("Session umask must be in the range 0 to 0777.");
if (umask != 0 && (umask & 0600) != 0)
throw torrent::input_error("Session umask must not mask owner read/write permissions (0600).");
m_umask = umask;
}
void void
SessionManager::save_resume_download(core::Download* download) { SessionManager::save_resume_download(core::Download* download) {
assert(torrent::this_thread::thread() == torrent::main_thread::thread()); assert(torrent::this_thread::thread() == torrent::main_thread::thread());
@@ -379,7 +403,7 @@ SessionManager::process_next_save_request_unsafe() {
}; };
try { try {
DownloadStorer::save_and_move_streams(itr->second.path, m_use_fsyncdisk, DownloadStorer::save_and_move_streams(itr->second.path, m_use_fsyncdisk, file_mode(),
itr->second.torrent_stream.get(), itr->second.torrent_stream.get(),
itr->second.rtorrent_stream.get(), itr->second.rtorrent_stream.get(),
itr->second.libtorrent_stream.get()); itr->second.libtorrent_stream.get());
@@ -405,24 +429,41 @@ SessionManager::process_finished_saves() {
if (!m_active) if (!m_active)
throw torrent::internal_error("SessionManager::process_finished_saves() called while not active."); throw torrent::internal_error("SessionManager::process_finished_saves() called while not active.");
for (auto& request : m_finished_saves) { for (auto& request : m_finished_saves)
consume_finished_save_unsafe(request);
m_finished_saves.clear();
callback_save_request();
}
// Storage errors share a five-minute log limit and count what they suppress.
// Callers hold m_mutex.
void
SessionManager::report_storage_error_unsafe(const std::string& what) {
if (m_last_storage_error_message + std::chrono::minutes(5) > torrent::this_thread::cached_time()) {
m_ignored_storage_error_count++;
return;
}
lt_log_print(torrent::LOG_ERRORS, "Storage errors saving session data for download: ignored:%u : %s", m_ignored_storage_error_count, what.c_str());
m_last_storage_error_message = torrent::this_thread::cached_time();
m_ignored_storage_error_count = 0;
}
// The one policy for a finished save, on the session thread and in shutdown
// flushes: storage errors go to report_storage_error_unsafe(), internal and
// unknown errors propagate. Callers hold m_mutex.
void
SessionManager::consume_finished_save_unsafe(ProcessingSave& request) {
try { try {
request.first.get(); request.first.get();
} catch (torrent::storage_error& e) { } catch (torrent::storage_error& e) {
LT_LOG("error saving download : storage error :download:%p path:%s : %s", request.second.download, request.second.path.c_str(), e.what()); LT_LOG("error saving download : storage error :download:%p path:%s : %s", request.second.download, request.second.path.c_str(), e.what());
report_storage_error_unsafe(e.what());
if (m_last_storage_error_message + std::chrono::minutes(5) > torrent::this_thread::cached_time()) { return;
m_ignored_storage_error_count++;
continue;
}
lt_log_print(torrent::LOG_ERROR, "Storage errors saving session data for download: ignored:%u : %s", m_ignored_storage_error_count, e.what());
m_last_storage_error_message = torrent::this_thread::cached_time();
m_ignored_storage_error_count = 0;
continue;
} catch (torrent::internal_error& e) { } catch (torrent::internal_error& e) {
LT_LOG("error saving download : internal error : download:%p path:%s : %s", request.second.download, request.second.path.c_str(), e.what()); LT_LOG("error saving download : internal error : download:%p path:%s : %s", request.second.download, request.second.path.c_str(), e.what());
@@ -436,9 +477,12 @@ SessionManager::process_finished_saves() {
LT_LOG("finished saving download : download:%p path:%s", request.second.download, request.second.path.c_str()); LT_LOG("finished saving download : download:%p path:%s", request.second.download, request.second.path.c_str());
} }
m_finished_saves.clear(); void
SessionManager::flush_shutdown_saves() {
callback_save_request(); assert(torrent::this_thread::thread() == torrent::main_thread::thread());
flush_all_pending_builds();
std::unique_lock<std::mutex> lock(m_mutex);
flush_all_and_wait_unsafe(lock);
} }
void void
@@ -460,10 +504,17 @@ SessionManager::flush_all_and_wait_unsafe(std::unique_lock<std::mutex>& lock) {
m_finished_condition.wait(lock); m_finished_condition.wait(lock);
for (auto& request : m_finished_saves) for (auto& request : m_finished_saves)
LT_LOG("finished saving download : download:%p path:%s", request.second.download, request.second.path.c_str()); consume_finished_save_unsafe(request);
m_finished_saves.clear(); m_finished_saves.clear();
// Report and reset what was suppressed since the last error line. The count
// includes saves the session thread consumed while this flush waited.
if (m_ignored_storage_error_count != 0) {
lt_log_print(torrent::LOG_ERRORS, "Storage errors flushing session data: ignored:%u", m_ignored_storage_error_count);
m_ignored_storage_error_count = 0;
}
LT_LOG("flushed all pending saves", 0); LT_LOG("flushed all pending saves", 0);
} }
+12
View File
@@ -6,6 +6,8 @@
#include <deque> #include <deque>
#include <future> #include <future>
#include <list> #include <list>
#include <cstdint>
#include <sys/types.h>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <sstream> #include <sstream>
@@ -59,6 +61,10 @@ public:
bool use_lock() const; bool use_lock() const;
void set_use_lock(bool use_lock); void set_use_lock(bool use_lock);
int umask() const;
void set_umask(int64_t umask);
mode_t file_mode() const;
void save_full_download(core::Download* download); void save_full_download(core::Download* download);
void save_resume_download(core::Download* download); void save_resume_download(core::Download* download);
void remove_download(core::Download* download); void remove_download(core::Download* download);
@@ -73,6 +79,7 @@ protected:
void cleanup(); void cleanup();
void flush_all_pending_builds(); void flush_all_pending_builds();
void flush_shutdown_saves();
private: private:
void callback_pending_builds(); void callback_pending_builds();
@@ -92,6 +99,9 @@ private:
using ProcessingSave = std::pair<std::future<void>, SaveRequest>; using ProcessingSave = std::pair<std::future<void>, SaveRequest>;
void consume_finished_save_unsafe(ProcessingSave& request);
void report_storage_error_unsafe(const std::string& what);
torrent::system::Thread* m_thread; torrent::system::Thread* m_thread;
torrent::system::callback_id m_callback_id; torrent::system::callback_id m_callback_id;
@@ -99,6 +109,7 @@ private:
std::string m_path; std::string m_path;
bool m_use_fsyncdisk{true}; bool m_use_fsyncdisk{true};
bool m_use_lock{true}; bool m_use_lock{true};
int m_umask{};
align_cacheline align_cacheline
@@ -133,6 +144,7 @@ inline bool SessionManager::is_used() const { return !m_path.e
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 m_use_fsyncdisk; } 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 int SessionManager::umask() const { return m_umask; }
inline void SessionManager::flush_all_pending_builds() { process_pending_builds(true); } inline void SessionManager::flush_all_pending_builds() { process_pending_builds(true); }
} // namespace session } // namespace session
+1 -1
View File
@@ -142,7 +142,7 @@ parse_config_file_comments(const std::string& path) {
else else
throw torrent::input_error("Unknown command in config file comment: " + line); throw torrent::input_error("Unknown command in config file comment: " + line);
lt_log_print(torrent::LOG_NOTICE, "Pre-config command: %s=%s", command.c_str(), args.c_str()); lt_log_print(torrent::LOG_EVENTS, "Pre-config command: %s=%s", command.c_str(), args.c_str());
} }
} }
+1 -1
View File
@@ -366,7 +366,7 @@ DownloadList::receive_exit_input(Input type) {
} }
} catch (torrent::input_error& e) { } catch (torrent::input_error& e) {
lt_log_print(torrent::LOG_WARN, "Input failed: %s", e.what()); lt_log_print(torrent::LOG_ERRORS, "Input failed: %s", e.what());
} }
activate_display(DISPLAY_DOWNLOAD_LIST); activate_display(DISPLAY_DOWNLOAD_LIST);
+3 -2
View File
@@ -119,7 +119,7 @@ ElementDownloadList::receive_command(const char* cmd) {
m_view->set_last_changed(); m_view->set_last_changed();
} catch (torrent::input_error& e) { } catch (torrent::input_error& e) {
lt_log_print(torrent::LOG_WARN, "Command failed: %s", e.what()); lt_log_print(torrent::LOG_ERRORS, "Command failed: %s", e.what());
return; return;
} }
} }
@@ -197,8 +197,9 @@ ElementDownloadList::receive_cycle_throttle() {
return; return;
core::Download* download = m_view->focus()->get(); core::Download* download = m_view->focus()->get();
if (download->is_active()) { if (download->is_active()) {
lt_log_print(torrent::LOG_TORRENT_WARN, "Cannot change throttle on active download."); lt_log_print_hash(torrent::LOG_TORRENT_DEBUG, download->info()->hash(), "cannot change throttle on active download");
return; return;
} }
+7 -7
View File
@@ -380,7 +380,7 @@ Root::set_input_history_size(int size) {
void void
Root::load_input_history() { Root::load_input_history() {
if (m_control == nullptr || !session_thread::manager()->is_used()) { if (m_control == nullptr || !session_thread::manager()->is_used()) {
lt_log_print(torrent::LOG_DEBUG, "ignoring input history file"); lt_log_print(torrent::LOG_DEBUG_TRACE, "ignoring input history file");
return; return;
} }
@@ -415,10 +415,10 @@ Root::load_input_history() {
} }
if (history_file.bad()) { if (history_file.bad()) {
lt_log_print(torrent::LOG_DEBUG, "input history file corrupted, discarding (path:%s)", history_filename.c_str()); lt_log_print(torrent::LOG_DEBUG_TRACE, "input history file corrupted, discarding (path:%s)", history_filename.c_str());
return; return;
} else { } else {
lt_log_print(torrent::LOG_DEBUG, "input history file read (path:%s)", history_filename.c_str()); lt_log_print(torrent::LOG_DEBUG_TRACE, "input history file read (path:%s)", history_filename.c_str());
} }
for (const auto& [entry, category] : input_history_tmp) { for (const auto& [entry, category] : input_history_tmp) {
@@ -441,7 +441,7 @@ Root::load_input_history() {
} }
} }
} else { } else {
lt_log_print(torrent::LOG_DEBUG, "could not open input history file (path:%s)", history_filename.c_str()); lt_log_print(torrent::LOG_DEBUG_TRACE, "could not open input history file (path:%s)", history_filename.c_str());
} }
} }
@@ -455,7 +455,7 @@ Root::save_input_history() {
auto history_file = std::fstream(history_filename_tmp.c_str(), std::ios::out | std::ios::trunc); auto history_file = std::fstream(history_filename_tmp.c_str(), std::ios::out | std::ios::trunc);
if (!history_file.is_open()) { if (!history_file.is_open()) {
lt_log_print(torrent::LOG_DEBUG, "could not open input history file for writing (path:%s)", history_filename.c_str()); lt_log_print(torrent::LOG_DEBUG_TRACE, "could not open input history file for writing (path:%s)", history_filename.c_str());
return; return;
} }
@@ -471,10 +471,10 @@ Root::save_input_history() {
history_file.close(); 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_TRACE, "input history file corrupted during writing, discarding (path:%s)", history_filename.c_str());
return; return;
} else { } else {
lt_log_print(torrent::LOG_DEBUG, "input history file written (path:%s)", history_filename.c_str()); lt_log_print(torrent::LOG_DEBUG_TRACE, "input history file written (path:%s)", history_filename.c_str());
} }
std::rename(history_filename_tmp.c_str(), history_filename.c_str()); std::rename(history_filename_tmp.c_str(), history_filename.c_str());
+10
View File
@@ -51,14 +51,20 @@ rtorrent_Test_Rpc_SOURCES = $(rtorrent_Test_Common) \
rpc/test_command_slot.h \ rpc/test_command_slot.h \
rpc/test_object_storage.cc \ rpc/test_object_storage.cc \
rpc/test_object_storage.h \ rpc/test_object_storage.h \
rpc/test_parse.cc \
rpc/test_parse.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.cc \
rpc/test_rpc_manager.h rpc/test_rpc_manager.h
rtorrent_Test_Src_SOURCES = $(rtorrent_Test_Common) \ rtorrent_Test_Src_SOURCES = $(rtorrent_Test_Common) \
src/test_command_download.cc \
src/test_command_download.h \
src/test_command_dynamic.cc \ src/test_command_dynamic.cc \
src/test_command_dynamic.h \ src/test_command_dynamic.h \
src/test_command_groups.cc \
src/test_command_groups.h \
src/test_command_ip.cc \ src/test_command_ip.cc \
src/test_command_ip.h \ src/test_command_ip.h \
src/test_command_system.cc \ src/test_command_system.cc \
@@ -69,6 +75,10 @@ rtorrent_Test_Src_SOURCES = $(rtorrent_Test_Common) \
src/test_command_string.h \ src/test_command_string.h \
src/test_command_throttle.cc \ src/test_command_throttle.cc \
src/test_command_throttle.h \ src/test_command_throttle.h \
src/test_command_tracker.cc \
src/test_command_tracker.h \
src/test_download_list.cc \
src/test_download_list.h \
src/test_input_path_input.cc \ src/test_input_path_input.cc \
src/test_input_path_input.h \ src/test_input_path_input.h \
src/test_session_commit.cc \ src/test_session_commit.cc \
+50
View File
@@ -2,7 +2,10 @@
#include "test/rpc/test_command.h" #include "test/rpc/test_command.h"
#include <torrent/exceptions.h>
#include "rpc/command.h" #include "rpc/command.h"
#include "rpc/parse_commands.h"
CPPUNIT_TEST_SUITE_REGISTRATION(TestCommand); CPPUNIT_TEST_SUITE_REGISTRATION(TestCommand);
@@ -83,3 +86,50 @@ TestCommand::test_stack_double() {
rpc::command_base::pop_stack(&stack_first, last_stack_first); rpc::command_base::pop_stack(&stack_first, last_stack_first);
CPPUNIT_ASSERT(command_stack_all_empty()); CPPUNIT_ASSERT(command_stack_all_empty());
} }
void
TestCommand::test_preparsed_commands() {
unsigned int prepare_count = 0;
rpc::preparsed_commands commands([&prepare_count](auto& prepared) {
++prepare_count;
// Reentrant access must not invoke the same callback recursively.
prepared.prepare_if_needed();
prepared.push_back(rpc::parse_command_object("string.length=abc"));
});
CPPUNIT_ASSERT_EQUAL(0u, prepare_count);
CPPUNIT_ASSERT(commands.empty());
commands.prepare_if_needed();
CPPUNIT_ASSERT_EQUAL(1u, prepare_count);
size_t count = 0;
for (auto& itr : commands) {
CPPUNIT_ASSERT(itr.is_dict_key());
++count;
}
CPPUNIT_ASSERT_EQUAL(size_t(1), count);
CPPUNIT_ASSERT_EQUAL(1u, prepare_count);
for (auto& itr : commands)
CPPUNIT_ASSERT(itr.is_dict_key());
CPPUNIT_ASSERT_EQUAL(1u, prepare_count);
}
void
TestCommand::test_parse_command_object() {
auto command = rpc::parse_command_object("\tstring.length=abc ");
CPPUNIT_ASSERT(command.is_dict_key());
CPPUNIT_ASSERT_EQUAL(std::string("string.length"), command.as_dict_key());
// This helper has no way to return the next-command pointer, so it must not
// silently accept a multipart command separated by ';'.
CPPUNIT_ASSERT_THROW(rpc::parse_command_object("string.length=abc;string.length=def"), torrent::input_error);
// Unlike parse_command (which parses command files), this helper handles one
// multicall command and must reject newline boundaries, including CRLF.
CPPUNIT_ASSERT_THROW(rpc::parse_command_object("string.length=abc\nstring.length=def"), torrent::input_error);
CPPUNIT_ASSERT_THROW(rpc::parse_command_object("string.length=abc\r\nstring.length=def"), torrent::input_error);
}
+4
View File
@@ -5,10 +5,14 @@ class TestCommand : public test_fixture {
CPPUNIT_TEST(test_stack); CPPUNIT_TEST(test_stack);
CPPUNIT_TEST(test_stack_double); CPPUNIT_TEST(test_stack_double);
CPPUNIT_TEST(test_preparsed_commands);
CPPUNIT_TEST(test_parse_command_object);
CPPUNIT_TEST_SUITE_END(); CPPUNIT_TEST_SUITE_END();
public: public:
void test_stack(); void test_stack();
void test_stack_double(); void test_stack_double();
void test_preparsed_commands();
void test_parse_command_object();
}; };
+95
View File
@@ -0,0 +1,95 @@
#include "config.h"
#include "test/rpc/test_parse.h"
#include <cstdint>
#include <limits>
#include "rpc/parse.h"
CPPUNIT_TEST_SUITE_REGISTRATION(TestParse);
void
TestParse::test_whole_value_in_range() {
int64_t value = 0;
CPPUNIT_ASSERT(rpc::parse_whole_value_nothrow("9223372036854775807", &value));
CPPUNIT_ASSERT_EQUAL(std::numeric_limits<int64_t>::max(), value);
CPPUNIT_ASSERT(rpc::parse_whole_value_nothrow("-9223372036854775808", &value));
CPPUNIT_ASSERT_EQUAL(std::numeric_limits<int64_t>::min(), value);
}
void
TestParse::test_whole_value_out_of_range() {
int64_t value = 0;
CPPUNIT_ASSERT(!rpc::parse_whole_value_nothrow("9223372036854775808", &value));
CPPUNIT_ASSERT(!rpc::parse_whole_value_nothrow("-9223372036854775809", &value));
CPPUNIT_ASSERT(!rpc::parse_whole_value_nothrow("99999999999999999999999999", &value));
}
void
TestParse::test_whole_value_bases() {
int64_t value = 0;
CPPUNIT_ASSERT(rpc::parse_whole_value_nothrow("0x1f", &value));
CPPUNIT_ASSERT_EQUAL(int64_t{31}, value);
CPPUNIT_ASSERT(rpc::parse_whole_value_nothrow("0X1F", &value));
CPPUNIT_ASSERT_EQUAL(int64_t{31}, value);
CPPUNIT_ASSERT(!rpc::parse_whole_value_nothrow("-0x1f", &value));
CPPUNIT_ASSERT(rpc::parse_whole_value_nothrow("0022", &value));
CPPUNIT_ASSERT_EQUAL(int64_t{022}, value);
CPPUNIT_ASSERT(!rpc::parse_whole_value_nothrow("0028", &value));
CPPUNIT_ASSERT(!rpc::parse_whole_value_nothrow("+022", &value));
CPPUNIT_ASSERT(!rpc::parse_whole_value_nothrow("-022", &value));
CPPUNIT_ASSERT(rpc::parse_whole_value_nothrow("22", &value));
CPPUNIT_ASSERT_EQUAL(int64_t{22}, value);
CPPUNIT_ASSERT(rpc::parse_whole_value_nothrow("1f", &value, 16));
CPPUNIT_ASSERT_EQUAL(int64_t{31}, value);
CPPUNIT_ASSERT(rpc::parse_whole_value_nothrow("0x1f", &value, 16));
CPPUNIT_ASSERT_EQUAL(int64_t{31}, value);
CPPUNIT_ASSERT(rpc::parse_whole_value_nothrow("22", &value, 8));
CPPUNIT_ASSERT_EQUAL(int64_t{022}, value);
CPPUNIT_ASSERT(rpc::parse_whole_value_nothrow("022", &value, 8));
CPPUNIT_ASSERT_EQUAL(int64_t{022}, value);
const char* no_digits = "0x";
CPPUNIT_ASSERT(!rpc::parse_whole_value_nothrow(no_digits, &value));
CPPUNIT_ASSERT_EQUAL(no_digits + 1, rpc::parse_value_nothrow(no_digits, &value));
const char* bad_digits = "0xzz";
CPPUNIT_ASSERT(!rpc::parse_whole_value_nothrow(bad_digits, &value));
CPPUNIT_ASSERT_EQUAL(bad_digits + 1, rpc::parse_value_nothrow(bad_digits, &value));
}
void
TestParse::test_whole_value_prefixes() {
int64_t value = 0;
CPPUNIT_ASSERT(!rpc::parse_whole_value_nothrow("+5", &value));
CPPUNIT_ASSERT(rpc::parse_whole_value_nothrow(" 5 ", &value));
CPPUNIT_ASSERT_EQUAL(int64_t{5}, value);
CPPUNIT_ASSERT(rpc::parse_whole_value_nothrow("5k", &value));
CPPUNIT_ASSERT_EQUAL(int64_t{5120}, value);
CPPUNIT_ASSERT(rpc::parse_whole_value_nothrow("yes", &value));
CPPUNIT_ASSERT_EQUAL(int64_t{1}, value);
CPPUNIT_ASSERT(rpc::parse_whole_value_nothrow("false", &value));
CPPUNIT_ASSERT_EQUAL(int64_t{0}, value);
CPPUNIT_ASSERT(!rpc::parse_whole_value_nothrow("junk", &value));
CPPUNIT_ASSERT(!rpc::parse_whole_value_nothrow("", &value));
}
+18
View File
@@ -0,0 +1,18 @@
#include "test/helpers/test_fixture.h"
class TestParse : public test_fixture {
CPPUNIT_TEST_SUITE(TestParse);
CPPUNIT_TEST(test_whole_value_in_range);
CPPUNIT_TEST(test_whole_value_out_of_range);
CPPUNIT_TEST(test_whole_value_bases);
CPPUNIT_TEST(test_whole_value_prefixes);
CPPUNIT_TEST_SUITE_END();
public:
void test_whole_value_in_range();
void test_whole_value_out_of_range();
void test_whole_value_bases();
void test_whole_value_prefixes();
};
+99
View File
@@ -0,0 +1,99 @@
#include "config.h"
#include "test/src/test_command_download.h"
#include <torrent/download.h>
#include <torrent/object.h>
#include <torrent/torrent.h>
#include <torrent/utils/string_manip.h>
#include "control.h"
#include "core/download.h"
#include "globals.h"
#include "rpc/parse_commands.h"
CPPUNIT_TEST_SUITE_REGISTRATION(TestCommandDownload);
void initialize_command_download();
namespace {
// Valid UTF-8 with a carriage return and a C0 control character.
const std::string control_name = "cr\r ctl\x01 \xc3\xa9.bin";
core::Download*
insert_download(core::DownloadList* list, const std::string& name) {
torrent::Object info = torrent::Object::create_map();
info.insert_key("name", name);
info.insert_key("length", int64_t{16});
info.insert_key("piece length", int64_t{262144});
info.insert_key("pieces", std::string(20, char(0)));
auto* object = new torrent::Object(torrent::Object::create_map());
object->insert_key("info", info);
auto* download = new core::Download(torrent::download_add(object, 0));
list->insert(download);
return download;
}
torrent::Object
call_d(const char* key, core::Download* download) {
return rpc::commands.call_command_d(key, download, torrent::Object());
}
}
void
TestCommandDownload::setUp() {
// torrent::download_add() opens a socket through the mocked fd functions.
mock_redirect_defaults();
torrent::initialize_main_thread();
torrent::initialize();
if (control == nullptr)
control = new Control;
if (!rpc::commands.has("d.name"))
initialize_command_download();
}
void
TestCommandDownload::tearDown() {
m_list.clear();
torrent::cleanup();
}
void
TestCommandDownload::test_name_or_base64() {
auto* plain = insert_download(&m_list, "plain.bin");
auto* download = insert_download(&m_list, control_name);
auto plain_result = call_d("d.name.or_base64", plain);
CPPUNIT_ASSERT_EQUAL(std::string("plain.bin"), plain_result.as_string());
CPPUNIT_ASSERT_EQUAL(uint32_t{0}, plain_result.flags());
auto result = call_d("d.name.or_base64", download);
CPPUNIT_ASSERT_EQUAL(torrent::utils::transform_to_base64(control_name), result.as_string());
CPPUNIT_ASSERT_EQUAL(torrent::Object::flag_base64 | torrent::Object::flag_as_binary, result.flags());
CPPUNIT_ASSERT_EQUAL(control_name, call_d("d.name", download).as_string());
CPPUNIT_ASSERT_EQUAL(uint32_t{0}, call_d("d.name", download).flags());
}
void
TestCommandDownload::test_name_or_as_binary() {
auto* plain = insert_download(&m_list, "plain.bin");
auto* download = insert_download(&m_list, control_name);
auto plain_result = call_d("d.name.or_as_binary", plain);
CPPUNIT_ASSERT_EQUAL(std::string("plain.bin"), plain_result.as_string());
CPPUNIT_ASSERT_EQUAL(uint32_t{0}, plain_result.flags());
auto result = call_d("d.name.or_as_binary", download);
CPPUNIT_ASSERT_EQUAL(control_name, result.as_string());
CPPUNIT_ASSERT_EQUAL(torrent::Object::flag_as_binary, result.flags());
}
+21
View File
@@ -0,0 +1,21 @@
#include "core/download_list.h"
#include "test/helpers/test_fixture.h"
class TestCommandDownload : public test_fixture {
CPPUNIT_TEST_SUITE(TestCommandDownload);
CPPUNIT_TEST(test_name_or_base64);
CPPUNIT_TEST(test_name_or_as_binary);
CPPUNIT_TEST_SUITE_END();
public:
void setUp();
void tearDown();
void test_name_or_base64();
void test_name_or_as_binary();
private:
core::DownloadList m_list;
};
+65
View File
@@ -0,0 +1,65 @@
#include "config.h"
#include "test/src/test_command_groups.h"
#include <torrent/torrent.h>
#include "control.h"
#include "globals.h"
#include "rpc/parse_commands.h"
CPPUNIT_TEST_SUITE_REGISTRATION(TestCommandGroups);
void initialize_command_groups();
static void
call_set(const char* value) {
torrent::Object::list_type args;
args.push_back(torrent::Object(int64_t{0}));
args.push_back(torrent::Object(std::string(value)));
rpc::commands.call_command("choke_group.up.max.set", torrent::Object::create_list_range(args.begin(), args.end()));
}
static int64_t
call_get(const char* key) {
return rpc::commands.call_command(key, torrent::Object(int64_t{0})).as_value();
}
void
TestCommandGroups::setUp() {
torrent::initialize_main_thread();
torrent::initialize();
if (control == nullptr)
control = new Control;
if (!rpc::commands.has("choke_group.up.max.set"))
initialize_command_groups();
}
void
TestCommandGroups::tearDown() {
torrent::cleanup();
}
void
TestCommandGroups::test_max_unchoked_in_range() {
call_set("50");
CPPUNIT_ASSERT_EQUAL(int64_t{50}, call_get("choke_group.up.max"));
CPPUNIT_ASSERT_EQUAL(int64_t{0}, call_get("choke_group.up.max.unlimited"));
call_set("-1");
CPPUNIT_ASSERT_EQUAL(int64_t{1}, call_get("choke_group.up.max.unlimited"));
}
void
TestCommandGroups::test_max_unchoked_out_of_range() {
call_set("50");
CPPUNIT_ASSERT_THROW(call_set("4294967296"), torrent::input_error);
CPPUNIT_ASSERT_EQUAL(int64_t{50}, call_get("choke_group.up.max"));
CPPUNIT_ASSERT_THROW(call_set("-2"), torrent::input_error);
CPPUNIT_ASSERT_EQUAL(int64_t{50}, call_get("choke_group.up.max"));
}
+17
View File
@@ -0,0 +1,17 @@
#include "test/helpers/test_fixture.h"
class TestCommandGroups : public test_fixture {
CPPUNIT_TEST_SUITE(TestCommandGroups);
CPPUNIT_TEST(test_max_unchoked_in_range);
CPPUNIT_TEST(test_max_unchoked_out_of_range);
CPPUNIT_TEST_SUITE_END();
public:
void setUp();
void tearDown();
void test_max_unchoked_in_range();
void test_max_unchoked_out_of_range();
};
+73
View File
@@ -0,0 +1,73 @@
#include "config.h"
#include "test/src/test_command_tracker.h"
#include <torrent/runtime/network_config.h>
#include <torrent/torrent.h>
#include "control.h"
#include "globals.h"
#include "command_helpers.h"
#include "rpc/parse_commands.h"
CPPUNIT_TEST_SUITE_REGISTRATION(TestCommandTracker);
void initialize_command_tracker();
static void
call_set(const char* value) {
rpc::commands.call_command("dht.override_port.set", torrent::Object(std::string(value)));
}
static uint16_t
override_port() {
return torrent::runtime::network_config()->override_dht_port();
}
void
TestCommandTracker::setUp() {
torrent::initialize_main_thread();
torrent::initialize();
if (control == nullptr)
control = new Control;
if (!rpc::commands.has("dht.override_port.set"))
initialize_command_tracker();
}
void
TestCommandTracker::tearDown() {
torrent::cleanup();
}
void
TestCommandTracker::test_dht_override_port_in_range() {
call_set("6881");
CPPUNIT_ASSERT_EQUAL(uint16_t{6881}, override_port());
call_set("65535");
CPPUNIT_ASSERT_EQUAL(uint16_t{65535}, override_port());
}
void
TestCommandTracker::test_dht_override_port_out_of_range() {
call_set("6881");
CPPUNIT_ASSERT_THROW(call_set("70000"), torrent::input_error);
CPPUNIT_ASSERT_EQUAL(uint16_t{6881}, override_port());
CPPUNIT_ASSERT_THROW(call_set("-1"), torrent::input_error);
CPPUNIT_ASSERT_EQUAL(uint16_t{6881}, override_port());
}
void
TestCommandTracker::test_checked_port_value() {
CPPUNIT_ASSERT_EQUAL(uint16_t{0}, checked_port_value(0, "test"));
CPPUNIT_ASSERT_EQUAL(uint16_t{6881}, checked_port_value(6881, "test"));
CPPUNIT_ASSERT_EQUAL(uint16_t{65535}, checked_port_value(65535, "test"));
CPPUNIT_ASSERT_THROW(checked_port_value(-1, "test"), torrent::input_error);
CPPUNIT_ASSERT_THROW(checked_port_value(65536, "test"), torrent::input_error);
CPPUNIT_ASSERT_THROW(checked_port_value(4294967296, "test"), torrent::input_error);
}
+19
View File
@@ -0,0 +1,19 @@
#include "test/helpers/test_fixture.h"
class TestCommandTracker : public test_fixture {
CPPUNIT_TEST_SUITE(TestCommandTracker);
CPPUNIT_TEST(test_dht_override_port_in_range);
CPPUNIT_TEST(test_dht_override_port_out_of_range);
CPPUNIT_TEST(test_checked_port_value);
CPPUNIT_TEST_SUITE_END();
public:
void setUp();
void tearDown();
void test_dht_override_port_in_range();
void test_dht_override_port_out_of_range();
void test_checked_port_value();
};
+68
View File
@@ -0,0 +1,68 @@
#include "config.h"
#include "test/src/test_download_list.h"
#include <cstdio>
#include <torrent/download.h>
#include <torrent/download_info.h>
#include <torrent/hash_string.h>
#include <torrent/object.h>
#include <torrent/torrent.h>
#include "control.h"
#include "core/download.h"
#include "globals.h"
CPPUNIT_TEST_SUITE_REGISTRATION(TestDownloadList);
static std::string
insert_download(core::DownloadList* list) {
torrent::Object info = torrent::Object::create_map();
info.insert_key("name", std::string("test_download_list"));
info.insert_key("length", int64_t{16});
info.insert_key("piece length", int64_t{262144});
info.insert_key("pieces", std::string(20, char(0)));
auto* object = new torrent::Object(torrent::Object::create_map());
object->insert_key("info", info);
auto download = torrent::download_add(object, 0);
list->insert(new core::Download(download));
char buffer[41];
for (unsigned int i = 0; i < torrent::HashString::size_data; i++)
snprintf(buffer + i * 2, 3, "%02x", static_cast<unsigned char>(download.info()->hash()[i]));
return std::string(buffer, 40);
}
void
TestDownloadList::setUp() {
torrent::initialize_main_thread();
torrent::initialize();
if (control == nullptr)
control = new Control;
m_hex = insert_download(&m_list);
}
void
TestDownloadList::tearDown() {
m_list.clear();
torrent::cleanup();
}
void
TestDownloadList::test_find_hex() {
CPPUNIT_ASSERT(m_list.find_hex(m_hex.c_str()) != m_list.end());
}
void
TestDownloadList::test_find_hex_wrong_length() {
CPPUNIT_ASSERT(m_list.find_hex((m_hex + "f").c_str()) == m_list.end());
CPPUNIT_ASSERT(m_list.find_hex((m_hex + m_hex).c_str()) == m_list.end());
CPPUNIT_ASSERT(m_list.find_hex(m_hex.substr(0, 39).c_str()) == m_list.end());
}
+24
View File
@@ -0,0 +1,24 @@
#include <string>
#include "core/download_list.h"
#include "test/helpers/test_fixture.h"
class TestDownloadList : public test_fixture {
CPPUNIT_TEST_SUITE(TestDownloadList);
CPPUNIT_TEST(test_find_hex);
CPPUNIT_TEST(test_find_hex_wrong_length);
CPPUNIT_TEST_SUITE_END();
public:
void setUp();
void tearDown();
void test_find_hex();
void test_find_hex_wrong_length();
private:
core::DownloadList m_list;
std::string m_hex;
};
+1 -1
View File
@@ -73,7 +73,7 @@ TestSessionCommit::commit_and_verify(bool use_fsyncdisk) {
std::stringstream rtorrent_stream("rtorrent-data"); std::stringstream rtorrent_stream("rtorrent-data");
std::stringstream libtorrent_stream("libtorrent-data"); std::stringstream libtorrent_stream("libtorrent-data");
session::DownloadStorer::save_and_move_streams(path, use_fsyncdisk, &torrent_stream, &rtorrent_stream, &libtorrent_stream); session::DownloadStorer::save_and_move_streams(path, use_fsyncdisk, 0600, &torrent_stream, &rtorrent_stream, &libtorrent_stream);
CPPUNIT_ASSERT_EQUAL(std::string("torrent-data"), read_file(path)); 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("rtorrent-data"), read_file(path + ".rtorrent"));
+17 -2
View File
@@ -39,12 +39,12 @@ read_file(const std::string& path) {
} }
void void
save_session_files(const std::string& path) { save_session_files(const std::string& path, mode_t file_mode = 0600) {
std::stringstream torrent_stream("torrent-data"); std::stringstream torrent_stream("torrent-data");
std::stringstream rtorrent_stream("rtorrent-data"); std::stringstream rtorrent_stream("rtorrent-data");
std::stringstream libtorrent_stream("libtorrent-data"); std::stringstream libtorrent_stream("libtorrent-data");
session::DownloadStorer::save_and_move_streams(path, false, &torrent_stream, &rtorrent_stream, &libtorrent_stream); session::DownloadStorer::save_and_move_streams(path, false, file_mode, &torrent_stream, &rtorrent_stream, &libtorrent_stream);
} }
unsigned int unsigned int
@@ -130,6 +130,21 @@ TestSessionStorer::test_saved_files_are_owner_only() {
CPPUNIT_ASSERT_EQUAL(0600u, permissions_of(path + ".libtorrent_resume")); CPPUNIT_ASSERT_EQUAL(0600u, permissions_of(path + ".libtorrent_resume"));
} }
void
TestSessionStorer::test_saved_files_use_configured_mode() {
for (mode_t mode : {0660, 0640}) {
auto path = m_session_dir + "/" + entry_name;
auto prev_umask = ::umask(0);
save_session_files(path, mode);
::umask(prev_umask);
CPPUNIT_ASSERT_EQUAL((unsigned int)mode, permissions_of(path));
CPPUNIT_ASSERT_EQUAL((unsigned int)mode, permissions_of(path + ".rtorrent"));
CPPUNIT_ASSERT_EQUAL((unsigned int)mode, permissions_of(path + ".libtorrent_resume"));
}
}
// Entries listed for loading must report their real type so that a symlink in // Entries listed for loading must report their real type so that a symlink in
// the session directory is skipped instead of loaded. // the session directory is skipped instead of loaded.
void void
+2
View File
@@ -7,6 +7,7 @@ class TestSessionStorer : public test_fixture {
CPPUNIT_TEST(test_temp_file_symlink_is_not_followed); CPPUNIT_TEST(test_temp_file_symlink_is_not_followed);
CPPUNIT_TEST(test_saved_files_are_owner_only); CPPUNIT_TEST(test_saved_files_are_owner_only);
CPPUNIT_TEST(test_saved_files_use_configured_mode);
CPPUNIT_TEST(test_symlinked_entry_is_not_a_file); CPPUNIT_TEST(test_symlinked_entry_is_not_a_file);
CPPUNIT_TEST_SUITE_END(); CPPUNIT_TEST_SUITE_END();
@@ -17,6 +18,7 @@ public:
void test_temp_file_symlink_is_not_followed(); void test_temp_file_symlink_is_not_followed();
void test_saved_files_are_owner_only(); void test_saved_files_are_owner_only();
void test_saved_files_use_configured_mode();
void test_symlinked_entry_is_not_a_file(); void test_symlinked_entry_is_not_a_file();
private: private:
+3 -3
View File
@@ -57,7 +57,7 @@ assert_arg_count_error(const std::string& line) {
void void
TestSetup::test_config_comment_log_add_output() { TestSetup::test_config_comment_log_add_output() {
temp_config_file file("# do:log.add_output=debug,test_output"); temp_config_file file("# do:log.add_output=debug_trace,test_output");
CPPUNIT_ASSERT_NO_THROW(parse_config_file_comments(file.path())); CPPUNIT_ASSERT_NO_THROW(parse_config_file_comments(file.path()));
} }
@@ -69,10 +69,10 @@ TestSetup::test_config_comment_log_add_output_no_args() {
void void
TestSetup::test_config_comment_log_add_output_one_arg() { TestSetup::test_config_comment_log_add_output_one_arg() {
assert_arg_count_error("# do:log.add_output=debug"); assert_arg_count_error("# do:log.add_output=debug_trace");
} }
void void
TestSetup::test_config_comment_log_add_output_too_many_args() { TestSetup::test_config_comment_log_add_output_too_many_args() {
assert_arg_count_error("# do:log.add_output=debug,test_output,extra"); assert_arg_count_error("# do:log.add_output=debug_trace,test_output,extra");
} }