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);
}
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();
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)
}
} // namespace utils
-
-
-
-
-
#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
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);
{
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
{
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);
}
signalfd_siginfo sigInfo;
utils::read(mFD, &sigInfo, sizeof(sigInfo));
- LOGD("Got signal: " << sigInfo.ssi_signo);
+ LOGT("Got signal: " << sigInfo.ssi_signo);
{
Lock lock(mMutex);
#include <mutex>
#include <unordered_map>
#include <memory>
+#include <vector>
namespace utils {
/**
* 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
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;
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();
};
--- /dev/null
+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)
--- /dev/null
+#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;
+}
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);
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)
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)
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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
int fd = utils::open(mTerminals.PTYs[0].ptsName,
O_RDWR | O_CLOEXEC | O_NOCTTY);
setupIOControlTTY(fd);
-
}
{
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);
}
* 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();
#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)
Start start(mConfig);
start.execute();
-
- // TODO: UGLY: REMOVEME: read from 1st terminal
- readTerminal(mConfig.mTerminals.PTYs[0]);
}
void ContainerImpl::stop()
attach.execute();
}
+void ContainerImpl::console()
+{
+ Console console(mConfig.mTerminals);
+ console.execute();
+}
+
const std::vector<Namespace>& ContainerImpl::getNamespaces() const
{
return mNamespaces;
// Other
void attach(const std::vector<std::string>& argv,
const std::string& cwdInContainer);
+ void console();
// Network interfaces setup/config
/**
// 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,
};
struct TerminalsConfig {
- int count;
+ unsigned int count;
std::vector<TerminalConfig> PTYs;
- TerminalsConfig(const int count = 1)
+ TerminalsConfig(const unsigned int count = 1)
: count(count)
{}
}
}
-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];
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)) {
#ifndef LXCPP_TERMINAL_HPP
#define LXCPP_TERMINAL_HPP
+#include <termios.h>
+
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);
} // namespace lxcpp
-
#endif // LXCPP_TERMINAL_HPP
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));
}
{
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));