ethernet_interface: Write configs from parser

This guarantees the values are valid and that the file is always swapped
out atomically instead of written piecewise.

Tested: Manually executed settings changes on the BMC and saw them
persist to the RWFS as expected.

Change-Id: I719c856e713ce054726cc7f2f7844c0539b631b6
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/src/ethernet_interface.cpp b/src/ethernet_interface.cpp
index a3b2519..3afe48c 100644
--- a/src/ethernet_interface.cpp
+++ b/src/ethernet_interface.cpp
@@ -19,7 +19,6 @@
 
 #include <algorithm>
 #include <filesystem>
-#include <fstream>
 #include <phosphor-logging/elog-errors.hpp>
 #include <phosphor-logging/log.hpp>
 #include <sdbusplus/bus/match.hpp>
@@ -960,154 +959,123 @@
         intf.second->writeConfigurationFile();
     }
 
-    auto path = config::pathForIntfConf(manager.getConfDir(), interfaceName());
-    std::fstream stream(path.c_str(), std::fstream::out);
-    if (!stream.is_open())
+    config::Parser config;
+    config.map["Match"].emplace_back()["Name"].emplace_back(interfaceName());
     {
-        log<level::ERR>("Unable to open the file",
-                        entry("FILE=%s", path.c_str()));
-        elog<InternalFailure>();
-    }
-
-    // Write the device
-    stream << "[Match]\n";
-    stream << "Name=" << interfaceName() << "\n";
-
-    auto addrs = getAddresses();
-
-    // Write the link section
-    stream << "[Link]\n";
+        auto& link = config.map["Link"].emplace_back();
 #ifdef PERSIST_MAC
-    auto mac = MacAddressIntf::macAddress();
-    if (!mac.empty())
-    {
-        stream << "MACAddress=" << mac << "\n";
-    }
+        auto mac = MacAddressIntf::macAddress();
+        if (!mac.empty())
+        {
+            link["MACAddress"].emplace_back(mac);
+        }
 #endif
-
-    if (!EthernetInterfaceIntf::nicEnabled())
-    {
-        stream << "Unmanaged=yes\n";
+        if (!EthernetInterfaceIntf::nicEnabled())
+        {
+            link["Unmanaged"].emplace_back("yes");
+        }
     }
-
-    // write the network section
-    stream << "[Network]\n";
+    {
+        auto& network = config.map["Network"].emplace_back();
+        auto& lla = network["LinkLocalAddressing"];
 #ifdef LINK_LOCAL_AUTOCONFIGURATION
-    stream << "LinkLocalAddressing=yes\n";
+        lla.emplace_back("yes");
 #else
-    stream << "LinkLocalAddressing=no\n";
+        lla.emplace_back("no");
 #endif
-    stream << std::boolalpha
-           << "IPv6AcceptRA=" << EthernetInterfaceIntf::ipv6AcceptRA() << "\n";
-
-    // Add the VLAN entry
-    for (const auto& intf : vlanInterfaces)
-    {
-        stream << "VLAN=" << intf.second->EthernetInterface::interfaceName()
-               << "\n";
-    }
-    // Add the NTP server
-    for (const auto& ntp : EthernetInterfaceIntf::ntpServers())
-    {
-        stream << "NTP=" << ntp << "\n";
-    }
-
-    // Add the DNS entry
-    for (const auto& dns : EthernetInterfaceIntf::staticNameServers())
-    {
-        stream << "DNS=" << dns << "\n";
-    }
-
-    // Add the DHCP entry
-    stream << "DHCP="s +
-                  mapDHCPToSystemd[EthernetInterfaceIntf::dhcpEnabled()] + "\n";
-
-    stream << "[IPv6AcceptRA]\n";
-    stream << "DHCPv6Client=";
-    stream << (dhcpIsEnabled(IP::Protocol::IPv6) ? "true" : "false");
-    stream << "\n";
-
-    // Static IP addresses
-    for (const auto& addr : addrs)
-    {
-        if (originIsManuallyAssigned(addr.second->origin()) &&
-            !dhcpIsEnabled(addr.second->type()))
+        network["IPv6AcceptRA"].emplace_back(
+            EthernetInterfaceIntf::ipv6AcceptRA() ? "true" : "false");
+        network["DHCP"].emplace_back(
+            mapDHCPToSystemd[EthernetInterfaceIntf::dhcpEnabled()]);
         {
-            // Process all static addresses
-            std::string address = addr.second->address() + "/" +
-                                  std::to_string(addr.second->prefixLength());
+            auto& vlans = network["VLAN"];
+            for (const auto& intf : vlanInterfaces)
+            {
+                vlans.emplace_back(
+                    intf.second->EthernetInterface::interfaceName());
+            }
+        }
+        {
+            auto& ntps = network["NTP"];
+            for (const auto& ntp : EthernetInterfaceIntf::ntpServers())
+            {
+                ntps.emplace_back(ntp);
+            }
+        }
+        {
+            auto& dnss = network["DNS"];
+            for (const auto& dns : EthernetInterfaceIntf::staticNameServers())
+            {
+                dnss.emplace_back(dns);
+            }
+        }
+        {
+            auto& address = network["Address"];
+            for (const auto& addr : getAddresses())
+            {
+                if (originIsManuallyAssigned(addr.second->origin()) &&
+                    !dhcpIsEnabled(addr.second->type()))
+                {
+                    address.emplace_back(
+                        fmt::format("{}/{}", addr.second->address(),
+                                    addr.second->prefixLength()));
+                }
+            }
+        }
+        {
+            auto& gateways = network["Gateway"];
+            if (!dhcpIsEnabled(IP::Protocol::IPv4))
+            {
+                auto gateway = EthernetInterfaceIntf::defaultGateway();
+                if (!gateway.empty())
+                {
+                    gateways.emplace_back(gateway);
+                }
+            }
 
-            // build the address entries. Do not use [Network] shortcuts to
-            // insert address entries.
-            stream << "[Address]\n";
-            stream << "Address=" << address << "\n";
+            if (!dhcpIsEnabled(IP::Protocol::IPv6))
+            {
+                auto gateway6 = EthernetInterfaceIntf::defaultGateway6();
+                if (!gateway6.empty())
+                {
+                    gateways.emplace_back(gateway6);
+                }
+            }
         }
     }
-
-    if (!dhcpIsEnabled(IP::Protocol::IPv4))
+    config.map["IPv6AcceptRA"].emplace_back()["DHCPv6Client"].emplace_back(
+        dhcpIsEnabled(IP::Protocol::IPv6) ? "true" : "false");
     {
-        auto gateway = EthernetInterfaceIntf::defaultGateway();
-        if (!gateway.empty())
+        auto& neighbors = config.map["Neighbor"];
+        for (const auto& sneighbor : staticNeighbors)
         {
-            stream << "[Route]\n";
-            stream << "Gateway=" << gateway << "\n";
+            auto& neighbor = neighbors.emplace_back();
+            neighbor["Address"].emplace_back(sneighbor.second->ipAddress());
+            neighbor["MACAddress"].emplace_back(sneighbor.second->macAddress());
         }
     }
-
-    if (!dhcpIsEnabled(IP::Protocol::IPv6))
     {
-        auto gateway6 = EthernetInterfaceIntf::defaultGateway6();
-        if (!gateway6.empty())
+        auto& dhcp = config.map["DHCP"].emplace_back();
+        dhcp["ClientIdentifier"].emplace_back("mac");
+        if (manager.getDHCPConf())
         {
-            stream << "[Route]\n";
-            stream << "Gateway=" << gateway6 << "\n";
+            const auto& conf = *manager.getDHCPConf();
+            auto dns_enabled = conf.dnsEnabled() ? "true" : "false";
+            dhcp["UseDNS"].emplace_back(dns_enabled);
+            dhcp["UseDomains"].emplace_back(dns_enabled);
+            dhcp["UseNTP"].emplace_back(conf.ntpEnabled() ? "true" : "false");
+            dhcp["UseHostname"].emplace_back(conf.hostNameEnabled() ? "true"
+                                                                    : "false");
+            dhcp["SendHostname"].emplace_back(
+                conf.sendHostNameEnabled() ? "true" : "false");
         }
     }
-
-    // Write the neighbor sections
-    for (const auto& neighbor : staticNeighbors)
-    {
-        stream << "[Neighbor]"
-               << "\n";
-        stream << "Address=" << neighbor.second->ipAddress() << "\n";
-        stream << "MACAddress=" << neighbor.second->macAddress() << "\n";
-    }
-
-    // Write the dhcp section irrespective of whether DHCP is enabled or not
-    writeDHCPSection(stream);
-
-    stream.close();
+    auto path = config::pathForIntfConf(manager.getConfDir(), interfaceName());
+    config.writeFile(path);
     auto msg = fmt::format("Wrote networkd file: {}", path.native());
     log<level::INFO>(msg.c_str(), entry("FILE=%s", path.c_str()));
 }
 
-void EthernetInterface::writeDHCPSection(std::fstream& stream)
-{
-    using namespace std::string_literals;
-    // write the dhcp section
-    stream << "[DHCP]\n";
-
-    // Hardcoding the client identifier to mac, to address below issue
-    // https://github.com/openbmc/openbmc/issues/1280
-    stream << "ClientIdentifier=mac\n";
-    if (manager.getDHCPConf())
-    {
-        auto value = manager.getDHCPConf()->dnsEnabled() ? "true"s : "false"s;
-        stream << "UseDNS="s + value + "\n";
-        stream << "UseDomains="s + value + "\n";
-
-        value = manager.getDHCPConf()->ntpEnabled() ? "true"s : "false"s;
-        stream << "UseNTP="s + value + "\n";
-
-        value = manager.getDHCPConf()->hostNameEnabled() ? "true"s : "false"s;
-        stream << "UseHostname="s + value + "\n";
-
-        value =
-            manager.getDHCPConf()->sendHostNameEnabled() ? "true"s : "false"s;
-        stream << "SendHostname="s + value + "\n";
-    }
-}
-
 std::string EthernetInterface::macAddress([[maybe_unused]] std::string value)
 {
 #ifdef PERSIST_MAC
diff --git a/src/ethernet_interface.hpp b/src/ethernet_interface.hpp
index c519623..e4be9ab 100644
--- a/src/ethernet_interface.hpp
+++ b/src/ethernet_interface.hpp
@@ -304,9 +304,6 @@
     static std::string generateNeighborId(const std::string& ipAddress,
                                           const std::string& macAddress);
 
-    /** @brief write the dhcp section **/
-    void writeDHCPSection(std::fstream& stream);
-
     /** @brief get the NTP server list from the network conf
      *
      */