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