Compare commits

...

3 Commits

Author SHA1 Message Date
copilot-swe-agent[bot] d47ce9d129 Apply remaining changes
Co-authored-by: rakshasa <475125+rakshasa@users.noreply.github.com>
2026-10-19 22:20:32 +00:00
copilot-swe-agent[bot] 7a38112149 Initial plan 2026-10-19 22:19:20 +00:00
LastElaphine 8cab343a82 Update example configuration files to align with wiki template 2026-10-18 13:22:33 +02:00
11 changed files with 95 additions and 34 deletions
+12 -5
View File
@@ -45,6 +45,13 @@
#
#session.path.set = ./session
# Umask for files written to the session directory, default 0077 (files
# are mode 0600). Use 0007 for 0660 or 0027 for 0640. The owner must keep
# read/write access. Session files contain tracker announce URLs
# (potentially passkeys), so loosening permissions is a user choice.
#
#session.umask.set = 0077
# Watch a directory for new torrents, and stop those that have been
# deleted. Use directory.watch.ready for network shares or other watch
# directories where files may be copied or written directly into place.
@@ -52,11 +59,11 @@
# watch directory.
#
#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.
#
#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.
#
@@ -100,8 +107,8 @@
# Alternative calls to bind and IP that should handle dynamic IP's.
#
#schedule2 = ip_tick,0,1800,ip=rakshasa
#schedule2 = bind_tick,0,1800,bind=rakshasa
#schedule = ip_tick,0,1800,ip=rakshasa
#schedule = 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 +152,4 @@
#
#network.scgi.open_port = "127.0.0.1:5000"
#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)"
+15 -9
View File
@@ -30,7 +30,6 @@ 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
@@ -42,14 +41,22 @@ throttle.min_peers.seed.set = 30
throttle.max_peers.seed.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
# an `ulimit` of 1024 (a common default). You MUST leave
# a ceiling of handles reserved for rTorrent's internal needs!
network.http.max_open.set = 50
network.max_open_files.set = 600
network.max_open_sockets.set = 300
# Deprecated, see: https://github.com/rakshasa/rtorrent/wiki/Socket-Manager-and-Resource-Allocation
# Memory resource usage (increase if you have a large number of items loaded,
# and/or the available resources to spend)
@@ -65,11 +72,10 @@ 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
schedule2 = monitor_diskspace, 15, 60, ((close_low_diskspace, 1000M))
schedule = 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
@@ -88,8 +94,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)
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")))
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")))
# Run the rTorrent process as a daemon in the background
# (and control via XMLRPC sockets)
+5 -10
View File
@@ -60,7 +60,6 @@ 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
@@ -72,14 +71,11 @@ 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!
rc.network.http.max_open = 50
rc.network.max_open_files = 600
rc.network.max_open_sockets = 300
-- Deprecated, see: https://github.com/rakshasa/rtorrent/wiki/Socket-Manager-and-Resource-Allocation
-- Memory resource usage (increase if you have a large number of items loaded,
-- and/or the available resources to spend)
@@ -97,11 +93,10 @@ 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.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.view.sort_current('seeding', 'greater=d.ratio=')
--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')
-- 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.schedule2('watch_load', '11', '10', 'load.verbose=(cat, (cfg.watch), "load/*.torrent")')
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")')
-- Run the rTorrent process as a daemon in the background
-- (and control via XMLRPC sockets)
+3
View File
@@ -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 ("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(); });
@@ -171,6 +173,7 @@ 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");
+12 -5
View File
@@ -5,6 +5,7 @@
#include <cerrno>
#include <fcntl.h>
#include <fstream>
#include <sys/stat.h>
#include <unistd.h>
#include <torrent/exceptions.h>
#include <torrent/object.h>
@@ -123,7 +124,7 @@ is_correct_format(const std::string& f) {
}
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
// an entry that appeared after the unlink, and O_NOFOLLOW keeps a symlink
// 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)
throw torrent::storage_error("failed to open file for writing : " + path);
// open() applies the process umask, so set the configured mode explicitly.
if (::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();
@@ -196,7 +203,7 @@ sync_directory_of(const std::string& path) {
} // namespace anonymous
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* rtorrent_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";
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(rtorrent_path + ".new", use_fsyncdisk, *rtorrent_stream);
save_stream(libtorrent_path + ".new", use_fsyncdisk, file_mode, *libtorrent_stream);
save_stream(rtorrent_path + ".new", use_fsyncdisk, file_mode, *rtorrent_stream);
if (torrent_stream) {
if (::rename((torrent_path + ".new").c_str(), torrent_path.c_str()) == -1)
+2 -1
View File
@@ -3,6 +3,7 @@
#include <memory>
#include <sstream>
#include <sys/types.h>
#include <string>
#include <torrent/common.h>
@@ -33,7 +34,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,
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* rtorrent_stream,
const std::stringstream* libtorrent_stream);
+17 -1
View File
@@ -65,6 +65,22 @@ SessionManager::set_use_lock(bool use_lock) {
m_use_lock = use_lock;
}
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 & 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());
@@ -379,7 +395,7 @@ SessionManager::process_next_save_request_unsafe() {
};
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.rtorrent_stream.get(),
itr->second.libtorrent_stream.get());
+9
View File
@@ -6,6 +6,8 @@
#include <deque>
#include <future>
#include <list>
#include <cstdint>
#include <sys/types.h>
#include <memory>
#include <mutex>
#include <sstream>
@@ -59,6 +61,10 @@ 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);
@@ -99,6 +105,7 @@ private:
std::string m_path;
bool m_use_fsyncdisk{true};
bool m_use_lock{true};
int m_umask{0077};
align_cacheline
@@ -133,6 +140,8 @@ 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 mode_t SessionManager::file_mode() const { return 0666 & ~m_umask; }
inline void SessionManager::flush_all_pending_builds() { process_pending_builds(true); }
} // namespace session
+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, &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("rtorrent-data"), read_file(path + ".rtorrent"));
+17 -2
View File
@@ -39,12 +39,12 @@ read_file(const std::string& path) {
}
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 rtorrent_stream("rtorrent-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
@@ -130,6 +130,21 @@ 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,6 +7,7 @@ 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();
@@ -17,6 +18,7 @@ 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: