Add a IPMI blob handler for SMBIOS tables

smbios-mdr has dependencies on intel-ipmi-oem which makes BIOS sending
SMBIOS tables to BMC through VGA shared memory. For platforms without
intel-ipmi-oem, implement a IPMI blob hanler so that BIOS can send
SMBIOS tables through IPMI blob interfaces.

Test:
Unit tests for the IPMI blob handler.
Manual test that transfers SMBIOS tables to BMC through IPMI blob
interfaces on a platform host.

Signed-off-by: Jie Yang <jjy@google.com>
Change-Id: I9bc1ae7e9bfaa793e47e38fa19049f0f69355189
diff --git a/src/smbios-ipmi-blobs/handler.cpp b/src/smbios-ipmi-blobs/handler.cpp
new file mode 100644
index 0000000..51d9d95
--- /dev/null
+++ b/src/smbios-ipmi-blobs/handler.cpp
@@ -0,0 +1,280 @@
+#include "handler.hpp"
+
+#include "mdrv2.hpp"
+#include "smbios_mdrv2.hpp"
+
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <ipmid/api.hpp>
+#include <phosphor-logging/log.hpp>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/exception.hpp>
+#include <sdbusplus/message.hpp>
+
+#include <algorithm>
+#include <cstdint>
+#include <ctime>
+#include <fstream>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace blobs
+{
+
+namespace internal
+{
+
+constexpr const char* mdrV2Service = "xyz.openbmc_project.Smbios.MDR_V2";
+constexpr const char* mdrV2Interface = "xyz.openbmc_project.Smbios.MDR_V2";
+
+bool syncSmbiosData()
+{
+    bool status = false;
+    sdbusplus::bus::bus bus =
+        sdbusplus::bus::bus(ipmid_get_sd_bus_connection());
+    sdbusplus::message::message method =
+        bus.new_method_call(mdrV2Service, phosphor::smbios::mdrV2Path,
+                            mdrV2Interface, "AgentSynchronizeData");
+
+    try
+    {
+        sdbusplus::message::message reply = bus.call(method);
+        reply.read(status);
+    }
+    catch (sdbusplus::exception_t& e)
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            "Error Sync data with service",
+            phosphor::logging::entry("ERROR=%s", e.what()),
+            phosphor::logging::entry("SERVICE=%s", mdrV2Service),
+            phosphor::logging::entry("PATH=%s", phosphor::smbios::mdrV2Path));
+        return false;
+    }
+
+    if (!status)
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            "Sync data with service failure");
+        return false;
+    }
+
+    return true;
+}
+
+} // namespace internal
+
+bool SmbiosBlobHandler::canHandleBlob(const std::string& path)
+{
+    return path == blobId;
+}
+
+std::vector<std::string> SmbiosBlobHandler::getBlobIds()
+{
+    return std::vector<std::string>(1, blobId);
+}
+
+bool SmbiosBlobHandler::deleteBlob(const std::string& path)
+{
+    return false;
+}
+
+bool SmbiosBlobHandler::stat(const std::string& path, struct BlobMeta* meta)
+{
+    if (!blobPtr || blobPtr->blobId != path)
+    {
+        return false;
+    }
+
+    meta->size = blobPtr->buffer.size();
+    meta->blobState = blobPtr->state;
+    return true;
+}
+
+bool SmbiosBlobHandler::open(uint16_t session, uint16_t flags,
+                             const std::string& path)
+{
+    if (flags & blobs::OpenFlags::read)
+    {
+        /* Disable the read operation. */
+        return false;
+    }
+
+    /* The handler only allows one session. If an open blob exists, return
+     * false directly.
+     */
+    if (blobPtr)
+    {
+        return false;
+    }
+    blobPtr = std::make_unique<SmbiosBlob>(session, path, flags);
+    return true;
+}
+
+std::vector<uint8_t> SmbiosBlobHandler::read(uint16_t session, uint32_t offset,
+                                             uint32_t requestedSize)
+{
+    /* SMBIOS blob handler does not support read. */
+    return std::vector<uint8_t>();
+}
+
+bool SmbiosBlobHandler::write(uint16_t session, uint32_t offset,
+                              const std::vector<uint8_t>& data)
+{
+    if (!blobPtr || blobPtr->sessionId != session)
+    {
+        return false;
+    }
+
+    if (!(blobPtr->state & blobs::StateFlags::open_write))
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            "No open blob to write");
+        return false;
+    }
+
+    /* Is the offset beyond the array? */
+    if (offset >= maxBufferSize)
+    {
+        return false;
+    }
+
+    /* Determine whether all their bytes will fit. */
+    uint32_t remain = maxBufferSize - offset;
+    if (data.size() > remain)
+    {
+        return false;
+    }
+
+    /* Resize the buffer if what we're writing will go over the size */
+    uint32_t newBufferSize = data.size() + offset;
+    if (newBufferSize > blobPtr->buffer.size())
+    {
+        blobPtr->buffer.resize(newBufferSize);
+    }
+
+    std::memcpy(blobPtr->buffer.data() + offset, data.data(), data.size());
+    return true;
+}
+
+bool SmbiosBlobHandler::writeMeta(uint16_t session, uint32_t offset,
+                                  const std::vector<uint8_t>& data)
+{
+    return false;
+}
+
+bool SmbiosBlobHandler::commit(uint16_t session,
+                               const std::vector<uint8_t>& data)
+{
+    if (!data.empty())
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            "Unexpected data provided to commit call");
+        return false;
+    }
+
+    if (!blobPtr || blobPtr->sessionId != session)
+    {
+        return false;
+    }
+
+    /* If a blob is committing or commited, return true directly. But if last
+     * commit fails, may try to commit again.
+     */
+    if (blobPtr->state &
+        (blobs::StateFlags::committing | blobs::StateFlags::committed))
+    {
+        return true;
+    }
+
+    /* Clear the commit_error bit. */
+    blobPtr->state &= ~blobs::StateFlags::commit_error;
+
+    MDRSMBIOSHeader mdrHdr;
+    mdrHdr.mdrType = mdrTypeII;
+    mdrHdr.timestamp = std::time(nullptr);
+    mdrHdr.dataSize = blobPtr->buffer.size();
+    if (access(smbiosPath, F_OK) == -1)
+    {
+        int flag = mkdir(smbiosPath, S_IRWXU);
+        if (flag != 0)
+        {
+            phosphor::logging::log<phosphor::logging::level::ERR>(
+                "create folder failed for writting smbios file");
+            blobPtr->state |= blobs::StateFlags::commit_error;
+            return false;
+        }
+    }
+
+    std::ofstream smbiosFile(mdrType2File,
+                             std::ios_base::binary | std::ios_base::trunc);
+    if (!smbiosFile.good())
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            "Write data from flash error - Open SMBIOS table file failure");
+        blobPtr->state |= blobs::StateFlags::commit_error;
+        return false;
+    }
+
+    smbiosFile.exceptions(std::ofstream::badbit | std::ofstream::failbit);
+    try
+    {
+        smbiosFile.write(reinterpret_cast<char*>(&mdrHdr),
+                         sizeof(MDRSMBIOSHeader));
+        smbiosFile.write(reinterpret_cast<char*>(blobPtr->buffer.data()),
+                         mdrHdr.dataSize);
+        blobPtr->state |= blobs::StateFlags::committing;
+    }
+    catch (std::ofstream::failure& e)
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            "Write data from flash error - write data error",
+            phosphor::logging::entry("ERROR=%s", e.what()));
+        blobPtr->state |= blobs::StateFlags::commit_error;
+        return false;
+    }
+
+    if (!internal::syncSmbiosData())
+    {
+        blobPtr->state &= ~blobs::StateFlags::committing;
+        blobPtr->state |= blobs::StateFlags::commit_error;
+        return false;
+    }
+
+    // Unset committing state and set committed state
+    blobPtr->state &= ~blobs::StateFlags::committing;
+    blobPtr->state |= blobs::StateFlags::committed;
+
+    return true;
+}
+
+bool SmbiosBlobHandler::close(uint16_t session)
+{
+    if (!blobPtr || blobPtr->sessionId != session)
+    {
+        return false;
+    }
+
+    blobPtr = nullptr;
+    return true;
+}
+
+bool SmbiosBlobHandler::stat(uint16_t session, struct BlobMeta* meta)
+{
+    if (!blobPtr || blobPtr->sessionId != session)
+    {
+        return false;
+    }
+
+    meta->size = blobPtr->buffer.size();
+    meta->blobState = blobPtr->state;
+    return true;
+}
+
+bool SmbiosBlobHandler::expire(uint16_t session)
+{
+    return close(session);
+}
+
+} // namespace blobs