Add sys command for powercycle on host shutdown.

The new command will trigger a power cycle the next time the host shuts
down. This can exist in parallel with the existing mechanism to trigger
a power cycle after a specified time interval.

The implementation of host state detection and power cycling is platfrom
specific; the new command will just add a temporary file that marks the
system ready to powercycle on the next shutdown. Usually, a systemd unit
would be enabled by the presence of this file to handle the power
cycling process.

Signed-off-by: Shounak Mitra <shounak@google.com>
Change-Id: I0cc40307748fb996be3f6062d8cba1a4b5049683
diff --git a/README.md b/README.md
index e0a09d4..4e79158 100644
--- a/README.md
+++ b/README.md
@@ -181,3 +181,20 @@
 |0x00|0x06|Subcommand
 |0x01|Model name length (say N)|Model name length
 |0x02...0x02 + N - 1|Model name|Model name without null terminator
+
+### HardResetOnShutdown - SubCommand 0x08
+
+Tells the BMC to powercycle the next time the host shuts down.
+
+Request
+
+|Byte(s) |Value |Data
+|--------|------|----
+|0x00|0x08|Subcommand
+
+Response
+
+|Byte(s) |Value |Data
+|--------|------|----
+|0x00|0x08|Subcommand
+
diff --git a/commands.hpp b/commands.hpp
index e8f1efa..19632ef 100644
--- a/commands.hpp
+++ b/commands.hpp
@@ -23,6 +23,8 @@
     SysEntityName = 6,
     // Returns the machine name of the image
     SysMachineName = 7,
+    // Arm for psu reset on host shutdown
+    SysPsuHardResetOnShutdown = 8,
 };
 
 } // namespace ipmi
diff --git a/handler.cpp b/handler.cpp
index f9a964d..4f1bc56 100644
--- a/handler.cpp
+++ b/handler.cpp
@@ -199,6 +199,20 @@
     }
 }
 
+static constexpr auto RESET_ON_SHUTDOWN_FILENAME = "/run/powercycle_on_s5";
+
+void Handler::psuResetOnShutdown() const
+{
+    std::ofstream ofs;
+    ofs.open(RESET_ON_SHUTDOWN_FILENAME, std::ofstream::out);
+    if (!ofs.good())
+    {
+        std::fprintf(stderr, "Unable to open file for output.\n");
+        throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR);
+    }
+    ofs.close();
+}
+
 std::string Handler::getEntityName(std::uint8_t id, std::uint8_t instance)
 {
     // Check if we support this Entity ID.
diff --git a/handler.hpp b/handler.hpp
index 766ddde..2db83df 100644
--- a/handler.hpp
+++ b/handler.hpp
@@ -53,6 +53,13 @@
     virtual void psuResetDelay(std::uint32_t delay) const = 0;
 
     /**
+     * Arm for PSU reset on host shutdown.
+     *
+     * @throw IpmiException on failure.
+     */
+    virtual void psuResetOnShutdown() const = 0;
+
+    /**
      * Return the entity name.
      * On the first call to this method it'll build the list of entities.
      * @todo Consider moving the list building to construction time (and ignore
diff --git a/handler_impl.hpp b/handler_impl.hpp
index 7771da7..f1ea491 100644
--- a/handler_impl.hpp
+++ b/handler_impl.hpp
@@ -28,6 +28,7 @@
     std::int64_t getRxPackets(const std::string& name) const override;
     VersionTuple getCpldVersion(unsigned int id) const override;
     void psuResetDelay(std::uint32_t delay) const override;
+    void psuResetOnShutdown() const override;
     std::string getEntityName(std::uint8_t id, std::uint8_t instance) override;
     std::string getMachineName() override;
     void buildI2cPcieMapping() override;
diff --git a/ipmi.cpp b/ipmi.cpp
index 509ec15..cdf3b64 100644
--- a/ipmi.cpp
+++ b/ipmi.cpp
@@ -66,6 +66,9 @@
             return getEntityName(reqBuf, replyCmdBuf, dataLen, handler);
         case SysMachineName:
             return getMachineName(reqBuf, replyCmdBuf, dataLen, handler);
+        case SysPsuHardResetOnShutdown:
+            return psuHardResetOnShutdown(reqBuf, replyCmdBuf, dataLen,
+                                          handler);
         default:
             std::fprintf(stderr, "Invalid subcommand: 0x%x\n", reqBuf[0]);
             return IPMI_CC_INVALID;
diff --git a/psu.cpp b/psu.cpp
index d3ded47..49c0b72 100644
--- a/psu.cpp
+++ b/psu.cpp
@@ -56,5 +56,34 @@
     return IPMI_CC_OK;
 }
 
+ipmi_ret_t psuHardResetOnShutdown(const uint8_t* reqBuf, uint8_t* replyBuf,
+                                  size_t* dataLen,
+                                  const HandlerInterface* handler)
+{
+    struct PsuResetOnShutdownRequest request;
+
+    if ((*dataLen) < sizeof(request))
+    {
+        std::fprintf(stderr, "Invalid command length: %u\n",
+                     static_cast<uint32_t>(*dataLen));
+        return IPMI_CC_REQ_DATA_LEN_INVALID;
+    }
+
+    std::memcpy(&request, &reqBuf[0], sizeof(request));
+    try
+    {
+        handler->psuResetOnShutdown();
+    }
+    catch (const IpmiException& e)
+    {
+        return e.getIpmiError();
+    }
+
+    replyBuf[0] = SysPsuHardResetOnShutdown;
+    (*dataLen) = sizeof(uint8_t);
+
+    return IPMI_CC_OK;
+}
+
 } // namespace ipmi
 } // namespace google
diff --git a/psu.hpp b/psu.hpp
index d45ce7f..8407a06 100644
--- a/psu.hpp
+++ b/psu.hpp
@@ -16,9 +16,19 @@
     uint32_t delay;
 } __attribute__((packed));
 
+struct PsuResetOnShutdownRequest
+{
+    uint8_t subcommand;
+} __attribute__((packed));
+
 // Set a time-delayed PSU hard reset.
 ipmi_ret_t psuHardReset(const uint8_t* reqBuf, uint8_t* replyBuf,
                         size_t* dataLen, const HandlerInterface* handler);
 
+// Arm for PSU hard reset on host shutdown.
+ipmi_ret_t psuHardResetOnShutdown(const uint8_t* reqBuf, uint8_t* replyBuf,
+                                  size_t* dataLen,
+                                  const HandlerInterface* handler);
+
 } // namespace ipmi
 } // namespace google
diff --git a/test/handler_mock.hpp b/test/handler_mock.hpp
index f214db8..e20886a 100644
--- a/test/handler_mock.hpp
+++ b/test/handler_mock.hpp
@@ -26,6 +26,7 @@
                        std::tuple<std::uint8_t, std::uint8_t, std::uint8_t,
                                   std::uint8_t>(unsigned int));
     MOCK_CONST_METHOD1(psuResetDelay, void(std::uint32_t));
+    MOCK_CONST_METHOD0(psuResetOnShutdown, void());
     MOCK_METHOD2(getEntityName, std::string(std::uint8_t, std::uint8_t));
     MOCK_METHOD0(getMachineName, std::string());
     MOCK_METHOD0(buildI2cPcieMapping, void());
diff --git a/test/psu_unittest.cpp b/test/psu_unittest.cpp
index 7127c3d..adf3278 100644
--- a/test/psu_unittest.cpp
+++ b/test/psu_unittest.cpp
@@ -46,5 +46,32 @@
               psuHardReset(request.data(), reply, &dataLen, &hMock));
 }
 
+TEST(PsuResetOnShutdownCommandTest, InvalidRequestLength)
+{
+    std::vector<std::uint8_t> request;
+    size_t dataLen = request.size();
+    std::uint8_t reply[MAX_IPMI_BUFFER];
+
+    HandlerMock hMock;
+    EXPECT_EQ(IPMI_CC_REQ_DATA_LEN_INVALID,
+              psuHardResetOnShutdown(request.data(), reply, &dataLen, &hMock));
+}
+
+TEST(PsuResetOnShutdownCommandTest, ValidRequest)
+{
+    struct PsuResetOnShutdownRequest requestContents;
+    requestContents.subcommand = SysOEMCommands::SysPsuHardReset;
+
+    std::vector<std::uint8_t> request(sizeof(requestContents));
+    std::memcpy(request.data(), &requestContents, sizeof(requestContents));
+    size_t dataLen = request.size();
+    std::uint8_t reply[MAX_IPMI_BUFFER];
+
+    HandlerMock hMock;
+    EXPECT_CALL(hMock, psuResetOnShutdown());
+    EXPECT_EQ(IPMI_CC_OK,
+              psuHardResetOnShutdown(request.data(), reply, &dataLen, &hMock));
+}
+
 } // namespace ipmi
 } // namespace google