)
};
-struct DeclareLinkIn
-{
+struct DeclareLinkIn {
std::string source;
std::string zone;
std::string target;
)
};
-struct GrantDeviceIn
-{
+struct GrantDeviceIn {
std::string id;
std::string device;
uint32_t flags;
)
};
-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;
#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) {
#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("--");
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("--");
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");
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;
}
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
#define COMMON_LXC_ZONE_HPP
#include <string>
+#include <functional>
#include <sys/types.h>
// fwd declaration of lxc internals
*/
class LxcZone {
public:
+ typedef std::function<int()> Call;
+
enum class State {
STOPPED,
STARTING,
*/
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();
};
#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"
{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;
#include <unistd.h>
#include <poll.h>
#include <sys/resource.h>
+#include <sys/socket.h>
#include <boost/filesystem.hpp>
namespace fs = boost::filesystem;
namespace utils {
+// TODO: Add here various fixes from config::FDStore
+
namespace {
void waitForEvent(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 (;;) {
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 (;;) {
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
*/
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
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";
" <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>"
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;
#include "lxc/exception.hpp"
#include "utils/scoped-dir.hpp"
+#include <fcntl.h>
#include <thread>
#include <chrono>
#include <boost/filesystem.hpp>
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()