Dbus support
authorPiotr Bartosiewicz <p.bartosiewi@partner.samsung.com>
Wed, 5 Mar 2014 09:54:53 +0000 (10:54 +0100)
committerJan Olszak <j.olszak@samsung.com>
Mon, 19 May 2014 11:47:15 +0000 (13:47 +0200)
[Bug/Feature]   N/A
[Cause]         N/A
[Solution]      N/A
[Verification]  Build, install, run tests

Change-Id: Ide6cc4ce714d4554fd165978f29a1af648df9457

22 files changed:
packaging/security-containers.spec
src/server/include/dbus-connection.hpp [new file with mode: 0644]
src/server/include/dbus-exception.hpp [new file with mode: 0644]
src/server/include/file-wait.hpp [new file with mode: 0644]
src/server/include/glib-loop.hpp [new file with mode: 0644]
src/server/include/latch.hpp [new file with mode: 0644]
src/server/src/CMakeLists.txt
src/server/src/dbus-connection.cpp [new file with mode: 0644]
src/server/src/file-wait.cpp [new file with mode: 0644]
src/server/src/glib-loop.cpp [new file with mode: 0644]
src/server/src/latch.cpp [new file with mode: 0644]
src/server/src/main.cpp
src/server/unit_tests/CMakeLists.txt
src/server/unit_tests/config/ut-dbus-connection/ut-dbus.conf [new file with mode: 0644]
src/server/unit_tests/dbus-client-test.cpp [new file with mode: 0644]
src/server/unit_tests/dbus-client-test.hpp [new file with mode: 0644]
src/server/unit_tests/dbus-server-test.cpp [new file with mode: 0644]
src/server/unit_tests/dbus-server-test.hpp [new file with mode: 0644]
src/server/unit_tests/dbus-test-common.hpp [new file with mode: 0644]
src/server/unit_tests/scoped-daemon.cpp [new file with mode: 0644]
src/server/unit_tests/scoped-daemon.hpp [new file with mode: 0644]
src/server/unit_tests/ut-dbus-connection.cpp [new file with mode: 0644]

index c9bcf7a..9932fb6 100644 (file)
@@ -12,6 +12,7 @@ BuildRequires: libvirt
 BuildRequires: libvirt-devel
 BuildRequires: libjson
 BuildRequires: libjson-devel
+BuildRequires: pkgconfig(glib-2.0)
 
 %description
 This package provides a daemon used to manage containers - start, stop and switch
@@ -103,3 +104,4 @@ Unit tests for both: server and client.
 %config %attr(644,root,root) /etc/security-containers/config/tests/ut-scs-container-manager/*.conf
 %config %attr(644,root,root) /etc/security-containers/config/tests/ut-scs-container-manager/containers/*.conf
 %config %attr(644,root,root) /etc/security-containers/config/tests/ut-scs-container-manager/libvirt-config/*.xml
+%config %attr(644,root,root) /etc/security-containers/config/tests/ut-dbus-connection/*.conf
diff --git a/src/server/include/dbus-connection.hpp b/src/server/include/dbus-connection.hpp
new file mode 100644 (file)
index 0000000..06c410b
--- /dev/null
@@ -0,0 +1,156 @@
+/*
+ *  Copyright (c) 2000 - 2014 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ *  Contact: Bumjin Im <bj.im@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    dbus-connection.hpp
+ * @author  Piotr Bartosiewicz (p.bartosiewi@partner.samsung.com)
+ * @brief   Dbus connection class
+ */
+
+#ifndef DBUS_CONNECTION_HPP
+#define DBUS_CONNECTION_HPP
+
+#include <memory>
+#include <string>
+#include <functional>
+#include <gio/gio.h>
+
+/**
+ * An interface used to set a result to a method call.
+ */
+class MethodResultBuilder {
+public:
+    virtual ~MethodResultBuilder() {}
+    virtual void set(GVariant* parameters) = 0;
+    virtual void setVoid() = 0;
+    virtual void setError(const std::string& name, const std::string& message) = 0;
+};
+
+/**
+ * Dbus connection.
+ * Provides a functionality that allows to call dbus methods,
+ * register dbus interfaces, etc.
+ *
+ * TODO divide to interface and implementation header
+ * TODO wrap GVariant type
+ */
+class DbusConnection {
+public:
+    typedef std::shared_ptr<DbusConnection> Pointer;
+
+    typedef std::function<void()> VoidCallback;
+
+    typedef std::function<void(const std::string& objectPath,
+                               const std::string& interface,
+                               const std::string& method,
+                               GVariant* parameters,
+                               MethodResultBuilder& result
+                              )> MethodCallCallback;
+
+    /**
+     * Creates a connection to the dbus with given address.
+     */
+    static Pointer create(const std::string& address);
+
+    /**
+     * Creates a connection to the system dbus.
+     */
+    static Pointer createSystem();
+
+    ~DbusConnection();
+
+    /**
+     * Sets a name to the dbus connection.
+     * It allows other client to call methods using this name.
+     */
+    void setName(const std::string& name,
+                 const VoidCallback& onNameAcquired,
+                 const VoidCallback& onNameLost);
+
+    /**
+     * Emits dbus signal.
+     */
+    void emitSignal(const std::string& objectPath,
+                    const std::string& interface,
+                    const std::string& name,
+                    GVariant* parameters);
+
+    /**
+     * Subscribes to a signal.
+     * TODO not finished
+     */
+    void signalSubscribe();
+
+    /**
+     * Registers an object with given definition.
+     * Api calls will be handled by given callback.
+     */
+    void registerObject(const std::string& objectPath,
+                        const std::string& objectDefinitionXml,
+                        const MethodCallCallback& callback);
+
+    /**
+     * Call a dbus method
+     */
+    GVariant* callMethod(const std::string& busName,
+                         const std::string& objectPath,
+                         const std::string& interface,
+                         const std::string& method,
+                         GVariant* parameters,
+                         const GVariantType* replyType);
+
+    /**
+     * Returns an xml with meta description of specified dbus object.
+     */
+    std::string introspect(const std::string& busName, const std::string& objectPath);
+
+private:
+    struct NameCallbacks {
+        VoidCallback nameAcquired;
+        VoidCallback nameLost;
+
+        NameCallbacks(const VoidCallback& acquired, const VoidCallback& lost)
+            : nameAcquired(acquired), nameLost(lost) {}
+    };
+
+    GDBusConnection* mConnection;
+    guint mNameId;
+
+    DbusConnection(const std::string& address);
+
+    static void onNameAcquired(GDBusConnection* connection, const gchar* name, gpointer userData);
+    static void onNameLost(GDBusConnection* connection, const gchar* name, gpointer userData);
+    static void onSignal(GDBusConnection* connection,
+                         const gchar* sender,
+                         const gchar* object,
+                         const gchar* interface,
+                         const gchar* name,
+                         GVariant* parameters,
+                         gpointer userData);
+    static void onMethodCall(GDBusConnection* connection,
+                             const gchar* sender,
+                             const gchar* objectPath,
+                             const gchar* interface,
+                             const gchar* method,
+                             GVariant* parameters,
+                             GDBusMethodInvocation* invocation,
+                             gpointer userData);
+};
+
+
+#endif //DBUS_CONNECTION_HPP
diff --git a/src/server/include/dbus-exception.hpp b/src/server/include/dbus-exception.hpp
new file mode 100644 (file)
index 0000000..77186de
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ *  Copyright (c) 2000 - 2014 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ *  Contact: Bumjin Im <bj.im@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    dbus-exception.hpp
+ * @author  Piotr Bartosiewicz (p.bartosiewi@partner.samsung.com)
+ * @brief   Dbus exceptions
+ */
+
+
+#ifndef DBUS_EXCEPTION_HPP
+#define DBUS_EXCEPTION_HPP
+
+#include <stdexcept>
+
+/**
+ * Base class for dbus exceptions
+ */
+struct DbusException: public std::runtime_error {
+    using std::runtime_error::runtime_error;
+};
+
+/**
+ * Dbus connection failed exception
+ */
+struct DbusConnectException: public DbusException {
+    using DbusException::DbusException;
+};
+
+/**
+ * Dbus operation failed exception
+ * TODO split to more specific exceptions
+ */
+struct DbusOperationException: public DbusException {
+    using DbusException::DbusException;
+};
+
+#endif // DBUS_EXCEPTION_HPP
diff --git a/src/server/include/file-wait.hpp b/src/server/include/file-wait.hpp
new file mode 100644 (file)
index 0000000..3e7723f
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ *  Copyright (c) 2000 - 2014 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ *  Contact: Bumjin Im <bj.im@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    file-wait.hpp
+ * @author  Piotr Bartosiewicz (p.bartosiewi@partner.samsung.com)
+ * @brief   Wait for file utility function
+ */
+
+#ifndef FILE_WAIT_HPP
+#define FILE_WAIT_HPP
+
+#include <string>
+
+//TODO It is used in unit tests now, but it is unclear
+//     whether the same solution will be used in daemon.
+void waitForFile(const std::string& filename, const unsigned int timeoutMs);
+
+#endif // FILE_WAIT_HPP
diff --git a/src/server/include/glib-loop.hpp b/src/server/include/glib-loop.hpp
new file mode 100644 (file)
index 0000000..b3db8bf
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ *  Copyright (c) 2000 - 2014 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ *  Contact: Bumjin Im <bj.im@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    glib-loop.hpp
+ * @author  Piotr Bartosiewicz (p.bartosiewi@partner.samsung.com)
+ * @brief   C++ wrapper of glib main loop
+ */
+
+#ifndef GLIB_LOOP_HPP
+#define GLIB_LOOP_HPP
+
+#include <thread>
+#include <memory>
+
+struct _GMainLoop;
+typedef struct _GMainLoop GMainLoop;
+
+/**
+ * Glib loop controller. Loop is running in separate thread.
+ */
+class ScopedGlibLoop {
+public:
+    /**
+     * Starts a loop in separate thread.
+     */
+    ScopedGlibLoop();
+
+    /**
+     * Stops loop and waits for a thread.
+     */
+    ~ScopedGlibLoop();
+
+private:
+    std::unique_ptr<GMainLoop, void(*)(GMainLoop*)> mLoop;
+    std::thread mLoopThread;
+};
+
+#endif //GLIB_LOOP_HPP
diff --git a/src/server/include/latch.hpp b/src/server/include/latch.hpp
new file mode 100644 (file)
index 0000000..efc0876
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ *  Copyright (c) 2000 - 2014 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ *  Contact: Bumjin Im <bj.im@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    latch.hpp
+ * @author  Piotr Bartosiewicz (p.bartosiewi@partner.samsung.com)
+ * @brief   Synchronization latch
+ */
+
+#ifndef LATCH_HPP
+#define LATCH_HPP
+
+#include <mutex>
+#include <condition_variable>
+
+/**
+ * A synchronization aid that allows one thread to wait until
+ * an operation being performed in other thread completes.
+ * It has a similar function as std::promise<void> but allows
+ * multiple calls to set.
+ */
+class Latch {
+public:
+    Latch();
+
+    /**
+     * Sets an event occurred.
+     */
+    void set();
+
+    /**
+     * Waits for a single occurrence of event.
+     */
+    void wait();
+
+    /**
+     * Waits with timeout.
+     * @return false on timeout
+     */
+    bool wait(const unsigned int timeoutMs);
+
+    /**
+     * Check if there are no pending events.
+     */
+    bool empty();
+private:
+    std::mutex mMutex;
+    std::condition_variable mCondition;
+    int mCount;
+};
+
+#endif // LATCH_HPP
index e513d7a..ae8f50b 100644 (file)
@@ -27,7 +27,7 @@ ADD_EXECUTABLE(${SERVER_CODENAME} ${project_SRCS} )
 ## Link libraries ##############################################################
 FIND_PACKAGE (Boost COMPONENTS program_options REQUIRED)
 
-PKG_CHECK_MODULES(SERVER_DEPS REQUIRED libvirt json)
+PKG_CHECK_MODULES(SERVER_DEPS REQUIRED libvirt json gio-2.0)
 INCLUDE_DIRECTORIES(SYSTEM ${SERVER_DEPS_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS})
 TARGET_LINK_LIBRARIES(${SERVER_CODENAME} ${SERVER_DEPS_LIBRARIES} ${Boost_LIBRARIES})
 
diff --git a/src/server/src/dbus-connection.cpp b/src/server/src/dbus-connection.cpp
new file mode 100644 (file)
index 0000000..73621b9
--- /dev/null
@@ -0,0 +1,320 @@
+/*
+ *  Copyright (c) 2000 - 2014 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ *  Contact: Bumjin Im <bj.im@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    dbus-connection.cpp
+ * @author  Piotr Bartosiewicz (p.bartosiewi@partner.samsung.com)
+ * @brief   Dbus connection class
+ */
+
+#include "dbus-connection.hpp"
+#include "dbus-exception.hpp"
+#include "scs-log.hpp"
+
+namespace {
+
+const std::string SYSTEM_BUS_ADDRESS = "unix:path=/var/run/dbus/system_bus_socket";
+const std::string INTROSPECT_INTERFACE = "org.freedesktop.DBus.Introspectable";
+const std::string INTROSPECT_METHOD = "Introspect";
+
+const int CALL_METHOD_TIMEOUT_MS = 1000;
+
+template<class Callback>
+void deleteCallback(gpointer data)
+{
+    delete reinterpret_cast<Callback*>(data);
+}
+
+class ScopedError {
+public:
+    ScopedError() : mError(NULL) {}
+    ~ScopedError()
+    {
+        if (mError) {
+            g_error_free(mError);
+        }
+    }
+    operator bool () const
+    {
+        return mError;
+    }
+    GError** operator& ()
+    {
+        return &mError;
+    }
+    const GError* operator->() const
+    {
+        return mError;
+    }
+    friend std::ostream& operator<<(std::ostream& os, const ScopedError& e)
+    {
+        os << e->message;
+        return os;
+    }
+private:
+    GError* mError;
+};
+
+class MethodResultBuilderImpl : public MethodResultBuilder {
+public:
+    MethodResultBuilderImpl(GDBusMethodInvocation* invocation)
+        : mInvocation(invocation), mResultSet(false) {}
+    void set(GVariant* parameters)
+    {
+        g_dbus_method_invocation_return_value(mInvocation, parameters);
+        mResultSet = true;
+    }
+    void setVoid()
+    {
+        set(NULL);
+    }
+    void setError(const std::string& name, const std::string& message)
+    {
+        g_dbus_method_invocation_return_dbus_error(mInvocation, name.c_str(), message.c_str());
+        mResultSet = true;
+    }
+    bool isUndefined() const
+    {
+        return !mResultSet;
+    }
+private:
+    GDBusMethodInvocation* mInvocation;
+    bool mResultSet;
+};
+
+} // namespace
+
+DbusConnection::Pointer DbusConnection::create(const std::string& address)
+{
+    return Pointer(new DbusConnection(address));
+}
+
+DbusConnection::Pointer DbusConnection::createSystem()
+{
+    return create(SYSTEM_BUS_ADDRESS);
+}
+
+DbusConnection::DbusConnection(const std::string& address)
+    : mConnection(NULL)
+    , mNameId(0)
+{
+    ScopedError error;
+    const GDBusConnectionFlags flags =
+        static_cast<GDBusConnectionFlags>(G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
+                                          G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION);
+    mConnection = g_dbus_connection_new_for_address_sync(address.c_str(),
+                                                         flags,
+                                                         NULL,
+                                                         NULL,
+                                                         &error);
+    if (error) {
+        LOGE("Could not create connection for address " << address << "; " << error);
+        throw DbusConnectException("Could not connect");
+    }
+}
+
+DbusConnection::~DbusConnection()
+{
+    if (mNameId) {
+        g_bus_unown_name(mNameId);
+    }
+    //TODO should we unregister, flush, close etc?
+    //if (!g_dbus_connection_close_sync(mConnection, NULL, NULL)) {
+    //    LOGE("Could not close connection");
+    //}
+    g_object_unref(mConnection);
+    LOGT("Connection deleted");
+}
+
+void DbusConnection::setName(const std::string& name,
+                             const VoidCallback& onNameAcquired,
+                             const VoidCallback& onNameLost)
+{
+    mNameId = g_bus_own_name_on_connection(mConnection,
+                                           name.c_str(),
+                                           G_BUS_NAME_OWNER_FLAGS_NONE,
+                                           &DbusConnection::onNameAcquired,
+                                           &DbusConnection::onNameLost,
+                                           new NameCallbacks(onNameAcquired, onNameLost),
+                                           &deleteCallback<NameCallbacks>);
+}
+
+void DbusConnection::onNameAcquired(GDBusConnection*, const gchar* name, gpointer userData)
+{
+    LOGD("Name acquired " << name);
+    const NameCallbacks& callbacks = *reinterpret_cast<const NameCallbacks*>(userData);
+    if (callbacks.nameAcquired) {
+        callbacks.nameAcquired();
+    }
+}
+
+void DbusConnection::onNameLost(GDBusConnection*, const gchar* name, gpointer userData)
+{
+    LOGE("Name lost " << name);
+    const NameCallbacks& callbacks = *reinterpret_cast<const NameCallbacks*>(userData);
+    if (callbacks.nameLost) {
+        callbacks.nameLost();
+    }
+}
+
+void DbusConnection::emitSignal(const std::string& objectPath,
+                                const std::string& interface,
+                                const std::string& name,
+                                GVariant* parameters)
+{
+    ScopedError error;
+    g_dbus_connection_emit_signal(mConnection,
+                                  NULL,
+                                  objectPath.c_str(),
+                                  interface.c_str(),
+                                  name.c_str(),
+                                  parameters,
+                                  &error);
+    if (error) {
+        LOGE("Emit signal failed; " << error);
+        throw DbusOperationException("could not emit signal");
+    }
+}
+
+void DbusConnection::signalSubscribe()
+{
+    g_dbus_connection_signal_subscribe(mConnection,
+                                       NULL,
+                                       NULL,
+                                       NULL,
+                                       NULL,
+                                       NULL,
+                                       G_DBUS_SIGNAL_FLAGS_NONE,
+                                       &DbusConnection::onSignal,
+                                       NULL,//data
+                                       NULL);
+}
+
+void DbusConnection::onSignal(GDBusConnection*,
+                              const gchar* sender,
+                              const gchar* object,
+                              const gchar* interface,
+                              const gchar* name,
+                              GVariant* /*parameters*/,
+                              gpointer /*userData*/)
+{
+    LOGD("Signal: " << sender << "; " << object << "; " << interface << "; " << name);
+    //TODO call some callback
+}
+
+std::string DbusConnection::introspect(const std::string& busName, const std::string& objectPath)
+{
+    GVariant* result = DbusConnection::callMethod(busName,
+                                                  objectPath,
+                                                  INTROSPECT_INTERFACE,
+                                                  INTROSPECT_METHOD,
+                                                  NULL,
+                                                  G_VARIANT_TYPE("(s)"));
+    const gchar* s;
+    g_variant_get(result, "(&s)", &s);
+    std::string xml = s;
+    g_variant_unref(result);
+    return xml;
+}
+
+void DbusConnection::registerObject(const std::string& objectPath,
+                                    const std::string& objectDefinitionXml,
+                                    const MethodCallCallback& callback)
+{
+    ScopedError error;
+    GDBusNodeInfo* nodeInfo = g_dbus_node_info_new_for_xml(objectDefinitionXml.c_str(), &error);
+    if (error) {
+        LOGE("Invalid xml");
+        throw std::logic_error("invalid xml"); //TODO invalid argument exception
+    }
+    if (nodeInfo->interfaces == NULL ||
+            nodeInfo->interfaces[0] == NULL ||
+            nodeInfo->interfaces[1] != NULL) {
+        LOGE("Wrong number of interfaces");
+        g_dbus_node_info_unref(nodeInfo);
+        throw std::logic_error("Wrong number of interfaces"); //TODO invalid argument exception
+    }
+    GDBusInterfaceInfo* interfaceInfo = nodeInfo->interfaces[0];
+
+    GDBusInterfaceVTable vtable;
+    vtable.method_call = &DbusConnection::onMethodCall;
+    vtable.get_property = NULL;
+    vtable.set_property = NULL;
+
+    g_dbus_connection_register_object(mConnection,
+                                      objectPath.c_str(),
+                                      interfaceInfo,
+                                      &vtable,
+                                      new MethodCallCallback(callback),
+                                      &deleteCallback<MethodCallCallback>,
+                                      &error);
+    g_dbus_node_info_unref(nodeInfo);
+    if (error) {
+        LOGE("Register object failed; " << error);
+        throw DbusOperationException("register object failed");
+    }
+}
+
+void DbusConnection::onMethodCall(GDBusConnection*,
+                                  const gchar*,
+                                  const gchar* objectPath,
+                                  const gchar* interface,
+                                  const gchar* method,
+                                  GVariant* parameters,
+                                  GDBusMethodInvocation* invocation,
+                                  gpointer userData)
+{
+    const MethodCallCallback& callback = *static_cast<const MethodCallCallback*>(userData);
+
+    LOGD("MethodCall; " << objectPath << "; " << interface << "; " << method);
+
+    MethodResultBuilderImpl resultBuilder(invocation);
+    if (callback) {
+        callback(objectPath, interface, method, parameters, resultBuilder);
+    }
+
+    if (resultBuilder.isUndefined()) {
+        resultBuilder.setError("org.freedesktop.DBus.Error.UnknownMethod", "Not implemented");
+    }
+}
+
+GVariant* DbusConnection::callMethod(const std::string& busName,
+                                     const std::string& objectPath,
+                                     const std::string& interface,
+                                     const std::string& method,
+                                     GVariant* parameters,
+                                     const GVariantType* replyType)
+{
+    ScopedError error;
+    GVariant* result = g_dbus_connection_call_sync(mConnection,
+                                                   busName.c_str(),
+                                                   objectPath.c_str(),
+                                                   interface.c_str(),
+                                                   method.c_str(),
+                                                   parameters,
+                                                   replyType,
+                                                   G_DBUS_CALL_FLAGS_NONE,
+                                                   CALL_METHOD_TIMEOUT_MS,
+                                                   NULL,
+                                                   &error);
+    if (error) {
+        LOGE("Call method failed; " << error);
+        throw DbusOperationException("call method failed");//TODO split to different exceptions
+    }
+    return result;
+}
diff --git a/src/server/src/file-wait.cpp b/src/server/src/file-wait.cpp
new file mode 100644 (file)
index 0000000..681dcb9
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ *  Copyright (c) 2000 - 2014 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ *  Contact: Bumjin Im <bj.im@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    file-wait.cpp
+ * @author  Piotr Bartosiewicz (p.bartosiewi@partner.samsung.com)
+ * @brief   Wait for file utility function
+ */
+
+#include "file-wait.hpp"
+#include "scs-log.hpp"
+#include <sys/stat.h>
+#include <unistd.h>
+#include <stdexcept>
+
+const unsigned int GRANULARITY = 10;
+
+void waitForFile(const std::string& filename, const unsigned int timeoutMs)
+{
+    //TODO this is a temporary solution, use inotify instead of sleep
+    struct stat s;
+    unsigned int loops = 0;
+    while (stat(filename.c_str(), &s) == -1) {
+        if (errno != ENOENT) {
+            throw std::runtime_error("file access error: " + filename);
+        }
+        ++ loops;
+        if (loops * GRANULARITY > timeoutMs) {
+            throw std::runtime_error("timeout");
+        }
+        usleep(GRANULARITY * 1000);
+    }
+}
diff --git a/src/server/src/glib-loop.cpp b/src/server/src/glib-loop.cpp
new file mode 100644 (file)
index 0000000..8fe9239
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ *  Copyright (c) 2000 - 2014 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ *  Contact: Bumjin Im <bj.im@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    glib-loop.cpp
+ * @author  Piotr Bartosiewicz (p.bartosiewi@partner.samsung.com)
+ * @brief   C++ wrapper of glib main loop
+ */
+
+#include "glib-loop.hpp"
+#include <glib.h>
+
+ScopedGlibLoop::ScopedGlibLoop()
+    : mLoop(g_main_loop_new(NULL, FALSE), g_main_loop_unref)
+{
+    mLoopThread = std::thread([this] {g_main_loop_run(mLoop.get());});
+}
+
+ScopedGlibLoop::~ScopedGlibLoop()
+{
+    // ensure loop is running (avoid race condition when stop is called to early)
+    while (!g_main_loop_is_running(mLoop.get())) {
+        std::this_thread::yield();
+    }
+    //stop loop and wait
+    g_main_loop_quit(mLoop.get());
+    mLoopThread.join();
+}
diff --git a/src/server/src/latch.cpp b/src/server/src/latch.cpp
new file mode 100644 (file)
index 0000000..862178c
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ *  Copyright (c) 2000 - 2014 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ *  Contact: Bumjin Im <bj.im@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    latch.cpp
+ * @author  Piotr Bartosiewicz (p.bartosiewi@partner.samsung.com)
+ * @brief   Synchronization latch
+ */
+
+#include "latch.hpp"
+
+Latch::Latch()
+    : mCount(0)
+{
+}
+
+void Latch::set()
+{
+    std::unique_lock<std::mutex> lock(mMutex);
+    ++mCount;
+    mCondition.notify_one();
+}
+
+void Latch::wait()
+{
+    std::unique_lock<std::mutex> lock(mMutex);
+    mCondition.wait(lock, [this] {return mCount > 0;});
+    --mCount;
+}
+
+bool Latch::wait(const unsigned int timeoutMs)
+{
+    std::unique_lock<std::mutex> lock(mMutex);
+    if (!mCondition.wait_for(lock, std::chrono::milliseconds(timeoutMs),
+                             [this] {return mCount > 0;})) {
+        return false;
+    }
+    --mCount;
+    return true;
+}
+
+bool Latch::empty()
+{
+    std::unique_lock<std::mutex> lock(mMutex);
+    return mCount == 0;
+}
index 699c1a6..f557e7a 100644 (file)
  * @brief   Main file for the Security Containers Daemon
  */
 
+#include "glib-loop.hpp"
+#include "latch.hpp"
+#include "scs-log.hpp"
 #include <boost/program_options.hpp>
 #include <iostream>
+#include <signal.h>
 
 namespace po = boost::program_options;
 
 namespace {
+
 const std::string PROGRAM_NAME_AND_VERSION =
     "Security Containers Server " PROGRAM_VERSION;
+
+Latch signalLatch;
+
+void signalHandler(int sig)
+{
+    LOGI("Got signal " << sig);
+    signalLatch.set();
+}
+
+void runDaemon()
+{
+    signal(SIGINT, signalHandler);
+    signal(SIGTERM, signalHandler);
+
+    LOGI("Starting daemon...");
+    {
+        ScopedGlibLoop loop;
+        //TODO bootstrap
+        LOGI("Daemon started");
+        signalLatch.wait();
+        LOGI("Stopping daemon...");
+    }
+    LOGI("Daemon stopped");
 }
 
+} // namespace
+
 int main(int argc, char* argv[])
 {
     po::options_description desc("Allowed options");
@@ -73,5 +103,7 @@ int main(int argc, char* argv[])
         return 0;
     }
 
+    runDaemon();
+
     return 0;
 }
index d0c380b..e8bc168 100644 (file)
@@ -22,7 +22,11 @@ FILE(GLOB_RECURSE project_SRCS *.cpp)
 FILE(GLOB src_SRCS ${SERVER_FOLDER}/src/scs-container-admin.cpp
                    ${SERVER_FOLDER}/src/scs-container.cpp
                    ${SERVER_FOLDER}/src/scs-container-manager.cpp
-                   ${SERVER_FOLDER}/src/scs-configuration.cpp)
+                   ${SERVER_FOLDER}/src/scs-configuration.cpp
+                   ${SERVER_FOLDER}/src/dbus-connection.cpp
+                   ${SERVER_FOLDER}/src/glib-loop.cpp
+                   ${SERVER_FOLDER}/src/file-wait.cpp
+                   ${SERVER_FOLDER}/src/latch.cpp)
 
 ## Setup target ################################################################
 SET(UT_SERVER_CODENAME "${PROJECT_NAME}-server-unit-tests")
@@ -31,7 +35,7 @@ ADD_EXECUTABLE(${UT_SERVER_CODENAME} ${project_SRCS} ${src_SRCS})
 ## Link libraries ##############################################################
 FIND_PACKAGE (Boost COMPONENTS unit_test_framework REQUIRED)
 
-PKG_CHECK_MODULES(UT_SERVER_DEPS REQUIRED libvirt json)
+PKG_CHECK_MODULES(UT_SERVER_DEPS REQUIRED libvirt json gio-2.0)
 
 INCLUDE_DIRECTORIES(SYSTEM ${UT_SERVER_DEPS_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS})
 TARGET_LINK_LIBRARIES(${UT_SERVER_CODENAME} ${UT_SERVER_DEPS_LIBRARIES} ${Boost_LIBRARIES})
@@ -42,6 +46,7 @@ INSTALL(TARGETS ${UT_SERVER_CODENAME} DESTINATION bin)
 FILE(GLOB manager_CONF         ${SERVER_FOLDER}/unit_tests/config/ut-scs-container-manager/*.conf)
 FILE(GLOB container_CONF       ${SERVER_FOLDER}/unit_tests/config/ut-scs-container-manager/containers/*.conf)
 FILE(GLOB containeradmin_CONF  ${SERVER_FOLDER}/unit_tests/config/ut-scs-container-manager/libvirt-config/*.xml)
+FILE(GLOB dbus_CONF            ${SERVER_FOLDER}/unit_tests/config/ut-dbus-connection/*.conf)
 
 INSTALL(FILES        ${manager_CONF}
         DESTINATION  ${SC_CONFIG_INSTALL_DIR}/tests/ut-scs-container-manager)
@@ -49,3 +54,5 @@ INSTALL(FILES        ${container_CONF}
         DESTINATION  ${SC_CONFIG_INSTALL_DIR}/tests/ut-scs-container-manager/containers)
 INSTALL(FILES        ${containeradmin_CONF}
         DESTINATION  ${SC_CONFIG_INSTALL_DIR}/tests/ut-scs-container-manager/libvirt-config)
+INSTALL(FILES        ${dbus_CONF}
+        DESTINATION  ${SC_CONFIG_INSTALL_DIR}/tests/ut-dbus-connection)
diff --git a/src/server/unit_tests/config/ut-dbus-connection/ut-dbus.conf b/src/server/unit_tests/config/ut-dbus-connection/ut-dbus.conf
new file mode 100644 (file)
index 0000000..7b56fa4
--- /dev/null
@@ -0,0 +1,18 @@
+<!-- This configuration file controls the containers message bus -->
+
+<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+
+<busconfig>
+    <type>custom</type>
+    <listen>unix:path=/tmp/container_socket</listen>
+
+    <policy context="default">
+        <!-- Allow everything to be sent -->
+        <allow send_destination="*" eavesdrop="true"/>
+        <!-- Allow everything to be received -->
+        <allow eavesdrop="true"/>
+        <!-- Allow anyone to own anything -->
+        <allow own="*"/>
+    </policy>
+</busconfig>
diff --git a/src/server/unit_tests/dbus-client-test.cpp b/src/server/unit_tests/dbus-client-test.cpp
new file mode 100644 (file)
index 0000000..a1a8148
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ *  Copyright (c) 2000 - 2014 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ *  Contact: Bumjin Im <bj.im@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    dbus-client-test.cpp
+ * @author  Piotr Bartosiewicz (p.bartosiewi@partner.samsung.com)
+ * @brief   Example dbus api client
+ */
+
+#include "dbus-client-test.hpp"
+#include "dbus-connection.hpp"
+#include "dbus-test-common.hpp"
+
+
+DbusClientTest::DbusClientTest()
+{
+    mConnection = DbusConnection::create(DBUS_ADDRESS);
+}
+
+void DbusClientTest::noop()
+{
+    GVariant* result = mConnection->callMethod(TESTAPI_BUS_NAME,
+                                               TESTAPI_OBJECT_PATH,
+                                               TESTAPI_INTERFACE,
+                                               TESTAPI_METHOD_NOOP,
+                                               NULL,
+                                               NULL);
+    g_variant_unref(result);
+}
+
+std::string DbusClientTest::process(const std::string& arg)
+{
+    GVariant* parameters = g_variant_new("(s)", arg.c_str());
+    GVariant* result = mConnection->callMethod(TESTAPI_BUS_NAME,
+                                               TESTAPI_OBJECT_PATH,
+                                               TESTAPI_INTERFACE,
+                                               TESTAPI_METHOD_PROCESS,
+                                               parameters,
+                                               G_VARIANT_TYPE("(s)"));
+    const gchar* cresult;
+    g_variant_get(result, "(&s)", &cresult);
+    std::string ret = cresult;
+    g_variant_unref(result);
+    return ret;
+}
+
+void DbusClientTest::throwException(int arg)
+{
+    GVariant* parameters = g_variant_new("(i)", arg);
+    GVariant* result = mConnection->callMethod(TESTAPI_BUS_NAME,
+                                               TESTAPI_OBJECT_PATH,
+                                               TESTAPI_INTERFACE,
+                                               TESTAPI_METHOD_THROW,
+                                               parameters,
+                                               NULL);
+    g_variant_unref(result);
+}
diff --git a/src/server/unit_tests/dbus-client-test.hpp b/src/server/unit_tests/dbus-client-test.hpp
new file mode 100644 (file)
index 0000000..8cf8b4e
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ *  Copyright (c) 2000 - 2014 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ *  Contact: Bumjin Im <bj.im@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    dbus-client-test.hpp
+ * @author  Piotr Bartosiewicz (p.bartosiewi@partner.samsung.com)
+ * @brief   Example dbus api client
+ */
+
+#ifndef DBUS_CLIENT_TEST_HPP
+#define DBUS_CLIENT_TEST_HPP
+
+#include <string>
+#include <memory>
+
+class DbusConnection;
+typedef std::shared_ptr<DbusConnection> DbusConnectionPtr;//TODO include dbus-connection-iface.h
+
+/**
+ * Simple dbus client for test purposes.
+ * Class used to test all possible kinds of dbus calls.
+ */
+class DbusClientTest {
+public:
+    DbusClientTest();
+
+    // interface methods
+    void noop();
+    std::string process(const std::string& arg);
+    void throwException(int arg);
+
+private:
+    DbusConnectionPtr mConnection;
+};
+
+#endif //DBUS_CLIENT_TEST_HPP
diff --git a/src/server/unit_tests/dbus-server-test.cpp b/src/server/unit_tests/dbus-server-test.cpp
new file mode 100644 (file)
index 0000000..22a19f4
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ *  Copyright (c) 2000 - 2014 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ *  Contact: Bumjin Im <bj.im@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    dbus-server-test.cpp
+ * @author  Piotr Bartosiewicz (p.bartosiewi@partner.samsung.com)
+ * @brief   Example dbus api server
+ */
+
+#include "dbus-server-test.hpp"
+#include "dbus-connection.hpp"
+#include "dbus-exception.hpp"
+#include "dbus-test-common.hpp"
+#include "scs-log.hpp"
+
+
+DbusServerTest::DbusServerTest()
+    : mNameAcquired(false)
+    , mPendingDisconnect(false)
+{
+    mConnection = DbusConnection::create(DBUS_ADDRESS);
+    mConnection->setName(TESTAPI_BUS_NAME,
+                         std::bind(&DbusServerTest::onNameAcquired, this),
+                         std::bind(&DbusServerTest::onDisconnect, this));
+    if (!waitForName()) {
+        mConnection.reset();
+        throw DbusConnectException("Could not acquire name");
+    }
+    using namespace std::placeholders;
+    mConnection->registerObject(TESTAPI_OBJECT_PATH, TESTAPI_DEFINITION,
+                                std::bind(&DbusServerTest::onMessageCall, this, _1, _2, _3, _4, _5));
+}
+
+bool DbusServerTest::waitForName()
+{
+    std::unique_lock<std::mutex> lock(mMutex);
+    mNameCondition.wait(lock, [this] {return mNameAcquired || mPendingDisconnect;});
+    return mNameAcquired;
+}
+
+void DbusServerTest::setDisconnectCallback(const DisconnectCallback& callback)
+{
+    std::unique_lock<std::mutex> lock(mMutex);
+    mDisconnectCallback = callback;
+    if (mPendingDisconnect) {
+        mPendingDisconnect = false;
+        mDisconnectCallback();
+    }
+
+}
+
+void DbusServerTest::onNameAcquired()
+{
+    std::unique_lock<std::mutex> lock(mMutex);
+    mNameAcquired = true;
+    mNameCondition.notify_one();
+}
+
+void DbusServerTest::onDisconnect()
+{
+    std::unique_lock<std::mutex> lock(mMutex);
+    if (mDisconnectCallback) {
+        mDisconnectCallback();
+    } else {
+        mPendingDisconnect = true;
+        mNameCondition.notify_one();
+    }
+}
+
+void DbusServerTest::noop()
+{
+}
+
+std::string DbusServerTest::process(const std::string& arg)
+{
+    return "Processed: " + arg;
+}
+
+void DbusServerTest::throwException(int arg)
+{
+    if (arg != 0) {
+        throw std::runtime_error("Argument: " + std::to_string(arg));
+    }
+}
+
+void DbusServerTest::onMessageCall(
+    const std::string& objectPath,
+    const std::string& interface,
+    const std::string& method,
+    GVariant* parameters,
+    MethodResultBuilder& result)
+{
+    try {
+        if (objectPath != TESTAPI_OBJECT_PATH || interface != TESTAPI_INTERFACE) {
+            throw std::logic_error("unsupported interface");
+        }
+
+        if (method == TESTAPI_METHOD_NOOP) {
+            noop();
+            result.setVoid();
+        } else if (method == TESTAPI_METHOD_PROCESS) {
+            const gchar* arg;
+            g_variant_get(parameters, "(&s)", &arg);
+            std::string ret = process(arg);
+            GVariant* variant = g_variant_new("(s)", ret.c_str());
+            result.set(variant);
+        } else if (method == TESTAPI_METHOD_THROW) {
+            int arg;
+            g_variant_get(parameters, "(i)", &arg);
+            throwException(arg);
+            result.setVoid();
+        } else {
+            LOGE("unknown method; should never happen");
+        }
+    } catch (const std::exception& e) {
+        result.setError("com.samsung.Exception", e.what());
+    }
+}
diff --git a/src/server/unit_tests/dbus-server-test.hpp b/src/server/unit_tests/dbus-server-test.hpp
new file mode 100644 (file)
index 0000000..cb406f8
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ *  Copyright (c) 2000 - 2014 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ *  Contact: Bumjin Im <bj.im@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    dbus-server-test.hpp
+ * @author  Piotr Bartosiewicz (p.bartosiewi@partner.samsung.com)
+ * @brief   Example dbus api server
+ */
+
+#ifndef DBUS_SERVER_TEST_HPP
+#define DBUS_SERVER_TEST_HPP
+
+#include <string>
+#include <memory>
+#include <mutex>
+#include <condition_variable>
+#include <gio/gio.h>//TODO how to hide this?
+
+class DbusConnection;
+typedef std::shared_ptr<DbusConnection> DbusConnectionPtr;//TODO include dbus-connection-iface.h
+class MethodResultBuilder;
+
+/**
+ * Simple dbus server for test purposes.
+ * Class used to test all possible kinds of callbacks.
+ */
+class DbusServerTest {
+public:
+    DbusServerTest();
+
+    typedef std::function<void()> DisconnectCallback;
+    void setDisconnectCallback(const DisconnectCallback& callback);
+
+private:
+    //{ interface methods
+    void noop();
+    std::string process(const std::string& arg);
+    void throwException(int arg);
+    //}
+
+    DbusConnectionPtr mConnection;
+    DisconnectCallback mDisconnectCallback;
+    bool mNameAcquired;
+    bool mPendingDisconnect;
+    std::mutex mMutex;
+    std::condition_variable mNameCondition;
+
+
+    bool waitForName();
+
+    void onNameAcquired();
+    void onDisconnect();
+
+    void onMessageCall(
+        const std::string& objectPath,
+        const std::string& interface,
+        const std::string& method,
+        GVariant* parameters,
+        MethodResultBuilder& result);
+};
+
+#endif //DBUS_SERVER_TEST_HPP
diff --git a/src/server/unit_tests/dbus-test-common.hpp b/src/server/unit_tests/dbus-test-common.hpp
new file mode 100644 (file)
index 0000000..47d47d5
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ *  Copyright (c) 2000 - 2014 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ *  Contact: Bumjin Im <bj.im@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    dbus-test-common.hpp
+ * @author  Piotr Bartosiewicz (p.bartosiewi@partner.samsung.com)
+ * @brief   Common definitions for dbus tests
+ */
+
+#ifndef DBUS_TEST_COMMON_HPP
+#define DBUS_TEST_COMMON_HPP
+
+#include <string>
+
+const std::string DBUS_SOCKET_FILE       = "/tmp/container_socket";
+const std::string DBUS_ADDRESS           = "unix:path=" + DBUS_SOCKET_FILE;
+
+const std::string TESTAPI_BUS_NAME       = "com.samsung.tests";
+const std::string TESTAPI_OBJECT_PATH    = "/com/samsung/tests";
+const std::string TESTAPI_INTERFACE      = "tests.api";
+const std::string TESTAPI_METHOD_NOOP    = "Noop";
+const std::string TESTAPI_METHOD_PROCESS = "Process";
+const std::string TESTAPI_METHOD_THROW   = "Throw";
+
+const std::string TESTAPI_DEFINITION =
+    "<node>"
+    "  <interface name='" + TESTAPI_INTERFACE + "'>"
+    "    <method name='" + TESTAPI_METHOD_NOOP + "'/>"
+    "    <method name='" + TESTAPI_METHOD_PROCESS + "'>"
+    "      <arg type='s' name='argument' direction='in'/>"
+    "      <arg type='s' name='response' direction='out'/>"
+    "    </method>"
+    "    <method name='" + TESTAPI_METHOD_THROW + "'>"
+    "      <arg type='i' name='argument' direction='in'/>"
+    "    </method>"
+    "  </interface>"
+    "</node>";
+
+#endif //DBUS_TEST_COMMON_HPP
diff --git a/src/server/unit_tests/scoped-daemon.cpp b/src/server/unit_tests/scoped-daemon.cpp
new file mode 100644 (file)
index 0000000..575c316
--- /dev/null
@@ -0,0 +1,154 @@
+/*
+ *  Copyright (c) 2000 - 2014 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ *  Contact: Bumjin Im <bj.im@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    scoped-daemon.cpp
+ * @author  Piotr Bartosiewicz (p.bartosiewi@partner.samsung.com)
+ * @brief   Starts external daemon in constructor, stops it in destructor
+ */
+
+#include "scoped-daemon.hpp"
+#include "scs-log.hpp"
+#include <unistd.h>
+#include <sys/wait.h>
+#include <sys/prctl.h>
+#include <stdexcept>
+
+/*
+ * Scoped Daemon - sequence diagram.
+ *
+ *
+ *                |(main process)
+ *                |
+ *   constructor  |
+ *   ------------>|_______
+ *                |       |(launcher process)
+ *                |       |_______
+ *                |       |       |(daemon process)
+ *                |       |       |
+ *                |       |       |
+ *   destructor   |       |       |
+ *   ------------>|  sig  |       |
+ *                |------>|  sig  |
+ *                |       |------>|
+ *                |       |_______|
+ *                |_______|
+ *   destructor   |
+ *      ends      |
+ *
+ *
+ * Launcher helper process is used to monitor main process.
+ * When e.g. it crashes or hits an assert then launcher kills daemon and itself.
+ */
+
+namespace {
+
+volatile pid_t daemonPid = -1;// available in launcher process only;
+
+void startDaemon(const char* path, const char* const argv[])
+{
+    execv(path, const_cast<char* const*>(argv));
+    perror("exec failed");
+}
+
+void waitForDaemon()
+{
+    if (waitpid(daemonPid, NULL, 0) == -1) {
+        perror("wait for daemon failed");
+    }
+}
+
+void launcherSignalHandler(int sig)
+{
+    // forward to daemon
+    if (kill(daemonPid, sig) == -1) {
+        perror("kill daemon failed");
+    }
+}
+
+void registerLauncherSignalHandler()
+{
+    signal(SIGTERM, launcherSignalHandler);
+}
+
+void registerParentDiedNotification()
+{
+    prctl(PR_SET_PDEATHSIG, SIGTERM);
+}
+
+void cleanupProcess()
+{
+    signal(SIGCHLD, SIG_DFL);
+    signal(SIGINT, SIG_DFL);
+    signal(SIGTERM, SIG_DFL);
+    signal(SIGHUP, SIG_DFL);
+}
+
+void startByLauncher(const char* path, const char* const argv[])
+{
+    cleanupProcess();
+    daemonPid = fork();
+    if (daemonPid == -1) {
+        perror("fork failed");
+        return;
+    }
+    if (daemonPid == 0) {
+        startDaemon(path, argv);
+        _exit(1);
+    }
+    registerLauncherSignalHandler();
+    registerParentDiedNotification();
+    waitForDaemon();
+}
+
+} // namespace
+
+ScopedDaemon::ScopedDaemon(const char* path, const char* const argv[], const bool useLauncher)
+{
+    mPid = fork();
+    if (mPid == -1) {
+        throw std::runtime_error("fork failed");
+    }
+    if (mPid == 0) {
+        if (useLauncher) {
+            startByLauncher(path, argv);
+        } else {
+            startDaemon(path, argv);
+        }
+        _exit(0);
+    }
+}
+
+ScopedDaemon::~ScopedDaemon()
+{
+    stop();
+}
+
+void ScopedDaemon::stop()
+{
+    if (mPid == -1) {
+        return;
+    }
+    if (kill(mPid, SIGTERM) == -1) {
+        LOGE("kill failed");
+    }
+    if (waitpid(mPid, NULL, 0) == -1) {
+        LOGE("waitpid failed");
+    }
+    mPid = -1;
+}
diff --git a/src/server/unit_tests/scoped-daemon.hpp b/src/server/unit_tests/scoped-daemon.hpp
new file mode 100644 (file)
index 0000000..22167fc
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ *  Copyright (c) 2000 - 2014 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ *  Contact: Bumjin Im <bj.im@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    scoped-daemon.hpp
+ * @author  Piotr Bartosiewicz (p.bartosiewi@partner.samsung.com)
+ * @brief   Starts external daemon in constructor, stops it in destructor
+ */
+
+#ifndef SCOPED_DAEMON_HPP
+#define SCOPED_DAEMON_HPP
+
+#include <sys/types.h>
+
+/**
+ * External daemon launcher helper.
+ */
+class ScopedDaemon {
+public:
+    /**
+     * Starts a daemon.
+     * @param path daemon path
+     * @param argv arguments passed to the daemon
+     * @param useLauncher use additional launcher process
+     */
+    ScopedDaemon(const char* path, const char* const argv[], const bool useLauncher = true);
+
+    /**
+     * Stops a daemon if it is not stopped already.
+     */
+    ~ScopedDaemon();
+
+    /**
+     * Stops a daemon by sending SIGTERM and waits for a process.
+     */
+    void stop();
+private:
+    pid_t mPid;
+};
+
+#endif //SCOPED_DAEMON_HPP
diff --git a/src/server/unit_tests/ut-dbus-connection.cpp b/src/server/unit_tests/ut-dbus-connection.cpp
new file mode 100644 (file)
index 0000000..31cca22
--- /dev/null
@@ -0,0 +1,309 @@
+/*
+ *  Copyright (c) 2000 - 2014 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ *  Contact: Bumjin Im <bj.im@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    ut-dbus-connection.cpp
+ * @author  Piotr Bartosiewicz (p.bartosiewi@partner.samsung.com)
+ * @brief   Dbus connection unit tests
+ */
+
+#include "ut.hpp"
+#include "dbus-connection.hpp"
+#include "dbus-exception.hpp"
+#include "glib-loop.hpp"
+#include "scoped-daemon.hpp"
+#include "file-wait.hpp"
+#include "latch.hpp"
+#include "dbus-server-test.hpp"
+#include "dbus-client-test.hpp"
+#include "dbus-test-common.hpp"
+#include "scs-log.hpp"
+#include <thread>
+#include <mutex>
+#include <condition_variable>
+
+
+BOOST_AUTO_TEST_SUITE(DbusSuite)
+
+namespace {
+
+const char* DBUS_DAEMON_PROC = "/bin/dbus-daemon";
+const char* const DBUS_DAEMON_ARGS[] = {
+    DBUS_DAEMON_PROC,
+    "--config-file=/etc/security-containers/config/tests/ut-dbus-connection/ut-dbus.conf",
+    "--nofork",
+    NULL
+};
+const int DBUS_DAEMON_TIMEOUT = 1000;
+const int EVENT_TIMEOUT = 1000;
+
+class ScopedDbusDaemon : public ScopedDaemon {
+public:
+    ScopedDbusDaemon() : ScopedDaemon(DBUS_DAEMON_PROC, DBUS_DAEMON_ARGS)
+    {
+        waitForFile(DBUS_SOCKET_FILE, DBUS_DAEMON_TIMEOUT);
+    }
+};
+
+std::string getInterfaceFromIntrospectionXML(const std::string& xml, const std::string& name)
+{
+    std::string ret;
+    GDBusNodeInfo* nodeInfo = g_dbus_node_info_new_for_xml(xml.c_str(), NULL);
+    GDBusInterfaceInfo* iface = g_dbus_node_info_lookup_interface(nodeInfo, name.c_str());
+    if (iface) {
+        GString* gret = g_string_new("");
+        g_dbus_interface_info_generate_xml(iface, 0, gret);
+        ret.assign(gret->str, gret->len);
+        g_string_free(gret, TRUE);
+    }
+    g_dbus_node_info_unref(nodeInfo);
+    return ret;
+}
+
+} // namespace
+
+BOOST_AUTO_TEST_CASE(GlibLoopTest)
+{
+    ScopedGlibLoop loop;
+}
+
+BOOST_AUTO_TEST_CASE(DbusDaemonTest)
+{
+    ScopedDbusDaemon daemon;
+}
+
+BOOST_AUTO_TEST_CASE(NoDbusTest)
+{
+    ScopedGlibLoop loop;
+    BOOST_CHECK_THROW(DbusConnection::create(DBUS_ADDRESS), DbusConnectException);
+}
+
+BOOST_AUTO_TEST_CASE(SimpleTest)
+{
+    ScopedDbusDaemon daemon;
+    ScopedGlibLoop loop;
+    Latch nameAcquired, nameLost;
+    DbusConnection::Pointer conn1 = DbusConnection::create(DBUS_ADDRESS);
+    DbusConnection::Pointer conn2 = DbusConnection::create(DBUS_ADDRESS);
+    conn1->setName(TESTAPI_BUS_NAME,
+                   [&] {nameAcquired.set();},
+                   [&] {nameLost.set();});
+    DbusConnection::Pointer connSystem = DbusConnection::createSystem();
+    BOOST_CHECK(nameAcquired.wait(EVENT_TIMEOUT));
+    BOOST_CHECK(nameLost.empty());
+}
+
+BOOST_AUTO_TEST_CASE(ConnectionLostTest)
+{
+    ScopedDbusDaemon daemon;
+    ScopedGlibLoop loop;
+    Latch nameAcquired, nameLost;
+    DbusConnection::Pointer conn1 = DbusConnection::create(DBUS_ADDRESS);
+    conn1->setName(TESTAPI_BUS_NAME,
+                   [&] {nameAcquired.set();},
+                   [&] {nameLost.set();});
+    BOOST_CHECK(nameAcquired.wait(EVENT_TIMEOUT));
+    BOOST_CHECK(nameLost.empty());
+
+    // close dbus socket
+    daemon.stop();
+    BOOST_CHECK(nameLost.wait(EVENT_TIMEOUT));
+}
+
+BOOST_AUTO_TEST_CASE(NameOwnerTest)
+{
+    ScopedDbusDaemon daemon;
+    ScopedGlibLoop loop;
+
+    DbusConnection::Pointer conn1 = DbusConnection::create(DBUS_ADDRESS);
+    DbusConnection::Pointer conn2 = DbusConnection::create(DBUS_ADDRESS);
+
+    // acquire name by conn1
+    Latch nameAcquired1, nameLost1;
+    conn1->setName(TESTAPI_BUS_NAME,
+                   [&] {nameAcquired1.set();},
+                   [&] {nameLost1.set();});
+    BOOST_CHECK(nameAcquired1.wait(EVENT_TIMEOUT));
+    BOOST_CHECK(nameLost1.empty());
+
+    // conn2 can't acquire name
+    Latch nameAcquired2, nameLost2;
+    conn2->setName(TESTAPI_BUS_NAME,
+                   [&] {nameAcquired2.set();},
+                   [&] {nameLost2.set();});
+    BOOST_CHECK(nameLost2.wait(EVENT_TIMEOUT));
+    BOOST_CHECK(nameAcquired2.empty());
+
+    // close conn1
+    conn1.reset();
+    // depending on dbus implementation conn2 can automatically acquire the name
+    //BOOST_CHECK(nameAcquired2.wait(EVENT_TIMEOUT));
+}
+
+BOOST_AUTO_TEST_CASE(SignalTest)
+{
+    ScopedDbusDaemon daemon;
+    ScopedGlibLoop loop;
+    DbusConnection::Pointer conn1 = DbusConnection::create(DBUS_ADDRESS);
+    DbusConnection::Pointer conn2 = DbusConnection::create(DBUS_ADDRESS);
+    conn2->signalSubscribe();
+    conn1->emitSignal("/a/b/c", "a.b.c", "Foo", NULL);
+    sleep(1);
+}
+
+BOOST_AUTO_TEST_CASE(IntrospectSystemTest)
+{
+    ScopedDbusDaemon daemon;
+    ScopedGlibLoop loop;
+    DbusConnection::Pointer conn = DbusConnection::createSystem();
+    std::string xml = conn->introspect("org.freedesktop.DBus", "/org/freedesktop/DBus");
+    std::string iface = getInterfaceFromIntrospectionXML(xml, "org.freedesktop.DBus");
+    BOOST_CHECK(!iface.empty());
+}
+
+BOOST_AUTO_TEST_CASE(IntrospectTest)
+{
+    ScopedDbusDaemon daemon;
+    ScopedGlibLoop loop;
+    DbusConnection::Pointer conn1 = DbusConnection::create(DBUS_ADDRESS);
+    DbusConnection::Pointer conn2 = DbusConnection::create(DBUS_ADDRESS);
+
+    Latch nameAcquired;
+    conn1->setName(TESTAPI_BUS_NAME,
+                   [&] {nameAcquired.set();},
+                   [] {});
+    BOOST_REQUIRE(nameAcquired.wait(EVENT_TIMEOUT));
+    conn1->registerObject(TESTAPI_OBJECT_PATH, TESTAPI_DEFINITION,
+                          DbusConnection::MethodCallCallback());
+    std::string xml = conn2->introspect(TESTAPI_BUS_NAME, TESTAPI_OBJECT_PATH);
+    std::string iface = getInterfaceFromIntrospectionXML(xml, TESTAPI_INTERFACE);
+    BOOST_REQUIRE(!iface.empty());
+    BOOST_CHECK(std::string::npos != iface.find(TESTAPI_INTERFACE));
+    BOOST_CHECK(std::string::npos != iface.find(TESTAPI_METHOD_NOOP));
+    BOOST_CHECK(std::string::npos != iface.find(TESTAPI_METHOD_PROCESS));
+    BOOST_CHECK(std::string::npos != iface.find(TESTAPI_METHOD_THROW));
+}
+
+BOOST_AUTO_TEST_CASE(MethodCallTest)
+{
+    ScopedDbusDaemon daemon;
+    ScopedGlibLoop loop;
+    DbusConnection::Pointer conn1 = DbusConnection::create(DBUS_ADDRESS);
+    DbusConnection::Pointer conn2 = DbusConnection::create(DBUS_ADDRESS);
+
+    Latch nameAcquired;
+    conn1->setName(TESTAPI_BUS_NAME,
+                   [&] {nameAcquired.set();},
+                   [] {});
+    BOOST_REQUIRE(nameAcquired.wait(EVENT_TIMEOUT));
+    auto handler = [] (const std::string&, const std::string&, const std::string& method,
+                       GVariant* /*parameters*/, MethodResultBuilder& result) {
+        if (method == TESTAPI_METHOD_NOOP) {
+            result.setVoid();
+        }
+    };
+    conn1->registerObject(TESTAPI_OBJECT_PATH, TESTAPI_DEFINITION, handler);
+    BOOST_CHECK(conn2->callMethod(TESTAPI_BUS_NAME, TESTAPI_OBJECT_PATH,
+                                  TESTAPI_INTERFACE, TESTAPI_METHOD_NOOP, NULL, NULL));
+}
+
+BOOST_AUTO_TEST_CASE(MethodCallExceptionTest)
+{
+    ScopedDbusDaemon daemon;
+    ScopedGlibLoop loop;
+    DbusConnection::Pointer conn1 = DbusConnection::create(DBUS_ADDRESS);
+    DbusConnection::Pointer conn2 = DbusConnection::create(DBUS_ADDRESS);
+
+    Latch nameAcquired;
+    conn1->setName(TESTAPI_BUS_NAME,
+                   [&] {nameAcquired.set();},
+                   [] {});
+    BOOST_REQUIRE(nameAcquired.wait(EVENT_TIMEOUT));
+    conn1->registerObject(TESTAPI_OBJECT_PATH, TESTAPI_DEFINITION,
+                          DbusConnection::MethodCallCallback());
+    BOOST_CHECK_THROW(conn2->callMethod(TESTAPI_BUS_NAME, TESTAPI_OBJECT_PATH,
+                                        TESTAPI_INTERFACE, TESTAPI_METHOD_NOOP, NULL, NULL),
+                      DbusOperationException);
+    BOOST_CHECK_THROW(conn2->callMethod(TESTAPI_BUS_NAME, TESTAPI_OBJECT_PATH,
+                                        TESTAPI_INTERFACE, "Foo", NULL, NULL),
+                      DbusOperationException);
+    BOOST_CHECK_THROW(conn2->callMethod(TESTAPI_BUS_NAME, TESTAPI_OBJECT_PATH,
+                                        TESTAPI_INTERFACE + ".foo", TESTAPI_METHOD_NOOP, NULL, NULL),
+                      DbusOperationException);
+    BOOST_CHECK_THROW(conn2->callMethod(TESTAPI_BUS_NAME, TESTAPI_OBJECT_PATH + "/foo",
+                                        TESTAPI_INTERFACE, TESTAPI_METHOD_NOOP, NULL, NULL),
+                      DbusOperationException);
+}
+
+BOOST_AUTO_TEST_CASE(DbusApiTest)
+{
+    ScopedDbusDaemon daemon;
+    ScopedGlibLoop loop;
+    DbusServerTest server;
+    DbusClientTest client;
+
+    BOOST_CHECK_NO_THROW(client.noop());
+    BOOST_CHECK_EQUAL("Processed: arg", client.process("arg"));
+    BOOST_CHECK_NO_THROW(client.throwException(0));
+    BOOST_CHECK_THROW(client.throwException(666), std::exception);
+}
+
+BOOST_AUTO_TEST_CASE(DbusApiNameAcquiredTest)
+{
+    ScopedDbusDaemon daemon;
+    ScopedGlibLoop loop;
+    DbusServerTest server;
+    DbusClientTest client;
+
+    BOOST_CHECK_THROW(DbusServerTest(), DbusConnectException);
+    BOOST_CHECK_NO_THROW(client.noop());
+}
+
+BOOST_AUTO_TEST_CASE(DbusApiConnectionLost1Test)
+{
+    ScopedDbusDaemon daemon;
+    ScopedGlibLoop loop;
+    Latch disconnected;
+
+    DbusServerTest server;
+    server.setDisconnectCallback([&] {disconnected.set();});
+    DbusClientTest client;
+
+    BOOST_CHECK_NO_THROW(client.noop());
+    daemon.stop();
+    BOOST_CHECK(disconnected.wait(EVENT_TIMEOUT));
+    BOOST_CHECK_THROW(client.noop(), DbusOperationException);
+}
+
+BOOST_AUTO_TEST_CASE(DbusApiConnectionLost2Test)
+{
+    ScopedDbusDaemon daemon;
+    ScopedGlibLoop loop;
+    DbusServerTest server;
+    DbusClientTest client;
+
+    BOOST_CHECK_NO_THROW(client.noop());
+    daemon.stop();
+    BOOST_CHECK_THROW(client.noop(), DbusOperationException);
+
+    Latch disconnected;
+    server.setDisconnectCallback([&] {disconnected.set();});
+    BOOST_CHECK(disconnected.wait(EVENT_TIMEOUT));
+}
+
+BOOST_AUTO_TEST_SUITE_END()