1 /* 2 * Copyright 2018 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #include "updater.hpp" 18 19 #include "firmware_handler.hpp" 20 #include "tool_errors.hpp" 21 #include "util.hpp" 22 23 #include <algorithm> 24 #include <blobs-ipmid/blobs.hpp> 25 #include <cstring> 26 #include <ipmiblob/blob_errors.hpp> 27 #include <memory> 28 #include <string> 29 #include <thread> 30 #include <vector> 31 32 namespace host_tool 33 { 34 35 bool UpdateHandler::checkAvailable(const std::string& goalFirmware) 36 { 37 std::vector<std::string> blobs = blob->getBlobList(); 38 39 auto blobInst = std::find_if( 40 blobs.begin(), blobs.end(), [&goalFirmware](const std::string& iter) { 41 /* Running into weird scenarios where the string comparison doesn't 42 * work. TODO: revisit. 43 */ 44 return (0 == std::memcmp(goalFirmware.c_str(), iter.c_str(), 45 goalFirmware.length())); 46 // return (goalFirmware.compare(iter)); 47 }); 48 if (blobInst == blobs.end()) 49 { 50 std::fprintf(stderr, "%s not found\n", goalFirmware.c_str()); 51 return false; 52 } 53 54 /* Call stat on /flash/image (or /flash/tarball) and check if data interface 55 * is supported. 56 */ 57 ipmiblob::StatResponse stat; 58 59 try 60 { 61 stat = blob->getStat(goalFirmware); 62 } 63 catch (const ipmiblob::BlobException& b) 64 { 65 std::fprintf(stderr, "Received exception '%s' on getStat\n", b.what()); 66 return false; 67 } 68 69 auto supported = handler->supportedType(); 70 if ((stat.blob_state & supported) == 0) 71 { 72 std::fprintf(stderr, "data interface selected not supported.\n"); 73 return false; 74 } 75 76 return true; 77 } 78 79 void UpdateHandler::sendFile(const std::string& target, const std::string& path) 80 { 81 std::uint16_t session; 82 auto supported = handler->supportedType(); 83 84 try 85 { 86 session = blob->openBlob( 87 target, static_cast<std::uint16_t>(supported) | 88 static_cast<std::uint16_t>(blobs::OpenFlags::write)); 89 } 90 catch (const ipmiblob::BlobException& b) 91 { 92 throw ToolException("blob exception received: " + 93 std::string(b.what())); 94 } 95 96 if (!handler->sendContents(path, session)) 97 { 98 /* Need to close the session on failure, or it's stuck open (until the 99 * blob handler timeout is implemented, and even then, why make it wait. 100 */ 101 blob->closeBlob(session); 102 throw ToolException("Failed to send contents of " + path); 103 } 104 105 blob->closeBlob(session); 106 } 107 108 /* Poll an open verification session. Handling closing the session is not yet 109 * owned by this method. */ 110 bool pollVerificationStatus(std::uint16_t session, 111 ipmiblob::BlobInterface* blob) 112 { 113 using namespace std::chrono_literals; 114 115 static constexpr auto verificationSleep = 5s; 116 static constexpr int commandAttempts = 20; 117 int attempts = 0; 118 bool exitLoop = false; 119 blobs::FirmwareBlobHandler::VerifyCheckResponses result = 120 blobs::FirmwareBlobHandler::VerifyCheckResponses::other; 121 122 try 123 { 124 /* Reach back the current status from the verification service output. 125 */ 126 while (attempts++ < commandAttempts) 127 { 128 ipmiblob::StatResponse resp = blob->getStat(session); 129 130 if (resp.metadata.size() != sizeof(std::uint8_t)) 131 { 132 /* TODO: How do we want to handle the verification failures, 133 * because closing the session to the verify blob has a special 134 * as-of-yet not fully defined behavior. 135 */ 136 std::fprintf(stderr, "Received invalid metadata response!!!\n"); 137 } 138 139 result = 140 static_cast<blobs::FirmwareBlobHandler::VerifyCheckResponses>( 141 resp.metadata[0]); 142 143 switch (result) 144 { 145 case blobs::FirmwareBlobHandler::VerifyCheckResponses::failed: 146 std::fprintf(stderr, "failed\n"); 147 exitLoop = true; 148 break; 149 case blobs::FirmwareBlobHandler::VerifyCheckResponses::other: 150 std::fprintf(stderr, "other\n"); 151 break; 152 case blobs::FirmwareBlobHandler::VerifyCheckResponses::running: 153 std::fprintf(stderr, "running\n"); 154 break; 155 case blobs::FirmwareBlobHandler::VerifyCheckResponses::success: 156 std::fprintf(stderr, "success\n"); 157 exitLoop = true; 158 break; 159 default: 160 std::fprintf(stderr, "wat\n"); 161 } 162 163 if (exitLoop) 164 { 165 break; 166 } 167 std::this_thread::sleep_for(verificationSleep); 168 } 169 } 170 catch (const ipmiblob::BlobException& b) 171 { 172 throw ToolException("blob exception received: " + 173 std::string(b.what())); 174 } 175 176 /* TODO: If this is reached and it's not success, it may be worth just 177 * throwing a ToolException with a timeout message specifying the final 178 * read's value. 179 * 180 * TODO: Given that excepting from certain points leaves the BMC update 181 * state machine in an inconsistent state, we need to carefully evaluate 182 * which exceptions from the lower layers allow one to try and delete the 183 * blobs to rollback the state and progress. 184 */ 185 return (result == 186 blobs::FirmwareBlobHandler::VerifyCheckResponses::success); 187 } 188 189 bool UpdateHandler::verifyFile(const std::string& target) 190 { 191 std::uint16_t session; 192 bool success = false; 193 194 try 195 { 196 session = blob->openBlob( 197 target, static_cast<std::uint16_t>(blobs::OpenFlags::write)); 198 } 199 catch (const ipmiblob::BlobException& b) 200 { 201 throw ToolException("blob exception received: " + 202 std::string(b.what())); 203 } 204 205 std::fprintf( 206 stderr, 207 "Committing to verification file to trigger verification service\n"); 208 209 try 210 { 211 blob->commit(session, {}); 212 } 213 catch (const ipmiblob::BlobException& b) 214 { 215 throw ToolException("blob exception received: " + 216 std::string(b.what())); 217 } 218 219 std::fprintf(stderr, 220 "Calling stat on verification session to check status\n"); 221 222 if (pollVerificationStatus(session, blob)) 223 { 224 std::fprintf(stderr, "Verification returned success\n"); 225 success = true; 226 } 227 else 228 { 229 std::fprintf(stderr, "Verification returned non-success (could still " 230 "be running (unlikely))\n"); 231 } 232 233 blob->closeBlob(session); 234 return (success == true); 235 } 236 237 void updaterMain(UpdateHandler* updater, const std::string& imagePath, 238 const std::string& signaturePath) 239 { 240 /* TODO(venture): Add optional parameter to specify the flash type, default 241 * to legacy for now. 242 */ 243 bool goalSupported = updater->checkAvailable(blobs::staticLayoutBlobId); 244 if (!goalSupported) 245 { 246 throw ToolException("Goal firmware or interface not supported"); 247 } 248 249 /* Yay, our data handler is supported. */ 250 251 /* Send over the firmware image. */ 252 std::fprintf(stderr, "Sending over the firmware image.\n"); 253 updater->sendFile(blobs::staticLayoutBlobId, imagePath); 254 255 /* Send over the hash contents. */ 256 std::fprintf(stderr, "Sending over the hash file.\n"); 257 updater->sendFile(blobs::hashBlobId, signaturePath); 258 259 /* Trigger the verification by opening the verify file. */ 260 std::fprintf(stderr, "Opening the verification file\n"); 261 if (updater->verifyFile(blobs::verifyBlobId)) 262 { 263 std::fprintf(stderr, "succeeded\n"); 264 } 265 else 266 { 267 std::fprintf(stderr, "failed\n"); 268 } 269 } 270 271 } // namespace host_tool 272