From: g0dil Date: Thu, 24 Apr 2008 15:47:54 +0000 (+0000) Subject: Console: Basic readling support X-Git-Url: http://g0dil.de/git?a=commitdiff_plain;h=1a7c3a40d3e477b789c3fdfe7cacb01649d47edf;p=senf.git Console: Basic readling support git-svn-id: https://svn.berlios.de/svnroot/repos/senf/trunk@823 270642c3-0616-0410-b53a-bc976706d245 --- diff --git a/Console/Readline.cc b/Console/Readline.cc new file mode 100644 index 0000000..43da447 --- /dev/null +++ b/Console/Readline.cc @@ -0,0 +1,232 @@ +// $Id$ +// +// Copyright (C) 2008 +// Fraunhofer Institute for Open Communication Systems (FOKUS) +// Competence Center NETwork research (NET), St. Augustin, GERMANY +// Stefan Bund +// +// 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. + +/** \file + \brief Readline non-inline non-template implementation */ + +#include "Readline.hh" +//#include "Readline.ih" + +// Custom includes +#include +#include +#include +#include +#include +#include "../Utils/membind.hh" + +//#include "Readline.mpp" +#define prefix_ +///////////////////////////////cc.p//////////////////////////////////////// + +// +// Readline integration is a bit awkward. There are several things to it: +// +// - Readline uses global variables for all state. Therefore, we can use readline only for one +// console. +// - We need to make readline to read from the socket handle instead of some input stream. We can +// do this by setting a custom rl_getc_function. +// - We need to make readline to write to the NonblockingSocketOStream. This is possible in glibc +// by using a 'cookie stream'. +// - We need to correctly handle the terminal mode settings. Currently we unconditionally +// initialize the remote telnet by sending a fixed telnet option string and ignoring any otpions +// sent back to us. +// - We need to make sure, readline never uses stderr -> we must disable beeping +// - There are places, where readline calls read_key unconditionally even when NOT prompted by the +// callback that another key is available. One such place is completion. (The 'show all +// completions (y/n)?' question). For now, we disable completion support. +// + +/////////////////////////////////////////////////////////////////////////// +// senf::console::detail::ReadlineClientReader + +extern int readline_echoing_p; +extern int _rl_bell_preference; + +namespace { + + int readline_getc_function(FILE *) + { + if (senf::console::detail::ReadlineClientReader::active()) + return senf::console::detail::ReadlineClientReader::instance().getc(); + else + return -1; + } + + void readline_callback(char * input) + { + if (senf::console::detail::ReadlineClientReader::active() && input) + return senf::console::detail::ReadlineClientReader::instance().callback( + std::string(input) ); + } + + ssize_t readline_cookie_write_function(void * cookie, char const * buffer, size_t size) + { + if (senf::console::detail::ReadlineClientReader::active() && buffer) + senf::console::detail::ReadlineClientReader::instance().write( + std::string(buffer, size)); + return size; + } + + void readline_prep_term(int meta) + { + readline_echoing_p = 1; + } + + void readline_deprep_term() + {} + +} + +prefix_ senf::console::detail::ReadlineClientReader::ReadlineClientReader(Client & client) + : ClientReader(client), ch_ (-1), skipChars_ (0), + schedBinding_ ( client.handle(), + senf::membind(&ReadlineClientReader::charEvent, this), + Scheduler::EV_READ, + false ), + terminate_ (false) +{ + if (instance_ != 0) + throw DuplicateReaderException(); + instance_ = this; + + cookie_io_functions_t cookie_io = { 0, &readline_cookie_write_function, 0, 0 }; + rl_outstream = fopencookie(0, "a", cookie_io); + if (rl_outstream == 0) + SENF_THROW_SYSTEM_EXCEPTION(""); + if (setvbuf(rl_outstream, 0, _IONBF, BUFSIZ) < 0) + SENF_THROW_SYSTEM_EXCEPTION(""); + rl_instream = rl_outstream; + rl_terminal_name = "vt100"; + strncpy(nameBuffer_, client.name().c_str(), 128); + nameBuffer_[127] = 0; + rl_readline_name = nameBuffer_; + rl_prep_term_function = &readline_prep_term; + rl_deprep_term_function = &readline_deprep_term; + rl_getc_function = &readline_getc_function; + rl_bind_key('\t', &rl_insert); + using_history(); + + // Don't ask me, where I found this ... + static char options[] = { 0xFF, 0xFB, 0x01, // IAC WILL ECHO + 0xFF, 0xFE, 0x22, // IAC DONT LINEMODE + 0xFF, 0xFB, 0x03, // IAC WILL SGA + 0x00 }; + handle().write(options, options+sizeof(options)); + + strncpy(promptBuffer_, promptString().c_str(), 1024); + promptBuffer_[1023] = 0; + rl_callback_handler_install(promptBuffer_, &readline_callback); + + _rl_bell_preference = 0; // Set this *after* the config file has been read + + schedBinding_.enable(); +} + +prefix_ senf::console::detail::ReadlineClientReader::~ReadlineClientReader() +{ + rl_callback_handler_remove(); + fclose(rl_outstream); + rl_outstream = 0; + rl_instream = 0; + instance_ = 0; +} + +prefix_ void senf::console::detail::ReadlineClientReader::callback(std::string line) +{ + boost::trim(line); + if (!line.empty()) + add_history(line.c_str()); + handleInput(line); + stream() << std::flush; + strncpy(promptBuffer_, promptString().c_str(), 1024); + promptBuffer_[1023] = 0; + rl_set_prompt(promptBuffer_); +} + +prefix_ void senf::console::detail::ReadlineClientReader::v_disablePrompt() +{} + +prefix_ void senf::console::detail::ReadlineClientReader::v_enablePrompt() +{} + +prefix_ void senf::console::detail::ReadlineClientReader::v_translate(std::string & data) +{ + boost::replace_all(data, "\n", "\n\r"); + boost::replace_all(data, "\xff", "\xff\xff"); +} + +prefix_ void senf::console::detail::ReadlineClientReader::charEvent(Scheduler::EventId event) +{ + char ch; + if (event != Scheduler::EV_READ || handle().read(&ch, &ch+1) <= &ch) { + stopClient(); + return; + } + ch_ = ch; + + if (skipChars_ > 0) + --skipChars_; + else if (ch_ == static_cast(0xff)) + skipChars_ = 2; + else if (ch_ != 0) + rl_callback_read_char(); + + if (terminate_) + stopClient(); +} + +senf::console::detail::ReadlineClientReader * + senf::console::detail::ReadlineClientReader::instance_ (0); + +/////////////////////////////////////////////////////////////////////////// +// senf::console::detail::SafeReadlineClientReader + +prefix_ void senf::console::detail::SafeReadlineClientReader::v_disablePrompt() +{ + reader_->disablePrompt(); +} + +prefix_ void senf::console::detail::SafeReadlineClientReader::v_enablePrompt() +{ + reader_->enablePrompt(); +} + +prefix_ void senf::console::detail::SafeReadlineClientReader::v_translate(std::string & data) +{ + reader_->translate(data); +} + +///////////////////////////////cc.e//////////////////////////////////////// +#undef prefix_ +//#include "Readline.mpp" + + +// Local Variables: +// mode: c++ +// fill-column: 100 +// comment-column: 40 +// c-file-style: "senf" +// indent-tabs-mode: nil +// ispell-local-dictionary: "american" +// compile-command: "scons -u test" +// End: diff --git a/Console/Readline.cci b/Console/Readline.cci new file mode 100644 index 0000000..8d12264 --- /dev/null +++ b/Console/Readline.cci @@ -0,0 +1,88 @@ +// $Id$ +// +// Copyright (C) 2008 +// Fraunhofer Institute for Open Communication Systems (FOKUS) +// Competence Center NETwork research (NET), St. Augustin, GERMANY +// Stefan Bund +// +// 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. + +/** \file + \brief Readline inline non-template implementation */ + +//#include "Readline.ih" + +// Custom includes + +#define prefix_ inline +///////////////////////////////cci.p/////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////// +// senf::console::detail::ReadlineClientReader + +prefix_ bool senf::console::detail::ReadlineClientReader::active() +{ + return instance_ != 0; +} + +prefix_ senf::console::detail::ReadlineClientReader & +senf::console::detail::ReadlineClientReader::instance() +{ + return *instance_; +} + +prefix_ int senf::console::detail::ReadlineClientReader::getc() +{ + char ch (ch_); + ch_ = -1; + return ch; +} + +prefix_ void senf::console::detail::ReadlineClientReader::write(std::string text) +{ + translate(text); + handle().write(text); +} + +prefix_ void senf::console::detail::ReadlineClientReader::terminate() +{ + terminate_ = true; +} + +/////////////////////////////////////////////////////////////////////////// +// senf::console::detail::SafeReadlineClientReader + +prefix_ +senf::console::detail::SafeReadlineClientReader::SafeReadlineClientReader(Client & client) + : ClientReader (client), + reader_ ( ReadlineClientReader::active() + ? static_cast(new DumbClientReader(client)) + : static_cast(new ReadlineClientReader(client)) ) +{} + +///////////////////////////////cci.e/////////////////////////////////////// +#undef prefix_ + + +// Local Variables: +// mode: c++ +// fill-column: 100 +// comment-column: 40 +// c-file-style: "senf" +// indent-tabs-mode: nil +// ispell-local-dictionary: "american" +// compile-command: "scons -u test" +// End: diff --git a/Console/Readline.hh b/Console/Readline.hh new file mode 100644 index 0000000..7ac5741 --- /dev/null +++ b/Console/Readline.hh @@ -0,0 +1,121 @@ +// $Id$ +// +// Copyright (C) 2008 +// Fraunhofer Institute for Open Communication Systems (FOKUS) +// Competence Center NETwork research (NET), St. Augustin, GERMANY +// Stefan Bund +// +// 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. + +/** \file + \brief Readline public header */ + +#ifndef HH_Readline_ +#define HH_Readline_ 1 + +// Custom includes +#include +#include "Server.hh" +#include "../Utils/Exception.hh" +#include "../Scheduler/Scheduler.hh" +#include "../Scheduler/Binding.hh" + +//#include "Readline.mpp" +///////////////////////////////hh.p//////////////////////////////////////// + +namespace senf { +namespace console { +namespace detail { + + /** \brief Internal: GNU readline based ClientReader implementation + + This implementation of the ClientReader interface uses GNU readline library to provide a + rich editing environment for console sessions. Since an application can only use readline + once, only one ReadlineReader can be allocated at any time. + */ + class ReadlineClientReader + : public ClientReader + { + public: + ReadlineClientReader(Client & client); + ~ReadlineClientReader(); + + static bool active(); + static ReadlineClientReader & instance(); + + struct DuplicateReaderException : public Exception + { DuplicateReaderException() : Exception("duplicate readline instantiation") {} }; + + int getc(); + void callback(std::string line); + void write(std::string text); + void terminate(); + + private: + virtual void v_disablePrompt(); + virtual void v_enablePrompt(); + virtual void v_translate(std::string & data); + + void charEvent(Scheduler::EventId event); + + static ReadlineClientReader * instance_; + int ch_; + unsigned skipChars_; + char nameBuffer_[256]; + char promptBuffer_[1024]; + SchedulerBinding schedBinding_; + bool terminate_; + }; + + /** \brief Internal: Safe GNU readline based ClientReader implementation + + This implementation of the ClientReader interface forwards all functionality to either + ReadlineClientReader or DumbClientReader. A RadlineClientReader is used, if none is active, + otherwise a DumbClientReader is allocated. Using this ClientReader implementation, the first + console client will have readline functionality enabled, all further will have it disabled. + */ + class SafeReadlineClientReader + : public ClientReader + { + public: + SafeReadlineClientReader(Client & client); + + private: + virtual void v_disablePrompt(); + virtual void v_enablePrompt(); + virtual void v_translate(std::string & data); + + boost::scoped_ptr reader_; + }; + +}}} + +///////////////////////////////hh.e//////////////////////////////////////// +#include "Readline.cci" +//#include "Readline.ct" +//#include "Readline.cti" +#endif + + +// Local Variables: +// mode: c++ +// fill-column: 100 +// comment-column: 40 +// c-file-style: "senf" +// indent-tabs-mode: nil +// ispell-local-dictionary: "american" +// compile-command: "scons -u test" +// End: diff --git a/Console/Server.cc b/Console/Server.cc index 13820cf..d6491bf 100644 --- a/Console/Server.cc +++ b/Console/Server.cc @@ -36,6 +36,7 @@ #include "../Utils/senfassert.hh" #include "../Utils/membind.hh" #include "../Utils/Logger/SenfLog.hh" +#include "Readline.hh" //#include "Server.mpp" #define prefix_ @@ -48,8 +49,11 @@ prefix_ std::streamsize senf::console::detail::NonblockingSocketSink::write(cons std::streamsize n) { try { - if (handle_.writeable()) - handle_.write(s, s+n); + if (client_.handle().writeable()) { + std::string data (s, n); + client_.translate(data); + client_.handle().write( data ); + } } catch (SystemException & ex) { ; @@ -182,25 +186,24 @@ prefix_ void senf::console::detail::DumbClientReader::v_enablePrompt() showPrompt(); } +prefix_ void senf::console::detail::DumbClientReader::v_translate(std::string & data) +{} + /////////////////////////////////////////////////////////////////////////// // senf::console::Client prefix_ senf::console::Client::Client(Server & server, ClientHandle handle, std::string const & name) - : out_t(handle), senf::log::IOStreamTarget(out_t::member), server_ (server), - handle_ (handle), name_ (name), reader_ (0) + : out_t(boost::ref(*this)), senf::log::IOStreamTarget(out_t::member), server_ (server), + handle_ (handle), name_ (name), reader_ (new detail::SafeReadlineClientReader (*this)) { - reader_.reset( new detail::DumbClientReader (*this) ); - route< senf::SenfLog, senf::log::NOTICE >(); + handle_.facet().nodelay(); + // route< senf::SenfLog, senf::log::NOTICE >(); } -prefix_ senf::console::Client::~Client() -{} - -prefix_ void senf::console::Client::stop() +prefix_ void senf::console::Client::translate(std::string & data) { - // THIS COMMITS SUICIDE. THE INSTANCE IS GONE AFTER removeClient RETURNS - server_.removeClient(*this); + reader_->translate(data); } prefix_ void senf::console::Client::handleInput(std::string data) @@ -239,6 +242,7 @@ prefix_ void senf::console::Client::v_write(boost::posix_time::ptime timestamp, { reader_->disablePrompt(); IOStreamTarget::v_write(timestamp, stream, area, level, message); + out_t::member << std::flush; reader_->enablePrompt(); } diff --git a/Console/Server.cci b/Console/Server.cci index 5147dfd..8e0d434 100644 --- a/Console/Server.cci +++ b/Console/Server.cci @@ -33,8 +33,8 @@ /////////////////////////////////////////////////////////////////////////// // senf::console::detail::NonblockingSocketSink -prefix_ senf::console::detail::NonblockingSocketSink::NonblockingSocketSink(Handle handle) - : handle_ (handle) +prefix_ senf::console::detail::NonblockingSocketSink::NonblockingSocketSink(Client & client) + : client_ (client) {} /////////////////////////////////////////////////////////////////////////// @@ -69,7 +69,7 @@ prefix_ senf::console::detail::ClientReader::ClientHandle senf::console::detail: return client().handle(); } -prefix_ senf::console::detail::ClientReader::OutputStream & senf::console::detail::ClientReader::stream() +prefix_ std::ostream & senf::console::detail::ClientReader::stream() const { return client().stream(); @@ -96,6 +96,11 @@ prefix_ void senf::console::detail::ClientReader::enablePrompt() v_enablePrompt(); } +prefix_ void senf::console::detail::ClientReader::translate(std::string & data) +{ + v_translate(data); +} + prefix_ senf::console::detail::ClientReader::ClientReader(Client & client) : client_ (client) {} @@ -103,6 +108,21 @@ prefix_ senf::console::detail::ClientReader::ClientReader(Client & client) /////////////////////////////////////////////////////////////////////////// // senf::console::Client +prefix_ senf::console::Client::~Client() +{} + +prefix_ void senf::console::Client::stop() +{ + // THIS COMMITS SUICIDE. THE INSTANCE IS GONE AFTER removeClient RETURNS + server_.removeClient(*this); +} + +prefix_ std::string const & senf::console::Client::name() + const +{ + return name_; +} + prefix_ std::string senf::console::Client::promptString() const { @@ -115,7 +135,7 @@ prefix_ senf::console::Client::ClientHandle senf::console::Client::handle() return handle_; } -prefix_ senf::console::detail::NonblockingSocketOStream & senf::console::Client::stream() +prefix_ std::ostream & senf::console::Client::stream() { return out_t::member; } diff --git a/Console/Server.hh b/Console/Server.hh index 7a8b831..2206172 100644 --- a/Console/Server.hh +++ b/Console/Server.hh @@ -135,17 +135,18 @@ namespace console { void stop(); ///< Stop the client /**< This will close the client socket. */ + std::string const & name() const; + ClientHandle handle() const; + std::ostream & stream(); + std::string promptString() const; + protected: private: Client(Server & server, ClientHandle handle, std::string const & name); - std::string promptString() const; - ClientHandle handle() const; - detail::NonblockingSocketOStream & stream(); - + void translate(std::string & data); void handleInput(std::string input); - virtual void v_write(boost::posix_time::ptime timestamp, std::string const & stream, std::string const & area, unsigned level, std::string const & message); @@ -160,6 +161,7 @@ namespace console { friend class Server; friend class detail::ClientReader; + friend class detail::NonblockingSocketSink; }; }} diff --git a/Console/Server.ih b/Console/Server.ih index de12c41..bffdacb 100644 --- a/Console/Server.ih +++ b/Console/Server.ih @@ -53,11 +53,11 @@ namespace detail { WriteablePolicy, ConnectedCommunicationPolicy>::policy > Handle; - NonblockingSocketSink(Handle handle); + NonblockingSocketSink(Client & client); std::streamsize write(const char * s, std::streamsize n); private: - Handle handle_; + Client & client_; }; typedef boost::iostreams::stream NonblockingSocketOStream; @@ -75,20 +75,20 @@ namespace detail { { public: typedef ServerHandle::ClientSocketHandle ClientHandle; - typedef NonblockingSocketOStream OutputStream; virtual ~ClientReader() = 0; Client & client() const; std::string promptString() const; ClientHandle handle() const; - OutputStream & stream() const; + std::ostream & stream() const; void stopClient(); void handleInput(std::string const & input) const; void disablePrompt(); void enablePrompt(); + void translate(std::string & data); protected: ClientReader(Client & client); @@ -96,6 +96,7 @@ namespace detail { private: virtual void v_disablePrompt() = 0; virtual void v_enablePrompt() = 0; + virtual void v_translate(std::string & data) = 0; Client & client_; }; @@ -114,6 +115,7 @@ namespace detail { private: virtual void v_disablePrompt(); virtual void v_enablePrompt(); + virtual void v_translate(std::string & data); void clientData(senf::ReadHelper::ptr helper); void showPrompt(); diff --git a/Console/testServer.cc b/Console/testServer.cc index a1c3c2b..70b8d34 100644 --- a/Console/testServer.cc +++ b/Console/testServer.cc @@ -77,17 +77,14 @@ int main(int, char **) senf::console::root() .doc("This is the console test application"); senf::console::root() - .mkdir("network") + .mkdir("test") .doc("Network related settings"); - senf::console::root()["network"] - .mkdir("eth0") - .doc("Ethernet device eth0"); senf::console::root() .mkdir("server"); senf::console::root()["server"] .add("shutdown", &shutdownServer) .doc("Terminate server application"); - senf::console::root()["network"] + senf::console::root()["test"] .add("echo", &echo) .doc("Example of a function utilizing manual argument parsing"); diff --git a/SConstruct b/SConstruct index 5707f05..d316b40 100644 --- a/SConstruct +++ b/SConstruct @@ -142,7 +142,7 @@ def configFilesOpts(target, source, env, for_signature): env.Append( CPPPATH = [ '#/include' ], - LIBS = [ 'iberty', '$BOOSTREGEXLIB', '$BOOSTIOSTREAMSLIB' ], + LIBS = [ 'iberty', 'readline', '$BOOSTREGEXLIB', '$BOOSTIOSTREAMSLIB' ], TEST_EXTRA_LIBS = [ '$BOOSTFSLIB' ], DOXY_XREF_TYPES = [ 'bug', 'fixme', 'todo', 'idea' ], DOXY_HTML_XSL = '#/doclib/html-munge.xsl',