From 11976871ac9099e463fc0af172bb7b1d34c3469a Mon Sep 17 00:00:00 2001 From: rakshasa Date: Wed, 15 Aug 2007 21:38:44 +0000 Subject: [PATCH] * Dynamically sized SCGI read buffer. * Added 'call_command' that takes a view as the first parameter and then a list of commands. It will return a list of lists containing the results from those commands. * Fixed the xmlrpc-c-config usage. git-svn-id: svn://rakshasa.no/libtorrent/trunk/rtorrent@954 e378c898-3ddf-0310-93e7-cc216c733640 --- scripts/checks.m4 | 2 +- src/command_events.cc | 50 ++++++++++++++++--------- src/rpc/parse_commands.cc | 2 +- src/rpc/parse_commands.h | 1 + src/rpc/scgi_task.cc | 78 +++++++++++++++++++++++++-------------- src/rpc/scgi_task.h | 6 +++ 6 files changed, 92 insertions(+), 47 deletions(-) diff --git a/scripts/checks.m4 b/scripts/checks.m4 index 1ad8e2b8..55323d0c 100644 --- a/scripts/checks.m4 +++ b/scripts/checks.m4 @@ -349,7 +349,7 @@ AC_DEFUN([TORRENT_WITH_XMLRPC_C], [ else if eval xmlrpc-c-config --version 2>/dev/null >/dev/null; then CXXFLAGS="$CXXFLAGS `xmlrpc-c-config --cflags server-util`" - LIBS="$LIBS -lxmlrpc_server `xmlrpc-c-config --libs server-util`" + LIBS="$LIBS `xmlrpc-c-config server-util --libs`" AC_TRY_LINK( [ #include diff --git a/src/command_events.cc b/src/command_events.cc index b07fc874..646a0a36 100644 --- a/src/command_events.cc +++ b/src/command_events.cc @@ -270,33 +270,47 @@ apply_download_list(const torrent::Object& rawArgs) { torrent::Object apply_call_download(const torrent::Object& rawArgs) { const torrent::Object::list_type& args = rawArgs.as_list(); - torrent::Object::list_type::const_iterator argsItr = args.begin(); - if (argsItr == args.end() || ++argsItr == args.end()) + if (args.empty()) throw torrent::input_error("Too few arguments."); - const torrent::Object::string_type& infoHash = args.begin()->as_string(); +// const torrent::Object::string_type& infoHash = args.begin()->as_string(); - core::DownloadList* dList = control->core()->download_list(); - core::DownloadList::iterator dItr = dList->end(); +// core::DownloadList* dList = control->core()->download_list(); +// core::DownloadList::iterator dItr = dList->end(); - if (infoHash.size() == 40) - dItr = dList->find_hex(infoHash.c_str()); +// if (infoHash.size() == 40) +// dItr = dList->find_hex(infoHash.c_str()); - if (dItr == dList->end()) - throw torrent::input_error("Not a valid info-hash."); +// if (dItr == dList->end()) +// throw torrent::input_error("Not a valid info-hash."); - torrent::Object result; - const char* command = (argsItr++)->as_string().c_str(); + core::ViewManager* viewManager = control->view_manager(); + core::ViewManager::iterator viewItr; - if (argsItr == args.end()) - result = rpc::call_command_d(command, *dItr, torrent::Object()); - else if (argsItr == --args.end()) - result = rpc::call_command_d(command, *dItr, *argsItr); + if (!args.front().as_string().empty()) + viewItr = viewManager->find(args.front().as_string()); else - result = rpc::call_command_d_range(command, *dItr, argsItr, args.end()); + viewItr = viewManager->find("default"); - return result; + if (viewItr == viewManager->end()) + throw torrent::input_error("Could not find view."); + + // Add some pre-parsing of the commands, so we don't spend time + // parsing and searching command map for every single call. + torrent::Object resultRaw(torrent::Object::TYPE_LIST); + torrent::Object::list_type& result = resultRaw.as_list(); + + for (core::View::const_iterator vItr = (*viewItr)->begin_visible(), vLast = (*viewItr)->end_visible(); vItr != vLast; vItr++) { + torrent::Object::list_type& row = result.insert(result.end(), torrent::Object(torrent::Object::TYPE_LIST))->as_list(); + + for (torrent::Object::list_type::const_iterator cItr = ++args.begin(), cLast = args.end(); cItr != args.end(); cItr++) { + const std::string& cmd = cItr->as_string(); + row.push_back(rpc::parse_command_d_single(*vItr, cmd.c_str(), cmd.c_str() + cmd.size())); + } + } + + return resultRaw; } void @@ -340,5 +354,5 @@ initialize_command_events() { ADD_COMMAND_VALUE_UN("close_low_diskspace", std::ptr_fun(&apply_close_low_diskspace)); ADD_COMMAND_LIST("download_list", rak::ptr_fn(&apply_download_list)); -// ADD_COMMAND_LIST("call_download", rak::ptr_fn(&apply_call_download)); + ADD_COMMAND_LIST("call_download", rak::ptr_fn(&apply_call_download)); } diff --git a/src/rpc/parse_commands.cc b/src/rpc/parse_commands.cc index d83284e0..7c5c553f 100644 --- a/src/rpc/parse_commands.cc +++ b/src/rpc/parse_commands.cc @@ -204,7 +204,7 @@ parse_command_name(const char* first, const char* last, std::string* dest) { if (first == last || !std::isalpha(*first)) throw torrent::input_error("Invalid start of name."); - for ( ; first != last && (std::isalnum(*first) || *first == '_'); ++first) + for ( ; first != last && (std::isalnum(*first) || *first == '_' || *first == '.'); ++first) dest->push_back(*first); return first; diff --git a/src/rpc/parse_commands.h b/src/rpc/parse_commands.h index 0e5efaf2..898360cb 100644 --- a/src/rpc/parse_commands.h +++ b/src/rpc/parse_commands.h @@ -110,6 +110,7 @@ inline torrent::Object call_command_t(const char* key, torrent::Tracker* tracker inline torrent::Object call_command_d_range(const char* key, core::Download* download, torrent::Object::list_type::const_iterator first, torrent::Object::list_type::const_iterator last) { + // Change to using range ctor. torrent::Object rawArgs(torrent::Object::TYPE_LIST); torrent::Object::list_type& args = rawArgs.as_list(); diff --git a/src/rpc/scgi_task.cc b/src/rpc/scgi_task.cc index 28dec87f..276e7565 100644 --- a/src/rpc/scgi_task.cc +++ b/src/rpc/scgi_task.cc @@ -58,10 +58,11 @@ namespace rpc { void SCgiTask::open(SCgi* parent, int fd) { - m_parent = parent; + m_parent = parent; m_fileDesc = fd; - m_buffer = new char[(m_bufferSize = 2048)]; + m_buffer = new char[(m_bufferSize = default_buffer_size) + 1]; m_position = m_buffer; + m_body = NULL; control->poll()->open(this); control->poll()->insert_read(this); @@ -94,7 +95,7 @@ SCgiTask::close() { void SCgiTask::event_read() { - int bytes = ::recv(m_fileDesc, m_position, m_bufferSize - 1 - (m_position - m_buffer), 0); + int bytes = ::recv(m_fileDesc, m_position, m_bufferSize - (m_position - m_buffer), 0); if (bytes == -1) { if (!rak::error_number::current().is_blocked_momentary()) @@ -103,45 +104,68 @@ SCgiTask::event_read() { return; } + // The buffer has space to nul-terminate to ease the parsing below. m_position += bytes; *m_position = '\0'; - // Don't bother caching the parsed values, as we're likely to - // receive all the data we need the first time. - char* current; - char* contentPos; + if (m_body == NULL) { + // Don't bother caching the parsed values, as we're likely to + // receive all the data we need the first time. + char* current; - int headerSize = strtol(m_buffer, ¤t, 0); - int contentSize; + int contentSize; + int headerSize = strtol(m_buffer, ¤t, 0); - if (current == m_buffer || current == m_position) - // Need to validate the header size. - return; + if (current == m_buffer || current == m_position) + return; - if (*current != ':' || headerSize < 17) - goto event_read_failed; + if (*current != ':' || headerSize < 17 || headerSize > max_header_size) + goto event_read_failed; - if (std::distance(++current, m_position) < headerSize + 1) - return; + if (std::distance(++current, m_position) < headerSize + 1) + return; - if (std::memcmp(current, "CONTENT_LENGTH", 15) != 0) - goto event_read_failed; + if (std::memcmp(current, "CONTENT_LENGTH", 15) != 0) + goto event_read_failed; - contentSize = strtol(current + 15, &contentPos, 0); + char* contentPos; + contentSize = strtol(current + 15, &contentPos, 0); - if (*contentPos != '\0' || contentSize <= 0) - goto event_read_failed; + if (*contentPos != '\0' || contentSize <= 0 || contentSize > max_content_size) + goto event_read_failed; - // Start of the data. - current += headerSize + 1; + m_body = current + headerSize + 1; + headerSize = std::distance(m_buffer, m_body); - if (std::distance(current, m_position) < contentSize) + if ((unsigned int)(contentSize + headerSize) < m_bufferSize) { + m_bufferSize = contentSize + headerSize; + + } else if ((unsigned int)(contentSize + headerSize) <= default_buffer_size) { + m_bufferSize = contentSize; + + std::memmove(m_buffer, m_body, std::distance(m_body, m_position)); + m_position = m_buffer + std::distance(m_body, m_position); + m_body = m_buffer; + + } else { + char* tmp = new char[(m_bufferSize = contentSize)]; + std::memcpy(tmp, m_body, std::distance(m_body, m_position)); + delete [] m_buffer; + + m_position = tmp + std::distance(m_body, m_position); + m_buffer = tmp; + m_body = tmp; + } + } + + if ((unsigned int)std::distance(m_buffer, m_position) != m_bufferSize) return; control->poll()->remove_read(this); control->poll()->insert_write(this); - if (!m_parent->receive_call(this, current, contentSize)) + // Close if the call failed, else stay open to write back data. + if (!m_parent->receive_call(this, m_body, m_bufferSize - std::distance(m_buffer, m_body))) close(); return; @@ -175,7 +199,8 @@ SCgiTask::event_error() { bool SCgiTask::receive_write(const char* buffer, uint32_t length) { - if (length + 256 > m_bufferSize) { + // Need to cast due to a bug in MacOSX gcc-4.0.1. + if (length + 256 > std::max(m_bufferSize, (unsigned int)default_buffer_size)) { delete [] m_buffer; m_buffer = new char[length + 256]; } @@ -187,7 +212,6 @@ SCgiTask::receive_write(const char* buffer, uint32_t length) { m_bufferSize = length + headerSize; std::memcpy(m_buffer + headerSize, buffer, length); - event_write(); return true; diff --git a/src/rpc/scgi_task.h b/src/rpc/scgi_task.h index f9a1f20b..16a8c60e 100644 --- a/src/rpc/scgi_task.h +++ b/src/rpc/scgi_task.h @@ -49,6 +49,10 @@ class SCgi; class SCgiTask : public torrent::Event { public: + static const unsigned int default_buffer_size = 2047; + static const int max_header_size = 2000; + static const int max_content_size = (128 << 10); + SCgiTask() { m_fileDesc = -1; } bool is_open() const { return m_fileDesc != -1; } @@ -70,6 +74,8 @@ private: char* m_buffer; char* m_position; + char* m_body; + unsigned int m_bufferSize; };