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 "status.hpp" 21 #include "tool_errors.hpp" 22 #include "util.hpp" 23 24 #include <algorithm> 25 #include <blobs-ipmid/blobs.hpp> 26 #include <cstring> 27 #include <ipmiblob/blob_errors.hpp> 28 #include <memory> 29 #include <string> 30 #include <thread> 31 #include <vector> 32 33 namespace host_tool 34 { 35 36 bool UpdateHandler::checkAvailable(const std::string& goalFirmware) 37 { 38 std::vector<std::string> blobs = blob->getBlobList(); 39 40 auto blobInst = std::find_if( 41 blobs.begin(), blobs.end(), [&goalFirmware](const std::string& iter) { 42 /* Running into weird scenarios where the string comparison doesn't 43 * work. TODO: revisit. 44 */ 45 return (0 == std::memcmp(goalFirmware.c_str(), iter.c_str(), 46 goalFirmware.length())); 47 // return (goalFirmware.compare(iter)); 48 }); 49 if (blobInst == blobs.end()) 50 { 51 std::fprintf(stderr, "%s not found\n", goalFirmware.c_str()); 52 return false; 53 } 54 55 /* Call stat on /flash/image (or /flash/tarball) and check if data interface 56 * is supported. 57 */ 58 ipmiblob::StatResponse stat; 59 60 try 61 { 62 stat = blob->getStat(goalFirmware); 63 } 64 catch (const ipmiblob::BlobException& b) 65 { 66 std::fprintf(stderr, "Received exception '%s' on getStat\n", b.what()); 67 return false; 68 } 69 70 auto supported = handler->supportedType(); 71 if ((stat.blob_state & supported) == 0) 72 { 73 std::fprintf(stderr, "data interface selected not supported.\n"); 74 return false; 75 } 76 77 return true; 78 } 79 80 void UpdateHandler::sendFile(const std::string& target, const std::string& path) 81 { 82 std::uint16_t session; 83 auto supported = handler->supportedType(); 84 85 try 86 { 87 session = blob->openBlob( 88 target, static_cast<std::uint16_t>(supported) | 89 static_cast<std::uint16_t>(blobs::OpenFlags::write)); 90 } 91 catch (const ipmiblob::BlobException& b) 92 { 93 throw ToolException("blob exception received: " + 94 std::string(b.what())); 95 } 96 97 if (!handler->sendContents(path, session)) 98 { 99 /* Need to close the session on failure, or it's stuck open (until the 100 * blob handler timeout is implemented, and even then, why make it wait. 101 */ 102 blob->closeBlob(session); 103 throw ToolException("Failed to send contents of " + path); 104 } 105 106 blob->closeBlob(session); 107 } 108 109 /* Poll an open verification session. Handling closing the session is not yet 110 * owned by this method. 111 */ 112 bool pollStatus(std::uint16_t session, ipmiblob::BlobInterface* blob) 113 { 114 using namespace std::chrono_literals; 115 116 static constexpr auto verificationSleep = 5s; 117 ipmi_flash::ActionStatus result = ipmi_flash::ActionStatus::unknown; 118 119 try 120 { 121 static constexpr int commandAttempts = 20; 122 int attempts = 0; 123 bool exitLoop = false; 124 125 /* Reach back the current status from the verification service output. 126 */ 127 while (attempts++ < commandAttempts) 128 { 129 ipmiblob::StatResponse resp = blob->getStat(session); 130 131 if (resp.metadata.size() != sizeof(std::uint8_t)) 132 { 133 /* TODO: How do we want to handle the verification failures, 134 * because closing the session to the verify blob has a special 135 * as-of-yet not fully defined behavior. 136 */ 137 std::fprintf(stderr, "Received invalid metadata response!!!\n"); 138 } 139 140 result = static_cast<ipmi_flash::ActionStatus>(resp.metadata[0]); 141 142 switch (result) 143 { 144 case ipmi_flash::ActionStatus::failed: 145 std::fprintf(stderr, "failed\n"); 146 exitLoop = true; 147 break; 148 case ipmi_flash::ActionStatus::unknown: 149 std::fprintf(stderr, "other\n"); 150 break; 151 case ipmi_flash::ActionStatus::running: 152 std::fprintf(stderr, "running\n"); 153 break; 154 case ipmi_flash::ActionStatus::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 == ipmi_flash::ActionStatus::success); 185 } 186 187 bool UpdateHandler::verifyFile(const std::string& target) 188 { 189 std::uint16_t session; 190 bool success = false; 191 192 try 193 { 194 session = blob->openBlob( 195 target, static_cast<std::uint16_t>(blobs::OpenFlags::write)); 196 } 197 catch (const ipmiblob::BlobException& b) 198 { 199 throw ToolException("blob exception received: " + 200 std::string(b.what())); 201 } 202 203 std::fprintf(stderr, "Committing to %s to trigger service\n", 204 target.c_str()); 205 206 try 207 { 208 blob->commit(session, {}); 209 } 210 catch (const ipmiblob::BlobException& b) 211 { 212 throw ToolException("blob exception received: " + 213 std::string(b.what())); 214 } 215 216 std::fprintf(stderr, "Calling stat on %s session to check status\n", 217 target.c_str()); 218 219 if (pollStatus(session, blob)) 220 { 221 std::fprintf(stderr, "Returned success\n"); 222 success = true; 223 } 224 else 225 { 226 std::fprintf(stderr, "Returned non-success (could still " 227 "be running (unlikely))\n"); 228 } 229 230 blob->closeBlob(session); 231 return (success == true); 232 } 233 234 void UpdateHandler::cleanArtifacts() 235 { 236 /* open(), commit(), close() */ 237 std::uint16_t session; 238 239 /* Errors aren't important for this call. */ 240 try 241 { 242 std::fprintf(stderr, "Opening the cleanup blob\n"); 243 session = 244 blob->openBlob(ipmi_flash::cleanupBlobId, 245 static_cast<std::uint16_t>(blobs::OpenFlags::write)); 246 std::fprintf(stderr, "Committing to the cleanup blob\n"); 247 blob->commit(session, {}); 248 std::fprintf(stderr, "Closing cleanup blob\n"); 249 blob->closeBlob(session); 250 } 251 catch (...) 252 { 253 } 254 } 255 256 void updaterMain(UpdateHandlerInterface* updater, const std::string& imagePath, 257 const std::string& signaturePath) 258 { 259 /* TODO(venture): Add optional parameter to specify the flash type, default 260 * to legacy for now. 261 */ 262 bool goalSupported = 263 updater->checkAvailable(ipmi_flash::staticLayoutBlobId); 264 if (!goalSupported) 265 { 266 throw ToolException("Goal firmware or interface not supported"); 267 } 268 269 /* Yay, our data handler is supported. */ 270 try 271 { 272 273 /* Send over the firmware image. */ 274 std::fprintf(stderr, "Sending over the firmware image.\n"); 275 updater->sendFile(ipmi_flash::staticLayoutBlobId, imagePath); 276 277 /* Send over the hash contents. */ 278 std::fprintf(stderr, "Sending over the hash file.\n"); 279 updater->sendFile(ipmi_flash::hashBlobId, signaturePath); 280 281 /* Trigger the verification by opening and committing the verify file. 282 */ 283 std::fprintf(stderr, "Opening the verification file\n"); 284 if (updater->verifyFile(ipmi_flash::verifyBlobId)) 285 { 286 std::fprintf(stderr, "succeeded\n"); 287 } 288 else 289 { 290 std::fprintf(stderr, "failed\n"); 291 throw ToolException("Verification failed"); 292 } 293 294 /* Trigger the update by opening and committing the update file. */ 295 std::fprintf(stderr, "Opening the update file\n"); 296 if (updater->verifyFile(ipmi_flash::updateBlobId)) 297 { 298 std::fprintf(stderr, "succeeded\n"); 299 } 300 else 301 { 302 /* Depending on the update mechanism used, this may be 303 * uninteresting. For instance, for the static layout, we use the 304 * reboot update mechanism. Which doesn't always lead to a 305 * successful return before the BMC starts shutting down services. 306 */ 307 std::fprintf(stderr, "failed\n"); 308 throw ToolException("Update failed"); 309 } 310 } 311 catch (...) 312 { 313 updater->cleanArtifacts(); 314 throw; 315 } 316 } 317 318 } // namespace host_tool 319