diff --git a/Console/Server.cc b/Console/Server.cc index 53db4e7a280c70854bbac8a984dc4413be9b4595..13820cffbbc0fd5a4902805cf276b7404ced6840 100644 --- a/Console/Server.cc +++ b/Console/Server.cc @@ -125,42 +125,86 @@ prefix_ void senf::console::Server::removeClient(Client & client) clients_.erase(boost::intrusive_ptr<Client>(&client)); } +/////////////////////////////////////////////////////////////////////////// +// senf::console::detail::DumbClientReader + +prefix_ senf::console::detail::DumbClientReader::DumbClientReader(Client & client) + : ClientReader(client), promptLen_ (0), promptActive_ (false) +{ + showPrompt(); + ReadHelper<ClientHandle>::dispatch( handle(), 16384u, ReadUntil("\n"), + senf::membind(&DumbClientReader::clientData, this) ); +} + +prefix_ void +senf::console::detail::DumbClientReader::clientData(senf::ReadHelper<ClientHandle>::ptr helper) +{ + if (helper->error() || handle().eof()) { + // THIS COMMITS SUICIDE. THE INSTANCE IS GONE AFTER stopClient RETURNS + stopClient(); + return; + } + + promptLen_ = 0; + promptActive_ = false; + + std::string data (tail_ + helper->data()); + tail_ = helper->tail(); + boost::trim(data); // Gets rid of superfluous \r or \n characters + handleInput(data); + + showPrompt(); + ReadHelper<ClientHandle>::dispatch( handle(), 16384u, ReadUntil("\n"), + senf::membind(&DumbClientReader::clientData, this) ); + +} + +prefix_ void senf::console::detail::DumbClientReader::showPrompt() +{ + std::string prompt (promptString()); + + stream() << prompt << std::flush; + promptLen_ = prompt.size(); + promptActive_ = true; +} + +prefix_ void senf::console::detail::DumbClientReader::v_disablePrompt() +{ + if (promptActive_ && promptLen_ > 0) { + stream() << '\r' << std::string(' ', promptLen_) << '\r'; + promptLen_ = 0; + } +} + +prefix_ void senf::console::detail::DumbClientReader::v_enablePrompt() +{ + if (promptActive_ && ! promptLen_) + showPrompt(); +} + /////////////////////////////////////////////////////////////////////////// // 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), promptLen_(0) + handle_ (handle), name_ (name), reader_ (0) { - showPrompt(); - ReadHelper<ClientHandle>::dispatch( handle_, 16384u, ReadUntil("\n"), - senf::membind(&Client::clientData, this) ); + reader_.reset( new detail::DumbClientReader (*this) ); route< senf::SenfLog, senf::log::NOTICE >(); } prefix_ senf::console::Client::~Client() {} -prefix_ void senf::console::Client::stopClient() +prefix_ void senf::console::Client::stop() { // THIS COMMITS SUICIDE. THE INSTANCE IS GONE AFTER removeClient RETURNS server_.removeClient(*this); } -prefix_ void senf::console::Client::clientData(ReadHelper<ClientHandle>::ptr helper) +prefix_ void senf::console::Client::handleInput(std::string data) { - promptLen_ = 0; - if (helper->error() || handle_.eof()) { - // THIS COMMITS SUICIDE. THE INSTANCE IS GONE AFTER stopClient RETURNS - stopClient(); - return; - } - - std::string data (tail_ + helper->data()); - tail_ = helper->tail(); - boost::trim(data); // Gets rid of superfluous \r or \n characters - if (data.empty()) data = lastCommand_; else @@ -168,32 +212,24 @@ prefix_ void senf::console::Client::clientData(ReadHelper<ClientHandle>::ptr hel try { if (! parser_.parse(data, boost::bind<void>( boost::ref(executor_), - boost::ref(out_t::member), + boost::ref(stream()), _1 )) ) - out_t::member << "syntax error" << std::endl; + stream() << "syntax error" << std::endl; } catch (Executor::ExitException &) { - // THIS COMMITS SUICIDE. THE INSTANCE IS GONE AFTER stopClient RETURNS - stopClient(); + // This generates an EOF condition on the Handle. This EOF condition is expected + // to be handled gracefully by the ClientReader. We cannot call stop() here, since we + // are called from the client reader callback and that will continue executing even if we + // call stop here ... + handle_.facet<senf::TCPSocketProtocol>().shutdown(senf::TCPSocketProtocol::ShutRD); return; } catch (std::exception & ex) { - out_t::member << ex.what() << std::endl; + stream() << ex.what() << std::endl; } catch (...) { - out_t::member << "unidentified error (unknown exception thrown)" << std::endl; + stream() << "unidentified error (unknown exception thrown)" << std::endl; } - - showPrompt(); - ReadHelper<ClientHandle>::dispatch( handle_, 16384u, ReadUntil("\n"), - senf::membind(&Client::clientData, this) ); -} - -prefix_ void senf::console::Client::showPrompt() -{ - std::string path (executor_.cwd().path()); - out_t::member << name_ << ":" << path << "# " << std::flush; - promptLen_ = name_.size() + 1 + path.size() + 1; } prefix_ void senf::console::Client::v_write(boost::posix_time::ptime timestamp, @@ -201,11 +237,9 @@ prefix_ void senf::console::Client::v_write(boost::posix_time::ptime timestamp, std::string const & area, unsigned level, std::string const & message) { - if (promptLen_) - out_t::member << '\r' << std::string(' ', promptLen_) << '\r'; + reader_->disablePrompt(); IOStreamTarget::v_write(timestamp, stream, area, level, message); - if (promptLen_) - showPrompt(); + reader_->enablePrompt(); } ///////////////////////////////cc.e//////////////////////////////////////// diff --git a/Console/Server.cci b/Console/Server.cci index d88e82f9926d3ce214a6bb5cfcf93168ba76801a..5147dfdba89bdb752d116a17e29176268864bd38 100644 --- a/Console/Server.cci +++ b/Console/Server.cci @@ -45,6 +45,81 @@ prefix_ void senf::console::Server::name(std::string const & name) name_ = name; } +/////////////////////////////////////////////////////////////////////////// +// senf::console::detail::ClientReader + +prefix_ senf::console::detail::ClientReader::~ClientReader() +{} + +prefix_ senf::console::Client & senf::console::detail::ClientReader::client() + const +{ + return client_; +} + +prefix_ std::string senf::console::detail::ClientReader::promptString() + const +{ + return client().promptString(); +} + +prefix_ senf::console::detail::ClientReader::ClientHandle senf::console::detail::ClientReader::handle() + const +{ + return client().handle(); +} + +prefix_ senf::console::detail::ClientReader::OutputStream & senf::console::detail::ClientReader::stream() + const +{ + return client().stream(); +} + +prefix_ void senf::console::detail::ClientReader::stopClient() +{ + client().stop(); +} + +prefix_ void senf::console::detail::ClientReader::handleInput(std::string const & input) + const +{ + client().handleInput(input); +} + +prefix_ void senf::console::detail::ClientReader::disablePrompt() +{ + v_disablePrompt(); +} + +prefix_ void senf::console::detail::ClientReader::enablePrompt() +{ + v_enablePrompt(); +} + +prefix_ senf::console::detail::ClientReader::ClientReader(Client & client) + : client_ (client) +{} + +/////////////////////////////////////////////////////////////////////////// +// senf::console::Client + +prefix_ std::string senf::console::Client::promptString() + const +{ + return name_ + ":" + executor_.cwd().path() + "$ "; +} + +prefix_ senf::console::Client::ClientHandle senf::console::Client::handle() + const +{ + return handle_; +} + +prefix_ senf::console::detail::NonblockingSocketOStream & senf::console::Client::stream() +{ + return out_t::member; +} + ///////////////////////////////cci.e/////////////////////////////////////// #undef prefix_ diff --git a/Console/Server.hh b/Console/Server.hh index 59111e63970543f0a124a1b81393421ade5a24bc..7a8b8315f16d5b48831845f13aad4b49669c46c9 100644 --- a/Console/Server.hh +++ b/Console/Server.hh @@ -76,10 +76,8 @@ namespace console { public: /////////////////////////////////////////////////////////////////////////// // Types - - typedef senf::ServerSocketHandle< - senf::MakeSocketPolicy< senf::TCPv4SocketProtocol::Policy, - senf::UnspecifiedAddressingPolicy>::policy > ServerHandle; + + typedef detail::ServerHandle ServerHandle; ~Server(); @@ -128,12 +126,13 @@ namespace console { SENF_LOG_CLASS_AREA(); SENF_LOG_DEFAULT_LEVEL( senf::log::NOTICE ); + public: typedef Server::ServerHandle::ClientSocketHandle ClientHandle; ~Client(); - void stopClient(); ///< Stop the client + void stop(); ///< Stop the client /**< This will close the client socket. */ protected: @@ -141,8 +140,11 @@ namespace console { private: Client(Server & server, ClientHandle handle, std::string const & name); - void clientData(ReadHelper<ClientHandle>::ptr helper); - void showPrompt(); + std::string promptString() const; + ClientHandle handle() const; + detail::NonblockingSocketOStream & stream(); + + void handleInput(std::string input); virtual void v_write(boost::posix_time::ptime timestamp, std::string const & stream, std::string const & area, unsigned level, @@ -150,14 +152,14 @@ namespace console { Server & server_; ClientHandle handle_; - std::string tail_; CommandParser parser_; Executor executor_; std::string name_; - unsigned promptLen_; std::string lastCommand_; + boost::scoped_ptr<detail::ClientReader> reader_; friend class Server; + friend class detail::ClientReader; }; }} diff --git a/Console/Server.ih b/Console/Server.ih index eb6ab722c53221072d2cb0a2ea6b5f89454b0f26..de12c414d0d89260642e08f3801ece488335f7db 100644 --- a/Console/Server.ih +++ b/Console/Server.ih @@ -34,6 +34,10 @@ namespace senf { namespace console { + + class Server; + class Client; + namespace detail { /** \brief Internal: Nonblocking boost::iostreams::sink @@ -58,6 +62,67 @@ namespace detail { typedef boost::iostreams::stream<NonblockingSocketSink> NonblockingSocketOStream; + typedef senf::ServerSocketHandle< + senf::MakeSocketPolicy< senf::TCPv4SocketProtocol::Policy, + senf::UnspecifiedAddressingPolicy>::policy > ServerHandle; + + /** \brief Internal: Generic client interface + + The ClientReader encapsulates the interaction of a single network client with the user: It + manages prompt display and reading an interactive command. + */ + class ClientReader + { + public: + typedef ServerHandle::ClientSocketHandle ClientHandle; + typedef NonblockingSocketOStream OutputStream; + + virtual ~ClientReader() = 0; + + Client & client() const; + std::string promptString() const; + ClientHandle handle() const; + OutputStream & stream() const; + void stopClient(); + + void handleInput(std::string const & input) const; + + void disablePrompt(); + void enablePrompt(); + + protected: + ClientReader(Client & client); + + private: + virtual void v_disablePrompt() = 0; + virtual void v_enablePrompt() = 0; + + Client & client_; + }; + + /** \brief Internal: Primitive ClientReader implementation + + This implementation uses the cooked telnet mode to read lines from the console. It does not + support explicit line editing or any other advanced features. + */ + class DumbClientReader + : public ClientReader + { + public: + DumbClientReader(Client & client); + + private: + virtual void v_disablePrompt(); + virtual void v_enablePrompt(); + + void clientData(senf::ReadHelper<ClientHandle>::ptr helper); + void showPrompt(); + + std::string tail_; + unsigned promptLen_; + bool promptActive_; + }; + }}} ///////////////////////////////ih.e//////////////////////////////////////// diff --git a/Console/testServer.cc b/Console/testServer.cc index 0ede86578de137a3de1cb87c1d2d71d1ea54a759..a1c3c2b350cbebcc574eddeecbcbf90b90002c92 100644 --- a/Console/testServer.cc +++ b/Console/testServer.cc @@ -71,6 +71,7 @@ void shutdownServer() int main(int, char **) { + ::signal(SIGPIPE, SIG_IGN); senf::log::ConsoleTarget::instance().route< senf::SenfLog, senf::log::NOTICE >(); senf::console::root() diff --git a/Socket/Protocols/INet/TCPSocketProtocol.cc b/Socket/Protocols/INet/TCPSocketProtocol.cc index a11ac983031af1a4b592682b9a09558aa5bd58c4..781b5e645a40728c8f94c86d6532b4af759cf036 100644 --- a/Socket/Protocols/INet/TCPSocketProtocol.cc +++ b/Socket/Protocols/INet/TCPSocketProtocol.cc @@ -75,6 +75,13 @@ prefix_ unsigned senf::TCPSocketProtocol::siocoutq() return n; } +prefix_ void senf::TCPSocketProtocol::shutdown(ShutType type) + const +{ + if (::shutdown(fd(), type) < 0) + SENF_THROW_SYSTEM_EXCEPTION(""); +} + prefix_ unsigned senf::TCPSocketProtocol::available() const { diff --git a/Socket/Protocols/INet/TCPSocketProtocol.hh b/Socket/Protocols/INet/TCPSocketProtocol.hh index 2fb2b3e15f7df1071a103d7bdc0f72783bdbf7bb..b9850d1aed9d4e609347296584edc1003b1b06db 100644 --- a/Socket/Protocols/INet/TCPSocketProtocol.hh +++ b/Socket/Protocols/INet/TCPSocketProtocol.hh @@ -59,6 +59,9 @@ namespace senf { unsigned siocinq() const; ///< Return current size of the input queue unsigned siocoutq() const; ///< Return current size of the output queue + enum ShutType { ShutRD, ShutWR, ShutRDWR }; + void shutdown(ShutType type) const; + ///\name Abstract Interface Implementation ///@{ diff --git a/Utils/Daemon/Daemon.cc b/Utils/Daemon/Daemon.cc index 9457d8c43c9b5d98e70a8e564c12116de6c62c43..94d95ea5a4e476a66991dd900c1b8c4d29525e44 100644 --- a/Utils/Daemon/Daemon.cc +++ b/Utils/Daemon/Daemon.cc @@ -541,6 +541,9 @@ prefix_ void senf::Daemon::installSighandlers() ::sigaction(SIGHUP, &sa, NULL); + sa.sa_handler = SIG_IGN; + ::sigaction(SIGPIPE, &sa, NULL); + #ifdef SENF_DEBUG sa.sa_sigaction = &fatalSignalsHandler; sa.sa_flags = SA_RESTART | SA_SIGINFO;