Implement Set function for MAC address

Change-Id: I16992dda259246a66512792f06cbbb874e56a15d
Signed-off-by: Ratan Gupta <ratagupt@in.ibm.com>
diff --git a/ethernet_interface.cpp b/ethernet_interface.cpp
index c24a3e8..39c12cf 100644
--- a/ethernet_interface.cpp
+++ b/ethernet_interface.cpp
@@ -33,10 +33,6 @@
 using namespace phosphor::logging;
 using namespace sdbusplus::xyz::openbmc_project::Common::Error;
 
-constexpr auto MAC_ADDRESS_FORMAT = "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx";
-constexpr size_t SIZE_MAC = 18;
-constexpr size_t SIZE_BUFF = 512;
-
 EthernetInterface::EthernetInterface(sdbusplus::bus::bus& bus,
                                      const std::string& objPath,
                                      bool dhcpEnabled,
@@ -51,7 +47,7 @@
     std::replace(intfName.begin(), intfName.end(), '_', '.');
     interfaceName(intfName);
     EthernetInterfaceIntf::dHCPEnabled(dhcpEnabled);
-    mACAddress(getMACAddress(intfName));
+    MacAddressIntf::mACAddress(getMACAddress(intfName));
 
     // Emit deferred signal.
     if (emitSignal)
@@ -116,7 +112,7 @@
     if (dHCPEnabled())
     {
         log<level::INFO>("DHCP enabled on the interface"),
-                        entry("INTERFACE=%s",interfaceName());
+                        entry("INTERFACE=%s",interfaceName().c_str());
         return;
     }
 
@@ -200,7 +196,7 @@
         const std::string& interfaceName) const
 {
     struct ifreq ifr{};
-    char macAddress[SIZE_MAC] {};
+    char macAddress[mac_address::size] {};
 
     int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
     if (sock < 0)
@@ -218,7 +214,7 @@
         return macAddress;
     }
 
-    snprintf(macAddress, SIZE_MAC, MAC_ADDRESS_FORMAT,
+    snprintf(macAddress, mac_address::size, mac_address::format,
             ifr.ifr_hwaddr.sa_data[0], ifr.ifr_hwaddr.sa_data[1],
             ifr.ifr_hwaddr.sa_data[2], ifr.ifr_hwaddr.sa_data[3],
             ifr.ifr_hwaddr.sa_data[4], ifr.ifr_hwaddr.sa_data[5]);
@@ -510,5 +506,71 @@
     }
 }
 
+std::string EthernetInterface::mACAddress(std::string value)
+{
+    if (!mac_address::validate(value))
+    {
+        log<level::DEBUG>("MACAddress is not valid.",
+                          entry("MAC=%s", value.c_str()));
+        return MacAddressIntf::mACAddress();
+    }
+
+    // check whether MAC is broadcast mac.
+    auto intMac = mac_address::internal::convertToInt(value);
+
+    if (!(intMac ^ mac_address::broadcastMac))
+    {
+        log<level::DEBUG>("MACAddress is a broadcast mac.",
+                          entry("MAC=%s", value.c_str()));
+        return MacAddressIntf::mACAddress();
+    }
+
+    // Allow the mac to be set if one of the condition is true.
+    //   1) Incoming Mac is of local admin type.
+    //      or
+    //   2) Incoming mac is same as eeprom Mac.
+
+    if (!(intMac & mac_address::localAdminMask))
+    {
+        try
+        {
+            auto inventoryMac = mac_address::getfromInventory(bus);
+            auto intInventoryMac = mac_address::internal::convertToInt(inventoryMac);
+
+            if (intInventoryMac != intMac)
+            {
+                log<level::DEBUG>("Given MAC address is neither a local Admin \
+                                   type nor is same as in inventory");
+                return MacAddressIntf::mACAddress();
+            }
+        }
+        catch(InternalFailure& e)
+        {
+            log<level::ERR>("Exception occured during getting of MAC \
+                               address from Inventory");
+            return  MacAddressIntf::mACAddress();
+        }
+    }
+    auto interface = interfaceName();
+    execute("/sbin/fw_setenv", "fw_setenv", "ethaddr", value.c_str());
+    //TODO: would replace below three calls
+    //      with restarting of systemd-netwokd
+    //      through https://github.com/systemd/systemd/issues/6696
+    execute("/sbin/ip", "ip", "link", "set", "dev", interface.c_str(), "down");
+    execute("/sbin/ip", "ip", "link", "set", "dev", interface.c_str(), "address",
+            value.c_str());
+
+    execute("/sbin/ip", "ip", "link", "set", "dev", interface.c_str(), "up");
+
+    auto mac = MacAddressIntf::mACAddress(std::move(value));
+    //update all the vlan interfaces
+    for(const auto& intf: vlanInterfaces)
+    {
+        intf.second->updateMacAddress();
+    }
+    return mac;
+
+}
+
 }//namespace network
 }//namespace phosphor
diff --git a/ethernet_interface.hpp b/ethernet_interface.hpp
index 5283da4..3a746d2 100644
--- a/ethernet_interface.hpp
+++ b/ethernet_interface.hpp
@@ -27,6 +27,8 @@
 
 using EthernetInterfaceIntf =
     sdbusplus::xyz::openbmc_project::Network::server::EthernetInterface;
+using MacAddressIntf =
+    sdbusplus::xyz::openbmc_project::Network::server::MACAddress;
 
 namespace fs = std::experimental::filesystem;
 
@@ -113,6 +115,12 @@
         /** Set value of DHCPEnabled */
         bool dHCPEnabled(bool value) override;
 
+        /** @brief sets the MAC address.
+         *  @param[in] value - MAC address which needs to be set on the system.
+         *  @returns macAddress of the interface.
+         */
+        std::string mACAddress(std::string value) override;
+
         /** @brief create Vlan interface.
          *  @param[in] id- VLAN identifier.
          */
@@ -130,6 +138,7 @@
 
         using EthernetInterfaceIntf::dHCPEnabled;
         using EthernetInterfaceIntf::interfaceName;
+        using MacAddressIntf::mACAddress;
 
     protected:
 
diff --git a/util.cpp b/util.cpp
index e5394ba..679b2cb 100644
--- a/util.cpp
+++ b/util.cpp
@@ -364,11 +364,152 @@
     }
     catch (InternalFailure& e)
     {
-       log<level::INFO>("Exception occured during getting of DHCP value");
+        log<level::INFO>("Exception occured during getting of DHCP value");
     }
     return dhcp;
 }
 
+namespace internal
+{
 
+void executeCommandinChildProcess(const char* path, char** args)
+{
+    using namespace std::string_literals;
+    pid_t pid = fork();
+    int status {};
+
+    if (pid == 0)
+    {
+        execv(path, args);
+        auto error = errno;
+        // create the command from var args.
+        std::string command = path + " "s;
+
+        for(int i = 0; args[i]; i++)
+        {
+             command += args[i] + " "s;
+        }
+
+        log<level::ERR>("Couldn't exceute the command",
+                        entry("ERRNO=%d", error),
+                        entry("CMD=%s", command.c_str()));
+        elog<InternalFailure>();
+    }
+    else if (pid < 0)
+    {
+        auto error = errno;
+        log<level::ERR>("Error occurred during fork",
+                        entry("ERRNO=%d", error));
+        elog<InternalFailure>();
+    }
+    else if (pid > 0)
+    {
+        while (waitpid(pid, &status, 0) == -1)
+        {
+            if (errno != EINTR)
+            {   //Error other than EINTR
+                status = -1;
+                break;
+            }
+        }
+
+        if(status < 0)
+        {
+            std::string command = path + " "s;
+            for(int i = 0; args[i]; i++)
+            {
+                command += args[i] + " "s;
+            }
+
+            log<level::ERR>("Unable to execute the command",
+                            entry("CMD=%s", command.c_str(),
+                            entry("STATUS=%d", status)));
+            elog<InternalFailure>();
+        }
+    }
+
+}
+} //namespace internal
+
+namespace mac_address
+{
+
+constexpr auto mapperBus = "xyz.openbmc_project.ObjectMapper";
+constexpr auto mapperObj = "/xyz/openbmc_project/object_mapper";
+constexpr auto mapperIntf = "xyz.openbmc_project.ObjectMapper";
+constexpr auto propIntf = "org.freedesktop.DBus.Properties";
+constexpr auto methodGet = "Get";
+
+using DbusObjectPath = std::string;
+using DbusService = std::string;
+using DbusInterface = std::string;
+using ObjectTree = std::map<DbusObjectPath,
+                            std::map<DbusService, std::vector<DbusInterface>>>;
+
+constexpr auto invBus = "xyz.openbmc_project.Inventory.Manager";
+constexpr auto invNetworkIntf =
+        "xyz.openbmc_project.Inventory.Item.NetworkInterface";
+constexpr auto invRoot = "/xyz/openbmc_project/inventory";
+
+std::string getfromInventory(sdbusplus::bus::bus& bus)
+{
+    std::vector<DbusInterface> interfaces;
+    interfaces.emplace_back(invNetworkIntf);
+
+    auto depth = 0;
+
+    auto mapperCall = bus.new_method_call(mapperBus,
+                                          mapperObj,
+                                          mapperIntf,
+                                          "GetSubTree");
+
+    mapperCall.append(invRoot, depth, interfaces);
+
+    auto mapperReply = bus.call(mapperCall);
+    if (mapperReply.is_method_error())
+    {
+        log<level::ERR>("Error in mapper call");
+        elog<InternalFailure>();
+    }
+
+    ObjectTree objectTree;
+    mapperReply.read(objectTree);
+
+    if (objectTree.empty())
+    {
+        log<level::ERR>("No Object has implemented the interface",
+                        entry("INTERFACE=%s", invNetworkIntf));
+        elog<InternalFailure>();
+    }
+
+    // It is expected that only one object have impelmented this interface.
+
+    auto objPath = objectTree.begin()->first;
+    auto service = objectTree.begin()->second.begin()->first;
+
+    sdbusplus::message::variant<std::string> value;
+
+    auto method = bus.new_method_call(
+                      service.c_str(),
+                      objPath.c_str(),
+                      propIntf,
+                      methodGet);
+
+    method.append(invNetworkIntf, "MACAddress");
+
+    auto reply = bus.call(method);
+    if (reply.is_method_error())
+    {
+         log<level::ERR>("Failed to get MACAddress",
+                        entry("PATH=%s", objPath.c_str()),
+                        entry("INTERFACE=%s", invNetworkIntf));
+        elog<InternalFailure>();
+    }
+
+    reply.read(value);
+    return value.get<std::string>();
+}
+
+}//namespace mac_address
 }//namespace network
 }//namespace phosphor
diff --git a/util.hpp b/util.hpp
index 2015b0c..2d5c3f3 100644
--- a/util.hpp
+++ b/util.hpp
@@ -5,11 +5,60 @@
 #include "config.h"
 #include "types.hpp"
 #include <sdbusplus/bus.hpp>
+#include <regex>
 
 namespace phosphor
 {
 namespace network
 {
+namespace mac_address
+{
+
+constexpr auto regex = "^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$";
+constexpr auto localAdminMask = 0x020000000000;
+constexpr auto broadcastMac = 0xFFFFFFFFFFFF;
+
+constexpr auto format = "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx";
+constexpr size_t size = 18;
+
+/** @brief validate the mac address
+ *  @param[in] value - MAC address.
+ *  @returns true if validate otherwise false.
+ */
+inline bool validate(const std::string& value)
+{
+    std::regex regexToCheck(regex);
+    return std::regex_search(value, regexToCheck);
+}
+
+/** @brief gets the MAC address from the Inventory.
+ *  @param[in] bus - DBUS Bus Object.
+ */
+std::string getfromInventory(sdbusplus::bus::bus& bus);
+
+namespace internal
+{
+/** @brief Converts the given mac address into unsigned 64 bit integer
+ *  @param[in] value - MAC address.
+ *  @returns converted unsigned 64 bit number.
+ */
+inline uint64_t convertToInt(const std::string& value)
+{
+    unsigned char mac[6];
+
+    sscanf(value.c_str(), "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
+           mac + 0, mac + 1, mac + 2, mac + 3, mac + 4, mac + 5);
+    return
+        static_cast<uint64_t>(mac[0]) << 40 |
+        static_cast<uint64_t>(mac[1]) << 32 |
+        static_cast<uint64_t>(mac[2]) << 24 |
+        static_cast<uint64_t>(mac[3]) << 16 |
+        static_cast<uint64_t>(mac[4]) << 8 |
+        static_cast<uint64_t>(mac[5]);
+}
+
+}//namespace internal
+}//namespace mac_address
 
 /* @brief converts the given subnet into prefix notation.
  * @param[in] addressFamily - IP address family(AF_INET/AF_INET6).
@@ -74,6 +123,31 @@
  */
 bool getDHCPValue(const std::string& confDir, const std::string& intf);
 
+namespace internal
+{
+
+/* @brief runs the given command in child process.
+ * @param[in] path - path of the binary file which needs to be execeuted.
+ * @param[in] args - arguments of the command.
+ */
+void executeCommandinChildProcess(const char* path, char** args);
+
+} // namespace internal
+
+/* @brief runs the given command in child process.
+ * @param[in] path -path of the binary file which needs to be execeuted.
+ * @param[in] tArgs - arguments of the command.
+ */
+template<typename... ArgTypes>
+void execute(const char* path, ArgTypes&&... tArgs)
+{
+    using expandType = char*[];
+
+    expandType args = { const_cast<char*>(tArgs)..., nullptr };
+
+    internal::executeCommandinChildProcess(path, args);
+}
+
 } //namespace network
 
 class Descriptor
diff --git a/vlan_interface.cpp b/vlan_interface.cpp
index 8684a73..8f7c5ce 100644
--- a/vlan_interface.cpp
+++ b/vlan_interface.cpp
@@ -33,7 +33,7 @@
 {
     id(vlanID);
     VlanIface::interfaceName(EthernetInterface::interfaceName());
-    mACAddress(parentInterface.mACAddress());
+    MacAddressIntf::mACAddress(parentInterface.mACAddress());
 
     emit_object_added();
 }
diff --git a/vlan_interface.hpp b/vlan_interface.hpp
index d9ea078..3f273b7 100644
--- a/vlan_interface.hpp
+++ b/vlan_interface.hpp
@@ -63,6 +63,12 @@
                    and creates the vlan interface.*/
         void writeDeviceFile();
 
+        /** @brief copy the mac address from the parent interface.*/
+        void updateMacAddress()
+        {
+            MacAddressIntf::mACAddress(parentInterface.mACAddress());
+        }
+
     private:
 
         /** @brief VLAN Identifier. */