Compare commits

..

2 Commits

Author SHA1 Message Date
rakshasa 83c32cea55 Cleaned up changes to session manager. 2026-10-18 13:49:17 +02:00
Ivan ffaae64d83 Flush session saves before stopped announces and report shutdown save errors
Persist resume data on the first shutdown pass; consume futures with one error policy.
2026-10-17 14:36:57 +03:00
26 changed files with 102 additions and 349 deletions
@@ -64,8 +64,6 @@ jobs:
repository: ${{ env.PR_HEAD_REPO }}
ref: ${{ env.PR_HEAD_SHA }}
persist-credentials: false
# Only read by the comment action, never built or run.
allow-unsafe-pr-checkout: true
- name: Redownload analysis results
uses: actions/github-script@v7
with:
+3 -7
View File
@@ -16,7 +16,6 @@ jobs:
sudo apt-get install -y \
bear \
clang-tidy \
libcppunit-dev \
libcurl4-openssl-dev \
zlib1g-dev
- name: Fetch libtorrent
@@ -50,19 +49,16 @@ jobs:
autoconf -i
autoheader
automake --add-missing
mkdir build
cd build
"${GITHUB_WORKSPACE}/configure"
./configure
- name: Prepare compile_commands.json
run: |
cd build
bear -- make check TESTS=
bear -- make
- name: Create results directory
run: |
mkdir clang-tidy-result
- name: Analyze
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}/build/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}/config.h"
- name: Save PR metadata
run: |
echo "${{ github.event.number }}" > clang-tidy-result/pr-id.txt
+5 -5
View File
@@ -52,11 +52,11 @@
# watch directory.
#
#directory.watch.ready = ./watch, load.start
#schedule = watch_directory,5,5,load.start=./watch/*.torrent
#schedule2 = watch_directory,5,5,load.start=./watch/*.torrent
# Close torrents when disk-space is low.
#
#schedule = low_diskspace,5,60,close_low_diskspace=100M
#schedule2 = low_diskspace,5,60,close_low_diskspace=100M
# The IP address reported to the tracker.
#
@@ -100,8 +100,8 @@
# Alternative calls to bind and IP that should handle dynamic IP's.
#
#schedule = ip_tick,0,1800,ip=rakshasa
#schedule = bind_tick,0,1800,bind=rakshasa
#schedule2 = ip_tick,0,1800,ip=rakshasa
#schedule2 = bind_tick,0,1800,bind=rakshasa
# Encryption options, set to none (default) or any combination of the following:
# 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_local = (cat,(session.path),/rpc.sock)
#schedule = socket_chmod, 0, 0, "execute.nothrow=chmod,770,(cat,(session.path),/rpc.sock)"
#schedule2 = socket_chmod, 0, 0, "execute.nothrow=chmod,770,(cat,(session.path),/rpc.sock)"
+9 -15
View File
@@ -30,6 +30,7 @@ network.listen.port.random.set = no
# (conservative settings for 'private' trackers, change for 'public')
dht.mode.set = disable
protocol.pex.set = no
trackers.use_udp.set = no
# Peer settings
throttle.max_uploads.set = 100
@@ -41,22 +42,14 @@ throttle.min_peers.seed.set = 30
throttle.max_peers.seed.set = 80
trackers.numwant.set = 80
# 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
protocol.encryption.set = allow_incoming,try_outgoing,enable_retry
# Limits for file handle resources, this is optimized for
# an `ulimit` of 1024 (a common default). You MUST leave
# a ceiling of handles reserved for rTorrent's internal needs!
# Deprecated, see: https://github.com/rakshasa/rtorrent/wiki/Socket-Manager-and-Resource-Allocation
network.http.max_open.set = 50
network.max_open_files.set = 600
network.max_open_sockets.set = 300
# Memory resource usage (increase if you have a large number of items loaded,
# and/or the available resources to spend)
@@ -72,10 +65,11 @@ execute.nothrow = sh, -c, (cat, "echo >",\
(session.path), "rtorrent.pid", " ", (system.pid))
# Other operational settings (check & adapt)
encoding.add = utf8
system.umask.set = 0027
system.cwd.set = (directory.default)
network.http.dns_cache_timeout.set = 25
schedule = monitor_diskspace, 15, 60, ((close_low_diskspace, 1000M))
schedule2 = monitor_diskspace, 15, 60, ((close_low_diskspace, 1000M))
##pieces.hash.on_completion.set = no
##view.sort_current = seeding, greater=d.ratio=
##keys.layout.set = qwerty
@@ -94,8 +88,8 @@ method.insert = d.data_path, simple,\
method.insert = d.session_file, simple, "cat=(session.path), (d.hash), .torrent"
# Watch directories (add more as you like, but use unique schedule names)
schedule = watch_start, 10, 10, ((load.start_verbose, (cat, (cfg.watch), "start/*.torrent")))
schedule = watch_load, 11, 10, ((load.verbose, (cat, (cfg.watch), "load/*.torrent")))
schedule2 = watch_start, 10, 10, ((load.start_verbose, (cat, (cfg.watch), "start/*.torrent")))
schedule2 = watch_load, 11, 10, ((load.verbose, (cat, (cfg.watch), "load/*.torrent")))
# Run the rTorrent process as a daemon in the background
# (and control via XMLRPC sockets)
+10 -5
View File
@@ -60,6 +60,7 @@ rc.network.listen.port.random = false
-- (conservative settings for 'private' trackers, change for 'public')
rc.dht.mode = 'disable'
rc.protocol.pex = false
rc.trackers.use_udp = false
-- Peer settings
rc.throttle.max_uploads = 100
@@ -71,11 +72,14 @@ rc.throttle.min_peers.seed = 30
rc.throttle.max_peers.seed = 80
rc.trackers.numwant = 80
rc.protocol.encryption.set('allow_incoming', 'try_outgoing', 'enable_retry')
-- Limits for file handle resources, this is optimized for
-- an `ulimit` of 1024 (a common default). You MUST leave
-- a ceiling of handles reserved for rTorrent's internal needs!
-- Deprecated, see: https://github.com/rakshasa/rtorrent/wiki/Socket-Manager-and-Resource-Allocation
rc.network.http.max_open = 50
rc.network.max_open_files = 600
rc.network.max_open_sockets = 300
-- Memory resource usage (increase if you have a large number of items loaded,
-- and/or the available resources to spend)
@@ -93,10 +97,11 @@ rc.execute.nothrow(
}))
-- Other operational settings (check & adapt)
rc.encoding.add('utf8')
rc.system.umask = 0027
rc.system.cwd = rc.directory.default()
rc.network.http.dns_cache_timeout = 25
rc.schedule('monitor_diskspace', '15', '60', 'close_low_diskspace=1000M')
rc.schedule2('monitor_diskspace', '15', '60', 'close_low_diskspace=1000M')
--rc.pieces.hash.on_completion = false
--rc.view.sort_current('seeding', 'greater=d.ratio=')
--rc.keys.layout = 'qwerty'
@@ -116,8 +121,8 @@ rc.method.insert('d.data_path', 'simple',
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)
rc.schedule('watch_start', '10', '10', 'load.start_verbose=(cat, (cfg.watch), "start/*.torrent")')
rc.schedule('watch_load', '11', '10', 'load.verbose=(cat, (cfg.watch), "load/*.torrent")')
rc.schedule2('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")')
-- Run the rTorrent process as a daemon in the background
-- (and control via XMLRPC sockets)
+6 -6
View File
@@ -668,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_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.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_text_or_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_as_binary", [](auto* download, auto) { return retrieve_d_base_path(download).object_utf8_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_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(); });
@@ -677,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_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.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_text_or_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_as_binary", [](auto* download, auto) { return retrieve_d_base_filename(download).object_utf8_or_as_binary(); });
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.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.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_text_or_base64(); });
CMD2_DL("d.name.or_as_binary", [](auto* download, auto) { return download->info()->name().object_utf8_text_or_as_binary(); });
CMD2_DL("d.name.or_base64", [](auto* download, auto) { return download->info()->name().object_utf8_or_base64(); });
CMD2_DL("d.name.or_as_binary", [](auto* download, auto) { return download->info()->name().object_utf8_or_as_binary(); });
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(); });
+9 -9
View File
@@ -40,13 +40,13 @@ apply_f_path_components(torrent::File* file, int output_type) {
for (const auto& itr : *file->path()) {
switch (output_type) {
case 0: result.push_back(itr.str()); break;
case 1: result.push_back(itr.object_hex()); break;
case 2: result.push_back(itr.object_base64()); break;
case 3: result.push_back(itr.object_base64_as_binary()); break;
case 4: result.push_back(itr.object_as_binary()); break;
case 5: result.push_back(itr.object_utf8_text_or_base64()); break;
case 6: result.push_back(itr.object_utf8_text_or_as_binary()); break;
case 0: result.push_back(itr.str()); break;
case 1: result.push_back(itr.object_hex()); break;
case 2: result.push_back(itr.object_base64()); break;
case 3: result.push_back(itr.object_base64_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 6: result.push_back(itr.object_utf8_or_as_binary()); break;
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.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.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_text_or_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_as_binary", [](auto* file, auto) { return file->frozen_path().object_utf8_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_throw", [](auto* file, auto) { return resolve_path_or_throw(file->frozen_path().str()); });
-3
View File
@@ -123,8 +123,6 @@ initialize_command_local() {
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_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_ANY_V ("session.save", [dList](auto, auto) { return dList->session_save(); });
@@ -173,7 +171,6 @@ initialize_command_local() {
rpc::rpc.mark_safe("directory.default.realpath.or_empty");
rpc::rpc.mark_safe("directory.default.realpath.or_throw");
rpc::rpc.mark_safe("session.use_lock");
rpc::rpc.mark_safe("session.umask");
rpc::rpc.mark_safe("session.on_completion");
rpc::rpc.mark_safe("pieces.sync.always_safe");
+9 -9
View File
@@ -138,6 +138,15 @@ Control::handle_shutdown() {
if (scgi_thread::thread()->is_active())
scgi_thread::thread()->stop_thread_wait();
if (!m_shutdown_session_saved) {
// Flush the latest state before stopped announces can consume the timeout. 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_shutdown_quick) {
torrent::runtime::network_manager()->listen_close();
torrent::runtime::shutdown();
@@ -153,15 +162,6 @@ Control::handle_shutdown() {
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())
torrent::this_thread::scheduler()->wait_for_ceil_seconds(&m_task_shutdown_clear_requests, 10s);
+2 -2
View File
@@ -50,7 +50,6 @@ public:
bool is_shutdown_completed();
bool is_shutdown_received() { return m_shutdown_received; }
bool is_shutdown_started() { return m_shutdown_quick; }
bool is_shutdown_session_saved() { return m_shutdown_session_saved; }
void initialize();
void cleanup();
@@ -113,9 +112,10 @@ private:
int m_clear_requests_count{};
bool m_shutdown_session_saved{};
align_cacheline
bool m_shutdown_session_saved{};
std::atomic<bool> m_shutdown_received{};
std::atomic<bool> m_shutdown_quick{};
};
+6 -15
View File
@@ -278,21 +278,12 @@ DownloadFactory::receive_success() {
// Check first if we already have these values set in the session
// torrent, so that it is safe to change the values.
try {
if (rpc::call_command_value("system.file.split_size") >= 0)
torrent::file_split_all(download->download()->file_list(),
rpc::call_command_value("system.file.split_size"),
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);
}
//
// Need to also catch the exceptions.
if (rpc::call_command_value("system.file.split_size") >= 0)
torrent::file_split_all(download->download()->file_list(),
rpc::call_command_value("system.file.split_size"),
rpc::call_command_string("system.file.split_suffix"));
if (rtorrent->has_key_string("directory")) {
rpc::call_command("d.directory_base.set", rtorrent->get_key("directory"), rpc::make_target(download));
+3 -20
View File
@@ -586,23 +586,6 @@ DownloadList::hash_done(Download* download) {
if (download->is_done() && download->download()->info()->is_meta_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) {
case Download::variable_hashing_initial:
case Download::variable_hashing_rehash:
@@ -612,7 +595,7 @@ DownloadList::hash_done(Download* download) {
// f.ex deleted, then we clear the state and complete.
if (rpc::call_command_value("d.complete", rpc::make_target(download)) && !download->is_done()) {
set_state_stopped(download);
download->set_message(completed_unfinished_message);
download->set_message("Download registered as completed, but hash check returned unfinished chunks.");
}
// Save resume data so we update time-stamps and priorities if
@@ -636,7 +619,7 @@ DownloadList::hash_done(Download* download) {
if (download->is_done()) {
confirm_finished(download);
} else {
download->set_message(final_bad_chunks_message);
download->set_message("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");
@@ -648,7 +631,7 @@ DownloadList::hash_done(Download* download) {
case Download::variable_hashing_stopped:
default:
// Either an error or someone wrote to the hashing variable...
download->set_message(invalid_hashing_message);
download->set_message("Hash check completed but the \"hashing\" variable is in an invalid state.");
return;
}
+1 -2
View File
@@ -436,9 +436,8 @@ Manager::receive_hashing_changed() {
if (tryQuick) {
// 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));
}
if (!tryQuick || !foundHashing) {
} else {
(*itr)->set_hash_failed(true);
(*itr)->set_message("Hashing failed: " + std::string(e.what()));
+1 -5
View File
@@ -489,11 +489,7 @@ main(int argc, char** argv) {
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();
} catch (torrent::internal_error& e) {
+1 -31
View File
@@ -10,8 +10,6 @@
#include <torrent/net/fd.h>
#include <torrent/net/socket_address.h>
#include <torrent/system/poll.h>
#include <torrent/system/scheduler.h>
#include <torrent/utils/log.h>
#include <torrent/runtime/socket_manager.h>
#include "control.h"
@@ -27,8 +25,6 @@ SCgi::SCgi() {
std::generate(m_tasks.begin(), m_tasks.end(), []() { return std::make_unique<SCgiTask>(); });
m_current = m_tasks.begin();
m_resume_accept.slot() = [this] { resume_accept(); };
}
SCgi::~SCgi() {
@@ -125,9 +121,6 @@ SCgi::stop() {
if (!is_open())
return;
if (m_resume_accept.is_scheduled())
torrent::this_thread::scheduler()->erase(&m_resume_accept);
for (auto& itr : m_tasks) {
if (itr->is_open())
itr->close();
@@ -165,16 +158,9 @@ SCgi::event_read() {
return;
// Force a new event_read() call just to be sure we don't enter an infinite loop.
if (errno == ECONNABORTED || errno == EPROTO || errno == EPERM)
if (errno == ECONNABORTED)
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)));
}
@@ -203,22 +189,6 @@ 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
SCgi::event_write() {
throw torrent::internal_error("Listener does not support write().");
-5
View File
@@ -4,7 +4,6 @@
#include <array>
#include <memory>
#include <torrent/system/event.h>
#include <torrent/system/scheduler.h>
#include "rpc/scgi_task.h"
@@ -40,16 +39,12 @@ private:
using task_list = std::array<std::unique_ptr<SCgiTask>, max_tasks>;
void open(sockaddr* sa, unsigned int length);
void pause_accept(int error);
void resume_accept();
std::string m_path;
int m_logFd{-1};
task_list m_tasks;
task_list::iterator m_current;
torrent::system::SchedulerEntry m_resume_accept;
};
}
+5 -12
View File
@@ -5,7 +5,6 @@
#include <cerrno>
#include <fcntl.h>
#include <fstream>
#include <sys/stat.h>
#include <unistd.h>
#include <torrent/exceptions.h>
#include <torrent/object.h>
@@ -124,7 +123,7 @@ is_correct_format(const std::string& f) {
}
void
save_stream(const std::string& path, bool use_fsyncdisk, mode_t file_mode, const std::stringstream& stream) {
save_stream(const std::string& path, bool use_fsyncdisk, const std::stringstream& stream) {
// Remove any leftover temporary file first so that O_EXCL only ever fails on
// an entry that appeared after the unlink, and O_NOFOLLOW keeps a symlink
// planted in the session directory from redirecting the write.
@@ -137,12 +136,6 @@ save_stream(const std::string& path, bool use_fsyncdisk, mode_t file_mode, const
if (fd < 0)
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();
std::size_t remaining = data.size();
const char* cursor = data.data();
@@ -203,7 +196,7 @@ sync_directory_of(const std::string& path) {
} // namespace anonymous
void
DownloadStorer::save_and_move_streams(const std::string& path, bool use_fsyncdisk, mode_t file_mode,
DownloadStorer::save_and_move_streams(const std::string& path, bool use_fsyncdisk,
const std::stringstream* torrent_stream,
const std::stringstream* rtorrent_stream,
const std::stringstream* libtorrent_stream) {
@@ -212,10 +205,10 @@ DownloadStorer::save_and_move_streams(const std::string& path, bool use_fsyncdis
auto rtorrent_path = path + ".rtorrent";
if (torrent_stream)
save_stream(torrent_path + ".new", use_fsyncdisk, file_mode, *torrent_stream);
save_stream(torrent_path + ".new", use_fsyncdisk, *torrent_stream);
save_stream(libtorrent_path + ".new", use_fsyncdisk, file_mode, *libtorrent_stream);
save_stream(rtorrent_path + ".new", use_fsyncdisk, file_mode, *rtorrent_stream);
save_stream(libtorrent_path + ".new", use_fsyncdisk, *libtorrent_stream);
save_stream(rtorrent_path + ".new", use_fsyncdisk, *rtorrent_stream);
if (torrent_stream) {
if (::rename((torrent_path + ".new").c_str(), torrent_path.c_str()) == -1)
+1 -2
View File
@@ -3,7 +3,6 @@
#include <memory>
#include <sstream>
#include <sys/types.h>
#include <string>
#include <torrent/common.h>
@@ -34,7 +33,7 @@ public:
auto rtorrent_stream() { return std::move(m_rtorrent_stream); }
auto libtorrent_stream() { return std::move(m_libtorrent_stream); }
static void save_and_move_streams(const std::string& path, bool use_fsyncdisk, mode_t file_mode,
static void save_and_move_streams(const std::string& path, bool use_fsyncdisk,
const std::stringstream* torrent_stream,
const std::stringstream* rtorrent_stream,
const std::stringstream* libtorrent_stream);
+25 -42
View File
@@ -65,30 +65,6 @@ SessionManager::set_use_lock(bool 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
SessionManager::save_resume_download(core::Download* download) {
assert(torrent::this_thread::thread() == torrent::main_thread::thread());
@@ -403,7 +379,7 @@ SessionManager::process_next_save_request_unsafe() {
};
try {
DownloadStorer::save_and_move_streams(itr->second.path, m_use_fsyncdisk, file_mode(),
DownloadStorer::save_and_move_streams(itr->second.path, m_use_fsyncdisk,
itr->second.torrent_stream.get(),
itr->second.rtorrent_stream.get(),
itr->second.libtorrent_stream.get());
@@ -437,33 +413,34 @@ SessionManager::process_finished_saves() {
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) {
// Returns true when a storage error was suppressed.
bool
SessionManager::report_storage_error_unsafe(const std::string& msg) {
if (m_last_storage_error_message + std::chrono::minutes(5) > torrent::this_thread::cached_time()) {
m_ignored_storage_error_count++;
return;
return true;
}
lt_log_print(torrent::LOG_ERRORS, "Storage errors saving session data for download: ignored:%u : %s", m_ignored_storage_error_count, what.c_str());
lt_log_print(torrent::LOG_ERRORS, "Storage errors saving session data for download: ignored:%u : %s", m_ignored_storage_error_count, msg.c_str());
m_last_storage_error_message = torrent::this_thread::cached_time();
m_last_storage_error_message = torrent::this_thread::cached_time();
m_ignored_storage_error_count = 0;
return false;
}
// 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
// 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.
//
// Returns true when a storage error was suppressed.
bool
SessionManager::consume_finished_save_unsafe(ProcessingSave& request) {
try {
request.first.get();
} 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());
report_storage_error_unsafe(e.what());
return;
return report_storage_error_unsafe(e.what());
} 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());
@@ -475,13 +452,17 @@ SessionManager::consume_finished_save_unsafe(ProcessingSave& request) {
}
LT_LOG("finished saving download : download:%p path:%s", request.second.download, request.second.path.c_str());
return false;
}
void
SessionManager::flush_shutdown_saves() {
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);
}
@@ -503,15 +484,17 @@ SessionManager::flush_all_and_wait_unsafe(std::unique_lock<std::mutex>& lock) {
while (!m_processing_saves.empty())
m_finished_condition.wait(lock);
unsigned int suppressed = 0;
for (auto& request : m_finished_saves)
consume_finished_save_unsafe(request);
suppressed += consume_finished_save_unsafe(request) ? 1 : 0;
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);
lt_log_print(torrent::LOG_ERRORS, "Storage errors flushing session data: ignored:%u previous:%u",
suppressed, m_ignored_storage_error_count - suppressed);
m_ignored_storage_error_count = 0;
}
+3 -10
View File
@@ -6,8 +6,6 @@
#include <deque>
#include <future>
#include <list>
#include <cstdint>
#include <sys/types.h>
#include <memory>
#include <mutex>
#include <sstream>
@@ -61,10 +59,6 @@ public:
bool use_lock() const;
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_resume_download(core::Download* download);
void remove_download(core::Download* download);
@@ -82,6 +76,7 @@ protected:
void flush_shutdown_saves();
private:
void callback_pending_builds();
void callback_save_request();
void callback_finished_saves();
@@ -99,8 +94,8 @@ private:
using ProcessingSave = std::pair<std::future<void>, SaveRequest>;
void consume_finished_save_unsafe(ProcessingSave& request);
void report_storage_error_unsafe(const std::string& what);
bool consume_finished_save_unsafe(ProcessingSave& request);
bool report_storage_error_unsafe(const std::string& msg);
torrent::system::Thread* m_thread;
torrent::system::callback_id m_callback_id;
@@ -109,7 +104,6 @@ private:
std::string m_path;
bool m_use_fsyncdisk{true};
bool m_use_lock{true};
int m_umask{};
align_cacheline
@@ -144,7 +138,6 @@ inline bool SessionManager::is_used() const { return !m_path.e
inline std::string SessionManager::path() const { return m_path; }
inline bool SessionManager::use_fsyncdisk() const { return m_use_fsyncdisk; }
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); }
} // namespace session
-2
View File
@@ -59,8 +59,6 @@ rtorrent_Test_Rpc_SOURCES = $(rtorrent_Test_Common) \
rpc/test_rpc_manager.h
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.h \
src/test_command_groups.cc \
-99
View File
@@ -1,99 +0,0 @@
#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
@@ -1,21 +0,0 @@
#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;
};
+1 -1
View File
@@ -73,7 +73,7 @@ TestSessionCommit::commit_and_verify(bool use_fsyncdisk) {
std::stringstream rtorrent_stream("rtorrent-data");
std::stringstream libtorrent_stream("libtorrent-data");
session::DownloadStorer::save_and_move_streams(path, use_fsyncdisk, 0600, &torrent_stream, &rtorrent_stream, &libtorrent_stream);
session::DownloadStorer::save_and_move_streams(path, use_fsyncdisk, &torrent_stream, &rtorrent_stream, &libtorrent_stream);
CPPUNIT_ASSERT_EQUAL(std::string("torrent-data"), read_file(path));
CPPUNIT_ASSERT_EQUAL(std::string("rtorrent-data"), read_file(path + ".rtorrent"));
+2 -17
View File
@@ -39,12 +39,12 @@ read_file(const std::string& path) {
}
void
save_session_files(const std::string& path, mode_t file_mode = 0600) {
save_session_files(const std::string& path) {
std::stringstream torrent_stream("torrent-data");
std::stringstream rtorrent_stream("rtorrent-data");
std::stringstream libtorrent_stream("libtorrent-data");
session::DownloadStorer::save_and_move_streams(path, false, file_mode, &torrent_stream, &rtorrent_stream, &libtorrent_stream);
session::DownloadStorer::save_and_move_streams(path, false, &torrent_stream, &rtorrent_stream, &libtorrent_stream);
}
unsigned int
@@ -130,21 +130,6 @@ TestSessionStorer::test_saved_files_are_owner_only() {
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
// the session directory is skipped instead of loaded.
void
-2
View File
@@ -7,7 +7,6 @@ class TestSessionStorer : public test_fixture {
CPPUNIT_TEST(test_temp_file_symlink_is_not_followed);
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_SUITE_END();
@@ -18,7 +17,6 @@ public:
void test_temp_file_symlink_is_not_followed();
void test_saved_files_are_owner_only();
void test_saved_files_use_configured_mode();
void test_symlinked_entry_is_not_a_file();
private: