Various changes to InputMonitor 61/22561/11
authorJan Olszak <j.olszak@samsung.com>
Tue, 3 Jun 2014 07:49:07 +0000 (09:49 +0200)
committerPiotr Bartosiewicz <p.bartosiewi@partner.samsung.com>
Fri, 13 Jun 2014 12:41:04 +0000 (05:41 -0700)
[Bug/Feature]   InputMonitor had to be corrected
[Cause]         N/A
[Solution]      N/A
[Verification]  Build, install, run tests

Change-Id: Ibb7f71da0cfc19dad943a9e69badda3b6d866d70
Signed-off-by: Jan Olszak <j.olszak@samsung.com>
common/dbus/connection.cpp
common/utils/fs.cpp
common/utils/scoped-gerror.cpp [new file with mode: 0644]
common/utils/scoped-gerror.hpp [new file with mode: 0644]
server/CMakeLists.txt
server/configs/daemon.conf
server/input-monitor-config.hpp
server/input-monitor.cpp
server/input-monitor.hpp
tests/unit_tests/CMakeLists.txt
tests/unit_tests/server/ut-input-monitor.cpp

index 03513cc..479c253 100644 (file)
 #include "dbus/connection.hpp"
 #include "dbus/exception.hpp"
 #include "utils/callback-wrapper.hpp"
+#include "utils/scoped-gerror.hpp"
 #include "log/logger.hpp"
 
+using namespace security_containers::utils;
 
 namespace security_containers {
 namespace dbus {
@@ -41,40 +43,6 @@ const std::string INTROSPECT_METHOD = "Introspect";
 
 const int CALL_METHOD_TIMEOUT_MS = 1000;
 
-class ScopedError {
-public:
-    ScopedError() : mError(NULL) {}
-    ~ScopedError()
-    {
-        if (mError) {
-            g_error_free(mError);
-        }
-    }
-    void strip()
-    {
-        g_dbus_error_strip_remote_error(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)
@@ -102,7 +70,7 @@ private:
     bool mResultSet;
 };
 
-void throwDbusException(const ScopedError& e)
+void throwDbusException(const ScopedGError& e)
 {
     if (e->domain == g_io_error_quark()) {
         if (e->code == G_IO_ERROR_DBUS_ERROR) {
@@ -135,7 +103,7 @@ DbusConnection::DbusConnection(const std::string& address)
     : mConnection(NULL)
     , mNameId(0)
 {
-    ScopedError error;
+    ScopedGError error;
     const GDBusConnectionFlags flags =
         static_cast<GDBusConnectionFlags>(G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
                                           G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION);
@@ -200,7 +168,7 @@ void DbusConnection::emitSignal(const std::string& objectPath,
                                 const std::string& name,
                                 GVariant* parameters)
 {
-    ScopedError error;
+    ScopedGError error;
     g_dbus_connection_emit_signal(mConnection,
                                   NULL,
                                   objectPath.c_str(),
@@ -264,7 +232,7 @@ void DbusConnection::registerObject(const std::string& objectPath,
                                     const std::string& objectDefinitionXml,
                                     const MethodCallCallback& callback)
 {
-    ScopedError error;
+    ScopedGError error;
     GDBusNodeInfo* nodeInfo = g_dbus_node_info_new_for_xml(objectDefinitionXml.c_str(), &error);
     if (nodeInfo != NULL && (nodeInfo->interfaces == NULL ||
                              nodeInfo->interfaces[0] == NULL ||
@@ -333,7 +301,7 @@ GVariantPtr DbusConnection::callMethod(const std::string& busName,
                                        GVariant* parameters,
                                        const std::string& replyType)
 {
-    ScopedError error;
+    ScopedGError error;
     GVariant* result = g_dbus_connection_call_sync(mConnection,
                                                    busName.c_str(),
                                                    objectPath.c_str(),
index 4fe8363..57c8e6d 100644 (file)
@@ -101,23 +101,6 @@ bool isCharDevice(const std::string& path)
     return ::stat(path.c_str(), &s) == 0 && S_IFCHR == (s.st_mode & S_IFMT);
 }
 
-bool listDir(const std::string& path, std::vector<std::string>& files)
-{
-    DIR* dirp = ::opendir(path.c_str());
-    if (dirp == NULL) {
-        LOGE("Could not open directory" << path << "': " << strerror(errno));
-        return false;
-    }
-
-    struct dirent* entry;
-    while ((entry = ::readdir(dirp)) != NULL) {
-        files.push_back(entry->d_name);
-    }
-
-    ::closedir(dirp);
-    return true;
-}
-
 namespace {
 // NOTE: Should be the same as in systemd/src/core/mount-setup.c
 const std::string RUN_MOUNT_POINT_OPTIONS = "mode=755,smackfstransmute=System::Run";
diff --git a/common/utils/scoped-gerror.cpp b/common/utils/scoped-gerror.cpp
new file mode 100644 (file)
index 0000000..303dd4e
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ *  Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ *  Contact: Jan Olszak <j.olszak@samsung.com>
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+
+/**
+ * @file
+ * @author  Jan Olszak (j.olszak@samsung.com)
+ * @brief   Implementation of the wrapper for GError
+ */
+
+#include "config.hpp"
+
+#include "scoped-gerror.hpp"
+
+namespace security_containers {
+namespace utils {
+
+ScopedGError::ScopedGError()
+    : mError(NULL)
+{
+
+}
+
+ScopedGError::~ScopedGError()
+{
+    if (mError) {
+        g_error_free(mError);
+    }
+}
+
+bool ScopedGError::strip()
+{
+    return g_dbus_error_strip_remote_error(mError);
+}
+
+ScopedGError::operator bool () const
+{
+    return mError != nullptr;
+}
+
+GError** ScopedGError::operator& ()
+{
+    return &mError;
+}
+
+const GError* ScopedGError::operator->() const
+{
+    return mError;
+}
+
+std::ostream& operator<<(std::ostream& os, const ScopedGError& e)
+{
+    os << e->message;
+    return os;
+}
+
+} // namespace utils
+} // namespace security_containers
diff --git a/common/utils/scoped-gerror.hpp b/common/utils/scoped-gerror.hpp
new file mode 100644 (file)
index 0000000..06e17aa
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ *  Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ *  Contact: Jan Olszak <j.olszak@samsung.com>
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+
+/**
+ * @file
+ * @author  Jan Olszak (j.olszak@samsung.com)
+ * @brief   Declaration of the wrapper for GError
+ */
+
+#ifndef COMMON_SCOPED_GERROR_HPP
+#define COMMON_SCOPED_GERROR_HPP
+
+#include <iostream>
+#include <gio/gio.h>
+
+namespace security_containers{
+namespace utils {
+
+class ScopedGError {
+public:
+    ScopedGError();
+    ~ScopedGError();
+
+    /**
+     * Strip the error
+     */
+    bool strip();
+
+    /**
+     * Is error pointer NULL?
+     */
+    operator bool () const;
+
+    /**
+     * @return pointer to the GError
+     */
+    GError** operator& ();
+
+    /**
+     * @return  the GError
+     */
+    const GError* operator->() const;
+
+    /**
+     * Writes out the error message
+     * @param os the output stream
+     * @param e  error to write out
+     */
+    friend std::ostream& operator<<(std::ostream& os, const ScopedGError& e);
+
+private:
+    GError* mError;
+};
+
+} // namespace utils
+} // namespace security_containers
+
+#endif // COMMON_SCOPED_GERROR_HPP
index 42d9503..b93b010 100644 (file)
@@ -28,8 +28,7 @@ ADD_EXECUTABLE(${SERVER_CODENAME} ${project_SRCS} ${common_SRCS})
 
 
 ## Link libraries ##############################################################
-FIND_PACKAGE (Boost COMPONENTS program_options system filesystem)
-
+FIND_PACKAGE (Boost COMPONENTS program_options system filesystem regex)
 PKG_CHECK_MODULES(SERVER_DEPS REQUIRED libvirt libvirt-glib-1.0 json gio-2.0 libsystemd-journal)
 INCLUDE_DIRECTORIES(${COMMON_FOLDER})
 INCLUDE_DIRECTORIES(SYSTEM ${SERVER_DEPS_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS})
index c68a9f3..2610f5e 100644 (file)
@@ -2,7 +2,7 @@
     "containerConfigs" : ["containers/private.conf", "containers/business.conf" ],
     "foregroundId" : "private",
     "inputConfig" : {"enabled" : true,
-                     "device" : "gpio-keys.4",
+                     "device" : "gpio-keys",
                      "code" : 139,
                      "numberOfEvents" : 2,
                      "timeWindowMs" : 500}
index e210127..33e716f 100644 (file)
@@ -36,15 +36,30 @@ namespace security_containers {
 
 struct InputConfig {
 
+    /**
+     * Is monitoring input enabled?
+     */
     bool enabled;
 
     /**
-     * represents either a device name or an absolute device file path (must be a string starting
-     *  with '/' character)
+     * Device name or an absolute device file path
+     * (must be a string starting with '/' character)
      */
     std::string device;
+
+    /**
+     * Event code
+     */
     int code;
+
+    /**
+     * Number of events that will trigger an action
+     */
     int numberOfEvents;
+
+    /**
+     * Time window in which subsequent events will trigger an action
+     */
     int timeWindowMs;
 
     CONFIG_REGISTER
index ec8c300..13025a5 100644 (file)
@@ -1,7 +1,7 @@
 /*
  *  Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved
  *
- *  Contact: Pawel Broda <p.broda@partner.samsung.com>
+ *  Contact: Jan Olszak <j.olszak@samsung.com>
  *
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
 
 /**
  * @file
- * @author  Pawel Broda (p.broda@partner.samsung.com)
+ * @author  Jan Olszak (j.olszak@samsung.com)
  * @brief   C++ wrapper for glib input monitor
  */
 
-#include "exception.hpp"
-#include "input-monitor.hpp"
+#include "config.hpp"
+
 #include "input-monitor-config.hpp"
+#include "input-monitor.hpp"
+#include "exception.hpp"
 
 #include "log/logger.hpp"
 #include "utils/exception.hpp"
 #include "utils/fs.hpp"
+#include "utils/callback-wrapper.hpp"
+#include "utils/scoped-gerror.hpp"
 
 #include <cassert>
 #include <cstring>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
+
+#include <boost/filesystem.hpp>
+#include <boost/regex.hpp>
 #include <vector>
+#include <functional>
 
+using namespace security_containers::utils;
+namespace fs = boost::filesystem;
 
 namespace security_containers {
 
-// TODO: extract device helper and monitoring utilities from InputMonitor to separate files
-
+namespace {
+const int MAX_TIME_WINDOW_SEC = 10;
+const int KEY_PRESSED = 1;
+const int DEVICE_NAME_LENGTH = 256;
+const int MAX_NUMBER_OF_EVENTS = 10;
+const std::string DEVICE_DIR = "/dev/input/";
 
-const std::string InputMonitor::DEVICE_DIR = "/dev/input/";
+} // namespace
 
 
-std::string InputMonitor::findDeviceNode(const std::string& deviceName)
+InputMonitor::InputMonitor(const InputConfig& inputConfig,
+                           const NotifyCallback& notifyCallback)
+    : mConfig(inputConfig),
+      mNotifyCallback(notifyCallback)
 {
-    std::vector<std::string> files;
-
-    try {
-        utils::listDir(DEVICE_DIR, files);
-    } catch (UtilsException&) {
+    if (mConfig.timeWindowMs > MAX_TIME_WINDOW_SEC * 1000L) {
+        LOGE("Time window exceeds maximum: " << MAX_TIME_WINDOW_SEC);
         throw InputMonitorException();
     }
 
-    for (auto fileName: files) {
-        std::string fullDevicePath = DEVICE_DIR + fileName;
-        if (utils::isCharDevice(fullDevicePath)) {
-            char name[DEVICE_NAME_LENGTH];
-            memset(name, 0, sizeof(name));
-            int fd = open(fullDevicePath.c_str(), O_RDONLY);
-            if (fd < 0) {
-                LOGD("Failed to open'" << fullDevicePath << "'");
-                continue;
-            }
-
-            if (ioctl(fd, EVIOCGNAME(sizeof(name)), name) < 0) {
-                LOGD("Failed to send ioctl with request about device name to '"
-                     << fullDevicePath << "'");
-                close(fd);
-                continue;
-            }
-
-            close(fd);
-
-            if (deviceName == name) {
-                LOGI("Device file for '" << deviceName << "' found under '"
-                     << fullDevicePath << "'");
-                return fullDevicePath;
-            }
-        }
+    if (mConfig.numberOfEvents > MAX_NUMBER_OF_EVENTS) {
+        LOGE("Number of events exceeds maximum: " << MAX_NUMBER_OF_EVENTS);
+        throw InputMonitorException();
     }
 
-    return std::string();
-}
+    std::string devicePath = getDevicePath();
 
-bool InputMonitor::detectExpectedEvent(const struct input_event& ie)
-{
-    // log events. Example log:
-    // Event detected [/dev/input/event4]:
-    //         time: 946705528.918966 sec
-    //         type, code, value: 1, 139, 1
-    LOGT("Event detected [" << mConfig.device.c_str() << "]:\n"
-         << "\ttime: " << ie.time.tv_sec << "." << ie.time.tv_usec << " sec\n"
-         << "\ttype, code, value: " << ie.type << ", " << ie.code << ", " << ie.value);
-
-    if (ie.type == EV_KEY &&
-        ie.code == mConfig.code &&
-        ie.value == KEY_PRESSED) {
-        for (int i = mConfig.numberOfEvents - 1; i > 0; --i) {
-            mEventTime[i].tv_sec = mEventTime[i - 1].tv_sec;
-            mEventTime[i].tv_usec = mEventTime[i - 1].tv_usec;
-        }
-
-        mEventTime[0].tv_sec = ie.time.tv_sec;
-        mEventTime[0].tv_usec = ie.time.tv_usec;
-
-        // a) check, if given event sequence happened within specified time window
-        //    (this is necessary, because of multiplying in the next step)
-        // b) if yes, then compute the difference between the first and the last one
-        if (((mEventTime[0].tv_sec - mEventTime[mConfig.numberOfEvents - 1].tv_sec) < MAX_TIME_WINDOW_SEC) &&
-            (mEventTime[0].tv_sec - mEventTime[mConfig.numberOfEvents - 1].tv_sec) * 1000000L +
-             mEventTime[0].tv_usec - mEventTime[mConfig.numberOfEvents - 1].tv_usec < mConfig.timeWindowMs * 1000L) {
+    LOGT("Input monitor configuration: \n"
+         << "\tenabled: " << mConfig.enabled << "\n"
+         << "\tdevice: " << mConfig.device << "\n"
+         << "\tpath: " << devicePath << "\n"
+         << "\ttype: " << EV_KEY << "\n"
+         << "\tcode: " << mConfig.code << "\n"
+         << "\tvalue: " << KEY_PRESSED << "\n"
+         << "\tnumberOfEvents: " << mConfig.numberOfEvents << "\n"
+         << "\ttimeWindowMs: " << mConfig.timeWindowMs);
 
-            resetEventsTime();
+    createGIOChannel(devicePath);
+}
 
-            return true;
-        }
+InputMonitor::~InputMonitor()
+{
+    LOGD("Destroying InputMonitor");
+    ScopedGError error;
+    g_io_channel_unref(mChannelPtr);
+    if (g_io_channel_shutdown(mChannelPtr, FALSE, &error)
+            != G_IO_STATUS_NORMAL) {
+        LOGE("Error during shutting down GIOChannel: " << error->message);
     }
 
-    return false;
+    if (!g_source_remove(mSourceId)) {
+        LOGE("Error during removing the source");
+    }
 }
 
-void InputMonitor::readDevice(GIOChannel* gio)
+namespace {
+bool isDeviceWithName(const boost::regex& deviceNameRegex,
+                      const fs::directory_entry& directoryEntry)
 {
-    struct input_event ie;
-    gchar buff[sizeof(struct input_event)];
-    gsize nBytesRequested = sizeof(struct input_event);
-    gsize nBytesRead = 0;
-    gsize nBytesReadTotal = 0;
-    GError *err = NULL;
-
-    do {
-        GIOStatus readStatus = g_io_channel_read_chars(gio,
-                                                       &buff[nBytesReadTotal],
-                                                       nBytesRequested,
-                                                       &nBytesRead,
-                                                       &err);
+    std::string path = directoryEntry.path().string();
 
-         if (readStatus == G_IO_STATUS_ERROR || err != NULL) {
-             LOGE("Read from input monitor channel failed");
-             return;
-         }
+    if (!utils::isCharDevice(path)) {
+        return false;
+    }
 
-        nBytesRequested -= nBytesRead;
-        nBytesReadTotal += nBytesRead;
-    } while (nBytesRequested > 0);
+    int fd = open(path.c_str(), O_RDONLY);
+    if (fd < 0) {
+        LOGD("Failed to open " << path);
+        return false;
+    }
 
-    memcpy(&ie, buff, sizeof(struct input_event));
+    char name[DEVICE_NAME_LENGTH];
+    if (ioctl(fd, EVIOCGNAME(sizeof(name)), name) < 0) {
+        LOGD("Failed to get the device name of: " << path);
+        if (close(fd) < 0) {
+            LOGE("Error during closing file " << path);
+        }
+        return false;
+    }
 
-    if (detectExpectedEvent(ie)) {
-        LOGI("Input monitor detected pattern.");
-        mNotifyCallback();
+    if (close(fd) < 0) {
+        LOGE("Error during closing file " << path);
     }
-}
 
-gboolean InputMonitor::readDeviceCallback(GIOChannel* gio,
-                                          GIOCondition /*condition*/,
-                                          gpointer data)
-{
-        InputMonitor& inputMonitor = *reinterpret_cast<InputMonitor*>(data);
-        inputMonitor.readDevice(gio);
+    LOGD("Checking device: " << name);
+    if (boost::regex_match(name, deviceNameRegex)) {
+        LOGI("Device file found under: " << path);
+        return true;
+    }
 
-        return TRUE;
+    return false;
 }
+} // namespace
 
-InputMonitor::InputMonitor(const InputConfig& inputConfig,
-                           const InputNotifyCallback& inputNotifyCallback)
+std::string InputMonitor::getDevicePath()
 {
-    mConfig = inputConfig;
-    mNotifyCallback = inputNotifyCallback;
-
-    resetEventsTime();
-
-    if (mConfig.numberOfEvents > MAX_NUMBER_OF_EVENTS) {
-        LOGE("Input monitor numberOfEvents > MAX_NUMBER_OF_EVENTS\n"
-             << "\twhere MAX_NUMBER_OF_EVENTS = " << MAX_NUMBER_OF_EVENTS);
-        throw InputMonitorException();
+    std::string device = mConfig.device;
+    if (fs::path(device).is_absolute()
+            && fs::exists(device)) {
+        LOGD("Device file path is given");
+        return device;
     }
 
-    if (mConfig.timeWindowMs > MAX_TIME_WINDOW_SEC * 1000L) {
-        LOGE("Input monitor timeWindowMs > MAX_TIME_WINDOW_SEC * 1000L\n"
-             << "\twhere MAX_TIME_WINDOW_SEC = " << MAX_TIME_WINDOW_SEC);
+    // device name is given - device file path is to be determined
+    LOGT("Determining, which device node is assigned to '" << device << "'");
+    using namespace std::placeholders;
+    fs::directory_iterator end;
+    boost::regex deviceNameRegex(".*" + device + ".*");
+    const auto it = std::find_if(fs::directory_iterator(DEVICE_DIR),
+                                 end,
+                                 std::bind(isDeviceWithName, deviceNameRegex, _1));
+    if (it == end) {
+        LOGE("None of the files under '" << DEVICE_DIR <<
+             "' represents device named: " << device);
         throw InputMonitorException();
     }
 
-    std::string devicePath;
-    if (mConfig.device[0] == '/') { // device file path is given
-        devicePath = mConfig.device;
-    } else { // device name is given - device file path is to be determined
-        LOGT("Determining, which device node is assigned to '" << mConfig.device << "'");
-        devicePath = findDeviceNode(mConfig.device);
-        if (devicePath.empty()) {
-            LOGE("None of the files under '" << DEVICE_DIR << "' represents device named: "
-                 << mConfig.device.c_str());
-                throw InputMonitorException();
-        }
-    }
+    return it->path().string();
+}
 
-    LOGT("Input monitor configuration: \n"
-         << "\tenabled: " << mConfig.enabled << "\n"
-         << "\tdevice: " << mConfig.device.c_str() << "\n"
-         << "\tpath: " << devicePath.c_str() << "\n"
-         << "\ttype: " << EV_KEY << "\n"
-         << "\tcode: " << mConfig.code << "\n"
-         << "\tvalue: " << KEY_PRESSED << "\n"
-         << "\tnumberOfEvents: " << mConfig.numberOfEvents << "\n"
-         << "\ttimeWindowMs: " << mConfig.timeWindowMs);
 
-    GError *err = NULL;
 
-    // creating channel using steps below, i.e.:
-    // 1) open()
-    // 2) g_io_channel_unix_new()
-    // was chosen, because glib API does not allow to open a file
-    // in a non-blocking mode. There is no argument to pass additional
-    // flags and we need such a mode for FIFOs in the tests.
+void InputMonitor::createGIOChannel(const std::string& devicePath)
+{
+    // We need NONBLOCK for FIFOs in the tests
     int fd = open(devicePath.c_str(), O_RDONLY | O_NONBLOCK);
     if (fd < 0) {
-        LOGE("Cannot create input monitor channel. Device file: " << devicePath.c_str()
-             << " doesn't exist" );
+        LOGE("Cannot create input monitor channel. Device file: " <<
+             devicePath << " doesn't exist");
         throw InputMonitorException();
     }
 
-    GIOChannel *mChannel = g_io_channel_unix_new(fd);
+    mChannelPtr = g_io_channel_unix_new(fd);
 
-    // close the channel on final unref
-    g_io_channel_set_close_on_unref(mChannel,
-                                    TRUE);
-
-    // read binary
-    g_io_channel_set_encoding(mChannel,
-                              NULL,
-                              &err);
-
-    if (err != NULL) {
-        LOGE("Cannot set encoding for input monitor channel");
+    // Read binary data
+    if (g_io_channel_set_encoding(mChannelPtr,
+                                  NULL,
+                                  NULL) != G_IO_STATUS_NORMAL) {
+        LOGE("Cannot set encoding for input monitor channel ");
         throw InputMonitorException();
     }
 
-    if (!g_io_add_watch(mChannel, G_IO_IN, readDeviceCallback, this)) {
+    using namespace std::placeholders;
+    ReadDeviceCallback callback = std::bind(&InputMonitor::readDevice, this, _1);
+    // Add the callback
+    mSourceId = g_io_add_watch_full(mChannelPtr,
+                                    G_PRIORITY_DEFAULT,
+                                    G_IO_IN,
+                                    readDeviceCallback,
+                                    utils::createCallbackWrapper(callback, mGuard.spawn()),
+                                    &utils::deleteCallbackWrapper<ReadDeviceCallback>);
+    if (!mSourceId) {
         LOGE("Cannot add watch on device input file");
         throw InputMonitorException();
     }
 }
 
-void InputMonitor::resetEventsTime()
+gboolean InputMonitor::readDeviceCallback(GIOChannel* gio,
+        GIOCondition /*condition*/,
+        gpointer data)
 {
-    memset(mEventTime, 0, sizeof(struct timeval) * MAX_NUMBER_OF_EVENTS);
+    const ReadDeviceCallback& callback = utils::getCallbackFromPointer<ReadDeviceCallback>(data);
+    callback(gio);
+    return TRUE;
 }
 
-InputMonitor::~InputMonitor()
+void InputMonitor::readDevice(GIOChannel* gio)
 {
+    struct input_event ie;
+    gsize nBytesReadTotal = 0;
+    gsize nBytesRequested = sizeof(struct input_event);
+    ScopedGError error;
+
+    do {
+        gsize nBytesRead = 0;
+        GIOStatus readStatus = g_io_channel_read_chars(gio,
+                               &reinterpret_cast<gchar*>(&ie)[nBytesReadTotal],
+                               nBytesRequested,
+                               &nBytesRead,
+                               &error);
+
+        if (readStatus == G_IO_STATUS_ERROR) {
+            LOGE("Read from input monitor channel failed: " << error->message);
+            return;
+        }
+
+        nBytesRequested -= nBytesRead;
+        nBytesReadTotal += nBytesRead;
+    } while (nBytesRequested > 0);
+
+
+    if (isExpectedEventSequence(ie)) {
+        LOGI("Input monitor detected pattern.");
+        mNotifyCallback();
+    }
 }
 
+bool InputMonitor::isExpectedEventSequence(const struct input_event& ie)
+{
+    LOGT("Event detected [" << mConfig.device.c_str() << "]:\n"
+         << "\ttime: " << ie.time.tv_sec << "." << ie.time.tv_usec << " sec\n"
+         << "\ttype, code, value: " << ie.type << ", " << ie.code << ", " << ie.value);
+
+    if (ie.type != EV_KEY
+            || ie.code != mConfig.code
+            || ie.value != KEY_PRESSED) {
+        LOGT("Wrong kind of event");
+        return false;
+    }
+
+    mEventTimes.push_back(ie.time);
+
+    if (mEventTimes.size() < static_cast<unsigned int>(mConfig.numberOfEvents)) {
+        LOGT("Event sequence too short");
+        return false;
+    }
+
+    struct timeval oldest = mEventTimes.front();
+    mEventTimes.pop_front();
+    struct timeval latest = mEventTimes.back();
+
+    long int secDiff = latest.tv_sec - oldest.tv_sec;
+    if (secDiff >= MAX_TIME_WINDOW_SEC) {
+        LOGT("Time window exceeded");
+        return false;
+    }
+
+    long int timeDiff = secDiff * 1000L;
+    timeDiff += static_cast<long int>((latest.tv_usec - oldest.tv_usec) / 1000L);
+    if (timeDiff < mConfig.timeWindowMs) {
+        LOGD("Event sequence detected");
+        mEventTimes.clear();
+        return true;
+    }
+
+    LOGT("Event sequence not detected");
+    return false;
+}
 } // namespace security_containers
index 692a554..15262bc 100644 (file)
@@ -1,7 +1,7 @@
 /*
  *  Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved
  *
- *  Contact: Pawel Broda <p.broda@partner.samsung.com>
+ *  Contact: Jan Olszak <j.olszak@samsung.com>
  *
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
 
 /**
  * @file
- * @author  Pawel Broda (p.broda@partner.samsung.com)
+ * @author  Jan Olszak (j.olszak@samsung.com)
  * @brief   C++ wrapper for glib input monitor
  */
 
 #ifndef SERVER_INPUT_MONITOR_HPP
 #define SERVER_INPUT_MONITOR_HPP
 
-
 #include "input-monitor-config.hpp"
+#include "utils/callback-guard.hpp"
 
-#include <functional>
-#include <glib.h>
 #include <linux/input.h>
 #include <sys/time.h>
+#include <glib.h>
+
+#include <functional>
+#include <string>
+#include <list>
 
 
 namespace security_containers {
 
-
 class InputMonitor {
 public:
-    typedef std::function<void()> InputNotifyCallback;
-    InputMonitor(const InputConfig&, const InputNotifyCallback&);
+    typedef std::function<void()> NotifyCallback;
+
+    InputMonitor(const InputConfig& inputConfig,
+                 const NotifyCallback& notifyCallback);
     ~InputMonitor();
 
 private:
-    static const int MAX_NUMBER_OF_EVENTS = 5;
-    static const int MAX_TIME_WINDOW_SEC = 10;
-    static const int KEY_PRESSED = 1;
-    static const int DEVICE_NAME_LENGTH = 256;
-    static const std::string DEVICE_DIR;
-    struct timeval mEventTime[MAX_NUMBER_OF_EVENTS];
-    GIOChannel *mChannel;
+    typedef std::function<void(GIOChannel* gio)> ReadDeviceCallback;
+
     InputConfig mConfig;
+    NotifyCallback mNotifyCallback;
+
+    std::list<struct timeval> mEventTimes;
+    GIOChannel* mChannelPtr;
 
-    // External callback to be registered at InputMonitor instance
-    InputNotifyCallback mNotifyCallback;
+    std::string getDevicePath();
+    void createGIOChannel(const std::string& devicePath);
 
     // Internal callback to be registered at glib g_io_add_watch()
-    static gboolean readDeviceCallback(GIOChannel *, GIOCondition, gpointer);
-    std::string findDeviceNode(const std::string&);
-    bool detectExpectedEvent(const struct input_event&);
-    void readDevice(GIOChannel *);
-    void resetEventsTime();
+    static gboolean readDeviceCallback(GIOChannel*, GIOCondition, gpointer);
+    bool isExpectedEventSequence(const struct input_event&);
+    void readDevice(GIOChannel*);
+    utils::CallbackGuard mGuard;
+    guint mSourceId;
 };
 
 } // namespace security_containers
index 816f058..3be44b7 100644 (file)
@@ -32,7 +32,7 @@ ADD_EXECUTABLE(${UT_SERVER_CODENAME} ${project_SRCS} ${common_SRCS} ${server_SRC
 
 
 ## Link libraries ##############################################################
-FIND_PACKAGE (Boost COMPONENTS unit_test_framework system filesystem)
+FIND_PACKAGE (Boost COMPONENTS unit_test_framework system filesystem regex)
 
 PKG_CHECK_MODULES(UT_SERVER_DEPS REQUIRED libvirt libvirt-glib-1.0 json gio-2.0 libsystemd-journal)
 INCLUDE_DIRECTORIES(${COMMON_FOLDER} ${SERVER_FOLDER} ${UNIT_TESTS_FOLDER})
index 04191a7..a0bd9dd 100644 (file)
@@ -1,7 +1,7 @@
 /*
  *  Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved
  *
- *  Contact: Pawel Broda <p.broda@partner.samsung.com>
+ *  Contact: Jan Olszak <j.olszak@samsung.com>
  *
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -19,7 +19,7 @@
 
 /**
  * @file
- * @author  Pawel Broda (p.broda@partner.samsung.com)
+ * @author  Jan Olszak (j.olszak@samsung.com)
  * @brief   Unit tests of the InputMonitor class
  */
 
 #include "utils/glib-loop.hpp"
 #include "utils/latch.hpp"
 
-#include <atomic>
-#include <chrono>
-#include <fcntl.h>
-#include <memory>
-#include <string>
-#include <stdio.h>
 #include <sys/types.h>
 #include <sys/stat.h>
-#include <thread>
+#include <fcntl.h>
+#include <stdio.h>
 #include <unistd.h>
 
+#include <boost/filesystem.hpp>
+#include <atomic>
+#include <chrono>
+#include <memory>
+#include <string>
 
-BOOST_AUTO_TEST_SUITE(InputMonitorSuite)
 
 using namespace security_containers;
 using namespace security_containers::utils;
 
 namespace {
 
-const char* TEST_INPUT_DEVICE = "/tmp/testInputDevice";
+std::string TEST_INPUT_DEVICE =
+    boost::filesystem::unique_path("/tmp/testInputDevice-%%%%").string();
 
 const int EVENT_TYPE = 1;
 const int EVENT_CODE = 139;
 const int EVENT_BUTTON_RELEASED = 0;
 const int EVENT_BUTTON_PRESSED = 1;
 
-const unsigned int ONE_EVENT = 1;
-const unsigned int TEN_EVENTS = 10;
-
-const int SINGLE_EVENT_TIMEOUT = 10;
-
-std::atomic<unsigned int> counter(0);
-
-
-void callbackEmpty()
-{
-}
-
-void callbackCounter()
-{
-    counter++;
-}
-
-void resetCounter()
-{
-    counter = 0;
-}
-
-unsigned int getCounter()
-{
-    return counter;
-}
-
-/*
- * A wrapper for write() which restarts after EINTR.
- */
-ssize_t safeWrite(int fd, const void *buff, size_t len)
-{
-    size_t totalBytesWritten = 0;
-
-    while (len > 0) {
-        ssize_t bytesWritten = write(fd, buff, len);
-
-        if (bytesWritten < 0 && errno == EINTR) {
-            continue;
-        }
-
-        if (bytesWritten < 0) {
-            return bytesWritten;
-        }
-
-        if (bytesWritten == 0) {
-            return totalBytesWritten;
-        }
-
-        buff = (const char *)buff + bytesWritten;
-        len -= bytesWritten;
-        totalBytesWritten += bytesWritten;
-    }
+const int SINGLE_EVENT_TIMEOUT = 1000;
 
-    return totalBytesWritten;
-}
-
-class Fixture {
-public:
+struct Fixture {
     InputConfig inputConfig;
     utils::ScopedGlibLoop mLoop;
-    struct input_event ie[2];
+    struct input_event ie;
 
-    Fixture() {
+    Fixture()
+    {
         inputConfig.numberOfEvents = 2;
         inputConfig.device = TEST_INPUT_DEVICE;
         inputConfig.code = EVENT_CODE;
@@ -130,204 +75,136 @@ public:
         inputConfig.timeWindowMs = 500;
 
         // fill simulated events with init values
-        ie[0].time.tv_sec = 946707544;
-        ie[0].time.tv_usec = 0;
-        ie[0].type = EVENT_TYPE;
-        ie[0].code = EVENT_CODE;
-        ie[0].value = EVENT_BUTTON_RELEASED;
-
-        ie[1].time.tv_sec = 946707544;
-        ie[1].time.tv_usec = 0;
-        ie[1].type = 0;
-        ie[1].code = 0;
-        ie[1].value = 0;
-
-        resetCounter();
-        remove(TEST_INPUT_DEVICE);
-        mkfifo(TEST_INPUT_DEVICE, 0777);
+        ie.time.tv_sec = 946707544;
+        ie.time.tv_usec = 0;
+        ie.type = EVENT_TYPE;
+        ie.code = EVENT_CODE;
+        ie.value = EVENT_BUTTON_RELEASED;
+
+        ::remove(TEST_INPUT_DEVICE.c_str());
+        BOOST_CHECK(::mkfifo(TEST_INPUT_DEVICE.c_str(), S_IWUSR | S_IRUSR) >= 0);
     }
-    ~Fixture() {
-        remove(TEST_INPUT_DEVICE);
-        resetCounter();
+    ~Fixture()
+    {
+        ::remove(TEST_INPUT_DEVICE.c_str());
     }
 };
-
 } // namespace
 
-BOOST_AUTO_TEST_CASE(Config_OK)
-{
-    Fixture f;
+BOOST_FIXTURE_TEST_SUITE(InputMonitorSuite, Fixture)
 
-    BOOST_REQUIRE_NO_THROW(InputMonitor inputMonitor(f.inputConfig, callbackEmpty));
-}
-
-BOOST_AUTO_TEST_CASE(Config_numberOfEventsTooHigh)
+BOOST_AUTO_TEST_CASE(Config_OK)
 {
-    Fixture f;
-    f.inputConfig.numberOfEvents = 100;
-
-    BOOST_REQUIRE_THROW(InputMonitor inputMonitor(f.inputConfig, callbackEmpty),
-                        InputMonitorException);
+    BOOST_REQUIRE_NO_THROW(InputMonitor inputMonitor(inputConfig, InputMonitor::NotifyCallback()));
 }
 
 BOOST_AUTO_TEST_CASE(Config_timeWindowMsTooHigh)
 {
-    Fixture f;
-    f.inputConfig.timeWindowMs = 50000;
+    inputConfig.timeWindowMs = 50000;
 
-    BOOST_REQUIRE_THROW(InputMonitor inputMonitor(f.inputConfig, callbackEmpty),
+    BOOST_REQUIRE_THROW(InputMonitor inputMonitor(inputConfig, InputMonitor::NotifyCallback()),
                         InputMonitorException);
 }
 
 BOOST_AUTO_TEST_CASE(Config_deviceFilePathNotExisting)
 {
-    Fixture f;
-    f.inputConfig.device = std::string(TEST_INPUT_DEVICE) + "notexisting";
+    inputConfig.device = TEST_INPUT_DEVICE + "notExisting";
 
-    BOOST_REQUIRE_THROW(InputMonitor inputMonitor(f.inputConfig, callbackEmpty),
+    BOOST_REQUIRE_THROW(InputMonitor inputMonitor(inputConfig, InputMonitor::NotifyCallback()),
                         InputMonitorException);
 }
 
-// ============================================================================================
-// All of the following tests are based on the (almost) the same scenario:
-// 1) set up input monitor (create InputMonitor instance + register callback)
-// 2) simulate an event(s)
-//    a) prepare an example sequence of event(s) to trigger the callback (one or more times,
-//       depending on the test purpose)
-//    b) send it to the device
-// 3) verify that callback was triggered (one or more times accordingly)
-//
-// Button press + release events are simulated based on logs gathered from working system.
-// Example log:
-//
-//           // button pressed
-//           Event detected [gpio-keys.4]:
-//             time: 946691112.844176 sec
-//             type, code, value: 1, 139, 1
-//           Event detected [gpio-keys.4]:
-//             time: 946691112.844176 sec
-//             type, code, value: 0, 0, 0
-//
-//           // button released
-//           Event detected [gpio-keys.4]:
-//             time: 946691113.384301 sec
-//             type, code, value: 1, 139, 0
-//           Event detected [gpio-keys.4]:
-//             time: 946691113.384301 sec
-//             type, code, value: 0, 0, 0
-//
-//
-// ============================================================================================
-
-void sendEvents(unsigned int noOfEvents)
+void sendNEvents(unsigned int noOfEventsToSend)
 {
     Fixture f;
     Latch eventLatch;
 
     std::unique_ptr<InputMonitor> inputMonitor;
-    BOOST_REQUIRE_NO_THROW(inputMonitor.reset(new InputMonitor(f.inputConfig,
-            [&] { callbackCounter();
-                  if (getCounter() == noOfEvents) {
-                      eventLatch.set();
-                  }
-                })));
+    BOOST_REQUIRE_NO_THROW(inputMonitor.reset(new InputMonitor(f.inputConfig, [&] {eventLatch.set();})));
 
-    int fd = open(TEST_INPUT_DEVICE, O_WRONLY);
+    int fd = ::open(TEST_INPUT_DEVICE.c_str(), O_WRONLY);
+    BOOST_REQUIRE(fd >= 0);
 
-    for (unsigned int i = 0; i < noOfEvents * f.inputConfig.numberOfEvents; ++i) {
+    for (unsigned int i = 0; i < noOfEventsToSend * f.inputConfig.numberOfEvents; ++i) {
         // button pressed event
-        f.ie[0].value = EVENT_BUTTON_PRESSED;
-        f.ie[0].time.tv_usec += 5;
-        f.ie[1].time.tv_usec += 5;
-        ssize_t ret = safeWrite(fd, f.ie, 2 * sizeof(struct input_event));
+        f.ie.value = EVENT_BUTTON_PRESSED;
+        f.ie.time.tv_usec += 5;
+        ssize_t ret = ::write(fd, &f.ie, sizeof(struct input_event));
         BOOST_CHECK(ret > 0);
 
         std::this_thread::sleep_for(std::chrono::milliseconds(10));
 
         // button released event
-        f.ie[0].value = EVENT_BUTTON_RELEASED;
-        f.ie[0].time.tv_usec += 5;
-        f.ie[1].time.tv_usec += 5;
-        ret = safeWrite(fd, f.ie, 2 * sizeof(struct input_event));
+        f.ie.value = EVENT_BUTTON_RELEASED;
+        f.ie.time.tv_usec += 5;
+        ret = ::write(fd, &f.ie, sizeof(struct input_event));
         BOOST_CHECK(ret > 0);
     }
 
-    close(fd);
+    BOOST_CHECK(::close(fd) >= 0);
+    BOOST_CHECK(eventLatch.waitForN(noOfEventsToSend, SINGLE_EVENT_TIMEOUT * noOfEventsToSend));
 
-    BOOST_CHECK(eventLatch.wait(SINGLE_EVENT_TIMEOUT * noOfEvents));
-
-    // extra time to make sure, that no more events are read
-    std::this_thread::sleep_for(std::chrono::milliseconds(SINGLE_EVENT_TIMEOUT));
-
-    BOOST_CHECK(getCounter() == noOfEvents);
+    // Check if no more events are waiting
+    BOOST_CHECK(!eventLatch.wait(10));
 }
 
 BOOST_AUTO_TEST_CASE(Event_oneAtATime)
 {
-    sendEvents(ONE_EVENT);
+    sendNEvents(1);
 }
 
 BOOST_AUTO_TEST_CASE(Event_tenAtATime)
 {
-    sendEvents(TEN_EVENTS);
+    sendNEvents(10);
 }
 
-void sendEventsWithPauses(unsigned int noOfEvents)
+void sendNEventsWithPauses(unsigned int noOfEventsToSend)
 {
     Fixture f;
     Latch eventLatch;
 
     std::unique_ptr<InputMonitor> inputMonitor;
-    BOOST_REQUIRE_NO_THROW(inputMonitor.reset(new InputMonitor(f.inputConfig,
-            [&] { callbackCounter();
-                  if (getCounter() == noOfEvents) {
-                      eventLatch.set();
-                  }
-                })));
+    BOOST_REQUIRE_NO_THROW(inputMonitor.reset(new InputMonitor(f.inputConfig, [&] {eventLatch.set();})));
 
-    int fd = open(TEST_INPUT_DEVICE, O_WRONLY);
+    int fd = ::open(TEST_INPUT_DEVICE.c_str(), O_WRONLY);
+    BOOST_REQUIRE(fd >= 0);
 
-    for (unsigned int i = 0; i < noOfEvents * f.inputConfig.numberOfEvents; ++i) {
-        // button pressed event
-        f.ie[0].value = EVENT_BUTTON_PRESSED;
-        f.ie[0].time.tv_usec += 5;
-        f.ie[1].time.tv_usec += 5;
-        ssize_t ret = write(fd, f.ie, 2); // send first two bytes
+    for (unsigned int i = 0; i < noOfEventsToSend * f.inputConfig.numberOfEvents; ++i) {
+        // Send first two bytes of the button pressed event
+        f.ie.value = EVENT_BUTTON_PRESSED;
+        f.ie.time.tv_usec += 5;
+        ssize_t ret = ::write(fd, &f.ie, 2);
         BOOST_CHECK(ret > 0);
 
         std::this_thread::sleep_for(std::chrono::milliseconds(10));
 
-        ret = write(fd, &reinterpret_cast<char *>(f.ie)[2], 2 * sizeof(struct input_event) - 2); // send the remaining part
+        // Send the remaining part
+        ret = ::write(fd, &reinterpret_cast<char*>(&f.ie)[2], sizeof(struct input_event) - 2);
         BOOST_CHECK(ret > 0);
 
         std::this_thread::sleep_for(std::chrono::milliseconds(10));
 
-        // button released event
-        f.ie[0].value = EVENT_BUTTON_RELEASED;
-        f.ie[0].time.tv_usec += 5;
-        f.ie[1].time.tv_usec += 5;
-        ret = write(fd, f.ie, 2 * sizeof(struct input_event));
+        // Send the button released event
+        f.ie.value = EVENT_BUTTON_RELEASED;
+        f.ie.time.tv_usec += 5;
+        ret = ::write(fd, &f.ie, sizeof(struct input_event));
         BOOST_CHECK(ret > 0);
     }
+    BOOST_CHECK(::close(fd) >= 0);
+    BOOST_CHECK(eventLatch.waitForN(noOfEventsToSend, SINGLE_EVENT_TIMEOUT * noOfEventsToSend));
 
-    close(fd);
-
-    BOOST_CHECK(eventLatch.wait(SINGLE_EVENT_TIMEOUT * noOfEvents));
-
-    // extra time to make sure, that no more events are read
-    std::this_thread::sleep_for(std::chrono::milliseconds(SINGLE_EVENT_TIMEOUT));
-
-    BOOST_CHECK(getCounter() == noOfEvents);
+    // Check if no more events are waiting
+    BOOST_CHECK(!eventLatch.wait(10));
 }
 
 BOOST_AUTO_TEST_CASE(Event_oneAtATimeWithPauses)
 {
-    sendEventsWithPauses(ONE_EVENT);
+    sendNEventsWithPauses(1);
 }
 
 BOOST_AUTO_TEST_CASE(Event_tenAtATimeWithPauses)
 {
-    sendEventsWithPauses(TEN_EVENTS);
+    sendNEventsWithPauses(10);
 }