Implement Container::console() 08/49308/5
authorLukasz Pawelczyk <l.pawelczyk@samsung.com>
Thu, 8 Oct 2015 11:28:31 +0000 (13:28 +0200)
committerLukasz Pawelczyk <l.pawelczyk@samsung.com>
Wed, 14 Oct 2015 10:33:18 +0000 (12:33 +0200)
[Feature]       An ability to connect to a PTY given to the container
[Cause]         To be able to use the container
[Solution]      Use PTYs, connect to them and pass read/writes between
                stdin/stdout and pty master.
[Verification]  There is a simple test program in junk for now

Additional changes with patchset:
- extended signal functions
- fixed signalFD class

Change-Id: Ia6320ee32d537311ef2675eb79f3e837192251b8

21 files changed:
common/utils/signal.cpp
common/utils/signal.hpp
common/utils/signalfd.cpp
common/utils/signalfd.hpp
junk/Makefile [new file with mode: 0644]
junk/run-shell.cpp [new file with mode: 0644]
libs/ipc/epoll/event-poll.cpp
libs/lxcpp/CMakeLists.txt
libs/lxcpp/commands/console.cpp [new file with mode: 0644]
libs/lxcpp/commands/console.hpp [new file with mode: 0644]
libs/lxcpp/commands/prep-guest-terminal.cpp
libs/lxcpp/commands/prep-host-terminal.cpp
libs/lxcpp/commands/prep-host-terminal.hpp
libs/lxcpp/container-impl.cpp
libs/lxcpp/container-impl.hpp
libs/lxcpp/container.hpp
libs/lxcpp/terminal-config.hpp
libs/lxcpp/terminal.cpp
libs/lxcpp/terminal.hpp
server/server.cpp
tests/unit_tests/utils/ut-signalfd.cpp

index 51c45b1..d961879 100644 (file)
@@ -48,13 +48,13 @@ void setSignalMask(int how, const ::sigset_t& set)
 void changeSignal(int how, const int sigNum) {
     ::sigset_t set;
     if(-1 == ::sigemptyset(&set)) {
-        const std::string msg = "Error in sigfillset: " + getSystemErrorMessage();
+        const std::string msg = "Error in sigemptyset: " + getSystemErrorMessage();
         LOGE(msg);
         throw UtilsException(msg);
     }
 
     if(-1 ==::sigaddset(&set, sigNum)) {
-        const std::string msg = "Error in sigdelset: " + getSystemErrorMessage();
+        const std::string msg = "Error in sigaddset: " + getSystemErrorMessage();
         LOGE(msg);
         throw UtilsException(msg);
     }
@@ -76,6 +76,62 @@ void changeSignal(int how, const int sigNum) {
     return set;
 }
 
+bool isSignalPending(const int sigNum)
+{
+    ::sigset_t set;
+    if (::sigpending(&set) == -1) {
+        const std::string msg = "Error in sigpending: " + getSystemErrorMessage();
+        LOGE(msg);
+        throw UtilsException(msg);
+    }
+
+    int ret = ::sigismember(&set, sigNum);
+    if (ret == -1) {
+        const std::string msg = "Error in sigismember: " + getSystemErrorMessage();
+        LOGE(msg);
+        throw UtilsException(msg);
+    }
+
+    return ret;
+}
+
+bool waitForSignal(const int sigNum, int timeoutMs)
+{
+    int timeoutS = timeoutMs / 1000;
+    timeoutMs -= timeoutS * 1000;
+
+    struct ::timespec timeout = {
+        timeoutS,
+        timeoutMs * 1000000
+    };
+
+    ::sigset_t set;
+    if(-1 == ::sigemptyset(&set)) {
+        const std::string msg = "Error in sigemptyset: " + getSystemErrorMessage();
+        LOGE(msg);
+        throw UtilsException(msg);
+    }
+
+    if(-1 ==::sigaddset(&set, sigNum)) {
+        const std::string msg = "Error in sigaddset: " + getSystemErrorMessage();
+        LOGE(msg);
+        throw UtilsException(msg);
+    }
+
+    ::siginfo_t info;
+    if (::sigtimedwait(&set, &info, &timeout) == -1) {
+        if (errno == EAGAIN) {
+            return false;
+        }
+
+        const std::string msg = "Error in sigtimedwait: " + getSystemErrorMessage();
+        LOGE(msg);
+        throw UtilsException(msg);
+    }
+
+    return true;
+}
+
 bool isSignalBlocked(const int sigNum)
 {
     ::sigset_t set = getSignalMask();
@@ -119,18 +175,36 @@ void signalUnblock(const int sigNum)
     changeSignal(SIG_UNBLOCK, sigNum);
 }
 
-void signalIgnore(const std::initializer_list<int>& signals)
+std::vector<std::pair<int, struct ::sigaction>> signalIgnore(const std::initializer_list<int>& signals)
 {
     struct ::sigaction act;
+    struct ::sigaction old;
     act.sa_handler = SIG_IGN;
+    std::vector<std::pair<int, struct ::sigaction>> oldAct;
 
     for(const int s: signals) {
-        if(-1 == ::sigaction(s, &act, nullptr)) {
+        if(-1 == ::sigaction(s, &act, &old)) {
             const std::string msg = "Error in sigaction: " + getSystemErrorMessage();
             LOGE(msg);
             throw UtilsException(msg);
         }
+        oldAct.emplace_back(s, old);
     }
+
+    return oldAct;
+}
+
+struct ::sigaction signalSet(const int sigNum, const struct ::sigaction *sigAct)
+{
+    struct ::sigaction old;
+
+    if(-1 == ::sigaction(sigNum, sigAct, &old)) {
+        const std::string msg = "Error in sigaction: " + getSystemErrorMessage();
+        LOGE(msg);
+        throw UtilsException(msg);
+    }
+
+    return old;
 }
 
 void sendSignal(const pid_t pid, const int sigNum)
@@ -145,8 +219,3 @@ void sendSignal(const pid_t pid, const int sigNum)
 }
 
 } // namespace utils
-
-
-
-
-
index f4a42e3..cb64419 100644 (file)
 
 #include <initializer_list>
 #include <csignal>
+#include <vector>
+
 
 namespace utils {
 
+
 ::sigset_t getSignalMask();
+bool isSignalPending(const int sigNum);
+bool waitForSignal(const int sigNum, int timeoutMs);
 bool isSignalBlocked(const int sigNum);
 void signalBlockAllExcept(const std::initializer_list<int>& signals);
 void signalBlock(const int sigNum);
 void signalUnblock(const int sigNum);
-void signalIgnore(const std::initializer_list<int>& signals);
+std::vector<std::pair<int, struct ::sigaction>> signalIgnore(const std::initializer_list<int>& signals);
+struct ::sigaction signalSet(const int sigNum, const struct ::sigaction *sigAct);
 void sendSignal(const pid_t pid, const int sigNum);
 
+
 } // namespace utils
 
 
index 61d04d5..0156532 100644 (file)
@@ -35,9 +35,14 @@ namespace utils {
 SignalFD::SignalFD(ipc::epoll::EventPoll& eventPoll)
     :mEventPoll(eventPoll)
 {
-    ::sigset_t set = getSignalMask();
+    int error = ::sigemptyset(&mSet);
+    if (error == -1) {
+        const std::string msg = "Error in sigemptyset: " + getSystemErrorMessage();
+        LOGE(msg);
+        throw UtilsException(msg);
+    }
 
-    mFD = ::signalfd(-1, &set, SFD_CLOEXEC);
+    mFD = ::signalfd(-1, &mSet, SFD_CLOEXEC);
     if (mFD == -1) {
         const std::string msg = "Error in signalfd: " + getSystemErrorMessage();
         LOGE(msg);
@@ -51,6 +56,20 @@ SignalFD::~SignalFD()
 {
     mEventPoll.removeFD(mFD);
     utils::close(mFD);
+
+    // Unblock the signals that have been blocked previously, but also eat
+    // them if they were pending. It seems that signals are delivered twice,
+    // independently for signalfd and async. If we don't eat them before
+    // unblocking they will be delivered immediately potentially doing harm.
+    for (const int sigNum : mBlockedSignals) {
+        waitForSignal(sigNum, 0);
+
+        // Yes, there is a race here between waitForSignal and signalUnlock, but if
+        // a signal is sent at this point it's not by us, signalFD is inactive. So
+        // if that is the case I'd expect someone to have set some handler already.
+
+        signalUnblock(sigNum);
+    }
 }
 
 int SignalFD::getFD() const
@@ -62,35 +81,30 @@ void SignalFD::setHandler(const int sigNum, const Callback&& callback)
 {
     Lock lock(mMutex);
 
-    ::sigset_t set = getSignalMask();
-
-    int error = ::signalfd(mFD, &set, SFD_CLOEXEC);
-    if (error != mFD) {
-        const std::string msg = "Error in signalfd: " + getSystemErrorMessage();
-        LOGE(msg);
-        throw UtilsException(msg);
-    }
-
-    mCallbacks.insert({sigNum, callback});
-}
-
-void SignalFD::setHandlerAndBlock(const int sigNum, const Callback&& callback)
-{
-    Lock lock(mMutex);
-
     bool isBlocked = isSignalBlocked(sigNum);
-
-    ::sigset_t set = getSignalMask();
     if(!isBlocked) {
         signalBlock(sigNum);
+        mBlockedSignals.push_back(sigNum);
+    }
+
+    int error = ::sigaddset(&mSet, sigNum);
+    if (error == -1) {
+        const std::string msg = getSystemErrorMessage();
+        LOGE("Error in signalfd: " << msg);
+        if(!isBlocked) {
+            signalUnblock(sigNum);
+            mBlockedSignals.pop_back();
+        }
+        throw UtilsException("Error in signalfd: " + msg);
     }
 
-    int error = ::signalfd(mFD, &set, SFD_CLOEXEC);
+    error = ::signalfd(mFD, &mSet, SFD_CLOEXEC);
     if (error != mFD) {
         const std::string msg = getSystemErrorMessage();
         LOGE("Error in signalfd: " << msg);
         if(!isBlocked) {
             signalUnblock(sigNum);
+            mBlockedSignals.pop_back();
         }
         throw UtilsException("Error in signalfd: " + msg);
     }
@@ -103,7 +117,7 @@ void SignalFD::handleInternal()
     signalfd_siginfo sigInfo;
     utils::read(mFD, &sigInfo, sizeof(sigInfo));
 
-    LOGD("Got signal: " << sigInfo.ssi_signo);
+    LOGT("Got signal: " << sigInfo.ssi_signo);
 
     {
         Lock lock(mMutex);
index 4efed4b..cc72ed9 100644 (file)
@@ -34,6 +34,7 @@
 #include <mutex>
 #include <unordered_map>
 #include <memory>
+#include <vector>
 
 namespace utils {
 
@@ -55,7 +56,7 @@ public:
 
     /**
      * Add a callback for a specified signal.
-     * Doesn't block the signal.
+     * Blocks the async signal handler if it's not already blocked.
      *
      * @param sigNum number of the signal
      * @param callback handler callback
@@ -63,15 +64,6 @@ public:
     void setHandler(const int sigNum, const Callback&& callback);
 
     /**
-     * Add a callback for a specified signal
-     * Blocks the asynchronous signal handling.
-     *
-     * @param sigNum number of the signal
-     * @param callback handler callback
-     */
-    void setHandlerAndBlock(const int sigNum, const Callback&& callback);
-
-    /**
      * @return signal file descriptor
      */
     int getFD() const;
@@ -80,9 +72,11 @@ private:
     typedef std::unique_lock<std::mutex> Lock;
 
     int mFD;
+    ::sigset_t mSet;
     std::mutex mMutex;
     ipc::epoll::EventPoll& mEventPoll;
     std::unordered_map<int, Callback> mCallbacks;
+    std::vector<int> mBlockedSignals;
 
     void handleInternal();
 };
diff --git a/junk/Makefile b/junk/Makefile
new file mode 100644 (file)
index 0000000..4b93782
--- /dev/null
@@ -0,0 +1,19 @@
+C=clang
+CPP=clang++
+NAME1=run-shell
+CPPFLAGS=$(shell PKG_CONFIG_PATH=/usr/local/lib64/pkgconfig/ pkg-config --cflags liblxcpp libLogger) -g -std=c++11
+LDFLAGS=$(shell PKG_CONFIG_PATH=/usr/local/lib64/pkgconfig/ pkg-config --libs liblxcpp libLogger) -g
+
+OBJECTS1=$(NAME1).o
+HEADERS=
+
+all: $(NAME1)
+
+%.o: %.cpp $(HEADERS)
+       $(CPP) -c -o $@ $< $(CPPFLAGS)
+
+$(NAME1): $(OBJECTS1)
+       $(CPP) $(OBJECTS1) $(LDFLAGS) -o $(NAME1)
+
+clean:
+       rm -f $(OBJECTS1) $(NAME1)
diff --git a/junk/run-shell.cpp b/junk/run-shell.cpp
new file mode 100644 (file)
index 0000000..81ee0d8
--- /dev/null
@@ -0,0 +1,81 @@
+#include <unistd.h>
+#include <iostream>
+#include <vector>
+#include <lxcpp/lxcpp.hpp>
+#include <lxcpp/logger-config.hpp>
+#include <logger/logger.hpp>
+#include <sys/types.h>
+#include <unistd.h>
+#include <signal.h>
+
+using namespace lxcpp;
+
+pid_t initPid = -1;
+
+void sighandler(int signal)
+{
+    // remove the log in a deffered manner
+    pid_t pid = fork();
+
+    if (pid == 0)
+    {
+        pid_t pid = fork();
+
+        if (pid == 0)
+        {
+            if (initPid > 0)
+                ::kill(initPid, SIGTERM);
+            sleep(11);
+            ::unlink("/tmp/lxcpp-shell.txt");
+
+            exit(0);
+        }
+
+        exit(0);
+    }
+
+    wait();
+    exit(0);
+}
+
+int main(int argc, char *argv[])
+{
+    signal(SIGINT, &sighandler);
+
+    logger::setupLogger(logger::LogType::LOG_STDERR, logger::LogLevel::TRACE);
+    LOGT("Color test: TRACE");
+    LOGD("Color test: DEBUG");
+    LOGI("Color test: INFO");
+    LOGW("Color test: WARN");
+    LOGE("Color test: ERROR");
+
+    logger::setupLogger(logger::LogType::LOG_STDERR, logger::LogLevel::DEBUG);
+    //logger::setupLogger(logger::LogType::LOG_FILE, logger::LogLevel::TRACE, "/tmp/lxcpp-shell.txt");
+
+    std::vector<std::string> args;
+    args.push_back("/bin/bash");
+
+    try
+    {
+        Container* c = createContainer("test", "/");
+        c->setInit(args);
+        c->setLogger(logger::LogType::LOG_FILE, logger::LogLevel::TRACE, "/tmp/lxcpp-shell.txt");
+        c->setTerminalCount(4);
+        c->start();
+        c->console();
+        // You could run the console for the second time to see if it can be reattached
+        //c->console();
+
+        initPid = c->getInitPid();
+
+        delete c;
+    }
+    catch (const std::exception &e)
+    {
+        std::cout << "EXCEPTION: " << e.what() << std::endl;
+    }
+
+    sighandler(3);
+
+    return 0;
+}
index c4b1f40..8fe41b2 100644 (file)
@@ -87,7 +87,7 @@ void EventPoll::addFD(const int fd, const Events events, Callback&& callback)
 
 void EventPoll::modifyFD(const int fd, const Events events)
 {
-    // No need to lock and check mCallbacks map
+    // No need to lock as modify doesn't touch the mCallbacks map
     if (!modifyFDInternal(fd, events)) {
         const std::string msg = "Could not modify fd: " + std::to_string(fd);
         LOGE(msg);
index 0d35bad..e3056dc 100644 (file)
@@ -51,7 +51,7 @@ TARGET_COMPILE_DEFINITIONS(${PROJECT_NAME}
     PRIVATE ATTACH_PATH="${LIBEXEC_DIR}/${ATTACH_CODENAME}"
 )
 
-ADD_DEPENDENCIES(${PROJECT_NAME} Common Logger)
+ADD_DEPENDENCIES(${PROJECT_NAME} Common Logger Config Ipc)
 
 ## Link libraries ##############################################################
 FIND_PACKAGE(Boost COMPONENTS system filesystem)
@@ -59,7 +59,7 @@ PKG_CHECK_MODULES(LXCPP_DEPS REQUIRED glib-2.0)
 
 INCLUDE_DIRECTORIES(${LIBS_FOLDER} ${COMMON_FOLDER})
 INCLUDE_DIRECTORIES(SYSTEM ${Boost_INCLUDE_DIRS} ${LXCPP_DEPS_INCLUDE_DIRS} ${JSON_C_INCLUDE_DIRS})
-TARGET_LINK_LIBRARIES(${PROJECT_NAME} Common ${pkgs_LDFLAGS} ${Boost_LIBRARIES} Logger Config util)
+TARGET_LINK_LIBRARIES(${PROJECT_NAME} Common ${pkgs_LDFLAGS} util ${Boost_LIBRARIES} Logger Config Ipc)
 
 ## Generate the pc file ########################################################
 CONFIGURE_FILE(${PC_FILE}.in ${CMAKE_CURRENT_BINARY_DIR}/${PC_FILE} @ONLY)
diff --git a/libs/lxcpp/commands/console.cpp b/libs/lxcpp/commands/console.cpp
new file mode 100644 (file)
index 0000000..3210945
--- /dev/null
@@ -0,0 +1,292 @@
+/*
+ *  Copyright (C) 2015 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License version 2.1 as published by the Free Software Foundation.
+ *
+ *  This library 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+/**
+ * @file
+ * @author  Lukasz Pawelczyk (l.pawelczyk@samsung.com)
+ * @brief   Implementation of the console
+ */
+
+#include "lxcpp/commands/console.hpp"
+#include "lxcpp/exception.hpp"
+#include "lxcpp/terminal.hpp"
+#include "lxcpp/credentials.hpp"
+
+#include "ipc/epoll/event-poll.hpp"
+#include "logger/logger.hpp"
+#include "utils/fd-utils.hpp"
+#include "utils/signal.hpp"
+
+#include <unistd.h>
+#include <functional>
+#include <iostream>
+#include <cstring>
+#include <sys/ioctl.h>
+
+
+namespace lxcpp {
+
+
+Console::Console(TerminalsConfig &terminals, unsigned int terminalNum)
+    : mTerminals(terminals),
+      mTerminalNum(terminalNum),
+      mServiceMode(false),
+      mQuitReason(ConsoleQuitReason::NONE),
+      mEventPoll(),
+      mSignalFD(mEventPoll),
+      appToTermOffset(0),
+      termToAppOffset(0)
+{
+    if (terminalNum >= terminals.count) {
+        const std::string msg = "Requested terminal number does not exist";
+        LOGE(msg);
+        throw TerminalException(msg);
+    }
+}
+
+Console::~Console()
+{
+}
+
+void Console::execute()
+{
+    if (!lxcpp::isatty(STDIN_FILENO) || !lxcpp::isatty(STDOUT_FILENO)) {
+        const std::string msg = "Standard input is not a terminal, cannot launch the console";
+        LOGE(msg);
+        throw TerminalException(msg);
+    }
+
+    LOGD("Launching the console with: " << mTerminals.count << " pseudoterminal(s) on the guest side.");
+    std::cout << "Connected to the zone, escape characted is ^] or ^a q." << std::endl;
+    std::cout << "If the container has just a shell remember to set TERM to be equal to the one of your own terminal." << std::endl;
+    std::cout << "Terminal number: " << mTerminalNum << ", use ^a n/p to switch between them." << std::endl;
+
+    setupTTY();
+    resizePTY();
+
+    using namespace std::placeholders;
+    mEventPoll.addFD(STDIN_FILENO, EPOLLIN, std::bind(&Console::onStdInput, this, _1, _2));
+    mEventPoll.addFD(STDOUT_FILENO, 0, std::bind(&Console::onStdOutput, this, _1, _2));
+    mEventPoll.addFD(getCurrentFD(), EPOLLIN, std::bind(&Console::onPTY, this, _1, _2));
+
+    while (mQuitReason == ConsoleQuitReason::NONE) {
+        mEventPoll.dispatchIteration(-1);
+    }
+
+    mEventPoll.removeFD(getCurrentFD());
+    mEventPoll.removeFD(STDIN_FILENO);
+    mEventPoll.removeFD(STDOUT_FILENO);
+
+    restoreTTY();
+
+    switch (mQuitReason) {
+    case ConsoleQuitReason::USER:
+        std::cout << std::endl << "User requested quit" << std::endl;
+        break;
+    case ConsoleQuitReason::ERR:
+        std::cout << std::endl << "There has been an error on the terminal, quitting" << std::endl;
+        break;
+    case ConsoleQuitReason::HUP:
+        std::cout << std::endl << "Terminal disconnected, quitting" << std::endl;
+        break;
+    default:
+        std::cout << std::endl << "Unknown error, quitting" << std::endl;
+        break;
+    }
+
+    // make the class reusable with subsequent execute()s
+    mQuitReason = ConsoleQuitReason::NONE;
+    mServiceMode = false;
+}
+
+void Console::setupTTY()
+{
+    // save signal state, ignore several signal, setup resize window signal
+    mSignalStates = utils::signalIgnore({SIGQUIT, SIGTERM, SIGINT, SIGHUP, SIGPIPE, SIGWINCH});
+    mSignalFD.setHandler(SIGWINCH, std::bind(&Console::resizePTY, this));
+
+    // save terminal state
+    lxcpp::tcgetattr(STDIN_FILENO, &mTTYState);
+
+    // set the current terminal in raw mode:
+    struct termios newTTY = mTTYState;
+    ::cfmakeraw(&newTTY);
+    lxcpp::tcsetattr(STDIN_FILENO, TCSAFLUSH, &newTTY);
+}
+
+void Console::resizePTY()
+{
+    // resize the underlying PTY terminal to the size of the user terminal
+    struct winsize wsz;
+    utils::ioctl(STDIN_FILENO, TIOCGWINSZ, &wsz);
+    utils::ioctl(getCurrentFD(), TIOCSWINSZ, &wsz);
+}
+
+void Console::restoreTTY()
+{
+    // restore signal state
+    for (const auto sigInfo : mSignalStates) {
+        utils::signalSet(sigInfo.first, &sigInfo.second);
+    }
+
+    // restore terminal state
+    lxcpp::tcsetattr(STDIN_FILENO, TCSAFLUSH, &mTTYState);
+}
+
+void Console::onPTY(int fd, ipc::epoll::Events events)
+{
+    if ((events & EPOLLIN) == EPOLLIN) {
+        const size_t avail = IO_BUFFER_SIZE - appToTermOffset;
+        char *buf = appToTerm + appToTermOffset;
+
+        const ssize_t read = ::read(fd, buf, avail);
+        appToTermOffset += read;
+
+        if (appToTermOffset) {
+            mEventPoll.modifyFD(STDOUT_FILENO, EPOLLOUT);
+        }
+    }
+
+    if ((events & EPOLLOUT) == EPOLLOUT && termToAppOffset) {
+        const ssize_t written = ::write(fd, termToApp, termToAppOffset);
+        ::memmove(termToApp, termToApp + written, termToAppOffset - written);
+        termToAppOffset -= written;
+
+        if (termToAppOffset == 0) {
+            mEventPoll.modifyFD(fd, EPOLLIN);
+        }
+    }
+
+    checkForError(events);
+}
+
+void Console::onStdInput(int fd, ipc::epoll::Events events)
+{
+    if ((events & EPOLLIN) == EPOLLIN) {
+        const size_t avail = IO_BUFFER_SIZE - termToAppOffset;
+        char *buf = termToApp + termToAppOffset;
+        const ssize_t read = ::read(fd, buf, avail);
+
+        if (read == 1 && handleSpecial(buf[0])) {
+            return;
+        }
+
+        termToAppOffset += read;
+
+        if (termToAppOffset) {
+            mEventPoll.modifyFD(getCurrentFD(), EPOLLIN | EPOLLOUT);
+        }
+    }
+
+    checkForError(events);
+}
+
+void Console::onStdOutput(int fd, ipc::epoll::Events events)
+{
+    if ((events & EPOLLOUT) == EPOLLOUT && appToTermOffset) {
+        const ssize_t written = ::write(fd, appToTerm, appToTermOffset);
+        ::memmove(appToTerm, appToTerm + written, appToTermOffset - written);
+        appToTermOffset -= written;
+
+        if (appToTermOffset == 0) {
+            mEventPoll.modifyFD(fd, 0);
+        }
+    }
+
+    checkForError(events);
+}
+
+void Console::checkForError(ipc::epoll::Events events)
+{
+    // TODO: ignore EPOLLHUP for now, this allows us to cycle through not
+    // connected terminals. When we can handle full containers with getty()
+    // processes we'll decide what to do about that.
+#if 0
+    if ((events & EPOLLHUP) == EPOLLHUP) {
+        mQuitReason = ConsoleQuitReason::HUP;
+    }
+#endif
+    if ((events & EPOLLERR) == EPOLLERR) {
+        mQuitReason = ConsoleQuitReason::ERR;
+    }
+}
+
+bool Console::handleSpecial(const char key)
+{
+    if (mServiceMode) {
+        mServiceMode = false;
+
+        switch(key) {
+        case 'q':
+            mQuitReason = ConsoleQuitReason::USER;
+            return true;
+        case 'n':
+            consoleChange(ConsoleChange::NEXT);
+            return true;
+        case 'p':
+            consoleChange(ConsoleChange::PREV);
+            return true;
+        default:
+            return true;
+        }
+    }
+
+    if (key == 0x1d) { // ^]
+        mQuitReason = ConsoleQuitReason::USER;
+        return true;
+    }
+    if (key == 0x01) { // ^a
+        mServiceMode = true;
+        return true;
+    }
+
+    return false;
+}
+
+void Console::consoleChange(ConsoleChange direction)
+{
+    mEventPoll.removeFD(getCurrentFD());
+
+    if (direction == ConsoleChange::NEXT) {
+        ++mTerminalNum;
+    } else if (direction == ConsoleChange::PREV) {
+        --mTerminalNum;
+    }
+
+    mTerminalNum = (mTerminalNum + mTerminals.count) % mTerminals.count;
+
+    int mode = EPOLLIN;
+    if (termToAppOffset) {
+        mode &= EPOLLOUT;
+    }
+
+    restoreTTY();
+    std::cout << "Terminal number: " << mTerminalNum << std::endl;
+    setupTTY();
+    resizePTY();
+
+    using namespace std::placeholders;
+    mEventPoll.addFD(getCurrentFD(), mode, std::bind(&Console::onPTY, this, _1, _2));
+}
+
+int Console::getCurrentFD() const
+{
+    return mTerminals.PTYs[mTerminalNum].masterFD.value;
+}
+
+
+} // namespace lxcpp
diff --git a/libs/lxcpp/commands/console.hpp b/libs/lxcpp/commands/console.hpp
new file mode 100644 (file)
index 0000000..93ac248
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ *  Copyright (C) 2015 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License version 2.1 as published by the Free Software Foundation.
+ *
+ *  This library 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+/**
+ * @file
+ * @author  Lukasz Pawelczyk (l.pawelczyk@samsung.com)
+ * @brief   Headers for the console
+ */
+
+#ifndef LXCPP_COMMANDS_CONSOLE_HPP
+#define LXCPP_COMMANDS_CONSOLE_HPP
+
+#include "lxcpp/commands/command.hpp"
+#include "lxcpp/terminal-config.hpp"
+#include "lxcpp/terminal.hpp"
+
+#include "ipc/epoll/event-poll.hpp"
+#include "utils/signalfd.hpp"
+
+#include <signal.h>
+#include <array>
+
+
+namespace lxcpp {
+
+
+class Console final: Command {
+public:
+    /**
+     * Launches the console on the current terminal
+     *
+     * @param terminals    container's terminals config
+     * @param terminalNum  initial terminal to attach to
+     */
+    Console(TerminalsConfig &terminals, unsigned int terminalNum = 0);
+    ~Console();
+
+    void execute();
+
+private:
+    enum class ConsoleQuitReason : int {
+        NONE = 0,
+        USER = 1,
+        HUP = 2,
+        ERR = 3
+    };
+    enum class ConsoleChange : int {
+        NEXT = 0,
+        PREV = 1
+    };
+    static const int IO_BUFFER_SIZE = 1024;
+
+    TerminalsConfig &mTerminals;
+    int mTerminalNum;
+    bool mServiceMode;
+    ConsoleQuitReason mQuitReason;
+    ipc::epoll::EventPoll mEventPoll;
+    utils::SignalFD mSignalFD;
+    std::vector<std::pair<int, struct ::sigaction>> mSignalStates;
+    struct termios mTTYState;
+
+    char appToTerm[IO_BUFFER_SIZE];
+    int appToTermOffset;
+    char termToApp[IO_BUFFER_SIZE];
+    int termToAppOffset;
+
+    void setupTTY();
+    void restoreTTY();
+    void resizePTY();
+    void onPTY(int fd, ipc::epoll::Events events);
+    void onStdInput(int fd, ipc::epoll::Events events);
+    void onStdOutput(int fd, ipc::epoll::Events events);
+    void checkForError(ipc::epoll::Events events);
+    bool handleSpecial(char key);
+    void consoleChange(ConsoleChange direction);
+    int getCurrentFD() const;
+};
+
+
+} // namespace lxcpp
+
+
+#endif // LXCPP_COMMANDS_CONSOLE_HPP
index f496f86..5722fef 100644 (file)
@@ -57,7 +57,6 @@ void PrepGuestTerminal::execute()
     int fd = utils::open(mTerminals.PTYs[0].ptsName,
                          O_RDWR | O_CLOEXEC | O_NOCTTY);
     setupIOControlTTY(fd);
-
 }
 
 
index 1dc4f3d..0b57f89 100644 (file)
@@ -43,8 +43,8 @@ void PrepHostTerminal::execute()
 {
     LOGD("Creating " << mTerminals.count << " pseudoterminal(s) on the host side:");
 
-    for (int i = 0; i < mTerminals.count; ++i) {
-        const auto pty = lxcpp::openPty(true);
+    for (unsigned int i = 0; i < mTerminals.count; ++i) {
+        const auto pty = lxcpp::openPty(false);
         LOGD(pty.second << " has been created");
         mTerminals.PTYs.emplace_back(pty.first, pty.second);
     }
index f7153b2..9902a2d 100644 (file)
@@ -39,9 +39,9 @@ public:
      * It creates a number of pseudoterminals and stores them
      * to be passed to the guard and prepared for init process.
      *
-     * @param config container's config
+     * @param terminals  container's terminals config
      */
-    PrepHostTerminal(TerminalsConfig &config);
+    PrepHostTerminal(TerminalsConfig &terminals);
     ~PrepHostTerminal();
 
     void execute();
index 5f0da35..09b165f 100644 (file)
@@ -27,6 +27,7 @@
 #include "lxcpp/filesystem.hpp"
 #include "lxcpp/capability.hpp"
 #include "lxcpp/commands/attach.hpp"
+#include "lxcpp/commands/console.hpp"
 #include "lxcpp/commands/start.hpp"
 #include "lxcpp/commands/stop.hpp"
 #include "lxcpp/commands/prep-host-terminal.hpp"
 #include <stdio.h>
 
 
-namespace {
-
-// TODO: UGLY: REMOVEME:
-// It will be removed as soon as Container::console() will get implemented
-// I need it for now to know I didn't brake anything. It will be eradicated.
-void readTerminal(const lxcpp::TerminalConfig &term)
-{
-    char *buf = NULL;
-    size_t size = 0;
-    ssize_t ret;
-
-    printf("%s output:\n", term.ptsName.c_str());
-    usleep(10000);
-
-    FILE *fp = fdopen(term.masterFD.value, "r");
-    while((ret = getline(&buf, &size, fp)) != -1L) {
-        printf("%s", buf);
-    }
-    free(buf);
-}
-
-} // namespace
-
 namespace lxcpp {
 
 ContainerImpl::ContainerImpl(const std::string &name, const std::string &path)
@@ -182,9 +160,6 @@ void ContainerImpl::start()
 
     Start start(mConfig);
     start.execute();
-
-    // TODO: UGLY: REMOVEME: read from 1st terminal
-    readTerminal(mConfig.mTerminals.PTYs[0]);
 }
 
 void ContainerImpl::stop()
@@ -228,6 +203,12 @@ void ContainerImpl::attach(const std::vector<std::string>& argv,
     attach.execute();
 }
 
+void ContainerImpl::console()
+{
+    Console console(mConfig.mTerminals);
+    console.execute();
+}
+
 const std::vector<Namespace>& ContainerImpl::getNamespaces() const
 {
     return mNamespaces;
index 846536e..4b560f1 100644 (file)
@@ -68,6 +68,7 @@ public:
     // Other
     void attach(const std::vector<std::string>& argv,
                 const std::string& cwdInContainer);
+    void console();
 
     // Network interfaces setup/config
     /**
index ba32e64..2ca3a10 100644 (file)
@@ -75,6 +75,7 @@ public:
     // Other
     virtual void attach(const std::vector<std::string>& argv,
                         const std::string& cwdInContainer) = 0;
+    virtual void console() = 0;
 
     // Network interfaces setup/config
     virtual void addInterfaceConfig(const std::string& hostif,
index 3d6675a..f4a675c 100644 (file)
@@ -55,10 +55,10 @@ struct TerminalConfig {
 };
 
 struct TerminalsConfig {
-    int count;
+    unsigned int count;
     std::vector<TerminalConfig> PTYs;
 
-    TerminalsConfig(const int count = 1)
+    TerminalsConfig(const unsigned int count = 1)
         : count(count)
     {}
 
index 72c57af..5374ced 100644 (file)
@@ -49,24 +49,6 @@ void openpty(int *master, int *slave)
     }
 }
 
-void tcgetattr(const int fd, struct termios *termios_p)
-{
-    if (-1 == ::tcgetattr(fd, termios_p)) {
-        const std::string msg = "tcgetattr() failed: " + utils::getSystemErrorMessage();
-        LOGE(msg);
-        throw TerminalException(msg);
-    }
-}
-
-void tcsetattr(const int fd, const int optional_actions, const struct termios *termios_p)
-{
-    if (-1 == ::tcsetattr(fd, optional_actions, termios_p)) {
-        const std::string msg = "tcsetattr() failed: " + utils::getSystemErrorMessage();
-        LOGE(msg);
-        throw TerminalException(msg);
-    }
-}
-
 std::string ttyname_r(const int fd)
 {
     char ptsName[PATH_MAX];
@@ -135,6 +117,24 @@ bool isatty(int fd)
     throw TerminalException(msg);
 }
 
+void tcgetattr(const int fd, struct termios *termios_p)
+{
+    if (-1 == ::tcgetattr(fd, termios_p)) {
+        const std::string msg = "tcgetattr() failed: " + utils::getSystemErrorMessage();
+        LOGE(msg);
+        throw TerminalException(msg);
+    }
+}
+
+void tcsetattr(const int fd, const int optional_actions, const struct termios *termios_p)
+{
+    if (-1 == ::tcsetattr(fd, optional_actions, termios_p)) {
+        const std::string msg = "tcsetattr() failed: " + utils::getSystemErrorMessage();
+        LOGE(msg);
+        throw TerminalException(msg);
+    }
+}
+
 void setupIOControlTTY(const int ttyFD)
 {
     if (!lxcpp::isatty(ttyFD)) {
index 421e4ce..7b024f0 100644 (file)
@@ -24,6 +24,8 @@
 #ifndef LXCPP_TERMINAL_HPP
 #define LXCPP_TERMINAL_HPP
 
+#include <termios.h>
+
 
 namespace lxcpp {
 
@@ -40,11 +42,21 @@ namespace lxcpp {
 int nullStdFDs();
 
 /**
- * Checks if a file descriptor is a terminal
+ * Checks if a file descriptor is a terminal.
  */
 bool isatty(int fd);
 
 /**
+ * Get terminal attributes.
+ */
+void tcgetattr(const int fd, struct termios *termios_p);
+
+/**
+ * Set terminal attributes.
+ */
+void tcsetattr(const int fd, const int optional_actions, const struct termios *termios_p);
+
+/**
  * Setups the passed fd as a new control and IO (in, out, err) terminal
  */
 void setupIOControlTTY(const int ttyFD);
@@ -65,5 +77,4 @@ std::pair<int, std::string> openPty(bool rawMode);
 } // namespace lxcpp
 
 
-
 #endif // LXCPP_TERMINAL_HPP
index 2fc8a65..bb028c7 100644 (file)
@@ -84,8 +84,8 @@ Server::Server(const std::string& configPath)
       mZonesManager(mEventPoll, mConfigPath),
       mDispatchingThread(::pthread_self())
 {
-    mSignalFD.setHandlerAndBlock(SIGUSR1, std::bind(&Server::handleUpdate, this));
-    mSignalFD.setHandlerAndBlock(SIGINT, std::bind(&Server::handleStop, this));
+    mSignalFD.setHandler(SIGUSR1, std::bind(&Server::handleUpdate, this));
+    mSignalFD.setHandler(SIGINT, std::bind(&Server::handleStop, this));
     mSignalFD.setHandler(SIGTERM, std::bind(&Server::handleStop, this));
 }
 
index 9f002e0..5a78b73 100644 (file)
@@ -79,8 +79,8 @@ BOOST_AUTO_TEST_CASE(BlockingSignalHandler)
 {
     ipc::epoll::EventPoll poll;
     SignalFD s(poll);
-    s.setHandlerAndBlock(SIGUSR1, [](const int) {});
-    s.setHandlerAndBlock(SIGINT, [](const int) {});
+    s.setHandler(SIGUSR1, [](const int) {});
+    s.setHandler(SIGINT, [](const int) {});
 
     ::raise(SIGINT);
     std::this_thread::sleep_for(std::chrono::milliseconds(TIMEOUT));