bios_setting: Add a write handler
Tested:
```
// Test that it writes a new file if it doesn't exist
~# rm /run/oem_bios_setting
// Invalid command (no size / payload)
~# ipmitool raw 0x2e 0x32 0x79 0x2b 0x00 0x19
Unable to send RAW command (channel=0x0 netfn=0x2e lun=0x0 cmd=0x32 rsp=0xc7): Request data length invalid
// Invalid command (size doesn't match)
~# ipmitool raw 0x2e 0x32 0x79 0x2b 0x00 0x19 0x01 0x30 0x31
Unable to send RAW command (channel=0x0 netfn=0x2e lun=0x0 cmd=0x32 rsp=0xc7): Request data length invalid
// Command success
~# ipmitool raw 0x2e 0x32 0x79 0x2b 0x00 0x19 0x02 0x30 0x31
79 2b 00 19 02
// Read back
~# ipmitool raw 0x2e 0x32 0x79 0x2b 0x00 0x18
79 2b 00 18 02 30 31
~# cat /run/oem_bios_setting
01~#
// Verify overwrite works (truncates the previous bytes)
~# ipmitool raw 0x2e 0x32 0x79 0x2b 0x00 0x19 0x01 0x33
79 2b 00 19 01
~# cat /run/oem_bios_setting
3~#
~# ipmitool raw 0x2e 0x32 0x79 0x2b 0x00 0x18
79 2b 00 18 01 33
```
Signed-off-by: Brandon Kim <brandonkim@google.com>
Change-Id: I84db782da9b2f121c0a81a855692b5ca25ffda54
diff --git a/README.md b/README.md
index be77961..e43454a 100644
--- a/README.md
+++ b/README.md
@@ -552,3 +552,25 @@
| 0x00 | 0x18 | Subcommand |
| 0x01 | Length (N) | Number of payload bytes - Size limited to 64 |
| 0x2..0x02 + N - 1 | Payload bytes | Payload bytes |
+
+### WriteBiosSetting - SubCommand 0x19
+
+Write a BIOS setting, set at `/run/oem_bios_setting`.
+
+The response contains the length of the BIOS setting followed by the BIOS
+setting bytes read.
+
+Request
+
+| Byte(s) | Value | Data |
+| ----------------- | ------------- | -------------------------------------------- |
+| 0x00 | 0x19 | Subcommand |
+| 0x01 | Length (N) | Number of payload bytes - Size limited to 64 |
+| 0x2..0x02 + N - 1 | Payload bytes | Payload bytes |
+
+Response
+
+| Byte(s) | Value | Data |
+| ------- | ---------- | ------------------------------------ |
+| 0x00 | 0x19 | Subcommand |
+| 0x01 | Length (N) | Number of bytes successfully written |
diff --git a/bios_setting.cpp b/bios_setting.cpp
index d92f786..864b178 100644
--- a/bios_setting.cpp
+++ b/bios_setting.cpp
@@ -22,7 +22,9 @@
#include <stdplus/fd/create.hpp>
#include <stdplus/fd/managed.hpp>
#include <stdplus/fd/ops.hpp>
+#include <stdplus/numeric/endian.hpp>
#include <stdplus/print.hpp>
+#include <stdplus/raw.hpp>
#include <filesystem>
#include <fstream>
@@ -72,5 +74,54 @@
return ::ipmi::responseSuccess(SysOEMCommands::SysReadBiosSetting, reply);
}
+Resp writeBiosSetting(std::span<const uint8_t> data, HandlerInterface*,
+ const std::string& biosSettingPath)
+{
+ std::uint8_t payloadSize;
+ try
+ {
+ // This subspans the data automatically
+ payloadSize = stdplus::raw::extract<
+ stdplus::EndianPacked<decltype(payloadSize), std::endian::little>>(
+ data);
+ }
+ catch (const std::exception& e)
+ {
+ stdplus::print(stderr, "Extracting payload failed: {}\n", e.what());
+ return ::ipmi::responseReqDataLenInvalid();
+ }
+
+ if (data.size() != payloadSize)
+ {
+ stdplus::print(stderr, "Invalid command length {} vs. payloadSize {}\n",
+ static_cast<uint32_t>(data.size()),
+ static_cast<uint32_t>(payloadSize));
+ return ::ipmi::responseReqDataLenInvalid();
+ }
+
+ // Write the setting
+ try
+ {
+ stdplus::ManagedFd managedFd = stdplus::fd::open(
+ biosSettingPath,
+ stdplus::fd::OpenFlags(stdplus::fd::OpenAccess::WriteOnly)
+ .set(stdplus::fd::OpenFlag::Trunc)
+ .set(stdplus::fd::OpenFlag::Create));
+ stdplus::fd::writeExact(managedFd, data);
+ }
+ catch (const std::exception& e)
+ {
+ stdplus::print(stderr, "Write unsuccessful: {}\n", e.what());
+ return ::ipmi::responseRetBytesUnavailable();
+ }
+
+ // Reply format is: Length of the payload written
+ std::vector<std::uint8_t> reply;
+ reply.reserve(1);
+ reply.emplace_back(static_cast<uint8_t>(payloadSize));
+
+ return ::ipmi::responseSuccess(SysOEMCommands::SysWriteBiosSetting, reply);
+}
+
} // namespace ipmi
} // namespace google
diff --git a/bios_setting.hpp b/bios_setting.hpp
index 7445a7a..ced9dd8 100644
--- a/bios_setting.hpp
+++ b/bios_setting.hpp
@@ -33,6 +33,9 @@
Resp readBiosSetting(
std::span<const uint8_t> data, HandlerInterface* handler,
const std::string& biosSettingPath = "/run/oem_bios_setting");
+Resp writeBiosSetting(
+ std::span<const uint8_t> data, HandlerInterface* handler,
+ const std::string& biosSettingPath = "/run/oem_bios_setting");
} // namespace ipmi
} // namespace google
diff --git a/commands.hpp b/commands.hpp
index abe49f4..595d683 100644
--- a/commands.hpp
+++ b/commands.hpp
@@ -71,6 +71,8 @@
SysGetBMInstanceProperty = 23,
// Read OEM BIOS Setting
SysReadBiosSetting = 24,
+ // Write OEM BIOS Setting
+ SysWriteBiosSetting = 25,
};
} // namespace ipmi
diff --git a/ipmi.cpp b/ipmi.cpp
index 669c665..237b2b5 100644
--- a/ipmi.cpp
+++ b/ipmi.cpp
@@ -97,6 +97,8 @@
return getBMInstanceProperty(data, handler);
case SysReadBiosSetting:
return readBiosSetting(data, handler);
+ case SysWriteBiosSetting:
+ return writeBiosSetting(data, handler);
default:
stdplus::print(stderr, "Invalid subcommand: {:#x}\n", cmd);
return ::ipmi::responseInvalidCommand();
diff --git a/test/bios_setting_unittest.cpp b/test/bios_setting_unittest.cpp
index 45373f4..1d7feb8 100644
--- a/test/bios_setting_unittest.cpp
+++ b/test/bios_setting_unittest.cpp
@@ -84,5 +84,77 @@
std::remove(filename.c_str());
}
+TEST_F(BiosSettingTest, InvalidRequestWrite)
+{
+ // Empty request
+ std::vector<uint8_t> request = {};
+
+ HandlerMock hMock;
+ EXPECT_EQ(::ipmi::responseReqDataLenInvalid(),
+ writeBiosSetting(request, &hMock));
+
+ // Request with payload size 1 but no payload
+ request = {0x01};
+ EXPECT_EQ(::ipmi::responseReqDataLenInvalid(),
+ writeBiosSetting(request, &hMock));
+
+ // Request with payload size 1 but actual payload size of 2 bytes
+ request = {0x01, 0x02, 0x03};
+ EXPECT_EQ(::ipmi::responseReqDataLenInvalid(),
+ writeBiosSetting(request, &hMock));
+
+ // Request with payload size 2 but actual payload of 1 byte
+ request = {0x02, 0x02};
+ EXPECT_EQ(::ipmi::responseReqDataLenInvalid(),
+ writeBiosSetting(request, &hMock));
+}
+
+TEST_F(BiosSettingTest, SuccessfulWrite)
+{
+ std::vector<uint8_t> request = {0x02, 0xDE, 0xAD};
+
+ // Write a dummy file to get around permission issues with CI
+ // (Not needed in local CI)
+ writeTmpFile({});
+ HandlerMock hMock;
+ auto reply = writeBiosSetting(request, &hMock, filename);
+ auto result = ValidateReply(reply);
+ auto& data = result.second;
+
+ EXPECT_EQ(SysOEMCommands::SysWriteBiosSetting, result.first);
+ EXPECT_EQ(std::vector<uint8_t>{2}, data);
+
+ // Validate the payload is correct
+ reply = readBiosSetting(request, &hMock, filename);
+ result = ValidateReply(reply);
+ data = result.second;
+
+ EXPECT_EQ(SysOEMCommands::SysReadBiosSetting, result.first);
+ EXPECT_EQ(request.size() - 1, data.front());
+ EXPECT_EQ(request, data);
+
+ // Verify that we can write a shorter string and it'll replace the original
+ // content of the file
+ request = {0x01, 0x0A};
+
+ reply = writeBiosSetting(request, &hMock, filename);
+ result = ValidateReply(reply);
+ data = result.second;
+
+ EXPECT_EQ(SysOEMCommands::SysWriteBiosSetting, result.first);
+ EXPECT_EQ(std::vector<uint8_t>{1}, data);
+
+ // Validate the payload is correct
+ reply = readBiosSetting(request, &hMock, filename);
+ result = ValidateReply(reply);
+ data = result.second;
+
+ EXPECT_EQ(SysOEMCommands::SysReadBiosSetting, result.first);
+ EXPECT_EQ(request.size() - 1, data.front());
+ EXPECT_EQ(request, data);
+ // Cleanup the settings file
+ std::remove(filename.c_str());
+}
+
} // namespace ipmi
} // namespace google