IF(NOT DEFINED INPUT_EVENT_GROUP)
SET(INPUT_EVENT_GROUP "input")
ENDIF(NOT DEFINED INPUT_EVENT_GROUP)
+IF(NOT DEFINED DISK_GROUP)
+ SET(DISK_GROUP "disk")
+ENDIF(NOT DEFINED DISK_GROUP)
ADD_DEFINITIONS(-DSECURITY_CONTAINERS_USER="${SECURITY_CONTAINERS_USER}")
ADD_DEFINITIONS(-DLIBVIRT_GROUP="${LIBVIRT_GROUP}")
ADD_DEFINITIONS(-DINPUT_EVENT_GROUP="${INPUT_EVENT_GROUP}")
+ADD_DEFINITIONS(-DDISK_GROUP="${DISK_GROUP}")
## Python packages directory ###################################################
#include <sys/types.h>
#include <unistd.h>
+#include <iostream>
+
namespace security_containers {
namespace utils {
return true;
}
+namespace {
+
+bool copyDirContentsRec(const boost::filesystem::path& src, const boost::filesystem::path& dst)
+{
+ namespace fs = boost::filesystem;
+
+ // TODO: Right now this function skips files which produce error when copying. Errors show up
+ // when:
+ // a) fs::directory_iterator file(src) is created
+ // b) fs::copy(...) is called
+ // In both cases lack of permissions is the issue.
+ //
+ // In a) case we can't do much - SCS won't be able to read the directory and its contents. Such
+ // directories are not common in the filesystem, so they *probably* can be skipped.
+ //
+ // In b) case multiple directories have too strict permissions to be directly copied. This
+ // is a problem for some files crucial to container launch (ex. we cannot copy
+ // /usr/lib/systemd/systemd because /usr/lib has 555 permissions).
+ // To fix b) issue, copying must be done in two steps:
+ // 1. Copy file contents without permissions (this probably can be achieved by opening two
+ // files in-code with fstream and programatically copying data from one file to another).
+ // 2. Apply all available file attributes from source (permissions, owner UID/GID, xattrs...)
+
+ try {
+ for (fs::directory_iterator file(src);
+ file != fs::directory_iterator();
+ ++file) {
+ fs::path current(file->path());
+
+ boost::system::error_code ec;
+ fs::copy(current, dst / current.filename(), ec);
+ if(ec.value() != boost::system::errc::success) {
+ LOGW("Failed to copy " << current << ": " << ec.message());
+ }
+
+ if (!fs::is_symlink(current) && fs::is_directory(current)) {
+ if (!copyDirContentsRec(current, dst / current.filename())) {
+ return false;
+ }
+ }
+ }
+ } catch (fs::filesystem_error& e) {
+ LOGW(e.what());
+ }
+
+ return true;
+}
+
+} // namespace
+
+bool copyDirContents(const std::string& src, const std::string& dst)
+{
+ namespace fs = boost::filesystem;
+
+ return copyDirContentsRec(fs::path(src), fs::path(dst));
+}
+
bool createDir(const std::string& path, uid_t uid, uid_t gid, boost::filesystem::perms mode)
{
namespace fs = boost::filesystem;
return true;
}
+bool createEmptyDir(const std::string& path)
+{
+ namespace fs = boost::filesystem;
+
+ fs::path dirPath(path);
+ boost::system::error_code ec;
+ bool cleanDirCreated = false;
+
+ if (!fs::exists(dirPath)) {
+ if (!fs::create_directory(dirPath, ec)) {
+ LOGE("Failed to create dir. Error: " << ec.message());
+ return false;
+ }
+ cleanDirCreated = true;
+ } else if (!fs::is_directory(dirPath)) {
+ LOGE("Provided path already exists and is not a dir, cannot create.");
+ return false;
+ }
+
+ if (!cleanDirCreated) {
+ // check if directory is empty if it was already created
+ if (!fs::is_empty(dirPath)) {
+ LOGE("Directory has some data inside, cannot be used.");
+ return false;
+ }
+ }
+
+ return true;
+}
+
} // namespace utils
} // namespace security_containers
bool moveFile(const std::string& src, const std::string& dst);
/**
+ * Recursively copy contents of src dir to dst dir.
+ */
+bool copyDirContents(const std::string& src, const std::string& dst);
+
+/**
* Creates a directory with specific UID, GID and permissions set.
*/
bool createDir(const std::string& path, uid_t uid, uid_t gid, boost::filesystem::perms mode);
+/**
+ * Creates an empty directory, ready to serve as mount point.
+ * Succeeds either if path did not exist and was created successfully, or if already existing dir
+ * under the same path is empty and is not a mount point.
+ */
+bool createEmptyDir(const std::string& path);
+
} // namespace utils
} // namespace security_containers
--- /dev/null
+/*
+ * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Contact: Lukasz Kostyra <l.kostyra@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 img.hpp
+ * @author Lukasz Kostyra (l.kostyra@samsung.com)
+ * @brief Image utility functions declaration
+ */
+
+#include "config.hpp"
+#include "logger/logger.hpp"
+#include "utils/img.hpp"
+#include "utils/fs.hpp"
+#include "utils/paths.hpp"
+
+#include <sys/mount.h>
+#include <fcntl.h>
+#include <linux/loop.h>
+
+namespace security_containers {
+namespace utils {
+
+namespace {
+
+const std::string LOOP_DEV_PREFIX = "/dev/loop";
+const std::string LOOP_MOUNT_POINT_OPTIONS = "";
+const std::string LOOP_MOUNT_POINT_TYPE = "ext4";
+const unsigned long LOOP_MOUNT_POINT_FLAGS = MS_RDONLY;
+
+// Writes to ret if loop device (provided in loopdev arg) is free to use.
+// Returns true if check was successful, false if loop device FD was unavailable for some reason.
+bool isLoopDevFree(const std::string& loopdev, bool& ret)
+{
+ // initialize
+ ret = false;
+
+ // open loop device FD
+ int loopFD = ::open(loopdev.c_str(), O_RDWR);
+ if (loopFD < 0) {
+ LOGD("Failed to open loop device descriptor: " << ::strerror(errno));
+ return false;
+ }
+
+ // if ioctl with LOOP_GET_STATUS fails, device is not assigned and free to use
+ struct loop_info linfo;
+ if (::ioctl(loopFD, LOOP_GET_STATUS, &linfo)) {
+ ret = true;
+ }
+
+ ::close(loopFD);
+ return true;
+}
+
+bool mountLoop(const std::string& img,
+ const std::string& loopdev,
+ const std::string& path,
+ const std::string& type,
+ unsigned long flags,
+ const std::string& options)
+{
+ // to mount an image, we need to connect image FD with loop device FD
+ // get image file FD
+ int fileFD = ::open(img.c_str(), O_RDWR);
+ if (fileFD < 0) {
+ LOGD("Failed to open image file descriptor: " << ::strerror(errno));
+ return false;
+ }
+
+ // get loop device FD
+ int loopFD = ::open(loopdev.c_str(), O_RDWR);
+ if (loopFD < 0) {
+ LOGD("Failed to open loop device descriptor: " << ::strerror(errno));
+ ::close(fileFD);
+ return false;
+ }
+
+ // set loop device
+ if (::ioctl(loopFD, LOOP_SET_FD, fileFD)) {
+ LOGD("Failed to assign loop device to image: " << ::strerror(errno));
+ ::close(fileFD);
+ ::close(loopFD);
+ return false;
+ }
+
+ // mount loop device to path
+ if (::mount(loopdev.c_str(), path.c_str(), type.c_str(), flags, options.c_str()) != 0) {
+ LOGD("Mount failed for '" << path << "', options=" << options << ": " << strerror(errno));
+ ::ioctl(loopFD, LOOP_CLR_FD, 0);
+ ::close(fileFD);
+ ::close(loopFD);
+ return false;
+ }
+
+ ::close(fileFD);
+ ::close(loopFD);
+ return true;
+}
+
+} // namespace
+
+// Finds first available loop device and returns its path through ret.
+// Returns false if an error occurs, or if all available loop devices are taken.
+bool getFreeLoopDevice(std::string& ret)
+{
+ for (unsigned int i = 0; i < 8; ++i) {
+ // build path to loop device
+ const std::string loopdev = LOOP_DEV_PREFIX + std::to_string(i);
+ bool isFree = false;
+
+ // check if it is free
+ if (!isLoopDevFree(loopdev, isFree)) {
+ LOGD("Failed to check status of " << loopdev);
+ return false;
+ }
+
+ // if checked loop device is free, we can exit the function and return it
+ if (isFree) {
+ ret = loopdev;
+ return true;
+ }
+ }
+
+ LOGD("All loop devices are taken.");
+ return false;
+}
+
+bool mountImage(const std::string& image, const std::string& path, const std::string& loopdev)
+{
+ return mountLoop(image, path, loopdev,
+ LOOP_MOUNT_POINT_TYPE,
+ LOOP_MOUNT_POINT_FLAGS,
+ LOOP_MOUNT_POINT_OPTIONS);
+}
+
+bool umountImage(const std::string& path, const std::string& loopdev)
+{
+ if (::umount(path.c_str()) != 0) {
+ LOGD("Umount failed for '" << path << "': " << strerror(errno));
+ return false;
+ }
+
+ // clear loop device
+ int loopFD = ::open(loopdev.c_str(), O_RDWR);
+ if (loopFD < 0) {
+ LOGD("Failed to open fd for loop device 0");
+ return false;
+ }
+
+ if (::ioctl(loopFD, LOOP_CLR_FD, 0) < 0) {
+ LOGD("Failed to clear loop device.");
+ close(loopFD);
+ return false;
+ }
+
+ close(loopFD);
+ return true;
+}
+
+bool copyImageContents(const std::string& img, const std::string& dst)
+{
+ namespace fs = boost::filesystem;
+ boost::system::error_code ec;
+
+ // make sure that image exists
+ if (!fs::exists(fs::path(img))) {
+ LOGE("Image " << img << " does not exist");
+ return false;
+ }
+
+ const std::string mountPoint = createFilePath(dirName(img), "/mp/");
+ // create a mount point for copied image
+ if (!createEmptyDir(mountPoint)) {
+ LOGE("Cannot create mount point for copied image.");
+ return false;
+ }
+
+ // create dst directory
+ if (!createEmptyDir(dst)) {
+ LOGE("Cannot create directory for data.");
+ return false;
+ }
+
+ // find free loop device for image
+ std::string loopdev;
+ if (!utils::getFreeLoopDevice(loopdev)) {
+ LOGE("Failed to get free loop device.");
+ return false;
+ }
+
+ LOGT("Using " << loopdev << " to mount image");
+ // mount an image
+ if (!utils::mountImage(img, loopdev, mountPoint)) {
+ LOGE("Cannot mount image.");
+ return false;
+ }
+
+ // copy data
+ LOGI("Beginning image copy");
+ if (!utils::copyDirContents(mountPoint, dst)) {
+ LOGE("Failed to copy image.");
+ utils::umountImage(mountPoint, loopdev);
+ LOGD("Removing already copied data");
+ fs::remove_all(fs::path(dst));
+ return false;
+ }
+ LOGI("Finished image copy");
+
+ // umount image
+ if (!utils::umountImage(mountPoint, loopdev)) {
+ LOGE("Failed to umount image");
+ LOGD("Removing copied data");
+ fs::remove_all(fs::path(dst));
+ return false;
+ }
+
+ // remove mount point
+ if (!fs::remove(fs::path(mountPoint), ec)) {
+ LOGW("Failed to remove mount point: " << ec.message());
+ }
+
+ return true;
+}
+
+} // namespace utils
+} // namespace security_containers
--- /dev/null
+/*
+ * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Contact: Lukasz Kostyra <l.kostyra@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 img.hpp
+ * @author Lukasz Kostyra (l.kostyra@samsung.com)
+ * @brief Image utility functions declaration
+ */
+
+#ifndef COMMON_UTILS_IMG_HPP
+#define COMMON_UTILS_IMG_HPP
+
+namespace security_containers {
+namespace utils {
+
+/**
+ * Returns string with first free loop device.
+ */
+bool getFreeLoopDevice(std::string& ret);
+
+/**
+ * Mount an ext4 image from file on a given path by using a loop device.
+ */
+bool mountImage(const std::string& image, const std::string& loopdev, const std::string& path);
+
+/**
+ * Umounts previously mounted image.
+ * This call will also free loop device used to mount specified path.
+ */
+bool umountImage(const std::string& path, const std::string& loopdev);
+
+/**
+ * Mounts an image and copies its contents to dst directory.
+ */
+bool copyImageContents(const std::string& img, const std::string& dst);
+
+} // namespace utils
+} // namespace security_containers
+
+#endif // COMMON_UTILS_IMG_HPP
return path;
}
+/*
+ * Gets absolute path to specified file (if needed)
+ */
+inline std::string getAbsolutePath(const std::string& path, const std::string& base)
+{
+ if (path[0] == '/') {
+ return path;
+ } else {
+ return utils::createFilePath(base, "/", path);
+ }
+}
+
} // namespace utils
} // namespace security_containers
# The group that has read and write access to /dev/input/event* devices.
# It may vary between platforms.
%define input_event_group video
+# The group has access to /dev/loop* devices.
+%define disk_group disk
Name: security-containers
Version: 0.1.1
%dir /etc/security-containers
%dir /etc/security-containers/containers
%dir /etc/security-containers/libvirt-config
+%dir /etc/security-containers/templates
%config /etc/security-containers/daemon.conf
%config /etc/security-containers/containers/*.conf
%config /etc/security-containers/libvirt-config/*.xml
+%config /etc/security-containers/templates/*.conf
+%config /etc/security-containers/templates/*.xml
%{_unitdir}/security-containers.service
%{_unitdir}/multi-user.target.wants/security-containers.service
/etc/dbus-1/system.d/org.tizen.containers.host.conf
-DPYTHON_SITELIB=%{python_sitelib} \
-DSECURITY_CONTAINERS_USER=%{scs_user} \
-DLIBVIRT_GROUP=%{libvirt_group} \
- -DINPUT_EVENT_GROUP=%{input_event_group}
+ -DINPUT_EVENT_GROUP=%{input_event_group} \
+ -DDISK_GROUP=%{disk_group}
make -k %{?jobs:-j%jobs}
%install
FILE(GLOB container_CONF containers/*.conf)
FILE(GLOB admin_CONF libvirt-config/*.xml)
-
+FILE(GLOB template_CONF templates/*.conf templates/*.xml)
## Generate ####################################################################
CONFIGURE_FILE(systemd/security-containers.service.in
INSTALL(FILES ${admin_CONF}
DESTINATION ${SC_CONFIG_INSTALL_DIR}/libvirt-config)
+INSTALL(FILES ${template_CONF}
+ DESTINATION ${SC_CONFIG_INSTALL_DIR}/templates)
+
INSTALL(FILES ${CMAKE_BINARY_DIR}/systemd/security-containers.service
DESTINATION ${SYSTEMD_UNIT_DIR})
{
"containerConfigs" : ["containers/private.conf", "containers/business.conf" ],
"containersPath" : "/opt/usr/containers",
+ "containerImagePath" : "/opt/usr/containers/img/system-data.img",
+ "containerTemplatePath" : "templates",
+ "containerNewConfigPrefix" : "/var/lib/security-containers",
"runMountPointPrefix" : "/var/run/containers",
"foregroundId" : "private",
"defaultId" : "private",
Description=Security Containers Server
After=libvirtd.service
Requires=libvirtd.service
+ConditionVirtualization=no
[Service]
Type=simple
--- /dev/null
+<network>
+ <name>~NAME~</name>
+ <uuid>~UUID~</uuid>
+ <bridge name="virbr-~NAME~"/>
+ <forward/>
+ <dns forwardPlainNames="yes"/>
+ <ip address="10.0.~IP~.1" netmask="255.255.255.0">
+ <dhcp>
+ <range start="10.0.~IP~.2" end="10.0.~IP~.2"/>
+ </dhcp>
+ </ip>
+</network>
--- /dev/null
+<filter name='nwfilter-~NAME~' chain='root'>
+ <uuid>~UUID~</uuid>
+ <rule action='reject' direction='in' priority='100'>
+ <ip srcipaddr='10.0.~IP~.0' srcipmask='255.255.255.0'/>
+ </rule>
+ <rule action='reject' direction='out' priority='100'>
+ <ip dstipaddr='10.0.~IP~.0' srcipmask='255.255.255.0'/>
+ </rule>
+</filter>
--- /dev/null
+{
+ "cpuQuotaForeground" : -1,
+ "cpuQuotaBackground" : 1000,
+ "privilege" : 10,
+ "switchToDefaultAfterTimeout" : true,
+ "enableDbusIntegration" : true,
+ "config" : "../libvirt-config/~NAME~.xml",
+ "networkConfig" : "../libvirt-config/~NAME~-network.xml",
+ "networkFilterConfig" : "../libvirt-config/~NAME~-nwfilter.xml",
+ "runMountPoint" : "~NAME~/run",
+ "permittedToSend" : [ "/tmp/.*" ],
+ "permittedToRecv" : [ "/tmp/.*" ]
+}
--- /dev/null
+<domain type='lxc'>
+ <name>~NAME~</name>
+ <uuid>~UUID~</uuid>
+ <memory>102400</memory>
+ <os>
+ <type>exe</type>
+ <init>/usr/lib/systemd/systemd</init>
+ </os>
+ <on_poweroff>destroy</on_poweroff>
+ <on_reboot>restart</on_reboot>
+ <on_crash>destroy</on_crash>
+ <devices>
+ <console type='pty'/>
+ <hostdev mode='capabilities' type='misc'>
+ <source>
+ <char>/dev/fb0</char>
+ </source>
+ </hostdev>
+ <hostdev mode='capabilities' type='misc'>
+ <source>
+ <char>/dev/tty2</char>
+ </source>
+ </hostdev>
+ <hostdev mode='capabilities' type='misc'>
+ <source>
+ <char>/dev/tty3</char>
+ </source>
+ </hostdev>
+ <hostdev mode='capabilities' type='misc'>
+ <source>
+ <char>/dev/tty4</char>
+ </source>
+ </hostdev>
+ <hostdev mode='capabilities' type='misc'>
+ <source>
+ <char>/dev/tty5</char>
+ </source>
+ </hostdev>
+ <hostdev mode='capabilities' type='misc'>
+ <source>
+ <char>/dev/input/event0</char>
+ </source>
+ </hostdev>
+ <hostdev mode='capabilities' type='misc'>
+ <source>
+ <char>/dev/input/event1</char>
+ </source>
+ </hostdev>
+ <hostdev mode='capabilities' type='misc'>
+ <source>
+ <char>/dev/input/event2</char>
+ </source>
+ </hostdev>
+ <hostdev mode='capabilities' type='misc'>
+ <source>
+ <char>/dev/input/event3</char>
+ </source>
+ </hostdev>
+ <hostdev mode='capabilities' type='misc'>
+ <source>
+ <char>/dev/input/event4</char>
+ </source>
+ </hostdev>
+ <hostdev mode='capabilities' type='misc'>
+ <source>
+ <char>/dev/input/mice</char>
+ </source>
+ </hostdev>
+ <hostdev mode='capabilities' type='misc'>
+ <source>
+ <char>/dev/input/mouse0</char>
+ </source>
+ </hostdev>
+
+ <!--
+ DLOG devices
+ TODO: remove when dlogutil disappears
+ -->
+ <hostdev mode='capabilities' type='misc'>
+ <source>
+ <char>/dev/log_events</char>
+ </source>
+ </hostdev>
+ <hostdev mode='capabilities' type='misc'>
+ <source>
+ <char>/dev/log_main</char>
+ </source>
+ </hostdev>
+ <hostdev mode='capabilities' type='misc'>
+ <source>
+ <char>/dev/log_radio</char>
+ </source>
+ </hostdev>
+ <hostdev mode='capabilities' type='misc'>
+ <source>
+ <char>/dev/log_system</char>
+ </source>
+ </hostdev>
+
+ <interface type='network'>
+ <source network='~NAME~'/>
+ <filterref filter='nwfilter-~NAME~'/>
+ </interface>
+ <filesystem type='mount'>
+ <!-- TODO: this directory could be generated from .conf files-->
+ <source dir='/opt/usr/containers/~NAME~'/>
+ <target dir='/'/>
+ </filesystem>
+ <!-- Enable access to the containers dbus -->
+ <filesystem type='mount'>
+ <!-- TODO: this directory could be generated from .conf files-->
+ <source dir='/var/run/containers/~NAME~/run'/>
+ <target dir='/var/run'/>
+ </filesystem>
+ <!-- Enable access to the SMACK labels -->
+ <!-- TODO: This only helps investigating smack problems,
+ and should be removed eventually -->
+ <filesystem type='mount'>
+ <source dir='/sys/fs/smackfs'/>
+ <target dir='/sys/fs/smackfs'/>
+ </filesystem>
+ </devices>
+</domain>
throw ContainerOperationException(mId + ": Failed to register a libvirt lifecycle callback");
}
+ LOGT(mId << ": registered lifecycle callback");
+
mRebootCallbackId = virConnectDomainEventRegisterAny(virDomainGetConnect(mDom.get()),
mDom.get(),
VIR_DOMAIN_EVENT_ID_REBOOT,
mLifecycleCallbackId);
throw ContainerOperationException(mId + ": Failed to register a libvirt reboot callback");
}
+
+ LOGT(mId << ": registered reboot callback");
}
if (mReconnectThread.joinable()) {
mReconnectThread.join();
}
+
+ if (mStartThread.joinable()) {
+ mStartThread.join();
+ }
}
const std::vector<boost::regex>& Container::getPermittedToSend() const
goBackground();
}
+void Container::startAsync(const StartAsyncResultCallback& callback)
+{
+ if (mStartThread.joinable()) {
+ mStartThread.join();
+ }
+
+ auto startWrapper = [this, callback]() {
+ bool succeeded = false;
+
+ try {
+ start();
+ succeeded = true;
+ } catch(std::exception& e) {
+ LOGE(getId() << ": failed to start: " << e.what());
+ }
+
+ if (callback) {
+ callback(succeeded);
+ }
+ };
+
+ mStartThread = std::thread(startWrapper);
+}
+
void Container::stop()
{
Lock lock(mReconnectMutex);
typedef ContainerConnection::ProxyCallCallback ProxyCallCallback;
typedef std::function<void(const std::string& address)> DbusStateChangedCallback;
+ typedef std::function<void(bool succeeded)> StartAsyncResultCallback;
/**
* Returns a vector of regexps defining files permitted to be
void start();
/**
+ * Boot the container to the background in separate thread. This function immediately exits
+ * after container booting is started in another thread.
+ *
+ * @param callback Called after starting the container. Passes bool with result of starting.
+ */
+ void startAsync(const StartAsyncResultCallback& callback);
+
+ /**
* Try to shutdown the container, if failed, destroy it.
*/
void stop();
std::unique_ptr<ContainerAdmin> mAdmin;
std::unique_ptr<ContainerConnection> mConnection;
std::thread mReconnectThread;
+ std::thread mStartThread;
mutable std::recursive_mutex mReconnectMutex;
NotifyActiveContainerCallback mNotifyCallback;
DisplayOffCallback mDisplayOffCallback;
*/
std::string containersPath;
+ /**
+ * A path where the containers image reside. Empty path means that containers image won't be
+ * copied to containersPath when creating new container.
+ */
+ std::string containerImagePath;
+
+ /**
+ * A path where template configuration files for new containers reside
+ */
+ std::string containerTemplatePath;
+
+ /**
+ * Prefix added to a path for new container configuration files
+ */
+ std::string containerNewConfigPrefix;
+
/*
* Parameters describing input device used to switch between containers
*/
foregroundId,
defaultId,
containersPath,
+ containerImagePath,
+ containerTemplatePath,
+ containerNewConfigPrefix,
inputConfig,
runMountPointPrefix,
proxyCallRules
#include "config/manager.hpp"
#include "dbus/exception.hpp"
#include "utils/fs.hpp"
+#include "utils/img.hpp"
#include <boost/filesystem.hpp>
#include <boost/regex.hpp>
+#include <boost/uuid/uuid.hpp>
+#include <boost/uuid/uuid_io.hpp>
+#include <boost/uuid/uuid_generators.hpp>
+#include <boost/exception/diagnostic_information.hpp>
#include <cassert>
#include <string>
#include <climits>
}
const std::string HOST_ID = "host";
+const std::string CONTAINER_TEMPLATE_CONFIG_PATH = "template.conf";
+const std::string CONTAINER_TEMPLATE_LIBVIRT_CONFIG_PATH = "template.xml";
+const std::string CONTAINER_TEMPLATE_LIBVIRT_NETWORK_PATH = "template-network.xml";
+const std::string CONTAINER_TEMPLATE_LIBVIRT_NETWORK_FILTER_PATH = "template-nwfilter.xml";
+
+const boost::regex CONTAINER_NAME_REGEX("~NAME~");
+const boost::regex CONTAINER_UUID_REGEX("~UUID~");
+const boost::regex CONTAINER_IP_THIRD_OCTET_REGEX("~IP~");
+
+const unsigned int CONTAINER_IP_BASE_THIRD_OCTET = 100;
} // namespace
ContainersManager::ContainersManager(const std::string& managerConfigPath): mDetachOnExit(false)
{
LOGD("Instantiating ContainersManager object...");
- config::loadFromFile(managerConfigPath, mConfig);
+
+ mConfigPath = managerConfigPath;
+ config::loadFromFile(mConfigPath, mConfig);
mProxyCallPolicy.reset(new ProxyCallPolicy(mConfig.proxyCallRules));
mHostConnection.setSetActiveContainerCallback(bind(&ContainersManager::handleSetActiveContainerCall,
this, _1, _2));
- for (auto& containerConfig : mConfig.containerConfigs) {
- std::string containerConfigPath;
-
- if (containerConfig[0] == '/') {
- containerConfigPath = containerConfig;
- } else {
- std::string baseConfigPath = utils::dirName(managerConfigPath);
- containerConfigPath = utils::createFilePath(baseConfigPath, "/", containerConfig);
- }
-
- LOGD("Creating Container " << containerConfigPath);
- std::unique_ptr<Container> c(new Container(containerConfigPath,
- mConfig.runMountPointPrefix));
- const std::string id = c->getId();
- if (id == HOST_ID) {
- throw ContainerOperationException("Cannot use reserved container ID");
- }
-
- c->setNotifyActiveContainerCallback(bind(&ContainersManager::notifyActiveContainerHandler,
- this, id, _1, _2));
+ mHostConnection.setAddContainerCallback(bind(&ContainersManager::handleAddContainerCall,
+ this, _1, _2));
- c->setDisplayOffCallback(bind(&ContainersManager::displayOffHandler,
- this, id));
-
- c->setFileMoveRequestCallback(std::bind(&ContainersManager::handleContainerMoveFileRequest,
- this, id, _1, _2, _3));
-
- c->setProxyCallCallback(bind(&ContainersManager::handleProxyCall,
- this, id, _1, _2, _3, _4, _5, _6, _7));
-
- c->setDbusStateChangedCallback(bind(&ContainersManager::handleDbusStateChanged,
- this, id, _1));
-
- mContainers.insert(ContainerMap::value_type(id, std::move(c)));
+ for (auto& containerConfig : mConfig.containerConfigs) {
+ addContainer(containerConfig);
}
// check if default container exists, throw ContainerOperationException if not found
std::bind(&ContainersManager::switchingSequenceMonitorNotify,
this)));
}
+
+
}
ContainersManager::~ContainersManager()
LOGD("ContainersManager object destroyed");
}
+void ContainersManager::addContainer(const std::string& containerConfig)
+{
+ std::string baseConfigPath = utils::dirName(mConfigPath);
+ std::string containerConfigPath = utils::getAbsolutePath(containerConfig, baseConfigPath);
+
+ LOGT("Creating Container " << containerConfigPath);
+ std::unique_ptr<Container> c(new Container(containerConfigPath,
+ mConfig.runMountPointPrefix));
+ const std::string id = c->getId();
+ if (id == HOST_ID) {
+ throw ContainerOperationException("Cannot use reserved container ID");
+ }
+
+ using namespace std::placeholders;
+ c->setNotifyActiveContainerCallback(bind(&ContainersManager::notifyActiveContainerHandler,
+ this, id, _1, _2));
+
+ c->setDisplayOffCallback(bind(&ContainersManager::displayOffHandler,
+ this, id));
+
+ c->setFileMoveRequestCallback(bind(&ContainersManager::handleContainerMoveFileRequest,
+ this, id, _1, _2, _3));
+
+ c->setProxyCallCallback(bind(&ContainersManager::handleProxyCall,
+ this, id, _1, _2, _3, _4, _5, _6, _7));
+
+ c->setDbusStateChangedCallback(bind(&ContainersManager::handleDbusStateChanged,
+ this, id, _1));
+
+ mContainers.insert(ContainerMap::value_type(id, std::move(c)));
+}
+
void ContainersManager::focus(const std::string& containerId)
{
/* try to access the object first to throw immediately if it doesn't exist */
result->setVoid();
}
+
+void ContainersManager::generateNewConfig(const std::string& id,
+ const std::string& templatePath,
+ const std::string& resultPath)
+{
+ namespace fs = boost::filesystem;
+
+ std::string resultFileDir = utils::dirName(resultPath);
+ if (!fs::exists(resultFileDir)) {
+ if (!utils::createEmptyDir(resultFileDir)) {
+ LOGE("Unable to create directory for new config.");
+ throw ContainerOperationException("Unable to create directory for new config.");
+ }
+ }
+
+ fs::path resultFile(resultPath);
+ if (fs::exists(resultFile)) {
+ LOGT(resultPath << " already exists, removing");
+ fs::remove(resultFile);
+ }
+
+ std::string config;
+ if (!utils::readFileContent(templatePath, config)) {
+ LOGE("Failed to read template config file.");
+ throw ContainerOperationException("Failed to read template config file.");
+ }
+
+ std::string resultConfig = boost::regex_replace(config, CONTAINER_NAME_REGEX, id);
+
+ boost::uuids::uuid u = boost::uuids::random_generator()();
+ std::string uuidStr = to_string(u);
+ LOGD("uuid: " << uuidStr);
+ resultConfig = boost::regex_replace(resultConfig, CONTAINER_UUID_REGEX, uuidStr);
+
+ // generate third IP octet for network config
+ std::string thirdOctetStr = std::to_string(CONTAINER_IP_BASE_THIRD_OCTET + mContainers.size() + 1);
+ LOGD("ip_third_octet: " << thirdOctetStr);
+ resultConfig = boost::regex_replace(resultConfig, CONTAINER_IP_THIRD_OCTET_REGEX, thirdOctetStr);
+
+ if (!utils::saveFileContent(resultPath, resultConfig)) {
+ LOGE("Faield to save new config file.");
+ throw ContainerOperationException("Failed to save new config file.");
+ }
+
+ // restrict new config file so that only owner (security-containers) can write it
+ fs::permissions(resultPath, fs::perms::owner_all |
+ fs::perms::group_read |
+ fs::perms::others_read);
+}
+
+void ContainersManager::handleAddContainerCall(const std::string& id,
+ dbus::MethodResultBuilder::Pointer result)
+{
+ LOGI("Adding container " << id);
+
+ // TODO: This solution is temporary. It utilizes direct access to config files when creating new
+ // containers. Update this handler when config database will appear.
+ namespace fs = boost::filesystem;
+
+ boost::system::error_code ec;
+ const std::string containerPathStr = utils::createFilePath(mConfig.containersPath, "/", id, "/");
+
+ // check if container does not exist
+ if (mContainers.find(id) != mContainers.end()) {
+ LOGE("Cannot create " << id << " container - already exists!");
+ result->setError(api::host::ERROR_CONTAINER_CREATE_FAILED,
+ "Cannot create " + id + " container - already exists!");
+ return;
+ }
+
+ // copy container image if config contains path to image
+ LOGT("image path: " << mConfig.containerImagePath);
+ if (!mConfig.containerImagePath.empty()) {
+ if (!utils::copyImageContents(mConfig.containerImagePath, containerPathStr)) {
+ LOGE("Failed to copy container image.");
+ result->setError(api::host::ERROR_CONTAINER_CREATE_FAILED,
+ "Failed to copy container image.");
+ return;
+ }
+ }
+
+ // generate paths to new configuration files
+ std::string baseDir = utils::dirName(mConfigPath);
+ std::string configDir = utils::getAbsolutePath(mConfig.containerNewConfigPrefix, baseDir);
+ std::string templateDir = utils::getAbsolutePath(mConfig.containerTemplatePath, baseDir);
+
+ std::string configPath = utils::createFilePath(templateDir, "/", CONTAINER_TEMPLATE_CONFIG_PATH);
+ std::string newConfigPath = utils::createFilePath(configDir, "/containers/", id + ".conf");
+ std::string libvirtConfigPath = utils::createFilePath(templateDir, "/", CONTAINER_TEMPLATE_LIBVIRT_CONFIG_PATH);
+ std::string newLibvirtConfigPath = utils::createFilePath(configDir, "/libvirt-config/", id + ".xml");
+ std::string libvirtNetworkPath = utils::createFilePath(templateDir, "/", CONTAINER_TEMPLATE_LIBVIRT_NETWORK_PATH);
+ std::string newLibvirtNetworkPath = utils::createFilePath(configDir, "/libvirt-config/", id + "-network.xml");
+ std::string libvirtNetworkFilterPath = utils::createFilePath(templateDir, "/", CONTAINER_TEMPLATE_LIBVIRT_NETWORK_FILTER_PATH);
+ std::string newLibvirtNetworkFilterPath = utils::createFilePath(configDir, "/libvirt-config/", id + "-nwfilter.xml");
+
+ auto removeAllWrapper = [](const std::string& path) {
+ try {
+ LOGD("Removing copied data");
+ fs::remove_all(fs::path(path));
+ } catch(const boost::exception& e) {
+ LOGW("Failed to remove data: " << boost::diagnostic_information(e));
+ }
+ };
+
+ try {
+ LOGI("Generating config from " << configPath << " to " << newConfigPath);
+ generateNewConfig(id, configPath, newConfigPath);
+
+ LOGI("Generating config from " << libvirtConfigPath << " to " << newLibvirtConfigPath);
+ generateNewConfig(id, libvirtConfigPath, newLibvirtConfigPath);
+
+ LOGI("Generating config from " << libvirtNetworkPath << " to " << newLibvirtNetworkPath);
+ generateNewConfig(id, libvirtNetworkPath, newLibvirtNetworkPath);
+
+ LOGI("Generating config from " << libvirtNetworkFilterPath << " to " << newLibvirtNetworkFilterPath);
+ generateNewConfig(id, libvirtNetworkFilterPath, newLibvirtNetworkFilterPath);
+ } catch (SecurityContainersException& e) {
+ LOGE(e.what());
+ removeAllWrapper(containerPathStr);
+ result->setError(api::host::ERROR_CONTAINER_CREATE_FAILED, e.what());
+ return;
+ }
+
+ LOGT("Adding new container");
+ try {
+ addContainer(newConfigPath);
+ } catch (SecurityContainersException& e) {
+ LOGE(e.what());
+ removeAllWrapper(containerPathStr);
+ result->setError(api::host::ERROR_CONTAINER_CREATE_FAILED, e.what());
+ return;
+ }
+
+ auto resultCallback = [result, containerPathStr, removeAllWrapper](bool succeeded) {
+ if (succeeded) {
+ result->setVoid();
+ } else {
+ LOGE("Failed to start container.");
+ removeAllWrapper(containerPathStr);
+ result->setError(api::host::ERROR_CONTAINER_CREATE_FAILED,
+ "Failed to start container.");
+ }
+ };
+ mContainers[id]->startAsync(resultCallback);
+}
+
} // namespace security_containers
~ContainersManager();
/**
+ * Add new container.
+ *
+ * @param containerConfig config of new container
+ */
+ void addContainer(const std::string& containerConfig);
+
+ /**
* Focus this container, put it to the foreground.
* Method blocks until the focus is switched.
*
private:
ContainersManagerConfig mConfig;
+ std::string mConfigPath;
HostConnection mHostConnection;
// to hold InputMonitor pointer to monitor if container switching sequence is recognized
std::unique_ptr<InputMonitor> mSwitchingSequenceMonitor;
bool mDetachOnExit;
void switchingSequenceMonitorNotify();
+ void generateNewConfig(const std::string& id,
+ const std::string& templatePath,
+ const std::string& resultPath);
+
void notifyActiveContainerHandler(const std::string& caller,
const std::string& appliaction,
const std::string& message);
void handleGetActiveContainerIdCall(dbus::MethodResultBuilder::Pointer result);
void handleSetActiveContainerCall(const std::string& id,
dbus::MethodResultBuilder::Pointer result);
-
+ void handleAddContainerCall(const std::string& id,
+ dbus::MethodResultBuilder::Pointer result);
};
mSetActiveContainerCallback = callback;
}
+void HostConnection::setAddContainerCallback(const AddContainerCallback& callback)
+{
+ mAddContainerCallback = callback;
+}
+
void HostConnection::onMessageCall(const std::string& objectPath,
const std::string& interface,
const std::string& methodName,
}
return;
}
+
+ if (methodName == api::host::METHOD_ADD_CONTAINER) {
+ const gchar* id = NULL;
+ g_variant_get(parameters, "(&s)", &id);
+
+ if (mAddContainerCallback){
+ mAddContainerCallback(id, result);
+ }
+ }
}
void HostConnection::proxyCallAsync(const std::string& busName,
typedef std::function<void(const std::string& id,
dbus::MethodResultBuilder::Pointer result
)> SetActiveContainerCallback;
+ typedef std::function<void(const std::string& id,
+ dbus::MethodResultBuilder::Pointer result
+ )> AddContainerCallback;
/**
* Register proxy call callback
void setSetActiveContainerCallback(const SetActiveContainerCallback& callback);
/**
+ * Register a callback called to create new container
+ */
+ void setAddContainerCallback(const AddContainerCallback& callback);
+
+ /**
* Make a proxy call
*/
void proxyCallAsync(const std::string& busName,
GetContainerIdsCallback mGetContainerIdsCallback;
GetActiveContainerIdCallback mGetActiveContainerIdCallback;
SetActiveContainerCallback mSetActiveContainerCallback;
+ AddContainerCallback mAddContainerCallback;
void onNameAcquired();
void onNameLost();
const std::string INTERFACE = "org.tizen.containers.host.manager";
const std::string ERROR_CONTAINER_STOPPED = "org.tizen.containers.host.Error.ContainersStopped";
+const std::string ERROR_CONTAINER_CREATE_FAILED = "org.tizen.containers.host.Error.ContainerCreateFailed";
const std::string METHOD_GET_CONTAINER_DBUSES = "GetContainerDbuses";
const std::string METHOD_GET_CONTAINER_ID_LIST = "GetContainerIds";
const std::string METHOD_GET_ACTIVE_CONTAINER_ID = "GetActiveContainerId";
const std::string METHOD_SET_ACTIVE_CONTAINER = "SetActiveContainer";
+const std::string METHOD_ADD_CONTAINER = "AddContainer";
const std::string SIGNAL_CONTAINER_DBUS_STATE = "ContainerDbusState";
" <method name='" + METHOD_SET_ACTIVE_CONTAINER + "'>"
" <arg type='s' name='id' direction='in'/>"
" </method>"
+ " <method name='" + METHOD_ADD_CONTAINER + "'>"
+ " <arg type='s' name='id' direction='in'/>"
+ " </method>"
" <signal name='" + SIGNAL_CONTAINER_DBUS_STATE + "'>"
" <arg type='s' name='container'/>"
" <arg type='s' name='dbusAddress'/>"
#error "LIBVIRT_GROUP must be defined!"
#endif
+#ifndef DISK_GROUP
+#error "DISK_GROUP must be defined!"
+#endif
+
extern char** environ;
namespace security_containers {
}
}
+ // create directory for additional container data (if needed)
+ if (!config.containerNewConfigPrefix.empty()) {
+ if (!utils::createDir(config.containerNewConfigPrefix, uid, gid,
+ fs::perms::owner_all |
+ fs::perms::group_read | fs::perms::group_exe |
+ fs::perms::others_read | fs::perms::others_exe)) {
+ return false;
+ }
+ }
+
// Omit supplementaty group setup and root drop if the user is already switched.
// This situation will happen during daemon update triggered by SIGUSR1.
if (!runAsRoot && geteuid() == uid) {
// LIBVIRT_GROUP provides access to libvirt's daemon socket.
// INPUT_EVENT_GROUP provides access to /dev/input/event* devices used by InputMonitor.
- if (!utils::setSuppGroups({LIBVIRT_GROUP, INPUT_EVENT_GROUP})) {
+ // DISK_GROUP provides access to /dev/loop* devices, needed when adding new container to copy
+ // containers image
+ if (!utils::setSuppGroups({LIBVIRT_GROUP, INPUT_EVENT_GROUP, DISK_GROUP})) {
return false;
}
"foregroundId" : "ut-containers-manager-console1-dbus",
"defaultId" : "ut-containers-manager-console1-dbus",
"containersPath" : "/tmp",
+ "containerImagePath" : "",
+ "containerTemplatePath" : "",
+ "containerNewConfigPrefix" : "",
"runMountPointPrefix" : "",
"inputConfig" : {"enabled" : false,
"device" : "/dev/doesnotexist",
FILE(GLOB manager_manager_CONF ut-containers-manager/*.conf)
FILE(GLOB manager_container_CONF ut-containers-manager/containers/*.conf)
FILE(GLOB manager_admin_CONF ut-containers-manager/libvirt-config/*.xml)
+FILE(GLOB manager_admin_TEMPLATE ut-containers-manager/templates/*.conf
+ ut-containers-manager/templates/*.xml)
FILE(GLOB container_CONF ut-container/*.conf)
FILE(GLOB container_container_CONF ut-container/containers/*.conf)
${CMAKE_BINARY_DIR}/ut-containers-manager/libvirt-config/console2-dbus.xml @ONLY)
CONFIGURE_FILE(ut-containers-manager/libvirt-config/console3-dbus.xml.in
${CMAKE_BINARY_DIR}/ut-containers-manager/libvirt-config/console3-dbus.xml @ONLY)
+CONFIGURE_FILE(ut-containers-manager/templates/template.xml.in
+ ${CMAKE_BINARY_DIR}/ut-containers-manager/templates/template.xml @ONLY)
FILE(GLOB manager_admin_CONF_GEN ${CMAKE_BINARY_DIR}/ut-containers-manager/libvirt-config/*.xml)
+FILE(GLOB manager_admin_TEMPLATE_GEN ${CMAKE_BINARY_DIR}/ut-containers-manager/templates/*.xml)
CONFIGURE_FILE(ut-container/libvirt-config/test-dbus.xml.in
${CMAKE_BINARY_DIR}/ut-container/libvirt-config/test-dbus.xml @ONLY)
DESTINATION ${SC_TEST_CONFIG_INSTALL_DIR}/server/ut-containers-manager/libvirt-config)
INSTALL(FILES ${manager_admin_CONF_GEN}
DESTINATION ${SC_TEST_CONFIG_INSTALL_DIR}/server/ut-containers-manager/libvirt-config)
+INSTALL(FILES ${manager_admin_TEMPLATE}
+ DESTINATION ${SC_TEST_CONFIG_INSTALL_DIR}/server/ut-containers-manager/templates)
+INSTALL(FILES ${manager_admin_TEMPLATE_GEN}
+ DESTINATION ${SC_TEST_CONFIG_INSTALL_DIR}/server/ut-containers-manager/templates)
INSTALL(FILES ${container_CONF}
DESTINATION ${SC_TEST_CONFIG_INSTALL_DIR}/server/ut-container)
"foregroundId" : "ut-containers-manager-console1",
"defaultId" : "ut-containers-manager-console1",
"containersPath" : "/tmp",
+ "containerImagePath" : "",
+ "containerTemplatePath" : "templates",
+ "containerNewConfigPrefix" : "/usr/share/security-containers/tests/server/ut-containers-manager/",
"inputConfig" : {"enabled" : false,
"device" : "/dev/doesnotexist",
"code" : 139,
"foregroundId" : "ut-containers-manager-console1",
"defaultId" : "in_no_way_there_is_a_valid_id_here",
"containersPath" : "/tmp",
+ "containerImagePath" : "",
+ "containerTemplatePath" : "templates",
+ "containerNewConfigPrefix" : "/usr/share/security-containers/tests/server/ut-containers-manager/",
"inputConfig" : {"enabled" : false,
"device" : "/dev/doesnotexist",
"code" : 139,
"foregroundId" : "this_id_does_not_exist",
"defaultId" : "ut-containers-manager-console1",
"containersPath" : "/tmp",
+ "containerImagePath" : "",
+ "containerTemplatePath" : "templates",
+ "containerNewConfigPrefix" : "/usr/share/security-containers/tests/server/ut-containers-manager/",
"inputConfig" : {"enabled" : false,
"device" : "/dev/doesnotexist",
"code" : 139,
--- /dev/null
+<network>
+ <name>~NAME~-network</name>
+ <uuid>~UUID~</uuid>
+</network>
--- /dev/null
+<filter name='nwfilter-~NAME~' chain='root'>
+ <uuid>~UUID~</uuid>
+</filter>
--- /dev/null
+{
+ "privilege" : 20,
+ "switchToDefaultAfterTimeout" : true,
+ "config" : "../libvirt-config/~NAME~.xml",
+ "networkConfig" : "../libvirt-config/~NAME~-network.xml",
+ "networkFilterConfig" : "../libvirt-config/~NAME~-nwfilter.xml",
+ "cpuQuotaForeground" : -1,
+ "cpuQuotaBackground" : 1000,
+ "runMountPoint" : "/tmp/ut-containers-manager/~NAME~-dbus",
+ "enableDbusIntegration" : true,
+ "permittedToSend" : [ "/tmp/.*" ],
+ "permittedToRecv" : [ "/tmp/.*" ]
+}
--- /dev/null
+<domain type="lxc">
+ <name>~NAME~</name>
+ <uuid>~UUID~</uuid>
+ <memory>102400</memory>
+ <os>
+ <type>exe</type>
+ <init>/usr/bin/dbus-daemon</init>
+ <initarg>--nofork</initarg>
+ <initarg>--config-file=@SC_TEST_CONFIG_INSTALL_DIR@/server/ut-containers-manager/ut-dbus.conf</initarg>
+ <initarg>--address=unix:path=/tmp/ut-containers-manager/~NAME~-dbus/dbus/system_bus_socket</initarg>
+ </os>
+ <devices>
+ <console type="pty"/>
+ </devices>
+</domain>
"foregroundId" : "ut-containers-manager-console1",
"defaultId" : "ut-containers-manager-console1",
"containersPath" : "/tmp",
+ "containerImagePath" : "",
+ "containerTemplatePath" : "templates",
+ "containerNewConfigPrefix" : "/usr/share/security-containers/tests/server/ut-containers-manager/",
"inputConfig" : {"enabled" : false,
"device" : "/dev/doesnotexist",
"code" : 139,
"foregroundId" : "ut-containers-manager-console1-dbus",
"defaultId" : "ut-containers-manager-console1-dbus",
"containersPath" : "/tmp",
+ "containerImagePath" : "",
+ "containerTemplatePath" : "templates",
+ "containerNewConfigPrefix" : "/usr/share/security-containers/tests/server/ut-containers-manager/",
"runMountPointPrefix" : "",
"inputConfig" : {"enabled" : false,
"device" : "/dev/doesnotexist",
{
"containerConfigs" : ["containers/container1.conf", "missing/file/path/missing.conf", "containers/container3.conf"],
"containersPath" : "/tmp",
+ "containerImagePath" : "",
+ "containerTemplatePath" : "no_need_for_templates_in_this_test",
"runMountPointPrefix" : "",
"foregroundId" : "ut-server-container1",
"defaultId" : "ut-server-container1",
+ "containerNewConfigPrefix" : "",
"inputConfig" : {"enabled" : false,
"device" : "/dev/doesnotexist",
"code" : 139,
{
"containerConfigs" : ["containers/container1.conf", "containers/container2.conf", "containers/container3.conf"],
"containersPath" : "/tmp",
+ "containerImagePath" : "",
+ "containerTemplatePath" : "no_need_for_templates_in_this_test",
+ "containerNewConfigPrefix" : "",
"runMountPointPrefix" : "",
"foregroundId" : "ut-server-container1",
"defaultId" : "ut-server-container1",
#include "config/exception.hpp"
#include "utils/latch.hpp"
#include "utils/fs.hpp"
+#include "utils/img.hpp"
#include <vector>
#include <map>
const std::string BUGGY_CONFIG_PATH = SC_TEST_CONFIG_INSTALL_DIR "/server/ut-containers-manager/buggy-daemon.conf";
const std::string BUGGY_FOREGROUND_CONFIG_PATH = SC_TEST_CONFIG_INSTALL_DIR "/server/ut-containers-manager/buggy-foreground-daemon.conf";
const std::string BUGGY_DEFAULTID_CONFIG_PATH = SC_TEST_CONFIG_INSTALL_DIR "/server/ut-containers-manager/buggy-default-daemon.conf";
+const std::string TEST_CONTAINER_CONF_PATH = SC_TEST_CONFIG_INSTALL_DIR "/server/ut-containers-manager/containers/";
+const std::string TEST_CONTAINER_LIBVIRT_CONF_PATH = SC_TEST_CONFIG_INSTALL_DIR "/server/ut-containers-manager/libvirt-config/";
const std::string MISSING_CONFIG_PATH = "/this/is/a/missing/file/path/missing-daemon.conf";
const int EVENT_TIMEOUT = 5000;
const int TEST_DBUS_CONNECTION_CONTAINERS_COUNT = 3;
typedef std::function<void(const std::string& argument,
MethodResultBuilder::Pointer result
)> TestApiMethodCallback;
+ typedef std::function<void()> AddContainerResultCallback;
typedef std::map<std::string, std::string> Dbuses;
}
+ void callAsyncMethodAddContainer(const std::string& id,
+ const AddContainerResultCallback& result)
+ {
+ auto asyncResult = [result](dbus::AsyncMethodCallResult& asyncMethodCallResult) {
+ BOOST_CHECK(g_variant_is_of_type(asyncMethodCallResult.get(), G_VARIANT_TYPE_UNIT));
+ result();
+ };
+
+ assert(isHost());
+ GVariant* parameters = g_variant_new("(s)", id.c_str());
+ mClient->callMethodAsync(api::host::BUS_NAME,
+ api::host::OBJECT_PATH,
+ api::host::INTERFACE,
+ api::host::METHOD_ADD_CONTAINER,
+ parameters,
+ "()",
+ asyncResult);
+ }
+
private:
const int mId;
DbusConnection::Pointer mClient;
};
}
+class FileCleanerRAII {
+public:
+ FileCleanerRAII(const std::vector<std::string>& filePathsToClean):
+ mFilePathsToClean(filePathsToClean)
+ { }
+
+ ~FileCleanerRAII()
+ {
+ namespace fs = boost::filesystem;
+ for (const auto& file : mFilePathsToClean) {
+ fs::path f(file);
+ if (fs::exists(f)) {
+ fs::remove(f);
+ }
+ }
+ }
+
+private:
+ const std::vector<std::string> mFilePathsToClean;
+};
+
struct Fixture {
security_containers::utils::ScopedGlibLoop mLoop;
};
DbusException);
}
+BOOST_AUTO_TEST_CASE(AddContainerTest)
+{
+ const std::string newContainerId = "test1234";
+ const std::vector<std::string> newContainerConfigs = {
+ TEST_CONTAINER_CONF_PATH + newContainerId + ".conf",
+ TEST_CONTAINER_LIBVIRT_CONF_PATH + newContainerId + ".xml",
+ TEST_CONTAINER_LIBVIRT_CONF_PATH + newContainerId + "-network.xml",
+ TEST_CONTAINER_LIBVIRT_CONF_PATH + newContainerId + "-nwfilter.xml",
+ };
+ FileCleanerRAII cleaner(newContainerConfigs);
+
+ ContainersManager cm(TEST_DBUS_CONFIG_PATH);
+ cm.startAll();
+
+ Latch callDone;
+ auto resultCallback = [&]() {
+ callDone.set();
+ };
+
+ DbusAccessory dbus(DbusAccessory::HOST_ID);
+
+ // create new container
+ dbus.callAsyncMethodAddContainer(newContainerId, resultCallback);
+ callDone.wait(EVENT_TIMEOUT);
+
+ // focus new container
+ BOOST_REQUIRE_NO_THROW(cm.focus(newContainerId));
+ BOOST_CHECK(cm.getRunningForegroundContainerId() == newContainerId);
+}
+
BOOST_AUTO_TEST_SUITE_END()
const std::string FILE_CONTENT = "File content\n"
"Line 1\n"
"Line 2\n";
+const std::string FILE_CONTENT_2 = "Some other content\n"
+ "Just to see if\n"
+ "everything is copied correctly\n";
+const std::string FILE_CONTENT_3 = "More content\n"
+ "More and more content\n"
+ "That's a lot of data to test\n";
const std::string BUGGY_FILE_PATH = "/some/missing/file/path/file.txt";
const std::string TMP_PATH = "/tmp";
const std::string FILE_PATH_RANDOM =
boost::filesystem::unique_path("/tmp/mountPoint-%%%%").string();
const std::string MOUNT_POINT_RANDOM_2 =
boost::filesystem::unique_path("/tmp/mountPoint-%%%%").string();
+const std::string FILE_DIR_RANDOM_1 =
+ boost::filesystem::unique_path("testDir-%%%%").string();
+const std::string FILE_DIR_RANDOM_2 =
+ boost::filesystem::unique_path("testDir-%%%%").string();
+const std::string FILE_DIR_RANDOM_3 =
+ boost::filesystem::unique_path("testDir-%%%%").string();
const std::string FILE_NAME_RANDOM_1 =
boost::filesystem::unique_path("testFile-%%%%").string();
const std::string FILE_NAME_RANDOM_2 =
BOOST_REQUIRE(fs::remove(MOUNT_POINT_RANDOM_2, ec));
}
+BOOST_AUTO_TEST_CASE(CopyDirContentsTest)
+{
+ namespace fs = boost::filesystem;
+ std::string src, src_inner, dst, dst_inner;
+ boost::system::error_code ec;
+
+ src = TMP_PATH + "/" + FILE_DIR_RANDOM_1;
+ src_inner = src + "/" + FILE_DIR_RANDOM_3;
+
+ dst = TMP_PATH + "/" + FILE_DIR_RANDOM_2;
+ dst_inner = dst + "/" + FILE_DIR_RANDOM_3;
+
+ // create entire structure with files
+ BOOST_REQUIRE(fs::create_directory(src, ec));
+ BOOST_REQUIRE(ec.value() == 0);
+ BOOST_REQUIRE(fs::create_directory(src_inner, ec));
+ BOOST_REQUIRE(ec.value() == 0);
+
+ BOOST_REQUIRE(saveFileContent(src + "/" + FILE_NAME_RANDOM_1, FILE_CONTENT));
+ BOOST_REQUIRE(saveFileContent(src + "/" + FILE_NAME_RANDOM_2, FILE_CONTENT_2));
+ BOOST_REQUIRE(saveFileContent(src_inner + "/" + FILE_NAME_RANDOM_1, FILE_CONTENT_3));
+
+ BOOST_REQUIRE(fs::create_directory(dst, ec));
+ BOOST_REQUIRE(ec.value() == 0);
+
+ // copy data
+ BOOST_CHECK(copyDirContents(src, dst));
+
+ // check if copy is successful
+ BOOST_CHECK(fs::exists(dst + "/" + FILE_NAME_RANDOM_1));
+ BOOST_CHECK(fs::exists(dst + "/" + FILE_NAME_RANDOM_2));
+ BOOST_CHECK(fs::exists(dst_inner));
+ BOOST_CHECK(fs::exists(dst_inner + "/" + FILE_NAME_RANDOM_1));
+
+ BOOST_CHECK_EQUAL(readFileContent(dst + "/" + FILE_NAME_RANDOM_1), FILE_CONTENT);
+ BOOST_CHECK_EQUAL(readFileContent(dst + "/" + FILE_NAME_RANDOM_2), FILE_CONTENT_2);
+ BOOST_CHECK_EQUAL(readFileContent(dst_inner + "/" + FILE_NAME_RANDOM_1), FILE_CONTENT_3);
+}
+
BOOST_AUTO_TEST_SUITE_END()