diff --git a/rak/path.h b/rak/path.h index 08adaa1b..64daf355 100644 --- a/rak/path.h +++ b/rak/path.h @@ -58,6 +58,31 @@ path_expand(const std::string& path) { return home + path.substr(1); } +// Don't inline this... +// +// Same strlcpy as found in *bsd. +inline size_t +strlcpy(char *dest, const char *src, size_t size) { + size_t n = size; + const char* first = src; + + if (n != 0) { + while (--n != 0) + if ((*dest++ = *src++) == '\0') + break; + } + + if (n == 0) { + if (size != 0) + *dest = '\0'; + + while (*src++) + ; + } + + return src - first - 1; +} + inline char* path_expand(const char* src, char* first, char* last) { if (*src == '~') { @@ -67,10 +92,14 @@ path_expand(const char* src, char* first, char* last) { return first; first += strlcpy(first, home, std::distance(first, last)); + + if (first > last) + return last; + src++; } - return first + strlcpy(first, src, std::distance(first, last)); + return std::min(first + strlcpy(first, src, std::distance(first, last)), last); } } diff --git a/scripts/checks.m4 b/scripts/checks.m4 index 817c114e..1ad8e2b8 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 `xmlrpc-c-config --libs server-util` -lxmlrpc_server" + LIBS="$LIBS -lxmlrpc_server `xmlrpc-c-config --libs server-util`" AC_TRY_LINK( [ #include diff --git a/src/command_local.cc b/src/command_local.cc index 0c7531a1..b5547099 100644 --- a/src/command_local.cc +++ b/src/command_local.cc @@ -36,7 +36,9 @@ #include "config.h" +#include #include +#include #include #include @@ -53,6 +55,29 @@ typedef torrent::ChunkManager CM_t; +torrent::Object +apply_execute_log(const torrent::Object& rawArgs) { + if (rpc::execFile.log_fd() != -1) { + ::close(rpc::execFile.log_fd()); + rpc::execFile.set_log_fd(-1); + } + + if (rawArgs.is_string() && !rawArgs.as_string().empty()) { + int logFd = open(rak::path_expand(rawArgs.as_string()).c_str(), O_WRONLY | O_APPEND | O_CREAT, 0644); + + if (logFd < 0) + throw torrent::input_error("Could not open execute log file."); + + rpc::execFile.set_log_fd(logFd); + control->core()->push_log("Opened execute log file."); + + } else { + control->core()->push_log("Closed execute log file."); + } + + return torrent::Object(); +} + void initialize_command_local() { torrent::ChunkManager* chunkManager = torrent::chunk_manager(); @@ -87,4 +112,6 @@ initialize_command_local() { ADD_COMMAND_LIST("execute_nothrow", rak::bind2_mem_fn(&rpc::execFile, &rpc::ExecFile::execute_object, rpc::ExecFile::flag_expand_tilde)); ADD_COMMAND_LIST("execute_raw", rak::bind2_mem_fn(&rpc::execFile, &rpc::ExecFile::execute_object, rpc::ExecFile::flag_throw)); ADD_COMMAND_LIST("execute_raw_nothrow", rak::bind2_mem_fn(&rpc::execFile, &rpc::ExecFile::execute_object, 0)); + + ADD_COMMAND_STRING_UN("execute_log", std::ptr_fun(&apply_execute_log)); } diff --git a/src/command_ui.cc b/src/command_ui.cc index f1fbdedc..2719fbd2 100644 --- a/src/command_ui.cc +++ b/src/command_ui.cc @@ -111,24 +111,7 @@ apply_view_list(const torrent::Object&) { torrent::Object apply_print(const torrent::Object& rawArgs) { char buffer[1024]; - char* current = buffer; - - for (torrent::Object::list_type::const_iterator itr = rawArgs.as_list().begin(), last = rawArgs.as_list().end(); itr != last; itr++) { - switch (itr->type()) { - case torrent::Object::TYPE_STRING: - { - int len = std::min(itr->as_string().size(), buffer + 1024 - current); - - std::memcpy(current, itr->as_string().c_str(), len + 1); - current += len; - break; - } - case torrent::Object::TYPE_VALUE: - default: - current += snprintf(current, buffer + 1024 - current, "%lli", itr->as_value()); - break; - } - } + rpc::print_object(buffer, buffer + 1024, &rawArgs, 0); control->core()->push_log(buffer); return torrent::Object(); diff --git a/src/rpc/exec_file.cc b/src/rpc/exec_file.cc index 9b91be7b..00d77f19 100644 --- a/src/rpc/exec_file.cc +++ b/src/rpc/exec_file.cc @@ -36,25 +36,52 @@ #include "config.h" +#include #include #include #include #include #include "exec_file.h" +#include "parse.h" namespace rpc { +// Close m_logFd. + int ExecFile::execute(const char* file, char* const* argv) { + // Write the execued command and its parameters to the log fd. + if (m_logFd != -1) { + for (char* const* itr = argv; *itr != NULL; itr++) { + if (itr == argv) + write(m_logFd, "\n---\n", sizeof("\n---\n")); + else + write(m_logFd, " ", 1); + + write(m_logFd, *itr, std::strlen(*itr)); + } + + write(m_logFd, "\n---\n", sizeof("\n---\n")); + } + pid_t childPid = fork(); if (childPid == -1) throw torrent::input_error("ExecFile::execute(...) Fork failed."); if (childPid == 0) { + ::close(0); + ::close(1); + ::close(2); + + if (m_logFd != -1) { + dup2(m_logFd, 1); + dup2(m_logFd, 2); + } + // Close all fd's. - for (int i = 0, last = sysconf(_SC_OPEN_MAX); i != last; i++) + for (int i = 3, last = sysconf(_SC_OPEN_MAX); i != last; i++) ::close(i); int result = execvp(file, argv); @@ -68,6 +95,12 @@ ExecFile::execute(const char* file, char* const* argv) { throw torrent::internal_error("ExecFile::execute(...) waitpid failed."); // Check return value? + if (m_logFd) { + if (status == 0) + write(m_logFd, "\n--- Success ---\n", sizeof("\n--- Success ---\n")); + else + write(m_logFd, "\n--- Error ---\n", sizeof("\n--- Error ---\n")); + } return status; } @@ -91,35 +124,20 @@ ExecFile::execute_object(const torrent::Object& rawArgs, int flags) { if (argsCurrent == argsBuffer + max_args - 1) throw torrent::input_error("Too many arguments."); - switch (itr->type()) { - case torrent::Object::TYPE_STRING: - { - const std::string& str = itr->as_string(); + if (itr->is_string() && (!(flags & flag_expand_tilde) || *itr->as_string().c_str() != '~')) { + *argsCurrent = const_cast(itr->as_string().c_str()); - if ((flags & flag_expand_tilde) && *str.c_str() == '~') { - *argsCurrent = valueCurrent; - valueCurrent = rak::path_expand(str.c_str(), valueCurrent, valueBuffer + buffer_size) + 1; - } else { - *argsCurrent = const_cast(str.c_str()); - } - - break; - } - case torrent::Object::TYPE_VALUE: + } else { *argsCurrent = valueCurrent; + valueCurrent = print_object(valueCurrent, valueBuffer + buffer_size, &*itr, flags) + 1; - valueCurrent += snprintf(valueCurrent, valueBuffer + buffer_size - valueCurrent, "%lli", itr->as_value()) + 1; - break; - - default: - throw torrent::input_error("Invalid type."); - } + if (valueCurrent >= valueBuffer + buffer_size) + throw torrent::input_error("Overflowed execute arg buffer."); + } } *argsCurrent = NULL; - // Check if we overflowed the valueBuffer. - int status = execute(argsBuffer[0], argsBuffer); if ((flags & flag_throw) && status != 0) diff --git a/src/rpc/exec_file.h b/src/rpc/exec_file.h index 8f25a8c7..d7ccb113 100644 --- a/src/rpc/exec_file.h +++ b/src/rpc/exec_file.h @@ -46,15 +46,20 @@ public: static const unsigned int max_args = 128; static const unsigned int buffer_size = 4096; - static const int flag_throw = 0x1; - static const int flag_expand_tilde = 0x2; + static const int flag_expand_tilde = 0x1; + static const int flag_throw = 0x2; + + ExecFile() : m_logFd(-1) {} + + int log_fd() const { return m_logFd; } + void set_log_fd(int fd) { m_logFd = fd; } int execute(const char* file, char* const* argv); torrent::Object execute_object(const torrent::Object& rawArgs, int flags); private: - + int m_logFd; }; } diff --git a/src/rpc/parse.cc b/src/rpc/parse.cc index e5f9308c..a2b4cd79 100644 --- a/src/rpc/parse.cc +++ b/src/rpc/parse.cc @@ -37,6 +37,7 @@ #include "config.h" #include +#include #include #include "parse.h" @@ -60,7 +61,7 @@ parse_skip_wspace(const char* first) { } const char* -parse_string(const char* first, const char* last, std::string* dest) { +parse_string(const char* first, const char* last, std::string* dest, bool (*delim)(const char)) { if (first == last) return first; @@ -75,7 +76,7 @@ parse_string(const char* first, const char* last, std::string* dest) { return ++first; } else { - if (parse_is_seperator(*first) || std::isspace(*first)) + if (delim(*first)) return first; } @@ -165,19 +166,39 @@ parse_value_nothrow(const char* src, int64_t* value, int base, int unit) { return last; } +// Somewhat ugly... const char* -parse_list(const char* first, const char* last, torrent::Object* dest) { +parse_object(const char* first, const char* last, torrent::Object* dest, bool (*delim)(const char)) { + if (*first == '{') { + *dest = torrent::Object(torrent::Object::TYPE_LIST); + first = parse_list(first + 1, last, dest, &parse_is_delim_list); + first = parse_skip_wspace(first, last); + + if (first == last || *first != '}') + throw torrent::input_error("Could not find closing '}'."); + + return ++first; + + } else { + *dest = std::string(); + + return parse_string(first, last, &dest->as_string(), delim); + } +} + +const char* +parse_list(const char* first, const char* last, torrent::Object* dest, bool (*delim)(const char)) { if (!dest->is_list()) throw torrent::internal_error("parse_list(...) !dest->is_list()."); while (true) { - std::string str; + torrent::Object tmp; first = parse_skip_wspace(first, last); - first = parse_string(first, last, &str); + first = parse_object(first, last, &tmp, delim); first = parse_skip_wspace(first, last); - dest->as_list().push_back(str); + dest->as_list().push_back(tmp); if (first == last || !parse_is_seperator(*first)) break; @@ -190,20 +211,16 @@ parse_list(const char* first, const char* last, torrent::Object* dest) { const char* parse_whole_list(const char* first, const char* last, torrent::Object* dest) { - std::string str; - first = parse_skip_wspace(first, last); - first = parse_string(first, last, &str); + first = parse_object(first, last, dest); first = parse_skip_wspace(first, last); if (first != last && parse_is_seperator(*first)) { - *dest = torrent::Object(torrent::Object::TYPE_LIST); + torrent::Object tmp = torrent::Object(torrent::Object::TYPE_LIST); + tmp.swap(*dest); - dest->as_list().push_back(str); + dest->as_list().push_back(tmp); first = parse_list(++first, last, dest); - - } else { - *dest = str; } return first; @@ -332,4 +349,44 @@ convert_to_value_nothrow(const torrent::Object& src, int64_t* value, int base, i return true; } +char* +print_object(char* first, char* last, const torrent::Object* src, int flags) { + switch (src->type()) { + case torrent::Object::TYPE_STRING: + { + const std::string& str = src->as_string(); + + if ((flags & print_expand_tilde) && *str.c_str() == '~') { + return rak::path_expand(str.c_str(), first, last); + + } else { + if (first == last) + return first; + + size_t n = std::min(str.size(), std::distance(first, last) - 1); + + std::memcpy(first, str.c_str(), n); + *(first += n) = '\0'; + + return first; + } + } + case torrent::Object::TYPE_VALUE: + return std::max(first + snprintf(first, std::distance(first, last), "%lli", src->as_value()), last); + + case torrent::Object::TYPE_LIST: + for (torrent::Object::list_type::const_iterator itr = src->as_list().begin(), itrEnd = src->as_list().end(); itr != itrEnd; itr++) { + first = print_object(first, last, &*itr, flags); + + // Don't expand tilde after the first element in the list. + flags &= ~print_expand_tilde; + } + + return first; + + default: + throw torrent::input_error("Invalid type."); + } +} + } diff --git a/src/rpc/parse.h b/src/rpc/parse.h index 9a5f035e..1dd05259 100644 --- a/src/rpc/parse.h +++ b/src/rpc/parse.h @@ -54,10 +54,13 @@ inline bool parse_is_escape(const char c) { return c == '\\'; } inline bool parse_is_seperator(const char c) { return c == ','; } inline bool parse_is_space(const char c) { return c == ' ' || c == '\t'; } +inline bool parse_is_delim_default(const char c) { return parse_is_seperator(c) || std::isspace(c); } +inline bool parse_is_delim_list(const char c) { return parse_is_seperator(c) || c == '}' || std::isspace(c); } + const char* parse_skip_wspace(const char* first); const char* parse_skip_wspace(const char* first, const char* last); -const char* parse_string(const char* first, const char* last, std::string* dest); +const char* parse_string(const char* first, const char* last, std::string* dest, bool (*delim)(const char) = &parse_is_delim_default); void parse_whole_string(const char* first, const char* last, std::string* dest); const char* parse_value(const char* src, int64_t* value, int base = 0, int unit = 1); @@ -66,7 +69,8 @@ const char* parse_value_nothrow(const char* src, int64_t* value, int base = 0, i void parse_whole_value(const char* src, int64_t* value, int base = 0, int unit = 1); bool parse_whole_value_nothrow(const char* src, int64_t* value, int base = 0, int unit = 1); -const char* parse_list(const char* first, const char* last, torrent::Object* dest); +const char* parse_object(const char* first, const char* last, torrent::Object* dest, bool (*delim)(const char) = &parse_is_delim_default); +const char* parse_list(const char* first, const char* last, torrent::Object* dest, bool (*delim)(const char) = &parse_is_delim_default); const char* parse_whole_list(const char* first, const char* last, torrent::Object* dest); std::string convert_list_to_string(const torrent::Object& src); @@ -84,6 +88,10 @@ convert_to_single_argument(const torrent::Object& args) { return args; } +static const int print_expand_tilde = 0x1; + +char* print_object(char* first, char* last, const torrent::Object* src, int flags); + } #endif diff --git a/src/rpc/parse_commands.cc b/src/rpc/parse_commands.cc index c1924ba7..d83284e0 100644 --- a/src/rpc/parse_commands.cc +++ b/src/rpc/parse_commands.cc @@ -77,6 +77,23 @@ parse_count_escaped(const char* first, const char* last) { return escaped; } +// Replace any strings starting with '$' with the result of the +// result of the command. +// +// Find a better name. +void +parse_command_execute(core::Download* download, torrent::Object* object) { + if (object->is_list()) { + for (torrent::Object::list_type::iterator itr = object->as_list().begin(), last = object->as_list().end(); itr != last; itr++) + parse_command_execute(download, &*itr); + + } else if (*object->as_string().c_str() == '$') { + const std::string& str = object->as_string(); + + *object = parse_command_d_single(download, str.c_str() + 1, str.c_str() + str.size()); + } +} + // Set 'download' to NULL to call the generic functions, thus reusing // the code below for both cases. std::pair @@ -110,22 +127,7 @@ parse_command(core::Download* download, const char* first, const char* last) { // Replace any strings starting with '$' with the result of the // following command. - if (args.is_list()) { - for (torrent::Object::list_type::iterator itr = args.as_list().begin(), last = args.as_list().end(); itr != last; itr++) { - if (!itr->is_string()) - continue; - - const std::string& str = itr->as_string(); - - if (*str.c_str() == '$') - *itr = parse_command_d_single(download, str.c_str() + 1, str.c_str() + str.size()); - } - - } else if (*args.as_string().c_str() == '$') { - const std::string& str = args.as_string(); - - args = parse_command_d_single(download, str.c_str() + 1, str.c_str() + str.size()); - } + parse_command_execute(download, &args); return std::make_pair(commands.call_command_d(key.c_str(), download, args), first); }