--- /dev/null
+/*
+* 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
--- /dev/null
+/*
+* 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
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;
}
}
+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() +
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
#include "lxcpp/container-impl.hpp"
#include "lxcpp/exception.hpp"
+#include "lxcpp/process.hpp"
+
+#include "utils/exception.hpp"
+
+#include <unistd.h>
namespace lxcpp {
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
#define LXCPP_CONTAINER_IMPL_HPP
#include "lxcpp/container.hpp"
+#include "lxcpp/namespace.hpp"
+
+#include "utils/channel.hpp"
namespace lxcpp {
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
#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;
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
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);
// 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]);
}
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
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
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)
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);
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);
--- /dev/null
+/*
+ * 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()