Compare commits

...

143 Commits

Author SHA1 Message Date
rakshasa f6ea7b326f Merge branch 'master' into silasmariusz-exec-arg-buffer 2026-10-04 09:35:41 +02:00
Silas Mariusz 4492474f7e Use heap allocated buffer for rpc::ExecFile::execute_object() and increase size to 128Kb. 2026-10-04 09:34:42 +02:00
rakshasa 3af0410a6e Restrict parse value to strtoll with restrictions on input. 2026-10-01 10:32:43 +02:00
rakshasa 3c94b03323 Restrict parse value to strtoll with restrictions on input. 2026-10-01 10:32:43 +02:00
rakshasa c5d54fbb97 Restrict parse value to strtoll with restrictions on input. 2026-10-01 10:32:43 +02:00
Silas Mariusz 44d51171e1 Mark the system.file.allocate getter untrusted-safe, because d.open reads it. 2026-09-30 10:55:48 +02:00
noctuum dc916278c2 Do not switch an active download to initial_seed
`confirm_finished` did, and the `input_error` reached `main`.
2026-09-29 10:51:58 +02:00
noctuum 366b936ded Check m_entry against size() in set_entry 2026-09-29 10:18:35 +02:00
noctuum 8bcc4c6e92 Read the unpacked object in the raw string case
A list holding one raw string reached this case and threw on `src`.
2026-09-29 10:00:30 +02:00
noctuum 5421a35349 Remove code that has no remaining user
Three declarations had no definition and the rest had no user left.
2026-09-28 18:40:17 +02:00
noctuum d5ce0984fc Reset the freed pointers in XmlRpc::cleanup
`is_valid()` tests `m_env`, so it stayed true after `cleanup()`.
2026-09-28 18:01:05 +02:00
noctuum a89ce3cd7b Check first against last in parse_object
A command ending in `=` reaches it with an empty range.
2026-09-28 17:34:06 +02:00
noctuum d98a441dc4 Sort the transfer chunks by index
The default comparison ordered the pointers, not the chunk indexes.
2026-09-28 17:11:08 +02:00
noctuum fc6e57c3fa Check pthread_sigmask by its return value
`pthread_sigmask` returns the error number and does not set `errno`.
2026-09-28 16:46:02 +02:00
noctuum 8e23ba8b17 Reject an empty argument list in execute_lua
`lua.execute` with no arguments dereferenced `args.begin()`.
2026-09-28 11:36:53 +02:00
noctuum e656801cfa Limit tab completion to text before the cursor
`substr` takes a count, so the text after the cursor joined the prefix.
2026-09-28 11:00:25 +02:00
noctuum 9f6fd7994e Guard control in the main catch handlers
A start without HOME throws before control is constructed.
2026-09-28 10:46:13 +02:00
noctuum bb77aa644c Stop at max_active in scheduler.simple.removed
The limit was checked once, then every download was resumed.
2026-09-28 10:28:46 +02:00
xirvik 83a5e7687b Sync the session directory after committing the renames
Syncing the files alone does not make the new names survive a crash.
2026-09-25 09:31:08 +02:00
Jari Sundell 910b9b35fc Reorder mutex and atomic variable declarations 2026-09-25 08:52:56 +02:00
xirvik e3051df78c Give SessionManager::m_active its own cache line
Prevents false sharing with the adjacent m_mutex under concurrent access.
2026-09-25 08:52:56 +02:00
xirvik 21287a1355 Make SessionManager::m_active atomic
It is written under m_mutex but read under m_pending_builds_mutex in one place.
2026-09-25 08:52:56 +02:00
xirvik 6471dc181e Guard the download against handlers that erase it mid-close
Track a weak_ptr lifetime handle and make erase ignore re-entrant erase.
2026-09-24 10:22:40 +02:00
xirvik a5c39566c8 Write session files with O_NOFOLLOW and mode 0600
Also make directory_entry::is_file() report the real type so symlinks are skipped.
2026-09-24 09:52:03 +02:00
xirvik 019c16a077 rpc: multicall requires methodName as the struct's first member
Faults clearly instead of the confusing positional parse error it replaces.
2026-09-24 09:20:25 +02:00
xirvik 439d23ce62 Bound JSON-RPC input by size and nesting depth
Enforce the nesting bound in one parse, capping allocation by depth, not input size.
2026-09-23 09:25:25 +02:00
xirvik 83b03c2c1e Range-check global throttle rates before narrowing them to kB.
Multiplying kB back to bytes in unsigned int wrapped 2^32 to 0, which means unlimited.
2026-09-23 09:01:02 +02:00
rakshasa 05cdd03cd2 Tagged release 0.16.24. 2026-09-22 15:24:24 +02:00
xirvik d1a6942ea7 Keep the object_storage key terminator inside the key buffer
find_raw_string now honours the raw_string size instead of scanning for a nul.
2026-09-21 09:22:26 +02:00
xirvik 3652329e2d Pass the typed download filter as an argument, not command text
A pattern containing '}' closed the match command early and ran the rest as commands.
2026-09-20 16:39:14 +02:00
xirvik 7916e6022f Stop the value command on the untrusted-safe allowlist crashing the daemon
glibc leaves endptr unset for a small invalid base; ERANGE throws instead of clamping.
2026-09-20 15:58:47 +02:00
xirvik c59da0848e Dump the ipv4 filter by reference instead of copying the whole map
A static table owns the map; the loop only reads it on the main thread. No test needed.
2026-09-20 15:33:09 +02:00
xirvik e2f66ff08c Drop the address range parser and its includes, dead since 29edda2f
External linkage kept -Wunused-function quiet; a deletion of dead code needs no test.
2026-09-20 15:01:44 +02:00
xirvik f0b6f2cb47 Fail p.multicall when a command changes the connection list
Disconnecting a peer frees it and reorders the list, so the iteration cannot continue.
2026-09-20 14:05:49 +02:00
xirvik d334b8d339 Stop printing a strerror of si_errno in the SIGBUS dump
Linux leaves si_errno zero there, so the line always read Error: Success.
2026-09-20 10:05:55 +02:00
xirvik 19b71a98b7 Fix data race reading the log buffer from the display thread
Copy entries under the buffer lock before rendering; other threads mutate the deque.
2026-09-20 09:42:59 +02:00
xirvik 8ee3b984dd Give network.xmlrpc.size_limit.set a floor
A limit below any valid request rejects the request that would raise it again.
2026-09-20 09:23:12 +02:00
xirvik 4e7129c71c Keep starting when the DHT cache file is rejected
The router could throw while loading it, stopping rtorrent from starting at all.
2026-09-20 08:53:34 +02:00
xirvik 80ae6a4590 Answer an oversized RPC response with a fault
Cap XML-RPC and JSON-RPC output at the SCGI response limit before handing it over.
2026-09-19 18:04:09 +02:00
xirvik 2086e8e780 Check the argument count before indexing config comment log args
A '# do:log.add_output=debug' line read args[1] past the end of the vector.
2026-09-19 12:13:43 +02:00
xirvik d9439d7a95 Dispatch view.event_added over the added half of the changed range
fb75510 pointed it at the removed half, so it fired on departing downloads instead.
2026-09-19 11:41:58 +02:00
noctuum 0d3404576a Reject a throttle rate that overflows
rate * 1024 wraps to zero at 2^54, which means no limit.
2026-09-19 11:11:00 +02:00
noctuum 84a0d86f2c Own the fault string in xmlrpc_error_c
xmlrpc_env_set_fault frees the buffer the pointer came from.
2026-09-19 10:34:46 +02:00
noctuum 1b15b7c518 Report a failed write in log.vmmap.dump
The dump is only flushed at fclose, whose result was discarded.
2026-09-19 10:11:23 +02:00
noctuum b1be898ba1 Reject an over-long or empty object_storage key
key_type stores a key of key_size or longer as the empty key.
2026-09-18 16:54:48 +02:00
Jari Sundell bd00b5f6fe Use explicit device name binding rather than a custom syntax. 2026-09-18 23:19:43 +09:00
noctuum b34eb1e895 Own the download factory until commit
A malformed data uri threw before commit, and nothing deleted it.
2026-09-18 11:24:30 +02:00
noctuum b857acd081 Increment d.state_counter on pause
The start path increments it; the pause path assigned it to itself.
2026-09-18 11:05:31 +02:00
noctuum 988aca1281 Release the scgi task buffer on close
clear() keeps the capacity, and the task is pooled until exit.
2026-09-18 10:53:58 +02:00
noctuum 378c7ee854 Reject a throttle rate that does not parse
A discarded parse result left the rate at zero, which is unlimited.
2026-09-18 10:37:14 +02:00
noctuum da9606bfae Take ownership of the magnet metadata object
Nothing freed the bencode map the meta download built.
2026-09-18 10:17:28 +02:00
noctuum 8423803b6e Check the flush on the dht cache and history
good() ran before close(), so a failed write replaced the file.
2026-09-17 16:13:55 +02:00
noctuum c1ce2febf0 Reject an over-long line instead of truncating
getline sets failbit, which both loops read as end of input.
2026-09-17 11:33:44 +02:00
noctuum 5ebb7bd151 Call the target deleter in the tinyxml2 backend
A tracker target allocates, and the deleter went out of scope unused.
2026-09-17 10:33:45 +02:00
noctuum 50f0cad0c6 Release the xmlrpc array item on a throw
xmlrpc_to_target throws for an invalid target, skipping the DECREF.
2026-09-17 10:14:21 +02:00
noctuum 3717649bb3 Guard the value iterator in method.insert.list
The list branch dereferenced end() when no value was given.
2026-09-17 09:54:14 +02:00
noctuum c32b56e9d8 Do not reschedule a deleted scheduler item
schedule.remove from the scheduled command deletes the item.
2026-09-16 11:37:19 +02:00
noctuum de54a4c2ad Clamp the negative width in wstring_width
A narrow terminal makes the file list width go below zero.
2026-09-16 11:18:54 +02:00
noctuum 26fdca8f4d Use empty_visible in the view focus movers
empty() counts filtered entries, so size() - 1 underflows.
2026-09-16 10:57:25 +02:00
noctuum 00bcdaee23 Check the info-hash target in p.call_target
Without it an unknown info-hash segfaults the process.
2026-09-16 10:29:52 +02:00
rakshasa 3787dbe7f3 Tagged release 0.16.23. 2026-09-15 14:49:21 +02:00
noctuum 282c86597a Fix a use after free in the xmlrpc-c index path
The string was freed before it was tested, and tmp was never released.
2026-09-14 11:32:11 +02:00
noctuum c41e63248a Check the flush and sync on session file writes
good() was checked before close(), where the data is actually flushed.
2026-09-14 11:10:12 +02:00
noctuum 641626029a Fix a one byte write past address_copy
The index bound was the buffer size, not the last writable index.
2026-09-14 10:53:12 +02:00
noctuum 0c507c581f Release the zlib deflate state in gzip_compress_to_vector.
deflateEnd was never called, leaking 168 kB per compressed response.
2026-09-13 10:43:16 +02:00
noctuum af0b600b54 Add unit tests for ipv4_range_parse.
Covers single addresses, explicit ranges and the cidr mask boundaries.
2026-09-13 10:05:39 +02:00
noctuum c984a69fd1 Fix cidr /32 ip filter entries covering everything above the address.
Shifting end_mask by the full type width is undefined behavior.
2026-09-13 10:05:39 +02:00
Jakob Breivik Grimstveit 20002ce937 Fix false invalid session error on empty URI
Avoid triggering 'Session data is invalid' when loading downloads with an empty URI.
2026-09-11 12:05:59 +02:00
rakshasa 3caa31113d Bumped scgi max content length to 64mb. 2026-09-06 14:50:19 +02:00
Nicolas PARLANT 0373d227c1 add missing headers for libcxx-23
Signed-off-by: Nicolas PARLANT <nicolas.parlant@parhuet.fr>
2026-09-03 09:59:12 +02:00
rakshasa a8be7e443a Tagged release 0.16.22. 2026-09-02 09:39:57 +02:00
aeiouaeiouaeiouaeiouaeiouaeiou f327523a07 gzip.h: fix build with Clang <17
Signed-off-by: aeiouaeiouaeiouaeiouaeiouaeiou <aeioudev@outlook.com>
2026-08-30 11:18:25 +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
rakshasa 0c11deac50 Tagged release 0.16.18. 2026-07-17 10:15:28 +02:00
Jari Sundell f6b3ad0efd Minor cleanup of pex and other commands. 2026-07-15 19:40:13 +09:00
Jari Sundell 5d350f0bcc Use new encryption modes. 2026-07-14 17:34:14 +09:00
Jari Sundell 86fa0d195f Changing listen/dht port changes the listening/dht ports. 2026-07-12 17:19:33 +09:00
Jari Sundell 2354c9cdb5 Moved ChunkManager out of public header directory. 2026-07-10 17:54:37 +09:00
Jari Sundell 1b25bc7f56 Moved all sync/diskspace related methods to MemoryManager. 2026-07-10 02:21:08 +09:00
Jari Sundell c6bde213b4 Added runtime::MemoryManager to split out unrelated features from ChunkManager. 2026-07-09 01:56:30 +09:00
Jari Sundell 959448acda Fixed command_base t_pod align static asserts. 2026-07-08 17:15:56 +09:00
Jari Sundell 3b6da6feac Reordered http queue slots to avoid race conditions. 2026-07-07 16:34:19 +09:00
Jari Sundell ea2d22cb87 Removed unused add/remove error-event code. 2026-07-07 03:05:20 +09:00
152 changed files with 4787 additions and 1517 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]) m4_pattern_allow([PKG_CHECK_EXISTS])
AC_INIT([rtorrent],[0.16.17],[sundell.software@gmail.com]) AC_INIT([rtorrent],[0.16.24],[sundell.software@gmail.com])
AC_CONFIG_HEADERS([config.h]) AC_CONFIG_HEADERS([config.h])
AC_CONFIG_MACRO_DIRS([scripts]) AC_CONFIG_MACRO_DIRS([scripts])
@@ -14,7 +14,7 @@ AX_CXX_COMPILE_STDCXX(20, noext, mandatory)
PKG_PROG_PKG_CONFIG PKG_PROG_PKG_CONFIG
AC_DEFINE([API_VERSION], [23], [api version]) AC_DEFINE([API_VERSION], [28], [api version])
RAK_CHECK_CFLAGS RAK_CHECK_CFLAGS
RAK_CHECK_CXXFLAGS RAK_CHECK_CXXFLAGS
@@ -22,6 +22,8 @@ RAK_ENABLE_DEBUG
RAK_ENABLE_EXTRA_DEBUG RAK_ENABLE_EXTRA_DEBUG
RAK_ENABLE_WERROR RAK_ENABLE_WERROR
TORRENT_CHECK_ATOMIC
AC_ARG_ENABLE(execinfo, AC_ARG_ENABLE(execinfo,
AS_HELP_STRING([--disable-execinfo], AS_HELP_STRING([--disable-execinfo],
[disable libexecinfo [[default=enable]]]), [disable libexecinfo [[default=enable]]]),
@@ -47,7 +49,7 @@ fi
PKG_CHECK_MODULES([CPPUNIT], [cppunit],, [no_cppunit="yes"]) PKG_CHECK_MODULES([CPPUNIT], [cppunit],, [no_cppunit="yes"])
PKG_CHECK_MODULES([ZLIB], [zlib]) PKG_CHECK_MODULES([ZLIB], [zlib])
PKG_CHECK_MODULES([DEPENDENCIES], [libtorrent >= 0.16.17]) PKG_CHECK_MODULES([DEPENDENCIES], [libtorrent >= 0.16.24])
AC_LANG_PUSH(C++) AC_LANG_PUSH(C++)
TORRENT_WITH_XMLRPC_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 Only update global build variables immediately before generating the output,
dnl to avoid affecting the global build environment for other autoconf checks. 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" CFLAGS="$CFLAGS $PTHREAD_CFLAGS $CURSES_CFLAGS $ZLIB_CFLAGS $DEPENDENCIES_CFLAGS"
CXXFLAGS="$CXXFLAGS $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 = 127.0.0.1
#network.local_address.set = rakshasa.no #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 # The IP address the listening socket and outgoing connections is
# bound to. # bound to.
# #
@@ -71,11 +79,11 @@
# Port range to use for listening. # 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. # 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 # Set RPC type
#network.rpc.use_xmlrpc.set = true #network.rpc.use_xmlrpc.set = true
+2 -2
View File
@@ -23,8 +23,8 @@ execute.throw = sh, -c, (cat,\
"\"",(cfg.watch),"/start\" ") "\"",(cfg.watch),"/start\" ")
# Listening port for incoming peer traffic (fixed; you can also randomize it) # Listening port for incoming peer traffic (fixed; you can also randomize it)
network.port_range.set = 50000-50000 network.listen.port.range.set = 50000-50000
network.port_random.set = no network.listen.port.random.set = no
# Tracker-less torrent and UDP tracker support # Tracker-less torrent and UDP tracker support
# (conservative settings for 'private' trackers, change for 'public') # (conservative settings for 'private' trackers, change for 'public')
+2 -2
View File
@@ -53,8 +53,8 @@ rc.execute.throw(
cfg.watch..'/start'})) cfg.watch..'/start'}))
-- Listening port for incoming peer traffic (fixed; you can also randomize it) -- Listening port for incoming peer traffic (fixed; you can also randomize it)
rc.network.port_range = '50000-50000' rc.network.listen.port.range = '50000-50000'
rc.network.port_random = false rc.network.listen.port.random = false
-- Tracker-less torrent and UDP tracker support -- Tracker-less torrent and UDP tracker support
-- (conservative settings for 'private' trackers, change for 'public') -- (conservative settings for 'private' trackers, change for 'public')
+4 -2
View File
@@ -17,7 +17,6 @@ libsub_root_a_SOURCES = \
core/http_queue.h \ core/http_queue.h \
core/manager.cc \ core/manager.cc \
core/manager.h \ core/manager.h \
core/range_map.h \
core/view.cc \ core/view.cc \
core/view.h \ core/view.h \
core/view_manager.cc \ core/view_manager.cc \
@@ -171,9 +170,10 @@ libsub_root_a_SOURCES = \
utils/functional.h \ utils/functional.h \
utils/gzip.cc \ utils/gzip.cc \
utils/gzip.h \ utils/gzip.h \
utils/list_focus.h \
utils/lockfile.cc \ utils/lockfile.cc \
utils/lockfile.h \ utils/lockfile.h \
utils/waitpid_queue.cc \
utils/waitpid_queue.h \
utils/watch_ready_queue.cc \ utils/watch_ready_queue.cc \
utils/watch_ready_queue.h \ utils/watch_ready_queue.h \
\ \
@@ -192,6 +192,8 @@ libsub_root_a_SOURCES = \
command_throttle.cc \ command_throttle.cc \
command_tracker.cc \ command_tracker.cc \
command_scheduler.cc \ command_scheduler.cc \
command_string.cc \
command_system.cc \
command_ui.cc \ command_ui.cc \
control.cc \ control.cc \
control.h \ control.h \
+34 -17
View File
@@ -142,16 +142,6 @@ apply_d_delete_tied(core::Download* download) {
return torrent::Object(); 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 torrent::Object
apply_d_connection_type(core::Download* download, const std::string& name) { apply_d_connection_type(core::Download* download, const std::string& name) {
torrent::Download::ConnectionType t = torrent::Download::ConnectionType t =
@@ -412,13 +402,21 @@ p_multicall(core::Download* download, const torrent::Object::list_type& args) {
auto resultRaw = torrent::Object::create_list(); auto resultRaw = torrent::Object::create_list();
auto& result = resultRaw.as_list(); auto& result = resultRaw.as_list();
for (const auto& connection : *download->connection_list()) { auto* connection_list = download->connection_list();
const auto change_counter = connection_list->change_counter();
for (const auto& connection : *connection_list) {
torrent::Object::list_type& row = result.insert(result.end(), torrent::Object::create_list())->as_list(); torrent::Object::list_type& row = result.insert(result.end(), torrent::Object::create_list())->as_list();
for (auto cItr = ++args.begin(); cItr != args.end(); cItr++) { for (auto cItr = ++args.begin(); cItr != args.end(); cItr++) {
const std::string& cmd = cItr->as_string(); const std::string& cmd = cItr->as_string();
row.push_back(rpc::parse_command(rpc::make_target(connection), cmd.c_str(), cmd.c_str() + cmd.size()).first); row.push_back(rpc::parse_command(rpc::make_target(connection), cmd.c_str(), cmd.c_str() + cmd.size()).first);
// Erasing a peer frees it and swaps the last element into its place, so
// neither this peer nor the iteration survives a change to the list.
if (connection_list->change_counter() != change_counter)
throw torrent::input_error("Command changed the connection list during p.multicall.");
} }
} }
@@ -441,6 +439,9 @@ p_call_target(const torrent::Object::list_type& args) {
const std::string& peer_id = itr++->as_string(); const std::string& peer_id = itr++->as_string();
const std::string& command_key = itr++->as_string(); const std::string& command_key = itr++->as_string();
if (download == nullptr)
throw torrent::input_error("invalid parameters: info-hash not found");
torrent::HashString hash; torrent::HashString hash;
if (peer_id.size() != 40) if (peer_id.size() != 40)
@@ -655,6 +656,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.as_binary", [](auto* download, auto) { return retrieve_d_base_path(download).object_as_binary(); });
CMD2_DL("d.base_path.or_base64", [](auto* download, auto) { return retrieve_d_base_path(download).object_utf8_or_base64(); }); CMD2_DL("d.base_path.or_base64", [](auto* download, auto) { return retrieve_d_base_path(download).object_utf8_or_base64(); });
CMD2_DL("d.base_path.or_as_binary", [](auto* download, auto) { return retrieve_d_base_path(download).object_utf8_or_as_binary(); }); CMD2_DL("d.base_path.or_as_binary", [](auto* download, auto) { return retrieve_d_base_path(download).object_utf8_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", [](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.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(); }); CMD2_DL("d.base_filename.base64", [](auto* download, auto) { return retrieve_d_base_filename(download).object_base64(); });
@@ -716,7 +719,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.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.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", 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.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)); CMD2_DL_V ("d.check_hash", std::bind(&core::DownloadList::check_hash, control->core()->download_list(), std::placeholders::_1));
@@ -771,6 +774,11 @@ initialize_command_download() {
CMD2_DL_VAR_STRING_PUBLIC("d.tied_to_file", "rtorrent", "tied_to_file"); 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_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 // The "state_changed" variable is required to be a valid unix time
// value, it indicates the last time the torrent changed its state, // value, it indicates the last time the torrent changed its state,
// resume/pause. // resume/pause.
@@ -877,10 +885,11 @@ initialize_command_download() {
CMD2_DL_LIST ("d.tracker.insert", std::bind(&download_tracker_insert, std::placeholders::_1, std::placeholders::_2)); 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_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 ("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.realpath.or_empty", [](auto* download, auto) { return resolve_path(download->file_list()->root_dir()); });
CMD2_DL ("d.directory_base", CMD2_ON_FL(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_base.set", std::bind(&core::Download::set_root_directory, std::placeholders::_1, std::placeholders::_2)); 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", std::bind(&core::Download::priority, std::placeholders::_1));
CMD2_DL ("d.priority_str", std::bind(&retrieve_d_priority_str, std::placeholders::_1)); CMD2_DL ("d.priority_str", std::bind(&retrieve_d_priority_str, std::placeholders::_1));
@@ -912,6 +921,14 @@ initialize_command_download() {
rpc::rpc.mark_safe("d.local_id_html"); rpc::rpc.mark_safe("d.local_id_html");
rpc::rpc.mark_safe("d.bitfield"); rpc::rpc.mark_safe("d.bitfield");
rpc::rpc.mark_safe("d.base_path"); 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.hex");
rpc::rpc.mark_safe("d.base_path.base64"); rpc::rpc.mark_safe("d.base_path.base64");
rpc::rpc.mark_safe("d.base_path.base64_as_binary"); rpc::rpc.mark_safe("d.base_path.base64_as_binary");
@@ -933,7 +950,6 @@ initialize_command_download() {
rpc::rpc.mark_safe("d.name.or_base64"); rpc::rpc.mark_safe("d.name.or_base64");
rpc::rpc.mark_safe("d.name.or_as_binary"); rpc::rpc.mark_safe("d.name.or_as_binary");
rpc::rpc.mark_safe("d.directory"); 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.creation_date");
rpc::rpc.mark_safe("d.load_date"); rpc::rpc.mark_safe("d.load_date");
rpc::rpc.mark_safe("d.up.rate"); rpc::rpc.mark_safe("d.up.rate");
@@ -974,6 +990,7 @@ initialize_command_download() {
rpc::rpc.mark_safe("d.size_pex"); rpc::rpc.mark_safe("d.size_pex");
rpc::rpc.mark_safe("d.completed_bytes"); rpc::rpc.mark_safe("d.completed_bytes");
rpc::rpc.mark_safe("d.complete"); 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.timestamp.finished");
rpc::rpc.mark_safe("d.bytes_done"); rpc::rpc.mark_safe("d.bytes_done");
rpc::rpc.mark_safe("d.peers_accounted"); rpc::rpc.mark_safe("d.peers_accounted");
+17 -11
View File
@@ -157,7 +157,7 @@ system_method_insert_object(const torrent::Object::list_type& args, int flags) {
torrent::Object valueList = torrent::Object::create_list(); torrent::Object valueList = torrent::Object::create_list();
torrent::Object::list_type& valueListType = valueList.as_list(); torrent::Object::list_type& valueListType = valueList.as_list();
if ((itrArgs)->is_list()) if (itrArgs != args.end() && (itrArgs)->is_list())
valueListType = (itrArgs)->as_list(); valueListType = (itrArgs)->as_list();
control->object_storage()->insert_str(raw_key, valueList, flags); control->object_storage()->insert_str(raw_key, valueList, flags);
@@ -444,16 +444,20 @@ initialize_command_dynamic() {
CMD2_ANY ("catch", std::bind(&cmd_catch, std::placeholders::_1, std::placeholders::_2)); CMD2_ANY ("catch", std::bind(&cmd_catch, std::placeholders::_1, std::placeholders::_2));
CMD2_ANY ("strings.choke_heuristics", std::bind(&torrent::option_list_strings, torrent::OPTION_CHOKE_HEURISTICS)); CMD2_ANY_STRING ("enum.log_group", [](auto, const auto& str) { return torrent::option_find_string_str(torrent::OPTION_LOG_GROUP, str); });
CMD2_ANY ("strings.choke_heuristics.upload", std::bind(&torrent::option_list_strings, torrent::OPTION_CHOKE_HEURISTICS_UPLOAD));
CMD2_ANY ("strings.choke_heuristics.download", std::bind(&torrent::option_list_strings, torrent::OPTION_CHOKE_HEURISTICS_DOWNLOAD)); CMD2_ANY ("strings.choke_heuristics", [](auto, auto) { return torrent::option_list_strings(torrent::OPTION_CHOKE_HEURISTICS); });
CMD2_ANY ("strings.connection_type", std::bind(&torrent::option_list_strings, torrent::OPTION_CONNECTION_TYPE)); CMD2_ANY ("strings.choke_heuristics.upload", [](auto, auto) { return torrent::option_list_strings(torrent::OPTION_CHOKE_HEURISTICS_UPLOAD); });
CMD2_ANY ("strings.encryption", std::bind(&torrent::option_list_strings, torrent::OPTION_ENCRYPTION)); CMD2_ANY ("strings.choke_heuristics.download", [](auto, auto) { return torrent::option_list_strings(torrent::OPTION_CHOKE_HEURISTICS_DOWNLOAD); });
CMD2_ANY ("strings.ip_filter", std::bind(&torrent::option_list_strings, torrent::OPTION_IP_FILTER)); CMD2_ANY ("strings.connection_type", [](auto, auto) { return torrent::option_list_strings(torrent::OPTION_CONNECTION_TYPE); });
CMD2_ANY ("strings.ip_tos", std::bind(&torrent::option_list_strings, torrent::OPTION_IP_TOS)); CMD2_ANY ("strings.encryption", [](auto, auto) { return torrent::Object::create_list(); });
CMD2_ANY ("strings.log_group", std::bind(&torrent::option_list_strings, torrent::OPTION_LOG_GROUP)); CMD2_ANY ("strings.encryption.handshake", [](auto, auto) { return torrent::option_list_strings(torrent::OPTION_ENCRYPTION_HANDSHAKE); });
CMD2_ANY ("strings.tracker_event", std::bind(&torrent::option_list_strings, torrent::OPTION_TRACKER_EVENT)); CMD2_ANY ("strings.encryption.stream", [](auto, auto) { return torrent::option_list_strings(torrent::OPTION_ENCRYPTION_STREAM); });
CMD2_ANY ("strings.tracker_mode", std::bind(&torrent::option_list_strings, torrent::OPTION_TRACKER_MODE)); CMD2_ANY ("strings.ip_filter", [](auto, auto) { return torrent::option_list_strings(torrent::OPTION_IP_FILTER); });
CMD2_ANY ("strings.ip_tos", [](auto, auto) { return torrent::option_list_strings(torrent::OPTION_IP_TOS); });
CMD2_ANY ("strings.log_group", [](auto, auto) { return torrent::option_list_strings(torrent::OPTION_LOG_GROUP); });
CMD2_ANY ("strings.tracker_event", [](auto, auto) { return torrent::option_list_strings(torrent::OPTION_TRACKER_EVENT); });
CMD2_ANY ("strings.tracker_mode", [](auto, auto) { return torrent::option_list_strings(torrent::OPTION_TRACKER_MODE); });
// clang-format on // clang-format on
#ifdef HAVE_XMLRPC_TINYXML2 #ifdef HAVE_XMLRPC_TINYXML2
@@ -468,6 +472,8 @@ initialize_command_dynamic() {
rpc::rpc.mark_safe("method.rlookup"); rpc::rpc.mark_safe("method.rlookup");
rpc::rpc.mark_safe("catch"); 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");
rpc::rpc.mark_safe("strings.choke_heuristics.upload"); rpc::rpc.mark_safe("strings.choke_heuristics.upload");
rpc::rpc.mark_safe("strings.choke_heuristics.download"); rpc::rpc.mark_safe("strings.choke_heuristics.download");
+29 -7
View File
@@ -51,7 +51,7 @@ apply_on_ratio(const torrent::Object& rawArgs) {
!(max_ratio > 0 && total_upload * 100 > total_done * max_ratio)) !(max_ratio > 0 && total_upload * 100 > total_done * max_ratio))
continue; continue;
downloads.push_back(*itr); downloads.push_back(itr->get());
} }
auto ratio_command = "group." + group_name + ".ratio.command"; auto ratio_command = "group." + group_name + ".ratio.command";
@@ -128,7 +128,7 @@ apply_remove_untied() {
} }
torrent::Object 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) if (args.size() != 4)
throw torrent::input_error("Wrong number of arguments."); throw torrent::input_error("Wrong number of arguments.");
@@ -138,6 +138,9 @@ apply_schedule(const torrent::Object::list_type& args) {
auto& arg2 = (itr++)->as_string(); auto& arg2 = (itr++)->as_string();
auto& arg3 = (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); control->command_scheduler()->parse(arg1, arg2, arg3, *itr);
return torrent::Object(); return torrent::Object();
@@ -170,7 +173,7 @@ apply_close_low_diskspace(int64_t arg, uint32_t skip_priority) {
torrent::FileList::cache_list cache; torrent::FileList::cache_list cache;
for (auto download : *control->core()->download_list()) { for (const auto& download : *control->core()->download_list()) {
if (!download->is_downloading()) if (!download->is_downloading())
continue; continue;
if (download->priority() >= skip_priority) if (download->priority() >= skip_priority)
@@ -237,15 +240,25 @@ d_multicall(const torrent::Object::list_type& args) {
// Add some pre-parsing of the commands, so we don't spend time // Add some pre-parsing of the commands, so we don't spend time
// parsing and searching command map for every single call. // 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 resultRaw = torrent::Object::create_list();
torrent::Object::list_type& result = resultRaw.as_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(); 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++) { 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(); auto& cmd = cItr->as_string();
row.push_back(rpc::parse_command(rpc::make_target(download), cmd.c_str(), cmd.c_str() + cmd.size()).first); row.push_back(rpc::parse_command(rpc::make_target(download), cmd.c_str(), cmd.c_str() + cmd.size()).first);
} }
@@ -268,10 +281,12 @@ d_multicall_filtered(const torrent::Object::list_type& args) {
if (view_itr == viewManager->end()) if (view_itr == viewManager->end())
throw torrent::input_error("Could not find view '" + arg->as_string() + "'."); 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; core::View::base_type dlist;
(*view_itr)->filter_by(*++arg, dlist); (*view_itr)->filter_by(*++arg, dlist);
// Generate result by iterating over all items // Generate result by iterating over all items
auto resultRaw = torrent::Object::create_list(); auto resultRaw = torrent::Object::create_list();
auto& result = resultRaw.as_list(); auto& result = resultRaw.as_list();
@@ -279,11 +294,17 @@ d_multicall_filtered(const torrent::Object::list_type& args) {
++arg; // skip to first command ++arg; // skip to first command
for (const auto& item : dlist) { for (const auto& item : dlist) {
if (item.use_count() == 1)
continue;
// Add empty row to result // Add empty row to result
torrent::Object::list_type& row = result.insert(result.end(), torrent::Object::create_list())->as_list(); torrent::Object::list_type& row = result.insert(result.end(), torrent::Object::create_list())->as_list();
// Call the provided commands and assemble their results // Call the provided commands and assemble their results
for (torrent::Object::list_const_iterator command = arg; command != args.end(); command++) { for (torrent::Object::list_const_iterator command = arg; command != args.end(); command++) {
if (item.use_count() == 1)
break;
auto& cmdstr = command->as_string(); auto& cmdstr = command->as_string();
row.push_back(rpc::parse_command(rpc::make_target(item), cmdstr.c_str(), cmdstr.c_str() + cmdstr.size()).first); row.push_back(rpc::parse_command(rpc::make_target(item), cmdstr.c_str(), cmdstr.c_str() + cmdstr.size()).first);
} }
@@ -340,7 +361,8 @@ initialize_command_events() {
CMD2_ANY ("close_untied", [](auto, auto) { return apply_close_untied(); }); CMD2_ANY ("close_untied", [](auto, auto) { return apply_close_untied(); });
CMD2_ANY ("remove_untied", [](auto, auto) { return apply_remove_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("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("import", [](auto, auto& str) { return apply_import(str); });
+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.as_binary", [](auto* file, auto) { return file->frozen_path().object_as_binary(); });
CMD2_FILE("f.frozen_path.or_base64", [](auto* file, auto) { return file->frozen_path().object_utf8_or_base64(); }); CMD2_FILE("f.frozen_path.or_base64", [](auto* file, auto) { return file->frozen_path().object_utf8_or_base64(); });
CMD2_FILE("f.frozen_path.or_as_binary", [](auto* file, auto) { return file->frozen_path().object_utf8_or_as_binary(); }); CMD2_FILE("f.frozen_path.or_as_binary", [](auto* file, auto) { return file->frozen_path().object_utf8_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_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)); 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_components");
rpc::rpc.mark_safe("f.path_depth"); rpc::rpc.mark_safe("f.path_depth");
rpc::rpc.mark_safe("f.frozen_path"); 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.hex");
rpc::rpc.mark_safe("f.frozen_path.base64"); rpc::rpc.mark_safe("f.frozen_path.base64");
rpc::rpc.mark_safe("f.frozen_path.base64_as_binary"); rpc::rpc.mark_safe("f.frozen_path.base64_as_binary");
+15 -9
View File
@@ -1,5 +1,8 @@
#include "config.h" #include "config.h"
#include <limits>
#include <memory>
#include <torrent/download/resource_manager.h> #include <torrent/download/resource_manager.h>
#include <torrent/download/choke_group.h> #include <torrent/download/choke_group.h>
#include <torrent/download/choke_queue.h> #include <torrent/download/choke_queue.h>
@@ -111,7 +114,7 @@ apply_cg_all_update_balance(bool is_up) {
// //
#else #else
std::vector<torrent::choke_group*> cg_list_hack; std::vector<std::unique_ptr<torrent::choke_group>> cg_list_hack;
int64_t int64_t
cg_get_index(const torrent::Object& raw_args) { cg_get_index(const torrent::Object& raw_args) {
@@ -121,7 +124,7 @@ cg_get_index(const torrent::Object& raw_args) {
if (arg.is_string()) { if (arg.is_string()) {
if (!rpc::parse_whole_value_nothrow(arg.as_string().c_str(), &index)) { 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()) if (itr == cg_list_hack.end())
throw torrent::input_error("Choke group not found."); throw torrent::input_error("Choke group not found.");
@@ -149,7 +152,7 @@ cg_get_group(const torrent::Object& raw_args) {
if ((size_t)index >= cg_list_hack.size()) if ((size_t)index >= cg_list_hack.size())
throw torrent::input_error("Choke group not found."); 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(); } int64_t cg_d_group(core::Download* download) { return download->group(); }
@@ -162,7 +165,7 @@ torrent::Object
apply_cg_list() { apply_cg_list() {
torrent::Object::list_type result; torrent::Object::list_type result;
for (auto itr : cg_list_hack) for (const auto& itr : cg_list_hack)
result.push_back(itr->name()); result.push_back(itr->name());
return torrent::Object::from_list(result); return torrent::Object::from_list(result);
@@ -180,10 +183,10 @@ apply_cg_insert(const std::string& arg) {
if (rpc::parse_whole_value_nothrow(arg.c_str(), &dummy)) if (rpc::parse_whole_value_nothrow(arg.c_str(), &dummy))
throw torrent::input_error("Cannot use a value string as choke group name."); 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."); 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()->set_name(arg);
cg_list_hack.back()->up_queue()->set_heuristics(torrent::HEURISTICS_UPLOAD_LEECH); cg_list_hack.back()->up_queue()->set_heuristics(torrent::HEURISTICS_UPLOAD_LEECH);
@@ -194,7 +197,7 @@ apply_cg_insert(const std::string& arg) {
torrent::Object torrent::Object
apply_cg_index_of(const std::string& arg) { 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()) if (itr == cg_list_hack.end())
throw torrent::input_error("Choke group not found."); throw torrent::input_error("Choke group not found.");
@@ -206,7 +209,7 @@ torrent::Object
apply_cg_all_update_balance(bool is_up) { apply_cg_all_update_balance(bool is_up) {
LT_LOG_SUBSYSTEM("apply update balance: hack is_up:%i", (int)is_up); LT_LOG_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) if (is_up)
itr->up_queue()->balance(); itr->up_queue()->balance();
else else
@@ -230,6 +233,9 @@ apply_cg_max_set(const torrent::Object::list_type& args, bool is_up) {
int64_t second_arg = 0; int64_t second_arg = 0;
rpc::parse_whole_value(args.back().as_string().c_str(), &second_arg); rpc::parse_whole_value(args.back().as_string().c_str(), &second_arg);
if (second_arg < -1 || second_arg > std::numeric_limits<uint32_t>::max())
throw torrent::input_error("Max unchoked must be between -1 and 4294967295.");
if (is_up) if (is_up)
cg_get_group(args.front())->up_queue()->set_max_unchoked(second_arg); cg_get_group(args.front())->up_queue()->set_max_unchoked(second_arg);
else else
@@ -346,7 +352,7 @@ initialize_command_groups() {
#else #else
apply_cg_insert("default"); 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)); CMD_ANY_STRING ("choke_group.index_of", std::bind(&apply_cg_index_of, std::placeholders::_2));
#endif #endif
+4
View File
@@ -19,6 +19,8 @@ void initialize_command_groups();
void initialize_command_throttle(); void initialize_command_throttle();
void initialize_command_tracker(); void initialize_command_tracker();
void initialize_command_scheduler(); void initialize_command_scheduler();
void initialize_command_string();
void initialize_command_system();
void initialize_command_ui(); void initialize_command_ui();
void void
@@ -37,4 +39,6 @@ initialize_commands() {
initialize_command_throttle(); initialize_command_throttle();
initialize_command_tracker(); initialize_command_tracker();
initialize_command_scheduler(); initialize_command_scheduler();
initialize_command_string();
initialize_command_system();
} }
+13
View File
@@ -1,12 +1,25 @@
#ifndef RTORRENT_UTILS_COMMAND_HELPERS_H #ifndef RTORRENT_UTILS_COMMAND_HELPERS_H
#define RTORRENT_UTILS_COMMAND_HELPERS_H #define RTORRENT_UTILS_COMMAND_HELPERS_H
#include <cstdint>
#include <limits>
#include <string>
#include <torrent/exceptions.h>
#include "rpc/command.h" #include "rpc/command.h"
#include "rpc/parse_commands.h" #include "rpc/parse_commands.h"
#include "rpc/object_storage.h" #include "rpc/object_storage.h"
void initialize_commands(); void initialize_commands();
inline uint16_t
checked_port_value(int64_t value, const char* label) {
if (value < 0 || value > std::numeric_limits<uint16_t>::max())
throw torrent::input_error(std::string("Invalid ") + label + " port number.");
return static_cast<uint16_t>(value);
}
// //
// Aliases with CMD_* for the below // Aliases with CMD_* for the below
// //
+11 -3
View File
@@ -131,7 +131,7 @@ ipv4_range_parse(const char* address, uint32_t* address_start, uint32_t* address
// copy everything up to '#' to address_copy and work from there // copy everything up to '#' to address_copy and work from there
while(address[address_start_index] != '#' && address[address_start_index] != '\r' && while(address[address_start_index] != '#' && address[address_start_index] != '\r' &&
address[address_start_index] != '\n' && address[address_start_index] != '\0' && address[address_start_index] != '\n' && address[address_start_index] != '\0' &&
address_start_index < 4096 ) { address_start_index < 4095 ) {
address_copy[address_start_index] = address[address_start_index]; address_copy[address_start_index] = address[address_start_index];
address_start_index++; address_start_index++;
@@ -206,6 +206,9 @@ ipv4_range_parse(const char* address, uint32_t* address_start, uint32_t* address
if (mask_bits == 0) { if (mask_bits == 0) {
mask = 0; mask = 0;
end_mask = ~(uint32_t)0; end_mask = ~(uint32_t)0;
} else if (mask_bits == 32) {
mask = ~(uint32_t)0;
end_mask = 0;
} else { } else {
mask = (~mask) << (32-mask_bits); mask = (~mask) << (32-mask_bits);
end_mask = (~end_mask) >> mask_bits; end_mask = (~end_mask) >> mask_bits;
@@ -316,6 +319,11 @@ apply_ipv4_filter_load(const torrent::Object::list_type& args) {
ipv4_filter_parse(buffer, value); ipv4_filter_parse(buffer, value);
} }
if (file.fail() && !file.eof()) {
lineNumber++;
throw torrent::input_error("Exceeded max line length.");
}
} catch (torrent::input_error& e) { } catch (torrent::input_error& e) {
snprintf(buffer, 2048, "Error in ip filter file: %s:%u: %s", filename.c_str(), lineNumber, e.what()); snprintf(buffer, 2048, "Error in ip filter file: %s:%u: %s", filename.c_str(), lineNumber, e.what());
@@ -336,8 +344,8 @@ apply_ipv4_filter_dump() {
torrent::Object raw_result = torrent::Object::create_list(); torrent::Object raw_result = torrent::Object::create_list();
torrent::Object::list_type& result = raw_result.as_list(); torrent::Object::list_type& result = raw_result.as_list();
torrent::ipv4_table::range_map_type range_map = torrent::PeerList::ipv4_filter()->range_map; const torrent::ipv4_table::range_map_type& range_map = torrent::PeerList::ipv4_filter()->range_map;
torrent::ipv4_table::range_map_type::iterator iter = range_map.begin(); torrent::ipv4_table::range_map_type::const_iterator iter = range_map.begin();
while(iter != range_map.end()) { while(iter != range_map.end()) {
char buffer[64]; char buffer[64];
+39 -232
View File
@@ -1,33 +1,16 @@
#include "config.h" #include "config.h"
#include <cerrno> #include <torrent/runtime/memory_manager.h>
#include <fcntl.h> #include <torrent/utils/log.h>
#include <functional>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <torrent/torrent.h>
#include <torrent/chunk_manager.h>
#include <torrent/data/file_manager.h>
#include <torrent/data/chunk_utils.h>
#include <torrent/runtime/runtime.h>
#include <torrent/runtime/socket_manager.h>
#include <torrent/utils/chrono.h>
#include <torrent/utils/option_strings.h>
#include "command_helpers.h"
#include "control.h"
#include "core/download.h" #include "core/download.h"
#include "core/download_list.h"
#include "core/manager.h" #include "core/manager.h"
#include "rpc/parse_commands.h" #include "rpc/parse_commands.h"
#include "rpc/lua.h"
#include "rpc/scgi.h" #include "rpc/scgi.h"
#include "session/session_manager.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::ChunkManager CM_t;
typedef torrent::FileManager FM_t; typedef torrent::FileManager FM_t;
@@ -43,49 +26,6 @@ apply_pieces_stats_total_size() {
return 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 inline torrent::Object::list_const_iterator
post_increment(torrent::Object::list_const_iterator& itr, const torrent::Object::list_const_iterator& last) { post_increment(torrent::Object::list_const_iterator& itr, const torrent::Object::list_const_iterator& last) {
if (itr == 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", rpc::commands.call("method.insert", rpc::create_object_list("group." + name + ".ratio.enable", "simple",
"schedule=group." + name + ".ratio,5,60,on_ratio=" + name)); "schedule=group." + name + ".ratio,5,60,on_ratio=" + name));
rpc::commands.call("method.insert", rpc::create_object_list("group." + name + ".ratio.disable", "simple", 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", rpc::commands.call("method.insert", rpc::create_object_list("group." + name + ".ratio.command", "simple",
"d.try_close= ;d.ignore_commands.set=1")); "d.try_close= ;d.ignore_commands.set=1"));
rpc::commands.call("method.insert", rpc::create_object_list("group." + name + ".view", "string", view)); rpc::commands.call("method.insert", rpc::create_object_list("group." + name + ".view", "string", view));
@@ -137,167 +77,49 @@ group_insert(const torrent::Object::list_type& args) {
return name; return name;
} }
static const int file_print_use_space = 0x1;
static const int file_print_delim_space = 0x2;
void
file_print_list(torrent::Object::list_const_iterator first, torrent::Object::list_const_iterator last, FILE* output, int flags) {
while (first != last) {
switch (first->type()) {
case torrent::Object::TYPE_STRING:
fprintf(output, (const char*)" %s" + !(flags & file_print_use_space), first->as_string().c_str());
break;
case torrent::Object::TYPE_VALUE:
fprintf(output, (const char*)" %" PRIi64 + !(flags & file_print_use_space), first->as_value());
break;
case torrent::Object::TYPE_LIST:
file_print_list(first->as_list().begin(), first->as_list().end(), output, 0);
break;
case torrent::Object::TYPE_NONE:
break;
default:
throw torrent::input_error("Invalid type.");
}
flags |= (flags & file_print_delim_space) >> 1;
first++;
}
}
torrent::Object
cmd_file_append(const torrent::Object::list_type& args) {
if (args.empty())
throw torrent::input_error("Invalid number of arguments.");
FILE* output = fopen(args.front().as_string().c_str(), "a");
if (output == nullptr)
throw torrent::input_error("Could not append to file '" + args.front().as_string() + "': " + std::strerror(errno));
try {
file_print_list(++args.begin(), args.end(), output, file_print_delim_space);
fprintf(output, "\n");
} catch (...) {
fclose(output);
throw;
}
fclose(output);
return torrent::Object();
}
void void
initialize_command_local() { initialize_command_local() {
core::DownloadList* dList = control->core()->download_list(); core::DownloadList* dList = control->core()->download_list();
torrent::ChunkManager* chunkManager = torrent::chunk_manager();
torrent::FileManager* fileManager = torrent::file_manager();
if (rpc::call_command_value("method.use_deprecated") == 1) { CMD_ANY ("pieces.sync.always_safe", [](auto, auto) { return torrent::runtime::memory_manager()->safe_sync(); });
CMD_ANY_LIST ("file.append", std::bind(&cmd_file_append, std::placeholders::_2)); 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 0; });
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 ("system.hostname", std::bind(&system_hostname)); CMD_ANY ("pieces.preload.type", [](auto, auto) { return torrent::runtime::memory_manager()->preload_type(); });
CMD_ANY ("system.pid", std::bind(&getpid)); CMD_ANY_VALUE_V ("pieces.preload.type.set", [](auto, auto& value) { return torrent::runtime::memory_manager()->set_preload_type(value); });
CMD_ANY ("pieces.preload.min_size", [](auto, auto) { return torrent::runtime::memory_manager()->preload_min_size(); });
CMD_VAR_C_STRING("system.api_version", (int64_t)API_VERSION); CMD_ANY_VALUE_V ("pieces.preload.min_size.set", [](auto, auto& value) { return torrent::runtime::memory_manager()->set_preload_min_size(value); });
CMD_VAR_C_STRING("system.client_version", PACKAGE_VERSION); CMD_ANY ("pieces.preload.min_rate", [](auto, auto) { return torrent::runtime::memory_manager()->preload_required_rate(); });
CMD_VAR_C_STRING("system.library_version", torrent::runtime::version()); CMD_ANY_VALUE_V ("pieces.preload.min_rate.set", [](auto, auto& value) { return torrent::runtime::memory_manager()->set_preload_required_rate(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_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); });
}
CMD_ANY ("pieces.sync.always_safe", std::bind(&CM_t::safe_sync, chunkManager));
CMD_ANY_VALUE_V ("pieces.sync.always_safe.set", std::bind(&CM_t::set_safe_sync, chunkManager, std::placeholders::_2));
CMD_ANY ("pieces.sync.safe_free_diskspace", std::bind(&CM_t::safe_free_diskspace, chunkManager));
CMD_ANY ("pieces.sync.timeout", std::bind(&CM_t::timeout_sync, chunkManager));
CMD_ANY_VALUE_V ("pieces.sync.timeout.set", std::bind(&CM_t::set_timeout_sync, chunkManager, std::placeholders::_2));
CMD_ANY ("pieces.sync.timeout_safe", std::bind(&CM_t::timeout_safe_sync, chunkManager));
CMD_ANY_VALUE_V ("pieces.sync.timeout_safe.set", std::bind(&CM_t::set_timeout_safe_sync, chunkManager, std::placeholders::_2));
CMD_ANY ("pieces.sync.queue_size", std::bind(&CM_t::sync_queue_size, chunkManager));
CMD_ANY ("pieces.preload.type", std::bind(&CM_t::preload_type, chunkManager));
CMD_ANY_VALUE_V ("pieces.preload.type.set", std::bind(&CM_t::set_preload_type, chunkManager, std::placeholders::_2));
CMD_ANY ("pieces.preload.min_size", std::bind(&CM_t::preload_min_size, chunkManager));
CMD_ANY_VALUE_V ("pieces.preload.min_size.set", std::bind(&CM_t::set_preload_min_size, chunkManager, std::placeholders::_2));
CMD_ANY ("pieces.preload.min_rate", std::bind(&CM_t::preload_required_rate, chunkManager));
CMD_ANY_VALUE_V ("pieces.preload.min_rate.set", std::bind(&CM_t::set_preload_required_rate, chunkManager, std::placeholders::_2));
CMD_ANY ("pieces.memory.current", std::bind(&CM_t::memory_usage, chunkManager));
CMD_ANY ("pieces.memory.sync_queue", std::bind(&CM_t::sync_queue_memory_usage, chunkManager));
CMD_ANY ("pieces.memory.block_count", std::bind(&CM_t::memory_block_count, chunkManager));
CMD_ANY ("pieces.memory.max", std::bind(&CM_t::max_memory_usage, chunkManager));
CMD_ANY_VALUE_V ("pieces.memory.max.set", std::bind(&CM_t::set_max_memory_usage, chunkManager, std::placeholders::_2));
CMD_ANY ("pieces.stats_preloaded", std::bind(&CM_t::stats_preloaded, chunkManager));
CMD_ANY ("pieces.stats_not_preloaded", std::bind(&CM_t::stats_not_preloaded, chunkManager));
CMD_ANY ("pieces.stats_preloaded", [](auto, auto) { return torrent::runtime::memory_manager()->stats_preloaded(); });
CMD_ANY ("pieces.stats_not_preloaded", [](auto, auto) { return torrent::runtime::memory_manager()->stats_not_preloaded(); });
CMD_ANY ("pieces.stats.total_size", std::bind(&apply_pieces_stats_total_size)); CMD_ANY ("pieces.stats.total_size", std::bind(&apply_pieces_stats_total_size));
CMD_ANY ("pieces.memory.current", [](auto, auto) { return torrent::runtime::memory_manager()->memory_usage(); });
CMD_ANY ("pieces.memory.sync_queue", [](auto, auto) { return torrent::runtime::memory_manager()->sync_queue_memory_usage(); });
CMD_ANY ("pieces.memory.block_count", [](auto, auto) { return torrent::runtime::memory_manager()->memory_block_count(); });
CMD_ANY ("pieces.memory.max", [](auto, auto) { return torrent::runtime::memory_manager()->max_memory_usage(); });
CMD_ANY_VALUE_V ("pieces.memory.max.set", [](auto, auto& value) { return torrent::runtime::memory_manager()->set_max_memory_usage(value); });
CMD_ANY ("pieces.hash.queue_size", std::bind(&torrent::main_thread::hash_queue_size)); CMD_ANY ("pieces.hash.queue_size", std::bind(&torrent::main_thread::hash_queue_size));
CMD_VAR_BOOL ("pieces.hash.on_completion", true); CMD_VAR_BOOL ("pieces.hash.on_completion", true);
CMD_VAR_STRING ("directory.default", "./"); 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_VAR_STRING ("session.name", "");
CMD_ANY ("session.path", [](auto, auto) { return session_thread::manager()->path(); }); 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_STRING_V("session.path.set", [](auto, auto& str) { return session_thread::manager()->set_path(str); });
CMD_ANY ("session.use_lock", [](auto, auto) { return session_thread::manager()->use_lock(); }); CMD_ANY ("session.use_lock", [](auto, auto) { return session_thread::manager()->use_lock(); });
CMD_ANY_VALUE_V ("session.use_lock.set", [](auto, auto& value) { return session_thread::manager()->set_use_lock(value); }); CMD_ANY_VALUE_V ("session.use_lock.set", [](auto, auto& value) { return session_thread::manager()->set_use_lock(value); });
@@ -342,27 +164,12 @@ initialize_command_local() {
CMD_ANY_LIST ("group.insert", std::bind(&group_insert, std::placeholders::_2)); 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("directory.default");
rpc::rpc.mark_safe("session.path"); 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.use_lock");
rpc::rpc.mark_safe("session.on_completion"); rpc::rpc.mark_safe("session.on_completion");
+34 -2
View File
@@ -1,6 +1,7 @@
#include "config.h" #include "config.h"
#include <fcntl.h> #include <fcntl.h>
#include <iterator>
#include <stdio.h> #include <stdio.h>
#include <unistd.h> #include <unistd.h>
#include <torrent/data/chunk_utils.h> #include <torrent/data/chunk_utils.h>
@@ -15,6 +16,7 @@
#include "core/download.h" #include "core/download.h"
#include "core/download_list.h" #include "core/download_list.h"
#include "core/manager.h" #include "core/manager.h"
#include "rpc/parse.h"
#include "rpc/parse_commands.h" #include "rpc/parse_commands.h"
torrent::Object torrent::Object
@@ -38,6 +40,32 @@ apply_log_add_output(const torrent::Object::list_type& args) {
return torrent::Object(); 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. // TODO: Deprecated.
torrent::Object torrent::Object
apply_log(const torrent::Object::string_type& arg, int logType) { apply_log(const torrent::Object::string_type& arg, int logType) {
@@ -94,7 +122,10 @@ log_vmmap_dump(const std::string& str) {
fprintf(log_file, "%8p-%8p [%5llxk]\n", all_mapping.ptr, (char*)all_mapping.ptr + all_mapping.length, (long long unsigned int)(all_mapping.length / 1024)); fprintf(log_file, "%8p-%8p [%5llxk]\n", all_mapping.ptr, (char*)all_mapping.ptr + all_mapping.length, (long long unsigned int)(all_mapping.length / 1024));
} }
fclose(log_file); // The buffered output only reaches the kernel here.
if (fclose(log_file) != 0)
throw torrent::input_error("Could not write log file: " + str);
return torrent::Object(); return torrent::Object();
} }
@@ -107,11 +138,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.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", 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_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_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.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.execute", std::bind(&apply_log, std::placeholders::_2, 0));
CMD2_ANY_STRING ("log.vmmap.dump", std::bind(&log_vmmap_dump, std::placeholders::_2)); CMD2_ANY_STRING ("log.vmmap.dump", std::bind(&log_vmmap_dump, std::placeholders::_2));
+146 -27
View File
@@ -9,7 +9,9 @@
#include <torrent/download/resource_manager.h> #include <torrent/download/resource_manager.h>
#include <torrent/net/http_stack.h> #include <torrent/net/http_stack.h>
#include <torrent/net/socket_address.h> #include <torrent/net/socket_address.h>
#include <torrent/runtime/client_config.h>
#include <torrent/runtime/network_config.h> #include <torrent/runtime/network_config.h>
#include <torrent/runtime/network_manager.h>
#include <torrent/runtime/proxy_manager.h> #include <torrent/runtime/proxy_manager.h>
#include <torrent/runtime/runtime.h> #include <torrent/runtime/runtime.h>
#include <torrent/runtime/socket_manager.h> #include <torrent/runtime/socket_manager.h>
@@ -33,21 +35,115 @@
#endif #endif
torrent::Object torrent::Object
apply_encryption(const torrent::Object::list_type& args) { listen_port_range() {
uint32_t options_mask = torrent::runtime::NetworkConfig::encryption_none; auto port_range = torrent::runtime::client_config()->listen_port_range();
for (const auto& arg : args) { return std::to_string(port_range.first) + "-" + std::to_string(port_range.second);
uint32_t opt = torrent::option_find_string(torrent::OPTION_ENCRYPTION, arg.as_string().c_str()); }
if (opt == torrent::runtime::NetworkConfig::encryption_none) void
options_mask = torrent::runtime::NetworkConfig::encryption_none; set_listen_port_range(const std::string& arg) {
else unsigned int port_first{}, port_last{};
options_mask |= opt;
if (std::sscanf(arg.c_str(), "%i-%i", &port_first, &port_last) != 2)
throw torrent::input_error("Invalid port_range argument.");
if (port_first >= (1 << 16) || port_last >= (1 << 16))
throw torrent::input_error("Port range out-of-bounds.");
torrent::runtime::client_config()->set_listen_port_range(port_first, port_last);
}
torrent::Object
get_encryption() {
auto encryption_modes = torrent::runtime::network_config()->encryption_modes();
return torrent::option_to_str_or_throw(torrent::OPTION_ENCRYPTION_HANDSHAKE, encryption_modes.first) + "," +
torrent::option_to_str_or_throw(torrent::OPTION_ENCRYPTION_STREAM, encryption_modes.second);
}
torrent::Object
get_handshake_encryption() {
auto encryption_modes = torrent::runtime::network_config()->encryption_modes();
return torrent::option_to_str_or_throw(torrent::OPTION_ENCRYPTION_MODE, encryption_modes.first);
}
torrent::Object
get_stream_encryption() {
auto encryption_modes = torrent::runtime::network_config()->encryption_modes();
return torrent::option_to_str_or_throw(torrent::OPTION_ENCRYPTION_MODE, encryption_modes.second);
}
torrent::Object
apply_obsolete_encryption(const torrent::Object::list_type& args) {
torrent::encryption_mode handshake_mode{torrent::ENCRYPTION_MODE_ALLOW};
torrent::encryption_mode stream_mode{torrent::ENCRYPTION_MODE_ALLOW};
for (auto& itr : args) {
auto arg = itr.as_string();
if (arg == "none") {
handshake_mode = torrent::ENCRYPTION_MODE_DENY;
stream_mode = torrent::ENCRYPTION_MODE_DENY;
break;
} else if (arg == "allow_incoming") {
} else if (arg == "try_outgoing") {
} else if (arg == "require") {
handshake_mode = torrent::ENCRYPTION_MODE_REQUIRE;
} else if (arg == "require_RC4" || arg == "require_rc4") {
handshake_mode = torrent::ENCRYPTION_MODE_REQUIRE;
stream_mode = torrent::ENCRYPTION_MODE_REQUIRE;
break;
} else if (arg == "enable_retry") {
} else if (arg == "prefer_plaintext") {
} else {
throw torrent::input_error("Invalid encryption option: '" + arg + "'");
}
} }
torrent::runtime::network_config()->set_encryption_options(options_mask); lt_log_print(torrent::LOG_WARN, "Obsolete encryption options used, use 'handshake_{deny,allow,prefer,require}, stream_{deny,allow,prefer,require}' instead.");
return torrent::Object(); torrent::runtime::network_config()->set_encryption_modes(handshake_mode, stream_mode);
return {};
}
torrent::Object
apply_encryption(const torrent::Object::list_type& args) {
if (args.empty())
throw torrent::input_error("No encryption options specified.");
torrent::encryption_mode encryption_mode, handshake_mode, stream_mode;
if (args.size() == 1) {
try {
encryption_mode = static_cast<torrent::encryption_mode>(torrent::option_find_string_str(torrent::OPTION_ENCRYPTION_MODE, args.front().as_string()));
} catch (torrent::input_error& e) {
return apply_obsolete_encryption(args);
}
torrent::runtime::network_config()->set_encryption_modes(encryption_mode, encryption_mode);
return {};
}
if (args.size() != 2)
return apply_obsolete_encryption(args);
try {
handshake_mode = static_cast<torrent::encryption_mode>(torrent::option_find_string_str(torrent::OPTION_ENCRYPTION_HANDSHAKE, args.front().as_string()));
stream_mode = static_cast<torrent::encryption_mode>(torrent::option_find_string_str(torrent::OPTION_ENCRYPTION_STREAM, args.back().as_string()));
} catch (torrent::input_error& e) {
return apply_obsolete_encryption(args);
}
torrent::runtime::network_config()->set_encryption_modes(handshake_mode, stream_mode);
return {};
} }
torrent::Object torrent::Object
@@ -220,17 +316,22 @@ initialize_command_network() {
auto http_stack = torrent::net_thread::http_stack(); auto http_stack = torrent::net_thread::http_stack();
auto nw_config = torrent::runtime::network_config(); auto nw_config = torrent::runtime::network_config();
// Isn't port_open used? CMD_ANY ("network.listen.port", [](auto, auto) { return torrent::runtime::network_manager()->listen_port(); });
CMD_VAR_BOOL ("network.port_open", true); CMD_ANY_VALUE_V ("network.listen.port.set", [](auto, auto& value) { return torrent::runtime::network_manager()->set_listen_port(checked_port_value(value, "listen")); });
CMD_VAR_BOOL ("network.port_random", true); CMD_ANY ("network.listen.port.random", [](auto, auto) { return torrent::runtime::client_config()->listen_port_random(); });
CMD_VAR_STRING ("network.port_range", "6881-6999"); CMD_ANY_VALUE_V ("network.listen.port.random.set", [](auto, auto& value) { return torrent::runtime::client_config()->set_listen_port_random(value); });
CMD_ANY ("network.listen.port.range", [](auto, auto) { return listen_port_range(); });
CMD_ANY_STRING_V("network.listen.port.range.set", [](auto, auto& value) { return set_listen_port_range(value); });
CMD_ANY ("network.listen.backlog", [](auto, auto) { return torrent::runtime::network_config()->listen_backlog(); });
CMD_ANY_VALUE_V ("network.listen.backlog.set", [](auto, auto& value) { return torrent::runtime::network_config()->set_listen_backlog(value); });
CMD_ANY ("network.listen.port", [](auto, auto) { return torrent::runtime::listen_port(); }); CMD_ANY ("protocol.pex", [](auto, auto) { return torrent::runtime::client_config()->is_pex_enabled(); });
CMD_ANY ("network.listen.backlog", [nw_config](auto, auto) { return nw_config->listen_backlog(); }); CMD_ANY_VALUE_V ("protocol.pex.set", [](auto, auto& value) { return torrent::runtime::client_config()->set_pex_enabled(value); });
CMD_ANY_VALUE_V ("network.listen.backlog.set", [nw_config](auto, auto& value) { return nw_config->set_listen_backlog(value); });
CMD_VAR_BOOL ("protocol.pex", true); CMD_ANY_LIST ("protocol.encryption", [](auto, auto) { return get_encryption(); });
CMD_ANY_LIST ("protocol.encryption.set", [](auto, auto& args) { return apply_encryption(args); }); CMD_ANY_LIST ("protocol.encryption.set", [](auto, auto& args) { return apply_encryption(args); });
CMD_ANY_LIST ("protocol.encryption.handshake", [](auto, auto) { return get_handshake_encryption(); });
CMD_ANY_LIST ("protocol.encryption.stream", [](auto, auto) { return get_stream_encryption(); });
CMD_VAR_STRING ("protocol.connection.leech", "leech"); CMD_VAR_STRING ("protocol.connection.leech", "leech");
CMD_VAR_STRING ("protocol.connection.seed", "seed"); CMD_VAR_STRING ("protocol.connection.seed", "seed");
@@ -265,18 +366,31 @@ initialize_command_network() {
CMD_ANY_STRING ("network.tos.set", [](auto, auto& str) { return apply_tos(str); }); CMD_ANY_STRING ("network.tos.set", [](auto, auto& str) { return apply_tos(str); });
CMD_ANY ("network.bind_address", [nw_config](auto, auto) { return nw_config->bind_address_best_match_str(); }); CMD_ANY ("network.bind_address", [nw_config](auto, auto) { return nw_config->bind_address_best_match_str(); });
CMD_ANY_STRING_V("network.bind_address.set", [nw_config](auto, auto& str) { return nw_config->set_bind_address_str(str); }); CMD_ANY_STRING_V("network.bind_address.set", [nw_config](auto, auto& str) { return nw_config->set_bind_address(str); });
CMD_ANY ("network.bind_address.ipv4", [nw_config](auto, auto) { return nw_config->bind_inet_address_str(); }); CMD_ANY ("network.bind_address.ipv4", [nw_config](auto, auto) { return nw_config->bind_inet_address_str(); });
CMD_ANY_STRING_V("network.bind_address.ipv4.set", [nw_config](auto, auto& str) { return nw_config->set_bind_inet_address_str(str); }); CMD_ANY_STRING_V("network.bind_address.ipv4.set", [nw_config](auto, auto& str) { return nw_config->set_bind_inet_address(str); });
CMD_ANY ("network.bind_address.ipv6", [nw_config](auto, auto) { return nw_config->bind_inet6_address_str(); }); CMD_ANY ("network.bind_address.ipv6", [nw_config](auto, auto) { return nw_config->bind_inet6_address_str(); });
CMD_ANY_STRING_V("network.bind_address.ipv6.set", [nw_config](auto, auto& str) { return nw_config->set_bind_inet6_address_str(str); }); CMD_ANY_STRING_V("network.bind_address.ipv6.set", [nw_config](auto, auto& str) { return nw_config->set_bind_inet6_address(str); });
CMD_ANY_STRING_V("network.bind_device.set", [nw_config](auto, auto& str) { return nw_config->set_bind_device_name(str); });
CMD_ANY ("network.bind_device.ipv4", [nw_config](auto, auto) { return nw_config->bind_inet_device_name(); });
CMD_ANY_STRING_V("network.bind_device.ipv4.set", [nw_config](auto, auto& str) { return nw_config->set_bind_inet_device_name(str); });
CMD_ANY ("network.bind_device.ipv6", [nw_config](auto, auto) { return nw_config->bind_inet6_device_name(); });
CMD_ANY_STRING_V("network.bind_device.ipv6.set", [nw_config](auto, auto& str) { return nw_config->set_bind_inet6_device_name(str); });
CMD_ANY ("network.local_address", [nw_config](auto, auto) { return nw_config->local_address_best_match_str(); }); CMD_ANY ("network.local_address", [nw_config](auto, auto) { return nw_config->local_address_best_match_str(); });
CMD_ANY_STRING_V("network.local_address.set", [nw_config](auto, auto& str) { return nw_config->set_local_address_str(str); }); CMD_ANY_STRING_V("network.local_address.set", [nw_config](auto, auto& str) { return nw_config->set_local_address(str); });
CMD_ANY ("network.local_address.ipv4", [nw_config](auto, auto) { return nw_config->local_inet_address_str(); }); CMD_ANY ("network.local_address.ipv4", [nw_config](auto, auto) { return nw_config->local_inet_address_str(); });
CMD_ANY_STRING_V("network.local_address.ipv4.set", [nw_config](auto, auto& str) { return nw_config->set_local_inet_address_str(str); }); CMD_ANY_STRING_V("network.local_address.ipv4.set", [nw_config](auto, auto& str) { return nw_config->set_local_inet_address(str); });
CMD_ANY ("network.local_address.ipv6", [nw_config](auto, auto) { return nw_config->local_inet6_address_str(); }); 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_STRING_V("network.local_address.ipv6.set", [nw_config](auto, auto& str) { return nw_config->set_local_inet6_address(str); });
CMD_ANY ("network.local_port", [nw_config](auto, auto) { return nw_config->local_port_best_match(); });
CMD_ANY_VALUE_V ("network.local_port.set", [nw_config](auto, auto& value) { return nw_config->set_local_port(checked_port_value(value, "local")); });
CMD_ANY ("network.local_port.ipv4", [nw_config](auto, auto) { return nw_config->local_inet_port(); });
CMD_ANY_VALUE_V ("network.local_port.ipv4.set", [nw_config](auto, auto& value) { return nw_config->set_local_inet_port(checked_port_value(value, "local ipv4")); });
CMD_ANY ("network.local_port.ipv6", [nw_config](auto, auto) { return nw_config->local_inet6_port(); });
CMD_ANY_VALUE_V ("network.local_port.ipv6.set", [nw_config](auto, auto& value) { return nw_config->set_local_inet6_port(checked_port_value(value, "local ipv6")); });
CMD_ANY ("network.proxy.global", [](auto, auto) { return torrent::runtime::proxy_manager()->proxy_url(); }); CMD_ANY ("network.proxy.global", [](auto, auto) { return torrent::runtime::proxy_manager()->proxy_url(); });
CMD_ANY_STRING_V("network.proxy.global.set", [](auto, auto& str) { return torrent::runtime::proxy_manager()->set_proxy_url(str); }); CMD_ANY_STRING_V("network.proxy.global.set", [](auto, auto& str) { return torrent::runtime::proxy_manager()->set_proxy_url(str); });
@@ -301,8 +415,10 @@ initialize_command_network() {
CMD_ANY ("network.xmlrpc.size_limit", [](auto, auto) { return rpc::rpc.size_limit(); }); CMD_ANY ("network.xmlrpc.size_limit", [](auto, auto) { return rpc::rpc.size_limit(); });
CMD_ANY_VALUE_V ("network.xmlrpc.size_limit.set", [](auto, auto& arg) { return rpc::rpc.set_size_limit(arg); }); CMD_ANY_VALUE_V ("network.xmlrpc.size_limit.set", [](auto, auto& arg) { return rpc::rpc.set_size_limit(arg); });
CMD_VAR_BOOL ("network.rpc.use_xmlrpc", true); CMD_ANY ("network.rpc.use_xmlrpc", [](auto, auto) { return rpc::rpc.use_xmlrpc(); });
CMD_VAR_BOOL ("network.rpc.use_jsonrpc", true); CMD_ANY_VALUE_V ("network.rpc.use_xmlrpc.set", [](auto, auto& arg) { return rpc::rpc.set_use_xmlrpc(arg); });
CMD_ANY ("network.rpc.use_jsonrpc", [](auto, auto) { return rpc::rpc.use_jsonrpc(); });
CMD_ANY_VALUE_V ("network.rpc.use_jsonrpc.set", [](auto, auto& arg) { return rpc::rpc.set_use_jsonrpc(arg); });
CMD_ANY ("network.block.ipv4", [nw_config](auto, auto) { return nw_config->is_block_ipv4(); }); CMD_ANY ("network.block.ipv4", [nw_config](auto, auto) { return nw_config->is_block_ipv4(); });
CMD_ANY_VALUE_V ("network.block.ipv4.set", [nw_config](auto, auto& value) { return nw_config->set_block_ipv4(value); }); CMD_ANY_VALUE_V ("network.block.ipv4.set", [nw_config](auto, auto& value) { return nw_config->set_block_ipv4(value); });
@@ -334,6 +450,9 @@ initialize_command_network() {
rpc::rpc.mark_safe("network.receive_buffer.size"); rpc::rpc.mark_safe("network.receive_buffer.size");
rpc::rpc.mark_safe("network.bind_address"); rpc::rpc.mark_safe("network.bind_address");
rpc::rpc.mark_safe("network.local_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.xmlrpc.size_limit");
rpc::rpc.mark_safe("network.open_sockets"); rpc::rpc.mark_safe("network.open_sockets");
+5 -1
View File
@@ -28,9 +28,10 @@ cmd_scheduler_simple_removed(core::Download* download) {
control->core()->download_list()->pause(download); control->core()->download_list()->pause(download);
core::View* viewActive = *control->view_manager()->find("active"); core::View* viewActive = *control->view_manager()->find("active");
unsigned int numActive = viewActive->size_visible();
int64_t maxActive = rpc::call_command("scheduler.max_active", torrent::Object()).as_value(); int64_t maxActive = rpc::call_command("scheduler.max_active", torrent::Object()).as_value();
if ((int64_t)viewActive->size_visible() >= maxActive) if ((int64_t)numActive >= maxActive)
return torrent::Object(); return torrent::Object();
// The 'started' view contains all the views we may choose amongst. // The 'started' view contains all the views we may choose amongst.
@@ -41,6 +42,9 @@ cmd_scheduler_simple_removed(core::Download* download) {
continue; continue;
control->core()->download_list()->resume(*itr); control->core()->download_list()->resume(*itr);
if ((int64_t)++numActive >= maxActive)
break;
} }
return torrent::Object(); return torrent::Object();
+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);
}
+196
View File
@@ -0,0 +1,196 @@
#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.allocate");
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");
}
}
+29 -60
View File
@@ -1,10 +1,9 @@
#include "config.h" #include "config.h"
#include <cstdio> #include <limits>
#include <torrent/throttle.h> #include <torrent/throttle.h>
#include <torrent/rate.h> #include <torrent/rate.h>
#include <torrent/download/resource_manager.h> #include <torrent/download/resource_manager.h>
#include <torrent/net/socket_address.h>
#include "core/manager.h" #include "core/manager.h"
#include "ui/root.h" #include "ui/root.h"
@@ -15,59 +14,6 @@
#include "control.h" #include "control.h"
#include "command_helpers.h" #include "command_helpers.h"
std::pair<uint32_t, uint32_t>
parse_address_range(const torrent::Object::list_type& args, torrent::Object::list_type::const_iterator itr) {
unsigned int prefixWidth, ret;
char dummy;
char host[1024];
torrent::sa_unique_ptr sa;
ret = std::sscanf(itr->as_string().c_str(), "%1023[^/]/%d%c", host, &prefixWidth, &dummy);
if (ret < 1)
throw torrent::input_error("Invalid address/prefix.");
try {
sa = torrent::sa_copy(torrent::sa_lookup_address(host, AF_INET).get());
} catch (torrent::input_error& e) {
throw torrent::input_error("Could not resolve host: " + std::string(e.what()));
}
uint32_t begin, end;
auto sa_addr = htonl(reinterpret_cast<sockaddr_in*>(sa.get())->sin_addr.s_addr);
begin = end = sa_addr;
if (ret == 2) {
if (++itr != args.end())
throw torrent::input_error("Cannot specify both network and range end.");
uint32_t netmask = std::numeric_limits<uint32_t>::max() << (32 - prefixWidth);
if (prefixWidth >= 32 || sa_addr & ~netmask)
throw torrent::input_error("Invalid address/prefix.");
end = sa_addr | ~netmask;
} else if (++itr != args.end()) {
try {
sa = torrent::sa_copy(torrent::sa_lookup_address(itr->as_string(), AF_INET).get());
} catch (torrent::input_error& e) {
throw torrent::input_error("Could not resolve host: " + std::string(e.what()));
}
sa_addr = htonl(reinterpret_cast<sockaddr_in*>(sa.get())->sin_addr.s_addr);
end = sa_addr;
}
// convert to [begin, end) making sure the end doesn't overflow
// (this precludes 255.255.255.255 from ever matching, but that's not a real IP anyway)
return std::make_pair((uint32_t)begin, (uint32_t)std::max(end, end + 1));
}
torrent::Object torrent::Object
apply_throttle(const torrent::Object::list_type& args, bool up) { apply_throttle(const torrent::Object::list_type& args, bool up) {
auto arg_itr = args.begin(); auto arg_itr = args.begin();
@@ -83,11 +29,16 @@ apply_throttle(const torrent::Object::list_type& args, bool up) {
throw torrent::input_error("Missing throttle rate for '" + name + "'."); throw torrent::input_error("Missing throttle rate for '" + name + "'.");
int64_t rate; int64_t rate;
rpc::parse_whole_value_nothrow(arg_itr->as_string().c_str(), &rate);
if (!rpc::parse_whole_value_nothrow(arg_itr->as_string().c_str(), &rate))
throw torrent::input_error("Invalid throttle rate for '" + name + "'.");
if (rate < 0) if (rate < 0)
throw torrent::input_error("Throttle rate must be non-negative."); throw torrent::input_error("Throttle rate must be non-negative.");
if (rate > (std::numeric_limits<int64_t>::max() >> 10))
throw torrent::input_error("Throttle rate is too large.");
auto itr = control->core()->throttles().find(name); auto itr = control->core()->throttles().find(name);
if (itr == control->core()->throttles().end()) if (itr == control->core()->throttles().end())
@@ -138,6 +89,24 @@ throttle_update(const char* variable, int64_t value) {
return torrent::Object(); return torrent::Object();
} }
static unsigned int
throttle_rate_to_kb(int64_t rate) {
if (rate < 0 || rate > std::numeric_limits<unsigned int>::max() - 1)
throw torrent::input_error("Throttle rate must be between 0 and 4294967294.");
return static_cast<unsigned int>(rate >> 10);
}
static void
set_up_throttle_i64(ui::Root* root, int64_t rate) {
root->set_up_throttle(throttle_rate_to_kb(rate));
}
static void
set_down_throttle_i64(ui::Root* root, int64_t rate) {
root->set_down_throttle(throttle_rate_to_kb(rate));
}
void void
initialize_command_throttle() { initialize_command_throttle() {
CMD2_ANY ("throttle.unchoked_uploads", std::bind(&torrent::ResourceManager::currently_upload_unchoked, torrent::resource_manager())); CMD2_ANY ("throttle.unchoked_uploads", std::bind(&torrent::ResourceManager::currently_upload_unchoked, torrent::resource_manager()));
@@ -174,13 +143,13 @@ initialize_command_throttle() {
CMD2_ANY ("throttle.global_up.rate", std::bind(&torrent::Rate::rate, torrent::up_rate())); CMD2_ANY ("throttle.global_up.rate", std::bind(&torrent::Rate::rate, torrent::up_rate()));
CMD2_ANY ("throttle.global_up.total", std::bind(&torrent::Rate::total, torrent::up_rate())); CMD2_ANY ("throttle.global_up.total", std::bind(&torrent::Rate::total, torrent::up_rate()));
CMD2_ANY ("throttle.global_up.max_rate", std::bind(&torrent::Throttle::max_rate, torrent::up_throttle_global())); CMD2_ANY ("throttle.global_up.max_rate", std::bind(&torrent::Throttle::max_rate, torrent::up_throttle_global()));
CMD2_ANY_VALUE_V ("throttle.global_up.max_rate.set", std::bind(&ui::Root::set_up_throttle_i64, control->ui(), std::placeholders::_2)); CMD2_ANY_VALUE_V ("throttle.global_up.max_rate.set", std::bind(&set_up_throttle_i64, control->ui(), std::placeholders::_2));
CMD2_ANY_VALUE_KB("throttle.global_up.max_rate.set_kb", std::bind(&ui::Root::set_up_throttle_i64, control->ui(), std::placeholders::_2)); CMD2_ANY_VALUE_KB("throttle.global_up.max_rate.set_kb", std::bind(&set_up_throttle_i64, control->ui(), std::placeholders::_2));
CMD2_ANY ("throttle.global_down.rate", std::bind(&torrent::Rate::rate, torrent::down_rate())); CMD2_ANY ("throttle.global_down.rate", std::bind(&torrent::Rate::rate, torrent::down_rate()));
CMD2_ANY ("throttle.global_down.total", std::bind(&torrent::Rate::total, torrent::down_rate())); CMD2_ANY ("throttle.global_down.total", std::bind(&torrent::Rate::total, torrent::down_rate()));
CMD2_ANY ("throttle.global_down.max_rate", std::bind(&torrent::Throttle::max_rate, torrent::down_throttle_global())); CMD2_ANY ("throttle.global_down.max_rate", std::bind(&torrent::Throttle::max_rate, torrent::down_throttle_global()));
CMD2_ANY_VALUE_V ("throttle.global_down.max_rate.set", std::bind(&ui::Root::set_down_throttle_i64, control->ui(), std::placeholders::_2)); CMD2_ANY_VALUE_V ("throttle.global_down.max_rate.set", std::bind(&set_down_throttle_i64, control->ui(), std::placeholders::_2));
CMD2_ANY_VALUE_KB("throttle.global_down.max_rate.set_kb", std::bind(&ui::Root::set_down_throttle_i64, control->ui(), std::placeholders::_2)); CMD2_ANY_VALUE_KB("throttle.global_down.max_rate.set_kb", std::bind(&set_down_throttle_i64, control->ui(), std::placeholders::_2));
// Temporary names, need to change this to accept real rates rather // Temporary names, need to change this to accept real rates rather
// than kB. // than kB.
+3 -3
View File
@@ -68,11 +68,11 @@ apply_dht_add_node(const std::string& arg) {
torrent::Object torrent::Object
apply_enable_trackers(int64_t arg) { apply_enable_trackers(int64_t arg) {
if (arg == 0) { 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(); }); download->tracker_controller().for_each([](auto& tracker) { tracker.disable(); });
} else { } 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(); }); download->tracker_controller().for_each([](auto& tracker) { tracker.enable(); });
} }
@@ -142,7 +142,7 @@ initialize_command_tracker() {
lt_log_print(torrent::LOG_DHT_ERROR, "dht.port.set is no longer supported, use dht.override_port.set", 0); lt_log_print(torrent::LOG_DHT_ERROR, "dht.port.set is no longer supported, use dht.override_port.set", 0);
}); });
CMD2_ANY ("dht.override_port", [](auto, auto) { return torrent::runtime::network_config()->override_dht_port(); }); CMD2_ANY ("dht.override_port", [](auto, auto) { return torrent::runtime::network_config()->override_dht_port(); });
CMD2_ANY_VALUE_V ("dht.override_port.set", [](auto, auto& value) { return torrent::runtime::network_config()->set_override_dht_port(value); }); CMD2_ANY_VALUE_V ("dht.override_port.set", [](auto, auto& value) { return torrent::runtime::network_manager()->set_dht_port(checked_port_value(value, "DHT override")); });
CMD2_ANY_STRING ("dht.add_node", [](auto, auto& str) { return apply_dht_add_node(str); }); CMD2_ANY_STRING ("dht.add_node", [](auto, auto& str) { return apply_dht_add_node(str); });
CMD2_ANY ("dht.statistics", [](auto, auto) { return control->dht_manager()->dht_statistics(); }); CMD2_ANY ("dht.statistics", [](auto, auto) { return control->dht_manager()->dht_statistics(); });
+23 -5
View File
@@ -2,7 +2,9 @@
#include <sys/types.h> #include <sys/types.h>
#include <cerrno>
#include <ctime> #include <ctime>
#include <limits>
#include <regex> #include <regex>
#include <torrent/utils/log.h> #include <torrent/utils/log.h>
@@ -125,11 +127,20 @@ apply_value([[maybe_unused]] rpc::target_type target, const torrent::Object::lis
if (args.front().is_value()) { if (args.front().is_value()) {
val = args.front().as_value(); val = args.front().as_value();
} else { } else {
int base = args.size() > 1 ? args.back().is_value() ? int64_t base = args.size() > 1 ? args.back().is_value() ?
args.back().as_value() : strtol(args.back().as_string().c_str(), NULL, 10) : 10; args.back().as_value() : strtoll(args.back().as_string().c_str(), NULL, 10) : 10;
if (base != 0 && (base < 2 || base > 36))
throw torrent::input_error("'value' base must be 0 or between 2 and 36!");
char* endptr = 0; char* endptr = 0;
errno = 0;
val = strtoll(args.front().as_string().c_str(), &endptr, base); val = strtoll(args.front().as_string().c_str(), &endptr, base);
if (errno == ERANGE)
throw torrent::input_error("Number out of range: " + args.front().as_string());
while (*endptr == ' ' || *endptr == '\n') ++endptr; while (*endptr == ' ' || *endptr == '\n') ++endptr;
if (*endptr) if (*endptr)
throw torrent::input_error("Junk at end of number: " + args.front().as_string()); throw torrent::input_error("Junk at end of number: " + args.front().as_string());
@@ -407,8 +418,9 @@ apply_to_time(const torrent::Object& rawArgs, int flags) {
torrent::Object torrent::Object
apply_to_elapsed_time(const torrent::Object& rawArgs) { apply_to_elapsed_time(const torrent::Object& rawArgs) {
auto cached_seconds = torrent::this_thread::cached_seconds().count(); 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]; char buffer[48];
snprintf(buffer, 48, "%2d:%02d:%02d", (int)(arg / 3600), (int)((arg / 60) % 60), (int)(arg % 60)); snprintf(buffer, 48, "%2d:%02d:%02d", (int)(arg / 3600), (int)((arg / 60) % 60), (int)(arg % 60));
@@ -674,8 +686,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"); throw torrent::input_error(std::string(name) + ": Wrong argument type");
} }
if (divides && !rhs && itr != args.begin()) if (divides && itr != args.begin()) {
throw torrent::input_error(std::string(name) + ": Division by zero!"); 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); val = itr == args.begin() ? rhs : op(val, rhs);
@@ -916,6 +933,7 @@ initialize_command_ui() {
rpc::rpc.mark_safe("view.set_visible"); rpc::rpc.mark_safe("view.set_visible");
rpc::rpc.mark_safe("view.set_not_visible"); rpc::rpc.mark_safe("view.set_not_visible");
rpc::rpc.mark_safe("view.list");
rpc::rpc.mark_safe("cat"); rpc::rpc.mark_safe("cat");
rpc::rpc.mark_safe("if"); rpc::rpc.mark_safe("if");
-3
View File
@@ -70,9 +70,6 @@ Control::initialize() {
display::Window::slot_unschedule([this](display::Window* w) { m_display->unschedule(w); }); display::Window::slot_unschedule([this](display::Window* w) { m_display->unschedule(w); });
display::Window::slot_adjust([this]() { m_display->adjust_layout(); }); display::Window::slot_adjust([this]() { m_display->adjust_layout(); });
torrent::net_thread::http_stack()->set_user_agent(USER_AGENT);
m_core->listen_open();
m_core->set_hashing_view(*m_view_manager->find_throw("hashing")); m_core->set_hashing_view(*m_view_manager->find_throw("hashing"));
m_ui->init(this); m_ui->init(this);
+5 -3
View File
@@ -6,7 +6,7 @@
#include <memory> #include <memory>
#include <sys/types.h> #include <sys/types.h>
#include <torrent/torrent.h> #include <torrent/torrent.h>
#include <torrent/utils/scheduler.h> #include <torrent/system/scheduler.h>
namespace ui { namespace ui {
class Root; class Root;
@@ -107,8 +107,8 @@ private:
mode_t m_umask; mode_t m_umask;
std::string m_workingDirectory; std::string m_workingDirectory;
torrent::utils::SchedulerEntry m_task_shutdown; torrent::system::SchedulerEntry m_task_shutdown;
torrent::utils::SchedulerEntry m_task_shutdown_clear_requests; torrent::system::SchedulerEntry m_task_shutdown_clear_requests;
int m_clear_requests_count{}; int m_clear_requests_count{};
@@ -118,4 +118,6 @@ private:
std::atomic<bool> m_shutdown_quick{}; std::atomic<bool> m_shutdown_quick{};
}; };
extern Control* control;
#endif #endif
+33 -10
View File
@@ -4,10 +4,12 @@
#include <fstream> #include <fstream>
#include <sstream> #include <sstream>
#include <torrent/exceptions.h>
#include <torrent/object.h> #include <torrent/object.h>
#include <torrent/object_stream.h> #include <torrent/object_stream.h>
#include <torrent/rate.h> #include <torrent/rate.h>
#include <torrent/runtime/network_manager.h> #include <torrent/runtime/network_manager.h>
#include <torrent/runtime/runtime.h>
#include <torrent/tracker/dht_controller.h> #include <torrent/tracker/dht_controller.h>
#include <torrent/utils/log.h> #include <torrent/utils/log.h>
@@ -61,7 +63,14 @@ DhtManager::load_dht_cache() {
LT_LOG("could not open cache file (path:%s)", cache_filename.c_str()); LT_LOG("could not open cache file (path:%s)", cache_filename.c_str());
} }
torrent::runtime::network_manager()->dht_controller()->initialize(cache); try {
torrent::runtime::network_manager()->dht_controller()->initialize(cache);
} catch (const torrent::base_error& e) {
LT_LOG_ERROR("cache file rejected, discarding (path:%s errmsg:%s)", cache_filename.c_str(), e.what());
torrent::runtime::network_manager()->dht_controller()->initialize(torrent::Object::create_map());
}
if (m_start == dht_on) if (m_start == dht_on)
start_dht(); start_dht();
@@ -131,28 +140,40 @@ DhtManager::save_dht_cache() {
torrent::Object cache = torrent::Object::create_map(); torrent::Object cache = torrent::Object::create_map();
cache_file << *torrent::runtime::network_manager()->dht_controller()->store_cache(&cache); cache_file << *torrent::runtime::network_manager()->dht_controller()->store_cache(&cache);
// The data only reaches the kernel here, so this is where a full disk is seen.
cache_file.close();
if (!cache_file.good()) if (!cache_file.good())
return; return;
cache_file.close();
::rename(filename_tmp.c_str(), filename.c_str()); ::rename(filename_tmp.c_str(), filename.c_str());
} }
void void
DhtManager::set_mode_by_user(const std::string& arg) { DhtManager::set_mode_by_user(const std::string& arg) {
for (int i = 0; i < dht_settings_num; i++) { unsigned int mode = [arg]() {
if (arg == dht_settings[i]) { for (int i = 0; i < dht_settings_num; i++) {
m_set_by_user = true; if (arg == dht_settings[i])
return set_mode_directly(i); return i;
} }
throw torrent::input_error("Invalid dht mode: " + arg);
}();
m_set_by_user = true;
if (!torrent::runtime::is_network_initialized()) {
m_start = mode;
return;
} }
set_mode_directly(mode);
} }
void void
DhtManager::set_mode_directly(unsigned int mode) { DhtManager::set_mode_directly(unsigned int mode) {
if (mode >= dht_settings_num) if (mode >= dht_settings_num)
throw torrent::input_error("Invalid argument."); throw torrent::input_error("Invalid dht mode.");
m_start = mode; m_start = mode;
@@ -164,8 +185,10 @@ DhtManager::set_mode_directly(unsigned int mode) {
void void
DhtManager::set_auto_if_untouched_and_has_session() { DhtManager::set_auto_if_untouched_and_has_session() {
if (m_set_by_user) if (m_set_by_user) {
set_mode_directly(m_start);
return; return;
}
if (rpc::call_command_string("session.path").empty()) { if (rpc::call_command_string("session.path").empty()) {
LT_LOG("DHT auto-start disabled, session path not set.", 0); LT_LOG("DHT auto-start disabled, session path not set.", 0);
+3 -3
View File
@@ -2,7 +2,7 @@
#define RTORRENT_CORE_DHT_MANAGER_H #define RTORRENT_CORE_DHT_MANAGER_H
#include <torrent/object.h> #include <torrent/object.h>
#include <torrent/utils/scheduler.h> #include <torrent/system/scheduler.h>
namespace core { namespace core {
@@ -42,8 +42,8 @@ private:
uint64_t m_dhtPrevBytesUp; uint64_t m_dhtPrevBytesUp;
uint64_t m_dhtPrevBytesDown; uint64_t m_dhtPrevBytesDown;
torrent::utils::SchedulerEntry m_update_timeout; torrent::system::SchedulerEntry m_update_timeout;
torrent::utils::SchedulerEntry m_stop_timeout; torrent::system::SchedulerEntry m_stop_timeout;
bool m_warned{}; bool m_warned{};
bool m_set_by_user{}; bool m_set_by_user{};
+13 -3
View File
@@ -101,7 +101,18 @@ Download::set_throttle_name(const std::string& name) {
} }
void 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 // If the download is open, hashed and has completed chunks make
// sure to verify that the download files are still present. // 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()))) { !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."); 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); 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."); 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)); file_list->set_root_dir(expand_path(path));
bencode()->get_key("rtorrent").insert_key("directory", path); bencode()->get_key("rtorrent").insert_key("directory", path);
+13 -1
View File
@@ -1,6 +1,7 @@
#ifndef RTORRENT_CORE_DOWNLOAD_H #ifndef RTORRENT_CORE_DOWNLOAD_H
#define RTORRENT_CORE_DOWNLOAD_H #define RTORRENT_CORE_DOWNLOAD_H
#include <memory>
#include <torrent/common.h> #include <torrent/common.h>
#include <torrent/download.h> #include <torrent/download.h>
#include <torrent/download_info.h> #include <torrent/download_info.h>
@@ -50,6 +51,14 @@ public:
bool is_hash_checking() const { return m_download.is_hash_checking(); } bool is_hash_checking() const { return m_download.is_hash_checking(); }
bool is_hash_failed() const { return m_hashFailed; } bool is_hash_failed() const { return m_hashFailed; }
// Expires once the download is erased, even if other owners keep the
// object alive. Take it before triggering events that may erase.
std::weak_ptr<void> lifetime() const { return m_lifetime; }
void release_lifetime() { m_lifetime.reset(); }
bool is_erasing() const { return m_erasing; }
void set_erasing() { m_erasing = true; }
void set_hash_failed(bool v) { m_hashFailed = v; } void set_hash_failed(bool v) { m_hashFailed = v; }
download_type* download() { return &m_download; } download_type* download() { return &m_download; }
@@ -78,7 +87,8 @@ public:
uint32_t resume_flags() { return m_resumeFlags; } uint32_t resume_flags() { return m_resumeFlags; }
void set_resume_flags(uint32_t flags) { m_resumeFlags = flags; } 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); void set_throttle_name(const std::string& name);
@@ -101,6 +111,8 @@ private:
// Store the FileList instance so we can use slots etc on it. // Store the FileList instance so we can use slots etc on it.
download_type m_download; download_type m_download;
bool m_hashFailed{}; bool m_hashFailed{};
bool m_erasing{};
std::shared_ptr<void> m_lifetime{std::make_shared<char>()};
std::string m_message; std::string m_message;
uint32_t m_resumeFlags{default_resume_flags}; uint32_t m_resumeFlags{default_resume_flags};
unsigned int m_group{}; unsigned int m_group{};
+76 -29
View File
@@ -7,6 +7,7 @@
#include <functional> #include <functional>
#include <sstream> #include <sstream>
#include <stdexcept> #include <stdexcept>
#include <sys/stat.h>
#include <torrent/utils/log.h> #include <torrent/utils/log.h>
#include <torrent/utils/resume.h> #include <torrent/utils/resume.h>
#include <torrent/object.h> #include <torrent/object.h>
@@ -15,6 +16,7 @@
#include <torrent/rate.h> #include <torrent/rate.h>
#include <torrent/data/file_utils.h> #include <torrent/data/file_utils.h>
#include <torrent/net/http_stack.h> #include <torrent/net/http_stack.h>
#include <torrent/runtime/client_config.h>
#include <torrent/utils/string_manip.h> #include <torrent/utils/string_manip.h>
#include "control.h" #include "control.h"
@@ -34,8 +36,15 @@ is_network_uri(const std::string& uri) {
std::strncmp(uri.c_str(), "ftp://", 6) == 0; std::strncmp(uri.c_str(), "ftp://", 6) == 0;
} }
static constexpr const char* session_invalid_message = "Session data is invalid, ignoring it";
static std::unique_ptr<torrent::Object> static std::unique_ptr<torrent::Object>
download_factory_load_stream(const char* filename) { download_factory_load_stream(const char* filename, bool* is_invalid) {
struct stat sb;
if (stat(filename, &sb) != 0 || !S_ISREG(sb.st_mode))
return std::unique_ptr<torrent::Object>();
std::fstream stream(filename, std::ios::in | std::ios::binary); std::fstream stream(filename, std::ios::in | std::ios::binary);
if (!stream.is_open()) if (!stream.is_open())
@@ -44,8 +53,10 @@ download_factory_load_stream(const char* filename) {
auto obj = std::make_unique<torrent::Object>(); auto obj = std::make_unique<torrent::Object>();
stream >> *obj; stream >> *obj;
if (!stream.good()) if (!stream.good() || !obj->is_map()) {
*is_invalid = true;
return std::unique_ptr<torrent::Object>(); return std::unique_ptr<torrent::Object>();
}
return obj; return obj;
} }
@@ -104,40 +115,42 @@ DownloadFactory::receive_load() {
throw torrent::internal_error("DownloadFactory::load*() called on an object with m_stream != NULL"); throw torrent::internal_error("DownloadFactory::load*() called on an object with m_stream != NULL");
if (is_network_uri(m_uri)) { if (is_network_uri(m_uri)) {
// Http handling here.
m_stream.reset(new std::stringstream); m_stream.reset(new std::stringstream);
m_manager->http_queue()->insert(m_uri, m_stream, auto done_fn = [this]() { receive_loaded(); };
[this]() { receive_loaded(); }, auto failed_fn = [this](const std::string& error) { receive_failed(error); };
[this](const std::string& error) { receive_failed(error); }
); m_manager->http_queue()->insert(m_uri, m_stream, done_fn, failed_fn);
m_variables["tied_to_file"] = (int64_t)false; m_variables["tied_to_file"] = (int64_t)false;
return;
}
} else if (is_magnet_uri(m_uri)) { if (is_magnet_uri(m_uri)) {
// DEBUG: Use m_object. // DEBUG: Use m_object.
m_stream.reset(new std::stringstream()); m_stream.reset(new std::stringstream());
*m_stream << "d10:magnet-uri" << m_uri.length() << ":" << m_uri << "e"; *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();
} else {
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;
stream >> *m_object;
if (!stream.good())
return receive_failed("Reading torrent file failed");
m_isFile = true;
receive_loaded(); receive_loaded();
return;
} }
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;
stream >> *m_object;
if (!stream.good())
return receive_failed("Reading torrent file failed");
m_isFile = true;
receive_loaded();
} }
void void
@@ -158,8 +171,21 @@ DownloadFactory::receive_commit() {
void void
DownloadFactory::receive_success() { DownloadFactory::receive_success() {
auto rtorrent_object = download_factory_load_stream((expand_path(m_uri) + ".rtorrent").c_str()); bool session_invalid = false;
auto libtorrent_resume_object = download_factory_load_stream((expand_path(m_uri) + ".libtorrent_resume").c_str());
std::unique_ptr<torrent::Object> rtorrent_object;
std::unique_ptr<torrent::Object> libtorrent_resume_object;
if (m_session) {
if (m_uri.empty())
throw torrent::input_error("Session torrent URI is empty.");
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 (session_invalid)
lt_log_print(torrent::LOG_ERROR, "%s: %s", session_invalid_message, m_uri.c_str());
}
uint32_t tracker_key; uint32_t tracker_key;
@@ -181,6 +207,14 @@ DownloadFactory::receive_success() {
return; return;
} }
if (session_invalid) {
download->set_hash_failed(true);
download->set_message(session_invalid_message);
if (m_printLog)
m_manager->push_log_std(std::string(session_invalid_message) + ": \"" + m_uri + "\"");
}
torrent::Object* root = download->bencode(); torrent::Object* root = download->bencode();
if (download->download()->info()->is_meta_download()) { if (download->download()->info()->is_meta_download()) {
@@ -269,11 +303,24 @@ DownloadFactory::receive_success() {
if (!m_session && m_variables["tied_to_file"].as_value()) if (!m_session && m_variables["tied_to_file"].as_value())
rpc::call_command("d.tied_to_file.set", m_uri.empty() ? m_variables["tied_file"] : m_uri, rpc::make_target(download)); rpc::call_command("d.tied_to_file.set", m_uri.empty() ? m_variables["tied_file"] : m_uri, rpc::make_target(download));
rpc::call_command("d.peer_exchange.set", rpc::call_command_value("protocol.pex"), rpc::make_target(download)); rpc::call_command("d.peer_exchange.set", torrent::runtime::client_config()->is_pex_enabled(), rpc::make_target(download));
torrent::resume_load_addresses(*download->download(), resumeObject); try {
torrent::resume_load_file_priorities(*download->download(), resumeObject); torrent::resume_load_addresses(*download->download(), resumeObject);
torrent::resume_load_tracker_settings(*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_printLog)
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 // The action of inserting might cause the torrent to be
// opened/started or such. Figure out a nicer way of handling this. // opened/started or such. Figure out a nicer way of handling this.
+4 -4
View File
@@ -9,7 +9,7 @@
#include <iosfwd> #include <iosfwd>
#include <torrent/object.h> #include <torrent/object.h>
#include <torrent/utils/scheduler.h> #include <torrent/system/scheduler.h>
#include "http_queue.h" #include "http_queue.h"
@@ -79,9 +79,9 @@ private:
command_list_type m_commands; command_list_type m_commands;
torrent::Object::map_type m_variables; torrent::Object::map_type m_variables;
slot_void m_slot_finished; slot_void m_slot_finished;
torrent::utils::SchedulerEntry m_task_load; torrent::system::SchedulerEntry m_task_load;
torrent::utils::SchedulerEntry m_task_commit; torrent::system::SchedulerEntry m_task_commit;
}; };
bool is_network_uri(const std::string& uri); bool is_network_uri(const std::string& uri);
+116 -31
View File
@@ -37,7 +37,7 @@ namespace core {
inline void inline void
DownloadList::check_contains([[maybe_unused]] Download* d) { DownloadList::check_contains([[maybe_unused]] Download* d) {
#ifdef USE_EXTRA_DEBUG #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."); throw torrent::internal_error("DownloadList::check_contains(...) failed.");
#endif #endif
} }
@@ -54,7 +54,6 @@ DownloadList::clear() {
base_type::pop_back(); base_type::pop_back();
torrent::download_remove(*download->download()); torrent::download_remove(*download->download());
delete download;
} catch (torrent::internal_error& e) { } catch (torrent::internal_error& e) {
lt_log_print(torrent::LOG_ERROR, "DownloadList::clear() failed to close or remove download: %s", e.what()); lt_log_print(torrent::LOG_ERROR, "DownloadList::clear() failed to close or remove download: %s", e.what());
@@ -70,7 +69,7 @@ DownloadList::clear() {
void void
DownloadList::session_save() { DownloadList::session_save() {
for (auto& download : *this) 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->dht_manager()->save_dht_cache();
control->ui()->save_input_history(); control->ui()->save_input_history();
@@ -78,12 +77,12 @@ DownloadList::session_save() {
DownloadList::iterator DownloadList::iterator
DownloadList::find(const torrent::HashString& hash) { 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 DownloadList::iterator
DownloadList::find_hex(const char* hash) { DownloadList::find_hex(const char* hash) {
if (strlen(hash) < 40) if (strlen(hash) != 40)
return end(); return end();
torrent::HashString key; torrent::HashString key;
@@ -91,14 +90,14 @@ DownloadList::find_hex(const char* hash) {
if (torrent::utils::transform_from_hex(hash, hash + 40, key) != key.end()) if (torrent::utils::transform_from_hex(hash, hash + 40, key) != key.end())
return 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* Download*
DownloadList::find_hex_ptr(const char* hash) { DownloadList::find_hex_ptr(const char* hash) {
iterator itr = find_hex(hash); iterator itr = find_hex(hash);
return itr != end() ? *itr : NULL; return itr != end() ? itr->get() : NULL;
} }
Download* Download*
@@ -159,7 +158,7 @@ DownloadList::create(std::istream* str, uint32_t tracker_key, bool printLog) {
DownloadList::iterator DownloadList::iterator
DownloadList::insert(Download* download) { 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."); lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Inserting download.");
@@ -170,7 +169,7 @@ DownloadList::insert(Download* download) {
// This needs to be separated into two different calls to ensure // This needs to be separated into two different calls to ensure
// the download remains in the view. // the download remains in the view.
for (auto v : *control->view_manager()) for (auto v : *control->view_manager())
v->insert(download); v->insert(*itr);
for (auto v : *control->view_manager()) for (auto v : *control->view_manager())
v->filter_download(download); v->filter_download(download);
@@ -187,7 +186,7 @@ DownloadList::insert(Download* download) {
void void
DownloadList::erase_ptr(Download* download) { 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 DownloadList::iterator
@@ -195,21 +194,27 @@ DownloadList::erase(iterator itr) {
if (itr == end()) if (itr == end())
throw torrent::internal_error("DownloadList::erase(...) could not find download."); throw torrent::internal_error("DownloadList::erase(...) could not find download.");
// An event handler below may erase the same download again.
if ((*itr)->is_erasing())
return std::next(itr);
(*itr)->set_erasing();
lt_log_print_info(torrent::LOG_TORRENT_INFO, (*itr)->info(), "download_list", "Erasing download."); lt_log_print_info(torrent::LOG_TORRENT_INFO, (*itr)->info(), "download_list", "Erasing download.");
// Makes sure close doesn't restart hashing of this download. // Makes sure close doesn't restart hashing of this download.
(*itr)->set_hash_failed(true); (*itr)->set_hash_failed(true);
close(*itr); close(*itr);
session_thread::manager()->remove_download(*itr); session_thread::manager()->remove_download(itr->get());
DL_TRIGGER_EVENT(*itr, "event.download.erased"); DL_TRIGGER_EVENT(*itr, "event.download.erased");
for (auto v : *control->view_manager()) for (auto v : *control->view_manager())
v->erase(*itr); v->erase(itr->get());
(*itr)->release_lifetime();
torrent::download_remove(*(*itr)->download()); torrent::download_remove(*(*itr)->download());
delete *itr;
return base_type::erase(itr); return base_type::erase(itr);
} }
@@ -256,9 +261,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 void
DownloadList::close_directly(Download* download) { DownloadList::close_files(Download* download) {
lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Closing download directly."); lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Closing download files.");
if (download->download()->info()->is_active()) { if (download->download()->info()->is_active()) {
download->download()->stop(torrent::Download::stop_skip_tracker); download->download()->stop(torrent::Download::stop_skip_tracker);
@@ -271,6 +278,53 @@ DownloadList::close_directly(Download* download) {
download->download()->close(); download->download()->close();
} }
void
DownloadList::close_directly(Download* download) {
lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Closing download directly.");
auto lifetime = download->lifetime();
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");
if (lifetime.expired())
return;
update_paused_state(download);
}
if (was_open) {
DL_TRIGGER_EVENT(download, "event.download.hash_removed");
if (lifetime.expired())
return;
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)) + 1, 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 void
DownloadList::close_quick(Download* download) { DownloadList::close_quick(Download* download) {
lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Closing download quickly."); lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Closing download quickly.");
@@ -292,8 +346,13 @@ DownloadList::close_throw(Download* download) {
// When pause gets called it will clear the initial hash check state // When pause gets called it will clear the initial hash check state
// and set hash failed. This should ensure hashing doesn't restart // and set hash failed. This should ensure hashing doesn't restart
// until resume gets called. // until resume gets called.
auto lifetime = download->lifetime();
pause(download); pause(download);
if (lifetime.expired())
return;
// Check for is_open after pause due to hashing. // Check for is_open after pause due to hashing.
if (!download->is_open()) if (!download->is_open())
return; return;
@@ -313,6 +372,10 @@ DownloadList::close_throw(Download* download) {
throw torrent::internal_error("DownloadList::close_throw(...) called but we're going into a hashing loop."); throw torrent::internal_error("DownloadList::close_throw(...) called but we're going into a hashing loop.");
DL_TRIGGER_EVENT(download, "event.download.hash_removed"); DL_TRIGGER_EVENT(download, "event.download.hash_removed");
if (lifetime.expired())
return;
DL_TRIGGER_EVENT(download, "event.download.closed"); DL_TRIGGER_EVENT(download, "event.download.closed");
} }
@@ -419,6 +482,8 @@ DownloadList::pause(Download* download, int flags) {
lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Pausing download: flags:%0x.", flags); lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Pausing download: flags:%0x.", flags);
auto lifetime = download->lifetime();
try { try {
download->set_resume_flags(Download::default_resume_flags); download->set_resume_flags(Download::default_resume_flags);
@@ -432,6 +497,9 @@ DownloadList::pause(Download* download, int flags) {
rpc::call_command_set_value("d.hashing.set", Download::variable_hashing_stopped, rpc::make_target(download)); rpc::call_command_set_value("d.hashing.set", Download::variable_hashing_stopped, rpc::make_target(download));
DL_TRIGGER_EVENT(download, "event.download.hash_removed"); DL_TRIGGER_EVENT(download, "event.download.hash_removed");
if (lifetime.expired())
return;
} }
if (!download->download()->info()->is_active()) if (!download->download()->info()->is_active())
@@ -445,15 +513,10 @@ DownloadList::pause(Download* download, int flags) {
// view. // view.
DL_TRIGGER_EVENT(download, "event.download.paused"); DL_TRIGGER_EVENT(download, "event.download.paused");
auto cached_seconds = torrent::this_thread::cached_seconds().count(); if (lifetime.expired())
return;
rpc::call_command("d.state_changed.set", cached_seconds, rpc::make_target(download)); update_paused_state(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));
// Save the state after all the slots, etc have been called so we // Save the state after all the slots, etc have been called so we
// include the modifications they may make. // include the modifications they may make.
@@ -524,7 +587,7 @@ DownloadList::hash_done(Download* download) {
// If the download was previously completed but the files were // If the download was previously completed but the files were
// f.ex deleted, then we clear the state and complete. // f.ex deleted, then we clear the state and complete.
if (rpc::call_command_value("d.complete", rpc::make_target(download)) && !download->is_done()) { if (rpc::call_command_value("d.complete", rpc::make_target(download)) && !download->is_done()) {
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."); download->set_message("Download registered as completed, but hash check returned unfinished chunks.");
} }
@@ -533,9 +596,15 @@ DownloadList::hash_done(Download* download) {
rpc::call_command("d.complete.set", (int64_t)download->is_done(), rpc::make_target(download)); rpc::call_command("d.complete.set", (int64_t)download->is_done(), rpc::make_target(download));
torrent::resume_save_progress(*download->download(), download->download()->bencode()->get_key("libtorrent_resume")); torrent::resume_save_progress(*download->download(), download->download()->bencode()->get_key("libtorrent_resume"));
if (rpc::call_command_value("d.state", rpc::make_target(download)) == 1) if (rpc::call_command_value("d.state", rpc::make_target(download)) == 1) {
auto lifetime = download->lifetime();
resume(download, download->resume_flags()); resume(download, download->resume_flags());
if (lifetime.expired())
return;
}
break; break;
case Download::variable_hashing_last: case Download::variable_hashing_last:
@@ -572,11 +641,24 @@ DownloadList::hash_queue(Download* download, int type) {
// HACK // HACK
if (download->is_open()) { if (download->is_open()) {
auto lifetime = download->lifetime();
pause(download, torrent::Download::stop_skip_tracker); pause(download, torrent::Download::stop_skip_tracker);
if (lifetime.expired())
return;
download->download()->close(); download->download()->close();
DL_TRIGGER_EVENT(download, "event.download.hash_removed"); DL_TRIGGER_EVENT(download, "event.download.hash_removed");
if (lifetime.expired())
return;
DL_TRIGGER_EVENT(download, "event.download.closed"); DL_TRIGGER_EVENT(download, "event.download.closed");
if (lifetime.expired())
return;
} }
torrent::resume_clear_progress(*download->download(), download->download()->bencode()->get_key("libtorrent_resume")); torrent::resume_clear_progress(*download->download(), download->download()->bencode()->get_key("libtorrent_resume"));
@@ -627,6 +709,10 @@ DownloadList::confirm_finished(Download* download) {
if (choke_up.is_string_empty()) choke_up = rpc::call_command("protocol.choke_heuristics.up.seed", torrent::Object(), rpc::make_target(download)); if (choke_up.is_string_empty()) choke_up = rpc::call_command("protocol.choke_heuristics.up.seed", torrent::Object(), rpc::make_target(download));
if (choke_down.is_string_empty()) choke_down = rpc::call_command("protocol.choke_heuristics.down.seed", torrent::Object(), rpc::make_target(download)); if (choke_down.is_string_empty()) choke_down = rpc::call_command("protocol.choke_heuristics.down.seed", torrent::Object(), rpc::make_target(download));
// libtorrent refuses initial seeding on an active download.
if (download->is_active() && conn_current.as_string() == "initial_seed")
conn_current = "seed";
rpc::call_command("d.connection_current.set", conn_current, rpc::make_target(download)); rpc::call_command("d.connection_current.set", conn_current, rpc::make_target(download));
rpc::call_command("d.up.choke_heuristics.set", choke_up, rpc::make_target(download)); rpc::call_command("d.up.choke_heuristics.set", choke_up, rpc::make_target(download));
rpc::call_command("d.down.choke_heuristics.set", choke_down, rpc::make_target(download)); rpc::call_command("d.down.choke_heuristics.set", choke_down, rpc::make_target(download));
@@ -652,12 +738,12 @@ DownloadList::confirm_finished(Download* download) {
// up/downloaded baseline. // up/downloaded baseline.
download->download()->send_completed(); download->download()->send_completed();
// Save the hash in case the finished event erases it. // The finished event may erase the download.
torrent::HashString infohash = download->info()->hash(); auto lifetime = download->lifetime();
DL_TRIGGER_EVENT(download, "event.download.finished"); DL_TRIGGER_EVENT(download, "event.download.finished");
if (find(infohash) == end()) if (lifetime.expired())
return; return;
// if (download->resume_flags() != Download::default_resume_flags) // if (download->resume_flags() != Download::default_resume_flags)
@@ -696,11 +782,10 @@ DownloadList::process_meta_download(Download* download) {
return; return;
} }
torrent::Object* bencode = new torrent::Object(torrent::Object::create_map()); auto bencode = std::make_unique<torrent::Object>(torrent::Object::create_map());
file >> bencode->insert_key("info", torrent::Object()); file >> bencode->insert_key("info", torrent::Object());
if (file.fail()) { if (file.fail()) {
delete bencode;
lt_log_print(torrent::LOG_TORRENT_ERROR, "Could not create download, the input is not a valid torrent."); lt_log_print(torrent::LOG_TORRENT_ERROR, "Could not create download, the input is not a valid torrent.");
return; return;
} }
@@ -716,7 +801,7 @@ DownloadList::process_meta_download(Download* download) {
erase_ptr(download); erase_ptr(download);
control->core()->try_create_download_from_meta_download(bencode, metafile); control->core()->try_create_download_from_meta_download(std::move(bencode), metafile);
} }
} }
+15 -2
View File
@@ -3,6 +3,7 @@
#include <iosfwd> #include <iosfwd>
#include <list> #include <list>
#include <memory>
#include <string> #include <string>
namespace torrent { namespace torrent {
@@ -20,9 +21,9 @@ class Download;
// //
// Fix apply_on_ratio if the base_type is changed. // 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: public:
typedef std::list<Download*> base_type; typedef std::list<std::shared_ptr<Download>> base_type;
using base_type::iterator; using base_type::iterator;
using base_type::const_iterator; using base_type::const_iterator;
@@ -64,8 +65,17 @@ public:
bool open(Download* d); bool open(Download* d);
void open_throw(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(Download* d);
void close_directly(Download* d); void close_directly(Download* d);
void close_files(Download* d);
void close_quick(Download* d); void close_quick(Download* d);
void close_throw(Download* d); void close_throw(Download* d);
@@ -123,6 +133,9 @@ private:
void hash_done(Download* d); void hash_done(Download* d);
void hash_queue(Download* d, int type); 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); inline void check_contains(Download* d);
void received_finished(Download* d); void received_finished(Download* d);
+3 -6
View File
@@ -19,14 +19,11 @@ HttpQueue::insert(const std::string& url, std::shared_ptr<std::ostream> stream,
for (auto& slot : m_signal_insert) for (auto& slot : m_signal_insert)
slot(*itr); slot(*itr);
itr->add_done_slot(torrent::this_thread::thread(), [this, itr]() { erase(itr); });
itr->add_failed_slot(torrent::this_thread::thread(), [this, itr](auto) { erase(itr); });
itr->add_done_slot(torrent::this_thread::thread(), std::move(done_fn)); itr->add_done_slot(torrent::this_thread::thread(), std::move(done_fn));
itr->add_failed_slot(torrent::this_thread::thread(), std::move(failed_fn)); itr->add_done_slot(torrent::this_thread::thread(), [this, itr]() { erase(itr); });
// TODO: Downloading http torrents doesn't seem to work. itr->add_failed_slot(torrent::this_thread::thread(), std::move(failed_fn));
// TODO: Quitting no longer works. itr->add_failed_slot(torrent::this_thread::thread(), [this, itr](auto) { erase(itr); });
torrent::net_thread::http_stack()->start_get(*itr); torrent::net_thread::http_stack()->start_get(*itr);
+12 -41
View File
@@ -33,6 +33,8 @@
#include "core/http_queue.h" #include "core/http_queue.h"
#include "core/view.h" #include "core/view.h"
#include <torrent/runtime/client_config.h>
namespace core { namespace core {
const int Manager::create_start; const int Manager::create_start;
@@ -152,48 +154,14 @@ Manager::cleanup() {
void void
Manager::shutdown(bool force) { Manager::shutdown(bool force) {
if (!force) { if (!force) {
for (auto d : *m_download_list) for (const auto& d : *m_download_list)
m_download_list->pause_default(d); m_download_list->pause_default(d.get());
} else { } else {
for (auto d : *m_download_list) for (const auto& d : *m_download_list)
m_download_list->close_quick(d); m_download_list->close_quick(d);
} }
} }
void
Manager::listen_open() {
// This stuff really should be moved outside of manager, make it
// part of the init script.
if (!rpc::call_command_value("network.port_open"))
return;
int portFirst, portLast;
torrent::Object portRange = rpc::call_command("network.port_range");
if (!portRange.is_string())
throw torrent::input_error("Invalid port_range argument type.");
if (std::sscanf(portRange.as_string().c_str(), "%i-%i", &portFirst, &portLast) != 2)
throw torrent::input_error("Invalid port_range argument.");
if (portFirst > portLast || portLast >= (1 << 16))
throw torrent::input_error("Invalid port range.");
if (rpc::call_command_value("network.port_random")) {
int boundary = portFirst + random() % (portLast - portFirst + 1);
if (torrent::runtime::network_manager()->listen_open(boundary, portLast) ||
torrent::runtime::network_manager()->listen_open(portFirst, boundary))
return;
} else {
if (torrent::runtime::network_manager()->listen_open(portFirst, portLast))
return;
}
throw torrent::input_error("Could not open/bind port for listening: " + std::string(std::strerror(errno)));
}
void void
Manager::receive_http_failed(std::string msg) { Manager::receive_http_failed(std::string msg) {
push_log_std("Http download error: \"" + msg + "\""); push_log_std("Http download error: \"" + msg + "\"");
@@ -227,14 +195,14 @@ Manager::try_create_download(const std::string& uri, int flags, const command_li
return; return;
// Adding download. // Adding download.
DownloadFactory* f = new DownloadFactory(this); auto f = std::make_unique<DownloadFactory>(this);
f->variables()["tied_to_file"] = (int64_t)(bool)(flags & create_tied); f->variables()["tied_to_file"] = (int64_t)(bool)(flags & create_tied);
f->commands().insert(f->commands().end(), commands.begin(), commands.end()); f->commands().insert(f->commands().end(), commands.begin(), commands.end());
f->set_start(flags & create_start); f->set_start(flags & create_start);
f->set_print_log(!(flags & create_quiet)); f->set_print_log(!(flags & create_quiet));
f->slot_finished([f]() { delete f; }); f->slot_finished([factory = f.get()]() { delete factory; });
if (is_data_uri(uri)) { if (is_data_uri(uri)) {
// Allow the use of data URIs, primarily for JSON-RPC which // Allow the use of data URIs, primarily for JSON-RPC which
@@ -248,10 +216,13 @@ Manager::try_create_download(const std::string& uri, int flags, const command_li
} }
f->commit(); f->commit();
// From here the finished slot deletes it.
f.release();
} }
void void
Manager::try_create_download_from_meta_download(torrent::Object* bencode, const std::string& metafile) { Manager::try_create_download_from_meta_download(std::unique_ptr<torrent::Object> bencode, const std::string& metafile) {
DownloadFactory* f = new DownloadFactory(this); DownloadFactory* f = new DownloadFactory(this);
f->variables()["tied_to_file"] = (int64_t)true; f->variables()["tied_to_file"] = (int64_t)true;
@@ -437,7 +408,7 @@ Manager::receive_hashing_changed() {
continue; continue;
try { 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 // Since the bitfield is allocated on loading of resume load or
// hash start, and unallocated on close, we know that if it it // hash start, and unallocated on close, we know that if it it
+1 -4
View File
@@ -8,7 +8,6 @@
#include <torrent/object.h> #include <torrent/object.h>
#include "download_list.h" #include "download_list.h"
#include "range_map.h"
namespace torrent { namespace torrent {
class Bencode; class Bencode;
@@ -55,8 +54,6 @@ public:
void cleanup(); void cleanup();
void listen_open();
const std::string& magnet_path(); const std::string& magnet_path();
void set_magnet_path(const std::string& path); void set_magnet_path(const std::string& path);
@@ -78,7 +75,7 @@ public:
// Temporary, find a better place for this. // Temporary, find a better place for this.
void try_create_download(const std::string& uri, int flags, const command_list_type& commands); void try_create_download(const std::string& uri, int flags, const command_list_type& commands);
void try_create_download_expand(const std::string& uri, int flags, command_list_type commands = command_list_type()); 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_from_meta_download(std::unique_ptr<torrent::Object> bencode, const std::string& metafile);
private: private:
void create_http(const std::string& uri); void create_http(const std::string& uri);
-199
View File
@@ -1,199 +0,0 @@
// rTorrent - BitTorrent client
// Copyright (C) 2005-2008, Jari Sundell
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// In addition, as a special exception, the copyright holders give
// permission to link the code of portions of this program with the
// OpenSSL library under certain conditions as described in each
// individual source file, and distribute linked combinations
// including the two.
//
// You must obey the GNU General Public License in all respects for
// all of the code used other than OpenSSL. If you modify file(s)
// with this exception, you may extend this exception to your version
// of the file(s), but you are not obligated to do so. If you do not
// wish to do so, delete this exception statement from your version.
// If you delete this exception statement from all source files in the
// program, then also delete it here.
//
// Contact: Jari Sundell <sundell.software@gmail.com>
#ifndef RTORRENT_CORE_RANGE_MAP_H
#define RTORRENT_CORE_RANGE_MAP_H
#include <map>
#include <stdexcept>
namespace core {
// Associate values with a range of keys, and retrieve for any key in the range.
// The template arguments have the same semantics as std::map.
// Exception: if set_merge is used, the value type must have a defined operator ==.
template<typename Key, typename T, typename Compare = std::less<Key>,
typename Alloc = std::allocator<std::pair<const Key, T> > >
class RangeMap : private std::map<Key, std::pair<Key, T>, Compare,
typename std::allocator_traits<Alloc>::template rebind_alloc<std::pair<const Key, std::pair<Key, T>>>> {
typedef std::map<Key, std::pair<Key, T>, Compare,
typename std::allocator_traits<Alloc>::template rebind_alloc<std::pair<const Key, std::pair<Key, T>>>> base_type;
//std::allocator_traits<Alloc>::template rebind_alloc<std::pair<const Key, std::pair<Key, T>>>
public:
RangeMap() = default;
RangeMap(const Compare& c) : base_type(c) {}
typedef typename base_type::iterator iterator;
typedef typename base_type::reverse_iterator reverse_iterator;
typedef typename base_type::const_iterator const_iterator;
typedef typename base_type::const_reverse_iterator const_reverse_iterator;
// using typename base_type::const_iterator;
// using typename base_type::const_reverse_iterator;
using base_type::clear;
using base_type::swap;
using base_type::size;
using base_type::empty;
using base_type::begin;
using base_type::end;
using base_type::rbegin;
using base_type::rend;
using base_type::key_comp;
using base_type::value_comp;
// Store a value for the range [begin, end). Returns iterator for the range.
const_iterator set_range(const Key& begin, const Key& end, const T& value);
// Same, but merge adjacent ranges having the same value. Returns iterator for the merged range.
const_iterator set_merge(Key begin, const Key& end, const T& value);
// Find range containing the given key, or end().
const_iterator find(const Key& key) const;
// Retrieve value for key in a range, throw std::out_of_range if range does not exist.
const T& get(const Key& key) const;
// Retrieve value for key in a range, return def if range does not exist.
T get(const Key& key, T def) const;
private:
iterator crop_overlap(const Key& begin, const Key& end);
};
// Semantics of an entry:
// .first End of range (exclusive), map key.
// .second.first Beginning of range.
// .second.second Value.
template<typename Key, typename T, typename C, typename A>
inline typename RangeMap<Key,T,C,A>::iterator
RangeMap<Key,T,C,A>::crop_overlap(const Key& _begin, const Key& _end) {
typename RangeMap::iterator itr = base_type::upper_bound(_begin);
while (itr != end() && key_comp()(itr->second.first, _end)) {
// There's a subrange before the new begin: need new entry (new range end means new key).
if (key_comp()(itr->second.first, _begin))
base_type::insert(itr, typename RangeMap::value_type(_begin, itr->second));
// Old end is within our range: erase entry.
if (!key_comp()(_end, itr->first)) {
base_type::erase(itr++);
// Otherwise simply set the new begin of the old range.
} else {
itr->second.first = _end;
++itr;
}
}
return itr;
}
template<typename Key, typename T, typename C, typename A>
inline typename RangeMap<Key,T,C,A>::const_iterator
RangeMap<Key,T,C,A>::set_merge(Key _begin, const Key& _end, const T& value) {
if (!key_comp()(_begin, _end))
return end();
// Crop overlapping ranges and return iterator to first range after the one we're inserting.
typename RangeMap::iterator itr = crop_overlap(_begin, _end);
// Check if range before new one is adjacent and has same value: if so erase it and use its beginning.
if (itr != begin()) {
typename RangeMap::iterator prev = itr;
if (!key_comp()((--prev)->first, _begin) && prev->second.second == value) {
_begin = prev->second.first;
base_type::erase(prev);
}
}
// Range after new one is adjacent and has same value: set new beginning.
if (itr != end() && !key_comp()(_end, itr->second.first) && itr->second.second == value) {
itr->second.first = _begin;
return itr;
}
// Otherwise, this range isn't mergeable, make new entry.
return base_type::insert(itr, typename RangeMap::value_type(_end, typename RangeMap::mapped_type(_begin, value)));
}
template<typename Key, typename T, typename C, typename A>
inline typename RangeMap<Key,T,C,A>::const_iterator
RangeMap<Key,T,C,A>::set_range(const Key& _begin, const Key& _end, const T& value) {
if (!key_comp()(_begin, _end))
return end();
return base_type::insert(crop_overlap(_begin, _end), typename RangeMap::value_type(_end, typename RangeMap::mapped_type(_begin, value)));
}
template<typename Key, typename T, typename C, typename A>
inline typename RangeMap<Key,T,C,A>::const_iterator
RangeMap<Key,T,C,A>::find(const Key& key) const {
typename RangeMap::const_iterator itr = base_type::upper_bound(key);
if (itr != end() && key_comp()(key, itr->second.first))
itr = end();
return itr;
}
template<typename Key, typename T, typename C, typename A>
inline const T&
RangeMap<Key,T,C,A>::get(const Key& key) const {
typename RangeMap::const_iterator itr = find(key);
if (itr == end())
throw std::out_of_range("RangeMap::get");
return itr->second.second;
}
template<typename Key, typename T, typename C, typename A>
inline T
RangeMap<Key,T,C,A>::get(const Key& key, T def) const {
typename RangeMap::const_iterator itr = find(key);
return (itr == end() ? def : itr->second.second);
}
}
#endif
+42 -18
View File
@@ -16,10 +16,20 @@
namespace core { namespace core {
// Also add focus thingie here? // 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 { struct view_downloads_compare {
view_downloads_compare(const torrent::Object& cmd) : view_downloads_compare(const torrent::Object& cmd) :
m_command(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 { bool operator()(Download* d1, Download* d2) const {
try { try {
if (m_command.is_empty()) if (m_command.is_empty())
@@ -54,6 +64,10 @@ struct view_downloads_filter {
view_downloads_filter(const torrent::Object& cmd, const torrent::Object& cmd2) : view_downloads_filter(const torrent::Object& cmd, const torrent::Object& cmd2) :
m_command(cmd), m_command2(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 { bool operator()(Download* d1) const {
return this->evalCmd(m_command, d1) && this->evalCmd(m_command2, d1); return this->evalCmd(m_command, d1) && this->evalCmd(m_command2, d1);
} }
@@ -152,7 +166,7 @@ View::initialize(const std::string& name) {
void void
View::erase(Download* download) { 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()) { if (itr >= end_visible()) {
erase_internal(itr); erase_internal(itr);
@@ -165,22 +179,24 @@ View::erase(Download* download) {
void void
View::set_visible(Download* download) { 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()) if (itr == end_filtered())
return; return;
// Don't optimize erase since we want to keep the order of the // Don't optimize erase since we want to keep the order of the
// non-visible elements. // non-visible elements.
auto entry = *itr;
base_type::erase(itr); base_type::erase(itr);
insert_visible(download); insert_visible(entry);
rpc::call_object_nothrow(m_event_added, rpc::make_target(download)); rpc::call_object_nothrow(m_event_added, rpc::make_target(download));
} }
void void
View::set_not_visible(Download* download) { 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()) if (itr == end_visible())
return; return;
@@ -190,15 +206,17 @@ View::set_not_visible(Download* download) {
// Don't optimize erase since we want to keep the order of the // Don't optimize erase since we want to keep the order of the
// non-visible elements. // non-visible elements.
auto entry = *itr;
base_type::erase(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)); rpc::call_object_nothrow(m_event_removed, rpc::make_target(download));
} }
void void
View::next_focus(unsigned int i) { View::next_focus(unsigned int i) {
if (empty()) if (empty_visible())
return; return;
// If at the boundary, roll over // If at the boundary, roll over
@@ -221,7 +239,7 @@ View::next_focus(unsigned int i) {
void void
View::prev_focus(unsigned int i) { View::prev_focus(unsigned int i) {
if (empty()) if (empty_visible())
return; return;
// If at the boundary, roll over // If at the boundary, roll over
@@ -241,12 +259,12 @@ View::prev_focus(unsigned int i) {
void void
View::sort() { 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. // Don't go randomly switching around equivalent elements.
std::stable_sort(begin(), end_visible(), view_downloads_compare(m_sortCurrent)); 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(); emit_changed();
} }
@@ -280,10 +298,10 @@ View::filter() {
// set the elements to NULL as we trigger commands on them. Or // set the elements to NULL as we trigger commands on them. Or
// perhaps always clear them, thus not throwing anything. // perhaps always clear them, thus not throwing anything.
if (!m_event_removed.is_empty()) 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()) 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(splitChanged, changed.end(), [this](const auto& d) { rpc::call_object_d_nothrow(m_event_added, d.get()); });
emit_changed(); 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); view_downloads_filter matches = view_downloads_filter(condition, m_temp_filter);
for (iterator itr = begin_visible(); itr != end_visible(); ++itr) for (iterator itr = begin_visible(); itr != end_visible(); ++itr)
if (matches(*itr)) if (matches(itr->get()))
result.push_back(*itr); result.push_back(*itr);
} }
void void
View::filter_download(core::Download* download) { 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()) if (itr == base_type::end())
throw torrent::internal_error("View::filter_download(...) could not find download."); throw torrent::internal_error("View::filter_download(...) could not find download.");
if (view_downloads_filter(m_filter, m_temp_filter)(download)) { if (view_downloads_filter(m_filter, m_temp_filter)(download)) {
if (itr >= end_visible()) { if (itr >= end_visible()) {
auto entry = *itr;
erase_internal(itr); erase_internal(itr);
insert_visible(download); insert_visible(entry);
rpc::call_object_nothrow(m_event_added, rpc::make_target(download)); rpc::call_object_nothrow(m_event_added, rpc::make_target(download));
@@ -317,16 +337,20 @@ View::filter_download(core::Download* download) {
// already visible. // already visible.
// //
// Consider removing this. // Consider removing this.
auto entry = *itr;
erase_internal(itr); erase_internal(itr);
insert_visible(download); insert_visible(entry);
} }
} else { } else {
if (itr >= end_visible()) if (itr >= end_visible())
return; return;
auto entry = *itr;
erase_internal(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)); rpc::call_object_nothrow(m_event_removed, rpc::make_target(download));
} }
@@ -345,8 +369,8 @@ View::clear_filter_on() {
} }
inline void inline void
View::insert_visible(Download* d) { View::insert_visible(const std::shared_ptr<Download>& d) {
auto itr = std::find_if(begin_visible(), end_visible(), [this, d](auto d2) { return view_downloads_compare(m_sortNew)(d, d2); }); 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_size++;
m_focus += (m_focus >= position(itr)); m_focus += (m_focus >= position(itr));
+9 -8
View File
@@ -15,10 +15,11 @@
#include <functional> #include <functional>
#include <list> #include <list>
#include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
#include <torrent/object.h> #include <torrent/object.h>
#include <torrent/utils/scheduler.h> #include <torrent/system/scheduler.h>
#include "globals.h" #include "globals.h"
@@ -26,9 +27,9 @@ namespace core {
class Download; class Download;
class View : private std::vector<Download*> { class View : private std::vector<std::shared_ptr<Download>> {
public: public:
typedef std::vector<Download*> base_type; typedef std::vector<std::shared_ptr<Download>> base_type;
typedef std::function<void()> slot_void; typedef std::function<void()> slot_void;
typedef std::list<slot_void> signal_void; typedef std::list<slot_void> signal_void;
@@ -72,7 +73,7 @@ public:
emit_changed(); 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 erase(Download* download);
void set_visible(Download* download); void set_visible(Download* download);
@@ -124,9 +125,9 @@ private:
View(const View&); View(const View&);
void operator=(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); inline void erase_internal(iterator itr);
void emit_changed(); void emit_changed();
@@ -153,8 +154,8 @@ private:
std::chrono::microseconds m_last_changed{}; std::chrono::microseconds m_last_changed{};
signal_void m_signal_changed; signal_void m_signal_changed;
torrent::utils::SchedulerEntry m_delay_changed; torrent::system::SchedulerEntry m_delay_changed;
}; };
} // namespace core } // namespace core
+4 -4
View File
@@ -1,7 +1,7 @@
#ifndef RTORRENT_DISPLAY_MANAGER_H #ifndef RTORRENT_DISPLAY_MANAGER_H
#define RTORRENT_DISPLAY_MANAGER_H #define RTORRENT_DISPLAY_MANAGER_H
#include <torrent/utils/scheduler.h> #include <torrent/system/scheduler.h>
#include "display/frame.h" #include "display/frame.h"
@@ -31,9 +31,9 @@ private:
bool m_force_redraw{false}; bool m_force_redraw{false};
Frame m_root_frame; Frame m_root_frame;
std::chrono::microseconds m_time_last_update{}; std::chrono::microseconds m_time_last_update{};
torrent::utils::ExternalScheduler m_scheduler; torrent::system::ExternalScheduler m_scheduler;
torrent::utils::SchedulerEntry m_task_update; 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()) { if (!torrent::up_throttle_global()->is_throttled()) {
first = print_buffer(first, last, "[Throttle off"); first = print_buffer(first, last, "[Throttle off");
} else { } 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()) if (!throttle_up_names.empty())
first = print_status_throttle_limit(first, last, true, throttle_up_names); 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()) { if (!torrent::down_throttle_global()->is_throttled()) {
first = print_buffer(first, last, " / off KB]"); first = print_buffer(first, last, " / off KB]");
} else { } 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()) if (!throttle_down_names.empty())
first = print_status_throttle_limit(first, last, false, throttle_down_names); first = print_status_throttle_limit(first, last, false, throttle_down_names);
-6
View File
@@ -16,15 +16,12 @@ namespace utils {
namespace torrent { namespace torrent {
class ClientInfo; class ClientInfo;
class Entry;
} }
class Control; class Control;
namespace display { namespace display {
char* print_string(char* first, char* last, char* str);
char* print_hhmmss(char* first, char* last, time_t t); char* print_hhmmss(char* first, char* last, time_t t);
char* print_hhmmss_local(char* first, char* last, time_t t); char* print_hhmmss_local(char* first, char* last, time_t t);
char* print_ddhhmm(char* first, char* last, time_t t); char* print_ddhhmm(char* first, char* last, time_t t);
@@ -42,9 +39,6 @@ char* print_download_percentage_done(char* first, char* last, core::Downlo
char* print_client_version(char* first, char* last, const torrent::ClientInfo& clientInfo); char* print_client_version(char* first, char* last, const torrent::ClientInfo& clientInfo);
char* print_entry_tags(char* first, char* last);
char* print_entry_file(char* first, char* last, const torrent::Entry& entry);
char* print_status_throttle_limit(char* first, char* last, bool up, const std::vector<std::string>& throttle_names); char* print_status_throttle_limit(char* first, char* last, bool up, const std::vector<std::string>& throttle_names);
char* print_status_throttle_rate(char* first, char* last, bool up, const std::vector<std::string>& throttle_names, const double& global_rate); char* print_status_throttle_rate(char* first, char* last, bool up, const std::vector<std::string>& throttle_names, const double& global_rate);
+2 -2
View File
@@ -2,7 +2,7 @@
#define RTORRENT_WINDOW_BASE_H #define RTORRENT_WINDOW_BASE_H
#include <functional> #include <functional>
#include <torrent/utils/scheduler.h> #include <torrent/system/scheduler.h>
#include <torrent/system/thread.h> #include <torrent/system/thread.h>
#include "canvas.h" #include "canvas.h"
@@ -99,7 +99,7 @@ protected:
extent_type m_max_width; extent_type m_max_width;
extent_type m_max_height; 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 // Return a range with a distance of no more than __distance and
+2 -1
View File
@@ -57,7 +57,8 @@ WindowDownloadChunksSeen::redraw() {
const torrent::TransferList* transfers = m_download->download()->transfer_list(); const torrent::TransferList* transfers = m_download->download()->transfer_list();
std::vector<torrent::BlockList*> transferChunks(transfers->begin(), transfers->end()); std::vector<torrent::BlockList*> transferChunks(transfers->begin(), transfers->end());
std::sort(transferChunks.begin(), transferChunks.end()); std::sort(transferChunks.begin(), transferChunks.end(),
[](const auto& left, const auto& right) { return left->index() < right->index(); });
std::vector<torrent::BlockList*>::const_iterator itrTransfer = transferChunks.begin(); std::vector<torrent::BlockList*>::const_iterator itrTransfer = transferChunks.begin();
+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 // Return a pair of ints, representing a) the ncurses attributes and b) the ncurses color pair ID to use
std::pair<int, int> std::pair<int, int>
WindowDownloadList::get_attr_color(core::View::iterator selected) { 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; 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 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(); bool active = item->is_open() && item->is_active();
@@ -132,15 +132,15 @@ WindowDownloadList::redraw() {
ColorKind focus_color = is_focused ? RCOLOR_FOCUS : RCOLOR_LABEL; ColorKind focus_color = is_focused ? RCOLOR_FOCUS : RCOLOR_LABEL;
auto attr_color = get_attr_color(range.first); 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->print(0, pos, "%c %s", focus_char, buffer.data());
m_canvas->set_attr(2, pos++, -1, attr_color.first, attr_color.second); 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->print(0, pos, "%c %s", focus_char, buffer.data());
m_canvas->set_attr(2, pos++, -1, focus_color); 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->print(0, pos, "%c %s", focus_char, buffer.data());
m_canvas->set_attr(2, pos++, -1, focus_color); m_canvas->set_attr(2, pos++, -1, focus_color);
@@ -152,7 +152,7 @@ WindowDownloadList::redraw() {
char focus_char = range.first == m_view->focus() ? '*' : ' '; char focus_char = range.first == m_view->focus() ? '*' : ' ';
auto attr_color = get_attr_color(range.first); 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->print(0, pos, "%c %s", focus_char, buffer.data());
m_canvas->set_attr(2, pos++, -1, attr_color.first, attr_color.second); m_canvas->set_attr(2, pos++, -1, attr_color.first, attr_color.second);
+4 -1
View File
@@ -25,7 +25,10 @@ WindowFileList::WindowFileList(const ui::ElementFileList* element) :
// Convert std::string to std::wstring of given width (in screen positions), // Convert std::string to std::wstring of given width (in screen positions),
// taking into account that some characters may be occupying two screen positions. // taking into account that some characters may be occupying two screen positions.
std::wstring std::wstring
wstring_width(const std::string& i_str, [[maybe_unused]] int width) { wstring_width(const std::string& i_str, int width) {
if (width < 0)
width = 0;
std::wstring result(width + 1, L' '); std::wstring result(width + 1, L' ');
size_t length = std::mbstowcs(result.data(), i_str.c_str(), width); size_t length = std::mbstowcs(result.data(), i_str.c_str(), width);
+1 -1
View File
@@ -47,7 +47,7 @@ private:
signal_curl_get::iterator m_conn_insert; signal_curl_get::iterator m_conn_insert;
signal_curl_get::iterator m_conn_erase; signal_curl_get::iterator m_conn_erase;
torrent::utils::SchedulerEntry m_task_deactivate; torrent::system::SchedulerEntry m_task_deactivate;
}; };
} }
+20 -6
View File
@@ -1,6 +1,8 @@
#include "config.h" #include "config.h"
#include <ctime> #include <ctime>
#include <mutex>
#include <vector>
#include <torrent/torrent.h> #include <torrent/torrent.h>
#include <torrent/system/callbacks.h> #include <torrent/system/callbacks.h>
#include <torrent/system/thread.h> #include <torrent/system/thread.h>
@@ -41,13 +43,20 @@ WindowLog::redraw() {
int pos = m_canvas->height(); int pos = m_canvas->height();
for (iterator itr = m_log->end(), last = find_older(); itr != last && pos > 0; --pos) { std::vector<torrent::log_entry> entries;
itr--;
{
std::lock_guard<torrent::log_buffer> guard(*m_log);
for (iterator itr = m_log->end(), last = find_older(); itr != last && entries.size() < static_cast<size_t>(pos); )
entries.push_back(*--itr);
}
for (const auto& entry : entries) {
char buffer[16]; char buffer[16];
print_hhmmss_local(buffer, buffer + 16, static_cast<time_t>(itr->timestamp)); print_hhmmss_local(buffer, buffer + 16, static_cast<time_t>(entry.timestamp));
m_canvas->print(0, pos - 1, "(%s) %s", buffer, itr->message.c_str()); m_canvas->print(0, --pos, "(%s) %s", buffer, entry.message.c_str());
} }
} }
@@ -60,8 +69,13 @@ WindowLog::receive_update() {
if (!is_active()) if (!is_active())
return; return;
auto itr = find_older(); std::iterator_traits<iterator>::difference_type height;
auto height = std::min(std::distance(itr, (iterator)m_log->end()), (std::iterator_traits<iterator>::difference_type)10);
{
std::lock_guard<torrent::log_buffer> guard(*m_log);
height = std::min(std::distance(find_older(), (iterator)m_log->end()), (std::iterator_traits<iterator>::difference_type)10);
}
if (height != m_max_height) { if (height != m_max_height) {
m_min_height = height != 0 ? 1 : 0; m_min_height = height != 0 ? 1 : 0;
+6 -4
View File
@@ -1,8 +1,8 @@
#ifndef RTORRENT_DISPLAY_WINDOW_LOG_H #ifndef RTORRENT_DISPLAY_WINDOW_LOG_H
#define 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/log_buffer.h>
#include <torrent/utils/scheduler.h>
#include "window.h" #include "window.h"
@@ -22,10 +22,12 @@ public:
private: private:
inline iterator find_older(); inline iterator find_older();
torrent::log_buffer* m_log; torrent::log_buffer* m_log;
torrent::utils::SchedulerEntry m_task_update; torrent::system::SchedulerEntry m_task_update;
align_cacheline std::atomic<bool> m_log_updating{}; align_cacheline
std::atomic<bool> m_log_updating{};
}; };
} }
+18 -6
View File
@@ -1,6 +1,8 @@
#include "config.h" #include "config.h"
#include <ctime> #include <ctime>
#include <mutex>
#include <vector>
#include "canvas.h" #include "canvas.h"
#include "utils.h" #include "utils.h"
@@ -29,24 +31,34 @@ WindowLogComplete::redraw() {
int pos = m_canvas->height(); int pos = m_canvas->height();
for (iterator itr = m_log->end(), last = m_log->begin(); itr != last && pos > 0; ) { std::vector<torrent::log_entry> entries;
itr--;
{
std::lock_guard<torrent::log_buffer> guard(*m_log);
for (iterator itr = m_log->end(), last = m_log->begin(); itr != last && entries.size() < static_cast<size_t>(pos); )
entries.push_back(*--itr);
}
for (const auto& entry : entries) {
if (pos <= 0)
break;
char buffer[16]; char buffer[16];
// Use an arbitrary min width of 60 for allowing multiple // Use an arbitrary min width of 60 for allowing multiple
// lines. This should ensure we don't mess up the display when the // lines. This should ensure we don't mess up the display when the
// screen is shrunk too much. // screen is shrunk too much.
unsigned int timeWidth = 3 + print_hhmmss_local(buffer, buffer + 16, static_cast<time_t>(itr->timestamp)) - buffer; unsigned int timeWidth = 3 + print_hhmmss_local(buffer, buffer + 16, static_cast<time_t>(entry.timestamp)) - buffer;
unsigned int logWidth = m_canvas->width() > 60 ? (m_canvas->width() - timeWidth) : (60 - timeWidth); unsigned int logWidth = m_canvas->width() > 60 ? (m_canvas->width() - timeWidth) : (60 - timeWidth);
unsigned int logHeight = (itr->message.size() + logWidth - 1) / logWidth; unsigned int logHeight = (entry.message.size() + logWidth - 1) / logWidth;
for (unsigned int j = logHeight; j > 0 && pos > 0; --j, --pos) for (unsigned int j = logHeight; j > 0 && pos > 0; --j, --pos)
if (j == 1) if (j == 1)
m_canvas->print(0, pos - 1, "(%s) %s", buffer, itr->message.substr(0, m_canvas->width() - timeWidth).c_str()); m_canvas->print(0, pos - 1, "(%s) %s", buffer, entry.message.substr(0, m_canvas->width() - timeWidth).c_str());
else else
m_canvas->print(timeWidth, pos - 1, "%s", itr->message.substr(logWidth * (j - 1), m_canvas->width() - timeWidth).c_str()); m_canvas->print(timeWidth, pos - 1, "%s", entry.message.substr(logWidth * (j - 1), m_canvas->width() - timeWidth).c_str());
} }
} }
+30
View File
@@ -3,6 +3,7 @@
#include "globals.h" #include "globals.h"
#include <cstdlib> #include <cstdlib>
#include <stdlib.h>
#include <torrent/exceptions.h> #include <torrent/exceptions.h>
rpc::ip_table_list ip_tables; rpc::ip_table_list ip_tables;
@@ -28,3 +29,32 @@ expand_path(const std::string& path) {
return 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; extern Control* control;
std::string expand_path(const std::string& path); 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 { namespace rpc {
class SCgi; class SCgi;
-2
View File
@@ -69,8 +69,6 @@ public:
bool pressed(int key); bool pressed(int key);
void ignore(int key) { (*this)[key] = slot_void(); }
private: private:
bool m_enabled{true}; bool m_enabled{true};
}; };
-1
View File
@@ -13,7 +13,6 @@ void
InputEvent::insert() { InputEvent::insert() {
torrent::this_thread::poll()->open(this); torrent::this_thread::poll()->open(this);
torrent::this_thread::poll()->insert_read(this); torrent::this_thread::poll()->insert_read(this);
torrent::this_thread::poll()->insert_error(this);
} }
void void
+3 -4
View File
@@ -2,16 +2,15 @@
#define RTORRENT_INPUT_INPUT_EVENT_H #define RTORRENT_INPUT_INPUT_EVENT_H
#include <functional> #include <functional>
#include <torrent/system/event.h>
#include <torrent/event.h>
namespace input { namespace input {
class InputEvent : public torrent::Event { class InputEvent : public torrent::system::Event {
public: public:
typedef std::function<void (int)> slot_int; typedef std::function<void (int)> slot_int;
InputEvent(int fd) { m_fileDesc = fd; } InputEvent(int fd) { set_file_descriptor(fd); }
const char* type_name() const override { return "input"; } const char* type_name() const override { return "input"; }
+1 -1
View File
@@ -100,7 +100,7 @@ PathInput::receive_do_complete() {
entry.s_name += '/'; entry.s_name += '/';
} }
range_type r = find_incomplete(dir, str().substr(dirEnd, get_pos())); range_type r = find_incomplete(dir, str().substr(dirEnd, get_pos() - dirEnd));
if (r.first == r.second) if (r.first == r.second)
return; // Show some nice colors here. return; // Show some nice colors here.
+38 -15
View File
@@ -11,6 +11,9 @@
#include <torrent/exceptions.h> #include <torrent/exceptions.h>
#include <torrent/data/chunk_utils.h> #include <torrent/data/chunk_utils.h>
#include <torrent/net/fd.h> #include <torrent/net/fd.h>
#include <torrent/net/http_stack.h>
#include <torrent/runtime/memory_manager.h>
#include <torrent/runtime/runtime.h>
#include <torrent/utils/chrono.h> #include <torrent/utils/chrono.h>
#include <torrent/utils/log.h> #include <torrent/utils/log.h>
@@ -149,8 +152,8 @@ main(int argc, char** argv) {
SignalHandler::set_sigaction_handler(SIGBUS, &handle_sigbus); SignalHandler::set_sigaction_handler(SIGBUS, &handle_sigbus);
torrent::log_add_group_output(torrent::LOG_NOTICE, "important"); torrent::log_add_group_output(torrent::LOG_NOTICE, "important");
torrent::log_add_group_output(torrent::LOG_DHT_ERROR, "important"); torrent::log_add_group_output(torrent::LOG_DHT_ERROR, "important");
torrent::log_add_group_output(torrent::LOG_INFO, "complete"); torrent::log_add_group_output(torrent::LOG_INFO, "complete");
torrent::log_add_group_output(torrent::LOG_DHT_ERROR, "complete"); torrent::log_add_group_output(torrent::LOG_DHT_ERROR, "complete");
@@ -298,8 +301,6 @@ main(int argc, char** argv) {
"schedule = low_diskspace,5,60,((close_low_diskspace,500M))\n" "schedule = low_diskspace,5,60,((close_low_diskspace,500M))\n"
"schedule = prune_file_status,3600,86400,((system.file_status_cache.prune))\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" "ui.color.focus.set=reverse\n"
); );
@@ -343,8 +344,9 @@ main(int argc, char** argv) {
CMD_REDIRECT("directory", "directory.default.set"); CMD_REDIRECT("directory", "directory.default.set");
CMD_REDIRECT("session", "session.path.set"); CMD_REDIRECT("session", "session.path.set");
CMD_REDIRECT("scgi_port", "network.scgi.open_port"); CMD_REDIRECT_NO_EXPORT("port_range", "network.listen.port.range.set");
CMD_REDIRECT("scgi_local", "network.scgi.open_local"); 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_time", "convert.gm_time");
CMD_REDIRECT("to_gm_date", "convert.gm_date"); CMD_REDIRECT("to_gm_date", "convert.gm_date");
@@ -377,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."); 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) { // if (rpc::call_command_value("method.use_intermediate") == 1) {
// } else if (rpc::call_command_value("method.use_intermediate") == 2) { // } else if (rpc::call_command_value("method.use_intermediate") == 2) {
@@ -388,17 +396,14 @@ main(int argc, char** argv) {
CMD_REDIRECT("schedule2", "schedule"); CMD_REDIRECT("schedule2", "schedule");
CMD_REDIRECT("schedule_remove2", "schedule.remove"); CMD_REDIRECT("schedule_remove2", "schedule.remove");
// TODO: Remove file.append when cleaning these up.
CMD_REDIRECT("bind", "network.bind_address.set"); CMD_REDIRECT("bind", "network.bind_address.set");
CMD_REDIRECT("ip", "network.local_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. // TODO: Check if dht is on by default.
CMD_REDIRECT("dht", "dht.mode.set"); CMD_REDIRECT("dht", "dht.mode.set");
CMD_REDIRECT("port_random", "network.port_random.set"); CMD_REDIRECT("port_random", "network.listen.port.random.set");
CMD_REDIRECT("proxy_address", "network.proxy_address.set"); CMD_REDIRECT("proxy_address", "network.proxy.http.set");
CMD_REDIRECT("key_layout", "keys.layout.set"); CMD_REDIRECT("key_layout", "keys.layout.set");
@@ -417,6 +422,20 @@ main(int argc, char** argv) {
lt_log_print(torrent::LOG_WARN, "The 'throttle.ip' command is deprecated and does nothing."); lt_log_print(torrent::LOG_WARN, "The 'throttle.ip' command is deprecated and does nothing.");
return torrent::Object(); return torrent::Object();
}); });
CMD_ANY("network.port_open", [](auto, auto) {
lt_log_print(torrent::LOG_WARN, "The 'network.port_open' command is deprecated and does nothing.");
return torrent::Object();
});
CMD_ANY("network.port_open.set", [](auto, auto) {
lt_log_print(torrent::LOG_WARN, "The 'network.port_open.set' command is deprecated and does nothing.");
return torrent::Object();
});
CMD_REDIRECT("network.port_random", "network.listen.port.random");
CMD_REDIRECT("network.port_random.set", "network.listen.port.random.set");
CMD_REDIRECT("network.port_range", "network.listen.port.range");
CMD_REDIRECT("network.port_range.set", "network.listen.port.range.set");
} }
{ {
@@ -444,10 +463,14 @@ main(int argc, char** argv) {
}); });
LT_LOG("seeded srandom and srand48 (seed:%u)", random_seed); LT_LOG("seeded srandom and srand48 (seed:%u)", random_seed);
LT_LOG("max memory usage: %" PRIu64, torrent::runtime::memory_manager()->max_memory_usage());
control->initialize(); control->initialize();
control->ui()->load_input_history(); control->ui()->load_input_history();
torrent::net_thread::http_stack()->set_user_agent(USER_AGENT);
torrent::runtime::initialize_network();
// Load session torrents and perform scheduled tasks to ensure session torrents are loaded // Load session torrents and perform scheduled tasks to ensure session torrents are loaded
// before arg torrents. // before arg torrents.
control->dht_manager()->set_auto_if_untouched_and_has_session(); control->dht_manager()->set_auto_if_untouched_and_has_session();
@@ -469,7 +492,8 @@ main(int argc, char** argv) {
control->cleanup(); control->cleanup();
} catch (torrent::internal_error& e) { } catch (torrent::internal_error& e) {
control->cleanup_exception(); if (control != nullptr)
control->cleanup_exception();
std::cout << "rtorrent: caught torrent::internal_error: " std::cout << "rtorrent: caught torrent::internal_error: "
<< e.what() << std::endl << e.what() << std::endl
@@ -482,7 +506,8 @@ main(int argc, char** argv) {
return -1; return -1;
} catch (std::exception& e) { } catch (std::exception& e) {
control->cleanup_exception(); if (control != nullptr)
control->cleanup_exception();
std::cout << "rtorrent: caught" << typeid(e).name() << " : " << e.what() << std::endl; std::cout << "rtorrent: caught" << typeid(e).name() << " : " << e.what() << std::endl;
@@ -527,8 +552,6 @@ handle_sigbus(int signum, siginfo_t* sa, [[maybe_unused]] void* ptr) {
#else #else
output << "Stack dump not enabled." << std::endl; output << "Stack dump not enabled." << std::endl;
#endif #endif
output << std::endl << "Error: " << std::strerror(sa->si_errno) << std::endl;
const char* signal_reason; const char* signal_reason;
switch (sa->si_code) { switch (sa->si_code) {
+9 -1
View File
@@ -1,5 +1,7 @@
#include "config.h" #include "config.h"
#include <limits>
#include "core/download.h" #include "core/download.h"
#include "parse.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, 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 template <typename T> const torrent::Object
+15 -16
View File
@@ -55,12 +55,11 @@ struct rt_triple : private std::pair<T1, T2> {
base_type(src.first, src.second), third(src.third) {} base_type(src.first, src.second), third(src.third) {}
}; };
typedef rt_triple<int, void*, void*> target_type;
class command_base; class command_base;
typedef const torrent::Object (*command_base_call_type)(command_base*, target_type, const torrent::Object&); using target_type = rt_triple<int, void*, void*>;
typedef std::function<torrent::Object (target_type, const torrent::Object&)> base_function; using base_function = std::function<torrent::Object (target_type, const torrent::Object&)>;
using command_base_call_type = const torrent::Object (command_base*, target_type, const torrent::Object&);
template <typename tmpl> struct command_base_is_valid {}; template <typename tmpl> struct command_base_is_valid {};
template <command_base_call_type tmpl_func> struct command_base_is_type {}; template <command_base_call_type tmpl_func> struct command_base_is_type {};
@@ -84,17 +83,18 @@ public:
typedef const torrent::Object (*download_pair_slot) (command_base*, core::Download*, core::Download*, const torrent::Object&); typedef const torrent::Object (*download_pair_slot) (command_base*, core::Download*, core::Download*, const torrent::Object&);
static const int target_generic = 0; static constexpr int target_generic = 0;
static const int target_any = 1; static constexpr int target_any = 1;
static const int target_download = 2; static constexpr int target_download = 2;
static const int target_peer = 3; static constexpr int target_peer = 3;
static const int target_tracker = 4; static constexpr int target_tracker = 4;
static const int target_file = 5; static constexpr int target_file = 5;
static const int target_file_itr = 6; static constexpr int target_file_itr = 6;
static constexpr int target_download_pair = 7;
static const int target_download_pair = 7; static constexpr unsigned int max_arguments = 10;
static const unsigned int max_arguments = 10; static constexpr std::size_t optimal_alignment = std::max(alignof(std::max_align_t), alignof(base_function));
struct stack_type { struct stack_type {
torrent::Object* begin() { return reinterpret_cast<torrent::Object*>(buffer); } torrent::Object* begin() { return reinterpret_cast<torrent::Object*>(buffer); }
@@ -169,8 +169,7 @@ public:
template <typename T> template <typename T>
void set_function(T s, [[maybe_unused]] int value = command_base_is_valid<T>::value) { void set_function(T s, [[maybe_unused]] int value = command_base_is_valid<T>::value) {
static_assert(sizeof(T) <= sizeof(t_pod), "t_pod storage overflow"); static_assert(sizeof(T) <= sizeof(t_pod), "t_pod storage overflow");
static_assert(alignof(std::max_align_t) % alignof(T) == 0, "t_pod alignment insufficient for type"); static_assert(optimal_alignment >= alignof(T), "t_pod alignment insufficient for type");
static_assert(alignof(std::max_align_t) >= alignof(T), "t_pod structural capacity mismatch");
if (m_dest_helper) if (m_dest_helper)
m_dest_helper(t_pod); m_dest_helper(t_pod);
@@ -200,7 +199,7 @@ protected:
using copy_fn_t = void (*)(void* dest, const void* src); using copy_fn_t = void (*)(void* dest, const void* src);
using dest_fn_t = void (*)(void* ptr); using dest_fn_t = void (*)(void* ptr);
alignas(std::max_align_t) char t_pod[sizeof(base_function)]; alignas(optimal_alignment) char t_pod[sizeof(base_function)];
copy_fn_t m_copy_helper; copy_fn_t m_copy_helper;
dest_fn_t m_dest_helper; dest_fn_t m_dest_helper;
+5
View File
@@ -1,6 +1,7 @@
#ifndef RTORRENT_RPC_COMMAND_MAP_H #ifndef RTORRENT_RPC_COMMAND_MAP_H
#define RTORRENT_RPC_COMMAND_MAP_H #define RTORRENT_RPC_COMMAND_MAP_H
#include <memory>
#include <map> #include <map>
#include <string> #include <string>
#include <cstring> #include <cstring>
@@ -104,6 +105,10 @@ inline target_type make_target(T target) {
return target_type((int)target_type_id<T>::value, 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> template <typename T>
inline target_type make_target_pair(T target1, T target2) { inline target_type make_target_pair(T target1, T target2) {
return target_type((int)target_type_id<T, T>::value, target1, target2); return target_type((int)target_type_id<T, T>::value, target1, target2);
+7 -1
View File
@@ -61,14 +61,20 @@ CommandScheduler::call_item(value_type item) {
// Remove the item before calling the command if it should be // Remove the item before calling the command if it should be
// removed. // removed.
std::string key = item->key();
try { try {
rpc::call_object(item->command()); rpc::call_object(item->command());
} catch (torrent::input_error& e) { } catch (torrent::input_error& e) {
if (m_slotErrorMessage) if (m_slotErrorMessage)
m_slotErrorMessage("Scheduled command failed: " + item->key() + ": " + e.what()); m_slotErrorMessage("Scheduled command failed: " + key + ": " + e.what());
} }
// The command is allowed to erase or replace this item, which deletes it.
if (std::find(begin(), end(), item) == end())
return;
// Still schedule if we caught a torrrent::input_error? // Still schedule if we caught a torrrent::input_error?
auto next = item->next_time_scheduled(); auto next = item->next_time_scheduled();
+2 -2
View File
@@ -5,7 +5,7 @@
#include <functional> #include <functional>
#include <torrent/object.h> #include <torrent/object.h>
#include <torrent/utils/scheduler.h> #include <torrent/system/scheduler.h>
namespace rpc { namespace rpc {
@@ -43,7 +43,7 @@ private:
uint32_t m_interval{}; uint32_t m_interval{};
std::chrono::microseconds m_time_scheduled; std::chrono::microseconds m_time_scheduled;
torrent::utils::SchedulerEntry m_task; torrent::system::SchedulerEntry m_task;
// Flags for various things. // Flags for various things.
}; };
+51 -149
View File
@@ -1,22 +1,24 @@
#include "config.h" #include "config.h"
#include <cerrno> #include <memory>
#include <cstring>
#include <fcntl.h> #include "rpc/exec_file.h"
#include <spawn.h>
#include <string> // #include <cassert>
// #include <cerrno>
// #include <cstring>
// #include <fcntl.h>
// #include <spawn.h>
// #include <string>
#include <unistd.h> #include <unistd.h>
#include <sys/types.h> // #include <sys/types.h>
#include <sys/wait.h> #include <sys/uio.h>
#include <torrent/net/fd.h> // #include <torrent/net/fd.h>
#include <torrent/system/system.h>
#include <torrent/system/thread.h> #include <torrent/system/thread.h>
#include <torrent/system/spawn_process.h>
#include <torrent/system/types.h>
#include "exec_file.h" #include "rpc/parse.h"
#include "parse.h"
// Standard POSIX environment pointer
extern char** environ;
namespace rpc { namespace rpc {
@@ -24,157 +26,56 @@ namespace rpc {
int int
ExecFile::execute(const char* file, char* const* argv, int flags) { ExecFile::execute(const char* file, char* const* argv, int flags) {
// Write the executed command and its parameters to the log fd. torrent::system::SpawnProcess spawn_process;
[[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) { if (m_log_fd != -1) {
for (char* const* itr = argv; *itr != NULL; itr++) { std::vector<struct iovec> iovecs;
if (itr == argv) iovecs.reserve(32);
result = write(m_log_fd, "\n---\n", sizeof("\n---\n")); iovecs.push_back({const_cast<char*>("\n---\n"), 5});
else
result = write(m_log_fd, " ", 1);
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{}; int spawn_status = spawn_process.execute(file, argv);
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);
if (spawn_status != 0) { if (spawn_status != 0) {
if (pipe_0 != -1) if (m_log_fd != -1) {
torrent::fd_close(pipe_0); auto prefix = "\n--- posix_spawn failed: ";
auto errno_str = torrent::system::errno_enum_str(spawn_status) + " ---\n";
if (pipe_1 != -1) struct iovec iovecs[2] = {
torrent::fd_close(pipe_1); {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)); 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 (flags & flag_background) {
if (m_log_fd != -1) m_waitpid_queue.close_pid(spawn_process.child_pid());
result = write(m_log_fd, "\n--- Running in Background ---\n", sizeof("\n--- Running in Background ---\n"));
return 0; return 0;
} }
int status; if (flags & flag_capture)
m_capture = spawn_process.capture_child_output();
while (::waitpid(child_pid, &status, 0) == -1) { return spawn_process.wait_for_child();
switch (errno) {
case EINTR:
continue;
case ECHILD:
throw torrent::internal_error("ExecFile::execute(...) waitpid failed with ECHILD, child process not found.");
case EINVAL:
throw torrent::internal_error("ExecFile::execute(...) waitpid failed with EINVAL.");
default:
throw torrent::internal_error("ExecFile::execute(...) waitpid failed with unexpected error: " + std::string(std::strerror(errno)));
}
};
// Check return value?
if (m_log_fd != -1) {
if (WIFEXITED(status) && WEXITSTATUS(status) == 0)
result = write(m_log_fd, "\n--- Success ---\n", sizeof("\n--- Success ---\n"));
else
result = write(m_log_fd, "\n--- Error ---\n", sizeof("\n--- Error ---\n"));
}
return status;
} }
torrent::Object torrent::Object
@@ -182,8 +83,9 @@ ExecFile::execute_object(const torrent::Object& rawArgs, int flags) {
char* argsBuffer[max_args]; char* argsBuffer[max_args];
char** argsCurrent = argsBuffer; char** argsCurrent = argsBuffer;
// Size of value strings are less than 24. // On the heap: a buffer of buffer_size does not belong on the stack of whichever thread runs the command.
char valueBuffer[buffer_size+1]; auto valueStorage = std::make_unique<char[]>(buffer_size + 1);
char* valueBuffer = valueStorage.get();
char* valueCurrent = valueBuffer; char* valueCurrent = valueBuffer;
if (rawArgs.is_list()) { if (rawArgs.is_list()) {
@@ -234,4 +136,4 @@ ExecFile::execute_object(const torrent::Object& rawArgs, int flags) {
return torrent::Object((int64_t)status); return torrent::Object((int64_t)status);
} }
} } // namespace rpc
+5 -1
View File
@@ -3,12 +3,14 @@
#include <torrent/object.h> #include <torrent/object.h>
#include "utils/waitpid_queue.h"
namespace rpc { namespace rpc {
class ExecFile { class ExecFile {
public: public:
static constexpr unsigned int max_args = 128; static constexpr unsigned int max_args = 128;
static constexpr unsigned int buffer_size = 4096; static constexpr unsigned int buffer_size = 128 * 1024;
static constexpr int flag_expand_tilde = 0x1; static constexpr int flag_expand_tilde = 0x1;
static constexpr int flag_throw = 0x2; static constexpr int flag_throw = 0x2;
@@ -24,6 +26,8 @@ public:
private: private:
int m_log_fd{-1}; int m_log_fd{-1};
std::string m_capture; std::string m_capture;
utils::WaitpidQueue m_waitpid_queue;
}; };
} }
+61 -10
View File
@@ -2,8 +2,10 @@
#include "rpc/jsonrpc.h" #include "rpc/jsonrpc.h"
#include <cstddef>
#include <cstdint> #include <cstdint>
#include <string> #include <string>
#include <utility>
#include <torrent/common.h> #include <torrent/common.h>
#include <torrent/torrent.h> #include <torrent/torrent.h>
#include <torrent/utils/string_manip.h> #include <torrent/utils/string_manip.h>
@@ -13,6 +15,7 @@
#include "rpc/command_map.h" #include "rpc/command_map.h"
#include "rpc/nlohmann/json.h" #include "rpc/nlohmann/json.h"
#include "rpc/parse_commands.h" #include "rpc/parse_commands.h"
#include "rpc/scgi_task.h"
#include "torrent/exceptions.h" #include "torrent/exceptions.h"
#include "torrent/object.h" #include "torrent/object.h"
#include "utils/functional.h" #include "utils/functional.h"
@@ -26,10 +29,12 @@ constexpr int JSONRPC_METHOD_NOT_FOUND_ERROR = -32601;
constexpr int JSONRPC_INVALID_PARAMS_ERROR = -32602; constexpr int JSONRPC_INVALID_PARAMS_ERROR = -32602;
constexpr int JSONRPC_INTERNAL_ERROR = -32000; constexpr int JSONRPC_INTERNAL_ERROR = -32000;
constexpr uint32_t max_json_depth = 1024;
using json = nlohmann::json; using json = nlohmann::json;
torrent::Object torrent::Object
json_to_object(const json& value) { json_to_object(const json& value, uint32_t depth = 0) {
switch (value.type()) { switch (value.type()) {
case json::value_t::number_unsigned: case json::value_t::number_unsigned:
case json::value_t::number_integer: case json::value_t::number_integer:
@@ -42,20 +47,26 @@ json_to_object(const json& value) {
return torrent::Object(value.get<std::string>()); return torrent::Object(value.get<std::string>());
case json::value_t::array: { 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_raw = torrent::Object::create_list();
auto& array = array_raw.as_list(); auto& array = array_raw.as_list();
for (const auto& entry : value) for (const auto& entry : value)
array.push_back(json_to_object(entry)); array.push_back(json_to_object(entry, depth));
return array_raw; return array_raw;
} }
case json::value_t::object: { 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_raw = torrent::Object::create_map();
auto& map = map_raw.as_map(); auto& map = map_raw.as_map();
for (const auto& entry : value.items()) 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; return map_raw;
} }
@@ -138,7 +149,7 @@ jsonrpc_call_command(const std::string& method, const json& params) {
CommandMap::iterator itr = commands.find(method.c_str()); 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); throw rpc_error(JSONRPC_METHOD_NOT_FOUND_ERROR, "method not found: " + method);
} }
@@ -214,13 +225,49 @@ handle_notification(const json& request) noexcept {
} }
} }
namespace {
using json_input_adapter = decltype(nlohmann::detail::input_adapter(std::declval<const char*>(), std::declval<const char*>()));
using json_dom_parser = nlohmann::detail::json_sax_dom_parser<json, json_input_adapter>;
class json_depth_limited_parser : public json_dom_parser {
public:
explicit json_depth_limited_parser(json& root) : json_dom_parser(root) {}
bool start_object(std::size_t length) { return enter() && json_dom_parser::start_object(length); }
bool start_array(std::size_t length) { return enter() && json_dom_parser::start_array(length); }
bool end_object() { m_depth--; return json_dom_parser::end_object(); }
bool end_array() { m_depth--; return json_dom_parser::end_array(); }
private:
bool enter() { return ++m_depth <= max_json_depth; }
uint32_t m_depth{0};
};
} // namespace
bool bool
JsonRpc::process(const char* in_buffer, uint32_t length, slot_write callback) { JsonRpc::process(const char* in_buffer, uint32_t length, slot_write callback) {
json response; json response;
json body; json body;
if (length > m_size_limit) {
auto err_str = json_error(JSONRPC_INVALID_REQUEST_ERROR, "content size exceeds maximum RPC limit", nullptr).dump();
return callback(err_str.c_str(), err_str.size());
}
try { try {
body = json::parse(in_buffer, in_buffer + length); json_depth_limited_parser handler(body);
if (!json::sax_parse(in_buffer, in_buffer + length, &handler)) {
auto err_str = json_error(JSONRPC_INVALID_REQUEST_ERROR, "maximum nesting depth exceeded", nullptr).dump();
return callback(err_str.c_str(), err_str.size());
}
switch (body.type()) { switch (body.type()) {
case json::value_t::object: { case json::value_t::object: {
if (!body.contains("id")) { if (!body.contains("id")) {
@@ -256,13 +303,17 @@ JsonRpc::process(const char* in_buffer, uint32_t length, slot_write callback) {
std::string response_str = response.dump(); std::string response_str = response.dump();
if (response_str.size() > SCgiTask::max_response_size) {
const auto& id = response.is_object() && response.contains("id") ? response["id"] : json(nullptr);
auto err_str = json_error(JSONRPC_INTERNAL_ERROR, "response size exceeds maximum RPC limit", id).dump();
return callback(err_str.c_str(), err_str.size());
}
return callback(response_str.c_str(), response_str.size()); return callback(response_str.c_str(), response_str.size());
} catch (json::parse_error& e) { } catch (json::exception& e) {
auto err_str = json_error(JSONRPC_PARSE_ERROR, e.what(), nullptr).dump(-1, ' ', false, json::error_handler_t::replace); // Exception strings may contain invalid UTF-8, hence the ::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
auto err_str = json_error(JSONRPC_PARSE_ERROR, e.what(), nullptr).dump(-1, ' ', false, json::error_handler_t::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()); return callback(err_str.c_str(), err_str.size());
} }
+7
View File
@@ -5,6 +5,8 @@
#include <cstdint> #include <cstdint>
#include "rpc/scgi_task.h"
namespace rpc { namespace rpc {
class JsonRpc { class JsonRpc {
@@ -17,6 +19,11 @@ public:
bool process(const char* in_buffer, uint32_t length, slot_write callback); bool process(const char* in_buffer, uint32_t length, slot_write callback);
void insert_command(const char* name, const char* parm, const char* doc) {}; void insert_command(const char* name, const char* parm, const char* doc) {};
void set_size_limit(uint64_t size) { m_size_limit = size; }
private:
uint64_t m_size_limit{SCgiTask::max_content_size};
}; };
} // namespace rpc } // namespace rpc
+4
View File
@@ -394,6 +394,10 @@ execute_lua(LuaEngine* engine, rpc::target_type target_type, torrent::Object con
switch (raw_args.type()) { switch (raw_args.type()) {
case torrent::Object::TYPE_LIST: { case torrent::Object::TYPE_LIST: {
const torrent::Object::list_type& args = raw_args.as_list(); const torrent::Object::list_type& args = raw_args.as_list();
if (args.empty())
throw torrent::input_error("Too few arguments.");
if (flags & LuaEngine::flag_string) { if (flags & LuaEngine::flag_string) {
check_lua_status(l_state, luaL_loadstring(l_state, args.begin()->as_string().c_str())); check_lua_status(l_state, luaL_loadstring(l_state, args.begin()->as_string().c_str()));
} else { } else {
+7 -6
View File
@@ -26,10 +26,7 @@ const size_t object_storage::key_size;
object_storage::iterator object_storage::iterator
object_storage::find_raw_string(const torrent::raw_string& key) { object_storage::find_raw_string(const torrent::raw_string& key) {
fixed_key_type<64> k; return find(key_type::from_raw_string(key));
k.set_c_str(key.data());
return find(k);
} }
object_storage::iterator object_storage::iterator
@@ -64,8 +61,12 @@ object_storage::insert(const char* key_data, uint32_t key_size, const torrent::O
if (std::find(key_data, key_data + key_size, '\0') != key_data + key_size) if (std::find(key_data, key_data + key_size, '\0') != key_data + key_size)
throw torrent::input_error("Found nul-char in string."); throw torrent::input_error("Found nul-char in string.");
// Check for size > key_size. // key_type turns a key this long into the empty key.
// Check for empty string. if (key_size >= object_storage::key_size)
throw torrent::input_error("Key is too long.");
if (key_size == 0)
throw torrent::input_error("Key is empty.");
bool use_raw = false; bool use_raw = false;
torrent::Object object; torrent::Object object;
+1 -8
View File
@@ -158,14 +158,7 @@ fixed_key_type<MaxSize>::set_data(const value_type* src_data, size_type src_size
template <size_t MaxSize> inline void template <size_t MaxSize> inline void
fixed_key_type<MaxSize>::set_c_str(const value_type* src_data) { fixed_key_type<MaxSize>::set_c_str(const value_type* src_data) {
value_type* itr = m_data; set_c_str(src_data, std::strlen(src_data));
const value_type* last = m_data + max_size;
while (itr != last && *src_data != '\0')
*itr++ = *src_data++;
*itr = '\0';
m_size = std::distance(m_data, itr);
} }
template <size_t MaxSize> inline void template <size_t MaxSize> inline void
+76 -27
View File
@@ -1,7 +1,10 @@
#include "config.h" #include "config.h"
#include <cctype>
#include <charconv>
#include <cstring> #include <cstring>
#include <cstdio> #include <cstdio>
#include <limits>
#include <locale> #include <locale>
#include <torrent/exceptions.h> #include <torrent/exceptions.h>
@@ -10,6 +13,8 @@
namespace rpc { namespace rpc {
constexpr uint32_t max_parse_depth = 1024;
const char* const char*
parse_skip_wspace(const char* first, const char* last) { parse_skip_wspace(const char* first, const char* last) {
while (first != last && parse_is_space(*first)) while (first != last && parse_is_space(*first))
@@ -99,21 +104,56 @@ parse_whole_value_nothrow(const char* src, int64_t* value, int base, int unit) {
return true; 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* const char*
parse_value_nothrow(const char* src, int64_t* value, int base, int unit) { parse_value_nothrow(const char* src, int64_t* value, int base, int unit) {
if (unit <= 0) if (unit <= 0)
throw torrent::input_error("Command::string_to_value_unit(...) received unit <= 0."); throw torrent::input_error("Command::string_to_value_unit(...) received unit <= 0.");
char* last; if (base != 0 && base != 8 && base != 10 && base != 16)
throw torrent::input_error("Command::string_to_value_unit(...) received invalid base.");
const char* first = src;
while (parse_is_space(*src))
src++;
if (src[0] == '+')
return first;
if (src[0] == '-') {
if (base == 8 || base == 16)
return first;
if (src[1] == '0')
return first;
}
if (base == 10 && src[0] == '0' && (src[1] >= '0' && src[1] <= '9'))
return first;
char* last{};
errno = 0;
*value = strtoll(src, &last, base); *value = strtoll(src, &last, base);
if (errno == ERANGE)
return first;
if (last == src) { if (last == src) {
if (strcasecmp(src, "no") == 0) { *value = 0; return src + strlen("no"); } *value = 0;
if (strcasecmp(src, "yes") == 0) { *value = 1; return src + strlen("yes"); }
if (strcasecmp(src, "true") == 0) { *value = 1; return src + strlen("true"); } if (strcasecmp(src, "no") == 0) { *value = 0; return src + strlen("no"); }
if (strcasecmp(src, "yes") == 0) { *value = 1; return src + strlen("yes"); }
if (strcasecmp(src, "true") == 0) { *value = 1; return src + strlen("true"); }
if (strcasecmp(src, "false") == 0) { *value = 0; return src + strlen("false"); } if (strcasecmp(src, "false") == 0) { *value = 0; return src + strlen("false"); }
return src; return first;
} }
switch (*last) { switch (*last) {
@@ -121,20 +161,26 @@ parse_value_nothrow(const char* src, int64_t* value, int base, int unit) {
case 'B': ++last; break; case 'B': ++last; break;
case 'k': case 'k':
case 'K': case 'K':
if (*value > (int64_t)0x1FFFFFFFFFFFFF) return src; // overflow guard if (!value_fits_shifted(*value, 10)) return first; // overflow guard
*value = *value << 10; ++last; break; *value = *value << 10; ++last; break;
case 'm': case 'm':
case 'M': case 'M':
if (*value > (int64_t)0x7FFFFFFFFFF) return src; // overflow guard if (!value_fits_shifted(*value, 20)) return first; // overflow guard
*value = *value << 20; ++last; break; *value = *value << 20; ++last; break;
case 'g': case 'g':
case 'G': case 'G':
if (*value > (int64_t)0x1FFFFFFFF) return src; // overflow guard if (!value_fits_shifted(*value, 30)) return first; // overflow guard
*value = *value << 30; ++last; break; *value = *value << 30; ++last; break;
// case ' ': // case ' ':
// case '\0': *value = *value * unit; break; // case '\0': *value = *value * unit; break;
// default: throw torrent::input_error("Could not parse value."); // 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 first; // overflow guard
*value = *value * unit;
break;
} }
return last; return last;
@@ -142,10 +188,13 @@ parse_value_nothrow(const char* src, int64_t* value, int base, int unit) {
// Somewhat ugly... // Somewhat ugly...
const char* 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 (*first == '{') { if (++depth >= max_parse_depth)
throw torrent::input_error("Max parse depth reached.");
if (first != last && *first == '{') {
*dest = torrent::Object::create_list(); *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); first = parse_skip_wspace(first, last);
if (first == last || *first != '}') if (first == last || *first != '}')
@@ -153,19 +202,19 @@ parse_object(const char* first, const char* last, torrent::Object* dest, bool (*
return ++first; return ++first;
} else if (*first == '(') { } else if (first != last && *first == '(') {
int32_t depth = 1; int32_t parentheses = 1;
while (first + 1 != last && *(first + 1) == '(') { while (first + 1 != last && *(first + 1) == '(') {
first++; first++;
depth++; parentheses++;
} }
if (depth > 3) if (parentheses > 3)
throw torrent::input_error("Max 3 parentheses per object allowed."); throw torrent::input_error("Max 3 parentheses per object allowed.");
*dest = torrent::Object::create_dict_key(); *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_string(first + 1, last, &dest->as_dict_key(), &parse_is_delim_func);
first = parse_skip_wspace(first, last); first = parse_skip_wspace(first, last);
@@ -176,16 +225,16 @@ parse_object(const char* first, const char* last, torrent::Object* dest, bool (*
if (*first == ',') { if (*first == ',') {
// This will always create a list even for single argument functions... // This will always create a list even for single argument functions...
dest->as_dict_obj() = torrent::Object::create_list(); 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); first = parse_skip_wspace(first, last);
} }
while (depth != 0 && first != last && *first == ')') { while (parentheses != 0 && first != last && *first == ')') {
first++; first++;
depth--; parentheses--;
} }
if (depth != 0) if (parentheses != 0)
throw torrent::input_error("Parentheses mismatch."); throw torrent::input_error("Parentheses mismatch.");
return first; return first;
@@ -198,7 +247,7 @@ parse_object(const char* first, const char* last, torrent::Object* dest, bool (*
} }
const char* 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()) if (!dest->is_list())
throw torrent::internal_error("parse_list(...) !dest->is_list()."); throw torrent::internal_error("parse_list(...) !dest->is_list().");
@@ -206,7 +255,7 @@ parse_list(const char* first, const char* last, torrent::Object* dest, bool (*de
torrent::Object tmp; torrent::Object tmp;
first = parse_skip_wspace(first, last); 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); first = parse_skip_wspace(first, last);
dest->as_list().push_back(tmp); dest->as_list().push_back(tmp);
@@ -221,9 +270,9 @@ parse_list(const char* first, const char* last, torrent::Object* dest, bool (*de
} }
const char* 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_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); first = parse_skip_wspace(first, last);
if (first != last && parse_is_seperator(*first)) { if (first != last && parse_is_seperator(*first)) {
@@ -231,7 +280,7 @@ parse_whole_list(const char* first, const char* last, torrent::Object* dest, boo
tmp.swap(*dest); tmp.swap(*dest);
dest->as_list().push_back(tmp); dest->as_list().push_back(tmp);
first = parse_list(++first, last, dest, delim); first = parse_list(++first, last, dest, delim, depth);
} }
return first; return first;
@@ -375,7 +424,7 @@ convert_to_value_nothrow(const torrent::Object& src, int64_t* value, int base, i
== unpacked.as_string().c_str() + unpacked.as_string().size(); == unpacked.as_string().c_str() + unpacked.as_string().size();
case torrent::Object::TYPE_RAW_STRING: { case torrent::Object::TYPE_RAW_STRING: {
const torrent::raw_string& str = src.as_raw_string(); const torrent::raw_string& str = unpacked.as_raw_string();
auto buffer = std::make_unique<char[]>(str.size() + 1); auto buffer = std::make_unique<char[]>(str.size() + 1);
std::memcpy(buffer.get(), str.data(), str.size()); std::memcpy(buffer.get(), str.data(), str.size());
+3 -37
View File
@@ -1,37 +1,3 @@
// rTorrent - BitTorrent client
// Copyright (C) 2005-2011, Jari Sundell
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// In addition, as a special exception, the copyright holders give
// permission to link the code of portions of this program with the
// OpenSSL library under certain conditions as described in each
// individual source file, and distribute linked combinations
// including the two.
//
// You must obey the GNU General Public License in all respects for
// all of the code used other than OpenSSL. If you modify file(s)
// with this exception, you may extend this exception to your version
// of the file(s), but you are not obligated to do so. If you do not
// wish to do so, delete this exception statement from your version.
// If you delete this exception statement from all source files in the
// program, then also delete it here.
//
// Contact: Jari Sundell <sundell.software@gmail.com>
#ifndef RTORRENT_RPC_PARSE_H #ifndef RTORRENT_RPC_PARSE_H
#define RTORRENT_RPC_PARSE_H #define RTORRENT_RPC_PARSE_H
@@ -77,9 +43,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); 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); 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_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); 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); 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); std::string convert_to_string(const torrent::Object& src);
+5
View File
@@ -181,6 +181,11 @@ parse_command_file(const std::string& path) {
getCount = 0; getCount = 0;
} }
if (file.fail() && !file.eof()) {
lineNumber++;
throw torrent::input_error("Exceeded max line length.");
}
} catch (torrent::input_error& e) { } catch (torrent::input_error& e) {
snprintf(buffer, 2048, "Error in option file: %s:%u: %s", path.c_str(), lineNumber, e.what()); snprintf(buffer, 2048, "Error in option file: %s:%u: %s", path.c_str(), lineNumber, e.what());
+15 -34
View File
@@ -113,25 +113,20 @@ bool
RpcManager::process(RPCType type, const char* in_buffer, uint32_t length, slot_response_callback callback) { RpcManager::process(RPCType type, const char* in_buffer, uint32_t length, slot_response_callback callback) {
switch (type) { switch (type) {
case RPCType::XML: case RPCType::XML:
// TODO: 'network.rpc.use_xmlrpc' should be a bool in RpcManager, not a command variable. if (!m_xmlrpc.is_valid() || !m_use_xmlrpc) {
if (m_xmlrpc.is_valid() && rpc::call_command_value("network.rpc.use_xmlrpc")) {
return m_xmlrpc.process(in_buffer, length, callback);
} else {
const std::string response = "<?xml version=\"1.0\"?><methodResponse><fault><value><struct><member><name>faultCode</name><value><i8>-501</i8></value></member><member><name>faultString</name><value><string>XML-RPC not supported</string></value></member></struct></value></fault></methodResponse>"; const std::string response = "<?xml version=\"1.0\"?><methodResponse><fault><value><struct><member><name>faultCode</name><value><i8>-501</i8></value></member><member><name>faultString</name><value><string>XML-RPC not supported</string></value></member></struct></value></fault></methodResponse>";
return callback(response.c_str(), response.size()); return callback(response.c_str(), response.size());
} }
break;
return m_xmlrpc.process(in_buffer, length, callback);
case RPCType::JSON: case RPCType::JSON:
if (rpc::call_command_value("network.rpc.use_jsonrpc")) { if (!m_use_jsonrpc) {
return m_jsonrpc.process(in_buffer, length, callback);
} else {
const std::string response = "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32601,\"message\":\"JSON-RPC not supported\"},\"id\":null}"; const std::string response = "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32601,\"message\":\"JSON-RPC not supported\"},\"id\":null}";
return callback(response.c_str(), response.size()); return callback(response.c_str(), response.size());
} }
break;
return m_jsonrpc.process(in_buffer, length, callback);
default: default:
throw torrent::input_error("invalid parameters: unknown RPC type"); throw torrent::input_error("invalid parameters: unknown RPC type");
@@ -172,30 +167,16 @@ RpcManager::cleanup() {
m_jsonrpc.cleanup(); m_jsonrpc.cleanup();
} }
bool
RpcManager::is_type_enabled(RPCType type) const {
switch (type) {
case RPCType::XML:
return m_is_xmlrpc_enabled;
case RPCType::JSON:
return m_is_jsonrpc_enabled;
default:
throw torrent::input_error("invalid parameters: unknown RPC type");
}
}
void void
RpcManager::set_type_enabled(RPCType type, bool enabled) { RpcManager::set_size_limit(uint64_t size) {
switch (type) { if (size > SCgiTask::max_content_size)
case RPCType::XML: throw torrent::input_error("XMLRPC size limit cannot exceed the SCGI content size limit.");
m_is_xmlrpc_enabled = enabled;
break; if (size < min_size_limit)
case RPCType::JSON: throw torrent::input_error("XMLRPC size limit is too small to hold a request.");
m_is_jsonrpc_enabled = enabled;
break; m_xmlrpc.set_size_limit(size);
default: m_jsonrpc.set_size_limit(size);
throw torrent::input_error("invalid parameters: unknown RPC type");
}
} }
void void
+18 -5
View File
@@ -53,6 +53,10 @@ public:
enum RPCType { XML, enum RPCType { XML,
JSON }; JSON };
// A limit below the shortest useful request rejects every request, the one
// that would raise it again included.
static constexpr uint64_t min_size_limit = 1024;
RpcManager() = default; RpcManager() = default;
~RpcManager() = default; ~RpcManager() = default;
@@ -62,13 +66,16 @@ public:
void cleanup(); void cleanup();
int64_t size_limit() { return m_xmlrpc.size_limit(); }; 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(); } int dialect() { return m_xmlrpc.dialect(); }
void set_dialect(int dialect) { m_xmlrpc.set_dialect(dialect); } void set_dialect(int dialect) { m_xmlrpc.set_dialect(dialect); }
bool is_type_enabled(RPCType type) const; bool use_xmlrpc() const;
void set_type_enabled(RPCType type, bool enabled); void set_use_xmlrpc(bool v);
bool use_jsonrpc() const;
void set_use_jsonrpc(bool v);
bool process(RPCType type, const char* in_buffer, uint32_t length, slot_response_callback callback); bool process(RPCType type, const char* in_buffer, uint32_t length, slot_response_callback callback);
bool process_untrusted(RPCType type, const char* in_buffer, uint32_t length, slot_response_callback callback); bool process_untrusted(RPCType type, const char* in_buffer, uint32_t length, slot_response_callback callback);
@@ -102,8 +109,9 @@ private:
JsonRpc m_jsonrpc; JsonRpc m_jsonrpc;
bool m_handlers_initialized{}; bool m_handlers_initialized{};
bool m_is_jsonrpc_enabled{true};
bool m_is_xmlrpc_enabled{true}; bool m_use_jsonrpc{true};
bool m_use_xmlrpc{true};
std::atomic<bool> m_scgi_allow_compression{true}; std::atomic<bool> m_scgi_allow_compression{true};
std::atomic<unsigned int> m_scgi_min_compress_size{1000}; std::atomic<unsigned int> m_scgi_min_compress_size{1000};
@@ -116,6 +124,11 @@ private:
extern RpcManager rpc; extern RpcManager rpc;
inline bool RpcManager::use_xmlrpc() const { return m_use_xmlrpc; }
inline void RpcManager::set_use_xmlrpc(bool v) { m_use_xmlrpc = v; }
inline bool RpcManager::use_jsonrpc() const { return m_use_jsonrpc; }
inline void RpcManager::set_use_jsonrpc(bool v) { m_use_jsonrpc = v; }
} // namespace rpc } // namespace rpc
#endif #endif
-1
View File
@@ -111,7 +111,6 @@ SCgi::activate() {
torrent::this_thread::poll()->open(this); torrent::this_thread::poll()->open(this);
torrent::this_thread::poll()->insert_read(this); torrent::this_thread::poll()->insert_read(this);
torrent::this_thread::poll()->insert_error(this);
} }
// TODO: This should close the fd to avoid reuse. // TODO: This should close the fd to avoid reuse.
+2 -2
View File
@@ -3,13 +3,13 @@
#include <array> #include <array>
#include <memory> #include <memory>
#include <torrent/event.h> #include <torrent/system/event.h>
#include "rpc/scgi_task.h" #include "rpc/scgi_task.h"
namespace rpc { namespace rpc {
class SCgi : public torrent::Event { class SCgi : public torrent::system::Event {
public: public:
static const int max_tasks = 100; static const int max_tasks = 100;
+25 -10
View File
@@ -14,6 +14,7 @@
#include <torrent/runtime/socket_manager.h> #include <torrent/runtime/socket_manager.h>
#include <torrent/system/callbacks.h> #include <torrent/system/callbacks.h>
#include <torrent/system/poll.h> #include <torrent/system/poll.h>
#include <torrent/system/scheduler.h>
#include <torrent/utils/log.h> #include <torrent/utils/log.h>
#include "control.h" #include "control.h"
@@ -27,7 +28,9 @@ namespace rpc {
SCgiTask::SCgiTask() SCgiTask::SCgiTask()
: m_callback_id(torrent::system::make_callback_id()) { : m_callback_id(torrent::system::make_callback_id()) {
set_file_descriptor(-1); m_task_timeout.slot() = [this]() { close(); };
reset_file_descriptor();
} }
void void
@@ -49,7 +52,8 @@ SCgiTask::open(SCgi* parent, int fd) {
torrent::this_thread::poll()->open(this); torrent::this_thread::poll()->open(this);
torrent::this_thread::poll()->insert_read(this); torrent::this_thread::poll()->insert_read(this);
torrent::this_thread::poll()->insert_error(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); auto lock = std::lock_guard<std::mutex>(m_result_mutex);
@@ -62,10 +66,12 @@ SCgiTask::cancel_open() {
if (!is_open()) if (!is_open())
return; return;
torrent::this_thread::scheduler()->erase(&m_task_timeout);
torrent::this_thread::poll()->remove_and_close(this); torrent::this_thread::poll()->remove_and_close(this);
torrent::fd_close(file_descriptor()); torrent::fd_close(file_descriptor());
set_file_descriptor(-1); reset_file_descriptor();
}; };
void void
@@ -73,19 +79,22 @@ SCgiTask::close() {
if (!is_open()) if (!is_open())
return; 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::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]() { torrent::runtime::socket_manager()->close_event_or_throw(this, [this]() {
torrent::this_thread::poll()->remove_and_close(this); torrent::this_thread::poll()->remove_and_close(this);
torrent::fd_close(file_descriptor()); torrent::fd_close(file_descriptor());
set_file_descriptor(-1); reset_file_descriptor();
}); });
// The callbacks are guaranteed to be finished/canceled at this point. // The callbacks are guaranteed to be finished/canceled at this point.
auto lock = std::lock_guard<std::mutex>(m_result_mutex); auto lock = std::lock_guard<std::mutex>(m_result_mutex);
m_buffer.clear(); // clear() would keep the capacity, and the task is pooled for the lifetime of the process.
std::vector<char>().swap(m_buffer);
} }
void void
@@ -95,10 +104,14 @@ SCgiTask::event_read() {
if (m_content_length == 0) if (m_content_length == 0)
read_length--; read_length--;
if (read_length <= 0) if (read_length <= 0) {
throw torrent::internal_error("SCgiTask::event_read() no space in buffer for event_read."); if (m_content_length == 0)
return close();
int bytes = ::recv(m_fileDesc, m_buffer.data() + m_position, read_length, 0); 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);
if (bytes <= 0) { if (bytes <= 0) {
if (bytes == 0 || !(errno == EAGAIN || errno == EINTR)) if (bytes == 0 || !(errno == EAGAIN || errno == EINTR))
@@ -155,6 +168,8 @@ SCgiTask::event_read() {
torrent::this_thread::poll()->remove_read(this); 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) { if (m_parent->log_fd() >= 0) {
[[maybe_unused]] int result; [[maybe_unused]] int result;
@@ -181,7 +196,7 @@ event_read_failed:
void void
SCgiTask::event_write() { SCgiTask::event_write() {
int bytes = ::send(m_fileDesc, m_buffer.data() + m_position, m_buffer.size() - m_position, 0); int bytes = ::send(file_descriptor(), m_buffer.data() + m_position, m_buffer.size() - m_position, 0);
if (bytes == -1) { if (bytes == -1) {
if (!(errno == EAGAIN || errno == EINTR)) if (!(errno == EAGAIN || errno == EINTR))
@@ -390,7 +405,7 @@ void
SCgiTask::receive_write(const char* buffer, uint32_t length) { SCgiTask::receive_write(const char* buffer, uint32_t length) {
assert(torrent::this_thread::thread() == torrent::main_thread::thread()); assert(torrent::this_thread::thread() == torrent::main_thread::thread());
if (buffer == nullptr || length > (100 << 20)) if (buffer == nullptr || length > max_response_size)
throw torrent::internal_error("SCgiTask::receive_write(...) received bad input."); throw torrent::internal_error("SCgiTask::receive_write(...) received bad input.");
// Main thread callback already locked this mutex. // Main thread callback already locked this mutex.
+15 -7
View File
@@ -1,20 +1,27 @@
#ifndef RTORRENT_RPC_SCGI_TASK_H #ifndef RTORRENT_RPC_SCGI_TASK_H
#define RTORRENT_RPC_SCGI_TASK_H #define RTORRENT_RPC_SCGI_TASK_H
#include <chrono>
#include <cstdint>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <vector> #include <vector>
#include <torrent/event.h> #include <torrent/system/event.h>
#include <torrent/system/scheduler.h>
namespace rpc { namespace rpc {
class SCgi; class SCgi;
class SCgiTask : public torrent::Event { class SCgiTask : public torrent::system::Event {
public: public:
static constexpr int default_buffer_size = 8191; static constexpr int default_buffer_size = 8191;
static constexpr int max_header_size = 2000; static constexpr int max_header_size = 2000;
static constexpr int max_content_size = (2 << 23); static constexpr int max_content_size = (1 << 26);
static constexpr uint32_t max_response_size = (100 << 20);
static constexpr auto timeout_request = std::chrono::seconds(60);
enum ContentType { XML, JSON }; enum ContentType { XML, JSON };
@@ -22,8 +29,8 @@ public:
const char* type_name() const override { return "scgi-task"; } const char* type_name() const override { return "scgi-task"; }
bool is_open() const { return m_fileDesc != -1; } bool is_open() const { return file_descriptor() != -1; }
bool is_available() const { return m_fileDesc == -1; } bool is_available() const { return file_descriptor() == -1; }
void open(SCgi* parent, int fd); void open(SCgi* parent, int fd);
void cancel_open(); void cancel_open();
@@ -54,8 +61,9 @@ private:
void plaintext_response(const char* buffer, uint32_t content_length); void plaintext_response(const char* buffer, uint32_t content_length);
void gzip_response(const char* buffer, uint32_t content_length); void gzip_response(const char* buffer, uint32_t content_length);
SCgi* m_parent{}; SCgi* m_parent{};
torrent::system::callback_id m_callback_id; torrent::system::callback_id m_callback_id;
torrent::system::SchedulerEntry m_task_timeout;
std::mutex m_result_mutex; std::mutex m_result_mutex;
+16 -11
View File
@@ -27,11 +27,11 @@ public:
virtual ~xmlrpc_error_c() throw() {} virtual ~xmlrpc_error_c() throw() {}
virtual int type() const throw() { return m_type; } virtual int type() const throw() { return m_type; }
virtual const char* what() const throw() { return m_msg; } virtual const char* what() const throw() { return m_msg.c_str(); }
private: private:
int m_type; int m_type;
const char* m_msg; std::string m_msg;
}; };
torrent::Object xmlrpc_to_object(xmlrpc_env* env, xmlrpc_value* value, int call_type = 0, rpc::target_type* target = NULL, std::function<void()>* deleter = NULL); torrent::Object xmlrpc_to_object(xmlrpc_env* env, xmlrpc_value* value, int call_type = 0, rpc::target_type* target = NULL, std::function<void()>* deleter = NULL);
@@ -44,10 +44,9 @@ xmlrpc_list_entry_to_object(xmlrpc_env* env, xmlrpc_value* src, int index) {
if (env->fault_occurred) if (env->fault_occurred)
throw xmlrpc_error_c(env); throw xmlrpc_error_c(env);
torrent::Object obj = xmlrpc_to_object(env, tmp); utils::scope_guard guard([tmp]() { xmlrpc_DECREF(tmp); });
xmlrpc_DECREF(tmp);
return obj; return xmlrpc_to_object(env, tmp);
} }
int64_t int64_t
@@ -77,16 +76,18 @@ xmlrpc_list_entry_to_value(xmlrpc_env* env, xmlrpc_value* src, int index) {
{ {
const char* str; const char* str;
xmlrpc_read_string(env, tmp, &str); xmlrpc_read_string(env, tmp, &str);
xmlrpc_DECREF(tmp);
if (env->fault_occurred) if (env->fault_occurred)
throw xmlrpc_error_c(env); throw xmlrpc_error_c(env);
const char* end = str; const char* end = str;
int64_t v3 = ::strtoll(str, (char**)&end, 0); int64_t v3 = ::strtoll(str, (char**)&end, 0);
bool invalid = *str == '\0' || *end != '\0';
::free((void*)str); ::free((void*)str);
if (*str == '\0' || *end != '\0') if (invalid)
throw xmlrpc_error_c(XMLRPC_TYPE_ERROR, "Invalid index."); throw xmlrpc_error_c(XMLRPC_TYPE_ERROR, "Invalid index.");
return v3; return v3;
@@ -211,11 +212,11 @@ xmlrpc_to_object(xmlrpc_env* env, xmlrpc_value* value, int call_type, rpc::targe
if (env->fault_occurred) if (env->fault_occurred)
throw xmlrpc_error_c(env); throw xmlrpc_error_c(env);
utils::scope_guard guard([tmp]() { xmlrpc_DECREF(tmp); });
if (target != nullptr) if (target != nullptr)
std::tie(*target, *deleter) = xmlrpc_to_target(env, tmp, call_type); std::tie(*target, *deleter) = xmlrpc_to_target(env, tmp, call_type);
xmlrpc_DECREF(tmp);
if (env->fault_occurred) if (env->fault_occurred)
throw xmlrpc_error_c(env); throw xmlrpc_error_c(env);
@@ -439,6 +440,8 @@ XmlRpc::cleanup() {
xmlrpc_registry_free((xmlrpc_registry*)m_registry); xmlrpc_registry_free((xmlrpc_registry*)m_registry);
xmlrpc_env_clean((xmlrpc_env*)m_env); xmlrpc_env_clean((xmlrpc_env*)m_env);
delete (xmlrpc_env*)m_env; delete (xmlrpc_env*)m_env;
m_env = nullptr;
m_registry = nullptr;
} }
bool bool
@@ -451,6 +454,11 @@ XmlRpc::process(const char* inBuffer, uint32_t length, slot_write slotWrite) {
if (local_env.fault_occurred && local_env.fault_code == XMLRPC_INTERNAL_ERROR) if (local_env.fault_occurred && local_env.fault_code == XMLRPC_INTERNAL_ERROR)
throw torrent::internal_error("Internal error in XMLRPC."); 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), bool result = slotWrite((const char*)xmlrpc_mem_block_contents(memblock),
xmlrpc_mem_block_size(memblock)); xmlrpc_mem_block_size(memblock));
@@ -524,9 +532,6 @@ XmlRpc::size_limit() {
void void
XmlRpc::set_size_limit(uint64_t size) { 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); xmlrpc_limit_set(XMLRPC_XML_SIZE_LIMIT_ID, size);
} }
+46 -13
View File
@@ -19,6 +19,7 @@
#include "rpc/tinyxml2/tinyxml2.h" #include "rpc/tinyxml2/tinyxml2.h"
#include "rpc/rpc_manager.h" #include "rpc/rpc_manager.h"
#include "utils/base64.h" #include "utils/base64.h"
#include "utils/functional.h"
#include "xmlrpc.h" #include "xmlrpc.h"
namespace rpc { namespace rpc {
@@ -49,13 +50,23 @@ element_access(const tinyxml2::XMLElement* elem, std::initializer_list<std::stri
return result; 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 long long
element_to_int(const tinyxml2::XMLNode* elem) { element_to_int(const tinyxml2::XMLNode* elem) {
char* pos; char* pos;
if (elem->FirstChild() == nullptr) { if (elem->FirstChild() == nullptr) {
throw rpc_error(XMLRPC_TYPE_ERROR, "unable to parse empty integer"); 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); auto result = std::strtoll(str, &pos, 10);
if (pos == str || *pos != '\0') if (pos == str || *pos != '\0')
throw rpc_error(XMLRPC_TYPE_ERROR, "unable to parse integer value"); throw rpc_error(XMLRPC_TYPE_ERROR, "unable to parse integer value");
@@ -85,7 +96,7 @@ xml_value_to_object(const tinyxml2::XMLNode* elem) {
if (child_element == nullptr) if (child_element == nullptr)
return torrent::Object(""); 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 || } else if (std::strncmp(root_type, "int", sizeof("int")) == 0 ||
std::strncmp(root_type, "i4", sizeof("i4")) == 0 || std::strncmp(root_type, "i4", sizeof("i4")) == 0 ||
@@ -98,7 +109,7 @@ xml_value_to_object(const tinyxml2::XMLNode* elem) {
if (child_element == nullptr) if (child_element == nullptr)
throw rpc_error(XMLRPC_TYPE_ERROR, "empty boolean element"); 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") if (boolean_text == "1")
return torrent::Object((int64_t)1); return torrent::Object((int64_t)1);
@@ -129,7 +140,12 @@ xml_value_to_object(const tinyxml2::XMLNode* elem) {
if (name_element == nullptr) if (name_element == nullptr)
throw rpc_error(XMLRPC_PARSE_ERROR, "struct member missing name element"); 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; return map_raw;
@@ -140,7 +156,7 @@ xml_value_to_object(const tinyxml2::XMLNode* elem) {
if (child_element == nullptr) if (child_element == nullptr)
return torrent::Object(""); 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 { } else {
throw rpc_error(XMLRPC_INTERNAL_ERROR, "received unsupported value type: " + std::string(root_type)); throw rpc_error(XMLRPC_INTERNAL_ERROR, "received unsupported value type: " + std::string(root_type));
@@ -243,6 +259,8 @@ execute_command(std::string method_name, const tinyxml2::XMLElement* params_elem
torrent::Object params_raw = torrent::Object::create_list(); torrent::Object params_raw = torrent::Object::create_list();
torrent::Object::list_type& params = params_raw.as_list(); torrent::Object::list_type& params = params_raw.as_list();
rpc::target_type target = rpc::make_target(); rpc::target_type target = rpc::make_target();
std::function<void()> deleter = []() {};
utils::scope_guard guard([&deleter]() { deleter(); });
if (params_element != nullptr) { if (params_element != nullptr) {
if (std::strncmp(params_element->Name(), "params", sizeof("params")) == 0) { if (std::strncmp(params_element->Name(), "params", sizeof("params")) == 0) {
@@ -250,8 +268,6 @@ execute_command(std::string method_name, const tinyxml2::XMLElement* params_elem
const auto* child = params_element->FirstChildElement("param"); const auto* child = params_element->FirstChildElement("param");
if (child != nullptr) { if (child != nullptr) {
std::function<void()> deleter = []() {};
RpcManager::object_to_target(xml_value_to_object(child->FirstChildElement("value")), cmd_itr->second.m_flags, &target, &deleter); RpcManager::object_to_target(xml_value_to_object(child->FirstChildElement("value")), cmd_itr->second.m_flags, &target, &deleter);
child = child->NextSiblingElement("param"); child = child->NextSiblingElement("param");
@@ -267,8 +283,6 @@ execute_command(std::string method_name, const tinyxml2::XMLElement* params_elem
const auto* child = params_element->FirstChildElement("data")->FirstChildElement("value"); const auto* child = params_element->FirstChildElement("data")->FirstChildElement("value");
if (child != nullptr) { if (child != nullptr) {
std::function<void()> deleter = []() {};
RpcManager::object_to_target(xml_value_to_object(child), cmd_itr->second.m_flags, &target, &deleter); RpcManager::object_to_target(xml_value_to_object(child), cmd_itr->second.m_flags, &target, &deleter);
child = child->NextSiblingElement("value"); child = child->NextSiblingElement("value");
@@ -300,6 +314,9 @@ process_document(const tinyxml2::XMLDocument* doc, tinyxml2::XMLPrinter* printer
if (doc->FirstChildElement("methodCall")->FirstChildElement("methodName") == nullptr) if (doc->FirstChildElement("methodCall")->FirstChildElement("methodName") == nullptr)
throw rpc_error(XMLRPC_PARSE_ERROR, "methodName element not found"); throw rpc_error(XMLRPC_PARSE_ERROR, "methodName element not found");
auto method_name = doc->FirstChildElement("methodCall")->FirstChildElement("methodName")->GetText(); 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; torrent::Object result;
// Add a shim here for system.multicall to allow better code reuse, and // Add a shim here for system.multicall to allow better code reuse, and
@@ -309,12 +326,20 @@ process_document(const tinyxml2::XMLDocument* doc, tinyxml2::XMLPrinter* printer
auto& result_list = result.as_list(); auto& result_list = result.as_list();
auto parent_elements = element_access(doc->RootElement(), {"params", "param", "value", "array", "data"}); auto parent_elements = element_access(doc->RootElement(), {"params", "param", "value", "array", "data"});
for (auto child = parent_elements->FirstChildElement("value"); child; child = child->NextSiblingElement("value")) { for (auto child = parent_elements->FirstChildElement("value"); child; child = child->NextSiblingElement("value")) {
auto sub_method_name = element_access(child, {"struct", "member", "value", "string"})->GetText(); auto method_name_member = element_access(child, {"struct", "member"});
auto member_name = method_name_member->FirstChildElement("name");
if (member_name == nullptr || member_name->GetText() == nullptr ||
std::strncmp(member_name->GetText(), "methodName", sizeof("methodName")) != 0)
throw rpc_error(XMLRPC_PARSE_ERROR, "multicall struct's first member must be methodName");
auto sub_method_name = element_access(method_name_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, // If sub_params ends up a nullptr at the end of this if-chian,
// execute_command will turn it into an empty list // execute_command will turn it into an empty list
auto sub_params = element_access(child, {"struct", "member"}); auto sub_params = method_name_member->NextSiblingElement("member");
if (sub_params != nullptr)
sub_params = sub_params->NextSiblingElement("member");
if (sub_params != nullptr) if (sub_params != nullptr)
sub_params = sub_params->FirstChildElement("value"); sub_params = sub_params->FirstChildElement("value");
if (sub_params != nullptr) if (sub_params != nullptr)
@@ -405,6 +430,14 @@ XmlRpc::process(const char* inBuffer, uint32_t length, slot_write slotWrite) {
// remains. // remains.
tinyxml2::XMLPrinter printer(nullptr, true, 0); tinyxml2::XMLPrinter printer(nullptr, true, 0);
process_document(&doc, &printer); process_document(&doc, &printer);
if (printer.CStrSize() - 1 > static_cast<int>(SCgiTask::max_response_size)) {
tinyxml2::XMLPrinter fault_printer(nullptr, true, 0);
print_xmlrpc_fault(XMLRPC_LIMIT_EXCEEDED_ERROR, "Response size exceeds maximum XML-RPC limit", &fault_printer);
return slotWrite(fault_printer.CStr(), fault_printer.CStrSize() - 1);
}
return slotWrite(printer.CStr(), printer.CStrSize() - 1); return slotWrite(printer.CStr(), printer.CStrSize() - 1);
} catch (rpc_error& e) { } catch (rpc_error& e) {
tinyxml2::XMLPrinter printer(nullptr, true, 0); tinyxml2::XMLPrinter printer(nullptr, true, 0);
+69 -17
View File
@@ -2,6 +2,7 @@
#include "download_storer.h" #include "download_storer.h"
#include <cerrno>
#include <fcntl.h> #include <fcntl.h>
#include <fstream> #include <fstream>
#include <unistd.h> #include <unistd.h>
@@ -16,6 +17,13 @@
#include "core/download.h" #include "core/download.h"
#include "utils/directory.h" #include "utils/directory.h"
// O_DIRECTORY makes the open refuse anything that is not a directory. A
// platform that does not define it still gets the read-only directory
// handle fsync needs, so fall back to no extra flag.
#ifndef O_DIRECTORY
#define O_DIRECTORY 0
#endif
namespace session { namespace session {
DownloadStorer::DownloadStorer(core::Download* download) DownloadStorer::DownloadStorer(core::Download* download)
@@ -116,34 +124,73 @@ is_correct_format(const std::string& f) {
void void
save_stream(const std::string& path, bool use_fsyncdisk, const std::stringstream& stream) { save_stream(const std::string& path, bool use_fsyncdisk, const std::stringstream& stream) {
std::fstream output(path.c_str(), std::ios::out | std::ios::trunc); // Remove any leftover temporary file first so that O_EXCL only ever fails on
// an entry that appeared after the unlink, and O_NOFOLLOW keeps a symlink
// planted in the session directory from redirecting the write.
if (::unlink(path.c_str()) == -1 && errno != ENOENT)
throw torrent::storage_error("failed to remove stale file : " + path);
// TODO: If we cannot open more files, wait for some to finish and try again. // TODO: If we cannot open more files, wait for some to finish and try again.
if (!output.is_open()) int fd = ::open(path.c_str(), O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW, 0600);
throw torrent::storage_error("failed to open file for writing : " + path);
output << stream.rdbuf();
if (!output.good())
throw torrent::storage_error("failed to write stream to file : " + path);
output.close();
// Ensure that the new file is actually written to the disk
int fd = ::open(path.c_str(), O_WRONLY);
if (fd < 0) if (fd < 0)
throw torrent::storage_error("failed to open file descriptor for fsync : " + path); throw torrent::storage_error("failed to open file for writing : " + path);
const auto data = stream.view();
std::size_t remaining = data.size();
const char* cursor = data.data();
while (remaining != 0) {
ssize_t result = ::write(fd, cursor, remaining);
if (result == -1) {
if (errno == EINTR)
continue;
::close(fd);
throw torrent::storage_error("failed to write stream to file : " + path);
}
cursor += result;
remaining -= result;
}
if (use_fsyncdisk) { if (use_fsyncdisk) {
#ifdef __APPLE__ #ifdef __APPLE__
::fsync(fd); int sync_result = ::fsync(fd);
#else #else
::fdatasync(fd); int sync_result = ::fdatasync(fd);
#endif #endif
if (sync_result == -1) {
::close(fd);
throw torrent::storage_error("failed to sync file to disk : " + path);
}
} }
::close(fd); // A full disk may only be seen when the descriptor is closed.
if (::close(fd) == -1)
throw torrent::storage_error("failed to close file descriptor : " + path);
}
void
sync_directory_of(const std::string& path) {
auto separator = path.rfind('/');
auto directory = separator == std::string::npos ? std::string(".") :
separator == 0 ? std::string("/") : path.substr(0, separator);
int fd = ::open(directory.c_str(), O_RDONLY | O_DIRECTORY);
if (fd < 0)
throw torrent::storage_error("failed to open session directory for sync : " + directory);
if (::fsync(fd) == -1) {
::close(fd);
throw torrent::storage_error("failed to sync session directory : " + directory);
}
if (::close(fd) == -1)
throw torrent::storage_error("failed to close session directory : " + directory);
} }
} // namespace anonymous } // namespace anonymous
@@ -173,6 +220,11 @@ DownloadStorer::save_and_move_streams(const std::string& path, bool use_fsyncdis
if (::rename((rtorrent_path + ".new").c_str(), rtorrent_path.c_str()) == -1) if (::rename((rtorrent_path + ".new").c_str(), rtorrent_path.c_str()) == -1)
throw torrent::storage_error("failed to rename rtorrent resume file : " + rtorrent_path); throw torrent::storage_error("failed to rename rtorrent resume file : " + rtorrent_path);
// Syncing the files themselves does not persist the renames; the directory
// holding them has to be synced for the new names to survive a crash.
if (use_fsyncdisk)
sync_directory_of(path);
} }
utils::Directory utils::Directory
-1
View File
@@ -75,7 +75,6 @@ SessionManager::save_resume_download(core::Download* download) {
{ {
std::unique_lock<std::mutex> lock(m_pending_builds_mutex); std::unique_lock<std::mutex> lock(m_pending_builds_mutex);
// TODO: This is under the wrong lock.
if (!m_active) if (!m_active)
throw torrent::internal_error("SessionManager::save_resume_download() called while not active."); throw torrent::internal_error("SessionManager::save_resume_download() called while not active.");
+8 -3
View File
@@ -1,6 +1,7 @@
#ifndef RTORRENT_SESSION_SESSION_MANAGER_H #ifndef RTORRENT_SESSION_SESSION_MANAGER_H
#define RTORRENT_SESSION_SESSION_MANAGER_H #define RTORRENT_SESSION_SESSION_MANAGER_H
#include <atomic>
#include <condition_variable> #include <condition_variable>
#include <deque> #include <deque>
#include <future> #include <future>
@@ -99,9 +100,13 @@ private:
bool m_use_fsyncdisk{true}; bool m_use_fsyncdisk{true};
bool m_use_lock{true}; bool m_use_lock{true};
align_cacheline std::mutex m_mutex; align_cacheline
bool m_active{}; std::atomic<bool> m_active{};
align_cacheline
std::mutex m_mutex;
std::deque<SaveRequest> m_save_requests; std::deque<SaveRequest> m_save_requests;
std::atomic<size_t> m_save_request_counter{}; std::atomic<size_t> m_save_request_counter{};
@@ -126,7 +131,7 @@ private:
inline bool SessionManager::is_used() const { return !m_path.empty(); } inline bool SessionManager::is_used() const { return !m_path.empty(); }
inline std::string SessionManager::path() const { return m_path; } inline std::string SessionManager::path() const { return m_path; }
inline bool SessionManager::use_fsyncdisk() const { return true; } inline bool SessionManager::use_fsyncdisk() const { return m_use_fsyncdisk; }
inline bool SessionManager::use_lock() const { return m_use_lock; } inline bool SessionManager::use_lock() const { return m_use_lock; }
inline void SessionManager::flush_all_pending_builds() { process_pending_builds(true); } inline void SessionManager::flush_all_pending_builds() { process_pending_builds(true); }
+8 -4
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('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('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('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.port_range.set", arg); }); optionParser.insert_option('p', [](auto& arg) { rpc::call_command_set_string("network.listen.port.range.set", arg); });
optionParser.insert_option('s', [](auto& arg) { rpc::call_command_set_string("session", arg); }); optionParser.insert_option('s', [](auto& arg) { rpc::call_command_set_string("session", arg); });
optionParser.insert_option('O', [](auto& arg) { rpc::parse_command_single_std(arg); }); optionParser.insert_option('O', [](auto& arg) { rpc::parse_command_single_std(arg); });
@@ -86,9 +86,13 @@ config_comment_log(const std::string& command, const std::string& raw_args) {
pos = next_pos + 1; pos = next_pos + 1;
} }
if (command == "log.add_output") if (command == "log.add_output") {
if (args.size() != 2)
throw torrent::input_error("Invalid number of arguments.");
log_add_group_output_str(args[0], args[1]); log_add_group_output_str(args[0], args[1]);
else if (command == "log.open_file")
} else if (command == "log.open_file")
apply_log_open_str(0, args); apply_log_open_str(0, args);
else if (command == "log.open_file.flush") else if (command == "log.open_file.flush")
apply_log_open_str(log_flag_flush, args); apply_log_open_str(log_flag_flush, args);
+1
View File
@@ -3,6 +3,7 @@
#include <functional> #include <functional>
#include <string> #include <string>
#include <vector>
int parse_main_options(int argc, char** argv); int parse_main_options(int argc, char** argv);
void parse_config_file(int argc, char** argv, std::function<void (const std::string&)> parse_fn); void parse_config_file(int argc, char** argv, std::function<void (const std::string&)> parse_fn);
+8 -4
View File
@@ -63,8 +63,10 @@ SignalHandler::set_block(unsigned int signum) {
sigemptyset(&mask); sigemptyset(&mask);
sigaddset(&mask, signum); sigaddset(&mask, signum);
if (pthread_sigmask(SIG_BLOCK, &mask, NULL) == -1) int result = pthread_sigmask(SIG_BLOCK, &mask, NULL);
throw std::logic_error("Could not block signal: " + std::string(std::strerror(errno)));
if (result != 0)
throw std::logic_error("Could not block signal: " + std::string(std::strerror(result)));
} }
void void
@@ -77,8 +79,10 @@ SignalHandler::set_unblock(unsigned int signum) {
sigemptyset(&mask); sigemptyset(&mask);
sigaddset(&mask, signum); sigaddset(&mask, signum);
if (pthread_sigmask(SIG_UNBLOCK, &mask, NULL) == -1) int result = pthread_sigmask(SIG_UNBLOCK, &mask, NULL);
throw std::logic_error("Could not unblock signal: " + std::string(std::strerror(errno)));
if (result != 0)
throw std::logic_error("Could not unblock signal: " + std::string(std::strerror(result)));
} }
void void
+7 -7
View File
@@ -1,9 +1,9 @@
#include <memory>
#include "config.h" #include "config.h"
#include <cassert> #include <cassert>
#include <torrent/exceptions.h> #include <torrent/exceptions.h>
#include <torrent/chunk_manager.h>
#include <torrent/throttle.h> #include <torrent/throttle.h>
#include <torrent/torrent.h> #include <torrent/torrent.h>
#include <torrent/data/file_list.h> #include <torrent/data/file_list.h>
@@ -31,10 +31,12 @@
namespace ui { namespace ui {
Download::Download(core::Download* d) : Download::Download(const std::shared_ptr<core::Download>& download)
m_download(d) { : m_download(download.get()) {
m_windowDownloadStatus = new WDownloadStatus(d); auto* d = download.get();
m_windowDownloadStatus = std::make_unique<WDownloadStatus>(d);
m_windowDownloadStatus->set_bottom(true); m_windowDownloadStatus->set_bottom(true);
m_uiArray[DISPLAY_MENU] = create_menu(); m_uiArray[DISPLAY_MENU] = create_menu();
@@ -60,8 +62,6 @@ Download::~Download() {
assert(!is_active() && "ui::Download::~Download() called on an active object."); assert(!is_active() && "ui::Download::~Download() called on an active object.");
std::for_each(m_uiArray, m_uiArray + DISPLAY_MAX_SIZE, [](ElementBase* eb) { delete eb; }); std::for_each(m_uiArray, m_uiArray + DISPLAY_MAX_SIZE, [](ElementBase* eb) { delete eb; });
delete m_windowDownloadStatus;
} }
inline ElementBase* inline ElementBase*
@@ -171,7 +171,7 @@ Download::activate(display::Frame* frame, [[maybe_unused]] bool focus) {
m_frame = frame; m_frame = frame;
m_frame->initialize_row(2); m_frame->initialize_row(2);
m_frame->frame(1)->initialize_window(m_windowDownloadStatus); m_frame->frame(1)->initialize_window(m_windowDownloadStatus.get());
m_windowDownloadStatus->set_active(true); m_windowDownloadStatus->set_active(true);
activate_display_menu(DISPLAY_PEER_LIST); activate_display_menu(DISPLAY_PEER_LIST);
+3 -37
View File
@@ -1,45 +1,11 @@
// rTorrent - BitTorrent client
// Copyright (C) 2005-2011, Jari Sundell
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// In addition, as a special exception, the copyright holders give
// permission to link the code of portions of this program with the
// OpenSSL library under certain conditions as described in each
// individual source file, and distribute linked combinations
// including the two.
//
// You must obey the GNU General Public License in all respects for
// all of the code used other than OpenSSL. If you modify file(s)
// with this exception, you may extend this exception to your version
// of the file(s), but you are not obligated to do so. If you do not
// wish to do so, delete this exception statement from your version.
// If you delete this exception statement from all source files in the
// program, then also delete it here.
//
// Contact: Jari Sundell <sundell.software@gmail.com>
#ifndef RTORRENT_UI_DOWNLOAD_H #ifndef RTORRENT_UI_DOWNLOAD_H
#define RTORRENT_UI_DOWNLOAD_H #define RTORRENT_UI_DOWNLOAD_H
#include <list> #include <list>
#include <memory>
#include <torrent/peer/peer.h> #include <torrent/peer/peer.h>
#include "display/manager.h" #include "display/manager.h"
#include "utils/list_focus.h"
#include "element_base.h" #include "element_base.h"
@@ -70,7 +36,7 @@ public:
DISPLAY_MAX_SIZE DISPLAY_MAX_SIZE
} Display; } Display;
Download(core::Download* d); Download(const std::shared_ptr<core::Download>& d);
~Download(); ~Download();
void activate(display::Frame* frame, bool focus = true); void activate(display::Frame* frame, bool focus = true);
@@ -113,7 +79,7 @@ private:
bool m_focusDisplay{}; bool m_focusDisplay{};
WDownloadStatus* m_windowDownloadStatus; std::unique_ptr<WDownloadStatus> m_windowDownloadStatus;
}; };
} }
+37 -9
View File
@@ -30,6 +30,38 @@
namespace ui { namespace ui {
std::string
create_filter_pattern(const std::string& input) {
std::string pattern = input;
if (pattern.empty())
return pattern;
if (pattern.back() != '$')
pattern = pattern + ".*";
if (pattern.front() != '^')
pattern = ".*" + pattern;
std::transform(pattern.begin(), pattern.end(), pattern.begin(), ::tolower);
return pattern;
}
torrent::Object
create_filter_command(const std::string& pattern) {
torrent::Object name = torrent::Object::create_dict_key();
name.as_dict_key() = "d.name";
torrent::Object args = torrent::Object::create_list();
args.as_list().push_back(name);
args.as_list().push_back(torrent::Object(pattern));
torrent::Object command = torrent::Object::create_dict_key();
command.as_dict_key() = "match";
command.as_dict_obj() = args;
return command;
}
DownloadList::DownloadList() { DownloadList::DownloadList() {
m_uiArray[DISPLAY_DOWNLOAD] = NULL; m_uiArray[DISPLAY_DOWNLOAD] = NULL;
m_uiArray[DISPLAY_DOWNLOAD_LIST] = new ElementDownloadList(); m_uiArray[DISPLAY_DOWNLOAD_LIST] = new ElementDownloadList();
@@ -89,7 +121,7 @@ DownloadList::unfocus_download(core::Download* d) {
if (m_state == DISPLAY_DOWNLOAD && d == static_cast<Download*>(m_uiArray[DISPLAY_DOWNLOAD])->download()) if (m_state == DISPLAY_DOWNLOAD && d == static_cast<Download*>(m_uiArray[DISPLAY_DOWNLOAD])->download())
activate_display(DISPLAY_DOWNLOAD_LIST); 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(); current_view()->next_focus();
} }
@@ -319,16 +351,12 @@ DownloadList::receive_exit_input(Input type) {
current_view()->filter(); current_view()->filter();
current_view()->sort(); current_view()->sort();
} else { } else {
std::string pattern = input->str(); std::string pattern = create_filter_pattern(input->str());
if (pattern.back() != '$')
pattern = pattern + ".*";
if (pattern.front() != '^')
pattern = ".*" + pattern;
std::transform(pattern.begin(), pattern.end(), pattern.begin(), ::tolower);
std::string temp_filter = "match={d.name=," + pattern + "}";
if (rpc::call_command_value("view.filter.temp.log")) if (rpc::call_command_value("view.filter.temp.log"))
control->core()->push_log_std("Temporary filter on '" + current_view()->name() + "' view: " + pattern); control->core()->push_log_std("Temporary filter on '" + current_view()->name() + "' view: " + pattern);
current_view()->set_filter_temp(temp_filter);
current_view()->set_filter_temp(create_filter_command(pattern));
current_view()->filter(); current_view()->filter();
} }
break; break;
+9
View File
@@ -1,6 +1,9 @@
#ifndef RTORRENT_UI_DOWNLOAD_LIST_H #ifndef RTORRENT_UI_DOWNLOAD_LIST_H
#define RTORRENT_UI_DOWNLOAD_LIST_H #define RTORRENT_UI_DOWNLOAD_LIST_H
#include <string>
#include <torrent/object.h>
#include "element_base.h" #include "element_base.h"
#include "globals.h" #include "globals.h"
#include "display/manager.h" #include "display/manager.h"
@@ -29,6 +32,12 @@ namespace ui {
class Download; class Download;
// Wrap a user-typed download name pattern in the anchors 'match' expects.
std::string create_filter_pattern(const std::string& input);
// Build the 'match' command the temporary view filter runs against d.name.
torrent::Object create_filter_command(const std::string& pattern);
class DownloadList : public ElementBase { class DownloadList : public ElementBase {
public: public:
typedef display::WindowDownloadList WList; typedef display::WindowDownloadList WList;
+1 -1
View File
@@ -196,7 +196,7 @@ ElementDownloadList::receive_cycle_throttle() {
if (m_view->focus() == m_view->end_visible()) if (m_view->focus() == m_view->end_visible())
return; return;
core::Download* download = *m_view->focus(); core::Download* download = m_view->focus()->get();
if (download->is_active()) { if (download->is_active()) {
lt_log_print(torrent::LOG_TORRENT_WARN, "Cannot change throttle on active download."); lt_log_print(torrent::LOG_TORRENT_WARN, "Cannot change throttle on active download.");
return; return;
+6 -4
View File
@@ -15,13 +15,13 @@ namespace ui {
class ElementLogComplete : public ElementBase { class ElementLogComplete : public ElementBase {
public: public:
typedef display::WindowLogComplete WLogComplete; using WLogComplete = display::WindowLogComplete;
ElementLogComplete(torrent::log_buffer* l); ElementLogComplete(torrent::log_buffer* l);
~ElementLogComplete() override; ~ElementLogComplete() override;
void activate(display::Frame* frame, bool focus = true); void activate(display::Frame* frame, bool focus = true) override;
void disable(); void disable() override;
display::Window* window(); display::Window* window();
@@ -32,7 +32,9 @@ private:
torrent::log_buffer* m_log; torrent::log_buffer* m_log;
align_cacheline std::atomic<bool> m_log_updating{}; align_cacheline
std::atomic<bool> m_log_updating{};
}; };
} }
+1 -1
View File
@@ -179,7 +179,7 @@ ElementMenu::set_entry(size_type idx, bool triggerSlot) {
m_entry = idx; m_entry = idx;
focus_entry(m_entry); focus_entry(m_entry);
if (triggerSlot) if (triggerSlot && m_entry < size())
base_type::operator[](m_entry).m_slotFocus(); base_type::operator[](m_entry).m_slotFocus();
m_window->mark_dirty(); m_window->mark_dirty();
+7 -4
View File
@@ -4,6 +4,7 @@
#include <fstream> #include <fstream>
#include <stdexcept> #include <stdexcept>
#include <string>
#include <string.h> #include <string.h>
#include <torrent/throttle.h> #include <torrent/throttle.h>
#include <torrent/torrent.h> #include <torrent/torrent.h>
@@ -354,8 +355,9 @@ Root::reset_input_history_attributes(ui::DownloadList::Input type) {
void void
Root::set_input_history_size(int size) { Root::set_input_history_size(int size) {
if (size < 1) if (size < 1 || size > max_input_history_size)
throw torrent::input_error("Invalid 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) { for (auto& [entry, category] : m_input_history) {
// Reserve the latest input history entries if new size is smaller than original. // Reserve the latest input history entries if new size is smaller than original.
@@ -465,6 +467,9 @@ Root::save_input_history() {
history_file << entry << "|" + category.at((pitr->second + i) % m_input_history_length) + "\n"; history_file << entry << "|" + category.at((pitr->second + i) % m_input_history_length) + "\n";
} }
// The data only reaches the kernel here, so this is where a full disk is seen.
history_file.close();
if (!history_file.good()) { if (!history_file.good()) {
lt_log_print(torrent::LOG_DEBUG, "input history file corrupted during writing, discarding (path:%s)", history_filename.c_str()); lt_log_print(torrent::LOG_DEBUG, "input history file corrupted during writing, discarding (path:%s)", history_filename.c_str());
return; return;
@@ -472,8 +477,6 @@ Root::save_input_history() {
lt_log_print(torrent::LOG_DEBUG, "input history file written (path:%s)", history_filename.c_str()); lt_log_print(torrent::LOG_DEBUG, "input history file written (path:%s)", history_filename.c_str());
} }
history_file.close();
std::rename(history_filename_tmp.c_str(), history_filename.c_str()); std::rename(history_filename_tmp.c_str(), history_filename.c_str());
} }
+2 -4
View File
@@ -54,6 +54,8 @@ public:
typedef std::vector<std::string> InputHistoryCategory; typedef std::vector<std::string> InputHistoryCategory;
typedef std::map<int, InputHistoryCategory> InputHistory; typedef std::map<int, InputHistoryCategory> InputHistory;
static constexpr int max_input_history_size = 4096;
Root(); Root();
void init(Control* c); void init(Control* c);
@@ -68,10 +70,6 @@ public:
void set_down_throttle(unsigned int throttle); void set_down_throttle(unsigned int throttle);
void set_up_throttle(unsigned int throttle); void set_up_throttle(unsigned int throttle);
// Rename to raw or something, make base function.
void set_down_throttle_i64(int64_t throttle) { set_down_throttle(throttle >> 10); }
void set_up_throttle_i64(int64_t throttle) { set_up_throttle(throttle >> 10); }
void adjust_down_throttle(int throttle); void adjust_down_throttle(int throttle);
void adjust_up_throttle(int throttle); void adjust_up_throttle(int throttle);
+27 -5
View File
@@ -5,6 +5,7 @@
#include <algorithm> #include <algorithm>
#include <cstdlib> #include <cstdlib>
#include <dirent.h> #include <dirent.h>
#include <fcntl.h>
#include <functional> #include <functional>
#include <sys/stat.h> #include <sys/stat.h>
#include <torrent/exceptions.h> #include <torrent/exceptions.h>
@@ -13,6 +14,24 @@
namespace utils { namespace utils {
namespace {
uint8_t
entry_type_from_mode(mode_t mode) {
if (S_ISREG(mode))
return DT_REG;
if (S_ISDIR(mode))
return DT_DIR;
if (S_ISLNK(mode))
return DT_LNK;
return DT_UNKNOWN;
}
} // namespace
// Keep this? // Keep this?
bool bool
Directory::is_valid() const { Directory::is_valid() const {
@@ -38,9 +57,6 @@ Directory::update(int flags) {
return false; return false;
struct dirent* entry; struct dirent* entry;
#ifdef __sun__
struct stat s;
#endif
while ((entry = readdir(d)) != NULL) { while ((entry = readdir(d)) != NULL) {
if ((flags & update_hide_dot) && entry->d_name[0] == '.') if ((flags & update_hide_dot) && entry->d_name[0] == '.')
@@ -49,16 +65,22 @@ Directory::update(int flags) {
iterator itr = base_type::insert(end(), value_type()); iterator itr = base_type::insert(end(), value_type());
#ifdef __sun__ #ifdef __sun__
stat(entry->d_name, &s);
itr->s_fileno = entry->d_ino; itr->s_fileno = entry->d_ino;
itr->s_reclen = 0; itr->s_reclen = 0;
itr->s_type = s.st_mode; itr->s_type = DT_UNKNOWN;
#else #else
itr->s_fileno = entry->d_fileno; itr->s_fileno = entry->d_fileno;
itr->s_reclen = entry->d_reclen; itr->s_reclen = entry->d_reclen;
itr->s_type = entry->d_type; itr->s_type = entry->d_type;
#endif #endif
if (itr->s_type == DT_UNKNOWN) {
struct stat st;
if (fstatat(dirfd(d), entry->d_name, &st, AT_SYMLINK_NOFOLLOW) == 0)
itr->s_type = entry_type_from_mode(st.st_mode);
}
#ifdef DIRENT_NAMLEN_EXISTS_FOOBAR #ifdef DIRENT_NAMLEN_EXISTS_FOOBAR
itr->s_name = std::string(entry->d_name, entry->d_name + entry->d_namlen); itr->s_name = std::string(entry->d_name, entry->d_name + entry->d_namlen);
#else #else
+2 -2
View File
@@ -2,14 +2,14 @@
#define RTORRENT_UTILS_DIRECTORY_H #define RTORRENT_UTILS_DIRECTORY_H
#include <cstdint> #include <cstdint>
#include <dirent.h>
#include <string> #include <string>
#include <vector> #include <vector>
namespace utils { namespace utils {
struct directory_entry { struct directory_entry {
// Fix. bool is_file() const { return s_type == DT_REG; }
bool is_file() const { return true; }
// The name and types should match POSIX. // The name and types should match POSIX.
uint32_t s_fileno; uint32_t s_fileno;
+4
View File
@@ -5,6 +5,8 @@
#include <zlib.h> #include <zlib.h>
#include <torrent/exceptions.h> #include <torrent/exceptions.h>
#include "utils/functional.h"
namespace utils { namespace utils {
void void
@@ -21,6 +23,8 @@ gzip_compress_to_vector(const char* buffer, unsigned int length, std::vector<cha
if (deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, window_bits | gzip_encoding, gzip_level, Z_DEFAULT_STRATEGY) != Z_OK) if (deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, window_bits | gzip_encoding, gzip_level, Z_DEFAULT_STRATEGY) != Z_OK)
throw torrent::internal_error("gzip_compress_to_vector(...) could not initialize gzip deflate."); throw torrent::internal_error("gzip_compress_to_vector(...) could not initialize gzip deflate.");
scope_guard guard([&zs]() { deflateEnd(&zs); });
auto max_response_size = deflateBound(&zs, length); auto max_response_size = deflateBound(&zs, length);
output.resize(offset + max_response_size); output.resize(offset + max_response_size);

Some files were not shown because too many files have changed in this diff Show More