Add suppport for retrieving machine name
This adds support for reading /etc/os-release, parsing out
OPENBMC_TARGET_MACHINE and returning this to the caller.
Change-Id: If2a419b9a77597686f5137efce97b1150142f181
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/Makefile.am b/Makefile.am
index 43f65aa..34e60bd 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -47,6 +47,7 @@
psu.cpp \
pcie_i2c.cpp \
entity_name.cpp \
+ machine_name.cpp \
handler.cpp \
util.cpp \
ipmi.cpp
diff --git a/README.md b/README.md
index c13e717..e0a09d4 100644
--- a/README.md
+++ b/README.md
@@ -162,3 +162,22 @@
|0x00|0x06|Subcommand
|0x01|Entity name length (say N)|Entity name length
|0x02...0x02 + N - 1|Entity name|Entity name without null terminator
+
+### GetMachineName - SubCommand 0x07
+
+The BMC parses /etc/os-release for the OPENBMC_TARGET_MACHINE field and returns
+its value.
+
+Request
+
+|Byte(s) |Value |Data
+|--------|------|----
+|0x00|0x06|Subcommand
+
+Response
+
+|Byte(s) |Value |Data
+|--------|------|----
+|0x00|0x06|Subcommand
+|0x01|Model name length (say N)|Model name length
+|0x02...0x02 + N - 1|Model name|Model name without null terminator
diff --git a/commands.hpp b/commands.hpp
index dfe8f29..e8f1efa 100644
--- a/commands.hpp
+++ b/commands.hpp
@@ -21,6 +21,8 @@
SysPcieSlotI2cBusMapping = 5,
// The Sys "entity id:entity instance" to entity name mapping command.
SysEntityName = 6,
+ // Returns the machine name of the image
+ SysMachineName = 7,
};
} // namespace ipmi
diff --git a/handler.cpp b/handler.cpp
index 4605e2f..028c97b 100644
--- a/handler.cpp
+++ b/handler.cpp
@@ -33,6 +33,7 @@
#include <sdbusplus/bus.hpp>
#include <sstream>
#include <string>
+#include <string_view>
#include <tuple>
#include <xyz/openbmc_project/Common/error.hpp>
@@ -233,6 +234,47 @@
return entityName;
}
+std::string Handler::getMachineName()
+{
+ const char* path = "/etc/os-release";
+ std::ifstream ifs(path);
+ if (ifs.fail())
+ {
+ std::fprintf(stderr, "Failed to open: %s\n", path);
+ throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR);
+ }
+
+ std::string line;
+ while (true)
+ {
+ std::getline(ifs, line);
+ if (ifs.eof())
+ {
+ std::fprintf(stderr, "Failed to find OPENBMC_TARGET_MACHINE: %s\n",
+ path);
+ throw IpmiException(IPMI_CC_INVALID);
+ }
+ if (ifs.fail())
+ {
+ std::fprintf(stderr, "Failed to read: %s\n", path);
+ throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR);
+ }
+ std::string_view lineView(line);
+ constexpr std::string_view prefix = "OPENBMC_TARGET_MACHINE=";
+ if (lineView.substr(0, prefix.size()) != prefix)
+ {
+ continue;
+ }
+ lineView.remove_prefix(prefix.size());
+ lineView.remove_prefix(
+ std::min(lineView.find_first_not_of('"'), lineView.size()));
+ lineView.remove_suffix(
+ lineView.size() - 1 -
+ std::min(lineView.find_last_not_of('"'), lineView.size() - 1));
+ return std::string(lineView);
+ }
+}
+
std::string readNameFromConfig(const std::string& type, uint8_t instance,
const Json& config)
{
diff --git a/handler.hpp b/handler.hpp
index 19fd1ea..766ddde 100644
--- a/handler.hpp
+++ b/handler.hpp
@@ -67,6 +67,14 @@
std::uint8_t instance) = 0;
/**
+ * Return the name of the machine, parsed from release information.
+ *
+ * @return the machine name
+ * @throw IpmiException on failure.
+ */
+ virtual std::string getMachineName() = 0;
+
+ /**
* Populate the i2c-pcie mapping vector.
*/
virtual void buildI2cPcieMapping() = 0;
diff --git a/handler_impl.hpp b/handler_impl.hpp
index b9946a1..130a4b0 100644
--- a/handler_impl.hpp
+++ b/handler_impl.hpp
@@ -28,6 +28,7 @@
VersionTuple getCpldVersion(unsigned int id) const override;
void psuResetDelay(std::uint32_t delay) const override;
std::string getEntityName(std::uint8_t id, std::uint8_t instance) override;
+ std::string getMachineName() override;
void buildI2cPcieMapping() override;
size_t getI2cPcieMappingSize() const override;
std::tuple<std::uint32_t, std::string>
diff --git a/ipmi.cpp b/ipmi.cpp
index d297efc..509ec15 100644
--- a/ipmi.cpp
+++ b/ipmi.cpp
@@ -22,6 +22,7 @@
#include "entity_name.hpp"
#include "eth.hpp"
#include "handler.hpp"
+#include "machine_name.hpp"
#include "pcie_i2c.hpp"
#include "psu.hpp"
@@ -63,6 +64,8 @@
return pcieSlotI2cBusMapping(reqBuf, replyCmdBuf, dataLen, handler);
case SysEntityName:
return getEntityName(reqBuf, replyCmdBuf, dataLen, handler);
+ case SysMachineName:
+ return getMachineName(reqBuf, replyCmdBuf, dataLen, handler);
default:
std::fprintf(stderr, "Invalid subcommand: 0x%x\n", reqBuf[0]);
return IPMI_CC_INVALID;
diff --git a/machine_name.cpp b/machine_name.cpp
new file mode 100644
index 0000000..9fcbd10
--- /dev/null
+++ b/machine_name.cpp
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2020 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "machine_name.hpp"
+
+#include "errors.hpp"
+
+#include <cstddef>
+#include <cstdio>
+#include <cstring>
+#include <optional>
+#include <string>
+
+namespace google
+{
+namespace ipmi
+{
+
+struct GetMachineNameRequest
+{
+ uint8_t subcommand;
+} __attribute__((packed));
+
+struct GetMachineNameReply
+{
+ uint8_t subcommand;
+ uint8_t machineNameLength;
+ uint8_t machineName[0];
+} __attribute__((packed));
+
+ipmi_ret_t getMachineName(const uint8_t* reqBuf, uint8_t* replyBuf,
+ size_t* dataLen, HandlerInterface* handler)
+{
+ GetMachineNameRequest request;
+ if (*dataLen < sizeof(request))
+ {
+ std::fprintf(stderr, "Invalid command length: %zu\n", *dataLen);
+ return IPMI_CC_REQ_DATA_LEN_INVALID;
+ }
+ std::memcpy(&request, reqBuf, sizeof(request));
+
+ static std::optional<std::string> machineName;
+ if (!machineName)
+ {
+ try
+ {
+ machineName = handler->getMachineName();
+ }
+ catch (const IpmiException& e)
+ {
+ return e.getIpmiError();
+ }
+ }
+
+ GetMachineNameReply reply;
+ size_t len = sizeof(reply) + machineName->size();
+ if (len > MAX_IPMI_BUFFER)
+ {
+ std::fprintf(stderr, "Response would overflow response buffer\n");
+ return IPMI_CC_INVALID;
+ }
+ reply.subcommand = request.subcommand;
+ reply.machineNameLength = machineName->size();
+ std::memcpy(replyBuf, &reply, sizeof(reply));
+ std::memcpy(replyBuf + sizeof(reply), machineName->data(),
+ machineName->size());
+ (*dataLen) = len;
+ return IPMI_CC_OK;
+}
+
+} // namespace ipmi
+} // namespace google
diff --git a/machine_name.hpp b/machine_name.hpp
new file mode 100644
index 0000000..7412929
--- /dev/null
+++ b/machine_name.hpp
@@ -0,0 +1,17 @@
+#pragma once
+
+#include "handler.hpp"
+
+#include <ipmid/api.h>
+
+namespace google
+{
+namespace ipmi
+{
+
+// Handle the machine name command.
+ipmi_ret_t getMachineName(const uint8_t* reqBuf, uint8_t* replyBuf,
+ size_t* dataLen, HandlerInterface* handler);
+
+} // namespace ipmi
+} // namespace google
diff --git a/test/Makefile.am b/test/Makefile.am
index 08b5a1c..d10f4ba 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -41,6 +41,10 @@
entity_unittest_SOURCES = entity_unittest.cpp
entity_unittest_LDADD = $(top_builddir)/libsyscmds_common.la
+check_PROGRAMS += machine_unittest
+machine_unittest_SOURCES = machine_unittest.cpp
+machine_unittest_LDADD = $(top_builddir)/libsyscmds_common.la
+
check_PROGRAMS += pcie_unittest
pcie_unittest_SOURCES = pcie_unittest.cpp
pcie_unittest_LDADD = $(top_builddir)/libsyscmds_common.la
diff --git a/test/handler_mock.hpp b/test/handler_mock.hpp
index 1643a1f..f214db8 100644
--- a/test/handler_mock.hpp
+++ b/test/handler_mock.hpp
@@ -27,6 +27,7 @@
std::uint8_t>(unsigned int));
MOCK_CONST_METHOD1(psuResetDelay, void(std::uint32_t));
MOCK_METHOD2(getEntityName, std::string(std::uint8_t, std::uint8_t));
+ MOCK_METHOD0(getMachineName, std::string());
MOCK_METHOD0(buildI2cPcieMapping, void());
MOCK_CONST_METHOD0(getI2cPcieMappingSize, size_t());
MOCK_CONST_METHOD1(getI2cEntry,
diff --git a/test/machine_unittest.cpp b/test/machine_unittest.cpp
new file mode 100644
index 0000000..0e50a69
--- /dev/null
+++ b/test/machine_unittest.cpp
@@ -0,0 +1,73 @@
+#include "commands.hpp"
+#include "errors.hpp"
+#include "handler_mock.hpp"
+#include "machine_name.hpp"
+
+#include <cstdint>
+#include <cstring>
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#define MAX_IPMI_BUFFER 64
+
+using ::testing::Return;
+using ::testing::Throw;
+
+namespace google
+{
+namespace ipmi
+{
+
+TEST(MachineNameCommandTest, InvalidCommandLength)
+{
+ std::vector<std::uint8_t> request = {};
+ size_t dataLen = request.size();
+ std::uint8_t reply[MAX_IPMI_BUFFER];
+
+ ::testing::StrictMock<HandlerMock> hMock;
+
+ EXPECT_EQ(IPMI_CC_REQ_DATA_LEN_INVALID,
+ getMachineName(request.data(), reply, &dataLen, &hMock));
+}
+
+TEST(MachineNameCommandTest, InvalidFile)
+{
+ std::vector<std::uint8_t> request = {SysOEMCommands::SysMachineName};
+ size_t dataLen = request.size();
+ std::uint8_t reply[MAX_IPMI_BUFFER];
+
+ ::testing::StrictMock<HandlerMock> hMock;
+ EXPECT_CALL(hMock, getMachineName()).WillOnce(Throw(IpmiException(5)));
+
+ EXPECT_EQ(5, getMachineName(request.data(), reply, &dataLen, &hMock));
+}
+
+TEST(MachineNameCommandTest, CachesValidRequest)
+{
+ std::vector<std::uint8_t> request = {SysOEMCommands::SysMachineName};
+ size_t dataLen = request.size();
+ std::uint8_t reply[MAX_IPMI_BUFFER];
+ const std::string ret = "Machine";
+
+ ::testing::StrictMock<HandlerMock> hMock;
+ EXPECT_CALL(hMock, getMachineName()).WillOnce(Return(ret));
+
+ EXPECT_EQ(IPMI_CC_OK,
+ getMachineName(request.data(), reply, &dataLen, &hMock));
+ EXPECT_EQ(SysOEMCommands::SysMachineName, reply[0]);
+ EXPECT_EQ(ret.size(), reply[1]);
+ EXPECT_EQ(0, std::memcmp(&reply[2], ret.data(), ret.size()));
+
+ dataLen = request.size();
+ memset(reply, 0, sizeof(reply));
+ EXPECT_EQ(IPMI_CC_OK,
+ getMachineName(request.data(), reply, &dataLen, &hMock));
+ EXPECT_EQ(SysOEMCommands::SysMachineName, reply[0]);
+ EXPECT_EQ(ret.size(), reply[1]);
+ EXPECT_EQ(0, std::memcmp(&reply[2], ret.data(), ret.size()));
+}
+
+} // namespace ipmi
+} // namespace google