summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVishwanatha Subbanna <vishwa@linux.vnet.ibm.com>2017-10-16 23:17:18 +0530
committerVishwanatha Subbanna <vishwa@linux.vnet.ibm.com>2017-10-24 22:00:05 +0530
commitca4ce1b402f402edfe65f69eb6e6782b10cee473 (patch)
treedf014a45a687df5a19b7c4fb04c7c860415133fc
parent9e9fdf960e6afce7c6c90d5b4c41d494b6a33167 (diff)
downloadphosphor-networkd-ca4ce1b402f402edfe65f69eb6e6782b10cee473.tar.gz
phosphor-networkd-ca4ce1b402f402edfe65f69eb6e6782b10cee473.zip
Add inotify watch support
Added code to register for inotify events on the needed path and the user callback on events Change-Id: I90529f8e96fcbecfe0a1b943c3c435dab79222c3 Signed-off-by: Vishwanatha Subbanna <vishwa@linux.vnet.ibm.com>
-rw-r--r--Makefile.am6
-rw-r--r--test/Makefile.am8
-rw-r--r--test/test_watch.cpp90
-rw-r--r--watch.cpp139
-rw-r--r--watch.hpp114
5 files changed, 354 insertions, 3 deletions
diff --git a/Makefile.am b/Makefile.am
index e2f2fcf..3b91845 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -23,7 +23,8 @@ noinst_HEADERS = \
vlan_interface.hpp \
rtnetlink_server.hpp \
timer.hpp \
- dns_updater.hpp
+ dns_updater.hpp \
+ watch.hpp
phosphor_network_manager_SOURCES = \
ethernet_interface.cpp \
@@ -41,7 +42,8 @@ phosphor_network_manager_SOURCES = \
vlan_interface.cpp \
rtnetlink_server.cpp \
timer.cpp \
- dns_updater.cpp
+ dns_updater.cpp \
+ watch.cpp
CLEANFILES = \
xyz/openbmc_project/Network/VLAN/Create/server.cpp \
diff --git a/test/Makefile.am b/test/Makefile.am
index 5c70fbe..5a1e712 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -2,7 +2,7 @@ AM_CPPFLAGS = -I${top_srcdir} -I${top_builddir}
TESTS = $(check_PROGRAMS)
-check_PROGRAMS = test test_dns_updater
+check_PROGRAMS = test test_dns_updater test_watch
test_SOURCES = \
test_util.cpp \
@@ -13,6 +13,7 @@ test_SOURCES = \
test_vlan_interface.cpp
test_dns_updater_SOURCES = test_dns_updater.cpp
+test_watch_SOURCES = test_watch.cpp
generic_cpp_flags = -Igtest $(GTEST_CPPFLAGS) $(AM_CPPFLAGS)
@@ -37,6 +38,10 @@ test_dns_updater_CPPFLAGS = ${generic_cpp_flags}
test_dns_updater_CXXFLAGS = ${generic_cxx_flags}
test_dns_updater_LDFLAGS = ${generic_ld_flags}
+test_watch_CPPFLAGS = ${generic_cpp_flags}
+test_watch_CXXFLAGS = ${generic_cxx_flags}
+test_watch_LDFLAGS = ${generic_ld_flags}
+
test_LDADD = $(top_builddir)/ethernet_interface.o \
$(top_builddir)/network_manager.o \
$(top_builddir)/network_config.o \
@@ -52,3 +57,4 @@ test_LDADD = $(top_builddir)/ethernet_interface.o \
$(top_builddir)/xyz/openbmc_project/Network/IP/Create/phosphor_network_manager-server.o
test_dns_updater_LDADD = $(top_builddir)/dns_updater.o
+test_watch_LDADD = $(top_builddir)/watch.o
diff --git a/test/test_watch.cpp b/test/test_watch.cpp
new file mode 100644
index 0000000..dac43b3
--- /dev/null
+++ b/test/test_watch.cpp
@@ -0,0 +1,90 @@
+#include "watch.hpp"
+#include "types.hpp"
+
+#include <gtest/gtest.h>
+
+#include <fstream>
+#include <experimental/filesystem>
+
+static constexpr auto TRIGGER_FILE = "/tmp/netif_state";
+
+namespace fs = std::experimental::filesystem;
+
+class WatchTest : public ::testing::Test
+{
+ public:
+ // systemd event handler
+ sd_event* events;
+
+ // Need this so that events can be initialized.
+ int rc;
+
+ // Gets called as part of each TEST_F construction
+ WatchTest()
+ : rc(sd_event_default(&events)),
+ eventPtr(events)
+ {
+ // Create a file containing DNS entries like in netif/state
+ std::ofstream file(TRIGGER_FILE);
+ file << "";
+
+ // Check for successful creation of
+ // event handler
+ EXPECT_GE(rc, 0);
+ }
+
+ // Gets called as part of each TEST_F destruction
+ ~WatchTest()
+ {
+ if (fs::exists(TRIGGER_FILE))
+ {
+ fs::remove(TRIGGER_FILE);
+ }
+ }
+
+ // unique_ptr for sd_event
+ phosphor::network::EventPtr eventPtr;
+
+ // Count of callback invocation
+ int count = 0;
+
+ // This is supposed to get hit twice
+ // Once at the beginning to see if there is anything
+ // and the second time when the data is fired.
+ void callBackHandler(const fs::path& file)
+ {
+ count++;
+
+ // Expect that the file is what we wanted
+ EXPECT_EQ(file, TRIGGER_FILE);
+ }
+};
+
+/** @brief Makes sure that the inotify event is fired
+ */
+TEST_F(WatchTest, validateEventNotification)
+{
+ // Create a watch object and register the handler
+ phosphor::network::inotify::Watch watch(eventPtr, TRIGGER_FILE,
+ std::bind(&WatchTest::callBackHandler, this,
+ std::placeholders::_1));
+
+ // Reading the event post subscription
+ callBackHandler(TRIGGER_FILE);
+
+ // Callback function must have hit by now
+ EXPECT_EQ(1, count);
+
+ // Make a run and see that no changes
+ sd_event_run(eventPtr.get(), 10);
+ EXPECT_EQ(1, count);
+
+ // Pump the data and get notification
+ {
+ std::ofstream file(TRIGGER_FILE);
+ file <<"DNS=1.2.3.4\n";
+ }
+
+ sd_event_run(eventPtr.get(), 10);
+ EXPECT_EQ(2, count);
+}
diff --git a/watch.cpp b/watch.cpp
new file mode 100644
index 0000000..3cacefd
--- /dev/null
+++ b/watch.cpp
@@ -0,0 +1,139 @@
+#include "watch.hpp"
+
+#include <xyz/openbmc_project/Common/error.hpp>
+#include <phosphor-logging/log.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+
+#include <sys/inotify.h>
+#include <errno.h>
+
+namespace phosphor
+{
+namespace network
+{
+namespace inotify
+{
+
+using namespace phosphor::logging;
+using namespace sdbusplus::xyz::openbmc_project::Common::Error;
+
+Watch::Watch(phosphor::network::EventPtr& eventPtr,
+ fs::path path,
+ UserCallBack userFunc,
+ int flags,
+ uint32_t mask,
+ uint32_t events) :
+ path(path),
+ userFunc(userFunc),
+ flags(flags),
+ mask(mask),
+ events(events),
+ fd(inotifyInit())
+{
+ // Check if watch file exists
+ // This is supposed to be there always
+ if (!fs::is_regular_file(path))
+ {
+ log<level::ERR>("Watch file doesn't exist",
+ entry("FILE=%s", path.c_str()));
+ elog<InternalFailure>();
+ }
+
+ auto dirPath = path.parent_path();
+ wd = inotify_add_watch(fd(), dirPath.c_str(), mask);
+ if (wd == -1)
+ {
+ log<level::ERR>("Error from inotify_add_watch",
+ entry("ERRNO=%d", errno));
+ elog<InternalFailure>();
+ }
+
+ // Register the fd with sd_event infrastructure and setup a
+ // callback handler to be invoked on events
+ auto rc = sd_event_add_io(eventPtr.get(),
+ nullptr,
+ fd(),
+ events,
+ Watch::processEvents,
+ this);
+ if (rc < 0)
+ {
+ // Failed to add to event loop
+ log<level::ERR>("Error registering with sd_event_add_io",
+ entry("RC=%d", rc));
+ elog<InternalFailure>();
+ }
+}
+
+int Watch::inotifyInit()
+{
+ auto fd = inotify_init1(flags);
+ if (fd < 0)
+ {
+ log<level::ERR>("Error from inotify_init1",
+ entry("ERRNO=%d", errno));
+ elog<InternalFailure>();
+ }
+ return fd;
+}
+
+int Watch::processEvents(sd_event_source* eventSource,
+ int fd,
+ uint32_t retEvents,
+ void* userData)
+{
+ auto watch = static_cast<Watch*>(userData);
+
+ // Not the ones we are interested in
+ if (!(retEvents & watch->events))
+ {
+ return 0;
+ }
+
+ // Buffer size to be used while reading events.
+ // per inotify(7), below number should be fine for reading
+ // at-least one event
+ constexpr auto maxBytes = sizeof(struct inotify_event) + NAME_MAX + 1;
+ uint8_t eventData[maxBytes]{};
+
+ auto bytes = read(fd, eventData, maxBytes);
+ if (bytes <= 0)
+ {
+ // Failed to read inotify event data
+ // Report error and return
+ log<level::ERR>("Error reading inotify event",
+ entry("ERRNO=%d", errno));
+ report<InternalFailure>();
+ return 0;
+ }
+
+ auto offset = 0;
+ auto stateFile = watch->path.filename();
+ while (offset < bytes)
+ {
+ auto event = reinterpret_cast<inotify_event*>(&eventData[offset]);
+
+ // Filter the interesting ones
+ auto mask = event->mask & watch->mask;
+ if (mask)
+ {
+ if((event->len > 0) &&
+ (strstr(event->name, stateFile.string().c_str())))
+ {
+ if (watch->userFunc)
+ {
+ watch->userFunc(watch->path);
+ }
+ // Found the event of interest
+ break;
+ }
+ }
+ // Move past this entry
+ offset += offsetof(inotify_event, name) + event->len;
+ }
+ return 0;
+}
+
+} // namespace inotify
+} // namespace network
+} // namespace phosphor
diff --git a/watch.hpp b/watch.hpp
new file mode 100644
index 0000000..0e20420
--- /dev/null
+++ b/watch.hpp
@@ -0,0 +1,114 @@
+#pragma once
+
+#include "util.hpp"
+#include "types.hpp"
+#include "dns_updater.hpp"
+
+#include <systemd/sd-event.h>
+
+#include <sys/inotify.h>
+#include <map>
+#include <functional>
+#include <experimental/filesystem>
+
+namespace phosphor
+{
+namespace network
+{
+namespace inotify
+{
+
+namespace fs = std::experimental::filesystem;
+
+// Auxiliary callback to be invoked on inotify events
+using UserCallBack = std::function<void(const std::string&)>;
+
+/** @class Watch
+ *
+ * @brief Adds inotify watch on directory
+ *
+ * @details Calls back user function on matching events
+ */
+class Watch
+{
+ public:
+ Watch() = delete;
+ Watch(const Watch&) = delete;
+ Watch& operator=(const Watch&) = delete;
+ Watch(Watch&&) = delete;
+ Watch& operator=(Watch&&) = delete;
+
+ /** @brief Hooks inotify watch with sd-event
+ *
+ * @param[in] eventPtr - Reference to sd_event wrapped in unique_ptr
+ * @param[in] path - File path to be watched
+ * @param[in] userFunc - User specific callback function on events
+ * @param[in] flags - Flags to be supplied to inotify
+ * @param[in] mask - Mask of events to be supplied to inotify
+ * @param[in] events - Events to be watched
+ */
+ Watch(phosphor::network::EventPtr& eventPtr,
+ const fs::path path,
+ UserCallBack userFunc,
+ int flags = IN_NONBLOCK,
+ uint32_t mask = IN_CLOSE_WRITE,
+ uint32_t events = EPOLLIN);
+
+ /** @brief Remove inotify watch and close fd's */
+ ~Watch()
+ {
+ if ((fd() >= 0) && (wd >= 0))
+ {
+ inotify_rm_watch(fd(), wd);
+ }
+ }
+
+ private:
+ /** @brief Callback invoked when inotify event fires
+ *
+ * @details On a matching event, calls back into user supplied
+ * function if there is one registered
+ *
+ * @param[in] eventSource - Event source
+ * @param[in] fd - Inotify fd
+ * @param[in] retEvents - Events that matched for fd
+ * @param[in] userData - Pointer to Watch object
+ *
+ * @returns 0 on success, -1 on fail
+ */
+ static int processEvents(sd_event_source* eventSource,
+ int fd,
+ uint32_t retEvents,
+ void* userData);
+
+ /** @brief Initializes an inotify instance
+ *
+ * @return Descriptor on success, -1 on failure
+ */
+ int inotifyInit();
+
+ /** @brief File path to be watched */
+ const fs::path path;
+
+ /** @brief User callback function */
+ UserCallBack userFunc;
+
+ /** @brief Inotify flags */
+ int flags;
+
+ /** @brief Mask of events */
+ uint32_t mask;
+
+ /** @brief Events to be watched */
+ uint32_t events;
+
+ /** @brief Watch descriptor */
+ int wd = -1;
+
+ /** @brief File descriptor manager */
+ phosphor::Descriptor fd;
+};
+
+} // namespace inotify
+} // namespace network
+} // namespace phosphor
OpenPOWER on IntegriCloud