bmc: add ActionPack notion to bundle actions

Each firmware type will provide its own set of action implementations
for each step, preparation, verification, and update.

Signed-off-by: Patrick Venture <venture@google.com>
Change-Id: Id6409ac356a74e9094272b37709861e2a33d9862
diff --git a/bmc/firmware_handler.cpp b/bmc/firmware_handler.cpp
index 855c3fa..d193a4d 100644
--- a/bmc/firmware_handler.cpp
+++ b/bmc/firmware_handler.cpp
@@ -40,10 +40,7 @@
 std::unique_ptr<blobs::GenericBlobInterface>
     FirmwareBlobHandler::CreateFirmwareBlobHandler(
         const std::vector<HandlerPack>& firmwares,
-        const std::vector<DataHandlerPack>& transports,
-        std::unique_ptr<TriggerableActionInterface> preparation,
-        std::unique_ptr<TriggerableActionInterface> verification,
-        std::unique_ptr<TriggerableActionInterface> update)
+        const std::vector<DataHandlerPack>& transports, ActionMap&& actionPacks)
 {
     /* There must be at least one. */
     if (!firmwares.size())
@@ -75,8 +72,7 @@
     }
 
     return std::make_unique<FirmwareBlobHandler>(
-        firmwares, blobs, transports, bitmask, std::move(preparation),
-        std::move(verification), std::move(update));
+        firmwares, blobs, transports, bitmask, std::move(actionPacks));
 }
 
 /* Check if the path is in our supported list (or active list). */
@@ -172,6 +168,7 @@
 ActionStatus FirmwareBlobHandler::getActionStatus()
 {
     ActionStatus value = ActionStatus::unknown;
+    auto* pack = getActionPack();
 
     switch (state)
     {
@@ -179,7 +176,13 @@
             value = ActionStatus::unknown;
             break;
         case UpdateState::verificationStarted:
-            value = verification->status();
+            /* If we got here, there must be data AND a hash, not just a hash,
+             * therefore pack will be known. */
+            if (!pack)
+            {
+                break;
+            }
+            value = pack->verification->status();
             lastVerificationStatus = value;
             break;
         case UpdateState::verificationCompleted:
@@ -189,7 +192,11 @@
             value = ActionStatus::unknown;
             break;
         case UpdateState::updateStarted:
-            value = update->status();
+            if (!pack)
+            {
+                break;
+            }
+            value = pack->update->status();
             lastUpdateStatus = value;
             break;
         case UpdateState::updateCompleted:
@@ -340,6 +347,9 @@
 
     switch (state)
     {
+        case UpdateState::notYetStarted:
+            /* Only hashBlobId and firmware BlobIds present. */
+            break;
         case UpdateState::uploadInProgress:
             /* Unreachable code because if it's started a file is open. */
             break;
@@ -391,6 +401,35 @@
             break;
     }
 
+    /* To support multiple firmware options, we need to make sure they're
+     * opening the one they already opened during this update sequence, or it's
+     * the first time they're opening it.
+     */
+    if (path != hashBlobId)
+    {
+        /* If they're not opening the hashBlobId they must be opening a firmware
+         * handler.
+         */
+        if (openedFirmwareType.empty())
+        {
+            /* First time for this sequence. */
+            openedFirmwareType = path;
+        }
+        else
+        {
+            if (openedFirmwareType != path)
+            {
+                /* Previously, in this sequence they opened /flash/image, and
+                 * now they're opening /flash/bios without finishing out
+                 * /flash/image (for example).
+                 */
+                std::fprintf(stderr, "Trying to open alternate firmware while "
+                                     "unfinished with other firmware.\n");
+                return false;
+            }
+        }
+    }
+
     /* There are two abstractions at play, how you get the data and how you
      * handle that data. such that, whether the data comes from the PCI bridge
      * or LPC bridge is not connected to whether the data goes into a static
@@ -731,8 +770,12 @@
         /* Store this transition logic here instead of ::open() */
         if (!preparationTriggered)
         {
-            preparation->trigger();
-            preparationTriggered = true;
+            auto* pack = getActionPack();
+            if (pack)
+            {
+                pack->preparation->trigger();
+                preparationTriggered = true;
+            }
         }
     }
 }
@@ -763,17 +806,28 @@
     removeBlobId(activeImageBlobId);
     removeBlobId(activeHashBlobId);
 
+    openedFirmwareType = "";
     changeState(UpdateState::notYetStarted);
 }
 
 void FirmwareBlobHandler::abortVerification()
 {
-    verification->abort();
+    auto* pack = getActionPack();
+    if (pack)
+    {
+        pack->verification->abort();
+    }
 }
 
 bool FirmwareBlobHandler::triggerVerification()
 {
-    bool result = verification->trigger();
+    auto* pack = getActionPack();
+    if (!pack)
+    {
+        return false;
+    }
+
+    bool result = pack->verification->trigger();
     if (result)
     {
         changeState(UpdateState::verificationStarted);
@@ -784,12 +838,22 @@
 
 void FirmwareBlobHandler::abortUpdate()
 {
-    update->abort();
+    auto* pack = getActionPack();
+    if (pack)
+    {
+        pack->update->abort();
+    }
 }
 
 bool FirmwareBlobHandler::triggerUpdate()
 {
-    bool result = update->trigger();
+    auto* pack = getActionPack();
+    if (!pack)
+    {
+        return false;
+    }
+
+    bool result = pack->update->trigger();
     if (result)
     {
         changeState(UpdateState::updateStarted);