From 95bde33bc81d699e404fc1d3ffe378ec98b5f259 Mon Sep 17 00:00:00 2001 From: Lukasz Pawelczyk Date: Fri, 9 Oct 2015 16:37:59 +0200 Subject: [PATCH 01/16] Fix the color codes effectively fixing the BOLD colors [Feature] N/A [Cause] All the colors were always bold [Solution] Use proper ANSII color codes [Verification] Visual Change-Id: Id8be7fbbdad46e9952fcecfa2bdd8328eb4e7164 --- common/utils/ccolor.hpp | 16 ++++++++-------- tests/scripts/vsm_test_parser.py | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/common/utils/ccolor.hpp b/common/utils/ccolor.hpp index 1700967..b579354 100644 --- a/common/utils/ccolor.hpp +++ b/common/utils/ccolor.hpp @@ -31,14 +31,14 @@ namespace utils { enum class Color : unsigned int { DEFAULT = 0, - BLACK = 90, - RED = 91, - GREEN = 92, - YELLOW = 93, - BLUE = 94, - MAGENTA = 95, - CYAN = 96, - WHITE = 97 + BLACK = 30, + RED = 31, + GREEN = 32, + YELLOW = 33, + BLUE = 34, + MAGENTA = 35, + CYAN = 36, + WHITE = 37 }; enum class Attributes : unsigned int { diff --git a/tests/scripts/vsm_test_parser.py b/tests/scripts/vsm_test_parser.py index fc0eae6..ee63a55 100644 --- a/tests/scripts/vsm_test_parser.py +++ b/tests/scripts/vsm_test_parser.py @@ -3,14 +3,14 @@ import sys -BLACK = "\033[90m" -RED = "\033[91m" -GREEN = "\033[92m" -YELLOW = "\033[93m" -BLUE = "\033[94m" -MAGENTA = "\033[95m" -CYAN = "\033[96m" -WHITE = "\033[97m" +BLACK = "\033[30m" +RED = "\033[31m" +GREEN = "\033[32m" +YELLOW = "\033[33m" +BLUE = "\033[34m" +MAGENTA = "\033[35m" +CYAN = "\033[36m" +WHITE = "\033[37m" BOLD = "\033[1m" ENDC = "\033[0m" -- 2.7.4 From 2f62c1f25d9a9a8360f3d671ade64b61dd857f97 Mon Sep 17 00:00:00 2001 From: Krzysztof Dynowski Date: Thu, 8 Oct 2015 14:03:43 +0200 Subject: [PATCH 02/16] lxcpp: cgroups API [Feature] Control-groups API for containers [Cause] N/A [Solution] N/A [Verification] N/A Change-Id: I69605383b40e3b3e1a8c2f6942e85023b367728e --- libs/lxcpp/CMakeLists.txt | 3 +- libs/lxcpp/cgroups/cgroup.cpp | 26 +++++++++++++ libs/lxcpp/cgroups/cgroup.hpp | 83 ++++++++++++++++++++++++++++++++++++++++ libs/lxcpp/cgroups/subsystem.hpp | 81 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 192 insertions(+), 1 deletion(-) create mode 100644 libs/lxcpp/cgroups/cgroup.cpp create mode 100644 libs/lxcpp/cgroups/cgroup.hpp create mode 100644 libs/lxcpp/cgroups/subsystem.hpp diff --git a/libs/lxcpp/CMakeLists.txt b/libs/lxcpp/CMakeLists.txt index 9e49843..0d35bad 100644 --- a/libs/lxcpp/CMakeLists.txt +++ b/libs/lxcpp/CMakeLists.txt @@ -33,13 +33,14 @@ FILE(GLOB HEADERS_COMMANDS commands/*.hpp) FILE(GLOB SRCS *.cpp *.hpp) FILE(GLOB SRCS_COMMANDS commands/*.cpp) +FILE(GLOB SRCS_CGROUPS cgroups/*.cpp) SET(_LIB_VERSION_ "${VERSION}") SET(_LIB_SOVERSION_ "0") SET(PC_FILE "lib${PROJECT_NAME}.pc") ## Setup target ################################################################ -ADD_LIBRARY(${PROJECT_NAME} SHARED ${SRCS} ${SRCS_COMMANDS}) +ADD_LIBRARY(${PROJECT_NAME} SHARED ${SRCS} ${SRCS_COMMANDS} ${SRCS_CGROUPS}) SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES SOVERSION ${_LIB_SOVERSION_} VERSION ${_LIB_VERSION_} diff --git a/libs/lxcpp/cgroups/cgroup.cpp b/libs/lxcpp/cgroups/cgroup.cpp new file mode 100644 index 0000000..574af61 --- /dev/null +++ b/libs/lxcpp/cgroups/cgroup.cpp @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2015 Samsung Electronics Co., Ltd All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @author Krzysztof Dynowski (k.dynowski@samsumg.com) + * @brief Control-groups management + */ + +#include "lxcpp/cgroups/cgroup.hpp" + +// added this file now, to make hpp go through compilation diff --git a/libs/lxcpp/cgroups/cgroup.hpp b/libs/lxcpp/cgroups/cgroup.hpp new file mode 100644 index 0000000..b47268f --- /dev/null +++ b/libs/lxcpp/cgroups/cgroup.hpp @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2015 Samsung Electronics Co., Ltd All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @author Krzysztof Dynowski (k.dynowski@samsumg.com) + * @brief Control-groups management + */ + +#ifndef LXCPP_CGROUPS_CGROUP_HPP +#define LXCPP_CGROUPS_CGROUP_HPP + +#include "lxcpp/cgroups/subsystem.hpp" + +class CGroup { + +public: + /** + * Define control-group object + */ + CGroup(const Subsystem& subsys, const std::string& name) : + mSubsys(subsys), + mName(name) + { + } + + /** + * Check if cgroup exists + * @return true if cgroup path (subsys.path / mName) exists + */ + bool exists(); + + /** + * Create cgroup directory + * Equivalent of: mkdir subsys.path / mName + */ + void create(); + + /** + * Destroy cgroup directory + * Equivalent of: rmdir subsys.path / mName + * Note: set memory.force_empty before removing a cgroup to avoid moving out-of-use page caches to parent + */ + void destroy(); + + /** + * Set cgroup parameter to value (name validity depends on subsystem) + * Equivalent of: echo value > mSubsys_path/mName/mSubsys_name.param + */ + void setValue(const std::string& param, const std::string& value); + + /** + * Get cgroup parameter + * Equivalent of: cat mSubsys_path/mName/mSubsys_name.param + */ + std::string getValue(const std::string& key); + + /** + * Move process to this cgroup (process can't be removed from a cgroup) + * Equivalent of: echo pid > mSubsys_path/mName/tasks + */ + void moveProcess(pid_t pid); + +private: + const Subsystem& mSubsys; // referred subsystem + const std::string& mName; // path relative to subsystem "root" +}; + +#endif // LXCPP_CGROUPS_CGROUP_HPP diff --git a/libs/lxcpp/cgroups/subsystem.hpp b/libs/lxcpp/cgroups/subsystem.hpp new file mode 100644 index 0000000..bb9887e --- /dev/null +++ b/libs/lxcpp/cgroups/subsystem.hpp @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2015 Samsung Electronics Co., Ltd All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @author Krzysztof Dynowski (k.dynowski@samsumg.com) + * @brief Control-groups management + */ + +#ifndef LXCPP_CGROUPS_SUBSYSTEM_HPP +#define LXCPP_CGROUPS_SUBSYSTEM_HPP + +#include + +#include +#include + +class Subsystem { +public: + /** + * Define subsystem object + */ + Subsystem(const std::string& name); + + /** + * Check if named subsystem is supported by the kernel + * @return true if subsystem is listed in /proc/cgroups + */ + bool isAvailable(); + + /** + * Check if named subsystem is mounted (added to hierarchy) + * @return true if subsystem has a mount point (as read from /proc/mounts) + */ + bool isAttached(); + + /** + * Attach subsystem hierarchy to filesystem + * Equivalent of: mount -t cgroup -o subs(coma-sep) subs(underln-sep) path + */ + static void attach(const std::string& path, std::vector subs); + + /** + * Detach subsstem hierarchy from filesystem + * Equivalent of: umount path + */ + static void detach(const std::string& path); + + /** + * Get list of available subsytems + * @return parse contents of /proc/cgroups + */ + static std::vector availableSubsystems(); + + /** + * Get control groups list for a process (in format subsys_name:cgroup_name) + * eg. "cpu:/user/test_user" + * Equivalent of: cat /proc/pid/cgroup + */ + static std::vector getCGroups(pid_t pid); + +private: + const std::string& mName; + std::string mPath; +}; + +#endif // LXCPP_CGROUPS_SUBSYSTEM_HPP -- 2.7.4 From 64b5dbb4b7ae47002e35e05765637c04c526b0b6 Mon Sep 17 00:00:00 2001 From: Jan Olszak Date: Wed, 7 Oct 2015 18:13:52 +0200 Subject: [PATCH 03/16] config: enum class serialization [Feature] Serialization of strongly typed enums [Cause] N/A [Solution] N/A [Verification] Build, install, run tests. Change-Id: Ie4d112995cff8d58c0cc6fad6c0646ec03d5aa83 --- libs/config/from-fdstore-visitor.hpp | 6 ++++++ libs/config/from-gvariant-visitor.hpp | 7 +++++++ libs/config/from-json-visitor.hpp | 9 ++++++++- libs/config/from-kvjson-visitor.hpp | 11 ++++++++++- libs/config/kvstore.hpp | 26 ++++++++++++++++++++++---- libs/config/to-fdstore-visitor.hpp | 6 ++++++ libs/config/to-gvariant-visitor.hpp | 6 ++++++ libs/config/to-json-visitor.hpp | 9 ++++++++- tests/unit_tests/config/testconfig-example.hpp | 9 +++++++++ tests/unit_tests/config/ut-configuration.cpp | 1 + 10 files changed, 83 insertions(+), 7 deletions(-) diff --git a/libs/config/from-fdstore-visitor.hpp b/libs/config/from-fdstore-visitor.hpp index f0c8dbe..8b7d082 100644 --- a/libs/config/from-fdstore-visitor.hpp +++ b/libs/config/from-fdstore-visitor.hpp @@ -93,6 +93,12 @@ private: value.accept(visitor); } + template::value, int>::type = 0> + void readInternal(T& value) + { + readInternal(*reinterpret_cast::type*>(&value)); + } + template void readInternal(std::vector& values) { diff --git a/libs/config/from-gvariant-visitor.hpp b/libs/config/from-gvariant-visitor.hpp index afc1beb..78e001d 100644 --- a/libs/config/from-gvariant-visitor.hpp +++ b/libs/config/from-gvariant-visitor.hpp @@ -200,6 +200,13 @@ private: visitFields(values, &visitor, &iter); } + template::value, int>::type = 0> + static void fromGVariant(GVariant* object, T& value) + { + fromGVariant(object, + *reinterpret_cast::type*>(&value)); + } + template static typename std::enable_if::value>::type fromGVariant(GVariant* object, T& value) diff --git a/libs/config/from-json-visitor.hpp b/libs/config/from-json-visitor.hpp index 9bdf19b..b599084 100644 --- a/libs/config/from-json-visitor.hpp +++ b/libs/config/from-json-visitor.hpp @@ -192,7 +192,14 @@ private: visitFields(values, &visitor, object, idx); } - template::value>::type> + template::value, int>::type = 0> + static void fromJsonObject(json_object* object, T& value) + { + fromJsonObject(object, + *reinterpret_cast::type*>(&value)); + } + + template::value, int>::type = 0> static void fromJsonObject(json_object* object, T& value) { checkType(object, json_type_object); diff --git a/libs/config/from-kvjson-visitor.hpp b/libs/config/from-kvjson-visitor.hpp index d9dbce7..e66b90d 100644 --- a/libs/config/from-kvjson-visitor.hpp +++ b/libs/config/from-kvjson-visitor.hpp @@ -107,7 +107,16 @@ private: mObject = object ? json_object_get(object) : nullptr; } - template::value, int>::type = 0> + template::value, int>::type = 0> + void getValue(const std::string& name, T& t) + { + getValue(name, + *reinterpret_cast::type*>(&t)); + } + + template::value && + !std::is_enum::value, int>::type = 0> void getValue(const std::string& name, T& t) { std::string k = key(mKeyPrefix, name); diff --git a/libs/config/kvstore.hpp b/libs/config/kvstore.hpp index 1206d08..40aa199 100644 --- a/libs/config/kvstore.hpp +++ b/libs/config/kvstore.hpp @@ -139,7 +139,9 @@ private: void setInternal(const std::string& key, const char* value); void setInternal(const std::string& key, const std::initializer_list& values); void setInternal(const std::string& key, const std::vector& values); - template + template::value, int>::type = 0> + void setInternal(const std::string& key, const T& value); + template::value, int>::type = 0> void setInternal(const std::string& key, const T& value); template void setInternal(const std::string& key, const std::vector& values); @@ -151,7 +153,9 @@ private: std::string getInternal(const std::string& key, std::string*); char* getInternal(const std::string& key, char**); std::vector getInternal(const std::string& key, std::vector*); - template + template::value, int>::type = 0> + T getInternal(const std::string& key, T*); + template::value, int>::type = 0> T getInternal(const std::string& key, T*); template std::vector getInternal(const std::string& key, std::vector*); @@ -195,12 +199,19 @@ T fromString(const std::string& strValue) } // namespace -template +template::value, int>::type> void KVStore::setInternal(const std::string& key, const T& value) { setInternal(key, toString(value)); } +template::value, int>::type> +void KVStore::setInternal(const std::string& key, const T& value) +{ + setInternal(key, + static_cast::type>(value)); +} + template void KVStore::setInternal(const std::string& key, const std::vector& values) { @@ -247,12 +258,19 @@ void KVStore::setInternal(const std::string& key, const std::pair& values) setInternal(key, strValues); } -template +template::value, int>::type> T KVStore::getInternal(const std::string& key, T*) { return fromString(getInternal(key, static_cast(nullptr))); } +template::value, int>::type> +T KVStore::getInternal(const std::string& key, T*) +{ + return static_cast(getInternal(key, + static_cast::type*>(nullptr))); +} + template std::array KVStore::getInternal(const std::string& key, std::array*) { diff --git a/libs/config/to-fdstore-visitor.hpp b/libs/config/to-fdstore-visitor.hpp index ba9bb8e..b3e6b84 100644 --- a/libs/config/to-fdstore-visitor.hpp +++ b/libs/config/to-fdstore-visitor.hpp @@ -78,6 +78,12 @@ private: mStore.sendFD(fd.value); } + template::value, int>::type = 0> + void writeInternal(const T& value) + { + writeInternal(static_cast::type>(value)); + } + template::value, int>::type = 0> void writeInternal(const T& value) { diff --git a/libs/config/to-gvariant-visitor.hpp b/libs/config/to-gvariant-visitor.hpp index 09c04e6..c0df2da 100644 --- a/libs/config/to-gvariant-visitor.hpp +++ b/libs/config/to-gvariant-visitor.hpp @@ -143,6 +143,12 @@ private: g_variant_builder_close(mBuilder); } + template::value, int>::type = 0> + void writeInternal(const T& value) + { + writeInternal(static_cast::type>(value)); + } + template typename std::enable_if::value && !isUnion::value>::type writeInternal(const T& value) diff --git a/libs/config/to-json-visitor.hpp b/libs/config/to-json-visitor.hpp index 3b8facf..ba0d6ed 100644 --- a/libs/config/to-json-visitor.hpp +++ b/libs/config/to-json-visitor.hpp @@ -168,13 +168,20 @@ private: return array; } - template::value>::type> + template::value, int>::type = 0> static json_object* toJsonObject(const T& value) { ToJsonVisitor visitor; value.accept(visitor); return visitor.detach(); } + + template::value, int>::type = 0> + static json_object* toJsonObject(const T& value) + { + return toJsonObject(static_cast::type>(value)); + } + }; } // namespace config diff --git a/tests/unit_tests/config/testconfig-example.hpp b/tests/unit_tests/config/testconfig-example.hpp index 3cdd876..0ddd975 100644 --- a/tests/unit_tests/config/testconfig-example.hpp +++ b/tests/unit_tests/config/testconfig-example.hpp @@ -29,6 +29,11 @@ #include "config/fields.hpp" #include "config/fields-union.hpp" +enum class TestEnum: int { + FIRST = 0, + SECOND = 12 +}; + struct TestConfig { // subtree class struct SubConfig { @@ -91,6 +96,7 @@ struct TestConfig { char* cstringVal; double doubleVal; bool boolVal; + TestEnum enumVal; std::vector emptyIntVector; std::vector intVector; @@ -118,6 +124,7 @@ struct TestConfig { cstringVal, doubleVal, boolVal, + enumVal, emptyIntVector, intVector, @@ -163,6 +170,7 @@ const std::string jsonTestString = "\"cstringVal\": \"blah\", " "\"doubleVal\": -1.234000, " "\"boolVal\": true, " + "\"enumVal\": 12, " "\"emptyIntVector\": [ ], " "\"intVector\": [ 1, 2, 3 ], " "\"stringVector\": [ \"a\", \"b\" ], " @@ -188,6 +196,7 @@ const std::string jsonEmptyTestString = "\"stringVal\": \"\", " "\"cstringVal\": \"\", " "\"boolVal\": false, " + "\"enumVal\": 0, " "\"emptyIntVector\": [ ], " "\"intVector\": [ ], " "\"stringVector\": [ ], " diff --git a/tests/unit_tests/config/ut-configuration.cpp b/tests/unit_tests/config/ut-configuration.cpp index e50ed92..ab1a200 100644 --- a/tests/unit_tests/config/ut-configuration.cpp +++ b/tests/unit_tests/config/ut-configuration.cpp @@ -67,6 +67,7 @@ BOOST_AUTO_TEST_CASE(FromJsonString) BOOST_CHECK_EQUAL("blah", testConfig.cstringVal); BOOST_CHECK_CLOSE(-1.234, testConfig.doubleVal, TOLERANCE); BOOST_CHECK_EQUAL(true, testConfig.boolVal); + BOOST_CHECK(TestEnum::SECOND == testConfig.enumVal); BOOST_REQUIRE_EQUAL(0, testConfig.emptyIntVector.size()); -- 2.7.4 From 9f5816c9a34f40b6acfc8924ee914d5165c1e35f Mon Sep 17 00:00:00 2001 From: Jan Olszak Date: Thu, 8 Oct 2015 14:19:22 +0200 Subject: [PATCH 04/16] lxcpp: Switch on serialization from libConfig [Feature] Use std::pair and enum serialization [Cause] N/A [Solution] N/A [Verification] Build, install, run tests Change-Id: I6edb4543bf85eacfb2efe13065970326b30aba17 --- libs/lxcpp/attach/attach-config.hpp | 14 ++++++-------- libs/lxcpp/attach/attach-helper.cpp | 4 ++-- libs/lxcpp/commands/attach.cpp | 2 +- libs/lxcpp/commands/attach.hpp | 2 +- libs/lxcpp/container-impl.cpp | 2 +- libs/lxcpp/container-impl.hpp | 2 +- libs/lxcpp/container.hpp | 2 +- libs/lxcpp/guard/guard.cpp | 11 +---------- libs/lxcpp/process.cpp | 16 ++++++++++++++++ libs/lxcpp/process.hpp | 2 ++ 10 files changed, 32 insertions(+), 25 deletions(-) diff --git a/libs/lxcpp/attach/attach-config.hpp b/libs/lxcpp/attach/attach-config.hpp index 9788abe..94e2626 100644 --- a/libs/lxcpp/attach/attach-config.hpp +++ b/libs/lxcpp/attach/attach-config.hpp @@ -39,7 +39,7 @@ namespace lxcpp { struct AttachConfig { /// Arguments passed by user, argv[0] is the binary's path in container - std::vector argv; + std::vector argv; /// PID of the container's init process pid_t initPid; @@ -73,7 +73,7 @@ struct AttachConfig { AttachConfig() = default; - AttachConfig(const std::vector& argv, + AttachConfig(const std::vector& argv, const pid_t initPid, const std::vector& namespaces, const uid_t uid, @@ -99,19 +99,17 @@ struct AttachConfig { CONFIG_REGISTER ( //TODO: Uncomment and fix cstring serialization - // argv, + argv, initPid, - //TODO: Uncomment and fix Namespace serialization (or remove Namespace) - // namespaces, + namespaces, uid, gid, ttyFD, supplementaryGids, capsToKeep, workDirInContainer, - envToKeep - //TODO: Uncomment and fix std::pair serialization - // envToSet + envToKeep, + envToSet ) }; diff --git a/libs/lxcpp/attach/attach-helper.cpp b/libs/lxcpp/attach/attach-helper.cpp index 321502f..5aaaecb 100644 --- a/libs/lxcpp/attach/attach-helper.cpp +++ b/libs/lxcpp/attach/attach-helper.cpp @@ -70,8 +70,8 @@ int child(void* data) ::_exit(EXIT_FAILURE); } - // Run user's binary - ::execve(config.argv[0], const_cast(config.argv.data()), nullptr); + lxcpp::execve(config.argv); + return EXIT_FAILURE; } diff --git a/libs/lxcpp/commands/attach.cpp b/libs/lxcpp/commands/attach.cpp index 2bfa592..a059986 100644 --- a/libs/lxcpp/commands/attach.cpp +++ b/libs/lxcpp/commands/attach.cpp @@ -34,7 +34,7 @@ namespace lxcpp { Attach::Attach(const lxcpp::ContainerImpl& container, - const std::vector& argv, + const std::vector& argv, const uid_t uid, const gid_t gid, const std::string& ttyPath, diff --git a/libs/lxcpp/commands/attach.hpp b/libs/lxcpp/commands/attach.hpp index ce39b83..123a384 100644 --- a/libs/lxcpp/commands/attach.hpp +++ b/libs/lxcpp/commands/attach.hpp @@ -56,7 +56,7 @@ public: * @param envToSet new environment variables that will be set */ Attach(const lxcpp::ContainerImpl& container, - const std::vector& argv, + const std::vector& argv, const uid_t uid, const gid_t gid, const std::string& ttyPath, diff --git a/libs/lxcpp/container-impl.cpp b/libs/lxcpp/container-impl.cpp index c7035e4..9ff4b52 100644 --- a/libs/lxcpp/container-impl.cpp +++ b/libs/lxcpp/container-impl.cpp @@ -205,7 +205,7 @@ void ContainerImpl::reboot() throw NotImplementedException(); } -void ContainerImpl::attach(const std::vector& argv, +void ContainerImpl::attach(const std::vector& argv, const std::string& cwdInContainer) { Attach attach(*this, diff --git a/libs/lxcpp/container-impl.hpp b/libs/lxcpp/container-impl.hpp index 7393974..041303b 100644 --- a/libs/lxcpp/container-impl.hpp +++ b/libs/lxcpp/container-impl.hpp @@ -65,7 +65,7 @@ public: void reboot(); // Other - void attach(const std::vector& argv, + void attach(const std::vector& argv, const std::string& cwdInContainer); // Network interfaces setup/config diff --git a/libs/lxcpp/container.hpp b/libs/lxcpp/container.hpp index 51dda8c..7f98aed 100644 --- a/libs/lxcpp/container.hpp +++ b/libs/lxcpp/container.hpp @@ -71,7 +71,7 @@ public: virtual void reboot() = 0; // Other - virtual void attach(const std::vector& argv, + virtual void attach(const std::vector& argv, const std::string& cwdInContainer) = 0; // Network interfaces setup/config diff --git a/libs/lxcpp/guard/guard.cpp b/libs/lxcpp/guard/guard.cpp index 952cdf5..369d423 100644 --- a/libs/lxcpp/guard/guard.cpp +++ b/libs/lxcpp/guard/guard.cpp @@ -38,16 +38,7 @@ namespace lxcpp { void startContainer(const ContainerConfig &cfg) { - std::vector argv; - argv.reserve(cfg.mInit.size() + 1); - for (auto const & it : cfg.mInit) { - argv.push_back(it.c_str()); - } - argv.push_back(nullptr); - - LOGD("Executing container's init: " << argv[0]); - ::execve(argv[0], const_cast(argv.data()), NULL); - ::_exit(EXIT_FAILURE); + lxcpp::execve(cfg.mInit); } int startGuard(int channelFD) diff --git a/libs/lxcpp/process.cpp b/libs/lxcpp/process.cpp index 0353d82..204dec2 100644 --- a/libs/lxcpp/process.cpp +++ b/libs/lxcpp/process.cpp @@ -156,4 +156,20 @@ void unshare(const Namespace ns) throw ProcessSetupException(msg); } } + +void execve(const std::vector& argv) +{ + // Prepare the arguments + std::vector tmpArgv; + tmpArgv.reserve(argv.size() + 1); + + for (auto const &str : argv) { + tmpArgv.push_back(str.c_str()); + } + tmpArgv.push_back(nullptr); + + // Run user's binary + ::execve(tmpArgv[0], const_cast(tmpArgv.data()), nullptr); +} + } // namespace lxcpp diff --git a/libs/lxcpp/process.hpp b/libs/lxcpp/process.hpp index 869e3d6..75c1e4f 100644 --- a/libs/lxcpp/process.hpp +++ b/libs/lxcpp/process.hpp @@ -49,6 +49,8 @@ int waitpid(const pid_t pid); void unshare(const Namespace ns); +void execve(const std::vector& argv); + } // namespace lxcpp #endif // LXCPP_PROCESS_HPP \ No newline at end of file -- 2.7.4 From 3b6129779f3829bd1a1987087ccdd7d14a4af98d Mon Sep 17 00:00:00 2001 From: Jan Olszak Date: Thu, 8 Oct 2015 17:26:23 +0200 Subject: [PATCH 05/16] lxcpp: Added simple container tests [Feature] setInit and setLogger tests [Cause] N/A [Solution] N/A [Verification] Build, install, run tests Change-Id: I944c76aa4483c6c287a6b032b4d7456ab58f7d44 --- libs/lxcpp/container.hpp | 2 +- libs/lxcpp/guard/guard.cpp | 2 +- tests/unit_tests/lxcpp/ut-container.cpp | 75 ++++++++++++++++++++++++++++++++- 3 files changed, 75 insertions(+), 4 deletions(-) diff --git a/libs/lxcpp/container.hpp b/libs/lxcpp/container.hpp index 7f98aed..e3d537f 100644 --- a/libs/lxcpp/container.hpp +++ b/libs/lxcpp/container.hpp @@ -59,7 +59,7 @@ public: virtual void setLogger(const logger::LogType type, const logger::LogLevel level, - const std::string &arg) = 0; + const std::string &arg = "") = 0; virtual void setTerminalCount(const unsigned int count) = 0; diff --git a/libs/lxcpp/guard/guard.cpp b/libs/lxcpp/guard/guard.cpp index 369d423..28e572a 100644 --- a/libs/lxcpp/guard/guard.cpp +++ b/libs/lxcpp/guard/guard.cpp @@ -60,7 +60,7 @@ int startGuard(int channelFD) setProcTitle(title); } catch (std::exception &e) { // Ignore, this is optional - LOGW("Failed to set the guard process title"); + LOGW("Failed to set the guard process title: " << e.what()); } // TODO: container preparation part 1 diff --git a/tests/unit_tests/lxcpp/ut-container.cpp b/tests/unit_tests/lxcpp/ut-container.cpp index 35672e0..ff72b94 100644 --- a/tests/unit_tests/lxcpp/ut-container.cpp +++ b/tests/unit_tests/lxcpp/ut-container.cpp @@ -29,10 +29,29 @@ #include "lxcpp/lxcpp.hpp" #include "lxcpp/exception.hpp" +#include "utils/scoped-dir.hpp" + +#include + namespace { +const std::string TEST_DIR = "/tmp/ut-zones"; +const std::string ROOT_DIR = TEST_DIR + "/root"; +const std::string NON_EXISTANT_BINARY = TEST_DIR + "/nonexistantpath/bash"; +const std::string LOGGER_FILE = TEST_DIR + "/loggerFile"; + +const std::vector COMMAND = {"/bin/bash", + "-c", "trap exit SIGTERM; while true; do sleep 0.1; done" + }; + struct Fixture { - Fixture() {} + utils::ScopedDir mTestDir; + utils::ScopedDir mRoot; + + Fixture() + :mTestDir(TEST_DIR), + mRoot(ROOT_DIR) + {} ~Fixture() {} }; @@ -44,8 +63,60 @@ using namespace lxcpp; BOOST_AUTO_TEST_CASE(ConstructorDestructor) { - auto c = createContainer("FirstTestContainer", "/"); + auto c = createContainer("ConstructorDestructor", ROOT_DIR); delete c; } +BOOST_AUTO_TEST_CASE(SetInit) +{ + auto c = std::unique_ptr(createContainer("SetInit", "/")); + + BOOST_CHECK_THROW(c->setInit({""}), ConfigureException); + BOOST_CHECK_THROW(c->setInit({}), ConfigureException); + BOOST_CHECK_THROW(c->setInit({NON_EXISTANT_BINARY}), ConfigureException); + + BOOST_CHECK_NO_THROW(c->setInit(COMMAND)); +} + +BOOST_AUTO_TEST_CASE(SetLogger) +{ + auto c = std::unique_ptr(createContainer("SetLogger", ROOT_DIR)); + + BOOST_CHECK_NO_THROW(c->setLogger(logger::LogType::LOG_NULL, + logger::LogLevel::DEBUG)); + BOOST_CHECK_NO_THROW(c->setLogger(logger::LogType::LOG_JOURNALD, + logger::LogLevel::DEBUG)); + BOOST_CHECK_NO_THROW(c->setLogger(logger::LogType::LOG_SYSLOG, + logger::LogLevel::DEBUG)); + BOOST_CHECK_NO_THROW(c->setLogger(logger::LogType::LOG_STDERR, + logger::LogLevel::DEBUG)); + + BOOST_CHECK_NO_THROW(c->setLogger(logger::LogType::LOG_FILE, + logger::LogLevel::DEBUG, + LOGGER_FILE)); + BOOST_CHECK_NO_THROW(c->setLogger(logger::LogType::LOG_PERSISTENT_FILE, + logger::LogLevel::DEBUG, + LOGGER_FILE)); + + BOOST_CHECK_THROW(c->setLogger(logger::LogType::LOG_FILE, + logger::LogLevel::DEBUG, + ""), + BadArgument); + + BOOST_CHECK_THROW(c->setLogger(logger::LogType::LOG_PERSISTENT_FILE, + logger::LogLevel::DEBUG, + ""), + BadArgument); +} + +// BOOST_AUTO_TEST_CASE(StartStop) +// { +// auto c = std::unique_ptr(createContainer("StartStop", "/")); +// BOOST_CHECK_NO_THROW(c->setInit(COMMAND)); +// BOOST_CHECK_NO_THROW(c->setLogger(logger::LogType::LOG_PERSISTENT_FILE, +// logger::LogLevel::DEBUG, +// LOGGER_FILE)); +// BOOST_CHECK_NO_THROW(c->start()); +// } + BOOST_AUTO_TEST_SUITE_END() -- 2.7.4 From 94f4fbe894f1b987af9cb4183f14f8c5c43010be Mon Sep 17 00:00:00 2001 From: Jan Olszak Date: Mon, 12 Oct 2015 12:43:10 +0200 Subject: [PATCH 06/16] lxcpp: Stopping container's init process [Feature] Terminating container's init process [Cause] N/A [Solution] N/A [Verification] Build, install and run tests Change-Id: I77af77016d06555b6eea3e133e25a44574580c67 --- common/utils/signal.cpp | 11 +++++++ common/utils/signal.hpp | 1 + libs/lxcpp/commands/stop.cpp | 51 ++++++++++++++++++++++++++++++ libs/lxcpp/commands/stop.hpp | 56 +++++++++++++++++++++++++++++++++ libs/lxcpp/container-impl.cpp | 4 ++- libs/lxcpp/utils.cpp | 4 +-- tests/unit_tests/lxcpp/ut-container.cpp | 19 +++++------ 7 files changed, 134 insertions(+), 12 deletions(-) create mode 100644 libs/lxcpp/commands/stop.cpp create mode 100644 libs/lxcpp/commands/stop.hpp diff --git a/common/utils/signal.cpp b/common/utils/signal.cpp index bad3b0b..51c45b1 100644 --- a/common/utils/signal.cpp +++ b/common/utils/signal.cpp @@ -133,6 +133,17 @@ void signalIgnore(const std::initializer_list& signals) } } +void sendSignal(const pid_t pid, const int sigNum) +{ + if (-1 == ::kill(pid, sigNum)) { + const std::string msg = "Error during killing pid: " + std::to_string(pid) + + " sigNum: " + std::to_string(sigNum) + + ": " + getSystemErrorMessage(); + LOGE(msg); + throw UtilsException(msg); + } +} + } // namespace utils diff --git a/common/utils/signal.hpp b/common/utils/signal.hpp index 3ecf166..f4a42e3 100644 --- a/common/utils/signal.hpp +++ b/common/utils/signal.hpp @@ -36,6 +36,7 @@ void signalBlockAllExcept(const std::initializer_list& signals); void signalBlock(const int sigNum); void signalUnblock(const int sigNum); void signalIgnore(const std::initializer_list& signals); +void sendSignal(const pid_t pid, const int sigNum); } // namespace utils diff --git a/libs/lxcpp/commands/stop.cpp b/libs/lxcpp/commands/stop.cpp new file mode 100644 index 0000000..111ef9f --- /dev/null +++ b/libs/lxcpp/commands/stop.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2015 Samsung Electronics Co., Ltd All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @author Jan Olszak (j.olszak@samsung.com) + * @brief Implementation of stopping a container + */ + +#include "lxcpp/commands/stop.hpp" +#include "lxcpp/exception.hpp" +#include "lxcpp/process.hpp" + +#include "logger/logger.hpp" +#include "utils/signal.hpp" + +namespace lxcpp { + +Stop::Stop(ContainerConfig &config) + : mConfig(config) +{ +} + +Stop::~Stop() +{ +} + +void Stop::execute() +{ + LOGD("Stopping container: " << mConfig.mName); + + // TODO: Use initctl/systemd-initctl if available in container + + utils::sendSignal(mConfig.mInitPid, SIGTERM); +} + +} // namespace lxcpp diff --git a/libs/lxcpp/commands/stop.hpp b/libs/lxcpp/commands/stop.hpp new file mode 100644 index 0000000..291a0cd --- /dev/null +++ b/libs/lxcpp/commands/stop.hpp @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2015 Samsung Electronics Co., Ltd All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @author Jan Olszak (j.olszak@samsung.com) + * @brief Implementation of stopping a container + */ + +#ifndef LXCPP_COMMANDS_STOP_HPP +#define LXCPP_COMMANDS_STOP_HPP + +#include "lxcpp/commands/command.hpp" +#include "lxcpp/container-config.hpp" + +#include + + +namespace lxcpp { + + +class Stop final: Command { +public: + /** + * Stops the container + * + * @param config container's config + */ + Stop(ContainerConfig &config); + ~Stop(); + + void execute(); + +private: + ContainerConfig &mConfig; +}; + + +} // namespace lxcpp + + +#endif // LXCPP_COMMANDS_STOP_HPP diff --git a/libs/lxcpp/container-impl.cpp b/libs/lxcpp/container-impl.cpp index 9ff4b52..d3258aa 100644 --- a/libs/lxcpp/container-impl.cpp +++ b/libs/lxcpp/container-impl.cpp @@ -29,6 +29,7 @@ #include "lxcpp/capability.hpp" #include "lxcpp/commands/attach.hpp" #include "lxcpp/commands/start.hpp" +#include "lxcpp/commands/stop.hpp" #include "lxcpp/commands/prep-host-terminal.hpp" #include "logger/logger.hpp" @@ -187,7 +188,8 @@ void ContainerImpl::stop() // TODO: things to do when shuttting down the container: // - close PTY master FDs from the config so we won't keep PTYs open - throw NotImplementedException(); + Stop stop(mConfig); + stop.execute(); } void ContainerImpl::freeze() diff --git a/libs/lxcpp/utils.cpp b/libs/lxcpp/utils.cpp index 0c731b5..d0418af 100644 --- a/libs/lxcpp/utils.cpp +++ b/libs/lxcpp/utils.cpp @@ -54,8 +54,8 @@ void setProcTitle(const std::string &title) // Skip the first 47 fields, entries 48-49 are ARG_START and ARG_END. std::advance(it, 47); - unsigned long argStart = std::stol(*it++); - unsigned long argEnd = std::stol(*it++); + unsigned long argStart = std::stoul(*it++); + unsigned long argEnd = std::stoul(*it); f.close(); diff --git a/tests/unit_tests/lxcpp/ut-container.cpp b/tests/unit_tests/lxcpp/ut-container.cpp index ff72b94..1c1a76c 100644 --- a/tests/unit_tests/lxcpp/ut-container.cpp +++ b/tests/unit_tests/lxcpp/ut-container.cpp @@ -109,14 +109,15 @@ BOOST_AUTO_TEST_CASE(SetLogger) BadArgument); } -// BOOST_AUTO_TEST_CASE(StartStop) -// { -// auto c = std::unique_ptr(createContainer("StartStop", "/")); -// BOOST_CHECK_NO_THROW(c->setInit(COMMAND)); -// BOOST_CHECK_NO_THROW(c->setLogger(logger::LogType::LOG_PERSISTENT_FILE, -// logger::LogLevel::DEBUG, -// LOGGER_FILE)); -// BOOST_CHECK_NO_THROW(c->start()); -// } +BOOST_AUTO_TEST_CASE(StartStop) +{ + auto c = std::unique_ptr(createContainer("StartStop", "/")); + BOOST_CHECK_NO_THROW(c->setInit(COMMAND)); + BOOST_CHECK_NO_THROW(c->setLogger(logger::LogType::LOG_PERSISTENT_FILE, + logger::LogLevel::DEBUG, + LOGGER_FILE)); + BOOST_CHECK_NO_THROW(c->start()); + BOOST_CHECK_NO_THROW(c->stop()); +} BOOST_AUTO_TEST_SUITE_END() -- 2.7.4 From 046432b8d6840b9d70891caaab2c5155229e2f77 Mon Sep 17 00:00:00 2001 From: "Maciej J. Karpiuk" Date: Mon, 12 Oct 2015 12:29:54 +0200 Subject: [PATCH 07/16] vasum: housekeeping, removal of 'using namespace std;' [Feature] N/A [Cause] N/A [Solution] N/A [Verification] N/A Change-Id: I583b5ad44cd0d460c6b0c4db0d05217ab05cc2e4 --- cli/command-line-interface.cpp | 126 ++++++++++++++++++++--------------------- client/vasum-client-impl.cpp | 43 +++++++------- client/vasum-client-impl.hpp | 2 +- client/vasum-client.cpp | 4 +- server/netdev.cpp | 69 +++++++++++----------- 5 files changed, 119 insertions(+), 125 deletions(-) diff --git a/cli/command-line-interface.cpp b/cli/command-line-interface.cpp index 27d7cac..ad5400d 100644 --- a/cli/command-line-interface.cpp +++ b/cli/command-line-interface.cpp @@ -44,8 +44,6 @@ #include #include -using namespace std; - namespace vasum { namespace cli { @@ -92,23 +90,23 @@ std::string netdevToString(const VsmNetdev& netdev) return out; } -typedef vector> Table; +typedef std::vector> Table; -ostream& operator<<(ostream& out, const Table& table) +std::ostream& operator<<(std::ostream& out, const Table& table) { - vector sizes; + std::vector sizes; for (const auto& row : table) { if (sizes.size() < row.size()) { sizes.resize(row.size()); } for (size_t i = 0; i < row.size(); ++i) { - sizes[i] = max(sizes[i], row[i].length()); + sizes[i] = std::max(sizes[i], row[i].length()); } } for (const auto& row : table) { for (size_t i = 0; i < row.size(); ++i) { - out << left << setw(sizes[i]+2) << row[i]; + out << std::left << std::setw(sizes[i]+2) << row[i]; } out << "\n"; } @@ -129,7 +127,7 @@ enum macvlan_mode macvlanFromString(const std::string& mode) { if (mode == "passthru") { return MACVLAN_MODE_PASSTHRU; } - throw runtime_error("Unsupported macvlan mode"); + throw std::runtime_error("Unsupported macvlan mode"); } void buildZoneList(std::vector& list) @@ -144,7 +142,7 @@ void buildZoneList(std::vector& list) vsm_array_string_free(ids); } -void buildNetdevList(const std::string& zone,std::vector& list) +void buildNetdevList(const std::string& zone, std::vector& list) { using namespace std::placeholders; VsmArrayString ids; @@ -167,9 +165,9 @@ const std::vector CommandLineInterface::buildCompletionList(const A } ArgSpec as = mArgsSpec[a.size() - 2]; - string::size_type s = 0U; - string::size_type e = s; - while (e != string::npos) { + std::string::size_type s = 0U; + std::string::size_type e = s; + while (e != std::string::npos) { e = as.format.find('|', s); std::string ss = as.format.substr(s, e - s); s = e + 1; @@ -197,12 +195,12 @@ void CommandLineInterface::connect() CommandLineInterface::client = vsm_client_create(); if (CommandLineInterface::client == nullptr) { - throw runtime_error("Can't create client"); + throw std::runtime_error("Can't create client"); } status = vsm_connect(client); if (VSMCLIENT_SUCCESS != status) { - string msg = vsm_get_status_message(CommandLineInterface::client); + std::string msg = vsm_get_status_message(CommandLineInterface::client); vsm_client_free(CommandLineInterface::client); CommandLineInterface::client = nullptr; throw IOException(msg); @@ -211,7 +209,7 @@ void CommandLineInterface::connect() void CommandLineInterface::disconnect() { - string msg; + std::string msg; VsmStatus status; if (CommandLineInterface::client == nullptr) { @@ -227,17 +225,17 @@ void CommandLineInterface::disconnect() CommandLineInterface::client = nullptr; if (VSMCLIENT_SUCCESS != status) { - throw runtime_error(msg); + throw std::runtime_error(msg); } } -void CommandLineInterface::executeCallback(const function& fun) +void CommandLineInterface::executeCallback(const std::function& fun) { CommandLineInterface::connect(); VsmStatus status = fun(CommandLineInterface::client); if (VSMCLIENT_SUCCESS != status) { - throw runtime_error(vsm_get_status_message(CommandLineInterface::client)); + throw std::runtime_error(vsm_get_status_message(CommandLineInterface::client)); } } @@ -306,7 +304,7 @@ void set_active_zone(const Args& argv) using namespace std::placeholders; if (argv.size() < 2) { - throw runtime_error("Not enough parameters"); + throw std::runtime_error("Not enough parameters"); } CommandLineInterface::executeCallback(bind(vsm_set_active_zone, _1, argv[1].c_str())); @@ -317,7 +315,7 @@ void create_zone(const Args& argv) using namespace std::placeholders; if (argv.size() < 2) { - throw runtime_error("Not enough parameters"); + throw std::runtime_error("Not enough parameters"); } if (argv.size() >= 3 && !argv[2].empty()) { @@ -332,7 +330,7 @@ void destroy_zone(const Args& argv) using namespace std::placeholders; if (argv.size() < 2) { - throw runtime_error("Not enough parameters"); + throw std::runtime_error("Not enough parameters"); } CommandLineInterface::executeCallback(bind(vsm_destroy_zone, _1, argv[1].c_str(), 1)); @@ -343,7 +341,7 @@ void shutdown_zone(const Args& argv) using namespace std::placeholders; if (argv.size() < 2) { - throw runtime_error("Not enough parameters"); + throw std::runtime_error("Not enough parameters"); } CommandLineInterface::executeCallback(bind(vsm_shutdown_zone, _1, argv[1].c_str())); @@ -354,7 +352,7 @@ void start_zone(const Args& argv) using namespace std::placeholders; if (argv.size() < 2) { - throw runtime_error("Not enough parameters"); + throw std::runtime_error("Not enough parameters"); } CommandLineInterface::executeCallback(bind(vsm_start_zone, _1, argv[1].c_str())); @@ -365,7 +363,7 @@ void console_zone(const Args& argv) using namespace std::placeholders; if (argv.size() < 2) { - throw runtime_error("Not enough parameters"); + throw std::runtime_error("Not enough parameters"); } VsmZone zone; @@ -373,7 +371,7 @@ void console_zone(const Args& argv) if (zoneStateToString(vsm_zone_get_state(zone)) != "RUNNING") { vsm_zone_free(zone); - throw runtime_error("Zone '" + argv[1] + "' is not running"); + throw std::runtime_error("Zone '" + argv[1] + "' is not running"); } std::string zonesPath = vsm_zone_get_rootfs(zone); @@ -389,7 +387,7 @@ void console_zone(const Args& argv) .add("-P").add(zonesPath.c_str()); if (!execv("/usr/bin/lxc-console", const_cast(args.c_array()))) { - throw runtime_error("Could not log into zone"); + throw std::runtime_error("Could not log into zone"); } } @@ -398,7 +396,7 @@ void lock_zone(const Args& argv) using namespace std::placeholders; if (argv.size() < 2) { - throw runtime_error("Not enough parameters"); + throw std::runtime_error("Not enough parameters"); } CommandLineInterface::executeCallback(bind(vsm_lock_zone, _1, argv[1].c_str())); @@ -409,7 +407,7 @@ void unlock_zone(const Args& argv) using namespace std::placeholders; if (argv.size() < 2) { - throw runtime_error("Not enough parameters"); + throw std::runtime_error("Not enough parameters"); } CommandLineInterface::executeCallback(bind(vsm_unlock_zone, _1, argv[1].c_str())); @@ -439,17 +437,17 @@ void get_zones_status(const Args& argv) for (VsmString* id = ids; *id; ++id) { VsmZone zone; CommandLineInterface::executeCallback(bind(vsm_lookup_zone_by_id, _1, *id, &zone)); - assert(string(vsm_zone_get_id(zone)) == string(*id)); - table.push_back({string(vsm_zone_get_id(zone)) == string(activeId) ? "YES" : "NO", + assert(std::string(vsm_zone_get_id(zone)) == std::string(*id)); + table.push_back({std::string(vsm_zone_get_id(zone)) == std::string(activeId) ? "YES" : "NO", vsm_zone_get_id(zone), zoneStateToString(vsm_zone_get_state(zone)), - to_string(vsm_zone_get_terminal(zone)), + std::to_string(vsm_zone_get_terminal(zone)), vsm_zone_get_rootfs(zone)}); vsm_zone_free(zone); } vsm_string_free(activeId); vsm_array_string_free(ids); - cout << table << endl; + std::cout << table << std::endl; } void get_zone_ids(const Args& /*argv*/) @@ -458,12 +456,12 @@ void get_zone_ids(const Args& /*argv*/) VsmArrayString ids; CommandLineInterface::executeCallback(bind(vsm_get_zone_ids, _1, &ids)); - string delim; + std::string delim; for (VsmString* id = ids; *id; ++id) { - cout << delim << *id; + std::cout << delim << *id; delim = ", "; } - cout << endl; + std::cout << std::endl; vsm_array_string_free(ids); } @@ -473,7 +471,7 @@ void get_active_zone(const Args& /*argv*/) VsmString id; CommandLineInterface::executeCallback(bind(vsm_get_active_zone_id, _1, &id)); - cout << id << endl; + std::cout << id << std::endl; vsm_string_free(id); } @@ -482,7 +480,7 @@ void grant_device(const Args& argv) using namespace std::placeholders; if (argv.size() < 3) { - throw runtime_error("Not enough parameters"); + throw std::runtime_error("Not enough parameters"); } uint32_t flags = O_RDWR; @@ -494,7 +492,7 @@ void revoke_device(const Args& argv) using namespace std::placeholders; if (argv.size() < 3) { - throw runtime_error("Not enough parameters"); + throw std::runtime_error("Not enough parameters"); } CommandLineInterface::executeCallback(bind(vsm_revoke_device, _1, argv[1].c_str(), argv[2].c_str())); @@ -505,13 +503,13 @@ void create_netdev(const Args& argv) using namespace std::placeholders; if (argv.size() < 3) { - throw runtime_error("Not enough parameters"); + throw std::runtime_error("Not enough parameters"); } std::string nettype = argv[2]; if (nettype == "phys") { if (argv.size() < 4) { - throw runtime_error("Not enough parameters"); + throw std::runtime_error("Not enough parameters"); } CommandLineInterface::executeCallback(bind(vsm_create_netdev_phys, _1, @@ -520,7 +518,7 @@ void create_netdev(const Args& argv) } else if (nettype == "veth") { if (argv.size() < 5) { - throw runtime_error("Not enough parameters"); + throw std::runtime_error("Not enough parameters"); } CommandLineInterface::executeCallback(bind(vsm_create_netdev_veth, _1, @@ -530,7 +528,7 @@ void create_netdev(const Args& argv) } else if (nettype == "macvlan") { if (argv.size() < 6) { - throw runtime_error("Not enough parameters"); + throw std::runtime_error("Not enough parameters"); } CommandLineInterface::executeCallback(bind(vsm_create_netdev_macvlan, _1, @@ -540,7 +538,7 @@ void create_netdev(const Args& argv) macvlanFromString(argv[5].c_str()))); } else - throw runtime_error("Wrong nettype option " + nettype); + throw std::runtime_error("Wrong nettype option " + nettype); } void destroy_netdev(const Args& argv) @@ -548,7 +546,7 @@ void destroy_netdev(const Args& argv) using namespace std::placeholders; if (argv.size() < 3) { - throw runtime_error("Not enough parameters"); + throw std::runtime_error("Not enough parameters"); } CommandLineInterface::executeCallback(bind(vsm_destroy_netdev, _1, @@ -561,7 +559,7 @@ void netdev_list(const Args& argv) using namespace std::placeholders; if (argv.size() < 2) { - throw runtime_error("Not enough parameters"); + throw std::runtime_error("Not enough parameters"); } if (argv.size() < 3) { VsmArrayString ids; @@ -569,15 +567,15 @@ void netdev_list(const Args& argv) _1, argv[1].c_str(), &ids)); - string delim; + std::string delim; for (VsmString* id = ids; *id; ++id) { - cout << delim << *id; + std::cout << delim << *id; delim = ", "; } if (delim.empty()) { - cout << "There is no network device in zone"; + std::cout << "There is no network device in zone"; } - cout << endl; + std::cout << std::endl; vsm_array_string_free(ids); } else { @@ -588,7 +586,7 @@ void netdev_list(const Args& argv) argv[1].c_str(), argv[2].c_str(), &netdev)); - cout << netdevToString(netdev) << endl; + std::cout << netdevToString(netdev) << std::endl; vsm_netdev_free(netdev); VsmAddrList addrs = NULL; @@ -601,9 +599,9 @@ void netdev_list(const Args& argv) for (unsigned i=0; i < listsize; ++i) { int type=vsm_addrlist_get_type(addrs, i); if (inet_ntop(type, vsm_addrlist_get_addr(addrs, i), buf, INET6_ADDRSTRLEN) == NULL) { - throw runtime_error("Wrong address received ["+std::to_string(i)+"] type="+std::to_string(type)); + throw std::runtime_error("Wrong address received ["+std::to_string(i)+"] type="+std::to_string(type)); } - cout << buf << "/" << vsm_addrlist_get_prefix(addrs, i) << endl; + std::cout << buf << "/" << vsm_addrlist_get_prefix(addrs, i) << std::endl; } vsm_addrlist_free(addrs); } @@ -613,31 +611,31 @@ void netdev_add_ip_addr(const Args& argv) { using namespace std::placeholders; if (argv.size() < 5) { - throw runtime_error("Not enough parameters"); + throw std::runtime_error("Not enough parameters"); } if (argv[3].find(':') == std::string::npos) { in_addr addr; if (inet_pton(AF_INET, argv[3].c_str(), &addr) != 1) { - throw runtime_error("Wrong address format"); + throw std::runtime_error("Wrong address format"); }; CommandLineInterface::executeCallback(bind(vsm_netdev_add_ipv4_addr, _1, argv[1].c_str(), argv[2].c_str(), &addr, - stoi(argv[4].c_str()))); + std::stoi(argv[4].c_str()))); } else { in6_addr addr; if (inet_pton(AF_INET6, argv[3].c_str(), &addr) != 1) { - throw runtime_error("Wrong address format"); + throw std::runtime_error("Wrong address format"); }; CommandLineInterface::executeCallback(bind(vsm_netdev_add_ipv6_addr, _1, argv[1].c_str(), argv[2].c_str(), &addr, - stoi(argv[4].c_str()))); + std::stoi(argv[4].c_str()))); } } @@ -645,31 +643,31 @@ void netdev_del_ip_addr(const Args& argv) { using namespace std::placeholders; if (argv.size() < 5) { - throw runtime_error("Not enough parameters"); + throw std::runtime_error("Not enough parameters"); } if (argv[3].find(':') == std::string::npos) { in_addr addr; if (inet_pton(AF_INET, argv[3].c_str(), &addr) != 1) { - throw runtime_error("Wrong address format"); + throw std::runtime_error("Wrong address format"); }; CommandLineInterface::executeCallback(bind(vsm_netdev_del_ipv4_addr, _1, argv[1].c_str(), argv[2].c_str(), &addr, - stoi(argv[4].c_str()))); + std::stoi(argv[4].c_str()))); } else { in6_addr addr; if (inet_pton(AF_INET6, argv[3].c_str(), &addr) != 1) { - throw runtime_error("Wrong address format"); + throw std::runtime_error("Wrong address format"); }; CommandLineInterface::executeCallback(bind(vsm_netdev_del_ipv6_addr, _1, argv[1].c_str(), argv[2].c_str(), &addr, - stoi(argv[4].c_str()))); + std::stoi(argv[4].c_str()))); } } @@ -678,7 +676,7 @@ void netdev_up(const Args& argv) using namespace std::placeholders; if (argv.size() < 3) { - throw runtime_error("Not enough parameters"); + throw std::runtime_error("Not enough parameters"); } CommandLineInterface::executeCallback(bind(vsm_netdev_up, _1, @@ -691,7 +689,7 @@ void netdev_down(const Args& argv) using namespace std::placeholders; if (argv.size() < 3) { - throw runtime_error("Not enough parameters"); + throw std::runtime_error("Not enough parameters"); } CommandLineInterface::executeCallback(bind(vsm_netdev_down, _1, diff --git a/client/vasum-client-impl.cpp b/client/vasum-client-impl.cpp index 41cc919..30cc74c 100644 --- a/client/vasum-client-impl.cpp +++ b/client/vasum-client-impl.cpp @@ -49,7 +49,6 @@ #include #include -using namespace std; using namespace utils; using namespace vasum; @@ -103,7 +102,7 @@ void convert(const api::ZoneInfoOut& info, Zone& zone) zone = vsmZone; } -string toString(const in_addr* addr) +std::string toString(const in_addr* addr) { char buf[INET_ADDRSTRLEN]; const char* ret = inet_ntop(AF_INET, addr, buf, INET_ADDRSTRLEN); @@ -113,7 +112,7 @@ string toString(const in_addr* addr) return ret; } -string toString(const in6_addr* addr) +std::string toString(const in6_addr* addr) { char buf[INET6_ADDRSTRLEN]; const char* ret = inet_ntop(AF_INET6, addr, buf, INET6_ADDRSTRLEN); @@ -123,9 +122,9 @@ string toString(const in6_addr* addr) return ret; } -bool readFirstLineOfFile(const string& path, string& ret) +bool readFirstLineOfFile(const std::string& path, std::string& ret) { - ifstream file(path); + std::ifstream file(path); if (!file) { return false; } @@ -146,7 +145,7 @@ Client::Status::Status() { } -Client::Status::Status(VsmStatus status, const string& msg) +Client::Status::Status(VsmStatus status, const std::string& msg) : mVsmStatus(status), mMsg(msg) { } @@ -182,7 +181,7 @@ ipc::epoll::EventPoll& Client::getEventPoll() const } } -VsmStatus Client::coverException(const function& worker) noexcept +VsmStatus Client::coverException(const std::function& worker) noexcept { try { worker(); @@ -202,7 +201,7 @@ VsmStatus Client::coverException(const function& worker) noexcept mStatus = Status(VSMCLIENT_CUSTOM_ERROR, ex.what()); } catch (const ipc::IPCException& ex) { mStatus = Status(VSMCLIENT_IO_ERROR, ex.what()); - } catch (const exception& ex) { + } catch (const std::exception& ex) { mStatus = Status(VSMCLIENT_CUSTOM_ERROR, ex.what()); } VsmStatus ret = mStatus.mVsmStatus; @@ -295,7 +294,7 @@ const char* Client::vsm_get_status_message() const noexcept VsmStatus Client::vsm_get_status() const noexcept { - lock_guard lock(mStatusMutex); + std::lock_guard lock(mStatusMutex); return mStatus.mVsmStatus; } @@ -354,14 +353,14 @@ VsmStatus Client::vsm_lookup_zone_by_pid(int pid, VsmString* id) noexcept return coverException([&] { IS_SET(id); - const string path = "/proc/" + to_string(pid) + "/cpuset"; + const std::string path = "/proc/" + std::to_string(pid) + "/cpuset"; - string cpuset; + std::string cpuset; if (!readFirstLineOfFile(path, cpuset)) { throw InvalidArgumentException("Process not found"); } - string zoneId; + std::string zoneId; if (!parseZoneIdFromCpuSet(cpuset, zoneId)) { throw OperationFailedException("unknown format of cpuset"); } @@ -407,7 +406,7 @@ VsmStatus Client::vsm_create_zone(const char* id, const char* tname) noexcept return coverException([&] { IS_SET(id); - string template_name = tname ? tname : "default"; + std::string template_name = tname ? tname : "default"; mClient->callSync( api::ipc::METHOD_CREATE_ZONE, std::make_shared(api::CreateZoneIn{ id, template_name }), @@ -552,7 +551,7 @@ VsmStatus Client::vsm_netdev_get_ip_addr(const char* id, std::vector addrAttrs; for(const auto& addrAttr : split(addrAttrs, attr.second, is_any_of(","))) { size_t pos = addrAttr.find(":"); - if (pos == string::npos) continue; + if (pos == std::string::npos) continue; if (addrAttr.substr(0, pos) == "prefixlen") { addr.prefix = atoi(addrAttr.substr(pos + 1).c_str()); @@ -610,7 +609,7 @@ VsmStatus Client::vsm_netdev_add_ipv4_addr(const char* id, IS_SET(netdevId); IS_SET(addr); - string value = "ip:" + toString(addr) + ",""prefixlen:" + to_string(prefix); + std::string value = "ip:" + toString(addr) + ",""prefixlen:" + std::to_string(prefix); mClient->callSync( api::ipc::METHOD_SET_NETDEV_ATTRS, std::make_shared( @@ -628,7 +627,7 @@ VsmStatus Client::vsm_netdev_add_ipv6_addr(const char* id, IS_SET(netdevId); IS_SET(addr); - string value = "ip:" + toString(addr) + ",""prefixlen:" + to_string(prefix); + std::string value = "ip:" + toString(addr) + ",""prefixlen:" + std::to_string(prefix); mClient->callSync( api::ipc::METHOD_SET_NETDEV_ATTRS, std::make_shared( @@ -647,7 +646,7 @@ VsmStatus Client::vsm_netdev_del_ipv4_addr(const char* id, IS_SET(addr); //CIDR notation - string ip = toString(addr) + "/" + to_string(prefix); + std::string ip = toString(addr) + "/" + std::to_string(prefix); mClient->callSync( api::ipc::METHOD_DELETE_NETDEV_IP_ADDRESS, std::make_shared( @@ -666,7 +665,7 @@ VsmStatus Client::vsm_netdev_del_ipv6_addr(const char* id, IS_SET(addr); //CIDR notation - string ip = toString(addr) + "/" + to_string(prefix); + std::string ip = toString(addr) + "/" + std::to_string(prefix); mClient->callSync( api::ipc::METHOD_DELETE_NETDEV_IP_ADDRESS, std::make_shared( @@ -684,8 +683,8 @@ VsmStatus Client::vsm_netdev_up(const char* id, const char* netdevId) noexcept mClient->callSync( api::ipc::METHOD_SET_NETDEV_ATTRS, std::make_shared( - api::SetNetDevAttrsIn{ id, netdevId, { { "flags", to_string(IFF_UP) }, - { "change", to_string(IFF_UP) } } })); + api::SetNetDevAttrsIn{ id, netdevId, { { "flags", std::to_string(IFF_UP) }, + { "change", std::to_string(IFF_UP) } } })); }); } @@ -698,8 +697,8 @@ VsmStatus Client::vsm_netdev_down(const char* id, const char* netdevId) noexcept mClient->callSync( api::ipc::METHOD_SET_NETDEV_ATTRS, std::make_shared( - api::SetNetDevAttrsIn{ id, netdevId, { { "flags", to_string(~IFF_UP) }, - { "change", to_string(IFF_UP) } } })); + api::SetNetDevAttrsIn{ id, netdevId, { { "flags", std::to_string(~IFF_UP) }, + { "change", std::to_string(IFF_UP) } } })); }); } diff --git a/client/vasum-client-impl.hpp b/client/vasum-client-impl.hpp index dbc9001..7278e68 100644 --- a/client/vasum-client-impl.hpp +++ b/client/vasum-client-impl.hpp @@ -81,7 +81,7 @@ typedef struct { * * Client uses dbus API. */ -class Client { +class Client final { public: Client() noexcept; ~Client() noexcept; diff --git a/client/vasum-client.cpp b/client/vasum-client.cpp index 9cf15b3..ccc21d5 100644 --- a/client/vasum-client.cpp +++ b/client/vasum-client.cpp @@ -33,8 +33,6 @@ #define API __attribute__((visibility("default"))) #endif // API -using namespace std; - namespace { Client& getClient(VsmClient client) @@ -77,7 +75,7 @@ API VsmStatus vsm_get_dispatcher_type(VsmClient client, VsmDispacherType* dispac API VsmClient vsm_client_create() { - Client* clientPtr = new(nothrow) Client(); + Client* clientPtr = new(std::nothrow) Client(); return reinterpret_cast(clientPtr); } diff --git a/server/netdev.cpp b/server/netdev.cpp index 8387c84..9565ed3 100644 --- a/server/netdev.cpp +++ b/server/netdev.cpp @@ -63,18 +63,18 @@ #define BRIDGE_FLAGS_MASTER 1 #endif -using namespace std; using namespace utils; using namespace vasum::netlink; +using std::get; namespace vasum { namespace netdev { namespace { -string getUniqueVethName() +std::string getUniqueVethName() { - auto find = [](const ifaddrs* ifaddr, const string& name) -> bool { + auto find = [](const ifaddrs* ifaddr, const std::string& name) -> bool { for (const ifaddrs* ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { if (name == ifa->ifa_name) { return true; @@ -85,17 +85,17 @@ string getUniqueVethName() ifaddrs* ifaddr; getifaddrs(&ifaddr); - string newName; + std::string newName; int i = 0; do { - newName = "veth0" + to_string(++i); + newName = "veth0" + std::to_string(++i); } while (find(ifaddr, newName)); freeifaddrs(ifaddr); return newName; } -uint32_t getInterfaceIndex(const string& name) { +uint32_t getInterfaceIndex(const std::string& name) { uint32_t index = if_nametoindex(name.c_str()); if (!index) { const std::string msg = "Can't get " + name + " interface index (" + getSystemErrorMessage() + ")"; @@ -105,7 +105,7 @@ uint32_t getInterfaceIndex(const string& name) { return index; } -uint32_t getInterfaceIndex(const string& name, pid_t nsPid) { +uint32_t getInterfaceIndex(const std::string& name, pid_t nsPid) { NetlinkMessage nlm(RTM_GETLINK, NLM_F_REQUEST | NLM_F_ACK); ifinfomsg infoPeer = utils::make_clean(); infoPeer.ifi_family = AF_UNSPEC; @@ -126,14 +126,14 @@ int getIpFamily(const std::string& ip) return ip.find(':') == std::string::npos ? AF_INET : AF_INET6; } -void validateNetdevName(const string& name) +void validateNetdevName(const std::string& name) { if (name.size() <= 1 || name.size() >= IFNAMSIZ) { throw ZoneOperationException("Invalid netdev name format"); } } -void createPipedNetdev(const string& netdev1, const string& netdev2) +void createPipedNetdev(const std::string& netdev1, const std::string& netdev2) { validateNetdevName(netdev1); validateNetdevName(netdev2); @@ -156,7 +156,7 @@ void createPipedNetdev(const string& netdev1, const string& netdev2) send(nlm); } -void attachToBridge(const string& bridge, const string& netdev) +void attachToBridge(const std::string& bridge, const std::string& netdev) { validateNetdevName(bridge); validateNetdevName(netdev); @@ -184,7 +184,7 @@ void attachToBridge(const string& bridge, const string& netdev) close(fd); } -int setFlags(const string& name, uint32_t mask, uint32_t flags) +int setFlags(const std::string& name, uint32_t mask, uint32_t flags) { uint32_t index = getInterfaceIndex(name); NetlinkMessage nlm(RTM_NEWLINK, NLM_F_REQUEST | NLM_F_ACK); @@ -199,12 +199,12 @@ int setFlags(const string& name, uint32_t mask, uint32_t flags) return 0; } -void up(const string& netdev) +void up(const std::string& netdev) { setFlags(netdev, IFF_UP, IFF_UP); } -void moveToNS(const string& netdev, pid_t pid) +void moveToNS(const std::string& netdev, pid_t pid) { uint32_t index = getInterfaceIndex(netdev); NetlinkMessage nlm(RTM_NEWLINK, NLM_F_REQUEST | NLM_F_ACK); @@ -216,7 +216,7 @@ void moveToNS(const string& netdev, pid_t pid) send(nlm); } -void createMacvlan(const string& master, const string& slave, const macvlan_mode& mode) +void createMacvlan(const std::string& master, const std::string& slave, const macvlan_mode& mode) { validateNetdevName(master); validateNetdevName(slave); @@ -283,7 +283,7 @@ std::vector getIpAddresses(const pid_t nsPid, int family, uint32_t index) LOGE(msg); throw VasumException(msg); } - attrs.push_back(make_tuple("ip", buf)); + attrs.push_back(std::make_tuple("ip", buf)); break; default: response.skipAttribute(); @@ -372,19 +372,19 @@ void deleteIpAddress(const pid_t nsPid, } // namespace -void createVeth(const pid_t& nsPid, const string& nsDev, const string& hostDev) +void createVeth(const pid_t& nsPid, const std::string& nsDev, const std::string& hostDev) { - string hostVeth = getUniqueVethName(); + std::string hostVeth = getUniqueVethName(); LOGT("Creating veth: bridge: " << hostDev << ", port: " << hostVeth << ", zone: " << nsDev); createPipedNetdev(nsDev, hostVeth); try { attachToBridge(hostDev, hostVeth); up(hostVeth); moveToNS(nsDev, nsPid); - } catch(const exception& ex) { + } catch(const std::exception& ex) { try { destroyNetdev(hostVeth); - } catch (const exception& ex) { + } catch (const std::exception& ex) { LOGE("Can't destroy netdev pipe: " << hostVeth << ", " << nsDev); } throw; @@ -392,8 +392,8 @@ void createVeth(const pid_t& nsPid, const string& nsDev, const string& hostDev) } void createMacvlan(const pid_t& nsPid, - const string& nsDev, - const string& hostDev, + const std::string& nsDev, + const std::string& hostDev, const macvlan_mode& mode) { LOGT("Creating macvlan: host: " << hostDev << ", zone: " << nsDev << ", mode: " << mode); @@ -401,17 +401,17 @@ void createMacvlan(const pid_t& nsPid, try { up(nsDev); moveToNS(nsDev, nsPid); - } catch(const exception& ex) { + } catch(const std::exception& ex) { try { destroyNetdev(nsDev); - } catch (const exception& ex) { + } catch (const std::exception& ex) { LOGE("Can't destroy netdev: " << nsDev); } throw; } } -void movePhys(const pid_t& nsPid, const string& devId) +void movePhys(const pid_t& nsPid, const std::string& devId) { LOGT("Creating phys: dev: " << devId); moveToNS(devId, nsPid); @@ -437,7 +437,7 @@ std::vector listNetdev(const pid_t& nsPid) return interfaces; } -void destroyNetdev(const string& netdev, const pid_t pid) +void destroyNetdev(const std::string& netdev, const pid_t pid) { LOGT("Destroying netdev: " << netdev); validateNetdevName(netdev); @@ -451,7 +451,7 @@ void destroyNetdev(const string& netdev, const pid_t pid) send(nlm, pid); } -void createBridge(const string& netdev) +void createBridge(const std::string& netdev) { LOGT("Creating bridge: " << netdev); validateNetdevName(netdev); @@ -477,7 +477,7 @@ Attrs getAttrs(const pid_t nsPid, const std::string& netdev) { auto joinAddresses = [](const Attrs& attrs) -> std::string { bool first = true; - stringstream ss; + std::stringstream ss; for (const auto& attr : attrs) { ss << (first ? "" : ",") << get<0>(attr) << ":" << get<1>(attr); first = false; @@ -539,7 +539,7 @@ Attrs getAttrs(const pid_t nsPid, const std::string& netdev) void setAttrs(const pid_t nsPid, const std::string& netdev, const Attrs& attrs) { - const set supportedAttrs{"flags", "change", "type", "mtu", "link", "ipv4", "ipv6"}; + const std::set supportedAttrs{"flags", "change", "type", "mtu", "link", "ipv4", "ipv6"}; LOGT("Setting network device informations: " << netdev); validateNetdevName(netdev); @@ -581,8 +581,8 @@ void setAttrs(const pid_t nsPid, const std::string& netdev, const Attrs& attrs) } //TODO: Multiple addresses should be set at once (add support NLM_F_MULTI to NetlinkMessage). - vector ipv4; - vector ipv6; + std::vector ipv4; + std::vector ipv6; for (const auto& attr : attrs) { if (get<0>(attr) == "ipv4") { ipv4.push_back(get<1>(attr)); @@ -592,14 +592,14 @@ void setAttrs(const pid_t nsPid, const std::string& netdev, const Attrs& attrs) } } - auto setIp = [nsPid](const vector& ips, uint32_t index, int family) -> void { + auto setIp = [nsPid](const std::vector& ips, uint32_t index, int family) -> void { using namespace boost::algorithm; for (const auto& ip : ips) { Attrs attrs; - vector params; + std::vector params; for (const auto& addrAttr : split(params, ip, is_any_of(","))) { size_t pos = addrAttr.find(":"); - if (pos == string::npos || pos == addrAttr.length()) { + if (pos == std::string::npos || pos == addrAttr.length()) { const std::string msg = "Wrong input data format: ill formed address attribute: " + addrAttr; LOGE(msg); throw VasumException(msg); @@ -620,7 +620,7 @@ void deleteIpAddress(const pid_t nsPid, { uint32_t index = getInterfaceIndex(netdev, nsPid); size_t slash = ip.find('/'); - if (slash == string::npos) { + if (slash == std::string::npos) { const std::string msg = "Wrong address format: it is not CIDR notation: can't find '/'"; LOGE(msg); throw VasumException(msg); @@ -639,4 +639,3 @@ void deleteIpAddress(const pid_t nsPid, } //namespace netdev } //namespace vasum - -- 2.7.4 From 732fd0b180e78e53b791c18dc39a18f1fa8e8e30 Mon Sep 17 00:00:00 2001 From: Jan Olszak Date: Mon, 12 Oct 2015 17:45:21 +0200 Subject: [PATCH 08/16] lxcpp: Adding init process to UTS, PID, NET, IPC namespaces [Feature] Starting init in some namespaces. MNT and USER need more work. [Cause] N/A [Solution] Refactored Guard [Verification] Build, install and run tests Change-Id: I3c5ec5c833da34c9ece6acf2c4965e8c57ab966d --- libs/lxcpp/container-config.hpp | 13 +++++- libs/lxcpp/container-impl.cpp | 6 ++- libs/lxcpp/container-impl.hpp | 1 + libs/lxcpp/container.hpp | 2 + libs/lxcpp/guard/guard.cpp | 73 ++++++++++++++++++--------------- libs/lxcpp/guard/guard.hpp | 11 ++++- libs/lxcpp/guard/main.cpp | 11 +++-- tests/unit_tests/lxcpp/ut-container.cpp | 1 + 8 files changed, 78 insertions(+), 40 deletions(-) diff --git a/libs/lxcpp/container-config.hpp b/libs/lxcpp/container-config.hpp index c84b4ef..9990bdc 100644 --- a/libs/lxcpp/container-config.hpp +++ b/libs/lxcpp/container-config.hpp @@ -109,7 +109,15 @@ struct ContainerConfig { */ TerminalsConfig mTerminals; - ContainerConfig() : mGuardPid(-1), mInitPid(-1) {} + /** + * Namespace types used to create the container + * + * Set: setNamespaces() + * Get: none + */ + int mNamespaces; + + ContainerConfig() : mGuardPid(-1), mInitPid(-1), mNamespaces(0) {} CONFIG_REGISTER ( @@ -119,7 +127,8 @@ struct ContainerConfig { mInitPid, mInit, mLogger, - mTerminals + mTerminals, + mNamespaces ) }; diff --git a/libs/lxcpp/container-impl.cpp b/libs/lxcpp/container-impl.cpp index d3258aa..5f0da35 100644 --- a/libs/lxcpp/container-impl.cpp +++ b/libs/lxcpp/container-impl.cpp @@ -25,7 +25,6 @@ #include "lxcpp/exception.hpp" #include "lxcpp/process.hpp" #include "lxcpp/filesystem.hpp" -#include "lxcpp/namespace.hpp" #include "lxcpp/capability.hpp" #include "lxcpp/commands/attach.hpp" #include "lxcpp/commands/start.hpp" @@ -169,6 +168,11 @@ void ContainerImpl::setTerminalCount(const unsigned int count) mConfig.mTerminals.count = count; } +void ContainerImpl::setNamespaces(const int namespaces) +{ + mConfig.mNamespaces = namespaces; +} + void ContainerImpl::start() { // TODO: check config consistency and completeness somehow diff --git a/libs/lxcpp/container-impl.hpp b/libs/lxcpp/container-impl.hpp index 041303b..846536e 100644 --- a/libs/lxcpp/container-impl.hpp +++ b/libs/lxcpp/container-impl.hpp @@ -56,6 +56,7 @@ public: void setTerminalCount(const unsigned int count); const std::vector& getNamespaces() const; + void setNamespaces(const int namespaces); // Execution actions void start(); diff --git a/libs/lxcpp/container.hpp b/libs/lxcpp/container.hpp index e3d537f..ba32e64 100644 --- a/libs/lxcpp/container.hpp +++ b/libs/lxcpp/container.hpp @@ -26,6 +26,7 @@ #include "lxcpp/network-config.hpp" #include "lxcpp/logger-config.hpp" + #include #include @@ -62,6 +63,7 @@ public: const std::string &arg = "") = 0; virtual void setTerminalCount(const unsigned int count) = 0; + virtual void setNamespaces(const int namespaces) = 0; // Execution actions virtual void start() = 0; diff --git a/libs/lxcpp/guard/guard.cpp b/libs/lxcpp/guard/guard.cpp index 28e572a..4f56771 100644 --- a/libs/lxcpp/guard/guard.cpp +++ b/libs/lxcpp/guard/guard.cpp @@ -32,64 +32,71 @@ #include #include - namespace lxcpp { +namespace { -void startContainer(const ContainerConfig &cfg) +int startContainer(void* data) { - lxcpp::execve(cfg.mInit); + ContainerConfig& config = *static_cast(data); + + // TODO: container preparation part 2 + + PrepGuestTerminal terminals(config.mTerminals); + terminals.execute(); + + lxcpp::execve(config.mInit); + + return EXIT_FAILURE; } -int startGuard(int channelFD) +} // namespace + + +Guard::Guard(const int channelFD) + : mChannel(channelFD) { - ContainerConfig cfg; - utils::Channel channel(channelFD); - channel.setCloseOnExec(true); - config::loadFromFD(channel.getFD(), cfg); + mChannel.setCloseOnExec(true); + config::loadFromFD(mChannel.getFD(), mConfig); - logger::setupLogger(cfg.mLogger.getType(), - cfg.mLogger.getLevel(), - cfg.mLogger.getArg()); + logger::setupLogger(mConfig.mLogger.getType(), + mConfig.mLogger.getLevel(), + mConfig.mLogger.getArg()); - LOGD("Guard started, config & logging restored"); + LOGD("Config & logging restored"); try { LOGD("Setting the guard process title"); - const std::string title = "[LXCPP] " + cfg.mName + " " + cfg.mRootPath; + const std::string title = "[LXCPP] " + mConfig.mName + " " + mConfig.mRootPath; setProcTitle(title); } catch (std::exception &e) { // Ignore, this is optional LOGW("Failed to set the guard process title: " << e.what()); } +} - // TODO: container preparation part 1 - - // TODO: switch to clone - LOGD("Forking container's init process"); - pid_t pid = lxcpp::fork(); - - if (pid == 0) { - // TODO: container preparation part 2 +Guard::~Guard() +{ +} - PrepGuestTerminal terminals(cfg.mTerminals); - terminals.execute(); +int Guard::execute() +{ + // TODO: container preparation part 1 - startContainer(cfg); - ::_exit(EXIT_FAILURE); - } + const pid_t initPid = lxcpp::clone(startContainer, + &mConfig, + mConfig.mNamespaces); - cfg.mGuardPid = ::getpid(); - cfg.mInitPid = pid; + mConfig.mGuardPid = ::getpid(); + mConfig.mInitPid = initPid; - channel.write(cfg.mGuardPid); - channel.write(cfg.mInitPid); - channel.shutdown(); + mChannel.write(mConfig.mGuardPid); + mChannel.write(mConfig.mInitPid); + mChannel.shutdown(); - int status = lxcpp::waitpid(pid); + int status = lxcpp::waitpid(initPid); LOGD("Init exited with status: " << status); return status; } - } // namespace lxcpp diff --git a/libs/lxcpp/guard/guard.hpp b/libs/lxcpp/guard/guard.hpp index 78457ae..741d72a 100644 --- a/libs/lxcpp/guard/guard.hpp +++ b/libs/lxcpp/guard/guard.hpp @@ -28,11 +28,20 @@ #include "lxcpp/container-config.hpp" #include "utils/channel.hpp" + namespace lxcpp { +class Guard { +public: + Guard(const int channelFD); + ~Guard(); -int startGuard(int channelFD); + int execute(); +private: + utils::Channel mChannel; + ContainerConfig mConfig; +}; } // namespace lxcpp diff --git a/libs/lxcpp/guard/main.cpp b/libs/lxcpp/guard/main.cpp index 78c7476..ff26418 100644 --- a/libs/lxcpp/guard/main.cpp +++ b/libs/lxcpp/guard/main.cpp @@ -35,8 +35,6 @@ int main(int argc, char *argv[]) ::_exit(EXIT_FAILURE); } - int channelFD = std::stoi(argv[1]); - // NOTE: this might not be required now, but I leave it here not to forget. // We need to investigate this with vasum and think about possibility of // poorly written software that leaks file descriptors and might use LXCPP. @@ -48,5 +46,12 @@ int main(int argc, char *argv[]) } #endif - return lxcpp::startGuard(channelFD); + try { + int fd = std::stoi(argv[1]); + lxcpp::Guard guard(fd); + return guard.execute(); + } catch(std::exception& e) { + // LOGE("Unexpected: " << utils::getTypeName(e) << ": " << e.what()); + return EXIT_FAILURE; + } } diff --git a/tests/unit_tests/lxcpp/ut-container.cpp b/tests/unit_tests/lxcpp/ut-container.cpp index 1c1a76c..17eee93 100644 --- a/tests/unit_tests/lxcpp/ut-container.cpp +++ b/tests/unit_tests/lxcpp/ut-container.cpp @@ -116,6 +116,7 @@ BOOST_AUTO_TEST_CASE(StartStop) BOOST_CHECK_NO_THROW(c->setLogger(logger::LogType::LOG_PERSISTENT_FILE, logger::LogLevel::DEBUG, LOGGER_FILE)); + BOOST_CHECK_NO_THROW(c->setNamespaces(CLONE_NEWIPC | CLONE_NEWNET | CLONE_NEWPID | CLONE_NEWUTS)); BOOST_CHECK_NO_THROW(c->start()); BOOST_CHECK_NO_THROW(c->stop()); } -- 2.7.4 From fdd084637d80bc804cced5e846158e8ac31c2252 Mon Sep 17 00:00:00 2001 From: Lukasz Pawelczyk Date: Thu, 8 Oct 2015 13:28:31 +0200 Subject: [PATCH 09/16] Implement Container::console() [Feature] An ability to connect to a PTY given to the container [Cause] To be able to use the container [Solution] Use PTYs, connect to them and pass read/writes between stdin/stdout and pty master. [Verification] There is a simple test program in junk for now Additional changes with patchset: - extended signal functions - fixed signalFD class Change-Id: Ia6320ee32d537311ef2675eb79f3e837192251b8 --- common/utils/signal.cpp | 87 ++++++++- common/utils/signal.hpp | 9 +- common/utils/signalfd.cpp | 58 +++--- common/utils/signalfd.hpp | 14 +- junk/Makefile | 19 ++ junk/run-shell.cpp | 81 ++++++++ libs/ipc/epoll/event-poll.cpp | 2 +- libs/lxcpp/CMakeLists.txt | 4 +- libs/lxcpp/commands/console.cpp | 292 ++++++++++++++++++++++++++++ libs/lxcpp/commands/console.hpp | 97 +++++++++ libs/lxcpp/commands/prep-guest-terminal.cpp | 1 - libs/lxcpp/commands/prep-host-terminal.cpp | 4 +- libs/lxcpp/commands/prep-host-terminal.hpp | 4 +- libs/lxcpp/container-impl.cpp | 33 +--- libs/lxcpp/container-impl.hpp | 1 + libs/lxcpp/container.hpp | 1 + libs/lxcpp/terminal-config.hpp | 4 +- libs/lxcpp/terminal.cpp | 36 ++-- libs/lxcpp/terminal.hpp | 15 +- server/server.cpp | 4 +- tests/unit_tests/utils/ut-signalfd.cpp | 4 +- 21 files changed, 668 insertions(+), 102 deletions(-) create mode 100644 junk/Makefile create mode 100644 junk/run-shell.cpp create mode 100644 libs/lxcpp/commands/console.cpp create mode 100644 libs/lxcpp/commands/console.hpp diff --git a/common/utils/signal.cpp b/common/utils/signal.cpp index 51c45b1..d961879 100644 --- a/common/utils/signal.cpp +++ b/common/utils/signal.cpp @@ -48,13 +48,13 @@ void setSignalMask(int how, const ::sigset_t& set) void changeSignal(int how, const int sigNum) { ::sigset_t set; if(-1 == ::sigemptyset(&set)) { - const std::string msg = "Error in sigfillset: " + getSystemErrorMessage(); + const std::string msg = "Error in sigemptyset: " + getSystemErrorMessage(); LOGE(msg); throw UtilsException(msg); } if(-1 ==::sigaddset(&set, sigNum)) { - const std::string msg = "Error in sigdelset: " + getSystemErrorMessage(); + const std::string msg = "Error in sigaddset: " + getSystemErrorMessage(); LOGE(msg); throw UtilsException(msg); } @@ -76,6 +76,62 @@ void changeSignal(int how, const int sigNum) { return set; } +bool isSignalPending(const int sigNum) +{ + ::sigset_t set; + if (::sigpending(&set) == -1) { + const std::string msg = "Error in sigpending: " + getSystemErrorMessage(); + LOGE(msg); + throw UtilsException(msg); + } + + int ret = ::sigismember(&set, sigNum); + if (ret == -1) { + const std::string msg = "Error in sigismember: " + getSystemErrorMessage(); + LOGE(msg); + throw UtilsException(msg); + } + + return ret; +} + +bool waitForSignal(const int sigNum, int timeoutMs) +{ + int timeoutS = timeoutMs / 1000; + timeoutMs -= timeoutS * 1000; + + struct ::timespec timeout = { + timeoutS, + timeoutMs * 1000000 + }; + + ::sigset_t set; + if(-1 == ::sigemptyset(&set)) { + const std::string msg = "Error in sigemptyset: " + getSystemErrorMessage(); + LOGE(msg); + throw UtilsException(msg); + } + + if(-1 ==::sigaddset(&set, sigNum)) { + const std::string msg = "Error in sigaddset: " + getSystemErrorMessage(); + LOGE(msg); + throw UtilsException(msg); + } + + ::siginfo_t info; + if (::sigtimedwait(&set, &info, &timeout) == -1) { + if (errno == EAGAIN) { + return false; + } + + const std::string msg = "Error in sigtimedwait: " + getSystemErrorMessage(); + LOGE(msg); + throw UtilsException(msg); + } + + return true; +} + bool isSignalBlocked(const int sigNum) { ::sigset_t set = getSignalMask(); @@ -119,18 +175,36 @@ void signalUnblock(const int sigNum) changeSignal(SIG_UNBLOCK, sigNum); } -void signalIgnore(const std::initializer_list& signals) +std::vector> signalIgnore(const std::initializer_list& signals) { struct ::sigaction act; + struct ::sigaction old; act.sa_handler = SIG_IGN; + std::vector> oldAct; for(const int s: signals) { - if(-1 == ::sigaction(s, &act, nullptr)) { + if(-1 == ::sigaction(s, &act, &old)) { const std::string msg = "Error in sigaction: " + getSystemErrorMessage(); LOGE(msg); throw UtilsException(msg); } + oldAct.emplace_back(s, old); } + + return oldAct; +} + +struct ::sigaction signalSet(const int sigNum, const struct ::sigaction *sigAct) +{ + struct ::sigaction old; + + if(-1 == ::sigaction(sigNum, sigAct, &old)) { + const std::string msg = "Error in sigaction: " + getSystemErrorMessage(); + LOGE(msg); + throw UtilsException(msg); + } + + return old; } void sendSignal(const pid_t pid, const int sigNum) @@ -145,8 +219,3 @@ void sendSignal(const pid_t pid, const int sigNum) } } // namespace utils - - - - - diff --git a/common/utils/signal.hpp b/common/utils/signal.hpp index f4a42e3..cb64419 100644 --- a/common/utils/signal.hpp +++ b/common/utils/signal.hpp @@ -27,17 +27,24 @@ #include #include +#include + namespace utils { + ::sigset_t getSignalMask(); +bool isSignalPending(const int sigNum); +bool waitForSignal(const int sigNum, int timeoutMs); bool isSignalBlocked(const int sigNum); void signalBlockAllExcept(const std::initializer_list& signals); void signalBlock(const int sigNum); void signalUnblock(const int sigNum); -void signalIgnore(const std::initializer_list& signals); +std::vector> signalIgnore(const std::initializer_list& signals); +struct ::sigaction signalSet(const int sigNum, const struct ::sigaction *sigAct); void sendSignal(const pid_t pid, const int sigNum); + } // namespace utils diff --git a/common/utils/signalfd.cpp b/common/utils/signalfd.cpp index 61d04d5..0156532 100644 --- a/common/utils/signalfd.cpp +++ b/common/utils/signalfd.cpp @@ -35,9 +35,14 @@ namespace utils { SignalFD::SignalFD(ipc::epoll::EventPoll& eventPoll) :mEventPoll(eventPoll) { - ::sigset_t set = getSignalMask(); + int error = ::sigemptyset(&mSet); + if (error == -1) { + const std::string msg = "Error in sigemptyset: " + getSystemErrorMessage(); + LOGE(msg); + throw UtilsException(msg); + } - mFD = ::signalfd(-1, &set, SFD_CLOEXEC); + mFD = ::signalfd(-1, &mSet, SFD_CLOEXEC); if (mFD == -1) { const std::string msg = "Error in signalfd: " + getSystemErrorMessage(); LOGE(msg); @@ -51,6 +56,20 @@ SignalFD::~SignalFD() { mEventPoll.removeFD(mFD); utils::close(mFD); + + // Unblock the signals that have been blocked previously, but also eat + // them if they were pending. It seems that signals are delivered twice, + // independently for signalfd and async. If we don't eat them before + // unblocking they will be delivered immediately potentially doing harm. + for (const int sigNum : mBlockedSignals) { + waitForSignal(sigNum, 0); + + // Yes, there is a race here between waitForSignal and signalUnlock, but if + // a signal is sent at this point it's not by us, signalFD is inactive. So + // if that is the case I'd expect someone to have set some handler already. + + signalUnblock(sigNum); + } } int SignalFD::getFD() const @@ -62,35 +81,30 @@ void SignalFD::setHandler(const int sigNum, const Callback&& callback) { Lock lock(mMutex); - ::sigset_t set = getSignalMask(); - - int error = ::signalfd(mFD, &set, SFD_CLOEXEC); - if (error != mFD) { - const std::string msg = "Error in signalfd: " + getSystemErrorMessage(); - LOGE(msg); - throw UtilsException(msg); - } - - mCallbacks.insert({sigNum, callback}); -} - -void SignalFD::setHandlerAndBlock(const int sigNum, const Callback&& callback) -{ - Lock lock(mMutex); - bool isBlocked = isSignalBlocked(sigNum); - - ::sigset_t set = getSignalMask(); if(!isBlocked) { signalBlock(sigNum); + mBlockedSignals.push_back(sigNum); + } + + int error = ::sigaddset(&mSet, sigNum); + if (error == -1) { + const std::string msg = getSystemErrorMessage(); + LOGE("Error in signalfd: " << msg); + if(!isBlocked) { + signalUnblock(sigNum); + mBlockedSignals.pop_back(); + } + throw UtilsException("Error in signalfd: " + msg); } - int error = ::signalfd(mFD, &set, SFD_CLOEXEC); + error = ::signalfd(mFD, &mSet, SFD_CLOEXEC); if (error != mFD) { const std::string msg = getSystemErrorMessage(); LOGE("Error in signalfd: " << msg); if(!isBlocked) { signalUnblock(sigNum); + mBlockedSignals.pop_back(); } throw UtilsException("Error in signalfd: " + msg); } @@ -103,7 +117,7 @@ void SignalFD::handleInternal() signalfd_siginfo sigInfo; utils::read(mFD, &sigInfo, sizeof(sigInfo)); - LOGD("Got signal: " << sigInfo.ssi_signo); + LOGT("Got signal: " << sigInfo.ssi_signo); { Lock lock(mMutex); diff --git a/common/utils/signalfd.hpp b/common/utils/signalfd.hpp index 4efed4b..cc72ed9 100644 --- a/common/utils/signalfd.hpp +++ b/common/utils/signalfd.hpp @@ -34,6 +34,7 @@ #include #include #include +#include namespace utils { @@ -55,7 +56,7 @@ public: /** * Add a callback for a specified signal. - * Doesn't block the signal. + * Blocks the async signal handler if it's not already blocked. * * @param sigNum number of the signal * @param callback handler callback @@ -63,15 +64,6 @@ public: void setHandler(const int sigNum, const Callback&& callback); /** - * Add a callback for a specified signal - * Blocks the asynchronous signal handling. - * - * @param sigNum number of the signal - * @param callback handler callback - */ - void setHandlerAndBlock(const int sigNum, const Callback&& callback); - - /** * @return signal file descriptor */ int getFD() const; @@ -80,9 +72,11 @@ private: typedef std::unique_lock Lock; int mFD; + ::sigset_t mSet; std::mutex mMutex; ipc::epoll::EventPoll& mEventPoll; std::unordered_map mCallbacks; + std::vector mBlockedSignals; void handleInternal(); }; diff --git a/junk/Makefile b/junk/Makefile new file mode 100644 index 0000000..4b93782 --- /dev/null +++ b/junk/Makefile @@ -0,0 +1,19 @@ +C=clang +CPP=clang++ +NAME1=run-shell +CPPFLAGS=$(shell PKG_CONFIG_PATH=/usr/local/lib64/pkgconfig/ pkg-config --cflags liblxcpp libLogger) -g -std=c++11 +LDFLAGS=$(shell PKG_CONFIG_PATH=/usr/local/lib64/pkgconfig/ pkg-config --libs liblxcpp libLogger) -g + +OBJECTS1=$(NAME1).o +HEADERS= + +all: $(NAME1) + +%.o: %.cpp $(HEADERS) + $(CPP) -c -o $@ $< $(CPPFLAGS) + +$(NAME1): $(OBJECTS1) + $(CPP) $(OBJECTS1) $(LDFLAGS) -o $(NAME1) + +clean: + rm -f $(OBJECTS1) $(NAME1) diff --git a/junk/run-shell.cpp b/junk/run-shell.cpp new file mode 100644 index 0000000..81ee0d8 --- /dev/null +++ b/junk/run-shell.cpp @@ -0,0 +1,81 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace lxcpp; + +pid_t initPid = -1; + +void sighandler(int signal) +{ + // remove the log in a deffered manner + pid_t pid = fork(); + + if (pid == 0) + { + pid_t pid = fork(); + + if (pid == 0) + { + if (initPid > 0) + ::kill(initPid, SIGTERM); + sleep(11); + ::unlink("/tmp/lxcpp-shell.txt"); + + exit(0); + } + + exit(0); + } + + wait(); + exit(0); +} + +int main(int argc, char *argv[]) +{ + signal(SIGINT, &sighandler); + + logger::setupLogger(logger::LogType::LOG_STDERR, logger::LogLevel::TRACE); + LOGT("Color test: TRACE"); + LOGD("Color test: DEBUG"); + LOGI("Color test: INFO"); + LOGW("Color test: WARN"); + LOGE("Color test: ERROR"); + + logger::setupLogger(logger::LogType::LOG_STDERR, logger::LogLevel::DEBUG); + //logger::setupLogger(logger::LogType::LOG_FILE, logger::LogLevel::TRACE, "/tmp/lxcpp-shell.txt"); + + std::vector args; + args.push_back("/bin/bash"); + + try + { + Container* c = createContainer("test", "/"); + c->setInit(args); + c->setLogger(logger::LogType::LOG_FILE, logger::LogLevel::TRACE, "/tmp/lxcpp-shell.txt"); + c->setTerminalCount(4); + c->start(); + c->console(); + // You could run the console for the second time to see if it can be reattached + //c->console(); + + initPid = c->getInitPid(); + + delete c; + } + catch (const std::exception &e) + { + std::cout << "EXCEPTION: " << e.what() << std::endl; + } + + sighandler(3); + + return 0; +} diff --git a/libs/ipc/epoll/event-poll.cpp b/libs/ipc/epoll/event-poll.cpp index c4b1f40..8fe41b2 100644 --- a/libs/ipc/epoll/event-poll.cpp +++ b/libs/ipc/epoll/event-poll.cpp @@ -87,7 +87,7 @@ void EventPoll::addFD(const int fd, const Events events, Callback&& callback) void EventPoll::modifyFD(const int fd, const Events events) { - // No need to lock and check mCallbacks map + // No need to lock as modify doesn't touch the mCallbacks map if (!modifyFDInternal(fd, events)) { const std::string msg = "Could not modify fd: " + std::to_string(fd); LOGE(msg); diff --git a/libs/lxcpp/CMakeLists.txt b/libs/lxcpp/CMakeLists.txt index 0d35bad..e3056dc 100644 --- a/libs/lxcpp/CMakeLists.txt +++ b/libs/lxcpp/CMakeLists.txt @@ -51,7 +51,7 @@ TARGET_COMPILE_DEFINITIONS(${PROJECT_NAME} PRIVATE ATTACH_PATH="${LIBEXEC_DIR}/${ATTACH_CODENAME}" ) -ADD_DEPENDENCIES(${PROJECT_NAME} Common Logger) +ADD_DEPENDENCIES(${PROJECT_NAME} Common Logger Config Ipc) ## Link libraries ############################################################## FIND_PACKAGE(Boost COMPONENTS system filesystem) @@ -59,7 +59,7 @@ PKG_CHECK_MODULES(LXCPP_DEPS REQUIRED glib-2.0) INCLUDE_DIRECTORIES(${LIBS_FOLDER} ${COMMON_FOLDER}) INCLUDE_DIRECTORIES(SYSTEM ${Boost_INCLUDE_DIRS} ${LXCPP_DEPS_INCLUDE_DIRS} ${JSON_C_INCLUDE_DIRS}) -TARGET_LINK_LIBRARIES(${PROJECT_NAME} Common ${pkgs_LDFLAGS} ${Boost_LIBRARIES} Logger Config util) +TARGET_LINK_LIBRARIES(${PROJECT_NAME} Common ${pkgs_LDFLAGS} util ${Boost_LIBRARIES} Logger Config Ipc) ## Generate the pc file ######################################################## CONFIGURE_FILE(${PC_FILE}.in ${CMAKE_CURRENT_BINARY_DIR}/${PC_FILE} @ONLY) diff --git a/libs/lxcpp/commands/console.cpp b/libs/lxcpp/commands/console.cpp new file mode 100644 index 0000000..3210945 --- /dev/null +++ b/libs/lxcpp/commands/console.cpp @@ -0,0 +1,292 @@ +/* + * Copyright (C) 2015 Samsung Electronics Co., Ltd All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @author Lukasz Pawelczyk (l.pawelczyk@samsung.com) + * @brief Implementation of the console + */ + +#include "lxcpp/commands/console.hpp" +#include "lxcpp/exception.hpp" +#include "lxcpp/terminal.hpp" +#include "lxcpp/credentials.hpp" + +#include "ipc/epoll/event-poll.hpp" +#include "logger/logger.hpp" +#include "utils/fd-utils.hpp" +#include "utils/signal.hpp" + +#include +#include +#include +#include +#include + + +namespace lxcpp { + + +Console::Console(TerminalsConfig &terminals, unsigned int terminalNum) + : mTerminals(terminals), + mTerminalNum(terminalNum), + mServiceMode(false), + mQuitReason(ConsoleQuitReason::NONE), + mEventPoll(), + mSignalFD(mEventPoll), + appToTermOffset(0), + termToAppOffset(0) +{ + if (terminalNum >= terminals.count) { + const std::string msg = "Requested terminal number does not exist"; + LOGE(msg); + throw TerminalException(msg); + } +} + +Console::~Console() +{ +} + +void Console::execute() +{ + if (!lxcpp::isatty(STDIN_FILENO) || !lxcpp::isatty(STDOUT_FILENO)) { + const std::string msg = "Standard input is not a terminal, cannot launch the console"; + LOGE(msg); + throw TerminalException(msg); + } + + LOGD("Launching the console with: " << mTerminals.count << " pseudoterminal(s) on the guest side."); + std::cout << "Connected to the zone, escape characted is ^] or ^a q." << std::endl; + std::cout << "If the container has just a shell remember to set TERM to be equal to the one of your own terminal." << std::endl; + std::cout << "Terminal number: " << mTerminalNum << ", use ^a n/p to switch between them." << std::endl; + + setupTTY(); + resizePTY(); + + using namespace std::placeholders; + mEventPoll.addFD(STDIN_FILENO, EPOLLIN, std::bind(&Console::onStdInput, this, _1, _2)); + mEventPoll.addFD(STDOUT_FILENO, 0, std::bind(&Console::onStdOutput, this, _1, _2)); + mEventPoll.addFD(getCurrentFD(), EPOLLIN, std::bind(&Console::onPTY, this, _1, _2)); + + while (mQuitReason == ConsoleQuitReason::NONE) { + mEventPoll.dispatchIteration(-1); + } + + mEventPoll.removeFD(getCurrentFD()); + mEventPoll.removeFD(STDIN_FILENO); + mEventPoll.removeFD(STDOUT_FILENO); + + restoreTTY(); + + switch (mQuitReason) { + case ConsoleQuitReason::USER: + std::cout << std::endl << "User requested quit" << std::endl; + break; + case ConsoleQuitReason::ERR: + std::cout << std::endl << "There has been an error on the terminal, quitting" << std::endl; + break; + case ConsoleQuitReason::HUP: + std::cout << std::endl << "Terminal disconnected, quitting" << std::endl; + break; + default: + std::cout << std::endl << "Unknown error, quitting" << std::endl; + break; + } + + // make the class reusable with subsequent execute()s + mQuitReason = ConsoleQuitReason::NONE; + mServiceMode = false; +} + +void Console::setupTTY() +{ + // save signal state, ignore several signal, setup resize window signal + mSignalStates = utils::signalIgnore({SIGQUIT, SIGTERM, SIGINT, SIGHUP, SIGPIPE, SIGWINCH}); + mSignalFD.setHandler(SIGWINCH, std::bind(&Console::resizePTY, this)); + + // save terminal state + lxcpp::tcgetattr(STDIN_FILENO, &mTTYState); + + // set the current terminal in raw mode: + struct termios newTTY = mTTYState; + ::cfmakeraw(&newTTY); + lxcpp::tcsetattr(STDIN_FILENO, TCSAFLUSH, &newTTY); +} + +void Console::resizePTY() +{ + // resize the underlying PTY terminal to the size of the user terminal + struct winsize wsz; + utils::ioctl(STDIN_FILENO, TIOCGWINSZ, &wsz); + utils::ioctl(getCurrentFD(), TIOCSWINSZ, &wsz); +} + +void Console::restoreTTY() +{ + // restore signal state + for (const auto sigInfo : mSignalStates) { + utils::signalSet(sigInfo.first, &sigInfo.second); + } + + // restore terminal state + lxcpp::tcsetattr(STDIN_FILENO, TCSAFLUSH, &mTTYState); +} + +void Console::onPTY(int fd, ipc::epoll::Events events) +{ + if ((events & EPOLLIN) == EPOLLIN) { + const size_t avail = IO_BUFFER_SIZE - appToTermOffset; + char *buf = appToTerm + appToTermOffset; + + const ssize_t read = ::read(fd, buf, avail); + appToTermOffset += read; + + if (appToTermOffset) { + mEventPoll.modifyFD(STDOUT_FILENO, EPOLLOUT); + } + } + + if ((events & EPOLLOUT) == EPOLLOUT && termToAppOffset) { + const ssize_t written = ::write(fd, termToApp, termToAppOffset); + ::memmove(termToApp, termToApp + written, termToAppOffset - written); + termToAppOffset -= written; + + if (termToAppOffset == 0) { + mEventPoll.modifyFD(fd, EPOLLIN); + } + } + + checkForError(events); +} + +void Console::onStdInput(int fd, ipc::epoll::Events events) +{ + if ((events & EPOLLIN) == EPOLLIN) { + const size_t avail = IO_BUFFER_SIZE - termToAppOffset; + char *buf = termToApp + termToAppOffset; + const ssize_t read = ::read(fd, buf, avail); + + if (read == 1 && handleSpecial(buf[0])) { + return; + } + + termToAppOffset += read; + + if (termToAppOffset) { + mEventPoll.modifyFD(getCurrentFD(), EPOLLIN | EPOLLOUT); + } + } + + checkForError(events); +} + +void Console::onStdOutput(int fd, ipc::epoll::Events events) +{ + if ((events & EPOLLOUT) == EPOLLOUT && appToTermOffset) { + const ssize_t written = ::write(fd, appToTerm, appToTermOffset); + ::memmove(appToTerm, appToTerm + written, appToTermOffset - written); + appToTermOffset -= written; + + if (appToTermOffset == 0) { + mEventPoll.modifyFD(fd, 0); + } + } + + checkForError(events); +} + +void Console::checkForError(ipc::epoll::Events events) +{ + // TODO: ignore EPOLLHUP for now, this allows us to cycle through not + // connected terminals. When we can handle full containers with getty() + // processes we'll decide what to do about that. +#if 0 + if ((events & EPOLLHUP) == EPOLLHUP) { + mQuitReason = ConsoleQuitReason::HUP; + } +#endif + if ((events & EPOLLERR) == EPOLLERR) { + mQuitReason = ConsoleQuitReason::ERR; + } +} + +bool Console::handleSpecial(const char key) +{ + if (mServiceMode) { + mServiceMode = false; + + switch(key) { + case 'q': + mQuitReason = ConsoleQuitReason::USER; + return true; + case 'n': + consoleChange(ConsoleChange::NEXT); + return true; + case 'p': + consoleChange(ConsoleChange::PREV); + return true; + default: + return true; + } + } + + if (key == 0x1d) { // ^] + mQuitReason = ConsoleQuitReason::USER; + return true; + } + if (key == 0x01) { // ^a + mServiceMode = true; + return true; + } + + return false; +} + +void Console::consoleChange(ConsoleChange direction) +{ + mEventPoll.removeFD(getCurrentFD()); + + if (direction == ConsoleChange::NEXT) { + ++mTerminalNum; + } else if (direction == ConsoleChange::PREV) { + --mTerminalNum; + } + + mTerminalNum = (mTerminalNum + mTerminals.count) % mTerminals.count; + + int mode = EPOLLIN; + if (termToAppOffset) { + mode &= EPOLLOUT; + } + + restoreTTY(); + std::cout << "Terminal number: " << mTerminalNum << std::endl; + setupTTY(); + resizePTY(); + + using namespace std::placeholders; + mEventPoll.addFD(getCurrentFD(), mode, std::bind(&Console::onPTY, this, _1, _2)); +} + +int Console::getCurrentFD() const +{ + return mTerminals.PTYs[mTerminalNum].masterFD.value; +} + + +} // namespace lxcpp diff --git a/libs/lxcpp/commands/console.hpp b/libs/lxcpp/commands/console.hpp new file mode 100644 index 0000000..93ac248 --- /dev/null +++ b/libs/lxcpp/commands/console.hpp @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2015 Samsung Electronics Co., Ltd All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @author Lukasz Pawelczyk (l.pawelczyk@samsung.com) + * @brief Headers for the console + */ + +#ifndef LXCPP_COMMANDS_CONSOLE_HPP +#define LXCPP_COMMANDS_CONSOLE_HPP + +#include "lxcpp/commands/command.hpp" +#include "lxcpp/terminal-config.hpp" +#include "lxcpp/terminal.hpp" + +#include "ipc/epoll/event-poll.hpp" +#include "utils/signalfd.hpp" + +#include +#include + + +namespace lxcpp { + + +class Console final: Command { +public: + /** + * Launches the console on the current terminal + * + * @param terminals container's terminals config + * @param terminalNum initial terminal to attach to + */ + Console(TerminalsConfig &terminals, unsigned int terminalNum = 0); + ~Console(); + + void execute(); + +private: + enum class ConsoleQuitReason : int { + NONE = 0, + USER = 1, + HUP = 2, + ERR = 3 + }; + enum class ConsoleChange : int { + NEXT = 0, + PREV = 1 + }; + static const int IO_BUFFER_SIZE = 1024; + + TerminalsConfig &mTerminals; + int mTerminalNum; + bool mServiceMode; + ConsoleQuitReason mQuitReason; + ipc::epoll::EventPoll mEventPoll; + utils::SignalFD mSignalFD; + std::vector> mSignalStates; + struct termios mTTYState; + + char appToTerm[IO_BUFFER_SIZE]; + int appToTermOffset; + char termToApp[IO_BUFFER_SIZE]; + int termToAppOffset; + + void setupTTY(); + void restoreTTY(); + void resizePTY(); + void onPTY(int fd, ipc::epoll::Events events); + void onStdInput(int fd, ipc::epoll::Events events); + void onStdOutput(int fd, ipc::epoll::Events events); + void checkForError(ipc::epoll::Events events); + bool handleSpecial(char key); + void consoleChange(ConsoleChange direction); + int getCurrentFD() const; +}; + + +} // namespace lxcpp + + +#endif // LXCPP_COMMANDS_CONSOLE_HPP diff --git a/libs/lxcpp/commands/prep-guest-terminal.cpp b/libs/lxcpp/commands/prep-guest-terminal.cpp index f496f86..5722fef 100644 --- a/libs/lxcpp/commands/prep-guest-terminal.cpp +++ b/libs/lxcpp/commands/prep-guest-terminal.cpp @@ -57,7 +57,6 @@ void PrepGuestTerminal::execute() int fd = utils::open(mTerminals.PTYs[0].ptsName, O_RDWR | O_CLOEXEC | O_NOCTTY); setupIOControlTTY(fd); - } diff --git a/libs/lxcpp/commands/prep-host-terminal.cpp b/libs/lxcpp/commands/prep-host-terminal.cpp index 1dc4f3d..0b57f89 100644 --- a/libs/lxcpp/commands/prep-host-terminal.cpp +++ b/libs/lxcpp/commands/prep-host-terminal.cpp @@ -43,8 +43,8 @@ void PrepHostTerminal::execute() { LOGD("Creating " << mTerminals.count << " pseudoterminal(s) on the host side:"); - for (int i = 0; i < mTerminals.count; ++i) { - const auto pty = lxcpp::openPty(true); + for (unsigned int i = 0; i < mTerminals.count; ++i) { + const auto pty = lxcpp::openPty(false); LOGD(pty.second << " has been created"); mTerminals.PTYs.emplace_back(pty.first, pty.second); } diff --git a/libs/lxcpp/commands/prep-host-terminal.hpp b/libs/lxcpp/commands/prep-host-terminal.hpp index f7153b2..9902a2d 100644 --- a/libs/lxcpp/commands/prep-host-terminal.hpp +++ b/libs/lxcpp/commands/prep-host-terminal.hpp @@ -39,9 +39,9 @@ public: * It creates a number of pseudoterminals and stores them * to be passed to the guard and prepared for init process. * - * @param config container's config + * @param terminals container's terminals config */ - PrepHostTerminal(TerminalsConfig &config); + PrepHostTerminal(TerminalsConfig &terminals); ~PrepHostTerminal(); void execute(); diff --git a/libs/lxcpp/container-impl.cpp b/libs/lxcpp/container-impl.cpp index 5f0da35..09b165f 100644 --- a/libs/lxcpp/container-impl.cpp +++ b/libs/lxcpp/container-impl.cpp @@ -27,6 +27,7 @@ #include "lxcpp/filesystem.hpp" #include "lxcpp/capability.hpp" #include "lxcpp/commands/attach.hpp" +#include "lxcpp/commands/console.hpp" #include "lxcpp/commands/start.hpp" #include "lxcpp/commands/stop.hpp" #include "lxcpp/commands/prep-host-terminal.hpp" @@ -46,29 +47,6 @@ #include -namespace { - -// TODO: UGLY: REMOVEME: -// It will be removed as soon as Container::console() will get implemented -// I need it for now to know I didn't brake anything. It will be eradicated. -void readTerminal(const lxcpp::TerminalConfig &term) -{ - char *buf = NULL; - size_t size = 0; - ssize_t ret; - - printf("%s output:\n", term.ptsName.c_str()); - usleep(10000); - - FILE *fp = fdopen(term.masterFD.value, "r"); - while((ret = getline(&buf, &size, fp)) != -1L) { - printf("%s", buf); - } - free(buf); -} - -} // namespace - namespace lxcpp { ContainerImpl::ContainerImpl(const std::string &name, const std::string &path) @@ -182,9 +160,6 @@ void ContainerImpl::start() Start start(mConfig); start.execute(); - - // TODO: UGLY: REMOVEME: read from 1st terminal - readTerminal(mConfig.mTerminals.PTYs[0]); } void ContainerImpl::stop() @@ -228,6 +203,12 @@ void ContainerImpl::attach(const std::vector& argv, attach.execute(); } +void ContainerImpl::console() +{ + Console console(mConfig.mTerminals); + console.execute(); +} + const std::vector& ContainerImpl::getNamespaces() const { return mNamespaces; diff --git a/libs/lxcpp/container-impl.hpp b/libs/lxcpp/container-impl.hpp index 846536e..4b560f1 100644 --- a/libs/lxcpp/container-impl.hpp +++ b/libs/lxcpp/container-impl.hpp @@ -68,6 +68,7 @@ public: // Other void attach(const std::vector& argv, const std::string& cwdInContainer); + void console(); // Network interfaces setup/config /** diff --git a/libs/lxcpp/container.hpp b/libs/lxcpp/container.hpp index ba32e64..2ca3a10 100644 --- a/libs/lxcpp/container.hpp +++ b/libs/lxcpp/container.hpp @@ -75,6 +75,7 @@ public: // Other virtual void attach(const std::vector& argv, const std::string& cwdInContainer) = 0; + virtual void console() = 0; // Network interfaces setup/config virtual void addInterfaceConfig(const std::string& hostif, diff --git a/libs/lxcpp/terminal-config.hpp b/libs/lxcpp/terminal-config.hpp index 3d6675a..f4a675c 100644 --- a/libs/lxcpp/terminal-config.hpp +++ b/libs/lxcpp/terminal-config.hpp @@ -55,10 +55,10 @@ struct TerminalConfig { }; struct TerminalsConfig { - int count; + unsigned int count; std::vector PTYs; - TerminalsConfig(const int count = 1) + TerminalsConfig(const unsigned int count = 1) : count(count) {} diff --git a/libs/lxcpp/terminal.cpp b/libs/lxcpp/terminal.cpp index 72c57af..5374ced 100644 --- a/libs/lxcpp/terminal.cpp +++ b/libs/lxcpp/terminal.cpp @@ -49,24 +49,6 @@ void openpty(int *master, int *slave) } } -void tcgetattr(const int fd, struct termios *termios_p) -{ - if (-1 == ::tcgetattr(fd, termios_p)) { - const std::string msg = "tcgetattr() failed: " + utils::getSystemErrorMessage(); - LOGE(msg); - throw TerminalException(msg); - } -} - -void tcsetattr(const int fd, const int optional_actions, const struct termios *termios_p) -{ - if (-1 == ::tcsetattr(fd, optional_actions, termios_p)) { - const std::string msg = "tcsetattr() failed: " + utils::getSystemErrorMessage(); - LOGE(msg); - throw TerminalException(msg); - } -} - std::string ttyname_r(const int fd) { char ptsName[PATH_MAX]; @@ -135,6 +117,24 @@ bool isatty(int fd) throw TerminalException(msg); } +void tcgetattr(const int fd, struct termios *termios_p) +{ + if (-1 == ::tcgetattr(fd, termios_p)) { + const std::string msg = "tcgetattr() failed: " + utils::getSystemErrorMessage(); + LOGE(msg); + throw TerminalException(msg); + } +} + +void tcsetattr(const int fd, const int optional_actions, const struct termios *termios_p) +{ + if (-1 == ::tcsetattr(fd, optional_actions, termios_p)) { + const std::string msg = "tcsetattr() failed: " + utils::getSystemErrorMessage(); + LOGE(msg); + throw TerminalException(msg); + } +} + void setupIOControlTTY(const int ttyFD) { if (!lxcpp::isatty(ttyFD)) { diff --git a/libs/lxcpp/terminal.hpp b/libs/lxcpp/terminal.hpp index 421e4ce..7b024f0 100644 --- a/libs/lxcpp/terminal.hpp +++ b/libs/lxcpp/terminal.hpp @@ -24,6 +24,8 @@ #ifndef LXCPP_TERMINAL_HPP #define LXCPP_TERMINAL_HPP +#include + namespace lxcpp { @@ -40,11 +42,21 @@ namespace lxcpp { int nullStdFDs(); /** - * Checks if a file descriptor is a terminal + * Checks if a file descriptor is a terminal. */ bool isatty(int fd); /** + * Get terminal attributes. + */ +void tcgetattr(const int fd, struct termios *termios_p); + +/** + * Set terminal attributes. + */ +void tcsetattr(const int fd, const int optional_actions, const struct termios *termios_p); + +/** * Setups the passed fd as a new control and IO (in, out, err) terminal */ void setupIOControlTTY(const int ttyFD); @@ -65,5 +77,4 @@ std::pair openPty(bool rawMode); } // namespace lxcpp - #endif // LXCPP_TERMINAL_HPP diff --git a/server/server.cpp b/server/server.cpp index 2fc8a65..bb028c7 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -84,8 +84,8 @@ Server::Server(const std::string& configPath) mZonesManager(mEventPoll, mConfigPath), mDispatchingThread(::pthread_self()) { - mSignalFD.setHandlerAndBlock(SIGUSR1, std::bind(&Server::handleUpdate, this)); - mSignalFD.setHandlerAndBlock(SIGINT, std::bind(&Server::handleStop, this)); + mSignalFD.setHandler(SIGUSR1, std::bind(&Server::handleUpdate, this)); + mSignalFD.setHandler(SIGINT, std::bind(&Server::handleStop, this)); mSignalFD.setHandler(SIGTERM, std::bind(&Server::handleStop, this)); } diff --git a/tests/unit_tests/utils/ut-signalfd.cpp b/tests/unit_tests/utils/ut-signalfd.cpp index 9f002e0..5a78b73 100644 --- a/tests/unit_tests/utils/ut-signalfd.cpp +++ b/tests/unit_tests/utils/ut-signalfd.cpp @@ -79,8 +79,8 @@ BOOST_AUTO_TEST_CASE(BlockingSignalHandler) { ipc::epoll::EventPoll poll; SignalFD s(poll); - s.setHandlerAndBlock(SIGUSR1, [](const int) {}); - s.setHandlerAndBlock(SIGINT, [](const int) {}); + s.setHandler(SIGUSR1, [](const int) {}); + s.setHandler(SIGINT, [](const int) {}); ::raise(SIGINT); std::this_thread::sleep_for(std::chrono::milliseconds(TIMEOUT)); -- 2.7.4 From edb255463ee3a03acd4a5171783d17e6ec82dbbc Mon Sep 17 00:00:00 2001 From: Jan Olszak Date: Tue, 13 Oct 2015 17:20:41 +0200 Subject: [PATCH 10/16] lxcpp: Removed Namespace enum [Feature] Regular Linux CLONE_* flags should be used instead of the removed enum [Cause] N/A [Solution] N/A [Verification] Build, install and run tests Change-Id: I29921d1387da76fb50b269f0e92ebb592cf51751 --- libs/lxcpp/attach/attach-config.hpp | 5 ++-- libs/lxcpp/container-config.hpp | 2 +- libs/lxcpp/container-impl.cpp | 11 ++++---- libs/lxcpp/container-impl.hpp | 5 +--- libs/lxcpp/container.hpp | 2 ++ libs/lxcpp/namespace.cpp | 41 +++++++---------------------- libs/lxcpp/namespace.hpp | 20 ++------------ libs/lxcpp/process.cpp | 46 +++++++++++++++++++-------------- libs/lxcpp/process.hpp | 10 ++----- tests/unit_tests/lxcpp/ut-namespace.cpp | 23 ++++++----------- tests/unit_tests/lxcpp/ut-process.cpp | 27 ++++++------------- 11 files changed, 67 insertions(+), 125 deletions(-) diff --git a/libs/lxcpp/attach/attach-config.hpp b/libs/lxcpp/attach/attach-config.hpp index 94e2626..beb425f 100644 --- a/libs/lxcpp/attach/attach-config.hpp +++ b/libs/lxcpp/attach/attach-config.hpp @@ -45,7 +45,7 @@ struct AttachConfig { pid_t initPid; /// Namespaces to which we'll attach - std::vector namespaces; + int namespaces; /// User ID to set uid_t uid; @@ -75,7 +75,7 @@ struct AttachConfig { AttachConfig(const std::vector& argv, const pid_t initPid, - const std::vector& namespaces, + const int namespaces, const uid_t uid, const gid_t gid, const std::vector& supplementaryGids, @@ -98,7 +98,6 @@ struct AttachConfig { CONFIG_REGISTER ( - //TODO: Uncomment and fix cstring serialization argv, initPid, namespaces, diff --git a/libs/lxcpp/container-config.hpp b/libs/lxcpp/container-config.hpp index 9990bdc..4fbf86d 100644 --- a/libs/lxcpp/container-config.hpp +++ b/libs/lxcpp/container-config.hpp @@ -113,7 +113,7 @@ struct ContainerConfig { * Namespace types used to create the container * * Set: setNamespaces() - * Get: none + * Get: getNamespaces() */ int mNamespaces; diff --git a/libs/lxcpp/container-impl.cpp b/libs/lxcpp/container-impl.cpp index 09b165f..84f9b44 100644 --- a/libs/lxcpp/container-impl.cpp +++ b/libs/lxcpp/container-impl.cpp @@ -151,6 +151,12 @@ void ContainerImpl::setNamespaces(const int namespaces) mConfig.mNamespaces = namespaces; } + +int ContainerImpl::getNamespaces() const +{ + return mConfig.mNamespaces; +} + void ContainerImpl::start() { // TODO: check config consistency and completeness somehow @@ -209,11 +215,6 @@ void ContainerImpl::console() console.execute(); } -const std::vector& ContainerImpl::getNamespaces() const -{ - return mNamespaces; -} - void ContainerImpl::addInterfaceConfig(const std::string& hostif, const std::string& zoneif, InterfaceType type, diff --git a/libs/lxcpp/container-impl.hpp b/libs/lxcpp/container-impl.hpp index 4b560f1..d3db492 100644 --- a/libs/lxcpp/container-impl.hpp +++ b/libs/lxcpp/container-impl.hpp @@ -55,8 +55,8 @@ public: void setTerminalCount(const unsigned int count); - const std::vector& getNamespaces() const; void setNamespaces(const int namespaces); + int getNamespaces() const; // Execution actions void start(); @@ -98,9 +98,6 @@ public: private: ContainerConfig mConfig; - - // TODO: convert to ContainerConfig struct - std::vector mNamespaces; }; } // namespace lxcpp diff --git a/libs/lxcpp/container.hpp b/libs/lxcpp/container.hpp index 2ca3a10..096463b 100644 --- a/libs/lxcpp/container.hpp +++ b/libs/lxcpp/container.hpp @@ -63,7 +63,9 @@ public: const std::string &arg = "") = 0; virtual void setTerminalCount(const unsigned int count) = 0; + virtual void setNamespaces(const int namespaces) = 0; + virtual int getNamespaces() const = 0; // Execution actions virtual void start() = 0; diff --git a/libs/lxcpp/namespace.cpp b/libs/lxcpp/namespace.cpp index 0ee8c88..57c8649 100644 --- a/libs/lxcpp/namespace.cpp +++ b/libs/lxcpp/namespace.cpp @@ -25,31 +25,22 @@ #include "lxcpp/exception.hpp" #include "logger/logger.hpp" -#include -#include - namespace lxcpp { -Namespace operator|(const Namespace a, const Namespace b) -{ - return static_cast(static_cast::type>(a) | - static_cast::type>(b)); -} - -std::string toString(const Namespace ns) +std::string nsToString(const int ns) { switch(ns) { - case Namespace::USER: + case CLONE_NEWUSER: return "user"; - case Namespace::MNT: + case CLONE_NEWNS: return "mnt"; - case Namespace::PID: + case CLONE_NEWPID: return "pid"; - case Namespace::UTS: + case CLONE_NEWUTS: return "uts"; - case Namespace::IPC: + case CLONE_NEWIPC: return "ipc"; - case Namespace::NET: + case CLONE_NEWNET: return "net"; default: const std::string msg = "Bad namespace passed to the function"; @@ -58,28 +49,14 @@ std::string toString(const Namespace ns) } } -int toFlag(const std::vector& namespaces) -{ - Namespace flag = std::accumulate(namespaces.begin(), - namespaces.end(), - static_cast(0), - std::bit_or()); - return static_cast(flag); -} - -int toFlag(const Namespace ns) -{ - return static_cast(ns); -} - std::string getNsPath(const pid_t pid) { return "/proc/" + std::to_string(pid) + "/ns"; } -std::string getPath(const pid_t pid, const Namespace ns) +std::string getPath(const pid_t pid, const int ns) { - return getNsPath(pid) + "/" + toString(ns); + return getNsPath(pid) + "/" + nsToString(ns); } } // namespace lxcpp diff --git a/libs/lxcpp/namespace.hpp b/libs/lxcpp/namespace.hpp index be6d033..91a4c9e 100644 --- a/libs/lxcpp/namespace.hpp +++ b/libs/lxcpp/namespace.hpp @@ -26,30 +26,14 @@ #include #include -#include namespace lxcpp { -enum class Namespace : int { - USER = CLONE_NEWUSER, - MNT = CLONE_NEWNS, - PID = CLONE_NEWPID, - UTS = CLONE_NEWUTS, - IPC = CLONE_NEWIPC, - NET = CLONE_NEWNET -}; - -Namespace operator |(const Namespace a, const Namespace b); - -std::string toString(const Namespace ns); +std::string nsToString(const int ns); std::string getNsPath(const pid_t pid); -std::string getPath(const pid_t pid, const Namespace ns); - -int toFlag(const Namespace ns); - -int toFlag(const std::vector& namespaces); +std::string getPath(const pid_t pid, const int ns); } // namespace lxcpp diff --git a/libs/lxcpp/process.cpp b/libs/lxcpp/process.cpp index 204dec2..6d2e6d8 100644 --- a/libs/lxcpp/process.cpp +++ b/libs/lxcpp/process.cpp @@ -34,6 +34,8 @@ #include #include +#include + namespace lxcpp { pid_t fork() @@ -69,40 +71,44 @@ pid_t clone(int (*function)(void *), return pid; } -pid_t clone(int (*function)(void *), - void *args, - const std::vector& namespaces, - const int additionalFlags) -{ - return clone(function, args, toFlag(namespaces) | additionalFlags); -} - -void setns(const pid_t pid, const std::vector& namespaces) +void setns(const pid_t pid, int requestedNamespaces) { int dirFD = utils::open(getNsPath(pid), O_DIRECTORY | O_CLOEXEC); + static const std::array NAMESPACES {{ + CLONE_NEWUSER, CLONE_NEWNS, CLONE_NEWPID, CLONE_NEWUTS, CLONE_NEWIPC, CLONE_NEWNET + }}; + // Open FDs connected with the requested namespaces - std::vector fds(namespaces.size(), -1); - for(size_t i = 0; i < namespaces.size(); ++i) { - fds[i] = ::openat(dirFD, - toString(namespaces[i]).c_str(), + std::vector fds; + for(const int ns: NAMESPACES) { + if (!(ns & requestedNamespaces)) { + // This namespace wasn't requested + continue; + } + + int fd = ::openat(dirFD, + nsToString(ns).c_str(), O_RDONLY | O_CLOEXEC); - if(fds[i] < 0) { + if(fd < 0) { const std::string msg = "openat() failed: " + utils::getSystemErrorMessage(); - for (size_t j = 0; j < i; ++j) { - utils::close(fds[j]); + // Cleanup file descriptors + for (const int d: fds) { + utils::close(d); } utils::close(dirFD); LOGE(msg); throw ProcessSetupException(msg); } + + fds.push_back(fd); } - // Setns for every namespace + // Setns to every requested namespace for(size_t i = 0; i < fds.size(); ++i) { - if(-1 == ::setns(fds[i], toFlag(namespaces[i]))) { + if(-1 == ::setns(fds[i], 0 /* we're sure it's a fd of the right namespace*/)) { const std::string msg = "setns() failed: " + utils::getSystemErrorMessage(); for (size_t j = i; j < fds.size(); ++j) { @@ -148,9 +154,9 @@ int waitpid(const pid_t pid) throw ProcessSetupException(msg); } -void unshare(const Namespace ns) +void unshare(const int ns) { - if(-1 == ::unshare(toFlag(ns))) { + if(-1 == ::unshare(ns)) { const std::string msg = "unshare() failed: " + utils::getSystemErrorMessage(); LOGE(msg); throw ProcessSetupException(msg); diff --git a/libs/lxcpp/process.hpp b/libs/lxcpp/process.hpp index 75c1e4f..2b33e4e 100644 --- a/libs/lxcpp/process.hpp +++ b/libs/lxcpp/process.hpp @@ -37,17 +37,11 @@ pid_t clone(int (*function)(void *), void *args, const int flags); -pid_t clone(int (*function)(void *), - void *args, - const std::vector& namespaces, - const int additionalFlags = 0); - -void setns(const pid_t pid, - const std::vector& namespaces); +void setns(const pid_t pid, const int namespaces); int waitpid(const pid_t pid); -void unshare(const Namespace ns); +void unshare(const int ns); void execve(const std::vector& argv); diff --git a/tests/unit_tests/lxcpp/ut-namespace.cpp b/tests/unit_tests/lxcpp/ut-namespace.cpp index 9e10e81..ba715c9 100644 --- a/tests/unit_tests/lxcpp/ut-namespace.cpp +++ b/tests/unit_tests/lxcpp/ut-namespace.cpp @@ -43,22 +43,15 @@ BOOST_FIXTURE_TEST_SUITE(LxcppNamespaceSuite, Fixture) using namespace lxcpp; -const std::array NAMESPACES {{ - Namespace::USER, - Namespace::MNT, - Namespace::PID, - Namespace::UTS, - Namespace::IPC, - Namespace::NET +static const std::array NAMESPACES {{ + CLONE_NEWUSER, + CLONE_NEWNS, + CLONE_NEWPID, + CLONE_NEWUTS, + CLONE_NEWIPC, + CLONE_NEWNET }}; -BOOST_AUTO_TEST_CASE(OR) -{ - Namespace a = Namespace::USER; - Namespace b = Namespace::MNT; - BOOST_CHECK_EQUAL(CLONE_NEWUSER | CLONE_NEWNS, static_cast(a | b)); -} - BOOST_AUTO_TEST_CASE(GetPath) { for(const auto ns: NAMESPACES) { @@ -69,7 +62,7 @@ BOOST_AUTO_TEST_CASE(GetPath) BOOST_AUTO_TEST_CASE(ToString) { for(const auto ns: NAMESPACES) { - toString(ns); + nsToString(ns); } } diff --git a/tests/unit_tests/lxcpp/ut-process.cpp b/tests/unit_tests/lxcpp/ut-process.cpp index 662dd82..6ec5ff0 100644 --- a/tests/unit_tests/lxcpp/ut-process.cpp +++ b/tests/unit_tests/lxcpp/ut-process.cpp @@ -42,7 +42,7 @@ struct Fixture { ~Fixture() {} }; -int clonefn(void* /*args*/) { +int clonefn(void*) { return 0; } @@ -52,19 +52,12 @@ BOOST_FIXTURE_TEST_SUITE(LxcppProcessSuite, Fixture) using namespace lxcpp; -const std::vector NAMESPACES {{ - Namespace::USER, - Namespace::MNT, - Namespace::PID, - Namespace::UTS, - Namespace::IPC, - Namespace::NET - }}; - BOOST_AUTO_TEST_CASE(Clone) { - BOOST_CHECK_NO_THROW(lxcpp::clone(clonefn, nullptr, NAMESPACES)); - BOOST_CHECK_NO_THROW(lxcpp::clone(clonefn, nullptr, {Namespace::MNT})); + BOOST_CHECK_NO_THROW(lxcpp::clone(clonefn, + nullptr, + CLONE_NEWUSER | CLONE_NEWNS | CLONE_NEWPID | CLONE_NEWUTS | CLONE_NEWIPC | CLONE_NEWNET)); + BOOST_CHECK_NO_THROW(lxcpp::clone(clonefn, nullptr, CLONE_NEWNS)); } BOOST_AUTO_TEST_CASE(Setns) @@ -75,12 +68,8 @@ BOOST_AUTO_TEST_CASE(Setns) pid_t pid = lxcpp::fork(); if (pid == 0) { try { - lxcpp::setns(::getpid(), {Namespace::MNT, - Namespace::PID, - Namespace::UTS, - Namespace::IPC, - Namespace::NET - }); + lxcpp::setns(::getpid(), + CLONE_NEWNS | CLONE_NEWPID | CLONE_NEWUTS | CLONE_NEWIPC | CLONE_NEWNET); ::_exit(TEST_PASSED); } catch(...) { ::_exit(ERROR); @@ -100,7 +89,7 @@ BOOST_AUTO_TEST_CASE(SetnsUserNamespace) pid_t pid = lxcpp::fork(); if (pid == 0) { try { - lxcpp::setns(::getpid(), {Namespace::USER}); + lxcpp::setns(::getpid(), CLONE_NEWUSER); ::_exit(ERROR); } catch(ProcessSetupException) { ::_exit(TEST_PASSED); -- 2.7.4 From 273e398513a0cddef7039acc046c265ace94b282 Mon Sep 17 00:00:00 2001 From: Lukasz Pawelczyk Date: Wed, 14 Oct 2015 13:43:41 +0200 Subject: [PATCH 11/16] Don't force compiler colors by default, add an option for that [Feature] Make it possible to compile without colors. [Cause] Compilation can be on a terminal (or non-terminal) that does not understand them. [Solution] Add a cmake option to force compiler colors, by default they are "auto" [Verification] cmake -DVASUM_BUILD_FORCE_COMPILER_COLORS=true/false Change-Id: I60376daa9801e01901af2f9f8d9d3dd5d6c18494 --- CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0bd22e5..c0c800c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,7 +35,9 @@ INCLUDE(GNUInstallDirs) IF (( "${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" AND NOT (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9)) OR ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" )) - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdiagnostics-color=always") + IF (VASUM_BUILD_FORCE_COMPILER_COLORS) + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdiagnostics-color=always") + ENDIF() ENDIF() ## Compiler flags, depending on the build type ################################# -- 2.7.4 From 31d69907cbefe58b1e923d95a26268683b6dec18 Mon Sep 17 00:00:00 2001 From: Krzysztof Dynowski Date: Thu, 3 Sep 2015 09:34:37 +0200 Subject: [PATCH 12/16] lxcpp: network implementation (part 3) [Feature] Network implementation for lxcpp (routing table manipulation) [Cause] N/A [Solution] N/A [Verification] Build, install, run tests Change-Id: I983a4b565c4b9e1b07ae8ab568445729c0f80eb8 --- common/utils/execute.cpp | 1 + libs/lxcpp/container-impl.cpp | 2 +- libs/lxcpp/network.cpp | 376 +++++++++++++++++++++++++++++----- libs/lxcpp/network.hpp | 69 +++++-- tests/unit_tests/lxcpp/ut-network.cpp | 280 +++++++++++++++++++++---- 5 files changed, 625 insertions(+), 103 deletions(-) diff --git a/common/utils/execute.cpp b/common/utils/execute.cpp index d047a2e..29c888e 100644 --- a/common/utils/execute.cpp +++ b/common/utils/execute.cpp @@ -99,6 +99,7 @@ bool executeAndWait(const char* fname, const char* const* argv, int& status) bool success = executeAndWait([=]() { execv(fname, const_cast(argv)); + LOGE("execv failed: " << getSystemErrorMessage()); _exit(EXIT_FAILURE); }, status); if (!success) { diff --git a/libs/lxcpp/container-impl.cpp b/libs/lxcpp/container-impl.cpp index 84f9b44..e19012c 100644 --- a/libs/lxcpp/container-impl.cpp +++ b/libs/lxcpp/container-impl.cpp @@ -240,7 +240,7 @@ NetworkInterfaceInfo ContainerImpl::getInterfaceInfo(const std::string& ifname) std::vector addrs; std::string macaddr; int mtu = 0, flags = 0; - Attrs attrs = ni.getAttrs(); + const Attrs& attrs = ni.getAttrs(); for (const Attr& a : attrs) { switch (a.name) { case AttrName::MAC: diff --git a/libs/lxcpp/network.cpp b/libs/lxcpp/network.cpp index 1214e5c..7b9c0b3 100644 --- a/libs/lxcpp/network.cpp +++ b/libs/lxcpp/network.cpp @@ -70,7 +70,18 @@ uint32_t getInterfaceIndex(const std::string& name) return index; } -uint32_t getInterfaceIndex(const std::string& name, pid_t pid) +std::string getInterfaceName(uint32_t index) +{ + char buf[IFNAMSIZ]; + if (::if_indextoname(index, buf) == NULL) { + const std::string msg = "No interface for index: " + std::to_string(index); + LOGE(msg); + throw NetworkException(msg); + } + return buf; +} + +uint32_t getInterfaceIndex(pid_t pid, const std::string& name) { NetlinkMessage nlm(RTM_GETLINK, NLM_F_REQUEST | NLM_F_ACK); ifinfomsg info = utils::make_clean(); @@ -81,7 +92,7 @@ uint32_t getInterfaceIndex(const std::string& name, pid_t pid) NetlinkResponse response = send(nlm, pid); if (!response.hasMessage()) { - const std::string msg = "Can't get interface index"; + const std::string msg = "Can't get interface index for " + name; LOGE(msg); throw NetworkException(msg); } @@ -90,7 +101,7 @@ uint32_t getInterfaceIndex(const std::string& name, pid_t pid) return info.ifi_index; } -void bridgeModify(const std::string& ifname, uint32_t masterIndex) { +void bridgeModify(pid_t pid, const std::string& ifname, uint32_t masterIndex) { NetlinkMessage nlm(RTM_SETLINK, NLM_F_REQUEST | NLM_F_ACK); ifinfomsg info = utils::make_clean(); info.ifi_family = AF_UNSPEC; @@ -98,12 +109,12 @@ void bridgeModify(const std::string& ifname, uint32_t masterIndex) { info.ifi_index = getInterfaceIndex(ifname); nlm.put(info) .put(IFLA_MASTER, masterIndex); - send(nlm); + send(nlm, pid); } -void getAddressList(std::vector& addrs, int family, const std::string& ifname, pid_t pid) +void getAddressList(pid_t pid, std::vector& addrs, int family, const std::string& ifname) { - uint32_t index = getInterfaceIndex(ifname, pid); + uint32_t index = getInterfaceIndex(pid, ifname); NetlinkMessage nlm(RTM_GETADDR, NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP); ifaddrmsg infoAddr = utils::make_clean(); infoAddr.ifa_family = family; //test AF_PACKET to get all AF_INET* ? @@ -174,6 +185,33 @@ void getAddressList(std::vector& addrs, int family, const std::string& response.fetchNextMessage(); } } + +/* + * convert string-hex to byte array + */ +void toMacAddressArray(const std::string& s, char *buf, size_t bs) +{ + static const std::string hex = "0123456789ABCDEF"; + int nibble = 0; //nibble counter, one nibble = 4 bits (or one hex digit) + int v = 0; + ::memset(buf, 0, bs); + for (size_t i = 0; i < s.length() && bs > 0; ++i) { + const char c = s.at(i); + size_t pos = hex.find(c); + if (pos == std::string::npos) { // ignore eny non-hex character + continue; + } + v <<= 4; + v |= pos; // add nibble + if (++nibble == 2) { // two nibbles collected (one byte) + *buf++ = v; // assign the byte + --bs; // decrease space left + v = 0; // reset byte and nibble counter + nibble = 0; + } + } +} + } // anonymous namespace @@ -236,7 +274,9 @@ std::string toString(const InetAddr& a) { return ""; } -InetAddr::InetAddr(uint32_t f, int p, const std::string& a) +InetAddr::InetAddr(const std::string& a, unsigned p, uint32_t f) : + prefix(p), + flags(f) { if (a.find(":") != std::string::npos) { setType(InetAddrType::IPV6); @@ -245,8 +285,6 @@ InetAddr::InetAddr(uint32_t f, int p, const std::string& a) setType(InetAddrType::IPV4); fromString(a, getAddr()); } - flags = f; - prefix = p; } void NetworkInterface::create(InterfaceType type, @@ -285,7 +323,7 @@ void NetworkInterface::createVeth(const std::string& peerif) .endNested() .endNested() .endNested(); - send(nlm); + send(nlm, mContainerPid); } void NetworkInterface::createBridge() @@ -304,7 +342,7 @@ void NetworkInterface::createBridge() .endNested() .endNested() .put(IFLA_IFNAME, mIfname); //bridge name (will be created) - send(nlm); + send(nlm, mContainerPid); } void NetworkInterface::createMacVLan(const std::string& maserif, MacVLanMode mode) @@ -323,7 +361,7 @@ void NetworkInterface::createMacVLan(const std::string& maserif, MacVLanMode mod .endNested() .put(IFLA_LINK, index) //master index .put(IFLA_IFNAME, mIfname); //slave name (will be created) - send(nlm); + send(nlm, mContainerPid); } void NetworkInterface::moveToContainer(pid_t pid) @@ -334,7 +372,7 @@ void NetworkInterface::moveToContainer(pid_t pid) info.ifi_index = getInterfaceIndex(mIfname); nlm.put(info) .put(IFLA_NET_NS_PID, pid); - send(nlm); + send(nlm, mContainerPid); mContainerPid = pid; } @@ -345,28 +383,25 @@ void NetworkInterface::destroy() ifinfomsg info = utils::make_clean(); info.ifi_family = AF_UNSPEC; info.ifi_change = CHANGE_FLAGS_DEFAULT; - info.ifi_index = getInterfaceIndex(mIfname, mContainerPid); + info.ifi_index = getInterfaceIndex(mContainerPid, mIfname); nlm.put(info) .put(IFLA_IFNAME, mIfname); - send(nlm); + send(nlm, mContainerPid); } NetStatus NetworkInterface::status() const { - NetlinkMessage nlm(RTM_GETLINK, NLM_F_REQUEST | NLM_F_ACK); - ifinfomsg info = utils::make_clean(); - info.ifi_family = AF_UNSPEC; - info.ifi_change = CHANGE_FLAGS_DEFAULT; - nlm.put(info) - .put(IFLA_IFNAME, mIfname); - - NetlinkResponse response = send(nlm, mContainerPid); - if (!response.hasMessage()) { - throw NetworkException("Can't get interface information"); - } - - response.fetch(info); - return (info.ifi_flags & IFF_UP) != 0 ? NetStatus::UP : NetStatus::DOWN; + const Attrs& attrs = getAttrs(); + NetStatus st = NetStatus::DOWN; + for (const Attr& a : attrs) + if (a.name == AttrName::FLAGS) { + uint32_t f = stoul(a.value); + if (f & IFF_UP) { + st = NetStatus::UP; + break; + } + } + return st; } void NetworkInterface::renameFrom(const std::string& oldif) @@ -374,7 +409,7 @@ void NetworkInterface::renameFrom(const std::string& oldif) NetlinkMessage nlm(RTM_SETLINK, NLM_F_REQUEST | NLM_F_ACK); ifinfomsg info = utils::make_clean(); info.ifi_family = AF_UNSPEC; - info.ifi_index = getInterfaceIndex(oldif, mContainerPid); + info.ifi_index = getInterfaceIndex(mContainerPid, oldif); info.ifi_change = CHANGE_FLAGS_DEFAULT; nlm.put(info) @@ -384,15 +419,14 @@ void NetworkInterface::renameFrom(const std::string& oldif) void NetworkInterface::addToBridge(const std::string& bridge) { - bridgeModify(mIfname, getInterfaceIndex(bridge)); + bridgeModify(mContainerPid, mIfname, getInterfaceIndex(mContainerPid, bridge)); } void NetworkInterface::delFromBridge() { - bridgeModify(mIfname, 0); + bridgeModify(mContainerPid, mIfname, 0); } - void NetworkInterface::setAttrs(const Attrs& attrs) { if (attrs.empty()) { @@ -402,7 +436,7 @@ void NetworkInterface::setAttrs(const Attrs& attrs) //TODO check this: NetlinkMessage nlm(RTM_SETLINK, NLM_F_REQUEST | NLM_F_ACK); NetlinkMessage nlm(RTM_NEWLINK, NLM_F_REQUEST | NLM_F_CREATE | NLM_F_ACK); ifinfomsg info = utils::make_clean(); - info.ifi_index = getInterfaceIndex(mIfname, mContainerPid); + info.ifi_index = getInterfaceIndex(mContainerPid, mIfname); info.ifi_family = AF_UNSPEC; info.ifi_change = CHANGE_FLAGS_DEFAULT; @@ -437,7 +471,9 @@ void NetworkInterface::setAttrs(const Attrs& attrs) nlm.put(IFLA_TXQLEN, txq); } if (!mac.empty()) { - nlm.put(IFLA_ADDRESS, mac); + char buf[6]; + toMacAddressArray(mac, buf, 6); + nlm.put(IFLA_ADDRESS, buf); } NetlinkResponse response = send(nlm, mContainerPid); @@ -460,8 +496,8 @@ Attrs NetworkInterface::getAttrs() const throw NetworkException("Can't get interface information"); } - Attrs attrs; response.fetch(info); + Attrs attrs; attrs.push_back(Attr{AttrName::FLAGS, std::to_string(info.ifi_flags)}); attrs.push_back(Attr{AttrName::TYPE, std::to_string(info.ifi_type)}); @@ -471,7 +507,7 @@ Attrs NetworkInterface::getAttrs() const * a few types of networks require 64-bit addresses instead. */ std::string mac; - uint32_t mtu, link, txq; + uint32_t tmp; int attrType = response.getAttributeType(); switch (attrType) { case IFLA_ADDRESS: //1 @@ -479,17 +515,18 @@ Attrs NetworkInterface::getAttrs() const attrs.push_back(Attr{AttrName::MAC, utils::toHexString(mac.c_str(), mac.size())}); break; case IFLA_MTU: //4 - response.fetch(IFLA_MTU, mtu); - attrs.push_back(Attr{AttrName::MTU, std::to_string(mtu)}); + response.fetch(IFLA_MTU, tmp); + attrs.push_back(Attr{AttrName::MTU, std::to_string(tmp)}); break; case IFLA_LINK://5 - response.fetch(IFLA_LINK, link); - attrs.push_back(Attr{AttrName::LINK, std::to_string(link)}); + response.fetch(IFLA_LINK, tmp); + attrs.push_back(Attr{AttrName::LINK, std::to_string(tmp)}); break; case IFLA_TXQLEN://13 - response.fetch(IFLA_TXQLEN, txq); - attrs.push_back(Attr{AttrName::TXQLEN, std::to_string(txq)}); + response.fetch(IFLA_TXQLEN, tmp); + attrs.push_back(Attr{AttrName::TXQLEN, std::to_string(tmp)}); break; + case IFLA_OPERSTATE://16 (IF_OPER_DOWN,...,IF_OPER_UP) case IFLA_BROADCAST://2 MAC broadcast case IFLA_IFNAME: //3 case IFLA_QDISC: //6 queue discipline @@ -500,6 +537,7 @@ Attrs NetworkInterface::getAttrs() const case IFLA_WIRELESS: //11 case IFLA_PROTINFO: //12 case IFLA_MAP: //14 + case IFLA_WEIGHT: //15 default: response.skipAttribute(); break; @@ -512,7 +550,7 @@ void NetworkInterface::addInetAddr(const InetAddr& addr) { NetlinkMessage nlm(RTM_NEWADDR, NLM_F_CREATE | NLM_F_REQUEST | NLM_F_ACK); ifaddrmsg infoAddr = utils::make_clean(); - infoAddr.ifa_index = getInterfaceIndex(mIfname, mContainerPid); + infoAddr.ifa_index = getInterfaceIndex(mContainerPid, mIfname); infoAddr.ifa_family = addr.getType() == InetAddrType::IPV4 ? AF_INET : AF_INET6; infoAddr.ifa_prefixlen = addr.prefix; infoAddr.ifa_flags = addr.flags; @@ -533,7 +571,7 @@ void NetworkInterface::delInetAddr(const InetAddr& addr) { NetlinkMessage nlm(RTM_DELADDR, NLM_F_REQUEST | NLM_F_ACK); ifaddrmsg infoAddr = utils::make_clean(); - infoAddr.ifa_index = getInterfaceIndex(mIfname, mContainerPid); + infoAddr.ifa_index = getInterfaceIndex(mContainerPid, mIfname); infoAddr.ifa_family = addr.getType() == InetAddrType::IPV4 ? AF_INET : AF_INET6; infoAddr.ifa_prefixlen = addr.prefix; infoAddr.ifa_flags = addr.flags; @@ -553,10 +591,250 @@ void NetworkInterface::delInetAddr(const InetAddr& addr) std::vector NetworkInterface::getInetAddressList() const { std::vector addrs; - getAddressList(addrs, AF_UNSPEC, mIfname, mContainerPid); + getAddressList(mContainerPid, addrs, AF_UNSPEC, mIfname); return addrs; } +static rt_class_t getRoutingTableClass(const RoutingTable rt) +{ + switch (rt) { + case RoutingTable::UNSPEC: + return RT_TABLE_UNSPEC; //0 + case RoutingTable::COMPAT: + return RT_TABLE_COMPAT; //252 + case RoutingTable::DEFAULT: + return RT_TABLE_DEFAULT;//253 + case RoutingTable::MAIN: + return RT_TABLE_MAIN; //254 + case RoutingTable::LOCAL: + return RT_TABLE_LOCAL; //255 + default: //all other are user tables (1...251), but I use only '1' + return static_cast(1); + } +} +static RoutingTable getRoutingTable(unsigned tbl) +{ + switch (tbl) { + case RT_TABLE_UNSPEC: + return RoutingTable::UNSPEC; + case RT_TABLE_COMPAT: + return RoutingTable::COMPAT; + case RT_TABLE_DEFAULT: + return RoutingTable::DEFAULT; + case RT_TABLE_MAIN: + return RoutingTable::MAIN; + case RT_TABLE_LOCAL: + return RoutingTable::LOCAL; + default: + return RoutingTable::USER; + } +} + +void NetworkInterface::addRoute(const Route& route, const RoutingTable rt) +{ + InetAddrType type = route.dst.getType(); + if (route.src.getType() != type) { + const std::string msg = "Family type must be the same"; + LOGE(msg); + throw NetworkException(msg); + } + uint32_t index = getInterfaceIndex(mContainerPid, mIfname); + NetlinkMessage nlm(RTM_NEWROUTE, NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL | NLM_F_ACK); + + rtmsg msg = utils::make_clean(); + + if (type == InetAddrType::IPV6) { + msg.rtm_family = AF_INET6; + } else if (type == InetAddrType::IPV4) { + msg.rtm_family = AF_INET; + } + msg.rtm_table = getRoutingTableClass(rt); + msg.rtm_protocol = RTPROT_BOOT; + msg.rtm_scope = RT_SCOPE_UNIVERSE; + msg.rtm_type = RTN_UNICAST; + msg.rtm_dst_len = route.dst.prefix; + + nlm.put(msg); + + if (type == InetAddrType::IPV6) { + if (route.dst.prefix == 0) + nlm.put(RTA_GATEWAY, route.dst.getAddr()); + else + nlm.put(RTA_DST, route.dst.getAddr()); + + if (route.src.prefix == 128) { + nlm.put(RTA_PREFSRC, route.src.getAddr()); + } + } else if (type == InetAddrType::IPV4) { + if (route.dst.prefix == 0) + nlm.put(RTA_GATEWAY, route.dst.getAddr()); + else + nlm.put(RTA_DST, route.dst.getAddr()); + + if (route.src.prefix == 32) { + nlm.put(RTA_PREFSRC, route.src.getAddr()); + } + } + + nlm.put(RTA_OIF, index); + + send(nlm, mContainerPid); +} + +void NetworkInterface::delRoute(const Route& route, const RoutingTable rt) +{ + InetAddrType type = route.dst.getType(); + if (route.src.getType() != type) { + const std::string msg = "Family type must be the same"; + LOGE(msg); + throw NetworkException(msg); + } + uint32_t index = getInterfaceIndex(mContainerPid, mIfname); + NetlinkMessage nlm(RTM_DELROUTE, NLM_F_REQUEST | NLM_F_ACK); + + rtmsg msg = utils::make_clean(); + msg.rtm_scope = RT_SCOPE_NOWHERE; + msg.rtm_table = getRoutingTableClass(rt); + msg.rtm_dst_len = route.dst.prefix; + if (type == InetAddrType::IPV6) { + msg.rtm_family = AF_INET6; + } else if (type == InetAddrType::IPV4) { + msg.rtm_family = AF_INET; + } + nlm.put(msg); + + if (type == InetAddrType::IPV6) { + nlm.put(RTA_DST, route.dst.getAddr()); + } else if (type == InetAddrType::IPV4) { + nlm.put(RTA_DST, route.dst.getAddr()); + } + nlm.put(RTA_OIF, index); + + send(nlm, mContainerPid); +} + +static std::vector getRoutesImpl(pid_t pid, rt_class_t tbl, const std::string& ifname, int family) +{ + uint32_t searchindex = 0; + NetlinkMessage nlm(RTM_GETROUTE, NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP); + ifinfomsg info = utils::make_clean(); + info.ifi_family = family; + info.ifi_change = CHANGE_FLAGS_DEFAULT; + + nlm.put(info); + + NetlinkResponse response = send(nlm, pid); + + if (!ifname.empty()) { + searchindex = getInterfaceIndex(pid, ifname); + } + + std::vector routes; + for ( ; response.hasMessage(); response.fetchNextMessage()) { + if (response.getMessageType() != RTM_NEWROUTE) { + LOGW("Not route info in response"); + continue; + } + rtmsg rt; + response.fetch(rt); + if (tbl != RT_TABLE_UNSPEC && rt.rtm_table != tbl) { + continue; + } + if (rt.rtm_flags & RTM_F_CLONED) { + continue; + } + + Route route; + uint32_t index = 0; + route.dst.flags = 0; + route.src.flags = 0; + route.dst.prefix = 0; + route.src.prefix = 0; + route.table = getRoutingTable(rt.rtm_table); + + if (rt.rtm_family == AF_INET6) { + route.dst.setType(InetAddrType::IPV6); + route.src.setType(InetAddrType::IPV6); + } else if (rt.rtm_family == AF_INET) { + route.dst.setType(InetAddrType::IPV4); + route.src.setType(InetAddrType::IPV4); + } else { + const std::string msg = "Unsupported inet family"; + LOGE(msg); + throw NetworkException(msg); + } + + route.dst.prefix = rt.rtm_dst_len; + while (response.hasAttribute()) { + std::string tmpval; + uint32_t tmp; + int attrType = response.getAttributeType(); + //int len = response.getAttributeLength(); + switch (attrType) { + case RTA_DST: // 1 + case RTA_GATEWAY:// 5 + if (route.dst.getType() == InetAddrType::IPV6) { + response.fetch(attrType, route.dst.getAddr()); + } else { + response.fetch(attrType, route.dst.getAddr()); + } + break; + case RTA_SRC: // 2 + case RTA_PREFSRC:// 7 + if (route.src.getType() == InetAddrType::IPV6) { + response.fetch(attrType, route.src.getAddr()); + route.src.prefix = 128; + } else { + response.fetch(attrType, route.src.getAddr()); + route.src.prefix = 32; + } + break; + case RTA_OIF: //4 + response.fetch(RTA_OIF, tmp); + index = tmp; + break; + case RTA_PRIORITY: // 6 + response.fetch(RTA_PRIORITY, tmp); + route.metric = tmp; + break; + case RTA_TABLE: //15 extends and ovewrites rt.rtm_table + response.fetch(RTA_TABLE, tmp); + route.table = getRoutingTable(tmp); + break; + + case RTA_CACHEINFO://12 + response.skipAttribute(); + break; + case RTA_IIF: // 3 + case RTA_METRICS: // 8 array of struct rtattr + case RTA_MULTIPATH:// 9 array of struct rtnexthop + case RTA_FLOW: //11 + default: + if (!searchindex) { + response.fetch(attrType, tmpval); + LOGW("rtAttr " << attrType << ":" << + utils::toHexString(tmpval.c_str(), tmpval.size())); + } else { + response.skipAttribute(); + } + break; + } + } + if (index == 0) continue; + + if (searchindex == 0 || searchindex == index) { + route.ifname = getInterfaceName(index); + routes.push_back(route); + } + } + return routes; +} + +std::vector NetworkInterface::getRoutes(const RoutingTable rt) const +{ + return getRoutesImpl(mContainerPid, getRoutingTableClass(rt), mIfname, AF_UNSPEC); +} + void NetworkInterface::up() { Attrs attrs; @@ -617,4 +895,10 @@ std::vector NetworkInterface::getInterfaces(pid_t initpid) return iflist; } +std::vector NetworkInterface::getRoutes(pid_t initpid, const RoutingTable rt) +{ + rt_class_t tbl = getRoutingTableClass(rt); + return getRoutesImpl(initpid, tbl, "", AF_UNSPEC); +} + } // namespace lxcpp diff --git a/libs/lxcpp/network.hpp b/libs/lxcpp/network.hpp index 0affc5f..ac94c44 100644 --- a/libs/lxcpp/network.hpp +++ b/libs/lxcpp/network.hpp @@ -54,8 +54,8 @@ enum class InetAddrType { */ class InetAddr { public: - InetAddr() {} - InetAddr(uint32_t flags, int prefix, const std::string& addr); + InetAddr() = default; + InetAddr(const std::string& addr, unsigned prefix, uint32_t flags=0); InetAddrType getType() const { return static_cast(type); @@ -77,8 +77,8 @@ public: return *(reinterpret_cast(v)); } + unsigned prefix; uint32_t flags; - int prefix; CONFIG_REGISTER ( @@ -127,7 +127,9 @@ enum class RoutingTable { LOCAL, USER, }; -inline std::string toString(RoutingTable rt) { + +inline std::string toString(const RoutingTable rt) +{ switch (rt) { case RoutingTable::UNSPEC: return "unspec"; @@ -144,6 +146,14 @@ inline std::string toString(RoutingTable rt) { } } +struct Route { + InetAddr dst; + InetAddr src; + unsigned metric; + std::string ifname; + RoutingTable table; +}; + enum class AttrName { MAC, FLAGS, @@ -205,10 +215,10 @@ std::string toString(const InetAddr& a); * operates on netlink device */ class NetworkInterface { + //TODO implement Netlink singleton per pid public: /** * Create network interface object for the ifname in the container (network namespace) - * Note: pid=0 is kernel */ NetworkInterface(const std::string& ifname, pid_t pid = 0) : mIfname(ifname), @@ -224,7 +234,8 @@ public: NetStatus status() const; /** - * Create network interface in container identified by mContainerPid + * Create network interface in container identified by @mContainerPid + * * Equivalent to: ip link add @mIfname type @type [...] * Create pair of virtual ethernet interfaces * ip link add @mIfname type veth peer name @peerif @@ -233,7 +244,7 @@ public: * Create psedo-ethernet interface on existing one * ip link add @mIfname type macvlan link @peerif [mode @a mode] */ - void create(InterfaceType type, const std::string& peerif, MacVLanMode mode = MacVLanMode::PRIVATE); + void create(InterfaceType type, const std::string& peerif = "", MacVLanMode mode = MacVLanMode::PRIVATE); /** * Delete interface @@ -243,9 +254,9 @@ public: /** * Move interface to container - * Equivalent to: ip link set dev @hostif netns @mContainerPid + * Equivalent to: ip link set dev @mIfname netns @pid */ - void moveToContainer(pid_t p); + void moveToContainer(pid_t pid); /** * Rename interface name @@ -291,8 +302,26 @@ public: std::vector getInetAddressList() const; /** + * Add route to specified routing table + * Equivalent to: ip route add @route.dst.addr/@route.dst.prefix dev @mIfname (if route.src.prefix=0) + */ + void addRoute(const Route& route, const RoutingTable rt = RoutingTable::MAIN); + + /** + * Remove route from specified routing table + * Equivalent to: ip route del @route.dst.addr dev @mIfname + */ + void delRoute(const Route& route, const RoutingTable rt = RoutingTable::MAIN); + + /** + * Retrieve routing table for the interface + * Equivalent to: ip route show dev @mIfname table @rt + */ + std::vector getRoutes(const RoutingTable rt = RoutingTable::MAIN) const; + + /** * Set interface up - * Equivalent to: ip link set @mInface up + * Equivalent to: ip link set @mIfname up */ void up(); @@ -304,20 +333,26 @@ public: /** * Set MAC address attribute - * Equivalent to: ip link set @mIface address @macaddr + * Equivalent to: ip link set @mIfname address @macaddr * @macaddr in format AA:BB:CC:DD:FF:GG + * + * Note: two lower bits of first byte (leftmost) specifies MAC address class: + * b1: 0=unicast, 1=broadcast + * b2: 0=global, 1=local + * in most cases should be b2=0, b1=1 + * (see: https://en.wikipedia.org/wiki/MAC_address) */ void setMACAddress(const std::string& macaddr); /** * Set MTU attribute - * Equivalent to: ip link set @mIface mtu @mtu + * Equivalent to: ip link set @mIfname mtu @mtu */ void setMTU(int mtu); /** * Set TxQ attribute - * Equivalent to: ip link set @mIface txqueue @txlen + * Equivalent to: ip link set @mIfname txqueue @txlen */ void setTxLength(int txlen); @@ -327,11 +362,19 @@ public: */ static std::vector getInterfaces(pid_t initpid); + /** + * Get list of routes (specified routing table) + * Equivalent to: ip route show table @rt + */ + static std::vector getRoutes(pid_t initpid, const RoutingTable rt = RoutingTable::MAIN); + private: void createVeth(const std::string& peerif); void createBridge(); void createMacVLan(const std::string& masterif, MacVLanMode mode); + void modifyRoute(int cmd, const InetAddr& src, const InetAddr& dst); + const std::string mIfname; ///< network interface name inside zone pid_t mContainerPid; ///< Container pid to operate on (0 means kernel) }; diff --git a/tests/unit_tests/lxcpp/ut-network.cpp b/tests/unit_tests/lxcpp/ut-network.cpp index f797a6c..a4741f6 100644 --- a/tests/unit_tests/lxcpp/ut-network.cpp +++ b/tests/unit_tests/lxcpp/ut-network.cpp @@ -23,7 +23,10 @@ #include "config.hpp" #include "config/manager.hpp" +#include "logger/logger.hpp" #include "lxcpp/network-config.hpp" +#include "lxcpp/process.hpp" +#include "utils/execute.hpp" #include "ut.hpp" #include @@ -31,10 +34,9 @@ #include -namespace { - using namespace lxcpp; -using namespace config; + +namespace { struct Fixture { Fixture() {} @@ -49,8 +51,71 @@ struct Fixture { } while (std::find(iflist.begin(), iflist.end(), name) != iflist.end()); return name; } + + static void sendCmd(int fd, const char *txt) { + if (::write(fd, txt, 2) != 2) { + throw std::runtime_error("pipe write error"); + } + } }; + +int child_exec(void *_fd) +{ + int *fd = (int *)_fd; + char cmdbuf[2]; + char cmd = '-'; + + ::close(fd[1]); + try { + lxcpp::NetworkInterface("lo").up(); + for(;;) { + // child: waiting for parent + if (::read(fd[0], cmdbuf, 2) != 2) { + break; + } + + cmd = cmdbuf[0]; + + if (cmd == '0') { + break; + } else if (cmd == 'a') { + const char *argv[] = { + "ip", "a", NULL + }; + if (!utils::executeAndWait("/sbin/ip", argv)) { + throw std::runtime_error("ip addr failed"); + } + } else if (cmd == 'r') { + const char *argv[] = { + "ip", "route", "list", NULL + }; + if (!utils::executeAndWait("/sbin/ip", argv)) { + throw std::runtime_error("ip route failed"); + } + } else if (cmd == 's') { + const char *argv[] = { + "bash", NULL + }; + if (!utils::executeAndWait("/bin/bash", argv)) { + throw std::runtime_error("bash failed"); + } + } else if (cmd == 'c') { + LOGW("connecting ... to be done"); + } else { + continue; + } + } + + //cleanup + ::close(fd[0]); + _exit(EXIT_SUCCESS); + + } catch(...) { + _exit(EXIT_FAILURE); + } +} + } // namespace /* @@ -64,31 +129,12 @@ BOOST_FIXTURE_TEST_SUITE(LxcppNetworkSuite, Fixture) BOOST_AUTO_TEST_CASE(NetworkListInterfaces) { std::vector iflist; - BOOST_CHECK_NO_THROW(iflist = NetworkInterface::getInterfaces(0)); - - for (const auto& i : iflist) { - NetworkInterface ni(i); - std::cout << i << ": "; - Attrs attrs; - std::vector addrs; - BOOST_CHECK_NO_THROW(attrs = ni.getAttrs()); - for (const Attr& a : attrs) { - std::cout << a.name << "=" << a.value; - if (a.name == AttrName::FLAGS) { - uint32_t f = stoul(a.value); - std::cout << "("; - std::cout << ((f & IFF_UP) !=0 ? "UP" : "DONW"); - std::cout << ")"; - } - std::cout << "; "; - } - std::cout << std::endl; - - BOOST_CHECK_NO_THROW(addrs = ni.getInetAddressList()); - for (const InetAddr& a : addrs) { - std::cout << " " << toString(a) << std::endl; - } + BOOST_CHECK_NO_THROW(iflist=NetworkInterface::getInterfaces(0)); + for (const auto& ifn : iflist) { + const Attrs& attrs = NetworkInterface(ifn).getAttrs(); + BOOST_CHECK(attrs.size() > 0); } + } BOOST_AUTO_TEST_CASE(NetworkConfigSerialization) @@ -101,8 +147,7 @@ BOOST_AUTO_TEST_CASE(NetworkConfigSerialization) cfg.addInterfaceConfig("host-veth1", "zone-eth1", InterfaceType::BRIDGE); cfg.addInterfaceConfig("host-veth2", "zone-eth2", InterfaceType::MACVLAN); - InetAddr addr(0, 24, "1.2.3.4"); - cfg.addInetConfig("zone-eth0", addr); + cfg.addInetConfig("zone-eth0", InetAddr("1.2.3.4", 24)); config::saveToJsonFile(tmpConfigFile, cfg); @@ -120,30 +165,27 @@ BOOST_AUTO_TEST_CASE(NetworkConfigSerialization) BOOST_CHECK(ni1.getMode() == ni2.getMode()); } } + BOOST_AUTO_TEST_CASE(NetworkBridgeCreateDestroy) { - std::string name = getUniqueName("lolo"); + std::string name = getUniqueName("test-br"); NetworkInterface ni(name); - InetAddr myip(0, 32, "10.100.1.1"); + InetAddr myip("10.100.1.1", 32); - BOOST_CHECK_NO_THROW(ni.create(InterfaceType::BRIDGE, "")); - BOOST_CHECK_NO_THROW(ni.addInetAddr(myip);) + BOOST_CHECK_NO_THROW(ni.create(InterfaceType::BRIDGE)); + ni.setMACAddress("12:22:33:44:55:66"); // note bit0=0 within first byte !!! + BOOST_CHECK_NO_THROW(ni.addInetAddr(myip)); std::vector iflist = NetworkInterface::getInterfaces(0); BOOST_CHECK(std::find(iflist.begin(), iflist.end(), name) != iflist.end()); std::vector addrs = ni.getInetAddressList(); BOOST_CHECK(std::find(addrs.begin(), addrs.end(), myip) != addrs.end()); - for (const auto& i : iflist) { - std::cout << " " << i; - } - std::cout << std::endl; - for (const InetAddr& a : addrs) { - std::cout << " " << toString(a) << std::endl; - } BOOST_CHECK_NO_THROW(ni.delInetAddr(myip)); BOOST_CHECK_NO_THROW(ni.destroy()); + iflist = NetworkInterface::getInterfaces(0); + BOOST_CHECK(std::find(iflist.begin(), iflist.end(), ni.getName()) == iflist.end()); } BOOST_AUTO_TEST_CASE(NetworkMacVLanCreateDestroy) @@ -151,7 +193,9 @@ BOOST_AUTO_TEST_CASE(NetworkMacVLanCreateDestroy) std::string masterif; std::vector iflist = NetworkInterface::getInterfaces(0); for (const auto& ifn : iflist) { - if (ifn == "lo") continue; + if (ifn == "lo") { + continue; + } NetworkInterface n(ifn); if (n.status() == NetStatus::UP) { masterif = ifn; @@ -159,14 +203,164 @@ BOOST_AUTO_TEST_CASE(NetworkMacVLanCreateDestroy) } } - NetworkInterface ni(getUniqueName("lolo")); - std::cout << " creating MACVLAN on " << masterif << std::endl; + NetworkInterface ni(getUniqueName("test-vlan")); + // creating MACVLAN on masterif BOOST_CHECK_NO_THROW(ni.create(InterfaceType::MACVLAN, masterif, MacVLanMode::VEPA)); iflist = NetworkInterface::getInterfaces(0); BOOST_CHECK(std::find(iflist.begin(), iflist.end(), ni.getName()) != iflist.end()); + // destroy MACVLAN BOOST_CHECK_NO_THROW(ni.destroy()); + + iflist = NetworkInterface::getInterfaces(0); + BOOST_CHECK(std::find(iflist.begin(), iflist.end(), ni.getName()) == iflist.end()); +} + +BOOST_AUTO_TEST_CASE(NetworkListRoutes) +{ + unsigned mainLo = 0; + std::vector routes; + // tbl MAIN, all devs + BOOST_CHECK_NO_THROW(routes = NetworkInterface::getRoutes(0)); + for (auto route : routes) { + if (route.ifname == "lo") { + ++mainLo; + } + } + + // tbl LOCAL, all devs + BOOST_CHECK_NO_THROW(routes = NetworkInterface::getRoutes(0,RoutingTable::LOCAL)); + + // tbl DEFAULT, all devs + BOOST_CHECK_NO_THROW(routes = NetworkInterface::getRoutes(0,RoutingTable::DEFAULT)); + + NetworkInterface ni("lo"); + // tbl MAIN, dev lo + BOOST_CHECK_NO_THROW(routes = ni.getRoutes()); + BOOST_CHECK(routes.size() == mainLo); + + // tbl LOCAL, dev lo + BOOST_CHECK_NO_THROW(routes = ni.getRoutes(RoutingTable::LOCAL)); +} + +BOOST_AUTO_TEST_CASE(NetworkAddDelRoute) +{ + std::vector routes; + + Route route = { + InetAddr("10.100.1.0", 24),//dst - destination network + InetAddr("", 0), //src - not specified (prefix=0) + 0, // metric + "", // ifname (used only when read routes) + RoutingTable::UNSPEC // table (used only when read rotes) + }; + + NetworkInterface ni("lo"); + + BOOST_CHECK_NO_THROW(ni.addRoute(route)); + BOOST_CHECK_NO_THROW(routes = ni.getRoutes()); + BOOST_CHECK(std::find_if(routes.begin(), routes.end(), + [&route](const Route& item) -> bool { + return item.dst == route.dst; + } + ) != routes.end() + ); + + BOOST_CHECK_NO_THROW(ni.delRoute(route)); + BOOST_CHECK_NO_THROW(routes = ni.getRoutes()); + BOOST_CHECK(std::find_if(routes.begin(), routes.end(), + [&route](const Route& item) -> bool { + return item.dst == route.dst; + } + ) == routes.end() + ); +} + +BOOST_AUTO_TEST_CASE(NetworkNamespaceCreate) +{ + int fd[2]; + int r = ::pipe(fd); + BOOST_CHECK(r != -1); + + pid_t pid = lxcpp::clone(child_exec, fd, CLONE_NEWNET); + ::close(fd[0]); + + //directives for child process + sendCmd(fd[1], "0"); // exit + + // waiting for child to finish + int status; + BOOST_CHECK_NO_THROW(status = lxcpp::waitpid(pid)); + + ::close(fd[1]); + BOOST_CHECK_MESSAGE(status == 0, "child failed"); +} + +// this test case shows how to create container with network +// Note: this test needs some preparation to successfuly connect an external site: +// 1. allow network forwading (echo 1 > /proc/sys/net/ipv4/ip_forward) +// 2. configure ip masquarading (iptables -t nat -A POSTROUTING -s 10.0.0.0/16 ! -d 10.0.0.0/16 -j MASQUERADE) +BOOST_AUTO_TEST_CASE(NetworkNamespaceVETH) +{ + const char *vbr = "vbr"; + const char *veth1 = "veth-ma"; + const char *veth2 = "veth-sl"; + + int fd[2]; + int r = ::pipe(fd); + BOOST_CHECK(r != -1); + + pid_t pid = lxcpp::clone(child_exec, fd, CLONE_NEWNET); + ::close(fd[0]); + + NetworkInterface br(vbr); + NetworkInterface v1(veth1); + NetworkInterface v2(veth2); + + NetworkInterface("lo", pid).up(); + + // creating Bridge vbr + BOOST_CHECK_NO_THROW(br.create(InterfaceType::BRIDGE)); + BOOST_CHECK_NO_THROW(br.up()); + br.addInetAddr(InetAddr("10.0.0.1", 24)); + + // creating VETH pair veth1 <-> veth2 + BOOST_CHECK_NO_THROW(v1.create(InterfaceType::VETH, v2.getName())); + + // add veth1 to bridge + BOOST_CHECK_NO_THROW(v1.addToBridge(br.getName())); + + // move veth2 to network namespace (container) + BOOST_CHECK_NO_THROW(v2.moveToContainer(pid)); + v2.up(); + v2.addInetAddr(InetAddr("10.0.0.2", 24)); + + v1.up(); // after v2 up and configured + + // add default route + v2.addRoute(Route{ + InetAddr("10.0.0.1", 0), //dst - gateway + InetAddr("", 0), //src - not specified (prefix=0) + 0, + "", + RoutingTable::UNSPEC + }); + + //directives for child process + sendCmd(fd[1], "a"); // ip addr show + sendCmd(fd[1], "r"); // ip route list + sendCmd(fd[1], "c"); // connect extern (needs configured NAT) + //sendCmd(fd[1], "s"); // exec shell + sendCmd(fd[1], "0"); // exit + + // waiting for child to finish + int status; + BOOST_CHECK_NO_THROW(status = lxcpp::waitpid(pid)); + ::close(fd[1]); + BOOST_CHECK_MESSAGE(status == 0, "child failed"); + + BOOST_CHECK_NO_THROW(br.destroy()); } BOOST_AUTO_TEST_SUITE_END() -- 2.7.4 From 70c609070756512c76ab883311dae531c27a8d37 Mon Sep 17 00:00:00 2001 From: "Maciej J. Karpiuk" Date: Thu, 15 Oct 2015 11:42:52 +0200 Subject: [PATCH 13/16] lxcpp: provisioning implementation (part 1) [Feature] Provisioning implementation for lxcpp (declare, list, remove) [Cause] N/A [Solution] N/A [Verification] Build, install, run tests Change-Id: I3823587de023a9f1a9e34208ae9e188521dbe3b1 --- libs/lxcpp/commands/command.hpp | 6 +- libs/lxcpp/commands/provision.cpp | 89 +++++++++++++++ libs/lxcpp/commands/provision.hpp | 116 ++++++++++++++++++++ libs/lxcpp/container-config.hpp | 12 +- libs/lxcpp/container-impl.cpp | 98 ++++++++++++++++- libs/lxcpp/container-impl.hpp | 22 ++++ libs/lxcpp/container.hpp | 23 ++++ libs/lxcpp/exception.hpp | 5 + libs/lxcpp/guard/guard.cpp | 9 ++ libs/lxcpp/provision-config.cpp | 115 +++++++++++++++++++ libs/lxcpp/provision-config.hpp | 170 +++++++++++++++++++++++++++++ tests/unit_tests/lxcpp/ut-provisioning.cpp | 164 ++++++++++++++++++++++++++++ 12 files changed, 826 insertions(+), 3 deletions(-) create mode 100644 libs/lxcpp/commands/provision.cpp create mode 100644 libs/lxcpp/commands/provision.hpp create mode 100644 libs/lxcpp/provision-config.cpp create mode 100644 libs/lxcpp/provision-config.hpp create mode 100644 tests/unit_tests/lxcpp/ut-provisioning.cpp diff --git a/libs/lxcpp/commands/command.hpp b/libs/lxcpp/commands/command.hpp index cd301ee..a1ba473 100644 --- a/libs/lxcpp/commands/command.hpp +++ b/libs/lxcpp/commands/command.hpp @@ -28,9 +28,13 @@ namespace lxcpp { class Command { public: + // do sth [mandatory] virtual void execute() = 0; + + // roll-back execute() action [optional] + virtual void revert() {} }; } // namespace lxcpp -#endif // LXCPP_COMMANDS_COMMAND_HPP \ No newline at end of file +#endif // LXCPP_COMMANDS_COMMAND_HPP diff --git a/libs/lxcpp/commands/provision.cpp b/libs/lxcpp/commands/provision.cpp new file mode 100644 index 0000000..ba97393 --- /dev/null +++ b/libs/lxcpp/commands/provision.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2015 Samsung Electronics Co., Ltd All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @author Maciej Karpiuk (m.karpiuk2@samsung.com) + * @brief Add new provisioned file/dir/link/mount command + */ + +#include "lxcpp/commands/provision.hpp" +#include "lxcpp/container.hpp" +#include "lxcpp/provision-config.hpp" + +namespace lxcpp { + +void Provisions::execute() +{ + for(const auto & file : mConfig.mProvisions.files) { + ProvisionFile(mConfig, file).execute(); + } + + for(const auto & mount : mConfig.mProvisions.mounts) { + ProvisionMount(mConfig, mount).execute(); + } + + for(const auto & link : mConfig.mProvisions.links) { + ProvisionLink(mConfig, link).execute(); + } +} +void Provisions::revert() +{ + for(const auto & file : mConfig.mProvisions.files) { + ProvisionFile(mConfig, file).revert(); + } + + for(const auto & mount : mConfig.mProvisions.mounts) { + ProvisionMount(mConfig, mount).revert(); + } + + for(const auto & link : mConfig.mProvisions.links) { + ProvisionLink(mConfig, link).revert(); + } +} + + +void ProvisionFile::execute() +{ + // MJK TODO: add file +} +void ProvisionFile::revert() +{ + // MJK TODO: remove file from container +} + + +void ProvisionMount::execute() +{ + // MJK TODO: add mount +} +void ProvisionMount::revert() +{ + // MJK TODO: remove mount from container +} + + +void ProvisionLink::execute() +{ + // MJK TODO: add link +} +void ProvisionLink::revert() +{ + // MJK TODO: remove link from container +} + +} // namespace lxcpp diff --git a/libs/lxcpp/commands/provision.hpp b/libs/lxcpp/commands/provision.hpp new file mode 100644 index 0000000..0b7411a --- /dev/null +++ b/libs/lxcpp/commands/provision.hpp @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2015 Samsung Electronics Co., Ltd All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @author Maciej Karpiuk (m.karpiuk2@samsung.com) + * @brief Add new provisioned file/dir/link/mount command + */ + +#ifndef LXCPP_COMMAND_PROVISION_HPP +#define LXCPP_COMMAND_PROVISION_HPP + +#include "lxcpp/commands/command.hpp" +#include "lxcpp/container-config.hpp" +#include "lxcpp/provision-config.hpp" + +#include + +namespace lxcpp { + +class Provisions final: Command { +public: + /** + * Runs call in the container's context + * + * Add/remove all file/fifo/dir/mount/link provisions to/from the container + */ + Provisions(ContainerConfig &config) : mConfig(config) + { + } + + void execute(); + void revert(); + +private: + ContainerConfig& mConfig; +}; + + +class ProvisionFile final: Command { +public: + /** + * Runs call in the container's context + * + * Add/remove new file/fifo/dir provision to/from the container + */ + ProvisionFile(ContainerConfig &config, const provision::File &file) : + mConfig(config), mFile(file) + { + } + + void execute(); + void revert(); + +private: + ContainerConfig& mConfig; + const provision::File& mFile; +}; + +class ProvisionMount final: Command { +public: + /** + * Runs call in the container's context + * + * Add/remove new mount provision to/from the container + */ + ProvisionMount(ContainerConfig &config, const provision::Mount &mount) : + mConfig(config), mMount(mount) + { + } + + void execute(); + void revert(); + +private: + ContainerConfig& mConfig; + const provision::Mount& mMount; +}; + +class ProvisionLink final: Command { +public: + /** + * Runs call in the container's context + * + * Add/remove link provision to/from the container + */ + ProvisionLink(ContainerConfig &config, const provision::Link &link) : + mConfig(config), mLink(link) + { + } + + void execute(); + void revert(); + +private: + ContainerConfig& mConfig; + const provision::Link& mLink; +}; + +} // namespace lxcpp + +#endif // LXCPP_COMMAND_PROVISION_HPP diff --git a/libs/lxcpp/container-config.hpp b/libs/lxcpp/container-config.hpp index 4fbf86d..1a487cf 100644 --- a/libs/lxcpp/container-config.hpp +++ b/libs/lxcpp/container-config.hpp @@ -27,6 +27,7 @@ #include "lxcpp/logger-config.hpp" #include "lxcpp/network-config.hpp" #include "lxcpp/terminal-config.hpp" +#include "lxcpp/provision-config.hpp" #include #include @@ -117,6 +118,14 @@ struct ContainerConfig { */ int mNamespaces; + /** + * available files/dirs/mounts/links + * + * Set: by container provision manipulation methods + * Get: getFiles(), getMounts(), getLinks() + */ + ProvisionConfig mProvisions; + ContainerConfig() : mGuardPid(-1), mInitPid(-1), mNamespaces(0) {} CONFIG_REGISTER @@ -128,7 +137,8 @@ struct ContainerConfig { mInit, mLogger, mTerminals, - mNamespaces + mNamespaces, + mProvisions ) }; diff --git a/libs/lxcpp/container-impl.cpp b/libs/lxcpp/container-impl.cpp index e19012c..29d6cb0 100644 --- a/libs/lxcpp/container-impl.cpp +++ b/libs/lxcpp/container-impl.cpp @@ -31,6 +31,7 @@ #include "lxcpp/commands/start.hpp" #include "lxcpp/commands/stop.hpp" #include "lxcpp/commands/prep-host-terminal.hpp" +#include "lxcpp/commands/provision.hpp" #include "logger/logger.hpp" #include "utils/exception.hpp" @@ -170,7 +171,7 @@ void ContainerImpl::start() void ContainerImpl::stop() { - // TODO: things to do when shuttting down the container: + // TODO: things to do when shutting down the container: // - close PTY master FDs from the config so we won't keep PTYs open Stop stop(mConfig); @@ -215,6 +216,12 @@ void ContainerImpl::console() console.execute(); } +bool ContainerImpl::isRunning() const +{ + // TODO: race condition may occur, sync needed + return getInitPid() != -1; +} + void ContainerImpl::addInterfaceConfig(const std::string& hostif, const std::string& zoneif, InterfaceType type, @@ -305,4 +312,93 @@ void ContainerImpl::delInetAddr(const std::string& ifname, const InetAddr& addr) ni.delInetAddr(addr); } +void ContainerImpl::declareFile(const provision::File::Type type, + const std::string& path, + const int32_t flags, + const int32_t mode) +{ + provision::File newFile({type, path, flags, mode}); + mConfig.mProvisions.addFile(newFile); + // TODO: update guard config + + if (isRunning()) { + ProvisionFile fileCmd(mConfig, newFile); + fileCmd.execute(); + } +} + +const FileVector& ContainerImpl::getFiles() const +{ + return mConfig.mProvisions.getFiles(); +} + +void ContainerImpl::removeFile(const provision::File& item) +{ + mConfig.mProvisions.removeFile(item); + + if (isRunning()) { + ProvisionFile fileCmd(mConfig, item); + fileCmd.revert(); + } +} + +void ContainerImpl::declareMount(const std::string& source, + const std::string& target, + const std::string& type, + const int64_t flags, + const std::string& data) +{ + provision::Mount newMount({source, target, type, flags, data}); + mConfig.mProvisions.addMount(newMount); + // TODO: update guard config + + if (isRunning()) { + ProvisionMount mountCmd(mConfig, newMount); + mountCmd.execute(); + } +} + +const MountVector& ContainerImpl::getMounts() const +{ + return mConfig.mProvisions.getMounts(); +} + +void ContainerImpl::removeMount(const provision::Mount& item) +{ + mConfig.mProvisions.removeMount(item); + + if (isRunning()) { + ProvisionMount mountCmd(mConfig, item); + mountCmd.revert(); + } +} + +void ContainerImpl::declareLink(const std::string& source, + const std::string& target) +{ + provision::Link newLink({source, target}); + mConfig.mProvisions.addLink(newLink); + // TODO: update guard config + + if (isRunning()) { + ProvisionLink linkCmd(mConfig, newLink); + linkCmd.execute(); + } +} + +const LinkVector& ContainerImpl::getLinks() const +{ + return mConfig.mProvisions.getLinks(); +} + +void ContainerImpl::removeLink(const provision::Link& item) +{ + mConfig.mProvisions.removeLink(item); + + if (isRunning()) { + ProvisionLink linkCmd(mConfig, item); + linkCmd.revert(); + } +} + } // namespace lxcpp diff --git a/libs/lxcpp/container-impl.hpp b/libs/lxcpp/container-impl.hpp index d3db492..f1cba58 100644 --- a/libs/lxcpp/container-impl.hpp +++ b/libs/lxcpp/container-impl.hpp @@ -69,6 +69,7 @@ public: void attach(const std::vector& argv, const std::string& cwdInContainer); void console(); + bool isRunning() const; // Network interfaces setup/config /** @@ -96,6 +97,27 @@ public: void addInetAddr(const std::string& ifname, const InetAddr& addr); void delInetAddr(const std::string& ifname, const InetAddr& addr); + // Provisioning + void declareFile(const provision::File::Type type, + const std::string& path, + const int32_t flags, + const int32_t mode); + const FileVector& getFiles() const; + void removeFile(const provision::File& item); + + void declareMount(const std::string& source, + const std::string& target, + const std::string& type, + const int64_t flags, + const std::string& data); + const MountVector& getMounts() const; + void removeMount(const provision::Mount& item); + + void declareLink(const std::string& source, + const std::string& target); + const LinkVector& getLinks() const; + void removeLink(const provision::Link& item); + private: ContainerConfig mConfig; }; diff --git a/libs/lxcpp/container.hpp b/libs/lxcpp/container.hpp index 096463b..2d0e696 100644 --- a/libs/lxcpp/container.hpp +++ b/libs/lxcpp/container.hpp @@ -25,6 +25,7 @@ #define LXCPP_CONTAINER_HPP #include "lxcpp/network-config.hpp" +#include "lxcpp/provision-config.hpp" #include "lxcpp/logger-config.hpp" #include @@ -78,6 +79,7 @@ public: virtual void attach(const std::vector& argv, const std::string& cwdInContainer) = 0; virtual void console() = 0; + virtual bool isRunning() const = 0; // Network interfaces setup/config virtual void addInterfaceConfig(const std::string& hostif, @@ -100,6 +102,27 @@ public: virtual void setDown(const std::string& ifname) = 0; virtual void addInetAddr(const std::string& ifname, const InetAddr& addr) = 0; virtual void delInetAddr(const std::string& ifname, const InetAddr& addr) = 0; + + // Provisioning + virtual void declareFile(const provision::File::Type type, + const std::string& path, + const int32_t flags, + const int32_t mode) = 0; + virtual const FileVector& getFiles() const = 0; + virtual void removeFile(const provision::File& item) = 0; + + virtual void declareMount(const std::string& source, + const std::string& target, + const std::string& type, + const int64_t flags, + const std::string& data) = 0; + virtual const MountVector& getMounts() const = 0; + virtual void removeMount(const provision::Mount& item) = 0; + + virtual void declareLink(const std::string& source, + const std::string& target) = 0; + virtual const LinkVector& getLinks() const = 0; + virtual void removeLink(const provision::Link& item) = 0; }; } // namespace lxcpp diff --git a/libs/lxcpp/exception.hpp b/libs/lxcpp/exception.hpp index 387e2eb..37fec5a 100644 --- a/libs/lxcpp/exception.hpp +++ b/libs/lxcpp/exception.hpp @@ -96,6 +96,11 @@ struct ConfigureException: public Exception { : Exception(message) {} }; +struct ProvisionException: public Exception { + explicit ProvisionException(const std::string& message = "Provision error") + : Exception(message) {} +}; + } // namespace lxcpp #endif // LXCPP_EXCEPTION_HPP diff --git a/libs/lxcpp/guard/guard.cpp b/libs/lxcpp/guard/guard.cpp index 4f56771..f96fe7a 100644 --- a/libs/lxcpp/guard/guard.cpp +++ b/libs/lxcpp/guard/guard.cpp @@ -25,6 +25,7 @@ #include "lxcpp/guard/guard.hpp" #include "lxcpp/process.hpp" #include "lxcpp/commands/prep-guest-terminal.hpp" +#include "lxcpp/commands/provision.hpp" #include "config/manager.hpp" #include "logger/logger.hpp" @@ -42,6 +43,9 @@ int startContainer(void* data) // TODO: container preparation part 2 + Provisions provisions(config); + provisions.execute(); + PrepGuestTerminal terminals(config.mTerminals); terminals.execute(); @@ -96,6 +100,11 @@ int Guard::execute() int status = lxcpp::waitpid(initPid); LOGD("Init exited with status: " << status); + + // TODO: cleanup after child exits + Provisions provisions(mConfig); + provisions.revert(); + return status; } diff --git a/libs/lxcpp/provision-config.cpp b/libs/lxcpp/provision-config.cpp new file mode 100644 index 0000000..459ba70 --- /dev/null +++ b/libs/lxcpp/provision-config.cpp @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2015 Samsung Electronics Co., Ltd All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @author Maciej Karpiuk (m.karpiuk2@samsung.com) + * @brief Provisioning configuration + */ + +#include +#include +#include "lxcpp/container-config.hpp" + +using namespace lxcpp; +using namespace provision; + +void ProvisionConfig::addFile(const File& newFile) +{ + auto it = std::find(files.begin(), files.end(), newFile); + if (it != files.end()) { + const std::string msg = + "Can't add file. Provision already exists: " + newFile.getId(); + LOGE(msg); + throw ProvisionException(msg); + } + files.push_back(newFile); +} + +const FileVector& ProvisionConfig::getFiles() const +{ + return files; +} + +void ProvisionConfig::removeFile(const File& item) +{ + const auto it = std::find(files.begin(), files.end(), item); + if (it == files.end()) { + const std::string msg = "Can't find provision: " + item.getId(); + LOGE(msg); + throw ProvisionException(msg); + } + files.erase(it); +} + + +void ProvisionConfig::addMount(const Mount& newMount) +{ + auto it = std::find(mounts.begin(), mounts.end(), newMount); + if (it != mounts.end()) { + const std::string msg = + "Can't add mount. Provision already exists: " + newMount.getId(); + LOGE(msg); + throw ProvisionException(msg); + } + mounts.push_back(newMount); +} + +const MountVector& ProvisionConfig::getMounts() const +{ + return mounts; +} + +void ProvisionConfig::removeMount(const Mount& item) +{ + const auto it = std::find(mounts.begin(), mounts.end(), item); + if (it == mounts.end()) { + const std::string msg = "Can't find provision: " + item.getId(); + LOGE(msg); + throw ProvisionException(msg); + } + mounts.erase(it); +} + + +void ProvisionConfig::addLink(const Link& newLink) +{ + auto it = std::find(links.begin(), links.end(), newLink); + if (it != links.end()) { + const std::string msg = + "Can't add link. Provision already exists: " + newLink.getId(); + LOGE(msg); + throw ProvisionException(msg); + } + links.push_back(newLink); +} + +const LinkVector& ProvisionConfig::getLinks() const +{ + return links; +} + +void ProvisionConfig::removeLink(const Link& item) +{ + const auto it = std::find(links.begin(), links.end(), item); + if (it == links.end()) { + const std::string msg = "Can't find provision: " + item.getId(); + LOGE(msg); + throw ProvisionException(msg); + } + links.erase(it); +} diff --git a/libs/lxcpp/provision-config.hpp b/libs/lxcpp/provision-config.hpp new file mode 100644 index 0000000..fb9cbc4 --- /dev/null +++ b/libs/lxcpp/provision-config.hpp @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2015 Samsung Electronics Co., Ltd All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @author Maciej Karpiuk (m.karpiuk2@samsung.com) + * @brief Provisioning configuration + */ + +#ifndef LXCPP_PROVISION_CONFIG_HPP +#define LXCPP_PROVISION_CONFIG_HPP + +#include "config/config.hpp" +#include "config/fields.hpp" +#include "config/fields-union.hpp" + +#include +#include +#include + +namespace lxcpp { +namespace provision { + +typedef std::string ProvisionID; + +/** + * Provision configuration items + */ +struct File +{ + enum class Type : int + { + DIRECTORY, + FIFO, + REGULAR + }; + + ProvisionID getId() const + { + return "file " + + path + " " + + std::to_string(static_cast(type)) + " " + + std::to_string(flags) + " " + + std::to_string(mode); + } + + Type type; + std::string path; + std::int32_t flags; + std::int32_t mode; + + CONFIG_REGISTER + ( + type, + path, + flags, + mode + ) + + bool operator==(const File& m) const + { + return ((m.type == type) && (m.path == path) && + (m.flags == flags) && (m.mode == mode)); + } +}; + +struct Mount +{ + ProvisionID getId() const + { + return "mount " + + source + " " + + target + " " + + type + " " + + std::to_string(flags) + " " + + data; + } + + std::string source; + std::string target; + std::string type; + std::int64_t flags; + std::string data; + + CONFIG_REGISTER + ( + source, + target, + type, + flags, + data + ) + + bool operator==(const Mount& m) const + { + return ((m.source == source) && (m.target == target) && (m.type == type) && + (m.flags == flags) && (m.data == data)); + } +}; + +struct Link +{ + ProvisionID getId() const + { + return "link " + source + " " + target; + } + + std::string source; + std::string target; + + CONFIG_REGISTER + ( + source, + target + ) + + bool operator==(const Link& m) const + { + return ((m.source == source) && (m.target == target)); + } +}; +} // namespace provision + +typedef std::vector FileVector; +typedef std::vector MountVector; +typedef std::vector LinkVector; + +struct ProvisionConfig +{ + FileVector files; + MountVector mounts; + LinkVector links; + + void addFile(const provision::File& newFile); + const FileVector& getFiles() const; + void removeFile(const provision::File& item); + + void addMount(const provision::Mount& newMount); + const MountVector& getMounts() const; + void removeMount(const provision::Mount& item); + + void addLink(const provision::Link& newLink); + const LinkVector& getLinks() const; + void removeLink(const provision::Link& item); + + CONFIG_REGISTER + ( + files, + mounts, + links + ) +}; + +} // namespace lxcpp + +#endif // LXCPP_PROVISION_CONFIG_HPP diff --git a/tests/unit_tests/lxcpp/ut-provisioning.cpp b/tests/unit_tests/lxcpp/ut-provisioning.cpp new file mode 100644 index 0000000..d2a535c --- /dev/null +++ b/tests/unit_tests/lxcpp/ut-provisioning.cpp @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2015 Samsung Electronics Co., Ltd All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @author Maciej Karpiuk (m.karpiuk2@samsung.com) + * @brief Unit tests of lxcpp provisioning + */ + +#include "config.hpp" +#include "config/manager.hpp" +#include "lxcpp/lxcpp.hpp" +#include "lxcpp/container.hpp" +#include "lxcpp/container-config.hpp" +#include "lxcpp/provision-config.hpp" +#include "ut.hpp" +#include "utils/scoped-dir.hpp" +#include + +namespace { + +using namespace lxcpp; +using namespace config; +using namespace provision; + +const std::string TEST_DIR = "/tmp/ut-provisioning"; +const std::string ROOT_DIR = TEST_DIR + "/root"; + +struct Fixture { + Fixture() : mTestPath(ROOT_DIR) { + container = std::unique_ptr(createContainer("ProvisioningTester", ROOT_DIR)); + } + ~Fixture() {} + + std::unique_ptr container; + utils::ScopedDir mTestPath; +}; + +} // namespace + + +BOOST_FIXTURE_TEST_SUITE(LxcppProvisioningSuite, Fixture) + +BOOST_AUTO_TEST_CASE(ListProvisionsEmptyContainer) +{ + BOOST_REQUIRE(container->getFiles().size() == 0); + BOOST_REQUIRE(container->getMounts().size() == 0); + BOOST_REQUIRE(container->getLinks().size() == 0); +} + +BOOST_AUTO_TEST_CASE(AddDeclareFile) +{ + container->declareFile(File::Type::FIFO, "path", 0747, 0777); + container->declareFile(File::Type::REGULAR, "path", 0747, 0777); + + std::vector fileList = container->getFiles(); + BOOST_REQUIRE_EQUAL(fileList.size(), 2); + + BOOST_REQUIRE(fileList[0].type == File::Type::FIFO); + BOOST_REQUIRE(fileList[0].path == "path"); + BOOST_REQUIRE(fileList[0].flags == 0747); + BOOST_REQUIRE(fileList[0].mode == 0777); + BOOST_REQUIRE(fileList[1].type == File::Type::REGULAR); + + BOOST_REQUIRE_NO_THROW(container->removeFile(fileList[0])); + BOOST_REQUIRE_EQUAL(container->getFiles().size(), 1); + File dummyFile({File::Type::FIFO, "dummy", 1, 2}); + BOOST_REQUIRE_THROW(container->removeFile(dummyFile), ProvisionException); + BOOST_REQUIRE_NO_THROW(container->removeFile(fileList[1])); + BOOST_REQUIRE_EQUAL(container->getFiles().size(), 0); +} + +BOOST_AUTO_TEST_CASE(AddDeclareMount) +{ + container->declareMount("/fake/path1", "/fake/path2", "tmpfs", 077, "fake"); + container->declareMount("/fake/path2", "/fake/path2", "tmpfs", 077, "fake"); + BOOST_CHECK_THROW(container->declareMount("/fake/path2", "/fake/path2", "tmpfs", 077, "fake"), + ProvisionException); + + std::vector mountList = container->getMounts(); + BOOST_REQUIRE_EQUAL(mountList.size(), 2); + + BOOST_REQUIRE(mountList[0].source == "/fake/path1"); + BOOST_REQUIRE(mountList[0].target == "/fake/path2"); + BOOST_REQUIRE(mountList[0].type == "tmpfs"); + BOOST_REQUIRE(mountList[0].flags == 077); + BOOST_REQUIRE(mountList[0].data == "fake"); + + BOOST_REQUIRE(mountList[1].source == "/fake/path2"); + BOOST_REQUIRE(mountList[1].target == "/fake/path2"); + BOOST_REQUIRE(mountList[1].type == "tmpfs"); + BOOST_REQUIRE(mountList[1].flags == 077); + BOOST_REQUIRE(mountList[1].data == "fake"); + + BOOST_REQUIRE_NO_THROW(container->removeMount(mountList[0])); + BOOST_REQUIRE_EQUAL(container->getMounts().size(), 1); + Mount dummyMount({"a", "b", "c", 1, "d"}); + BOOST_REQUIRE_THROW(container->removeMount(dummyMount), ProvisionException); + BOOST_REQUIRE_NO_THROW(container->removeMount(mountList[1])); + BOOST_REQUIRE_EQUAL(container->getMounts().size(), 0); +} + +BOOST_AUTO_TEST_CASE(AddDeclareLink) +{ + container->declareLink("/fake/path1", "/fake/path2"); + container->declareLink("/fake/path2", "/fake/path2"); + BOOST_CHECK_THROW(container->declareLink("/fake/path2", "/fake/path2"), + ProvisionException); + + std::vector linkList = container->getLinks(); + BOOST_REQUIRE_EQUAL(linkList.size(), 2); + + BOOST_REQUIRE(linkList[0].source == "/fake/path1"); + BOOST_REQUIRE(linkList[0].target == "/fake/path2"); + BOOST_REQUIRE(linkList[1].source == "/fake/path2"); + BOOST_REQUIRE(linkList[1].target == "/fake/path2"); + + BOOST_REQUIRE_NO_THROW(container->removeLink(linkList[0])); + BOOST_REQUIRE_EQUAL(container->getLinks().size(), 1); + Link dummyLink({"a", "b"}); + BOOST_REQUIRE_THROW(container->removeLink(dummyLink), ProvisionException); + BOOST_REQUIRE_NO_THROW(container->removeLink(linkList[1])); + BOOST_REQUIRE_EQUAL(container->getLinks().size(), 0); +} + +BOOST_AUTO_TEST_CASE(ProvisioningConfigSerialization) +{ + std::string tmpConfigFile = "/tmp/fileconfig.conf"; + std::string tmpConfigMount = "/tmp/mountconfig.conf"; + std::string tmpConfigLink = "/tmp/linkconfig.conf"; + + File saved_file ({File::Type::REGULAR, "path", 0747, 0777}); + Mount saved_mount({"/fake/path1", "/fake/path2", "tmpfs", 077, "fake"}); + Link saved_link ({"/fake/path1", "/fake/path2"}); + config::saveToJsonFile(tmpConfigFile, saved_file); + config::saveToJsonFile(tmpConfigMount, saved_mount); + config::saveToJsonFile(tmpConfigLink, saved_link); + + File loaded_file; + Mount loaded_mount; + Link loaded_link; + config::loadFromJsonFile(tmpConfigFile, loaded_file); + config::loadFromJsonFile(tmpConfigMount, loaded_mount); + config::loadFromJsonFile(tmpConfigLink, loaded_link); + BOOST_REQUIRE(saved_file == loaded_file); + BOOST_REQUIRE(saved_mount == loaded_mount); + BOOST_REQUIRE(saved_link == loaded_link); +} + +BOOST_AUTO_TEST_SUITE_END() -- 2.7.4 From 3547bec83a06ddd27429bc5280690ebc2852732c Mon Sep 17 00:00:00 2001 From: Dariusz Michaluk Date: Wed, 30 Sep 2015 11:01:49 +0200 Subject: [PATCH 14/16] Various changes relating to create Fedora (LXDE or KDE) Desktop [Bug/Feature] Various changes relating to create Fedora Desktop [Cause] N/A [Solution] N/A [Verification] Build, run server, run tests. Change-Id: I847ab904012bea962b9961ad70a4e6bed9a69968 --- packaging/vasum.spec | 2 +- server/configs/daemon.conf.in | 7 +- server/configs/templates/fedora-kde-demo.conf | 19 ++ server/configs/templates/fedora-kde-demo.sh | 214 ++++++++++++++++++++ .../configs/templates/fedora-kde-wayland-demo.conf | 19 ++ .../configs/templates/fedora-kde-wayland-demo.sh | 217 +++++++++++++++++++++ server/configs/templates/fedora-lxde-demo.conf | 19 ++ server/configs/templates/fedora-lxde-demo.sh | 213 ++++++++++++++++++++ .../templates/{fedora.conf => fedora-minimal.conf} | 4 +- .../templates/{fedora.sh => fedora-minimal.sh} | 2 +- server/configs/templates/tizen-common-wayland.sh | 2 +- server/configs/templates/ubuntu.sh | 2 +- server/zones-manager-config.hpp | 6 + server/zones-manager.cpp | 29 ++- tests/unit_tests/configs/test-daemon.conf.in | 1 + 15 files changed, 732 insertions(+), 24 deletions(-) create mode 100644 server/configs/templates/fedora-kde-demo.conf create mode 100755 server/configs/templates/fedora-kde-demo.sh create mode 100644 server/configs/templates/fedora-kde-wayland-demo.conf create mode 100755 server/configs/templates/fedora-kde-wayland-demo.sh create mode 100644 server/configs/templates/fedora-lxde-demo.conf create mode 100755 server/configs/templates/fedora-lxde-demo.sh rename server/configs/templates/{fedora.conf => fedora-minimal.conf} (86%) rename server/configs/templates/{fedora.sh => fedora-minimal.sh} (97%) diff --git a/packaging/vasum.spec b/packaging/vasum.spec index 546c782..447bbcf 100644 --- a/packaging/vasum.spec +++ b/packaging/vasum.spec @@ -108,7 +108,7 @@ mkdir -p %{buildroot}/%{_datadir}/zones %if %{platform_type} == "TIZEN" ln -s tizen.conf %{buildroot}/etc/vasum/templates/default.conf %else -ln -s fedora.conf %{buildroot}/etc/vasum/templates/default.conf +ln -s fedora-minimal.conf %{buildroot}/etc/vasum/templates/default.conf %endif %clean diff --git a/server/configs/daemon.conf.in b/server/configs/daemon.conf.in index 96d748f..d107343 100644 --- a/server/configs/daemon.conf.in +++ b/server/configs/daemon.conf.in @@ -7,10 +7,11 @@ "zoneTemplateDir" : "/etc/vasum/templates/", "runMountPointPrefix" : "/var/run/zones", "defaultId" : "", - "availableVTs" : [3, 4, 5, 6], + "hostVT" : 2, + "availableVTs" : [5, 6, 7, 8, 9], "inputConfig" : {"enabled" : false, - "device" : "gpio_keys.6", - "code" : 116, + "device" : "DELL Dell USB Entry Keyboard", + "code" : 1, "numberOfEvents" : 2, "timeWindowMs" : 500}, "proxyCallRules" : [] diff --git a/server/configs/templates/fedora-kde-demo.conf b/server/configs/templates/fedora-kde-demo.conf new file mode 100644 index 0000000..ca47a33 --- /dev/null +++ b/server/configs/templates/fedora-kde-demo.conf @@ -0,0 +1,19 @@ +{ + "zoneTemplate" : "fedora-kde-demo.sh", + "initWithArgs" : [], + "requestedState" : "stopped", + "ipv4Gateway" : "", + "ipv4" : "", + "cpuQuotaForeground" : -1, + "cpuQuotaBackground" : -1, + "privilege" : 10, + "vt" : 0, + "shutdownTimeout" : 10, + "switchToDefaultAfterTimeout" : true, + "runMountPoint" : "~NAME~/run", + "provisions" : [], + "validLinkPrefixes" : [ "/tmp/", + "/run/", + "/opt/usr/data/", + "/opt/usr/dbsapce/" ] +} diff --git a/server/configs/templates/fedora-kde-demo.sh b/server/configs/templates/fedora-kde-demo.sh new file mode 100755 index 0000000..971a102 --- /dev/null +++ b/server/configs/templates/fedora-kde-demo.sh @@ -0,0 +1,214 @@ +#!/bin/bash + +# template script for creating Fedora LXC container +# This script is a wrapper for the lxc-fedora template +# +# Copyright (c) 2015 Samsung Electronics Co., Ltd All Rights Reserved +# +# Contact: Dariusz Michaluk +# +# 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. + +usage() +{ + cat < + [-p|--path=] [--rootfs=] [--vt=] + [--ipv4=] [--ipv4-gateway=] [-h|--help] +Mandatory args: + -n,--name zone name + -p,--path path to zone config files + --rootfs path to zone rootfs +Optional args: + --vt zone virtual terminal + --ipv4 zone IP address + --ipv4-gateway zone gateway + -h,--help print help +EOF + return 0 +} + +options=$(getopt -o hp:n: -l help,rootfs:,path:,vt:,name:,ipv4:,ipv4-gateway: -- "$@") +if [ $? -ne 0 ]; then + usage $(basename $0) + exit 1 +fi +eval set -- "$options" + +while true +do + case "$1" in + -h|--help) usage $(basename $0) && exit 0;; + --rootfs) rootfs=$2; shift 2;; + -p|--path) path=$2; shift 2;; + --vt) vt=$2; shift 2;; + -n|--name) name=$2; shift 2;; + --ipv4) ipv4=$2; shift 2;; + --ipv4-gateway) ipv4_gateway=$2; shift 2;; + --) shift 1; break ;; + *) break ;; + esac +done + +if [ "$(id -u)" != "0" ]; then + echo "This script should be run as 'root'" + exit 1 +fi + +if [ -z $name ]; then + echo "Zone name must be given" + exit 1 +fi + +if [ -z "$path" ]; then + echo "'path' parameter is required" + exit 1 +fi + +if [ -z "$rootfs" ]; then + echo "'rootfs' parameter is required" + exit 1 +fi + +/usr/share/lxc/templates/lxc-fedora --name="$name" --path="$path" --rootfs="$rootfs" + +if [ "$vt" -gt "0" ]; then + echo "Setup Desktop" + chroot $rootfs yum -y --nogpgcheck groupinstall "KDE Plasma Workspaces" + chroot $rootfs yum -y --nogpgcheck groupinstall "Firefox Web Browser" + chroot $rootfs yum -y --nogpgcheck install openarena + chroot $rootfs yum -y --nogpgcheck install http://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm \ + http://download1.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm + chroot $rootfs yum -y --nogpgcheck install gstreamer1-libav gstreamer1-vaapi gstreamer1-plugins-{good,good-extras,ugly} + chroot $rootfs yum -y remove pulseaudio + chroot $rootfs passwd -d root + + cat <>${rootfs}/etc/X11/xorg.conf +Section "ServerFlags" + Option "AutoAddDevices" "false" +EndSection +Section "InputDevice" + Identifier "event0" + Driver "evdev" + Option "Device" "/dev/input/event0" + Option "AutoServerLayout" "true" +EndSection +Section "InputDevice" + Identifier "event1" + Driver "evdev" + Option "Device" "/dev/input/event1" + Option "AutoServerLayout" "true" +EndSection +Section "InputDevice" + Identifier "event10" + Driver "evdev" + Option "Device" "/dev/input/event10" + Option "AutoServerLayout" "true" +EndSection +Section "InputDevice" + Identifier "event2" + Driver "evdev" + Option "Device" "/dev/input/event2" + Option "AutoServerLayout" "true" +EndSection +Section "InputDevice" + Identifier "event3" + Driver "evdev" + Option "Device" "/dev/input/event3" + Option "AutoServerLayout" "true" +EndSection +Section "InputDevice" + Identifier "event4" + Driver "evdev" + Option "Device" "/dev/input/event4" + Option "AutoServerLayout" "true" +EndSection +Section "InputDevice" + Identifier "event5" + Driver "evdev" + Option "Device" "/dev/input/event5" + Option "AutoServerLayout" "true" +EndSection +Section "InputDevice" + Identifier "event6" + Driver "evdev" + Option "Device" "/dev/input/event6" + Option "AutoServerLayout" "true" +EndSection +Section "InputDevice" + Identifier "event7" + Driver "evdev" + Option "Device" "/dev/input/event7" + Option "AutoServerLayout" "true" +EndSection +Section "InputDevice" + Identifier "event8" + Driver "evdev" + Option "Device" "/dev/input/event8" + Option "AutoServerLayout" "true" +EndSection +Section "InputDevice" + Identifier "event9" + Driver "evdev" + Option "Device" "/dev/input/event9" + Option "AutoServerLayout" "true" +EndSection +Section "InputDevice" + Identifier "mice" + Driver "evdev" + Option "Device" "/dev/input/mice" + Option "AutoServerLayout" "true" +EndSection +Section "InputDevice" + Identifier "mouse0" + Driver "evdev" + Option "Device" "/dev/input/mouse0" + Option "AutoServerLayout" "true" +EndSection +EOF + + chroot ${rootfs} rm -rf /etc/systemd/system/display-manager.service + cat <${rootfs}/etc/systemd/system/display-manager.service +[Unit] +Description=Start Desktop + +[Service] +ExecStartPre=/bin/mknod /dev/tty${vt} c 0x4 0x${vt} +ExecStart=/bin/startx -- :0 vt${vt} + +[Install] +WantedBy=graphical.target +EOF + + chroot $rootfs sed -i 's/configDir=${HOME}/export HOME=\/root; configDir=${HOME}/g' /bin/startkde + chroot $rootfs ln -sf /dev/null /etc/systemd/system/initial-setup-graphical.service + chroot $rootfs ln -sf /usr/lib/systemd/system/graphical.target /etc/systemd/system/default.target + chroot $rootfs mkdir -p /etc/systemd/system/graphical.target.wants + chroot $rootfs ln -sf /etc/systemd/system/display-manager.service /etc/systemd/system/graphical.target.wants/display-manager.service + + cat <>${path}/config +lxc.mount.entry = /dev/dri dev/dri none bind,optional,create=dir +lxc.mount.entry = /dev/input dev/input none bind,optional,create=dir +lxc.mount.entry = /dev/snd dev/snd none bind,optional,create=dir +### /dev/dri/* +lxc.cgroup.devices.allow = c 226:* rwm +### /dev/input/* +lxc.cgroup.devices.allow = c 13:* rwm +### /dev/snd/* +lxc.cgroup.devices.allow = c 116:* rwm +### /dev/tty* +lxc.cgroup.devices.allow = c 4:* rwm +EOF + +fi diff --git a/server/configs/templates/fedora-kde-wayland-demo.conf b/server/configs/templates/fedora-kde-wayland-demo.conf new file mode 100644 index 0000000..2131567 --- /dev/null +++ b/server/configs/templates/fedora-kde-wayland-demo.conf @@ -0,0 +1,19 @@ +{ + "zoneTemplate" : "fedora-kde-wayland-demo.sh", + "initWithArgs" : [], + "requestedState" : "stopped", + "ipv4Gateway" : "", + "ipv4" : "", + "cpuQuotaForeground" : -1, + "cpuQuotaBackground" : -1, + "privilege" : 10, + "vt" : 0, + "shutdownTimeout" : 10, + "switchToDefaultAfterTimeout" : true, + "runMountPoint" : "~NAME~/run", + "provisions" : [], + "validLinkPrefixes" : [ "/tmp/", + "/run/", + "/opt/usr/data/", + "/opt/usr/dbsapce/" ] +} diff --git a/server/configs/templates/fedora-kde-wayland-demo.sh b/server/configs/templates/fedora-kde-wayland-demo.sh new file mode 100755 index 0000000..f915a9f --- /dev/null +++ b/server/configs/templates/fedora-kde-wayland-demo.sh @@ -0,0 +1,217 @@ +#!/bin/bash + +# template script for creating Fedora LXC container +# This script is a wrapper for the lxc-fedora template +# +# Copyright (c) 2015 Samsung Electronics Co., Ltd All Rights Reserved +# +# Contact: Dariusz Michaluk +# +# 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. + +usage() +{ + cat < + [-p|--path=] [--rootfs=] [--vt=] + [--ipv4=] [--ipv4-gateway=] [-h|--help] +Mandatory args: + -n,--name zone name + -p,--path path to zone config files + --rootfs path to zone rootfs +Optional args: + --vt zone virtual terminal + --ipv4 zone IP address + --ipv4-gateway zone gateway + -h,--help print help +EOF + return 0 +} + +options=$(getopt -o hp:n: -l help,rootfs:,path:,vt:,name:,ipv4:,ipv4-gateway: -- "$@") +if [ $? -ne 0 ]; then + usage $(basename $0) + exit 1 +fi +eval set -- "$options" + +while true +do + case "$1" in + -h|--help) usage $(basename $0) && exit 0;; + --rootfs) rootfs=$2; shift 2;; + -p|--path) path=$2; shift 2;; + --vt) vt=$2; shift 2;; + -n|--name) name=$2; shift 2;; + --ipv4) ipv4=$2; shift 2;; + --ipv4-gateway) ipv4_gateway=$2; shift 2;; + --) shift 1; break ;; + *) break ;; + esac +done + +if [ "$(id -u)" != "0" ]; then + echo "This script should be run as 'root'" + exit 1 +fi + +if [ -z $name ]; then + echo "Zone name must be given" + exit 1 +fi + +if [ -z "$path" ]; then + echo "'path' parameter is required" + exit 1 +fi + +if [ -z "$rootfs" ]; then + echo "'rootfs' parameter is required" + exit 1 +fi + +/usr/share/lxc/templates/lxc-fedora --name="$name" --path="$path" --rootfs="$rootfs" + +if [ "$vt" -gt "0" ]; then + echo "Setup Desktop" + chroot $rootfs yum -y --nogpgcheck groupinstall "KDE Plasma Workspaces" + chroot $rootfs yum -y --nogpgcheck install kwin-wayland qt5-qtwayland xorg-x11-server-Xwayland + chroot $rootfs yum -y --nogpgcheck groupinstall "Firefox Web Browser" + chroot $rootfs yum -y remove pulseaudio + chroot $rootfs passwd -d root + + cat <>${rootfs}/etc/X11/xorg.conf +Section "ServerFlags" + Option "AutoAddDevices" "false" +EndSection +Section "InputDevice" + Identifier "event0" + Driver "evdev" + Option "Device" "/dev/input/event0" + Option "AutoServerLayout" "true" +EndSection +Section "InputDevice" + Identifier "event1" + Driver "evdev" + Option "Device" "/dev/input/event1" + Option "AutoServerLayout" "true" +EndSection +Section "InputDevice" + Identifier "event10" + Driver "evdev" + Option "Device" "/dev/input/event10" + Option "AutoServerLayout" "true" +EndSection +Section "InputDevice" + Identifier "event2" + Driver "evdev" + Option "Device" "/dev/input/event2" + Option "AutoServerLayout" "true" +EndSection +Section "InputDevice" + Identifier "event3" + Driver "evdev" + Option "Device" "/dev/input/event3" + Option "AutoServerLayout" "true" +EndSection +Section "InputDevice" + Identifier "event4" + Driver "evdev" + Option "Device" "/dev/input/event4" + Option "AutoServerLayout" "true" +EndSection +Section "InputDevice" + Identifier "event5" + Driver "evdev" + Option "Device" "/dev/input/event5" + Option "AutoServerLayout" "true" +EndSection +Section "InputDevice" + Identifier "event6" + Driver "evdev" + Option "Device" "/dev/input/event6" + Option "AutoServerLayout" "true" +EndSection +Section "InputDevice" + Identifier "event7" + Driver "evdev" + Option "Device" "/dev/input/event7" + Option "AutoServerLayout" "true" +EndSection +Section "InputDevice" + Identifier "event8" + Driver "evdev" + Option "Device" "/dev/input/event8" + Option "AutoServerLayout" "true" +EndSection +Section "InputDevice" + Identifier "event9" + Driver "evdev" + Option "Device" "/dev/input/event9" + Option "AutoServerLayout" "true" +EndSection +Section "InputDevice" + Identifier "mice" + Driver "evdev" + Option "Device" "/dev/input/mice" + Option "AutoServerLayout" "true" +EndSection +Section "InputDevice" + Identifier "mouse0" + Driver "evdev" + Option "Device" "/dev/input/mouse0" + Option "AutoServerLayout" "true" +EndSection +EOF + + chroot ${rootfs} rm -rf /etc/systemd/system/display-manager.service + cat <${rootfs}/etc/systemd/system/display-manager.service +[Unit] +Description=Start Desktop + +[Service] +ExecStartPre=/bin/mknod /dev/tty${vt} c 0x4 0x${vt} +ExecStart=/bin/startx -- :0 vt${vt} + +[Install] +WantedBy=graphical.target +EOF + + chroot $rootfs rm -rf /bin/startkde + chroot $rootfs cp /bin/startplasmacompositor /bin/startkde + chroot $rootfs sed -i 's/--libinput/--width=1920 --height=1080 --libinput/g' /bin/startkde + chroot $rootfs sed -i 's/\/usr\/libexec\/startplasma/\/bin\/plasmashell/g' /bin/startkde + chroot $rootfs sed -i 's/configDir=${HOME}/export XDG_RUNTIME_DIR=\/tmp; configDir=${HOME}/g' /bin/startkde + chroot $rootfs sed -i 's/X-KDE-Kded-autoload=true/X-KDE-Kded-autoload=false/g' /usr/share/kservices5/kded/*.desktop + + chroot $rootfs ln -sf /dev/null /etc/systemd/system/initial-setup-graphical.service + chroot $rootfs ln -sf /usr/lib/systemd/system/graphical.target /etc/systemd/system/default.target + chroot $rootfs mkdir -p /etc/systemd/system/graphical.target.wants + chroot $rootfs ln -sf /etc/systemd/system/display-manager.service /etc/systemd/system/graphical.target.wants/display-manager.service + + cat <>${path}/config +lxc.mount.entry = /dev/dri dev/dri none bind,optional,create=dir +lxc.mount.entry = /dev/input dev/input none bind,optional,create=dir +lxc.mount.entry = /dev/snd dev/snd none bind,optional,create=dir +### /dev/dri/* +lxc.cgroup.devices.allow = c 226:* rwm +### /dev/input/* +lxc.cgroup.devices.allow = c 13:* rwm +### /dev/snd/* +lxc.cgroup.devices.allow = c 116:* rwm +### /dev/tty* +lxc.cgroup.devices.allow = c 4:* rwm +EOF + +fi diff --git a/server/configs/templates/fedora-lxde-demo.conf b/server/configs/templates/fedora-lxde-demo.conf new file mode 100644 index 0000000..847bb4d --- /dev/null +++ b/server/configs/templates/fedora-lxde-demo.conf @@ -0,0 +1,19 @@ +{ + "zoneTemplate" : "fedora-lxde-demo.sh", + "initWithArgs" : [], + "requestedState" : "stopped", + "ipv4Gateway" : "", + "ipv4" : "", + "cpuQuotaForeground" : -1, + "cpuQuotaBackground" : -1, + "privilege" : 10, + "vt" : 0, + "shutdownTimeout" : 10, + "switchToDefaultAfterTimeout" : true, + "runMountPoint" : "~NAME~/run", + "provisions" : [], + "validLinkPrefixes" : [ "/tmp/", + "/run/", + "/opt/usr/data/", + "/opt/usr/dbsapce/" ] +} diff --git a/server/configs/templates/fedora-lxde-demo.sh b/server/configs/templates/fedora-lxde-demo.sh new file mode 100755 index 0000000..7bd4e02 --- /dev/null +++ b/server/configs/templates/fedora-lxde-demo.sh @@ -0,0 +1,213 @@ +#!/bin/bash + +# template script for creating Fedora LXC container +# This script is a wrapper for the lxc-fedora template +# +# Copyright (c) 2015 Samsung Electronics Co., Ltd All Rights Reserved +# +# Contact: Dariusz Michaluk +# +# 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. + +usage() +{ + cat < + [-p|--path=] [--rootfs=] [--vt=] + [--ipv4=] [--ipv4-gateway=] [-h|--help] +Mandatory args: + -n,--name zone name + -p,--path path to zone config files + --rootfs path to zone rootfs +Optional args: + --vt zone virtual terminal + --ipv4 zone IP address + --ipv4-gateway zone gateway + -h,--help print help +EOF + return 0 +} + +options=$(getopt -o hp:n: -l help,rootfs:,path:,vt:,name:,ipv4:,ipv4-gateway: -- "$@") +if [ $? -ne 0 ]; then + usage $(basename $0) + exit 1 +fi +eval set -- "$options" + +while true +do + case "$1" in + -h|--help) usage $(basename $0) && exit 0;; + --rootfs) rootfs=$2; shift 2;; + -p|--path) path=$2; shift 2;; + --vt) vt=$2; shift 2;; + -n|--name) name=$2; shift 2;; + --ipv4) ipv4=$2; shift 2;; + --ipv4-gateway) ipv4_gateway=$2; shift 2;; + --) shift 1; break ;; + *) break ;; + esac +done + +if [ "$(id -u)" != "0" ]; then + echo "This script should be run as 'root'" + exit 1 +fi + +if [ -z $name ]; then + echo "Zone name must be given" + exit 1 +fi + +if [ -z "$path" ]; then + echo "'path' parameter is required" + exit 1 +fi + +if [ -z "$rootfs" ]; then + echo "'rootfs' parameter is required" + exit 1 +fi + +/usr/share/lxc/templates/lxc-fedora --name="$name" --path="$path" --rootfs="$rootfs" + +if [ "$vt" -gt "0" ]; then + echo "Setup Desktop" + chroot $rootfs yum -y --nogpgcheck groupinstall "LXDE Desktop" + chroot $rootfs yum -y --nogpgcheck groupinstall "Firefox Web Browser" + chroot $rootfs yum -y --nogpgcheck install openarena + chroot $rootfs yum -y --nogpgcheck install http://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm \ + http://download1.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm + chroot $rootfs yum -y --nogpgcheck install gstreamer1-libav gstreamer1-vaapi gstreamer1-plugins-{good,good-extras,ugly} + chroot $rootfs yum -y remove pulseaudio + chroot $rootfs passwd -d root + + cat <>${rootfs}/etc/X11/xorg.conf +Section "ServerFlags" + Option "AutoAddDevices" "false" +EndSection +Section "InputDevice" + Identifier "event0" + Driver "evdev" + Option "Device" "/dev/input/event0" + Option "AutoServerLayout" "true" +EndSection +Section "InputDevice" + Identifier "event1" + Driver "evdev" + Option "Device" "/dev/input/event1" + Option "AutoServerLayout" "true" +EndSection +Section "InputDevice" + Identifier "event10" + Driver "evdev" + Option "Device" "/dev/input/event10" + Option "AutoServerLayout" "true" +EndSection +Section "InputDevice" + Identifier "event2" + Driver "evdev" + Option "Device" "/dev/input/event2" + Option "AutoServerLayout" "true" +EndSection +Section "InputDevice" + Identifier "event3" + Driver "evdev" + Option "Device" "/dev/input/event3" + Option "AutoServerLayout" "true" +EndSection +Section "InputDevice" + Identifier "event4" + Driver "evdev" + Option "Device" "/dev/input/event4" + Option "AutoServerLayout" "true" +EndSection +Section "InputDevice" + Identifier "event5" + Driver "evdev" + Option "Device" "/dev/input/event5" + Option "AutoServerLayout" "true" +EndSection +Section "InputDevice" + Identifier "event6" + Driver "evdev" + Option "Device" "/dev/input/event6" + Option "AutoServerLayout" "true" +EndSection +Section "InputDevice" + Identifier "event7" + Driver "evdev" + Option "Device" "/dev/input/event7" + Option "AutoServerLayout" "true" +EndSection +Section "InputDevice" + Identifier "event8" + Driver "evdev" + Option "Device" "/dev/input/event8" + Option "AutoServerLayout" "true" +EndSection +Section "InputDevice" + Identifier "event9" + Driver "evdev" + Option "Device" "/dev/input/event9" + Option "AutoServerLayout" "true" +EndSection +Section "InputDevice" + Identifier "mice" + Driver "evdev" + Option "Device" "/dev/input/mice" + Option "AutoServerLayout" "true" +EndSection +Section "InputDevice" + Identifier "mouse0" + Driver "evdev" + Option "Device" "/dev/input/mouse0" + Option "AutoServerLayout" "true" +EndSection +EOF + + chroot ${rootfs} rm -rf /etc/systemd/system/display-manager.service + cat <${rootfs}/etc/systemd/system/display-manager.service +[Unit] +Description=Start Desktop + +[Service] +ExecStartPre=/bin/mknod /dev/tty${vt} c 0x4 0x${vt} +ExecStart=/bin/startx -- :0 vt${vt} + +[Install] +WantedBy=graphical.target +EOF + + chroot $rootfs ln -sf /dev/null /etc/systemd/system/initial-setup-graphical.service + chroot $rootfs ln -sf /usr/lib/systemd/system/graphical.target /etc/systemd/system/default.target + chroot $rootfs mkdir -p /etc/systemd/system/graphical.target.wants + chroot $rootfs ln -sf /etc/systemd/system/display-manager.service /etc/systemd/system/graphical.target.wants/display-manager.service + + cat <>${path}/config +lxc.mount.entry = /dev/dri dev/dri none bind,optional,create=dir +lxc.mount.entry = /dev/input dev/input none bind,optional,create=dir +lxc.mount.entry = /dev/snd dev/snd none bind,optional,create=dir +### /dev/dri/* +lxc.cgroup.devices.allow = c 226:* rwm +### /dev/input/* +lxc.cgroup.devices.allow = c 13:* rwm +### /dev/snd/* +lxc.cgroup.devices.allow = c 116:* rwm +### /dev/tty* +lxc.cgroup.devices.allow = c 4:* rwm +EOF + +fi diff --git a/server/configs/templates/fedora.conf b/server/configs/templates/fedora-minimal.conf similarity index 86% rename from server/configs/templates/fedora.conf rename to server/configs/templates/fedora-minimal.conf index 12e0d2a..e8271ec 100644 --- a/server/configs/templates/fedora.conf +++ b/server/configs/templates/fedora-minimal.conf @@ -1,11 +1,11 @@ { - "zoneTemplate" : "fedora.sh", + "zoneTemplate" : "fedora-minimal.sh", "initWithArgs" : [], "requestedState" : "stopped", "ipv4Gateway" : "", "ipv4" : "", "cpuQuotaForeground" : -1, - "cpuQuotaBackground" : 1000, + "cpuQuotaBackground" : -1, "privilege" : 10, "vt" : -1, "shutdownTimeout" : 10, diff --git a/server/configs/templates/fedora.sh b/server/configs/templates/fedora-minimal.sh similarity index 97% rename from server/configs/templates/fedora.sh rename to server/configs/templates/fedora-minimal.sh index dc4db3b..604b08c 100755 --- a/server/configs/templates/fedora.sh +++ b/server/configs/templates/fedora-minimal.sh @@ -49,7 +49,7 @@ eval set -- "$options" while true do case "$1" in - -h|--help) usage $0 && exit 0;; + -h|--help) usage $(basename $0) && exit 0;; --rootfs) rootfs=$2; shift 2;; -p|--path) path=$2; shift 2;; --vt) vt=$2; shift 2;; diff --git a/server/configs/templates/tizen-common-wayland.sh b/server/configs/templates/tizen-common-wayland.sh index 05cca6d..4f7c407 100755 --- a/server/configs/templates/tizen-common-wayland.sh +++ b/server/configs/templates/tizen-common-wayland.sh @@ -48,7 +48,7 @@ eval set -- "$options" while true do case "$1" in - -h|--help) usage $0 && exit 0;; + -h|--help) usage $(basename $0) && exit 0;; --rootfs) rootfs=$2; shift 2;; -p|--path) path=$2; shift 2;; --vt) vt=$2; shift 2;; diff --git a/server/configs/templates/ubuntu.sh b/server/configs/templates/ubuntu.sh index edcf649..1253b4c 100755 --- a/server/configs/templates/ubuntu.sh +++ b/server/configs/templates/ubuntu.sh @@ -49,7 +49,7 @@ eval set -- "$options" while true do case "$1" in - -h|--help) usage $0 && exit 0;; + -h|--help) usage $(basename $0) && exit 0;; --rootfs) rootfs=$2; shift 2;; -p|--path) path=$2; shift 2;; --vt) vt=$2; shift 2;; diff --git a/server/zones-manager-config.hpp b/server/zones-manager-config.hpp index e300e75..cd7d4c0 100644 --- a/server/zones-manager-config.hpp +++ b/server/zones-manager-config.hpp @@ -65,6 +65,11 @@ struct ZonesManagerConfig { std::string zoneTemplateDir; /** + * Host VT + */ + int hostVT; + + /** * VTs available for zones */ std::vector availableVTs; @@ -91,6 +96,7 @@ struct ZonesManagerConfig { cleanUpZonesPath, zoneImagePath, zoneTemplateDir, + hostVT, availableVTs, inputConfig, runMountPointPrefix, diff --git a/server/zones-manager.cpp b/server/zones-manager.cpp index baf3ed1..7a2bf11 100644 --- a/server/zones-manager.cpp +++ b/server/zones-manager.cpp @@ -37,6 +37,7 @@ #include "utils/fs.hpp" #include "utils/img.hpp" #include "utils/environment.hpp" +#include "utils/vt.hpp" #include "api/messages.hpp" #include @@ -392,8 +393,7 @@ void ZonesManager::focusInternal(Zones::iterator iter) if (iter == mZones.end()) { if (!mActiveZoneId.empty()) { LOGI("Focus to: host"); - // give back the focus to the host - // TODO switch to host vt + utils::activateVT(mConfig.hostVT); mActiveZoneId.clear(); } return; @@ -519,10 +519,7 @@ ZonesManager::Zones::iterator ZonesManager::getRunningForegroundZoneIterator() } auto iter = findZone(mActiveZoneId); if (!get(iter).isRunning()) { - // Can zone change its state by itself? - // Maybe when it is shut down by itself? TODO check it LOGW("Active zone " << mActiveZoneId << " is not running any more!"); - assert(false); return mZones.end(); } return iter; @@ -1149,21 +1146,23 @@ void ZonesManager::generateNewConfig(const std::string& id, ZONE_NAME_REGEX, id); - if (dynamicConfig.vt >= 0 && !dynamicConfig.ipv4Gateway.empty() && !dynamicConfig.ipv4.empty()) { + if (dynamicConfig.vt >= 0) { // generate first free VT number const int freeVT = getVTForNewZone(); LOGD("VT number: " << freeVT); dynamicConfig.vt = freeVT; - // generate third IP octet for network config - std::string thirdOctetStr = std::to_string(ZONE_IP_BASE_THIRD_OCTET + freeVT); - LOGD("IP third octet: " << thirdOctetStr); - dynamicConfig.ipv4Gateway = boost::regex_replace(dynamicConfig.ipv4Gateway, - ZONE_IP_THIRD_OCTET_REGEX, - thirdOctetStr); - dynamicConfig.ipv4 = boost::regex_replace(dynamicConfig.ipv4, - ZONE_IP_THIRD_OCTET_REGEX, - thirdOctetStr); + if (!dynamicConfig.ipv4Gateway.empty() && !dynamicConfig.ipv4.empty()) { + // generate third IP octet for network config + std::string thirdOctetStr = std::to_string(ZONE_IP_BASE_THIRD_OCTET + freeVT); + LOGD("IP third octet: " << thirdOctetStr); + dynamicConfig.ipv4Gateway = boost::regex_replace(dynamicConfig.ipv4Gateway, + ZONE_IP_THIRD_OCTET_REGEX, + thirdOctetStr); + dynamicConfig.ipv4 = boost::regex_replace(dynamicConfig.ipv4, + ZONE_IP_THIRD_OCTET_REGEX, + thirdOctetStr); + } } // save dynamic config diff --git a/tests/unit_tests/configs/test-daemon.conf.in b/tests/unit_tests/configs/test-daemon.conf.in index 66ca69f..2ed411b 100644 --- a/tests/unit_tests/configs/test-daemon.conf.in +++ b/tests/unit_tests/configs/test-daemon.conf.in @@ -7,6 +7,7 @@ "zoneTemplateDir" : "@VSM_TEST_CONFIG_INSTALL_DIR@/templates/", "runMountPointPrefix" : "", "defaultId" : "", + "hostVT" : 2, "availableVTs" : [], "inputConfig" : {"enabled" : false, "device" : "gpio-keys.4", -- 2.7.4 From d56d6393f67cd587e0d3e8c18a57a4f49a701c2c Mon Sep 17 00:00:00 2001 From: Jan Olszak Date: Mon, 19 Oct 2015 14:49:45 +0200 Subject: [PATCH 15/16] common: Inotify implementation [Feature] Asynchronous file system events [Cause] N/A [Solution] N/A [Verification] Build, install and run tests Change-Id: I53b1d75026550cf3563dce7fb1ea50eede4fb5cb --- common/utils/eventfd.hpp | 2 +- common/utils/fs.cpp | 53 +++++++++--- common/utils/fs.hpp | 5 ++ common/utils/inotify.cpp | 151 ++++++++++++++++++++++++++++++++++ common/utils/inotify.hpp | 88 ++++++++++++++++++++ common/utils/paths.hpp | 3 +- common/utils/signalfd.hpp | 4 +- tests/unit_tests/utils/ut-inotify.cpp | 150 +++++++++++++++++++++++++++++++++ 8 files changed, 439 insertions(+), 17 deletions(-) create mode 100644 common/utils/inotify.cpp create mode 100644 common/utils/inotify.hpp create mode 100644 tests/unit_tests/utils/ut-inotify.cpp diff --git a/common/utils/eventfd.hpp b/common/utils/eventfd.hpp index f49bbff..698847d 100644 --- a/common/utils/eventfd.hpp +++ b/common/utils/eventfd.hpp @@ -31,7 +31,7 @@ class EventFD { public: EventFD(); - ~EventFD(); + virtual ~EventFD(); EventFD(const EventFD& eventfd) = delete; EventFD& operator=(const EventFD&) = delete; diff --git a/common/utils/fs.cpp b/common/utils/fs.cpp index a7a979a..62e6c37 100644 --- a/common/utils/fs.cpp +++ b/common/utils/fs.cpp @@ -135,6 +135,35 @@ bool isCharDevice(const std::string& path) return ::stat(path.c_str(), &s) == 0 && S_IFCHR == (s.st_mode & S_IFMT); } +void assertIsDir(const std::string& path) +{ + if (path.empty()) { + const std::string msg = "Empty path"; + LOGE(msg); + throw UtilsException(msg); + } + + struct stat s; + if (::stat(path.c_str(), &s)) { + const std::string msg = "Error in stat() " + path + ": " + getSystemErrorMessage(); + LOGE(msg); + throw UtilsException(msg); + } + + if(!(s.st_mode & S_IFDIR)) { + const std::string msg = "Not a directory"; + LOGE(msg); + throw UtilsException(msg); + } + + if(::access(path.c_str(), X_OK) < 0) { + const std::string msg = "Not a traversable directory"; + LOGE(msg); + throw UtilsException(msg); + } +} + + namespace { // NOTE: Should be the same as in systemd/src/core/mount-setup.c const std::string RUN_MOUNT_POINT_OPTIONS = "mode=755,smackfstransmute=System::Run"; @@ -270,8 +299,8 @@ bool copyDirContentsRec(const boost::filesystem::path& src, const boost::filesys { try { for (fs::directory_iterator file(src); - file != fs::directory_iterator(); - ++file) { + file != fs::directory_iterator(); + ++file) { fs::path current(file->path()); fs::path destination = dst / current.filename(); @@ -400,7 +429,7 @@ bool createDirs(const std::string& path, mode_t mode) fs::remove(*iter, errorCode); if (errorCode) { LOGE("Error during cleaning: dir: " << *iter - << ", msg: " << errorCode.message()); + << ", msg: " << errorCode.message()); } } return false; @@ -455,12 +484,12 @@ bool createFile(const std::string& path, int flags, mode_t mode) bool createFifo(const std::string& path, mode_t mode) { - int ret = ::mkfifo(path.c_str(), mode); - if (ret < 0) { - LOGE("Failed to make fifo: path=host:" << path); - return false; - } - return true; + int ret = ::mkfifo(path.c_str(), mode); + if (ret < 0) { + LOGE("Failed to make fifo: path=host:" << path); + return false; + } + return true; } bool copyFile(const std::string& src, const std::string& dest) @@ -510,9 +539,9 @@ bool createLink(const std::string& src, const std::string& dest) bool retSmack = copySmackLabel(src, dest); if (!retSmack) { LOGE("Failed to copy smack label: path=host:" - << src - << ", path=host:" - << dest); + << src + << ", path=host:" + << dest); boost::system::error_code ec; fs::remove(dest, ec); if (!ec) { diff --git a/common/utils/fs.hpp b/common/utils/fs.hpp index bff2e70..2237450 100644 --- a/common/utils/fs.hpp +++ b/common/utils/fs.hpp @@ -65,6 +65,11 @@ bool removeFile(const std::string& path); bool isCharDevice(const std::string& path); /** + * Checks if a path exists and points to a directory + */ +void assertIsDir(const std::string& path); + +/** * List all (including '.' and '..' entries) dir entries */ bool listDir(const std::string& path, std::vector& files); diff --git a/common/utils/inotify.cpp b/common/utils/inotify.cpp new file mode 100644 index 0000000..a94d95b --- /dev/null +++ b/common/utils/inotify.cpp @@ -0,0 +1,151 @@ +/* +* Copyright (c) 2015 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 Inotify wrapper + */ + +#include "utils/inotify.hpp" +#include "utils/paths.hpp" +#include "utils/fs.hpp" +#include "utils/fd-utils.hpp" +#include "utils/exception.hpp" + +#include "logger/logger.hpp" + +#include + +#include + + +namespace utils { + +Inotify::Inotify(ipc::epoll::EventPoll& eventPoll) + :mEventPoll(eventPoll) +{ + mFD = ::inotify_init1(IN_CLOEXEC); + if (mFD == -1) { + const std::string msg = "Error in inotify_init1: " + getSystemErrorMessage(); + LOGE(msg); + throw UtilsException(msg); + } + + mEventPoll.addFD(mFD, EPOLLIN, std::bind(&Inotify::handleInternal, this)); +} + +Inotify::~Inotify() +{ + Lock lock(mMutex); + + for(const auto& handler: mHandlers) { + if (-1 == ::inotify_rm_watch(mFD, handler.watchID)) { + LOGE("Error in inotify_rm_watch: " + getSystemErrorMessage()); + } + } + mEventPoll.removeFD(mFD); +} + +int Inotify::getFD() const +{ + return mFD; +} + +void Inotify::setHandler(const std::string& path, + const uint32_t eventMask, + const Callback&& callback) +{ + Lock lock(mMutex); + + removeHandlerInternal(path); + + int watchID = ::inotify_add_watch(mFD, path.c_str(), eventMask); + if (-1 == watchID) { + const std::string msg = "Error in inotify_add_watch: " + getSystemErrorMessage(); + LOGE(msg); + throw UtilsException(msg); + } + + mHandlers.push_back({path, watchID, callback}); +} + +void Inotify::removeHandlerInternal(const std::string& path) +{ + // Find the corresponding handler's data + auto it = std::find_if(mHandlers.begin(), mHandlers.end(), [&path](const Handler& h) { + return path == h.path; + }); + + if (it == mHandlers.end()) { + return; + } + + // Unwatch the path + if (-1 == ::inotify_rm_watch(mFD, it->watchID)) { + const std::string msg = "Error in inotify_rm_watch: " + getSystemErrorMessage(); + LOGE(msg); + throw UtilsException(msg); + } + + mHandlers.erase(it); +} + +void Inotify::removeHandler(const std::string& path) +{ + Lock lock(mMutex); + removeHandlerInternal(path); +} + +void Inotify::handleInternal() +{ + Lock lock(mMutex); + + // Get how much data is awaiting + unsigned int bufferSize; + utils::ioctl(mFD, FIONREAD, &bufferSize); + + // Read all events into a buffer + std::vector buffer(bufferSize); + utils::read(mFD, buffer.data(), bufferSize); + + // Handle all events + unsigned int offset = 0; + while (offset < bufferSize) { + struct ::inotify_event *event = reinterpret_cast(&buffer[offset]); + offset = offset + sizeof(inotify_event) + event->len; + + if(event->mask & IN_IGNORED) { + // Watch was removed - ignore + continue; + } + + auto it = std::find_if(mHandlers.begin(), mHandlers.end(), [event](const Handler& h) { + return event->wd == h.watchID; + }); + if (it == mHandlers.end()) { + // Meantime the callback was deleted by another callback + LOGE("No callback for file: " << event->name); + continue; + } + + it->call(event->name, event->mask); + } +} + +} // namespace utils diff --git a/common/utils/inotify.hpp b/common/utils/inotify.hpp new file mode 100644 index 0000000..15c90b8 --- /dev/null +++ b/common/utils/inotify.hpp @@ -0,0 +1,88 @@ +/* +* Copyright (c) 2015 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 Inotify wrapper + */ + +#ifndef COMMON_UTILS_INOTIFY_HPP +#define COMMON_UTILS_INOTIFY_HPP + +#include "ipc/epoll/event-poll.hpp" + +#include +#include +#include + +#include + + +namespace utils { + +/** + * Inotify monitors a directory and when a specified file or folder + * is created or deleted it calls a corresponding handler. + */ +class Inotify { +public: + typedef std::function Callback; + + Inotify(ipc::epoll::EventPoll& eventPoll); + virtual ~Inotify(); + + Inotify(const Inotify&) = delete; + Inotify& operator=(const Inotify&) = delete; + + /** + * Add a callback for a specified path + */ + void setHandler(const std::string& path, const uint32_t eventMask, const Callback&& callback); + + /** + * Stop watching the path + */ + void removeHandler(const std::string& path); + + /** + * @return inotify file descriptor + */ + int getFD() const; + +private: + struct Handler { + std::string path; + int watchID; + Callback call; + }; + + typedef std::lock_guard Lock; + std::recursive_mutex mMutex; + + int mFD; + ipc::epoll::EventPoll& mEventPoll; + std::vector mHandlers; + + void handleInternal(); + void removeHandlerInternal(const std::string& path); +}; + +} // namespace utils + +#endif // COMMON_UTILS_INOTIFY_HPP diff --git a/common/utils/paths.hpp b/common/utils/paths.hpp index 26ebde6..2757130 100644 --- a/common/utils/paths.hpp +++ b/common/utils/paths.hpp @@ -87,7 +87,7 @@ inline void removeTrailingSlash(std::string& path) } } -} // anonymous namespace +} // namespace /* * Gets the dir name of a file path, analogous to dirname(1) @@ -118,7 +118,6 @@ inline std::string getAbsolutePath(const std::string& path, const std::string& b } } - } // namespace utils diff --git a/common/utils/signalfd.hpp b/common/utils/signalfd.hpp index cc72ed9..ea62e70 100644 --- a/common/utils/signalfd.hpp +++ b/common/utils/signalfd.hpp @@ -19,7 +19,7 @@ /** * @file * @author Jan Olszak (j.olszak@samsung.com) - * @brief Eventfd wrapper + * @brief Signalfd wrapper */ #ifndef COMMON_UTILS_SIGNALFD_HPP @@ -49,7 +49,7 @@ public: typedef std::function Callback; SignalFD(ipc::epoll::EventPoll& eventPoll); - ~SignalFD(); + virtual ~SignalFD(); SignalFD(const SignalFD& signalfd) = delete; SignalFD& operator=(const SignalFD&) = delete; diff --git a/tests/unit_tests/utils/ut-inotify.cpp b/tests/unit_tests/utils/ut-inotify.cpp new file mode 100644 index 0000000..31b9e54 --- /dev/null +++ b/tests/unit_tests/utils/ut-inotify.cpp @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2015 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 Unit tests of Inotify + */ + +#include "config.hpp" +#include "ut.hpp" + +#include "utils/inotify.hpp" +#include "utils/fs.hpp" +#include "utils/scoped-dir.hpp" +#include "utils/value-latch.hpp" + +#include "logger/logger.hpp" + +#include "ipc/epoll/event-poll.hpp" +#include "ipc/epoll/thread-dispatcher.hpp" + +#include + +using namespace utils; +namespace fs = boost::filesystem; + +namespace { + +const std::string TEST_DIR = "/tmp/ut-inotify/"; +const std::string DIR_NAME = "dir"; +const std::string FILE_NAME = "file.txt"; + +const std::string DIR_PATH = TEST_DIR + DIR_NAME; +const std::string FILE_PATH = TEST_DIR + FILE_NAME; + + +struct Fixture { + utils::ScopedDir mTestDir; + + Fixture() + :mTestDir(TEST_DIR) + {} +}; + +} // namespace + + +BOOST_FIXTURE_TEST_SUITE(InotifySuite, Fixture) + +BOOST_AUTO_TEST_CASE(ConstructorDesctructor) +{ + ipc::epoll::EventPoll poll; + Inotify i(poll); +} + +BOOST_AUTO_TEST_CASE(CreateDeleteFileHandler) +{ + ipc::epoll::ThreadDispatcher dispatcher; + Inotify i(dispatcher.getPoll()); + + // Callback on creation + ValueLatch createResult; + i.setHandler(TEST_DIR, IN_CREATE, [&](const std::string& name, uint32_t) { + createResult.set(name); + }); + utils::createFile(FILE_PATH, O_WRONLY | O_CREAT, 0666); + BOOST_REQUIRE_EQUAL(createResult.get(), FILE_NAME); + + // Redefine the callback for delete + ValueLatch deleteResult; + i.setHandler(TEST_DIR, IN_DELETE, [&](const std::string& name, uint32_t) { + deleteResult.set(name); + }); + fs::remove(FILE_PATH); + BOOST_REQUIRE_EQUAL(deleteResult.get(), FILE_NAME); +} + +BOOST_AUTO_TEST_CASE(CreateDeleteDirHandler) +{ + ipc::epoll::ThreadDispatcher dispatcher; + Inotify i(dispatcher.getPoll()); + + // Callback on creation + ValueLatch createResult; + i.setHandler(TEST_DIR, IN_CREATE, [&](const std::string& name, uint32_t) { + createResult.set(name); + }); + utils::createEmptyDir(DIR_PATH); + BOOST_REQUIRE_EQUAL(createResult.get(), DIR_NAME); + + + // Redefine the callback for delete + ValueLatch deleteResult; + i.setHandler(TEST_DIR, IN_DELETE, [&](const std::string& name, uint32_t) { + deleteResult.set(name); + }); + fs::remove_all(DIR_PATH); + BOOST_REQUIRE_EQUAL(deleteResult.get(), DIR_NAME); +} + +BOOST_AUTO_TEST_CASE(NoFalseEventHandler) +{ + ipc::epoll::ThreadDispatcher dispatcher; + Inotify i(dispatcher.getPoll()); + + utils::createFile(FILE_PATH, O_WRONLY | O_CREAT, 0666); + + // Callback on creation shouldn't be called + ValueLatch createResult; + i.setHandler(TEST_DIR, IN_CREATE, [&](const std::string& name, uint32_t) { + createResult.set(name); + }); + fs::remove(FILE_PATH); + BOOST_REQUIRE_THROW(createResult.get(10), UtilsException); +} + +BOOST_AUTO_TEST_CASE(RemoveHandler) +{ + ipc::epoll::ThreadDispatcher dispatcher; + Inotify i(dispatcher.getPoll()); + + // Callback on creation + ValueLatch createResult; + i.setHandler(TEST_DIR, IN_CREATE, [&](const std::string& name, uint32_t) { + createResult.set(name); + }); + i.removeHandler(TEST_DIR); + utils::createFile(FILE_PATH, O_WRONLY | O_CREAT, 0666); + fs::remove(FILE_PATH); + BOOST_REQUIRE_THROW(createResult.get(10), UtilsException); +} + +BOOST_AUTO_TEST_SUITE_END() -- 2.7.4 From 5d612bd13106f445abeeebf310bd3dfc8d47e742 Mon Sep 17 00:00:00 2001 From: Krzysztof Dynowski Date: Mon, 12 Oct 2015 17:38:27 +0200 Subject: [PATCH 16/16] lxcpp: cgroups API implementation (part 1) [Feature] Control-groups API for containers [Cause] N/A [Solution] N/A [Verification] build, install, run unit tests Change-Id: I13e921b7568b9d2adf70c9499360d77daf80dee2 --- common/utils/fs.cpp | 2 +- common/utils/text.cpp | 31 ++++++ common/utils/text.hpp | 14 ++- libs/lxcpp/cgroups/cgroup.cpp | 72 +++++++++++++- libs/lxcpp/cgroups/cgroup.hpp | 23 +++-- libs/lxcpp/cgroups/subsystem.cpp | 171 ++++++++++++++++++++++++++++++++++ libs/lxcpp/cgroups/subsystem.hpp | 26 +++++- libs/lxcpp/exception.hpp | 5 + tests/unit_tests/lxcpp/ut-cgroups.cpp | 83 +++++++++++++++++ tests/unit_tests/lxcpp/ut-network.cpp | 1 - tests/unit_tests/utils/ut-text.cpp | 44 +++++++++ 11 files changed, 452 insertions(+), 20 deletions(-) create mode 100644 libs/lxcpp/cgroups/subsystem.cpp create mode 100644 tests/unit_tests/lxcpp/ut-cgroups.cpp create mode 100644 tests/unit_tests/utils/ut-text.cpp diff --git a/common/utils/fs.cpp b/common/utils/fs.cpp index 62e6c37..454ea4c 100644 --- a/common/utils/fs.cpp +++ b/common/utils/fs.cpp @@ -220,7 +220,7 @@ bool mount(const std::string& source, bool umount(const std::string& path) { if (::umount(path.c_str()) != 0) { - LOGD("Umount failed for '" << path << "': " << getSystemErrorMessage()); + LOGE("Umount failed for '" << path << "': " << getSystemErrorMessage()); return false; } return true; diff --git a/common/utils/text.cpp b/common/utils/text.cpp index 5bdf335..72ba65f 100644 --- a/common/utils/text.cpp +++ b/common/utils/text.cpp @@ -22,6 +22,7 @@ */ #include "utils/text.hpp" +#include namespace utils { namespace { @@ -40,4 +41,34 @@ std::string toHexString(const void *data, unsigned len) return s; } +std::string join(const std::vector& vec, const char *delim) +{ + std::stringstream res; + for (const auto& s : vec) { + if (res.tellp() > 0) { + res << delim; + } + res << s; + } + return res.str(); +} + +std::vector split(const std::string& str, const std::string& delim) +{ + std::vector tokens; + if (str.empty()) { + return tokens; + } + + for (std::string::size_type startPos = 0; ; ) { + std::string::size_type endPos = str.find_first_of(delim, startPos); + tokens.push_back(str.substr(startPos, endPos)); + if (endPos == std::string::npos) { + break; + } + startPos = endPos + 1; + } + return tokens; +} + } // namespace utils diff --git a/common/utils/text.hpp b/common/utils/text.hpp index 435f421..465f649 100644 --- a/common/utils/text.hpp +++ b/common/utils/text.hpp @@ -25,14 +25,10 @@ #define COMMON_UTILS_TEXT_HPP #include +#include namespace utils { -/** - * Convert binary bytes array to hex string representation - */ -std::string toHexString(const void *data, unsigned len); - inline bool beginsWith(std::string const &value, std::string const &part) { if (part.size() > value.size()) { @@ -49,6 +45,14 @@ inline bool endsWith(std::string const &value, std::string const &part) return std::equal(part.rbegin(), part.rend(), value.rbegin()); } +/** + * Convert binary bytes array to hex string representation + */ +std::string toHexString(const void *data, unsigned len); + +std::string join(const std::vector& vec, const char *delim); +std::vector split(const std::string& str, const std::string& delim); + } // namespace utils #endif // COMMON_UTILS_TEXT_HPP diff --git a/libs/lxcpp/cgroups/cgroup.cpp b/libs/lxcpp/cgroups/cgroup.cpp index 574af61..688058d 100644 --- a/libs/lxcpp/cgroups/cgroup.cpp +++ b/libs/lxcpp/cgroups/cgroup.cpp @@ -22,5 +22,75 @@ */ #include "lxcpp/cgroups/cgroup.hpp" +#include "lxcpp/exception.hpp" +#include "utils/fs.hpp" +#include "logger/logger.hpp" -// added this file now, to make hpp go through compilation +namespace fs = boost::filesystem; + +namespace lxcpp { + +namespace { +std::string getSubsysName(const std::string& s) { + auto p = s.find(':'); + if (p == std::string::npos) { + const std::string msg = "wgrong cgroup format"; + LOGE(msg); + throw CGroupException(msg); + } + return s.substr(0, p); +} +std::string getCGroupName(const std::string& s) { + auto p = s.find(':'); + if (p == std::string::npos) { + const std::string msg = "wgrong cgroup format"; + LOGE(msg); + throw CGroupException(msg); + } + return s.substr(p + 1); +} +} // namespace + +CGroup::CGroup(const std::string& subsysAndCgroup) : + mSubsys(std::move(getSubsysName(subsysAndCgroup))), + mName(std::move(getCGroupName(subsysAndCgroup))) +{ +} + +bool CGroup::exists() const +{ + const fs::path path = fs::path(mSubsys.getMountPoint()) / mName; + return fs::is_directory(path); +} + +void CGroup::create() +{ + const fs::path path = fs::path(mSubsys.getMountPoint()) / mName; + fs::create_directory(path); +} + +void CGroup::destroy() +{ + const fs::path path = fs::path(mSubsys.getMountPoint()) / mName; + fs::remove_all(path); +} + +void CGroup::setValue(const std::string& param, const std::string& value) +{ + const fs::path path = fs::path(mSubsys.getMountPoint()) / mName / (mSubsys.getName() + "." + param); + utils::saveFileContent(path.string(), value); +} + +std::string CGroup::getValue(const std::string& param) const +{ + const fs::path path = fs::path(mSubsys.getMountPoint()) / mName / (mSubsys.getName() + "." + param); + return utils::readFileContent(path.string()); +} + +void CGroup::assignProcess(pid_t pid) +{ + const fs::path path = fs::path(mSubsys.getMountPoint()) / mName / "tasks"; + utils::saveFileContent(path.string(), std::to_string(pid)); +} + +} //namespace lxcpp diff --git a/libs/lxcpp/cgroups/cgroup.hpp b/libs/lxcpp/cgroups/cgroup.hpp index b47268f..f8d5698 100644 --- a/libs/lxcpp/cgroups/cgroup.hpp +++ b/libs/lxcpp/cgroups/cgroup.hpp @@ -26,23 +26,30 @@ #include "lxcpp/cgroups/subsystem.hpp" +namespace lxcpp { + class CGroup { public: /** * Define control-group object */ - CGroup(const Subsystem& subsys, const std::string& name) : + CGroup(const std::string& subsys, const std::string& name) : mSubsys(subsys), mName(name) { } /** + * Define control-group object (format "subsys:cgroup_path") + */ + CGroup(const std::string& subsysAndCgroup); + + /** * Check if cgroup exists * @return true if cgroup path (subsys.path / mName) exists */ - bool exists(); + bool exists() const; /** * Create cgroup directory @@ -67,17 +74,19 @@ public: * Get cgroup parameter * Equivalent of: cat mSubsys_path/mName/mSubsys_name.param */ - std::string getValue(const std::string& key); + std::string getValue(const std::string& param) const; /** - * Move process to this cgroup (process can't be removed from a cgroup) + * Assign process to this cgroup (will be removed from previous cgroup automatically) * Equivalent of: echo pid > mSubsys_path/mName/tasks */ - void moveProcess(pid_t pid); + void assignProcess(pid_t pid); private: - const Subsystem& mSubsys; // referred subsystem - const std::string& mName; // path relative to subsystem "root" + const Subsystem mSubsys; // referred subsystem + const std::string mName; // path relative to subsystem "root" }; +} //namespace lxcpp + #endif // LXCPP_CGROUPS_CGROUP_HPP diff --git a/libs/lxcpp/cgroups/subsystem.cpp b/libs/lxcpp/cgroups/subsystem.cpp new file mode 100644 index 0000000..dc18683 --- /dev/null +++ b/libs/lxcpp/cgroups/subsystem.cpp @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2015 Samsung Electronics Co., Ltd All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @author Krzysztof Dynowski (k.dynowski@samsumg.com) + * @brief Control-groups management + */ + +#include "lxcpp/cgroups/subsystem.hpp" +#include "lxcpp/exception.hpp" + +#include "utils/exception.hpp" +#include "utils/text.hpp" +#include "utils/fs.hpp" +#include "logger/logger.hpp" + +#include +#include +#include +#include +#include + +namespace lxcpp { + +Subsystem::Subsystem(const std::string& name) : mName(name) +{ + if (mName.empty()) { + const std::string msg = "CGroup name is empty"; + LOGE(msg); + throw CGroupException(msg); + } + + std::vector av = availableSubsystems(); + //find mount point for a name + std::ifstream fileStream("/proc/mounts"); + if (!fileStream.good()) { + const std::string msg = "Failed to open /proc/mounts"; + LOGE(msg); + throw CGroupException(msg); + } + + std::string line; + while (std::getline(fileStream, line).good()) { + std::istringstream iss(line); + auto it = std::istream_iterator(iss); + it++; //skip device name (fake for cgroup filesystem type) + std::string path = *it++; //mount point + if (it->compare("cgroup") != 0) { //filesystem type + continue; + } + it++; //skip filesystem type + if (it->find(mName) != std::string::npos) { + mPath = std::move(path); + break; + } + } +} + +bool Subsystem::isAvailable() const +{ + if (mName.empty()) { + const std::string msg = "CGroup name is empty"; + LOGE(msg); + throw CGroupException(msg); + } + std::vector av = availableSubsystems(); + return std::find(av.begin(), av.end(), mName) != av.end(); +} + +bool Subsystem::isAttached() const +{ + return !mPath.empty(); +} + +const std::string& Subsystem::getMountPoint() const +{ + if (!isAttached()) { + const std::string msg = "CGroup '" + mName + "' is not attached"; + LOGE(msg); + throw CGroupException(msg); + } + return mPath; +} + +void Subsystem::attach(const std::string& path, const std::vector& subs) +{ + if (path.empty()) { + const std::string msg = "Trying attach to emtpy path"; + LOGE(msg); + throw CGroupException(msg); + } + if (!utils::createDirs(path,0777)) { + throw CGroupException("Can't create mount point: " + path + ", " + utils::getSystemErrorMessage()); + } + if (!utils::mount("cgroup", path, "cgroup", 0, utils::join(subs,","))) { + throw CGroupException("Can't mount cgroup: " + path + ", " + utils::getSystemErrorMessage()); + } +} + +void Subsystem::detach(const std::string& path) +{ + if (!utils::umount(path)) { + throw CGroupException("Can't umount cgroup: " + path + ", " + utils::getSystemErrorMessage()); + } +} + +std::vector Subsystem::availableSubsystems() +{ + std::ifstream fileStream("/proc/cgroups"); + if (!fileStream.good()) { + const std::string msg = "Failed to open /proc/cgroups"; + LOGE(msg); + throw CGroupException(msg); + } + + std::vector subs; + std::string line; + while (std::getline(fileStream, line).good()) { + if (utils::beginsWith(line, "#")) { + continue; + } + std::istringstream iss(line); + auto it = std::istream_iterator(iss); + std::string n = *it++; //subsystem name + subs.push_back(n); + } + return subs; +} + +std::vector Subsystem::getCGroups(pid_t pid) +{ + std::ifstream fileStream("/proc/" + std::to_string(pid) + "/cgroup"); + if (!fileStream.good()) { + const std::string msg = "Failed to open /proc//cgroup"; + LOGE(msg); + throw CGroupException(msg); + } + + std::vector subs; + std::string line; + while (std::getline(fileStream, line).good()) { + if (utils::beginsWith(line, "#")) { + continue; + } + // istream_iterator does not support delimiter + std::istringstream iss(line); + std::string n, p; + std::getline(iss, n, ':'); // ignore + std::getline(iss, n, ':'); // subsystem name + std::getline(iss, p, ':'); // cgroup path + subs.push_back(n + ":" + p); + } + return subs; +} + +} //namespace lxcpp diff --git a/libs/lxcpp/cgroups/subsystem.hpp b/libs/lxcpp/cgroups/subsystem.hpp index bb9887e..a4b7c75 100644 --- a/libs/lxcpp/cgroups/subsystem.hpp +++ b/libs/lxcpp/cgroups/subsystem.hpp @@ -29,6 +29,8 @@ #include #include +namespace lxcpp { + class Subsystem { public: /** @@ -36,23 +38,35 @@ public: */ Subsystem(const std::string& name); + const std::string& getName() const + { + return mName; + } + /** * Check if named subsystem is supported by the kernel * @return true if subsystem is listed in /proc/cgroups */ - bool isAvailable(); + bool isAvailable() const; /** * Check if named subsystem is mounted (added to hierarchy) * @return true if subsystem has a mount point (as read from /proc/mounts) */ - bool isAttached(); + bool isAttached() const; + + /** + * Get mount point of this subsystem + * @return subsystem mount point (as read from /proc/mounts) + */ + const std::string& getMountPoint() const; /** * Attach subsystem hierarchy to filesystem - * Equivalent of: mount -t cgroup -o subs(coma-sep) subs(underln-sep) path + * Equivalent of: mount -t cgroup -o subs(coma-sep) cgroup path + * Note: cgroup root must be already mounted (eg. /sys/fs/cgroup) as tmpfs */ - static void attach(const std::string& path, std::vector subs); + static void attach(const std::string& path, const std::vector& subs); /** * Detach subsstem hierarchy from filesystem @@ -74,8 +88,10 @@ public: static std::vector getCGroups(pid_t pid); private: - const std::string& mName; + std::string mName; std::string mPath; }; +} //namespace lxcpp + #endif // LXCPP_CGROUPS_SUBSYSTEM_HPP diff --git a/libs/lxcpp/exception.hpp b/libs/lxcpp/exception.hpp index 37fec5a..061bc6c 100644 --- a/libs/lxcpp/exception.hpp +++ b/libs/lxcpp/exception.hpp @@ -91,6 +91,11 @@ struct NetworkException : public Exception { : Exception(message) {} }; +struct CGroupException : public Exception { + explicit CGroupException (const std::string& message = "Error during setting up a cgroup") + : Exception(message) {} +}; + struct ConfigureException: public Exception { explicit ConfigureException(const std::string& message = "Error while configuring a container") : Exception(message) {} diff --git a/tests/unit_tests/lxcpp/ut-cgroups.cpp b/tests/unit_tests/lxcpp/ut-cgroups.cpp new file mode 100644 index 0000000..510aba3 --- /dev/null +++ b/tests/unit_tests/lxcpp/ut-cgroups.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2015 Samsung Electronics Co., Ltd All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @author Krzysztof Dynowski (k.dynowski@samsung.com) + * @brief Unit tests of lxcpp cgroups managment + */ + +#include "config.hpp" +#include "ut.hpp" +#include "logger/logger.hpp" + +#include "lxcpp/cgroups/subsystem.hpp" + +namespace { + +struct Fixture { + Fixture() {} + ~Fixture() {} +}; + +} // namespace + +BOOST_FIXTURE_TEST_SUITE(LxcppCGroupsSuite, Fixture) + +using namespace lxcpp; + +// assume cgroups are supprted by the system +BOOST_AUTO_TEST_CASE(GetAvailable) +{ + std::vector subs; + BOOST_CHECK_NO_THROW(subs = Subsystem::availableSubsystems()); + BOOST_CHECK(subs.size() > 0); + for (auto n : subs){ + Subsystem s(n); + LOGD(s.getName() << ": " << (s.isAttached()?s.getMountPoint():"[not attached]")); + } +} + +BOOST_AUTO_TEST_CASE(GetCGroupsByPid) +{ + std::vector cg; + BOOST_CHECK_NO_THROW(cg=Subsystem::getCGroups(::getpid())); + BOOST_CHECK(cg.size() > 0); +} + +BOOST_AUTO_TEST_CASE(SubsysAttach) +{ + Subsystem sub("freezer"); + BOOST_CHECK_MESSAGE(sub.getName() == "freezer", "freezer not equal"); + BOOST_CHECK_MESSAGE(sub.isAvailable(), "freezer not found"); + + if (!sub.isAvailable()) return ; + + + if (sub.isAttached()) { + std::string mp = sub.getMountPoint(); + BOOST_CHECK_NO_THROW(Subsystem::detach(mp)); + BOOST_CHECK_NO_THROW(Subsystem::attach(mp, {sub.getName()})); + } + else { + std::string mp = "/sys/fs/cgroup/" + sub.getName(); + BOOST_CHECK_NO_THROW(Subsystem::attach(mp, {sub.getName()})); + BOOST_CHECK_NO_THROW(Subsystem::detach(mp)); + } +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/unit_tests/lxcpp/ut-network.cpp b/tests/unit_tests/lxcpp/ut-network.cpp index a4741f6..75b94c1 100644 --- a/tests/unit_tests/lxcpp/ut-network.cpp +++ b/tests/unit_tests/lxcpp/ut-network.cpp @@ -30,7 +30,6 @@ #include "ut.hpp" #include -#include #include diff --git a/tests/unit_tests/utils/ut-text.cpp b/tests/unit_tests/utils/ut-text.cpp new file mode 100644 index 0000000..b7d8a6d --- /dev/null +++ b/tests/unit_tests/utils/ut-text.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2015 Samsung Electronics Co., Ltd All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @author Krzysztof Dynowski (k.dynowski@samsung.com) + * @brief Unit tests of text helpers + */ + +#include "config.hpp" +#include "ut.hpp" + +#include "utils/text.hpp" + +BOOST_AUTO_TEST_SUITE(TextUtilsSuite) + +BOOST_AUTO_TEST_CASE(SplitText) +{ + std::vector v; + v = utils::split("", ","); + BOOST_CHECK(v.size() == 0); + v = utils::split("a", ","); + BOOST_CHECK(v.size() == 1); + v = utils::split(",", ","); + BOOST_CHECK(v.size() == 2); + v = utils::split("1,2", ","); + BOOST_CHECK(v.size() == 2); +} + +BOOST_AUTO_TEST_SUITE_END() -- 2.7.4