From 49bb643cf30a618f39e601fe58b2b41bb178503c Mon Sep 17 00:00:00 2001 From: rakshasa Date: Fri, 22 Feb 2008 14:11:40 +0000 Subject: [PATCH] * Added system.time{,_seconds,_usec} commands. * Cleaned up XMLRPC handling and added support for empty targets so it will handle print and cat properly. git-svn-id: svn://rakshasa.no/libtorrent/trunk/rtorrent@1039 e378c898-3ddf-0310-93e7-cc216c733640 --- rak/timer.h | 2 + src/command_events.cc | 2 +- src/command_local.cc | 30 +-- src/command_ui.cc | 33 ++-- src/rpc/xmlrpc.cc | 424 ++++++++++++++++++++---------------------- 5 files changed, 236 insertions(+), 255 deletions(-) diff --git a/rak/timer.h b/rak/timer.h index cdb875ee..701a752d 100644 --- a/rak/timer.h +++ b/rak/timer.h @@ -63,6 +63,8 @@ class timer { } static timer current(); + static int64_t current_seconds() { return current().seconds(); } + static int64_t current_usec() { return current().usec(); } static timer from_seconds(uint32_t seconds) { return rak::timer((uint64_t)seconds * 1000000); } static timer from_milliseconds(uint32_t seconds) { return rak::timer((uint64_t)seconds * 1000); } diff --git a/src/command_events.cc b/src/command_events.cc index 776fc549..836600f3 100644 --- a/src/command_events.cc +++ b/src/command_events.cc @@ -213,7 +213,7 @@ apply_schedule(const torrent::Object& rawArgs) { torrent::Object apply_load(int flags, const torrent::Object& rawArgs) { - const torrent::Object::list_type& args = rawArgs.as_list(); + const torrent::Object::list_type& args = rawArgs.as_list(); torrent::Object::list_const_iterator argsItr = args.begin(); if (argsItr == args.end()) diff --git a/src/command_local.cc b/src/command_local.cc index 679dbb2f..fc095ca4 100644 --- a/src/command_local.cc +++ b/src/command_local.cc @@ -131,8 +131,12 @@ initialize_command_local() { ADD_COMMAND_VOID("system.file_status_cache.size", rak::make_mem_fun((utils::FileStatusCache::base_type*)control->core()->file_status_cache(), &utils::FileStatusCache::size)); ADD_COMMAND_VOID("system.file_status_cache.prune", rak::make_mem_fun(control->core()->file_status_cache(), &utils::FileStatusCache::prune)); - ADD_COMMAND_VALUE_SET_OCT("system.", "umask", std::ptr_fun(&umask)); - ADD_COMMAND_STRING_PREFIX("system.", "cwd", std::ptr_fun(system_set_cwd), rak::ptr_fun(&system_get_cwd)); + ADD_COMMAND_VOID("system.time", rak::make_mem_fun(&cachedTime, &rak::timer::seconds)); + ADD_COMMAND_VOID("system.time_seconds", rak::ptr_fun(&rak::timer::current_seconds)); + ADD_COMMAND_VOID("system.time_usec", rak::ptr_fun(&rak::timer::current_usec)); + + ADD_COMMAND_VALUE_SET_OCT("system.", "umask", std::ptr_fun(&umask)); + ADD_COMMAND_STRING_PREFIX("system.", "cwd", std::ptr_fun(system_set_cwd), rak::ptr_fun(&system_get_cwd)); ADD_VARIABLE_STRING("name", ""); @@ -140,19 +144,19 @@ initialize_command_local() { ADD_VARIABLE_VALUE("split_file_size", -1); ADD_VARIABLE_STRING("split_suffix", ".part"); - ADD_COMMAND_VOID("get_memory_usage", rak::make_mem_fun(chunkManager, &CM_t::memory_usage)); - ADD_COMMAND_VALUE_TRI("max_memory_usage", rak::make_mem_fun(chunkManager, &CM_t::set_max_memory_usage), rak::make_mem_fun(chunkManager, &CM_t::max_memory_usage)); - ADD_COMMAND_VALUE_TRI("safe_sync", rak::make_mem_fun(chunkManager, &CM_t::set_safe_sync), rak::make_mem_fun(chunkManager, &CM_t::safe_sync)); - ADD_COMMAND_VOID("get_safe_free_diskspace", rak::make_mem_fun(chunkManager, &CM_t::safe_free_diskspace)); - ADD_COMMAND_VALUE_TRI("timeout_sync", rak::make_mem_fun(chunkManager, &CM_t::set_timeout_sync), rak::make_mem_fun(chunkManager, &CM_t::timeout_sync)); - ADD_COMMAND_VALUE_TRI("timeout_safe_sync", rak::make_mem_fun(chunkManager, &CM_t::set_timeout_safe_sync), rak::make_mem_fun(chunkManager, &CM_t::timeout_safe_sync)); + ADD_COMMAND_VOID("get_memory_usage", rak::make_mem_fun(chunkManager, &CM_t::memory_usage)); + ADD_COMMAND_VALUE_TRI("max_memory_usage", rak::make_mem_fun(chunkManager, &CM_t::set_max_memory_usage), rak::make_mem_fun(chunkManager, &CM_t::max_memory_usage)); + ADD_COMMAND_VALUE_TRI("safe_sync", rak::make_mem_fun(chunkManager, &CM_t::set_safe_sync), rak::make_mem_fun(chunkManager, &CM_t::safe_sync)); + ADD_COMMAND_VOID("get_safe_free_diskspace", rak::make_mem_fun(chunkManager, &CM_t::safe_free_diskspace)); + ADD_COMMAND_VALUE_TRI("timeout_sync", rak::make_mem_fun(chunkManager, &CM_t::set_timeout_sync), rak::make_mem_fun(chunkManager, &CM_t::timeout_sync)); + ADD_COMMAND_VALUE_TRI("timeout_safe_sync", rak::make_mem_fun(chunkManager, &CM_t::set_timeout_safe_sync), rak::make_mem_fun(chunkManager, &CM_t::timeout_safe_sync)); - ADD_COMMAND_VALUE_TRI("preload_type", rak::make_mem_fun(chunkManager, &CM_t::set_preload_type), rak::make_mem_fun(chunkManager, &CM_t::preload_type)); - ADD_COMMAND_VALUE_TRI("preload_min_size", rak::make_mem_fun(chunkManager, &CM_t::set_preload_min_size), rak::make_mem_fun(chunkManager, &CM_t::preload_min_size)); - ADD_COMMAND_VALUE_TRI_KB("preload_required_rate", rak::make_mem_fun(chunkManager, &CM_t::set_preload_required_rate), rak::make_mem_fun(chunkManager, &CM_t::preload_required_rate)); + ADD_COMMAND_VALUE_TRI("preload_type", rak::make_mem_fun(chunkManager, &CM_t::set_preload_type), rak::make_mem_fun(chunkManager, &CM_t::preload_type)); + ADD_COMMAND_VALUE_TRI("preload_min_size", rak::make_mem_fun(chunkManager, &CM_t::set_preload_min_size), rak::make_mem_fun(chunkManager, &CM_t::preload_min_size)); + ADD_COMMAND_VALUE_TRI_KB("preload_required_rate", rak::make_mem_fun(chunkManager, &CM_t::set_preload_required_rate), rak::make_mem_fun(chunkManager, &CM_t::preload_required_rate)); - ADD_COMMAND_VOID("get_stats_preloaded", rak::make_mem_fun(chunkManager, &CM_t::stats_preloaded)); - ADD_COMMAND_VOID("get_stats_not_preloaded", rak::make_mem_fun(chunkManager, &CM_t::stats_not_preloaded)); + ADD_COMMAND_VOID("get_stats_preloaded", rak::make_mem_fun(chunkManager, &CM_t::stats_preloaded)); + ADD_COMMAND_VOID("get_stats_not_preloaded", rak::make_mem_fun(chunkManager, &CM_t::stats_not_preloaded)); ADD_VARIABLE_STRING("directory", "./"); diff --git a/src/command_ui.cc b/src/command_ui.cc index a63a6f03..506391c8 100644 --- a/src/command_ui.cc +++ b/src/command_ui.cc @@ -145,29 +145,24 @@ apply_cat(rpc::target_type target, const torrent::Object& rawArgs) { } torrent::Object -apply_to_date(const torrent::Object& rawArgs) { +apply_to_time(int flags, const torrent::Object& rawArgs) { + std::tm *u; time_t t = (uint64_t)rawArgs.as_value(); - std::tm *u = std::gmtime(&t); + + if (flags & 0x1) + u = std::localtime(&t); + else + u = std::gmtime(&t); if (u == NULL) return torrent::Object(); char buffer[11]; - snprintf(buffer, 11, "%02u/%02u/%04u", u->tm_mday, (u->tm_mon + 1), (1900 + u->tm_year)); - - return std::string(buffer); -} -torrent::Object -apply_to_time(const torrent::Object& rawArgs) { - time_t t = (uint64_t)rawArgs.as_value(); - std::tm *u = std::gmtime(&t); - - if (u == NULL) - return torrent::Object(); - - char buffer[9]; - snprintf(buffer, 9, "%2d:%02d:%02d", u->tm_hour, u->tm_min, u->tm_sec); + if (flags & 0x2) + snprintf(buffer, 11, "%02u/%02u/%04u", u->tm_mday, (u->tm_mon + 1), (1900 + u->tm_year)); + else + snprintf(buffer, 9, "%2d:%02d:%02d", u->tm_hour, u->tm_min, u->tm_sec); return std::string(buffer); } @@ -311,8 +306,10 @@ initialize_command_ui() { // support for seperation of commands and literals. ADD_ANY_NONE("branch", rak::bind_ptr_fn(&apply_if, 1)); - ADD_COMMAND_VALUE("to_date", rak::ptr_fn(&apply_to_date)); - ADD_COMMAND_VALUE("to_time", rak::ptr_fn(&apply_to_time)); + ADD_COMMAND_VALUE("to_gm_time", rak::bind_ptr_fn(&apply_to_time, 0)); + ADD_COMMAND_VALUE("to_gm_date", rak::bind_ptr_fn(&apply_to_time, 0x2)); + ADD_COMMAND_VALUE("to_time", rak::bind_ptr_fn(&apply_to_time, 0x1)); + ADD_COMMAND_VALUE("to_date", rak::bind_ptr_fn(&apply_to_time, 0x1 | 0x2)); ADD_COMMAND_VALUE("to_elapsed_time", rak::ptr_fn(&apply_to_elapsed_time)); ADD_COMMAND_VALUE("to_kb", rak::ptr_fn(&apply_to_kb)); ADD_COMMAND_VALUE("to_mb", rak::ptr_fn(&apply_to_mb)); diff --git a/src/rpc/xmlrpc.cc b/src/rpc/xmlrpc.cc index 767b8187..525f4c57 100644 --- a/src/rpc/xmlrpc.cc +++ b/src/rpc/xmlrpc.cc @@ -51,8 +51,136 @@ namespace rpc { #ifdef HAVE_XMLRPC_C +class xmlrpc_error : public torrent::base_error { +public: + xmlrpc_error(xmlrpc_env* env) : m_type(env->fault_code), m_msg(env->fault_string) {} + xmlrpc_error(int type, char* msg) : m_type(type), m_msg(msg) {} + virtual ~xmlrpc_error() throw() {} + + virtual int type() const throw() { return m_type; } + virtual const char* what() const throw() { return m_msg; } + +private: + int m_type; + char* m_msg; +}; + +torrent::Object xmlrpc_to_object(xmlrpc_env* env, xmlrpc_value* value, int callType = 0, rpc::target_type* target = NULL); + +inline torrent::Object +xmlrpc_list_entry_to_object(xmlrpc_env* env, xmlrpc_value* src, int index) { + xmlrpc_value* tmp; + xmlrpc_array_read_item(env, src, index, &tmp); + + if (env->fault_occurred) + throw xmlrpc_error(env); + + torrent::Object obj = xmlrpc_to_object(env, tmp); + xmlrpc_DECREF(tmp); + + return obj; +} + +int64_t +xmlrpc_list_entry_to_value(xmlrpc_env* env, xmlrpc_value* src, int index) { + xmlrpc_value* tmp; + xmlrpc_array_read_item(env, src, index, &tmp); + + if (env->fault_occurred) + throw xmlrpc_error(env); + + switch (xmlrpc_value_type(tmp)) { + case XMLRPC_TYPE_INT: + int v; + xmlrpc_read_int(env, tmp, &v); + xmlrpc_DECREF(tmp); + return v; + +#ifdef XMLRPC_HAVE_I8 + case XMLRPC_TYPE_I8: + long long v2; + xmlrpc_read_i8(env, tmp, &v2); + xmlrpc_DECREF(tmp); + return v2; +#endif + + case XMLRPC_TYPE_STRING: + { + const char* str; + xmlrpc_read_string(env, tmp, &str); + + if (env->fault_occurred) + throw xmlrpc_error(env); + + const char* end = str; + int64_t v3 = ::strtoll(str, (char**)&end, 0); + + ::free((void*)str); + + if (*str == '\0' || *end != '\0') + throw xmlrpc_error(XMLRPC_TYPE_ERROR, "Invalid index."); + + return v3; + } + + default: + xmlrpc_DECREF(tmp); + throw xmlrpc_error(XMLRPC_TYPE_ERROR, "Invalid type found."); + } +} + +// Consider making a helper function that creates a target_type from a +// torrent::Object, then we can just use xmlrpc_to_object. +rpc::target_type +xmlrpc_to_target(xmlrpc_env* env, xmlrpc_value* value) { + switch (xmlrpc_value_type(value)) { + case XMLRPC_TYPE_STRING: + const char* str; + xmlrpc_read_string(env, value, &str); + + if (env->fault_occurred) + throw xmlrpc_error(env); + + if (std::strlen(str) == 40) { + core::Download* download = xmlrpc.get_slot_find_download()(str); + ::free((void*)str); + + if (download == NULL) + throw xmlrpc_error(XMLRPC_TYPE_ERROR, "Could not find info-hash."); + + return rpc::make_target(download); + + } else if (std::strlen(str) == 0) { + ::free((void*)str); + return rpc::make_target(); + } + + ::free((void*)str); + throw xmlrpc_error(XMLRPC_TYPE_ERROR, "Unsupported target type found."); + + default: + return rpc::make_target(); + } +} + +rpc::target_type +xmlrpc_to_index_type(int index, int callType, core::Download* download) { + void* result; + + switch (callType) { + case XmlRpc::call_file: result = xmlrpc.get_slot_find_file()(download, index); break; + case XmlRpc::call_tracker: result = xmlrpc.get_slot_find_tracker()(download, index); break; + default: result = NULL; break; + } + + if (result == NULL) + throw xmlrpc_error(XMLRPC_TYPE_ERROR, "Invalid index."); + + return rpc::make_target(callType, result); +} + torrent::Object -xmlrpc_to_object(xmlrpc_env* env, xmlrpc_value* value) { +xmlrpc_to_object(xmlrpc_env* env, xmlrpc_value* value, int callType, rpc::target_type* target) { switch (xmlrpc_value_type(value)) { case XMLRPC_TYPE_INT: int v; @@ -73,19 +201,28 @@ xmlrpc_to_object(xmlrpc_env* env, xmlrpc_value* value) { // case XMLRPC_TYPE_DATETIME: case XMLRPC_TYPE_STRING: - { - const char* valueString; - xmlrpc_read_string(env, value, &valueString); - if (env->fault_occurred) + if (callType != XmlRpc::call_generic) { + // When the call type is not supposed to be void, we'll try to + // convert it to a command target. It's not that important that + // it is converted to the right type here, as an mismatch will + // be caught when executing the command. + *target = xmlrpc_to_target(env, value); return torrent::Object(); - torrent::Object result = torrent::Object(std::string(valueString)); + } else { + const char* valueString; + xmlrpc_read_string(env, value, &valueString); - // Urgh, seriously? - ::free((void*)valueString); - return result; - } + if (env->fault_occurred) + throw xmlrpc_error(env); + + torrent::Object result = torrent::Object(std::string(valueString)); + + // Urgh, seriously? + ::free((void*)valueString); + return result; + } case XMLRPC_TYPE_BASE64: { @@ -95,7 +232,7 @@ xmlrpc_to_object(xmlrpc_env* env, xmlrpc_value* value) { xmlrpc_read_base64(env, value, &valueSize, (const unsigned char**)&valueString); if (env->fault_occurred) - return torrent::Object(); + throw xmlrpc_error(env); torrent::Object result = torrent::Object(std::string(valueString, valueSize)); @@ -106,28 +243,56 @@ xmlrpc_to_object(xmlrpc_env* env, xmlrpc_value* value) { case XMLRPC_TYPE_ARRAY: { - torrent::Object result = torrent::Object::create_list(); - torrent::Object::list_type& listRef = result.as_list(); - + unsigned int current = 0; unsigned int last = xmlrpc_array_size(env, value); if (env->fault_occurred) - return torrent::Object(); + throw xmlrpc_error(env); + + if (callType != XmlRpc::call_generic) { + if (last < 1) + throw xmlrpc_error(XMLRPC_TYPE_ERROR, "Too few arguments."); - // Move this into a helper function. - for (unsigned int i = 0; i != last; i++) { - // Need to decref. xmlrpc_value* tmp; - xmlrpc_array_read_item(env, value, i, &tmp); + xmlrpc_array_read_item(env, value, current++, &tmp); if (env->fault_occurred) - return torrent::Object(); + throw xmlrpc_error(env); - listRef.push_back(xmlrpc_to_object(env, tmp)); + *target = xmlrpc_to_target(env, tmp); xmlrpc_DECREF(tmp); + + if (env->fault_occurred) + throw xmlrpc_error(env); + + if (target->first == XmlRpc::call_download && + (callType == XmlRpc::call_file || callType == XmlRpc::call_tracker)) { + // If we have a download target and the call type requires + // another contained type, then we try to use the next + // parameter as the index to support old-style calls. + + if (current == last) + throw xmlrpc_error(XMLRPC_TYPE_ERROR, "Too few arguments."); + + *target = xmlrpc_to_index_type(xmlrpc_list_entry_to_value(env, value, current++), callType, (core::Download*)target->second); + } } - return result; + if (current + 1 < last) { + torrent::Object result = torrent::Object::create_list(); + torrent::Object::list_type& listRef = result.as_list(); + + while (current != last) + listRef.push_back(xmlrpc_list_entry_to_object(env, value, current++)); + + return result; + + } else if (current + 1 == last) { + return xmlrpc_list_entry_to_object(env, value, current); + + } else { + return torrent::Object(); + } } // case XMLRPC_TYPE_STRUCT: @@ -135,202 +300,10 @@ xmlrpc_to_object(xmlrpc_env* env, xmlrpc_value* value) { // case XMLRPC_TYPE_NIL: // case XMLRPC_TYPE_DEAD: default: - xmlrpc_env_set_fault(env, XMLRPC_TYPE_ERROR, "Unsupported type found."); - return torrent::Object(); + throw xmlrpc_error(XMLRPC_TYPE_ERROR, "Unsupported type found."); } } -core::Download* -xmlrpc_to_download(xmlrpc_env* env, xmlrpc_value* value) { - core::Download* download = NULL; - - switch (xmlrpc_value_type(value)) { - case XMLRPC_TYPE_STRING: - { - const char* str; - xmlrpc_read_string(env, value, &str); - - if (env->fault_occurred) - return NULL; - - if (std::strlen(str) != 40 || - (download = xmlrpc.get_slot_find_download()(str)) == NULL) - xmlrpc_env_set_fault(env, XMLRPC_TYPE_ERROR, "Could not find info-hash."); - - // Urgh, seriously? - ::free((void*)str); - return download; - } - - default: - xmlrpc_env_set_fault(env, XMLRPC_TYPE_ERROR, "Unsupported type found."); - return NULL; - } -} - -void* -xmlrpc_to_index_type(xmlrpc_env* env, xmlrpc_value* value, int callType, core::Download* download) { - int index; - - switch (xmlrpc_value_type(value)) { - case XMLRPC_TYPE_INT: - xmlrpc_read_int(env, value, &index); - break; - -#ifdef XMLRPC_HAVE_I8 - case XMLRPC_TYPE_I8: - long long v2; - xmlrpc_read_i8(env, value, &v2); - - index = v2; - break; -#endif - - case XMLRPC_TYPE_STRING: - { - const char* str; - xmlrpc_read_string(env, value, &str); - - if (env->fault_occurred) - return NULL; - - const char* end = str; - index = ::strtol(str, (char**)&end, 0); - - ::free((void*)str); - - if (*str == '\0' || *end != '\0') { - xmlrpc_env_set_fault(env, XMLRPC_TYPE_ERROR, "Invalid index."); - return NULL; - } - - break; - } - - default: - xmlrpc_env_set_fault(env, XMLRPC_TYPE_ERROR, "Unsupported type found."); - return NULL; - } - - if (env->fault_occurred) - return NULL; - - void* result; - - switch (callType) { - case XmlRpc::call_file: result = xmlrpc.get_slot_find_file()(download, index); break; - case XmlRpc::call_tracker: result = xmlrpc.get_slot_find_tracker()(download, index); break; - default: result = NULL; break; - } - - if (result == NULL) - xmlrpc_env_set_fault(env, XMLRPC_TYPE_ERROR, "Invalid index."); - - return result; -} - -// This should really be cleaned up and support for an array of -// downloads should be added. -torrent::Object -xmlrpc_to_object_target(xmlrpc_env* env, xmlrpc_value* value, int callType, void** target) { - switch (xmlrpc_value_type(value)) { - case XMLRPC_TYPE_STRING: - if (callType != XmlRpc::call_download) { - xmlrpc_env_set_fault(env, XMLRPC_TYPE_ERROR, "Unsupported type found."); - break; - } - - *target = xmlrpc_to_download(env, value); - break; - - case XMLRPC_TYPE_ARRAY: - { - unsigned int current = 0; - unsigned int last = xmlrpc_array_size(env, value); - - if (env->fault_occurred) - break; - - if (last < 1) { - xmlrpc_env_set_fault(env, XMLRPC_TYPE_ERROR, "Too few arguments."); - break; - } - - { - xmlrpc_value* tmp; - xmlrpc_array_read_item(env, value, current++, &tmp); - - if (env->fault_occurred) - break; - - *target = xmlrpc_to_download(env, tmp); - xmlrpc_DECREF(tmp); - - if (env->fault_occurred) - break; - } - - if (callType == XmlRpc::call_file || callType == XmlRpc::call_tracker) { - if (current == last) { - xmlrpc_env_set_fault(env, XMLRPC_TYPE_ERROR, "Too few arguments."); - break; - } - - xmlrpc_value* tmp; - xmlrpc_array_read_item(env, value, current++, &tmp); - - if (env->fault_occurred) - break; - - *target = xmlrpc_to_index_type(env, tmp, callType, (core::Download*)*target); - xmlrpc_DECREF(tmp); - - if (env->fault_occurred) - break; - } - - torrent::Object result; - - if (current + 1 < last) { - result = torrent::Object::create_list(); - torrent::Object::list_type& listRef = result.as_list(); - - // Move this into a helper function? - while (current != last) { - xmlrpc_value* tmp; - xmlrpc_array_read_item(env, value, current++, &tmp); - - if (env->fault_occurred) - break; - - listRef.push_back(xmlrpc_to_object(env, tmp)); - xmlrpc_DECREF(tmp); - } - - return result; - - } else if (current + 1 == last) { - // Need to decref. - xmlrpc_value* tmp; - xmlrpc_array_read_item(env, value, current, &tmp); - - if (env->fault_occurred) - break; - - result = xmlrpc_to_object(env, tmp); - xmlrpc_DECREF(tmp); - } - - return result; - } - - default: - xmlrpc_env_set_fault(env, XMLRPC_TYPE_ERROR, "Unsupported type found."); - } - - return torrent::Object(); -} - xmlrpc_value* object_to_xmlrpc(xmlrpc_env* env, const torrent::Object& object) { switch (object.type()) { @@ -380,15 +353,20 @@ xmlrpc_call_command(xmlrpc_env* env, xmlrpc_value* args, void* voidServerInfo) { return NULL; } - int type = itr->second.target(); - void* target = NULL; - torrent::Object object = (type == 0) ? xmlrpc_to_object(env, args) : xmlrpc_to_object_target(env, args, type, &target); - - if (env->fault_occurred) - return NULL; - try { - return object_to_xmlrpc(env, rpc::commands.call_command(itr, object, rpc::make_target(type, target))); + torrent::Object object; + rpc::target_type target = rpc::make_target(); + + xmlrpc_to_object(env, args, itr->second.target(), &target).swap(object); + + if (env->fault_occurred) + return NULL; + + return object_to_xmlrpc(env, rpc::commands.call_command(itr, object, target)); + + } catch (xmlrpc_error& e) { + xmlrpc_env_set_fault(env, e.type(), e.what()); + return NULL; } catch (torrent::local_error& e) { xmlrpc_env_set_fault(env, XMLRPC_PARSE_ERROR, e.what());