lxcpp: Simple attach implementation 40/46340/8
authorJan Olszak <j.olszak@samsung.com>
Tue, 18 Aug 2015 10:02:12 +0000 (12:02 +0200)
committerLukasz Pawelczyk <l.pawelczyk@samsung.com>
Fri, 21 Aug 2015 09:50:23 +0000 (02:50 -0700)
[Feature]       Running code in the container's context
                Socketpair wrapper - Channel
[Cause]         N/A
[Solution]      N/A
[Verification]  Build, install, run tests

Change-Id: Ib5b1011c5f8578ab9e258bcbea3cd7aa3bc233a3

12 files changed:
common/utils/channel.cpp [new file with mode: 0644]
common/utils/channel.hpp [new file with mode: 0644]
common/utils/execute.cpp
common/utils/fd-utils.cpp
common/utils/fd-utils.hpp
libs/lxcpp/container-impl.cpp
libs/lxcpp/container-impl.hpp
libs/lxcpp/container.hpp
libs/lxcpp/process.cpp
libs/lxcpp/process.hpp
tests/unit_tests/lxcpp/ut-process.cpp
tests/unit_tests/utils/ut-channel.cpp [new file with mode: 0644]

diff --git a/common/utils/channel.cpp b/common/utils/channel.cpp
new file mode 100644 (file)
index 0000000..fbb110d
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+*  Copyright (c) 2015 Samsung Electronics Co., Ltd All Rights Reserved
+*
+*  Contact: Jan Olszak <j.olszak@samsung.com>
+*
+*  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   IPC implementation for related processes
+ */
+
+#include "utils/channel.hpp"
+#include "utils/exception.hpp"
+
+#include "logger/logger.hpp"
+
+#include <sys/socket.h>
+
+namespace {
+const int LEFT = 0;
+const int RIGHT = 1;
+}
+
+namespace utils {
+
+Channel::Channel()
+    : mSocketIndex(-1)
+{
+    if (::socketpair(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, mSockets) < 0) {
+        const std::string msg = "socketpair() failed: " +
+                                utils::getSystemErrorMessage();
+        LOGE(msg);
+        throw UtilsException(msg);
+    }
+}
+
+Channel::~Channel()
+{
+    closeSocket(LEFT);
+    closeSocket(RIGHT);
+}
+
+void Channel::setLeft()
+{
+    mSocketIndex = LEFT;
+    utils::close(mSockets[RIGHT]);
+    mSockets[RIGHT] = -1;
+}
+
+void Channel::setRight()
+{
+    mSocketIndex = RIGHT;
+    utils::close(mSockets[LEFT]);
+    mSockets[LEFT] = -1;
+}
+
+void Channel::shutdown()
+{
+    assert(mSocketIndex != -1 && "Channel's end isn't set");
+    closeSocket(mSocketIndex);
+}
+
+void Channel::closeSocket(int socketIndex)
+{
+    utils::shutdown(mSockets[socketIndex]);
+    utils::close(mSockets[socketIndex]);
+    mSockets[socketIndex] = -1;
+}
+
+} // namespace utils
diff --git a/common/utils/channel.hpp b/common/utils/channel.hpp
new file mode 100644 (file)
index 0000000..f537121
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+*  Copyright (c) 2015 Samsung Electronics Co., Ltd All Rights Reserved
+*
+*  Contact: Jan Olszak <j.olszak@samsung.com>
+*
+*  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   IPC implementation for related processes
+ */
+
+#ifndef COMMON_UTILS_CHANNEL_HPP
+#define COMMON_UTILS_CHANNEL_HPP
+
+#include "utils/fd-utils.hpp"
+#include <cassert>
+
+namespace utils {
+
+/**
+ * Channel is implemented with a pair of anonymous sockets
+ */
+class Channel {
+public:
+    Channel();
+    ~Channel();
+
+    Channel(const Channel&) = delete;
+    Channel& operator=(const Channel&) = delete;
+
+    /**
+     * Use the "left" end of the channel
+     * Closes the "right" end
+     */
+    void setLeft();
+
+    /**
+    * Use the "right" end of the channel
+    * Closes the "left" end
+    */
+    void setRight();
+
+    /**
+     * Gracefully shutdown the used end of the channel
+     */
+    void shutdown();
+
+    /**
+     * Send the data to the other end of the channel
+     *
+     * @param data data to send
+     */
+    template<typename Data>
+    void write(const Data& data);
+
+    /**
+     * Receive data of a given type (size)
+     */
+    template<typename Data>
+    Data read();
+
+private:
+
+    void closeSocket(int socketIndex);
+
+    int mSocketIndex;
+    int mSockets[2];
+};
+
+template<typename Data>
+void Channel::write(const Data& data)
+{
+    assert(mSocketIndex != -1 && "Channel's end isn't set");
+
+    utils::write(mSockets[mSocketIndex], &data, sizeof(Data));
+}
+
+template<typename Data>
+Data Channel::read()
+{
+    assert(mSocketIndex != -1 && "Channel's end isn't set");
+
+    Data data;
+    utils::read(mSockets[mSocketIndex], &data, sizeof(Data));
+    return data;
+}
+
+} // namespace utils
+
+#endif // COMMON_UTILS_CHANNEL_HPP
index 55fc912..d047a2e 100644 (file)
@@ -118,7 +118,7 @@ bool executeAndWait(const char* fname, const char* const* argv)
 
 bool waitPid(pid_t pid, int& status)
 {
-    while (waitpid(pid, &status, 0) == -1) {
+    while (::waitpid(pid, &status, 0) == -1) {
         if (errno != EINTR) {
             LOGE("waitpid() failed: " << getSystemErrorMessage());
             return false;
index 4fe9a17..030d1fc 100644 (file)
@@ -111,6 +111,19 @@ void close(int fd)
     }
 }
 
+void shutdown(int fd)
+{
+    if (fd < 0) {
+        return;
+    }
+
+    if (-1 == ::shutdown(fd, SHUT_RDWR)) {
+        std::string msg = "shutdown() failed: " + getSystemErrorMessage();
+        LOGE(msg);
+        throw UtilsException(msg);
+    }
+}
+
 void write(int fd, const void* bufferPtr, const size_t size, int timeoutMS)
 {
     chr::high_resolution_clock::time_point deadline = chr::high_resolution_clock::now() +
index d6a4032..56e3f41 100644 (file)
@@ -35,6 +35,11 @@ namespace utils {
 void close(int fd);
 
 /**
+ * Shut down part of a full-duplex connection
+ */
+void shutdown(int fd);
+
+/**
  * Write to a file descriptor, throw on error.
  *
  * @param fd file descriptor
index e4f290d..6fd9775 100644 (file)
 
 #include "lxcpp/container-impl.hpp"
 #include "lxcpp/exception.hpp"
+#include "lxcpp/process.hpp"
+
+#include "utils/exception.hpp"
+
+#include <unistd.h>
 
 namespace lxcpp {
 
@@ -94,4 +99,57 @@ std::string ContainerImpl::getRootPath()
     throw NotImplementedException();
 }
 
+
+int ContainerImpl::attachChild(void* data) {
+    try {
+        return (*static_cast<Container::AttachCall*>(data))();
+    } catch(...) {
+        return -1; // Non-zero on failure
+    }
+    return 0; // Success
+}
+
+void ContainerImpl::attachParent(utils::Channel& channel, const pid_t interPid)
+{
+    // TODO: Setup cgroups etc
+    pid_t childPid = channel.read<pid_t>();
+
+    // Wait for the Intermediate process
+    lxcpp::waitpid(interPid);
+
+    // Wait for the Child process
+    lxcpp::waitpid(childPid);
+}
+
+void ContainerImpl::attachIntermediate(utils::Channel& channel, Container::AttachCall& call)
+{
+    lxcpp::setns(mInitPid, mNamespaces);
+
+    // PID namespace won't affect the returned pid
+    // CLONE_PARENT: Child's PPID == Caller's PID
+    const pid_t pid = lxcpp::clone(&ContainerImpl::attachChild,
+                                   &call,
+                                   CLONE_PARENT);
+    channel.write(pid);
+}
+
+void ContainerImpl::attach(Container::AttachCall& call)
+{
+    utils::Channel channel;
+
+    const pid_t interPid = lxcpp::fork();
+    if (interPid > 0) {
+        channel.setLeft();
+        attachParent(channel, interPid);
+        channel.shutdown();
+    } else {
+        channel.setRight();
+        attachIntermediate(channel, call);
+        channel.shutdown();
+        ::_exit(0);
+    }
+}
+
+
+
 } // namespace lxcpp
index f5d9547..e236932 100644 (file)
@@ -25,6 +25,9 @@
 #define LXCPP_CONTAINER_IMPL_HPP
 
 #include "lxcpp/container.hpp"
+#include "lxcpp/namespace.hpp"
+
+#include "utils/channel.hpp"
 
 namespace lxcpp {
 
@@ -49,6 +52,19 @@ public:
     void destroy();
     void setRootPath(const std::string& path);
     std::string getRootPath();
+
+    // Other
+    void attach(Container::AttachCall& attachCall);
+
+private:
+
+    // Methods for different stages of setting up the attachment
+    void attachParent(utils::Channel& channel, const pid_t pid);
+    void attachIntermediate(utils::Channel& channel, Container::AttachCall& call);
+    static int attachChild(void* data);
+
+    pid_t mInitPid;
+    std::vector<Namespace> mNamespaces;
 };
 
 } // namespace lxcpp
index 74caa51..4f2d697 100644 (file)
 #define LXCPP_CONTAINER_HPP
 
 #include <string>
+#include <functional>
 
 namespace lxcpp {
 
 class Container {
 public:
+    typedef std::function<int()> AttachCall;
+
     virtual ~Container() {};
 
     virtual std::string getName() = 0;
@@ -48,6 +51,9 @@ public:
     virtual void destroy() = 0;
     virtual void setRootPath(const std::string& path) = 0;
     virtual std::string getRootPath() = 0;
+
+    // Other
+    virtual void attach(AttachCall& attachCall) = 0;
 };
 
 } // namespace lxcpp
index 68fd62c..b707353 100644 (file)
 
 namespace lxcpp {
 
+pid_t fork()
+{
+    pid_t pid = ::fork();
+    if (pid < 0) {
+        const std::string msg = "fork() failed: " +
+                                utils::getSystemErrorMessage();
+        LOGE(msg);
+        throw ProcessSetupException(msg);
+    }
+    return pid;
+}
+
 pid_t clone(int (*function)(void *),
             void *args,
-            const std::vector<Namespace>& namespaces,
-            const int additionalFlags)
+            const int flags)
 {
     // Won't fail, well known resource name
     size_t stackSize = ::sysconf(_SC_PAGESIZE);
@@ -47,56 +58,66 @@ pid_t clone(int (*function)(void *),
     // PAGESIZE is enough, it'll exec after this
     char *stack = static_cast<char*>(::alloca(stackSize));
 
-    pid_t pid = ::clone(function, stack  + stackSize, toFlag(namespaces) | additionalFlags | SIGCHLD, args);
+    pid_t pid = ::clone(function, stack  + stackSize, flags | SIGCHLD, args);
     if (pid < 0) {
-        const std::string msg = utils::getSystemErrorMessage();
-        LOGE("clone() failed: " << msg);
-        throw ProcessSetupException("clone() failed " + msg);
+        const std::string msg = "clone() failed: " +
+                                utils::getSystemErrorMessage();
+        LOGE(msg);
+        throw ProcessSetupException(msg);
     }
 
     return pid;
 }
 
-void setns(const std::vector<Namespace>& namespaces)
+pid_t clone(int (*function)(void *),
+            void *args,
+            const std::vector<Namespace>& namespaces,
+            const int additionalFlags)
 {
-    pid_t pid = ::getpid();
+    return clone(function, args, toFlag(namespaces) | additionalFlags);
+}
 
+void setns(const pid_t pid, const std::vector<Namespace>& namespaces)
+{
     int dirFD = ::open(getNsPath(pid).c_str(), O_DIRECTORY | O_CLOEXEC);
     if(dirFD < 0) {
-        const std::string msg = utils::getSystemErrorMessage();
-        LOGE("open() failed: " << msg);
-        throw ProcessSetupException("open() failed: " + msg);
+        const std::string msg = "open() failed: " +
+                                utils::getSystemErrorMessage();
+        LOGE(msg);
+        throw ProcessSetupException(msg);
     }
 
     // Open FDs connected with the requested namespaces
     std::vector<int> fds(namespaces.size(), -1);
     for(size_t i = 0; i < namespaces.size(); ++i) {
-        fds[i] = ::openat(dirFD, toString(namespaces[i]).c_str(), O_RDONLY | O_CLOEXEC);
+        fds[i] = ::openat(dirFD,
+                          toString(namespaces[i]).c_str(),
+                          O_RDONLY | O_CLOEXEC);
         if(fds[i] < 0) {
-            const std::string msg = utils::getSystemErrorMessage();
+            const std::string msg = "openat() failed: " + utils::getSystemErrorMessage();
 
             for (size_t j = 0; j < i; ++j) {
                 utils::close(fds[j]);
             }
             utils::close(dirFD);
 
-            LOGE("openat() failed: " << msg);
-            throw ProcessSetupException("openat() failed: " + msg);
+            LOGE(msg);
+            throw ProcessSetupException(msg);
         }
     }
 
     // Setns for every namespace
     for(size_t i = 0; i < fds.size(); ++i) {
         if(-1 == ::setns(fds[i], toFlag(namespaces[i]))) {
-            const std::string msg = utils::getSystemErrorMessage();
+            const std::string msg = "setns() failed: " + utils::getSystemErrorMessage();
 
             for (size_t j = i; j < fds.size(); ++j) {
                 utils::close(fds[j]);
             }
             utils::close(dirFD);
 
-            LOGE("setns() failed: " << msg);
-            throw ProcessSetupException("setns() failed: " + msg);
+            LOGE(msg);
+            throw ProcessSetupException(msg);
         }
         utils::close(fds[i]);
     }
@@ -104,4 +125,33 @@ void setns(const std::vector<Namespace>& namespaces)
     utils::close(dirFD);
 }
 
+int waitpid(const pid_t pid)
+{
+    int status;
+    while (-1 == ::waitpid(pid, &status, 0)) {
+        if (errno == EINTR) {
+            LOGT("waitpid() interrupted, retrying");
+            continue;
+        }
+        const std::string msg = "waitpid() failed: " + utils::getSystemErrorMessage();
+        LOGE(msg);
+        throw ProcessSetupException(msg);
+    }
+
+    // Return child's return status if everything is OK
+    if (WIFEXITED(status)) {
+        return WEXITSTATUS(status);
+    }
+
+    // Something went wrong in the child
+    std::string msg;
+    if (WIFSIGNALED(status)) {
+        msg = "Child killed by signal " + std::to_string(WTERMSIG(status));
+    } else {
+        msg = "Unknown eror in child process";
+    }
+    LOGE(msg);
+    throw ProcessSetupException(msg);
+}
+
 } // namespace lxcpp
\ No newline at end of file
index 1255589..a640e1a 100644 (file)
 
 namespace lxcpp {
 
+pid_t fork();
+
+pid_t clone(int (*function)(void *),
+            void *args,
+            const int flags);
+
 pid_t clone(int (*function)(void *),
             void *args,
             const std::vector<Namespace>& namespaces,
             const int additionalFlags = 0);
 
-void setns(const std::vector<Namespace>& namespaces);
+void setns(const pid_t pid,
+           const std::vector<Namespace>& namespaces);
+
+int waitpid(const pid_t pid);
 
 } // namespace lxcpp
 
index cae893a..662dd82 100644 (file)
@@ -63,8 +63,8 @@ const std::vector<Namespace> NAMESPACES  {{
 
 BOOST_AUTO_TEST_CASE(Clone)
 {
-    BOOST_CHECK_NO_THROW(clone(clonefn, nullptr, NAMESPACES));
-    BOOST_CHECK_NO_THROW(clone(clonefn, nullptr, {Namespace::MNT}));
+    BOOST_CHECK_NO_THROW(lxcpp::clone(clonefn, nullptr, NAMESPACES));
+    BOOST_CHECK_NO_THROW(lxcpp::clone(clonefn, nullptr, {Namespace::MNT}));
 }
 
 BOOST_AUTO_TEST_CASE(Setns)
@@ -72,22 +72,20 @@ BOOST_AUTO_TEST_CASE(Setns)
     const int TEST_PASSED = 0;
     const int ERROR = 1;
 
-    pid_t pid = fork();
-    if (pid==-1) {
-        BOOST_REQUIRE(false);
-    } else if(pid ==0) {
+    pid_t pid = lxcpp::fork();
+    if (pid == 0) {
         try {
-            setns({Namespace::MNT,
-                   Namespace::PID,
-                   Namespace::UTS,
-                   Namespace::IPC,
-                   Namespace::NET
-                  });
-            _exit(TEST_PASSED);
+            lxcpp::setns(::getpid(), {Namespace::MNT,
+                                      Namespace::PID,
+                                      Namespace::UTS,
+                                      Namespace::IPC,
+                                      Namespace::NET
+                                     });
+            ::_exit(TEST_PASSED);
         } catch(...) {
-            _exit(ERROR);
+            ::_exit(ERROR);
         }
-    } else if(pid>0) {
+    } else if (pid >  0) {
         int status = -1;
         BOOST_REQUIRE(utils::waitPid(pid, status));
         BOOST_REQUIRE(status == TEST_PASSED);
@@ -99,19 +97,17 @@ BOOST_AUTO_TEST_CASE(SetnsUserNamespace)
     const int TEST_PASSED = 0;
     const int ERROR = -1;
 
-    pid_t pid = fork();
-    if (pid==-1) {
-        BOOST_REQUIRE(false);
-    } else if(pid ==0) {
+    pid_t pid = lxcpp::fork();
+    if (pid == 0) {
         try {
-            setns({Namespace::USER});
-            _exit(ERROR);
+            lxcpp::setns(::getpid(), {Namespace::USER});
+            ::_exit(ERROR);
         } catch(ProcessSetupException) {
-            _exit(TEST_PASSED);
+            ::_exit(TEST_PASSED);
         } catch(...) {
-            _exit(ERROR);
+            ::_exit(ERROR);
         }
-    } else if(pid>0) {
+    } else if (pid > 0) {
         int status;
         BOOST_REQUIRE(utils::waitPid(pid, status));
         BOOST_REQUIRE(status == TEST_PASSED);
diff --git a/tests/unit_tests/utils/ut-channel.cpp b/tests/unit_tests/utils/ut-channel.cpp
new file mode 100644 (file)
index 0000000..8c469ee
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ *  Copyright (c) 2015 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ *  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 the channel class
+ */
+
+#include "config.hpp"
+#include "ut.hpp"
+
+#include "utils/channel.hpp"
+#include "utils/execute.hpp"
+
+BOOST_AUTO_TEST_SUITE(ChannelSuite)
+
+using namespace utils;
+
+BOOST_AUTO_TEST_CASE(ConstructorDestructor)
+{
+    Channel c;
+}
+
+BOOST_AUTO_TEST_CASE(SetLeftRight)
+{
+    const int TEST_PASSED = 0;
+    const int ERROR = 1;
+    const int DATA = 1234;
+
+    Channel c;
+
+    pid_t pid = ::fork();
+    if (pid == -1) {
+        BOOST_REQUIRE(false);
+    }
+
+    if (pid == 0) {
+        try {
+            c.setLeft();
+            c.write(DATA);
+            c.shutdown();
+            ::_exit(TEST_PASSED);
+        } catch(...) {
+            ::_exit(ERROR);
+        }
+    }
+
+    c.setRight();
+
+    int recData = c.read<int>();
+
+    BOOST_REQUIRE(recData == DATA);
+
+    int status = -1;
+    BOOST_REQUIRE(utils::waitPid(pid, status));
+    BOOST_REQUIRE(status == TEST_PASSED);
+    c.shutdown();
+
+}
+
+BOOST_AUTO_TEST_SUITE_END()