Initial commit for the Dump core file monitor infrastructure.

Add an inotify watch to the known core dump location.

Resolves openbmc/openbmc#1504

Change-Id: I0093c9f601d82917ca2efb53a4d47ed98f0eaa7f
Signed-off-by: Jayanth Othayoth <ojayanth@in.ibm.com>
diff --git a/Makefile.am b/Makefile.am
index 6ff8965..30d5a88 100755
--- a/Makefile.am
+++ b/Makefile.am
@@ -2,35 +2,54 @@
 
 # Build these headers, don't install them
 noinst_HEADERS = \
-	dump_entry.hpp
+	dump_entry.hpp \
+	dump_watch.hpp
 
 nobase_nodist_include_HEADERS = \
+	xyz/openbmc_project/Dump/Monitor/error.hpp \
 	xyz/openbmc_project/Dump/Internal/Create/server.hpp
 
 sbin_PROGRAMS = \
-	phosphor-dump-manager
+	phosphor-dump-manager \
+	phosphor-dump-monitor
 
 phosphor_dump_manager_SOURCES = \
 	dump_manager_main.cpp \
 	dump_entry.cpp \
 	xyz/openbmc_project/Dump/Internal/Create/server.cpp
 
+phosphor_dump_monitor_SOURCES = \
+	dump_watch_main.cpp \
+	dump_watch.cpp \
+	xyz/openbmc_project/Dump/Monitor/error.cpp
+
 phosphor_dump_manager_CXXFLAGS = \
 	$(PHOSPHOR_DBUS_INTERFACES_CFLAGS) \
-	$(SDBUSPLUS_CFLAGS)
+	$(SDBUSPLUS_CFLAGS) \
+	$(PHOSPHOR_LOGGING_CFLAGS)
+
+phosphor_dump_monitor_CXXFLAGS = \
+	$(PHOSPHOR_DBUS_INTERFACES_CFLAGS) \
+	$(PHOSPHOR_LOGGING_CFLAGS)
 
 phosphor_dump_manager_LDADD = \
 	$(PHOSPHOR_DBUS_INTERFACES_LIBS) \
-	$(SDBUSPLUS_LIBS)
+	$(SDBUSPLUS_LIBS) \
+	$(PHOSPHOR_LOGGING_LIBS)
+
+phosphor_dump_monitor_LDADD = \
+	$(PHOSPHOR_DBUS_INTERFACES_LIBS) \
+	$(PHOSPHOR_LOGGING_LIBS) \
+	-lstdc++fs
 
 # Be sure to build needed files before compiling
 BUILT_SOURCES = \
-        xyz/openbmc_project/Dump/Internal/Create/server.cpp \
-        xyz/openbmc_project/Dump/Internal/Create/server.hpp
+	xyz/openbmc_project/Dump/Internal/Create/server.cpp \
+	xyz/openbmc_project/Dump/Internal/Create/server.hpp \
+	xyz/openbmc_project/Dump/Monitor/error.cpp \
+	xyz/openbmc_project/Dump/Monitor/error.hpp
 
-CLEANFILES = \
-        xyz/openbmc_project/Dump/Internal/Create/server.cpp \
-        xyz/openbmc_project/Dump/Internal/Create/server.hpp
+CLEANFILES=${BUILT_SOURCES}
 
 xyz/openbmc_project/Dump/Internal/Create/server.cpp: \
 xyz/openbmc_project/Dump/Internal/Create.interface.yaml \
@@ -44,3 +63,12 @@
 	@mkdir -p `dirname $@`
 	$(SDBUSPLUSPLUS) -r $(srcdir) interface server-header \
 xyz.openbmc_project.Dump.Internal.Create > $@
+
+xyz/openbmc_project/Dump/Monitor/error.hpp: \
+${top_srcdir}/xyz/openbmc_project/Dump/Monitor.errors.yaml
+	@mkdir -p `dirname $@`
+	$(SDBUSPLUSPLUS) -r $(srcdir) error exception-header xyz.openbmc_project.Dump.Monitor > $@
+
+xyz/openbmc_project/Dump/Monitor/error.cpp: ${top_srcdir}/xyz/openbmc_project/Dump/Monitor.errors.yaml
+	@mkdir -p `dirname $@`
+	$(SDBUSPLUSPLUS) -r $(srcdir) error exception-cpp xyz.openbmc_project.Dump.Monitor > $@
diff --git a/configure.ac b/configure.ac
index 9cf8ba2..fb1f382 100644
--- a/configure.ac
+++ b/configure.ac
@@ -13,10 +13,19 @@
 AC_CHECK_PROG([DIRNAME], dirname, dirname)
 
 # Check for libraries
+PKG_CHECK_MODULES([SYSTEMD], [libsystemd >= 221],,\
+    AC_MSG_ERROR(["systemd required and not found."]))
 PKG_CHECK_MODULES([PHOSPHOR_DBUS_INTERFACES], [phosphor-dbus-interfaces],,\
     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."]))
+
+# Check for sdbus++
+AC_PATH_PROG([SDBUSPLUSPLUS], [sdbus++])
+AS_IF([test "x$SDBUSPLUSPLUS" == "x"],
+    AC_MSG_ERROR(["Requires sdbus++"]))
 
 # Check for sdbus++
 AC_PATH_PROG([SDBUSPLUSPLUS], [sdbus++])
@@ -38,5 +47,9 @@
 AS_IF([test "x$DUMP_OBJPATH" == "x"], [DUMP_OBJPATH="/xyz/openbmc_project/dump"])
 AC_DEFINE_UNQUOTED([DUMP_OBJPATH], ["$DUMP_OBJPATH"], [The dump manager Dbus root])
 
+AC_ARG_VAR(CORE_FILE_DIR, [Directory where core dumps are placed])
+AS_IF([test "x$CORE_FILE_DIR" == "x"], [CORE_FILE_DIR="/var/lib/systemd/coredump"])
+AC_DEFINE_UNQUOTED([CORE_FILE_DIR], ["$CORE_FILE_DIR"], [Directory where core dumps are placed])
+
 AC_CONFIG_FILES([Makefile])
 AC_OUTPUT
diff --git a/dump_watch.cpp b/dump_watch.cpp
new file mode 100644
index 0000000..6208466
--- /dev/null
+++ b/dump_watch.cpp
@@ -0,0 +1,159 @@
+#include <stdexcept>
+#include <cstddef>
+#include <cstring>
+#include <string>
+#include <vector>
+#include <sys/inotify.h>
+#include <unistd.h>
+#include "config.h"
+#include "dump_watch.hpp"
+#include <experimental/filesystem>
+#include <phosphor-logging/log.hpp>
+#include <phosphor-logging/elog.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include "elog-errors.hpp"
+#include <xyz/openbmc_project/Dump/Monitor/error.hpp>
+#include "xyz/openbmc_project/Common/error.hpp"
+
+namespace phosphor
+{
+namespace dump
+{
+
+CustomFd::~CustomFd()
+{
+    if (fd >= 0)
+    {
+        close(fd);
+    }
+}
+
+namespace inotify
+{
+
+using namespace std::string_literals;
+using namespace phosphor::logging;
+using namespace sdbusplus::xyz::openbmc_project::Dump::Monitor::Error;
+using namespace sdbusplus::xyz::openbmc_project::Common::Error;
+namespace fs = std::experimental::filesystem;
+
+Watch::~Watch()
+{
+    if ((fd() >= 0) && (wd >= 0))
+    {
+        inotify_rm_watch(fd(), wd);
+    }
+}
+
+Watch::Watch(sd_event* loop):
+    fd(inotifyInit())
+{
+    // Check if CORE DIR exists.
+    fs::path coreDirPath(CORE_FILE_DIR);
+    if (!fs::is_directory(coreDirPath))
+    {
+        namespace metadata = xyz::openbmc_project::Dump::Monitor;
+        elog<InvalidCorePath>(metadata::InvalidCorePath::PATH(CORE_FILE_DIR));
+    }
+
+    //Check for existing coredumps, Dump manager should handle this before
+    //starting the core monitoring.
+    //This is to handle coredumps created prior to Dump applications start,
+    //or missing coredump reporting due to Dump application crashs.
+    if (!fs::is_empty(coreDirPath))
+    {
+        //TODO openbmc/openbmc#1510 Enable Dump collection function here.
+    }
+
+    wd = inotify_add_watch(fd(), CORE_FILE_DIR, IN_CLOSE_WRITE);
+    if (-1 == wd)
+    {
+        auto error = errno;
+        log<level::ERR>("Error occurred during the inotify_add_watch call",
+                        entry("ERRNO=%s", strerror(error)));
+        elog<InternalFailure>();
+    }
+
+    auto rc = sd_event_add_io(loop,
+                              nullptr,
+                              fd(),
+                              EPOLLIN,
+                              callback,
+                              this);
+    if (0 > rc)
+    {
+        // Failed to add to event loop
+        auto error = errno;
+        log<level::ERR>("Error occurred during the sd_event_add_io call",
+                        entry("ERRNO=%s", strerror(error)));
+        elog<InternalFailure>();
+    }
+}
+
+int Watch::inotifyInit()
+{
+    auto fd = inotify_init1(IN_NONBLOCK);
+
+    if (-1 == fd)
+    {
+        auto error = errno;
+        log<level::ERR>("Error occurred during the inotify_init1",
+                        entry("ERRNO=%s", strerror(error)));
+        elog<InternalFailure>();
+    }
+
+    return fd;
+}
+
+int Watch::callback(sd_event_source* s,
+                    int fd,
+                    uint32_t revents,
+                    void* userdata)
+{
+    if (!(revents & EPOLLIN))
+    {
+        return 0;
+    }
+
+    //Maximum inotify events supported in the buffer
+    constexpr auto maxBytes = sizeof(struct inotify_event) + NAME_MAX + 1;
+    uint8_t buffer[maxBytes];
+
+    auto bytes = read(fd, buffer, maxBytes);
+    if (0 > bytes)
+    {
+        //Failed to read inotify event
+        //Report error and return
+        auto error = errno;
+        log<level::ERR>("Error occurred during the read",
+                        entry("ERRNO=%s", strerror(error)));
+        report<InternalFailure>();
+        return 0;
+    }
+
+    auto offset = 0;
+
+    std::vector<fs::path> corePaths;
+
+    while (offset < bytes)
+    {
+        auto event = reinterpret_cast<inotify_event*>(&buffer[offset]);
+        if ((event->mask & IN_CLOSE_WRITE) && !(event->mask & IN_ISDIR))
+        {
+            corePaths.emplace_back(fs::path(CORE_FILE_DIR) / event->name);
+        }
+
+        offset += offsetof(inotify_event, name) + event->len;
+    }
+
+    // Generate new BMC Dump( Core dump Type) incase valid cores
+    if (!corePaths.empty())
+    {
+        //TODO openbmc/openbmc#1510 Enable Dump collection function here
+    }
+    return 0;
+}
+
+} // namespace inotify
+} // namespace dump
+} // namespace phosphor
diff --git a/dump_watch.hpp b/dump_watch.hpp
new file mode 100644
index 0000000..3f7bceb
--- /dev/null
+++ b/dump_watch.hpp
@@ -0,0 +1,95 @@
+#pragma once
+
+#include <map>
+#include <memory>
+#include <systemd/sd-event.h>
+#include <unistd.h>
+
+namespace phosphor
+{
+namespace dump
+{
+
+/** @struct CustomFd
+ *
+ *  RAII wrapper for file descriptor.
+ */
+struct CustomFd
+{
+    private:
+        /** @brief File descriptor */
+        int fd = -1;
+
+    public:
+        CustomFd(const CustomFd&) = delete;
+        CustomFd& operator=(const CustomFd&) = delete;
+        CustomFd(CustomFd&&) = delete;
+        CustomFd& operator=(CustomFd&&) = delete;
+
+        /** @brief Saves File descriptor and uses it to do file operation
+          *
+          *  @param[in] fd - File descriptor
+          */
+        CustomFd(int fd) : fd(fd) {}
+
+        ~CustomFd();
+
+        int operator()() const
+        {
+            return fd;
+        }
+};
+
+namespace inotify
+{
+/** @class Watch
+ *
+ *  @brief Adds inotify watch on core file directories.
+ *
+ *  The inotify watch is hooked up with sd-event, so that on call back,
+ *  appropriate actions are taken to collect the core files.
+ */
+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);
+
+        /**  initialize an inotify instance and returns file descriptor */
+        int inotifyInit();
+
+        /** @brief core file directory watch descriptor */
+        int wd = -1;
+
+        /** @brief file descriptor manager */
+        CustomFd fd;
+};
+
+} // namespace inotify
+} // namespace dump
+} // namespace phosphor
diff --git a/dump_watch_main.cpp b/dump_watch_main.cpp
new file mode 100644
index 0000000..6c7d98b
--- /dev/null
+++ b/dump_watch_main.cpp
@@ -0,0 +1,43 @@
+#include <exception>
+#include "dump_watch.hpp"
+#include <phosphor-logging/log.hpp>
+#include <phosphor-logging/elog.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include "elog-errors.hpp"
+#include <xyz/openbmc_project/Dump/Monitor/error.hpp>
+#include "xyz/openbmc_project/Common/error.hpp"
+
+int main(int argc, char* argv[])
+{
+    sd_event* loop = nullptr;
+    sd_event_default(&loop);
+
+    using namespace phosphor::logging;
+
+    using InvalidCorePath =
+        sdbusplus::xyz::openbmc_project::Dump::Monitor::Error::InvalidCorePath;
+    using InternalFailure =
+        sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
+
+    try
+    {
+        phosphor::dump::inotify::Watch watch(loop);
+        sd_event_loop(loop);
+    }
+
+    catch (InvalidCorePath& e)
+    {
+        commit<InvalidCorePath>();
+        return -1;
+    }
+
+    catch (InternalFailure& e)
+    {
+        commit<InternalFailure>();
+        return -1;
+    }
+
+    sd_event_unref(loop);
+
+    return 0;
+}
diff --git a/elog-errors.hpp b/elog-errors.hpp
new file mode 100644
index 0000000..f5c6466
--- /dev/null
+++ b/elog-errors.hpp
@@ -0,0 +1,89 @@
+// This file was autogenerated.  Do not edit!
+// See elog-gen.py for more details
+#pragma once
+
+#include <string>
+#include <tuple>
+#include <type_traits>
+#include <sdbusplus/exception.hpp>
+#include <phosphor-logging/log.hpp>
+#include <phosphor-logging/elog.hpp>
+
+namespace sdbusplus
+{
+namespace xyz
+{
+namespace openbmc_project
+{
+namespace Dump
+{
+namespace Monitor
+{
+namespace Error
+{
+    struct InvalidCorePath;
+} // namespace Error
+} // namespace Monitor
+} // namespace Dump
+} // namespace openbmc_project
+} // namespace xyz
+} // namespace sdbusplus
+
+
+namespace phosphor
+{
+
+namespace logging
+{
+
+namespace xyz
+{
+namespace openbmc_project
+{
+namespace Dump
+{
+namespace Monitor
+{
+namespace _InvalidCorePath
+{
+
+struct PATH
+{
+    static constexpr auto str = "PATH=%s";
+    static constexpr auto str_short = "PATH";
+    using type = std::tuple<std::decay_t<decltype(str)>,const char*>;
+    explicit constexpr PATH(const char* a) : _entry(entry(str, a)) {};
+    type _entry;
+};
+
+}  // namespace _InvalidCorePath
+
+struct InvalidCorePath
+{
+    static constexpr auto L = level::ERR;
+    using PATH = _InvalidCorePath::PATH;
+    using metadata_types = std::tuple<PATH>;
+
+};
+
+} // namespace Monitor
+} // namespace Dump
+} // namespace openbmc_project
+} // namespace xyz
+
+
+namespace details
+{
+
+template <>
+struct map_exception_type<sdbusplus::xyz::openbmc_project::Dump::Monitor::Error::InvalidCorePath>
+{
+    using type = xyz::openbmc_project::Dump::Monitor::InvalidCorePath;
+};
+
+}
+
+
+} // namespace logging
+
+} // namespace phosphor
diff --git a/xyz/openbmc_project/Dump/Monitor.errors.yaml b/xyz/openbmc_project/Dump/Monitor.errors.yaml
new file mode 100644
index 0000000..7a2478c
--- /dev/null
+++ b/xyz/openbmc_project/Dump/Monitor.errors.yaml
@@ -0,0 +1,2 @@
+- name: InvalidCorePath
+  description: Invalid core directory path
diff --git a/xyz/openbmc_project/Dump/Monitor.metadata.yaml b/xyz/openbmc_project/Dump/Monitor.metadata.yaml
new file mode 100644
index 0000000..045a4ea
--- /dev/null
+++ b/xyz/openbmc_project/Dump/Monitor.metadata.yaml
@@ -0,0 +1,5 @@
+- name: InvalidCorePath
+  level: ERR
+  meta:
+    - str: "PATH=%s"
+      type: string