From b25dcc4c14d972123e109517585ff36989d05d1a Mon Sep 17 00:00:00 2001 From: Jan Olszak Date: Mon, 3 Nov 2014 08:52:00 +0200 Subject: [PATCH 01/16] IPC via UX sockets [Bug/Feature] IPC for communication between the library and daemon [Cause] N/A [Solution] N/A [Verification] Build, install, run tests Change-Id: I9880c7b4f3104b93f38d0e6ad86762fb17013d28 --- common/ipc/client.cpp | 80 +++++++ common/ipc/client.hpp | 156 +++++++++++++ common/ipc/exception.hpp | 45 ++++ common/ipc/internals/acceptor.cpp | 131 +++++++++++ common/ipc/internals/acceptor.hpp | 87 +++++++ common/ipc/internals/event-queue.hpp | 112 +++++++++ common/ipc/internals/eventfd.cpp | 77 +++++++ common/ipc/internals/eventfd.hpp | 62 +++++ common/ipc/internals/processor.cpp | 434 +++++++++++++++++++++++++++++++++++ common/ipc/internals/processor.hpp | 418 +++++++++++++++++++++++++++++++++ common/ipc/internals/socket.cpp | 200 ++++++++++++++++ common/ipc/internals/socket.hpp | 116 ++++++++++ common/ipc/internals/utils.cpp | 134 +++++++++++ common/ipc/internals/utils.hpp | 77 +++++++ common/ipc/service.cpp | 86 +++++++ common/ipc/service.hpp | 168 ++++++++++++++ common/ipc/types.hpp | 48 ++++ packaging/security-containers.spec | 1 + server/CMakeLists.txt | 2 +- tests/unit_tests/CMakeLists.txt | 3 +- tests/unit_tests/ipc/ut-ipc.cpp | 391 +++++++++++++++++++++++++++++++ 21 files changed, 2826 insertions(+), 2 deletions(-) create mode 100644 common/ipc/client.cpp create mode 100644 common/ipc/client.hpp create mode 100644 common/ipc/exception.hpp create mode 100644 common/ipc/internals/acceptor.cpp create mode 100644 common/ipc/internals/acceptor.hpp create mode 100644 common/ipc/internals/event-queue.hpp create mode 100644 common/ipc/internals/eventfd.cpp create mode 100644 common/ipc/internals/eventfd.hpp create mode 100644 common/ipc/internals/processor.cpp create mode 100644 common/ipc/internals/processor.hpp create mode 100644 common/ipc/internals/socket.cpp create mode 100644 common/ipc/internals/socket.hpp create mode 100644 common/ipc/internals/utils.cpp create mode 100644 common/ipc/internals/utils.hpp create mode 100644 common/ipc/service.cpp create mode 100644 common/ipc/service.hpp create mode 100644 common/ipc/types.hpp create mode 100644 tests/unit_tests/ipc/ut-ipc.cpp diff --git a/common/ipc/client.cpp b/common/ipc/client.cpp new file mode 100644 index 0000000..c1651f3 --- /dev/null +++ b/common/ipc/client.cpp @@ -0,0 +1,80 @@ +/* +* Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved +* +* Contact: Jan Olszak +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License +*/ + +/** + * @file + * @author Jan Olszak (j.olszak@samsung.com) + * @brief Handling client connections + */ + +#include "config.hpp" + +#include "ipc/client.hpp" +#include "ipc/internals/socket.hpp" +#include "ipc/exception.hpp" + +namespace security_containers { +namespace ipc { + +Client::Client(const std::string& socketPath) + : mSocketPath(socketPath) +{ + LOGD("Creating client"); +} + +Client::~Client() +{ + LOGD("Destroying client..."); + try { + stop(); + } catch (IPCException& e) { + LOGE("Error in Client's destructor: " << e.what()); + } + LOGD("Destroyed client"); +} + +void Client::start() +{ + LOGD("Starting client..."); + + // Initialize the connection with the server + LOGD("Connecting to " + mSocketPath); + auto socketPtr = std::make_shared(Socket::connectSocket(mSocketPath)); + mServiceID = mProcessor.addPeer(socketPtr); + + // Start listening + mProcessor.start(); + + LOGD("Started client"); +} + +void Client::stop() +{ + LOGD("Stopping client..."); + mProcessor.stop(); + LOGD("Stopped"); +} + +void Client::removeMethod(const MethodID methodID) +{ + LOGD("Removing method id: " << methodID); + mProcessor.removeMethod(methodID); +} + +} // namespace ipc +} // namespace security_containers diff --git a/common/ipc/client.hpp b/common/ipc/client.hpp new file mode 100644 index 0000000..7429b24 --- /dev/null +++ b/common/ipc/client.hpp @@ -0,0 +1,156 @@ +/* +* Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved +* +* Contact: Jan Olszak +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License +*/ + +/** + * @file + * @author Jan Olszak (j.olszak@samsung.com) + * @brief Handling client connections + */ + +#ifndef COMMON_IPC_CLIENT_HPP +#define COMMON_IPC_CLIENT_HPP + +#include "ipc/internals/processor.hpp" +#include "ipc/types.hpp" +#include "logger/logger.hpp" + +#include + +namespace security_containers { +namespace ipc { + +/** + * This class wraps communication via UX sockets for client applications. + * It uses serialization mechanism from libConfig. + * + * There is one additional thread: + * - PROCESSOR is responsible for the communication and calling the callbacks + * + * For message format @see ipc::Processor + */ +class Client { +public: + typedef Processor::MethodID MethodID; + + /** + * @param serverPath path to the server's socket + */ + Client(const std::string& serverPath); + ~Client(); + + Client(const Client&) = delete; + Client& operator=(const Client&) = delete; + + /** + * Starts the worker thread + */ + void start(); + + /** + * Stops all worker thread + */ + void stop(); + + /** + * Saves the callback connected to the method id. + * When a message with the given method id is received + * the data will be parsed and passed to this callback. + * + * @param methodID API dependent id of the method + * @param methodCallback method handling implementation + */ + template + void addMethodHandler(const MethodID methodID, + const typename MethodHandler::type& method); + + /** + * Removes the callback + * + * @param methodID API dependent id of the method + */ + void removeMethod(const MethodID methodID); + + /** + * Synchronous method call. + * + * @param methodID API dependent id of the method + * @param data data to send + * @param timeoutMS how long to wait for the return value before throw + * @return result data + */ + template + std::shared_ptr callSync(const MethodID methodID, + const std::shared_ptr& data, + unsigned int timeoutMS = 500); + + /** + * Asynchronous method call. The return callback will be called on + * return data arrival. It will be run in the PROCESSOR thread. + * + * + * @param methodID API dependent id of the method + * @param sendCallback callback for data serialization + * @param resultCallback callback for result serialization and handling + */ + template + void callAsync(const MethodID methodID, + const std::shared_ptr& data, + const typename ResultHandler::type& resultCallback); + +private: + Processor::PeerID mServiceID; + Processor mProcessor; + std::string mSocketPath; +}; + +template +void Client::addMethodHandler(const MethodID methodID, + const typename MethodHandler::type& method) +{ + LOGD("Adding method with id " << methodID); + mProcessor.addMethodHandler(methodID, method); + LOGD("Added method with id " << methodID); +} + +template +std::shared_ptr Client::callSync(const MethodID methodID, + const std::shared_ptr& data, + unsigned int timeoutMS) +{ + LOGD("Sync calling method: " << methodID); + return mProcessor.callSync(methodID, mServiceID, data, timeoutMS); +} + +template +void Client::callAsync(const MethodID methodID, + const std::shared_ptr& data, + const typename ResultHandler::type& resultCallback) +{ + LOGD("Async calling method: " << methodID); + mProcessor.callAsync(methodID, + mServiceID, + data, + resultCallback); + LOGD("Async called method: " << methodID); +} + +} // namespace ipc +} // namespace security_containers + +#endif // COMMON_IPC_CLIENT_HPP diff --git a/common/ipc/exception.hpp b/common/ipc/exception.hpp new file mode 100644 index 0000000..67d9c86 --- /dev/null +++ b/common/ipc/exception.hpp @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved + * + * Contact: Jan Olszak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +/** + * @file + * @author Jan Olszak (j.olszak@samsung.com) + * @brief Exceptions for the IPC + */ + + +#ifndef COMMON_IPC_EXCEPTION_HPP +#define COMMON_IPC_EXCEPTION_HPP + +#include "base-exception.hpp" + +namespace security_containers { + + +/** + * Base class for exceptions in IPC + */ +struct IPCException: public SecurityContainersException { + IPCException(const std::string& error) : SecurityContainersException(error) {} +}; + + +} + + +#endif // COMMON_IPC_EXCEPTION_HPP diff --git a/common/ipc/internals/acceptor.cpp b/common/ipc/internals/acceptor.cpp new file mode 100644 index 0000000..9738546 --- /dev/null +++ b/common/ipc/internals/acceptor.cpp @@ -0,0 +1,131 @@ +/* +* Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved +* +* Contact: Jan Olszak +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License +*/ + +/** + * @file + * @author Jan Olszak (j.olszak@samsung.com) + * @brief Class for accepting new connections + */ + +#include "config.hpp" + +#include "ipc/exception.hpp" +#include "ipc/internals/utils.hpp" +#include "ipc/internals/acceptor.hpp" +#include "logger/logger.hpp" + +#include +#include +#include +#include +#include + +namespace security_containers { +namespace ipc { + +Acceptor::Acceptor(const std::string& socketPath, const NewConnectionCallback& newConnectionCallback) + : mNewConnectionCallback(newConnectionCallback), + mSocket(Socket::createSocket(socketPath)) +{ + LOGT("Creating Acceptor for socket " << socketPath); +} + +Acceptor::~Acceptor() +{ + LOGT("Destroying Acceptor"); + try { + stop(); + } catch (IPCException& e) { + LOGE("Error in destructor: " << e.what()); + } + LOGT("Destroyed Acceptor"); +} + +void Acceptor::start() +{ + LOGT("Starting Acceptor"); + if (!mThread.joinable()) { + mThread = std::thread(&Acceptor::run, this); + } + LOGT("Started Acceptor"); +} + +void Acceptor::stop() +{ + LOGT("Stopping Acceptor"); + if (mThread.joinable()) { + LOGT("Event::FINISH -> Acceptor"); + mEventQueue.send(Event::FINISH); + LOGT("Waiting for Acceptor to finish"); + mThread.join(); + } + LOGT("Stopped Acceptor"); +} + +void Acceptor::run() +{ + // Setup polling structure + std::vector fds(2); + + fds[0].fd = mEventQueue.getFD(); + fds[0].events = POLLIN; + + fds[1].fd = mSocket.getFD(); + fds[1].events = POLLIN; + + // Main loop + bool isRunning = true; + while (isRunning) { + LOGT("Waiting for new connections..."); + + int ret = ::poll(fds.data(), fds.size(), -1 /*blocking call*/); + + LOGT("...Incoming connection!"); + + if (ret == -1 || ret == 0) { + if (errno == EINTR) { + continue; + } + LOGE("Error in poll: " << std::string(strerror(errno))); + throw IPCException("Error in poll: " + std::string(strerror(errno))); + break; + } + + // Check for incoming connections + if (fds[1].revents & POLLIN) { + fds[1].revents = 0; + std::shared_ptr tmpSocket = mSocket.accept(); + mNewConnectionCallback(tmpSocket); + } + + // Check for incoming events + if (fds[0].revents & POLLIN) { + fds[0].revents = 0; + + if (mEventQueue.receive() == Event::FINISH) { + LOGD("Event FINISH"); + isRunning = false; + break; + } + } + } + LOGT("Exiting run"); +} + +} // namespace ipc +} // namespace security_containers diff --git a/common/ipc/internals/acceptor.hpp b/common/ipc/internals/acceptor.hpp new file mode 100644 index 0000000..b863400 --- /dev/null +++ b/common/ipc/internals/acceptor.hpp @@ -0,0 +1,87 @@ +/* +* Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved +* +* Contact: Jan Olszak +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License +*/ + +/** + * @file + * @author Jan Olszak (j.olszak@samsung.com) + * @brief Class for accepting new connections + */ + +#ifndef COMMON_IPC_INTERNALS_ACCEPTOR_HPP +#define COMMON_IPC_INTERNALS_ACCEPTOR_HPP + +#include "config.hpp" + +#include "ipc/internals/socket.hpp" +#include "ipc/internals/event-queue.hpp" + +#include +#include + +namespace security_containers { +namespace ipc { + +/** + * Accepts new connections and passes the new socket to a callback. + */ +class Acceptor { +public: + + typedef std::function& socketPtr)> NewConnectionCallback; + + /** + * Class for accepting new connections. + * + * @param socketPath path to the socket + * @param newConnectionCallback called on new connections + */ + Acceptor(const std::string& socketPath, + const NewConnectionCallback& newConnectionCallback); + ~Acceptor(); + + Acceptor(const Acceptor& acceptor) = delete; + Acceptor& operator=(const Acceptor&) = delete; + + /** + * Starts the thread accepting the new connections. + */ + void start(); + + /** + * Stops the accepting thread. + */ + void stop(); + +private: + enum class Event : int { + FINISH // Shutdown request + }; + + NewConnectionCallback mNewConnectionCallback; + Socket mSocket; + + EventQueue mEventQueue; + std::thread mThread; + + void run(); +}; + +} // namespace ipc +} // namespace security_containers + +#endif // COMMON_IPC_INTERNALS_ACCEPTOR_HPP diff --git a/common/ipc/internals/event-queue.hpp b/common/ipc/internals/event-queue.hpp new file mode 100644 index 0000000..82cb2ff --- /dev/null +++ b/common/ipc/internals/event-queue.hpp @@ -0,0 +1,112 @@ +/* +* Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved +* +* Contact: Jan Olszak +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License +*/ + +/** + * @file + * @author Jan Olszak (j.olszak@samsung.com) + * @brief Class for passing events using eventfd mechanism + */ + +#ifndef COMMON_IPC_INTERNALS_EVENT_QUEUE_HPP +#define COMMON_IPC_INTERNALS_EVENT_QUEUE_HPP + +#include "ipc/exception.hpp" +#include "ipc/internals/eventfd.hpp" +#include "logger/logger.hpp" + +#include +#include +#include + +namespace security_containers { +namespace ipc { + + +/** + * This class implements a simple FIFO queue of events. + * One can listen for the event with select/poll/epoll. + * + * @tparam MessageType type to pass as event's value + */ +template +class EventQueue { +public: + EventQueue() = default; + ~EventQueue() = default; + EventQueue(const EventQueue& eventQueue) = delete; + EventQueue& operator=(const EventQueue&) = delete; + + /** + * @return reference to the event's file descriptor + */ + int getFD() const; + + /** + * Send an event of a given value + * + * @param value size of the buffer + */ + void send(const MessageType& mess); + + /** + * Receives the signal. + * Blocks if there is no event. + * + * @return event's value + */ + MessageType receive(); + +private: + typedef std::lock_guard Lock; + + std::mutex mCommunicationMutex; + std::queue mMessages; + + EventFD mEventFD; +}; + +template +int EventQueue::getFD() const +{ + return mEventFD.getFD(); +} + +template +void EventQueue::send(const MessageType& mess) +{ + Lock lock(mCommunicationMutex); + LOGT("Sending event"); + mMessages.push(mess); + mEventFD.send(); +} + +template +MessageType EventQueue::receive() +{ + Lock lock(mCommunicationMutex); + mEventFD.receive(); + LOGT("Received event"); + MessageType mess = mMessages.front(); + mMessages.pop(); + return mess; +} + +} // namespace ipc +} // namespace security_containers + +#endif // COMMON_IPC_INTERNALS_EVENT_QUEUE_HPP diff --git a/common/ipc/internals/eventfd.cpp b/common/ipc/internals/eventfd.cpp new file mode 100644 index 0000000..c8a17b6 --- /dev/null +++ b/common/ipc/internals/eventfd.cpp @@ -0,0 +1,77 @@ +/* +* Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved +* +* Contact: Jan Olszak +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License +*/ + +/** + * @file + * @author Jan Olszak (j.olszak@samsung.com) + * @brief Linux socket wrapper + */ + +#include "config.hpp" + +#include "ipc/internals/eventfd.hpp" +#include "ipc/internals/utils.hpp" +#include "ipc/exception.hpp" +#include "logger/logger.hpp" + +#include +#include +#include +#include + +namespace security_containers { +namespace ipc { + +EventFD::EventFD() +{ + mFD = ::eventfd(0, EFD_SEMAPHORE); + if (mFD == -1) { + LOGE("Error in eventfd: " << std::string(strerror(errno))); + throw IPCException("Error in eventfd: " + std::string(strerror(errno))); + } +} + +EventFD::~EventFD() +{ + try { + ipc::close(mFD); + } catch (IPCException& e) { + LOGE("Error in Event's destructor: " << e.what()); + } +} + +int EventFD::getFD() const +{ + return mFD; +} + +void EventFD::send() +{ + const std::uint64_t toSend = 1; + ipc::write(mFD, &toSend, sizeof(toSend)); +} + +void EventFD::receive() +{ + std::uint64_t readBuffer; + ipc::read(mFD, &readBuffer, sizeof(readBuffer)); +} + + +} // namespace ipc +} // namespace security_containers diff --git a/common/ipc/internals/eventfd.hpp b/common/ipc/internals/eventfd.hpp new file mode 100644 index 0000000..9de6f17 --- /dev/null +++ b/common/ipc/internals/eventfd.hpp @@ -0,0 +1,62 @@ +/* +* Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved +* +* Contact: Jan Olszak +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License +*/ + +/** + * @file + * @author Jan Olszak (j.olszak@samsung.com) + * @brief Eventfd wrapper + */ + +#ifndef COMMON_IPC_INTERNALS_EVENTFD_HPP +#define COMMON_IPC_INTERNALS_EVENTFD_HPP + +namespace security_containers { +namespace ipc { + +class EventFD { +public: + + EventFD(); + ~EventFD(); + EventFD(const EventFD& eventfd) = delete; + EventFD& operator=(const EventFD&) = delete; + + /** + * @return event's file descriptor. + */ + int getFD() const; + + /** + * Send an event of a given value + */ + void send(); + + /** + * Receives the signal. + * Blocks if there is no event. + */ + void receive(); + +private: + int mFD; +}; + +} // namespace ipc +} // namespace security_containers + +#endif // COMMON_IPC_INTERNALS_EVENTFD_HPP diff --git a/common/ipc/internals/processor.cpp b/common/ipc/internals/processor.cpp new file mode 100644 index 0000000..0465574 --- /dev/null +++ b/common/ipc/internals/processor.cpp @@ -0,0 +1,434 @@ +/* +* Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved +* +* Contact: Jan Olszak +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License +*/ + +/** + * @file + * @author Jan Olszak (j.olszak@samsung.com) + * @brief Data and event processing thread + */ + +#include "config.hpp" + +#include "ipc/exception.hpp" +#include "ipc/internals/processor.hpp" +#include "ipc/internals/utils.hpp" + +#include +#include +#include +#include + +#include +#include + + +namespace security_containers { +namespace ipc { + +const Processor::MethodID Processor::RETURN_METHOD_ID = std::numeric_limits::max(); + +Processor::Processor(const PeerCallback& newPeerCallback, + const PeerCallback& removedPeerCallback, + const unsigned int maxNumberOfPeers) + : mNewPeerCallback(newPeerCallback), + mRemovedPeerCallback(removedPeerCallback), + mMaxNumberOfPeers(maxNumberOfPeers), + mMessageIDCounter(0), + mPeerIDCounter(0) +{ + LOGT("Creating Processor"); +} + +Processor::~Processor() +{ + LOGT("Destroying Processor"); + try { + stop(); + } catch (IPCException& e) { + LOGE("Error in Processor's destructor: " << e.what()); + } + LOGT("Destroyed Processor"); +} + +void Processor::start() +{ + LOGT("Starting Processor"); + if (!mThread.joinable()) { + mThread = std::thread(&Processor::run, this); + } + LOGT("Started Processor"); +} + +void Processor::stop() +{ + LOGT("Stopping Processor"); + if (mThread.joinable()) { + mEventQueue.send(Event::FINISH); + mThread.join(); + } + LOGT("Stopped Processor"); +} + +void Processor::removeMethod(const MethodID methodID) +{ + LOGT("Removing method " << methodID); + Lock lock(mCallsMutex); + mMethodsCallbacks.erase(methodID); +} + +Processor::PeerID Processor::addPeer(const std::shared_ptr& socketPtr) +{ + LOGT("Adding socket"); + PeerID peerID; + { + Lock lock(mSocketsMutex); + peerID = getNextPeerID(); + SocketInfo socketInfo; + socketInfo.peerID = peerID; + socketInfo.socketPtr = std::move(socketPtr); + mNewSockets.push(std::move(socketInfo)); + } + LOGI("New peer added. Id: " << peerID); + mEventQueue.send(Event::NEW_PEER); + + return peerID; +} + +void Processor::removePeer(PeerID peerID) +{ + LOGW("Removing naughty peer. ID: " << peerID); + { + Lock lock(mSocketsMutex); + mSockets.erase(peerID); + } + resetPolling(); +} + +void Processor::resetPolling() +{ + LOGI("Resetting polling"); + // Setup polling on eventfd and sockets + Lock lock(mSocketsMutex); + mFDs.resize(mSockets.size() + 1); + + mFDs[0].fd = mEventQueue.getFD(); + mFDs[0].events = POLLIN; + + auto socketIt = mSockets.begin(); + for (unsigned int i = 1; i < mFDs.size(); ++i) { + mFDs[i].fd = socketIt->second->getFD(); + mFDs[i].events = POLLIN | POLLHUP; // Listen for input events + ++socketIt; + // TODO: It's possible to block on writing to fd. Maybe listen for POLLOUT too? + } +} + +void Processor::run() +{ + resetPolling(); + + mIsRunning = true; + while (mIsRunning) { + LOGT("Waiting for communication..."); + int ret = poll(mFDs.data(), mFDs.size(), -1 /*blocking call*/); + LOGT("... incoming communication!"); + if (ret == -1 || ret == 0) { + if (errno == EINTR) { + continue; + } + LOGE("Error in poll: " << std::string(strerror(errno))); + throw IPCException("Error in poll: " + std::string(strerror(errno))); + } + + // Check for lost connections: + if (handleLostConnections()) { + // mFDs changed + continue; + } + + // Check for incoming data. + if (handleInputs()) { + // mFDs changed + continue; + } + + // Check for incoming events + if (handleEvent()) { + // mFDs changed + continue; + } + } +} + +bool Processor::handleLostConnections() +{ + std::list peersToRemove; + + { + Lock lock(mSocketsMutex); + auto socketIt = mSockets.begin(); + for (unsigned int i = 1; i < mFDs.size(); ++i, ++socketIt) { + if (mFDs[i].revents & POLLHUP) { + LOGI("Lost connection to peer: " << socketIt->first); + mFDs[i].revents &= ~(POLLHUP); + peersToRemove.push_back(socketIt->first); + } + } + + for (const auto peerID : peersToRemove) { + LOGT("Removing peer. ID: " << peerID); + mSockets.erase(peerID); + } + } + + if (!peersToRemove.empty()) { + resetPolling(); + } + + return !peersToRemove.empty(); +} + +bool Processor::handleInputs() +{ + std::list> > peersWithInput; + { + Lock lock(mSocketsMutex); + auto socketIt = mSockets.begin(); + for (unsigned int i = 1; i < mFDs.size(); ++i, ++socketIt) { + if (mFDs[i].revents & POLLIN) { + mFDs[i].revents &= ~(POLLIN); + peersWithInput.push_back(*socketIt); + } + } + } + + bool pollChanged = false; + // Handle input outside the critical section + for (const auto& peer : peersWithInput) { + pollChanged = pollChanged || handleInput(peer.first, *peer.second); + } + return pollChanged; +} + +bool Processor::handleInput(const PeerID peerID, const Socket& socket) +{ + LOGT("Handle incoming data"); + MethodID methodID; + MessageID messageID; + { + LOGI("Locking"); + Socket::Guard guard = socket.getGuard(); + socket.read(&methodID, sizeof(methodID)); + socket.read(&messageID, sizeof(messageID)); + LOGI("Locked"); + + if (methodID == RETURN_METHOD_ID) { + LOGI("Return value for messageID: " << messageID); + ReturnCallbacks returnCallbacks; + try { + Lock lock(mReturnCallbacksMutex); + LOGT("Getting the return callback"); + returnCallbacks = std::move(mReturnCallbacks.at(messageID)); + mReturnCallbacks.erase(messageID); + } catch (const std::out_of_range&) { + LOGW("No return callback for messageID: " << messageID); + return false; + } + + std::shared_ptr data; + try { + LOGT("Parsing incoming return data"); + data = returnCallbacks.parse(socket.getFD()); + } catch (const IPCException&) { + removePeer(peerID); + return true; + } + + guard.unlock(); + + LOGT("Process callback for methodID: " << methodID << "; messageID: " << messageID); + returnCallbacks.process(data); + + } else { + LOGI("Remote call; methodID: " << methodID << " messageID: " << messageID); + std::shared_ptr methodCallbacks; + try { + Lock lock(mCallsMutex); + methodCallbacks = mMethodsCallbacks.at(methodID); + } catch (const std::out_of_range&) { + LOGW("No method callback for methodID: " << methodID); + removePeer(peerID); + return true; + } + + std::shared_ptr data; + try { + LOGT("Parsing incoming data"); + data = methodCallbacks->parse(socket.getFD()); + } catch (const IPCException&) { + removePeer(peerID); + return true; + } + + guard.unlock(); + + LOGT("Process callback for methodID: " << methodID << "; messageID: " << messageID); + std::shared_ptr returnData = methodCallbacks->method(data); + + LOGT("Sending return data; methodID: " << methodID << "; messageID: " << messageID); + try { + // Send the call with the socket + Socket::Guard guard = socket.getGuard(); + socket.write(&RETURN_METHOD_ID, sizeof(RETURN_METHOD_ID)); + socket.write(&messageID, sizeof(messageID)); + methodCallbacks->serialize(socket.getFD(), returnData); + } catch (const IPCException&) { + removePeer(peerID); + return true; + } + } + } + + return false; +} + +bool Processor::handleEvent() +{ + if (!(mFDs[0].revents & POLLIN)) { + // No event to serve + return false; + } + + mFDs[0].revents &= ~(POLLIN); + + switch (mEventQueue.receive()) { + case Event::FINISH: { + LOGD("Event FINISH"); + mIsRunning = false; + return false; + } + + case Event::CALL: { + LOGD("Event CALL"); + handleCall(); + return false; + } + + case Event::NEW_PEER: { + LOGD("Event NEW_PEER"); + SocketInfo socketInfo; + { + Lock lock(mSocketsMutex); + + socketInfo = std::move(mNewSockets.front()); + mNewSockets.pop(); + + if (mSockets.size() > mMaxNumberOfPeers) { + LOGE("There are too many peers. I don't accept the connection with " << socketInfo.peerID); + + } + if (mSockets.count(socketInfo.peerID) != 0) { + LOGE("There already was a socket for peerID: " << socketInfo.peerID); + } + mSockets[socketInfo.peerID] = socketInfo.socketPtr; + } + resetPolling(); + + if (mNewPeerCallback) { + // Notify about the new user. + mNewPeerCallback(socketInfo.peerID); + } + return true; + } + } + + return false; +} + +Processor::MessageID Processor::getNextMessageID() +{ + // TODO: This method of generating UIDs is buggy. To be changed. + return ++mMessageIDCounter; +} + +Processor::PeerID Processor::getNextPeerID() +{ + // TODO: This method of generating UIDs is buggy. To be changed. + return ++mPeerIDCounter; +} + +Processor::Call Processor::getCall() +{ + Lock lock(mCallsMutex); + if (mCalls.empty()) { + LOGE("Calls queue empty"); + throw IPCException("Calls queue empty"); + } + Call call = std::move(mCalls.front()); + mCalls.pop(); + return call; +} + +void Processor::handleCall() +{ + LOGT("Handle call from another thread"); + Call call = getCall(); + + ReturnCallbacks returnCallbacks; + returnCallbacks.parse = call.parse; + returnCallbacks.process = call.process; + + std::shared_ptr socketPtr; + try { + // Get the addressee's socket + Lock lock(mSocketsMutex); + socketPtr = mSockets.at(call.peerID); + } catch (const std::out_of_range&) { + LOGE("Peer disconnected. No socket with a peerID: " << call.peerID); + return; + } + + MessageID messageID = getNextMessageID(); + + { + // Set what to do with the return message + Lock lock(mReturnCallbacksMutex); + if (mReturnCallbacks.count(messageID) != 0) { + LOGE("There already was a return callback for messageID: " << messageID); + } + mReturnCallbacks[messageID] = std::move(returnCallbacks); + } + + try { + // Send the call with the socket + Socket::Guard guard = socketPtr->getGuard(); + socketPtr->write(&call.methodID, sizeof(call.methodID)); + socketPtr->write(&messageID, sizeof(messageID)); + call.serialize(socketPtr->getFD(), call.data); + } catch (const std::exception& e) { + LOGE("Error during sending a message: " << e.what()); + { + Lock lock(mReturnCallbacksMutex); + mReturnCallbacks.erase(messageID); + } + // TODO: User should get the error code. + } +} + +} // namespace ipc +} // namespace security_containers diff --git a/common/ipc/internals/processor.hpp b/common/ipc/internals/processor.hpp new file mode 100644 index 0000000..cb14a67 --- /dev/null +++ b/common/ipc/internals/processor.hpp @@ -0,0 +1,418 @@ +/* +* Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved +* +* Contact: Jan Olszak +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License +*/ + +/** + * @file + * @author Jan Olszak (j.olszak@samsung.com) + * @brief Data and event processing thread + */ + +#ifndef COMMON_IPC_INTERNALS_PROCESSOR_HPP +#define COMMON_IPC_INTERNALS_PROCESSOR_HPP + +#include "ipc/internals/socket.hpp" +#include "ipc/internals/event-queue.hpp" +#include "ipc/exception.hpp" +#include "ipc/types.hpp" +#include "config/manager.hpp" +#include "config/is-visitable.hpp" +#include "logger/logger.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace security_containers { +namespace ipc { +namespace { +const unsigned int DEFAULT_MAX_NUMBER_OF_PEERS = 500; +} +/** +* This class wraps communication via UX sockets +* +* It's intended to be used both in Client and Service classes. +* It uses a serialization mechanism from libConfig. +* Library user will only have to pass the types that each call will send and receive +* +* Message format: +* - MethodID - probably casted enum. +* MethodID == std::numeric_limits::max() is reserved for return messages +* - MessageID - unique id of a message exchange sent by this object instance. Used to identify reply messages. +* - Rest: The data written in a callback. One type per method.ReturnCallbacks +* +* TODO: +* - error codes passed to async callbacks +* - remove ReturnCallbacks on peer disconnect +* - on sync timeout erase the return callback +* - don't throw timeout if the message is already processed +* - naming convention or methods that just commissions the PROCESS thread to do something +* - removePeer API function +*/ +class Processor { +public: + typedef std::function PeerCallback; + typedef unsigned int PeerID; + typedef unsigned int MethodID; + + /** + * Method ID. Used to indicate a message with the return value. + */ + static const MethodID RETURN_METHOD_ID; + /** + * Constructs the Processor, but doesn't start it. + * The object is ready to add methods. + * + * @param newPeerCallback called when a new peer arrives + * @param removedPeerCallback called when the Processor stops listening for this peer + */ + Processor(const PeerCallback& newPeerCallback = nullptr, + const PeerCallback& removedPeerCallback = nullptr, + const unsigned int maxNumberOfPeers = DEFAULT_MAX_NUMBER_OF_PEERS); + ~Processor(); + + Processor(const Processor&) = delete; + Processor(Processor&&) = delete; + Processor& operator=(const Processor&) = delete; + + /** + * Start the processing thread. + * Quits immediately after starting the thread. + */ + void start(); + + /** + * Stops the processing thread. + * No incoming data will be handled after. + */ + void stop(); + + /** + * From now on socket is owned by the Processor object. + * Calls the newPeerCallback. + * + * @param socketPtr pointer to the new socket + * @return peerID of the new socket + */ + PeerID addPeer(const std::shared_ptr& socketPtr); + + /** + * Saves the callbacks connected to the method id. + * When a message with the given method id is received, + * the data will be passed to the serialization callback through file descriptor. + * + * Then the process callback will be called with the parsed data. + * + * @param methodID API dependent id of the method + * @param process data processing callback + * @tparam SentDataType data type to send + * @tparam ReceivedDataType data type to receive + */ + template + void addMethodHandler(const MethodID methodID, + const typename MethodHandler::type& process); + + /** + * Removes the callback + * + * @param methodID API dependent id of the method + */ + void removeMethod(const MethodID methodID); + + /** + * Synchronous method call. + * + * @param methodID API dependent id of the method + * @param peerID id of the peer + * @param data data to sent + * @param timeoutMS how long to wait for the return value before throw + * @tparam SentDataType data type to send + * @tparam ReceivedDataType data type to receive + */ + template + std::shared_ptr callSync(const MethodID methodID, + const PeerID peerID, + const std::shared_ptr& data, + unsigned int timeoutMS = 500); + + /** + * Asynchronous method call + * + * @param methodID API dependent id of the method + * @param peerID id of the peer + * @param data data to sent + * @param process callback processing the return data + * @tparam SentDataType data type to send + * @tparam ReceivedDataType data type to receive + */ + template + void callAsync(const MethodID methodID, + const PeerID peerID, + const std::shared_ptr& data, + const typename ResultHandler::type& process); + + +private: + typedef std::function& data)> SerializeCallback; + typedef std::function(int fd)> ParseCallback; + typedef std::lock_guard Lock; + typedef unsigned int MessageID; + + struct Call { + Call(const Call& other) = delete; + Call& operator=(const Call&) = delete; + Call() = default; + Call(Call&&) = default; + + PeerID peerID; + MethodID methodID; + std::shared_ptr data; + SerializeCallback serialize; + ParseCallback parse; + ResultHandler::type process; + }; + + struct MethodHandlers { + MethodHandlers(const MethodHandlers& other) = delete; + MethodHandlers& operator=(const MethodHandlers&) = delete; + MethodHandlers() = default; + MethodHandlers(MethodHandlers&&) = default; + MethodHandlers& operator=(MethodHandlers &&) = default; + + SerializeCallback serialize; + ParseCallback parse; + MethodHandler::type method; + }; + + struct ReturnCallbacks { + ReturnCallbacks(const ReturnCallbacks& other) = delete; + ReturnCallbacks& operator=(const ReturnCallbacks&) = delete; + ReturnCallbacks() = default; + ReturnCallbacks(ReturnCallbacks&&) = default; + ReturnCallbacks& operator=(ReturnCallbacks &&) = default; + + ParseCallback parse; + ResultHandler::type process; + }; + + struct SocketInfo { + SocketInfo(const SocketInfo& other) = delete; + SocketInfo& operator=(const SocketInfo&) = delete; + SocketInfo() = default; + SocketInfo(SocketInfo&&) = default; + SocketInfo& operator=(SocketInfo &&) = default; + + std::shared_ptr socketPtr; + PeerID peerID; + }; + + enum class Event : int { + FINISH, // Shutdown request + CALL, // New method call in the queue + NEW_PEER // New peer in the queue + }; + EventQueue mEventQueue; + + + bool mIsRunning; + + // Mutex for the Calls queue and the map of methods. + std::mutex mCallsMutex; + std::queue mCalls; + std::unordered_map> mMethodsCallbacks; + + // Mutex for changing mSockets map. + // Shouldn't be locked on any read/write, that could block. Just copy the ptr. + std::mutex mSocketsMutex; + std::unordered_map > mSockets; + std::queue mNewSockets; + + // Mutex for modifying the map with return callbacks + std::mutex mReturnCallbacksMutex; + std::unordered_map mReturnCallbacks; + + + PeerCallback mNewPeerCallback; + PeerCallback mRemovedPeerCallback; + + unsigned int mMaxNumberOfPeers; + + std::thread mThread; + std::vector mFDs; + + std::atomic mMessageIDCounter; + std::atomic mPeerIDCounter; + + void run(); + bool handleEvent(); + void handleCall(); + bool handleLostConnections(); + bool handleInputs(); + bool handleInput(const PeerID peerID, const Socket& socket); + void resetPolling(); + MessageID getNextMessageID(); + PeerID getNextPeerID(); + Call getCall(); + void removePeer(PeerID peerID); + +}; + +template +void Processor::addMethodHandler(const MethodID methodID, + const typename MethodHandler::type& method) +{ + static_assert(config::isVisitable::value, + "Use the libConfig library"); + static_assert(config::isVisitable::value, + "Use the libConfig library"); + + if (methodID == RETURN_METHOD_ID) { + LOGE("Forbidden methodID: " << methodID); + throw IPCException("Forbidden methodID: " + std::to_string(methodID)); + } + + using namespace std::placeholders; + + MethodHandlers methodCall; + + methodCall.parse = [](const int fd)->std::shared_ptr { + std::shared_ptr data(new ReceivedDataType()); + config::loadFromFD(fd, *data); + return data; + }; + + methodCall.serialize = [](const int fd, std::shared_ptr& data)->void { + config::saveToFD(fd, *std::static_pointer_cast(data)); + }; + + methodCall.method = [method](std::shared_ptr& data)->std::shared_ptr { + std::shared_ptr tmpData = std::static_pointer_cast(data); + return method(tmpData); + }; + + { + Lock lock(mCallsMutex); + mMethodsCallbacks[methodID] = std::make_shared(std::move(methodCall)); + } +} + +template +void Processor::callAsync(const MethodID methodID, + const PeerID peerID, + const std::shared_ptr& data, + const typename ResultHandler::type& process) +{ + static_assert(config::isVisitable::value, + "Use the libConfig library"); + static_assert(config::isVisitable::value, + "Use the libConfig library"); + + if (!mThread.joinable()) { + LOGE("The Processor thread is not started. Can't send any data."); + throw IPCException("The Processor thread is not started. Can't send any data."); + } + + using namespace std::placeholders; + + Call call; + call.peerID = peerID; + call.methodID = methodID; + call.data = data; + + call.parse = [](const int fd)->std::shared_ptr { + std::shared_ptr data(new ReceivedDataType()); + config::loadFromFD(fd, *data); + return data; + }; + + call.serialize = [](const int fd, std::shared_ptr& data)->void { + config::saveToFD(fd, *std::static_pointer_cast(data)); + }; + + call.process = [process](std::shared_ptr& data)->void { + std::shared_ptr tmpData = std::static_pointer_cast(data); + return process(tmpData); + }; + + { + Lock lock(mCallsMutex); + mCalls.push(std::move(call)); + } + + mEventQueue.send(Event::CALL); +} + + +template +std::shared_ptr Processor::callSync(const MethodID methodID, + const PeerID peerID, + const std::shared_ptr& data, + unsigned int timeoutMS) +{ + static_assert(config::isVisitable::value, + "Use the libConfig library"); + static_assert(config::isVisitable::value, + "Use the libConfig library"); + + if (!mThread.joinable()) { + LOGE("The Processor thread is not started. Can't send any data."); + throw IPCException("The Processor thread is not started. Can't send any data."); + } + + std::shared_ptr result; + + std::mutex mtx; + std::unique_lock lck(mtx); + std::condition_variable cv; + + auto process = [&result, &cv](std::shared_ptr returnedData) { + result = returnedData; + cv.notify_one(); + }; + + callAsync(methodID, + peerID, + data, + process); + + auto isResultInitialized = [&result]() { + return static_cast(result); + }; + + if (!cv.wait_for(lck, std::chrono::milliseconds(timeoutMS), isResultInitialized)) { + LOGE("Function call timeout; methodID: " << methodID); + throw IPCException("Function call timeout; methodID: " + std::to_string(methodID)); + } + + return result; +} + + +} // namespace ipc +} // namespace security_containers + +#endif // COMMON_IPC_INTERNALS_PROCESSOR_HPP diff --git a/common/ipc/internals/socket.cpp b/common/ipc/internals/socket.cpp new file mode 100644 index 0000000..002b9cf --- /dev/null +++ b/common/ipc/internals/socket.cpp @@ -0,0 +1,200 @@ +/* +* Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved +* +* Contact: Jan Olszak +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License +*/ + +/** + * @file + * @author Jan Olszak (j.olszak@samsung.com) + * @brief Linux socket wrapper + */ + +#include "config.hpp" + +#include "ipc/exception.hpp" +#include "ipc/internals/socket.hpp" +#include "ipc/internals/utils.hpp" +#include "logger/logger.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace security_containers { +namespace ipc { + +namespace { +const int MAX_QUEUE_LENGTH = 1000; +} + +Socket::Socket(int socketFD) + : mFD(socketFD) +{ +} + +Socket::Socket(Socket&& socket) + : mFD(socket.mFD) +{ + socket.mFD = -1; +} + +Socket::~Socket() +{ + try { + ipc::close(mFD); + } catch (IPCException& e) { + LOGE("Error in Socket's destructor: " << e.what()); + } +} + +Socket::Guard Socket::getGuard() const +{ + return Guard(mCommunicationMutex); +} + +int Socket::getFD() const +{ + return mFD; +} + +std::shared_ptr Socket::accept() +{ + int sockfd = ::accept(mFD, nullptr, nullptr); + if (sockfd == -1) { + LOGE("Error in accept: " << std::string(strerror(errno))); + IPCException("Error in accept: " + std::string(strerror(errno))); + } + return std::make_shared(sockfd); +} + +void Socket::write(const void* bufferPtr, const size_t size) const +{ + Guard guard(mCommunicationMutex); + ipc::write(mFD, bufferPtr, size); +} + +void Socket::read(void* bufferPtr, const size_t size) const +{ + Guard guard(mCommunicationMutex); + ipc::read(mFD, bufferPtr, size); +} + +int Socket::getSystemdSocket(const std::string& path) +{ + int n = ::sd_listen_fds(-1 /*Block further calls to sd_listen_fds*/); + if (n < 0) { + LOGE("sd_listen_fds fails with errno: " + n); + throw IPCException("sd_listen_fds fails with errno: " + n); + } + + for (int fd = SD_LISTEN_FDS_START; + fd < SD_LISTEN_FDS_START + n; + ++fd) { + if (0 < ::sd_is_socket_unix(SD_LISTEN_FDS_START, SOCK_STREAM, 1, path.c_str(), 0)) { + return fd; + } + } + LOGW("No usable sockets were passed by systemd."); + return -1; +} + +int Socket::createDomainSocket(const std::string& path) +{ + // Isn't the path too long? + if (path.size() >= sizeof(sockaddr_un::sun_path)) { + LOGE("Socket's path too long"); + throw IPCException("Socket's path too long"); + } + + int sockfd = ::socket(AF_UNIX, SOCK_STREAM, 0); + if (sockfd == -1) { + LOGE("Error in socket: " + std::string(strerror(errno))); + throw IPCException("Error in socket: " + std::string(strerror(errno))); + } + + ::sockaddr_un serverAddress; + serverAddress.sun_family = AF_UNIX; + ::strncpy(serverAddress.sun_path, path.c_str(), sizeof(sockaddr_un::sun_path)); + unlink(serverAddress.sun_path); + + // Everybody can access the socket + // TODO: Use SMACK to guard the socket + if (-1 == ::bind(sockfd, + reinterpret_cast(&serverAddress), + sizeof(struct sockaddr_un))) { + std::string message = strerror(errno); + ::close(sockfd); + LOGE("Error in bind: " << message); + IPCException("Error in bind: " + message); + } + + if (-1 == ::listen(sockfd, + MAX_QUEUE_LENGTH)) { + std::string message = strerror(errno); + ::close(sockfd); + LOGE("Error in listen: " << message); + IPCException("Error in listen: " + message); + } + + return sockfd; +} + +Socket Socket::createSocket(const std::string& path) +{ + // Initialize a socket + int fd = getSystemdSocket(path); + fd = fd != -1 ? fd : createDomainSocket(path); + + return Socket(fd); +} + +Socket Socket::connectSocket(const std::string& path) +{ + // Isn't the path too long? + if (path.size() >= sizeof(sockaddr_un::sun_path)) { + LOGE("Socket's path too long"); + throw IPCException("Socket's path too long"); + } + + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd == -1) { + LOGE("Error in socket: " + std::string(strerror(errno))); + throw IPCException("Error in socket: " + std::string(strerror(errno))); + } + + sockaddr_un serverAddress; + serverAddress.sun_family = AF_UNIX; + strncpy(serverAddress.sun_path, path.c_str(), sizeof(sockaddr_un::sun_path)); + + if (-1 == connect(fd, + reinterpret_cast(&serverAddress), + sizeof(struct sockaddr_un))) { + ::close(fd); + LOGE("Error in connect: " + std::string(strerror(errno))); + throw IPCException("Error in connect: " + std::string(strerror(errno))); + } + + return Socket(fd); +} + +} // namespace ipc +} // namespace security_containers diff --git a/common/ipc/internals/socket.hpp b/common/ipc/internals/socket.hpp new file mode 100644 index 0000000..b7a6d40 --- /dev/null +++ b/common/ipc/internals/socket.hpp @@ -0,0 +1,116 @@ +/* +* Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved +* +* Contact: Jan Olszak +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License +*/ + +/** + * @file + * @author Jan Olszak (j.olszak@samsung.com) + * @brief Linux socket wrapper + */ + +#ifndef COMMON_IPC_INTERNALS_SOCKET_HPP +#define COMMON_IPC_INTERNALS_SOCKET_HPP + +#include +#include +#include + +namespace security_containers { +namespace ipc { + +/** + * This class wraps all operations possible to do with a socket. + * + * It has operations both for client and server application. + */ +class Socket { +public: + + typedef std::unique_lock Guard; + + /** + * Default constructor. + * If socketFD is passed then it's passed by the Socket + * + * @param socketFD socket obtained outside the class. + */ + Socket(int socketFD = -1); + Socket(Socket&& socket); + ~Socket(); + Socket& operator=(const Socket&) = delete; + + /** + * @return reference to the socket's file descriptor + */ + int getFD() const; + + /** + * Write data using the file descriptor + * + * @param bufferPtr buffer with the data + * @param size size of the buffer + */ + void write(const void* bufferPtr, const size_t size) const; + + /** + * Reads a value of the given type. + * + * @param bufferPtr buffer with the data + * @param size size of the buffer + */ + void read(void* bufferPtr, const size_t size) const; + + /** + * Accepts connection. Used by a server application. + * Blocking, called by a server. + */ + std::shared_ptr accept() ; + + /** + */ + Socket::Guard getGuard() const; + + + /** + * Prepares socket for accepting connections. + * Called by a server. + * + * @param path path to the socket + * @return created socket + */ + static Socket createSocket(const std::string& path); + + /** + * Connects to a socket. Called as a client. + * + * @param path path to the socket + * @return connected socket + */ + static Socket connectSocket(const std::string& path); + +private: + int mFD; + mutable std::recursive_mutex mCommunicationMutex; + + static int createDomainSocket(const std::string& path); + static int getSystemdSocket(const std::string& path); +}; + +} // namespace ipc +} // namespace security_containers + +#endif // COMMON_IPC_INTERNALS_SOCKET_HPP diff --git a/common/ipc/internals/utils.cpp b/common/ipc/internals/utils.cpp new file mode 100644 index 0000000..e98b60d --- /dev/null +++ b/common/ipc/internals/utils.cpp @@ -0,0 +1,134 @@ +/* +* Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved +* +* Contact: Jan Olszak +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License +*/ + +/** + * @file + * @author Jan Olszak (j.olszak@samsung.com) + * @brief Utility functions + */ + +#include "config.hpp" + +#include "ipc/exception.hpp" +#include "ipc/internals/utils.hpp" +#include "logger/logger.hpp" + +#include +#include +#include + +#include +#include + +namespace fs = boost::filesystem; + +namespace security_containers { +namespace ipc { + +void close(int fd) +{ + if (fd < 0) { + return; + } + + for (;;) { + if (-1 == ::close(fd)) { + if (errno == EINTR) { + LOGD("Close interrupted by a signal, retrying"); + continue; + } + LOGE("Error in close: " << std::string(strerror(errno))); + throw IPCException("Error in close: " + std::string(strerror(errno))); + } + break; + } +} + +void write(int fd, const void* bufferPtr, const size_t size) +{ + size_t nTotal = 0; + int n; + + do { + n = ::write(fd, + reinterpret_cast(bufferPtr) + nTotal, + size - nTotal); + if (n < 0) { + if (errno == EINTR) { + LOGD("Write interrupted by a signal, retrying"); + continue; + } + LOGE("Error during writing: " + std::string(strerror(errno))); + throw IPCException("Error during witting: " + std::string(strerror(errno))); + } + nTotal += n; + } while (nTotal < size); +} + +void read(int fd, void* bufferPtr, const size_t size) +{ + size_t nTotal = 0; + int n; + + do { + n = ::read(fd, + reinterpret_cast(bufferPtr) + nTotal, + size - nTotal); + if (n < 0) { + if (errno == EINTR) { + LOGD("Read interrupted by a signal, retrying"); + continue; + } + LOGE("Error during reading: " + std::string(strerror(errno))); + throw IPCException("Error during reading: " + std::string(strerror(errno))); + } + nTotal += n; + } while (nTotal < size); +} + +unsigned int getMaxFDNumber() +{ + struct rlimit rlim; + if (-1 == getrlimit(RLIMIT_NOFILE, &rlim)) { + LOGE("Error during getrlimit: " + std::string(strerror(errno))); + throw IPCException("Error during getrlimit: " + std::string(strerror(errno))); + } + return rlim.rlim_cur; +} + +void setMaxFDNumber(unsigned int limit) +{ + struct rlimit rlim; + rlim.rlim_cur = limit; + rlim.rlim_max = limit; + if (-1 == setrlimit(RLIMIT_NOFILE, &rlim)) { + LOGE("Error during setrlimit: " + std::string(strerror(errno))); + throw IPCException("Error during setrlimit: " + std::string(strerror(errno))); + } +} + +unsigned int getFDNumber() +{ + const std::string path = "/proc/self/fd/"; + return std::distance(fs::directory_iterator(path), + fs::directory_iterator()); +} + +} // namespace ipc +} // namespace security_containers + diff --git a/common/ipc/internals/utils.hpp b/common/ipc/internals/utils.hpp new file mode 100644 index 0000000..0b1815d --- /dev/null +++ b/common/ipc/internals/utils.hpp @@ -0,0 +1,77 @@ +/* +* Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved +* +* Contact: Jan Olszak +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License +*/ + +/** + * @file + * @author Jan Olszak (j.olszak@samsung.com) + * @brief Utility functions + */ + +#ifndef COMMON_IPC_INTERNALS_UTILS_HPP +#define COMMON_IPC_INTERNALS_UTILS_HPP + +#include + +namespace security_containers { +namespace ipc { + +/** + * Close the file descriptor. + * Repeat until + */ +void close(int fd); + +/** + * Write to a file descriptor, throw on error. + * + * @param fd file descriptor + * @param bufferPtr pointer to the data buffer + * @param size size of data to write + */ +void write(int fd, const void* bufferPtr, const size_t size); + +/** + * Read from a file descriptor, throw on error. + * + * @param fd file descriptor + * @param bufferPtr pointer to the data buffer + * @param size size of the data to read + */ +void read(int fd, void* bufferPtr, const size_t size); + +/** + * @return the max number of file descriptors for this process. + */ +unsigned int getMaxFDNumber(); + +/** + * Set the software and hardware limit of file descriptors for this process. + * + * @param limit limit of file descriptors + */ +void setMaxFDNumber(unsigned int limit); + +/** + * @return number of opened file descriptors by this process + */ +unsigned int getFDNumber(); + +} // namespace ipc +} // namespace security_containers + +#endif // COMMON_IPC_INTERNALS_UTILS_HPP diff --git a/common/ipc/service.cpp b/common/ipc/service.cpp new file mode 100644 index 0000000..d2590d6 --- /dev/null +++ b/common/ipc/service.cpp @@ -0,0 +1,86 @@ +// /* +// * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved +// * +// * Contact: Jan Olszak +// * +// * Licensed under the Apache License, Version 2.0 (the "License"); +// * you may not use this file except in compliance with the License. +// * You may obtain a copy of the License at +// * +// * http://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS" BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License +// */ + +// /** +// * @file +// * @author Jan Olszak (j.olszak@samsung.com) +// * @brief Implementation of the IPC handling class +// */ + +#include "config.hpp" + +#include "ipc/service.hpp" +#include "ipc/exception.hpp" +#include "logger/logger.hpp" + +using namespace std::placeholders; + +namespace security_containers { +namespace ipc { + +Service::Service(const std::string& socketPath, + const PeerCallback& addPeerCallback, + const PeerCallback& removePeerCallback) + : mProcessor(addPeerCallback, removePeerCallback), + mAcceptor(socketPath, std::bind(&Processor::addPeer, &mProcessor, _1)) + +{ + LOGD("Creating server"); +} + +Service::~Service() +{ + LOGD("Destroying server..."); + try { + stop(); + } catch (IPCException& e) { + LOGE("Error in Service's destructor: " << e.what()); + } + LOGD("Destroyed"); +} + +void Service::start() +{ + LOGD("Starting server"); + mProcessor.start(); + + // There can be an incoming connection from mAcceptor before mProcessor is listening, + // but it's OK. It will handle the connection when ready. So no need to wait for mProcessor. + mAcceptor.start(); + + LOGD("Started server"); +} + +void Service::stop() +{ + LOGD("Stopping server.."); + mAcceptor.stop(); + mProcessor.stop(); + LOGD("Stopped"); +} + +void Service::removeMethod(const MethodID methodID) +{ + LOGD("Removing method " << methodID); + mProcessor.removeMethod(methodID); + LOGD("Removed " << methodID); +} + + +} // namespace ipc +} // namespace security_containers diff --git a/common/ipc/service.hpp b/common/ipc/service.hpp new file mode 100644 index 0000000..08f7ec9 --- /dev/null +++ b/common/ipc/service.hpp @@ -0,0 +1,168 @@ +/* +* Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved +* +* Contact: Jan Olszak +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License +*/ + +/** + * @file + * @author Jan Olszak (j.olszak@samsung.com) + * @brief Declaration of the IPC handling class + */ + +#ifndef COMMON_IPC_SERVICE_HPP +#define COMMON_IPC_SERVICE_HPP + +#include "ipc/internals/processor.hpp" +#include "ipc/internals/acceptor.hpp" +#include "ipc/types.hpp" +#include "logger/logger.hpp" + +#include + +namespace security_containers { +namespace ipc { + + +/** + * This class wraps communication via UX sockets. + * It uses serialization mechanism from libConfig. + * + * There are two working threads: + * - ACCEPTOR accepts incoming connections and passes them to PROCESSOR + * - PROCESSOR is responsible for the communication and calling the callbacks + * + * For message format @see ipc::Processor + */ +class Service { +public: + typedef Processor::PeerCallback PeerCallback; + typedef Processor::PeerID PeerID; + typedef Processor::MethodID MethodID; + + /** + * @param path path to the socket + */ + Service(const std::string& path, + const PeerCallback& addPeerCallback = nullptr, + const PeerCallback& removePeerCallback = nullptr); + ~Service(); + + Service(const Service&) = delete; + Service& operator=(const Service&) = delete; + + /** + * Starts the worker and acceptor threads + */ + void start(); + + /** + * Stops all working threads + */ + void stop(); + + /** + * Saves the callback connected to the method id. + * When a message with the given method id is received + * the data will be parsed and passed to this callback. + * + * @param methodID API dependent id of the method + * @param methodCallback method handling implementation + */ + template + void addMethodHandler(const MethodID methodID, + const typename MethodHandler::type& method); + + /** + * Removes the callback + * + * @param methodID API dependent id of the method + */ + void removeMethod(const MethodID methodID); + + /** + * Synchronous method call. + * + * @param methodID API dependent id of the method + * @param data data to send + * @return result data + */ + template + std::shared_ptr callSync(const MethodID methodID, + const PeerID peerID, + const std::shared_ptr& data, + unsigned int timeoutMS); + + /** + * Asynchronous method call. The return callback will be called on + * return data arrival. It will be run in the PROCESSOR thread. + * + * + * @param methodID API dependent id of the method + * @param sendCallback callback for data serialization + * @param resultCallback callback for result serialization and handling + */ + template + void callAsync(const MethodID methodID, + const PeerID peerID, + const std::shared_ptr& data, + const typename ResultHandler::type& resultCallback); + +private: + typedef std::lock_guard Lock; + Processor mProcessor; + Acceptor mAcceptor; +}; + + +template +void Service::addMethodHandler(const MethodID methodID, + const typename MethodHandler::type& method) +{ + LOGD("Adding method with id " << methodID); + mProcessor.addMethodHandler(methodID, method); + LOGD("Added method with id " << methodID); +} + +template +std::shared_ptr Service::callSync(const MethodID methodID, + const PeerID peerID, + const std::shared_ptr& data, + unsigned int timeoutMS = 500) +{ + LOGD("Sync calling method: " << methodID << " for user: " << peerID); + return mProcessor.callSync(methodID, peerID, data, timeoutMS); +} + +template +void Service::callAsync(const MethodID methodID, + const PeerID peerID, + const std::shared_ptr& data, + const typename ResultHandler::type& resultCallback) +{ + LOGD("Async calling method: " << methodID << " for user: " << peerID); + mProcessor.callAsync(methodID, + peerID, + data, + resultCallback); + LOGD("Async called method: " << methodID << "for user: " << peerID); +} + + +} // namespace ipc +} // namespace security_containers + +#endif // COMMON_IPC_SERVICE_HPP diff --git a/common/ipc/types.hpp b/common/ipc/types.hpp new file mode 100644 index 0000000..4269664 --- /dev/null +++ b/common/ipc/types.hpp @@ -0,0 +1,48 @@ +/* +* Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved +* +* Contact: Jan Olszak +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License +*/ + +/** + * @file + * @author Jan Olszak (j.olszak@samsung.com) + * @brief Handler types definitions + */ + +#ifndef COMMON_IPC_HANDLERS_HPP +#define COMMON_IPC_HANDLERS_HPP + +#include +#include + +namespace security_containers { +namespace ipc { + +template +struct MethodHandler { + typedef std::function(std::shared_ptr&)> type; +}; + + +template +struct ResultHandler { + typedef std::function&)> type; +}; + +} // namespace ipc +} // namespace security_containers + +#endif // COMMON_IPC_HANDLERS_HPP diff --git a/packaging/security-containers.spec b/packaging/security-containers.spec index 00a05a6..4453648 100644 --- a/packaging/security-containers.spec +++ b/packaging/security-containers.spec @@ -27,6 +27,7 @@ BuildRequires: pkgconfig(libLogger) BuildRequires: pkgconfig(libSimpleDbus) BuildRequires: pkgconfig(glib-2.0) BuildRequires: pkgconfig(libsystemd-journal) +BuildRequires: pkgconfig(libsystemd-daemon) BuildRequires: pkgconfig(libvirt-glib-1.0) BuildRequires: pkgconfig(sqlite3) Requires: libvirt-daemon >= 1.2.4 diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index 2237f52..333171a 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -30,7 +30,7 @@ ADD_EXECUTABLE(${SERVER_CODENAME} ${project_SRCS} ${common_SRCS}) ## Link libraries ############################################################## FIND_PACKAGE(Boost COMPONENTS program_options system filesystem regex) PKG_CHECK_MODULES(SERVER_DEPS REQUIRED libvirt libvirt-glib-1.0 json gio-2.0 libsystemd-journal - libcap-ng libLogger libSimpleDbus libConfig) + libcap-ng libLogger libSimpleDbus libConfig libsystemd-daemon) INCLUDE_DIRECTORIES(${COMMON_FOLDER}) INCLUDE_DIRECTORIES(SYSTEM ${SERVER_DEPS_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS}) diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt index 3bb265d..caea433 100644 --- a/tests/unit_tests/CMakeLists.txt +++ b/tests/unit_tests/CMakeLists.txt @@ -36,7 +36,8 @@ ADD_EXECUTABLE(${UT_SERVER_CODENAME} ${project_SRCS} ${common_SRCS} ${server_SRC FIND_PACKAGE (Boost COMPONENTS unit_test_framework system filesystem regex) PKG_CHECK_MODULES(UT_SERVER_DEPS REQUIRED libvirt libvirt-glib-1.0 json gio-2.0 - libsystemd-journal libcap-ng libLogger libSimpleDbus libConfig) + libsystemd-journal libcap-ng libLogger libSimpleDbus libConfig + libsystemd-daemon) INCLUDE_DIRECTORIES(${COMMON_FOLDER} ${SERVER_FOLDER} ${UNIT_TESTS_FOLDER} ${CLIENT_FOLDER}) INCLUDE_DIRECTORIES(SYSTEM ${UT_SERVER_DEPS_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS}) TARGET_LINK_LIBRARIES(${UT_SERVER_CODENAME} ${UT_SERVER_DEPS_LIBRARIES} ${Boost_LIBRARIES}) diff --git a/tests/unit_tests/ipc/ut-ipc.cpp b/tests/unit_tests/ipc/ut-ipc.cpp new file mode 100644 index 0000000..48ad1e5 --- /dev/null +++ b/tests/unit_tests/ipc/ut-ipc.cpp @@ -0,0 +1,391 @@ +/* + * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved + * + * Contact: Jan Olszak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + + +/** + * @file + * @author Jan Olszak (j.olszak@samsung.com) + * @brief Tests of the IPC + */ + +// TODO: Test connection limit +// TODO: Refactor tests - function for setting up env + + +#include "config.hpp" +#include "ut.hpp" + +#include "ipc/service.hpp" +#include "ipc/client.hpp" +#include "ipc/types.hpp" + +#include "config/fields.hpp" +#include "logger/logger.hpp" + +#include +#include +#include +#include +#include + +using namespace security_containers; +using namespace security_containers::ipc; +namespace fs = boost::filesystem; + +namespace { +struct Fixture { + std::string socketPath; + + Fixture() + : socketPath(fs::unique_path("/tmp/ipc-%%%%.socket").string()) + { + } + ~Fixture() + { + fs::remove(socketPath); + } +}; + +struct SendData { + int intVal; + SendData(int i = 0): intVal(i) {} + + CONFIG_REGISTER + ( + intVal + ) +}; + +struct EmptyData { + CONFIG_REGISTER_EMPTY +}; + +std::shared_ptr returnEmptyCallback(std::shared_ptr&) +{ + return std::shared_ptr(new EmptyData()); +} + +std::shared_ptr returnDataCallback(std::shared_ptr&) +{ + return std::shared_ptr(new SendData(1)); +} + +std::shared_ptr echoCallback(std::shared_ptr& data) +{ + return data; +} + +void testEcho(Client& c, const Client::MethodID methodID) +{ + std::shared_ptr sentData(new SendData(34)); + std::shared_ptr recvData = c.callSync(methodID, sentData); + BOOST_CHECK_EQUAL(recvData->intVal, sentData->intVal); +} + +void testEcho(Service& s, const Client::MethodID methodID, const Service::PeerID peerID) +{ + std::shared_ptr sentData(new SendData(56)); + std::shared_ptr recvData = s.callSync(methodID, peerID, sentData); + BOOST_CHECK_EQUAL(recvData->intVal, sentData->intVal); +} + +} // namespace + + +BOOST_FIXTURE_TEST_SUITE(IPCSuite, Fixture) + +BOOST_AUTO_TEST_CASE(ConstructorDestructorTest) +{ + Service s(socketPath); + Client c(socketPath); +} + +BOOST_AUTO_TEST_CASE(ServiceAddRemoveMethodTest) +{ + Service s(socketPath); + + s.addMethodHandler(1, returnEmptyCallback); + s.addMethodHandler(1, returnDataCallback); + + s.start(); + + s.addMethodHandler(1, echoCallback); + s.addMethodHandler(2, returnDataCallback); + + Client c(socketPath); + c.start(); + testEcho(c, 1); + + s.removeMethod(1); + s.removeMethod(2); + + BOOST_CHECK_THROW(testEcho(c, 2), IPCException); +} + +BOOST_AUTO_TEST_CASE(ClientAddRemoveMethodTest) +{ + std::mutex mtx; + std::unique_lock lck(mtx); + std::condition_variable cv; + unsigned int peerID = 0; + auto newPeerCallback = [&cv, &peerID](unsigned int newPeerID) { + peerID = newPeerID; + cv.notify_one(); + }; + Service s(socketPath, newPeerCallback); + s.start(); + Client c(socketPath); + + c.addMethodHandler(1, returnEmptyCallback); + c.addMethodHandler(1, returnDataCallback); + + c.start(); + + c.addMethodHandler(1, echoCallback); + c.addMethodHandler(2, returnDataCallback); + + BOOST_CHECK(cv.wait_for(lck, std::chrono::milliseconds(1000), [&peerID]() { + return peerID != 0; + })); + + testEcho(s, 1, peerID); + + c.removeMethod(1); + c.removeMethod(2); + + BOOST_CHECK_THROW(testEcho(s, 1, peerID), IPCException); +} + +BOOST_AUTO_TEST_CASE(ServiceStartStopTest) +{ + Service s(socketPath); + + s.addMethodHandler(1, returnDataCallback); + + s.start(); + s.stop(); + s.start(); + s.stop(); + + s.start(); + s.start(); +} + +BOOST_AUTO_TEST_CASE(ClientStartStopTest) +{ + Service s(socketPath); + Client c(socketPath); + c.addMethodHandler(1, returnDataCallback); + + c.start(); + c.stop(); + c.start(); + c.stop(); + + c.start(); + c.start(); + + c.stop(); + c.stop(); +} + +BOOST_AUTO_TEST_CASE(SyncClientToServiceEchoTest) +{ + Service s(socketPath); + s.addMethodHandler(1, echoCallback); + s.addMethodHandler(2, echoCallback); + + s.start(); + Client c(socketPath); + c.start(); + testEcho(c, 1); + testEcho(c, 2); +} + +BOOST_AUTO_TEST_CASE(RestartTest) +{ + Service s(socketPath); + s.addMethodHandler(1, echoCallback); + s.start(); + s.addMethodHandler(2, echoCallback); + + Client c(socketPath); + c.start(); + testEcho(c, 1); + testEcho(c, 2); + + c.stop(); + c.start(); + + testEcho(c, 1); + testEcho(c, 2); + + s.stop(); + s.start(); + + testEcho(c, 1); + testEcho(c, 2); +} + +BOOST_AUTO_TEST_CASE(SyncServiceToClientEchoTest) +{ + std::mutex mtx; + std::unique_lock lck(mtx); + std::condition_variable cv; + unsigned int peerID = 0; + auto newPeerCallback = [&cv, &peerID](unsigned int newPeerID) { + peerID = newPeerID; + cv.notify_one(); + }; + Service s(socketPath, newPeerCallback); + s.start(); + Client c(socketPath); + c.addMethodHandler(1, echoCallback); + c.start(); + + BOOST_CHECK(cv.wait_for(lck, std::chrono::milliseconds(1000), [&peerID]() { + return peerID != 0; + })); + + std::shared_ptr sentData(new SendData(56)); + std::shared_ptr recvData = s.callSync(1, peerID, sentData); + BOOST_CHECK_EQUAL(recvData->intVal, sentData->intVal); +} + +BOOST_AUTO_TEST_CASE(AsyncClientToServiceEchoTest) +{ + // Setup Service and Client + Service s(socketPath); + s.addMethodHandler(1, echoCallback); + s.start(); + Client c(socketPath); + c.start(); + + std::mutex mtx; + std::unique_lock lck(mtx); + std::condition_variable cv; + + //Async call + std::shared_ptr sentData(new SendData(34)); + std::shared_ptr recvData; + auto dataBack = [&cv, &recvData](std::shared_ptr& data) { + recvData = data; + cv.notify_one(); + }; + c.callAsync(1, sentData, dataBack); + + // Wait for the response + BOOST_CHECK(cv.wait_for(lck, std::chrono::milliseconds(100), [&recvData]() { + return static_cast(recvData); + })); + + BOOST_CHECK_EQUAL(recvData->intVal, sentData->intVal); +} + +BOOST_AUTO_TEST_CASE(AsyncServiceToClientEchoTest) +{ + std::mutex mtx; + std::unique_lock lck(mtx); + std::condition_variable cv; + + // Setup Service and Client + unsigned int peerID = 0; + auto newPeerCallback = [&cv, &peerID](unsigned int newPeerID) { + peerID = newPeerID; + cv.notify_one(); + }; + Service s(socketPath, newPeerCallback); + s.start(); + Client c(socketPath); + c.addMethodHandler(1, echoCallback); + c.start(); + + // Wait for the connection + BOOST_CHECK(cv.wait_for(lck, std::chrono::milliseconds(1000), [&peerID]() { + return peerID != 0; + })); + + // Async call + std::shared_ptr sentData(new SendData(56)); + std::shared_ptr recvData; + + auto dataBack = [&cv, &recvData](std::shared_ptr& data) { + recvData = data; + cv.notify_one(); + }; + + s.callAsync(1, peerID, sentData, dataBack); + + // Wait for the response + BOOST_CHECK(cv.wait_for(lck, std::chrono::milliseconds(1000), [&recvData]() { + return recvData.get() != nullptr; + })); + + BOOST_CHECK_EQUAL(recvData->intVal, sentData->intVal); +} + + +BOOST_AUTO_TEST_CASE(SyncTimeoutTest) +{ + Service s(socketPath); + s.addMethodHandler(1, echoCallback); + + s.start(); + Client c(socketPath); + c.start(); + + std::shared_ptr sentData(new SendData(78)); + + BOOST_CHECK_THROW((c.callSync(1, sentData, 1)), IPCException); +} + + +// BOOST_AUTO_TEST_CASE(ConnectionLimitTest) +// { +// unsigned oldLimit = ipc::getMaxFDNumber(); +// ipc::setMaxFDNumber(50); + +// // Setup Service and many Clients +// Service s(socketPath); +// s.addMethodHandler(1, echoCallback); +// s.start(); + +// std::list clients; +// for (int i = 0; i < 100; ++i) { +// try { +// clients.push_back(Client(socketPath)); +// clients.back().start(); +// } catch (...) {} +// } + +// unsigned seed = std::chrono::system_clock::now().time_since_epoch().count(); +// std::mt19937 generator(seed); +// for (auto it = clients.begin(); it != clients.end(); ++it) { +// try { +// std::shared_ptr sentData(new SendData(generator())); +// std::shared_ptr recvData = it->callSync(1, sentData); +// BOOST_CHECK_EQUAL(recvData->intVal, sentData->intVal); +// } catch (...) {} +// } + +// ipc::setMaxFDNumber(oldLimit); +// } + + + +BOOST_AUTO_TEST_SUITE_END() -- 2.7.4 From 4019fbbe44260e19bd1f335425f5256034cbe350 Mon Sep 17 00:00:00 2001 From: Piotr Bartosiewicz Date: Wed, 5 Nov 2014 15:00:03 +0100 Subject: [PATCH 02/16] Lxc server configs, missing ContainerAdmin stuff WORK IN PROGRESS [Bug/Feature] N/A [Cause] N/A [Solution] N/A [Verification] All tests should pass Change-Id: I87505823a5b1c543ee495ede010430bb8c745736 --- common/lxc/domain.cpp | 81 ++++- common/lxc/domain.hpp | 29 +- packaging/security-containers.spec | 2 + server/configs/CMakeLists.txt | 4 + server/configs/containers/business.conf | 2 +- server/configs/containers/private.conf | 2 +- server/configs/daemon.conf | 4 +- server/configs/lxc-templates/business.sh | 38 ++ server/configs/lxc-templates/private.sh | 38 ++ server/container-admin.cpp | 300 +++------------- server/container-admin.hpp | 92 +---- tests/unit_tests/lxc/ut-domain.cpp | 152 ++++++-- tests/unit_tests/server/configs/CMakeLists.txt | 10 - .../containers/{buggy.conf.in => buggy.conf} | 3 + .../ut-container-admin/containers/missing.conf | 3 + ...t-no-shutdown.conf.in => test-no-shutdown.conf} | 3 + .../containers/{test.conf.in => test.conf} | 3 + tests/unit_tests/server/ut-container-admin.cpp | 396 +++++++-------------- tests/unit_tests/server/ut-container.cpp | 13 +- 19 files changed, 490 insertions(+), 685 deletions(-) create mode 100755 server/configs/lxc-templates/business.sh create mode 100755 server/configs/lxc-templates/private.sh rename tests/unit_tests/server/configs/ut-container-admin/containers/{buggy.conf.in => buggy.conf} (71%) rename tests/unit_tests/server/configs/ut-container-admin/containers/{test-no-shutdown.conf.in => test-no-shutdown.conf} (71%) rename tests/unit_tests/server/configs/ut-container-admin/containers/{test.conf.in => test.conf} (65%) diff --git a/common/lxc/domain.cpp b/common/lxc/domain.cpp index 07b7c77..0959959 100644 --- a/common/lxc/domain.cpp +++ b/common/lxc/domain.cpp @@ -30,9 +30,23 @@ #include #include +#include + namespace security_containers { namespace lxc { +namespace { + const std::map STATE_MAP = { + {"STOPPED", LxcDomain::State::STOPPED}, + {"STARTING", LxcDomain::State::STARTING}, + {"RUNNING", LxcDomain::State::RUNNING}, + {"STOPPING", LxcDomain::State::STOPPING}, + {"ABORTING", LxcDomain::State::ABORTING}, + {"FREEZING", LxcDomain::State::FREEZING}, + {"FROZEN", LxcDomain::State::FROZEN}, + {"THAWED", LxcDomain::State::THAWED} + }; +} // namespace LxcDomain::LxcDomain(const std::string& lxcPath, const std::string& domainName) : mContainer(nullptr) @@ -70,63 +84,92 @@ bool LxcDomain::isDefined() return mContainer->is_defined(mContainer); } -bool LxcDomain::isRunning() -{ - return mContainer->is_running(mContainer); -} - -std::string LxcDomain::getState() +LxcDomain::State LxcDomain::getState() { - return mContainer->state(mContainer); + const std::string str = mContainer->state(mContainer); + return STATE_MAP.at(str); } -void LxcDomain::create(const std::string& templatePath) +bool LxcDomain::create(const std::string& templatePath) { if (!mContainer->create(mContainer, templatePath.c_str(), NULL, NULL, 0, NULL)) { LOGE("Could not create domain " + getName()); - throw LxcException("Could not create domain"); + return false; } + return true; } -void LxcDomain::destroy() +bool LxcDomain::destroy() { if (!mContainer->destroy(mContainer)) { LOGE("Could not destroy domain " + getName()); - throw LxcException("Could not destroy domain"); + return false; } + return true; } -void LxcDomain::start(const char* const* argv) +bool LxcDomain::start(const char* const* argv) { + if (mContainer->is_running(mContainer)) { + LOGE("Already started " + getName()); + return false; + } + if (!mContainer->want_daemonize(mContainer, true)) { + LOGE("Could not configure domain " + getName()); + return false; + } if (!mContainer->start(mContainer, false, const_cast(argv))) { LOGE("Could not start domain " + getName()); - throw LxcException("Could not start domain"); + return false; } + return true; } -void LxcDomain::stop() +bool LxcDomain::stop() { if (!mContainer->stop(mContainer)) { LOGE("Could not stop domain " + getName()); - throw LxcException("Stop domain failed"); + return false; } + return true; } -void LxcDomain::reboot() +bool LxcDomain::reboot() { if (!mContainer->reboot(mContainer)) { LOGE("Could not reboot domain " + getName()); - throw LxcException("Reboot domain failed"); + return false; } + return true; } -void LxcDomain::shutdown(int timeout) +bool LxcDomain::shutdown(int timeout) { if (!mContainer->shutdown(mContainer, timeout)) { LOGE("Could not gracefully shutdown domain " + getName() + " in " << timeout << "s"); - throw LxcException("Shutdown domain failed"); + return false; } + return true; } +bool LxcDomain::freeze() +{ + if (!mContainer->freeze(mContainer)) { + LOGE("Could not freeze domain " + getName()); + return false; + } + return true; +} + +bool LxcDomain::unfreeze() +{ + if (!mContainer->unfreeze(mContainer)) { + LOGE("Could not unfreeze domain " + getName()); + return false; + } + return true; +} + + } // namespace lxc } // namespace security_containers diff --git a/common/lxc/domain.hpp b/common/lxc/domain.hpp index 2f0cbf8..3202f54 100644 --- a/common/lxc/domain.hpp +++ b/common/lxc/domain.hpp @@ -39,6 +39,17 @@ namespace lxc { */ class LxcDomain { public: + enum class State { + STOPPED, + STARTING, + RUNNING, + STOPPING, + ABORTING, + FREEZING, + FROZEN, + THAWED + }; + LxcDomain(const std::string& lxcPath, const std::string& domainName); ~LxcDomain(); @@ -50,17 +61,19 @@ public: std::string getConfigItem(const std::string& key); bool isDefined(); - bool isRunning(); - std::string getState(); + State getState(); + + bool create(const std::string& templatePath); + bool destroy(); - void create(const std::string& templatePath); - void destroy(); + bool start(const char* const* argv); + bool stop(); + bool reboot(); + bool shutdown(int timeout); - void start(const char* const* argv); - void stop(); - void reboot(); - void shutdown(int timeout); + bool freeze(); + bool unfreeze(); private: lxc_container* mContainer; }; diff --git a/packaging/security-containers.spec b/packaging/security-containers.spec index e73694c..11e7c99 100644 --- a/packaging/security-containers.spec +++ b/packaging/security-containers.spec @@ -40,8 +40,10 @@ between them. A process from inside a container can request a switch of context %attr(755,root,root) %{_bindir}/security-containers-server %dir /etc/security-containers %dir /etc/security-containers/containers +%dir /etc/security-containers/lxc-templates %config /etc/security-containers/daemon.conf %config /etc/security-containers/containers/*.conf +%attr(755,root,root) /etc/security-containers/lxc-templates/*.sh %{_unitdir}/security-containers.service %{_unitdir}/multi-user.target.wants/security-containers.service /etc/dbus-1/system.d/org.tizen.containers.host.conf diff --git a/server/configs/CMakeLists.txt b/server/configs/CMakeLists.txt index 310f407..ab4a94f 100644 --- a/server/configs/CMakeLists.txt +++ b/server/configs/CMakeLists.txt @@ -20,6 +20,7 @@ MESSAGE(STATUS "Installing configs to " ${SC_CONFIG_INSTALL_DIR}) FILE(GLOB container_CONF containers/*.conf) +FILE(GLOB admin_CONF lxc-templates/*.sh) ## Generate #################################################################### CONFIGURE_FILE(systemd/security-containers.service.in @@ -40,5 +41,8 @@ INSTALL(FILES ${CMAKE_BINARY_DIR}/dbus-1/system.d/org.tizen.containers.hos INSTALL(FILES ${container_CONF} DESTINATION ${SC_CONFIG_INSTALL_DIR}/containers) +INSTALL(PROGRAMS ${admin_CONF} + DESTINATION ${SC_CONFIG_INSTALL_DIR}/lxc-templates) + INSTALL(FILES ${CMAKE_BINARY_DIR}/systemd/security-containers.service DESTINATION ${SYSTEMD_UNIT_DIR}) diff --git a/server/configs/containers/business.conf b/server/configs/containers/business.conf index c141111..92eb06d 100644 --- a/server/configs/containers/business.conf +++ b/server/configs/containers/business.conf @@ -1,6 +1,6 @@ { "name" : "business", - "lxcTemplate" : "", + "lxcTemplate" : "business.sh", "initWithArgs" : [], "cpuQuotaForeground" : -1, "cpuQuotaBackground" : 10000, diff --git a/server/configs/containers/private.conf b/server/configs/containers/private.conf index 94261b8..074f4f3 100644 --- a/server/configs/containers/private.conf +++ b/server/configs/containers/private.conf @@ -1,6 +1,6 @@ { "name" : "private", - "lxcTemplate" : "", + "lxcTemplate" : "private.sh", "initWithArgs" : [], "cpuQuotaForeground" : -1, "cpuQuotaBackground" : 10000, diff --git a/server/configs/daemon.conf b/server/configs/daemon.conf index 116b32e..058a3cd 100644 --- a/server/configs/daemon.conf +++ b/server/configs/daemon.conf @@ -7,8 +7,8 @@ "runMountPointPrefix" : "/var/run/containers", "foregroundId" : "private", "defaultId" : "private", - "lxcTemplatePrefix" : "TODO", - "inputConfig" : {"enabled" : true, + "lxcTemplatePrefix" : "/etc/security-containers/lxc-templates", + "inputConfig" : {"enabled" : false, "device" : "gpio_keys.6", "code" : 139, "numberOfEvents" : 1, diff --git a/server/configs/lxc-templates/business.sh b/server/configs/lxc-templates/business.sh new file mode 100755 index 0000000..75be9e6 --- /dev/null +++ b/server/configs/lxc-templates/business.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +echo LXC template, args: $@ + +options=$(getopt -o p:n: -l rootfs:,path:,name: -- "$@") +if [ $? -ne 0 ]; then + exit 1 +fi +eval set -- "$options" + +while true +do + case "$1" in + -p|--path) path=$2; shift 2;; + --rootfs) rootfs=$2; shift 2;; + -n|--name) name=$2; shift 2;; + --) shift 1; break ;; + *) break ;; + esac +done + +# XXX assume rootfs if mounted from iso + +# Prepare container configuration file +> ${path}/config +cat <> ${path}/config +lxc.utsname = ${name} +lxc.rootfs = ${rootfs} + +lxc.haltsignal = SIGTERM + +lxc.pts = 256 +lxc.tty = 0 + +lxc.mount.auto = proc sys cgroup +lxc.mount.entry = /var/run/containers/business/run var/run none rw,bind 0 0 +EOF + diff --git a/server/configs/lxc-templates/private.sh b/server/configs/lxc-templates/private.sh new file mode 100755 index 0000000..2926e55 --- /dev/null +++ b/server/configs/lxc-templates/private.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +echo LXC template, args: $@ + +options=$(getopt -o p:n: -l rootfs:,path:,name: -- "$@") +if [ $? -ne 0 ]; then + exit 1 +fi +eval set -- "$options" + +while true +do + case "$1" in + -p|--path) path=$2; shift 2;; + --rootfs) rootfs=$2; shift 2;; + -n|--name) name=$2; shift 2;; + --) shift 1; break ;; + *) break ;; + esac +done + +# XXX assume rootfs if mounted from iso + +# Prepare container configuration file +> ${path}/config +cat <> ${path}/config +lxc.utsname = ${name} +lxc.rootfs = ${rootfs} + +lxc.haltsignal = SIGTERM + +lxc.pts = 256 +lxc.tty = 0 + +lxc.mount.auto = proc sys cgroup +lxc.mount.entry = /var/run/containers/private/run var/run none rw,bind 0 0 +EOF + diff --git a/server/container-admin.cpp b/server/container-admin.cpp index 11c88fd..71185db 100644 --- a/server/container-admin.cpp +++ b/server/container-admin.cpp @@ -28,16 +28,9 @@ #include "exception.hpp" #include "logger/logger.hpp" -#include "utils/fs.hpp" #include "utils/paths.hpp" -#include "utils/latch.hpp" -#include "utils/callback-wrapper.hpp" #include -#include -#include -#include -#include namespace security_containers { @@ -45,7 +38,7 @@ namespace security_containers { namespace { // TODO: this should be in container's configuration file -const int SHUTDOWN_WAIT = 10 * 1000; +const int SHUTDOWN_WAIT = 10; class Args { public: @@ -89,50 +82,19 @@ ContainerAdmin::ContainerAdmin(const std::string& containersPath, : mConfig(config), mDom(containersPath, config.name), mId(mDom.getName()), - mDetachOnExit(false), - mLifecycleCallbackId(-1), - mRebootCallbackId(-1), - mNextIdForListener(1) + mDetachOnExit(false) { LOGD(mId << ": Instantiating ContainerAdmin object"); if (!mDom.isDefined()) { - std::string lxcTemplate = utils::getAbsolutePath(config.lxcTemplate, lxcTemplatePrefix); + const std::string lxcTemplate = utils::getAbsolutePath(config.lxcTemplate, + lxcTemplatePrefix); LOGI(mId << ": Creating domain from template: " << lxcTemplate); - mDom.create(lxcTemplate); + if (!mDom.create(lxcTemplate)) { + throw ContainerOperationException("Could not create domain"); + } } - -// // ContainerAdmin owns those callbacks -// mLifecycleCallbackId = virConnectDomainEventRegisterAny(virDomainGetConnect(mDom.get()), -// mDom.get(), -// VIR_DOMAIN_EVENT_ID_LIFECYCLE, -// VIR_DOMAIN_EVENT_CALLBACK(&ContainerAdmin::libvirtLifecycleCallback), -// utils::createCallbackWrapper(this, mLibvirtGuard.spawn()), -// &utils::deleteCallbackWrapper); -// -// if (mLifecycleCallbackId < 0) { -// LOGE(mId << ": Failed to register a libvirt lifecycle callback"); -// throw ContainerOperationException(mId + ": Failed to register a libvirt lifecycle callback"); -// } -// -// LOGT(mId << ": registered lifecycle callback"); -// -// mRebootCallbackId = virConnectDomainEventRegisterAny(virDomainGetConnect(mDom.get()), -// mDom.get(), -// VIR_DOMAIN_EVENT_ID_REBOOT, -// VIR_DOMAIN_EVENT_CALLBACK(&ContainerAdmin::libvirtRebootCallback), -// utils::createCallbackWrapper(this, mLibvirtGuard.spawn()), -// &utils::deleteCallbackWrapper); -// -// if (mRebootCallbackId < 0) { -// LOGE(mId << ": Failed to register a libvirt reboot callback"); -// virConnectDomainEventDeregisterAny(virDomainGetConnect(mDom.get()), -// mLifecycleCallbackId); -// throw ContainerOperationException(mId + ": Failed to register a libvirt reboot callback"); -// } -// -// LOGT(mId << ": registered reboot callback"); } @@ -140,22 +102,10 @@ ContainerAdmin::~ContainerAdmin() { LOGD(mId << ": Destroying ContainerAdmin object..."); -// // Deregister callbacks -// if (mLifecycleCallbackId >= 0) { -// virConnectDomainEventDeregisterAny(virDomainGetConnect(mDom.get()), -// mLifecycleCallbackId); -// } -// if (mRebootCallbackId >= 0) { -// virConnectDomainEventDeregisterAny(virDomainGetConnect(mDom.get()), -// mRebootCallbackId); -// } -// - // Try to forcefully stop if (!mDetachOnExit) { - try { - destroy(); - } catch (ServerException&) { - LOGE(mId << ": Failed to destroy the container"); + // Try to forcefully stop + if (!mDom.stop()) { + LOGE(mId << ": Failed to stop the container"); } } @@ -177,25 +127,19 @@ void ContainerAdmin::start() return; } - Args args(mConfig.initWithArgs); + const Args args(mConfig.initWithArgs); + bool result; if (args.empty()) { - mDom.start(NULL); + result = mDom.start(NULL); } else { LOGD(mId << ": Init: " << args); - mDom.start(args.getAsCArray()); + result = mDom.start(args.getAsCArray()); + } + + if (!result) { + throw ContainerOperationException("Could not start container"); } -// // In order to update daemon without shutting down the containers -// // autodestroy option must NOT be set. It's best to create domain -// // without any flags. -// u_int flags = VIR_DOMAIN_NONE; -// -// if (virDomainCreateWithFlags(mDom.get(), flags) < 0) { -// LOGE(mId << ": Failed to start the container\n" -// << libvirt::libvirtFormatError()); -// throw ContainerOperationException(); -// } -// LOGD(mId << ": Started"); } @@ -208,154 +152,64 @@ void ContainerAdmin::stop() return; } - mDom.stop(); + if (!mDom.shutdown(SHUTDOWN_WAIT)) { + // force stop + if (!mDom.stop()) { + throw ContainerOperationException("Could not stop container"); + } + } -// utils::Latch stoppedOccured; -// -// LifecycleListener setStopped = [&](const int eventId, const int detailId) { -// if (eventId == VIR_DOMAIN_EVENT_STOPPED) { -// if (detailId != VIR_DOMAIN_EVENT_STOPPED_SHUTDOWN) { -// LOGW(mId << ": shutdown requested, but the container stopped with a different status: " -// << libvirt::libvirtEventDetailToString(eventId, detailId)); -// } -// stoppedOccured.set(); -// } -// }; -// -// ListenerId id = registerLifecycleListener(setStopped, nullptr); -// shutdown(); -// bool stopped = stoppedOccured.wait(SHUTDOWN_WAIT); -// removeListener(id); -// -// if (!stopped) { -// LOGW(mId << ": Gracefull shutdown timed out, the container is still running, destroying"); -// destroy(); -// } -// LOGD(mId << ": Stopping procedure ended"); } void ContainerAdmin::destroy() { - LOGD(mId << ": Destroying..."); - if (isStopped()) { - LOGD(mId << ": Already crashed/down/off - nothing to do"); - return; - } - - mDom.stop();//TODO - -// setSchedulerLevel(SchedulerLevel::FOREGROUND); -// -// // Forceful termination of the guest -// u_int flags = VIR_DOMAIN_DESTROY_DEFAULT; -// -// if (virDomainDestroyFlags(mDom.get(), flags) < 0) { -// LOGE(mId << ": Error while destroying the container:\n" -// << libvirt::libvirtFormatError()); -// throw ContainerOperationException(); -// } -// - LOGD(mId << ": Destroyed"); -} + LOGD(mId << ": Destroying procedure started..."); - -void ContainerAdmin::shutdown() -{ - LOGD(mId << ": Shutting down..."); - if (isStopped()) { - LOGD(mId << ": Already crashed/down/off - nothing to do"); - return; + if (!mDom.destroy()) { + throw ContainerOperationException("Could not destroy container"); } - mDom.stop(); //TODO - -// setSchedulerLevel(SchedulerLevel::FOREGROUND); -// -// if (virDomainShutdownFlags(mDom.get(), VIR_DOMAIN_SHUTDOWN_SIGNAL) < 0) { -// LOGE(mId << ": Error while shutting down the container:\n" -// << libvirt::libvirtFormatError()); -// throw ContainerOperationException(); -// } -// - LOGD(mId << ": Shut down initiated (async)"); + LOGD(mId << ": Destroying procedure ended"); } bool ContainerAdmin::isRunning() { - return mDom.isRunning(); + return mDom.getState() == lxc::LxcDomain::State::RUNNING; } bool ContainerAdmin::isStopped() { - return !mDom.isRunning();//TODO + return mDom.getState() == lxc::LxcDomain::State::STOPPED; } void ContainerAdmin::suspend() { -// assert(mDom); -// -// LOGD(mId << ": Pausing..."); -// if (isPaused()) { -// LOGD(mId << ": Already paused - nothing to do..."); -// return; -// } -// -// if (virDomainSuspend(mDom.get()) < 0) { -// LOGE(mId << ": Error while suspending the container:\n" -// << libvirt::libvirtFormatError()); -// throw ContainerOperationException(); -// } -// -// LOGD(mId << ": Paused"); + LOGD(mId << ": Pausing..."); + if (!mDom.freeze()) { + throw ContainerOperationException("Could not pause container"); + } + LOGD(mId << ": Paused"); } void ContainerAdmin::resume() { -// assert(mDom); -// -// LOGD(mId << ": Resuming..."); -// if (!isPaused()) { -// LOGD(mId << ": Is not paused - nothing to do..."); -// return; -// } -// -// if (virDomainResume(mDom.get()) < 0) { -// LOGE(mId << ": Error while resuming the container:\n" -// << libvirt::libvirtFormatError()); -// throw ContainerOperationException(); -// } -// -// LOGD(mId << ": Resumed"); + LOGD(mId << ": Resuming..."); + if (!mDom.unfreeze()) { + throw ContainerOperationException("Could not resume container"); + } + LOGD(mId << ": Resumed"); } bool ContainerAdmin::isPaused() { -// return getState() == VIR_DOMAIN_PAUSED; - return false;//TODO -} - - -int ContainerAdmin::getState() -{ -// assert(mDom); -// -// int state; -// -// if (virDomainGetState(mDom.get(), &state, NULL, 0)) { -// LOGE(mId << ": Error while getting the container's state:\n" -// << libvirt::libvirtFormatError()); -// throw ContainerOperationException(); -// } -// -// return state; - return 0; + return mDom.getState() == lxc::LxcDomain::State::FROZEN; } @@ -443,77 +297,5 @@ std::int64_t ContainerAdmin::getSchedulerQuota() return 0; } -ContainerAdmin::ListenerId ContainerAdmin::registerLifecycleListener(const ContainerAdmin::LifecycleListener& listener, - const utils::CallbackGuard::Tracker& tracker) -{ - - utils::CallbackWrapper wrap(listener, tracker); - - std::unique_lock lock(mListenerMutex); - unsigned int id = mNextIdForListener++; - mLifecycleListeners.insert(LifecycleListenerMap::value_type(id, std::move(wrap))); - - return id; -} - -ContainerAdmin::ListenerId ContainerAdmin::registerRebootListener(const ContainerAdmin::RebootListener& listener, - const utils::CallbackGuard::Tracker& tracker) -{ - - utils::CallbackWrapper wrap(listener, tracker); - - std::unique_lock lock(mListenerMutex); - unsigned int id = mNextIdForListener++; - mRebootListeners.insert(RebootListenerMap::value_type(id, std::move(wrap))); - - return id; -} - -void ContainerAdmin::removeListener(const ContainerAdmin::ListenerId id) -{ - std::unique_lock lock(mListenerMutex); - mLifecycleListeners.erase(id); - mRebootListeners.erase(id); -} - -//int ContainerAdmin::libvirtLifecycleCallback(virConnectPtr /*con*/, -// virDomainPtr /*dom*/, -// int event, -// int detail, -// void* opaque) -//{ -// ContainerAdmin* thisPtr = utils::getCallbackFromPointer(opaque); -// -// LOGI(thisPtr->getId() -// << ": Lifecycle event: " -// << libvirt::libvirtEventToString(event) -// << ": " -// << libvirt::libvirtEventDetailToString(event, detail)); -// -// std::unique_lock lock(thisPtr->mListenerMutex); -// for (auto& it : thisPtr->mLifecycleListeners) { -// LifecycleListener f = it.second.get(); -// f(event, detail); -// } -// -// // ignored, libvirt's legacy -// return 0; -//} -// -//void ContainerAdmin::libvirtRebootCallback(virConnectPtr /*con*/, -// virDomainPtr /*dom*/, -// void* opaque) -//{ -// ContainerAdmin* thisPtr = utils::getCallbackFromPointer(opaque); -// -// LOGI(thisPtr->getId() << ": Reboot event"); -// -// std::unique_lock lock(thisPtr->mListenerMutex); -// for (auto& it : thisPtr->mRebootListeners) { -// RebootListener f = it.second.get(); -// f(); -// } -//} - } // namespace security_containers diff --git a/server/container-admin.hpp b/server/container-admin.hpp index f2ca5f9..79b0193 100644 --- a/server/container-admin.hpp +++ b/server/container-admin.hpp @@ -27,16 +27,8 @@ #define SERVER_CONTAINER_ADMIN_HPP #include "container-config.hpp" - -#include "utils/callback-guard.hpp" -#include "utils/callback-wrapper.hpp" #include "lxc/domain.hpp" -#include -#include -#include -#include - namespace security_containers { @@ -49,25 +41,6 @@ enum class SchedulerLevel { class ContainerAdmin { public: - /** - * A listener ID type. - */ - typedef unsigned int ListenerId; - - /** - * An invalid ListenerId value. - */ - static const ListenerId LISTENER_ID_INVALID = 0; - - /** - * A function type used for the lifecycle listener - */ - typedef std::function LifecycleListener; - - /** - * A function type used for the reboot listener - */ - typedef std::function RebootListener; /** * ContainerAdmin constructor @@ -91,29 +64,23 @@ public: void start(); /** - * Try to shutdown the container, if failed, destroy it. + * Try to shutdown the container, if failed, kill it. */ void stop(); /** - * Forcefully stop the container. + * Destroy stopped container. In particular it removes whole containers rootfs. */ void destroy(); /** - * Gracefully shutdown the container. - * This method will NOT block until container is shut down. - */ - void shutdown(); - - /** * @return Is the container running? */ bool isRunning(); /** * Check if the container is stopped. It's NOT equivalent to !isRunning, - * because it checks different internal libvirt's states. There are other states, + * because it checks different internal lxc states. There are other states, * (e.g. paused) when the container isn't running nor stopped. * * @return Is the container stopped? @@ -154,66 +121,13 @@ public: */ std::int64_t getSchedulerQuota(); - /** - * Sets a listener for a lifecycle event. - * It's a caller's responsibility to remove the listener - * prior to destroying the object. - * - * @return listener ID that can be used to remove. - */ - ListenerId registerLifecycleListener(const LifecycleListener& listener, - const utils::CallbackGuard::Tracker& tracker); - - /** - * Sets a listener for a reboot event. - * It's a caller's responsibility to remove the listener - * prior to destroying the object. - * - * @return listener ID that can be used to remove. - */ - ListenerId registerRebootListener(const RebootListener& listener, - const utils::CallbackGuard::Tracker& tracker); - - /** - * Remove a previously registered listener. - */ - void removeListener(const ListenerId id); - private: const ContainerConfig& mConfig; lxc::LxcDomain mDom; const std::string mId; bool mDetachOnExit; - int getState(); // get the libvirt's domain state void setSchedulerParams(std::uint64_t cpuShares, std::uint64_t vcpuPeriod, std::int64_t vcpuQuota); - - // for handling libvirt callbacks - utils::CallbackGuard mLibvirtGuard; - int mLifecycleCallbackId; - int mRebootCallbackId; - -// // virConnectDomainEventCallback -// static int libvirtLifecycleCallback(virConnectPtr con, -// virDomainPtr dom, -// int event, -// int detail, -// void* opaque); -// -// // virConnectDomainEventGenericCallback -// static void libvirtRebootCallback(virConnectPtr con, -// virDomainPtr dom, -// void* opaque); - - // for handling external listeners triggered from libvirt callbacks - // TODO, the Listener type might not be unique, reimplement using proper listeners - typedef std::map> LifecycleListenerMap; - typedef std::map> RebootListenerMap; - - std::mutex mListenerMutex; - unsigned int mNextIdForListener; - LifecycleListenerMap mLifecycleListeners; - RebootListenerMap mRebootListeners; }; diff --git a/tests/unit_tests/lxc/ut-domain.cpp b/tests/unit_tests/lxc/ut-domain.cpp index b3b693a..206d2c7 100644 --- a/tests/unit_tests/lxc/ut-domain.cpp +++ b/tests/unit_tests/lxc/ut-domain.cpp @@ -60,12 +60,18 @@ struct Fixture { { LxcDomain lxc(LXC_PATH, DOMAIN_NAME); if (lxc.isDefined()) { - if (lxc.isRunning()) { + if (lxc.getState() != LxcDomain::State::STOPPED) { lxc.stop(); } lxc.destroy(); } } + + void waitForInit() + { + // wait for init fully started (wait for bash to be able to trap SIGTERM) + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + } }; } // namespace @@ -82,13 +88,13 @@ BOOST_AUTO_TEST_CASE(CreateDestroyTest) LxcDomain lxc(LXC_PATH, DOMAIN_NAME); BOOST_CHECK(!lxc.isDefined()); - lxc.create(TEMPLATE); + BOOST_CHECK(lxc.create(TEMPLATE)); BOOST_CHECK(lxc.isDefined()); BOOST_CHECK_EQUAL(lxc.getConfigItem("lxc.rootfs"), LXC_PATH + DOMAIN_NAME + "/rootfs"); BOOST_CHECK_THROW(lxc.getConfigItem("xxx"), LxcException); - lxc.destroy(); + BOOST_CHECK(lxc.destroy()); BOOST_CHECK(!lxc.isDefined()); } @@ -97,46 +103,146 @@ BOOST_AUTO_TEST_CASE(StartShutdownTest) { { LxcDomain lxc(LXC_PATH, DOMAIN_NAME); - lxc.create(TEMPLATE); + BOOST_CHECK(lxc.create(TEMPLATE)); } LxcDomain lxc(LXC_PATH, DOMAIN_NAME); - BOOST_CHECK_EQUAL("STOPPED", lxc.getState()); + BOOST_CHECK(lxc.getState() == LxcDomain::State::STOPPED); const char* argv[] = { "/bin/sh", "-c", "trap exit SIGTERM; read", NULL }; - lxc.start(argv); - // wait for bash to be able to trap SIGTERM - std::this_thread::sleep_for(std::chrono::milliseconds(200)); - BOOST_CHECK_EQUAL("RUNNING", lxc.getState()); - lxc.shutdown(2); - BOOST_CHECK_EQUAL("STOPPED", lxc.getState()); - - lxc.destroy(); + BOOST_CHECK(lxc.start(argv)); + BOOST_CHECK(lxc.getState() == LxcDomain::State::RUNNING); + waitForInit(); + BOOST_CHECK(lxc.shutdown(2)); + BOOST_CHECK(lxc.getState() == LxcDomain::State::STOPPED); + + BOOST_CHECK(lxc.destroy()); } BOOST_AUTO_TEST_CASE(StartStopTest) { { LxcDomain lxc(LXC_PATH, DOMAIN_NAME); - lxc.create(TEMPLATE); + BOOST_CHECK(lxc.create(TEMPLATE)); + } + LxcDomain lxc(LXC_PATH, DOMAIN_NAME); + BOOST_CHECK(lxc.getState() == LxcDomain::State::STOPPED); + const char* argv[] = { + "/bin/sh", + NULL + }; + BOOST_CHECK(lxc.start(argv)); + BOOST_CHECK(lxc.getState() == LxcDomain::State::RUNNING); + BOOST_CHECK(!lxc.shutdown(1)); + BOOST_CHECK(lxc.getState() == LxcDomain::State::RUNNING); + BOOST_CHECK(lxc.stop()); + BOOST_CHECK(lxc.getState() == LxcDomain::State::STOPPED); + + BOOST_CHECK(lxc.destroy()); +} + +BOOST_AUTO_TEST_CASE(StartHasStoppedTest) +{ + { + LxcDomain lxc(LXC_PATH, DOMAIN_NAME); + BOOST_CHECK(lxc.create(TEMPLATE)); } LxcDomain lxc(LXC_PATH, DOMAIN_NAME); - BOOST_CHECK_EQUAL("STOPPED", lxc.getState()); + BOOST_CHECK(lxc.getState() == LxcDomain::State::STOPPED); + const char* argv[] = { + "/bin/sh", + "-c", + "echo", + NULL + }; + BOOST_CHECK(lxc.start(argv)); + BOOST_CHECK(lxc.getState() == LxcDomain::State::RUNNING); + waitForInit(); + BOOST_CHECK(lxc.getState() == LxcDomain::State::STOPPED); + + BOOST_CHECK(lxc.destroy()); +} + +BOOST_AUTO_TEST_CASE(FreezeUnfreezeTest) +{ + LxcDomain lxc(LXC_PATH, DOMAIN_NAME); + BOOST_CHECK(lxc.create(TEMPLATE)); + const char* argv[] = { + "/bin/sh", + "-c", + "trap exit SIGTERM; read", + NULL + }; + BOOST_CHECK(lxc.start(argv)); + BOOST_CHECK(lxc.getState() == LxcDomain::State::RUNNING); + waitForInit(); + BOOST_CHECK(lxc.freeze()); + BOOST_CHECK(lxc.getState() == LxcDomain::State::FROZEN); + BOOST_CHECK(lxc.unfreeze()); + BOOST_CHECK(lxc.getState() == LxcDomain::State::RUNNING); + BOOST_CHECK(lxc.shutdown(2)); + BOOST_CHECK(lxc.getState() == LxcDomain::State::STOPPED); + + BOOST_CHECK(lxc.destroy()); +} + +BOOST_AUTO_TEST_CASE(FreezeStopTest) +{ + LxcDomain lxc(LXC_PATH, DOMAIN_NAME); + BOOST_CHECK(lxc.create(TEMPLATE)); + const char* argv[] = { + "/bin/sh", + "-c", + "trap exit SIGTERM; read", + NULL + }; + BOOST_CHECK(lxc.start(argv)); + BOOST_CHECK(lxc.getState() == LxcDomain::State::RUNNING); + waitForInit(); + BOOST_CHECK(lxc.freeze()); + BOOST_CHECK(lxc.getState() == LxcDomain::State::FROZEN); + BOOST_CHECK(!lxc.shutdown(1)); + BOOST_CHECK(lxc.getState() == LxcDomain::State::FROZEN); + BOOST_CHECK(lxc.stop()); + BOOST_CHECK(lxc.getState() == LxcDomain::State::STOPPED); + + BOOST_CHECK(lxc.destroy()); +} + +BOOST_AUTO_TEST_CASE(RepeatTest) +{ + LxcDomain lxc(LXC_PATH, DOMAIN_NAME); + BOOST_CHECK(lxc.create(TEMPLATE)); + BOOST_CHECK(!lxc.create(TEMPLATE));// forbidden const char* argv[] = { "/bin/sh", + "-c", + "trap exit SIGTERM; read", NULL }; - lxc.start(argv); - BOOST_CHECK_EQUAL("RUNNING", lxc.getState()); - BOOST_CHECK_THROW(lxc.shutdown(1), LxcException); - BOOST_CHECK_EQUAL("RUNNING", lxc.getState()); - lxc.stop(); - BOOST_CHECK_EQUAL("STOPPED", lxc.getState()); - - lxc.destroy(); + BOOST_CHECK(lxc.start(argv)); + BOOST_CHECK(lxc.getState() == LxcDomain::State::RUNNING); + waitForInit(); + BOOST_CHECK(!lxc.start(argv)); // forbidden + BOOST_CHECK(lxc.freeze()); + BOOST_CHECK(lxc.getState() == LxcDomain::State::FROZEN); + BOOST_CHECK(lxc.freeze()); // repeat is nop + BOOST_CHECK(lxc.getState() == LxcDomain::State::FROZEN); + BOOST_CHECK(lxc.unfreeze()); + BOOST_CHECK(lxc.getState() == LxcDomain::State::RUNNING); + BOOST_CHECK(lxc.unfreeze()); // repeat is nop + BOOST_CHECK(lxc.getState() == LxcDomain::State::RUNNING); + BOOST_CHECK(lxc.stop()); + BOOST_CHECK(lxc.getState() == LxcDomain::State::STOPPED); + BOOST_CHECK(lxc.stop()); // repeat is nop + BOOST_CHECK(lxc.getState() == LxcDomain::State::STOPPED); + + BOOST_CHECK(lxc.destroy()); + BOOST_CHECK(!lxc.isDefined()); + BOOST_CHECK(!lxc.destroy()); // forbidden (why?) } BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/unit_tests/server/configs/CMakeLists.txt b/tests/unit_tests/server/configs/CMakeLists.txt index 49cb292..b3f8a70 100644 --- a/tests/unit_tests/server/configs/CMakeLists.txt +++ b/tests/unit_tests/server/configs/CMakeLists.txt @@ -43,14 +43,6 @@ CONFIGURE_FILE(ut-server/buggy-daemon.conf.in ${CMAKE_BINARY_DIR}/ut-server/buggy-daemon.conf @ONLY) FILE(GLOB server_manager_CONF_GEN ${CMAKE_BINARY_DIR}/ut-server/*.conf) -CONFIGURE_FILE(ut-container-admin/containers/buggy.conf.in - ${CMAKE_BINARY_DIR}/ut-container-admin/containers/buggy.conf @ONLY) -CONFIGURE_FILE(ut-container-admin/containers/test.conf.in - ${CMAKE_BINARY_DIR}/ut-container-admin/containers/test.conf @ONLY) -CONFIGURE_FILE(ut-container-admin/containers/test-no-shutdown.conf.in - ${CMAKE_BINARY_DIR}/ut-container-admin/containers/test-no-shutdown.conf @ONLY) -FILE(GLOB admin_container_CONF_GEN ${CMAKE_BINARY_DIR}/ut-container-admin/containers/*.conf) - CONFIGURE_FILE(ut-network-admin/containers/test.conf.in ${CMAKE_BINARY_DIR}/ut-network-admin/containers/test.conf @ONLY) CONFIGURE_FILE(ut-network-admin/containers/buggy.conf.in @@ -110,8 +102,6 @@ INSTALL(FILES ${container_container_CONF_GEN} INSTALL(FILES ${admin_container_CONF} DESTINATION ${SC_TEST_CONFIG_INSTALL_DIR}/server/ut-container-admin/containers) -INSTALL(FILES ${admin_container_CONF_GEN} - DESTINATION ${SC_TEST_CONFIG_INSTALL_DIR}/server/ut-container-admin/containers) INSTALL(FILES ${network_container_CONF} DESTINATION ${SC_TEST_CONFIG_INSTALL_DIR}/server/ut-network-admin/containers) diff --git a/tests/unit_tests/server/configs/ut-container-admin/containers/buggy.conf.in b/tests/unit_tests/server/configs/ut-container-admin/containers/buggy.conf similarity index 71% rename from tests/unit_tests/server/configs/ut-container-admin/containers/buggy.conf.in rename to tests/unit_tests/server/configs/ut-container-admin/containers/buggy.conf index f4be18d..22f5810 100644 --- a/tests/unit_tests/server/configs/ut-container-admin/containers/buggy.conf.in +++ b/tests/unit_tests/server/configs/ut-container-admin/containers/buggy.conf @@ -1,4 +1,7 @@ { + "name" : "ut-container-admin-test", + "lxcTemplate" : "minimal.sh", + "initWithArgs" : ["/foo"], "privilege" : 10, "vt" : -1, "switchToDefaultAfterTimeout" : true, diff --git a/tests/unit_tests/server/configs/ut-container-admin/containers/missing.conf b/tests/unit_tests/server/configs/ut-container-admin/containers/missing.conf index f4be18d..6ff36fe 100644 --- a/tests/unit_tests/server/configs/ut-container-admin/containers/missing.conf +++ b/tests/unit_tests/server/configs/ut-container-admin/containers/missing.conf @@ -1,4 +1,7 @@ { + "name" : "ut-container-admin-test", + "lxcTemplate" : "missing.sh", + "initWithArgs" : ["/bin/sh", "-c", "trap exit SIGTERM; read"], "privilege" : 10, "vt" : -1, "switchToDefaultAfterTimeout" : true, diff --git a/tests/unit_tests/server/configs/ut-container-admin/containers/test-no-shutdown.conf.in b/tests/unit_tests/server/configs/ut-container-admin/containers/test-no-shutdown.conf similarity index 71% rename from tests/unit_tests/server/configs/ut-container-admin/containers/test-no-shutdown.conf.in rename to tests/unit_tests/server/configs/ut-container-admin/containers/test-no-shutdown.conf index f4be18d..7877f8e 100644 --- a/tests/unit_tests/server/configs/ut-container-admin/containers/test-no-shutdown.conf.in +++ b/tests/unit_tests/server/configs/ut-container-admin/containers/test-no-shutdown.conf @@ -1,4 +1,7 @@ { + "name" : "ut-container-admin-test", + "lxcTemplate" : "minimal.sh", + "initWithArgs" : ["/bin/sh"], "privilege" : 10, "vt" : -1, "switchToDefaultAfterTimeout" : true, diff --git a/tests/unit_tests/server/configs/ut-container-admin/containers/test.conf.in b/tests/unit_tests/server/configs/ut-container-admin/containers/test.conf similarity index 65% rename from tests/unit_tests/server/configs/ut-container-admin/containers/test.conf.in rename to tests/unit_tests/server/configs/ut-container-admin/containers/test.conf index f4be18d..a7cf00d 100644 --- a/tests/unit_tests/server/configs/ut-container-admin/containers/test.conf.in +++ b/tests/unit_tests/server/configs/ut-container-admin/containers/test.conf @@ -1,4 +1,7 @@ { + "name" : "ut-container-admin-test", + "lxcTemplate" : "minimal.sh", + "initWithArgs" : ["/bin/sh", "-c", "trap exit SIGTERM; read"], "privilege" : 10, "vt" : -1, "switchToDefaultAfterTimeout" : true, diff --git a/tests/unit_tests/server/ut-container-admin.cpp b/tests/unit_tests/server/ut-container-admin.cpp index 2294b77..6d2ba6c 100644 --- a/tests/unit_tests/server/ut-container-admin.cpp +++ b/tests/unit_tests/server/ut-container-admin.cpp @@ -23,274 +23,136 @@ * @brief Unit tests of the ContainerAdmin class */ -//#include "config.hpp" -//#include "ut.hpp" -// -//#include "container-admin.hpp" -//#include "exception.hpp" -// -//#include "utils/latch.hpp" -//#include "utils/glib-loop.hpp" -//#include "utils/exception.hpp" -//#include "utils/callback-guard.hpp" -//#include "libvirt/exception.hpp" -//#include "config/manager.hpp" -// -//#include -//#include -//#include -//#include -// -// -//using namespace security_containers; -// -//namespace { -// -//const std::string TEST_CONFIG_PATH = SC_TEST_CONFIG_INSTALL_DIR "/server/ut-container-admin/containers/test.conf"; -//const std::string TEST_NO_SHUTDOWN_CONFIG_PATH = SC_TEST_CONFIG_INSTALL_DIR "/server/ut-container-admin/containers/test-no-shutdown.conf"; -//const std::string BUGGY_CONFIG_PATH = SC_TEST_CONFIG_INSTALL_DIR "/server/ut-container-admin/containers/buggy.conf"; -//const std::string MISSING_CONFIG_PATH = SC_TEST_CONFIG_INSTALL_DIR "/server/ut-container-admin/containers/missing.conf"; -//const unsigned int WAIT_TIMEOUT = 5 * 1000; -//const unsigned int WAIT_STOP_TIMEOUT = 15 * 1000; -// -//void ensureStarted() -//{ -// std::this_thread::sleep_for(std::chrono::milliseconds(200)); -//} -// -//struct Fixture { -// utils::ScopedGlibLoop mLoop; -// utils::CallbackGuard mGuard; -//}; -// -//} // namespace -// -// -//BOOST_FIXTURE_TEST_SUITE(ContainerAdminSuite, Fixture) -// -//BOOST_AUTO_TEST_CASE(ConstructorDestructorTest) -//{ -// ContainerConfig config; -// config::loadFromFile(TEST_CONFIG_PATH, config); -// std::unique_ptr admin; -// BOOST_REQUIRE_NO_THROW(admin.reset(new ContainerAdmin(config))); -// BOOST_REQUIRE_NO_THROW(admin.reset()); -//} -// -//BOOST_AUTO_TEST_CASE(BuggyConfigTest) -//{ -// ContainerConfig config; -// config::loadFromFile(BUGGY_CONFIG_PATH, config); -// BOOST_REQUIRE_THROW(ContainerAdmin ca(config), LibvirtOperationException); -//} -// -//BOOST_AUTO_TEST_CASE(MissingConfigTest) -//{ -// ContainerConfig config; -// config::loadFromFile(MISSING_CONFIG_PATH, config); -// BOOST_REQUIRE_THROW(ContainerAdmin ca(config), UtilsException); -//} -// -//BOOST_AUTO_TEST_CASE(StartTest) -//{ -// utils::Latch booted; -// ContainerAdmin::ListenerId id = ContainerAdmin::LISTENER_ID_INVALID; -// ContainerConfig config; -// config::loadFromFile(TEST_CONFIG_PATH, config); -// ContainerAdmin ca(config); -// -// ContainerAdmin::LifecycleListener bootedListener = [&](const int event, const int detail) { -// if (event == VIR_DOMAIN_EVENT_STARTED && detail == VIR_DOMAIN_EVENT_STARTED_BOOTED) { -// booted.set(); -// } -// }; -// BOOST_REQUIRE_NO_THROW(id = ca.registerLifecycleListener(bootedListener, mGuard.spawn())); -// -// BOOST_REQUIRE_NO_THROW(ca.start()); -// ensureStarted(); -// -// BOOST_CHECK(booted.wait(WAIT_TIMEOUT)); -// BOOST_CHECK(ca.isRunning()); -// -// BOOST_REQUIRE_NO_THROW(ca.removeListener(id)); -//} -// -//BOOST_AUTO_TEST_CASE(ShutdownTest) -//{ -// utils::Latch shutdown; -// ContainerAdmin::ListenerId id = ContainerAdmin::LISTENER_ID_INVALID; -// ContainerConfig config; -// config::loadFromFile(TEST_CONFIG_PATH, config); -// ContainerAdmin ca(config); -// -// ContainerAdmin::LifecycleListener shutdownListener = [&](const int event, const int detail) { -// if (event == VIR_DOMAIN_EVENT_STOPPED && detail == VIR_DOMAIN_EVENT_STOPPED_SHUTDOWN) { -// shutdown.set(); -// } -// }; -// -// BOOST_REQUIRE_NO_THROW(ca.start()); -// ensureStarted(); -// BOOST_REQUIRE(ca.isRunning()); -// BOOST_REQUIRE_NO_THROW(id = ca.registerLifecycleListener(shutdownListener, mGuard.spawn())); -// -// BOOST_REQUIRE_NO_THROW(ca.shutdown()); -// BOOST_CHECK(shutdown.wait(WAIT_TIMEOUT)); -// BOOST_CHECK(!ca.isRunning()); -// BOOST_CHECK(ca.isStopped()); -// -// BOOST_REQUIRE_NO_THROW(ca.removeListener(id)); -//} -// -//BOOST_AUTO_TEST_CASE(DestroyTest) -//{ -// utils::Latch destroyed; -// ContainerAdmin::ListenerId id = ContainerAdmin::LISTENER_ID_INVALID; -// ContainerConfig config; -// config::loadFromFile(TEST_CONFIG_PATH, config); -// ContainerAdmin ca(config); -// -// ContainerAdmin::LifecycleListener destroyedListener = [&](const int event, const int detail) { -// if (event == VIR_DOMAIN_EVENT_STOPPED && detail == VIR_DOMAIN_EVENT_STOPPED_DESTROYED) { -// destroyed.set(); -// } -// }; -// -// BOOST_REQUIRE_NO_THROW(ca.start()); -// ensureStarted(); -// BOOST_REQUIRE(ca.isRunning()); -// BOOST_REQUIRE_NO_THROW(id = ca.registerLifecycleListener(destroyedListener, mGuard.spawn())); -// -// BOOST_REQUIRE_NO_THROW(ca.destroy()); -// BOOST_CHECK(destroyed.wait(WAIT_TIMEOUT)); -// BOOST_CHECK(!ca.isRunning()); -// BOOST_CHECK(ca.isStopped()); -// -// BOOST_REQUIRE_NO_THROW(ca.removeListener(id)); -//} -// -//BOOST_AUTO_TEST_CASE(StopShutdownTest) -//{ -// utils::Latch shutdown; -// ContainerAdmin::ListenerId id = ContainerAdmin::LISTENER_ID_INVALID; -// ContainerConfig config; -// config::loadFromFile(TEST_CONFIG_PATH, config); -// ContainerAdmin ca(config); -// -// ContainerAdmin::LifecycleListener shutdownListener = [&](const int event, const int detail) { -// if (event == VIR_DOMAIN_EVENT_STOPPED && detail == VIR_DOMAIN_EVENT_STOPPED_SHUTDOWN) { -// shutdown.set(); -// } -// }; -// -// BOOST_REQUIRE_NO_THROW(ca.start()); -// ensureStarted(); -// BOOST_REQUIRE(ca.isRunning()); -// BOOST_REQUIRE_NO_THROW(id = ca.registerLifecycleListener(shutdownListener, mGuard.spawn())); -// -// BOOST_REQUIRE_NO_THROW(ca.stop()); -// BOOST_CHECK(shutdown.wait(WAIT_TIMEOUT)); -// BOOST_CHECK(!ca.isRunning()); -// BOOST_CHECK(ca.isStopped()); -// -// BOOST_REQUIRE_NO_THROW(ca.removeListener(id)); -//} -// -//// This test needs to wait for a shutdown timer in stop() method. This takes 10s+. -//BOOST_AUTO_TEST_CASE(StopDestroyTest) -//{ -// utils::Latch destroyed; -// ContainerAdmin::ListenerId id = ContainerAdmin::LISTENER_ID_INVALID; -// ContainerConfig config; -// config::loadFromFile(TEST_NO_SHUTDOWN_CONFIG_PATH, config); -// ContainerAdmin ca(config); -// -// ContainerAdmin::LifecycleListener destroyedListener = [&](const int event, const int detail) { -// if (event == VIR_DOMAIN_EVENT_STOPPED && detail == VIR_DOMAIN_EVENT_STOPPED_DESTROYED) { -// destroyed.set(); -// } -// }; -// -// BOOST_REQUIRE_NO_THROW(ca.start()); -// ensureStarted(); -// BOOST_REQUIRE(ca.isRunning()); -// BOOST_REQUIRE_NO_THROW(id = ca.registerLifecycleListener(destroyedListener, mGuard.spawn())); -// -// BOOST_REQUIRE_NO_THROW(ca.stop()); -// BOOST_CHECK(destroyed.wait(WAIT_STOP_TIMEOUT)); -// BOOST_CHECK(!ca.isRunning()); -// BOOST_CHECK(ca.isStopped()); -// -// BOOST_REQUIRE_NO_THROW(ca.removeListener(id)); -//} -// -//BOOST_AUTO_TEST_CASE(SuspendTest) -//{ -// utils::Latch paused; -// ContainerAdmin::ListenerId id = ContainerAdmin::LISTENER_ID_INVALID; -// ContainerConfig config; -// config::loadFromFile(TEST_CONFIG_PATH, config); -// ContainerAdmin ca(config); -// -// ContainerAdmin::LifecycleListener pausedListener = [&](const int event, const int detail) { -// if (event == VIR_DOMAIN_EVENT_SUSPENDED && detail == VIR_DOMAIN_EVENT_SUSPENDED_PAUSED) { -// paused.set(); -// } -// }; -// -// BOOST_REQUIRE_NO_THROW(ca.start()) -// ensureStarted(); -// BOOST_REQUIRE(ca.isRunning()); -// BOOST_REQUIRE_NO_THROW(id = ca.registerLifecycleListener(pausedListener, mGuard.spawn())); -// -// BOOST_REQUIRE_NO_THROW(ca.suspend()); -// BOOST_CHECK(paused.wait(WAIT_TIMEOUT)); -// BOOST_CHECK(!ca.isRunning()); -// BOOST_CHECK(ca.isPaused()); -// -// BOOST_REQUIRE_NO_THROW(ca.removeListener(id)); -//} -// -//BOOST_AUTO_TEST_CASE(ResumeTest) -//{ -// utils::Latch unpaused; -// ContainerAdmin::ListenerId id = ContainerAdmin::LISTENER_ID_INVALID; -// ContainerConfig config; -// config::loadFromFile(TEST_CONFIG_PATH, config); -// ContainerAdmin ca(config); -// -// ContainerAdmin::LifecycleListener unpausedListener = [&](const int event, const int detail) { -// if (event == VIR_DOMAIN_EVENT_RESUMED && detail == VIR_DOMAIN_EVENT_RESUMED_UNPAUSED) { -// unpaused.set(); -// } -// }; -// -// BOOST_REQUIRE_NO_THROW(ca.start()); -// ensureStarted(); -// BOOST_REQUIRE(ca.isRunning()); -// BOOST_REQUIRE_NO_THROW(ca.suspend()) -// BOOST_REQUIRE(ca.isPaused()); -// BOOST_REQUIRE_NO_THROW(id = ca.registerLifecycleListener(unpausedListener, mGuard.spawn())); -// -// BOOST_REQUIRE_NO_THROW(ca.resume()); -// BOOST_CHECK(unpaused.wait(WAIT_TIMEOUT)); -// BOOST_CHECK(!ca.isPaused()); -// BOOST_CHECK(ca.isRunning()); -// -// BOOST_REQUIRE_NO_THROW(ca.removeListener(id)); -//} -// +#include "config.hpp" +#include "ut.hpp" + +#include "container-admin.hpp" +#include "exception.hpp" + +#include "utils/glib-loop.hpp" +#include "utils/scoped-dir.hpp" +#include "config/manager.hpp" + +using namespace security_containers; + +namespace { + +const std::string TEST_CONFIG_PATH = SC_TEST_CONFIG_INSTALL_DIR "/server/ut-container-admin/containers/test.conf"; +const std::string TEST_NO_SHUTDOWN_CONFIG_PATH = SC_TEST_CONFIG_INSTALL_DIR "/server/ut-container-admin/containers/test-no-shutdown.conf"; +const std::string BUGGY_CONFIG_PATH = SC_TEST_CONFIG_INSTALL_DIR "/server/ut-container-admin/containers/buggy.conf"; +const std::string MISSING_CONFIG_PATH = SC_TEST_CONFIG_INSTALL_DIR "/server/ut-container-admin/containers/missing.conf"; +const std::string CONTAINERS_PATH = "/tmp/ut-containers"; +const std::string LXC_TEMPLATES_PATH = SC_TEST_LXC_TEMPLATES_INSTALL_DIR; + +struct Fixture { + utils::ScopedGlibLoop mLoop; + utils::ScopedDir mContainersPathGuard = CONTAINERS_PATH; + + ContainerConfig mConfig; + + std::unique_ptr create(const std::string& configPath) + { + config::loadFromFile(configPath, mConfig); + return std::unique_ptr(new ContainerAdmin(CONTAINERS_PATH, + LXC_TEMPLATES_PATH, + mConfig)); + } + + void ensureStarted() + { + // wait for containers init to fully start + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + } +}; + +} // namespace + + +BOOST_FIXTURE_TEST_SUITE(ContainerAdminSuite, Fixture) + +BOOST_AUTO_TEST_CASE(ConstructorDestructorTest) +{ + auto admin = create(TEST_CONFIG_PATH); + admin.reset(); +} + +BOOST_AUTO_TEST_CASE(MissingConfigTest) +{ + BOOST_REQUIRE_THROW(create(MISSING_CONFIG_PATH), ContainerOperationException); +} + +BOOST_AUTO_TEST_CASE(StartTest) +{ + auto admin = create(TEST_CONFIG_PATH); + + admin->start(); + ensureStarted(); + + BOOST_CHECK(admin->isRunning()); +} + +BOOST_AUTO_TEST_CASE(StartBuggyTest) +{ + auto admin = create(BUGGY_CONFIG_PATH); + BOOST_REQUIRE_THROW(admin->start(), ContainerOperationException); +} + +BOOST_AUTO_TEST_CASE(StopShutdownTest) +{ + auto admin = create(TEST_CONFIG_PATH); + + admin->start(); + ensureStarted(); + BOOST_REQUIRE(admin->isRunning()); + + admin->stop(); + BOOST_CHECK(!admin->isRunning()); + BOOST_CHECK(admin->isStopped()); +} + +// This test needs to wait for a shutdown timer in stop() method. This takes 10s+. +BOOST_AUTO_TEST_CASE(StopDestroyTest) +{ + auto admin = create(TEST_NO_SHUTDOWN_CONFIG_PATH); + + admin->start(); + ensureStarted(); + BOOST_REQUIRE(admin->isRunning()); + + admin->stop(); + BOOST_CHECK(!admin->isRunning()); + BOOST_CHECK(admin->isStopped()); +} + +BOOST_AUTO_TEST_CASE(SuspendResumeTest) +{ + auto admin = create(TEST_NO_SHUTDOWN_CONFIG_PATH); + + admin->start(); + ensureStarted(); + BOOST_REQUIRE(admin->isRunning()); + + admin->suspend(); + BOOST_CHECK(!admin->isRunning()); + BOOST_CHECK(!admin->isStopped()); + BOOST_CHECK(admin->isPaused()); + + admin->resume(); + BOOST_CHECK(!admin->isPaused()); + BOOST_CHECK(!admin->isStopped()); + BOOST_CHECK(admin->isRunning()); +} + //BOOST_AUTO_TEST_CASE(SchedulerLevelTest) //{ -// ContainerConfig config; -// config::loadFromFile(TEST_CONFIG_PATH, config); -// ContainerAdmin ca(config); -// BOOST_REQUIRE_NO_THROW(ca.start()); +// auto admin = create(TEST_CONFIG_PATH); +// +// admin->start(); // ensureStarted(); -// BOOST_REQUIRE_NO_THROW(ca.setSchedulerLevel(SchedulerLevel::FOREGROUND)); -// BOOST_REQUIRE(ca.getSchedulerQuota() == config.cpuQuotaForeground); -// BOOST_REQUIRE_NO_THROW(ca.setSchedulerLevel(SchedulerLevel::BACKGROUND)); -// BOOST_REQUIRE(ca.getSchedulerQuota() == config.cpuQuotaBackground); +// BOOST_REQUIRE_NO_THROW(admin->setSchedulerLevel(SchedulerLevel::FOREGROUND)); +// BOOST_REQUIRE(admin->getSchedulerQuota() == config.cpuQuotaForeground); +// BOOST_REQUIRE_NO_THROW(admin->setSchedulerLevel(SchedulerLevel::BACKGROUND)); +// BOOST_REQUIRE(admin->getSchedulerQuota() == config.cpuQuotaBackground); //} -// -//BOOST_AUTO_TEST_SUITE_END() + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/unit_tests/server/ut-container.cpp b/tests/unit_tests/server/ut-container.cpp index dc2db18..fe5aa64 100644 --- a/tests/unit_tests/server/ut-container.cpp +++ b/tests/unit_tests/server/ut-container.cpp @@ -52,11 +52,6 @@ const std::string MISSING_CONFIG_PATH = "/this/is/a/missing/file/path/config.con const std::string CONTAINERS_PATH = "/tmp/ut-containers"; const std::string LXC_TEMPLATES_PATH = SC_TEST_LXC_TEMPLATES_INSTALL_DIR; -void ensureStarted() -{ - std::this_thread::sleep_for(std::chrono::milliseconds(200)); -} - struct Fixture { utils::ScopedGlibLoop mLoop; utils::ScopedDir mContainersPathGuard = CONTAINERS_PATH; @@ -69,6 +64,12 @@ struct Fixture { LXC_TEMPLATES_PATH, "")); } + + void ensureStarted() + { + // wait for containers init to fully start + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + } }; } // namespace @@ -84,7 +85,7 @@ BOOST_AUTO_TEST_CASE(ConstructorDestructorTest) BOOST_AUTO_TEST_CASE(BuggyConfigTest) { - BOOST_REQUIRE_THROW(create(BUGGY_CONFIG_PATH), std::exception);//TODO which one? + BOOST_REQUIRE_THROW(create(BUGGY_CONFIG_PATH), ContainerOperationException); } BOOST_AUTO_TEST_CASE(MissingConfigTest) -- 2.7.4 From 7f3a8c09fb8210c9480dd0b3d0137635fdd9cee8 Mon Sep 17 00:00:00 2001 From: Mateusz Malicki Date: Wed, 12 Nov 2014 14:43:18 +0100 Subject: [PATCH 03/16] Add client stubs for getting domain informations [Bug/Feature] Client stubs for getting domain informations [Cause] N/A [Solution] N/A [Verification] Build Change-Id: If800ae1396f3ec2e1a39d144ab6e93ea583b8949 --- client/security-containers-client-impl.cpp | 14 ++++++- client/security-containers-client-impl.hpp | 10 +++++ client/security-containers-client.cpp | 16 ++++++++ client/security-containers-client.h | 60 ++++++++++++++++++++++++++++++ 4 files changed, 99 insertions(+), 1 deletion(-) diff --git a/client/security-containers-client-impl.cpp b/client/security-containers-client-impl.cpp index fd85d2b..05636fe 100644 --- a/client/security-containers-client-impl.cpp +++ b/client/security-containers-client-impl.cpp @@ -357,7 +357,19 @@ VsmStatus Client::vsm_lookup_domain_by_pid(int pid, VsmString* id) noexcept *id = strdup(containerId.c_str()); mStatus = Status(); - return vsm_get_status();; + return vsm_get_status(); +} + +VsmStatus Client::vsm_lookup_domain_by_id(const char*, VsmDomain*) noexcept +{ + mStatus = Status(VSMCLIENT_OTHER_ERROR, "Not implemented"); + return vsm_get_status(); +} + +VsmStatus Client::vsm_lookup_domain_by_terminal_id(int, VsmString*) noexcept +{ + mStatus = Status(VSMCLIENT_OTHER_ERROR, "Not implemented"); + return vsm_get_status(); } VsmStatus Client::vsm_set_active_container(const char* id) noexcept diff --git a/client/security-containers-client-impl.hpp b/client/security-containers-client-impl.hpp index e88da49..bf1c39a 100644 --- a/client/security-containers-client-impl.hpp +++ b/client/security-containers-client-impl.hpp @@ -124,6 +124,16 @@ public: VsmStatus vsm_lookup_domain_by_pid(int pid, VsmString* id) noexcept; /** + * @see ::vsm_lookup_domain_by_id + */ + VsmStatus vsm_lookup_domain_by_id(const char* id, VsmDomain* domain) noexcept; + + /** + * @see ::vsm_lookup_domain_by_terminal_id + */ + VsmStatus vsm_lookup_domain_by_terminal_id(int terminal, VsmString* id) noexcept; + + /** * @see ::vsm_set_active_container */ VsmStatus vsm_set_active_container(const char* id) noexcept; diff --git a/client/security-containers-client.cpp b/client/security-containers-client.cpp index 2adc702..d1d99e4 100644 --- a/client/security-containers-client.cpp +++ b/client/security-containers-client.cpp @@ -88,6 +88,12 @@ API void vsm_string_free(VsmString string) free(string); } +API void vsm_domain_free(VsmDomain domain) +{ + free(domain->rootfs_path); + free(domain->id); + free(domain); +} API void vsm_client_free(VsmClient client) { @@ -126,6 +132,16 @@ API VsmStatus vsm_lookup_domain_by_pid(VsmClient client, int pid, VsmString* id) return getClient(client).vsm_lookup_domain_by_pid(pid, id); } +API VsmStatus vsm_lookup_domain_by_id(VsmClient client, const char* id, VsmDomain* domain) +{ + return getClient(client).vsm_lookup_domain_by_id(id, domain); +} + +API VsmStatus vsm_lookup_domain_by_terminal_id(VsmClient client, int terminal, VsmString* id) +{ + return getClient(client).vsm_lookup_domain_by_terminal_id(terminal, id); +} + API VsmStatus vsm_set_active_container(VsmClient client, const char* id) { return getClient(client).vsm_set_active_container(id); diff --git a/client/security-containers-client.h b/client/security-containers-client.h index 22a2a4e..4fe18f0 100644 --- a/client/security-containers-client.h +++ b/client/security-containers-client.h @@ -120,6 +120,38 @@ typedef enum { typedef unsigned int VsmSubscriptionId; /** + * States of domain + */ +typedef enum { + STOPPED, + STARTING, + RUNNING, + STOPPING, + ABORTING, + FREEZING, + FROZEN, + THAWED, + LOCKED, + MAX_STATE, + ACTIVATING = 128 +} VsmDomainState; + +/** + * Domain information structure + */ +struct VsmDomainStructure { + char* id; + int terminal; + VsmDomainState state; + char *rootfs_path; +}; + +/** + * Domain information + */ +typedef VsmDomainStructure* VsmDomain; + +/** * Start glib loop. * * Do not call this function if an application creates a glib loop itself. @@ -199,6 +231,12 @@ void vsm_array_string_free(VsmArrayString astring); */ void vsm_string_free(VsmString string); +/** + * Release VsmDomain + * + * @param domain VsmDomain + */ +void vsm_domain_free(VsmDomain domain); /** * @name Host API @@ -263,6 +301,28 @@ VsmStatus vsm_get_active_container_id(VsmClient client, VsmString* id); VsmStatus vsm_lookup_domain_by_pid(VsmClient client, int pid, VsmString* id); /** + * Get domain informations of domain with given id. + * + * @param[in] client security-containers-server's client + * @param[in] id domain name + * @param[out] domain domain informations + * @return status of this function call + * @remark Use @p vsm_doamin_free() to free memory occupied by @p domain + */ +VsmStatus vsm_lookup_domain_by_id(VsmClient client, const char* id, VsmDomain* domain); + +/** + * Get domain name with given terminal. + * + * @param[in] client security-containers-server's client + * @param[in] terminal terminal id + * @param[out] id domain name with given terminal + * @return status of this function call + * @remark Use @p vsm_string_free() to free memory occupied by @p id. + */ +VsmStatus vsm_lookup_domain_by_terminal_id(VsmClient client, int terminal, VsmString* id); + +/** * Set active (foreground) container. * * @param[in] client security-containers-server's client -- 2.7.4 From c658c5050ff820757012f0d2f0cf29bee172ab85 Mon Sep 17 00:00:00 2001 From: Mateusz Malicki Date: Wed, 12 Nov 2014 15:32:49 +0100 Subject: [PATCH 04/16] Add client stubs for domain lifetime management [Bug/Feature] Client stubs for domain lifetime management [Cause] N/A [Solution] N/A [Verification] Build Change-Id: Ibf6c8f769bf2bfab4e7960a6775fb1064990fc2d --- client/security-containers-client-impl.cpp | 12 ++++++++++++ client/security-containers-client-impl.hpp | 10 ++++++++++ client/security-containers-client.cpp | 10 ++++++++++ client/security-containers-client.h | 18 ++++++++++++++++++ 4 files changed, 50 insertions(+) diff --git a/client/security-containers-client-impl.cpp b/client/security-containers-client-impl.cpp index 05636fe..fc4699d 100644 --- a/client/security-containers-client-impl.cpp +++ b/client/security-containers-client-impl.cpp @@ -388,6 +388,18 @@ VsmStatus Client::vsm_create_domain(const char* id) noexcept return callMethod(HOST_INTERFACE, api::host::METHOD_ADD_CONTAINER, args_in); } +VsmStatus Client::vsm_shutdown_domain(const char*) noexcept +{ + mStatus = Status(VSMCLIENT_OTHER_ERROR, "Not implemented"); + return vsm_get_status(); +} + +VsmStatus Client::vsm_start_domain(const char*) noexcept +{ + mStatus = Status(VSMCLIENT_OTHER_ERROR, "Not implemented"); + return vsm_get_status(); +} + VsmStatus Client::vsm_add_state_callback(VsmContainerDbusStateCallback containerDbusStateCallback, void* data, VsmSubscriptionId* subscriptionId) noexcept diff --git a/client/security-containers-client-impl.hpp b/client/security-containers-client-impl.hpp index bf1c39a..0d450a7 100644 --- a/client/security-containers-client-impl.hpp +++ b/client/security-containers-client-impl.hpp @@ -144,6 +144,16 @@ public: VsmStatus vsm_create_domain(const char* id) noexcept; /** + * @see ::vsm_shutdown_domain + */ + VsmStatus vsm_shutdown_domain(const char* id) noexcept; + + /** + * @see ::vsm_start_domain + */ + VsmStatus vsm_start_domain(const char* id) noexcept; + + /** * @see ::vsm_add_state_callback */ VsmStatus vsm_add_state_callback(VsmContainerDbusStateCallback containerDbusStateCallback, diff --git a/client/security-containers-client.cpp b/client/security-containers-client.cpp index d1d99e4..c1dafeb 100644 --- a/client/security-containers-client.cpp +++ b/client/security-containers-client.cpp @@ -152,6 +152,16 @@ API VsmStatus vsm_create_domain(VsmClient client, const char* id) return getClient(client).vsm_create_domain(id); } +API VsmStatus vsm_shutdown_domain(VsmClient client, const char* id) +{ + return getClient(client).vsm_shutdown_domain(id); +} + +API VsmStatus vsm_start_domain(VsmClient client, const char* id) +{ + return getClient(client).vsm_start_domain(id); +} + API VsmStatus vsm_add_state_callback(VsmClient client, VsmContainerDbusStateCallback containerDbusStateCallback, void* data, diff --git a/client/security-containers-client.h b/client/security-containers-client.h index 4fe18f0..f19d3b7 100644 --- a/client/security-containers-client.h +++ b/client/security-containers-client.h @@ -341,6 +341,24 @@ VsmStatus vsm_set_active_container(VsmClient client, const char* id); VsmStatus vsm_create_domain(VsmClient client, const char* id); /** + * Shutdown domain + * + * @param[in] client security-containers-server's client + * @param[in] id domain name + * @return status of this function call + */ +VsmStatus vsm_shutdown_domain(VsmClient client, const char* id); + +/** + * Start domain + * + * @param[in] client security-containers-server's client + * @param[in] id domain name + * @return status of this function call + */ +VsmStatus vsm_start_domain(VsmClient client, const char* id); + +/** * Register dbus state change callback function. * * @note The callback function will be invoked on a different thread. -- 2.7.4 From 6544fc5632496132f08c94f48a5f731d9cfba860 Mon Sep 17 00:00:00 2001 From: Mateusz Malicki Date: Wed, 12 Nov 2014 16:10:30 +0100 Subject: [PATCH 05/16] Add client stubs for domain devices management [Bug/Feature] Client stubs for domain devices management [Cause] N/A [Solution] N/A [Verification] Build Change-Id: Ie34fff4507fa7277a2327983ae3bd6b7c732ae82 --- client/security-containers-client-impl.cpp | 60 ++++++++++ client/security-containers-client-impl.hpp | 67 +++++++++++ client/security-containers-client.cpp | 82 ++++++++++++++ client/security-containers-client.h | 171 ++++++++++++++++++++++++++++- 4 files changed, 378 insertions(+), 2 deletions(-) diff --git a/client/security-containers-client-impl.cpp b/client/security-containers-client-impl.cpp index fc4699d..a98349a 100644 --- a/client/security-containers-client-impl.cpp +++ b/client/security-containers-client-impl.cpp @@ -425,6 +425,66 @@ VsmStatus Client::vsm_del_state_callback(VsmSubscriptionId subscriptionId) noexc return signalUnsubscribe(subscriptionId); } +VsmStatus Client::vsm_domain_grant_device(const char*, const char*, uint32_t) noexcept +{ + mStatus = Status(VSMCLIENT_OTHER_ERROR, "Not implemented"); + return vsm_get_status(); +} + +VsmStatus Client::vsm_revoke_device(const char*, const char*) noexcept +{ + mStatus = Status(VSMCLIENT_OTHER_ERROR, "Not implemented"); + return vsm_get_status(); +} + +VsmStatus Client::vsm_domain_get_netdevs(const char*, VsmArrayString*) noexcept +{ + mStatus = Status(VSMCLIENT_OTHER_ERROR, "Not implemented"); + return vsm_get_status(); +} + +VsmStatus Client::vsm_netdev_get_ipv4_addr(const char*, const char*, struct in_addr*) noexcept +{ + mStatus = Status(VSMCLIENT_OTHER_ERROR, "Not implemented"); + return vsm_get_status(); +} + +VsmStatus Client::vsm_netdev_get_ipv6_addr(const char*, const char*, struct in6_addr*) noexcept +{ + mStatus = Status(VSMCLIENT_OTHER_ERROR, "Not implemented"); + return vsm_get_status(); +} + +VsmStatus Client::vsm_netdev_set_ipv4_addr(const char*, const char*, struct in_addr*, int) noexcept +{ + mStatus = Status(VSMCLIENT_OTHER_ERROR, "Not implemented"); + return vsm_get_status(); +} + +VsmStatus Client::vsm_netdev_set_ipv6_addr(const char*, const char*, struct in6_addr*, int) noexcept +{ + mStatus = Status(VSMCLIENT_OTHER_ERROR, "Not implemented"); + return vsm_get_status(); +} + +VsmStatus Client::vsm_create_netdev(const char*, VsmNetdevType, const char*, const char*) noexcept +{ + mStatus = Status(VSMCLIENT_OTHER_ERROR, "Not implemented"); + return vsm_get_status(); +} + +VsmStatus Client::vsm_destroy_netdev(const char*, const char*) noexcept +{ + mStatus = Status(VSMCLIENT_OTHER_ERROR, "Not implemented"); + return vsm_get_status(); +} + +VsmStatus Client::vsm_lookup_netdev_by_name(const char*, const char*, VsmNetdev*) noexcept +{ + mStatus = Status(VSMCLIENT_OTHER_ERROR, "Not implemented"); + return vsm_get_status(); +} + VsmStatus Client::vsm_notify_active_container(const char* application, const char* message) noexcept { assert(application); diff --git a/client/security-containers-client-impl.hpp b/client/security-containers-client-impl.hpp index 0d450a7..592771d 100644 --- a/client/security-containers-client-impl.hpp +++ b/client/security-containers-client-impl.hpp @@ -166,6 +166,73 @@ public: VsmStatus vsm_del_state_callback(VsmSubscriptionId subscriptionId) noexcept; /** + * @see ::vsm_del_state_callback + */ + VsmStatus vsm_domain_grant_device(const char* id, + const char* device, + uint32_t flags) noexcept; + + /** + * @see ::vsm_revoke_device + */ + VsmStatus vsm_revoke_device(const char* id, const char* device) noexcept; + + /** + * @see ::vsm_domain_get_netdevs + */ + VsmStatus vsm_domain_get_netdevs(const char* domain, VsmArrayString* netdevIds) noexcept; + + /** + * @see ::vsm_netdev_get_ipv4_addr + */ + VsmStatus vsm_netdev_get_ipv4_addr(const char* domain, + const char* netdevId, + struct in_addr *addr) noexcept; + + /** + * @see ::vsm_netdev_get_ipv6_addr + */ + VsmStatus vsm_netdev_get_ipv6_addr(const char* domain, + const char* netdevId, + struct in6_addr *addr) noexcept; + + /** + * @see ::vsm_netdev_set_ipv4_addr + */ + VsmStatus vsm_netdev_set_ipv4_addr(const char* domain, + const char* netdevId, + struct in_addr *addr, + int prefix) noexcept; + + /** + * @see ::vsm_netdev_set_ipv6_addr + */ + VsmStatus vsm_netdev_set_ipv6_addr(const char* domain, + const char* netdevId, + struct in6_addr *addr, + int prefix) noexcept; + + /** + * @see ::vsm_create_netdev + */ + VsmStatus vsm_create_netdev(const char* domain, + VsmNetdevType netdevType, + const char* target, + const char* netdevId) noexcept; + + /** + * @see ::vsm_destroy_netdev + */ + VsmStatus vsm_destroy_netdev(const char* domain, const char* netdevId) noexcept; + + /** + * @see ::vsm_lookup_netdev_by_name + */ + VsmStatus vsm_lookup_netdev_by_name(const char* domain, + const char* netdevId, + VsmNetdev* netdev) noexcept; + + /** * @see ::vsm_notify_active_container */ VsmStatus vsm_notify_active_container(const char* application, const char* message) noexcept; diff --git a/client/security-containers-client.cpp b/client/security-containers-client.cpp index c1dafeb..6616c80 100644 --- a/client/security-containers-client.cpp +++ b/client/security-containers-client.cpp @@ -95,6 +95,12 @@ API void vsm_domain_free(VsmDomain domain) free(domain); } +API void vsm_netdev_free(VsmNetdev netdev) +{ + free(netdev->name); + free(netdev); +} + API void vsm_client_free(VsmClient client) { if (client != NULL) { @@ -175,6 +181,82 @@ API VsmStatus vsm_del_state_callback(VsmClient client, VsmSubscriptionId subscri return getClient(client).vsm_del_state_callback(subscriptionId); } +API VsmStatus vsm_domain_grant_device(VsmClient client, + const char* id, + const char* device, + uint32_t flags) +{ + return getClient(client).vsm_domain_grant_device(id, device, flags); +} + +API VsmStatus vsm_revoke_device(VsmClient client, const char* id, const char* device) +{ + return getClient(client).vsm_revoke_device(id, device); +} + +API VsmStatus vsm_domain_get_netdevs(VsmClient client, + const char* domain, + VsmArrayString* netdevIds) +{ + return getClient(client).vsm_domain_get_netdevs(domain, netdevIds); +} + +API VsmStatus vsm_netdev_get_ipv4_addr(VsmClient client, + const char* domain, + const char* netdevId, + struct in_addr *addr) +{ + return getClient(client).vsm_netdev_get_ipv4_addr(domain, netdevId, addr); +} + +API VsmStatus vsm_netdev_get_ipv6_addr(VsmClient client, + const char* domain, + const char* netdevId, + struct in6_addr *addr) +{ + return getClient(client).vsm_netdev_get_ipv6_addr(domain, netdevId, addr); +} + +API VsmStatus vsm_netdev_set_ipv4_addr(VsmClient client, + const char* domain, + const char* netdevId, + struct in_addr *addr, + int prefix) +{ + return getClient(client).vsm_netdev_set_ipv4_addr(domain, netdevId, addr, prefix); +} + +API VsmStatus vsm_netdev_set_ipv6_addr(VsmClient client, + const char* domain, + const char* netdevId, + struct in6_addr *addr, + int prefix) +{ + return getClient(client).vsm_netdev_set_ipv6_addr(domain, netdevId, addr, prefix); +} + +API VsmStatus vsm_create_netdev(VsmClient client, + const char* domain, + VsmNetdevType netdevType, + const char* target, + const char* netdevId) +{ + return getClient(client).vsm_create_netdev(domain, netdevType, target, netdevId); +} + +API VsmStatus vsm_destroy_netdev(VsmClient client, const char* domain, const char* netdevId) +{ + return getClient(client).vsm_destroy_netdev(domain, netdevId); +} + +API VsmStatus vsm_lookup_netdev_by_name(VsmClient client, + const char* domain, + const char* netdevId, + VsmNetdev* netdev) +{ + return getClient(client).vsm_lookup_netdev_by_name(domain, netdevId, netdev); +} + API VsmStatus vsm_notify_active_container(VsmClient client, const char* application, const char* message) diff --git a/client/security-containers-client.h b/client/security-containers-client.h index f19d3b7..8afc67b 100644 --- a/client/security-containers-client.h +++ b/client/security-containers-client.h @@ -78,6 +78,8 @@ finish: #ifndef SECURITY_CONTAINERS_CLIENT_H #define SECURITY_CONTAINERS_CLIENT_H +#include + #ifdef __cplusplus extern "C" { @@ -139,12 +141,12 @@ typedef enum { /** * Domain information structure */ -struct VsmDomainStructure { +typedef struct { char* id; int terminal; VsmDomainState state; char *rootfs_path; -}; +} VsmDomainStructure; /** * Domain information @@ -152,6 +154,28 @@ struct VsmDomainStructure { typedef VsmDomainStructure* VsmDomain; /** + * Netowrk device type + */ +typedef enum { + VETH, + PHYS, + MACVLAN +} VsmNetdevType; + +/** + * Network device information structure + */ +typedef struct { + char* name; + VsmNetdevType type; +} VsmNetdevStructure; + +/** + * Network device information + */ +typedef VsmNetdevStructure* VsmNetdev; + +/** * Start glib loop. * * Do not call this function if an application creates a glib loop itself. @@ -239,6 +263,13 @@ void vsm_string_free(VsmString string); void vsm_domain_free(VsmDomain domain); /** + * Release VsmNetdev + * + * @param netdev VsmNetdev + */ +void vsm_netdev_free(VsmNetdev netdev); + +/** * @name Host API * * Functions using org.tizen.containers.host.manager D-Bus interface. @@ -384,6 +415,142 @@ VsmStatus vsm_add_state_callback(VsmClient client, */ VsmStatus vsm_del_state_callback(VsmClient client, VsmSubscriptionId subscriptionId); +/** + * Grant access to device + * + * @param[in] client security-containers-server's client + * @param[in] domain domain name + * @param[in] device device path + * @param[in] flags access flags + * @return status of this function call + */ +VsmStatus vsm_domain_grant_device(VsmClient client, + const char* domain, + const char* device, + uint32_t flags); + +/** + * Revoke access to device + * + * @param[in] client security-containers-server's client + * @param[in] domain domain name + * @param[in] device device path + * @return status of this function call + */ +VsmStatus vsm_revoke_device(VsmClient client, const char* domain, const char* device); + +/** + * Get array of netdev from given domain + * + * @param[in] client security-containers-server's client + * @param[in] domain domain name + * @param[out] netdevIds array of netdev id + * @return status of this function call + * @remark Use vsm_array_string_free() to free memory occupied by @p netdevIds. + */ +VsmStatus vsm_domain_get_netdevs(VsmClient client, const char* domain, VsmArrayString* netdevIds); + +/** + * Get ipv4 address for given netdevId + * + * @param[in] client security-containers-server's client + * @param[in] domain domain name + * @param[in] netdevId netdev id + * @param[out] addr ipv4 address + * @return status of this function call + */ +VsmStatus vsm_netdev_get_ipv4_addr(VsmClient client, + const char* domain, + const char* netdevId, + struct in_addr *addr); + +/** + * Get ipv6 address for given netdevId + * + * @param[in] client security-containers-server's client + * @param[in] domain domain name + * @param[in] netdevId netdev id + * @param[out] addr ipv6 address + * @return status of this function call + */ +VsmStatus vsm_netdev_get_ipv6_addr(VsmClient client, + const char* domain, + const char* netdevId, + struct in6_addr *addr); + +/** + * Set ipv4 address for given netdevId + * + * @param[in] client security-containers-server's client + * @param[in] domain domain name + * @param[in] netdevId netdev id + * @param[in] addr ipv4 address + * @param[in] prefix bit-length of the network prefix + * @return status of this function call + */ +VsmStatus vsm_netdev_set_ipv4_addr(VsmClient client, + const char* domain, + const char* netdevId, + struct in_addr *addr, + int prefix); + +/** + * Set ipv6 address for given netdevId + * + * @param[in] client security-containers-server's client + * @param[in] domain domain name + * @param[in] netdevId netdev id + * @param[in] addr ipv6 address + * @param[in] prefix bit-length of the network prefix + * @return status of this function call + */ +VsmStatus vsm_netdev_set_ipv6_addr(VsmClient client, + const char* domain, + const char* netdevId, + struct in6_addr *addr, + int prefix); + +/** + * Create netdev in domain + * + * @param[in] client security-containers-server's client + * @param[in] domain domain name + * @param[in] netdevType netdev type + * @param[in] target TODO: this is taken form domain-control + * @param[in] netdevId network device id + * @return status of this function call + */ +VsmStatus vsm_create_netdev(VsmClient client, + const char* domain, + VsmNetdevType netdevType, + const char* target, + const char* netdevId); + +/** + * Remove netdev from domain + * + * @param[in] client security-containers-server's client + * @param[in] domain domain name + * @param[in] netdevId network device id + * @return status of this function call + */ +VsmStatus vsm_destroy_netdev(VsmClient client, const char* domain, const char* netdevId); + +/** + * Get netdev informations + * + * @param[in] client security-containers-server's client + * @param[in] domain domain name + * @param[in] netdevId network device id + * @param[out] netdev netdev informations + * @return status of this function call + * @remark Use vsm_netdev_free() to free memory occupied by @p netdev. + */ +VsmStatus vsm_lookup_netdev_by_name(VsmClient client, + const char* domain, + const char* netdevId, + VsmNetdev* netdev); + /** @} */ // Host API -- 2.7.4 From a95a15f6b39abffd117038949dda0a208ac8f6f9 Mon Sep 17 00:00:00 2001 From: Mateusz Malicki Date: Fri, 14 Nov 2014 08:58:01 +0100 Subject: [PATCH 06/16] Add client stubs for domain image management [Bug/Feature] Client stubs for domain image management [Cause] N/A [Solution] N/A [Verification] Build Change-Id: I1e193a41b86d75333ed8dc0f165c42e52a1c16f9 --- cli/command-line-interface.cpp | 2 +- client/security-containers-client-impl.cpp | 12 +++++++++++- client/security-containers-client-impl.hpp | 7 ++++++- client/security-containers-client.cpp | 9 +++++++-- client/security-containers-client.h | 13 ++++++++++++- tests/unit_tests/client/ut-client.cpp | 2 +- 6 files changed, 38 insertions(+), 7 deletions(-) diff --git a/cli/command-line-interface.cpp b/cli/command-line-interface.cpp index 8af17a7..f3768d0 100644 --- a/cli/command-line-interface.cpp +++ b/cli/command-line-interface.cpp @@ -119,7 +119,7 @@ void create_domain(int pos, int argc, const char** argv) throw runtime_error("Not enough parameters"); } - one_shot(bind(vsm_create_domain, _1, argv[pos + 1])); + one_shot(bind(vsm_create_domain, _1, argv[pos + 1], nullptr)); } } // namespace cli diff --git a/client/security-containers-client-impl.cpp b/client/security-containers-client-impl.cpp index a98349a..205a8b2 100644 --- a/client/security-containers-client-impl.cpp +++ b/client/security-containers-client-impl.cpp @@ -380,14 +380,24 @@ VsmStatus Client::vsm_set_active_container(const char* id) noexcept return callMethod(HOST_INTERFACE, api::host::METHOD_SET_ACTIVE_CONTAINER, args_in); } -VsmStatus Client::vsm_create_domain(const char* id) noexcept +VsmStatus Client::vsm_create_domain(const char* id, const char* tname) noexcept { assert(id); + if (tname) { + mStatus = Status(VSMCLIENT_OTHER_ERROR, "Named template isn't implemented"); + return vsm_get_status(); + } GVariant* args_in = g_variant_new("(s)", id); return callMethod(HOST_INTERFACE, api::host::METHOD_ADD_CONTAINER, args_in); } +VsmStatus Client::vsm_destroy_domain(const char*) noexcept +{ + mStatus = Status(VSMCLIENT_OTHER_ERROR, "Not implemented"); + return vsm_get_status(); +} + VsmStatus Client::vsm_shutdown_domain(const char*) noexcept { mStatus = Status(VSMCLIENT_OTHER_ERROR, "Not implemented"); diff --git a/client/security-containers-client-impl.hpp b/client/security-containers-client-impl.hpp index 592771d..48e1c19 100644 --- a/client/security-containers-client-impl.hpp +++ b/client/security-containers-client-impl.hpp @@ -141,7 +141,12 @@ public: /** * @see ::vsm_create_domain */ - VsmStatus vsm_create_domain(const char* id) noexcept; + VsmStatus vsm_create_domain(const char* id, const char* tname) noexcept; + + /** + * @see ::vsm_destroy_domain + */ + VsmStatus vsm_destroy_domain(const char* id) noexcept; /** * @see ::vsm_shutdown_domain diff --git a/client/security-containers-client.cpp b/client/security-containers-client.cpp index 6616c80..e867a9e 100644 --- a/client/security-containers-client.cpp +++ b/client/security-containers-client.cpp @@ -153,9 +153,14 @@ API VsmStatus vsm_set_active_container(VsmClient client, const char* id) return getClient(client).vsm_set_active_container(id); } -API VsmStatus vsm_create_domain(VsmClient client, const char* id) +API VsmStatus vsm_create_domain(VsmClient client, const char* id, const char* tname) { - return getClient(client).vsm_create_domain(id); + return getClient(client).vsm_create_domain(id, tname); +} + +API VsmStatus vsm_destroy_domain(VsmClient client, const char* id) +{ + return getClient(client).vsm_destroy_domain(id); } API VsmStatus vsm_shutdown_domain(VsmClient client, const char* id) diff --git a/client/security-containers-client.h b/client/security-containers-client.h index 8afc67b..6419870 100644 --- a/client/security-containers-client.h +++ b/client/security-containers-client.h @@ -367,9 +367,20 @@ VsmStatus vsm_set_active_container(VsmClient client, const char* id); * * @param[in] client security-containers-server's client * @param[in] id container id + * @param[in] tname template name, NULL for default * @return status of this function call */ -VsmStatus vsm_create_domain(VsmClient client, const char* id); +VsmStatus vsm_create_domain(VsmClient client, const char* id, const char* tname); + +/** + * Remove domain + * + * @param[in] client security-containers-server's client + * @param[in] id container id + * @param[in] force if 0 data will be kept, otherwise data will be lost + * @return status of this function call + */ +VsmStatus vsm_destroy_domain(VsmStatus clent, const char* id, int force); /** * Shutdown domain diff --git a/tests/unit_tests/client/ut-client.cpp b/tests/unit_tests/client/ut-client.cpp index f26dbf3..3a5e0e6 100644 --- a/tests/unit_tests/client/ut-client.cpp +++ b/tests/unit_tests/client/ut-client.cpp @@ -208,7 +208,7 @@ BOOST_AUTO_TEST_CASE(AddContainerTest) VsmClient client = vsm_client_create(); VsmStatus status = vsm_connect(client); BOOST_REQUIRE_EQUAL(VSMCLIENT_SUCCESS, status); - status = vsm_create_domain(client, newActiveContainerId.c_str()); + status = vsm_create_domain(client, newActiveContainerId.c_str(), NULL); BOOST_REQUIRE_EQUAL(VSMCLIENT_CUSTOM_ERROR, status); vsm_client_free(client); } -- 2.7.4 From 34e721714c319bad60e60cbbf6e22f37ccb996b7 Mon Sep 17 00:00:00 2001 From: Piotr Bartosiewicz Date: Fri, 14 Nov 2014 12:20:04 +0100 Subject: [PATCH 07/16] Fix shutdown for systemd init [Bug/Feature] Systemd does not shutdown on signal [Cause] N/A [Solution] N/A [Verification] Build, install, run tests, run container Change-Id: Ic4b617c1a35a260803961fb17aba1da51c3af013 --- common/lxc/domain.cpp | 43 ++++++++++++ common/lxc/domain.hpp | 2 + common/utils/initctl.cpp | 90 +++++++++++++++++++++++++ common/utils/initctl.hpp | 44 ++++++++++++ server/configs/lxc-templates/business.sh | 4 +- server/configs/lxc-templates/private.sh | 4 +- tests/unit_tests/lxc/templates/minimal-dbus1.sh | 7 +- tests/unit_tests/lxc/templates/minimal-dbus2.sh | 7 +- tests/unit_tests/lxc/templates/minimal-dbus3.sh | 7 +- tests/unit_tests/lxc/templates/minimal.sh | 7 +- 10 files changed, 209 insertions(+), 6 deletions(-) create mode 100644 common/utils/initctl.cpp create mode 100644 common/utils/initctl.hpp diff --git a/common/lxc/domain.cpp b/common/lxc/domain.cpp index 0959959..57228c0 100644 --- a/common/lxc/domain.cpp +++ b/common/lxc/domain.cpp @@ -30,6 +30,9 @@ #include #include +#include +#include + #include namespace security_containers { @@ -145,6 +148,26 @@ bool LxcDomain::reboot() bool LxcDomain::shutdown(int timeout) { + State state = getState(); + if (state == State::STOPPED) { + return true; + } + if (state != State::RUNNING) { + LOGE("Could not gracefully shutdown domain " << getName()); + return false; + } + + // try shutdown by sending poweroff to init + if (setRunLevel(utils::RUNLEVEL_POWEROFF)) { + if (!mContainer->wait(mContainer, "STOPPED", timeout)) { + LOGE("Could not gracefully shutdown domain " + getName() + " in " << timeout << "s"); + return false; + } + return true; + } + LOGW("SetRunLevel failed for domain " + getName()); + + // fallback for other inits like bash: lxc sends 'lxc.haltsignal' signal to init if (!mContainer->shutdown(mContainer, timeout)) { LOGE("Could not gracefully shutdown domain " + getName() + " in " << timeout << "s"); return false; @@ -170,6 +193,26 @@ bool LxcDomain::unfreeze() return true; } +bool LxcDomain::setRunLevel(int runLevel) +{ + auto callback = [](void* param) { + utils::RunLevel runLevel = *reinterpret_cast(param); + return utils::setRunLevel(runLevel) ? 0 : 1; + }; + + lxc_attach_options_t options = LXC_ATTACH_OPTIONS_DEFAULT; + pid_t pid; + int ret = mContainer->attach(mContainer, callback, &runLevel, &options, &pid); + if (ret != 0) { + return false; + } + int status; + if (waitpid(pid, &status, 0) < 0) { + return false; + } + return status == 0; +} + } // namespace lxc } // namespace security_containers diff --git a/common/lxc/domain.hpp b/common/lxc/domain.hpp index 3202f54..8421d6d 100644 --- a/common/lxc/domain.hpp +++ b/common/lxc/domain.hpp @@ -76,6 +76,8 @@ public: bool unfreeze(); private: lxc_container* mContainer; + + bool setRunLevel(int runLevel); }; diff --git a/common/utils/initctl.cpp b/common/utils/initctl.cpp new file mode 100644 index 0000000..cfdea77 --- /dev/null +++ b/common/utils/initctl.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved + * + * Contact: Piotr Bartosiewicz + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +/** + * @file + * @author Piotr Bartosiewicz (p.bartosiewi@partner.samsung.com) + * @brief Api for talking to init via initctl + */ + +#include "config.hpp" +#include "utils/initctl.hpp" + +#include +#include +#include +#include + +namespace security_containers { +namespace utils { + +namespace { + struct InitctlRequest { + int magic; + int cmd; + int runlevel; + int sleeptime; + char data[368]; + }; + const int INITCTL_MAGIC = 0x03091969; + const int INITCTL_CMD_RUNLVL = 1; + + bool write(int fd, const void* data, size_t size) + { + while (size > 0) { + ssize_t r = ::write(fd, data, size); + if (r < 0) { + if (errno == EINTR) { + continue; + } + return false; + } + size -= r; + data = reinterpret_cast(data) + r; + } + return true; + } + + void close(int fd) + { + while (::close(fd) == -1 && errno == EINTR) {} + } +} + +bool setRunLevel(RunLevel runLevel) +{ + int fd = ::open("/dev/initctl", O_WRONLY|O_NONBLOCK|O_CLOEXEC|O_NOCTTY); + if (fd < 0) { + return false; + } + + InitctlRequest req; + memset(&req, 0, sizeof(req)); + req.magic = INITCTL_MAGIC; + req.cmd = INITCTL_CMD_RUNLVL; + req.runlevel = '0' + runLevel; + req.sleeptime = 0; + + bool ret = write(fd, &req, sizeof(req)); + close(fd); + return ret; +} + + +} // namespace utils +} // namespace security_containers diff --git a/common/utils/initctl.hpp b/common/utils/initctl.hpp new file mode 100644 index 0000000..2b97dd2 --- /dev/null +++ b/common/utils/initctl.hpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved + * + * Contact: Piotr Bartosiewicz + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +/** + * @file + * @author Piotr Bartosiewicz (p.bartosiewi@partner.samsung.com) + * @brief Api for talking to init via initctl + */ + +#ifndef COMMON_UTILS_INITCTL_HPP +#define COMMON_UTILS_INITCTL_HPP + + +namespace security_containers { +namespace utils { + +enum RunLevel : int { + RUNLEVEL_POWEROFF = 0, + RUNLEVEL_REBOOT = 6 +}; + +bool setRunLevel(RunLevel runLevel); + + +} // namespace utils +} // namespace security_containers + + +#endif // COMMON_UTILS_INITCTL_HPP diff --git a/server/configs/lxc-templates/business.sh b/server/configs/lxc-templates/business.sh index 75be9e6..09d67ca 100755 --- a/server/configs/lxc-templates/business.sh +++ b/server/configs/lxc-templates/business.sh @@ -27,7 +27,9 @@ cat <> ${path}/config lxc.utsname = ${name} lxc.rootfs = ${rootfs} -lxc.haltsignal = SIGTERM +# userns 1-to-1 mapping +#lxc.id_map = u 0 0 65536 +#lxc.id_map = g 0 0 65536 lxc.pts = 256 lxc.tty = 0 diff --git a/server/configs/lxc-templates/private.sh b/server/configs/lxc-templates/private.sh index 2926e55..731ff72 100755 --- a/server/configs/lxc-templates/private.sh +++ b/server/configs/lxc-templates/private.sh @@ -27,7 +27,9 @@ cat <> ${path}/config lxc.utsname = ${name} lxc.rootfs = ${rootfs} -lxc.haltsignal = SIGTERM +# userns 1-to-1 mapping +#lxc.id_map = u 0 0 65536 +#lxc.id_map = g 0 0 65536 lxc.pts = 256 lxc.tty = 0 diff --git a/tests/unit_tests/lxc/templates/minimal-dbus1.sh b/tests/unit_tests/lxc/templates/minimal-dbus1.sh index 6f967e5..35f816f 100755 --- a/tests/unit_tests/lxc/templates/minimal-dbus1.sh +++ b/tests/unit_tests/lxc/templates/minimal-dbus1.sh @@ -45,18 +45,23 @@ cat <> ${path}/config lxc.utsname = ${name} lxc.rootfs = ${rootfs} +# userns 1-to-1 mapping +lxc.id_map = u 0 0 65536 +lxc.id_map = g 0 0 65536 + lxc.haltsignal = SIGTERM lxc.pts = 256 lxc.tty = 0 +lxc.cgroup.devices.deny = a + lxc.mount.auto = proc sys cgroup lxc.mount.entry = /bin bin none ro,bind 0 0 lxc.mount.entry = /etc etc none ro,bind 0 0 lxc.mount.entry = /lib lib none ro,bind 0 0 lxc.mount.entry = /sbin sbin none ro,bind 0 0 lxc.mount.entry = /usr usr none ro,rbind 0 0 -lxc.mount.entry = devtmpfs dev devtmpfs rw,relatime,mode=755 0 0 lxc.mount.entry = /tmp/ut-run1 var/run none rw,bind 0 0 EOF diff --git a/tests/unit_tests/lxc/templates/minimal-dbus2.sh b/tests/unit_tests/lxc/templates/minimal-dbus2.sh index 1b5bf57..f8f963e 100755 --- a/tests/unit_tests/lxc/templates/minimal-dbus2.sh +++ b/tests/unit_tests/lxc/templates/minimal-dbus2.sh @@ -45,18 +45,23 @@ cat <> ${path}/config lxc.utsname = ${name} lxc.rootfs = ${rootfs} +# userns 1-to-1 mapping +lxc.id_map = u 0 0 65536 +lxc.id_map = g 0 0 65536 + lxc.haltsignal = SIGTERM lxc.pts = 256 lxc.tty = 0 +lxc.cgroup.devices.deny = a + lxc.mount.auto = proc sys cgroup lxc.mount.entry = /bin bin none ro,bind 0 0 lxc.mount.entry = /etc etc none ro,bind 0 0 lxc.mount.entry = /lib lib none ro,bind 0 0 lxc.mount.entry = /sbin sbin none ro,bind 0 0 lxc.mount.entry = /usr usr none ro,rbind 0 0 -lxc.mount.entry = devtmpfs dev devtmpfs rw,relatime,mode=755 0 0 lxc.mount.entry = /tmp/ut-run2 var/run none rw,bind 0 0 EOF diff --git a/tests/unit_tests/lxc/templates/minimal-dbus3.sh b/tests/unit_tests/lxc/templates/minimal-dbus3.sh index 9ace1c6..68f4f11 100755 --- a/tests/unit_tests/lxc/templates/minimal-dbus3.sh +++ b/tests/unit_tests/lxc/templates/minimal-dbus3.sh @@ -45,18 +45,23 @@ cat <> ${path}/config lxc.utsname = ${name} lxc.rootfs = ${rootfs} +# userns 1-to-1 mapping +lxc.id_map = u 0 0 65536 +lxc.id_map = g 0 0 65536 + lxc.haltsignal = SIGTERM lxc.pts = 256 lxc.tty = 0 +lxc.cgroup.devices.deny = a + lxc.mount.auto = proc sys cgroup lxc.mount.entry = /bin bin none ro,bind 0 0 lxc.mount.entry = /etc etc none ro,bind 0 0 lxc.mount.entry = /lib lib none ro,bind 0 0 lxc.mount.entry = /sbin sbin none ro,bind 0 0 lxc.mount.entry = /usr usr none ro,rbind 0 0 -lxc.mount.entry = devtmpfs dev devtmpfs rw,relatime,mode=755 0 0 lxc.mount.entry = /tmp/ut-run3 var/run none rw,bind 0 0 EOF diff --git a/tests/unit_tests/lxc/templates/minimal.sh b/tests/unit_tests/lxc/templates/minimal.sh index 64f6da7..547661e 100755 --- a/tests/unit_tests/lxc/templates/minimal.sh +++ b/tests/unit_tests/lxc/templates/minimal.sh @@ -43,18 +43,23 @@ cat <> ${path}/config lxc.utsname = ${name} lxc.rootfs = ${rootfs} +# userns 1-to-1 mapping +lxc.id_map = u 0 0 65536 +lxc.id_map = g 0 0 65536 + lxc.haltsignal = SIGTERM lxc.pts = 256 lxc.tty = 0 +lxc.cgroup.devices.deny = a + lxc.mount.auto = proc sys cgroup lxc.mount.entry = /bin bin none ro,bind 0 0 lxc.mount.entry = /etc etc none ro,bind 0 0 lxc.mount.entry = /lib lib none ro,bind 0 0 lxc.mount.entry = /sbin sbin none ro,bind 0 0 lxc.mount.entry = /usr usr none ro,rbind 0 0 -lxc.mount.entry = devtmpfs dev devtmpfs rw,relatime,mode=755 0 0 EOF if [ "$(uname -m)" = "x86_64" ]; then -- 2.7.4 From 9f9fbe48b66186939013584f675af127f30e1792 Mon Sep 17 00:00:00 2001 From: Jan Olszak Date: Wed, 12 Nov 2014 15:36:09 +0100 Subject: [PATCH 08/16] IPC: Pass error to return the value callback [Bug/Feature] Return value callback takes status enum Peer's socket is locket when processing the callbacks. [Cause] N/A [Solution] N/A [Verification] Build, install, run tests Change-Id: I74f30713e7c4fa6d8f35b79c2485137d1c9119c3 --- common/ipc/exception.hpp | 18 +++ common/ipc/internals/acceptor.cpp | 1 - common/ipc/internals/processor.cpp | 227 +++++++++++++++++++++++-------------- common/ipc/internals/processor.hpp | 38 +++++-- common/ipc/service.hpp | 4 +- common/ipc/types.cpp | 64 +++++++++++ common/ipc/types.hpp | 19 +++- tests/unit_tests/ipc/ut-ipc.cpp | 92 ++++++++++++++- 8 files changed, 359 insertions(+), 104 deletions(-) create mode 100644 common/ipc/types.cpp diff --git a/common/ipc/exception.hpp b/common/ipc/exception.hpp index 67d9c86..f9e8322 100644 --- a/common/ipc/exception.hpp +++ b/common/ipc/exception.hpp @@ -38,7 +38,25 @@ struct IPCException: public SecurityContainersException { IPCException(const std::string& error) : SecurityContainersException(error) {} }; +struct IPCParsingException: public IPCException { + IPCParsingException(const std::string& error) : IPCException(error) {} +}; + +struct IPCSerializationException: public IPCException { + IPCSerializationException(const std::string& error) : IPCException(error) {} +}; + +struct IPCPeerDisconnectedException: public IPCException { + IPCPeerDisconnectedException(const std::string& error) : IPCException(error) {} +}; + +struct IPCNaughtyPeerException: public IPCException { + IPCNaughtyPeerException(const std::string& error) : IPCException(error) {} +}; +struct IPCTimeoutException: public IPCException { + IPCTimeoutException(const std::string& error) : IPCException(error) {} +}; } diff --git a/common/ipc/internals/acceptor.cpp b/common/ipc/internals/acceptor.cpp index 9738546..193da61 100644 --- a/common/ipc/internals/acceptor.cpp +++ b/common/ipc/internals/acceptor.cpp @@ -103,7 +103,6 @@ void Acceptor::run() } LOGE("Error in poll: " << std::string(strerror(errno))); throw IPCException("Error in poll: " + std::string(strerror(errno))); - break; } // Check for incoming connections diff --git a/common/ipc/internals/processor.cpp b/common/ipc/internals/processor.cpp index 0465574..50327c0 100644 --- a/common/ipc/internals/processor.cpp +++ b/common/ipc/internals/processor.cpp @@ -36,10 +36,21 @@ #include #include - namespace security_containers { namespace ipc { +#define IGNORE_EXCEPTIONS(expr) \ + try \ + { \ + expr; \ + } \ + catch (const std::exception& e){ \ + LOGE("Callback threw an error: " << e.what()); \ + } + + + + const Processor::MethodID Processor::RETURN_METHOD_ID = std::numeric_limits::max(); Processor::Processor(const PeerCallback& newPeerCallback, @@ -98,10 +109,7 @@ Processor::PeerID Processor::addPeer(const std::shared_ptr& socketPtr) { Lock lock(mSocketsMutex); peerID = getNextPeerID(); - SocketInfo socketInfo; - socketInfo.peerID = peerID; - socketInfo.socketPtr = std::move(socketPtr); - mNewSockets.push(std::move(socketInfo)); + mNewSockets.emplace(peerID, std::move(socketPtr)); } LOGI("New peer added. Id: " << peerID); mEventQueue.send(Event::NEW_PEER); @@ -109,13 +117,29 @@ Processor::PeerID Processor::addPeer(const std::shared_ptr& socketPtr) return peerID; } -void Processor::removePeer(PeerID peerID) +void Processor::removePeer(const PeerID peerID, Status status) { LOGW("Removing naughty peer. ID: " << peerID); { Lock lock(mSocketsMutex); mSockets.erase(peerID); } + + { + // Erase associated return value callbacks + Lock lock(mReturnCallbacksMutex); + + std::shared_ptr data; + for (auto it = mReturnCallbacks.begin(); it != mReturnCallbacks.end();) { + if (it->second.peerID == peerID) { + IGNORE_EXCEPTIONS(it->second.process(status, data)); + it = mReturnCallbacks.erase(it); + } else { + ++it; + } + } + } + resetPolling(); } @@ -175,6 +199,7 @@ void Processor::run() } } + bool Processor::handleLostConnections() { std::list peersToRemove; @@ -190,14 +215,10 @@ bool Processor::handleLostConnections() } } - for (const auto peerID : peersToRemove) { - LOGT("Removing peer. ID: " << peerID); - mSockets.erase(peerID); - } } - if (!peersToRemove.empty()) { - resetPolling(); + for (const PeerID peerID : peersToRemove) { + removePeer(peerID, Status::PEER_DISCONNECTED); } return !peersToRemove.empty(); @@ -231,77 +252,102 @@ bool Processor::handleInput(const PeerID peerID, const Socket& socket) MethodID methodID; MessageID messageID; { - LOGI("Locking"); Socket::Guard guard = socket.getGuard(); socket.read(&methodID, sizeof(methodID)); socket.read(&messageID, sizeof(messageID)); - LOGI("Locked"); if (methodID == RETURN_METHOD_ID) { - LOGI("Return value for messageID: " << messageID); - ReturnCallbacks returnCallbacks; - try { - Lock lock(mReturnCallbacksMutex); - LOGT("Getting the return callback"); - returnCallbacks = std::move(mReturnCallbacks.at(messageID)); - mReturnCallbacks.erase(messageID); - } catch (const std::out_of_range&) { - LOGW("No return callback for messageID: " << messageID); - return false; - } + return onReturnValue(peerID, socket, messageID); + } else { + return onRemoteCall(peerID, socket, methodID, messageID); + } + } - std::shared_ptr data; - try { - LOGT("Parsing incoming return data"); - data = returnCallbacks.parse(socket.getFD()); - } catch (const IPCException&) { - removePeer(peerID); - return true; - } + return false; +} - guard.unlock(); +bool Processor::onReturnValue(const PeerID peerID, + const Socket& socket, + const MessageID messageID) +{ + LOGI("Return value for messageID: " << messageID); + ReturnCallbacks returnCallbacks; + try { + Lock lock(mReturnCallbacksMutex); + LOGT("Getting the return callback"); + returnCallbacks = std::move(mReturnCallbacks.at(messageID)); + mReturnCallbacks.erase(messageID); + } catch (const std::out_of_range&) { + LOGW("No return callback for messageID: " << messageID); + removePeer(peerID, Status::NAUGHTY_PEER); + return true; + } - LOGT("Process callback for methodID: " << methodID << "; messageID: " << messageID); - returnCallbacks.process(data); + std::shared_ptr data; + try { + LOGT("Parsing incoming return data"); + data = returnCallbacks.parse(socket.getFD()); + } catch (const std::exception& e) { + LOGE("Exception during parsing: " << e.what()); + IGNORE_EXCEPTIONS(returnCallbacks.process(Status::PARSING_ERROR, data)); + removePeer(peerID, Status::PARSING_ERROR); + return true; + } - } else { - LOGI("Remote call; methodID: " << methodID << " messageID: " << messageID); - std::shared_ptr methodCallbacks; - try { - Lock lock(mCallsMutex); - methodCallbacks = mMethodsCallbacks.at(methodID); - } catch (const std::out_of_range&) { - LOGW("No method callback for methodID: " << methodID); - removePeer(peerID); - return true; - } + LOGT("Process return value callback for messageID: " << messageID); + IGNORE_EXCEPTIONS(returnCallbacks.process(Status::OK, data)); - std::shared_ptr data; - try { - LOGT("Parsing incoming data"); - data = methodCallbacks->parse(socket.getFD()); - } catch (const IPCException&) { - removePeer(peerID); - return true; - } + return false; +} - guard.unlock(); - - LOGT("Process callback for methodID: " << methodID << "; messageID: " << messageID); - std::shared_ptr returnData = methodCallbacks->method(data); - - LOGT("Sending return data; methodID: " << methodID << "; messageID: " << messageID); - try { - // Send the call with the socket - Socket::Guard guard = socket.getGuard(); - socket.write(&RETURN_METHOD_ID, sizeof(RETURN_METHOD_ID)); - socket.write(&messageID, sizeof(messageID)); - methodCallbacks->serialize(socket.getFD(), returnData); - } catch (const IPCException&) { - removePeer(peerID); - return true; - } - } +bool Processor::onRemoteCall(const PeerID peerID, + const Socket& socket, + const MethodID methodID, + const MessageID messageID) +{ + LOGI("Remote call; methodID: " << methodID << " messageID: " << messageID); + + std::shared_ptr methodCallbacks; + try { + Lock lock(mCallsMutex); + methodCallbacks = mMethodsCallbacks.at(methodID); + } catch (const std::out_of_range&) { + LOGW("No method callback for methodID: " << methodID); + removePeer(peerID, Status::NAUGHTY_PEER); + return true; + } + + std::shared_ptr data; + try { + LOGT("Parsing incoming data"); + data = methodCallbacks->parse(socket.getFD()); + } catch (const std::exception& e) { + LOGE("Exception during parsing: " << e.what()); + removePeer(peerID, Status::PARSING_ERROR); + return true; + } + + LOGT("Process callback for methodID: " << methodID << "; messageID: " << messageID); + std::shared_ptr returnData; + try { + returnData = methodCallbacks->method(data); + } catch (const std::exception& e) { + LOGE("Exception in method handler: " << e.what()); + removePeer(peerID, Status::NAUGHTY_PEER); + return true; + } + + LOGT("Sending return data; methodID: " << methodID << "; messageID: " << messageID); + try { + // Send the call with the socket + Socket::Guard guard = socket.getGuard(); + socket.write(&RETURN_METHOD_ID, sizeof(RETURN_METHOD_ID)); + socket.write(&messageID, sizeof(messageID)); + methodCallbacks->serialize(socket.getFD(), returnData); + } catch (const std::exception& e) { + LOGE("Exception during serialization: " << e.what()); + removePeer(peerID, Status::SERIALIZATION_ERROR); + return true; } return false; @@ -325,8 +371,7 @@ bool Processor::handleEvent() case Event::CALL: { LOGD("Event CALL"); - handleCall(); - return false; + return handleCall(); } case Event::NEW_PEER: { @@ -340,15 +385,16 @@ bool Processor::handleEvent() if (mSockets.size() > mMaxNumberOfPeers) { LOGE("There are too many peers. I don't accept the connection with " << socketInfo.peerID); - + return false; } if (mSockets.count(socketInfo.peerID) != 0) { LOGE("There already was a socket for peerID: " << socketInfo.peerID); + return false; } - mSockets[socketInfo.peerID] = socketInfo.socketPtr; + + mSockets.emplace(socketInfo.peerID, std::move(socketInfo.socketPtr)); } resetPolling(); - if (mNewPeerCallback) { // Notify about the new user. mNewPeerCallback(socketInfo.peerID); @@ -384,23 +430,20 @@ Processor::Call Processor::getCall() return call; } -void Processor::handleCall() +bool Processor::handleCall() { - LOGT("Handle call from another thread"); + LOGT("Handle call (from another thread) to send a message."); Call call = getCall(); - ReturnCallbacks returnCallbacks; - returnCallbacks.parse = call.parse; - returnCallbacks.process = call.process; - std::shared_ptr socketPtr; try { - // Get the addressee's socket + // Get the peer's socket Lock lock(mSocketsMutex); socketPtr = mSockets.at(call.peerID); } catch (const std::out_of_range&) { LOGE("Peer disconnected. No socket with a peerID: " << call.peerID); - return; + IGNORE_EXCEPTIONS(call.process(Status::PEER_DISCONNECTED, call.data)); + return false; } MessageID messageID = getNextMessageID(); @@ -411,7 +454,9 @@ void Processor::handleCall() if (mReturnCallbacks.count(messageID) != 0) { LOGE("There already was a return callback for messageID: " << messageID); } - mReturnCallbacks[messageID] = std::move(returnCallbacks); + mReturnCallbacks.emplace(messageID, ReturnCallbacks(call.peerID, + std::move(call.parse), + std::move(call.process))); } try { @@ -422,12 +467,20 @@ void Processor::handleCall() call.serialize(socketPtr->getFD(), call.data); } catch (const std::exception& e) { LOGE("Error during sending a message: " << e.what()); + + // Inform about the error + IGNORE_EXCEPTIONS(mReturnCallbacks[messageID].process(Status::SERIALIZATION_ERROR, call.data)); + { Lock lock(mReturnCallbacksMutex); mReturnCallbacks.erase(messageID); } - // TODO: User should get the error code. + + removePeer(call.peerID, Status::SERIALIZATION_ERROR); + return true; } + + return false; } } // namespace ipc diff --git a/common/ipc/internals/processor.hpp b/common/ipc/internals/processor.hpp index cb14a67..3df3f9c 100644 --- a/common/ipc/internals/processor.hpp +++ b/common/ipc/internals/processor.hpp @@ -71,6 +71,8 @@ const unsigned int DEFAULT_MAX_NUMBER_OF_PEERS = 500; * - don't throw timeout if the message is already processed * - naming convention or methods that just commissions the PROCESS thread to do something * - removePeer API function +* - error handling - special message type +* - some mutexes may not be needed */ class Processor { public: @@ -214,6 +216,10 @@ private: ReturnCallbacks(ReturnCallbacks&&) = default; ReturnCallbacks& operator=(ReturnCallbacks &&) = default; + ReturnCallbacks(PeerID peerID, const ParseCallback& parse, const ResultHandler::type& process) + : peerID(peerID), parse(parse), process(process) {} + + PeerID peerID; ParseCallback parse; ResultHandler::type process; }; @@ -225,8 +231,11 @@ private: SocketInfo(SocketInfo&&) = default; SocketInfo& operator=(SocketInfo &&) = default; - std::shared_ptr socketPtr; + SocketInfo(const PeerID peerID, const std::shared_ptr& socketPtr) + : peerID(peerID), socketPtr(socketPtr) {} + PeerID peerID; + std::shared_ptr socketPtr; }; enum class Event : int { @@ -268,15 +277,22 @@ private: void run(); bool handleEvent(); - void handleCall(); + bool handleCall(); bool handleLostConnections(); bool handleInputs(); bool handleInput(const PeerID peerID, const Socket& socket); + bool onReturnValue(const PeerID peerID, + const Socket& socket, + const MessageID messageID); + bool onRemoteCall(const PeerID peerID, + const Socket& socket, + const MethodID methodID, + const MessageID messageID); void resetPolling(); MessageID getNextMessageID(); PeerID getNextPeerID(); Call getCall(); - void removePeer(PeerID peerID); + void removePeer(const PeerID peerID, Status status); }; @@ -352,9 +368,9 @@ void Processor::callAsync(const MethodID methodID, config::saveToFD(fd, *std::static_pointer_cast(data)); }; - call.process = [process](std::shared_ptr& data)->void { + call.process = [process](Status status, std::shared_ptr& data)->void { std::shared_ptr tmpData = std::static_pointer_cast(data); - return process(tmpData); + return process(status, tmpData); }; { @@ -387,8 +403,10 @@ std::shared_ptr Processor::callSync(const MethodID methodID, std::mutex mtx; std::unique_lock lck(mtx); std::condition_variable cv; + Status returnStatus = ipc::Status::UNDEFINED; - auto process = [&result, &cv](std::shared_ptr returnedData) { + auto process = [&result, &cv, &returnStatus](Status status, std::shared_ptr returnedData) { + returnStatus = status; result = returnedData; cv.notify_one(); }; @@ -399,15 +417,17 @@ std::shared_ptr Processor::callSync(const MethodID methodID, data, process); - auto isResultInitialized = [&result]() { - return static_cast(result); + auto isResultInitialized = [&returnStatus]() { + return returnStatus != ipc::Status::UNDEFINED; }; if (!cv.wait_for(lck, std::chrono::milliseconds(timeoutMS), isResultInitialized)) { LOGE("Function call timeout; methodID: " << methodID); - throw IPCException("Function call timeout; methodID: " + std::to_string(methodID)); + throw IPCTimeoutException("Function call timeout; methodID: " + std::to_string(methodID)); } + throwOnError(returnStatus); + return result; } diff --git a/common/ipc/service.hpp b/common/ipc/service.hpp index 08f7ec9..873673d 100644 --- a/common/ipc/service.hpp +++ b/common/ipc/service.hpp @@ -103,7 +103,7 @@ public: std::shared_ptr callSync(const MethodID methodID, const PeerID peerID, const std::shared_ptr& data, - unsigned int timeoutMS); + unsigned int timeoutMS = 500); /** * Asynchronous method call. The return callback will be called on @@ -140,7 +140,7 @@ template std::shared_ptr Service::callSync(const MethodID methodID, const PeerID peerID, const std::shared_ptr& data, - unsigned int timeoutMS = 500) + unsigned int timeoutMS) { LOGD("Sync calling method: " << methodID << " for user: " << peerID); return mProcessor.callSync(methodID, peerID, data, timeoutMS); diff --git a/common/ipc/types.cpp b/common/ipc/types.cpp new file mode 100644 index 0000000..e0ffc5b --- /dev/null +++ b/common/ipc/types.cpp @@ -0,0 +1,64 @@ +/* +* Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved +* +* Contact: Jan Olszak +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License +*/ + +/** + * @file + * @author Jan Olszak (j.olszak@samsung.com) + * @brief Types definitions and helper functions + */ + +#include "ipc/types.hpp" +#include "logger/logger.hpp" + + +namespace security_containers { +namespace ipc { + +std::string toString(const Status status) +{ + switch (status) { + case Status::OK: return "No error, everything is OK"; + case Status::PARSING_ERROR: return "Exception during reading/parsing data from the socket"; + case Status::SERIALIZATION_ERROR: return "Exception during writing/serializing data to the socket"; + case Status::PEER_DISCONNECTED: return "No such peer. Might got disconnected."; + case Status::NAUGHTY_PEER: return "Peer performed a forbidden action."; + case Status::UNDEFINED: return "Undefined state"; + default: return "Unknown status"; + } +} + +void throwOnError(const Status status) +{ + if (status == Status::OK) { + return; + } + + std::string message = toString(status); + LOGE(message); + + switch (status) { + case Status::PARSING_ERROR: throw IPCParsingException(message); + case Status::SERIALIZATION_ERROR: throw IPCSerializationException(message); + case Status::PEER_DISCONNECTED: throw IPCPeerDisconnectedException(message); + case Status::NAUGHTY_PEER: throw IPCNaughtyPeerException(message); + case Status::UNDEFINED: throw IPCException(message); + default: return throw IPCException(message); + } +} +} // namespace ipc +} // namespace security_containers diff --git a/common/ipc/types.hpp b/common/ipc/types.hpp index 4269664..c07e504 100644 --- a/common/ipc/types.hpp +++ b/common/ipc/types.hpp @@ -19,18 +19,33 @@ /** * @file * @author Jan Olszak (j.olszak@samsung.com) - * @brief Handler types definitions + * @brief Types definitions */ #ifndef COMMON_IPC_HANDLERS_HPP #define COMMON_IPC_HANDLERS_HPP +#include "ipc/exception.hpp" + #include #include +#include namespace security_containers { namespace ipc { +enum class Status : int { + OK = 0, + PARSING_ERROR, + SERIALIZATION_ERROR, + PEER_DISCONNECTED, + NAUGHTY_PEER, + UNDEFINED +}; + +std::string toString(const Status status); +void throwOnError(const Status status); + template struct MethodHandler { typedef std::function(std::shared_ptr&)> type; @@ -39,7 +54,7 @@ struct MethodHandler { template struct ResultHandler { - typedef std::function&)> type; + typedef std::function&)> type; }; } // namespace ipc diff --git a/tests/unit_tests/ipc/ut-ipc.cpp b/tests/unit_tests/ipc/ut-ipc.cpp index 48ad1e5..ea70b45 100644 --- a/tests/unit_tests/ipc/ut-ipc.cpp +++ b/tests/unit_tests/ipc/ut-ipc.cpp @@ -75,6 +75,21 @@ struct EmptyData { CONFIG_REGISTER_EMPTY }; +struct ThrowOnAcceptData { + template + void accept(Visitor) + { + LOGE("Serialization and parsing failed"); + throw std::exception(); + } + template + void accept(Visitor) const + { + LOGE("Const Serialization and parsing failed"); + throw std::exception(); + } +}; + std::shared_ptr returnEmptyCallback(std::shared_ptr&) { return std::shared_ptr(new EmptyData()); @@ -90,6 +105,12 @@ std::shared_ptr echoCallback(std::shared_ptr& data) return data; } +std::shared_ptr longEchoCallback(std::shared_ptr& data) +{ + std::this_thread::sleep_for(std::chrono::seconds(1)); + return data; +} + void testEcho(Client& c, const Client::MethodID methodID) { std::shared_ptr sentData(new SendData(34)); @@ -283,7 +304,8 @@ BOOST_AUTO_TEST_CASE(AsyncClientToServiceEchoTest) //Async call std::shared_ptr sentData(new SendData(34)); std::shared_ptr recvData; - auto dataBack = [&cv, &recvData](std::shared_ptr& data) { + auto dataBack = [&cv, &recvData](ipc::Status status, std::shared_ptr& data) { + BOOST_CHECK(status == ipc::Status::OK); recvData = data; cv.notify_one(); }; @@ -324,7 +346,8 @@ BOOST_AUTO_TEST_CASE(AsyncServiceToClientEchoTest) std::shared_ptr sentData(new SendData(56)); std::shared_ptr recvData; - auto dataBack = [&cv, &recvData](std::shared_ptr& data) { + auto dataBack = [&cv, &recvData](ipc::Status status, std::shared_ptr& data) { + BOOST_CHECK(status == ipc::Status::OK); recvData = data; cv.notify_one(); }; @@ -343,15 +366,78 @@ BOOST_AUTO_TEST_CASE(AsyncServiceToClientEchoTest) BOOST_AUTO_TEST_CASE(SyncTimeoutTest) { Service s(socketPath); + s.addMethodHandler(1, longEchoCallback); + + s.start(); + Client c(socketPath); + c.start(); + + std::shared_ptr sentData(new SendData(78)); + + BOOST_CHECK_THROW((c.callSync(1, sentData, 10)), IPCException); +} + +BOOST_AUTO_TEST_CASE(SerializationErrorTest) +{ + Service s(socketPath); + s.addMethodHandler(1, echoCallback); + s.start(); + + Client c(socketPath); + c.start(); + + std::shared_ptr throwingData(new ThrowOnAcceptData()); + + BOOST_CHECK_THROW((c.callSync(1, throwingData)), IPCSerializationException); + +} + +BOOST_AUTO_TEST_CASE(ParseErrorTest) +{ + Service s(socketPath); s.addMethodHandler(1, echoCallback); + s.start(); + + Client c(socketPath); + c.start(); + + std::shared_ptr sentData(new SendData(78)); + BOOST_CHECK_THROW((c.callSync(1, sentData, 10000)), IPCParsingException); +} + +BOOST_AUTO_TEST_CASE(DisconnectedPeerErrorTest) +{ + Service s(socketPath); + + auto method = [](std::shared_ptr&) { + return std::shared_ptr(new SendData(1)); + }; + // Method will throw during serialization and disconnect automatically + s.addMethodHandler(1, method); s.start(); + Client c(socketPath); c.start(); + std::mutex mtx; + std::unique_lock lck(mtx); + std::condition_variable cv; + ipc::Status retStatus = ipc::Status::UNDEFINED; + + auto dataBack = [&cv, &retStatus](ipc::Status status, std::shared_ptr&) { + retStatus = status; + cv.notify_one(); + }; + std::shared_ptr sentData(new SendData(78)); + c.callAsync(1, sentData, dataBack); - BOOST_CHECK_THROW((c.callSync(1, sentData, 1)), IPCException); + // Wait for the response + BOOST_CHECK(cv.wait_for(lck, std::chrono::seconds(10), [&retStatus]() { + return retStatus != ipc::Status::UNDEFINED; + })); + BOOST_CHECK(retStatus == ipc::Status::PEER_DISCONNECTED); } -- 2.7.4 From 40e9e5c6518f1821e406eb3877ed2fc8dee6f3b5 Mon Sep 17 00:00:00 2001 From: Piotr Bartosiewicz Date: Tue, 18 Nov 2014 13:34:58 +0100 Subject: [PATCH 09/16] Lxc networking [Bug/Feature] Add lxc network config. Remove dead network code. [Cause] N/A [Solution] N/A [Verification] Build, install, run Change-Id: I2883858dbd571a01c93f6cc8c6b47cffe970a42a --- packaging/security-containers.spec | 1 + server/configs/lxc-templates/business.sh | 34 ++++- server/configs/lxc-templates/private.sh | 34 ++++- server/container.cpp | 11 -- server/container.hpp | 3 - server/network-admin.cpp | 152 --------------------- server/network-admin.hpp | 83 ----------- tests/unit_tests/server/configs/CMakeLists.txt | 13 -- .../ut-network-admin/containers/buggy.conf.in | 11 -- .../ut-network-admin/containers/missing.conf | 11 -- .../ut-network-admin/containers/test.conf.in | 11 -- tests/unit_tests/server/ut-network-admin.cpp | 85 ------------ 12 files changed, 67 insertions(+), 382 deletions(-) delete mode 100644 server/network-admin.cpp delete mode 100644 server/network-admin.hpp delete mode 100644 tests/unit_tests/server/configs/ut-network-admin/containers/buggy.conf.in delete mode 100644 tests/unit_tests/server/configs/ut-network-admin/containers/missing.conf delete mode 100644 tests/unit_tests/server/configs/ut-network-admin/containers/test.conf.in delete mode 100644 tests/unit_tests/server/ut-network-admin.cpp diff --git a/packaging/security-containers.spec b/packaging/security-containers.spec index 11e7c99..99c0d1d 100644 --- a/packaging/security-containers.spec +++ b/packaging/security-containers.spec @@ -28,6 +28,7 @@ BuildRequires: pkgconfig(glib-2.0) BuildRequires: pkgconfig(libsystemd-journal) BuildRequires: pkgconfig(sqlite3) Requires(post): libcap-tools +Requires: bridge-utils %description This package provides a daemon used to manage containers - start, stop and switch diff --git a/server/configs/lxc-templates/business.sh b/server/configs/lxc-templates/business.sh index 09d67ca..21f7d2e 100755 --- a/server/configs/lxc-templates/business.sh +++ b/server/configs/lxc-templates/business.sh @@ -19,6 +19,9 @@ do esac done +br_name="virbr-${name}" +sub_net="101" # TODO from param + # XXX assume rootfs if mounted from iso # Prepare container configuration file @@ -35,6 +38,35 @@ lxc.pts = 256 lxc.tty = 0 lxc.mount.auto = proc sys cgroup -lxc.mount.entry = /var/run/containers/business/run var/run none rw,bind 0 0 +lxc.mount.entry = /var/run/containers/${name}/run var/run none rw,bind 0 0 + +lxc.network.type = veth +lxc.network.link = ${br_name} +lxc.network.flags = up +lxc.network.name = eth0 +lxc.network.veth.pair = veth-${name} +lxc.network.ipv4.gateway = 10.0.${sub_net}.1 +lxc.network.ipv4 = 10.0.${sub_net}.2/24 + +lxc.hook.pre-start = ${path}/pre-start.sh + +#lxc.loglevel = TRACE +#lxc.logfile = /tmp/${name}.log +EOF + +# prepare pre start hook +cat <> ${path}/pre-start.sh +if [ -z "\$(/usr/sbin/brctl show | /bin/grep -P "${br_name}\t")" ] +then + /usr/sbin/brctl addbr ${br_name} + /usr/sbin/brctl setfd ${br_name} 0 + /sbin/ifconfig ${br_name} 10.0.${sub_net}.1 netmask 255.255.255.0 up +fi +if [ -z "\$(/usr/sbin/iptables -t nat -S | /bin/grep MASQUERADE)" ] +then + /bin/echo 1 > /proc/sys/net/ipv4/ip_forward + /usr/sbin/iptables -t nat -A POSTROUTING -s 10.0.0.0/16 ! -d 10.0.0.0/16 -j MASQUERADE +fi EOF +chmod 755 ${path}/pre-start.sh diff --git a/server/configs/lxc-templates/private.sh b/server/configs/lxc-templates/private.sh index 731ff72..542093a 100755 --- a/server/configs/lxc-templates/private.sh +++ b/server/configs/lxc-templates/private.sh @@ -19,6 +19,9 @@ do esac done +br_name="virbr-${name}" +sub_net="102" # TODO from param + # XXX assume rootfs if mounted from iso # Prepare container configuration file @@ -35,6 +38,35 @@ lxc.pts = 256 lxc.tty = 0 lxc.mount.auto = proc sys cgroup -lxc.mount.entry = /var/run/containers/private/run var/run none rw,bind 0 0 +lxc.mount.entry = /var/run/containers/${name}/run var/run none rw,bind 0 0 + +lxc.network.type = veth +lxc.network.link = ${br_name} +lxc.network.flags = up +lxc.network.name = eth0 +lxc.network.veth.pair = veth-${name} +lxc.network.ipv4.gateway = 10.0.${sub_net}.1 +lxc.network.ipv4 = 10.0.${sub_net}.2/24 + +lxc.hook.pre-start = ${path}/pre-start.sh + +#lxc.loglevel = TRACE +#lxc.logfile = /tmp/${name}.log +EOF + +# prepare pre start hook +cat <> ${path}/pre-start.sh +if [ -z "\$(/usr/sbin/brctl show | /bin/grep -P "${br_name}\t")" ] +then + /usr/sbin/brctl addbr ${br_name} + /usr/sbin/brctl setfd ${br_name} 0 + /sbin/ifconfig ${br_name} 10.0.${sub_net}.1 netmask 255.255.255.0 up +fi +if [ -z "\$(/usr/sbin/iptables -t nat -S | /bin/grep MASQUERADE)" ] +then + /bin/echo 1 > /proc/sys/net/ipv4/ip_forward + /usr/sbin/iptables -t nat -A POSTROUTING -s 10.0.0.0/16 ! -d 10.0.0.0/16 -j MASQUERADE +fi EOF +chmod 755 ${path}/pre-start.sh diff --git a/server/container.cpp b/server/container.cpp index 17d383e..3fe421f 100644 --- a/server/container.cpp +++ b/server/container.cpp @@ -66,18 +66,10 @@ Container::Container(const std::string& containersPath, mPermittedToRecv.push_back(boost::regex(r)); } - //const std::string baseConfigPath = utils::dirName(containerConfigPath); - //mConfig.config = fs::absolute(mConfig.config, baseConfigPath).string(); - //mConfig.networkConfig = fs::absolute(mConfig.networkConfig, baseConfigPath).string(); - //mConfig.networkFilterConfig = fs::absolute(mConfig.networkFilterConfig, - // baseConfigPath).string(); if (!mConfig.runMountPoint.empty()) { mRunMountPoint = fs::absolute(mConfig.runMountPoint, baseRunMountPointPath).string(); } - //LOGT("Creating Network Admin " << mConfig.networkConfig); - mNetworkAdmin.reset(new NetworkAdmin(mConfig)); - //LOGT("Creating Container Admin " << mConfig.config); mAdmin.reset(new ContainerAdmin(containersPath, lxcTemplatePrefix, mConfig)); } @@ -127,7 +119,6 @@ void Container::start() if (mConfig.enableDbusIntegration) { mConnectionTransport.reset(new ContainerConnectionTransport(mRunMountPoint)); } - mNetworkAdmin->start(); mAdmin->start(); if (mConfig.enableDbusIntegration) { connect(); @@ -167,7 +158,6 @@ void Container::stop() Lock lock(mReconnectMutex); disconnect(); mAdmin->stop(); - mNetworkAdmin->stop(); mConnectionTransport.reset(); } @@ -239,7 +229,6 @@ void Container::goBackground() void Container::setDetachOnExit() { Lock lock(mReconnectMutex); - mNetworkAdmin->setDetachOnExit(); mAdmin->setDetachOnExit(); if (mConnectionTransport) { mConnectionTransport->setDetachOnExit(); diff --git a/server/container.hpp b/server/container.hpp index 0fcd837..f4140b0 100644 --- a/server/container.hpp +++ b/server/container.hpp @@ -30,8 +30,6 @@ #include "container-admin.hpp" #include "container-connection.hpp" #include "container-connection-transport.hpp" -#include "network-admin.hpp" - #include #include @@ -218,7 +216,6 @@ private: std::vector mPermittedToSend; std::vector mPermittedToRecv; std::unique_ptr mConnectionTransport; - std::unique_ptr mNetworkAdmin; std::unique_ptr mAdmin; std::unique_ptr mConnection; std::thread mReconnectThread; diff --git a/server/network-admin.cpp b/server/network-admin.cpp deleted file mode 100644 index a649d48..0000000 --- a/server/network-admin.cpp +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved - * - * Contact: Piotr Bartosiewicz - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -/** - * @file - * @author Piotr Bartosiewicz (p.bartosiewi@partner.samsung.com) - * @brief Implementation of class for administrating single network - */ - -#include "config.hpp" - -#include "network-admin.hpp" -#include "exception.hpp" - -//#include "libvirt/helpers.hpp" -#include "logger/logger.hpp" -#include "utils/fs.hpp" - -#include - - -namespace security_containers { - -namespace { - -//std::string getNetworkName(virNetworkPtr net) -//{ -// assert(net); -// -// const char* name = virNetworkGetName(net); -// if (name == nullptr) { -// LOGE("Failed to get the network's id:\n" -// << libvirt::libvirtFormatError()); -// throw ContainerOperationException(); -// } -// -// return name; -//} - -} // namespace - - -NetworkAdmin::NetworkAdmin(const ContainerConfig& config) - : mConfig(config), - //mNWFilter(utils::readFileContent(mConfig.networkFilterConfig)), - //mNetwork(utils::readFileContent(mConfig.networkConfig)), - mId("TODO"),//mId(getNetworkName(mNetwork.get())), - mDetachOnExit(false) -{ - LOGD(mId << ": Instantiating NetworkAdmin object"); -} - - -NetworkAdmin::~NetworkAdmin() -{ - LOGD(mId << ": Destroying NetworkAdmin object..."); - // Try to stop - if (!mDetachOnExit) { - try { - stop(); - } catch (ServerException&) { - LOGE(mId << ": Failed to stop the network"); - } - } - - LOGD(mId << ": NetworkAdmin object destroyed"); -} - - -const std::string& NetworkAdmin::getId() const -{ - return mId; -} - - -void NetworkAdmin::start() -{ -// assert(mNetwork); -// -// LOGD(mId << ": Starting..."); -// if (isActive()) { -// LOGD(mId << ": Already running - nothing to do..."); -// return; -// } -// -// if (virNetworkCreate(mNetwork.get()) < 0) { -// LOGE(mId << ": Failed to start the network\n" -// << libvirt::libvirtFormatError()); -// throw ContainerOperationException(); -// } -// -// LOGD(mId << ": Started"); -} - - -void NetworkAdmin::stop() -{ -// assert(mNetwork); -// -// LOGD(mId << ": Stopping procedure started..."); -// if (!isActive()) { -// LOGD(mId << ": Already crashed/down/off - nothing to do"); -// return; -// } -// -// if (virNetworkDestroy(mNetwork.get()) < 0) { -// LOGE(mId << ": Failed to destroy the network\n" -// << libvirt::libvirtFormatError()); -// throw ContainerOperationException(); -// } -// -// LOGD(mId << ": Stopping procedure ended"); -} - - -bool NetworkAdmin::isActive() -{ -// assert(mNetwork); -// int ret = virNetworkIsActive(mNetwork.get()); -// if (ret < 0) { -// LOGE(mId << ": Failed to get network state\n" -// << libvirt::libvirtFormatError()); -// throw ContainerOperationException(); -// } -// return ret > 0; - return false; -} - - -void NetworkAdmin::setDetachOnExit() -{ -// mDetachOnExit = true; -// mNWFilter.setDetachOnExit(); -} - - -} // namespace security_containers diff --git a/server/network-admin.hpp b/server/network-admin.hpp deleted file mode 100644 index 66a6f75..0000000 --- a/server/network-admin.hpp +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved - * - * Contact: Piotr Bartosiewicz - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -/** - * @file - * @author Piotr Bartosiewicz (p.bartosiewi@partner.samsung.com) - * @brief Declaration of the class for administrating single network - */ - - -#ifndef SERVER_NETWORK_ADMIN_HPP -#define SERVER_NETWORK_ADMIN_HPP - -#include "container-config.hpp" - -//#include "libvirt/network-filter.hpp" -//#include "libvirt/network.hpp" - - -namespace security_containers { - - -class NetworkAdmin { - -public: - - NetworkAdmin(const ContainerConfig& config); - virtual ~NetworkAdmin(); - - /** - * Get the network id - */ - const std::string& getId() const; - - /** - * Start network. - */ - void start(); - - /** - * Stop network. - */ - void stop(); - - /** - * @return Is the network active? - */ - bool isActive(); - - /** - * Set whether container should be detached on exit. - */ - void setDetachOnExit(); - - -private: - const ContainerConfig& mConfig; - //libvirt::LibvirtNWFilter mNWFilter; - //libvirt::LibvirtNetwork mNetwork; - const std::string mId; - bool mDetachOnExit; -}; - - -} // namespace security_containers - - -#endif // SERVER_NETWORK_ADMIN_HPP diff --git a/tests/unit_tests/server/configs/CMakeLists.txt b/tests/unit_tests/server/configs/CMakeLists.txt index b3f8a70..17e10a2 100644 --- a/tests/unit_tests/server/configs/CMakeLists.txt +++ b/tests/unit_tests/server/configs/CMakeLists.txt @@ -31,8 +31,6 @@ FILE(GLOB container_container_CONF ut-container/containers/*.conf) FILE(GLOB admin_container_CONF ut-container-admin/containers/*.conf) -FILE(GLOB network_container_CONF ut-network-admin/containers/*.conf) - FILE(GLOB connection_CONF ut-container-connection/*.conf) @@ -43,12 +41,6 @@ CONFIGURE_FILE(ut-server/buggy-daemon.conf.in ${CMAKE_BINARY_DIR}/ut-server/buggy-daemon.conf @ONLY) FILE(GLOB server_manager_CONF_GEN ${CMAKE_BINARY_DIR}/ut-server/*.conf) -CONFIGURE_FILE(ut-network-admin/containers/test.conf.in - ${CMAKE_BINARY_DIR}/ut-network-admin/containers/test.conf @ONLY) -CONFIGURE_FILE(ut-network-admin/containers/buggy.conf.in - ${CMAKE_BINARY_DIR}/ut-network-admin/containers/buggy.conf @ONLY) -FILE(GLOB network_container_CONF_GEN ${CMAKE_BINARY_DIR}/ut-network-admin/containers/*.conf) - CONFIGURE_FILE(ut-container/containers/test-dbus.conf.in ${CMAKE_BINARY_DIR}/ut-container/containers/test-dbus.conf @ONLY) FILE(GLOB container_container_CONF_GEN ${CMAKE_BINARY_DIR}/ut-container/containers/*.conf) @@ -103,11 +95,6 @@ INSTALL(FILES ${container_container_CONF_GEN} INSTALL(FILES ${admin_container_CONF} DESTINATION ${SC_TEST_CONFIG_INSTALL_DIR}/server/ut-container-admin/containers) -INSTALL(FILES ${network_container_CONF} - DESTINATION ${SC_TEST_CONFIG_INSTALL_DIR}/server/ut-network-admin/containers) -INSTALL(FILES ${network_container_CONF_GEN} - DESTINATION ${SC_TEST_CONFIG_INSTALL_DIR}/server/ut-network-admin/containers) - INSTALL(FILES ${connection_CONF} DESTINATION ${SC_TEST_CONFIG_INSTALL_DIR}/server/ut-container-connection) diff --git a/tests/unit_tests/server/configs/ut-network-admin/containers/buggy.conf.in b/tests/unit_tests/server/configs/ut-network-admin/containers/buggy.conf.in deleted file mode 100644 index f4be18d..0000000 --- a/tests/unit_tests/server/configs/ut-network-admin/containers/buggy.conf.in +++ /dev/null @@ -1,11 +0,0 @@ -{ - "privilege" : 10, - "vt" : -1, - "switchToDefaultAfterTimeout" : true, - "enableDbusIntegration" : false, - "cpuQuotaForeground" : -1, - "cpuQuotaBackground" : 1000, - "runMountPoint" : "", - "permittedToSend" : [], - "permittedToRecv" : [] -} diff --git a/tests/unit_tests/server/configs/ut-network-admin/containers/missing.conf b/tests/unit_tests/server/configs/ut-network-admin/containers/missing.conf deleted file mode 100644 index f4be18d..0000000 --- a/tests/unit_tests/server/configs/ut-network-admin/containers/missing.conf +++ /dev/null @@ -1,11 +0,0 @@ -{ - "privilege" : 10, - "vt" : -1, - "switchToDefaultAfterTimeout" : true, - "enableDbusIntegration" : false, - "cpuQuotaForeground" : -1, - "cpuQuotaBackground" : 1000, - "runMountPoint" : "", - "permittedToSend" : [], - "permittedToRecv" : [] -} diff --git a/tests/unit_tests/server/configs/ut-network-admin/containers/test.conf.in b/tests/unit_tests/server/configs/ut-network-admin/containers/test.conf.in deleted file mode 100644 index f4be18d..0000000 --- a/tests/unit_tests/server/configs/ut-network-admin/containers/test.conf.in +++ /dev/null @@ -1,11 +0,0 @@ -{ - "privilege" : 10, - "vt" : -1, - "switchToDefaultAfterTimeout" : true, - "enableDbusIntegration" : false, - "cpuQuotaForeground" : -1, - "cpuQuotaBackground" : 1000, - "runMountPoint" : "", - "permittedToSend" : [], - "permittedToRecv" : [] -} diff --git a/tests/unit_tests/server/ut-network-admin.cpp b/tests/unit_tests/server/ut-network-admin.cpp deleted file mode 100644 index f120ac6..0000000 --- a/tests/unit_tests/server/ut-network-admin.cpp +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved - * - * Contact: Piotr Bartosiewicz - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - - -/** - * @file - * @author Piotr Bartosiewicz (p.bartosiewi@partner.samsung.com) - * @brief Unit tests of the NetworkAdmin class - */ - -//#include "config.hpp" -//#include "ut.hpp" -// -//#include "network-admin.hpp" -// -//#include "utils/exception.hpp" -////#include "libvirt/exception.hpp" -//#include "config/manager.hpp" -// -// -//using namespace security_containers; -// -//namespace { -// -//const std::string TEST_CONFIG_PATH = SC_TEST_CONFIG_INSTALL_DIR "/server/ut-network-admin/containers/test.conf"; -//const std::string BUGGY_CONFIG_PATH = SC_TEST_CONFIG_INSTALL_DIR "/server/ut-network-admin/containers/buggy.conf"; -//const std::string MISSING_CONFIG_PATH = SC_TEST_CONFIG_INSTALL_DIR "/server/ut-network-admin/containers/missing.conf"; -// -//} // namespace -// -// -//BOOST_AUTO_TEST_SUITE(NetworkAdminSuite) -// -//BOOST_AUTO_TEST_CASE(ConstructorDestructorTest) -//{ -// ContainerConfig config; -// config::loadFromFile(TEST_CONFIG_PATH, config); -// std::unique_ptr admin; -// BOOST_REQUIRE_NO_THROW(admin.reset(new NetworkAdmin(config))); -// BOOST_REQUIRE_NO_THROW(admin.reset()); -//} -// -////BOOST_AUTO_TEST_CASE(BuggyConfigTest) -////{ -//// ContainerConfig config; -//// config::loadFromFile(BUGGY_CONFIG_PATH, config); -//// BOOST_REQUIRE_THROW(NetworkAdmin na(config), LibvirtOperationException); -////} -// -//BOOST_AUTO_TEST_CASE(MissingConfigTest) -//{ -// ContainerConfig config; -// config::loadFromFile(MISSING_CONFIG_PATH, config); -// BOOST_REQUIRE_THROW(NetworkAdmin na(config), UtilsException); -//} -// -//BOOST_AUTO_TEST_CASE(StartStopTest) -//{ -// ContainerConfig config; -// config::loadFromFile(TEST_CONFIG_PATH, config); -// NetworkAdmin net(config); -// -// BOOST_CHECK(!net.isActive()); -// BOOST_CHECK_NO_THROW(net.start()); -// BOOST_CHECK(net.isActive()); -// BOOST_CHECK_NO_THROW(net.stop()); -// BOOST_CHECK(!net.isActive()); -//} -// -//BOOST_AUTO_TEST_SUITE_END() -- 2.7.4 From 1d05535e3b32d1b1451d90cd1090417e66e1d17c Mon Sep 17 00:00:00 2001 From: Mateusz Malicki Date: Fri, 14 Nov 2014 16:38:16 +0100 Subject: [PATCH 10/16] Implement lookup_domain_by_id function in server, client and cli [Bug/Feature] Implement lookup_domain_by_id function in server, client and cli [Cause] N/A [Solution] N/A [Verification] Build, run appropriate function (through cli) Change-Id: I2908e760613532caadcc9c58a1d522d4ac7767c4 --- cli/command-line-interface.cpp | 45 ++++++++++++++++++++ cli/command-line-interface.hpp | 8 ++++ cli/main.cpp | 8 ++++ client/security-containers-client-impl.cpp | 67 ++++++++++++++++++++++++++++-- client/security-containers-client.h | 2 +- server/common-dbus-definitions.hpp | 1 + server/container.cpp | 5 +++ server/container.hpp | 5 +++ server/containers-manager.cpp | 34 +++++++++++++++ server/containers-manager.hpp | 1 + server/host-connection.cpp | 15 +++++++ server/host-connection.hpp | 9 ++++ server/host-dbus-definitions.hpp | 5 +++ 13 files changed, 201 insertions(+), 4 deletions(-) diff --git a/cli/command-line-interface.cpp b/cli/command-line-interface.cpp index f3768d0..d027e63 100644 --- a/cli/command-line-interface.cpp +++ b/cli/command-line-interface.cpp @@ -30,6 +30,7 @@ #include #include #include +#include using namespace std; @@ -80,6 +81,37 @@ finish: } } +ostream& operator<<(ostream& out, const VsmDomainState& state) +{ + const char* name; + switch (state) { + case STOPPED: name = "STOPPED"; break; + case STARTING: name = "STARTING"; break; + case RUNNING: name = "RUNNING"; break; + case STOPPING: name = "STOPPING"; break; + case ABORTING: name = "ABORTING"; break; + case FREEZING: name = "FREEZING"; break; + case FROZEN: name = "FROZEN"; break; + case THAWED: name = "THAWED"; break; + case LOCKED: name = "LOCKED"; break; + case MAX_STATE: name = "MAX_STATE"; break; + case ACTIVATING: name = "ACTIVATING"; break; + default: name = "MAX_STATE (ERROR)"; + } + + out << name; + return out; +} + +ostream& operator<<(ostream& out, const VsmDomain& domain) +{ + out << "Name: " << domain->id + << "\nTerminal: " << domain->terminal + << "\nState: " << domain->state + << "\nRoot: " << domain->rootfs_path; + return out; +} + } // namespace void CommandLineInterface::printUsage(std::ostream& out) const @@ -122,5 +154,18 @@ void create_domain(int pos, int argc, const char** argv) one_shot(bind(vsm_create_domain, _1, argv[pos + 1], nullptr)); } +void lookup_domain_by_id(int pos, int argc, const char** argv) +{ + using namespace std::placeholders; + if (argc <= pos + 1) { + throw runtime_error("Not enough parameters"); + } + + VsmDomain domain; + one_shot(bind(vsm_lookup_domain_by_id, _1, argv[pos + 1], &domain)); + cout << domain << endl; + vsm_domain_free(domain); +} + } // namespace cli } // namespace security_containers diff --git a/cli/command-line-interface.hpp b/cli/command-line-interface.hpp index 27415c1..c254d84 100644 --- a/cli/command-line-interface.hpp +++ b/cli/command-line-interface.hpp @@ -110,6 +110,14 @@ void set_active_container(int pos, int argc, const char** argv); */ void create_domain(int pos, int argc, const char** argv); +/** + * Parses command line arguments and call vsm_lookup_domain_by_id + * + * @see vsm_lookup_domain_by_id + */ +void lookup_domain_by_id(int pos, int argc, const char** argv); + + } // namespace cli } // namespace security_containers diff --git a/cli/main.cpp b/cli/main.cpp index 135385d..d701475 100644 --- a/cli/main.cpp +++ b/cli/main.cpp @@ -48,6 +48,14 @@ std::map commands = { "Create and add container", {{"container_id", "id container name"}} } + }, + { + "lookup_domain_by_id", { + lookup_domain_by_id, + "lookup_domain_by_id container_id", + "Prints informations about domain", + {{"container_id", "id container name"}} + } } }; diff --git a/client/security-containers-client-impl.cpp b/client/security-containers-client-impl.cpp index 205a8b2..2fcc776 100644 --- a/client/security-containers-client-impl.cpp +++ b/client/security-containers-client-impl.cpp @@ -88,6 +88,50 @@ void toBasic(GVariant* in, char** str) *str = buf; } +VsmDomainState getDomainState(const char* state) +{ + if (strcmp(state, "STOPPED") == 0) { + return STOPPED; + } else if (strcmp(state, "STARTING") == 0) { + return STARTING; + } else if (strcmp(state, "RUNNING") == 0) { + return RUNNING; + } else if (strcmp(state, "STOPPING") == 0) { + return STOPPING; + } else if (strcmp(state, "ABORTING") == 0) { + return ABORTING; + } else if (strcmp(state, "FREEZING") == 0) { + return FREEZING; + } else if (strcmp(state, "FROZEN") == 0) { + return FROZEN; + } else if (strcmp(state, "THAWED") == 0) { + return THAWED; + } else if (strcmp(state, "LOCKED") == 0) { + return LOCKED; + } else if (strcmp(state, "MAX_STATE") == 0) { + return MAX_STATE; + } else if (strcmp(state, "ACTIVATING") == 0) { + return ACTIVATING; + } + assert(!"UNKNOWN STATE"); + return (VsmDomainState)-1; +} + +void toBasic(GVariant* in, VsmDomain* domain) +{ + const char* id; + const char* path; + const char* state; + int terminal; + VsmDomain vsmDomain = (VsmDomain)malloc(sizeof(*vsmDomain)); + g_variant_get(in, "(siss)", &id, &terminal, &state, &path); + vsmDomain->id = strdup(id); + vsmDomain->terminal = terminal; + vsmDomain->state = getDomainState(state); + vsmDomain->rootfs_path = strdup(path); + *domain = vsmDomain; +} + template void toArray(GVariant* in, T** scArray) { @@ -360,10 +404,27 @@ VsmStatus Client::vsm_lookup_domain_by_pid(int pid, VsmString* id) noexcept return vsm_get_status(); } -VsmStatus Client::vsm_lookup_domain_by_id(const char*, VsmDomain*) noexcept +VsmStatus Client::vsm_lookup_domain_by_id(const char* id, VsmDomain* domain) noexcept { - mStatus = Status(VSMCLIENT_OTHER_ERROR, "Not implemented"); - return vsm_get_status(); + assert(id); + assert(domain); + + GVariant* out; + GVariant* args_in = g_variant_new("(s)", id); + VsmStatus ret = callMethod(HOST_INTERFACE, + api::host::METHOD_GET_CONTAINER_INFO, + args_in, + "((siss))", + &out); + if (ret != VSMCLIENT_SUCCESS) { + return ret; + } + GVariant* unpacked; + g_variant_get(out, "(*)", &unpacked); + toBasic(unpacked, domain); + g_variant_unref(unpacked); + g_variant_unref(out); + return ret; } VsmStatus Client::vsm_lookup_domain_by_terminal_id(int, VsmString*) noexcept diff --git a/client/security-containers-client.h b/client/security-containers-client.h index 6419870..ee16e01 100644 --- a/client/security-containers-client.h +++ b/client/security-containers-client.h @@ -338,7 +338,7 @@ VsmStatus vsm_lookup_domain_by_pid(VsmClient client, int pid, VsmString* id); * @param[in] id domain name * @param[out] domain domain informations * @return status of this function call - * @remark Use @p vsm_doamin_free() to free memory occupied by @p domain + * @remark Use @p vsm_domain_free() to free memory occupied by @p domain */ VsmStatus vsm_lookup_domain_by_id(VsmClient client, const char* id, VsmDomain* domain); diff --git a/server/common-dbus-definitions.hpp b/server/common-dbus-definitions.hpp index 8821b34..14bb037 100644 --- a/server/common-dbus-definitions.hpp +++ b/server/common-dbus-definitions.hpp @@ -33,6 +33,7 @@ namespace api { const std::string ERROR_FORBIDDEN = "org.tizen.containers.Error.Forbidden"; const std::string ERROR_FORWARDED = "org.tizen.containers.Error.Forwarded"; const std::string ERROR_UNKNOWN_ID = "org.tizen.containers.Error.UnknownId"; +const std::string ERROR_INTERNAL = "org.tizen.containers.Error.Internal"; const std::string METHOD_PROXY_CALL = "ProxyCall"; diff --git a/server/container.cpp b/server/container.cpp index 59328db..f4ac2cc 100644 --- a/server/container.cpp +++ b/server/container.cpp @@ -211,6 +211,11 @@ std::string Container::getDbusAddress() return mDbusAddress; } +int Container::getVT() const +{ + return mConfig.vt; +} + bool Container::activateVT() { Lock lock(mReconnectMutex); diff --git a/server/container.hpp b/server/container.hpp index 6800f7a..46fafec 100644 --- a/server/container.hpp +++ b/server/container.hpp @@ -204,6 +204,11 @@ public: */ std::string getDbusAddress(); + /** + * Get id of VT + */ + int getVT() const; + private: ContainerConfig mConfig; std::vector mPermittedToSend; diff --git a/server/containers-manager.cpp b/server/containers-manager.cpp index d0d0a36..466a4e6 100644 --- a/server/containers-manager.cpp +++ b/server/containers-manager.cpp @@ -102,6 +102,9 @@ ContainersManager::ContainersManager(const std::string& managerConfigPath): mDet mHostConnection.setGetActiveContainerIdCallback(bind(&ContainersManager::handleGetActiveContainerIdCall, this, _1)); + mHostConnection.setGetContainerInfoCallback(bind(&ContainersManager::handleGetContainerInfoCall, + this, _1, _2)); + mHostConnection.setSetActiveContainerCallback(bind(&ContainersManager::handleSetActiveContainerCall, this, _1, _2)); @@ -497,6 +500,37 @@ void ContainersManager::handleGetActiveContainerIdCall(dbus::MethodResultBuilder } } +void ContainersManager::handleGetContainerInfoCall(const std::string& id, + dbus::MethodResultBuilder::Pointer result) +{ + LOGI("GetContainerInfo call"); + if (mContainers.count(id) == 0) { + LOGE("No container with id=" << id); + result->setError(api::ERROR_UNKNOWN_ID, "No such container id"); + return; + } + const auto& container = mContainers[id]; + const char* state; + //TODO: Use the lookup map. + if (container->isRunning()) { + state = "RUNNING"; + } else if (container->isStopped()) { + state = "STOPPED"; + } else if (container->isPaused()) { + state = "FROZEN"; + } else { + LOGE("Unrecognized state of container id=" << id); + result->setError(api::ERROR_INTERNAL, "Unrecognized state of container"); + return; + } + const std::string rootPath = boost::filesystem::absolute(id, mConfig.containersPath).string(); + result->set(g_variant_new("((siss))", + id.c_str(), + container->getVT(), + state, + rootPath.c_str())); +} + void ContainersManager::handleSetActiveContainerCall(const std::string& id, dbus::MethodResultBuilder::Pointer result) { diff --git a/server/containers-manager.hpp b/server/containers-manager.hpp index 3cbf833..d2efce1 100644 --- a/server/containers-manager.hpp +++ b/server/containers-manager.hpp @@ -124,6 +124,7 @@ private: void handleDbusStateChanged(const std::string& containerId, const std::string& dbusAddress); void handleGetContainerIdsCall(dbus::MethodResultBuilder::Pointer result); void handleGetActiveContainerIdCall(dbus::MethodResultBuilder::Pointer result); + void handleGetContainerInfoCall(const std::string& id, dbus::MethodResultBuilder::Pointer result); void handleSetActiveContainerCall(const std::string& id, dbus::MethodResultBuilder::Pointer result); void handleAddContainerCall(const std::string& id, diff --git a/server/host-connection.cpp b/server/host-connection.cpp index d25bee7..1b38a9c 100644 --- a/server/host-connection.cpp +++ b/server/host-connection.cpp @@ -125,6 +125,11 @@ void HostConnection::setGetActiveContainerIdCallback(const GetActiveContainerIdC mGetActiveContainerIdCallback = callback; } +void HostConnection::setGetContainerInfoCallback(const GetContainerInfoCallback& callback) +{ + mGetContainerInfoCallback = callback; +} + void HostConnection::setSetActiveContainerCallback(const SetActiveContainerCallback& callback) { mSetActiveContainerCallback = callback; @@ -205,6 +210,16 @@ void HostConnection::onMessageCall(const std::string& objectPath, return; } + if (methodName == api::host::METHOD_GET_CONTAINER_INFO){ + const gchar* id = NULL; + g_variant_get(parameters, "(&s)", &id); + + if (mGetContainerInfoCallback) { + mGetContainerInfoCallback(id, result); + } + return; + } + if (methodName == api::host::METHOD_ADD_CONTAINER) { const gchar* id = NULL; g_variant_get(parameters, "(&s)", &id); diff --git a/server/host-connection.hpp b/server/host-connection.hpp index c5d1bcc..1dacd11 100644 --- a/server/host-connection.hpp +++ b/server/host-connection.hpp @@ -59,6 +59,9 @@ public: )> GetActiveContainerIdCallback; typedef std::function GetContainerInfoCallback; + typedef std::function SetActiveContainerCallback; typedef std::function" " " " " + " " + " " + " " + " " " " " " " " -- 2.7.4 From 20e2b2163f5de0b032fa8bdecfce0a46bba34825 Mon Sep 17 00:00:00 2001 From: Piotr Bartosiewicz Date: Wed, 19 Nov 2014 13:37:16 +0100 Subject: [PATCH 11/16] Fix compilation problems [Bug/Feature] Code does not build using older gcc [Cause] N/A [Solution] N/A [Verification] Build Change-Id: Ifd7dd7bce080ac16983a23d30585ace08a522f05 --- common/lxc/domain.cpp | 2 +- tests/unit_tests/client/ut-client.cpp | 15 ++++++++++----- tests/unit_tests/lxc/ut-domain.cpp | 3 ++- tests/unit_tests/server/ut-container-admin.cpp | 6 +++++- tests/unit_tests/server/ut-container.cpp | 6 +++++- tests/unit_tests/server/ut-containers-manager.cpp | 15 +++++++++++---- tests/unit_tests/server/ut-server.cpp | 6 +++++- 7 files changed, 39 insertions(+), 14 deletions(-) diff --git a/common/lxc/domain.cpp b/common/lxc/domain.cpp index 57228c0..bc2e74b 100644 --- a/common/lxc/domain.cpp +++ b/common/lxc/domain.cpp @@ -195,7 +195,7 @@ bool LxcDomain::unfreeze() bool LxcDomain::setRunLevel(int runLevel) { - auto callback = [](void* param) { + auto callback = [](void* param) -> int { utils::RunLevel runLevel = *reinterpret_cast(param); return utils::setRunLevel(runLevel) ? 0 : 1; }; diff --git a/tests/unit_tests/client/ut-client.cpp b/tests/unit_tests/client/ut-client.cpp index 9847a92..6e0aab0 100644 --- a/tests/unit_tests/client/ut-client.cpp +++ b/tests/unit_tests/client/ut-client.cpp @@ -56,14 +56,19 @@ struct Loop { struct Fixture { Loop loop; - utils::ScopedDir mContainersPathGuard = CONTAINERS_PATH; - utils::ScopedDir mRun1Guard = utils::ScopedDir("/tmp/ut-run1"); - utils::ScopedDir mRun2Guard = utils::ScopedDir("/tmp/ut-run2"); - utils::ScopedDir mRun3Guard = utils::ScopedDir("/tmp/ut-run3"); + utils::ScopedDir mContainersPathGuard; + utils::ScopedDir mRun1Guard; + utils::ScopedDir mRun2Guard; + utils::ScopedDir mRun3Guard; ContainersManager cm; - Fixture(): cm(TEST_DBUS_CONFIG_PATH) + Fixture() + : mContainersPathGuard(CONTAINERS_PATH) + , mRun1Guard("/tmp/ut-run1") + , mRun2Guard("/tmp/ut-run2") + , mRun3Guard("/tmp/ut-run3") + , cm(TEST_DBUS_CONFIG_PATH) { cm.startAll(); } diff --git a/tests/unit_tests/lxc/ut-domain.cpp b/tests/unit_tests/lxc/ut-domain.cpp index 206d2c7..aa6c3a9 100644 --- a/tests/unit_tests/lxc/ut-domain.cpp +++ b/tests/unit_tests/lxc/ut-domain.cpp @@ -44,9 +44,10 @@ const std::string DOMAIN_NAME = "ut-domain"; const std::string TEMPLATE = SC_TEST_LXC_TEMPLATES_INSTALL_DIR "/minimal.sh"; struct Fixture { - utils::ScopedDir mLxcDirGuard = LXC_PATH; + utils::ScopedDir mLxcDirGuard; Fixture() + : mLxcDirGuard(LXC_PATH) { cleanup(); } diff --git a/tests/unit_tests/server/ut-container-admin.cpp b/tests/unit_tests/server/ut-container-admin.cpp index 6d2ba6c..ae00abf 100644 --- a/tests/unit_tests/server/ut-container-admin.cpp +++ b/tests/unit_tests/server/ut-container-admin.cpp @@ -46,10 +46,14 @@ const std::string LXC_TEMPLATES_PATH = SC_TEST_LXC_TEMPLATES_INSTALL_DIR; struct Fixture { utils::ScopedGlibLoop mLoop; - utils::ScopedDir mContainersPathGuard = CONTAINERS_PATH; + utils::ScopedDir mContainersPathGuard; ContainerConfig mConfig; + Fixture() + : mContainersPathGuard(CONTAINERS_PATH) + {} + std::unique_ptr create(const std::string& configPath) { config::loadFromFile(configPath, mConfig); diff --git a/tests/unit_tests/server/ut-container.cpp b/tests/unit_tests/server/ut-container.cpp index fe5aa64..c42b80d 100644 --- a/tests/unit_tests/server/ut-container.cpp +++ b/tests/unit_tests/server/ut-container.cpp @@ -54,9 +54,13 @@ const std::string LXC_TEMPLATES_PATH = SC_TEST_LXC_TEMPLATES_INSTALL_DIR; struct Fixture { utils::ScopedGlibLoop mLoop; - utils::ScopedDir mContainersPathGuard = CONTAINERS_PATH; + utils::ScopedDir mContainersPathGuard; utils::ScopedDir mRunGuard; + Fixture() + : mContainersPathGuard(CONTAINERS_PATH) + {} + std::unique_ptr create(const std::string& configPath) { return std::unique_ptr(new Container(CONTAINERS_PATH, diff --git a/tests/unit_tests/server/ut-containers-manager.cpp b/tests/unit_tests/server/ut-containers-manager.cpp index 317981d..399cd12 100644 --- a/tests/unit_tests/server/ut-containers-manager.cpp +++ b/tests/unit_tests/server/ut-containers-manager.cpp @@ -379,10 +379,17 @@ private: struct Fixture { security_containers::utils::ScopedGlibLoop mLoop; - utils::ScopedDir mContainersPathGuard = CONTAINERS_PATH; - utils::ScopedDir mRun1Guard = utils::ScopedDir("/tmp/ut-run1"); - utils::ScopedDir mRun2Guard = utils::ScopedDir("/tmp/ut-run2"); - utils::ScopedDir mRun3Guard = utils::ScopedDir("/tmp/ut-run3"); + utils::ScopedDir mContainersPathGuard; + utils::ScopedDir mRun1Guard; + utils::ScopedDir mRun2Guard; + utils::ScopedDir mRun3Guard; + + Fixture() + : mContainersPathGuard(CONTAINERS_PATH) + , mRun1Guard("/tmp/ut-run1") + , mRun2Guard("/tmp/ut-run2") + , mRun3Guard("/tmp/ut-run3") + {} }; } // namespace diff --git a/tests/unit_tests/server/ut-server.cpp b/tests/unit_tests/server/ut-server.cpp index 2dbfabc..1c3c46b 100644 --- a/tests/unit_tests/server/ut-server.cpp +++ b/tests/unit_tests/server/ut-server.cpp @@ -38,7 +38,11 @@ namespace { const std::string CONTAINERS_PATH = "/tmp/ut-containers"; // the same as in daemon.conf struct Fixture { - security_containers::utils::ScopedDir mContainersPathGuard = CONTAINERS_PATH; + security_containers::utils::ScopedDir mContainersPathGuard; + + Fixture() + : mContainersPathGuard(CONTAINERS_PATH) + {} }; } // namespace -- 2.7.4 From 47782bbb0c4b7bf91aa773718d0c9f681fba6eaf Mon Sep 17 00:00:00 2001 From: Jan Olszak Date: Wed, 19 Nov 2014 15:26:28 +0100 Subject: [PATCH 12/16] Fixing build brake for gcc 4.6 [Bug/Feature] N/A [Cause] N/A [Solution] N/A [Verification] Build with older gcc Change-Id: I0c889f7d79e10403a18b915c0c30e91400542de4 --- common/ipc/internals/processor.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/common/ipc/internals/processor.cpp b/common/ipc/internals/processor.cpp index 50327c0..5634880 100644 --- a/common/ipc/internals/processor.cpp +++ b/common/ipc/internals/processor.cpp @@ -109,7 +109,8 @@ Processor::PeerID Processor::addPeer(const std::shared_ptr& socketPtr) { Lock lock(mSocketsMutex); peerID = getNextPeerID(); - mNewSockets.emplace(peerID, std::move(socketPtr)); + SocketInfo socketInfo(peerID, std::move(socketPtr)); + mNewSockets.push(std::move(socketInfo)); } LOGI("New peer added. Id: " << peerID); mEventQueue.send(Event::NEW_PEER); @@ -392,7 +393,7 @@ bool Processor::handleEvent() return false; } - mSockets.emplace(socketInfo.peerID, std::move(socketInfo.socketPtr)); + mSockets[socketInfo.peerID] = std::move(socketInfo.socketPtr); } resetPolling(); if (mNewPeerCallback) { @@ -454,9 +455,11 @@ bool Processor::handleCall() if (mReturnCallbacks.count(messageID) != 0) { LOGE("There already was a return callback for messageID: " << messageID); } - mReturnCallbacks.emplace(messageID, ReturnCallbacks(call.peerID, - std::move(call.parse), - std::move(call.process))); + + // move insertion + mReturnCallbacks[messageID] = std::move(ReturnCallbacks(call.peerID, + std::move(call.parse), + std::move(call.process))); } try { -- 2.7.4 From e3d2a40e88a88c3319bf3d06378549b4e1ce1e5e Mon Sep 17 00:00:00 2001 From: Piotr Bartosiewicz Date: Thu, 20 Nov 2014 13:09:49 +0100 Subject: [PATCH 13/16] Fix lxc templates [Bug/Feature] Tests was failing on some images [Cause] N/A [Solution] N/A [Verification] Build, install, run tests Change-Id: I9b2029539a4c5567b6eaed178ef1df2b7dce3c44 --- tests/unit_tests/lxc/templates/minimal-dbus1.sh | 3 +++ tests/unit_tests/lxc/templates/minimal-dbus2.sh | 3 +++ tests/unit_tests/lxc/templates/minimal-dbus3.sh | 3 +++ tests/unit_tests/lxc/templates/minimal.sh | 8 +++++++- 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/tests/unit_tests/lxc/templates/minimal-dbus1.sh b/tests/unit_tests/lxc/templates/minimal-dbus1.sh index 35f816f..ff1f2de 100755 --- a/tests/unit_tests/lxc/templates/minimal-dbus1.sh +++ b/tests/unit_tests/lxc/templates/minimal-dbus1.sh @@ -23,6 +23,7 @@ done ROOTFS_DIRS="\ ${rootfs}/bin \ ${rootfs}/dev \ +${rootfs}/dev/pts \ ${rootfs}/etc \ ${rootfs}/home \ ${rootfs}/lib \ @@ -34,6 +35,7 @@ ${rootfs}/sbin \ ${rootfs}/sys \ ${rootfs}/tmp \ ${rootfs}/usr \ +${rootfs}/opt \ ${rootfs}/var \ ${rootfs}/var/run " @@ -62,6 +64,7 @@ lxc.mount.entry = /etc etc none ro,bind 0 0 lxc.mount.entry = /lib lib none ro,bind 0 0 lxc.mount.entry = /sbin sbin none ro,bind 0 0 lxc.mount.entry = /usr usr none ro,rbind 0 0 +lxc.mount.entry = /opt opt none ro,rbind 0 0 lxc.mount.entry = /tmp/ut-run1 var/run none rw,bind 0 0 EOF diff --git a/tests/unit_tests/lxc/templates/minimal-dbus2.sh b/tests/unit_tests/lxc/templates/minimal-dbus2.sh index f8f963e..86984b7 100755 --- a/tests/unit_tests/lxc/templates/minimal-dbus2.sh +++ b/tests/unit_tests/lxc/templates/minimal-dbus2.sh @@ -23,6 +23,7 @@ done ROOTFS_DIRS="\ ${rootfs}/bin \ ${rootfs}/dev \ +${rootfs}/dev/pts \ ${rootfs}/etc \ ${rootfs}/home \ ${rootfs}/lib \ @@ -34,6 +35,7 @@ ${rootfs}/sbin \ ${rootfs}/sys \ ${rootfs}/tmp \ ${rootfs}/usr \ +${rootfs}/opt \ ${rootfs}/var \ ${rootfs}/var/run " @@ -62,6 +64,7 @@ lxc.mount.entry = /etc etc none ro,bind 0 0 lxc.mount.entry = /lib lib none ro,bind 0 0 lxc.mount.entry = /sbin sbin none ro,bind 0 0 lxc.mount.entry = /usr usr none ro,rbind 0 0 +lxc.mount.entry = /opt opt none ro,rbind 0 0 lxc.mount.entry = /tmp/ut-run2 var/run none rw,bind 0 0 EOF diff --git a/tests/unit_tests/lxc/templates/minimal-dbus3.sh b/tests/unit_tests/lxc/templates/minimal-dbus3.sh index 68f4f11..034b4fd 100755 --- a/tests/unit_tests/lxc/templates/minimal-dbus3.sh +++ b/tests/unit_tests/lxc/templates/minimal-dbus3.sh @@ -23,6 +23,7 @@ done ROOTFS_DIRS="\ ${rootfs}/bin \ ${rootfs}/dev \ +${rootfs}/dev/pts \ ${rootfs}/etc \ ${rootfs}/home \ ${rootfs}/lib \ @@ -34,6 +35,7 @@ ${rootfs}/sbin \ ${rootfs}/sys \ ${rootfs}/tmp \ ${rootfs}/usr \ +${rootfs}/opt \ ${rootfs}/var \ ${rootfs}/var/run " @@ -62,6 +64,7 @@ lxc.mount.entry = /etc etc none ro,bind 0 0 lxc.mount.entry = /lib lib none ro,bind 0 0 lxc.mount.entry = /sbin sbin none ro,bind 0 0 lxc.mount.entry = /usr usr none ro,rbind 0 0 +lxc.mount.entry = /opt opt none ro,rbind 0 0 lxc.mount.entry = /tmp/ut-run3 var/run none rw,bind 0 0 EOF diff --git a/tests/unit_tests/lxc/templates/minimal.sh b/tests/unit_tests/lxc/templates/minimal.sh index 547661e..873277e 100755 --- a/tests/unit_tests/lxc/templates/minimal.sh +++ b/tests/unit_tests/lxc/templates/minimal.sh @@ -23,6 +23,7 @@ done ROOTFS_DIRS="\ ${rootfs}/bin \ ${rootfs}/dev \ +${rootfs}/dev/pts \ ${rootfs}/etc \ ${rootfs}/home \ ${rootfs}/lib \ @@ -33,7 +34,8 @@ ${rootfs}/run \ ${rootfs}/sbin \ ${rootfs}/sys \ ${rootfs}/tmp \ -${rootfs}/usr +${rootfs}/usr \ +${rootfs}/opt " /bin/mkdir ${ROOTFS_DIRS} @@ -52,6 +54,9 @@ lxc.haltsignal = SIGTERM lxc.pts = 256 lxc.tty = 0 +#lxc.loglevel = TRACE +#lxc.logfile = /tmp/${name}.log + lxc.cgroup.devices.deny = a lxc.mount.auto = proc sys cgroup @@ -60,6 +65,7 @@ lxc.mount.entry = /etc etc none ro,bind 0 0 lxc.mount.entry = /lib lib none ro,bind 0 0 lxc.mount.entry = /sbin sbin none ro,bind 0 0 lxc.mount.entry = /usr usr none ro,rbind 0 0 +lxc.mount.entry = /opt opt none ro,rbind 0 0 EOF if [ "$(uname -m)" = "x86_64" ]; then -- 2.7.4 From 7ea2c7bdeffd0ebc8514840af180574b835b6784 Mon Sep 17 00:00:00 2001 From: Jan Olszak Date: Mon, 17 Nov 2014 12:58:32 +0100 Subject: [PATCH 14/16] IPC: NONBLOCK sockets [Bug/Feature] All writes and reads have timeout Timeout in callSync removes the peer [Cause] N/A [Solution] N/A [Verification] Build, install, run tests Change-Id: I86213b04e435a48bc56ae6f995a0c364b712a4d0 --- common/ipc/internals/event-queue.hpp | 12 ++++ common/ipc/internals/eventfd.cpp | 2 +- common/ipc/internals/processor.cpp | 120 +++++++++++++++++++++++++++------- common/ipc/internals/processor.hpp | 92 +++++++++++++++++++------- common/ipc/internals/socket.cpp | 12 +++- common/ipc/internals/utils.cpp | 123 ++++++++++++++++++++++++++--------- common/ipc/internals/utils.hpp | 6 +- common/ipc/types.cpp | 4 ++ common/ipc/types.hpp | 2 + tests/unit_tests/ipc/ut-ipc.cpp | 60 +++++++++++++++++ 10 files changed, 349 insertions(+), 84 deletions(-) diff --git a/common/ipc/internals/event-queue.hpp b/common/ipc/internals/event-queue.hpp index 82cb2ff..b50f0c4 100644 --- a/common/ipc/internals/event-queue.hpp +++ b/common/ipc/internals/event-queue.hpp @@ -71,6 +71,11 @@ public: */ MessageType receive(); + /** + * @return is the queue empty + */ + bool isEmpty(); + private: typedef std::lock_guard Lock; @@ -106,6 +111,13 @@ MessageType EventQueue::receive() return mess; } +template +bool EventQueue::isEmpty() +{ + Lock lock(mCommunicationMutex); + return mMessages.empty(); +} + } // namespace ipc } // namespace security_containers diff --git a/common/ipc/internals/eventfd.cpp b/common/ipc/internals/eventfd.cpp index c8a17b6..37cf7dd 100644 --- a/common/ipc/internals/eventfd.cpp +++ b/common/ipc/internals/eventfd.cpp @@ -39,7 +39,7 @@ namespace ipc { EventFD::EventFD() { - mFD = ::eventfd(0, EFD_SEMAPHORE); + mFD = ::eventfd(0, EFD_SEMAPHORE | EFD_NONBLOCK); if (mFD == -1) { LOGE("Error in eventfd: " << std::string(strerror(errno))); throw IPCException("Error in eventfd: " + std::string(strerror(errno))); diff --git a/common/ipc/internals/processor.cpp b/common/ipc/internals/processor.cpp index 5634880..9677991 100644 --- a/common/ipc/internals/processor.cpp +++ b/common/ipc/internals/processor.cpp @@ -48,9 +48,6 @@ namespace ipc { LOGE("Callback threw an error: " << e.what()); \ } - - - const Processor::MethodID Processor::RETURN_METHOD_ID = std::numeric_limits::max(); Processor::Processor(const PeerCallback& newPeerCallback, @@ -118,9 +115,31 @@ Processor::PeerID Processor::addPeer(const std::shared_ptr& socketPtr) return peerID; } -void Processor::removePeer(const PeerID peerID, Status status) +void Processor::removePeer(const PeerID peerID) { - LOGW("Removing naughty peer. ID: " << peerID); + std::shared_ptr conditionPtr(new std::condition_variable()); + + { + Lock lock(mSocketsMutex); + RemovePeerRequest request(peerID, conditionPtr); + mPeersToDelete.push(std::move(request)); + } + + mEventQueue.send(Event::DELETE_PEER); + + auto isPeerDeleted = [&peerID, this] { + Lock lock(mSocketsMutex); + return mSockets.count(peerID) == 0; + }; + + std::mutex mutex; + std::unique_lock lock(mutex); + conditionPtr->wait(lock, isPeerDeleted); +} + +void Processor::removePeerInternal(const PeerID peerID, Status status) +{ + LOGW("Removing peer. ID: " << peerID); { Lock lock(mSocketsMutex); mSockets.erase(peerID); @@ -141,9 +160,49 @@ void Processor::removePeer(const PeerID peerID, Status status) } } + if (mRemovedPeerCallback) { + // Notify about the deletion + mRemovedPeerCallback(peerID); + } + resetPolling(); } +void Processor::cleanCommunication() +{ + while (!mEventQueue.isEmpty()) { + switch (mEventQueue.receive()) { + case Event::FINISH: { + LOGD("Event FINISH after FINISH"); + break; + } + case Event::CALL: { + LOGD("Event CALL after FINISH"); + Call call = getCall(); + IGNORE_EXCEPTIONS(call.process(Status::CLOSING, call.data)); + break; + } + + case Event::NEW_PEER: { + LOGD("Event NEW_PEER after FINISH"); + break; + } + + case Event::DELETE_PEER: { + LOGD("Event DELETE_PEER after FINISH"); + RemovePeerRequest request; + { + Lock lock(mSocketsMutex); + request = std::move(mPeersToDelete.front()); + mPeersToDelete.pop(); + } + request.conditionPtr->notify_all(); + break; + } + } + } +} + void Processor::resetPolling() { LOGI("Resetting polling"); @@ -198,6 +257,8 @@ void Processor::run() continue; } } + + cleanCommunication(); } @@ -215,11 +276,10 @@ bool Processor::handleLostConnections() peersToRemove.push_back(socketIt->first); } } - } for (const PeerID peerID : peersToRemove) { - removePeer(peerID, Status::PEER_DISCONNECTED); + removePeerInternal(peerID, Status::PEER_DISCONNECTED); } return !peersToRemove.empty(); @@ -280,7 +340,7 @@ bool Processor::onReturnValue(const PeerID peerID, mReturnCallbacks.erase(messageID); } catch (const std::out_of_range&) { LOGW("No return callback for messageID: " << messageID); - removePeer(peerID, Status::NAUGHTY_PEER); + removePeerInternal(peerID, Status::NAUGHTY_PEER); return true; } @@ -291,7 +351,7 @@ bool Processor::onReturnValue(const PeerID peerID, } catch (const std::exception& e) { LOGE("Exception during parsing: " << e.what()); IGNORE_EXCEPTIONS(returnCallbacks.process(Status::PARSING_ERROR, data)); - removePeer(peerID, Status::PARSING_ERROR); + removePeerInternal(peerID, Status::PARSING_ERROR); return true; } @@ -314,7 +374,7 @@ bool Processor::onRemoteCall(const PeerID peerID, methodCallbacks = mMethodsCallbacks.at(methodID); } catch (const std::out_of_range&) { LOGW("No method callback for methodID: " << methodID); - removePeer(peerID, Status::NAUGHTY_PEER); + removePeerInternal(peerID, Status::NAUGHTY_PEER); return true; } @@ -324,7 +384,7 @@ bool Processor::onRemoteCall(const PeerID peerID, data = methodCallbacks->parse(socket.getFD()); } catch (const std::exception& e) { LOGE("Exception during parsing: " << e.what()); - removePeer(peerID, Status::PARSING_ERROR); + removePeerInternal(peerID, Status::PARSING_ERROR); return true; } @@ -334,7 +394,7 @@ bool Processor::onRemoteCall(const PeerID peerID, returnData = methodCallbacks->method(data); } catch (const std::exception& e) { LOGE("Exception in method handler: " << e.what()); - removePeer(peerID, Status::NAUGHTY_PEER); + removePeerInternal(peerID, Status::NAUGHTY_PEER); return true; } @@ -347,7 +407,7 @@ bool Processor::onRemoteCall(const PeerID peerID, methodCallbacks->serialize(socket.getFD(), returnData); } catch (const std::exception& e) { LOGE("Exception during serialization: " << e.what()); - removePeer(peerID, Status::SERIALIZATION_ERROR); + removePeerInternal(peerID, Status::SERIALIZATION_ERROR); return true; } @@ -402,6 +462,20 @@ bool Processor::handleEvent() } return true; } + + case Event::DELETE_PEER: { + LOGD("Event DELETE_PEER"); + RemovePeerRequest request; + { + Lock lock(mSocketsMutex); + request = std::move(mPeersToDelete.front()); + mPeersToDelete.pop(); + } + + removePeerInternal(request.peerID, Status::REMOVED_PEER); + request.conditionPtr->notify_all(); + return true; + } } return false; @@ -447,39 +521,37 @@ bool Processor::handleCall() return false; } - MessageID messageID = getNextMessageID(); - { // Set what to do with the return message Lock lock(mReturnCallbacksMutex); - if (mReturnCallbacks.count(messageID) != 0) { - LOGE("There already was a return callback for messageID: " << messageID); + if (mReturnCallbacks.count(call.messageID) != 0) { + LOGE("There already was a return callback for messageID: " << call.messageID); } // move insertion - mReturnCallbacks[messageID] = std::move(ReturnCallbacks(call.peerID, - std::move(call.parse), - std::move(call.process))); + mReturnCallbacks[call.messageID] = std::move(ReturnCallbacks(call.peerID, + std::move(call.parse), + std::move(call.process))); } try { // Send the call with the socket Socket::Guard guard = socketPtr->getGuard(); socketPtr->write(&call.methodID, sizeof(call.methodID)); - socketPtr->write(&messageID, sizeof(messageID)); + socketPtr->write(&call.messageID, sizeof(call.messageID)); call.serialize(socketPtr->getFD(), call.data); } catch (const std::exception& e) { LOGE("Error during sending a message: " << e.what()); // Inform about the error - IGNORE_EXCEPTIONS(mReturnCallbacks[messageID].process(Status::SERIALIZATION_ERROR, call.data)); + IGNORE_EXCEPTIONS(mReturnCallbacks[call.messageID].process(Status::SERIALIZATION_ERROR, call.data)); { Lock lock(mReturnCallbacksMutex); - mReturnCallbacks.erase(messageID); + mReturnCallbacks.erase(call.messageID); } - removePeer(call.peerID, Status::SERIALIZATION_ERROR); + removePeerInternal(call.peerID, Status::SERIALIZATION_ERROR); return true; } diff --git a/common/ipc/internals/processor.hpp b/common/ipc/internals/processor.hpp index 3df3f9c..e34bdb6 100644 --- a/common/ipc/internals/processor.hpp +++ b/common/ipc/internals/processor.hpp @@ -65,7 +65,6 @@ const unsigned int DEFAULT_MAX_NUMBER_OF_PEERS = 500; * - Rest: The data written in a callback. One type per method.ReturnCallbacks * * TODO: -* - error codes passed to async callbacks * - remove ReturnCallbacks on peer disconnect * - on sync timeout erase the return callback * - don't throw timeout if the message is already processed @@ -73,12 +72,15 @@ const unsigned int DEFAULT_MAX_NUMBER_OF_PEERS = 500; * - removePeer API function * - error handling - special message type * - some mutexes may not be needed +* - make addPeer synchronous like removePeer */ class Processor { public: typedef std::function PeerCallback; typedef unsigned int PeerID; typedef unsigned int MethodID; + typedef unsigned int MessageID; + /** * Method ID. Used to indicate a message with the return value. @@ -122,6 +124,13 @@ public: PeerID addPeer(const std::shared_ptr& socketPtr); /** + * Request removing peer and wait + * + * @param peerID id of the peer + */ + void removePeer(const PeerID peerID); + + /** * Saves the callbacks connected to the method id. * When a message with the given method id is received, * the data will be passed to the serialization callback through file descriptor. @@ -171,17 +180,16 @@ public: * @tparam ReceivedDataType data type to receive */ template - void callAsync(const MethodID methodID, - const PeerID peerID, - const std::shared_ptr& data, - const typename ResultHandler::type& process); + MessageID callAsync(const MethodID methodID, + const PeerID peerID, + const std::shared_ptr& data, + const typename ResultHandler::type& process); private: typedef std::function& data)> SerializeCallback; typedef std::function(int fd)> ParseCallback; typedef std::lock_guard Lock; - typedef unsigned int MessageID; struct Call { Call(const Call& other) = delete; @@ -195,6 +203,7 @@ private: SerializeCallback serialize; ParseCallback parse; ResultHandler::type process; + MessageID messageID; }; struct MethodHandlers { @@ -238,10 +247,26 @@ private: std::shared_ptr socketPtr; }; + struct RemovePeerRequest { + RemovePeerRequest(const RemovePeerRequest& other) = delete; + RemovePeerRequest& operator=(const RemovePeerRequest&) = delete; + RemovePeerRequest() = default; + RemovePeerRequest(RemovePeerRequest&&) = default; + RemovePeerRequest& operator=(RemovePeerRequest &&) = default; + + RemovePeerRequest(const PeerID peerID, + const std::shared_ptr& conditionPtr) + : peerID(peerID), conditionPtr(conditionPtr) {} + + PeerID peerID; + std::shared_ptr conditionPtr; + }; + enum class Event : int { FINISH, // Shutdown request CALL, // New method call in the queue - NEW_PEER // New peer in the queue + NEW_PEER, // New peer in the queue + DELETE_PEER // Delete peer }; EventQueue mEventQueue; @@ -258,6 +283,7 @@ private: std::mutex mSocketsMutex; std::unordered_map > mSockets; std::queue mNewSockets; + std::queue mPeersToDelete; // Mutex for modifying the map with return callbacks std::mutex mReturnCallbacksMutex; @@ -292,8 +318,8 @@ private: MessageID getNextMessageID(); PeerID getNextPeerID(); Call getCall(); - void removePeer(const PeerID peerID, Status status); - + void removePeerInternal(const PeerID peerID, Status status); + void cleanCommunication(); }; template @@ -336,10 +362,10 @@ void Processor::addMethodHandler(const MethodID methodID, } template -void Processor::callAsync(const MethodID methodID, - const PeerID peerID, - const std::shared_ptr& data, - const typename ResultHandler::type& process) +Processor::MessageID Processor::callAsync(const MethodID methodID, + const PeerID peerID, + const std::shared_ptr& data, + const typename ResultHandler::type& process) { static_assert(config::isVisitable::value, "Use the libConfig library"); @@ -357,6 +383,7 @@ void Processor::callAsync(const MethodID methodID, call.peerID = peerID; call.methodID = methodID; call.data = data; + call.messageID = getNextMessageID(); call.parse = [](const int fd)->std::shared_ptr { std::shared_ptr data(new ReceivedDataType()); @@ -379,6 +406,8 @@ void Processor::callAsync(const MethodID methodID, } mEventQueue.send(Event::CALL); + + return call.messageID; } @@ -400,30 +429,43 @@ std::shared_ptr Processor::callSync(const MethodID methodID, std::shared_ptr result; - std::mutex mtx; - std::unique_lock lck(mtx); + std::mutex mutex; std::condition_variable cv; Status returnStatus = ipc::Status::UNDEFINED; - auto process = [&result, &cv, &returnStatus](Status status, std::shared_ptr returnedData) { + auto process = [&result, &mutex, &cv, &returnStatus](Status status, std::shared_ptr returnedData) { + std::unique_lock lock(mutex); returnStatus = status; result = returnedData; - cv.notify_one(); + cv.notify_all(); }; - callAsync(methodID, - peerID, - data, - process); + MessageID messageID = callAsync(methodID, + peerID, + data, + process); auto isResultInitialized = [&returnStatus]() { return returnStatus != ipc::Status::UNDEFINED; }; - if (!cv.wait_for(lck, std::chrono::milliseconds(timeoutMS), isResultInitialized)) { - LOGE("Function call timeout; methodID: " << methodID); - throw IPCTimeoutException("Function call timeout; methodID: " + std::to_string(methodID)); + std::unique_lock lock(mutex); + if (!cv.wait_for(lock, std::chrono::milliseconds(timeoutMS), isResultInitialized)) { + bool isTimeout = false; + { + Lock lock(mReturnCallbacksMutex); + if (1 == mReturnCallbacks.erase(messageID)) { + isTimeout = true; + } + } + if (isTimeout) { + removePeer(peerID); + LOGE("Function call timeout; methodID: " << methodID); + throw IPCTimeoutException("Function call timeout; methodID: " + std::to_string(methodID)); + } else { + // Timeout started during the return value processing, so wait for it to finish + cv.wait(lock, isResultInitialized); + } } throwOnError(returnStatus); diff --git a/common/ipc/internals/socket.cpp b/common/ipc/internals/socket.cpp index 002b9cf..4ac977a 100644 --- a/common/ipc/internals/socket.cpp +++ b/common/ipc/internals/socket.cpp @@ -102,8 +102,8 @@ int Socket::getSystemdSocket(const std::string& path) { int n = ::sd_listen_fds(-1 /*Block further calls to sd_listen_fds*/); if (n < 0) { - LOGE("sd_listen_fds fails with errno: " + n); - throw IPCException("sd_listen_fds fails with errno: " + n); + LOGE("sd_listen_fds fails with errno: " << n); + throw IPCException("sd_listen_fds fails with errno: " + std::to_string(n)); } for (int fd = SD_LISTEN_FDS_START; @@ -193,6 +193,14 @@ Socket Socket::connectSocket(const std::string& path) throw IPCException("Error in connect: " + std::string(strerror(errno))); } + // Nonblock socket + int flags = fcntl(fd, F_GETFL, 0); + if (-1 == fcntl(fd, F_SETFL, flags | O_NONBLOCK)) { + ::close(fd); + LOGE("Error in fcntl: " + std::string(strerror(errno))); + throw IPCException("Error in fcntl: " + std::string(strerror(errno))); + } + return Socket(fd); } diff --git a/common/ipc/internals/utils.cpp b/common/ipc/internals/utils.cpp index e98b60d..bb11c80 100644 --- a/common/ipc/internals/utils.cpp +++ b/common/ipc/internals/utils.cpp @@ -30,16 +30,63 @@ #include #include +#include #include - +#include #include #include namespace fs = boost::filesystem; +namespace chr = std::chrono; namespace security_containers { namespace ipc { +namespace { + +void waitForEvent(int fd, + short event, + const chr::high_resolution_clock::time_point deadline) +{ + // Wait for the rest of the data + struct pollfd fds[1]; + fds[0].fd = fd; + fds[0].events = event | POLLHUP; + + for (;;) { + chr::milliseconds timeoutMS = chr::duration_cast(deadline - chr::high_resolution_clock::now()); + if (timeoutMS.count() < 0) { + LOGE("Timeout in read"); + throw IPCException("Timeout in read"); + } + + int ret = ::poll(fds, 1 /*fds size*/, timeoutMS.count()); + + if (ret == -1) { + if (errno == EINTR) { + continue; + } + LOGE("Error in poll: " + std::string(strerror(errno))); + throw IPCException("Error in poll: " + std::string(strerror(errno))); + } + + if (ret == 0) { + LOGE("Timeout in read"); + throw IPCException("Timeout in read"); + } + + if (fds[0].revents & POLLHUP) { + LOGE("Peer disconnected"); + throw IPCException("Peer disconnected"); + } + + // Here Comes the Sun + break; + } +} + +} // namespace + void close(int fd) { if (fd < 0) { @@ -59,46 +106,62 @@ void close(int fd) } } -void write(int fd, const void* bufferPtr, const size_t size) +void write(int fd, const void* bufferPtr, const size_t size, int timeoutMS) { + chr::high_resolution_clock::time_point deadline = chr::high_resolution_clock::now() + + chr::milliseconds(timeoutMS); + size_t nTotal = 0; - int n; + for (;;) { + int n = ::write(fd, + reinterpret_cast(bufferPtr) + nTotal, + size - nTotal); + if (n > 0) { + nTotal += n; + } else if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { + // Neglected errors + LOGD("Retrying write"); + } else { + LOGE("Error during reading: " + std::string(strerror(errno))); + throw IPCException("Error during reading: " + std::string(strerror(errno))); + } - do { - n = ::write(fd, - reinterpret_cast(bufferPtr) + nTotal, - size - nTotal); - if (n < 0) { - if (errno == EINTR) { - LOGD("Write interrupted by a signal, retrying"); - continue; - } - LOGE("Error during writing: " + std::string(strerror(errno))); - throw IPCException("Error during witting: " + std::string(strerror(errno))); + if (nTotal >= size) { + // All data is written, break loop + break; + } else { + waitForEvent(fd, POLLOUT, deadline); } - nTotal += n; - } while (nTotal < size); + } } -void read(int fd, void* bufferPtr, const size_t size) +void read(int fd, void* bufferPtr, const size_t size, int timeoutMS) { - size_t nTotal = 0; - int n; + chr::high_resolution_clock::time_point deadline = chr::high_resolution_clock::now() + + chr::milliseconds(timeoutMS); - do { - n = ::read(fd, - reinterpret_cast(bufferPtr) + nTotal, - size - nTotal); - if (n < 0) { - if (errno == EINTR) { - LOGD("Read interrupted by a signal, retrying"); - continue; - } + size_t nTotal = 0; + for (;;) { + int n = ::read(fd, + reinterpret_cast(bufferPtr) + nTotal, + size - nTotal); + if (n > 0) { + nTotal += n; + } else if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { + // Neglected errors + LOGD("Retrying read"); + } else { LOGE("Error during reading: " + std::string(strerror(errno))); throw IPCException("Error during reading: " + std::string(strerror(errno))); } - nTotal += n; - } while (nTotal < size); + + if (nTotal >= size) { + // All data is read, break loop + break; + } else { + waitForEvent(fd, POLLIN, deadline); + } + } } unsigned int getMaxFDNumber() diff --git a/common/ipc/internals/utils.hpp b/common/ipc/internals/utils.hpp index 0b1815d..a9a79a0 100644 --- a/common/ipc/internals/utils.hpp +++ b/common/ipc/internals/utils.hpp @@ -42,8 +42,9 @@ void close(int fd); * @param fd file descriptor * @param bufferPtr pointer to the data buffer * @param size size of data to write + * @param timeoutMS timeout in milliseconds */ -void write(int fd, const void* bufferPtr, const size_t size); +void write(int fd, const void* bufferPtr, const size_t size, int timeoutMS = 500); /** * Read from a file descriptor, throw on error. @@ -51,8 +52,9 @@ void write(int fd, const void* bufferPtr, const size_t size); * @param fd file descriptor * @param bufferPtr pointer to the data buffer * @param size size of the data to read + * @param timeoutMS timeout in milliseconds */ -void read(int fd, void* bufferPtr, const size_t size); +void read(int fd, void* bufferPtr, const size_t size, int timeoutMS = 500); /** * @return the max number of file descriptors for this process. diff --git a/common/ipc/types.cpp b/common/ipc/types.cpp index e0ffc5b..bce862c 100644 --- a/common/ipc/types.cpp +++ b/common/ipc/types.cpp @@ -37,6 +37,8 @@ std::string toString(const Status status) case Status::SERIALIZATION_ERROR: return "Exception during writing/serializing data to the socket"; case Status::PEER_DISCONNECTED: return "No such peer. Might got disconnected."; case Status::NAUGHTY_PEER: return "Peer performed a forbidden action."; + case Status::REMOVED_PEER: return "Removing peer"; + case Status::CLOSING: return "Closing IPC"; case Status::UNDEFINED: return "Undefined state"; default: return "Unknown status"; } @@ -56,6 +58,8 @@ void throwOnError(const Status status) case Status::SERIALIZATION_ERROR: throw IPCSerializationException(message); case Status::PEER_DISCONNECTED: throw IPCPeerDisconnectedException(message); case Status::NAUGHTY_PEER: throw IPCNaughtyPeerException(message); + case Status::REMOVED_PEER: throw IPCException(message); + case Status::CLOSING: throw IPCException(message); case Status::UNDEFINED: throw IPCException(message); default: return throw IPCException(message); } diff --git a/common/ipc/types.hpp b/common/ipc/types.hpp index c07e504..1bfaa4e 100644 --- a/common/ipc/types.hpp +++ b/common/ipc/types.hpp @@ -40,6 +40,8 @@ enum class Status : int { SERIALIZATION_ERROR, PEER_DISCONNECTED, NAUGHTY_PEER, + REMOVED_PEER, + CLOSING, UNDEFINED }; diff --git a/tests/unit_tests/ipc/ut-ipc.cpp b/tests/unit_tests/ipc/ut-ipc.cpp index ea70b45..5d284bd 100644 --- a/tests/unit_tests/ipc/ut-ipc.cpp +++ b/tests/unit_tests/ipc/ut-ipc.cpp @@ -71,6 +71,27 @@ struct SendData { ) }; +struct LongSendData { + LongSendData(int i = 0, int waitTime = 1000): mSendData(i), mWaitTime(waitTime), intVal(i) {} + + template + void accept(Visitor visitor) + { + std::this_thread::sleep_for(std::chrono::milliseconds(mWaitTime)); + mSendData.accept(visitor); + } + template + void accept(Visitor visitor) const + { + std::this_thread::sleep_for(std::chrono::milliseconds(mWaitTime)); + mSendData.accept(visitor); + } + + SendData mSendData; + int mWaitTime; + int intVal; +}; + struct EmptyData { CONFIG_REGISTER_EMPTY }; @@ -441,6 +462,45 @@ BOOST_AUTO_TEST_CASE(DisconnectedPeerErrorTest) } +BOOST_AUTO_TEST_CASE(ReadTimeoutTest) +{ + Service s(socketPath); + auto longEchoCallback = [](std::shared_ptr& data) { + return std::shared_ptr(new LongSendData(data->intVal)); + }; + s.addMethodHandler(1, longEchoCallback); + s.start(); + + Client c(socketPath); + c.start(); + + // Test timeout on read + std::shared_ptr sentData(new SendData(334)); + BOOST_CHECK_THROW((c.callSync(1, sentData, 100)), IPCException); +} + + +BOOST_AUTO_TEST_CASE(WriteTimeoutTest) +{ + Service s(socketPath); + s.addMethodHandler(1, echoCallback); + s.start(); + + Client c(socketPath); + c.start(); + + // Test echo with a minimal timeout + std::shared_ptr sentDataA(new LongSendData(34, 10 /*ms*/)); + std::shared_ptr recvData = c.callSync(1, sentDataA, 100); + BOOST_CHECK_EQUAL(recvData->intVal, sentDataA->intVal); + + // Test timeout on write + std::shared_ptr sentDataB(new LongSendData(34, 1000 /*ms*/)); + BOOST_CHECK_THROW((c.callSync(1, sentDataB, 100)), IPCTimeoutException); +} + + + // BOOST_AUTO_TEST_CASE(ConnectionLimitTest) // { // unsigned oldLimit = ipc::getMaxFDNumber(); -- 2.7.4 From ee57290a9ebdb2d3d6bc5f99950c9962ae8096d2 Mon Sep 17 00:00:00 2001 From: Jan Olszak Date: Mon, 24 Nov 2014 17:30:27 +0100 Subject: [PATCH 15/16] IPC: Refactoring [Bug/Feature] N/A [Cause] N/A [Solution] N/A [Verification] Build, install, run tests Change-Id: I421b6af0c5da5b6f1d73d69e491e976f84894272 --- common/ipc/internals/processor.cpp | 158 ++++++++++++++++++++----------------- common/ipc/internals/processor.hpp | 16 ++-- 2 files changed, 89 insertions(+), 85 deletions(-) diff --git a/common/ipc/internals/processor.cpp b/common/ipc/internals/processor.cpp index 9677991..9aadfff 100644 --- a/common/ipc/internals/processor.cpp +++ b/common/ipc/internals/processor.cpp @@ -110,7 +110,7 @@ Processor::PeerID Processor::addPeer(const std::shared_ptr& socketPtr) mNewSockets.push(std::move(socketInfo)); } LOGI("New peer added. Id: " << peerID); - mEventQueue.send(Event::NEW_PEER); + mEventQueue.send(Event::ADD_PEER); return peerID; } @@ -125,7 +125,7 @@ void Processor::removePeer(const PeerID peerID) mPeersToDelete.push(std::move(request)); } - mEventQueue.send(Event::DELETE_PEER); + mEventQueue.send(Event::REMOVE_PEER); auto isPeerDeleted = [&peerID, this] { Lock lock(mSocketsMutex); @@ -168,41 +168,6 @@ void Processor::removePeerInternal(const PeerID peerID, Status status) resetPolling(); } -void Processor::cleanCommunication() -{ - while (!mEventQueue.isEmpty()) { - switch (mEventQueue.receive()) { - case Event::FINISH: { - LOGD("Event FINISH after FINISH"); - break; - } - case Event::CALL: { - LOGD("Event CALL after FINISH"); - Call call = getCall(); - IGNORE_EXCEPTIONS(call.process(Status::CLOSING, call.data)); - break; - } - - case Event::NEW_PEER: { - LOGD("Event NEW_PEER after FINISH"); - break; - } - - case Event::DELETE_PEER: { - LOGD("Event DELETE_PEER after FINISH"); - RemovePeerRequest request; - { - Lock lock(mSocketsMutex); - request = std::move(mPeersToDelete.front()); - mPeersToDelete.pop(); - } - request.conditionPtr->notify_all(); - break; - } - } - } -} - void Processor::resetPolling() { LOGI("Resetting polling"); @@ -432,53 +397,63 @@ bool Processor::handleEvent() case Event::CALL: { LOGD("Event CALL"); - return handleCall(); + return onCall(); } - case Event::NEW_PEER: { - LOGD("Event NEW_PEER"); - SocketInfo socketInfo; - { - Lock lock(mSocketsMutex); + case Event::ADD_PEER: { + LOGD("Event ADD_PEER"); + return onNewPeer(); + } - socketInfo = std::move(mNewSockets.front()); - mNewSockets.pop(); + case Event::REMOVE_PEER: { + LOGD("Event REMOVE_PEER"); + return onRemovePeer(); + } + } - if (mSockets.size() > mMaxNumberOfPeers) { - LOGE("There are too many peers. I don't accept the connection with " << socketInfo.peerID); - return false; - } - if (mSockets.count(socketInfo.peerID) != 0) { - LOGE("There already was a socket for peerID: " << socketInfo.peerID); - return false; - } + return false; +} - mSockets[socketInfo.peerID] = std::move(socketInfo.socketPtr); - } - resetPolling(); - if (mNewPeerCallback) { - // Notify about the new user. - mNewPeerCallback(socketInfo.peerID); - } - return true; - } +bool Processor::onNewPeer() +{ + SocketInfo socketInfo; + { + Lock lock(mSocketsMutex); - case Event::DELETE_PEER: { - LOGD("Event DELETE_PEER"); - RemovePeerRequest request; - { - Lock lock(mSocketsMutex); - request = std::move(mPeersToDelete.front()); - mPeersToDelete.pop(); + socketInfo = std::move(mNewSockets.front()); + mNewSockets.pop(); + + if (mSockets.size() > mMaxNumberOfPeers) { + LOGE("There are too many peers. I don't accept the connection with " << socketInfo.peerID); + return false; + } + if (mSockets.count(socketInfo.peerID) != 0) { + LOGE("There already was a socket for peerID: " << socketInfo.peerID); + return false; } - removePeerInternal(request.peerID, Status::REMOVED_PEER); - request.conditionPtr->notify_all(); - return true; + mSockets[socketInfo.peerID] = std::move(socketInfo.socketPtr); } + resetPolling(); + if (mNewPeerCallback) { + // Notify about the new user. + mNewPeerCallback(socketInfo.peerID); } + return true; +} - return false; +bool Processor::onRemovePeer() +{ + RemovePeerRequest request; + { + Lock lock(mSocketsMutex); + request = std::move(mPeersToDelete.front()); + mPeersToDelete.pop(); + } + + removePeerInternal(request.peerID, Status::REMOVED_PEER); + request.conditionPtr->notify_all(); + return true; } Processor::MessageID Processor::getNextMessageID() @@ -505,7 +480,7 @@ Processor::Call Processor::getCall() return call; } -bool Processor::handleCall() +bool Processor::onCall() { LOGT("Handle call (from another thread) to send a message."); Call call = getCall(); @@ -558,5 +533,40 @@ bool Processor::handleCall() return false; } +void Processor::cleanCommunication() +{ + while (!mEventQueue.isEmpty()) { + switch (mEventQueue.receive()) { + case Event::FINISH: { + LOGD("Event FINISH after FINISH"); + break; + } + case Event::CALL: { + LOGD("Event CALL after FINISH"); + Call call = getCall(); + IGNORE_EXCEPTIONS(call.process(Status::CLOSING, call.data)); + break; + } + + case Event::ADD_PEER: { + LOGD("Event ADD_PEER after FINISH"); + break; + } + + case Event::REMOVE_PEER: { + LOGD("Event REMOVE_PEER after FINISH"); + RemovePeerRequest request; + { + Lock lock(mSocketsMutex); + request = std::move(mPeersToDelete.front()); + mPeersToDelete.pop(); + } + request.conditionPtr->notify_all(); + break; + } + } + } +} + } // namespace ipc } // namespace security_containers diff --git a/common/ipc/internals/processor.hpp b/common/ipc/internals/processor.hpp index e34bdb6..7554eb8 100644 --- a/common/ipc/internals/processor.hpp +++ b/common/ipc/internals/processor.hpp @@ -65,14 +65,7 @@ const unsigned int DEFAULT_MAX_NUMBER_OF_PEERS = 500; * - Rest: The data written in a callback. One type per method.ReturnCallbacks * * TODO: -* - remove ReturnCallbacks on peer disconnect -* - on sync timeout erase the return callback -* - don't throw timeout if the message is already processed -* - naming convention or methods that just commissions the PROCESS thread to do something -* - removePeer API function -* - error handling - special message type * - some mutexes may not be needed -* - make addPeer synchronous like removePeer */ class Processor { public: @@ -81,7 +74,6 @@ public: typedef unsigned int MethodID; typedef unsigned int MessageID; - /** * Method ID. Used to indicate a message with the return value. */ @@ -265,8 +257,8 @@ private: enum class Event : int { FINISH, // Shutdown request CALL, // New method call in the queue - NEW_PEER, // New peer in the queue - DELETE_PEER // Delete peer + ADD_PEER, // New peer in the queue + REMOVE_PEER // Remove peer }; EventQueue mEventQueue; @@ -303,7 +295,9 @@ private: void run(); bool handleEvent(); - bool handleCall(); + bool onCall(); + bool onNewPeer(); + bool onRemovePeer(); bool handleLostConnections(); bool handleInputs(); bool handleInput(const PeerID peerID, const Socket& socket); -- 2.7.4 From 873c1046ffe0a34ab7ec0a4d48e59e1adae754d3 Mon Sep 17 00:00:00 2001 From: Piotr Bartosiewicz Date: Mon, 24 Nov 2014 17:10:29 +0100 Subject: [PATCH 16/16] Fix create container from template [Bug/Feature] Templates stops working after migration to lxc [Cause] N/A [Solution] N/A [Verification] Build, install, run tests Change-Id: Ifbc0db612391eb7460b757b3cd12dda79183178b --- common/utils/environment.cpp | 10 +++-- common/utils/environment.hpp | 2 +- server/configs/CMakeLists.txt | 4 ++ server/configs/lxc-templates/template.sh | 72 ++++++++++++++++++++++++++++++++ server/configs/templates/template.conf | 3 ++ server/containers-manager.cpp | 26 +++++------- 6 files changed, 98 insertions(+), 19 deletions(-) create mode 100755 server/configs/lxc-templates/template.sh diff --git a/common/utils/environment.cpp b/common/utils/environment.cpp index eaaef12..73c7057 100644 --- a/common/utils/environment.cpp +++ b/common/utils/environment.cpp @@ -84,8 +84,9 @@ bool dropRoot(uid_t uid, gid_t gid, const std::vector& caps) return true; } -bool launchAsRoot(const std::function& func) +bool launchAsRoot(const std::function& func) { + // TODO optimize if getuid() == 0 pid_t pid = fork(); if (pid < 0) { LOGE("Fork failed: " << strerror(errno)); @@ -99,8 +100,11 @@ bool launchAsRoot(const std::function& func) } try { - func(); - } catch (std::exception& e) { + if (!func()) { + LOGE("Failed to successfully execute func"); + ::exit(EXIT_FAILURE); + } + } catch (const std::exception& e) { LOGE("Failed to successfully execute func: " << e.what()); ::exit(EXIT_FAILURE); } diff --git a/common/utils/environment.hpp b/common/utils/environment.hpp index 120b6ac..07a767e 100644 --- a/common/utils/environment.hpp +++ b/common/utils/environment.hpp @@ -50,7 +50,7 @@ bool dropRoot(uid_t uid, gid_t gid, const std::vector& caps); * * This function forks, sets UID 0 to child process and calls func. */ -bool launchAsRoot(const std::function& func); +bool launchAsRoot(const std::function& func); } // namespace utils diff --git a/server/configs/CMakeLists.txt b/server/configs/CMakeLists.txt index ab4a94f..e4f9df9 100644 --- a/server/configs/CMakeLists.txt +++ b/server/configs/CMakeLists.txt @@ -21,6 +21,7 @@ MESSAGE(STATUS "Installing configs to " ${SC_CONFIG_INSTALL_DIR}) FILE(GLOB container_CONF containers/*.conf) FILE(GLOB admin_CONF lxc-templates/*.sh) +FILE(GLOB template_CONF templates/*.conf) ## Generate #################################################################### CONFIGURE_FILE(systemd/security-containers.service.in @@ -44,5 +45,8 @@ INSTALL(FILES ${container_CONF} INSTALL(PROGRAMS ${admin_CONF} DESTINATION ${SC_CONFIG_INSTALL_DIR}/lxc-templates) +INSTALL(PROGRAMS ${template_CONF} + DESTINATION ${SC_CONFIG_INSTALL_DIR}/templates) + INSTALL(FILES ${CMAKE_BINARY_DIR}/systemd/security-containers.service DESTINATION ${SYSTEMD_UNIT_DIR}) diff --git a/server/configs/lxc-templates/template.sh b/server/configs/lxc-templates/template.sh new file mode 100755 index 0000000..c03a050 --- /dev/null +++ b/server/configs/lxc-templates/template.sh @@ -0,0 +1,72 @@ +#!/bin/bash + +echo LXC template, args: $@ + +options=$(getopt -o p:n: -l rootfs:,path:,name: -- "$@") +if [ $? -ne 0 ]; then + exit 1 +fi +eval set -- "$options" + +while true +do + case "$1" in + -p|--path) path=$2; shift 2;; + --rootfs) rootfs=$2; shift 2;; + -n|--name) name=$2; shift 2;; + --) shift 1; break ;; + *) break ;; + esac +done + +br_name="virbr-${name}" +sub_net="103" # TODO from param + +# XXX assume rootfs if mounted from iso + +# Prepare container configuration file +> ${path}/config +cat <> ${path}/config +lxc.utsname = ${name} +lxc.rootfs = ${rootfs} + +# userns 1-to-1 mapping +#lxc.id_map = u 0 0 65536 +#lxc.id_map = g 0 0 65536 + +lxc.pts = 256 +lxc.tty = 0 + +lxc.mount.auto = proc sys cgroup +lxc.mount.entry = /var/run/containers/${name}/run var/run none rw,bind 0 0 + +lxc.network.type = veth +lxc.network.link = ${br_name} +lxc.network.flags = up +lxc.network.name = eth0 +lxc.network.veth.pair = veth-${name} +lxc.network.ipv4.gateway = 10.0.${sub_net}.1 +lxc.network.ipv4 = 10.0.${sub_net}.2/24 + +lxc.hook.pre-start = ${path}/pre-start.sh + +#lxc.loglevel = TRACE +#lxc.logfile = /tmp/${name}.log +EOF + +# prepare pre start hook +cat <> ${path}/pre-start.sh +if [ -z "\$(/usr/sbin/brctl show | /bin/grep -P "${br_name}\t")" ] +then + /usr/sbin/brctl addbr ${br_name} + /usr/sbin/brctl setfd ${br_name} 0 + /sbin/ifconfig ${br_name} 10.0.${sub_net}.1 netmask 255.255.255.0 up +fi +if [ -z "\$(/usr/sbin/iptables -t nat -S | /bin/grep MASQUERADE)" ] +then + /bin/echo 1 > /proc/sys/net/ipv4/ip_forward + /usr/sbin/iptables -t nat -A POSTROUTING -s 10.0.0.0/16 ! -d 10.0.0.0/16 -j MASQUERADE +fi +EOF + +chmod 755 ${path}/pre-start.sh diff --git a/server/configs/templates/template.conf b/server/configs/templates/template.conf index a8f47fc..f91bf5f 100644 --- a/server/configs/templates/template.conf +++ b/server/configs/templates/template.conf @@ -1,4 +1,7 @@ { + "name" : "~NAME~", + "lxcTemplate" : "template.sh", + "initWithArgs" : [], "cpuQuotaForeground" : -1, "cpuQuotaBackground" : 1000, "privilege" : 10, diff --git a/server/containers-manager.cpp b/server/containers-manager.cpp index 62dbc14..def04c2 100644 --- a/server/containers-manager.cpp +++ b/server/containers-manager.cpp @@ -41,9 +41,6 @@ #include #include -#include -#include -#include #include #include #include @@ -70,7 +67,6 @@ const std::string HOST_ID = "host"; const std::string CONTAINER_TEMPLATE_CONFIG_PATH = "template.conf"; const boost::regex CONTAINER_NAME_REGEX("~NAME~"); -const boost::regex CONTAINER_UUID_REGEX("~UUID~"); const boost::regex CONTAINER_IP_THIRD_OCTET_REGEX("~IP~"); const unsigned int CONTAINER_IP_BASE_THIRD_OCTET = 100; @@ -522,12 +518,15 @@ void ContainersManager::handleGetContainerInfoCall(const std::string& id, result->setError(api::ERROR_INTERNAL, "Unrecognized state of container"); return; } - const std::string rootPath = boost::filesystem::absolute(id, mConfig.containersPath).string(); + const auto containerPath = boost::filesystem::absolute(id, mConfig.containersPath); + const auto rootfsDir = boost::filesystem::path("rootfs"); + const auto rootfsPath = containerPath / rootfsDir; + result->set(g_variant_new("((siss))", id.c_str(), container->getVT(), state, - rootPath.c_str())); + rootfsPath.string().c_str())); } void ContainersManager::handleSetActiveContainerCall(const std::string& id, @@ -581,14 +580,10 @@ void ContainersManager::generateNewConfig(const std::string& id, std::string resultConfig = boost::regex_replace(config, CONTAINER_NAME_REGEX, id); - boost::uuids::uuid u = boost::uuids::random_generator()(); - std::string uuidStr = to_string(u); - LOGD("uuid: " << uuidStr); - resultConfig = boost::regex_replace(resultConfig, CONTAINER_UUID_REGEX, uuidStr); - // generate third IP octet for network config + // TODO change algorithm after implementing removeContainer std::string thirdOctetStr = std::to_string(CONTAINER_IP_BASE_THIRD_OCTET + mContainers.size() + 1); - LOGD("ip_third_octet: " << thirdOctetStr); + LOGD("IP third octet: " << thirdOctetStr); resultConfig = boost::regex_replace(resultConfig, CONTAINER_IP_THIRD_OCTET_REGEX, thirdOctetStr); if (!utils::saveFileContent(resultPath, resultConfig)) { @@ -630,7 +625,7 @@ void ContainersManager::handleAddContainerCall(const std::string& id, } // copy container image if config contains path to image - LOGT("image path: " << mConfig.containerImagePath); + LOGT("Image path: " << mConfig.containerImagePath); if (!mConfig.containerImagePath.empty()) { auto copyImageContentsWrapper = std::bind(&utils::copyImageContents, mConfig.containerImagePath, @@ -659,6 +654,7 @@ void ContainersManager::handleAddContainerCall(const std::string& id, } catch(const std::exception& e) { LOGW("Failed to remove data: " << boost::diagnostic_information(e)); } + return true; }; try { @@ -682,13 +678,13 @@ void ContainersManager::handleAddContainerCall(const std::string& id, return; } - auto resultCallback = [this, id, result, containerPathStr, removeAllWrapper](bool succeeded) { + auto resultCallback = [this, id, result](bool succeeded) { if (succeeded) { focus(id); result->setVoid(); } else { LOGE("Failed to start container."); - utils::launchAsRoot(std::bind(removeAllWrapper, containerPathStr)); + // TODO removeContainer result->setError(api::host::ERROR_CONTAINER_CREATE_FAILED, "Failed to start container."); } -- 2.7.4