diff options
Diffstat (limited to 'contrib/epee')
-rw-r--r-- | contrib/epee/include/console_handler.h | 41 | ||||
-rw-r--r-- | contrib/epee/include/net/abstract_tcp_server.h | 5 | ||||
-rw-r--r-- | contrib/epee/include/net/abstract_tcp_server2.h | 2 | ||||
-rw-r--r-- | contrib/epee/include/net/abstract_tcp_server2.inl | 7 | ||||
-rw-r--r-- | contrib/epee/include/net/abstract_tcp_server_cp.inl | 5 | ||||
-rw-r--r-- | contrib/epee/include/net/http_server_handlers_map2.h | 2 | ||||
-rw-r--r-- | contrib/epee/include/net/levin_client_async.h | 3 | ||||
-rw-r--r-- | contrib/epee/include/net/net_utils_base.h | 122 | ||||
-rw-r--r-- | contrib/epee/include/readline_buffer.h | 40 | ||||
-rw-r--r-- | contrib/epee/include/string_tools.h | 2 | ||||
-rw-r--r-- | contrib/epee/src/CMakeLists.txt | 7 | ||||
-rw-r--r-- | contrib/epee/src/readline_buffer.cpp | 196 |
12 files changed, 405 insertions, 27 deletions
diff --git a/contrib/epee/include/console_handler.h b/contrib/epee/include/console_handler.h index 54e3e966d..bb20faa65 100644 --- a/contrib/epee/include/console_handler.h +++ b/contrib/epee/include/console_handler.h @@ -38,6 +38,10 @@ #endif #include <boost/thread.hpp> +#ifdef HAVE_READLINE + #include "readline_buffer.h" +#endif + namespace epee { class async_stdin_reader @@ -49,6 +53,10 @@ namespace epee , m_read_status(state_init) { m_reader_thread = boost::thread(std::bind(&async_stdin_reader::reader_thread_func, this)); +#ifdef HAVE_READLINE + m_readline_buffer.start(); + m_readline_thread = boost::thread(std::bind(&async_stdin_reader::readline_thread_func, this)); +#endif } ~async_stdin_reader() @@ -56,6 +64,13 @@ namespace epee stop(); } +#ifdef HAVE_READLINE + rdln::readline_buffer& get_readline_buffer() + { + return m_readline_buffer; + } +#endif + // Not thread safe. Only one thread can call this method at once. bool get_line(std::string& line) { @@ -98,6 +113,10 @@ namespace epee m_request_cv.notify_one(); m_reader_thread.join(); +#ifdef HAVE_READLINE + m_readline_buffer.stop(); + m_readline_thread.join(); +#endif } } @@ -174,6 +193,16 @@ namespace epee return true; } +#ifdef HAVE_READLINE + void readline_thread_func() + { + while (m_run.load(std::memory_order_relaxed)) + { + m_readline_buffer.process(); + } + } +#endif + void reader_thread_func() { while (true) @@ -187,7 +216,11 @@ namespace epee { if (m_run.load(std::memory_order_relaxed)) { +#ifdef HAVE_READLINE + m_readline_buffer.get_line(line); +#else std::getline(std::cin, line); +#endif read_ok = !std::cin.eof() && !std::cin.fail(); } } @@ -229,6 +262,10 @@ namespace epee private: boost::thread m_reader_thread; std::atomic<bool> m_run; +#ifdef HAVE_READLINE + boost::thread m_readline_thread; + rdln::readline_buffer m_readline_buffer; +#endif std::string m_line; bool m_has_read_request; @@ -277,12 +314,16 @@ namespace epee { if (!m_prompt.empty()) { +#ifdef HAVE_READLINE + m_stdin_reader.get_readline_buffer().set_prompt(m_prompt); +#else epee::set_console_color(epee::console_color_yellow, true); std::cout << m_prompt; if (' ' != m_prompt.back()) std::cout << ' '; epee::reset_console_color(); std::cout.flush(); +#endif } } diff --git a/contrib/epee/include/net/abstract_tcp_server.h b/contrib/epee/include/net/abstract_tcp_server.h index 1efd5091c..000305cfa 100644 --- a/contrib/epee/include/net/abstract_tcp_server.h +++ b/contrib/epee/include/net/abstract_tcp_server.h @@ -296,7 +296,7 @@ namespace net_utils } //---------------------------------------------------------------------------------------- template<class THandler> - bool abstract_tcp_server<THandler>::invoke_connection(SOCKET hnew_sock, long ip_from, int post_from) + bool abstract_tcp_server<THandler>::invoke_connection(SOCKET hnew_sock, const network_address &remote_address) { m_connections_lock.lock(); m_connections.push_back(thread_context()); @@ -304,8 +304,7 @@ namespace net_utils m_connections.back().m_socket = hnew_sock; m_connections.back().powner = this; m_connections.back().m_self_it = --m_connections.end(); - m_connections.back().m_context.m_remote_ip = ip_from; - m_connections.back().m_context.m_remote_port = post_from; + m_connections.back().m_context.m_remote_address = remote_address; m_connections.back().m_htread = threads_helper::create_thread(ConnectionHandlerProc, &m_connections.back()); return true; diff --git a/contrib/epee/include/net/abstract_tcp_server2.h b/contrib/epee/include/net/abstract_tcp_server2.h index 506949dbd..ca58d5467 100644 --- a/contrib/epee/include/net/abstract_tcp_server2.h +++ b/contrib/epee/include/net/abstract_tcp_server2.h @@ -69,7 +69,7 @@ namespace net_utils struct i_connection_filter { - virtual bool is_remote_ip_allowed(uint32_t adress)=0; + virtual bool is_remote_host_allowed(const epee::net_utils::network_address &address)=0; protected: virtual ~i_connection_filter(){} }; diff --git a/contrib/epee/include/net/abstract_tcp_server2.inl b/contrib/epee/include/net/abstract_tcp_server2.inl index 75a9c5be9..0fbd9ed28 100644 --- a/contrib/epee/include/net/abstract_tcp_server2.inl +++ b/contrib/epee/include/net/abstract_tcp_server2.inl @@ -133,6 +133,7 @@ PRAGMA_WARNING_DISABLE_VS(4355) boost::system::error_code ec; auto remote_ep = socket_.remote_endpoint(ec); CHECK_AND_NO_ASSERT_MES(!ec, false, "Failed to get remote endpoint: " << ec.message() << ':' << ec.value()); + CHECK_AND_NO_ASSERT_MES(remote_ep.address().is_v4(), false, "IPv6 not supported here"); auto local_ep = socket_.local_endpoint(ec); CHECK_AND_NO_ASSERT_MES(!ec, false, "Failed to get local endpoint: " << ec.message() << ':' << ec.value()); @@ -145,14 +146,14 @@ PRAGMA_WARNING_DISABLE_VS(4355) // that stuff turns out to be included, even though it's from src... Taking advantage random_uuid = crypto::rand<boost::uuids::uuid>(); - context.set_details(random_uuid, ip_, remote_ep.port(), is_income); + context.set_details(random_uuid, new epee::net_utils::ipv4_network_address(ip_, remote_ep.port()), is_income); _dbg3("[sock " << socket_.native_handle() << "] new connection from " << print_connection_context_short(context) << " to " << local_ep.address().to_string() << ':' << local_ep.port() << ", total sockets objects " << m_ref_sock_count); - if(m_pfilter && !m_pfilter->is_remote_ip_allowed(context.m_remote_ip)) + if(m_pfilter && !m_pfilter->is_remote_host_allowed(context.m_remote_address)) { - _dbg2("[sock " << socket_.native_handle() << "] ip denied " << string_tools::get_ip_string_from_int32(context.m_remote_ip) << ", shutdowning connection"); + _dbg2("[sock " << socket_.native_handle() << "] host denied " << context.m_remote_address.host_str() << ", shutdowning connection"); close(); return false; } diff --git a/contrib/epee/include/net/abstract_tcp_server_cp.inl b/contrib/epee/include/net/abstract_tcp_server_cp.inl index ba201e3bf..e0ef6470e 100644 --- a/contrib/epee/include/net/abstract_tcp_server_cp.inl +++ b/contrib/epee/include/net/abstract_tcp_server_cp.inl @@ -471,7 +471,7 @@ bool cp_server_impl<TProtocol>::run_server(int threads_count = 0) } //------------------------------------------------------------- template<class TProtocol> -bool cp_server_impl<TProtocol>::add_new_connection(SOCKET new_sock, long ip_from, int port_from) +bool cp_server_impl<TProtocol>::add_new_connection(SOCKET new_sock, const network_address &address_from) { PROFILE_FUNC("[add_new_connection]"); @@ -487,8 +487,7 @@ bool cp_server_impl<TProtocol>::add_new_connection(SOCKET new_sock, long ip_from m_connections_lock.unlock(); conn.init_buffers(); conn.m_sock = new_sock; - conn.context.m_remote_ip = ip_from; - conn.context.m_remote_port = port_from; + conn.context.m_remote_address = address_from; conn.m_completion_port = m_completion_port; { PROFILE_FUNC("[add_new_connection] CreateIoCompletionPort"); diff --git a/contrib/epee/include/net/http_server_handlers_map2.h b/contrib/epee/include/net/http_server_handlers_map2.h index 8e39b4104..429e3e1af 100644 --- a/contrib/epee/include/net/http_server_handlers_map2.h +++ b/contrib/epee/include/net/http_server_handlers_map2.h @@ -39,7 +39,7 @@ epee::net_utils::http::http_response_info& response, \ context_type& m_conn_context) \ {\ - LOG_PRINT_L2("HTTP [" << epee::string_tools::get_ip_string_from_int32(m_conn_context.m_remote_ip ) << "] " << query_info.m_http_method_str << " " << query_info.m_URI); \ + LOG_PRINT_L2("HTTP [" << m_conn_context.m_remote_address.host_str() << "] " << query_info.m_http_method_str << " " << query_info.m_URI); \ response.m_response_code = 200; \ response.m_response_comment = "Ok"; \ if(!handle_http_request_map(query_info, response, m_conn_context)) \ diff --git a/contrib/epee/include/net/levin_client_async.h b/contrib/epee/include/net/levin_client_async.h index 2cbdb4587..4b48070d6 100644 --- a/contrib/epee/include/net/levin_client_async.h +++ b/contrib/epee/include/net/levin_client_async.h @@ -492,8 +492,7 @@ namespace levin { net_utils::connection_context_base conn_context; - conn_context.m_remote_ip = m_ip; - conn_context.m_remote_port = m_port; + conn_context.m_remote_address = m_address; if(head.m_have_to_return_data) { std::string return_buff; diff --git a/contrib/epee/include/net/net_utils_base.h b/contrib/epee/include/net/net_utils_base.h index 4334029f7..ba59daf4f 100644 --- a/contrib/epee/include/net/net_utils_base.h +++ b/contrib/epee/include/net/net_utils_base.h @@ -29,8 +29,11 @@ #ifndef _NET_UTILS_BASE_H_ #define _NET_UTILS_BASE_H_ +#include <typeinfo> #include <boost/asio/io_service.hpp> #include <boost/uuid/uuid.hpp> +#include "serialization/keyvalue_serialization.h" +#include "net/local_ip.h" #include "string_tools.h" #include "misc_log_ex.h" @@ -46,14 +49,111 @@ namespace epee { namespace net_utils { + struct network_address_base + { + public: + bool operator==(const network_address_base &other) const { return m_full_id == other.m_full_id; } + bool operator!=(const network_address_base &other) const { return !operator==(other); } + bool operator<(const network_address_base &other) const { return m_full_id < other.m_full_id; } + bool is_same_host(const network_address_base &other) const { return m_host_id == other.m_host_id; } + virtual std::string str() const = 0; + virtual std::string host_str() const = 0; + virtual bool is_loopback() const = 0; + virtual bool is_local() const = 0; + virtual uint8_t get_type_id() const = 0; + protected: + // A very simple non cryptographic hash function by Fowler, Noll, Vo + uint64_t fnv1a(const uint8_t *data, size_t len) const { + uint64_t h = 0xcbf29ce484222325; + while (len--) + h = (h ^ *data++) * 0x100000001b3; + return h; + } + uint64_t m_host_id; + uint64_t m_full_id; + }; + struct ipv4_network_address: public network_address_base + { + void init_ids() + { + m_host_id = fnv1a((const uint8_t*)&m_ip, sizeof(m_ip)); + m_full_id = fnv1a((const uint8_t*)&m_ip, sizeof(m_ip) + sizeof(m_port)); + } + public: + ipv4_network_address(uint32_t ip, uint16_t port): network_address_base(), m_ip(ip), m_port(port) { init_ids(); } + uint32_t ip() const { return m_ip; } + uint16_t port() const { return m_port; } + virtual std::string str() const { return epee::string_tools::get_ip_string_from_int32(m_ip) + ":" + std::to_string(m_port); } + virtual std::string host_str() const { return epee::string_tools::get_ip_string_from_int32(m_ip); } + virtual bool is_loopback() const { return epee::net_utils::is_ip_loopback(m_ip); } + virtual bool is_local() const { return epee::net_utils::is_ip_local(m_ip); } + virtual uint8_t get_type_id() const { return ID; } + public: // serialization + static const uint8_t ID = 1; +#pragma pack(push) +#pragma pack(1) + uint32_t m_ip; + uint16_t m_port; +#pragma pack(pop) + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(m_ip) + KV_SERIALIZE(m_port) + if (!is_store) + const_cast<ipv4_network_address&>(this_ref).init_ids(); + END_KV_SERIALIZE_MAP() + }; + class network_address: public boost::shared_ptr<network_address_base> + { + public: + network_address() {} + network_address(ipv4_network_address *address): boost::shared_ptr<network_address_base>(address) {} + bool operator==(const network_address &other) const { return (*this)->operator==(*other); } + bool operator!=(const network_address &other) const { return (*this)->operator!=(*other); } + bool operator<(const network_address &other) const { return (*this)->operator<(*other); } + bool is_same_host(const network_address &other) const { return (*this)->is_same_host(*other); } + std::string str() const { return (*this) ? (*this)->str() : "<none>"; } + std::string host_str() const { return (*this) ? (*this)->host_str() : "<none>"; } + bool is_loopback() const { return (*this)->is_loopback(); } + bool is_local() const { return (*this)->is_local(); } + const std::type_info &type() const { return typeid(**this); } + uint8_t get_type_id() const { return (*this)->get_type_id(); } + template<typename Type> Type &as() { if (type() != typeid(Type)) throw std::runtime_error("Bad type"); return *(Type*)get(); } + template<typename Type> const Type &as() const { if (type() != typeid(Type)) throw std::runtime_error("Bad type"); return *(const Type*)get(); } + + BEGIN_KV_SERIALIZE_MAP() + uint8_t type = is_store ? this_ref.get_type_id() : 0; + epee::serialization::selector<is_store>::serialize(type, stg, hparent_section, "type"); + switch (type) + { + case ipv4_network_address::ID: + if (!is_store) + const_cast<network_address&>(this_ref).reset(new ipv4_network_address(0, 0)); + KV_SERIALIZE(as<ipv4_network_address>()); + break; + default: MERROR("Unsupported network address type: " << type); return false; + } + END_KV_SERIALIZE_MAP() + }; + inline bool create_network_address(network_address &address, const std::string &string, uint16_t default_port = 0) + { + uint32_t ip; + uint16_t port; + if (epee::string_tools::parse_peer_from_string(ip, port, string)) + { + if (default_port && !port) + port = default_port; + address.reset(new ipv4_network_address(ip, port)); + return true; + } + return false; + } /************************************************************************/ /* */ /************************************************************************/ struct connection_context_base { const boost::uuids::uuid m_connection_id; - const uint32_t m_remote_ip; - const uint32_t m_remote_port; + const network_address m_remote_address; const bool m_is_income; const time_t m_started; time_t m_last_recv; @@ -64,12 +164,11 @@ namespace net_utils double m_current_speed_up; connection_context_base(boost::uuids::uuid connection_id, - long remote_ip, int remote_port, bool is_income, + const network_address &remote_address, bool is_income, time_t last_recv = 0, time_t last_send = 0, uint64_t recv_cnt = 0, uint64_t send_cnt = 0): m_connection_id(connection_id), - m_remote_ip(remote_ip), - m_remote_port(remote_port), + m_remote_address(remote_address), m_is_income(is_income), m_started(time(NULL)), m_last_recv(last_recv), @@ -81,8 +180,7 @@ namespace net_utils {} connection_context_base(): m_connection_id(), - m_remote_ip(0), - m_remote_port(0), + m_remote_address(new ipv4_network_address(0,0)), m_is_income(false), m_started(time(NULL)), m_last_recv(0), @@ -95,17 +193,17 @@ namespace net_utils connection_context_base& operator=(const connection_context_base& a) { - set_details(a.m_connection_id, a.m_remote_ip, a.m_remote_port, a.m_is_income); + set_details(a.m_connection_id, a.m_remote_address, a.m_is_income); return *this; } private: template<class t_protocol_handler> friend class connection; - void set_details(boost::uuids::uuid connection_id, long remote_ip, int remote_port, bool is_income) + void set_details(boost::uuids::uuid connection_id, const network_address &remote_address, bool is_income) { this->~connection_context_base(); - new(this) connection_context_base(connection_id, remote_ip, remote_port, is_income); + new(this) connection_context_base(connection_id, remote_address, is_income); } }; @@ -135,7 +233,7 @@ namespace net_utils std::string print_connection_context(const connection_context_base& ctx) { std::stringstream ss; - ss << epee::string_tools::get_ip_string_from_int32(ctx.m_remote_ip) << ":" << ctx.m_remote_port << " " << epee::string_tools::get_str_from_guid_a(ctx.m_connection_id) << (ctx.m_is_income ? " INC":" OUT"); + ss << ctx.m_remote_address->str() << " " << epee::string_tools::get_str_from_guid_a(ctx.m_connection_id) << (ctx.m_is_income ? " INC":" OUT"); return ss.str(); } @@ -143,7 +241,7 @@ namespace net_utils std::string print_connection_context_short(const connection_context_base& ctx) { std::stringstream ss; - ss << epee::string_tools::get_ip_string_from_int32(ctx.m_remote_ip) << ":" << ctx.m_remote_port << (ctx.m_is_income ? " INC":" OUT"); + ss << ctx.m_remote_address->str() << (ctx.m_is_income ? " INC":" OUT"); return ss.str(); } diff --git a/contrib/epee/include/readline_buffer.h b/contrib/epee/include/readline_buffer.h new file mode 100644 index 000000000..7d929bc4c --- /dev/null +++ b/contrib/epee/include/readline_buffer.h @@ -0,0 +1,40 @@ +#pragma once + +#include <streambuf> +#include <sstream> +#include <iostream> + +namespace rdln +{ + class readline_buffer : public std::stringbuf + { + public: + readline_buffer(); + void start(); + void stop(); + int process(); + bool is_running() + { + return m_cout_buf != NULL; + } + void get_line(std::string& line); + void set_prompt(const std::string& prompt); + + protected: + virtual int sync(); + + private: + std::streambuf* m_cout_buf; + }; + + class suspend_readline + { + public: + suspend_readline(); + ~suspend_readline(); + private: + readline_buffer* m_buffer; + bool m_restart; + }; +} + diff --git a/contrib/epee/include/string_tools.h b/contrib/epee/include/string_tools.h index 16e60e99a..258caa49e 100644 --- a/contrib/epee/include/string_tools.h +++ b/contrib/epee/include/string_tools.h @@ -193,7 +193,7 @@ POP_WARNINGS //---------------------------------------------------------------------------- bool get_ip_int32_from_string(uint32_t& ip, const std::string& ip_str); //---------------------------------------------------------------------------- - inline bool parse_peer_from_string(uint32_t& ip, uint32_t& port, const std::string& addres) + inline bool parse_peer_from_string(uint32_t& ip, uint16_t& port, const std::string& addres) { //parse ip and address std::string::size_type p = addres.find(':'); diff --git a/contrib/epee/src/CMakeLists.txt b/contrib/epee/src/CMakeLists.txt index 437851833..c61a6e684 100644 --- a/contrib/epee/src/CMakeLists.txt +++ b/contrib/epee/src/CMakeLists.txt @@ -26,7 +26,12 @@ # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -add_library(epee STATIC hex.cpp http_auth.cpp mlog.cpp string_tools.cpp) +if (USE_READLINE AND GNU_READLINE_FOUND) + add_library(epee STATIC hex.cpp http_auth.cpp mlog.cpp string_tools.cpp readline_buffer.cpp) +else() + add_library(epee STATIC hex.cpp http_auth.cpp mlog.cpp string_tools.cpp) +endif() + # Build and install libepee if we're building for GUI if (BUILD_GUI_DEPS) if(IOS) diff --git a/contrib/epee/src/readline_buffer.cpp b/contrib/epee/src/readline_buffer.cpp new file mode 100644 index 000000000..68b739db9 --- /dev/null +++ b/contrib/epee/src/readline_buffer.cpp @@ -0,0 +1,196 @@ +#include "readline_buffer.h" +#include <readline/readline.h> +#include <readline/history.h> +#include <sys/select.h> +#include <unistd.h> +#include <mutex> +#include <condition_variable> + +static int process_input(); +static void install_line_handler(); +static void remove_line_handler(); + +static std::string last_line; +static std::string last_prompt; +std::mutex line_mutex, sync_mutex; +std::condition_variable have_line; + +namespace +{ + rdln::readline_buffer* current = NULL; +} + +rdln::suspend_readline::suspend_readline() +{ + m_buffer = current; + if(!m_buffer) + return; + m_restart = m_buffer->is_running(); + if(m_restart) + m_buffer->stop(); +} + +rdln::suspend_readline::~suspend_readline() +{ + if(!m_buffer) + return; + if(m_restart) + m_buffer->start(); +} + +rdln::readline_buffer::readline_buffer() +: std::stringbuf() +{ + current = this; +} + +void rdln::readline_buffer::start() +{ + if(m_cout_buf != NULL) + return; + m_cout_buf = std::cout.rdbuf(); + std::cout.rdbuf(this); + install_line_handler(); +} + +void rdln::readline_buffer::stop() +{ + if(m_cout_buf == NULL) + return; + std::cout.rdbuf(m_cout_buf); + m_cout_buf = NULL; + remove_line_handler(); +} + +void rdln::readline_buffer::get_line(std::string& line) +{ + std::unique_lock<std::mutex> lock(line_mutex); + have_line.wait(lock); + line = last_line; +} + +void rdln::readline_buffer::set_prompt(const std::string& prompt) +{ + last_prompt = prompt; + if(m_cout_buf == NULL) + return; + rl_set_prompt(last_prompt.c_str()); + rl_redisplay(); +} + +int rdln::readline_buffer::process() +{ + if(m_cout_buf == NULL) + return 0; + return process_input(); +} + +int rdln::readline_buffer::sync() +{ + std::lock_guard<std::mutex> lock(sync_mutex); + char* saved_line; + int saved_point; + + saved_point = rl_point; + saved_line = rl_copy_text(0, rl_end); + + rl_set_prompt(""); + rl_replace_line("", 0); + rl_redisplay(); + + do + { + char x = this->sgetc(); + m_cout_buf->sputc(x); + } + while ( this->snextc() != EOF ); + + rl_set_prompt(last_prompt.c_str()); + rl_replace_line(saved_line, 0); + rl_point = saved_point; + rl_redisplay(); + free(saved_line); + + return 0; +} + +static fd_set fds; + +static int process_input() +{ + int count; + struct timeval t; + + t.tv_sec = 0; + t.tv_usec = 0; + + FD_ZERO(&fds); + FD_SET(STDIN_FILENO, &fds); + count = select(FD_SETSIZE, &fds, NULL, NULL, &t); + if (count < 1) + { + return count; + } + rl_callback_read_char(); + return count; +} + +static void handle_line(char* line) +{ + if (line != NULL) + { + std::lock_guard<std::mutex> lock(sync_mutex); + rl_set_prompt(last_prompt.c_str()); + rl_already_prompted = 1; + return; + } + rl_set_prompt(""); + rl_replace_line("", 0); + rl_redisplay(); + rl_set_prompt(last_prompt.c_str()); +} + +static int handle_enter(int x, int y) +{ + std::lock_guard<std::mutex> lock(sync_mutex); + char* line = NULL; + + line = rl_copy_text(0, rl_end); + rl_set_prompt(""); + rl_replace_line("", 1); + rl_redisplay(); + + if (strcmp(line, "") != 0) + { + last_line = line; + add_history(line); + have_line.notify_one(); + } + free(line); + + rl_set_prompt(last_prompt.c_str()); + rl_redisplay(); + + rl_done = 1; + return 0; +} + +static int startup_hook() +{ + rl_bind_key(RETURN, handle_enter); + rl_bind_key(NEWLINE, handle_enter); + return 0; +} + +static void install_line_handler() +{ + rl_startup_hook = startup_hook; + rl_callback_handler_install("", handle_line); +} + +static void remove_line_handler() +{ + rl_unbind_key(RETURN); + rl_callback_handler_remove(); +} + |