Implement the INI config parser
Parse the systemd.network file
to get the configuration parameter.
Change-Id: Ic9c15a46158d2f1c0948e6eca729663e87051491
Signed-off-by: Ratan Gupta <ratagupt@in.ibm.com>
diff --git a/Makefile.am b/Makefile.am
index 02ba4c2..6903a75 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -18,6 +18,7 @@
types.hpp \
util.hpp \
routing_table.hpp \
+ config_parser.hpp \
system_configuration.hpp
phosphor_network_manager_SOURCES = \
@@ -30,7 +31,8 @@
xyz/openbmc_project/Network/VLAN/Create/server.cpp \
xyz/openbmc_project/Network/IP/Create/server.cpp \
util.cpp \
- routing_table.cpp
+ routing_table.cpp \
+ config_parser.cpp
CLEANFILES = \
xyz/openbmc_project/Network/VLAN/Create/server.cpp \
diff --git a/config_parser.cpp b/config_parser.cpp
new file mode 100644
index 0000000..51ef55b
--- /dev/null
+++ b/config_parser.cpp
@@ -0,0 +1,165 @@
+#include "config_parser.hpp"
+#include "xyz/openbmc_project/Common/error.hpp"
+#include <phosphor-logging/log.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+
+#include <fstream>
+#include <string>
+#include <algorithm>
+#include <unordered_map>
+#include <regex>
+#include <list>
+
+namespace phosphor
+{
+namespace network
+{
+namespace config
+{
+
+using namespace phosphor::logging;
+using namespace sdbusplus::xyz::openbmc_project::Common::Error;
+
+Parser::Parser(const fs::path& filePath)
+{
+ setFile(filePath);
+}
+
+
+KeyValues Parser::getSection(const std::string& section)
+{
+ auto it = sections.find(section);
+ if (it == sections.end())
+ {
+ log<level::ERR>("ConfigParser: Section not found",
+ entry("SECTION=%s",section));
+ elog<InternalFailure>();
+ }
+ return it->second;
+}
+
+std::vector<std::string> Parser::getValues(const std::string& section,
+ const std::string& key)
+{
+ std::vector<std::string> values;
+ auto keyValues = getSection(section);
+ auto it = keyValues.find(key);
+ if (it == keyValues.end())
+ {
+ log<level::ERR>("ConfigParser: Key not found",
+ entry("KEY=%s",key));
+ elog<InternalFailure>();
+ }
+ for (; it != keyValues.end() && key == it->first; it++)
+ {
+ values.push_back(it->second);
+ }
+ return values;
+}
+
+
+bool Parser::isValueExist(const std::string& section, const std::string& key,
+ const std::string& value)
+{
+ try
+ {
+ auto values = getValues(section, key);
+ auto it = std::find(values.begin(), values.end(), value);
+ return it != std::end(values) ? true : false;
+ }
+ catch (InternalFailure& e)
+ {
+ commit<InternalFailure>();
+ }
+ return false;
+}
+
+void Parser::setValue(const std::string& section, const std::string& key,
+ const std::string& value)
+{
+ KeyValues values;
+ try
+ {
+ values = getSection(section);
+ }
+ catch (InternalFailure& e)
+ {
+ // don't commit the error.
+
+ }
+ values.emplace(key, value);
+ sections.emplace(section, values);
+}
+
+#if 0
+void Parser::print()
+{
+ for (auto section : sections)
+ {
+ std::cout << "[" << section.first << "]\n\n";
+ for (auto keyValue : section.second)
+ {
+ std::cout << keyValue.first << "=" << keyValue.second << "\n";
+ }
+ }
+}
+#endif
+
+void Parser::setFile(const fs::path& filePath)
+{
+ this->filePath = filePath;
+ std::fstream stream;
+ stream.open(filePath.string(), std::fstream::in);
+
+ if (!stream.is_open())
+ {
+ return;
+ }
+ //clear all the section data.
+ sections.clear();
+ parse(stream);
+ stream.close();
+ }
+
+void Parser::parse(std::istream& in)
+{
+ static const std::regex commentRegex
+ {
+ R"x(\s*[;#])x"
+ };
+ static const std::regex sectionRegex
+ {
+ R"x(\s*\[([^\]]+)\])x"
+ };
+ static const std::regex valueRegex
+ {
+ R"x(\s*(\S[^ \t=]*)\s*=\s*(\S+)\s*$)x"
+ };
+ std::string section;
+ std::smatch pieces;
+ for (std::string line; std::getline(in, line);)
+ {
+ if (line.empty() || std::regex_match(line, pieces, commentRegex))
+ {
+ // skip comment lines and blank lines
+ }
+ else if (std::regex_match(line, pieces, sectionRegex))
+ {
+ if (pieces.size() == 2)
+ {
+ section = pieces[1].str();
+ }
+ }
+ else if (std::regex_match(line, pieces, valueRegex))
+ {
+ if (pieces.size() == 3)
+ {
+ setValue(section, pieces[1].str(), pieces[2].str());
+ }
+ }
+ }
+}
+
+}//namespace config
+}//namespace network
+}//namespace phosphor
diff --git a/config_parser.hpp b/config_parser.hpp
new file mode 100644
index 0000000..bf5cb2d
--- /dev/null
+++ b/config_parser.hpp
@@ -0,0 +1,89 @@
+#pragma once
+
+#include <string>
+#include <map>
+#include <unordered_map>
+#include <vector>
+#include <experimental/filesystem>
+
+namespace phosphor
+{
+namespace network
+{
+namespace config
+{
+
+using Section = std::string;
+using KeyValues = std::multimap<std::string, std::string>;
+namespace fs = std::experimental::filesystem;
+
+class Parser
+{
+ public:
+
+ Parser() = default;
+
+ /** @brief Constructor
+ * @param[in] fileName - Absolute path of the file which will be parsed.
+ */
+
+ Parser(const fs::path& fileName);
+
+ /** @brief Get the values of the given key and section.
+ * @param[in] section - section name.
+ * @param[in] key - key to look for.
+ * @returns the values associated with the key.
+ */
+
+ std::vector<std::string> getValues(const std::string& section,
+ const std::string& key);
+
+ /** @brief Set the value of the given key and section.
+ * @param[in] section - section name.
+ * @param[in] key - key name.
+ * @param[in] value - value.
+ */
+
+ void setValue(const std::string& section, const std::string& key,
+ const std::string& value);
+
+
+ /** @brief Set the file name and parse it.
+ * @param[in] fileName - Absolute path of the file.
+ */
+
+ void setFile(const fs::path& fileName);
+
+ private:
+
+ /** @brief Parses the given file and fills the data.
+ * @param[in] stream - inputstream.
+ */
+
+ void parse(std::istream& stream);
+
+ /** @brief Get all the key values of the given section.
+ * @param[in] section - section name.
+ * @returns the map of the key and value.
+ */
+
+ KeyValues getSection(const std::string& section);
+
+ /** @brief checks that whether the value exist in the
+ * given section.
+ * @param[in] section - section name.
+ * @param[in] key - key name.
+ * @param[in] value - value.
+ * @returns true if exist otherwise false.
+ */
+
+ bool isValueExist(const std::string& section, const std::string& key,
+ const std::string& value);
+
+ std::unordered_map<Section, KeyValues> sections;
+ fs::path filePath;
+};
+
+}//namespace config
+}//namespce network
+}//namespace phosphor
diff --git a/test/Makefile.am b/test/Makefile.am
index 8f951bf..bf1a935 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -8,7 +8,8 @@
test_util.cpp \
mock_syscall.cpp \
test_network_manager.cpp \
- test_ethernet_interface.cpp
+ test_ethernet_interface.cpp \
+ test_config_parser.cpp
test_CPPFLAGS = -Igtest $(GTEST_CPPFLAGS) $(AM_CPPFLAGS)
@@ -32,5 +33,6 @@
$(top_builddir)/routing_table.cpp \
$(top_builddir)/util.cpp \
$(top_builddir)/system_configuration.cpp \
+ $(top_builddir)/config_parser.cpp \
$(top_builddir)/xyz/openbmc_project/Network/VLAN/Create/phosphor_network_manager-server.o \
$(top_builddir)/xyz/openbmc_project/Network/IP/Create/phosphor_network_manager-server.o
diff --git a/test/test_config_parser.cpp b/test/test_config_parser.cpp
new file mode 100644
index 0000000..a07390b
--- /dev/null
+++ b/test/test_config_parser.cpp
@@ -0,0 +1,98 @@
+#include <gtest/gtest.h>
+
+#include "config_parser.hpp"
+
+#include "xyz/openbmc_project/Common/error.hpp"
+#include <phosphor-logging/elog-errors.hpp>
+
+#include "config.h"
+#include <exception>
+#include <stdexcept>
+#include <fstream>
+
+namespace phosphor
+{
+namespace network
+{
+
+class TestConfigParser : public testing::Test
+{
+ public:
+ config::Parser parser;
+ TestConfigParser()
+ {
+ remove("/tmp/eth0.network");
+ std::ofstream filestream("/tmp/eth0.network");
+
+ filestream << "[Match]\nName=eth0\n" <<
+ "[Network]\nDHCP=true\n[DHCP]\nClientIdentifier= mac\n";
+ filestream.close();
+ parser.setFile("/tmp/eth0.network");
+ }
+
+ bool isValueFound(const std::vector<std::string>& values,
+ const std::string& expectedValue)
+ {
+ for (const auto& value : values)
+ {
+ if (expectedValue == value)
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+};
+
+TEST_F(TestConfigParser, ReadConfigDataFromFile)
+{
+ auto values = parser.getValues("Network", "DHCP");
+ std::string expectedValue = "true";
+ bool found = isValueFound(values, expectedValue);
+ EXPECT_EQ(found, true);
+
+ values = parser.getValues("DHCP", "ClientIdentifier");
+ expectedValue = "mac";
+ found = isValueFound(values, expectedValue);
+ EXPECT_EQ(found, true);
+
+ values = parser.getValues("Match", "Name");
+ expectedValue = "eth0";
+ found = isValueFound(values, expectedValue);
+ EXPECT_EQ(found, true);
+}
+
+TEST_F(TestConfigParser, SectionNotExist)
+{
+ using namespace sdbusplus::xyz::openbmc_project::Common::Error;
+ bool caughtException = false;
+ try
+ {
+ parser.getValues("abc", "ipaddress");
+ }
+ catch (const std::exception& e)
+ {
+ caughtException = true;
+ }
+ EXPECT_EQ(true, caughtException);
+}
+
+TEST_F(TestConfigParser, KeyNotFound)
+{
+ using namespace sdbusplus::xyz::openbmc_project::Common::Error;
+ bool caughtException = false;
+ try
+ {
+ parser.getValues("Network", "abc");
+ }
+ catch (const std::exception& e)
+ {
+ caughtException = true;
+ }
+ EXPECT_EQ(true, caughtException);
+ remove("/tmp/eth0.network");
+}
+
+}//namespace network
+}//namespace phosphor
+