Add API to create new containers 84/26384/8
authorLukasz Kostyra <l.kostyra@samsung.com>
Tue, 5 Aug 2014 11:49:56 +0000 (13:49 +0200)
committerLukasz Kostyra <l.kostyra@samsung.com>
Tue, 16 Sep 2014 07:07:06 +0000 (09:07 +0200)
[Feature]       Dbus method to add new containers
[Cause]         Need of dynamic management of containers
[Solution]      Added dbus API to add new containers.
                Added new functions to utils needed during dynamic container creation.
[Verification]  Build, install, run unit tests.

Change-Id: I2044c416947dccc3e0e90302f6b56ea49db0baa1

39 files changed:
CMakeLists.txt
common/utils/fs.cpp
common/utils/fs.hpp
common/utils/img.cpp [new file with mode: 0644]
common/utils/img.hpp [new file with mode: 0644]
common/utils/paths.hpp
packaging/security-containers.spec
server/configs/CMakeLists.txt
server/configs/daemon.conf
server/configs/systemd/security-containers.service.in
server/configs/templates/template-network.xml [new file with mode: 0644]
server/configs/templates/template-nwfilter.xml [new file with mode: 0644]
server/configs/templates/template.conf [new file with mode: 0644]
server/configs/templates/template.xml [new file with mode: 0644]
server/container-admin.cpp
server/container.cpp
server/container.hpp
server/containers-manager-config.hpp
server/containers-manager.cpp
server/containers-manager.hpp
server/host-connection.cpp
server/host-connection.hpp
server/host-dbus-definitions.hpp
server/server.cpp
tests/unit_tests/client/configs/ut-client/test-dbus-daemon.conf
tests/unit_tests/server/configs/CMakeLists.txt
tests/unit_tests/server/configs/ut-containers-manager/buggy-daemon.conf
tests/unit_tests/server/configs/ut-containers-manager/buggy-default-daemon.conf
tests/unit_tests/server/configs/ut-containers-manager/buggy-foreground-daemon.conf
tests/unit_tests/server/configs/ut-containers-manager/templates/template-network.xml [new file with mode: 0644]
tests/unit_tests/server/configs/ut-containers-manager/templates/template-nwfilter.xml [new file with mode: 0644]
tests/unit_tests/server/configs/ut-containers-manager/templates/template.conf [new file with mode: 0644]
tests/unit_tests/server/configs/ut-containers-manager/templates/template.xml.in [new file with mode: 0644]
tests/unit_tests/server/configs/ut-containers-manager/test-daemon.conf
tests/unit_tests/server/configs/ut-containers-manager/test-dbus-daemon.conf
tests/unit_tests/server/configs/ut-server/buggy-daemon.conf
tests/unit_tests/server/configs/ut-server/test-daemon.conf
tests/unit_tests/server/ut-containers-manager.cpp
tests/unit_tests/utils/ut-fs.cpp

index 266773a..9cb880a 100644 (file)
@@ -69,10 +69,14 @@ ENDIF(NOT DEFINED LIBVIRT_GROUP)
 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 ###################################################
 
index d663831..a03c139 100644 (file)
@@ -39,6 +39,8 @@
 #include <sys/types.h>
 #include <unistd.h>
 
+#include <iostream>
+
 
 namespace security_containers {
 namespace utils {
@@ -199,6 +201,63 @@ bool moveFile(const std::string& src, const std::string& dst)
     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;
@@ -239,5 +298,35 @@ bool createDir(const std::string& path, uid_t uid, uid_t gid, 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
index 9cff7a9..0e2f00e 100644 (file)
@@ -87,10 +87,22 @@ bool hasSameMountPoint(const std::string& path1, const std::string& path2, bool&
 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
diff --git a/common/utils/img.cpp b/common/utils/img.cpp
new file mode 100644 (file)
index 0000000..4d7de88
--- /dev/null
@@ -0,0 +1,240 @@
+/*
+ *  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
diff --git a/common/utils/img.hpp b/common/utils/img.hpp
new file mode 100644 (file)
index 0000000..d42300e
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ *  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
index e357c71..14132b4 100644 (file)
@@ -107,6 +107,18 @@ inline std::string dirName(std::string path)
     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
index 30914b0..97bf4d5 100644 (file)
@@ -5,6 +5,8 @@
 # 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
@@ -40,9 +42,12 @@ between them. A process from inside a container can request a switch of context
 %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
@@ -65,7 +70,8 @@ between them. A process from inside a container can request a switch of context
          -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
index def06a2..81859c1 100644 (file)
@@ -21,7 +21,7 @@ MESSAGE(STATUS "Installing configs to " ${SC_CONFIG_INSTALL_DIR})
 
 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
@@ -45,5 +45,8 @@ INSTALL(FILES       ${container_CONF}
 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})
index 67ef356..db85284 100644 (file)
@@ -1,6 +1,9 @@
 {
     "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",
index f06dea3..c271341 100644 (file)
@@ -2,6 +2,7 @@
 Description=Security Containers Server
 After=libvirtd.service
 Requires=libvirtd.service
+ConditionVirtualization=no
 
 [Service]
 Type=simple
diff --git a/server/configs/templates/template-network.xml b/server/configs/templates/template-network.xml
new file mode 100644 (file)
index 0000000..f5ec171
--- /dev/null
@@ -0,0 +1,12 @@
+<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>
diff --git a/server/configs/templates/template-nwfilter.xml b/server/configs/templates/template-nwfilter.xml
new file mode 100644 (file)
index 0000000..82ea8cc
--- /dev/null
@@ -0,0 +1,9 @@
+<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>
diff --git a/server/configs/templates/template.conf b/server/configs/templates/template.conf
new file mode 100644 (file)
index 0000000..17480a0
--- /dev/null
@@ -0,0 +1,13 @@
+{
+    "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/.*" ]
+}
diff --git a/server/configs/templates/template.xml b/server/configs/templates/template.xml
new file mode 100644 (file)
index 0000000..bdb452d
--- /dev/null
@@ -0,0 +1,123 @@
+<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>
index 805e9c1..f60d942 100644 (file)
@@ -90,6 +90,8 @@ ContainerAdmin::ContainerAdmin(const ContainerConfig& config)
         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,
@@ -103,6 +105,8 @@ ContainerAdmin::ContainerAdmin(const ContainerConfig& config)
                                            mLifecycleCallbackId);
         throw ContainerOperationException(mId + ": Failed to register a libvirt reboot callback");
     }
+
+    LOGT(mId << ": registered reboot callback");
 }
 
 
index 60d124e..494b0ae 100644 (file)
@@ -91,6 +91,10 @@ Container::~Container()
     if (mReconnectThread.joinable()) {
         mReconnectThread.join();
     }
+
+    if (mStartThread.joinable()) {
+        mStartThread.join();
+    }
 }
 
 const std::vector<boost::regex>& Container::getPermittedToSend() const
@@ -131,6 +135,30 @@ void Container::start()
     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);
index f2d67c0..f741464 100644 (file)
@@ -56,6 +56,7 @@ public:
     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
@@ -87,6 +88,14 @@ public:
     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();
@@ -197,6 +206,7 @@ private:
     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;
index 310dca3..06b83be 100644 (file)
@@ -62,6 +62,22 @@ struct ContainersManagerConfig {
      */
     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
      */
@@ -83,6 +99,9 @@ struct ContainersManagerConfig {
         foregroundId,
         defaultId,
         containersPath,
+        containerImagePath,
+        containerTemplatePath,
+        containerNewConfigPrefix,
         inputConfig,
         runMountPointPrefix,
         proxyCallRules
index 90a6cfc..5dc909b 100644 (file)
 #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>
@@ -61,13 +66,25 @@ bool regexMatchVector(const std::string& str, const std::vector<boost::regex>& v
 }
 
 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));
 
@@ -87,40 +104,11 @@ ContainersManager::ContainersManager(const std::string& managerConfigPath): mDet
     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
@@ -139,6 +127,8 @@ ContainersManager::ContainersManager(const std::string& managerConfigPath): mDet
                                  std::bind(&ContainersManager::switchingSequenceMonitorNotify,
                                            this)));
     }
+
+
 }
 
 ContainersManager::~ContainersManager()
@@ -156,6 +146,38 @@ 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 */
@@ -465,4 +487,150 @@ void ContainersManager::handleSetActiveContainerCall(const std::string& id,
     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
index 33767a9..cd1194d 100644 (file)
@@ -48,6 +48,13 @@ public:
     ~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.
      *
@@ -77,6 +84,7 @@ public:
 
 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;
@@ -86,6 +94,10 @@ private:
     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);
@@ -108,7 +120,8 @@ private:
     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);
 };
 
 
index ae32de2..d25bee7 100644 (file)
@@ -130,6 +130,11 @@ void HostConnection::setSetActiveContainerCallback(const SetActiveContainerCallb
     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,
@@ -199,6 +204,15 @@ void HostConnection::onMessageCall(const std::string& objectPath,
         }
         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,
index bc9015d..c5d1bcc 100644 (file)
@@ -60,6 +60,9 @@ public:
     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
@@ -92,6 +95,11 @@ public:
     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,
@@ -112,6 +120,7 @@ private:
     GetContainerIdsCallback mGetContainerIdsCallback;
     GetActiveContainerIdCallback mGetActiveContainerIdCallback;
     SetActiveContainerCallback mSetActiveContainerCallback;
+    AddContainerCallback mAddContainerCallback;
 
     void onNameAcquired();
     void onNameLost();
index 12e8d4a..040b10d 100644 (file)
@@ -37,11 +37,13 @@ const std::string OBJECT_PATH                    = "/org/tizen/containers/host";
 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";
 
@@ -70,6 +72,9 @@ const std::string DEFINITION =
     "    <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'/>"
index 5c3420d..547d023 100644 (file)
 #error "LIBVIRT_GROUP must be defined!"
 #endif
 
+#ifndef DISK_GROUP
+#error "DISK_GROUP must be defined!"
+#endif
+
 extern char** environ;
 
 namespace security_containers {
@@ -165,6 +169,16 @@ bool Server::prepareEnvironment(const std::string& configPath, bool runAsRoot)
         }
     }
 
+    // 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) {
@@ -173,7 +187,9 @@ bool Server::prepareEnvironment(const std::string& configPath, bool runAsRoot)
 
     // 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;
     }
 
index 2a6ad35..707be02 100644 (file)
@@ -5,6 +5,9 @@
     "foregroundId" : "ut-containers-manager-console1-dbus",
     "defaultId" : "ut-containers-manager-console1-dbus",
     "containersPath" : "/tmp",
+    "containerImagePath" : "",
+    "containerTemplatePath" : "",
+    "containerNewConfigPrefix" : "",
     "runMountPointPrefix" : "",
     "inputConfig" : {"enabled" : false,
                      "device" : "/dev/doesnotexist",
index 816a5bc..ee1c7d9 100644 (file)
@@ -26,6 +26,8 @@ FILE(GLOB server_admin_CONF         ut-server/libvirt-config/*.xml)
 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)
@@ -61,7 +63,10 @@ CONFIGURE_FILE(ut-containers-manager/libvirt-config/console2-dbus.xml.in
                ${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)
@@ -84,6 +89,10 @@ INSTALL(FILES        ${manager_admin_CONF}
         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)
index 92abbe2..5ec98cc 100644 (file)
@@ -4,6 +4,9 @@
     "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,
index a19268c..ef5a597 100644 (file)
@@ -4,6 +4,9 @@
     "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,
index bcaba00..3faa0f7 100644 (file)
@@ -4,6 +4,9 @@
     "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,
diff --git a/tests/unit_tests/server/configs/ut-containers-manager/templates/template-network.xml b/tests/unit_tests/server/configs/ut-containers-manager/templates/template-network.xml
new file mode 100644 (file)
index 0000000..b357c0e
--- /dev/null
@@ -0,0 +1,4 @@
+<network>
+    <name>~NAME~-network</name>
+    <uuid>~UUID~</uuid>
+</network>
diff --git a/tests/unit_tests/server/configs/ut-containers-manager/templates/template-nwfilter.xml b/tests/unit_tests/server/configs/ut-containers-manager/templates/template-nwfilter.xml
new file mode 100644 (file)
index 0000000..b96197b
--- /dev/null
@@ -0,0 +1,3 @@
+<filter name='nwfilter-~NAME~' chain='root'>
+    <uuid>~UUID~</uuid>
+</filter>
diff --git a/tests/unit_tests/server/configs/ut-containers-manager/templates/template.conf b/tests/unit_tests/server/configs/ut-containers-manager/templates/template.conf
new file mode 100644 (file)
index 0000000..40dcacc
--- /dev/null
@@ -0,0 +1,13 @@
+{
+    "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/.*" ]
+}
diff --git a/tests/unit_tests/server/configs/ut-containers-manager/templates/template.xml.in b/tests/unit_tests/server/configs/ut-containers-manager/templates/template.xml.in
new file mode 100644 (file)
index 0000000..fbb12a5
--- /dev/null
@@ -0,0 +1,15 @@
+<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>
index 6147c03..9317b9d 100644 (file)
@@ -4,6 +4,9 @@
     "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,
index 2a6ad35..af183a1 100644 (file)
@@ -5,6 +5,9 @@
     "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",
index cad519c..4758406 100644 (file)
@@ -1,9 +1,12 @@
 {
     "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,
index 868f12b..767b3a5 100644 (file)
@@ -1,6 +1,9 @@
 {
     "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",
index 54a34bb..528d744 100644 (file)
@@ -40,6 +40,7 @@
 #include "config/exception.hpp"
 #include "utils/latch.hpp"
 #include "utils/fs.hpp"
+#include "utils/img.hpp"
 
 #include <vector>
 #include <map>
@@ -63,6 +64,8 @@ const std::string TEST_DBUS_CONFIG_PATH = SC_TEST_CONFIG_INSTALL_DIR "/server/ut
 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;
@@ -81,6 +84,7 @@ public:
     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;
 
@@ -303,6 +307,25 @@ public:
 
     }
 
+    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;
@@ -331,6 +354,27 @@ std::function<bool(const std::exception&)> expectedMessage(const std::string& me
     };
 }
 
+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;
 };
@@ -881,4 +925,34 @@ BOOST_AUTO_TEST_CASE(SetActiveContainerTest)
                         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()
index 8f7c965..43be58a 100644 (file)
@@ -44,6 +44,12 @@ const std::string FILE_PATH = SC_TEST_CONFIG_INSTALL_DIR "/utils/ut-fs/file.txt"
 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 =
@@ -52,6 +58,12 @@ const std::string MOUNT_POINT_RANDOM_1 =
     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 =
@@ -131,4 +143,43 @@ BOOST_AUTO_TEST_CASE(MoveFileTest)
     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()