| /* |
| * Copyright 2018 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 "updater.hpp" |
| |
| #include "firmware_handler.hpp" |
| #include "tool_errors.hpp" |
| |
| #include <algorithm> |
| #include <blobs-ipmid/blobs.hpp> |
| #include <cstring> |
| #include <ipmiblob/blob_errors.hpp> |
| #include <memory> |
| #include <string> |
| #include <thread> |
| |
| namespace host_tool |
| { |
| |
| /* Poll an open verification session. Handling closing the session is not yet |
| * owned by this method. */ |
| bool pollVerificationStatus(std::uint16_t session, |
| ipmiblob::BlobInterface* blob) |
| { |
| using namespace std::chrono_literals; |
| |
| static constexpr auto verificationSleep = 5s; |
| static constexpr int commandAttempts = 20; |
| int attempts = 0; |
| bool exitLoop = false; |
| blobs::FirmwareBlobHandler::VerifyCheckResponses result = |
| blobs::FirmwareBlobHandler::VerifyCheckResponses::other; |
| |
| try |
| { |
| /* Reach back the current status from the verification service output. |
| */ |
| while (attempts++ < commandAttempts) |
| { |
| ipmiblob::StatResponse resp = blob->getStat(session); |
| |
| if (resp.metadata.size() != sizeof(std::uint8_t)) |
| { |
| /* TODO: How do we want to handle the verification failures, |
| * because closing the session to the verify blob has a special |
| * as-of-yet not fully defined behavior. |
| */ |
| std::fprintf(stderr, "Received invalid metadata response!!!\n"); |
| } |
| |
| result = |
| static_cast<blobs::FirmwareBlobHandler::VerifyCheckResponses>( |
| resp.metadata[0]); |
| |
| switch (result) |
| { |
| case blobs::FirmwareBlobHandler::VerifyCheckResponses::failed: |
| std::fprintf(stderr, "failed\n"); |
| exitLoop = true; |
| break; |
| case blobs::FirmwareBlobHandler::VerifyCheckResponses::other: |
| std::fprintf(stderr, "other\n"); |
| break; |
| case blobs::FirmwareBlobHandler::VerifyCheckResponses::running: |
| std::fprintf(stderr, "running\n"); |
| break; |
| case blobs::FirmwareBlobHandler::VerifyCheckResponses::success: |
| std::fprintf(stderr, "success\n"); |
| exitLoop = true; |
| break; |
| default: |
| std::fprintf(stderr, "wat\n"); |
| } |
| |
| if (exitLoop) |
| { |
| break; |
| } |
| std::this_thread::sleep_for(verificationSleep); |
| } |
| } |
| catch (const ipmiblob::BlobException& b) |
| { |
| throw ToolException("blob exception received: " + |
| std::string(b.what())); |
| } |
| |
| /* TODO: If this is reached and it's not success, it may be worth just |
| * throwing a ToolException with a timeout message specifying the final |
| * read's value. |
| * |
| * TODO: Given that excepting from certain points leaves the BMC update |
| * state machine in an inconsistent state, we need to carefully evaluate |
| * which exceptions from the lower layers allow one to try and delete the |
| * blobs to rollback the state and progress. |
| */ |
| return (result == |
| blobs::FirmwareBlobHandler::VerifyCheckResponses::success); |
| } |
| |
| void updaterMain(ipmiblob::BlobInterface* blob, DataInterface* handler, |
| const std::string& imagePath, const std::string& signaturePath) |
| { |
| /* TODO(venture): Add optional parameter to specify the flash type, default |
| * to legacy for now. |
| * |
| * TODO(venture): Move the strings from the FirmwareHandler object to a |
| * boring utils object so it will be more happly linked cleanly to both the |
| * BMC and host-side. |
| */ |
| std::string goalFirmware = "/flash/image"; |
| std::string hashFilename = "/flash/hash"; |
| std::string verifyFilename = "/flash/verify"; |
| |
| /* Get list of blob_ids, check for /flash/image, or /flash/tarball. |
| * TODO(venture) the mechanism doesn't care, but the caller of burn_my_bmc |
| * will have in mind which they're sending and we need to verify it's |
| * available and use it. |
| */ |
| std::vector<std::string> blobs = blob->getBlobList(); |
| auto blobInst = std::find_if( |
| blobs.begin(), blobs.end(), [&goalFirmware](const std::string& iter) { |
| /* Running into weird scenarios where the string comparison doesn't |
| * work. TODO: revisit. |
| */ |
| return (0 == std::memcmp(goalFirmware.c_str(), iter.c_str(), |
| goalFirmware.length())); |
| // return (goalFirmware.compare(iter)); |
| }); |
| if (blobInst == blobs.end()) |
| { |
| throw ToolException(goalFirmware + " not found"); |
| } |
| |
| /* Call stat on /flash/image (or /flash/tarball) and check if data interface |
| * is supported. |
| */ |
| ipmiblob::StatResponse stat; |
| try |
| { |
| stat = blob->getStat(goalFirmware); |
| } |
| catch (const ipmiblob::BlobException& b) |
| { |
| throw ToolException("blob exception received: " + |
| std::string(b.what())); |
| } |
| |
| auto supported = handler->supportedType(); |
| if ((stat.blob_state & supported) == 0) |
| { |
| throw ToolException("data interface selected not supported."); |
| } |
| |
| /* Yay, our data handler is supported. */ |
| |
| /* Send over the firmware image. */ |
| std::fprintf(stderr, "Sending over the firmware image.\n"); |
| std::uint16_t session; |
| try |
| { |
| session = blob->openBlob( |
| goalFirmware, |
| static_cast<std::uint16_t>(supported) | |
| static_cast<std::uint16_t>(blobs::OpenFlags::write)); |
| } |
| catch (const ipmiblob::BlobException& b) |
| { |
| throw ToolException("blob exception received: " + |
| std::string(b.what())); |
| } |
| |
| if (!handler->sendContents(imagePath, session)) |
| { |
| /* Need to close the session on failure, or it's stuck open (until the |
| * blob handler timeout is implemented, and even then, why make it wait. |
| */ |
| blob->closeBlob(session); |
| throw ToolException("Failed to send contents of " + imagePath); |
| } |
| |
| blob->closeBlob(session); |
| |
| /* Send over the hash contents. */ |
| std::fprintf(stderr, "Sending over the hash file.\n"); |
| try |
| { |
| session = blob->openBlob( |
| hashFilename, |
| static_cast<std::uint16_t>(supported) | |
| static_cast<std::uint16_t>(blobs::OpenFlags::write)); |
| } |
| catch (const ipmiblob::BlobException& b) |
| { |
| throw ToolException("blob exception received: " + |
| std::string(b.what())); |
| } |
| |
| if (!handler->sendContents(signaturePath, session)) |
| { |
| blob->closeBlob(session); |
| throw ToolException("Failed to send contents of " + signaturePath); |
| } |
| |
| blob->closeBlob(session); |
| |
| /* Trigger the verification by opening the verify file. */ |
| std::fprintf(stderr, "Opening the verification file\n"); |
| try |
| { |
| session = blob->openBlob( |
| verifyFilename, |
| static_cast<std::uint16_t>(supported) | |
| static_cast<std::uint16_t>(blobs::OpenFlags::write)); |
| } |
| catch (const ipmiblob::BlobException& b) |
| { |
| throw ToolException("blob exception received: " + |
| std::string(b.what())); |
| } |
| |
| std::fprintf( |
| stderr, |
| "Committing to verification file to trigger verification service\n"); |
| try |
| { |
| blob->commit(session, {}); |
| } |
| catch (const ipmiblob::BlobException& b) |
| { |
| throw ToolException("blob exception received: " + |
| std::string(b.what())); |
| } |
| |
| std::fprintf(stderr, |
| "Calling stat on verification session to check status\n"); |
| |
| if (pollVerificationStatus(session, blob)) |
| { |
| std::fprintf(stderr, "Verification returned success\n"); |
| } |
| else |
| { |
| std::fprintf(stderr, "Verification returned non-success (could still " |
| "be running (unlikely))\n"); |
| } |
| |
| /* DO NOT CLOSE the verification session until it's done. |
| * TODO: Evaluate what closing verification should do? If the process is |
| * complete, nothing bad, maybe reset the entire state machine? This will |
| * benefit from a diagram. |
| */ |
| blob->closeBlob(session); |
| } |
| |
| } // namespace host_tool |