Console: Basic readling support
g0dil [Thu, 24 Apr 2008 15:47:54 +0000 (15:47 +0000)]
git-svn-id: https://svn.berlios.de/svnroot/repos/senf/trunk@823 270642c3-0616-0410-b53a-bc976706d245

Console/Readline.cc [new file with mode: 0644]
Console/Readline.cci [new file with mode: 0644]
Console/Readline.hh [new file with mode: 0644]
Console/Server.cc
Console/Server.cci
Console/Server.hh
Console/Server.ih
Console/testServer.cc
SConstruct

diff --git a/Console/Readline.cc b/Console/Readline.cc
new file mode 100644 (file)
index 0000000..43da447
--- /dev/null
@@ -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 <g0dil@berlios.de>
+//
+// 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 <stdio.h>
+#include <readline/readline.h>
+#include <readline/history.h>
+#include <boost/algorithm/string/trim.hpp>
+#include <boost/algorithm/string/replace.hpp>
+#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<char>(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"
+
+\f
+// 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 (file)
index 0000000..8d12264
--- /dev/null
@@ -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 <g0dil@berlios.de>
+//
+// 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<ClientReader*>(new DumbClientReader(client))
+                : static_cast<ClientReader*>(new ReadlineClientReader(client)) )
+{}
+
+///////////////////////////////cci.e///////////////////////////////////////
+#undef prefix_
+
+\f
+// 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 (file)
index 0000000..7ac5741
--- /dev/null
@@ -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 <g0dil@berlios.de>
+//
+// 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 <boost/scoped_ptr.hpp>
+#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<ClientReader> reader_;
+    };
+
+}}}
+
+///////////////////////////////hh.e////////////////////////////////////////
+#include "Readline.cci"
+//#include "Readline.ct"
+//#include "Readline.cti"
+#endif
+
+\f
+// 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:
index 13820cf..d6491bf 100644 (file)
@@ -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<senf::TCPSocketProtocol>().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();
 }
 
index 5147dfd..8e0d434 100644 (file)
@@ -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;
 }
index 7a8b831..2206172 100644 (file)
@@ -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;
     };
 
 }}
index de12c41..bffdacb 100644 (file)
@@ -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<NonblockingSocketSink> 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<ClientHandle>::ptr helper);
         void showPrompt();
index a1c3c2b..70b8d34 100644 (file)
@@ -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");
 
index 5707f05..d316b40 100644 (file)
@@ -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',