diff options
author | Vishwanatha Subbanna <vishwa@linux.vnet.ibm.com> | 2017-10-16 23:17:18 +0530 |
---|---|---|
committer | Vishwanatha Subbanna <vishwa@linux.vnet.ibm.com> | 2017-10-24 22:00:05 +0530 |
commit | ca4ce1b402f402edfe65f69eb6e6782b10cee473 (patch) | |
tree | df014a45a687df5a19b7c4fb04c7c860415133fc | |
parent | 9e9fdf960e6afce7c6c90d5b4c41d494b6a33167 (diff) | |
download | phosphor-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.am | 6 | ||||
-rw-r--r-- | test/Makefile.am | 8 | ||||
-rw-r--r-- | test/test_watch.cpp | 90 | ||||
-rw-r--r-- | watch.cpp | 139 | ||||
-rw-r--r-- | watch.hpp | 114 |
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 |