diff --git a/.github/workflows/L2-tests.yml b/.github/workflows/L2-tests.yml index c0076a4d..a8dbfc3a 100755 --- a/.github/workflows/L2-tests.yml +++ b/.github/workflows/L2-tests.yml @@ -38,8 +38,9 @@ jobs: uses: actions/setup-python@v4 with: python-version: '3.x' - - run: pip install jsonref - pip install coverage + - run: | + pip install jsonref + pip install coverage - name: Set up CMake uses: jwlawson/actions-setup-cmake@v1.14 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index c942d8d4..f42628f3 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -45,7 +45,7 @@ jobs: cmake -DCMAKE_BUILD_TYPE=Debug -DRDK_PLATFORM=DEV_VM -DCMAKE_INSTALL_PREFIX:PATH=/usr -DLEGACY_COMPONENTS=ON -DPLUGIN_TESTPLUGIN=ON -DPLUGIN_GPU=ON -DPLUGIN_LOCALTIME=ON -DPLUGIN_RTSCHEDULING=ON -DPLUGIN_HTTPPROXY=ON -DPLUGIN_APPSERVICES=ON -DPLUGIN_IONMEMORY=ON -DPLUGIN_DEVICEMAPPER=ON .. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} queries: +security-and-quality @@ -59,6 +59,6 @@ jobs: - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 with: category: "/language:${{ matrix.language }}" diff --git a/AppInfrastructure/Public/Dobby/IDobbyProxy.h b/AppInfrastructure/Public/Dobby/IDobbyProxy.h index 3e82b26d..646ad4ba 100644 --- a/AppInfrastructure/Public/Dobby/IDobbyProxy.h +++ b/AppInfrastructure/Public/Dobby/IDobbyProxy.h @@ -148,6 +148,14 @@ class IDobbyProxy : public AICommon::Notifier virtual bool wakeupContainer(int32_t descriptor) const = 0; + virtual bool addContainerMount(int32_t descriptor, + const std::string& source, + const std::string& destination, + const std::vector& mountFlags, + const std::string& mountData) const = 0; + + virtual bool removeContainerMount(int32_t descriptor, const std::string& source) const = 0; + virtual bool execInContainer(int32_t cd, const std::string& options, const std::string& command) const = 0; diff --git a/CMakeLists.txt b/CMakeLists.txt index ae51fe2b..6747d510 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,12 +22,12 @@ cmake_minimum_required( VERSION 3.7.0 ) include(GNUInstallDirs) # Project setup -project( Dobby VERSION "3.10.0" ) +project( Dobby VERSION "3.11.0" ) # Set the major and minor version numbers of dobby (also used by plugins) set( DOBBY_MAJOR_VERSION 3 ) -set( DOBBY_MINOR_VERSION 10 ) +set( DOBBY_MINOR_VERSION 11 ) set( DOBBY_MICRO_VERSION 0 ) set(INSTALL_CMAKE_DIR lib/cmake/Dobby) @@ -165,6 +165,12 @@ endif() find_package( breakpad QUIET ) +option( USE_OPEN_TREE_FOR_DYNAMIC_MOUNTS "use open_tree() syscall for dynamic mounts" OFF ) +if ( USE_OPEN_TREE_FOR_DYNAMIC_MOUNTS ) + message("will use open_tree syscall for dynamic mounts") + add_definitions( -DUSE_OPEN_TREE_FOR_DYNAMIC_MOUNTS ) +endif() + # Run libocispec to generate OCI config parsers and necessary C headers include(Findlibocispec) GenerateLibocispec(EXTRA_SCHEMA_PATH "${EXTERNAL_PLUGIN_SCHEMA}") diff --git a/README.md b/README.md index f22a1553..8c5c77ab 100644 --- a/README.md +++ b/README.md @@ -131,15 +131,19 @@ vagrant@dobby-vagrant:~$ DobbyTool help quit quit help help [command] shutdown shutdown -start start [options...] [command] +start start [options...] [command] stop stop [options...] pause pause resume resume +hibernate hibernate [options...] +wakeup wakeup +mount mount +unmount unmount exec exec [options...] list list info info -dumpspec dumpspec [options...] -bundle bundle [options...] +wait wait +set-log-level set-log-level set-dbus set-dbus |
``` For more information about a command, run `DobbyTool help [command]`. For example: diff --git a/bundle/lib/source/templates/OciConfigJson1.0.2-dobby.template b/bundle/lib/source/templates/OciConfigJson1.0.2-dobby.template index 9bd0afe2..3b21d2b0 100644 --- a/bundle/lib/source/templates/OciConfigJson1.0.2-dobby.template +++ b/bundle/lib/source/templates/OciConfigJson1.0.2-dobby.template @@ -105,8 +105,6 @@ static const char* ociJsonTemplate = R"JSON( "readonly": true }, - "rootfsPropagation": "rprivate", - "hostname": "dobby", "mounts": [ @@ -377,7 +375,8 @@ static const char* ociJsonTemplate = R"JSON( "/proc/irq", "/proc/sys", "/proc/sysrq-trigger" - ] + ], + "rootfsPropagation": "slave" }, {{#ENABLE_LEGACY_PLUGINS}} "legacyPlugins": { diff --git a/bundle/lib/source/templates/OciConfigJsonVM1.0.2-dobby.template b/bundle/lib/source/templates/OciConfigJsonVM1.0.2-dobby.template index 51c253b4..c6fb1130 100644 --- a/bundle/lib/source/templates/OciConfigJsonVM1.0.2-dobby.template +++ b/bundle/lib/source/templates/OciConfigJsonVM1.0.2-dobby.template @@ -105,8 +105,6 @@ static const char* ociJsonTemplate = R"JSON( "readonly": true }, - "rootfsPropagation": "rprivate", - "hostname": "dobby", "mounts": [ @@ -388,7 +386,8 @@ static const char* ociJsonTemplate = R"JSON( "/proc/irq", "/proc/sys", "/proc/sysrq-trigger" - ] + ], + "rootfsPropagation": "slave" }, {{#ENABLE_LEGACY_PLUGINS}} "legacyPlugins": { diff --git a/client/lib/include/DobbyProxy.h b/client/lib/include/DobbyProxy.h index 485b4ae2..538ca4e9 100644 --- a/client/lib/include/DobbyProxy.h +++ b/client/lib/include/DobbyProxy.h @@ -102,6 +102,14 @@ class DobbyProxy : public IDobbyProxy bool hibernateContainer(int32_t descriptor, const std::string& options) const override; bool wakeupContainer(int32_t descriptor) const override; + + bool addContainerMount(int32_t descriptor, + const std::string& source, + const std::string& destination, + const std::vector& mountFlags, + const std::string& mountData) const override; + + bool removeContainerMount(int32_t descriptor, const std::string& source) const override; bool execInContainer(int32_t cd, const std::string& options, diff --git a/client/lib/source/DobbyProxy.cpp b/client/lib/source/DobbyProxy.cpp index 46a871f1..b38daa67 100644 --- a/client/lib/source/DobbyProxy.cpp +++ b/client/lib/source/DobbyProxy.cpp @@ -890,6 +890,78 @@ bool DobbyProxy::wakeupContainer(int32_t cd) const return result; } +// ----------------------------------------------------------------------------- +/** + * @brief mounts a new host directory/device inside container + * + * @param[in] cd The container descriptor. + * @param[in] source path of the mount device on the host + * @param[in] destination path of the mount on the container + * @param[in] mountFlags The mount flags is a vector of srings containing the mount optiosn + * e.g. "rbind,ro" + * it should include "bind" + * @param[in] mountData string containing the mount data + * + * @return true on success, false on failure. + */ +bool DobbyProxy::addContainerMount(int32_t cd, const std::string& source, const std::string& destination, const std::vector& mountFlags, const std::string& mountData) const +{ + AI_LOG_FN_ENTRY(); + + // Send off the request + const AI_IPC::VariantList params = { cd, source, destination, mountFlags, mountData}; + AI_IPC::VariantList returns; + + bool result = false; + + if (invokeMethod(DOBBY_CTRL_INTERFACE, + DOBBY_CTRL_METHOD_MOUNT, + params, returns)) + { + if (!AI_IPC::parseVariantList(returns, &result)) + { + result = false; + } + } + + AI_LOG_FN_EXIT(); + return result; +} + +// ----------------------------------------------------------------------------- +/** + * @brief unmounts a directory/device inside the container + * + * @param[in] cd The container descriptor. + * @param[in] source path of the mount device on the host + * + * @return true on success, false on failure. + */ +bool DobbyProxy::removeContainerMount(int32_t cd, const std::string &source) const +{ + + AI_LOG_FN_ENTRY(); + + // Send off the request + const AI_IPC::VariantList params = { cd, source }; + AI_IPC::VariantList returns; + + bool result = false; + + if (invokeMethod(DOBBY_CTRL_INTERFACE, + DOBBY_CTRL_METHOD_UNMOUNT, + params, returns)) + { + if (!AI_IPC::parseVariantList(returns, &result)) + { + result = false; + } + } + + AI_LOG_FN_EXIT(); + return result; +} + // ----------------------------------------------------------------------------- /** * @brief Executes a command in the given container. diff --git a/client/tool/source/Main.cpp b/client/tool/source/Main.cpp index 48fe7f1c..9f841219 100644 --- a/client/tool/source/Main.cpp +++ b/client/tool/source/Main.cpp @@ -589,6 +589,112 @@ static void wakeupCommand(const std::shared_ptr& dobbyProxy, } } +// ----------------------------------------------------------------------------- +/** + * @brief + * + * + * + */ +static void mountCommand(const std::shared_ptr& dobbyProxy, + const std::shared_ptr& readLine, + const std::vector& args) +{ + if (args.size() < 4 || args[0].empty() || args[1].empty() || args[2].empty() || args[3].empty()) + { + readLine->printLnError("must provide at least 4 args; "); + return; + } + + std::string id = args[0]; + if (id.empty()) + { + readLine->printLnError("invalid container id '%s'", id.c_str()); + return; + } + std::string source(args[1]); + std::string destination(args[2]); + std::vector mountFlags; + std::string mountData; + + // parse args[3] which is a comma separated list of flags into a vector of strings + std::string flags = args[3]; + size_t pos = 0; + while ((pos = flags.find(",")) != std::string::npos) + { + std::string flag = flags.substr(0, pos); + mountFlags.push_back(flag); + flags.erase(0, pos + 1); + } + mountFlags.push_back(flags); + + // mountData is optional for now + if(args.size() >= 5 && !args[4].empty()) + { + mountData = args[4]; + } + + int32_t cd = getContainerDescriptor(dobbyProxy, id); + if (cd < 0) + { + readLine->printLnError("failed to find container '%s'", id.c_str()); + } + else + { + if (!dobbyProxy->addContainerMount(cd, source, destination, mountFlags, mountData)) + { + readLine->printLnError("failed to mount %s inside the container %s", source.c_str(), id.c_str()); + } + else + { + readLine->printLn("mount successful for container '%s'", id.c_str()); + } + } +} + +// ----------------------------------------------------------------------------- +/** + * @brief + * + * + * + */ +static void unmountCommand(const std::shared_ptr& dobbyProxy, + const std::shared_ptr& readLine, + const std::vector& args) +{ + if (args.size() < 2 || args[0].empty() || args[1].empty()) + { + readLine->printLnError("must provide at least two args; "); + return; + } + + std::string id = args[0]; + if (id.empty()) + { + readLine->printLnError("invalid container id '%s'", id.c_str()); + return; + } + std::string source(args[1]); + + int32_t cd = getContainerDescriptor(dobbyProxy, id); + if (cd < 0) + { + readLine->printLnError("failed to find container '%s'", id.c_str()); + } + else + { + if (!dobbyProxy->removeContainerMount(cd, source)) + { + readLine->printLnError("failed to unmount %s inside the container %s", source.c_str(), id.c_str()); + } + else + { + readLine->printLn("unmount successful for container '%s'", id.c_str()); + } + } +} + // ----------------------------------------------------------------------------- /** * @brief @@ -1179,7 +1285,19 @@ static void initCommands(const std::shared_ptr& readLine, "wakeup ", "wakeup a container with the given id\n", "\n"); - + + readLine->addCommand("mount", + std::bind(mountCommand, dobbyProxy, std::placeholders::_1, std::placeholders::_2), + "mount ", + "mount a directory from the host inside the container with the given id\n", + "\n"); + + readLine->addCommand("unmount", + std::bind(unmountCommand, dobbyProxy, std::placeholders::_1, std::placeholders::_2), + "unmount ", + "unmount a directory inside the container with the given id\n", + "\n"); + readLine->addCommand("exec", std::bind(execCommand, dobbyProxy, std::placeholders::_1, std::placeholders::_2), "exec [options...] ", diff --git a/daemon/lib/include/Dobby.h b/daemon/lib/include/Dobby.h index 78b0e708..3d6dee01 100644 --- a/daemon/lib/include/Dobby.h +++ b/daemon/lib/include/Dobby.h @@ -91,6 +91,8 @@ class Dobby DOBBY_DBUS_METHOD(resume); DOBBY_DBUS_METHOD(hibernate); DOBBY_DBUS_METHOD(wakeup); + DOBBY_DBUS_METHOD(addMount); + DOBBY_DBUS_METHOD(removeMount); DOBBY_DBUS_METHOD(exec); DOBBY_DBUS_METHOD(list); DOBBY_DBUS_METHOD(getState); diff --git a/daemon/lib/source/Dobby.cpp b/daemon/lib/source/Dobby.cpp index 7ccc9e15..f89743f6 100644 --- a/daemon/lib/source/Dobby.cpp +++ b/daemon/lib/source/Dobby.cpp @@ -650,6 +650,8 @@ void Dobby::initIpcMethods() { DOBBY_CTRL_INTERFACE, DOBBY_CTRL_METHOD_RESUME, &Dobby::resume }, { DOBBY_CTRL_INTERFACE, DOBBY_CTRL_METHOD_HIBERNATE, &Dobby::hibernate }, { DOBBY_CTRL_INTERFACE, DOBBY_CTRL_METHOD_WAKEUP, &Dobby::wakeup }, + { DOBBY_CTRL_INTERFACE, DOBBY_CTRL_METHOD_MOUNT, &Dobby::addMount }, + { DOBBY_CTRL_INTERFACE, DOBBY_CTRL_METHOD_UNMOUNT, &Dobby::removeMount }, { DOBBY_CTRL_INTERFACE, DOBBY_CTRL_METHOD_EXEC, &Dobby::exec }, { DOBBY_CTRL_INTERFACE, DOBBY_CTRL_METHOD_GETSTATE, &Dobby::getState }, { DOBBY_CTRL_INTERFACE, DOBBY_CTRL_METHOD_GETINFO, &Dobby::getInfo }, @@ -1436,6 +1438,119 @@ void Dobby::wakeup(std::shared_ptr replySender) AI_LOG_FN_EXIT(); } +// ----------------------------------------------------------------------------- +/** + * @brief mount a host directory/device inside container + * + * + * + * + */ +void Dobby::addMount(std::shared_ptr replySender) +{ + AI_LOG_FN_ENTRY(); + + // Expecting 5 arguments (int32_t cd, string source, string target, std::vector mountFlags, string mountData) + int32_t descriptor; + std::string source; + std::string destination; + std::vector mountFlags; + std::string mountData; + + if (!AI_IPC::parseVariantList + , std::string> + (replySender->getMethodCallArguments(), &descriptor, &source, &destination, &mountFlags, &mountData)) + { + AI_LOG_ERROR("error getting the args"); + } + else + { + auto doMountLambda = + [manager = mManager, descriptor, source, destination, mountFlags, mountData, replySender]() + { + // add the mount inside the container + bool result = manager->addMount(descriptor, source, destination, mountFlags, mountData); + + // Fire off the reply + if (!replySender->sendReply({ result })) + { + AI_LOG_ERROR("failed to send reply"); + } + }; + + // Queue the work, if successful then we're done + if (mWorkQueue->postWork(std::move(doMountLambda))) + { + AI_LOG_FN_EXIT(); + return; + } + } + + // Fire off the reply + AI_IPC::VariantList results = { false }; + if (!replySender->sendReply(results)) + { + AI_LOG_ERROR("failed to send reply"); + } + + AI_LOG_FN_EXIT(); +} + +// ----------------------------------------------------------------------------- +/** + * @brief unmount a directory/device inside container + * + * + * + * + */ +void Dobby::removeMount(std::shared_ptr replySender) +{ + AI_LOG_FN_ENTRY(); + + // Expecting 2 arguments (int32_t cd, string source) + int32_t descriptor; + std::string source; + + if (!AI_IPC::parseVariantList + + (replySender->getMethodCallArguments(), &descriptor, &source)) + { + AI_LOG_ERROR("error getting the args"); + } + else + { + auto doUnmountLambda = + [manager = mManager, descriptor, source, replySender]() + { + //remove the mount inside the container + bool result = manager->removeMount(descriptor, source); + + // Fire off the reply + if (!replySender->sendReply({ result })) + { + AI_LOG_ERROR("failed to send reply"); + } + }; + + // Queue the work, if successful then we're done + if (mWorkQueue->postWork(std::move(doUnmountLambda))) + { + AI_LOG_FN_EXIT(); + return; + } + } + + // Fire off the reply + AI_IPC::VariantList results = { false }; + if (!replySender->sendReply(results)) + { + AI_LOG_ERROR("failed to send reply"); + } + + AI_LOG_FN_EXIT(); +} + // ----------------------------------------------------------------------------- /** * @brief Executes a command in a container diff --git a/daemon/lib/source/DobbyHibernate.cpp b/daemon/lib/source/DobbyHibernate.cpp index 4508be3b..6ac62214 100644 --- a/daemon/lib/source/DobbyHibernate.cpp +++ b/daemon/lib/source/DobbyHibernate.cpp @@ -43,7 +43,8 @@ typedef enum { typedef enum { MEMCR_OK = 0, MEMCR_ERROR = -1, - MEMCR_INVALID_PID = -2 + MEMCR_INVALID_PID = -2, + MEMCR_SOCKET_READ_ERROR = -3 } ServerResponseCode; typedef struct { @@ -142,7 +143,6 @@ static bool SendRcvCmd(const ServerRequest* cmd, ServerResponse* resp, uint32_t AI_LOG_FN_ENTRY(); int cd; int ret; - struct sockaddr_in addr = { 0 }; resp->respCode = MEMCR_ERROR; cd = Connect(serverLocator, timeoutMs); @@ -154,7 +154,7 @@ static bool SendRcvCmd(const ServerRequest* cmd, ServerResponse* resp, uint32_t ret = write(cd, cmd, sizeof(ServerRequest)); if (ret != sizeof(ServerRequest)) { - AI_LOG_ERROR("Socket write failed: ret %d", ret); + AI_LOG_ERROR("Socket write failed: ret %d, %m", ret); close(cd); AI_LOG_FN_EXIT(); return false; @@ -162,7 +162,8 @@ static bool SendRcvCmd(const ServerRequest* cmd, ServerResponse* resp, uint32_t ret = read(cd, resp, sizeof(ServerResponse)); if (ret != sizeof(ServerResponse)) { - AI_LOG_ERROR("Socket read failed: ret %d", ret); + AI_LOG_ERROR("Socket read failed: ret %d, %m", ret); + resp->respCode = MEMCR_SOCKET_READ_ERROR; close(cd); AI_LOG_FN_EXIT(); return false; @@ -188,6 +189,10 @@ DobbyHibernate::Error DobbyHibernate::HibernateProcess(const pid_t pid, const ui AI_LOG_INFO("Hibernate process PID %d success", pid); AI_LOG_FN_EXIT(); return DobbyHibernate::Error::ErrorNone; + } else if (resp.respCode == MEMCR_SOCKET_READ_ERROR) { + AI_LOG_WARN("Error Hibernate timeout process PID %d ret %d", pid, resp.respCode); + AI_LOG_FN_EXIT(); + return DobbyHibernate::Error::ErrorTimeout; } else { AI_LOG_WARN("Error Hibernate process PID %d ret %d", pid, resp.respCode); AI_LOG_FN_EXIT(); diff --git a/daemon/lib/source/DobbyHibernate.h b/daemon/lib/source/DobbyHibernate.h index d060330b..8045173c 100644 --- a/daemon/lib/source/DobbyHibernate.h +++ b/daemon/lib/source/DobbyHibernate.h @@ -32,7 +32,8 @@ class DobbyHibernate enum Error { ErrorNone = 0, - ErrorGeneral = 1 + ErrorGeneral = 1, + ErrorTimeout = 2 }; enum CompressionAlg diff --git a/daemon/lib/source/DobbyManager.cpp b/daemon/lib/source/DobbyManager.cpp index 93d74a1e..6f8f7225 100644 --- a/daemon/lib/source/DobbyManager.cpp +++ b/daemon/lib/source/DobbyManager.cpp @@ -60,6 +60,13 @@ #include #include #include +#include + +#ifdef USE_OPEN_TREE_FOR_DYNAMIC_MOUNTS +# include +#endif + +#include #if defined(USE_SYSTEMD) #include @@ -476,7 +483,10 @@ void DobbyManager::cleanupContainersShutdown() while (it != mContainers.end()) { if ((it->second->state == DobbyContainer::State::Running) || \ - (it->second->state == DobbyContainer::State::Paused)) + (it->second->state == DobbyContainer::State::Paused) || \ + (it->second->state == DobbyContainer::State::Hibernating) || \ + (it->second->state == DobbyContainer::State::Hibernated) || \ + (it->second->state == DobbyContainer::State::Awakening)) { AI_LOG_INFO("Stopping container %s", it->first.c_str()); // By calling the "proper" stop method here, any listening services will be @@ -1566,35 +1576,7 @@ bool DobbyManager::hibernateContainer(int32_t cd, const std::string& options) std::thread hibernateThread = std::thread([=]() { - //TODO: --delay support is temporary and should be removed - int delayMs = 0; - size_t delayMsPos = options.find("--delay="); - if (delayMsPos != std::string::npos) - { - delayMs = std::stoi(&options[delayMsPos + std::string("--delay=").length()], nullptr, 10); - } DobbyHibernate::Error ret = DobbyHibernate::Error::ErrorNone; - - int delayChunkMs = 100; - while (delayMs > 0) - { - int sleepTime = delayMs > delayChunkMs? delayChunkMs : delayMs; - delayMs -= sleepTime; - std::this_thread::sleep_for(std::chrono::milliseconds(sleepTime)); - { - std::lock_guard locker(mLock); - if (mContainers.find(id) == mContainers.end() || - mContainers[id]->descriptor != cd || - mContainers[id]->state != DobbyContainer::State::Hibernating) - { - AI_LOG_WARN("Hibernation of: %s with descriptor %d aborted", id.c_str(), cd); - AI_LOG_FN_EXIT(); - return; - } - } - } - //TODO: --delay support end - // create a stats object for the container to get list of PIDs std::unique_lock locker(mLock); DobbyStats stats(it->first, mEnvironment, mUtilities); @@ -1619,7 +1601,8 @@ bool DobbyManager::hibernateContainer(int32_t cd, const std::string& options) if (ret != DobbyHibernate::Error::ErrorNone) { AI_LOG_WARN("Error hibernating pid: '%d'", pid); - // revert previous Hibernations and break + // try to revert current and previous Hibernations and break + DobbyHibernate::WakeupProcess(pid); while (pidIt != jsonPids.begin()) { --pidIt; @@ -1762,6 +1745,391 @@ bool DobbyManager::wakeupContainer(int32_t cd) return true; } +// ----------------------------------------------------------------------------- +/** + * @brief adds a mount to a running container + * + * @param[in] cd The descriptor of the container to checkpoint. + * @param[in] source The source path of the mount + * @param[in] destination The destination path of the mount + * @param[in] mountFlags The mount flags is vector of strings containing the mount flags + * e.g. "rbind, ro" + * @param[in] mountData a string containing the mountdata + * + * @return true if a container was successfully restored. + */ +bool DobbyManager::addMount(int32_t cd, const std::string &source, const std::string &destination, const std::vector &mountFlags, const std::string &mountData) +{ + AI_LOG_FN_ENTRY(); + + std::lock_guard locker(mLock); + + // find the container + auto it = mContainers.cbegin(); + for (; it != mContainers.cend(); ++it) + { + if (it->second && (it->second->descriptor == cd)) + break; + } + + if (it == mContainers.cend()) + { + AI_LOG_WARN("failed to find container with descriptor %d", cd); + AI_LOG_FN_EXIT(); + return false; + } + + const ContainerId &id = it->first; + + const pid_t containerPid = it->second->containerPid; + if (containerPid == 0) + { + AI_LOG_WARN("failed to find container pid for container with descriptor %d", cd); + AI_LOG_FN_EXIT(); + return false; + } + + uid_t containerUID = mUtilities->getUID(containerPid); + if(containerUID == static_cast(-1)) + { + AI_LOG_ERROR("failed to get UID for container with PID: %d", containerPid); + AI_LOG_FN_EXIT(); + return false; + } + + gid_t containerGID = mUtilities->getGID(containerPid); + if(containerGID == static_cast(-1)) + { + AI_LOG_ERROR("failed to get GID for container with PID: %d", containerPid); + AI_LOG_FN_EXIT(); + return false; + } + static const std::vector> mountFlagsNames = + { + { "rbind", MS_BIND | MS_REC }, + { "bind", MS_BIND }, + { "silent", MS_SILENT }, + { "ro", MS_RDONLY }, + { "sync", MS_SYNCHRONOUS }, + { "dirsync", MS_DIRSYNC }, + { "noatime", MS_NOATIME }, + { "nodiratime", MS_NODIRATIME }, + { "relatime", MS_RELATIME }, + { "strictatime", MS_STRICTATIME }, + { "noexec", MS_NOEXEC }, + { "nodev", MS_NODEV }, + { "nosuid", MS_NOSUID }, + }; + + // convert mountFlags strings vector to a single unsigned long + unsigned long mountOptions = 0; + for (const auto &flag : mountFlags) + { + bool found = false; + for (const auto &flagName : mountFlagsNames) + { + if (flag == flagName.first) + { + mountOptions |= flagName.second; + found = true; + break; + } + } + + if (!found) + { + AI_LOG_ERROR("unknown mount flag: %s, ignoring it", flag.c_str()); + } + } + + // return error if MS_BIND is not set + if (!(mountOptions & MS_BIND)) + { + AI_LOG_ERROR("MS_BIND flag is not set, aborting mount operation"); + AI_LOG_FN_EXIT(); + return false; + } + +#ifdef USE_OPEN_TREE_FOR_DYNAMIC_MOUNTS + int fdMnt = syscall(SYS_open_tree, -EBADF, source.c_str(), OPEN_TREE_CLOEXEC | OPEN_TREE_CLONE); + if (fdMnt < 0) + { + AI_LOG_ERROR("open_tree failed errno: %d container: %d", errno, cd); + AI_LOG_FN_EXIT(); + return false; + } + + auto doMoveMountLambda = [fdMnt, containerUID, containerGID, destination, mountOptions, mountData]() + { + // switch to uid / gid of the host since we are still in the host user namespace + if (syscall(SYS_setresgid, -1, containerGID, -1) != 0) + { + AI_LOG_ERROR("failed to setresgid for container with GID: %d", containerGID); + return false; + } + + if (syscall(SYS_setresuid, -1, containerUID, -1) != 0) + { + AI_LOG_ERROR("failed to setresuid for container with UID: %d", containerUID); + return false; + } + + if (mkdir(destination.c_str(), 0755) != 0) + { + AI_LOG_ERROR("failed to create destination directory %s", destination.c_str()); + return false; + } + + // revert back to root for the mount + if (syscall(SYS_setresgid, -1, 0, -1) != 0) + { + AI_LOG_ERROR("failed to setresgid for root"); + return false; + } + + if (syscall(SYS_setresuid, -1, 0, -1) != 0) + { + AI_LOG_ERROR("failed to setresuid for root"); + return false; + } + + int ret = syscall(SYS_move_mount, fdMnt, "", -EBADF, destination.c_str(), MOVE_MOUNT_F_EMPTY_PATH); + if (ret < 0) + { + AI_LOG_ERROR("move_mount failed errno: %d", errno); + return false; + } + + // mountData is ignored since we only support MS_BIND for now + if(mount("none", destination.c_str(), nullptr, MS_REMOUNT | mountOptions, nullptr)) + { + AI_LOG_WARN("remount failed for %s errno: %d", destination.c_str(), errno); + } + + return true; + + }; + + if(!mUtilities->callInNamespace(containerPid, CLONE_NEWNS, doMoveMountLambda)) + { + AI_LOG_ERROR("failed to addMount for %s in %s", source.c_str(), id.c_str()); + close(fdMnt); + AI_LOG_FN_EXIT(); + return false; + } + + close(fdMnt); + + AI_LOG_INFO("%s is mounted on %s inside %s", source.c_str(), destination.c_str(), id.c_str()); + AI_LOG_FN_EXIT(); + return true; +#else + std::string mountPointInsideContainer = destination; + std::string tempMountPointInsideContainer = std::string(MOUNT_TUNNEL_CONTAINER_PATH) + "/tmpdir"; + std::string tempMountPointOutsideContainer = std::string(MOUNT_TUNNEL_HOST_PATH) + "/tmpdir"; + + // create the temporary mount point outside the container + if (!mUtilities->mkdirRecursive(tempMountPointOutsideContainer.c_str(), 0755)) + { + AI_LOG_ERROR("failed to create temporary mount point %s", tempMountPointOutsideContainer.c_str()); + AI_LOG_FN_EXIT(); + return false; + } + + // mount the source dir on the temporary mount point outside the container + // this is needed to move the mount inside the container namespace later + if(mount(source.c_str(), tempMountPointOutsideContainer.c_str(), nullptr, mountOptions, mountData.data())) + { + AI_LOG_WARN("mount failed for %s errno: %d", destination.c_str(), errno); + mUtilities->rmdirRecursive(tempMountPointOutsideContainer.c_str()); + AI_LOG_FN_EXIT(); + return false; + } + + auto doMoveMountLambda = [containerUID, containerGID, tempMountPointInsideContainer, mountPointInsideContainer]() + { + // switch to uid / gid of the host since we are still in the host user namespace + if (syscall(SYS_setresgid, -1, containerGID, -1) != 0) + { + AI_LOG_ERROR("failed to setresgid for container with GID: %d", containerGID); + return false; + } + + if (syscall(SYS_setresuid, -1, containerUID, -1) != 0) + { + AI_LOG_ERROR("failed to setresuid for container with UID: %d", containerUID); + return false; + } + + if (mkdir(mountPointInsideContainer.c_str(), 0755) != 0) + { + AI_LOG_ERROR("failed to create destination directory %s", mountPointInsideContainer.c_str()); + return false; + } + // revert back to root for the mount + if (syscall(SYS_setresgid, -1, 0, -1) != 0) + { + AI_LOG_ERROR("failed to setresgid for root"); + return false; + } + + if (syscall(SYS_setresuid, -1, 0, -1) != 0) + { + AI_LOG_ERROR("failed to setresuid for root"); + return false; + } + // move the mount from the temporary mount point inside the container to the final mount point + if(mount(tempMountPointInsideContainer.c_str(), mountPointInsideContainer.c_str(), nullptr, MS_MOVE, nullptr)) + { + AI_LOG_WARN("mount failed for src %s, dest %s errno: %d", tempMountPointInsideContainer.c_str(), mountPointInsideContainer.c_str(), errno); + return false; + } + return true; + + }; + + bool success = true; + if(!mUtilities->callInNamespace(containerPid, CLONE_NEWNS, doMoveMountLambda)) + { + AI_LOG_ERROR("failed to addMount for %s in %s", source.c_str(), id.c_str()); + success = false; + } + + // cleanup the temporary mount on the host, we don't need it anymore + if (umount2(tempMountPointOutsideContainer.c_str(), UMOUNT_NOFOLLOW) != 0) + { + AI_LOG_SYS_ERROR(errno, "failed to unmount '%s'", + tempMountPointOutsideContainer.c_str()); + } + else + { + AI_LOG_INFO("unmounted temp mount @ '%s', now deleting mount point", + tempMountPointOutsideContainer.c_str()); + + // can now delete the temporary mount point + if (rmdir(tempMountPointOutsideContainer.c_str()) != 0) + { + AI_LOG_SYS_ERROR(errno, "failed to delete temp mount point @ '%s'", + tempMountPointOutsideContainer.c_str()); + }else{ + AI_LOG_INFO("deleted temp mount point @ '%s'", tempMountPointOutsideContainer.c_str()); + } + } + + if(success) + { + AI_LOG_INFO("%s is mounted on %s inside %s", source.c_str(), destination.c_str(), id.c_str()); + AI_LOG_FN_EXIT(); + return true; + } + else + { + AI_LOG_ERROR("failed to addMount for %s in %s", source.c_str(), id.c_str()); + AI_LOG_FN_EXIT(); + return false; + } +#endif +} + +// ----------------------------------------------------------------------------- +/** + * @brief removes a mount from a running container + * + * @param[in] cd The descriptor of the container to checkpoint. + * @param[in] source + * + * @return true if a container was successfully restored. + */ +bool DobbyManager::removeMount(int32_t cd, const std::string &source) +{ + AI_LOG_FN_ENTRY(); + + std::lock_guard locker(mLock); + + // find the container + auto it = mContainers.cbegin(); + for (; it != mContainers.cend(); ++it) + { + if (it->second && (it->second->descriptor == cd)) + break; + } + + if (it == mContainers.cend()) + { + AI_LOG_WARN("failed to find container with descriptor %d", cd); + AI_LOG_FN_EXIT(); + return false; + } + + const ContainerId &id = it->first; + + const pid_t containerPid = it->second->containerPid; + if (containerPid == 0) + { + AI_LOG_WARN("failed to find container pid for container with descriptor %d", cd); + AI_LOG_FN_EXIT(); + return false; + } + + uid_t containerUID = mUtilities->getUID(containerPid); + if(containerUID == static_cast(-1)) + { + AI_LOG_ERROR("failed to get UID for container with PID: %d", containerPid); + AI_LOG_FN_EXIT(); + return false; + } + gid_t containerGID = mUtilities->getGID(containerPid); + if(containerGID == static_cast(-1)) + { + AI_LOG_ERROR("failed to get GID for container with PID: %d", containerPid); + AI_LOG_FN_EXIT(); + return false; + } + + auto doUnmountLambda = [containerUID, containerGID, source]() + { + // unmount the directory + int ret = syscall(SYS_umount2, source.c_str(), MNT_DETACH); + if (ret < 0) + { + AI_LOG_ERROR("umount failed for %s errno: %d", source.c_str(), errno); + return false; + } + + // switch to uid / gid of the host since we are still in the host user namespace + if (syscall(SYS_setresgid, -1, containerGID, -1) != 0) + { + AI_LOG_ERROR("failed to setresgid for container with GID: %d", containerGID); + return false; + } + + if (syscall(SYS_setresuid, -1, containerUID, -1) != 0) + { + AI_LOG_ERROR("failed to setresuid for container with UID: %d", containerUID); + return false; + } + + // remove the unmounted directory + if (rmdir(source.c_str()) != 0) + { + AI_LOG_WARN("failed to remove source directory %s", source.c_str()); + } + return true; + + }; + + if(!mUtilities->callInNamespace(containerPid, CLONE_NEWNS, doUnmountLambda)) + { + AI_LOG_ERROR("failed to unmount %s in %s", source.c_str(), id.c_str()); + AI_LOG_FN_EXIT(); + return false; + } + + AI_LOG_INFO("%s is unmounted inside %s", source.c_str(), id.c_str()); + AI_LOG_FN_EXIT(); + return true; +} + // ----------------------------------------------------------------------------- /** * @brief Executes a command in a running container diff --git a/daemon/lib/source/include/DobbyManager.h b/daemon/lib/source/include/DobbyManager.h index 4431f52f..91a9876e 100644 --- a/daemon/lib/source/include/DobbyManager.h +++ b/daemon/lib/source/include/DobbyManager.h @@ -122,6 +122,14 @@ class DobbyManager bool hibernateContainer(int32_t cd, const std::string& options); bool wakeupContainer(int32_t cd); + bool addMount(int32_t cd, + const std::string& source, + const std::string& destination, + const std::vector& mountFlags, + const std::string& mountData); + + bool removeMount(int32_t cd, const std::string& source); + bool execInContainer(int32_t cd, const std::string& options, const std::string& command); diff --git a/daemon/process/CMakeLists.txt b/daemon/process/CMakeLists.txt index 6e4b30d4..e53de145 100644 --- a/daemon/process/CMakeLists.txt +++ b/daemon/process/CMakeLists.txt @@ -126,13 +126,15 @@ else() endif() endif() -message("Using settings from ${DOBBY_SETTINGS_FILE}") - -install( - FILES "${DOBBY_SETTINGS_FILE}" - RENAME dobby.json - DESTINATION /etc/ ) - +if ( "${DOBBY_SETTINGS_FILE}" STREQUAL "DONT_INSTALL_DEVICESETTINGSFILE" ) + message("Skipping the installation of device settings file") +else() + message("Using settings from ${DOBBY_SETTINGS_FILE}") + install( + FILES "${DOBBY_SETTINGS_FILE}" + RENAME dobby.json + DESTINATION /etc/ ) +endif() if (USE_SYSTEMD) # Install a systemd launch service config and enables it by default diff --git a/pluginLauncher/lib/include/DobbyRdkPluginManager.h b/pluginLauncher/lib/include/DobbyRdkPluginManager.h index a74fe1b7..2eeb581d 100644 --- a/pluginLauncher/lib/include/DobbyRdkPluginManager.h +++ b/pluginLauncher/lib/include/DobbyRdkPluginManager.h @@ -62,6 +62,11 @@ class DobbyRdkPluginManager // This is public as RDKPluginManager isn't responsible for handling logging std::shared_ptr getContainerLogger() const; void setExitStatus(int status) const; + + std::shared_ptr getContainerConfig() const + { + return mContainerConfig; + }; private: bool loadPlugins(); diff --git a/pluginLauncher/lib/include/DobbyRdkPluginUtils.h b/pluginLauncher/lib/include/DobbyRdkPluginUtils.h index 0886eb1a..2af4f08b 100644 --- a/pluginLauncher/lib/include/DobbyRdkPluginUtils.h +++ b/pluginLauncher/lib/include/DobbyRdkPluginUtils.h @@ -47,6 +47,9 @@ // but we don't programatically know the workspace dir in this code. #define ADDRESS_FILE_DIR "/tmp/dobby/plugin/networking/" +#define MOUNT_TUNNEL_CONTAINER_PATH "/mnt/.containermnttunnel" +#define MOUNT_TUNNEL_HOST_PATH "/tmp/.hostmnttunnel" + typedef struct ContainerNetworkInfo { std::string vethName; diff --git a/protocol/include/DobbyProtocol.h b/protocol/include/DobbyProtocol.h index 70139a3e..3e643092 100644 --- a/protocol/include/DobbyProtocol.h +++ b/protocol/include/DobbyProtocol.h @@ -54,6 +54,8 @@ #define DOBBY_CTRL_METHOD_RESUME "Resume" #define DOBBY_CTRL_METHOD_HIBERNATE "Hibernate" #define DOBBY_CTRL_METHOD_WAKEUP "Wakeup" +#define DOBBY_CTRL_METHOD_MOUNT "Mount" +#define DOBBY_CTRL_METHOD_UNMOUNT "Unmount" #define DOBBY_CTRL_METHOD_EXEC "Exec" #define DOBBY_CTRL_METHOD_GETSTATE "GetState" #define DOBBY_CTRL_METHOD_GETINFO "GetInfo" diff --git a/rdkPlugins/Storage/README.md b/rdkPlugins/Storage/README.md index 38b7a5f4..e4698a78 100644 --- a/rdkPlugins/Storage/README.md +++ b/rdkPlugins/Storage/README.md @@ -54,6 +54,18 @@ It will mount "source" into container "destination" only if the source exists on } } ``` +### Mount tunnel +Storage plugin will create a mount tunnel on devices running older linux kernels. +This will enable dynamic mounting of host devices/directories inside the container on devices running older linux kernels. + +You need to have `rootfsPropagation` set to `slave` in the OCI runtime configuration for the tunneling to work. +Some references : +- https://lwn.net/Articles/690679/ +- https://brauner.io/2023/02/28/mounting-into-mount-namespaces.html +- https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt + +Please note that devices with kernel 5.4 or higher don't need the mount tunnel for dynamic mounts and this code will be disabled at build time. + ### Mount Owners Add the following section to your OCI runtime configuration `config.json` file to configure mount ownership. diff --git a/rdkPlugins/Storage/source/DynamicMountDetails.cpp b/rdkPlugins/Storage/source/DynamicMountDetails.cpp index 733e37f7..0876a754 100644 --- a/rdkPlugins/Storage/source/DynamicMountDetails.cpp +++ b/rdkPlugins/Storage/source/DynamicMountDetails.cpp @@ -211,7 +211,7 @@ bool DynamicMountDetails::onPostStop() const if (stat(targetPath.c_str(), &buffer) == 0) { - if (remove(targetPath.c_str()) == 0) + if (umount(targetPath.c_str()) == 0) { success = true; } diff --git a/rdkPlugins/Storage/source/Storage.cpp b/rdkPlugins/Storage/source/Storage.cpp index b9ffdda3..376bddb0 100644 --- a/rdkPlugins/Storage/source/Storage.cpp +++ b/rdkPlugins/Storage/source/Storage.cpp @@ -24,6 +24,7 @@ #include #include #include +#include /** * Need to do this at the start of every plugin to make sure the correct @@ -45,6 +46,10 @@ Storage::Storage(std::shared_ptr &containerSpec, : mName("Storage"), mContainerConfig(containerSpec), mRootfsPath(rootfsPath), +#ifndef USE_OPEN_TREE_FOR_DYNAMIC_MOUNTS + mMountPointInsideContainer(rootfsPath + MOUNT_TUNNEL_CONTAINER_PATH), + mTempMountPointOutsideContainer(MOUNT_TUNNEL_HOST_PATH), +#endif mUtils(utils) { AI_LOG_FN_ENTRY(); @@ -89,6 +94,28 @@ bool Storage::preCreation() return false; } } + +#ifndef USE_OPEN_TREE_FOR_DYNAMIC_MOUNTS + // Create host directory for the mount tunnel + if (!DobbyRdkPluginUtils::mkdirRecursive(mTempMountPointOutsideContainer, 0755)) + { + AI_LOG_WARN("failed to create dir '%s'", mTempMountPointOutsideContainer.c_str()); + return false; + } + // Create directory inside the container rootfs for the mount tunnel + if (!DobbyRdkPluginUtils::mkdirRecursive(mMountPointInsideContainer, 0755)) + { + AI_LOG_WARN("failed to create dir '%s'", mMountPointInsideContainer.c_str()); + return false; + } + + if(mount(mTempMountPointOutsideContainer.c_str(), mTempMountPointOutsideContainer.c_str(), NULL, MS_BIND, NULL) != 0) + { + AI_LOG_SYS_ERROR(errno, "failed to bind mount '%s'", mTempMountPointOutsideContainer.c_str()); + return false; + } +#endif + AI_LOG_FN_EXIT(); return true; } @@ -107,7 +134,7 @@ bool Storage::createRuntime() // Setting permissions for generated directories if(!(*it)->setPermissions()) { - AI_LOG_ERROR_EXIT("failed to execute createRuntime loop hook"); + AI_LOG_ERROR_EXIT("failed to set permissions for loop mount"); return false; } } @@ -148,6 +175,25 @@ bool Storage::createContainer() { AI_LOG_FN_ENTRY(); +#ifndef USE_OPEN_TREE_FOR_DYNAMIC_MOUNTS + // create the mount tunnel, the mounts on the host will now be visible inside the container dynamically + if (mount(mTempMountPointOutsideContainer.c_str(), + mMountPointInsideContainer.c_str(), + "", MS_BIND, nullptr) != 0) + { + AI_LOG_ERROR_EXIT("failed to bind mount '%s' -> '%s'", + mTempMountPointOutsideContainer.c_str(), + mMountPointInsideContainer.c_str()); + return false; + } + else + { + AI_LOG_INFO("created mount tunnel '%s' -> '%s'", + mTempMountPointOutsideContainer.c_str(), + mMountPointInsideContainer.c_str()); + } +#endif + // Mount temp directory in proper place std::vector> loopMountDetails = getLoopMountDetails(); for(auto it = loopMountDetails.begin(); it != loopMountDetails.end(); it++) @@ -250,6 +296,28 @@ bool Storage::postStop() } } +#ifndef USE_OPEN_TREE_FOR_DYNAMIC_MOUNTS + // cleanup for the mount tunnel + if (umount2(mTempMountPointOutsideContainer.c_str(), UMOUNT_NOFOLLOW) != 0) + { + AI_LOG_SYS_ERROR(errno, "failed to unmount '%s'", + mTempMountPointOutsideContainer.c_str()); + } + else + { + AI_LOG_DEBUG("unmounted temp mount @ '%s', now deleting mount point", + mTempMountPointOutsideContainer.c_str()); + + // can now delete the temporary mount point + if (rmdir(mTempMountPointOutsideContainer.c_str()) != 0) + { + AI_LOG_ERROR_EXIT("failed to delete temp mount point @ '%s'", + mTempMountPointOutsideContainer.c_str()); + return false; + } + } +#endif + AI_LOG_FN_EXIT(); return true; } diff --git a/rdkPlugins/Storage/source/Storage.h b/rdkPlugins/Storage/source/Storage.h index 79f4cd20..e35c34ae 100644 --- a/rdkPlugins/Storage/source/Storage.h +++ b/rdkPlugins/Storage/source/Storage.h @@ -101,7 +101,10 @@ class Storage : public RdkPluginBase std::shared_ptr mContainerConfig; const std::string mRootfsPath; const std::shared_ptr mUtils; - +#ifndef USE_OPEN_TREE_FOR_DYNAMIC_MOUNTS + std::string mMountPointInsideContainer; + std::string mTempMountPointOutsideContainer; +#endif uint32_t getMappedId(uint32_t id, rt_defs_id_mapping **mapping, size_t mapping_len) const; }; diff --git a/tests/L1_testing/mocks/DobbyManagerMock.cpp b/tests/L1_testing/mocks/DobbyManagerMock.cpp index f08121d8..0637e2d0 100755 --- a/tests/L1_testing/mocks/DobbyManagerMock.cpp +++ b/tests/L1_testing/mocks/DobbyManagerMock.cpp @@ -125,6 +125,24 @@ bool DobbyManager::wakeupContainer(int32_t cd) return true; } +bool DobbyManager::addMount(int32_t cd, + const std::string& source, + const std::string& destination, + const std::vector& mountFlags, + const std::string& mountData) +{ + EXPECT_NE(impl, nullptr); + + return impl->addMount(cd, source, destination, mountFlags, mountData); +} + +bool DobbyManager::removeMount(int32_t cd, const std::string& source) +{ + EXPECT_NE(impl, nullptr); + + return impl->removeMount(cd, source); +} + bool DobbyManager::execInContainer(int32_t cd, const std::string& options, const std::string& command) diff --git a/tests/L1_testing/mocks/DobbyManagerMock.h b/tests/L1_testing/mocks/DobbyManagerMock.h index 379de272..11eaee40 100644 --- a/tests/L1_testing/mocks/DobbyManagerMock.h +++ b/tests/L1_testing/mocks/DobbyManagerMock.h @@ -60,6 +60,14 @@ class DobbyManagerMock : public DobbyManagerImpl { MOCK_METHOD(bool, wakeupContainer, (int32_t cd), (override)); + MOCK_METHOD(bool, addMount, (int32_t cd, + const std::string& source, + const std::string& destination, + const std::vector& mountFlags, + const std::string& mountData), (override)); + + MOCK_METHOD(bool, removeMount, (int32_t cd, const std::string& source), (override)); + MOCK_METHOD(bool, execInContainer, (int32_t cd, const std::string& options, const std::string& command), (override)); diff --git a/tests/L1_testing/mocks/DobbyUtils.h b/tests/L1_testing/mocks/DobbyUtils.h index 8a7d7ab7..9f647059 100644 --- a/tests/L1_testing/mocks/DobbyUtils.h +++ b/tests/L1_testing/mocks/DobbyUtils.h @@ -67,6 +67,8 @@ class DobbyUtilsImpl virtual bool callInNamespaceImpl(pid_t pid, int nsType, const std::function& func) const = 0; virtual bool callInNamespaceImpl(int namespaceFd, const std::function& func) const = 0; virtual int startTimerImpl(const std::chrono::milliseconds& timeout,bool oneShot,const std::function& handler) const = 0; + virtual uid_t getUID(pid_t pid) const=0; + virtual gid_t getGID(pid_t pid) const=0; }; class DobbyUtils : public virtual IDobbyUtils_v3 { @@ -106,9 +108,11 @@ class DobbyUtils : public virtual IDobbyUtils_v3 { void clearContainerMetaData(const ContainerId &id) override; bool insertEbtablesRule(const std::string &args) const override; bool deleteEbtablesRule(const std::string &args) const override; - bool callInNamespaceImpl(pid_t pid, int nsType, const std::function& func) const override; - bool callInNamespaceImpl(int namespaceFd, const std::function& func) const override; + bool callInNamespaceImpl(pid_t pid, int nsType, const std::function& func) const override; + bool callInNamespaceImpl(int namespaceFd, const std::function& func) const override; int startTimerImpl(const std::chrono::milliseconds& timeout,bool oneShot,const std::function& handler) const override; + uid_t getUID(pid_t pid) const override; + gid_t getGID(pid_t pid) const override; }; diff --git a/tests/L1_testing/mocks/DobbyUtilsMock.cpp b/tests/L1_testing/mocks/DobbyUtilsMock.cpp index a21d2363..58576381 100755 --- a/tests/L1_testing/mocks/DobbyUtilsMock.cpp +++ b/tests/L1_testing/mocks/DobbyUtilsMock.cpp @@ -215,14 +215,14 @@ bool DobbyUtils::deleteEbtablesRule(const std::string &args) const return impl->deleteEbtablesRule(args); } -bool DobbyUtils::callInNamespaceImpl(pid_t pid, int nsType, const std::function& func) const +bool DobbyUtils::callInNamespaceImpl(pid_t pid, int nsType, const std::function& func) const { EXPECT_NE(impl, nullptr); return impl->callInNamespaceImpl(pid,nsType,func); } -bool DobbyUtils::callInNamespaceImpl(int namespaceFd, const std::function& func) const +bool DobbyUtils::callInNamespaceImpl(int namespaceFd, const std::function& func) const { EXPECT_NE(impl, nullptr); @@ -237,3 +237,16 @@ int DobbyUtils::startTimerImpl(const std::chrono::milliseconds& timeout,bool one } +gid_t DobbyUtils::getGID(pid_t pid) const +{ + EXPECT_NE(impl, nullptr); + + return impl->getGID(pid); +} + +uid_t DobbyUtils::getUID(pid_t pid) const +{ + EXPECT_NE(impl, nullptr); + + return impl->getUID(pid); +} \ No newline at end of file diff --git a/tests/L1_testing/mocks/DobbyUtilsMock.h b/tests/L1_testing/mocks/DobbyUtilsMock.h index 1aca6c77..c3d6c078 100644 --- a/tests/L1_testing/mocks/DobbyUtilsMock.h +++ b/tests/L1_testing/mocks/DobbyUtilsMock.h @@ -53,5 +53,7 @@ class DobbyUtilsMock : public DobbyUtilsImpl{ MOCK_METHOD(bool, callInNamespaceImpl, (pid_t pid, int nsType, const std::function& func), (const, override)); MOCK_METHOD(bool, callInNamespaceImpl, (int namespaceFd, const std::function& func), (const, override)); MOCK_METHOD(int, startTimerImpl, (const std::chrono::milliseconds& timeout,bool oneShot,const std::function& handler), (const, override)); + MOCK_METHOD(gid_t, getUID, (pid_t pid), (const, override)); + MOCK_METHOD(gid_t, getGID, (pid_t pid), (const, override)); }; diff --git a/tests/L1_testing/mocks/dobbymanager/DobbyManager.h b/tests/L1_testing/mocks/dobbymanager/DobbyManager.h index 783ea294..f5d2e1c5 100644 --- a/tests/L1_testing/mocks/dobbymanager/DobbyManager.h +++ b/tests/L1_testing/mocks/dobbymanager/DobbyManager.h @@ -94,7 +94,11 @@ class DobbyManagerImpl { virtual bool hibernateContainer(int32_t cd, const std::string& options) = 0; virtual bool wakeupContainer(int32_t cd) = 0; + + virtual bool addMount(int32_t cd, const std::string& source, const std::string& destination, const std::vector& mountFlags, const std::string& mountData) = 0; + virtual bool removeMount(int32_t cd, const std::string& source) = 0; + virtual bool execInContainer(int32_t cd, const std::string& options, const std::string& command) = 0; @@ -157,6 +161,12 @@ class DobbyManager { bool resumeContainer(int32_t cd); bool hibernateContainer(int32_t cd, const std::string& options); bool wakeupContainer(int32_t cd); + bool addMount(int32_t cd, + const std::string& source, + const std::string& destination, + const std::vector& mountFlags, + const std::string& mountData); + bool removeMount(int32_t cd, const std::string& source); bool execInContainer(int32_t cd, const std::string& options, const std::string& command); diff --git a/tests/L1_testing/tests/DobbyManagerTest/DaemonDobbyManagerTest.cpp b/tests/L1_testing/tests/DobbyManagerTest/DaemonDobbyManagerTest.cpp index 48d5e3df..0a7783a9 100755 --- a/tests/L1_testing/tests/DobbyManagerTest/DaemonDobbyManagerTest.cpp +++ b/tests/L1_testing/tests/DobbyManagerTest/DaemonDobbyManagerTest.cpp @@ -3765,8 +3765,7 @@ TEST_F(DaemonDobbyManagerTest, execInContainer_FailedToExecuteCommand) TEST_F(DaemonDobbyManagerTest, execInContainer_FailureAsContainerNotRunning) { bool return_value; - pid_t pid1 = 1234; - pid_t pid2 = 0; + std::string options = "--tty"; std::string command = "fork exec"; int32_t cd = 1234; @@ -3886,3 +3885,111 @@ TEST_F(DaemonDobbyManagerTest, listContainers_WhenListIsHuge) expect_cleanupContainersShutdown(); } /* listContainers usecases ends here*/ + +/* ----------------------------------------------------------------------------- + * Test functions for :addMount + * + * + * Use case coverage: + * @Success :0 + * @Failure :2 + * ----------------------------------------------------------------------------- +*/ + +TEST_F(DaemonDobbyManagerTest, addMount_failwithoutBINDoption) +{ + int32_t cd = 1234; + std::vector mountFlags = {"ro"}; + std::string source = "/foo/bar1"; + std::string target = "/foo/bar2"; + std::string data = ""; + + ContainerId id = ContainerId::create("container1"); + expect_invalidContainerCleanupTask(); + + expect_startContainerFromBundle(cd,id); + + bool return_value = dobbyManager_test->addMount(cd, source, target, mountFlags, data); + EXPECT_EQ(return_value,false); +} + +/** + * @brief Test addMount. + * Check the addMount method failed when valid Container Id is not found + * + * @return false. + */ +TEST_F(DaemonDobbyManagerTest, addMount_FailedToFindContainer) +{ + int32_t cd = 1234; + int32_t expect_cd = 2345; + std::vector mountFlags = {"ro"}; + std::string source = "/foo/bar1"; + std::string target = "/foo/bar2"; + std::string data = ""; + + ContainerId id = ContainerId::create("container1"); + expect_invalidContainerCleanupTask(); + + expect_startContainerFromBundle(cd,id); + + bool return_value = dobbyManager_test->addMount(expect_cd, source, target, mountFlags, data); + EXPECT_EQ(return_value,false); +} + +/* ----------------------------------------------------------------------------- + * Test functions for :removeMount + * + * + * Use case coverage: + * @Success :1 + * @Failure :1 + * ----------------------------------------------------------------------------- +*/ +/** + * @brief Test removeMount. + * Check the removeMount method success when valid Container Id is found and correct arguments are passed + * + * @return false. + */ +TEST_F(DaemonDobbyManagerTest, removeMount_success) +{ + int32_t cd = 1234; + std::string source = "/foo/bar"; + + ContainerId id = ContainerId::create("container1"); + expect_invalidContainerCleanupTask(); + + expect_startContainerFromBundle(cd,id); + + EXPECT_CALL(*p_utilsMock,callInNamespaceImpl(::testing::_, ::testing::_, ::testing::_)) + .Times(1) + .WillOnce(::testing::Return(true)); + + bool return_value = dobbyManager_test->removeMount(cd, source); + + EXPECT_EQ(return_value,true); +} + +/** + * @brief Test removeMount. + * Check the removeMount method failed when valid Container Id is not found + * + * @return false. + */ +TEST_F(DaemonDobbyManagerTest, removeMount_FailedToFindContainer) +{ + int32_t cd = 1234; + int32_t expect_cd = 2345; + + std::string source = "/foo/bar"; + + ContainerId id = ContainerId::create("container1"); + expect_invalidContainerCleanupTask(); + + expect_startContainerFromBundle(cd,id); + + bool return_value = dobbyManager_test->removeMount(expect_cd, source); + EXPECT_EQ(return_value,false); +} + diff --git a/tests/L1_testing/tests/DobbyTest/DaemonDobbyTests.cpp b/tests/L1_testing/tests/DobbyTest/DaemonDobbyTests.cpp index 2a599a75..85a3fe07 100644 --- a/tests/L1_testing/tests/DobbyTest/DaemonDobbyTests.cpp +++ b/tests/L1_testing/tests/DobbyTest/DaemonDobbyTests.cpp @@ -3699,3 +3699,152 @@ TEST_F(DaemonDobbyTest, setDefaultAIDbusAddresses_success) dobby_test->setDefaultAIDbusAddresses(aiPrivateBusAddress,aiPublicBusAddress); } + + +/** + * @brief Test addMount with valid arguments and successful postWork. + * Check if addMount method handles the case with valid arguments and successful postWork. + * + * @return None. + */ +TEST_F(DaemonDobbyTest, addMountSuccess_validArg_postWorkSuccess) +{ + AI_IPC::VariantList validArgs = { + int32_t(123),/*Simulates a valid argument 'descriptor' with a value of 123*/ + std::string("/foo/bar/source"), + std::string("/foo/bar/dest"), + std::vector { "bind", "ro" }, + std::string("") }; + + EXPECT_CALL(*p_asyncReplySenderMock, getMethodCallArguments()) + .WillOnce(::testing::Return(validArgs)); + + EXPECT_CALL(*p_dobbyManagerMock, addMount(::testing::_,::testing::_,::testing::_,::testing::_,::testing::_)) + .WillOnce(::testing::Return(true)); + + EXPECT_CALL(*p_workQueueMock, postWork(::testing::_)) + .Times(1) + .WillOnce(::testing::Invoke( + [](const WorkFunc &work) { + work(); + return true; + })); + + EXPECT_CALL(*p_asyncReplySenderMock, sendReply(::testing::_)) + .Times(1) + .WillOnce(::testing::Invoke( + [](const AI_IPC::VariantList& replyArgs) { + bool expectedResult = true; + bool actualResult; + if (AI_IPC::parseVariantList + (replyArgs, &actualResult)) + { + EXPECT_EQ(actualResult, expectedResult); + } + return true; + })); + + dobby_test->addMount((std::shared_ptr)p_iasyncReplySender); +} +/** + * @brief Test addMount with empty arguments and failed postWork. + * + * @return None. + */ +TEST_F(DaemonDobbyTest, addMountFailure_invalidArg) +{ + EXPECT_CALL(*p_asyncReplySenderMock, getMethodCallArguments()) + .WillOnce(::testing::Return(AI_IPC::VariantList{})); + + EXPECT_CALL(*p_workQueueMock, postWork(::testing::_)).Times(0); + + EXPECT_CALL(*p_dobbyManagerMock, addMount(::testing::_,::testing::_,::testing::_,::testing::_,::testing::_)).Times(0); + + EXPECT_CALL(*p_asyncReplySenderMock, sendReply(::testing::_)) + .Times(1) + .WillOnce(::testing::Invoke( + [](const AI_IPC::VariantList& replyArgs) { + bool expectedResult = false; + bool actualResult; + if (AI_IPC::parseVariantList + (replyArgs, &actualResult)) + { + EXPECT_EQ(actualResult, expectedResult); + } + return true; + })); + + dobby_test->addMount((std::shared_ptr)p_iasyncReplySender); +} +/** + * @brief Test removeMount with valid arguments and successful postWork. + * Check if removeMount method handles the case with valid arguments and successful postWork. + * + * @return None. + */ +TEST_F(DaemonDobbyTest, removeMountSuccess_validArg_postWorkSuccess) +{ + AI_IPC::VariantList validArgs = { + int32_t(123),/*Simulates a valid argument 'descriptor' with a value of 123*/ + std::string("/foo/bar") }; + + EXPECT_CALL(*p_asyncReplySenderMock, getMethodCallArguments()) + .WillOnce(::testing::Return(validArgs)); + + EXPECT_CALL(*p_dobbyManagerMock, removeMount(::testing::_,::testing::_)) + .WillOnce(::testing::Return(true)); + + EXPECT_CALL(*p_workQueueMock, postWork(::testing::_)) + .Times(1) + .WillOnce(::testing::Invoke( + [](const WorkFunc &work) { + work(); + return true; + })); + + EXPECT_CALL(*p_asyncReplySenderMock, sendReply(::testing::_)) + .Times(1) + .WillOnce(::testing::Invoke( + [](const AI_IPC::VariantList& replyArgs) { + bool expectedResult = true; + bool actualResult; + if (AI_IPC::parseVariantList + (replyArgs, &actualResult)) + { + EXPECT_EQ(actualResult, expectedResult); + } + return true; + })); + + dobby_test->removeMount((std::shared_ptr)p_iasyncReplySender); +} +/** + * @brief Test removeMount with empty arguments and failed postWork. + * + * @return None. + */ +TEST_F(DaemonDobbyTest, removeMountFailure_invalidArg) +{ + EXPECT_CALL(*p_asyncReplySenderMock, getMethodCallArguments()) + .WillOnce(::testing::Return(AI_IPC::VariantList{})); + + EXPECT_CALL(*p_workQueueMock, postWork(::testing::_)).Times(0); + + EXPECT_CALL(*p_dobbyManagerMock, removeMount(::testing::_,::testing::_)).Times(0); + + EXPECT_CALL(*p_asyncReplySenderMock, sendReply(::testing::_)) + .Times(1) + .WillOnce(::testing::Invoke( + [](const AI_IPC::VariantList& replyArgs) { + bool expectedResult = false; + bool actualResult; + if (AI_IPC::parseVariantList + (replyArgs, &actualResult)) + { + EXPECT_EQ(actualResult, expectedResult); + } + return true; + })); + + dobby_test->removeMount((std::shared_ptr)p_iasyncReplySender); +} \ No newline at end of file diff --git a/tests/L1_testing/tests/DobbyUtilsTest/DobbyUtilsTest.cpp b/tests/L1_testing/tests/DobbyUtilsTest/DobbyUtilsTest.cpp index bda397ee..44dcb4ab 100644 --- a/tests/L1_testing/tests/DobbyUtilsTest/DobbyUtilsTest.cpp +++ b/tests/L1_testing/tests/DobbyUtilsTest/DobbyUtilsTest.cpp @@ -96,3 +96,15 @@ TEST_F(DobbyUtilsTest, TestContainerMetaData) EXPECT_EQ(test.getStringMetaData(t_id,"ipaddr",""),""); EXPECT_EQ(test.getIntegerMetaData(t_id,"port",0),0); } + +TEST_F(DobbyUtilsTest, TestgetUID) +{ + pid_t pid = getpid(); + EXPECT_EQ(test.getUID(pid),getuid()); +} + +TEST_F(DobbyUtilsTest, TestgetGID) +{ + pid_t pid = getpid(); + EXPECT_EQ(test.getGID(pid),getgid()); +} \ No newline at end of file diff --git a/utils/include/DobbyUtils.h b/utils/include/DobbyUtils.h index e8851a44..bade8120 100644 --- a/utils/include/DobbyUtils.h +++ b/utils/include/DobbyUtils.h @@ -84,13 +84,13 @@ class DobbyUtils : public virtual IDobbyUtils private: bool callInNamespaceImpl(pid_t pid, int nsType, - const std::function& func) const override; + const std::function& func) const override; bool callInNamespaceImpl(int namespaceFd, - const std::function& func) const override; + const std::function& func) const override; void nsThread(int newNsFd, int nsType, bool* success, - std::function& func) const; + std::function& func) const; public: bool writeTextFileAt(int dirFd, const std::string& path, @@ -148,6 +148,11 @@ class DobbyUtils : public virtual IDobbyUtils private: bool executeCommand(const std::string &command) const; + int getGIDorUID(pid_t pid, const std::string& idType) const; + +public: + uid_t getUID(pid_t pid) const override; + gid_t getGID(pid_t pid) const override; private: std::mutex mMetaDataLock; diff --git a/utils/include/IDobbyUtils.h b/utils/include/IDobbyUtils.h index 990331bc..e735c455 100644 --- a/utils/include/IDobbyUtils.h +++ b/utils/include/IDobbyUtils.h @@ -494,7 +494,7 @@ class IDobbyUtils_v1 * * @return true on success, false on failure. */ - virtual bool callInNamespaceImpl(pid_t pid, int nsType, const std::function& func) const = 0; + virtual bool callInNamespaceImpl(pid_t pid, int nsType, const std::function& func) const = 0; // ------------------------------------------------------------------------- /** @@ -508,7 +508,7 @@ class IDobbyUtils_v1 * * @return true on success, false on failure. */ - virtual bool callInNamespaceImpl(int namespaceFd, const std::function& func) const = 0; + virtual bool callInNamespaceImpl(int namespaceFd, const std::function& func) const = 0; // ------------------------------------------------------------------------- /** @@ -530,6 +530,29 @@ class IDobbyUtils_v1 bool oneShot, const std::function& handler) const = 0; + // ------------------------------------------------------------------------- + /** + * @brief Returns the GID for the given PID + * + * @see IDobbyUtils::getGID + * + * @param[in] pid The PID of the process to get the GID for + * + * @return the GID of the process, or 0 if the GID could not be found + */ + virtual gid_t getGID(pid_t pid) const = 0; + + // ------------------------------------------------------------------------- + /** + * @brief Returns the UID for the given PID + * + * @see IDobbyUtils::getUID + * + * @param[in] pid The PID of the process to get the UID for + * + * @return the UID of the process, or 0 if the UID could not be found + */ + virtual uid_t getUID(pid_t pid) const = 0; }; @@ -561,6 +584,8 @@ class IDobbyUtils_v2 : public virtual IDobbyUtils_v1 using IDobbyUtils_v1::cancelTimer; using IDobbyUtils_v1::getDriverMajorNumber; using IDobbyUtils_v1::deviceAllowed; + using IDobbyUtils_v1::getGID; + using IDobbyUtils_v1::getUID; public: @@ -626,6 +651,8 @@ class IDobbyUtils_v3 : public virtual IDobbyUtils_v2 using IDobbyUtils_v1::cancelTimer; using IDobbyUtils_v1::getDriverMajorNumber; using IDobbyUtils_v1::deviceAllowed; + using IDobbyUtils_v1::getGID; + using IDobbyUtils_v1::getUID; using IDobbyUtils_v2::setIntegerMetaData; using IDobbyUtils_v2::getIntegerMetaData; diff --git a/utils/source/DobbyUtils.cpp b/utils/source/DobbyUtils.cpp index 4c803a3d..92164ecd 100644 --- a/utils/source/DobbyUtils.cpp +++ b/utils/source/DobbyUtils.cpp @@ -541,7 +541,7 @@ int DobbyUtils::getNamespaceFd(pid_t pid, int nsType) const * @return true if successifully entered the namespace, otherwise false. */ void DobbyUtils::nsThread(int newNsFd, int nsType, bool* success, - std::function& func) const + std::function& func) const { AI_LOG_FN_ENTRY(); @@ -562,9 +562,8 @@ void DobbyUtils::nsThread(int newNsFd, int nsType, bool* success, } // execute the caller's function - func(); + *success = func(); - *success = true; AI_LOG_FN_EXIT(); } @@ -585,7 +584,7 @@ void DobbyUtils::nsThread(int newNsFd, int nsType, bool* success, * @return true if successifully entered the namespace, otherwise false. */ bool DobbyUtils::callInNamespaceImpl(int namespaceFd, - const std::function& func) const + const std::function& func) const { AI_LOG_FN_ENTRY(); @@ -628,7 +627,7 @@ bool DobbyUtils::callInNamespaceImpl(int namespaceFd, * @return true if successifully entered the namespace, otherwise false. */ bool DobbyUtils::callInNamespaceImpl(pid_t pid, int nsType, - const std::function& func) const + const std::function& func) const { AI_LOG_FN_ENTRY(); @@ -1661,4 +1660,73 @@ bool DobbyUtils::executeCommand(const std::string &command) const } return true; +} +// ------------------------------------------------------------------------- +/** + * @brief Returns the effective GID or UID for the given PID + * by parsing /proc//status + * + * @param[in] pid The PID of the process to get the GID for + * @param[in] idType The type of ID to get, either "Gid" or "Uid" + * + * @return the GID or UID of the process, or -1 if the ID could not be found + */ +int DobbyUtils::getGIDorUID(pid_t pid, const std::string& idType) const +{ + char filePathBuf[32]; + sprintf(filePathBuf, "/proc/%d/status", pid); + + int fd = open(filePathBuf, O_CLOEXEC | O_RDONLY); + if (fd < 0) + { + AI_LOG_SYS_ERROR(errno, "failed to open procfs file @ '%s'", filePathBuf); + return -1; + } + + __gnu_cxx::stdio_filebuf statusFileBuf(fd, std::ios::in); + std::istream statusFileStream(&statusFileBuf); + + std::string line; + while (std::getline(statusFileStream, line)) + { + if (line.compare(0, idType.size(), idType) == 0) + { + int realId = -1, effectiveId = -1, savedId = 1, filesystemId = 1; + + if (sscanf(line.c_str(), (idType + ":\t%d\t%d\t%d\t%d").c_str(), &realId, &effectiveId, &savedId, &filesystemId) != 4) + { + AI_LOG_WARN("failed to parse %s field, '%s'", idType.c_str(), line.c_str()); + return -1; + } + + return effectiveId; + } + } + + AI_LOG_WARN("failed to find the %s field in the '%s' file", idType.c_str(), filePathBuf); + return -1; +} +// ------------------------------------------------------------------------- +/** + * @brief Returns the GID for the given PID + * + * @param[in] pid The PID of the process to get the GID for + * + * @return the GID of the process, or -1 if the GID could not be found + */ +gid_t DobbyUtils::getGID(pid_t pid) const +{ + return static_cast(getGIDorUID(pid, "Gid")); +} +// ------------------------------------------------------------------------- +/** + * @brief Returns the UID for the given PID + * + * @param[in] pid The PID of the process to get the UID for + * + * @return the UID of the process, or -1 if the UID could not be found + */ +uid_t DobbyUtils::getUID(pid_t pid) const +{ + return static_cast(getGIDorUID(pid, "Uid")); } \ No newline at end of file