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 22 #include <algorithm> 23 #include <blobs-ipmid/blobs.hpp> 24 #include <cstring> 25 #include <ipmiblob/blob_errors.hpp> 26 #include <memory> 27 #include <string> 28 #include <thread> 29 30 namespace host_tool 31 { 32 33 /* Poll an open verification session. Handling closing the session is not yet 34 * owned by this method. */ 35 bool pollVerificationStatus(std::uint16_t session, 36 ipmiblob::BlobInterface* blob) 37 { 38 using namespace std::chrono_literals; 39 40 static constexpr auto verificationSleep = 5s; 41 static constexpr int commandAttempts = 20; 42 int attempts = 0; 43 bool exitLoop = false; 44 blobs::FirmwareBlobHandler::VerifyCheckResponses result = 45 blobs::FirmwareBlobHandler::VerifyCheckResponses::other; 46 47 try 48 { 49 /* Reach back the current status from the verification service output. 50 */ 51 while (attempts++ < commandAttempts) 52 { 53 ipmiblob::StatResponse resp = blob->getStat(session); 54 55 if (resp.metadata.size() != sizeof(std::uint8_t)) 56 { 57 /* TODO: How do we want to handle the verification failures, 58 * because closing the session to the verify blob has a special 59 * as-of-yet not fully defined behavior. 60 */ 61 std::fprintf(stderr, "Received invalid metadata response!!!\n"); 62 } 63 64 result = 65 static_cast<blobs::FirmwareBlobHandler::VerifyCheckResponses>( 66 resp.metadata[0]); 67 68 switch (result) 69 { 70 case blobs::FirmwareBlobHandler::VerifyCheckResponses::failed: 71 std::fprintf(stderr, "failed\n"); 72 exitLoop = true; 73 break; 74 case blobs::FirmwareBlobHandler::VerifyCheckResponses::other: 75 std::fprintf(stderr, "other\n"); 76 break; 77 case blobs::FirmwareBlobHandler::VerifyCheckResponses::running: 78 std::fprintf(stderr, "running\n"); 79 break; 80 case blobs::FirmwareBlobHandler::VerifyCheckResponses::success: 81 std::fprintf(stderr, "success\n"); 82 exitLoop = true; 83 break; 84 default: 85 std::fprintf(stderr, "wat\n"); 86 } 87 88 if (exitLoop) 89 { 90 break; 91 } 92 std::this_thread::sleep_for(verificationSleep); 93 } 94 } 95 catch (const ipmiblob::BlobException& b) 96 { 97 throw ToolException("blob exception received: " + 98 std::string(b.what())); 99 } 100 101 /* TODO: If this is reached and it's not success, it may be worth just 102 * throwing a ToolException with a timeout message specifying the final 103 * read's value. 104 * 105 * TODO: Given that excepting from certain points leaves the BMC update 106 * state machine in an inconsistent state, we need to carefully evaluate 107 * which exceptions from the lower layers allow one to try and delete the 108 * blobs to rollback the state and progress. 109 */ 110 return (result == 111 blobs::FirmwareBlobHandler::VerifyCheckResponses::success); 112 } 113 114 void updaterMain(ipmiblob::BlobInterface* blob, DataInterface* handler, 115 const std::string& imagePath, const std::string& signaturePath) 116 { 117 /* TODO(venture): Add optional parameter to specify the flash type, default 118 * to legacy for now. 119 * 120 * TODO(venture): Move the strings from the FirmwareHandler object to a 121 * boring utils object so it will be more happly linked cleanly to both the 122 * BMC and host-side. 123 */ 124 std::string goalFirmware = "/flash/image"; 125 std::string hashFilename = "/flash/hash"; 126 std::string verifyFilename = "/flash/verify"; 127 128 /* Get list of blob_ids, check for /flash/image, or /flash/tarball. 129 * TODO(venture) the mechanism doesn't care, but the caller of burn_my_bmc 130 * will have in mind which they're sending and we need to verify it's 131 * available and use it. 132 */ 133 std::vector<std::string> blobs = blob->getBlobList(); 134 auto blobInst = std::find_if( 135 blobs.begin(), blobs.end(), [&goalFirmware](const std::string& iter) { 136 /* Running into weird scenarios where the string comparison doesn't 137 * work. TODO: revisit. 138 */ 139 return (0 == std::memcmp(goalFirmware.c_str(), iter.c_str(), 140 goalFirmware.length())); 141 // return (goalFirmware.compare(iter)); 142 }); 143 if (blobInst == blobs.end()) 144 { 145 throw ToolException(goalFirmware + " not found"); 146 } 147 148 /* Call stat on /flash/image (or /flash/tarball) and check if data interface 149 * is supported. 150 */ 151 ipmiblob::StatResponse stat; 152 try 153 { 154 stat = blob->getStat(goalFirmware); 155 } 156 catch (const ipmiblob::BlobException& b) 157 { 158 throw ToolException("blob exception received: " + 159 std::string(b.what())); 160 } 161 162 auto supported = handler->supportedType(); 163 if ((stat.blob_state & supported) == 0) 164 { 165 throw ToolException("data interface selected not supported."); 166 } 167 168 /* Yay, our data handler is supported. */ 169 170 /* Send over the firmware image. */ 171 std::fprintf(stderr, "Sending over the firmware image.\n"); 172 std::uint16_t session; 173 try 174 { 175 session = blob->openBlob( 176 goalFirmware, 177 static_cast<std::uint16_t>(supported) | 178 static_cast<std::uint16_t>(blobs::OpenFlags::write)); 179 } 180 catch (const ipmiblob::BlobException& b) 181 { 182 throw ToolException("blob exception received: " + 183 std::string(b.what())); 184 } 185 186 if (!handler->sendContents(imagePath, session)) 187 { 188 /* Need to close the session on failure, or it's stuck open (until the 189 * blob handler timeout is implemented, and even then, why make it wait. 190 */ 191 blob->closeBlob(session); 192 throw ToolException("Failed to send contents of " + imagePath); 193 } 194 195 blob->closeBlob(session); 196 197 /* Send over the hash contents. */ 198 std::fprintf(stderr, "Sending over the hash file.\n"); 199 try 200 { 201 session = blob->openBlob( 202 hashFilename, 203 static_cast<std::uint16_t>(supported) | 204 static_cast<std::uint16_t>(blobs::OpenFlags::write)); 205 } 206 catch (const ipmiblob::BlobException& b) 207 { 208 throw ToolException("blob exception received: " + 209 std::string(b.what())); 210 } 211 212 if (!handler->sendContents(signaturePath, session)) 213 { 214 blob->closeBlob(session); 215 throw ToolException("Failed to send contents of " + signaturePath); 216 } 217 218 blob->closeBlob(session); 219 220 /* Trigger the verification by opening the verify file. */ 221 std::fprintf(stderr, "Opening the verification file\n"); 222 try 223 { 224 session = blob->openBlob( 225 verifyFilename, 226 static_cast<std::uint16_t>(supported) | 227 static_cast<std::uint16_t>(blobs::OpenFlags::write)); 228 } 229 catch (const ipmiblob::BlobException& b) 230 { 231 throw ToolException("blob exception received: " + 232 std::string(b.what())); 233 } 234 235 std::fprintf( 236 stderr, 237 "Committing to verification file to trigger verification service\n"); 238 try 239 { 240 blob->commit(session, {}); 241 } 242 catch (const ipmiblob::BlobException& b) 243 { 244 throw ToolException("blob exception received: " + 245 std::string(b.what())); 246 } 247 248 std::fprintf(stderr, 249 "Calling stat on verification session to check status\n"); 250 251 if (pollVerificationStatus(session, blob)) 252 { 253 std::fprintf(stderr, "Verification returned success\n"); 254 } 255 else 256 { 257 std::fprintf(stderr, "Verification returned non-success (could still " 258 "be running (unlikely))\n"); 259 } 260 261 /* DO NOT CLOSE the verification session until it's done. 262 * TODO: Evaluate what closing verification should do? If the process is 263 * complete, nothing bad, maybe reset the entire state machine? This will 264 * benefit from a diagram. 265 */ 266 blob->closeBlob(session); 267 268 return; 269 } 270 271 } // namespace host_tool 272