Create File in LxcZone 11/41211/9
authorJan Olszak <j.olszak@samsung.com>
Mon, 8 Jun 2015 14:17:16 +0000 (16:17 +0200)
committerJan Olszak <j.olszak@samsung.com>
Mon, 15 Jun 2015 13:47:25 +0000 (15:47 +0200)
[Feature]       Create files in zones
                Pass file descriptors to those files
                Api definition
[Cause]         N/A
[Solution]      N/A
[Verification]  Build, run all tests

Change-Id: I9780b8922fac2548bae642b0e732dd96998996ab

common/api/messages.hpp
common/lxc/zone.cpp
common/lxc/zone.hpp
common/utils/environment.cpp
common/utils/fd-utils.cpp
common/utils/fd-utils.hpp
server/host-dbus-definitions.hpp
server/host-ipc-definitions.hpp
tests/unit_tests/lxc/ut-zone.cpp

index ca4cab2..a00a2dd 100644 (file)
@@ -198,8 +198,7 @@ struct DeclareMountIn {
     )
 };
 
-struct DeclareLinkIn
-{
+struct DeclareLinkIn {
     std::string source;
     std::string zone;
     std::string target;
@@ -212,8 +211,7 @@ struct DeclareLinkIn
     )
 };
 
-struct GrantDeviceIn
-{
+struct GrantDeviceIn {
     std::string id;
     std::string device;
     uint32_t flags;
@@ -226,8 +224,27 @@ struct GrantDeviceIn
     )
 };
 
-struct Notification
-{
+struct CreateFileIn {
+    std::string id;
+    std::string path;
+    int32_t mode;
+    CONFIG_REGISTER
+    (
+        id,
+        path,
+        mode
+    )
+};
+
+struct CreateFileOut {
+    config::FileDescriptor fd;
+    CONFIG_REGISTER
+    (
+        fd
+    )
+};
+
+struct Notification {
     std::string zone;
     std::string application;
     std::string message;
index bbb1e02..5fd1762 100644 (file)
@@ -34,7 +34,9 @@
 #include "logger/logger.hpp"
 #include "lxc/zone.hpp"
 #include "lxc/exception.hpp"
+#include "utils/exception.hpp"
 #include "utils/execute.hpp"
+#include "utils/fd-utils.hpp"
 #ifdef USE_EXEC
 #include "utils/c-array.hpp"
 #endif
 #include <sys/wait.h>
 
 #include <map>
+#include <sys/socket.h>
+#include <fcntl.h>
+#include <unistd.h>
 
 namespace vasum {
 namespace lxc {
 
 namespace {
 #define ITEM(X) {#X, LxcZone::State::X},
-    const std::map<std::string, LxcZone::State> STATE_MAP = {
-        ITEM(STOPPED)
-        ITEM(STARTING)
-        ITEM(RUNNING)
-        ITEM(STOPPING)
-        ITEM(ABORTING)
-        ITEM(FREEZING)
-        ITEM(FROZEN)
-        ITEM(THAWED)
-    };
+const std::map<std::string, LxcZone::State> STATE_MAP = {
+    ITEM(STOPPED)
+    ITEM(STARTING)
+    ITEM(RUNNING)
+    ITEM(STOPPING)
+    ITEM(ABORTING)
+    ITEM(FREEZING)
+    ITEM(FROZEN)
+    ITEM(THAWED)
+};
 #undef ITEM
+
+int execFunction(void* data) {
+    // Executed by C code, so catch all exceptions
+    try {
+        return (*static_cast<std::function<int()>*>(data))();
+    } catch(...) {
+        return -1; // Non-zero on failure
+    }
+    return 0; // Success
+}
+
 } // namespace
 
-std::string LxcZone::toString(State state)
-{
+std::string LxcZone::toString(State state) {
 #define CASE(X) case LxcZone::State::X: return #X;
     switch (state) {
-    CASE(STOPPED)
-    CASE(STARTING)
-    CASE(RUNNING)
-    CASE(STOPPING)
-    CASE(ABORTING)
-    CASE(FREEZING)
-    CASE(FROZEN)
-    CASE(THAWED)
+        CASE(STOPPED)
+        CASE(STARTING)
+        CASE(RUNNING)
+        CASE(STOPPING)
+        CASE(ABORTING)
+        CASE(FREEZING)
+        CASE(FROZEN)
+        CASE(THAWED)
     }
 #undef CASE
     throw LxcException("Invalid state");
 }
 
 LxcZone::LxcZone(const std::string& lxcPath, const std::string& zoneName)
-  : mLxcContainer(nullptr)
+    : mLxcContainer(nullptr)
 {
     mLxcContainer = lxc_container_new(zoneName.c_str(), lxcPath.c_str());
     if (!mLxcContainer) {
@@ -129,9 +144,9 @@ bool LxcZone::create(const std::string& templatePath, const char* const* argv)
 #ifdef USE_EXEC
     utils::CStringArrayBuilder args;
     args.add("lxc-create")
-        .add("-n").add(mLxcContainer->name)
-        .add("-t").add(templatePath.c_str())
-        .add("-P").add(mLxcContainer->config_path);
+    .add("-n").add(mLxcContainer->name)
+    .add("-t").add(templatePath.c_str())
+    .add("-P").add(mLxcContainer->config_path);
 
     if (*argv) {
         args.add("--");
@@ -179,9 +194,9 @@ bool LxcZone::start(const char* const* argv)
 
     utils::CStringArrayBuilder args;
     args.add("lxc-start")
-        .add("-d")
-        .add("-n").add(mLxcContainer->name)
-        .add("-P").add(mLxcContainer->config_path);
+    .add("-d")
+    .add("-n").add(mLxcContainer->name)
+    .add("-P").add(mLxcContainer->config_path);
 
     if (*argv) {
         args.add("--");
@@ -254,10 +269,10 @@ bool LxcZone::shutdown(int timeout)
     utils::CStringArrayBuilder args;
     std::string timeoutStr = std::to_string(timeout);
     args.add("lxc-stop")
-        .add("-n").add(mLxcContainer->name)
-        .add("-P").add(mLxcContainer->config_path)
-        .add("-t").add(timeoutStr.c_str())
-        .add("--nokill");
+    .add("-n").add(mLxcContainer->name)
+    .add("-P").add(mLxcContainer->config_path)
+    .add("-t").add(timeoutStr.c_str())
+    .add("--nokill");
 
     if (!utils::executeAndWait("/usr/bin/lxc-stop", args.c_array())) {
         LOGE("Could not gracefully shutdown zone " << getName() << " in " << timeout << "s");
@@ -320,14 +335,37 @@ pid_t LxcZone::getInitPid() const
 
 bool LxcZone::setRunLevel(int runLevel)
 {
-    auto callback = [](void* param) -> int {
-        utils::RunLevel level = *reinterpret_cast<utils::RunLevel*>(param);
-        return utils::setRunLevel(level) ? 0 : 1;
+    Call call = [runLevel]() -> int {
+        return utils::setRunLevel(static_cast<utils::RunLevel>(runLevel)) ? 0 : 1;
     };
+    return runInZone(call);
+}
+
+void LxcZone::refresh()
+{
+    //TODO Consider make LxcZone state-less
+    std::string zoneName = mLxcContainer->name;
+    std::string lxcPath = mLxcContainer->config_path;
+    lxc_container_put(mLxcContainer);
+    mLxcContainer = lxc_container_new(zoneName.c_str(), lxcPath.c_str());
+}
 
+bool LxcZone::runInZone(Call& call)
+{
     lxc_attach_options_t options = LXC_ATTACH_OPTIONS_DEFAULT;
+    options.attach_flags = LXC_ATTACH_REMOUNT_PROC_SYS |
+                           LXC_ATTACH_DROP_CAPABILITIES |
+                           LXC_ATTACH_SET_PERSONALITY |
+                           LXC_ATTACH_LSM_EXEC |
+                           LXC_ATTACH_LSM_NOW |
+                           LXC_ATTACH_MOVE_TO_CGROUP;
+
     pid_t pid;
-    int ret = mLxcContainer->attach(mLxcContainer, callback, &runLevel, &options, &pid);
+    int ret = mLxcContainer->attach(mLxcContainer,
+                                    execFunction,
+                                    &call,
+                                    &options,
+                                    &pid);
     if (ret != 0) {
         return false;
     }
@@ -338,15 +376,38 @@ bool LxcZone::setRunLevel(int runLevel)
     return status == 0;
 }
 
-void LxcZone::refresh()
+int LxcZone::createFile(const std::string& path, const std::int32_t mode, int *fdPtr)
 {
-    //TODO Consider make LxcZone state-less
-    std::string zoneName = mLxcContainer->name;
-    std::string lxcPath = mLxcContainer->config_path;
-    lxc_container_put(mLxcContainer);
-    mLxcContainer = lxc_container_new(zoneName.c_str(), lxcPath.c_str());
-}
+    *fdPtr = -1;
+
+    int sockets[2];
+    if (::socketpair(AF_LOCAL, SOCK_STREAM, 0, sockets) < 0) {
+        LOGE("Can't create socket pair: " << utils::getSystemErrorMessage());
+        return false;
+    }
+
+    lxc::LxcZone::Call call = [&]()->int{
+        utils::close(sockets[1]);
+
+        int fd = ::open(path.c_str(), O_CREAT | O_EXCL, mode);
+        if (fd < 0) {
+            LOGE("Error during file creation: " << utils::getSystemErrorMessage());
+            utils::close(sockets[0]);
+            return -1;
+        }
+        utils::fdSend(sockets[0], fd);
+        utils::close(fd);
+        return 0;
+    };
 
+    runInZone(call);
+
+    utils::close(sockets[0]);
+    *fdPtr = utils::fdRecv(sockets[1]);
+    utils::close(sockets[1]);
+
+    return true;
+}
 
 } // namespace lxc
 } // namespace vasum
index 0ed19a3..0d34e6f 100644 (file)
@@ -26,6 +26,7 @@
 #define COMMON_LXC_ZONE_HPP
 
 #include <string>
+#include <functional>
 #include <sys/types.h>
 
 // fwd declaration of lxc internals
@@ -40,6 +41,8 @@ namespace lxc {
  */
 class LxcZone {
 public:
+    typedef std::function<int()> Call;
+
     enum class State {
         STOPPED,
         STARTING,
@@ -143,9 +146,27 @@ public:
      */
     pid_t getInitPid() const;
 
+    /**
+     * Attach to the Zone and run the call
+     *
+     * This call will fork, so ensure the call object will withstand this
+     *
+     * @param call function object to run
+     */
+    bool runInZone(Call& call);
+
+    /**
+     * Create a file inside the zone and return it's file descriptor
+     *
+     * @param path path in the container
+     * @param mode mode
+     *
+     * @return file descriptor of the file, opened in mode
+     */
+    int createFile(const std::string& path, const std::int32_t mode, int *fdPtr);
+
 private:
     lxc_container* mLxcContainer;
-
     bool setRunLevel(int runLevel);
     void refresh();
 };
index 4a04e0d..a28a528 100644 (file)
@@ -28,6 +28,7 @@
 #include "utils/execute.hpp"
 #include "utils/exception.hpp"
 #include "utils/make-clean.hpp"
+#include "utils/fd-utils.hpp"
 #include "base-exception.hpp"
 #include "logger/logger.hpp"
 
@@ -93,58 +94,6 @@ const std::map<int, std::string> NAMESPACES = {
     {CLONE_NEWUSER, "user"},
     {CLONE_NEWUTS, "uts"}};
 
-int fdRecv(int socket)
-{
-    msghdr msg = make_clean<msghdr>();
-    iovec iov = make_clean<iovec>();
-    char cmsgBuff[CMSG_SPACE(sizeof(int))];
-
-    msg.msg_iov = &iov;
-    msg.msg_iovlen = 1;
-
-    msg.msg_control = cmsgBuff;
-    msg.msg_controllen = sizeof(cmsgBuff);
-
-    int ret = recvmsg(socket, &msg, MSG_CMSG_CLOEXEC);
-    if (ret != 0 || msg.msg_flags & (MSG_TRUNC | MSG_ERRQUEUE | MSG_OOB | MSG_CTRUNC | MSG_EOR)) {
-        LOGE("Can't receive fd: ret: " << ret << ", flags: " << msg.msg_flags);
-        return -1;
-    }
-
-    cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
-    assert(cmsg->cmsg_level == SOL_SOCKET);
-    assert(cmsg->cmsg_type == SCM_RIGHTS);
-    assert(CMSG_NXTHDR(&msg, cmsg) == NULL);
-    return *reinterpret_cast<int*>(CMSG_DATA(cmsg));
-}
-
-bool fdSend(int socket, int fd)
-{
-    msghdr msg = make_clean<msghdr>();
-    struct iovec iov = make_clean<iovec>();
-    struct cmsghdr *cmsg = NULL;
-    char cmsgBuff[CMSG_SPACE(sizeof(int))];
-
-    msg.msg_iov = &iov;
-    msg.msg_iovlen = 1;
-
-    msg.msg_control = cmsgBuff;
-    msg.msg_controllen = sizeof(cmsgBuff);
-
-    cmsg = CMSG_FIRSTHDR(&msg);
-    cmsg->cmsg_level = SOL_SOCKET;
-    cmsg->cmsg_type = SCM_RIGHTS;
-    cmsg->cmsg_len = CMSG_LEN(sizeof(int));
-    *reinterpret_cast<int*>(CMSG_DATA(cmsg)) = fd;
-
-    int ret = sendmsg(socket, &msg, 0);
-    if (ret < 0) {
-        LOGE("Can't send fd: ret: " << ret);
-        return false;
-    }
-    return true;
-}
-
 inline bool isValidCap(unsigned int cap)
 {
     return cap <= CAP_LAST_CAP;
index d5179b1..4fe9a17 100644 (file)
@@ -34,6 +34,7 @@
 #include <unistd.h>
 #include <poll.h>
 #include <sys/resource.h>
+#include <sys/socket.h>
 #include <boost/filesystem.hpp>
 
 namespace fs = boost::filesystem;
@@ -41,6 +42,8 @@ namespace chr = std::chrono;
 
 namespace utils {
 
+// TODO: Add here various fixes from config::FDStore
+
 namespace {
 
 void waitForEvent(int fd,
@@ -111,7 +114,7 @@ void close(int fd)
 void write(int fd, const void* bufferPtr, const size_t size, int timeoutMS)
 {
     chr::high_resolution_clock::time_point deadline = chr::high_resolution_clock::now() +
-                                                      chr::milliseconds(timeoutMS);
+            chr::milliseconds(timeoutMS);
 
     size_t nTotal = 0;
     for (;;) {
@@ -140,7 +143,7 @@ void write(int fd, const void* bufferPtr, const size_t size, int timeoutMS)
 void read(int fd, void* bufferPtr, const size_t size, int timeoutMS)
 {
     chr::high_resolution_clock::time_point deadline = chr::high_resolution_clock::now() +
-                                                      chr::milliseconds(timeoutMS);
+            chr::milliseconds(timeoutMS);
 
     size_t nTotal = 0;
     for (;;) {
@@ -200,5 +203,134 @@ unsigned int getFDNumber()
                          fs::directory_iterator());
 }
 
+int fdRecv(int socket, const unsigned int timeoutMS)
+{
+    std::chrono::high_resolution_clock::time_point deadline =
+        std::chrono::high_resolution_clock::now() +
+        std::chrono::milliseconds(timeoutMS);
+
+    // Space for the file descriptor
+    union {
+        struct cmsghdr cmh;
+        char   control[CMSG_SPACE(sizeof(int))];
+    } controlUnion;
+
+    // Describe the data that we want to recive
+    controlUnion.cmh.cmsg_len = CMSG_LEN(sizeof(int));
+    controlUnion.cmh.cmsg_level = SOL_SOCKET;
+    controlUnion.cmh.cmsg_type = SCM_RIGHTS;
+
+    // Setup the input buffer
+    // Ensure at least 1 byte is transmited via the socket
+    char buf;
+    struct iovec iov;
+    iov.iov_base = &buf;
+    iov.iov_len = sizeof(char);
+
+    // Set the ancillary data buffer
+    // The socket has to be connected, so we don't need to specify the name
+    struct msghdr msgh;
+    ::memset(&msgh, 0, sizeof(msgh));
+
+    msgh.msg_iov = &iov;
+    msgh.msg_iovlen = 1;
+
+    msgh.msg_control = controlUnion.control;
+    msgh.msg_controllen = sizeof(controlUnion.control);
+
+    // Receive
+    for(;;) {
+        ssize_t ret = ::recvmsg(socket, &msgh, MSG_WAITALL);
+        if (ret < 0) {
+            if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {
+                // Neglected errors, retry
+            } else {
+                throw UtilsException("Error during recvmsg: " + getSystemErrorMessage());
+            }
+        } else if (ret == 0) {
+            throw UtilsException("Peer disconnected");
+        } else {
+            // We receive only 1 byte of data. No need to repeat
+            break;
+        }
+
+        waitForEvent(socket, POLLIN, deadline);
+    }
+
+    struct cmsghdr *cmhp;
+    cmhp = CMSG_FIRSTHDR(&msgh);
+    if (cmhp == NULL || cmhp->cmsg_len != CMSG_LEN(sizeof(int))) {
+        throw UtilsException("Bad cmsg length");
+    } else if (cmhp->cmsg_level != SOL_SOCKET) {
+        throw UtilsException("cmsg_level != SOL_SOCKET");
+    } else if (cmhp->cmsg_type != SCM_RIGHTS) {
+        throw UtilsException("cmsg_type != SCM_RIGHTS");
+    }
+
+    return *(reinterpret_cast<int*>(CMSG_DATA(cmhp)));
+}
+
+bool fdSend(int socket, int fd, const unsigned int timeoutMS)
+{
+    std::chrono::high_resolution_clock::time_point deadline =
+        std::chrono::high_resolution_clock::now() +
+        std::chrono::milliseconds(timeoutMS);
+
+    // Space for the file descriptor
+    union {
+        struct cmsghdr cmh;
+        char   control[CMSG_SPACE(sizeof(int))];
+    } controlUnion;
+
+    // Ensure at least 1 byte is transmited via the socket
+    struct iovec iov;
+    char buf = '!';
+    iov.iov_base = &buf;
+    iov.iov_len = sizeof(char);
+
+    // Fill the message to send:
+    // The socket has to be connected, so we don't need to specify the name
+    struct msghdr msgh;
+    ::memset(&msgh, 0, sizeof(msgh));
+
+    // Only iovec to transmit one element
+    msgh.msg_iov = &iov;
+    msgh.msg_iovlen = 1;
+
+    // Ancillary data buffer
+    msgh.msg_control = controlUnion.control;
+    msgh.msg_controllen = sizeof(controlUnion.control);
+
+    // Describe the data that we want to send
+    struct cmsghdr *cmhp;
+    cmhp = CMSG_FIRSTHDR(&msgh);
+    cmhp->cmsg_len = CMSG_LEN(sizeof(int));
+    cmhp->cmsg_level = SOL_SOCKET;
+    cmhp->cmsg_type = SCM_RIGHTS;
+    *(reinterpret_cast<int*>(CMSG_DATA(cmhp))) = fd;
+
+    // Send
+    for(;;) {
+        ssize_t ret = ::sendmsg(socket, &msgh, MSG_NOSIGNAL);
+        if (ret < 0) {
+            if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {
+                // Neglected errors, retry
+            } else {
+                throw UtilsException("Error during sendmsg: " + getSystemErrorMessage());
+            }
+        } else if (ret == 0) {
+            // Retry the sending
+        } else {
+            // We send only 1 byte of data. No need to repeat
+            break;
+        }
+
+        waitForEvent(socket, POLLOUT, deadline);
+    }
+
+    // TODO: It shouldn't return
+    return true;
+}
+
 } // namespace utils
 
index 209de2c..d6a4032 100644 (file)
@@ -71,6 +71,16 @@ void setMaxFDNumber(unsigned int limit);
  */
 unsigned int getFDNumber();
 
+/**
+ * Send Socket via Unix Domain socket
+ */
+bool fdSend(int socket, int fd, const unsigned int timeoutMS = 5000);
+
+/**
+ * Receive fd via Unix Domain socket
+ */
+int fdRecv(int socket, const unsigned int timeoutMS = 5000);
+
 } // namespace utils
 
 #endif // COMMON_UTILS_FD_HPP
index 2d013c9..142a6d2 100644 (file)
@@ -62,6 +62,7 @@ const std::string METHOD_UNLOCK_ZONE              = "UnlockZone";
 const std::string METHOD_GRANT_DEVICE             = "GrantDevice";
 const std::string METHOD_REVOKE_DEVICE            = "RevokeDevice";
 const std::string METHOD_PROXY_CALL               = "ProxyCall";
+const std::string METHOD_CREATE_FILE              = "CreateFile";
 
 const std::string METHOD_NOTIFY_ACTIVE_ZONE         = "NotifyActiveZone";
 const std::string METHOD_FILE_MOVE_REQUEST          = "FileMoveRequest";
@@ -177,6 +178,12 @@ const std::string DEFINITION =
     "      <arg type='s' name='id' direction='in'/>"
     "      <arg type='s' name='templateName' direction='in'/>"
     "    </method>"
+    "    <method name='" + METHOD_CREATE_FILE + "'>"
+    "      <arg type='s' name='id' direction='in'/>"
+    "      <arg type='s' name='path' direction='in'/>"
+    "      <arg type='i' name='mode' direction='in'/>"
+    "      <arg type='h' name='fileDescriptor' direction='out'/>"
+    "    </method>"
     "    <method name='" + METHOD_DESTROY_ZONE + "'>"
     "      <arg type='s' name='id' direction='in'/>"
     "    </method>"
index 0053efa..d1251eb 100644 (file)
@@ -56,6 +56,7 @@ const ::ipc::MethodID METHOD_LOCK_ZONE                = 23;
 const ::ipc::MethodID METHOD_UNLOCK_ZONE              = 24;
 const ::ipc::MethodID METHOD_GRANT_DEVICE             = 25;
 const ::ipc::MethodID METHOD_REVOKE_DEVICE            = 26;
+const ::ipc::MethodID METHOD_CREATE_FILE              = 27;
 
 const ::ipc::MethodID METHOD_NOTIFY_ACTIVE_ZONE         = 100;
 const ::ipc::MethodID METHOD_FILE_MOVE_REQUEST          = 101;
index e2683d6..f87427e 100644 (file)
@@ -30,6 +30,7 @@
 #include "lxc/exception.hpp"
 #include "utils/scoped-dir.hpp"
 
+#include <fcntl.h>
 #include <thread>
 #include <chrono>
 #include <boost/filesystem.hpp>
@@ -249,4 +250,39 @@ BOOST_AUTO_TEST_CASE(Repeat)
     BOOST_CHECK(!lxc.destroy()); // forbidden (why?)
 }
 
+BOOST_AUTO_TEST_CASE(CreateFile)
+{
+    // Create and start the container:
+    LxcZone lxc(ZONE_PATH, ZONE_NAME);
+    BOOST_REQUIRE(lxc.create(ZONE_TEMPLATE, TEMPLATE_ARGS));
+    const char* argv[] = {
+        "/bin/sh",
+        "-c",
+        "trap exit SIGTERM; read",
+        NULL
+    };
+    BOOST_REQUIRE(lxc.start(argv));
+    BOOST_REQUIRE(lxc.getState() == LxcZone::State::RUNNING);
+    waitForInit();
+
+    // The test
+    int fd;
+    BOOST_REQUIRE(lxc.createFile("./112.txt", O_RDWR, &fd));
+    BOOST_REQUIRE(::fcntl(fd, F_GETFD) != -1);
+    BOOST_REQUIRE(::close(fd) != -1);
+
+    BOOST_REQUIRE(lxc.createFile("/2.txt", O_RDONLY, &fd));
+    BOOST_REQUIRE(::fcntl(fd, F_GETFD) != -1);
+    BOOST_REQUIRE(::close(fd) != -1);
+
+    BOOST_REQUIRE(lxc.createFile("/3.txt", O_WRONLY, &fd));
+    BOOST_REQUIRE(::fcntl(fd, F_GETFD) != -1);
+    BOOST_REQUIRE(::close(fd) != -1);
+
+    // Close
+    BOOST_REQUIRE(lxc.stop());
+    BOOST_REQUIRE(lxc.getState() == LxcZone::State::STOPPED);
+    BOOST_REQUIRE(lxc.destroy());
+}
+
 BOOST_AUTO_TEST_SUITE_END()