image manager: add inotify watch

Add an inotify watch to the known software image location.

Hook the inotify fd with sd-event, so that on callback, version d-bus
objects can be created based on the newly added software image.

Resolves openbmc/openbmc#1444.

Change-Id: I5c460f820c8d3a851b8ddc969f26d38870c36991
Signed-off-by: Deepak Kodihalli <dkodihal@in.ibm.com>
diff --git a/Makefile.am b/Makefile.am
index 1e863d7..e4be065 100755
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,19 +1,34 @@
 AM_DEFAULT_SOURCE_EXT = .cpp
 
+# Build these headers, don't install them
+noinst_HEADERS = \
+	version_software_manager.hpp \
+	download_manager.hpp \
+	watch.hpp
+
 sbin_PROGRAMS = \
 	phosphor-version-software-manager \
 	phosphor-download-manager
 
 phosphor_version_software_manager_SOURCES = \
 	version_software_manager.cpp \
-	image_manager_main.cpp
+	image_manager_main.cpp \
+	watch.cpp
 
 phosphor_download_manager_SOURCES = \
 	download_manager.cpp \
 	download_manager_main.cpp
 
-generic_cxxflags = $(SYSTEMD_CFLAGS) $(PHOSPHOR_DBUS_INTERFACES_CFLAGS) $(SDBUSPLUS_CFLAGS)
-generic_ldflags = $(SYSTEMD_LIBS) $(PHOSPHOR_DBUS_INTERFACES_LIBS) $(SDBUSPLUS_LIBS)
+generic_cxxflags = \
+	$(SYSTEMD_CFLAGS) \
+	$(PHOSPHOR_DBUS_INTERFACES_CFLAGS) \
+	$(SDBUSPLUS_CFLAGS) \
+	$(PHOSPHOR_LOGGING_CFLAGS)
+generic_ldflags = \
+	$(SYSTEMD_LIBS) \
+	$(PHOSPHOR_DBUS_INTERFACES_LIBS) \
+	$(SDBUSPLUS_LIBS) \
+	$(PHOSPHOR_LOGGING_LIBS)
 
 phosphor_version_software_manager_CXXFLAGS = $(generic_cxxflags)
 phosphor_version_software_manager_LDFLAGS = $(generic_ldflags)
diff --git a/configure.ac b/configure.ac
index af227ae..1928422 100755
--- a/configure.ac
+++ b/configure.ac
@@ -16,6 +16,8 @@
     AC_MSG_ERROR(["Requires phosphor-dbus-interfaces package."]))
 PKG_CHECK_MODULES([SDBUSPLUS], [sdbusplus],,
     AC_MSG_ERROR(["Requires sdbusplus package."]))
+PKG_CHECK_MODULES([PHOSPHOR_LOGGING], [phosphor-logging],,\
+    AC_MSG_ERROR(["Requires phosphor-logging package."]))
 
 # Checks for library functions
 LT_INIT # Required for systemd linking
@@ -37,6 +39,10 @@
     [DOWNLOAD_BUSNAME="xyz.openbmc_project.Software.Download"])
 AC_DEFINE_UNQUOTED([DOWNLOAD_BUSNAME], ["$DOWNLOAD_BUSNAME"], [The DBus busname to own])
 
+AC_ARG_VAR(IMG_UPLOAD_DIR, [Directory where downloaded software images are placed])
+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])
+
 # Check for header files.
 AC_CHECK_HEADER(systemd/sd-bus.h, ,[AC_MSG_ERROR([Could not find systemd/sd-bus.h...systemd developement package required])])
 AC_CHECK_HEADER(sdbusplus/server.hpp, ,[AC_MSG_ERROR([Could not find sdbusplus/server.hpp...openbmc/sdbusplus package required])])
diff --git a/image_manager_main.cpp b/image_manager_main.cpp
index bd41b63..66f31f6 100644
--- a/image_manager_main.cpp
+++ b/image_manager_main.cpp
@@ -1,27 +1,38 @@
-#include <iostream>
 #include <cstdlib>
 #include <exception>
 #include <sdbusplus/bus.hpp>
+#include <phosphor-logging/log.hpp>
 #include "config.h"
 #include "version_software_manager.hpp"
+#include "watch.hpp"
 
 int main(int argc, char* argv[])
 {
     auto bus = sdbusplus::bus::new_default();
 
-    // Add sdbusplus ObjectManager.
+    sd_event* loop = nullptr;
+    sd_event_default(&loop);
+
     sdbusplus::server::manager::manager objManager(bus,
             SOFTWARE_OBJPATH);
-
     phosphor::software::manager::Version manager(bus,
             SOFTWARE_OBJPATH);
-
     bus.request_name(VERSION_BUSNAME);
 
-    while (true)
+    try
     {
-        bus.process_discard();
-        bus.wait();
+        phosphor::software::manager::Watch watch(loop);
+        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());
+        return -1;
+    }
+
+    sd_event_unref(loop);
+
     return 0;
 }
diff --git a/watch.cpp b/watch.cpp
new file mode 100644
index 0000000..a520dd2
--- /dev/null
+++ b/watch.cpp
@@ -0,0 +1,104 @@
+#include <stdexcept>
+#include <cstddef>
+#include <cstring>
+#include <string>
+#include <sys/inotify.h>
+#include <unistd.h>
+#include <phosphor-logging/log.hpp>
+#include "config.h"
+#include "watch.hpp"
+
+namespace phosphor
+{
+namespace software
+{
+namespace manager
+{
+
+using namespace std::string_literals;
+
+Watch::Watch(sd_event* loop)
+{
+    fd = inotify_init1(IN_NONBLOCK);
+    if (-1 == fd)
+    {
+        // Store a copy of errno, because the string creation below will
+        // invalidate errno due to one more system calls.
+        auto error = errno;
+        throw std::runtime_error(
+            "inotify_init1 failed, errno="s + std::strerror(error));
+    }
+
+    wd = inotify_add_watch(fd, IMG_UPLOAD_DIR, IN_CREATE);
+    if (-1 == wd)
+    {
+        auto error = errno;
+        close(fd);
+        throw std::runtime_error(
+            "inotify_add_watch failed, errno="s + std::strerror(error));
+    }
+
+    auto rc = sd_event_add_io(loop,
+                              nullptr,
+                              fd,
+                              EPOLLIN,
+                              callback,
+                              this);
+    if (0 > rc)
+    {
+        throw std::runtime_error(
+            "failed to add to event loop, rc="s + std::strerror(-rc));
+    }
+}
+
+Watch::~Watch()
+{
+    if ((-1 != fd) && (-1 != wd))
+    {
+        inotify_rm_watch(fd, wd);
+        close(fd);
+    }
+}
+
+int Watch::callback(sd_event_source* s,
+                    int fd,
+                    uint32_t revents,
+                    void* userdata)
+{
+    if (!(revents & EPOLLIN))
+    {
+        return 0;
+    }
+
+    constexpr auto maxBytes = 1024;
+    uint8_t buffer[maxBytes];
+    auto bytes = read(fd, buffer, maxBytes);
+    if (0 > bytes)
+    {
+        auto error = errno;
+        throw std::runtime_error(
+            "failed to read inotify event, errno="s + std::strerror(error));
+    }
+
+    auto offset = 0;
+    while (offset < bytes)
+    {
+        auto event = reinterpret_cast<inotify_event*>(&buffer[offset]);
+        if ((event->mask & IN_CREATE) && !(event->mask & IN_ISDIR))
+        {
+            // TODO: openbmc/openbmc#1352 - invoke method (which takes uploaded
+            // filepath) to construct software version d-bus objects.
+            // For now, log the image filename.
+            using namespace phosphor::logging;
+            log<level::INFO>(event->name);
+        }
+
+        offset += offsetof(inotify_event, name) + event->len;
+    }
+
+    return 0;
+}
+
+} // namespace manager
+} // namespace software
+} // namespace phosphor
diff --git a/watch.hpp b/watch.hpp
new file mode 100644
index 0000000..c125ef2
--- /dev/null
+++ b/watch.hpp
@@ -0,0 +1,60 @@
+#pragma once
+
+#include <systemd/sd-event.h>
+
+namespace phosphor
+{
+namespace software
+{
+namespace manager
+{
+
+/** @class Watch
+ *
+ *  @brief Adds inotify watch on software image upload directory
+ *
+ *  The inotify watch is hooked up with sd-event, so that on call back,
+ *  appropriate actions related to a software image upload can be taken.
+ */
+class Watch
+{
+    public:
+        /** @brief ctor - hook inotify watch with sd-event
+         *
+         *  @param[in] loop - sd-event object
+         */
+        Watch(sd_event* loop);
+
+        Watch(const Watch&) = delete;
+        Watch& operator=(const Watch&) = delete;
+        Watch(Watch&&) = default;
+        Watch& operator=(Watch&&) = default;
+
+        /** @brief dtor - remove inotify watch and close fd's
+         */
+        ~Watch();
+
+    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 Watch object
+         *  @returns 0 on success, -1 on fail
+         */
+        static int callback(sd_event_source* s,
+                            int fd,
+                            uint32_t revents,
+                            void* userdata);
+
+        /** @brief image upload directory watch descriptor */
+        int wd = -1;
+
+        /** @brief inotify file descriptor */
+        int fd = -1;
+};
+
+} // namespace manager
+} // namespace software
+} // namespace phosphor