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/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