--- /dev/null
+// $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:
--- /dev/null
+// $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:
--- /dev/null
+// $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:
#include "../Utils/senfassert.hh"
#include "../Utils/membind.hh"
#include "../Utils/Logger/SenfLog.hh"
+#include "Readline.hh"
//#include "Server.mpp"
#define prefix_
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) {
;
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)
{
reader_->disablePrompt();
IOStreamTarget::v_write(timestamp, stream, area, level, message);
+ out_t::member << std::flush;
reader_->enablePrompt();
}
///////////////////////////////////////////////////////////////////////////
// senf::console::detail::NonblockingSocketSink
-prefix_ senf::console::detail::NonblockingSocketSink::NonblockingSocketSink(Handle handle)
- : handle_ (handle)
+prefix_ senf::console::detail::NonblockingSocketSink::NonblockingSocketSink(Client & client)
+ : client_ (client)
{}
///////////////////////////////////////////////////////////////////////////
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();
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)
{}
///////////////////////////////////////////////////////////////////////////
// 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
{
return handle_;
}
-prefix_ senf::console::detail::NonblockingSocketOStream & senf::console::Client::stream()
+prefix_ std::ostream & senf::console::Client::stream()
{
return out_t::member;
}
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);
friend class Server;
friend class detail::ClientReader;
+ friend class detail::NonblockingSocketSink;
};
}}
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;
{
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);
private:
virtual void v_disablePrompt() = 0;
virtual void v_enablePrompt() = 0;
+ virtual void v_translate(std::string & data) = 0;
Client & client_;
};
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();
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");
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',