Compare commits

...

67 Commits

Author SHA1 Message Date
rakshasa 88782d005d Stuff. 2026-09-01 17:46:15 +02:00
rakshasa 5fcd779b15 Stuff. 2026-09-01 17:11:44 +02:00
rakshasa 7104512ac1 Stuff. 2026-09-01 17:10:29 +02:00
rakshasa bd82f6152e Stuff. 2026-09-01 16:40:37 +02:00
rakshasa 83eb4e016a Stuff. 2026-09-01 16:16:30 +02:00
rakshasa 6b60c219fa Stuff. 2026-08-31 09:52:34 +02:00
Jari Sundell 2804be2c9c Optionally sanitize download and file names by replacing slash with underscore. 2026-08-29 18:31:16 +09:00
noctuum 21019fde41 Add the download to the stopped view when a hash check contradicts d.complete.
Setting d.state alone left it in the started view, where d.start cannot restore it.
2026-08-28 09:31:55 +02:00
noctuum e4e858cc40 Stop the download in close_directly() rather than leaving the state behind.
It closed the files while d.state and the started view still called it running.
2026-08-28 09:31:55 +02:00
noctuum 60eb8f0383 Split the file closing in DownloadList::close_directly() into close_files().
Callers that only need the files released should not depend on how a stop behaves.
2026-08-28 09:31:55 +02:00
Jari Sundell 072cbae0c6 Expose system.files.close_idle for FileManager idle FD timeout by @j-c-m 2026-08-28 03:18:25 +09:00
xirvik 16bfaf2694 Reject an XMLRPC size limit above the SCGI content size limit. 2026-08-27 10:48:26 +02:00
xirvik c8da93bd30 Warn that pieces.sync.timeout_safe.set is no longer supported. 2026-08-26 11:21:14 +02:00
xirvik 2cd3ca052d Pass the gz flag to log.append_gz_file. 2026-08-26 10:54:22 +02:00
xirvik 8be1fcb3a4 Return m_use_fsyncdisk from SessionManager::use_fsyncdisk.
system.files.session.fdatasync always reported true even when disabled.
2026-08-26 10:32:12 +02:00
rakshasa 109a20c09c Tagged release 0.16.21. 2026-08-26 08:51:40 +02:00
xirvik ac1b2685d5 Guard the value suffixes against negative overflow.
The k, m and g guards only checked the positive side, so -4611686018427387904K became 0.
2026-08-24 08:57:58 +02:00
xirvik d7b2cfe4f2 Clamp the elapsed time conversion instead of overflowing.
convert.elapsed_time subtracted an unchecked value from the cached seconds.
2026-08-24 08:24:20 +02:00
xirvik dcfbf82a38 Mark view.list and the system.time getters as untrusted safe.
Read-only getters a client needs to list views and make sense of timestamps.
2026-08-24 07:37:23 +02:00
xirvik 3ae91b6536 Check flag_public_rpc when dispatching jsonrpc calls.
Private commands and non-exported redirects were reachable over jsonrpc.
2026-08-19 19:11:53 +02:00
xirvik af6d8a10ad Add a timeout to scgi tasks.
A hundred connections sending a partial header held the whole task pool.
2026-08-18 12:25:58 +02:00
xirvik 9066afc063 Guard the unit multiplication in value commands against overflow.
The kb variants multiply the argument by 1024 without checking the range.
2026-08-18 11:58:47 +02:00
xirvik a2fabb10de Range check the socket allocation and size values.
int64 arguments were truncated into uint32 setters, so -1 became 4294967295.
2026-08-18 11:38:01 +02:00
xirvik 13e073d9ff Reject division overflow in math.div and math.mod.
Dividing the smallest integer by -1 traps and takes the process with it.
2026-08-18 11:12:33 +02:00
xirvik d73f245692 Add a depth limit to the command parser.
A million nested braces in one argument exhausted the main thread's stack.
2026-08-18 10:52:38 +02:00
xirvik 7db356cae4 Add a depth limit to json_to_object.
Deeply nested params recursed until the main thread's stack was exhausted.
2026-08-18 10:34:07 +02:00
Auska 8378c0a83a Fix review: mark ipv4/ipv6 local_port safe, dedup port validation
- mark network.local_port.ipv4/ipv6 safe for untrusted RPC connections (mark_safe matches exact command keys).
- Replace duplicated multi-line port validation with a shared checked_local_port_value helper and single-line lambdas.
2026-08-17 11:29:50 +02:00
Auska 4c6f7afdc7 Add network.local_port commands for tracker port reporting
Mirror the network.local_address.* command family for ports:
- network.local_port            report the best-match local port
- network.local_port.set        set both the ipv4 and ipv6 local ports
- network.local_port.ipv4(.set) get/set the ipv4 local port only
- network.local_port.ipv6(.set) get/set the ipv6 local port only

Values are restricted to 0-65535; 0 (default) means unset, and trackers
then report the listening port.

Why: behind multiple layers of NAT the port peers must connect to can
differ from the port rtorrent is listening on. These commands let the
user manually advertise the forwarded public port per address family,
e.g.:
    network.local_port.ipv4.set = 6881
    network.local_port.ipv6.set = 6882

network.local_port is marked safe for RPC use alongside
network.local_address.
2026-08-17 11:29:50 +02:00
xirvik 385e149ccc Let the download list own its downloads through a shared pointer.
A multicall could then dispatch commands on an erased download.
2026-08-17 10:40:41 +02:00
rakshasa 58e627f2be Fixed '-i <ip>' command line argument. 2026-08-16 14:11:01 +02:00
xirvik 9ff0cd28d5 Add schedule.if_absent for clients that re-register periodic tasks.
schedule restarts a re-registered task's countdown, if_absent keeps the existing entry.
2026-08-16 13:36:19 +02:00
Jacob Reynolds 5e7dd98c67 Update rtorrent.rc.lua-example to use new network.listen.port.x commands
See #1935
2026-08-14 16:35:20 +02:00
xirvik b24db0eaa5 Do not dereference missing text nodes in the tinyxml2 backend.
Empty elements and element children crashed the parser on a null.
2026-08-14 13:16:17 +02:00
Jari Sundell 6fa139f7bf Reduce max_string_pad_size from 1<<20 to 1<<14 2026-08-14 12:46:37 +02:00
xirvik 68904ba005 Limit the padding string.rpad and string.lpad will build.
An untrusted caller could exhaust memory or the response limit.
2026-08-14 12:46:37 +02:00
xirvik b401068246 Let the choke group list own its groups.
Each choke_group.insert leaked a group, and size reported a stale copy.
2026-08-14 12:15:36 +02:00
xirvik 946a4b5c83 Check the focus iterator before dereferencing it on erase.
Erasing a download read past the end of the view.
2026-08-13 19:30:21 +02:00
xirvik a075001a73 Catch every json exception when processing a JSONRPC request.
A number overflow escaped the two handlers and killed the process.
2026-08-12 18:22:17 +02:00
xirvik 781520fef8 Close the connection when an SCGI header does not fit the buffer.
A malformed header aborted the process from the SCGI thread.
2026-08-12 15:48:39 +02:00
kunalkakade 224f06fdc2 Link against libatomic when 64-bit atomics require it. 2026-08-09 12:06:08 +02:00
xirvik 324811d6f0 Do not fail startup on unreadable session files.
The download is flagged as hash failed with a message instead of throwing.
2026-08-09 10:39:55 +02:00
Jari Sundell e51fad5bd2 Clean up comments in root.h
Removed comments regarding input history size limitations.
2026-08-09 10:19:35 +02:00
xirvik 44bb2d691f ui: bound the input history size.
Only the lower end was checked, so a large value threw bad_alloc and exited.
2026-08-09 10:19:35 +02:00
xirvik 161a36dedd Add system.sockets.<category>.min_alloc.limit.
A category can have a lower bound too, and a client needs to read it.
2026-08-08 19:39:59 +02:00
xirvik a69e44a096 Add system.sockets.<category>.max_alloc.limit.
A category can have a ceiling below the shared budget, and a client needs to read it.
2026-08-08 11:28:26 +02:00
Greg ORIOL 4dd8b1fd7e Update rtorrent.rc-example with newer network.listen 2026-08-08 10:44:41 +02:00
Greg ORIOL b5f6bb5652 Update rtorrent.rc with newer network.listen 2026-08-08 10:44:41 +02:00
kunalkakade 3ae6bbc394 Fix garbage throttle values in the status bar on 32-bit archs.
Closes #1683.
2026-08-07 15:45:47 +02:00
kunalkakade fdfca98f1c Add documentation for resolving paths. 2026-08-07 11:58:14 +02:00
kunalkakade 31196ff5f0 Add realpath variants of the commands that return paths.
Closes #1670.
2026-08-07 11:58:14 +02:00
kunalkakade cfb9e083d2 Add documentation for the string.* commands. 2026-08-07 11:35:35 +02:00
kunalkakade 5b4b607c98 Add string.* commands for text manipulation.
Closes #1366.
2026-08-07 11:35:35 +02:00
xirvik 87db1cf0f0 rpc: do not dereference a null result from the xmlrpc-c registry.
A failed call returns null, which was passed straight to xmlrpc_mem_block_contents.
2026-08-07 10:56:19 +02:00
rakshasa dd3ddf7c39 Tagged release 0.16.20. 2026-08-06 10:12:49 +02:00
rakshasa e56bffff4d Fixed socket category unit test. 2026-08-05 11:18:31 +02:00
xirvik d20c3234da Add system.sockets allocation budget commands.
They report the budget the socket manager enforces, so a client can show the limit.
2026-08-05 11:18:31 +02:00
Maks Verver dda5e4fa04 Fix deprecated command redirects (rtorrent -D) 2026-08-05 10:14:35 +02:00
Jari Sundell 9ab73352e1 Minor fixes to WaitpidQueue. 2026-08-05 00:59:33 +09:00
Jari Sundell f90a96d57a Added WaitpidQueue to handle background process reaping. 2026-08-04 19:03:26 +09:00
rakshasa 247ae7621a Tagged release 0.16.19. 2026-08-02 10:44:30 +02:00
xirvik 05831942a7 commands: don't register min_alloc/max_alloc for the generic socket category
SocketManager throws internal_error for category_generic, aborting rtorrent over RPC.
2026-07-30 12:42:24 +02:00
rakshasa ee18a1f3fc Added commit message linter. 2026-07-28 10:04:51 +02:00
Jari Sundell f52f20f1b6 Cleaned up system headers. 2026-07-26 19:53:18 +09:00
xirvik 41eef969ea commands: fix group.<name>.ratio.disable calling a removed command
group_insert() builds the disable method as "schedule_remove=...", but that
command no longer exists: the current name is schedule.remove, and the only
old-style aliases registered are schedule2 and schedule_remove2 (and those only
when deprecated commands are enabled at registration time). Every
group.<name>.ratio.disable therefore faults with

  Command "schedule_remove" does not exist.

including the built-in group.seeding.ratio.disable, so a ratio group can be
enabled but never disabled. The enable half already uses the current name.
2026-07-25 10:18:49 +02:00
fffe 1def5e8b36 Add log.print and enum.log_group methods 2026-07-22 11:08:32 +02:00
simonc56 8355870586 Mark 'd.timestamp.started' as safe in command download initialization 2026-07-20 08:21:32 +02:00
Jari Sundell ba2bc7e64d Various release bug fixes. 2026-07-19 18:53:57 +09:00
82 changed files with 2648 additions and 794 deletions
+65
View File
@@ -0,0 +1,65 @@
name: "Lint Commit Message Size"
on:
pull_request:
types: [opened, synchronize, reopened]
jobs:
check-commit-bounds:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Validate Line Count, Width, and Spacing
run: |
# Fetch commit hashes unique to this PR branch
COMMITS=$(git log --no-merges --pretty=format:"%H" origin/${{ github.base_ref }}..HEAD)
MAX_LINES=3
MAX_CHARS=90
FAILED=0
for commit in $COMMITS; do
SUBJECT=$(git log --format="%s" -n 1 $commit)
# Extract clean commit message, trimming trailing blank lines
RAW_MSG=$(git log --format="%B" -n 1 $commit)
CLEAN_MSG=$(echo "$RAW_MSG" | awk '{msg[NR]=$0} END {while(NR>0 && msg[NR]=="") NR--; for(i=1;i<=NR;i++) print msg[i]}')
# 1. Check total line count
LINE_COUNT=$(echo "$CLEAN_MSG" | wc -l)
if [ "$LINE_COUNT" -gt "$MAX_LINES" ]; then
echo "❌ Error: Commit message has too many lines ($LINE_COUNT/$MAX_LINES)."
echo " Commit: '$SUBJECT'"
FAILED=1
fi
# 2. Check for multiple consecutive blank lines
# This regex looks for 2 or more empty lines anywhere in the message
if echo "$CLEAN_MSG" | grep -Pz '(\r?\n){3,}'; then
echo "❌ Error: Commit message contains multiple consecutive line breaks."
echo " Commit: '$SUBJECT'"
FAILED=1
fi
# 3. Check maximum width of any individual line
while IFS= read -r line; do
LINE_LENGTH=${#line}
if [ "$LINE_LENGTH" -gt "$MAX_CHARS" ]; then
echo "❌ Error: Line length exceeds limit ($LINE_LENGTH / $MAX_CHARS chars)."
echo " Offending line: '$line'"
FAILED=1
fi
done <<< "$CLEAN_MSG"
done
# Fail the job if any check failed
if [ "$FAILED" -ne 0 ]; then
exit 1
fi
echo "✅ All commit messages passed style rules!"
+6 -4
View File
@@ -1,6 +1,6 @@
m4_pattern_allow([PKG_CHECK_EXISTS])
AC_INIT([rtorrent],[0.16.18],[sundell.software@gmail.com])
AC_INIT([rtorrent],[0.16.21],[sundell.software@gmail.com])
AC_CONFIG_HEADERS([config.h])
AC_CONFIG_MACRO_DIRS([scripts])
@@ -14,7 +14,7 @@ AX_CXX_COMPILE_STDCXX(20, noext, mandatory)
PKG_PROG_PKG_CONFIG
AC_DEFINE([API_VERSION], [23], [api version])
AC_DEFINE([API_VERSION], [26], [api version])
RAK_CHECK_CFLAGS
RAK_CHECK_CXXFLAGS
@@ -22,6 +22,8 @@ RAK_ENABLE_DEBUG
RAK_ENABLE_EXTRA_DEBUG
RAK_ENABLE_WERROR
TORRENT_CHECK_ATOMIC
AC_ARG_ENABLE(execinfo,
AS_HELP_STRING([--disable-execinfo],
[disable libexecinfo [[default=enable]]]),
@@ -47,7 +49,7 @@ fi
PKG_CHECK_MODULES([CPPUNIT], [cppunit],, [no_cppunit="yes"])
PKG_CHECK_MODULES([ZLIB], [zlib])
PKG_CHECK_MODULES([DEPENDENCIES], [libtorrent >= 0.16.18])
PKG_CHECK_MODULES([DEPENDENCIES], [libtorrent >= 0.16.21])
AC_LANG_PUSH(C++)
TORRENT_WITH_XMLRPC_C
@@ -68,7 +70,7 @@ AC_DEFINE(USER_AGENT, [std::string(PACKAGE "/" VERSION)], Http user agent)
dnl Only update global build variables immediately before generating the output,
dnl to avoid affecting the global build environment for other autoconf checks.
LIBS="$PTHREAD_LIBS $CURSES_LIB $CURSES_LIBS $ZLIB_LIBS $DEPENDENCIES_LIBS $LIBS"
LIBS="$ATOMIC_LIBS $PTHREAD_LIBS $CURSES_LIB $CURSES_LIBS $ZLIB_LIBS $DEPENDENCIES_LIBS $LIBS"
CFLAGS="$CFLAGS $PTHREAD_CFLAGS $CURSES_CFLAGS $ZLIB_CFLAGS $DEPENDENCIES_CFLAGS"
CXXFLAGS="$CXXFLAGS $PTHREAD_CFLAGS $CURSES_CFLAGS $ZLIB_CFLAGS $DEPENDENCIES_CFLAGS"
+60
View File
@@ -0,0 +1,60 @@
# Resolving paths
Commands that return a path have `.realpath` variants that resolve it to a
canonical one, with symlinks followed and any `.` or `..` components removed.
The intent is to make paths safer to hand to an external script.
A script called through `execute` receives whatever path rtorrent gives it, and
many scripts do no sanity checking of their own, so resolving the path before it
leaves rtorrent removes a class of surprises: a download directory that is a
symlink into somewhere unexpected, or a torrent whose name walks upwards out of
the directory it is supposed to live in.
# Instead of this
execute = ~/bin/on-finished, (d.base_path)
# Pass the resolved path
execute = ~/bin/on-finished, (d.base_path.realpath.or_throw)
## Available variants
Each command below comes in an `.or_empty` and an `.or_throw` form.
| Command | Resolves |
| --- | --- |
| `d.base_path.realpath.*` | `d.base_path` |
| `d.directory.realpath.*` | `d.directory` |
| `d.tied_to_file.realpath.*` | `d.tied_to_file` |
| `d.loaded_file.realpath.*` | `d.loaded_file` |
| `f.frozen_path.realpath.*` | `f.frozen_path` |
| `session.path.realpath.*` | `session.path` |
| `directory.default.realpath.*` | `directory.default` |
A leading `~` is expanded first, exactly as it is for `execute`, so
`~/downloads` resolves the same way it would on the command line.
## Paths that do not exist
Resolving requires the path to name an existing file or directory, which is
often not the case for a download whose data has not been written yet.
The two forms differ only in what they do about it.
`.or_empty` returns an empty string:
print = (d.base_path.realpath.or_empty) # ""
This keeps `d.multicall` usable over a view that mixes started and unstarted
downloads, since a single unresolvable path does not abort the whole call.
A script receiving one of these paths should still check that it is not empty
before acting on it.
`.or_throw` raises an error instead:
print = (d.base_path.realpath.or_throw) # Could not resolve path: '...'
Prefer this one wherever an unresolvable path means the command should not run
at all, such as a single `execute` on `event.download.finished`.
Note that a download only has a file list once it has been opened, so
`d.base_path` and `f.frozen_path` are empty at `event.download.inserted` time
and their `.realpath` variants resolve nothing.
+139
View File
@@ -0,0 +1,139 @@
# String functions
The `string.*` commands manipulate and inspect text from within the
configuration file.
They are pure functions with no side effects, so they can be nested freely and
are safe to call over RPC.
Every argument is converted to its string representation before use, which means
numbers can be passed where text is expected.
Commands that count characters, such as `string.length` and `string.substr`, count
utf-8 characters rather than bytes.
## string.length
# string.length = «text»
string.length = "héllo" # 5
Returns the number of utf-8 characters in the text.
## string.equals
# string.equals = «text», «other»[, ...]
string.equals = (d.name), "first.iso", "second.iso"
Returns `1` if the first argument equals any of the following arguments,
otherwise `0`.
## string.starts_with, string.ends_with
# string.starts_with = «text», «prefix»[, ...]
# string.ends_with = «text», «tail»[, ...]
string.starts_with = (t.url), "http://", "https://"
string.ends_with = (d.name), ".iso"
Returns `1` if the text begins, or ends, with any of the given prefixes or
tails.
## string.contains, string.contains_i
# string.contains = «haystack», «needle»[, ...]
# string.contains_i = «haystack», «needle»[, ...]
string.contains = (t.url), "retracker.local"
string.contains_i = (t.url), "RETRACKER.local"
Returns `1` if the haystack contains any of the needles.
The `_i` variant compares case-insensitively, and only handles ascii.
## string.substr
# string.substr = «text»[, «position»[, «count»[, «default»]]]
string.substr = "abcdef", 2, 3 # "cde"
string.substr = "abcdef", -2 # "ef"
string.substr = "abcdef", 10, 1, "?" # "?"
Extracts a part of the text, starting at `position` and spanning `count`
characters.
The position defaults to the start of the text, and the count to the rest of it.
A negative position is relative to the end of the text.
If the position falls outside the text, the default value is returned instead,
which is the empty string unless given.
## string.split
# string.split = «text», «delimiter»
string.split = "a.b.c", "." # {"a", "b", "c"}
string.split = "abc", "" # {"a", "b", "c"}
Splits the text into a list, keeping empty fields.
An empty delimiter splits the text into its utf-8 characters.
## string.join
# string.join = «delimiter»[, «object»[, ...]]
string.join = "-", (string.split, "a.b.c", ".") # "a-b-c"
Concatenates the objects, inserting the delimiter between them.
Lists are flattened, so the result of `string.split` can be joined back
together.
## string.lpad, string.rpad
# string.lpad = «text», «length»[, «padding»]
# string.rpad = «text», «length»[, «padding»]
string.lpad = 7, 3, 0 # "007"
string.rpad = "a", 3 # "a "
Pads the text at the start, or the end, until it is `length` characters long.
The padding defaults to a single space and is repeated as needed.
Text that is already long enough is returned unchanged.
## string.strip, string.lstrip, string.rstrip
# string.strip = «text»[, «strippable»[, ...]]
# string.lstrip = «text»[, «head»[, ...]]
# string.rstrip = «text»[, «tail»[, ...]]
string.strip = " padded " # "padded"
string.strip = "//path//", "/" # "path"
Removes characters from both ends of the text, or from only the start or the
end.
The arguments after the text form a set of utf-8 characters to remove.
Without them, whitespace is removed.
## string.map
# string.map = «text», {«old», «new»}[, ...]
string.map = (d.state), {0, "stopped"}, {1, "started"}
Returns the replacement of the first pair whose `old` value equals the whole
text.
If no pair matches, the text is returned unchanged.
## string.replace
# string.replace = «text», {«old», «new»}[, ...]
string.replace = "a-b-c", {"-", "+"} # "a+b+c"
Replaces every occurrence of `old` with `new`.
The pairs are applied in order, so a later pair operates on the result of the
earlier ones.
## Example: dropping unwanted trackers
The following disables every tracker that points at `retracker.local` as soon as
a download is inserted.
method.set_key = event.download.inserted, drop_retracker, \
((t.multicall, default, "branch=(string.contains,(t.url),retracker.local),((t.disable))"))
+10 -2
View File
@@ -63,6 +63,14 @@
#network.local_address.set = 127.0.0.1
#network.local_address.set = rakshasa.no
# The port reported to the tracker, separate for ipv4/ipv6. A value of 0
# (the default) falls back to reporting the listening port. network.local_port.set
# sets both the ipv4 and ipv6 ports at once.
#
#network.local_port.set = 6881
#network.local_port.ipv4.set = 6881
#network.local_port.ipv6.set = 6882
# The IP address the listening socket and outgoing connections is
# bound to.
#
@@ -71,11 +79,11 @@
# Port range to use for listening.
#
#network.port_range.set = 6890-6999
#network.listen.port.range.set = 6890-6999
# Start opening ports at a random position within the port range.
#
#network.port_random.set = no
#network.listen.port.random.set = no
# Set RPC type
#network.rpc.use_xmlrpc.set = true
+2 -2
View File
@@ -23,8 +23,8 @@ execute.throw = sh, -c, (cat,\
"\"",(cfg.watch),"/start\" ")
# Listening port for incoming peer traffic (fixed; you can also randomize it)
network.port_range.set = 50000-50000
network.port_random.set = no
network.listen.port.range.set = 50000-50000
network.listen.port.random.set = no
# Tracker-less torrent and UDP tracker support
# (conservative settings for 'private' trackers, change for 'public')
+2 -2
View File
@@ -53,8 +53,8 @@ rc.execute.throw(
cfg.watch..'/start'}))
-- Listening port for incoming peer traffic (fixed; you can also randomize it)
rc.network.port_range = '50000-50000'
rc.network.port_random = false
rc.network.listen.port.range = '50000-50000'
rc.network.listen.port.random = false
-- Tracker-less torrent and UDP tracker support
-- (conservative settings for 'private' trackers, change for 'public')
+5
View File
@@ -174,6 +174,8 @@ libsub_root_a_SOURCES = \
utils/list_focus.h \
utils/lockfile.cc \
utils/lockfile.h \
utils/waitpid_queue.cc \
utils/waitpid_queue.h \
utils/watch_ready_queue.cc \
utils/watch_ready_queue.h \
\
@@ -185,6 +187,7 @@ libsub_root_a_SOURCES = \
command_helpers.cc \
command_helpers.h \
command_groups.cc \
command_load.cc \
command_local.cc \
command_logging.cc \
command_network.cc \
@@ -192,6 +195,8 @@ libsub_root_a_SOURCES = \
command_throttle.cc \
command_tracker.cc \
command_scheduler.cc \
command_string.cc \
command_system.cc \
command_ui.cc \
control.cc \
control.h \
+22 -16
View File
@@ -142,16 +142,6 @@ apply_d_delete_tied(core::Download* download) {
return torrent::Object();
}
void
apply_d_directory(core::Download* download, const std::string& name) {
if (!download->file_list()->is_multi_file())
download->set_root_directory(name);
else if (name.empty() || *name.rbegin() == '/')
download->set_root_directory(name + download->info()->name().str());
else
download->set_root_directory(name + "/" + download->info()->name().str());
}
torrent::Object
apply_d_connection_type(core::Download* download, const std::string& name) {
torrent::Download::ConnectionType t =
@@ -655,6 +645,8 @@ initialize_command_download() {
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_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(); });
CMD2_DL("d.base_filename.hex", [](auto* download, auto) { return retrieve_d_base_filename(download).object_hex(); });
CMD2_DL("d.base_filename.base64", [](auto* download, auto) { return retrieve_d_base_filename(download).object_base64(); });
@@ -716,7 +708,7 @@ initialize_command_download() {
CMD2_DL_V ("d.pause", std::bind(&core::DownloadList::pause_default, control->core()->download_list(), std::placeholders::_1));
CMD2_DL_V ("d.open", std::bind(&core::DownloadList::open_throw, control->core()->download_list(), std::placeholders::_1));
CMD2_DL_V ("d.close", std::bind(&core::DownloadList::close_throw, control->core()->download_list(), std::placeholders::_1));
CMD2_DL_V ("d.close.directly", std::bind(&core::DownloadList::close_directly, control->core()->download_list(), std::placeholders::_1));
CMD2_DL_V ("d.close.directly", std::bind(&core::DownloadList::close_files, control->core()->download_list(), std::placeholders::_1));
CMD2_DL_V ("d.erase", std::bind(&core::DownloadList::erase_ptr, control->core()->download_list(), std::placeholders::_1));
CMD2_DL_V ("d.check_hash", std::bind(&core::DownloadList::check_hash, control->core()->download_list(), std::placeholders::_1));
@@ -771,6 +763,11 @@ initialize_command_download() {
CMD2_DL_VAR_STRING_PUBLIC("d.tied_to_file", "rtorrent", "tied_to_file");
CMD2_DL_VAR_STRING("d.loaded_file", "rtorrent", "loaded_file");
CMD2_DL("d.tied_to_file.realpath.or_empty", [](auto* download, auto) { return resolve_path(rpc::convert_to_string(download_get_variable(download, "rtorrent", "tied_to_file"))); });
CMD2_DL("d.tied_to_file.realpath.or_throw", [](auto* download, auto) { return resolve_path_or_throw(rpc::convert_to_string(download_get_variable(download, "rtorrent", "tied_to_file"))); });
CMD2_DL("d.loaded_file.realpath.or_empty", [](auto* download, auto) { return resolve_path(rpc::convert_to_string(download_get_variable(download, "rtorrent", "loaded_file"))); });
CMD2_DL("d.loaded_file.realpath.or_throw", [](auto* download, auto) { return resolve_path_or_throw(rpc::convert_to_string(download_get_variable(download, "rtorrent", "loaded_file"))); });
// The "state_changed" variable is required to be a valid unix time
// value, it indicates the last time the torrent changed its state,
// resume/pause.
@@ -877,10 +874,11 @@ initialize_command_download() {
CMD2_DL_LIST ("d.tracker.insert", std::bind(&download_tracker_insert, std::placeholders::_1, std::placeholders::_2));
CMD2_DL_VALUE_V ("d.tracker.send_scrape", [](auto download, uint64_t arg) { download->tracker_controller().scrape_request(arg); });
CMD2_DL ("d.directory", CMD2_ON_FL(root_dir));
CMD2_DL_STRING_V("d.directory.set", std::bind(&apply_d_directory, std::placeholders::_1, std::placeholders::_2));
CMD2_DL ("d.directory_base", CMD2_ON_FL(root_dir));
CMD2_DL_STRING_V("d.directory_base.set", std::bind(&core::Download::set_root_directory, std::placeholders::_1, std::placeholders::_2));
CMD2_DL ("d.directory", CMD2_ON_FL(root_dir));
CMD2_DL ("d.directory.realpath.or_empty", [](auto* download, auto) { return resolve_path(download->file_list()->root_dir()); });
CMD2_DL ("d.directory.realpath.or_throw", [](auto* download, auto) { return resolve_path_or_throw(download->file_list()->root_dir()); });
CMD2_DL_STRING_V("d.directory.set", [](auto* download, auto arg) { download->set_directory(arg); });
CMD2_DL_STRING_V("d.directory.base.set", [](auto* download, auto arg) { download->set_base_directory(arg); });
CMD2_DL ("d.priority", std::bind(&core::Download::priority, std::placeholders::_1));
CMD2_DL ("d.priority_str", std::bind(&retrieve_d_priority_str, std::placeholders::_1));
@@ -912,6 +910,14 @@ initialize_command_download() {
rpc::rpc.mark_safe("d.local_id_html");
rpc::rpc.mark_safe("d.bitfield");
rpc::rpc.mark_safe("d.base_path");
rpc::rpc.mark_safe("d.base_path.realpath.or_empty");
rpc::rpc.mark_safe("d.base_path.realpath.or_throw");
rpc::rpc.mark_safe("d.directory.realpath.or_empty");
rpc::rpc.mark_safe("d.directory.realpath.or_throw");
rpc::rpc.mark_safe("d.tied_to_file.realpath.or_empty");
rpc::rpc.mark_safe("d.tied_to_file.realpath.or_throw");
rpc::rpc.mark_safe("d.loaded_file.realpath.or_empty");
rpc::rpc.mark_safe("d.loaded_file.realpath.or_throw");
rpc::rpc.mark_safe("d.base_path.hex");
rpc::rpc.mark_safe("d.base_path.base64");
rpc::rpc.mark_safe("d.base_path.base64_as_binary");
@@ -933,7 +939,6 @@ initialize_command_download() {
rpc::rpc.mark_safe("d.name.or_base64");
rpc::rpc.mark_safe("d.name.or_as_binary");
rpc::rpc.mark_safe("d.directory");
rpc::rpc.mark_safe("d.directory_base");
rpc::rpc.mark_safe("d.creation_date");
rpc::rpc.mark_safe("d.load_date");
rpc::rpc.mark_safe("d.up.rate");
@@ -974,6 +979,7 @@ initialize_command_download() {
rpc::rpc.mark_safe("d.size_pex");
rpc::rpc.mark_safe("d.completed_bytes");
rpc::rpc.mark_safe("d.complete");
rpc::rpc.mark_safe("d.timestamp.started");
rpc::rpc.mark_safe("d.timestamp.finished");
rpc::rpc.mark_safe("d.bytes_done");
rpc::rpc.mark_safe("d.peers_accounted");
+4
View File
@@ -444,6 +444,8 @@ initialize_command_dynamic() {
CMD2_ANY ("catch", std::bind(&cmd_catch, std::placeholders::_1, std::placeholders::_2));
CMD2_ANY_STRING ("enum.log_group", [](auto, const auto& str) { return torrent::option_find_string_str(torrent::OPTION_LOG_GROUP, str); });
CMD2_ANY ("strings.choke_heuristics", [](auto, auto) { return torrent::option_list_strings(torrent::OPTION_CHOKE_HEURISTICS); });
CMD2_ANY ("strings.choke_heuristics.upload", [](auto, auto) { return torrent::option_list_strings(torrent::OPTION_CHOKE_HEURISTICS_UPLOAD); });
CMD2_ANY ("strings.choke_heuristics.download", [](auto, auto) { return torrent::option_list_strings(torrent::OPTION_CHOKE_HEURISTICS_DOWNLOAD); });
@@ -470,6 +472,8 @@ initialize_command_dynamic() {
rpc::rpc.mark_safe("method.rlookup");
rpc::rpc.mark_safe("catch");
rpc::rpc.mark_safe("enum.log_group");
rpc::rpc.mark_safe("strings.choke_heuristics");
rpc::rpc.mark_safe("strings.choke_heuristics.upload");
rpc::rpc.mark_safe("strings.choke_heuristics.download");
+30 -43
View File
@@ -1,25 +1,17 @@
#include "config.h"
#include <functional>
#include <cstdio>
#include <string>
#include <vector>
#include <torrent/rate.h>
#include <torrent/hash_string.h>
#include <torrent/exceptions.h>
#include <torrent/utils/log.h>
#include <torrent/utils/directory_events.h>
#include <torrent/utils/file_stat.h>
#include <torrent/utils/string_manip.h>
#include "globals.h"
#include "control.h"
#include "command_helpers.h"
#include "core/download.h"
#include "core/download_list.h"
#include "core/manager.h"
#include "core/view_manager.h"
#include "rpc/command_scheduler.h"
#include "rpc/parse.h"
#include "rpc/parse_commands.h"
#include "utils/watch_ready_queue.h"
@@ -51,7 +43,7 @@ apply_on_ratio(const torrent::Object& rawArgs) {
!(max_ratio > 0 && total_upload * 100 > total_done * max_ratio))
continue;
downloads.push_back(*itr);
downloads.push_back(itr->get());
}
auto ratio_command = "group." + group_name + ".ratio.command";
@@ -128,7 +120,7 @@ apply_remove_untied() {
}
torrent::Object
apply_schedule(const torrent::Object::list_type& args) {
apply_schedule(const torrent::Object::list_type& args, bool if_absent) {
if (args.size() != 4)
throw torrent::input_error("Wrong number of arguments.");
@@ -138,29 +130,14 @@ apply_schedule(const torrent::Object::list_type& args) {
auto& arg2 = (itr++)->as_string();
auto& arg3 = (itr++)->as_string();
if (if_absent && control->command_scheduler()->find(arg1) != control->command_scheduler()->end())
return torrent::Object();
control->command_scheduler()->parse(arg1, arg2, arg3, *itr);
return torrent::Object();
}
torrent::Object
apply_load(const torrent::Object::list_type& args, int flags) {
torrent::Object::list_const_iterator argsItr = args.begin();
if (argsItr == args.end())
throw torrent::input_error("Too few arguments.");
auto& filename = argsItr->as_string();
core::Manager::command_list_type commands;
while (++argsItr != args.end())
commands.push_back(argsItr->as_string());
control->core()->try_create_download_expand(filename, flags, commands);
return torrent::Object();
}
void apply_import(const std::string& path) { if (!rpc::parse_command_file(path)) throw torrent::input_error("Could not open option file: " + path); }
void apply_try_import(const std::string& path) { if (!rpc::parse_command_file(path)) control->core()->push_log_std("Could not read resource file: " + path); }
@@ -170,7 +147,7 @@ apply_close_low_diskspace(int64_t arg, uint32_t skip_priority) {
torrent::FileList::cache_list cache;
for (auto download : *control->core()->download_list()) {
for (const auto& download : *control->core()->download_list()) {
if (!download->is_downloading())
continue;
if (download->priority() >= skip_priority)
@@ -237,15 +214,25 @@ d_multicall(const torrent::Object::list_type& args) {
// Add some pre-parsing of the commands, so we don't spend time
// parsing and searching command map for every single call.
std::vector<core::Download*> dlist((*view_itr)->begin_visible(), (*view_itr)->end_visible());
// Hold a reference to each download so a command that erases one does not
// leave the rest of the loop dispatching on freed memory.
core::View::base_type dlist((*view_itr)->begin_visible(), (*view_itr)->end_visible());
torrent::Object resultRaw = torrent::Object::create_list();
torrent::Object::list_type& result = resultRaw.as_list();
for (auto download : dlist) {
for (const auto& download : dlist) {
if (download.use_count() == 1)
continue;
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++) {
// A command may erase this download, which destroys the torrent object it
// wraps; the list dropping its reference is what tells us.
if (download.use_count() == 1)
break;
auto& cmd = cItr->as_string();
row.push_back(rpc::parse_command(rpc::make_target(download), cmd.c_str(), cmd.c_str() + cmd.size()).first);
}
@@ -268,10 +255,12 @@ d_multicall_filtered(const torrent::Object::list_type& args) {
if (view_itr == viewManager->end())
throw torrent::input_error("Could not find view '" + arg->as_string() + "'.");
// Make a filtered copy of the current item list
// Make a filtered copy of the current item list, holding a reference to each
// download so a command that erases one cannot free it under us.
core::View::base_type dlist;
(*view_itr)->filter_by(*++arg, dlist);
// Generate result by iterating over all items
auto resultRaw = torrent::Object::create_list();
auto& result = resultRaw.as_list();
@@ -279,11 +268,17 @@ d_multicall_filtered(const torrent::Object::list_type& args) {
++arg; // skip to first command
for (const auto& item : dlist) {
if (item.use_count() == 1)
continue;
// Add empty row to result
torrent::Object::list_type& row = result.insert(result.end(), torrent::Object::create_list())->as_list();
// Call the provided commands and assemble their results
for (torrent::Object::list_const_iterator command = arg; command != args.end(); command++) {
if (item.use_count() == 1)
break;
auto& cmdstr = command->as_string();
row.push_back(rpc::parse_command(rpc::make_target(item), cmdstr.c_str(), cmdstr.c_str() + cmdstr.size()).first);
}
@@ -340,21 +335,13 @@ initialize_command_events() {
CMD2_ANY ("close_untied", [](auto, auto) { return apply_close_untied(); });
CMD2_ANY ("remove_untied", [](auto, auto) { return apply_remove_untied(); });
CMD2_ANY_LIST ("schedule", [](auto, auto& args) { return apply_schedule(args); });
CMD2_ANY_LIST ("schedule", [](auto, auto& args) { return apply_schedule(args, false); });
CMD2_ANY_LIST ("schedule.if_absent", [](auto, auto& args) { return apply_schedule(args, true); });
CMD2_ANY_STRING_V("schedule.remove", [](auto, auto& str) { return control->command_scheduler()->erase_str(str); });
CMD2_ANY_STRING_V("import", [](auto, auto& str) { return apply_import(str); });
CMD2_ANY_STRING_V("try_import", [](auto, auto& str) { return apply_try_import(str); });
CMD2_ANY_LIST ("load.normal", [](auto, auto& args) { return apply_load(args, core::Manager::create_quiet | core::Manager::create_tied); });
CMD2_ANY_LIST ("load.verbose", [](auto, auto& args) { return apply_load(args, core::Manager::create_tied); });
CMD2_ANY_LIST ("load.start", [](auto, auto& args) { return apply_load(args, core::Manager::create_quiet | core::Manager::create_tied | core::Manager::create_start); });
CMD2_ANY_LIST ("load.start_verbose", [](auto, auto& args) { return apply_load(args, core::Manager::create_tied | core::Manager::create_start); });
CMD2_ANY_LIST ("load.raw", [](auto, auto& args) { return apply_load(args, core::Manager::create_quiet | core::Manager::create_raw_data); });
CMD2_ANY_LIST ("load.raw_verbose", [](auto, auto& args) { return apply_load(args, core::Manager::create_raw_data); });
CMD2_ANY_LIST ("load.raw_start", [](auto, auto& args) { return apply_load(args, core::Manager::create_quiet | core::Manager::create_start | core::Manager::create_raw_data); });
CMD2_ANY_LIST ("load.raw_start_verbose", [](auto, auto& args) { return apply_load(args, core::Manager::create_start | core::Manager::create_raw_data); });
CMD2_ANY_VALUE ("close_low_diskspace", [](auto, auto& arg) { return apply_close_low_diskspace(arg, 99); });
CMD2_ANY_VALUE ("close_low_diskspace.normal", [](auto, auto& arg) { return apply_close_low_diskspace(arg, 3); });
+4
View File
@@ -116,6 +116,8 @@ initialize_command_file() {
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_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()); });
CMD2_FILE("f.match_depth_prev", std::bind(&torrent::File::match_depth_prev, std::placeholders::_1));
CMD2_FILE("f.match_depth_next", std::bind(&torrent::File::match_depth_next, std::placeholders::_1));
@@ -129,6 +131,8 @@ initialize_command_file() {
rpc::rpc.mark_safe("f.path_components");
rpc::rpc.mark_safe("f.path_depth");
rpc::rpc.mark_safe("f.frozen_path");
rpc::rpc.mark_safe("f.frozen_path.realpath.or_empty");
rpc::rpc.mark_safe("f.frozen_path.realpath.or_throw");
rpc::rpc.mark_safe("f.frozen_path.hex");
rpc::rpc.mark_safe("f.frozen_path.base64");
rpc::rpc.mark_safe("f.frozen_path.base64_as_binary");
+11 -9
View File
@@ -1,5 +1,7 @@
#include "config.h"
#include <memory>
#include <torrent/download/resource_manager.h>
#include <torrent/download/choke_group.h>
#include <torrent/download/choke_queue.h>
@@ -111,7 +113,7 @@ apply_cg_all_update_balance(bool is_up) {
//
#else
std::vector<torrent::choke_group*> cg_list_hack;
std::vector<std::unique_ptr<torrent::choke_group>> cg_list_hack;
int64_t
cg_get_index(const torrent::Object& raw_args) {
@@ -121,7 +123,7 @@ cg_get_index(const torrent::Object& raw_args) {
if (arg.is_string()) {
if (!rpc::parse_whole_value_nothrow(arg.as_string().c_str(), &index)) {
auto itr = std::find_if(cg_list_hack.begin(), cg_list_hack.end(), [&arg](torrent::choke_group* cg) { return arg.as_string() == cg->name(); });
auto itr = std::find_if(cg_list_hack.begin(), cg_list_hack.end(), [&arg](const auto& cg) { return arg.as_string() == cg->name(); });
if (itr == cg_list_hack.end())
throw torrent::input_error("Choke group not found.");
@@ -149,7 +151,7 @@ cg_get_group(const torrent::Object& raw_args) {
if ((size_t)index >= cg_list_hack.size())
throw torrent::input_error("Choke group not found.");
return cg_list_hack.at(index);
return cg_list_hack.at(index).get();
}
int64_t cg_d_group(core::Download* download) { return download->group(); }
@@ -162,7 +164,7 @@ torrent::Object
apply_cg_list() {
torrent::Object::list_type result;
for (auto itr : cg_list_hack)
for (const auto& itr : cg_list_hack)
result.push_back(itr->name());
return torrent::Object::from_list(result);
@@ -180,10 +182,10 @@ apply_cg_insert(const std::string& arg) {
if (rpc::parse_whole_value_nothrow(arg.c_str(), &dummy))
throw torrent::input_error("Cannot use a value string as choke group name.");
if (arg.empty() || std::any_of(cg_list_hack.begin(), cg_list_hack.end(), [&arg](auto cg) { return arg == cg->name(); }))
if (arg.empty() || std::any_of(cg_list_hack.begin(), cg_list_hack.end(), [&arg](const auto& cg) { return arg == cg->name(); }))
throw torrent::input_error("Duplicate name for choke group.");
cg_list_hack.push_back(new torrent::choke_group());
cg_list_hack.push_back(std::make_unique<torrent::choke_group>());
cg_list_hack.back()->set_name(arg);
cg_list_hack.back()->up_queue()->set_heuristics(torrent::HEURISTICS_UPLOAD_LEECH);
@@ -194,7 +196,7 @@ apply_cg_insert(const std::string& arg) {
torrent::Object
apply_cg_index_of(const std::string& arg) {
auto itr = std::find_if(cg_list_hack.begin(), cg_list_hack.end(), [&arg](torrent::choke_group* cg) { return arg == cg->name(); });
auto itr = std::find_if(cg_list_hack.begin(), cg_list_hack.end(), [&arg](const auto& cg) { return arg == cg->name(); });
if (itr == cg_list_hack.end())
throw torrent::input_error("Choke group not found.");
@@ -206,7 +208,7 @@ torrent::Object
apply_cg_all_update_balance(bool is_up) {
LT_LOG_SUBSYSTEM("apply update balance: hack is_up:%i", (int)is_up);
for (auto itr : cg_list_hack) {
for (const auto& itr : cg_list_hack) {
if (is_up)
itr->up_queue()->balance();
else
@@ -346,7 +348,7 @@ initialize_command_groups() {
#else
apply_cg_insert("default");
CMD_ANY ("choke_group.size", std::bind(&std::vector<torrent::choke_group*>::size, cg_list_hack));
CMD_ANY ("choke_group.size", [](auto, auto) { return (int64_t)cg_list_hack.size(); });
CMD_ANY_STRING ("choke_group.index_of", std::bind(&apply_cg_index_of, std::placeholders::_2));
#endif
+4
View File
@@ -19,6 +19,8 @@ void initialize_command_groups();
void initialize_command_throttle();
void initialize_command_tracker();
void initialize_command_scheduler();
void initialize_command_string();
void initialize_command_system();
void initialize_command_ui();
void
@@ -37,4 +39,6 @@ initialize_commands() {
initialize_command_throttle();
initialize_command_tracker();
initialize_command_scheduler();
initialize_command_string();
initialize_command_system();
}
+81
View File
@@ -0,0 +1,81 @@
#include "config.h"
#include "command_helpers.h"
#include "control.h"
#include "core/manager.h"
torrent::Object
apply_load_trusted(const torrent::Object::list_type& args, int flags) {
auto argsItr = args.begin();
if (argsItr == args.end())
throw torrent::input_error("Too few arguments.");
auto& filename = argsItr->as_string();
core::Manager::command_list_type commands;
while (++argsItr != args.end())
commands.push_back(argsItr->as_string());
control->core()->try_create_download_expand(filename, flags, commands);
return torrent::Object();
}
torrent::Object
apply_load_untrusted(const torrent::Object::list_type& args, int flags) {
auto argsItr = args.begin();
if (argsItr == args.end())
throw torrent::input_error("Too few arguments.");
auto& filename = argsItr->as_string();
core::Manager::command_list_type commands;
while (++argsItr != args.end())
commands.push_back(argsItr->as_string());
control->core()->try_create_download_untrusted(filename, flags, commands);
return torrent::Object();
}
void
initialize_command_load() {
CMD2_ANY_LIST ("load.normal", [](auto, auto& args) { return apply_load_trusted(args, core::Manager::create_quiet | core::Manager::create_tied); });
CMD2_ANY_LIST ("load.verbose", [](auto, auto& args) { return apply_load_trusted(args, core::Manager::create_tied); });
CMD2_ANY_LIST ("load.start", [](auto, auto& args) { return apply_load_trusted(args, core::Manager::create_quiet | core::Manager::create_tied | core::Manager::create_start); });
CMD2_ANY_LIST ("load.start_verbose", [](auto, auto& args) { return apply_load_trusted(args, core::Manager::create_tied | core::Manager::create_start); });
CMD2_ANY_LIST ("load.raw", [](auto, auto& args) { return apply_load_trusted(args, core::Manager::create_quiet | core::Manager::create_raw_data); });
CMD2_ANY_LIST ("load.raw_verbose", [](auto, auto& args) { return apply_load_trusted(args, core::Manager::create_raw_data); });
CMD2_ANY_LIST ("load.raw_start", [](auto, auto& args) { return apply_load_trusted(args, core::Manager::create_quiet | core::Manager::create_start | core::Manager::create_raw_data); });
CMD2_ANY_LIST ("load.raw_start_verbose", [](auto, auto& args) { return apply_load_trusted(args, core::Manager::create_start | core::Manager::create_raw_data); });
CMD2_ANY_LIST ("load.untrusted.normal", [](auto, auto& args) { return apply_load_untrusted(args, core::Manager::create_quiet); });
CMD2_ANY_LIST ("load.untrusted.verbose", [](auto, auto& args) { return apply_load_untrusted(args, 0); });
CMD2_ANY_LIST ("load.untrusted.start", [](auto, auto& args) { return apply_load_untrusted(args, core::Manager::create_quiet | core::Manager::create_start); });
CMD2_ANY_LIST ("load.untrusted.start_verbose", [](auto, auto& args) { return apply_load_untrusted(args, core::Manager::create_start); });
CMD2_ANY_LIST ("load.untrusted.raw", [](auto, auto& args) { return apply_load_untrusted(args, core::Manager::create_quiet | core::Manager::create_raw_data); });
CMD2_ANY_LIST ("load.untrusted.raw_verbose", [](auto, auto& args) { return apply_load_untrusted(args, core::Manager::create_raw_data); });
CMD2_ANY_LIST ("load.untrusted.raw_start", [](auto, auto& args) { return apply_load_untrusted(args, core::Manager::create_quiet | core::Manager::create_start | core::Manager::create_raw_data); });
CMD2_ANY_LIST ("load.untrusted.raw_start_verbose", [](auto, auto& args) { return apply_load_untrusted(args, core::Manager::create_start | core::Manager::create_raw_data); });
CMD2_ANY_VALUE_V("load.untrusted.mark_safe", [](auto, auto& arg) {
if (arg == 0)
return;
// TODO: Add a bool to core::Manager.
rpc::rpc.mark_safe("load.untrusted.normal");
rpc::rpc.mark_safe("load.untrusted.verbose");
rpc::rpc.mark_safe("load.untrusted.start");
rpc::rpc.mark_safe("load.untrusted.start_verbose");
rpc::rpc.mark_safe("load.untrusted.raw");
rpc::rpc.mark_safe("load.untrusted.raw_verbose");
rpc::rpc.mark_safe("load.untrusted.raw_start");
rpc::rpc.mark_safe("load.untrusted.raw_start_verbose");
});
}
+17 -159
View File
@@ -1,33 +1,16 @@
#include "config.h"
#include <cerrno>
#include <fcntl.h>
#include <functional>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <torrent/torrent.h>
#include <torrent/data/file_manager.h>
#include <torrent/data/chunk_utils.h>
#include <torrent/runtime/runtime.h>
#include <torrent/runtime/memory_manager.h>
#include <torrent/runtime/socket_manager.h>
#include <torrent/utils/chrono.h>
#include <torrent/utils/option_strings.h>
#include <torrent/utils/log.h>
#include "command_helpers.h"
#include "control.h"
#include "core/download.h"
#include "core/download_list.h"
#include "core/manager.h"
#include "rpc/parse_commands.h"
#include "rpc/lua.h"
#include "rpc/scgi.h"
#include "session/session_manager.h"
#include "utils/file_status_cache.h"
#include "globals.h"
#include "rpc/lua.h"
#include "control.h"
#include "command_helpers.h"
typedef torrent::ChunkManager CM_t;
typedef torrent::FileManager FM_t;
@@ -43,49 +26,6 @@ apply_pieces_stats_total_size() {
return size;
}
torrent::Object
system_env(const torrent::Object::string_type& arg) {
if (arg.empty())
throw torrent::input_error("system.env: Missing variable name.");
char* val = getenv(arg.c_str());
return std::string(val ? val : "");
}
torrent::Object
system_hostname() {
char buffer[1024];
if (gethostname(buffer, 1023) == -1)
throw torrent::input_error("Unable to read hostname.");
// if (shorten)
// *std::find(buffer, buffer + 1023, '.') = '\0';
return std::string(buffer);
}
torrent::Object
system_get_cwd() {
char* buffer = getcwd(NULL, 0);
if (buffer == NULL)
throw torrent::input_error("Unable to read cwd.");
torrent::Object result = torrent::Object(std::string(buffer));
free(buffer);
return result;
}
torrent::Object
system_set_cwd(const torrent::Object::string_type& rawArgs) {
if (::chdir(rawArgs.c_str()) != 0)
throw torrent::input_error("Could not change current working directory.");
return torrent::Object();
}
inline torrent::Object::list_const_iterator
post_increment(torrent::Object::list_const_iterator& itr, const torrent::Object::list_const_iterator& last) {
if (itr == last)
@@ -117,7 +57,7 @@ group_insert(const torrent::Object::list_type& args) {
rpc::commands.call("method.insert", rpc::create_object_list("group." + name + ".ratio.enable", "simple",
"schedule=group." + name + ".ratio,5,60,on_ratio=" + name));
rpc::commands.call("method.insert", rpc::create_object_list("group." + name + ".ratio.disable", "simple",
"schedule_remove=group." + name + ".ratio"));
"schedule.remove=group." + name + ".ratio"));
rpc::commands.call("method.insert", rpc::create_object_list("group." + name + ".ratio.command", "simple",
"d.try_close= ;d.ignore_commands.set=1"));
rpc::commands.call("method.insert", rpc::create_object_list("group." + name + ".view", "string", view));
@@ -187,88 +127,17 @@ cmd_file_append(const torrent::Object::list_type& args) {
void
initialize_command_local() {
core::DownloadList* dList = control->core()->download_list();
torrent::FileManager* fileManager = torrent::file_manager();
CMD_ANY ("system.hostname", std::bind(&system_hostname));
CMD_ANY ("system.pid", std::bind(&getpid));
CMD_VAR_C_STRING("system.api_version", (int64_t)API_VERSION);
CMD_VAR_C_STRING("system.client_version", PACKAGE_VERSION);
CMD_VAR_C_STRING("system.library_version", torrent::runtime::version());
CMD_VAR_VALUE ("system.file.allocate", 0);
CMD_VAR_VALUE ("system.file.max_size", (int64_t)512 << 30);
CMD_VAR_VALUE ("system.file.split_size", -1);
CMD_VAR_STRING ("system.file.split_suffix", ".part");
CMD_ANY ("system.file_status_cache.size", std::bind(&utils::FileStatusCache::size,
(utils::FileStatusCache::base_type*)control->core()->file_status_cache()));
CMD_ANY_V ("system.file_status_cache.prune", std::bind(&utils::FileStatusCache::prune, control->core()->file_status_cache()));
CMD_VAR_BOOL ("file.prioritize_toc", 0);
CMD_VAR_LIST ("file.prioritize_toc.first");
CMD_VAR_LIST ("file.prioritize_toc.last");
CMD_ANY ("system.files.advise_random", std::bind(&FM_t::advise_random, fileManager));
CMD_ANY_VALUE_V ("system.files.advise_random.set", std::bind(&FM_t::set_advise_random, fileManager, std::placeholders::_2));
CMD_ANY ("system.files.advise_random.hashing", std::bind(&FM_t::advise_random_hashing, fileManager));
CMD_ANY_VALUE_V ("system.files.advise_random.hashing.set", std::bind(&FM_t::set_advise_random_hashing, fileManager, std::placeholders::_2));
CMD_ANY ("system.files.session.fdatasync", [](auto, auto) { return session_thread::manager()->use_fsyncdisk(); });
CMD_ANY_VALUE_V ("system.files.session.fdatasync.set", [](auto, auto& value) { return session_thread::manager()->set_use_fsyncdisk(value); });
CMD_ANY ("system.files.opened_counter", std::bind(&FM_t::files_opened_counter, fileManager));
CMD_ANY ("system.files.closed_counter", std::bind(&FM_t::files_closed_counter, fileManager));
CMD_ANY ("system.files.failed_counter", std::bind(&FM_t::files_failed_counter, fileManager));
CMD_ANY_STRING ("system.env", [](auto, auto& str) { return system_env(str); });
CMD_ANY ("system.time", [](auto, auto) { return torrent::this_thread::cached_seconds().count(); });
CMD_ANY ("system.time_seconds", [](auto, auto) { return torrent::utils::cast_seconds(torrent::utils::time_since_epoch()).count(); });
CMD_ANY ("system.time_usec", [](auto, auto) { return torrent::utils::time_since_epoch().count(); });
CMD_ANY_VALUE_V ("system.umask.set", [](auto, auto& value) { return ::umask(value); });
CMD_VAR_BOOL ("system.daemon", false);
CMD_ANY_V ("system.shutdown.normal", [](auto, auto) { control->receive_normal_shutdown(); });
CMD_ANY_V ("system.shutdown.quick", [](auto, auto) { control->receive_quick_shutdown(); });
CMD_REDIRECT_NO_EXPORT("system.shutdown", "system.shutdown.normal");
CMD_ANY ("system.cwd", [](auto, auto) { return system_get_cwd(); });
CMD_ANY_STRING ("system.cwd.set", [](auto, auto& str) { return system_set_cwd(str); });
CMD_ANY ("system.sockets.size", [](auto, auto) { return torrent::runtime::socket_manager()->size(); });
CMD_ANY ("system.sockets.max_size", [](auto, auto) { return torrent::runtime::socket_manager()->max_size(); });
CMD_ANY_VALUE_V ("system.sockets.max_size.set", [](auto, auto& value) { return torrent::runtime::socket_manager()->set_max_size_and_adjust(value); });
CMD_ANY_V ("system.sockets.adjust_alloc", [](auto, auto) { torrent::runtime::socket_manager()->adjust_allocation(); });
for (uint32_t i = 0; i < torrent::runtime::SocketManager::category_count; ++i) {
auto category = static_cast<torrent::runtime::socket_manager_category_t>(i);
auto category_name = "system.sockets." + torrent::option_to_str_or_throw(torrent::OPTION_SOCKET_CATEGORY, i);
CMD_ANY (category_name + ".size", [category](auto, auto) { return torrent::runtime::socket_manager()->category_managed_size(category); });
CMD_ANY (category_name + ".max_size", [category](auto, auto) { return torrent::runtime::socket_manager()->category_max_size(category); });
CMD_ANY (category_name + ".min_alloc", [category](auto, auto) { return torrent::runtime::socket_manager()->category_min_allocation(category); });
CMD_ANY (category_name + ".max_alloc", [category](auto, auto) { return torrent::runtime::socket_manager()->category_max_allocation(category); });
if (i == 0)
continue;
CMD_ANY_VALUE_V(category_name + ".min_alloc.set", [category](auto, auto& value) { torrent::runtime::socket_manager()->set_category_min_allocation(category, value); });
CMD_ANY_VALUE_V(category_name + ".max_alloc.set", [category](auto, auto& value) { torrent::runtime::socket_manager()->set_category_max_allocation(category, value); });
}
core::DownloadList* dList = control->core()->download_list();
CMD_ANY ("pieces.sync.always_safe", [](auto, auto) { return torrent::runtime::memory_manager()->safe_sync(); });
CMD_ANY_VALUE_V ("pieces.sync.always_safe.set", [](auto, auto& value) { return torrent::runtime::memory_manager()->set_safe_sync(value); });
CMD_ANY ("pieces.sync.safe_free_diskspace", [](auto, auto) { return torrent::runtime::memory_manager()->sync_safe_free_diskspace(); });
CMD_ANY ("pieces.sync.timeout", [](auto, auto) { return torrent::runtime::memory_manager()->timeout_sync().count(); });
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 torrent::runtime::memory_manager()->timeout_safe_sync(); });
// CMD_ANY_VALUE_V ("pieces.sync.timeout_safe.set", [](auto, auto& value) { return torrent::runtime::memory_manager()->set_timeout_safe_sync(value); });
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.");
});
CMD_ANY ("pieces.sync.queue_size", [](auto, auto) { return torrent::runtime::memory_manager()->sync_queue_block_count(); });
CMD_ANY ("pieces.preload.type", [](auto, auto) { return torrent::runtime::memory_manager()->preload_type(); });
@@ -292,9 +161,13 @@ initialize_command_local() {
CMD_VAR_BOOL ("pieces.hash.on_completion", true);
CMD_VAR_STRING ("directory.default", "./");
CMD_ANY ("directory.default.realpath.or_empty", [](auto, auto) { return resolve_path(rpc::call_command_string("directory.default")); });
CMD_ANY ("directory.default.realpath.or_throw", [](auto, auto) { return resolve_path_or_throw(rpc::call_command_string("directory.default")); });
CMD_VAR_STRING ("session.name", "");
CMD_ANY ("session.path", [](auto, auto) { return session_thread::manager()->path(); });
CMD_ANY ("session.path.realpath.or_empty", [](auto, auto) { return resolve_path(session_thread::manager()->path()); });
CMD_ANY ("session.path.realpath.or_throw", [](auto, auto) { return resolve_path_or_throw(session_thread::manager()->path()); });
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); });
@@ -339,27 +212,12 @@ initialize_command_local() {
CMD_ANY_LIST ("group.insert", std::bind(&group_insert, std::placeholders::_2));
rpc::rpc.mark_safe("system.api_version");
rpc::rpc.mark_safe("system.client_version");
rpc::rpc.mark_safe("system.library_version");
rpc::rpc.mark_safe("system.file.max_size");
rpc::rpc.mark_safe("system.file.split_size");
rpc::rpc.mark_safe("system.file.split_suffix");
rpc::rpc.mark_safe("system.sockets.size");
rpc::rpc.mark_safe("system.sockets.max_size");
for (uint32_t i = 0; i < torrent::runtime::SocketManager::category_count; ++i) {
auto category_name = "system.sockets." + torrent::option_to_str_or_throw(torrent::OPTION_SOCKET_CATEGORY, i);
rpc::rpc.mark_safe(category_name + ".size");
rpc::rpc.mark_safe(category_name + ".max_size");
rpc::rpc.mark_safe(category_name + ".min_alloc");
rpc::rpc.mark_safe(category_name + ".max_alloc");
}
rpc::rpc.mark_safe("directory.default");
rpc::rpc.mark_safe("session.path");
rpc::rpc.mark_safe("session.path.realpath.or_empty");
rpc::rpc.mark_safe("session.path.realpath.or_throw");
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.on_completion");
+30 -1
View File
@@ -1,6 +1,7 @@
#include "config.h"
#include <fcntl.h>
#include <iterator>
#include <stdio.h>
#include <unistd.h>
#include <torrent/data/chunk_utils.h>
@@ -15,6 +16,7 @@
#include "core/download.h"
#include "core/download_list.h"
#include "core/manager.h"
#include "rpc/parse.h"
#include "rpc/parse_commands.h"
torrent::Object
@@ -38,6 +40,32 @@ apply_log_add_output(const torrent::Object::list_type& args) {
return torrent::Object();
}
torrent::Object
apply_log_print(const torrent::Object::list_type& args) {
if (args.size() < 2)
throw torrent::input_error("Invalid number of arguments.");
torrent::Object::value_type group;
if (args.front().is_value())
group = args.front().as_value();
else if (args.front().is_string())
group = torrent::option_find_string_str(torrent::OPTION_LOG_GROUP, args.front().as_string());
else
throw torrent::input_error("Invalid log group.");
if (group < 0 || group >= torrent::LOG_GROUP_MAX_SIZE)
throw torrent::input_error("Invalid log group.");
std::string message;
for (auto itr = std::next(args.begin()); itr != args.end(); ++itr)
rpc::print_object_std(&message, &*itr, 0);
torrent::log_groups[group].internal_print(message);
return torrent::Object();
}
// TODO: Deprecated.
torrent::Object
apply_log(const torrent::Object::string_type& arg, int logType) {
@@ -107,11 +135,12 @@ initialize_command_logging() {
CMD2_ANY_LIST ("log.open_gz_file_pid", std::bind(&apply_log_open, log_flag_append_pid | log_flag_use_gz, std::placeholders::_2));
CMD2_ANY_LIST ("log.append_file", std::bind(&apply_log_open, log_flag_append_file, std::placeholders::_2));
CMD2_ANY_LIST ("log.append_file.flush", std::bind(&apply_log_open, log_flag_append_file | log_flag_flush, std::placeholders::_2));
CMD2_ANY_LIST ("log.append_gz_file", std::bind(&apply_log_open, log_flag_append_file, std::placeholders::_2));
CMD2_ANY_LIST ("log.append_gz_file", std::bind(&apply_log_open, log_flag_append_file | log_flag_use_gz, std::placeholders::_2));
CMD2_ANY_STRING_V("log.close", std::bind(&torrent::log_close_output_str, std::placeholders::_2));
CMD2_ANY_LIST ("log.add_output", std::bind(&apply_log_add_output, std::placeholders::_2));
CMD2_ANY_LIST ("log.print", std::bind(&apply_log_print, std::placeholders::_2));
CMD2_ANY_STRING ("log.execute", std::bind(&apply_log, std::placeholders::_2, 0));
CMD2_ANY_STRING ("log.vmmap.dump", std::bind(&log_vmmap_dump, std::placeholders::_2));
+18
View File
@@ -54,6 +54,14 @@ set_listen_port_range(const std::string& arg) {
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
get_encryption() {
auto encryption_modes = torrent::runtime::network_config()->encryption_modes();
@@ -379,6 +387,13 @@ initialize_command_network() {
CMD_ANY ("network.local_address.ipv6", [nw_config](auto, auto) { return nw_config->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(str); });
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 ("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 ("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 ("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 ("network.proxy.http", [](auto, auto) { return torrent::runtime::proxy_manager()->http_proxy_url(); });
@@ -437,6 +452,9 @@ initialize_command_network() {
rpc::rpc.mark_safe("network.receive_buffer.size");
rpc::rpc.mark_safe("network.bind_address");
rpc::rpc.mark_safe("network.local_address");
rpc::rpc.mark_safe("network.local_port");
rpc::rpc.mark_safe("network.local_port.ipv4");
rpc::rpc.mark_safe("network.local_port.ipv6");
rpc::rpc.mark_safe("network.xmlrpc.size_limit");
rpc::rpc.mark_safe("network.open_sockets");
+365
View File
@@ -0,0 +1,365 @@
#include "config.h"
#include <algorithm>
#include <cctype>
#include <iterator>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include <torrent/exceptions.h>
#include <torrent/object.h>
#include "rpc/parse.h"
#include "rpc/rpc_manager.h"
#include "globals.h"
#include "control.h"
#include "command_helpers.h"
namespace {
constexpr int64_t max_string_pad_size = 1 << 14;
const std::string whitespace_characters = " \t\n\r\f\v";
// The byte offset of every utf-8 character in 'text', terminated by the offset
// past the last character. Bytes that are not valid utf-8 lead bytes are
// treated as single characters.
std::vector<size_t>
utf8_offsets(const std::string& text) {
std::vector<size_t> offsets;
for (size_t i = 0; i < text.size(); i++)
if (i == 0 || (static_cast<unsigned char>(text[i]) & 0xC0) != 0x80)
offsets.push_back(i);
offsets.push_back(text.size());
return offsets;
}
int64_t
utf8_length(const std::string& text) {
int64_t result = 0;
for (size_t i = 0; i < text.size(); i++)
if (i == 0 || (static_cast<unsigned char>(text[i]) & 0xC0) != 0x80)
result++;
return result;
}
std::string
utf8_substr(const std::string& text, const std::vector<size_t>& offsets, size_t first, size_t last) {
return text.substr(offsets[first], offsets[last] - offsets[first]);
}
std::set<std::string>
utf8_character_set(const std::string& text) {
auto offsets = utf8_offsets(text);
std::set<std::string> result;
for (size_t i = 0; i + 1 < offsets.size(); i++)
result.insert(utf8_substr(text, offsets, i, i + 1));
return result;
}
std::string
ascii_lowercase(std::string text) {
std::transform(text.begin(), text.end(), text.begin(), [](unsigned char c) { return std::tolower(c); });
return text;
}
// A 'max_count' of zero means the command accepts any number of arguments.
void
verify_argument_count(const char* name, const torrent::Object::list_type& args, size_t min_count, size_t max_count) {
if (args.size() < min_count || (max_count != 0 && args.size() > max_count))
throw torrent::input_error(std::string(name) + ": invalid number of arguments.");
}
const torrent::Object&
argument_at(const torrent::Object::list_type& args, size_t index) {
return *std::next(args.begin(), index);
}
void
flatten_argument(const torrent::Object& arg, std::vector<std::string>* dest) {
if (!arg.is_list()) {
dest->push_back(rpc::convert_to_string(arg));
return;
}
for (const auto& child : arg.as_list())
flatten_argument(child, dest);
}
// The {old, new} pairs shared by 'string.map' and 'string.replace'.
std::pair<std::string, std::string>
argument_to_pair(const char* name, const torrent::Object& arg) {
if (!arg.is_list() || arg.as_list().size() != 2)
throw torrent::input_error(std::string(name) + ": arguments after the text must be {old, new} pairs.");
return {rpc::convert_to_string(arg.as_list().front()), rpc::convert_to_string(arg.as_list().back())};
}
// Compares the first argument against every remaining argument, returning true
// as soon as one of them matches.
torrent::Object
apply_string_predicate(const char* name, const torrent::Object::list_type& args, bool (*predicate)(const std::string&, const std::string&)) {
verify_argument_count(name, args, 2, 0);
auto text = rpc::convert_to_string(args.front());
for (auto itr = std::next(args.begin()); itr != args.end(); itr++)
if (predicate(text, rpc::convert_to_string(*itr)))
return int64_t(1);
return int64_t(0);
}
bool
text_equals(const std::string& text, const std::string& other) {
return text == other;
}
bool
text_starts_with(const std::string& text, const std::string& prefix) {
return text.size() >= prefix.size() && text.compare(0, prefix.size(), prefix) == 0;
}
bool
text_ends_with(const std::string& text, const std::string& tail) {
return text.size() >= tail.size() && text.compare(text.size() - tail.size(), tail.size(), tail) == 0;
}
bool
text_contains(const std::string& text, const std::string& needle) {
return text.find(needle) != std::string::npos;
}
bool
text_contains_i(const std::string& text, const std::string& needle) {
return ascii_lowercase(text).find(ascii_lowercase(needle)) != std::string::npos;
}
torrent::Object
apply_string_length(const torrent::Object::list_type& args) {
verify_argument_count("string.length", args, 1, 1);
return utf8_length(rpc::convert_to_string(args.front()));
}
torrent::Object
apply_string_substr(const torrent::Object::list_type& args) {
verify_argument_count("string.substr", args, 1, 4);
auto text = rpc::convert_to_string(args.front());
auto offsets = utf8_offsets(text);
auto text_length = static_cast<int64_t>(offsets.size() - 1);
auto position = args.size() > 1 ? rpc::convert_to_value(argument_at(args, 1)) : 0;
auto fallback = args.size() > 3 ? rpc::convert_to_string(argument_at(args, 3)) : std::string();
// Negative positions are relative to the end of the string.
if (position < 0)
position += text_length;
if (position < 0 || position > text_length)
return fallback;
auto last = text_length;
if (args.size() > 2) {
auto count = rpc::convert_to_value(argument_at(args, 2));
if (count < 0)
throw torrent::input_error("string.substr: the character count cannot be negative.");
last = std::min(text_length, position + std::min(count, text_length));
}
return utf8_substr(text, offsets, position, last);
}
torrent::Object
apply_string_split(const torrent::Object::list_type& args) {
verify_argument_count("string.split", args, 2, 2);
auto text = rpc::convert_to_string(args.front());
auto delim = rpc::convert_to_string(args.back());
auto result = torrent::Object::create_list();
// An empty delimiter splits the text into its utf-8 characters.
if (delim.empty()) {
auto offsets = utf8_offsets(text);
for (size_t i = 0; i + 1 < offsets.size(); i++)
result.as_list().push_back(utf8_substr(text, offsets, i, i + 1));
return result;
}
size_t first = 0;
while (true) {
auto pos = text.find(delim, first);
if (pos == std::string::npos) {
result.as_list().push_back(text.substr(first));
return result;
}
result.as_list().push_back(text.substr(first, pos - first));
first = pos + delim.size();
}
}
torrent::Object
apply_string_join(const torrent::Object::list_type& args) {
verify_argument_count("string.join", args, 1, 0);
auto delim = rpc::convert_to_string(args.front());
std::vector<std::string> parts;
for (auto itr = std::next(args.begin()); itr != args.end(); itr++)
flatten_argument(*itr, &parts);
std::string result;
for (auto itr = parts.begin(); itr != parts.end(); itr++) {
if (itr != parts.begin())
result += delim;
result += *itr;
}
return result;
}
torrent::Object
apply_string_pad(const char* name, const torrent::Object::list_type& args, bool pad_start) {
verify_argument_count(name, args, 2, 3);
auto text = rpc::convert_to_string(args.front());
auto pad_size = rpc::convert_to_value(argument_at(args, 1));
auto padding = args.size() > 2 ? rpc::convert_to_string(argument_at(args, 2)) : std::string(" ");
auto text_length = utf8_length(text);
if (pad_size <= text_length || padding.empty())
return text;
if (pad_size - text_length > max_string_pad_size)
throw torrent::input_error(std::string(name) + ": padding is too large.");
auto padding_offsets = utf8_offsets(padding);
auto padding_length = static_cast<int64_t>(padding_offsets.size() - 1);
std::string result;
result.reserve(pad_size - text_length);
for (int64_t i = 0; i < pad_size - text_length; i++) {
auto index = static_cast<size_t>(i % padding_length);
result += utf8_substr(padding, padding_offsets, index, index + 1);
}
return pad_start ? result + text : text + result;
}
torrent::Object
apply_string_strip(const char* name, const torrent::Object::list_type& args, bool strip_start, bool strip_end) {
verify_argument_count(name, args, 1, 0);
auto text = rpc::convert_to_string(args.front());
std::string strippable;
for (auto itr = std::next(args.begin()); itr != args.end(); itr++)
strippable += rpc::convert_to_string(*itr);
if (args.size() == 1)
strippable = whitespace_characters;
auto characters = utf8_character_set(strippable);
auto offsets = utf8_offsets(text);
size_t first = 0;
size_t last = offsets.size() - 1;
while (strip_start && first < last && characters.count(utf8_substr(text, offsets, first, first + 1)) != 0)
first++;
while (strip_end && last > first && characters.count(utf8_substr(text, offsets, last - 1, last)) != 0)
last--;
return utf8_substr(text, offsets, first, last);
}
torrent::Object
apply_string_map(const torrent::Object::list_type& args) {
verify_argument_count("string.map", args, 2, 0);
auto text = rpc::convert_to_string(args.front());
for (auto itr = std::next(args.begin()); itr != args.end(); itr++) {
auto pair = argument_to_pair("string.map", *itr);
if (text == pair.first)
return pair.second;
}
return text;
}
torrent::Object
apply_string_replace(const torrent::Object::list_type& args) {
verify_argument_count("string.replace", args, 2, 0);
auto text = rpc::convert_to_string(args.front());
for (auto itr = std::next(args.begin()); itr != args.end(); itr++) {
auto pair = argument_to_pair("string.replace", *itr);
if (pair.first.empty())
throw torrent::input_error("string.replace: the replaced text cannot be empty.");
for (auto pos = text.find(pair.first); pos != std::string::npos; pos = text.find(pair.first, pos + pair.second.size()))
text.replace(pos, pair.first.size(), pair.second);
}
return text;
}
}
void
initialize_command_string() {
// clang-format off
CMD2_ANY_LIST("string.length", [](auto, const auto& args) { return apply_string_length(args); });
CMD2_ANY_LIST("string.substr", [](auto, const auto& args) { return apply_string_substr(args); });
CMD2_ANY_LIST("string.split", [](auto, const auto& args) { return apply_string_split(args); });
CMD2_ANY_LIST("string.join", [](auto, const auto& args) { return apply_string_join(args); });
CMD2_ANY_LIST("string.map", [](auto, const auto& args) { return apply_string_map(args); });
CMD2_ANY_LIST("string.replace", [](auto, const auto& args) { return apply_string_replace(args); });
CMD2_ANY_LIST("string.equals", [](auto, const auto& args) { return apply_string_predicate("string.equals", args, &text_equals); });
CMD2_ANY_LIST("string.starts_with", [](auto, const auto& args) { return apply_string_predicate("string.starts_with", args, &text_starts_with); });
CMD2_ANY_LIST("string.ends_with", [](auto, const auto& args) { return apply_string_predicate("string.ends_with", args, &text_ends_with); });
CMD2_ANY_LIST("string.contains", [](auto, const auto& args) { return apply_string_predicate("string.contains", args, &text_contains); });
CMD2_ANY_LIST("string.contains_i", [](auto, const auto& args) { return apply_string_predicate("string.contains_i", args, &text_contains_i); });
CMD2_ANY_LIST("string.lpad", [](auto, const auto& args) { return apply_string_pad("string.lpad", args, true); });
CMD2_ANY_LIST("string.rpad", [](auto, const auto& args) { return apply_string_pad("string.rpad", args, false); });
CMD2_ANY_LIST("string.strip", [](auto, const auto& args) { return apply_string_strip("string.strip", args, true, true); });
CMD2_ANY_LIST("string.lstrip", [](auto, const auto& args) { return apply_string_strip("string.lstrip", args, true, false); });
CMD2_ANY_LIST("string.rstrip", [](auto, const auto& args) { return apply_string_strip("string.rstrip", args, false, true); });
// clang-format on
for (const auto name : {"string.length", "string.substr", "string.split", "string.join", "string.map", "string.replace",
"string.equals", "string.starts_with", "string.ends_with", "string.contains", "string.contains_i",
"string.lpad", "string.rpad", "string.strip", "string.lstrip", "string.rstrip"})
rpc::rpc.mark_safe(name);
}
+195
View File
@@ -0,0 +1,195 @@
#include "config.h"
#include <unistd.h>
#include <sys/stat.h>
#include <torrent/data/file_manager.h>
#include <torrent/runtime/client_config.h>
#include <torrent/runtime/memory_manager.h>
#include <torrent/runtime/socket_manager.h>
#include <torrent/runtime/runtime.h>
#include <torrent/utils/chrono.h>
#include <torrent/utils/option_strings.h>
#include "command_helpers.h"
#include "control.h"
#include "globals.h"
#include "core/manager.h"
#include "session/session_manager.h"
#include "utils/file_status_cache.h"
torrent::Object
system_env(const torrent::Object::string_type& arg) {
if (arg.empty())
throw torrent::input_error("system.env: Missing variable name.");
char* val = getenv(arg.c_str());
return std::string(val ? val : "");
}
torrent::Object
system_hostname() {
char buffer[1024];
if (gethostname(buffer, 1023) == -1)
throw torrent::input_error("Unable to read hostname.");
// if (shorten)
// *std::find(buffer, buffer + 1023, '.') = '\0';
return std::string(buffer);
}
torrent::Object
system_get_cwd() {
char* buffer = getcwd(NULL, 0);
if (buffer == NULL)
throw torrent::input_error("Unable to read cwd.");
torrent::Object result = torrent::Object(std::string(buffer));
free(buffer);
return result;
}
torrent::Object
system_set_cwd(const torrent::Object::string_type& rawArgs) {
if (::chdir(rawArgs.c_str()) != 0)
throw torrent::input_error("Could not change current working directory.");
return torrent::Object();
}
uint32_t
checked_socket_value(int64_t value, const char* label) {
if (value < 0 || value > std::numeric_limits<uint32_t>::max())
throw torrent::input_error(std::string("Invalid ") + label + " value.");
return static_cast<uint32_t>(value);
}
void
initialize_command_system() {
CMD_ANY ("system.hostname", std::bind(&system_hostname));
CMD_ANY ("system.pid", std::bind(&getpid));
CMD_VAR_C_STRING("system.api_version", (int64_t)API_VERSION);
CMD_VAR_C_STRING("system.client_version", PACKAGE_VERSION);
CMD_VAR_C_STRING("system.library_version", torrent::runtime::version());
CMD_ANY ("system.torrent_name.use_sanitized", [](auto, auto) { return torrent::runtime::client_config()->torrent_name_use_sanitized(); });
CMD_ANY_VALUE_V ("system.torrent_name.use_sanitized.set", [](auto, auto& value) { return torrent::runtime::client_config()->set_torrent_name_use_sanitized(value); });
CMD_VAR_VALUE ("system.file.allocate", 0);
CMD_VAR_VALUE ("system.file.max_size", (int64_t)512 << 30);
CMD_VAR_VALUE ("system.file.split_size", -1);
CMD_VAR_STRING ("system.file.split_suffix", ".part");
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 ("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_VAR_BOOL ("file.prioritize_toc", 0);
CMD_VAR_LIST ("file.prioritize_toc.first");
CMD_VAR_LIST ("file.prioritize_toc.last");
CMD_ANY ("system.files.advise_random", [](auto, auto) { return torrent::file_manager()->advise_random(); });
CMD_ANY_VALUE_V ("system.files.advise_random.set", [](auto, auto& value) { return torrent::file_manager()->set_advise_random(value); });
CMD_ANY ("system.files.advise_random.hashing", [](auto, auto) { return torrent::file_manager()->advise_random_hashing(); });
CMD_ANY_VALUE_V ("system.files.advise_random.hashing.set", [](auto, auto& value) { return torrent::file_manager()->set_advise_random_hashing(value); });
CMD_ANY ("system.files.session.fdatasync", [](auto, auto) { return session_thread::manager()->use_fsyncdisk(); });
CMD_ANY_VALUE_V ("system.files.session.fdatasync.set", [](auto, auto& value) { return session_thread::manager()->set_use_fsyncdisk(value); });
CMD_ANY ("system.files.close_idle", [](auto, auto) { return torrent::file_manager()->close_idle_timeout().count(); });
CMD_ANY_VALUE_V ("system.files.close_idle.set", [](auto, auto& value) { return torrent::file_manager()->set_close_idle_timeout(value * 1s); });
CMD_ANY ("system.files.opened_counter", [](auto, auto) { return torrent::file_manager()->files_opened_counter(); });
CMD_ANY ("system.files.closed_counter", [](auto, auto) { return torrent::file_manager()->files_closed_counter(); });
CMD_ANY ("system.files.failed_counter", [](auto, auto) { return torrent::file_manager()->files_failed_counter(); });
CMD_ANY_STRING ("system.env", [](auto, auto& str) { return system_env(str); });
CMD_ANY ("system.time", [](auto, auto) { return torrent::this_thread::cached_seconds().count(); });
CMD_ANY ("system.time_seconds", [](auto, auto) { return torrent::utils::cast_seconds(torrent::utils::time_since_epoch()).count(); });
CMD_ANY ("system.time_usec", [](auto, auto) { return torrent::utils::time_since_epoch().count(); });
CMD_ANY_VALUE_V ("system.umask.set", [](auto, auto& value) { return ::umask(value); });
CMD_VAR_BOOL ("system.daemon", false);
CMD_ANY_V ("system.shutdown.normal", [](auto, auto) { control->receive_normal_shutdown(); });
CMD_ANY_V ("system.shutdown.quick", [](auto, auto) { control->receive_quick_shutdown(); });
CMD_REDIRECT_NO_EXPORT("system.shutdown", "system.shutdown.normal");
CMD_ANY ("system.cwd", [](auto, auto) { return system_get_cwd(); });
CMD_ANY_STRING ("system.cwd.set", [](auto, auto& str) { return system_set_cwd(str); });
CMD_ANY ("system.sockets.size", [](auto, auto) { return torrent::runtime::socket_manager()->size(); });
CMD_ANY ("system.sockets.max_size", [](auto, auto) { return torrent::runtime::socket_manager()->max_size(); });
CMD_ANY_VALUE_V ("system.sockets.max_size.set", [](auto, auto& value) {
return torrent::runtime::socket_manager()->set_max_size_and_adjust(checked_socket_value(value, "socket max size"));
});
CMD_ANY_V ("system.sockets.adjust_alloc", [](auto, auto) { torrent::runtime::socket_manager()->adjust_allocation(); });
CMD_ANY ("system.sockets.reserved_alloc", [](auto, auto) { return torrent::runtime::socket_manager()->reserved_allocation(); });
CMD_ANY ("system.sockets.available_alloc", [](auto, auto) { return torrent::runtime::socket_manager()->available_allocation(); });
for (uint32_t i = 0; i < torrent::runtime::SocketManager::category_count; ++i) {
auto category = static_cast<torrent::runtime::socket_manager_category_t>(i);
auto category_name = "system.sockets." + torrent::option_to_str_or_throw(torrent::OPTION_SOCKET_CATEGORY, i);
CMD_ANY (category_name + ".size", [category](auto, auto) { return torrent::runtime::socket_manager()->category_managed_size(category); });
CMD_ANY (category_name + ".max_size", [category](auto, auto) { return torrent::runtime::socket_manager()->category_max_size(category); });
if (i == 0) {
CMD_ANY (category_name + ".min_alloc", [](auto, auto) { return torrent::runtime::socket_manager()->generic_min_allocation(); });
continue;
}
CMD_ANY (category_name + ".max_alloc.limit", [category](auto, auto) { return torrent::runtime::socket_manager()->category_alloc_limit(category); });
CMD_ANY (category_name + ".min_alloc.limit", [category](auto, auto) { return torrent::runtime::socket_manager()->category_alloc_minimum(category); });
CMD_ANY (category_name + ".min_alloc", [category](auto, auto) { return torrent::runtime::socket_manager()->category_min_allocation(category); });
CMD_ANY (category_name + ".max_alloc", [category](auto, auto) { return torrent::runtime::socket_manager()->category_max_allocation(category); });
CMD_ANY_VALUE_V(category_name + ".min_alloc.set", [category](auto, auto& value) {
torrent::runtime::socket_manager()->set_category_min_allocation(category, checked_socket_value(value, "socket min alloc"));
});
CMD_ANY_VALUE_V(category_name + ".max_alloc.set", [category](auto, auto& value) {
torrent::runtime::socket_manager()->set_category_max_allocation(category, checked_socket_value(value, "socket max alloc"));
});
}
rpc::rpc.mark_safe("system.api_version");
rpc::rpc.mark_safe("system.client_version");
rpc::rpc.mark_safe("system.library_version");
rpc::rpc.mark_safe("system.time");
rpc::rpc.mark_safe("system.time_seconds");
rpc::rpc.mark_safe("system.time_usec");
rpc::rpc.mark_safe("system.file.max_size");
rpc::rpc.mark_safe("system.file.split_size");
rpc::rpc.mark_safe("system.file.split_suffix");
rpc::rpc.mark_safe("system.sockets.size");
rpc::rpc.mark_safe("system.sockets.max_size");
rpc::rpc.mark_safe("system.sockets.reserved_alloc");
rpc::rpc.mark_safe("system.sockets.available_alloc");
for (uint32_t i = 0; i < torrent::runtime::SocketManager::category_count; ++i) {
auto category_name = "system.sockets." + torrent::option_to_str_or_throw(torrent::OPTION_SOCKET_CATEGORY, i);
rpc::rpc.mark_safe(category_name + ".size");
rpc::rpc.mark_safe(category_name + ".max_size");
if (i == 0) {
rpc::rpc.mark_safe(category_name + ".min_alloc");
continue;
}
rpc::rpc.mark_safe(category_name + ".max_alloc.limit");
rpc::rpc.mark_safe(category_name + ".min_alloc.limit");
rpc::rpc.mark_safe(category_name + ".min_alloc");
rpc::rpc.mark_safe(category_name + ".max_alloc");
}
}
+2 -2
View File
@@ -68,11 +68,11 @@ apply_dht_add_node(const std::string& arg) {
torrent::Object
apply_enable_trackers(int64_t arg) {
if (arg == 0) {
for (auto download : *control->core()->download_list())
for (const auto& download : *control->core()->download_list())
download->tracker_controller().for_each([](auto& tracker) { tracker.disable(); });
} else {
for (auto download : *control->core()->download_list())
for (const auto& download : *control->core()->download_list())
download->tracker_controller().for_each([](auto& tracker) { tracker.enable(); });
}
+11 -3
View File
@@ -3,6 +3,7 @@
#include <sys/types.h>
#include <ctime>
#include <limits>
#include <regex>
#include <torrent/utils/log.h>
@@ -407,8 +408,9 @@ apply_to_time(const torrent::Object& rawArgs, int flags) {
torrent::Object
apply_to_elapsed_time(const torrent::Object& rawArgs) {
auto cached_seconds = torrent::this_thread::cached_seconds().count();
auto value = rawArgs.as_value();
uint64_t arg = cached_seconds - rawArgs.as_value();
uint64_t arg = value >= 0 && value <= cached_seconds ? cached_seconds - value : 0;
char buffer[48];
snprintf(buffer, 48, "%2d:%02d:%02d", (int)(arg / 3600), (int)((arg / 60) % 60), (int)(arg % 60));
@@ -674,8 +676,13 @@ apply_math_basic(const char* name, Comp op, const torrent::Object::list_type& ar
throw torrent::input_error(std::string(name) + ": Wrong argument type");
}
if (divides && !rhs && itr != args.begin())
throw torrent::input_error(std::string(name) + ": Division by zero!");
if (divides && itr != args.begin()) {
if (rhs == 0)
throw torrent::input_error(std::string(name) + ": Division by zero!");
if (val == std::numeric_limits<int64_t>::min() && rhs == -1)
throw torrent::input_error(std::string(name) + ": Division overflow!");
}
val = itr == args.begin() ? rhs : op(val, rhs);
@@ -916,6 +923,7 @@ initialize_command_ui() {
rpc::rpc.mark_safe("view.set_visible");
rpc::rpc.mark_safe("view.set_not_visible");
rpc::rpc.mark_safe("view.list");
rpc::rpc.mark_safe("cat");
rpc::rpc.mark_safe("if");
+5 -3
View File
@@ -6,7 +6,7 @@
#include <memory>
#include <sys/types.h>
#include <torrent/torrent.h>
#include <torrent/utils/scheduler.h>
#include <torrent/system/scheduler.h>
namespace ui {
class Root;
@@ -107,8 +107,8 @@ private:
mode_t m_umask;
std::string m_workingDirectory;
torrent::utils::SchedulerEntry m_task_shutdown;
torrent::utils::SchedulerEntry m_task_shutdown_clear_requests;
torrent::system::SchedulerEntry m_task_shutdown;
torrent::system::SchedulerEntry m_task_shutdown_clear_requests;
int m_clear_requests_count{};
@@ -118,4 +118,6 @@ private:
std::atomic<bool> m_shutdown_quick{};
};
extern Control* control;
#endif
+3 -3
View File
@@ -2,7 +2,7 @@
#define RTORRENT_CORE_DHT_MANAGER_H
#include <torrent/object.h>
#include <torrent/utils/scheduler.h>
#include <torrent/system/scheduler.h>
namespace core {
@@ -42,8 +42,8 @@ private:
uint64_t m_dhtPrevBytesUp;
uint64_t m_dhtPrevBytesDown;
torrent::utils::SchedulerEntry m_update_timeout;
torrent::utils::SchedulerEntry m_stop_timeout;
torrent::system::SchedulerEntry m_update_timeout;
torrent::system::SchedulerEntry m_stop_timeout;
bool m_warned{};
bool m_set_by_user{};
+13 -3
View File
@@ -101,7 +101,18 @@ Download::set_throttle_name(const std::string& name) {
}
void
Download::set_root_directory(const std::string& path) {
Download::set_directory(const std::string& path) {
if (!m_download.file_list()->is_multi_file())
return set_base_directory(path);
if (path.empty() || *path.rbegin() == '/')
return set_base_directory(path + m_download.info()->name_sanitized());
set_base_directory(path + "/" + m_download.info()->name_sanitized());
}
void
Download::set_base_directory(const std::string& path) {
// If the download is open, hashed and has completed chunks make
// sure to verify that the download files are still present.
//
@@ -120,13 +131,12 @@ Download::set_root_directory(const std::string& path) {
!file_stat.update(file_list->front()->frozen_path().str()))) {
set_message("Cannot change the directory of an open download after the files have been moved.");
rpc::call_command("d.state.set", (int64_t)0, rpc::make_target(this));
control->core()->download_list()->close_directly(this);
throw torrent::input_error("Cannot change the directory of an open download after the files have been moved.");
}
control->core()->download_list()->close_directly(this);
control->core()->download_list()->close_files(this);
file_list->set_root_dir(expand_path(path));
bencode()->get_key("rtorrent").insert_key("directory", path);
+3 -1
View File
@@ -1,6 +1,7 @@
#ifndef RTORRENT_CORE_DOWNLOAD_H
#define RTORRENT_CORE_DOWNLOAD_H
#include <memory>
#include <torrent/common.h>
#include <torrent/download.h>
#include <torrent/download_info.h>
@@ -78,7 +79,8 @@ public:
uint32_t resume_flags() { return m_resumeFlags; }
void set_resume_flags(uint32_t flags) { m_resumeFlags = flags; }
void set_root_directory(const std::string& path);
void set_directory(const std::string& path);
void set_base_directory(const std::string& path);
void set_throttle_name(const std::string& name);
+239 -76
View File
@@ -27,6 +27,8 @@
namespace core {
static constexpr const char* session_invalid_message = "Session data is invalid, ignoring it";
bool
is_network_uri(const std::string& uri) {
return
@@ -35,8 +37,16 @@ is_network_uri(const std::string& uri) {
std::strncmp(uri.c_str(), "ftp://", 6) == 0;
}
static std::unique_ptr<torrent::Object>
download_factory_load_stream(const char* filename) {
bool
is_magnet_uri(const std::string& uri) {
return
std::strncmp(uri.c_str(), "magnet:?", 8) == 0;
}
namespace {
std::unique_ptr<torrent::Object>
download_factory_load_stream(const char* filename, bool* is_invalid) {
std::fstream stream(filename, std::ios::in | std::ios::binary);
if (!stream.is_open())
@@ -45,23 +55,42 @@ download_factory_load_stream(const char* filename) {
auto obj = std::make_unique<torrent::Object>();
stream >> *obj;
if (!stream.good())
if (!stream.good() || !obj->is_map()) {
*is_invalid = true;
return std::unique_ptr<torrent::Object>();
}
return obj;
}
bool
is_magnet_uri(const std::string& uri) {
return
std::strncmp(uri.c_str(), "magnet:?", 8) == 0;
std::unique_ptr<torrent::Object>
create_untrusted_object(torrent::Object& obj) {
auto trusted_object = std::make_unique<torrent::Object>(torrent::Object::create_map());
if (obj.has_key("info"))
trusted_object->insert_key_move("info", obj.get_key("info"));
if (obj.has_key("announce"))
trusted_object->insert_key_move("announce", obj.get_key("announce"));
if (obj.has_key("announce-list"))
trusted_object->insert_key_move("announce-list", obj.get_key("announce-list"));
if (obj.has_key("creation date"))
trusted_object->insert_key_move("creation date", obj.get_key("creation date"));
if (obj.has_key("created by"))
trusted_object->insert_key_move("created by", obj.get_key("created by"));
return trusted_object;
}
DownloadFactory::DownloadFactory(Manager* m) :
m_manager(m) {
} // namespace anonymous
m_task_load.slot() = std::bind(&DownloadFactory::receive_load, this);
m_task_commit.slot() = std::bind(&DownloadFactory::receive_commit, this);
DownloadFactory::DownloadFactory(Manager* m, bool trusted)
: m_manager(m),
m_trusted(trusted) {
// m_variables["connection_leech"] = rpc::call_command("protocol.connection.leech");
// m_variables["connection_seed"] = rpc::call_command("protocol.connection.seed");
@@ -74,21 +103,49 @@ DownloadFactory::DownloadFactory(Manager* m) :
DownloadFactory::~DownloadFactory() {
torrent::this_thread::scheduler()->erase(&m_task_load);
torrent::this_thread::scheduler()->erase(&m_task_commit);
delete m_object;
}
void
DownloadFactory::load(const std::string& uri) {
DownloadFactory::load_trusted(const std::string& uri) {
if (!m_trusted)
throw torrent::internal_error("DownloadFactory::load_trusted() called on an untrusted object");
m_uri = uri;
m_task_load.slot() = [this]() { receive_load_trusted(); };
torrent::this_thread::scheduler()->wait_for(&m_task_load, 0ms);
}
// This function must be called before DownloadFactory::commit().
void
DownloadFactory::load_raw_data(const std::string& input) {
DownloadFactory::load_untrusted(const std::string& uri) {
if (m_trusted)
throw torrent::internal_error("DownloadFactory::load_untrusted() called on a trusted object");
m_uri = uri;
m_task_load.slot() = [this]() { receive_load_untrusted(); };
torrent::this_thread::scheduler()->wait_for(&m_task_load, 0ms);
}
void
DownloadFactory::load_raw_data_trusted(const std::string& input) {
if (!m_trusted)
throw torrent::internal_error("DownloadFactory::load_raw_data_trusted() called on an untrusted object");
if (m_stream)
throw torrent::internal_error("DownloadFactory::load*() called on an object with m_stream != NULL");
throw torrent::internal_error("DownloadFactory::load_raw_data_trusted() called on an object with m_stream != NULL");
m_stream.reset(new std::stringstream(input));
m_loaded = true;
}
void
DownloadFactory::load_raw_data_untrusted(const std::string& input) {
if (m_trusted)
throw torrent::internal_error("DownloadFactory::load_raw_data_untrusted() called on an trusted object");
if (m_stream)
throw torrent::internal_error("DownloadFactory::load_raw_data_untrusted() called on an object with m_stream != NULL");
m_stream.reset(new std::stringstream(input));
m_loaded = true;
@@ -96,53 +153,91 @@ DownloadFactory::load_raw_data(const std::string& input) {
void
DownloadFactory::commit() {
if (!m_trusted)
throw torrent::internal_error("DownloadFactory::commit() called on an untrusted object");
m_task_commit.slot() = [this]() { receive_commit(); };
torrent::this_thread::scheduler()->wait_for(&m_task_commit, 0ms);
}
void
DownloadFactory::receive_load() {
if (m_stream)
throw torrent::internal_error("DownloadFactory::load*() called on an object with m_stream != NULL");
DownloadFactory::process_load_network_uri() {
m_stream.reset(new std::stringstream);
if (is_network_uri(m_uri)) {
m_stream.reset(new std::stringstream);
// TODO: Add trusted flag.
auto done_fn = [this]() { receive_loaded(); };
auto failed_fn = [this](const std::string& error) { receive_failed(error); };
auto done_fn = [this]() { receive_loaded(); };
auto failed_fn = [this](const std::string& error) { receive_failed(error); };
m_manager->http_queue()->insert(m_uri, m_stream, done_fn, failed_fn);
m_manager->http_queue()->insert(m_uri, m_stream, done_fn, failed_fn);
m_variables["tied_to_file"] = (int64_t)false;
return;
}
m_variables["tied_to_file"] = (int64_t)false;
}
if (is_magnet_uri(m_uri)) {
// DEBUG: Use m_object.
m_stream.reset(new std::stringstream());
*m_stream << "d10:magnet-uri" << m_uri.length() << ":" << m_uri << "e";
void
DownloadFactory::process_load_magnet_uri() {
m_stream.reset(new std::stringstream());
*m_stream << "d10:magnet-uri" << m_uri.length() << ":" << m_uri << "e";
m_variables["tied_to_file"] = (int64_t)false;
m_variables["tied_to_file"] = (int64_t)false;
receive_loaded();
return;
}
receive_loaded();
}
void
DownloadFactory::process_load_file_uri() {
std::fstream stream(expand_path(m_uri).c_str(), std::ios::in | std::ios::binary);
if (!stream.is_open())
return receive_failed("Could not open file");
m_object = new torrent::Object;
m_object = std::make_unique<torrent::Object>();
stream >> *m_object;
if (!stream.good())
return receive_failed("Reading torrent file failed");
m_isFile = true;
m_is_file = true;
receive_loaded();
}
void
DownloadFactory::receive_load_trusted() {
if (!m_trusted)
throw torrent::internal_error("DownloadFactory::receive_load_trusted() called on an untrusted object");
if (m_stream)
throw torrent::internal_error("DownloadFactory::receive_load_trusted() called on an object with null m_stream");
if (is_network_uri(m_uri))
return process_load_network_uri();
if (is_magnet_uri(m_uri))
return process_load_magnet_uri();
process_load_file_uri();
}
void
DownloadFactory::receive_load_untrusted() {
if (m_trusted)
throw torrent::internal_error("DownloadFactory::receive_load_untrusted() called on a trusted object");
if (m_stream)
throw torrent::internal_error("DownloadFactory::load*() called on an object with null m_stream");
if (is_network_uri(m_uri))
return process_load_network_uri();
// TODO: Don't need to handle untrusted commands magnet URIs.
// if (is_magnet_uri(m_uri))
// return process_load_magnet_uri();
throw torrent::internal_error("DownloadFactory::receive_load_untrusted() called on a non-network/magnet URI");
}
void
DownloadFactory::receive_loaded() {
m_loaded = true;
@@ -161,58 +256,91 @@ DownloadFactory::receive_commit() {
void
DownloadFactory::receive_success() {
auto rtorrent_object = download_factory_load_stream((expand_path(m_uri) + ".rtorrent").c_str());
auto libtorrent_resume_object = download_factory_load_stream((expand_path(m_uri) + ".libtorrent_resume").c_str());
if (!m_trusted && m_session)
throw torrent::internal_error("DownloadFactory::receive_success() called on an untrusted object with m_session == true");
uint32_t tracker_key;
if (m_session && !m_is_file)
throw torrent::internal_error("DownloadFactory::receive_success() called on a non-file object with m_session == true");
if (rtorrent_object && rtorrent_object->has_key_value("key"))
tracker_key = rtorrent_object->get_key_value("key");
else
tracker_key = random() % (std::numeric_limits<uint32_t>::max() - 1) + 1;
bool session_invalid = false;
uint32_t tracker_key = tracker_key = random() % (std::numeric_limits<uint32_t>::max() - 1) + 1;
Download* download = m_stream != nullptr ?
m_manager->download_list()->create(m_stream.get(), tracker_key, m_printLog) :
m_manager->download_list()->create(m_object, tracker_key, m_printLog);
std::unique_ptr<torrent::Object> rtorrent_object, libtorrent_resume_object;
m_object = NULL;
if (m_session) {
rtorrent_object = download_factory_load_stream((expand_path(m_uri) + ".rtorrent").c_str(), &session_invalid);
libtorrent_resume_object = download_factory_load_stream((expand_path(m_uri) + ".libtorrent_resume").c_str(), &session_invalid);
if (download == NULL) {
// core::Manager should already have added the error message to
// the log.
if (session_invalid)
lt_log_print(torrent::LOG_ERROR, "%s: %s", session_invalid_message, m_uri.c_str());
if (rtorrent_object && rtorrent_object->has_key_value("key"))
tracker_key = rtorrent_object->get_key_value("key");
}
// TODO: This adds the torrent, so untrusted can add brokent torrents.
if (m_stream != nullptr)
object_from_stream();
if (m_trusted)
m_object = create_untrusted_object(*m_object);
Download* download = m_manager->download_list()->create(std::move(m_object), tracker_key, m_print_log);
if (download == nullptr) {
// core::Manager should already have added the error message to the log.
m_slot_finished();
return;
}
if (session_invalid) {
download->set_hash_failed(true);
download->set_message(session_invalid_message);
if (m_print_log)
m_manager->push_log_std(std::string(session_invalid_message) + ": \"" + m_uri + "\"");
}
torrent::Object* root = download->bencode();
if (download->download()->info()->is_meta_download()) {
if (!m_trusted)
throw torrent::internal_error("DownloadFactory::receive_success() called on an untrusted object with a meta download");
torrent::Object& meta = root->insert_key("rtorrent_meta_download", torrent::Object::create_map());
meta.insert_key("start", m_start);
meta.insert_key("print_log", m_printLog);
meta.insert_key("print_log", m_print_log);
torrent::Object::list_type& commands = meta.insert_key("commands", torrent::Object::create_list()).as_list();
// TODO: ADD UNTRUSTED!!!
auto& commands = meta.insert_key("commands", torrent::Object::create_list()).as_list();
for (auto& m_command : m_commands)
commands.push_back(m_command);
}
if (m_session) {
if (!m_trusted)
throw torrent::internal_error("DownloadFactory::receive_success() called on an untrusted object with m_session == true");
if (rtorrent_object)
root->insert_key_move("rtorrent", *rtorrent_object);
if (libtorrent_resume_object)
root->insert_key_move("libtorrent_resume", *libtorrent_resume_object);
} else if (!m_trusted) {
if (root->has_key("rtorrent") || root->has_key("libtorrent_resume"))
throw torrent::internal_error("DownloadFactory::receive_success() called on an untrusted object with 'rtorrent' or 'libtorrent_resume' keys");
} else {
// We only allow session torrents to keep their
// 'rtorrent/libtorrent' sections. The "fast_resume" section
// should be safe to keep.
// We only allow session torrents to keep their 'rtorrent/libtorrent' sections. The
// "fast_resume" section should be safe to keep.
root->erase_key("rtorrent");
}
torrent::Object* rtorrent = &root->insert_preserve_copy("rtorrent", torrent::Object::create_map()).first->second;
torrent::Object& resumeObject = root->insert_preserve_copy("libtorrent_resume", torrent::Object::create_map()).first->second;
auto* rtorrent = &root->insert_preserve_copy("rtorrent", torrent::Object::create_map()).first->second;
auto& resumeObject = root->insert_preserve_copy("libtorrent_resume", torrent::Object::create_map()).first->second;
rtorrent->insert_key("key", download->tracker_controller().key());
@@ -242,7 +370,7 @@ DownloadFactory::receive_success() {
}
// Skip forcing trackers to scrape when rtorrent starts
if (m_initLoad && rpc::call_command_value("trackers.delay_scrape"))
if (m_init_load && rpc::call_command_value("trackers.delay_scrape"))
download->set_resume_flags(torrent::Download::start_skip_tracker);
// Check first if we already have these values set in the session
@@ -274,9 +402,22 @@ DownloadFactory::receive_success() {
rpc::call_command("d.peer_exchange.set", torrent::runtime::client_config()->is_pex_enabled(), rpc::make_target(download));
torrent::resume_load_addresses(*download->download(), resumeObject);
torrent::resume_load_file_priorities(*download->download(), resumeObject);
torrent::resume_load_tracker_settings(*download->download(), resumeObject);
try {
torrent::resume_load_addresses(*download->download(), resumeObject);
torrent::resume_load_file_priorities(*download->download(), resumeObject);
torrent::resume_load_tracker_settings(*download->download(), resumeObject);
} catch (const torrent::input_error& e) {
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());
if (m_print_log)
m_manager->push_log_std(msg + ": \"" + m_uri + "\"");
download->set_hash_failed(true);
download->set_message(msg);
}
// The action of inserting might cause the torrent to be
// opened/started or such. Figure out a nicer way of handling this.
@@ -295,8 +436,13 @@ DownloadFactory::receive_success() {
if (torrent::log_groups[torrent::LOG_TORRENT_DEBUG].valid())
log_created(download, rtorrent);
for (const auto& command : m_commands)
rpc::parse_command_multiple_std(command, rpc::make_target(download));
if (m_trusted) {
for (const auto& command : m_commands)
rpc::parse_command_multiple_std(command, rpc::make_target(download));
} else {
// TODO: CALL UNTRUSTED COMMANDS
}
if (m_manager->download_list()->find(infohash) == m_manager->download_list()->end())
throw torrent::input_error("The newly created download was removed.");
@@ -310,7 +456,7 @@ DownloadFactory::receive_success() {
} catch (torrent::input_error& e) {
std::string msg = "Command on torrent creation failed: " + std::string(e.what());
if (m_printLog)
if (m_print_log)
m_manager->push_log_std(msg);
if (m_manager->download_list()->find(infohash) != m_manager->download_list()->end()) {
@@ -324,6 +470,18 @@ DownloadFactory::receive_success() {
m_slot_finished();
}
void
DownloadFactory::receive_failed(const std::string& msg) {
if (m_print_log) {
if (m_trusted)
m_manager->push_log_std("Failed to load torrent: " + msg + ": \"" + m_uri + "\"");
else
m_manager->push_log_std("Failed to load untrusted torrent: " + msg);
}
m_slot_finished();
}
void
DownloadFactory::log_created(Download* download, torrent::Object* rtorrent) {
std::stringstream dump;
@@ -350,15 +508,6 @@ DownloadFactory::log_created(Download* download, torrent::Object* rtorrent) {
lt_log_print_dump(torrent::LOG_TORRENT_DEBUG, dump_str.c_str(), dump_str.size(), "Creating new download:");
}
void
DownloadFactory::receive_failed(const std::string& msg) {
// Add message to log.
if (m_printLog)
m_manager->push_log_std(msg + ": \"" + m_uri + "\"");
m_slot_finished();
}
void
DownloadFactory::initialize_rtorrent(Download* download, torrent::Object* rtorrent) {
auto cached_seconds = torrent::this_thread::cached_seconds().count();
@@ -382,7 +531,7 @@ DownloadFactory::initialize_rtorrent(Download* download, torrent::Object* rtorre
rtorrent->insert_preserve_copy("timestamp.finished", (int64_t)0);
rtorrent->insert_preserve_copy("tied_to_file", "");
rtorrent->insert_key("loaded_file", m_isFile ? m_uri : std::string());
rtorrent->insert_key("loaded_file", m_is_file ? m_uri : std::string());
if (rtorrent->has_key_value("priority"))
rpc::call_command("d.priority.set", rtorrent->get_key_value("priority") % 4, rpc::make_target(download));
@@ -414,4 +563,18 @@ DownloadFactory::initialize_rtorrent(Download* download, torrent::Object* rtorre
rtorrent->insert_preserve_copy("choke_heuristics.down.seed", std::string());
}
void
DownloadFactory::object_from_stream() {
m_object = std::make_unique<torrent::Object>();
*m_stream >> *m_object;
if (!m_stream->good() || !m_object->is_map()) {
if (m_print_log)
lt_log_print(torrent::LOG_TORRENT_ERROR, "Could not create download, stream is not a valid torrent.");
throw torrent::input_error("Invalid torrent data");
}
}
}
+31 -18
View File
@@ -9,7 +9,7 @@
#include <iosfwd>
#include <torrent/object.h>
#include <torrent/utils/scheduler.h>
#include <torrent/system/scheduler.h>
#include "http_queue.h"
@@ -24,14 +24,18 @@ public:
typedef std::vector<std::string> command_list_type;
// Do not destroy this object while it is in a HttpQueue.
DownloadFactory(Manager* m);
DownloadFactory(Manager* m, bool trusted);
~DownloadFactory();
// Calling of receive_load() is delayed so you can change whatever
// you want without fear of the slots being triggered as you call
// load() or commit().
void load(const std::string& uri);
void load_raw_data(const std::string& input);
void load_trusted(const std::string& uri);
void load_untrusted(const std::string& uri);
void load_raw_data_trusted(const std::string& input);
void load_raw_data_untrusted(const std::string& input);
void commit();
command_list_type& commands() { return m_commands; }
@@ -43,16 +47,21 @@ public:
bool get_start() const { return m_start; }
void set_start(bool v) { m_start = v; }
bool get_init_load() const { return m_initLoad; }
void set_init_load(bool v) { m_initLoad = v; }
bool get_init_load() const { return m_init_load; }
void set_init_load(bool v) { m_init_load = v; }
bool print_log() const { return m_printLog; }
void set_print_log(bool v) { m_printLog = v; }
bool print_log() const { return m_print_log; }
void set_print_log(bool v) { m_print_log = v; }
void slot_finished(slot_void s) { m_slot_finished = s; }
private:
void receive_load();
void process_load_network_uri();
void process_load_magnet_uri();
void process_load_file_uri();
void receive_load_trusted();
void receive_load_untrusted();
void receive_loaded();
void receive_commit();
void receive_success();
@@ -62,26 +71,30 @@ private:
void initialize_rtorrent(Download* download, torrent::Object* rtorrent);
Manager* m_manager;
std::shared_ptr<std::iostream> m_stream;
torrent::Object* m_object{};
void object_from_stream();
Manager* m_manager;
std::shared_ptr<std::iostream> m_stream;
std::unique_ptr<torrent::Object> m_object{};
bool m_trusted{};
bool m_commited{};
bool m_loaded{};
std::string m_uri;
bool m_session{};
bool m_start{};
bool m_printLog{true};
bool m_isFile{};
bool m_initLoad{};
bool m_print_log{true};
bool m_is_file{};
bool m_init_load{};
command_list_type m_commands;
torrent::Object::map_type m_variables;
slot_void m_slot_finished;
torrent::utils::SchedulerEntry m_task_load;
torrent::utils::SchedulerEntry m_task_commit;
slot_void m_slot_finished;
torrent::system::SchedulerEntry m_task_load;
torrent::system::SchedulerEntry m_task_commit;
};
bool is_network_uri(const std::string& uri);
+59 -61
View File
@@ -37,7 +37,7 @@ namespace core {
inline void
DownloadList::check_contains([[maybe_unused]] Download* d) {
#ifdef USE_EXTRA_DEBUG
if (std::find(begin(), end(), d) == end())
if (std::find_if(begin(), end(), [d](const auto& entry) { return entry.get() == d; }) == end())
throw torrent::internal_error("DownloadList::check_contains(...) failed.");
#endif
}
@@ -54,7 +54,6 @@ DownloadList::clear() {
base_type::pop_back();
torrent::download_remove(*download->download());
delete download;
} catch (torrent::internal_error& e) {
lt_log_print(torrent::LOG_ERROR, "DownloadList::clear() failed to close or remove download: %s", e.what());
@@ -70,7 +69,7 @@ DownloadList::clear() {
void
DownloadList::session_save() {
for (auto& download : *this)
session_thread::manager()->save_resume_download(download);
session_thread::manager()->save_resume_download(download.get());
control->dht_manager()->save_dht_cache();
control->ui()->save_input_history();
@@ -78,7 +77,7 @@ DownloadList::session_save() {
DownloadList::iterator
DownloadList::find(const torrent::HashString& hash) {
return std::find_if(begin(), end(), [hash](Download* d) { return hash == d->info()->hash(); });
return std::find_if(begin(), end(), [hash](const auto& d) { return hash == d->info()->hash(); });
}
DownloadList::iterator
@@ -91,60 +90,28 @@ DownloadList::find_hex(const char* hash) {
if (torrent::utils::transform_from_hex(hash, hash + 40, key) != key.end())
return end();
return std::find_if(begin(), end(), [key](Download* d) { return key == d->info()->hash(); });
return std::find_if(begin(), end(), [key](const auto& d) { return key == d->info()->hash(); });
}
Download*
DownloadList::find_hex_ptr(const char* hash) {
iterator itr = find_hex(hash);
return itr != end() ? *itr : NULL;
return itr != end() ? itr->get() : NULL;
}
Download*
DownloadList::create(torrent::Object* obj, uint32_t tracker_key, bool printLog) {
DownloadList::create(std::unique_ptr<torrent::Object> obj, uint32_t tracker_key, bool printLog) {
torrent::Download download;
try {
download = torrent::download_add(obj, tracker_key);
} catch (torrent::local_error& e) {
if (printLog)
lt_log_print(torrent::LOG_TORRENT_ERROR, "Could not create download: %s", e.what());
delete obj;
return NULL;
}
// There's no non-critical exceptions that should be throwable by
// the ctor, so don't catch.
return new Download(download);
}
Download*
DownloadList::create(std::istream* str, uint32_t tracker_key, bool printLog) {
torrent::Object* object = new torrent::Object;
torrent::Download download;
auto obj_ptr = obj.get();
try {
*str >> *object;
// Don't throw input_error from here as gcc-3.3.5 produces bad
// code.
if (str->fail()) {
delete object;
if (printLog)
lt_log_print(torrent::LOG_TORRENT_ERROR, "Could not create download, the input is not a valid torrent.");
return NULL;
}
download = torrent::download_add(object, tracker_key);
download = torrent::download_add(obj.release(), tracker_key);
} catch (torrent::local_error& e) {
delete object;
// TODO: Fix download_add so it takes the unique_ptr.
delete obj_ptr;
if (printLog)
lt_log_print(torrent::LOG_TORRENT_ERROR, "Could not create download: %s", e.what());
@@ -159,7 +126,7 @@ DownloadList::create(std::istream* str, uint32_t tracker_key, bool printLog) {
DownloadList::iterator
DownloadList::insert(Download* download) {
iterator itr = base_type::insert(end(), 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.");
@@ -170,7 +137,7 @@ DownloadList::insert(Download* download) {
// This needs to be separated into two different calls to ensure
// the download remains in the view.
for (auto v : *control->view_manager())
v->insert(download);
v->insert(*itr);
for (auto v : *control->view_manager())
v->filter_download(download);
@@ -187,7 +154,7 @@ DownloadList::insert(Download* download) {
void
DownloadList::erase_ptr(Download* download) {
erase(std::find(begin(), end(), download));
erase(std::find_if(begin(), end(), [download](const auto& entry) { return entry.get() == download; }));
}
DownloadList::iterator
@@ -201,15 +168,14 @@ DownloadList::erase(iterator itr) {
(*itr)->set_hash_failed(true);
close(*itr);
session_thread::manager()->remove_download(*itr);
session_thread::manager()->remove_download(itr->get());
DL_TRIGGER_EVENT(*itr, "event.download.erased");
for (auto v : *control->view_manager())
v->erase(*itr);
v->erase(itr->get());
torrent::download_remove(*(*itr)->download());
delete *itr;
return base_type::erase(itr);
}
@@ -256,9 +222,11 @@ DownloadList::close(Download* download) {
}
}
// Releases the files without changing the download's state, for callers that
// need the files closed and will keep using the download.
void
DownloadList::close_directly(Download* download) {
lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Closing download directly.");
DownloadList::close_files(Download* download) {
lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Closing download files.");
if (download->download()->info()->is_active()) {
download->download()->stop(torrent::Download::stop_skip_tracker);
@@ -271,6 +239,44 @@ DownloadList::close_directly(Download* download) {
download->download()->close();
}
void
DownloadList::close_directly(Download* download) {
lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Closing download directly.");
bool was_active = download->download()->info()->is_active();
bool was_open = download->download()->info()->is_open();
close_files(download);
set_state_stopped(download);
if (was_active) {
DL_TRIGGER_EVENT(download, "event.download.paused");
update_paused_state(download);
}
if (was_open) {
DL_TRIGGER_EVENT(download, "event.download.hash_removed");
DL_TRIGGER_EVENT(download, "event.download.closed");
}
}
void
DownloadList::set_state_stopped(Download* download) {
control->view_manager()->find_ptr_throw("stopped")->set_visible(download);
rpc::call_command("d.state.set", (int64_t)0, rpc::make_target(download));
}
void
DownloadList::update_paused_state(Download* download) {
rpc::call_command("d.state_changed.set", torrent::this_thread::cached_seconds().count(), rpc::make_target(download));
rpc::call_command("d.state_counter.set", rpc::call_command_value("d.state_counter", rpc::make_target(download)), rpc::make_target(download));
// If initial seeding is complete, don't try it again when restarting.
if (download->is_done() &&
rpc::call_command("d.connection_current", torrent::Object(), rpc::make_target(download)).as_string() == "initial_seed")
rpc::call_command("d.connection_seed.set", rpc::call_command("d.connection_current", torrent::Object(), rpc::make_target(download)), rpc::make_target(download));
}
void
DownloadList::close_quick(Download* download) {
lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Closing download quickly.");
@@ -445,15 +451,7 @@ DownloadList::pause(Download* download, int flags) {
// view.
DL_TRIGGER_EVENT(download, "event.download.paused");
auto cached_seconds = torrent::this_thread::cached_seconds().count();
rpc::call_command("d.state_changed.set", cached_seconds, rpc::make_target(download));
rpc::call_command("d.state_counter.set", rpc::call_command_value("d.state_counter", rpc::make_target(download)), rpc::make_target(download));
// If initial seeding is complete, don't try it again when restarting.
if (download->is_done() &&
rpc::call_command("d.connection_current", torrent::Object(), rpc::make_target(download)).as_string() == "initial_seed")
rpc::call_command("d.connection_seed.set", rpc::call_command("d.connection_current", torrent::Object(), rpc::make_target(download)), rpc::make_target(download));
update_paused_state(download);
// Save the state after all the slots, etc have been called so we
// include the modifications they may make.
@@ -524,7 +522,7 @@ DownloadList::hash_done(Download* download) {
// If the download was previously completed but the files were
// f.ex deleted, then we clear the state and complete.
if (rpc::call_command_value("d.complete", rpc::make_target(download)) && !download->is_done()) {
rpc::call_command("d.state.set", (int64_t)0, rpc::make_target(download));
set_state_stopped(download);
download->set_message("Download registered as completed, but hash check returned unfinished chunks.");
}
+16 -4
View File
@@ -3,6 +3,7 @@
#include <iosfwd>
#include <list>
#include <memory>
#include <string>
namespace torrent {
@@ -20,9 +21,9 @@ class Download;
//
// Fix apply_on_ratio if the base_type is changed.
class DownloadList : private std::list<Download*> {
class DownloadList : private std::list<std::shared_ptr<Download>> {
public:
typedef std::list<Download*> base_type;
typedef std::list<std::shared_ptr<Download>> base_type;
using base_type::iterator;
using base_type::const_iterator;
@@ -51,8 +52,7 @@ public:
Download* find_hex_ptr(const char* hash);
// Might move this to DownloadFactory.
Download* create(std::istream* str, uint32_t tracker_key, bool printLog);
Download* create(torrent::Object* obj, uint32_t tracker_key, bool printLog);
Download* create(std::unique_ptr<torrent::Object> obj, uint32_t tracker_key, bool printLog);
iterator insert(Download* d);
@@ -64,8 +64,17 @@ public:
bool open(Download* d);
void open_throw(Download* d);
// Overloads for the list's own entries, so callers iterating it do not need
// to unwrap.
bool open(const value_type& d) { return open(d.get()); }
void close(const value_type& d) { close(d.get()); }
void close_quick(const value_type& d) { close_quick(d.get()); }
void pause(const value_type& d) { pause(d.get()); }
void resume(const value_type& d) { resume(d.get()); }
void close(Download* d);
void close_directly(Download* d);
void close_files(Download* d);
void close_quick(Download* d);
void close_throw(Download* d);
@@ -123,6 +132,9 @@ private:
void hash_done(Download* d);
void hash_queue(Download* d, int type);
void set_state_stopped(Download* d);
void update_paused_state(Download* d);
inline void check_contains(Download* d);
void received_finished(Download* d);
+79 -36
View File
@@ -37,6 +37,8 @@
namespace core {
void path_expand(std::vector<std::string>* paths, const std::string& pattern);
const int Manager::create_start;
const int Manager::create_tied;
const int Manager::create_quiet;
@@ -154,10 +156,10 @@ Manager::cleanup() {
void
Manager::shutdown(bool force) {
if (!force) {
for (auto d : *m_download_list)
m_download_list->pause_default(d);
for (const auto& d : *m_download_list)
m_download_list->pause_default(d.get());
} else {
for (auto d : *m_download_list)
for (const auto& d : *m_download_list)
m_download_list->close_quick(d);
}
}
@@ -195,7 +197,7 @@ Manager::try_create_download(const std::string& uri, int flags, const command_li
return;
// Adding download.
DownloadFactory* f = new DownloadFactory(this);
DownloadFactory* f = new DownloadFactory(this, true);
f->variables()["tied_to_file"] = (int64_t)(bool)(flags & create_tied);
f->commands().insert(f->commands().end(), commands.begin(), commands.end());
@@ -204,29 +206,52 @@ Manager::try_create_download(const std::string& uri, int flags, const command_li
f->set_print_log(!(flags & create_quiet));
f->slot_finished([f]() { delete f; });
if (is_data_uri(uri)) {
if (flags & create_raw_data) {
f->load_raw_data_trusted(uri);
} else if (is_data_uri(uri)) {
// Allow the use of data URIs, primarily for JSON-RPC which
// doesn't have a defined mechanism for binary data
f->load_raw_data(decode_data_uri(uri));
f->load_raw_data_trusted(decode_data_uri(uri));
f->variables()["tied_to_file"] = (int64_t)false;
} else if (flags & create_raw_data) {
f->load_raw_data(uri);
} else {
f->load(uri);
f->load_trusted(uri);
}
f->commit();
}
void
Manager::try_create_download_expand(const std::string& uri, int flags, command_list_type commands) {
if (flags & create_raw_data) {
try_create_download(uri, flags, commands);
return;
}
std::vector<std::string> paths;
paths.reserve(256);
path_expand(&paths, uri);
if (!paths.empty())
for (auto& path : paths)
try_create_download(path, flags, commands);
else
try_create_download(uri, flags, commands);
}
void
Manager::try_create_download_from_meta_download(torrent::Object* bencode, const std::string& metafile) {
DownloadFactory* f = new DownloadFactory(this);
DownloadFactory* f = new DownloadFactory(this, true);
f->variables()["tied_to_file"] = (int64_t)true;
f->variables()["tied_file"] = metafile;
f->variables()["tied_file"] = metafile;
torrent::Object& meta = bencode->get_key("rtorrent_meta_download");
torrent::Object::list_type& commands = meta.get_key_list("commands");
for (const auto& command : commands)
f->commands().insert(f->commands().end(), command.as_string());
@@ -239,13 +264,46 @@ Manager::try_create_download_from_meta_download(torrent::Object* bencode, const
std::stringstream s;
s.imbue(std::locale::classic());
s << *bencode;
f->load_raw_data(s.str());
f->load_raw_data_trusted(s.str());
f->commit();
}
utils::Directory
path_expand_transform(std::string path, const utils::directory_entry& entry) {
return path + entry.s_name;
void
Manager::try_create_download_untrusted(const std::string& uri, int flags, const command_list_type& commands) {
// TODO: Need to check if we are allowed, also rate limit.
if (!(flags & create_raw_data) &&
!is_network_uri(uri) &&
!is_magnet_uri(uri) &&
!is_data_uri(uri)) {
push_log_std("URI type not allowed for untrusted loads.");
return;
}
DownloadFactory* f = new DownloadFactory(this, false);
f->commands().insert(f->commands().end(), commands.begin(), commands.end());
f->set_start(flags & create_start);
f->set_print_log(!(flags & create_quiet));
f->slot_finished([f]() { delete f; });
if (flags & create_raw_data) {
f->load_raw_data_untrusted(uri);
} else if (is_data_uri(uri)) {
// Allow the use of data URIs, primarily for JSON-RPC which
// doesn't have a defined mechanism for binary data
f->load_raw_data_untrusted(decode_data_uri(uri));
f->variables()["tied_to_file"] = (int64_t)false;
} else {
// f->load_untrusted(uri);
throw torrent::internal_error("Manager::try_create_download_untrusted() unimplemented.");
}
f->commit();
}
namespace {
@@ -304,6 +362,11 @@ split_iterator(const Sequence&) {
}
utils::Directory
path_expand_transform(std::string path, const utils::directory_entry& entry) {
return path + entry.s_name;
}
// Move this somewhere better.
void
path_expand(std::vector<std::string>* paths, const std::string& pattern) {
@@ -360,26 +423,6 @@ manager_equal_tied(const std::string& path, Download* download) {
return path == rpc::call_command_string("d.tied_to_file", rpc::make_target(download));
}
void
Manager::try_create_download_expand(const std::string& uri, int flags, command_list_type commands) {
if (flags & create_raw_data) {
try_create_download(uri, flags, commands);
return;
}
std::vector<std::string> paths;
paths.reserve(256);
path_expand(&paths, uri);
if (!paths.empty())
for (auto& path : paths)
try_create_download(path, flags, commands);
else
try_create_download(uri, flags, commands);
}
// DownloadList's hashing related functions don't actually start the
// hashing, it only reacts to events. This functions checks the
// hashing view and starts hashing if nessesary.
@@ -405,7 +448,7 @@ Manager::receive_hashing_changed() {
continue;
try {
m_download_list->open_throw(*itr);
m_download_list->open_throw(itr->get());
// Since the bitfield is allocated on loading of resume load or
// hash start, and unallocated on close, we know that if it it
+2
View File
@@ -78,6 +78,8 @@ public:
void try_create_download_expand(const std::string& uri, int flags, command_list_type commands = command_list_type());
void try_create_download_from_meta_download(torrent::Object* bencode, const std::string& metafile);
void try_create_download_untrusted(const std::string& uri, int flags, const command_list_type& commands);
private:
void create_http(const std::string& uri);
void create_final(std::istream* s);
+40 -16
View File
@@ -16,10 +16,20 @@
namespace core {
// Also add focus thingie here?
// Matches the list entry that owns a given download.
inline auto
entry_is(Download* download) {
return [download](const std::shared_ptr<Download>& entry) { return entry.get() == download; };
}
struct view_downloads_compare {
view_downloads_compare(const torrent::Object& cmd) :
m_command(cmd) {}
bool operator()(const std::shared_ptr<Download>& d1, const std::shared_ptr<Download>& d2) const {
return (*this)(d1.get(), d2.get());
}
bool operator()(Download* d1, Download* d2) const {
try {
if (m_command.is_empty())
@@ -54,6 +64,10 @@ struct view_downloads_filter {
view_downloads_filter(const torrent::Object& cmd, const torrent::Object& cmd2) :
m_command(cmd), m_command2(cmd2) {}
bool operator()(const std::shared_ptr<Download>& d1) const {
return (*this)(d1.get());
}
bool operator()(Download* d1) const {
return this->evalCmd(m_command, d1) && this->evalCmd(m_command2, d1);
}
@@ -152,7 +166,7 @@ View::initialize(const std::string& name) {
void
View::erase(Download* download) {
iterator itr = std::find(base_type::begin(), base_type::end(), download);
iterator itr = std::find_if(base_type::begin(), base_type::end(), entry_is(download));
if (itr >= end_visible()) {
erase_internal(itr);
@@ -165,22 +179,24 @@ View::erase(Download* download) {
void
View::set_visible(Download* download) {
iterator itr = std::find(begin_filtered(), end_filtered(), download);
iterator itr = std::find_if(begin_filtered(), end_filtered(), entry_is(download));
if (itr == end_filtered())
return;
// Don't optimize erase since we want to keep the order of the
// non-visible elements.
auto entry = *itr;
base_type::erase(itr);
insert_visible(download);
insert_visible(entry);
rpc::call_object_nothrow(m_event_added, rpc::make_target(download));
}
void
View::set_not_visible(Download* download) {
iterator itr = std::find(begin_visible(), end_visible(), download);
iterator itr = std::find_if(begin_visible(), end_visible(), entry_is(download));
if (itr == end_visible())
return;
@@ -190,8 +206,10 @@ View::set_not_visible(Download* download) {
// Don't optimize erase since we want to keep the order of the
// non-visible elements.
auto entry = *itr;
base_type::erase(itr);
base_type::push_back(download);
base_type::push_back(entry);
rpc::call_object_nothrow(m_event_removed, rpc::make_target(download));
}
@@ -241,12 +259,12 @@ View::prev_focus(unsigned int i) {
void
View::sort() {
Download* curFocus = focus() != end_visible() ? *focus() : NULL;
Download* curFocus = focus() != end_visible() ? focus()->get() : NULL;
// Don't go randomly switching around equivalent elements.
std::stable_sort(begin(), end_visible(), view_downloads_compare(m_sortCurrent));
m_focus = position(std::find(begin(), end_visible(), curFocus));
m_focus = position(std::find_if(begin(), end_visible(), entry_is(curFocus)));
emit_changed();
}
@@ -280,10 +298,10 @@ View::filter() {
// set the elements to NULL as we trigger commands on them. Or
// perhaps always clear them, thus not throwing anything.
if (!m_event_removed.is_empty())
std::for_each(changed.begin(), splitChanged, std::bind(&rpc::call_object_d_nothrow, m_event_removed, std::placeholders::_1));
std::for_each(changed.begin(), splitChanged, [this](const auto& d) { rpc::call_object_d_nothrow(m_event_removed, d.get()); });
if (!m_event_added.is_empty())
std::for_each(changed.begin(), splitChanged, std::bind(&rpc::call_object_d_nothrow, m_event_added, std::placeholders::_1));
std::for_each(changed.begin(), splitChanged, [this](const auto& d) { rpc::call_object_d_nothrow(m_event_added, d.get()); });
emit_changed();
}
@@ -294,21 +312,23 @@ View::filter_by(const torrent::Object& condition, View::base_type& result) {
view_downloads_filter matches = view_downloads_filter(condition, m_temp_filter);
for (iterator itr = begin_visible(); itr != end_visible(); ++itr)
if (matches(*itr))
if (matches(itr->get()))
result.push_back(*itr);
}
void
View::filter_download(core::Download* download) {
iterator itr = std::find(base_type::begin(), base_type::end(), download);
iterator itr = std::find_if(base_type::begin(), base_type::end(), entry_is(download));
if (itr == base_type::end())
throw torrent::internal_error("View::filter_download(...) could not find download.");
if (view_downloads_filter(m_filter, m_temp_filter)(download)) {
if (itr >= end_visible()) {
auto entry = *itr;
erase_internal(itr);
insert_visible(download);
insert_visible(entry);
rpc::call_object_nothrow(m_event_added, rpc::make_target(download));
@@ -317,16 +337,20 @@ View::filter_download(core::Download* download) {
// already visible.
//
// Consider removing this.
auto entry = *itr;
erase_internal(itr);
insert_visible(download);
insert_visible(entry);
}
} else {
if (itr >= end_visible())
return;
auto entry = *itr;
erase_internal(itr);
base_type::push_back(download);
base_type::push_back(entry);
rpc::call_object_nothrow(m_event_removed, rpc::make_target(download));
}
@@ -345,8 +369,8 @@ View::clear_filter_on() {
}
inline void
View::insert_visible(Download* d) {
auto itr = std::find_if(begin_visible(), end_visible(), [this, d](auto d2) { return view_downloads_compare(m_sortNew)(d, d2); });
View::insert_visible(const std::shared_ptr<Download>& d) {
auto itr = std::find_if(begin_visible(), end_visible(), [this, &d](const auto& d2) { return view_downloads_compare(m_sortNew)(d.get(), d2.get()); });
m_size++;
m_focus += (m_focus >= position(itr));
+9 -8
View File
@@ -15,10 +15,11 @@
#include <functional>
#include <list>
#include <memory>
#include <string>
#include <vector>
#include <torrent/object.h>
#include <torrent/utils/scheduler.h>
#include <torrent/system/scheduler.h>
#include "globals.h"
@@ -26,9 +27,9 @@ namespace core {
class Download;
class View : private std::vector<Download*> {
class View : private std::vector<std::shared_ptr<Download>> {
public:
typedef std::vector<Download*> base_type;
typedef std::vector<std::shared_ptr<Download>> base_type;
typedef std::function<void()> slot_void;
typedef std::list<slot_void> signal_void;
@@ -72,7 +73,7 @@ public:
emit_changed();
}
void insert(Download* download) { base_type::push_back(download); }
void insert(const std::shared_ptr<Download>& download) { base_type::push_back(download); }
void erase(Download* download);
void set_visible(Download* download);
@@ -124,9 +125,9 @@ private:
View(const View&);
void operator=(const View&);
void push_back(Download* d) { base_type::push_back(d); }
void push_back(const std::shared_ptr<Download>& d) { base_type::push_back(d); }
inline void insert_visible(Download* d);
inline void insert_visible(const std::shared_ptr<Download>& d);
inline void erase_internal(iterator itr);
void emit_changed();
@@ -153,8 +154,8 @@ private:
std::chrono::microseconds m_last_changed{};
signal_void m_signal_changed;
torrent::utils::SchedulerEntry m_delay_changed;
signal_void m_signal_changed;
torrent::system::SchedulerEntry m_delay_changed;
};
} // namespace core
+4 -4
View File
@@ -1,7 +1,7 @@
#ifndef RTORRENT_DISPLAY_MANAGER_H
#define RTORRENT_DISPLAY_MANAGER_H
#include <torrent/utils/scheduler.h>
#include <torrent/system/scheduler.h>
#include "display/frame.h"
@@ -31,9 +31,9 @@ private:
bool m_force_redraw{false};
Frame m_root_frame;
std::chrono::microseconds m_time_last_update{};
torrent::utils::ExternalScheduler m_scheduler;
torrent::utils::SchedulerEntry m_task_update;
std::chrono::microseconds m_time_last_update{};
torrent::system::ExternalScheduler m_scheduler;
torrent::system::SchedulerEntry m_task_update;
};
}
+2 -2
View File
@@ -345,7 +345,7 @@ print_status_info(char* first, char* last) {
if (!torrent::up_throttle_global()->is_throttled()) {
first = print_buffer(first, last, "[Throttle off");
} else {
first = print_buffer(first, last, "[Throttle %3i", torrent::up_throttle_global()->max_rate() / 1024);
first = print_buffer(first, last, "[Throttle %3i", (int)(torrent::up_throttle_global()->max_rate() / 1024));
if (!throttle_up_names.empty())
first = print_status_throttle_limit(first, last, true, throttle_up_names);
@@ -354,7 +354,7 @@ print_status_info(char* first, char* last) {
if (!torrent::down_throttle_global()->is_throttled()) {
first = print_buffer(first, last, " / off KB]");
} else {
first = print_buffer(first, last, " / %3i", torrent::down_throttle_global()->max_rate() / 1024);
first = print_buffer(first, last, " / %3i", (int)(torrent::down_throttle_global()->max_rate() / 1024));
if (!throttle_down_names.empty())
first = print_status_throttle_limit(first, last, false, throttle_down_names);
+2 -2
View File
@@ -2,7 +2,7 @@
#define RTORRENT_WINDOW_BASE_H
#include <functional>
#include <torrent/utils/scheduler.h>
#include <torrent/system/scheduler.h>
#include <torrent/system/thread.h>
#include "canvas.h"
@@ -99,7 +99,7 @@ protected:
extent_type m_max_width;
extent_type m_max_height;
torrent::utils::SchedulerEntry m_task_update;
torrent::system::SchedulerEntry m_task_update;
};
// Return a range with a distance of no more than __distance and
+5 -5
View File
@@ -37,7 +37,7 @@ WindowDownloadList::set_view(core::View* l) {
// Return a pair of ints, representing a) the ncurses attributes and b) the ncurses color pair ID to use
std::pair<int, int>
WindowDownloadList::get_attr_color(core::View::iterator selected) {
core::Download* item = *selected;
core::Download* item = selected->get();
unsigned long focus_attr = selected == m_view->focus() ? m_canvas->attr_map().at(RCOLOR_FOCUS) : 0;
int offset = (((selected - m_view->begin_visible()) & 1) + 1) * RCOLOR_MAX; // Determine the even/odd offset for the color pair
bool active = item->is_open() && item->is_active();
@@ -132,15 +132,15 @@ WindowDownloadList::redraw() {
ColorKind focus_color = is_focused ? RCOLOR_FOCUS : RCOLOR_LABEL;
auto attr_color = get_attr_color(range.first);
print_download_title(buffer.data(), last, *range.first);
print_download_title(buffer.data(), last, range.first->get());
m_canvas->print(0, pos, "%c %s", focus_char, buffer.data());
m_canvas->set_attr(2, pos++, -1, attr_color.first, attr_color.second);
print_download_info_full(buffer.data(), last, *range.first);
print_download_info_full(buffer.data(), last, range.first->get());
m_canvas->print(0, pos, "%c %s", focus_char, buffer.data());
m_canvas->set_attr(2, pos++, -1, focus_color);
print_download_status(buffer.data(), last, *range.first);
print_download_status(buffer.data(), last, range.first->get());
m_canvas->print(0, pos, "%c %s", focus_char, buffer.data());
m_canvas->set_attr(2, pos++, -1, focus_color);
@@ -152,7 +152,7 @@ WindowDownloadList::redraw() {
char focus_char = range.first == m_view->focus() ? '*' : ' ';
auto attr_color = get_attr_color(range.first);
print_download_info_compact(buffer.data(), last, *range.first);
print_download_info_compact(buffer.data(), last, range.first->get());
m_canvas->print(0, pos, "%c %s", focus_char, buffer.data());
m_canvas->set_attr(2, pos++, -1, attr_color.first, attr_color.second);
+1 -1
View File
@@ -47,7 +47,7 @@ private:
signal_curl_get::iterator m_conn_insert;
signal_curl_get::iterator m_conn_erase;
torrent::utils::SchedulerEntry m_task_deactivate;
torrent::system::SchedulerEntry m_task_deactivate;
};
}
+6 -4
View File
@@ -1,8 +1,8 @@
#ifndef RTORRENT_DISPLAY_WINDOW_LOG_H
#define RTORRENT_DISPLAY_WINDOW_LOG_H
#include <torrent/system/scheduler.h>
#include <torrent/utils/log_buffer.h>
#include <torrent/utils/scheduler.h>
#include "window.h"
@@ -22,10 +22,12 @@ public:
private:
inline iterator find_older();
torrent::log_buffer* m_log;
torrent::utils::SchedulerEntry m_task_update;
torrent::log_buffer* m_log;
torrent::system::SchedulerEntry m_task_update;
align_cacheline std::atomic<bool> m_log_updating{};
align_cacheline
std::atomic<bool> m_log_updating{};
};
}
+30
View File
@@ -3,6 +3,7 @@
#include "globals.h"
#include <cstdlib>
#include <stdlib.h>
#include <torrent/exceptions.h>
rpc::ip_table_list ip_tables;
@@ -28,3 +29,32 @@ expand_path(const std::string& path) {
return path;
}
// Resolves a path to a canonical one with no symlinks or relative components,
// so it can safely be handed to an external script. Returns an empty string if
// the path does not name an existing file or directory.
std::string
resolve_path(const std::string& path) {
if (path.empty())
return std::string();
char* resolved = ::realpath(expand_path(path).c_str(), nullptr);
if (resolved == nullptr)
return std::string();
std::string result(resolved);
std::free(resolved);
return result;
}
std::string
resolve_path_or_throw(const std::string& path) {
auto result = resolve_path(path);
if (result.empty())
throw torrent::input_error("Could not resolve path: '" + path + "'.");
return result;
}
+2
View File
@@ -11,6 +11,8 @@ extern rpc::ip_table_list ip_tables;
extern Control* control;
std::string expand_path(const std::string& path);
std::string resolve_path(const std::string& path);
std::string resolve_path_or_throw(const std::string& path);
namespace rpc {
class SCgi;
+2 -3
View File
@@ -2,12 +2,11 @@
#define RTORRENT_INPUT_INPUT_EVENT_H
#include <functional>
#include <torrent/event.h>
#include <torrent/system/event.h>
namespace input {
class InputEvent : public torrent::Event {
class InputEvent : public torrent::system::Event {
public:
typedef std::function<void (int)> slot_int;
+11 -9
View File
@@ -301,8 +301,6 @@ main(int argc, char** argv) {
"schedule = low_diskspace,5,60,((close_low_diskspace,500M))\n"
"schedule = prune_file_status,3600,86400,((system.file_status_cache.prune))\n"
"protocol.encryption.set=allow_incoming,prefer_plaintext,enable_retry\n"
"ui.color.focus.set=reverse\n"
);
@@ -346,8 +344,9 @@ main(int argc, char** argv) {
CMD_REDIRECT("directory", "directory.default.set");
CMD_REDIRECT("session", "session.path.set");
CMD_REDIRECT("scgi_port", "network.scgi.open_port");
CMD_REDIRECT("scgi_local", "network.scgi.open_local");
CMD_REDIRECT_NO_EXPORT("port_range", "network.listen.port.range.set");
CMD_REDIRECT_NO_EXPORT("scgi_port", "network.scgi.open_port");
CMD_REDIRECT_NO_EXPORT("scgi_local", "network.scgi.open_local");
CMD_REDIRECT("to_gm_time", "convert.gm_time");
CMD_REDIRECT("to_gm_date", "convert.gm_date");
@@ -380,6 +379,12 @@ main(int argc, char** argv) {
lt_log_print(torrent::LOG_WARN, "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.
CMD_REDIRECT("d.directory_base", "d.directory");
CMD_REDIRECT("d.directory_base.set", "d.directory.base.set");
rpc::rpc.mark_safe("d.directory_base");
// if (rpc::call_command_value("method.use_intermediate") == 1) {
// } else if (rpc::call_command_value("method.use_intermediate") == 2) {
@@ -391,17 +396,14 @@ main(int argc, char** argv) {
CMD_REDIRECT("schedule2", "schedule");
CMD_REDIRECT("schedule_remove2", "schedule.remove");
// TODO: Remove file.append when cleaning these up.
CMD_REDIRECT("bind", "network.bind_address.set");
CMD_REDIRECT("ip", "network.local_address.set");
CMD_REDIRECT("port_range", "network.port_range.set");
// TODO: Check if dht is on by default.
CMD_REDIRECT("dht", "dht.mode.set");
CMD_REDIRECT("port_random", "network.port_random.set");
CMD_REDIRECT("proxy_address", "network.proxy_address.set");
CMD_REDIRECT("port_random", "network.listen.port.random.set");
CMD_REDIRECT("proxy_address", "network.proxy.http.set");
CMD_REDIRECT("key_layout", "keys.layout.set");
+9 -1
View File
@@ -1,5 +1,7 @@
#include "config.h"
#include <limits>
#include "core/download.h"
#include "parse.h"
@@ -41,7 +43,13 @@ command_base_call_value_base(command_base* command_raw, target_type target, cons
return command_base::_call<typename command_value_function<T>::type, T>(command_raw, target, val);
}
return command_base::_call<typename command_value_function<T>::type, T>(command_raw, target, unit * arg.as_value());
auto value = arg.as_value();
if (value > std::numeric_limits<int64_t>::max() / unit ||
value < std::numeric_limits<int64_t>::min() / unit)
throw torrent::input_error("Value out of range.");
return command_base::_call<typename command_value_function<T>::type, T>(command_raw, target, unit * value);
}
template <typename T> const torrent::Object
+5
View File
@@ -1,6 +1,7 @@
#ifndef RTORRENT_RPC_COMMAND_MAP_H
#define RTORRENT_RPC_COMMAND_MAP_H
#include <memory>
#include <map>
#include <string>
#include <cstring>
@@ -104,6 +105,10 @@ inline target_type make_target(T target) {
return target_type((int)target_type_id<T>::value, target);
}
inline target_type make_target(const std::shared_ptr<core::Download>& target) {
return make_target(target.get());
}
template <typename T>
inline target_type make_target_pair(T target1, T target2) {
return target_type((int)target_type_id<T, T>::value, target1, target2);
+2 -2
View File
@@ -5,7 +5,7 @@
#include <functional>
#include <torrent/object.h>
#include <torrent/utils/scheduler.h>
#include <torrent/system/scheduler.h>
namespace rpc {
@@ -43,7 +43,7 @@ private:
uint32_t m_interval{};
std::chrono::microseconds m_time_scheduled;
torrent::utils::SchedulerEntry m_task;
torrent::system::SchedulerEntry m_task;
// Flags for various things.
};
+44 -148
View File
@@ -1,23 +1,22 @@
#include "config.h"
#include <cassert>
#include <cerrno>
#include <cstring>
#include <fcntl.h>
#include <spawn.h>
#include <string>
#include "rpc/exec_file.h"
// #include <cassert>
// #include <cerrno>
// #include <cstring>
// #include <fcntl.h>
// #include <spawn.h>
// #include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <torrent/net/fd.h>
// #include <sys/types.h>
#include <sys/uio.h>
// #include <torrent/net/fd.h>
#include <torrent/system/thread.h>
#include <torrent/system/spawn_process.h>
#include <torrent/system/types.h>
#include "exec_file.h"
#include "parse.h"
// Standard POSIX environment pointer
extern char** environ;
#include "rpc/parse.h"
namespace rpc {
@@ -25,159 +24,56 @@ namespace rpc {
int
ExecFile::execute(const char* file, char* const* argv, int flags) {
assert(!((flags & flag_capture) && (flags & flag_background)));
torrent::system::SpawnProcess spawn_process;
// Write the executed command and its parameters to the log fd.
[[maybe_unused]] int result;
spawn_process.set_log_fd(m_log_fd);
spawn_process.set_background(flags & flag_background);
spawn_process.set_capture_output(flags & flag_capture);
if (m_log_fd != -1) {
for (char* const* itr = argv; *itr != NULL; itr++) {
if (itr == argv)
result = write(m_log_fd, "\n---\n", sizeof("\n---\n"));
else
result = write(m_log_fd, " ", 1);
std::vector<struct iovec> iovecs;
iovecs.reserve(32);
iovecs.push_back({const_cast<char*>("\n---\n"), 5});
result = write(m_log_fd, *itr, std::strlen(*itr));
for (auto* itr = argv; *itr != nullptr; itr++) {
if (itr != argv)
iovecs.push_back({const_cast<char*>(" "), 1});
iovecs.push_back({*itr, std::strlen(*itr)});
}
result = write(m_log_fd, "\n---\n", sizeof("\n---\n"));
iovecs.push_back({const_cast<char*>("\n---\n"), 5});
[[maybe_unused]] int result = ::writev(m_log_fd, iovecs.data(), iovecs.size());
}
posix_spawn_file_actions_t actions{};
if (posix_spawn_file_actions_init(&actions) != 0)
throw torrent::internal_error("ExecFile::execute(...) posix_spawn_file_actions_init failed.");
posix_spawnattr_t attr;
posix_spawnattr_init(&attr);
// Try to avoid leaking open fds to the spawned process. Prefer POSIX_SPAWN_CLOEXEC_DEFAULT
// (macOS-only) or posix_spawn_file_actions_addclosefrom_np (glibc >= 2.34, FreeBSD >= 13.1).
//
// Other platforms like musl libc, OpenBSD and NetBSD must rely on explicit O_CLOEXEC.
// Handle standard input redirection (/dev/null), posix_spawn_file_actions_addopen handles opening
// and dup2 natively
if (posix_spawn_file_actions_addopen(&actions, 0, "/dev/null", O_RDWR, 0) != 0) {
// Fallback if open fails inside action setup
posix_spawn_file_actions_addclose(&actions, 0);
}
int pipe_0 = -1;
int pipe_1 = -1;
// Handle standard output redirection
if (flags & flag_capture) {
torrent::fd_open_pipe(pipe_0, pipe_1);
posix_spawn_file_actions_adddup2(&actions, pipe_1, 1);
// Ensure the write end of the pipe is closed in the child after duplicating.
posix_spawn_file_actions_addclose(&actions, pipe_0);
posix_spawn_file_actions_addclose(&actions, pipe_1);
} else if (m_log_fd != -1) {
posix_spawn_file_actions_adddup2(&actions, m_log_fd, 1);
} else {
posix_spawn_file_actions_addopen(&actions, 1, "/dev/null", O_RDWR, 0);
}
if (m_log_fd != -1) {
posix_spawn_file_actions_adddup2(&actions, m_log_fd, 2);
} else {
posix_spawn_file_actions_addopen(&actions, 2, "/dev/null", O_RDWR, 0);
}
short spawn_flags = 0;
#if defined(POSIX_SPAWN_CLOEXEC_DEFAULT)
spawn_flags |= POSIX_SPAWN_CLOEXEC_DEFAULT;
#elif defined(HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCLOSEFROM_NP)
posix_spawn_file_actions_addclosefrom_np(&actions, 3);
#endif
if (flags & flag_background) {
#ifdef POSIX_SPAWN_SETSID
spawn_flags |= POSIX_SPAWN_SETSID;
#else
spawn_flags |= POSIX_SPAWN_SETPGROUP;
posix_spawnattr_setpgroup(&attr, 0);
#endif
}
posix_spawnattr_setflags(&attr, spawn_flags);
pid_t child_pid{};
int spawn_status = posix_spawnp(&child_pid, file, &actions, &attr, argv, environ);
posix_spawn_file_actions_destroy(&actions);
posix_spawnattr_destroy(&attr);
int spawn_status = spawn_process.execute(file, argv);
if (spawn_status != 0) {
if (pipe_0 != -1)
torrent::fd_close(pipe_0);
if (m_log_fd != -1) {
auto prefix = "\n--- posix_spawn failed: ";
auto errno_str = torrent::system::errno_enum_str(spawn_status) + " ---\n";
if (pipe_1 != -1)
torrent::fd_close(pipe_1);
struct iovec iovecs[2] = {
{const_cast<char*>(prefix), std::strlen(prefix)},
{const_cast<char*>(errno_str.c_str()), errno_str.size()},
};
[[maybe_unused]] int result = ::writev(m_log_fd, iovecs, 2);
}
throw torrent::input_error("ExecFile::execute() posix_spawn failed: " + torrent::system::errno_enum_str(spawn_status));
}
if (flags & flag_capture) {
m_capture = std::string();
torrent::fd_close(pipe_1);
char buffer[4096];
ssize_t length;
do {
length = read(pipe_0, buffer, sizeof(buffer));
if (length > 0)
m_capture += std::string(buffer, length);
} while (length > 0);
torrent::fd_close(pipe_0);
if (m_log_fd != -1) {
result = write(m_log_fd, "Captured output:\n", sizeof("Captured output:\n"));
result = write(m_log_fd, m_capture.data(), m_capture.length());
}
}
if (flags & flag_background) {
if (m_log_fd != -1)
result = write(m_log_fd, "\n--- Running in Background ---\n", sizeof("\n--- Running in Background ---\n"));
m_waitpid_queue.close_pid(spawn_process.child_pid());
return 0;
}
int status;
if (flags & flag_capture)
m_capture = spawn_process.capture_child_output();
while (::waitpid(child_pid, &status, 0) == -1) {
switch (errno) {
case EINTR:
continue;
case ECHILD:
throw torrent::internal_error("ExecFile::execute(...) waitpid failed with ECHILD, child process not found.");
case EINVAL:
throw torrent::internal_error("ExecFile::execute(...) waitpid failed with EINVAL.");
default:
throw torrent::internal_error("ExecFile::execute(...) waitpid failed with unexpected error: " + std::string(std::strerror(errno)));
}
};
// Check return value?
if (m_log_fd != -1) {
if (WIFEXITED(status) && WEXITSTATUS(status) == 0)
result = write(m_log_fd, "\n--- Success ---\n", sizeof("\n--- Success ---\n"));
else
result = write(m_log_fd, "\n--- Error ---\n", sizeof("\n--- Error ---\n"));
}
return status;
return spawn_process.wait_for_child();
}
torrent::Object
@@ -237,4 +133,4 @@ ExecFile::execute_object(const torrent::Object& rawArgs, int flags) {
return torrent::Object((int64_t)status);
}
}
} // namespace rpc
+4
View File
@@ -3,6 +3,8 @@
#include <torrent/object.h>
#include "utils/waitpid_queue.h"
namespace rpc {
class ExecFile {
@@ -24,6 +26,8 @@ public:
private:
int m_log_fd{-1};
std::string m_capture;
utils::WaitpidQueue m_waitpid_queue;
};
}
+14 -9
View File
@@ -26,10 +26,12 @@ constexpr int JSONRPC_METHOD_NOT_FOUND_ERROR = -32601;
constexpr int JSONRPC_INVALID_PARAMS_ERROR = -32602;
constexpr int JSONRPC_INTERNAL_ERROR = -32000;
constexpr uint32_t max_json_depth = 1024;
using json = nlohmann::json;
torrent::Object
json_to_object(const json& value) {
json_to_object(const json& value, uint32_t depth = 0) {
switch (value.type()) {
case json::value_t::number_unsigned:
case json::value_t::number_integer:
@@ -42,20 +44,26 @@ json_to_object(const json& value) {
return torrent::Object(value.get<std::string>());
case json::value_t::array: {
if (++depth >= max_json_depth)
throw torrent::input_error("invalid parameters: max depth reached");
auto array_raw = torrent::Object::create_list();
auto& array = array_raw.as_list();
for (const auto& entry : value)
array.push_back(json_to_object(entry));
array.push_back(json_to_object(entry, depth));
return array_raw;
}
case json::value_t::object: {
if (++depth >= max_json_depth)
throw torrent::input_error("invalid parameters: max depth reached");
auto map_raw = torrent::Object::create_map();
auto& map = map_raw.as_map();
for (const auto& entry : value.items())
map[entry.key()] = json_to_object(entry.value());
map[entry.key()] = json_to_object(entry.value(), depth);
return map_raw;
}
@@ -138,7 +146,7 @@ jsonrpc_call_command(const std::string& method, const json& params) {
CommandMap::iterator itr = commands.find(method.c_str());
if (itr == commands.end()) {
if (itr == commands.end() || !(itr->second.m_flags & CommandMap::flag_public_rpc)) {
throw rpc_error(JSONRPC_METHOD_NOT_FOUND_ERROR, "method not found: " + method);
}
@@ -258,11 +266,8 @@ JsonRpc::process(const char* in_buffer, uint32_t length, slot_write callback) {
return callback(response_str.c_str(), response_str.size());
} catch (json::parse_error& e) {
auto err_str = json_error(JSONRPC_PARSE_ERROR, e.what(), nullptr).dump(-1, ' ', false, json::error_handler_t::replace);
return callback(err_str.c_str(), err_str.size());
} catch (json::type_error& e) {
// Type errors may be caused by invalid UTF-8 strings in exception strings, hence the ::replace
} catch (json::exception& e) {
// Exception strings may contain invalid UTF-8, hence the ::replace
auto err_str = json_error(JSONRPC_PARSE_ERROR, e.what(), nullptr).dump(-1, ' ', false, json::error_handler_t::replace);
return callback(err_str.c_str(), err_str.size());
}
+37 -19
View File
@@ -2,6 +2,7 @@
#include <cstring>
#include <cstdio>
#include <limits>
#include <locale>
#include <torrent/exceptions.h>
@@ -10,6 +11,8 @@
namespace rpc {
constexpr uint32_t max_parse_depth = 1024;
const char*
parse_skip_wspace(const char* first, const char* last) {
while (first != last && parse_is_space(*first))
@@ -99,6 +102,12 @@ parse_whole_value_nothrow(const char* src, int64_t* value, int base, int unit) {
return true;
}
static bool
value_fits_shifted(int64_t value, int shift) {
return value <= (std::numeric_limits<int64_t>::max() >> shift) &&
value >= (std::numeric_limits<int64_t>::min() >> shift);
}
const char*
parse_value_nothrow(const char* src, int64_t* value, int base, int unit) {
if (unit <= 0)
@@ -121,20 +130,26 @@ parse_value_nothrow(const char* src, int64_t* value, int base, int unit) {
case 'B': ++last; break;
case 'k':
case 'K':
if (*value > (int64_t)0x1FFFFFFFFFFFFF) return src; // overflow guard
if (!value_fits_shifted(*value, 10)) return src; // overflow guard
*value = *value << 10; ++last; break;
case 'm':
case 'M':
if (*value > (int64_t)0x7FFFFFFFFFF) return src; // overflow guard
if (!value_fits_shifted(*value, 20)) return src; // overflow guard
*value = *value << 20; ++last; break;
case 'g':
case 'G':
if (*value > (int64_t)0x1FFFFFFFF) return src; // overflow guard
if (!value_fits_shifted(*value, 30)) return src; // overflow guard
*value = *value << 30; ++last; break;
// case ' ':
// case '\0': *value = *value * unit; break;
// default: throw torrent::input_error("Could not parse value.");
default: *value = *value * unit; break;
default:
if (*value > std::numeric_limits<int64_t>::max() / unit ||
*value < std::numeric_limits<int64_t>::min() / unit)
return src; // overflow guard
*value = *value * unit;
break;
}
return last;
@@ -142,10 +157,13 @@ parse_value_nothrow(const char* src, int64_t* value, int base, int unit) {
// Somewhat ugly...
const char*
parse_object(const char* first, const char* last, torrent::Object* dest, bool (*delim)(const char)) {
parse_object(const char* first, const char* last, torrent::Object* dest, bool (*delim)(const char), uint32_t depth) {
if (++depth >= max_parse_depth)
throw torrent::input_error("Max parse depth reached.");
if (*first == '{') {
*dest = torrent::Object::create_list();
first = parse_list(first + 1, last, dest, &parse_is_delim_block);
first = parse_list(first + 1, last, dest, &parse_is_delim_block, depth);
first = parse_skip_wspace(first, last);
if (first == last || *first != '}')
@@ -154,18 +172,18 @@ parse_object(const char* first, const char* last, torrent::Object* dest, bool (*
return ++first;
} else if (*first == '(') {
int32_t depth = 1;
int32_t parentheses = 1;
while (first + 1 != last && *(first + 1) == '(') {
first++;
depth++;
parentheses++;
}
if (depth > 3)
if (parentheses > 3)
throw torrent::input_error("Max 3 parentheses per object allowed.");
*dest = torrent::Object::create_dict_key();
dest->set_flags(torrent::Object::flag_function << (depth - 1));
dest->set_flags(torrent::Object::flag_function << (parentheses - 1));
first = parse_string(first + 1, last, &dest->as_dict_key(), &parse_is_delim_func);
first = parse_skip_wspace(first, last);
@@ -176,16 +194,16 @@ parse_object(const char* first, const char* last, torrent::Object* dest, bool (*
if (*first == ',') {
// This will always create a list even for single argument functions...
dest->as_dict_obj() = torrent::Object::create_list();
first = parse_list(first + 1, last, &dest->as_dict_obj(), &parse_is_delim_func);
first = parse_list(first + 1, last, &dest->as_dict_obj(), &parse_is_delim_func, depth);
first = parse_skip_wspace(first, last);
}
while (depth != 0 && first != last && *first == ')') {
while (parentheses != 0 && first != last && *first == ')') {
first++;
depth--;
parentheses--;
}
if (depth != 0)
if (parentheses != 0)
throw torrent::input_error("Parentheses mismatch.");
return first;
@@ -198,7 +216,7 @@ parse_object(const char* first, const char* last, torrent::Object* dest, bool (*
}
const char*
parse_list(const char* first, const char* last, torrent::Object* dest, bool (*delim)(const char)) {
parse_list(const char* first, const char* last, torrent::Object* dest, bool (*delim)(const char), uint32_t depth) {
if (!dest->is_list())
throw torrent::internal_error("parse_list(...) !dest->is_list().");
@@ -206,7 +224,7 @@ parse_list(const char* first, const char* last, torrent::Object* dest, bool (*de
torrent::Object tmp;
first = parse_skip_wspace(first, last);
first = parse_object(first, last, &tmp, delim);
first = parse_object(first, last, &tmp, delim, depth);
first = parse_skip_wspace(first, last);
dest->as_list().push_back(tmp);
@@ -221,9 +239,9 @@ parse_list(const char* first, const char* last, torrent::Object* dest, bool (*de
}
const char*
parse_whole_list(const char* first, const char* last, torrent::Object* dest, bool (*delim)(const char)) {
parse_whole_list(const char* first, const char* last, torrent::Object* dest, bool (*delim)(const char), uint32_t depth) {
first = parse_skip_wspace(first, last);
first = parse_object(first, last, dest, delim);
first = parse_object(first, last, dest, delim, depth);
first = parse_skip_wspace(first, last);
if (first != last && parse_is_seperator(*first)) {
@@ -231,7 +249,7 @@ parse_whole_list(const char* first, const char* last, torrent::Object* dest, boo
tmp.swap(*dest);
dest->as_list().push_back(tmp);
first = parse_list(++first, last, dest, delim);
first = parse_list(++first, last, dest, delim, depth);
}
return first;
+3 -3
View File
@@ -77,9 +77,9 @@ const char* parse_value_nothrow(const char* first, const char* last, int64_t* va
void parse_whole_value(const char* src, int64_t* value, int base = 0, int unit = 1);
bool parse_whole_value_nothrow(const char* src, int64_t* value, int base = 0, int unit = 1);
const char* parse_object (const char* first, const char* last, torrent::Object* dest, bool (*delim)(const char) = &parse_is_delim_default);
const char* parse_list (const char* first, const char* last, torrent::Object* dest, bool (*delim)(const char) = &parse_is_delim_default);
const char* parse_whole_list(const char* first, const char* last, torrent::Object* dest, bool (*delim)(const char) = &parse_is_delim_default);
const char* parse_object (const char* first, const char* last, torrent::Object* dest, bool (*delim)(const char) = &parse_is_delim_default, uint32_t depth = 0);
const char* parse_list (const char* first, const char* last, torrent::Object* dest, bool (*delim)(const char) = &parse_is_delim_default, uint32_t depth = 0);
const char* parse_whole_list(const char* first, const char* last, torrent::Object* dest, bool (*delim)(const char) = &parse_is_delim_default, uint32_t depth = 0);
std::string convert_to_string(const torrent::Object& src);
+8
View File
@@ -167,6 +167,14 @@ RpcManager::cleanup() {
m_jsonrpc.cleanup();
}
void
RpcManager::set_size_limit(uint64_t size) {
if (size > SCgiTask::max_content_size)
throw torrent::input_error("XMLRPC size limit cannot exceed the SCGI content size limit.");
m_xmlrpc.set_size_limit(size);
}
void
RpcManager::insert_command(const char* name, const char* parm, const char* doc) {
m_xmlrpc.insert_command(name, parm, doc);
+1 -1
View File
@@ -62,7 +62,7 @@ public:
void cleanup();
int64_t size_limit() { return m_xmlrpc.size_limit(); };
void set_size_limit(uint64_t size) { m_xmlrpc.set_size_limit(size); };
void set_size_limit(uint64_t size);
int dialect() { return m_xmlrpc.dialect(); }
void set_dialect(int dialect) { m_xmlrpc.set_dialect(dialect); }
+2 -2
View File
@@ -3,13 +3,13 @@
#include <array>
#include <memory>
#include <torrent/event.h>
#include <torrent/system/event.h>
#include "rpc/scgi_task.h"
namespace rpc {
class SCgi : public torrent::Event {
class SCgi : public torrent::system::Event {
public:
static const int max_tasks = 100;
+16 -1
View File
@@ -14,6 +14,7 @@
#include <torrent/runtime/socket_manager.h>
#include <torrent/system/callbacks.h>
#include <torrent/system/poll.h>
#include <torrent/system/scheduler.h>
#include <torrent/utils/log.h>
#include "control.h"
@@ -27,6 +28,8 @@ namespace rpc {
SCgiTask::SCgiTask()
: m_callback_id(torrent::system::make_callback_id()) {
m_task_timeout.slot() = [this]() { close(); };
reset_file_descriptor();
}
@@ -50,6 +53,8 @@ SCgiTask::open(SCgi* parent, int fd) {
torrent::this_thread::poll()->open(this);
torrent::this_thread::poll()->insert_read(this);
torrent::this_thread::scheduler()->update_wait_for_ceil_seconds(&m_task_timeout, timeout_request);
auto lock = std::lock_guard<std::mutex>(m_result_mutex);
// Leave room for terminating nul byte for parsing the header.
@@ -61,6 +66,8 @@ SCgiTask::cancel_open() {
if (!is_open())
return;
torrent::this_thread::scheduler()->erase(&m_task_timeout);
torrent::this_thread::poll()->remove_and_close(this);
torrent::fd_close(file_descriptor());
@@ -72,6 +79,8 @@ SCgiTask::close() {
if (!is_open())
return;
torrent::this_thread::scheduler()->erase(&m_task_timeout);
torrent::system::cancel_callback_and_wait(m_callback_id, scgi_thread::thread(), torrent::main_thread::thread());
torrent::runtime::socket_manager()->close_event_or_throw(this, [this]() {
@@ -94,8 +103,12 @@ SCgiTask::event_read() {
if (m_content_length == 0)
read_length--;
if (read_length <= 0)
if (read_length <= 0) {
if (m_content_length == 0)
return close();
throw torrent::internal_error("SCgiTask::event_read() no space in buffer for event_read.");
}
int bytes = ::recv(file_descriptor(), m_buffer.data() + m_position, read_length, 0);
@@ -154,6 +167,8 @@ SCgiTask::event_read() {
torrent::this_thread::poll()->remove_read(this);
torrent::this_thread::scheduler()->update_wait_for_ceil_seconds(&m_task_timeout, timeout_request);
if (m_parent->log_fd() >= 0) {
[[maybe_unused]] int result;
+9 -4
View File
@@ -1,21 +1,25 @@
#ifndef RTORRENT_RPC_SCGI_TASK_H
#define RTORRENT_RPC_SCGI_TASK_H
#include <chrono>
#include <memory>
#include <mutex>
#include <vector>
#include <torrent/event.h>
#include <torrent/system/event.h>
#include <torrent/system/scheduler.h>
namespace rpc {
class SCgi;
class SCgiTask : public torrent::Event {
class SCgiTask : public torrent::system::Event {
public:
static constexpr int default_buffer_size = 8191;
static constexpr int max_header_size = 2000;
static constexpr int max_content_size = (2 << 23);
static constexpr auto timeout_request = std::chrono::seconds(60);
enum ContentType { XML, JSON };
SCgiTask();
@@ -54,8 +58,9 @@ private:
void plaintext_response(const char* buffer, uint32_t content_length);
void gzip_response(const char* buffer, uint32_t content_length);
SCgi* m_parent{};
torrent::system::callback_id m_callback_id;
SCgi* m_parent{};
torrent::system::callback_id m_callback_id;
torrent::system::SchedulerEntry m_task_timeout;
std::mutex m_result_mutex;
+5 -3
View File
@@ -451,6 +451,11 @@ XmlRpc::process(const char* inBuffer, uint32_t length, slot_write slotWrite) {
if (local_env.fault_occurred && local_env.fault_code == XMLRPC_INTERNAL_ERROR)
throw torrent::internal_error("Internal error in XMLRPC.");
if (memblock == nullptr) {
xmlrpc_env_clean(&local_env);
return false;
}
bool result = slotWrite((const char*)xmlrpc_mem_block_contents(memblock),
xmlrpc_mem_block_size(memblock));
@@ -524,9 +529,6 @@ XmlRpc::size_limit() {
void
XmlRpc::set_size_limit(uint64_t size) {
if (size >= (64 << 20))
throw torrent::input_error("Invalid XMLRPC limit size.");
xmlrpc_limit_set(XMLRPC_XML_SIZE_LIMIT_ID, size);
}
+26 -5
View File
@@ -49,13 +49,23 @@ element_access(const tinyxml2::XMLElement* elem, std::initializer_list<std::stri
return result;
}
const char*
element_text_value(const tinyxml2::XMLNode* node) {
auto text = node->ToText();
if (text == nullptr)
throw rpc_error(XMLRPC_TYPE_ERROR, "expected a text value");
return text->Value();
}
long long
element_to_int(const tinyxml2::XMLNode* elem) {
char* pos;
if (elem->FirstChild() == nullptr) {
throw rpc_error(XMLRPC_TYPE_ERROR, "unable to parse empty integer");
}
auto str = elem->FirstChild()->ToText()->Value();
auto str = element_text_value(elem->FirstChild());
auto result = std::strtoll(str, &pos, 10);
if (pos == str || *pos != '\0')
throw rpc_error(XMLRPC_TYPE_ERROR, "unable to parse integer value");
@@ -85,7 +95,7 @@ xml_value_to_object(const tinyxml2::XMLNode* elem) {
if (child_element == nullptr)
return torrent::Object("");
return torrent::Object(child_element->ToText()->Value());
return torrent::Object(element_text_value(child_element));
} else if (std::strncmp(root_type, "int", sizeof("int")) == 0 ||
std::strncmp(root_type, "i4", sizeof("i4")) == 0 ||
@@ -98,7 +108,7 @@ xml_value_to_object(const tinyxml2::XMLNode* elem) {
if (child_element == nullptr)
throw rpc_error(XMLRPC_TYPE_ERROR, "empty boolean element");
auto boolean_text = std::string(child_element->ToText()->Value());
auto boolean_text = std::string(element_text_value(child_element));
if (boolean_text == "1")
return torrent::Object((int64_t)1);
@@ -129,7 +139,12 @@ xml_value_to_object(const tinyxml2::XMLNode* elem) {
if (name_element == nullptr)
throw rpc_error(XMLRPC_PARSE_ERROR, "struct member missing name element");
map[name_element->GetText()] = std::move(xml_value_to_object(child->FirstChildElement("value")));
auto name_text = name_element->GetText();
if (name_text == nullptr)
throw rpc_error(XMLRPC_PARSE_ERROR, "struct member has an empty name element");
map[name_text] = std::move(xml_value_to_object(child->FirstChildElement("value")));
}
return map_raw;
@@ -140,7 +155,7 @@ xml_value_to_object(const tinyxml2::XMLNode* elem) {
if (child_element == nullptr)
return torrent::Object("");
return torrent::Object(utils::decode_base64(utils::remove_newlines(child_element->ToText()->Value())));
return torrent::Object(utils::decode_base64(utils::remove_newlines(element_text_value(child_element))));
} else {
throw rpc_error(XMLRPC_INTERNAL_ERROR, "received unsupported value type: " + std::string(root_type));
@@ -300,6 +315,9 @@ process_document(const tinyxml2::XMLDocument* doc, tinyxml2::XMLPrinter* printer
if (doc->FirstChildElement("methodCall")->FirstChildElement("methodName") == nullptr)
throw rpc_error(XMLRPC_PARSE_ERROR, "methodName element not found");
auto method_name = doc->FirstChildElement("methodCall")->FirstChildElement("methodName")->GetText();
if (method_name == nullptr)
throw rpc_error(XMLRPC_PARSE_ERROR, "methodName element is empty");
torrent::Object result;
// Add a shim here for system.multicall to allow better code reuse, and
@@ -310,6 +328,9 @@ process_document(const tinyxml2::XMLDocument* doc, tinyxml2::XMLPrinter* printer
auto parent_elements = element_access(doc->RootElement(), {"params", "param", "value", "array", "data"});
for (auto child = parent_elements->FirstChildElement("value"); child; child = child->NextSiblingElement("value")) {
auto sub_method_name = element_access(child, {"struct", "member", "value", "string"})->GetText();
if (sub_method_name == nullptr)
throw rpc_error(XMLRPC_PARSE_ERROR, "multicall methodName element is empty");
// If sub_params ends up a nullptr at the end of this if-chian,
// execute_command will turn it into an empty list
auto sub_params = element_access(child, {"struct", "member"});
+1 -1
View File
@@ -126,7 +126,7 @@ private:
inline bool SessionManager::is_used() const { return !m_path.empty(); }
inline std::string SessionManager::path() const { return m_path; }
inline bool SessionManager::use_fsyncdisk() const { return true; }
inline bool SessionManager::use_fsyncdisk() const { return m_use_fsyncdisk; }
inline bool SessionManager::use_lock() const { return m_use_lock; }
inline void SessionManager::flush_all_pending_builds() { process_pending_builds(true); }
+6 -6
View File
@@ -37,8 +37,8 @@ parse_main_options(int argc, char** argv) {
optionParser.insert_option('b', [](auto& arg) { rpc::call_command_set_string("network.bind_address.set", arg); });
optionParser.insert_option('d', [](auto& arg) { rpc::call_command_set_string("directory.default.set", arg); });
optionParser.insert_option('i', [](auto& arg) { rpc::call_command_set_string("ip", arg); });
optionParser.insert_option('p', [](auto& arg) { rpc::call_command_set_string("network.port_range.set", arg); });
optionParser.insert_option('i', [](auto& arg) { rpc::call_command_set_string("network.local_address.set", arg); });
optionParser.insert_option('p', [](auto& arg) { rpc::call_command_set_string("network.listen.port.range.set", arg); });
optionParser.insert_option('s', [](auto& arg) { rpc::call_command_set_string("session", arg); });
optionParser.insert_option('O', [](auto& arg) { rpc::parse_command_single_std(arg); });
@@ -157,12 +157,12 @@ load_session_torrents(const std::string& path) {
if (!entry.is_file())
continue;
auto* f = new core::DownloadFactory(control->core());
auto* f = new core::DownloadFactory(control->core(), true);
f->set_session(true);
f->set_init_load(true);
f->slot_finished([f](){ delete f; });
f->load(entries.path() + entry.s_name);
f->load_trusted(entries.path() + entry.s_name);
f->commit();
}
}
@@ -170,12 +170,12 @@ load_session_torrents(const std::string& path) {
void
load_arg_torrents(char** first, char** last) {
for (; first != last; ++first) {
auto* f = new core::DownloadFactory(control->core());
auto* f = new core::DownloadFactory(control->core(), true);
f->set_start(true);
f->set_init_load(true);
f->slot_finished([f](){ delete f; });
f->load(*first);
f->load_trusted(*first);
f->commit();
}
}
+5 -2
View File
@@ -1,3 +1,4 @@
#include <memory>
#include "config.h"
#include <cassert>
@@ -30,8 +31,10 @@
namespace ui {
Download::Download(core::Download* d)
: m_download(d) {
Download::Download(const std::shared_ptr<core::Download>& download)
: m_download(download.get()) {
auto* d = download.get();
m_windowDownloadStatus = std::make_unique<WDownloadStatus>(d);
m_windowDownloadStatus->set_bottom(true);
+2 -1
View File
@@ -2,6 +2,7 @@
#define RTORRENT_UI_DOWNLOAD_H
#include <list>
#include <memory>
#include <torrent/peer/peer.h>
#include "display/manager.h"
@@ -36,7 +37,7 @@ public:
DISPLAY_MAX_SIZE
} Display;
Download(core::Download* d);
Download(const std::shared_ptr<core::Download>& d);
~Download();
void activate(display::Frame* frame, bool focus = true);
+1 -1
View File
@@ -89,7 +89,7 @@ DownloadList::unfocus_download(core::Download* d) {
if (m_state == DISPLAY_DOWNLOAD && d == static_cast<Download*>(m_uiArray[DISPLAY_DOWNLOAD])->download())
activate_display(DISPLAY_DOWNLOAD_LIST);
if (*current_view()->focus() == d && current_view()->focus() < current_view()->end_visible())
if (current_view()->focus() < current_view()->end_visible() && current_view()->focus()->get() == d)
current_view()->next_focus();
}
+1 -1
View File
@@ -196,7 +196,7 @@ ElementDownloadList::receive_cycle_throttle() {
if (m_view->focus() == m_view->end_visible())
return;
core::Download* download = *m_view->focus();
core::Download* download = m_view->focus()->get();
if (download->is_active()) {
lt_log_print(torrent::LOG_TORRENT_WARN, "Cannot change throttle on active download.");
return;
+4 -2
View File
@@ -4,6 +4,7 @@
#include <fstream>
#include <stdexcept>
#include <string>
#include <string.h>
#include <torrent/throttle.h>
#include <torrent/torrent.h>
@@ -354,8 +355,9 @@ Root::reset_input_history_attributes(ui::DownloadList::Input type) {
void
Root::set_input_history_size(int size) {
if (size < 1)
throw torrent::input_error("Invalid input history size.");
if (size < 1 || size > max_input_history_size)
throw torrent::input_error("Input history size must be between 1 and " +
std::to_string(max_input_history_size) + ".");
for (auto& [entry, category] : m_input_history) {
// Reserve the latest input history entries if new size is smaller than original.
+2
View File
@@ -54,6 +54,8 @@ public:
typedef std::vector<std::string> InputHistoryCategory;
typedef std::map<int, InputHistoryCategory> InputHistory;
static constexpr int max_input_history_size = 4096;
Root();
void init(Control* c);
+107
View File
@@ -0,0 +1,107 @@
#include "config.h"
#include "utils/waitpid_queue.h"
#include <sys/wait.h>
#include <torrent/exceptions.h>
namespace utils {
WaitpidQueue::WaitpidQueue() {
m_worker = std::async(std::launch::async, [this]() {
auto wait_time = 50ms;
while (true) {
if (!m_queue.empty()) {
auto start_time = std::chrono::steady_clock::now();
while (std::chrono::steady_clock::now() - start_time < wait_time) {
if (m_should_shutdown.load(std::memory_order_acquire))
return;
std::this_thread::sleep_for(50ms);
if (m_wakeup_worker.load(std::memory_order_acquire))
break;
}
} else {
m_wakeup_worker.wait(false, std::memory_order_acquire);
}
// Adds a small delay to allow new processes to finish if they're quickly spawned and
// terminated.
std::this_thread::sleep_for(50ms);
std::set<pid_t> queue;
{
std::lock_guard<std::mutex> guard(m_mutex);
if (m_should_shutdown)
return;
if (m_queue.empty())
throw torrent::internal_error("WaitpidQueue worker thread woke up but queue is empty.");
queue = m_queue;
m_wakeup_worker.store(false, std::memory_order_release);
}
wait_time = std::min(10 * 1000ms, wait_time * 2);
for (auto pid : queue) {
if (::waitpid(pid, nullptr, WNOHANG) == 0)
continue;
{
std::lock_guard<std::mutex> guard(m_mutex);
if (m_queue.erase(pid) != 1)
throw torrent::internal_error("WaitpidQueue worker thread could not find pid in queue.");
}
wait_time = std::max(50ms, wait_time / 2);
m_remaining.fetch_sub(1, std::memory_order_release);
m_remaining.notify_all();
}
}
});
}
// We don't wait for the worker thread to finish as waitpid isn't needed to be called on shutdown.
WaitpidQueue::~WaitpidQueue() {
{
std::lock_guard<std::mutex> guard(m_mutex);
m_should_shutdown = true;
}
m_wakeup_worker.store(true, std::memory_order_release);
m_wakeup_worker.notify_all();
}
void
WaitpidQueue::close_pid(pid_t pid) {
if (pid < 0)
throw torrent::internal_error("WaitpidQueue::close_pid() called with invalid pid.");
m_remaining.fetch_add(1, std::memory_order_acquire);
{
std::lock_guard<std::mutex> guard(m_mutex);
m_queue.insert(pid);
}
m_wakeup_worker.store(true, std::memory_order_release);
m_wakeup_worker.notify_all();
}
void
WaitpidQueue::wait_for(uint32_t max_remaining) {
while (m_remaining.load(std::memory_order_acquire) > max_remaining)
m_remaining.wait(max_remaining, std::memory_order_acquire);
}
} // namespace torrent::utils
+46
View File
@@ -0,0 +1,46 @@
#ifndef RTORRENT_UTILS_WAITPID_QUEUE_H
#define RTORRENT_UTILS_WAITPID_QUEUE_H
#include <future>
#include <set>
#include <torrent/system/common.h>
namespace utils {
class WaitpidQueue {
public:
WaitpidQueue();
~WaitpidQueue();
uint32_t size() const;
void close_pid(int pid);
void wait_for(uint32_t max_remaining);
// TODO: Add a signal handler to tell the worker thread to wake up. Use a counter to batch wakeups.
private:
WaitpidQueue(const WaitpidQueue&) = delete;
WaitpidQueue& operator=(const WaitpidQueue&) = delete;
std::future<void> m_worker;
align_cacheline
std::mutex m_mutex;
std::set<pid_t> m_queue;
align_cacheline
std::atomic<bool> m_wakeup_worker{};
std::atomic<bool> m_should_shutdown{};
std::atomic<uint32_t> m_remaining{};
};
inline uint32_t WaitpidQueue::size() const { return m_remaining.load(std::memory_order_acquire); }
} // namespace utils
#endif
+7 -5
View File
@@ -9,7 +9,7 @@
#include <utility>
#include <vector>
#include <torrent/utils/scheduler.h>
#include <torrent/system/scheduler.h>
namespace utils {
@@ -49,10 +49,12 @@ private:
void update_status(Entry* entry);
void schedule();
std::map<std::string, Entry> m_entries;
std::vector<Entry*> m_entry_queue;
torrent::utils::SchedulerEntry m_task_process;
bool m_active{true};
std::map<std::string, Entry> m_entries;
std::vector<Entry*> m_entry_queue;
bool m_active{true};
torrent::system::SchedulerEntry m_task_process;
};
}
+8
View File
@@ -35,6 +35,8 @@ rtorrent_Test_Rpc_SOURCES = $(rtorrent_Test_Common) \
rpc/test_command.h \
rpc/test_command_map.cc \
rpc/test_command_map.h \
rpc/test_command_scheduler.cc \
rpc/test_command_scheduler.h \
rpc/test_jsonrpc.cc \
rpc/test_jsonrpc.h \
rpc/test_xmlrpc.cc \
@@ -49,6 +51,12 @@ rtorrent_Test_Rpc_SOURCES = $(rtorrent_Test_Common) \
rtorrent_Test_Src_SOURCES = $(rtorrent_Test_Common) \
src/test_command_dynamic.cc \
src/test_command_dynamic.h \
src/test_command_system.cc \
src/test_command_system.h \
src/test_command_path.cc \
src/test_command_path.h \
src/test_command_string.cc \
src/test_command_string.h \
src/test_watch_ready_queue.cc \
src/test_watch_ready_queue.h
+22 -8
View File
@@ -2,9 +2,10 @@
#define LIBTORRENT_HELPERS_MOCK_COMPARE_H
#include <algorithm>
#include <map>
#include <type_traits>
#include <torrent/event.h>
#include <torrent/net/socket_address.h>
#include <torrent/system/event.h>
// Compare arguments to mock functions with what is expected. The lhs
// are the expected arguments, rhs are the ones called with.
@@ -13,13 +14,13 @@ template <typename Arg>
inline bool mock_compare_arg(Arg lhs, Arg rhs) { return lhs == rhs; }
template <int I, typename A, typename... Args>
typename std::enable_if<I == 1, int>::type
std::enable_if_t<I == 1, int>
mock_compare_tuple(const std::tuple<A, Args...>& lhs, const std::tuple<Args...>& rhs) {
return mock_compare_arg(std::get<I>(lhs), std::get<I - 1>(rhs)) ? 0 : 1;
}
template <int I, typename A, typename... Args>
typename std::enable_if<1 < I, int>::type
std::enable_if_t<1 < I, int>
mock_compare_tuple(const std::tuple<A, Args...>& lhs, const std::tuple<Args...>& rhs) {
auto res = mock_compare_tuple<I - 1>(lhs, rhs);
@@ -70,24 +71,37 @@ void mock_compare_add(T* v) {
// Specialize:
//
constexpr int mock_compare_gt_two_int = -0xFD30;
template <>
inline bool mock_compare_arg<int>(int lhs, int rhs) {
if (lhs == mock_compare_gt_two_int)
return rhs > 2;
if (rhs == mock_compare_gt_two_int)
return lhs > 2;
return lhs == rhs;
}
template <>
inline bool mock_compare_arg<sockaddr*>(sockaddr* lhs, sockaddr* rhs) {
return lhs != nullptr && rhs != nullptr && torrent::sa_equal(lhs, rhs);
}
template <>
inline bool mock_compare_arg<const sockaddr*>(const sockaddr* lhs, const sockaddr* rhs) {
return lhs != nullptr && rhs != nullptr && torrent::sa_equal(lhs, rhs);
}
template <>
inline bool mock_compare_arg<torrent::Event*>(torrent::Event* lhs, torrent::Event* rhs) {
if (mock_compare_map<torrent::Event>::is_key(lhs)) {
if (!mock_compare_map<torrent::Event>::has_value(rhs)) {
mock_compare_map<torrent::Event>::values[lhs] = rhs;
inline bool mock_compare_arg<torrent::system::Event*>(torrent::system::Event* lhs, torrent::system::Event* rhs) {
if (mock_compare_map<torrent::system::Event>::is_key(lhs)) {
if (!mock_compare_map<torrent::system::Event>::has_value(rhs)) {
mock_compare_map<torrent::system::Event>::values[lhs] = rhs;
return true;
}
return mock_compare_map<torrent::Event>::has_key(lhs) && mock_compare_map<torrent::Event>::get(lhs) == rhs;
return mock_compare_map<torrent::system::Event>::has_key(lhs) && mock_compare_map<torrent::system::Event>::get(lhs) == rhs;
}
return lhs == rhs;
+5 -66
View File
@@ -6,9 +6,9 @@
#include <iostream>
#include <unistd.h>
#include "torrent/event.h"
#include "torrent/net/socket_address.h"
#include "torrent/net/fd.h"
#include "torrent/system/event.h"
#include "torrent/utils/log.h"
#include "torrent/utils/random.h"
@@ -32,7 +32,7 @@ mock_clear(bool ignore_assert) {
MOCK_CLEANUP_MAP(torrent::random_uniform_uint16);
MOCK_CLEANUP_MAP(torrent::random_uniform_uint32);
mock_compare_map<torrent::Event>::values.clear();
mock_compare_map<torrent::system::Event>::values.clear();
}
} // namespace
@@ -55,7 +55,9 @@ mock_redirect_defaults([[maybe_unused]] mock_redirect_flags flags) {
mock_redirect(torrent::fd__close, std::function<int(int fildes)>([](int fildes) { return ::close(fildes); }));
mock_redirect(torrent::fd__fcntl_int, std::function<int(int fildes, int cmd, int arg)>([](int fildes, int cmd, int arg) { return ::fcntl(fildes, cmd, arg); }));
mock_redirect(torrent::fd__setsockopt_int, std::function<int(int socket, int level, int option_name, int option_value)>([](int socket, int level, int option_name, int option_value) { return ::setsockopt(socket, level, option_name, &option_value, sizeof(int)); }));
mock_redirect(torrent::fd__setsockopt_int, std::function<int(int socket, int level, int option_name, int option_value)>([](int socket, int level, int option_name, int option_value) {
return ::setsockopt(socket, level, option_name, &option_value, sizeof(int));
}));
mock_redirect(torrent::fd__socket, std::function<int(int domain, int type, int protocol)>([](int domain, int type, int protocol) { return ::socket(domain, type, protocol); }));
}
@@ -112,69 +114,6 @@ int fd__socket(int domain, int type, int protocol) {
return mock_call<int>(__func__, &torrent::fd__socket, domain, type, protocol);
}
//
// Mock functions for 'torrent/common.h':
//
namespace this_thread {
void event_open(Event* event) {
MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name());
return mock_call<void>(__func__, &torrent::this_thread::event_open, event);
}
void event_open_and_count(Event* event) {
MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name());
return mock_call<void>(__func__, &torrent::this_thread::event_open_and_count, event);
}
void event_close_and_count(Event* event) {
MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name());
return mock_call<void>(__func__, &torrent::this_thread::event_close_and_count, event);
}
void event_closed_and_count(Event* event) {
MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name());
return mock_call<void>(__func__, &torrent::this_thread::event_closed_and_count, event);
}
void event_insert_read(Event* event) {
MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name());
return mock_call<void>(__func__, &torrent::this_thread::event_insert_read, event);
}
void event_insert_write(Event* event) {
MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name());
return mock_call<void>(__func__, &torrent::this_thread::event_insert_write, event);
}
void event_insert_error(Event* event) {
MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name());
return mock_call<void>(__func__, &torrent::this_thread::event_insert_error, event);
}
void event_remove_read(Event* event) {
MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name());
return mock_call<void>(__func__, &torrent::this_thread::event_remove_read, event);
}
void event_remove_write(Event* event) {
MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name());
return mock_call<void>(__func__, &torrent::this_thread::event_remove_write, event);
}
void event_remove_error(Event* event) {
MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name());
return mock_call<void>(__func__, &torrent::this_thread::event_remove_error, event);
}
void event_remove_and_close(Event* event) {
MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name());
return mock_call<void>(__func__, &torrent::this_thread::event_remove_and_close, event);
}
}
//
// Mock functions for 'torrent/utils/random.h':
//
-1
View File
@@ -8,7 +8,6 @@
#include "torrent/exceptions.h"
#include "torrent/net/resolver.h"
#include "torrent/utils/log.h"
#include "torrent/utils/scheduler.h"
std::unique_ptr<TestMainThread>
TestMainThread::create() {
+65
View File
@@ -0,0 +1,65 @@
#include "config.h"
#include "test/rpc/test_command_scheduler.h"
#include <chrono>
#include "rpc/command_scheduler.h"
#include "rpc/command_scheduler_item.h"
#include "torrent/object.h"
CPPUNIT_TEST_SUITE_REGISTRATION(TestCommandScheduler);
namespace {
const torrent::Object test_command = torrent::Object(std::string("print=scheduled"));
std::chrono::microseconds
time_scheduled(rpc::CommandScheduler& scheduler, const std::string& key) {
auto itr = scheduler.find(key);
CPPUNIT_ASSERT(itr != scheduler.end());
return (*itr)->time_scheduled();
}
}
void
TestCommandScheduler::setUp() {
TestFixtureWithMainThread::setUp();
m_main_thread->test_set_cached_time(std::chrono::seconds(0));
}
void
TestCommandScheduler::tearDown() {
TestFixtureWithMainThread::tearDown();
}
void
TestCommandScheduler::test_parse_rearms_existing_key() {
rpc::CommandScheduler scheduler;
scheduler.parse("key", "3600", "3600", test_command);
auto first = time_scheduled(scheduler, "key");
m_main_thread->test_add_cached_time(std::chrono::seconds(600));
scheduler.parse("key", "3600", "3600", test_command);
CPPUNIT_ASSERT_EQUAL(size_t{1}, scheduler.size());
CPPUNIT_ASSERT(time_scheduled(scheduler, "key") == first + std::chrono::seconds(600));
}
void
TestCommandScheduler::test_find_locates_a_scheduled_key() {
rpc::CommandScheduler scheduler;
CPPUNIT_ASSERT(scheduler.find("key") == scheduler.end());
scheduler.parse("key", "3600", "3600", test_command);
CPPUNIT_ASSERT(scheduler.find("key") != scheduler.end());
CPPUNIT_ASSERT(scheduler.find("other") == scheduler.end());
}
+17
View File
@@ -0,0 +1,17 @@
#include "test/helpers/test_main_thread.h"
class TestCommandScheduler : public TestFixtureWithMainThread {
CPPUNIT_TEST_SUITE(TestCommandScheduler);
CPPUNIT_TEST(test_parse_rearms_existing_key);
CPPUNIT_TEST(test_find_locates_a_scheduled_key);
CPPUNIT_TEST_SUITE_END();
public:
void setUp();
void tearDown();
void test_parse_rearms_existing_key();
void test_find_locates_a_scheduled_key();
};
+116
View File
@@ -0,0 +1,116 @@
#include "config.h"
#include "test/src/test_command_path.h"
#include <cstdlib>
#include <sys/stat.h>
#include <unistd.h>
#include <torrent/exceptions.h>
#include <torrent/torrent.h>
#include "control.h"
#include "globals.h"
#include "rpc/parse_commands.h"
CPPUNIT_TEST_SUITE_REGISTRATION(TestCommandPath);
void initialize_command_local();
void
TestCommandPath::setUp() {
char temp_dir[] = "/tmp/rtorrent_test_path_XXXXXX";
CPPUNIT_ASSERT(mkdtemp(temp_dir) != nullptr);
// The temporary directory itself may sit behind a symlink, as /tmp does on
// macOS, so resolve it up front to keep the expected values exact.
m_temp_dir = resolve_path(temp_dir);
CPPUNIT_ASSERT(!m_temp_dir.empty());
CPPUNIT_ASSERT_EQUAL(0, mkdir((m_temp_dir + "/data").c_str(), 0755));
CPPUNIT_ASSERT_EQUAL(0, symlink((m_temp_dir + "/data").c_str(), (m_temp_dir + "/link").c_str()));
}
void
TestCommandPath::tearDown() {
unlink((m_temp_dir + "/link").c_str());
rmdir((m_temp_dir + "/data").c_str());
rmdir(m_temp_dir.c_str());
}
void
TestCommandPath::test_resolves_symlink() {
CPPUNIT_ASSERT_EQUAL(m_temp_dir + "/data", resolve_path(m_temp_dir + "/link"));
// A path that lies below a symlinked directory is resolved as well.
CPPUNIT_ASSERT_EQUAL(0, mkdir((m_temp_dir + "/data/below").c_str(), 0755));
CPPUNIT_ASSERT_EQUAL(m_temp_dir + "/data/below", resolve_path(m_temp_dir + "/link/below"));
rmdir((m_temp_dir + "/data/below").c_str());
}
void
TestCommandPath::test_removes_relative_components() {
CPPUNIT_ASSERT_EQUAL(m_temp_dir, resolve_path(m_temp_dir + "/data/.."));
CPPUNIT_ASSERT_EQUAL(m_temp_dir + "/data", resolve_path(m_temp_dir + "/./data"));
CPPUNIT_ASSERT_EQUAL(m_temp_dir + "/data", resolve_path(m_temp_dir + "/data/"));
// Trailing slashes and duplicated separators collapse.
CPPUNIT_ASSERT_EQUAL(m_temp_dir + "/data", resolve_path(m_temp_dir + "//data//"));
}
void
TestCommandPath::test_expands_tilde() {
const char* home = std::getenv("HOME");
if (home == nullptr || *home == '\0')
return;
CPPUNIT_ASSERT_EQUAL(resolve_path(home), resolve_path("~"));
CPPUNIT_ASSERT_THROW(resolve_path("~root/somewhere"), torrent::input_error);
}
void
TestCommandPath::test_missing_path_throws() {
CPPUNIT_ASSERT_THROW(resolve_path_or_throw(""), torrent::input_error);
CPPUNIT_ASSERT_THROW(resolve_path_or_throw(m_temp_dir + "/does_not_exist"), torrent::input_error);
CPPUNIT_ASSERT_EQUAL(m_temp_dir + "/data", resolve_path_or_throw(m_temp_dir + "/link"));
}
void
TestCommandPath::test_missing_path_is_empty() {
CPPUNIT_ASSERT_EQUAL(std::string(), resolve_path(""));
CPPUNIT_ASSERT_EQUAL(std::string(), resolve_path(m_temp_dir + "/does_not_exist"));
CPPUNIT_ASSERT_EQUAL(std::string(), resolve_path(m_temp_dir + "/does_not_exist/below"));
// A dangling symlink does not name an existing path either.
CPPUNIT_ASSERT_EQUAL(0, symlink((m_temp_dir + "/gone").c_str(), (m_temp_dir + "/dangling").c_str()));
CPPUNIT_ASSERT_EQUAL(std::string(), resolve_path(m_temp_dir + "/dangling"));
unlink((m_temp_dir + "/dangling").c_str());
}
void
TestCommandPath::test_commands() {
torrent::initialize_main_thread();
torrent::initialize();
if (control == nullptr)
control = new Control;
if (!rpc::commands.has("directory.default.realpath.or_empty"))
initialize_command_local();
rpc::commands.call_command("directory.default.set", m_temp_dir + "/link");
CPPUNIT_ASSERT_EQUAL(m_temp_dir + "/link", rpc::commands.call_command("directory.default", torrent::Object()).as_string());
CPPUNIT_ASSERT_EQUAL(m_temp_dir + "/data", rpc::commands.call_command("directory.default.realpath.or_empty", torrent::Object()).as_string());
CPPUNIT_ASSERT_EQUAL(m_temp_dir + "/data", rpc::commands.call_command("directory.default.realpath.or_throw", torrent::Object()).as_string());
// A directory that has not been created yet resolves to nothing, and the
// or_throw variant reports it instead.
rpc::commands.call_command("directory.default.set", m_temp_dir + "/missing");
CPPUNIT_ASSERT_EQUAL(std::string(), rpc::commands.call_command("directory.default.realpath.or_empty", torrent::Object()).as_string());
CPPUNIT_ASSERT_THROW(rpc::commands.call_command("directory.default.realpath.or_throw", torrent::Object()), torrent::input_error);
torrent::cleanup();
}
+30
View File
@@ -0,0 +1,30 @@
#include "test/helpers/test_fixture.h"
#include <string>
class TestCommandPath : public test_fixture {
CPPUNIT_TEST_SUITE(TestCommandPath);
CPPUNIT_TEST(test_resolves_symlink);
CPPUNIT_TEST(test_removes_relative_components);
CPPUNIT_TEST(test_expands_tilde);
CPPUNIT_TEST(test_missing_path_is_empty);
CPPUNIT_TEST(test_missing_path_throws);
CPPUNIT_TEST(test_commands);
CPPUNIT_TEST_SUITE_END();
public:
void setUp();
void tearDown();
void test_resolves_symlink();
void test_removes_relative_components();
void test_expands_tilde();
void test_missing_path_is_empty();
void test_missing_path_throws();
void test_commands();
private:
std::string m_temp_dir;
};
+261
View File
@@ -0,0 +1,261 @@
#include "config.h"
#include "test/src/test_command_string.h"
#include "rpc/parse_commands.h"
CPPUNIT_TEST_SUITE_REGISTRATION(TestCommandString);
void initialize_command_string();
namespace {
torrent::Object
args(std::initializer_list<torrent::Object> objects) {
auto result = torrent::Object::create_list();
for (const auto& object : objects)
result.as_list().push_back(object);
return result;
}
std::string
call_string(const char* key, std::initializer_list<torrent::Object> objects) {
return rpc::commands.call_command(key, args(objects)).as_string();
}
int64_t
call_value(const char* key, std::initializer_list<torrent::Object> objects) {
return rpc::commands.call_command(key, args(objects)).as_value();
}
torrent::Object::list_type
call_list(const char* key, std::initializer_list<torrent::Object> objects) {
return rpc::commands.call_command(key, args(objects)).as_list();
}
// Runs a command the way a line in the configuration file would.
torrent::Object
parse(const char* command) {
return rpc::parse_command_single(rpc::make_target(), command);
}
}
void
TestCommandString::setUp() {
if (!rpc::commands.has("string.length"))
initialize_command_string();
}
void
TestCommandString::tearDown() {
}
void
TestCommandString::test_length() {
CPPUNIT_ASSERT_EQUAL(int64_t(0), call_value("string.length", {""}));
CPPUNIT_ASSERT_EQUAL(int64_t(3), call_value("string.length", {"abc"}));
// The length is counted in utf-8 characters, not bytes.
CPPUNIT_ASSERT_EQUAL(int64_t(5), call_value("string.length", {"héllo"}));
CPPUNIT_ASSERT_EQUAL(int64_t(3), call_value("string.length", {"日本語"}));
// Values are converted to their string representation.
CPPUNIT_ASSERT_EQUAL(int64_t(4), call_value("string.length", {int64_t(1234)}));
}
void
TestCommandString::test_equals() {
CPPUNIT_ASSERT_EQUAL(int64_t(1), call_value("string.equals", {"abc", "abc"}));
CPPUNIT_ASSERT_EQUAL(int64_t(0), call_value("string.equals", {"abc", "abd"}));
CPPUNIT_ASSERT_EQUAL(int64_t(0), call_value("string.equals", {"abc", "ab"}));
CPPUNIT_ASSERT_EQUAL(int64_t(1), call_value("string.equals", {"abc", "x", "abc"}));
CPPUNIT_ASSERT_EQUAL(int64_t(0), call_value("string.equals", {"abc", "x", "y"}));
CPPUNIT_ASSERT_EQUAL(int64_t(1), call_value("string.equals", {int64_t(42), "42"}));
}
void
TestCommandString::test_starts_with() {
CPPUNIT_ASSERT_EQUAL(int64_t(1), call_value("string.starts_with", {"abcdef", "abc"}));
CPPUNIT_ASSERT_EQUAL(int64_t(1), call_value("string.starts_with", {"abcdef", ""}));
CPPUNIT_ASSERT_EQUAL(int64_t(0), call_value("string.starts_with", {"abcdef", "bcd"}));
CPPUNIT_ASSERT_EQUAL(int64_t(0), call_value("string.starts_with", {"ab", "abc"}));
CPPUNIT_ASSERT_EQUAL(int64_t(1), call_value("string.starts_with", {"abcdef", "x", "ab"}));
}
void
TestCommandString::test_ends_with() {
CPPUNIT_ASSERT_EQUAL(int64_t(1), call_value("string.ends_with", {"abcdef", "def"}));
CPPUNIT_ASSERT_EQUAL(int64_t(1), call_value("string.ends_with", {"abcdef", ""}));
CPPUNIT_ASSERT_EQUAL(int64_t(0), call_value("string.ends_with", {"abcdef", "cde"}));
CPPUNIT_ASSERT_EQUAL(int64_t(0), call_value("string.ends_with", {"ef", "def"}));
CPPUNIT_ASSERT_EQUAL(int64_t(1), call_value("string.ends_with", {"a.torrent", ".rar", ".torrent"}));
}
void
TestCommandString::test_contains() {
CPPUNIT_ASSERT_EQUAL(int64_t(1), call_value("string.contains", {"abcdef", "cde"}));
CPPUNIT_ASSERT_EQUAL(int64_t(0), call_value("string.contains", {"abcdef", "ace"}));
CPPUNIT_ASSERT_EQUAL(int64_t(1), call_value("string.contains", {"abcdef", "x", "bcd"}));
CPPUNIT_ASSERT_EQUAL(int64_t(0), call_value("string.contains", {"retracker.local", "RETRACKER"}));
CPPUNIT_ASSERT_EQUAL(int64_t(1), call_value("string.contains_i", {"retracker.local", "RETRACKER"}));
CPPUNIT_ASSERT_EQUAL(int64_t(1), call_value("string.contains_i", {"RETRACKER.LOCAL", "retracker"}));
CPPUNIT_ASSERT_EQUAL(int64_t(0), call_value("string.contains_i", {"abcdef", "xyz"}));
}
void
TestCommandString::test_substr() {
CPPUNIT_ASSERT_EQUAL(std::string("abcdef"), call_string("string.substr", {"abcdef"}));
CPPUNIT_ASSERT_EQUAL(std::string("cdef"), call_string("string.substr", {"abcdef", int64_t(2)}));
CPPUNIT_ASSERT_EQUAL(std::string("cde"), call_string("string.substr", {"abcdef", int64_t(2), int64_t(3)}));
CPPUNIT_ASSERT_EQUAL(std::string("cdef"), call_string("string.substr", {"abcdef", int64_t(2), int64_t(100)}));
CPPUNIT_ASSERT_EQUAL(std::string(""), call_string("string.substr", {"abcdef", int64_t(2), int64_t(0)}));
// Negative positions are relative to the end of the string.
CPPUNIT_ASSERT_EQUAL(std::string("ef"), call_string("string.substr", {"abcdef", int64_t(-2)}));
CPPUNIT_ASSERT_EQUAL(std::string("e"), call_string("string.substr", {"abcdef", int64_t(-2), int64_t(1)}));
// Out-of-bounds positions return the default value.
CPPUNIT_ASSERT_EQUAL(std::string(""), call_string("string.substr", {"abcdef", int64_t(10)}));
CPPUNIT_ASSERT_EQUAL(std::string("n/a"), call_string("string.substr", {"abcdef", int64_t(10), int64_t(1), "n/a"}));
CPPUNIT_ASSERT_EQUAL(std::string("n/a"), call_string("string.substr", {"abcdef", int64_t(-10), int64_t(1), "n/a"}));
// Positions and counts are in utf-8 characters, not bytes.
CPPUNIT_ASSERT_EQUAL(std::string("本"), call_string("string.substr", {"日本語", int64_t(1), int64_t(1)}));
CPPUNIT_ASSERT_EQUAL(std::string("本語"), call_string("string.substr", {"日本語", int64_t(-2)}));
}
void
TestCommandString::test_split() {
auto parts = call_list("string.split", {"a,b,c", ","});
CPPUNIT_ASSERT_EQUAL(size_t(3), parts.size());
CPPUNIT_ASSERT_EQUAL(std::string("a"), parts.front().as_string());
CPPUNIT_ASSERT_EQUAL(std::string("c"), parts.back().as_string());
// Empty fields are preserved.
CPPUNIT_ASSERT_EQUAL(size_t(3), call_list("string.split", {"a,,b", ","}).size());
CPPUNIT_ASSERT_EQUAL(size_t(1), call_list("string.split", {"abc", ","}).size());
// A multi-character delimiter is matched as a whole.
CPPUNIT_ASSERT_EQUAL(size_t(2), call_list("string.split", {"a::b", "::"}).size());
// An empty delimiter splits the text into its utf-8 characters.
auto characters = call_list("string.split", {"日本語", ""});
CPPUNIT_ASSERT_EQUAL(size_t(3), characters.size());
CPPUNIT_ASSERT_EQUAL(std::string("日"), characters.front().as_string());
}
void
TestCommandString::test_join() {
CPPUNIT_ASSERT_EQUAL(std::string("a, b"), call_string("string.join", {", ", "a", "b"}));
CPPUNIT_ASSERT_EQUAL(std::string("ab"), call_string("string.join", {"", "a", "b"}));
CPPUNIT_ASSERT_EQUAL(std::string(""), call_string("string.join", {", "}));
CPPUNIT_ASSERT_EQUAL(std::string("a"), call_string("string.join", {", ", "a"}));
CPPUNIT_ASSERT_EQUAL(std::string("1-2"), call_string("string.join", {"-", int64_t(1), int64_t(2)}));
// Lists are flattened, so the output of string.split can be joined again.
CPPUNIT_ASSERT_EQUAL(std::string("a-b-c"), call_string("string.join", {"-", args({"a", "b"}), "c"}));
}
void
TestCommandString::test_pad() {
CPPUNIT_ASSERT_EQUAL(std::string(" 7"), call_string("string.lpad", {"7", int64_t(3)}));
CPPUNIT_ASSERT_EQUAL(std::string("7 "), call_string("string.rpad", {"7", int64_t(3)}));
CPPUNIT_ASSERT_EQUAL(std::string("007"), call_string("string.lpad", {"7", int64_t(3), "0"}));
CPPUNIT_ASSERT_EQUAL(std::string("700"), call_string("string.rpad", {"7", int64_t(3), "0"}));
// Text that is already long enough is returned unchanged.
CPPUNIT_ASSERT_EQUAL(std::string("abcd"), call_string("string.lpad", {"abcd", int64_t(2)}));
CPPUNIT_ASSERT_EQUAL(std::string("abcd"), call_string("string.rpad", {"abcd", int64_t(4)}));
// Multi-character padding is repeated, and an empty padding is a no-op.
CPPUNIT_ASSERT_EQUAL(std::string("axyx"), call_string("string.rpad", {"a", int64_t(4), "xy"}));
CPPUNIT_ASSERT_EQUAL(std::string("a"), call_string("string.rpad", {"a", int64_t(4), ""}));
// Padding is counted in utf-8 characters, not bytes.
CPPUNIT_ASSERT_EQUAL(std::string("00日"), call_string("string.lpad", {"日", int64_t(3), "0"}));
}
void
TestCommandString::test_strip() {
CPPUNIT_ASSERT_EQUAL(std::string("a b"), call_string("string.strip", {" a b "}));
CPPUNIT_ASSERT_EQUAL(std::string("a"), call_string("string.strip", {"\t\n a \r\n"}));
CPPUNIT_ASSERT_EQUAL(std::string("a "), call_string("string.lstrip", {" a "}));
CPPUNIT_ASSERT_EQUAL(std::string(" a"), call_string("string.rstrip", {" a "}));
CPPUNIT_ASSERT_EQUAL(std::string("a"), call_string("string.strip", {"xxaxx", "x"}));
CPPUNIT_ASSERT_EQUAL(std::string("a"), call_string("string.strip", {"/x/a/x/", "/", "x"}));
CPPUNIT_ASSERT_EQUAL(std::string(""), call_string("string.strip", {"aaa", "a"}));
CPPUNIT_ASSERT_EQUAL(std::string(" a "), call_string("string.strip", {" a ", ""}));
// The strippable argument is a set of utf-8 characters.
CPPUNIT_ASSERT_EQUAL(std::string("a"), call_string("string.strip", {"日a日", "日"}));
}
void
TestCommandString::test_map() {
CPPUNIT_ASSERT_EQUAL(std::string("b"), call_string("string.map", {"a", args({"a", "b"})}));
CPPUNIT_ASSERT_EQUAL(std::string("c"), call_string("string.map", {"c", args({"a", "b"})}));
// Only whole-string matches are replaced.
CPPUNIT_ASSERT_EQUAL(std::string("ab"), call_string("string.map", {"ab", args({"a", "b"})}));
// The first matching pair wins.
CPPUNIT_ASSERT_EQUAL(std::string("y"), call_string("string.map", {"x", args({"a", "b"}), args({"x", "y"}), args({"x", "z"})}));
}
void
TestCommandString::test_replace() {
CPPUNIT_ASSERT_EQUAL(std::string("a+b+c"), call_string("string.replace", {"a-b-c", args({"-", "+"})}));
CPPUNIT_ASSERT_EQUAL(std::string("abc"), call_string("string.replace", {"a-b-c", args({"-", ""})}));
CPPUNIT_ASSERT_EQUAL(std::string("a-b-c"), call_string("string.replace", {"a-b-c", args({"x", "y"})}));
// Pairs are applied in order, left to right.
CPPUNIT_ASSERT_EQUAL(std::string("xby"), call_string("string.replace", {"abc", args({"a", "x"}), args({"c", "y"})}));
// A replacement that contains the replaced text terminates.
CPPUNIT_ASSERT_EQUAL(std::string("aaaa"), call_string("string.replace", {"aa", args({"a", "aa"})}));
}
void
TestCommandString::test_invalid_arguments() {
CPPUNIT_ASSERT_THROW(rpc::commands.call_command("string.length", torrent::Object()), torrent::input_error);
CPPUNIT_ASSERT_THROW(call_string("string.length", {"a", "b"}), torrent::input_error);
CPPUNIT_ASSERT_THROW(call_value("string.equals", {"a"}), torrent::input_error);
CPPUNIT_ASSERT_THROW(call_value("string.contains", {"a"}), torrent::input_error);
CPPUNIT_ASSERT_THROW(call_list("string.split", {"a"}), torrent::input_error);
CPPUNIT_ASSERT_THROW(call_string("string.lpad", {"a"}), torrent::input_error);
// The character count of string.substr cannot be negative.
CPPUNIT_ASSERT_THROW(call_string("string.substr", {"abc", int64_t(0), int64_t(-1)}), torrent::input_error);
// Both string.map and string.replace require {old, new} pairs.
CPPUNIT_ASSERT_THROW(call_string("string.map", {"a", "b"}), torrent::input_error);
CPPUNIT_ASSERT_THROW(call_string("string.replace", {"a", args({"a", "b", "c"})}), torrent::input_error);
CPPUNIT_ASSERT_THROW(call_string("string.replace", {"a", args({"", "b"})}), torrent::input_error);
}
void
TestCommandString::test_config_syntax() {
CPPUNIT_ASSERT_EQUAL(int64_t(3), parse("string.length=abc").as_value());
CPPUNIT_ASSERT_EQUAL(int64_t(1), parse("string.contains=retracker.local,retracker").as_value());
CPPUNIT_ASSERT_EQUAL(int64_t(1), parse("string.starts_with=udp://tracker.example.com,http://,udp://").as_value());
CPPUNIT_ASSERT_EQUAL(std::string("cde"), parse("string.substr=abcdef,2,3").as_string());
CPPUNIT_ASSERT_EQUAL(std::string("padded"), parse("string.strip=\" padded \"").as_string());
// The {old, new} pairs are written as a block in the configuration file.
CPPUNIT_ASSERT_EQUAL(std::string("a+b+c"), parse("string.replace=a-b-c,{-,+}").as_string());
CPPUNIT_ASSERT_EQUAL(std::string("y"), parse("string.map=x,{a,b},{x,y}").as_string());
// Commands nest, so a split can be joined back together.
CPPUNIT_ASSERT_EQUAL(std::string("a-b-c"), parse("string.join=-,(string.split,a.b.c,.)").as_string());
}
+41
View File
@@ -0,0 +1,41 @@
#include "test/helpers/test_fixture.h"
class TestCommandString : public test_fixture {
CPPUNIT_TEST_SUITE(TestCommandString);
CPPUNIT_TEST(test_length);
CPPUNIT_TEST(test_equals);
CPPUNIT_TEST(test_starts_with);
CPPUNIT_TEST(test_ends_with);
CPPUNIT_TEST(test_contains);
CPPUNIT_TEST(test_substr);
CPPUNIT_TEST(test_split);
CPPUNIT_TEST(test_join);
CPPUNIT_TEST(test_pad);
CPPUNIT_TEST(test_strip);
CPPUNIT_TEST(test_map);
CPPUNIT_TEST(test_replace);
CPPUNIT_TEST(test_invalid_arguments);
CPPUNIT_TEST(test_config_syntax);
CPPUNIT_TEST_SUITE_END();
public:
void setUp();
void tearDown();
void test_length();
void test_equals();
void test_starts_with();
void test_ends_with();
void test_contains();
void test_substr();
void test_split();
void test_join();
void test_pad();
void test_strip();
void test_map();
void test_replace();
void test_invalid_arguments();
void test_config_syntax();
};
+54
View File
@@ -0,0 +1,54 @@
#include "config.h"
#include "test/src/test_command_system.h"
#include <torrent/torrent.h>
#include <torrent/runtime/socket_manager.h>
#include <torrent/utils/option_strings.h>
#include "control.h"
#include "globals.h"
#include "rpc/parse_commands.h"
CPPUNIT_TEST_SUITE_REGISTRATION(TestCommandSystem);
void initialize_command_system();
void
TestCommandSystem::setUp() {
torrent::initialize_main_thread();
torrent::initialize();
if (control == nullptr)
control = new Control;
if (!rpc::commands.has("system.sockets.size"))
initialize_command_system();
}
void
TestCommandSystem::tearDown() {
torrent::cleanup();
}
void
TestCommandSystem::test_socket_category_commands() {
for (uint32_t i = 0; i < torrent::runtime::SocketManager::category_count; ++i) {
auto category = static_cast<torrent::runtime::socket_manager_category_t>(i);
auto name = "system.sockets." + torrent::option_to_str_or_throw(torrent::OPTION_SOCKET_CATEGORY, i);
for (const auto suffix : {".size", ".max_size", ".min_alloc", ".max_alloc"})
if (rpc::commands.has(name + suffix))
CPPUNIT_ASSERT_NO_THROW(rpc::commands.call(name + suffix));
const bool has_allocation = category != torrent::runtime::category_generic;
CPPUNIT_ASSERT(rpc::commands.has(name + ".size"));
CPPUNIT_ASSERT(rpc::commands.has(name + ".max_size"));
CPPUNIT_ASSERT(rpc::commands.has(name + ".min_alloc"));
CPPUNIT_ASSERT_EQUAL(has_allocation, rpc::commands.has(name + ".max_alloc"));
CPPUNIT_ASSERT_EQUAL(has_allocation, rpc::commands.has(name + ".min_alloc.set"));
CPPUNIT_ASSERT_EQUAL(has_allocation, rpc::commands.has(name + ".max_alloc.set"));
}
}
+15
View File
@@ -0,0 +1,15 @@
#include "test/helpers/test_fixture.h"
class TestCommandSystem : public test_fixture {
CPPUNIT_TEST_SUITE(TestCommandSystem);
CPPUNIT_TEST(test_socket_category_commands);
CPPUNIT_TEST_SUITE_END();
public:
void setUp();
void tearDown();
void test_socket_category_commands();
};