sync_manager: Create sync watch class

Create a watch class to monitor the files and directories
specified in the synclist file.
Store the file descriptors and file names in a map to be
able to know the full path of the file that triggered the
event. The watch descriptor number does not change so it
can be a single variable.

Change-Id: I211225ddc012af85d9be39ae5d40b8258d73435d
Signed-off-by: Adriana Kobylak <anoo@us.ibm.com>
diff --git a/Makefile.am b/Makefile.am
index b4755c5..9b6b508 100755
--- a/Makefile.am
+++ b/Makefile.am
@@ -45,9 +45,14 @@
 endif
 
 if WANT_SYNC
-noinst_HEADERS += sync_manager.hpp
+noinst_HEADERS += \
+	sync_manager.hpp \
+	sync_watch.hpp
 sbin_PROGRAMS += phosphor-sync-software-manager
-phosphor_sync_software_manager_SOURCES = sync_manager_main.cpp
+phosphor_sync_software_manager_SOURCES = \
+	sync_manager.cpp \
+	sync_watch.cpp \
+	sync_manager_main.cpp
 phosphor_sync_software_manager_CXXFLAGS = $(generic_cxxflags)
 phosphor_sync_software_manager_LDFLAGS = $(generic_ldflags)
 endif
diff --git a/configure.ac b/configure.ac
index 65afe9a..1155cfe 100755
--- a/configure.ac
+++ b/configure.ac
@@ -109,6 +109,14 @@
 AS_IF([test "x$IMG_UPLOAD_DIR" == "x"], [IMG_UPLOAD_DIR="/tmp/images"])
 AC_DEFINE_UNQUOTED([IMG_UPLOAD_DIR], ["$IMG_UPLOAD_DIR"], [Directory where downloaded software images are placed])
 
+AC_ARG_VAR(SYNC_LIST_FILE_NAME, [The name of the sync list file])
+AS_IF([test "x$SYNC_LIST_FILE_NAME" == "x"], [SYNC_LIST_FILE_NAME="synclist"])
+AC_DEFINE_UNQUOTED([SYNC_LIST_FILE_NAME], ["$SYNC_LIST_FILE_NAME"], [The name of the sync list file])
+
+AC_ARG_VAR(SYNC_LIST_DIR_PATH, [The path to the sync list file directory])
+AS_IF([test "x$SYNC_LIST_DIR_PATH" == "x"], [SYNC_LIST_DIR_PATH="/etc/"])
+AC_DEFINE_UNQUOTED([SYNC_LIST_DIR_PATH], ["$SYNC_LIST_DIR_PATH"], [The path to the sync list file directory])
+
 AC_ARG_VAR(MANIFEST_FILE_NAME, [The name of the MANIFEST file])
 AS_IF([test "x$MANIFEST_FILE_NAME" == "x"], [MANIFEST_FILE_NAME="MANIFEST"])
 AC_DEFINE_UNQUOTED([MANIFEST_FILE_NAME], ["$MANIFEST_FILE_NAME"], [The name of the MANIFEST file])
diff --git a/sync_manager.cpp b/sync_manager.cpp
new file mode 100644
index 0000000..6a50711
--- /dev/null
+++ b/sync_manager.cpp
@@ -0,0 +1,17 @@
+#include "sync_manager.hpp"
+
+namespace phosphor
+{
+namespace software
+{
+namespace manager
+{
+
+int Sync::processEntry()
+{
+    return 0;
+}
+
+} // namespace manager
+} // namespace software
+} // namepsace phosphor
diff --git a/sync_manager.hpp b/sync_manager.hpp
index a48a806..a940461 100644
--- a/sync_manager.hpp
+++ b/sync_manager.hpp
@@ -21,6 +21,12 @@
     Sync(Sync&&) = default;
     Sync& operator=(Sync&&) = default;
     ~Sync() = default;
+
+    /**
+     * @brief Process requested file or directory.
+     * @param[out] result - 0 if successful.
+     */
+    int processEntry();
 };
 
 } // namespace manager
diff --git a/sync_manager_main.cpp b/sync_manager_main.cpp
index 09aea5c..46a2d95 100644
--- a/sync_manager_main.cpp
+++ b/sync_manager_main.cpp
@@ -1,15 +1,41 @@
+#include <exception>
+#include <phosphor-logging/log.hpp>
 #include <sdbusplus/bus.hpp>
 #include <sdbusplus/server/manager.hpp>
+#include <systemd/sd-event.h>
 #include "config.h"
 #include "sync_manager.hpp"
+#include "sync_watch.hpp"
 
 int main(int argc, char* argv[])
 {
     auto bus = sdbusplus::bus::new_default();
 
+    sd_event* loop = nullptr;
+    sd_event_default(&loop);
+
     sdbusplus::server::manager::manager objManager(bus, SOFTWARE_OBJPATH);
-    phosphor::software::manager::Sync syncManager();
-    bus.request_name(SYNC_BUSNAME);
+
+    try
+    {
+        phosphor::software::manager::Sync syncManager;
+        bus.request_name(SYNC_BUSNAME);
+
+        using namespace phosphor::software::manager;
+        phosphor::software::manager::SyncWatch watch(
+            *loop, std::bind(std::mem_fn(&Sync::processEntry), &syncManager));
+        bus.attach_event(loop, SD_EVENT_PRIORITY_NORMAL);
+        sd_event_loop(loop);
+    }
+    catch (std::exception& e)
+    {
+        using namespace phosphor::logging;
+        log<level::ERR>(e.what());
+        sd_event_unref(loop);
+        return -1;
+    }
+
+    sd_event_unref(loop);
 
     return 0;
 }
diff --git a/sync_watch.cpp b/sync_watch.cpp
new file mode 100644
index 0000000..0fc61d8
--- /dev/null
+++ b/sync_watch.cpp
@@ -0,0 +1,89 @@
+#include <experimental/filesystem>
+#include <fstream>
+#include <phosphor-logging/log.hpp>
+#include <sys/inotify.h>
+#include <unistd.h>
+#include "config.h"
+#include "sync_watch.hpp"
+
+namespace phosphor
+{
+namespace software
+{
+namespace manager
+{
+
+using namespace phosphor::logging;
+namespace fs = std::experimental::filesystem;
+
+SyncWatch::SyncWatch(sd_event& loop,
+                     std::function<int(fs::path&)> syncCallback) :
+    syncCallback(syncCallback)
+{
+    auto syncfile = fs::path(SYNC_LIST_DIR_PATH) / SYNC_LIST_FILE_NAME;
+    if (fs::exists(syncfile))
+    {
+        std::string line;
+        std::ifstream file(syncfile.c_str());
+        while (std::getline(file, line))
+        {
+            auto fd = inotify_init1(IN_NONBLOCK);
+            if (-1 == fd)
+            {
+                log<level::ERR>("inotify_init1 failed",
+                                entry("ERRNO=%d", errno),
+                                entry("FILENAME=%s", line.c_str()),
+                                entry("SYNCFILE=%s", syncfile.c_str()));
+                continue;
+            }
+
+            auto wd = inotify_add_watch(fd, line.c_str(), IN_CLOSE_WRITE);
+            if (-1 == wd)
+            {
+                log<level::ERR>("inotify_add_watch failed",
+                                entry("ERRNO=%d", errno),
+                                entry("FILENAME=%s", line.c_str()),
+                                entry("SYNCFILE=%s", syncfile.c_str()));
+                close(fd);
+                continue;
+            }
+
+            auto rc =
+                sd_event_add_io(&loop, nullptr, fd, EPOLLIN, callback, this);
+            if (0 > rc)
+            {
+                log<level::ERR>("failed to add to event loop",
+                                entry("RC=%d", rc),
+                                entry("FILENAME=%s", line.c_str()),
+                                entry("SYNCFILE=%s", syncfile.c_str()));
+                inotify_rm_watch(fd, wd);
+                close(fd);
+                continue;
+            }
+
+            fileMap[fd].insert(std::make_pair(wd, fs::path(line)));
+        }
+    }
+}
+
+SyncWatch::~SyncWatch()
+{
+    for (const auto& fd : fileMap)
+    {
+        for (const auto& wd : fd.second)
+        {
+            inotify_rm_watch(fd.first, wd.first);
+        }
+        close(fd.first);
+    }
+}
+
+int SyncWatch::callback(sd_event_source* s, int fd, uint32_t revents,
+                        void* userdata)
+{
+    return 0;
+}
+
+} // namespace manager
+} // namespace software
+} // namespace phosphor
diff --git a/sync_watch.hpp b/sync_watch.hpp
new file mode 100644
index 0000000..a7f8557
--- /dev/null
+++ b/sync_watch.hpp
@@ -0,0 +1,66 @@
+#pragma once
+
+#include <experimental/filesystem>
+#include <functional>
+#include <systemd/sd-event.h>
+
+namespace phosphor
+{
+namespace software
+{
+namespace manager
+{
+
+namespace fs = std::experimental::filesystem;
+
+/** @class SyncWatch
+ *
+ *  @brief Adds inotify watch on persistent files to be synced
+ *
+ *  The inotify watch is hooked up with sd-event, so that on call back,
+ *  appropriate actions related to syncing files can be taken.
+ */
+class SyncWatch
+{
+  public:
+    /** @brief ctor - hook inotify watch with sd-event
+     *
+     *  @param[in] loop - sd-event object
+     *  @param[in] syncCallback - The callback function for processing
+     *                            files
+     */
+    SyncWatch(sd_event& loop, std::function<int(fs::path&)> syncCallback);
+
+    SyncWatch(const SyncWatch&) = delete;
+    SyncWatch& operator=(const SyncWatch&) = delete;
+    SyncWatch(SyncWatch&&) = default;
+    SyncWatch& operator=(SyncWatch&&) = default;
+
+    /** @brief dtor - remove inotify watch and close fd's
+     */
+    ~SyncWatch();
+
+  private:
+    /** @brief sd-event callback
+     *
+     *  @param[in] s - event source, floating (unused) in our case
+     *  @param[in] fd - inotify fd
+     *  @param[in] revents - events that matched for fd
+     *  @param[in] userdata - pointer to SyncWatch object
+     *  @returns 0 on success, -1 on fail
+     */
+    static int callback(sd_event_source* s, int fd, uint32_t revents,
+                        void* userdata);
+
+    /** @brief Map of file descriptors, watch descriptors, and file paths */
+    using fd = int;
+    using wd = int;
+    std::map<fd, std::map<wd, fs::path>> fileMap;
+
+    /** @brief The callback function for processing the inotify event */
+    std::function<int(fs::path&)> syncCallback;
+};
+
+} // namespace manager
+} // namespace software
+} // namespace phosphor