1bf58cd64SPatrick Venture /*
2bf58cd64SPatrick Venture  * Copyright 2018 Google Inc.
3bf58cd64SPatrick Venture  *
4bf58cd64SPatrick Venture  * Licensed under the Apache License, Version 2.0 (the "License");
5bf58cd64SPatrick Venture  * you may not use this file except in compliance with the License.
6bf58cd64SPatrick Venture  * You may obtain a copy of the License at
7bf58cd64SPatrick Venture  *
8bf58cd64SPatrick Venture  *     http://www.apache.org/licenses/LICENSE-2.0
9bf58cd64SPatrick Venture  *
10bf58cd64SPatrick Venture  * Unless required by applicable law or agreed to in writing, software
11bf58cd64SPatrick Venture  * distributed under the License is distributed on an "AS IS" BASIS,
12bf58cd64SPatrick Venture  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13bf58cd64SPatrick Venture  * See the License for the specific language governing permissions and
14bf58cd64SPatrick Venture  * limitations under the License.
15bf58cd64SPatrick Venture  */
16bf58cd64SPatrick Venture 
17bf58cd64SPatrick Venture #include "updater.hpp"
18bf58cd64SPatrick Venture 
19d61b0ff8SPatrick Venture #include "firmware_handler.hpp"
203ecb3503SPatrick Venture #include "status.hpp"
212bc23fe1SPatrick Venture #include "tool_errors.hpp"
227dad86fdSPatrick Venture #include "util.hpp"
230533d0b0SPatrick Venture 
2400887597SPatrick Venture #include <algorithm>
25664c5bc7SPatrick Venture #include <blobs-ipmid/blobs.hpp>
26339dece8SPatrick Venture #include <cstring>
27664c5bc7SPatrick Venture #include <ipmiblob/blob_errors.hpp>
28af69625fSPatrick Venture #include <memory>
292a927e87SPatrick Venture #include <string>
30d61b0ff8SPatrick Venture #include <thread>
3155646decSPatrick Venture #include <vector>
32af69625fSPatrick Venture 
339b534f06SPatrick Venture namespace host_tool
349b534f06SPatrick Venture {
359b534f06SPatrick Venture 
3655646decSPatrick Venture bool UpdateHandler::checkAvailable(const std::string& goalFirmware)
3755646decSPatrick Venture {
3855646decSPatrick Venture     std::vector<std::string> blobs = blob->getBlobList();
3955646decSPatrick Venture 
4055646decSPatrick Venture     auto blobInst = std::find_if(
4155646decSPatrick Venture         blobs.begin(), blobs.end(), [&goalFirmware](const std::string& iter) {
4255646decSPatrick Venture             /* Running into weird scenarios where the string comparison doesn't
4355646decSPatrick Venture              * work.  TODO: revisit.
4455646decSPatrick Venture              */
4555646decSPatrick Venture             return (0 == std::memcmp(goalFirmware.c_str(), iter.c_str(),
4655646decSPatrick Venture                                      goalFirmware.length()));
4755646decSPatrick Venture             // return (goalFirmware.compare(iter));
4855646decSPatrick Venture         });
4955646decSPatrick Venture     if (blobInst == blobs.end())
5055646decSPatrick Venture     {
5155646decSPatrick Venture         std::fprintf(stderr, "%s not found\n", goalFirmware.c_str());
5255646decSPatrick Venture         return false;
5355646decSPatrick Venture     }
5455646decSPatrick Venture 
5555646decSPatrick Venture     /* Call stat on /flash/image (or /flash/tarball) and check if data interface
5655646decSPatrick Venture      * is supported.
5755646decSPatrick Venture      */
5855646decSPatrick Venture     ipmiblob::StatResponse stat;
5955646decSPatrick Venture 
6055646decSPatrick Venture     try
6155646decSPatrick Venture     {
6255646decSPatrick Venture         stat = blob->getStat(goalFirmware);
6355646decSPatrick Venture     }
6455646decSPatrick Venture     catch (const ipmiblob::BlobException& b)
6555646decSPatrick Venture     {
6655646decSPatrick Venture         std::fprintf(stderr, "Received exception '%s' on getStat\n", b.what());
6755646decSPatrick Venture         return false;
6855646decSPatrick Venture     }
6955646decSPatrick Venture 
7055646decSPatrick Venture     auto supported = handler->supportedType();
7155646decSPatrick Venture     if ((stat.blob_state & supported) == 0)
7255646decSPatrick Venture     {
7355646decSPatrick Venture         std::fprintf(stderr, "data interface selected not supported.\n");
7455646decSPatrick Venture         return false;
7555646decSPatrick Venture     }
7655646decSPatrick Venture 
7755646decSPatrick Venture     return true;
7855646decSPatrick Venture }
7955646decSPatrick Venture 
8055646decSPatrick Venture void UpdateHandler::sendFile(const std::string& target, const std::string& path)
8155646decSPatrick Venture {
8255646decSPatrick Venture     std::uint16_t session;
8355646decSPatrick Venture     auto supported = handler->supportedType();
8455646decSPatrick Venture 
8555646decSPatrick Venture     try
8655646decSPatrick Venture     {
8755646decSPatrick Venture         session = blob->openBlob(
8855646decSPatrick Venture             target, static_cast<std::uint16_t>(supported) |
8955646decSPatrick Venture                         static_cast<std::uint16_t>(blobs::OpenFlags::write));
9055646decSPatrick Venture     }
9155646decSPatrick Venture     catch (const ipmiblob::BlobException& b)
9255646decSPatrick Venture     {
9355646decSPatrick Venture         throw ToolException("blob exception received: " +
9455646decSPatrick Venture                             std::string(b.what()));
9555646decSPatrick Venture     }
9655646decSPatrick Venture 
9755646decSPatrick Venture     if (!handler->sendContents(path, session))
9855646decSPatrick Venture     {
9955646decSPatrick Venture         /* Need to close the session on failure, or it's stuck open (until the
10055646decSPatrick Venture          * blob handler timeout is implemented, and even then, why make it wait.
10155646decSPatrick Venture          */
10255646decSPatrick Venture         blob->closeBlob(session);
10355646decSPatrick Venture         throw ToolException("Failed to send contents of " + path);
10455646decSPatrick Venture     }
10555646decSPatrick Venture 
10655646decSPatrick Venture     blob->closeBlob(session);
10755646decSPatrick Venture }
10855646decSPatrick Venture 
109d61b0ff8SPatrick Venture /* Poll an open verification session.  Handling closing the session is not yet
1101f09d414SPatrick Venture  * owned by this method.
1111f09d414SPatrick Venture  */
11214713becSPatrick Venture bool pollStatus(std::uint16_t session, ipmiblob::BlobInterface* blob)
113d61b0ff8SPatrick Venture {
114d61b0ff8SPatrick Venture     using namespace std::chrono_literals;
115d61b0ff8SPatrick Venture 
116d61b0ff8SPatrick Venture     static constexpr auto verificationSleep = 5s;
117da66fd84SPatrick Venture     ipmi_flash::ActionStatus result = ipmi_flash::ActionStatus::unknown;
118d61b0ff8SPatrick Venture 
119d61b0ff8SPatrick Venture     try
120d61b0ff8SPatrick Venture     {
121c674474cSPatrick Venture         static constexpr int commandAttempts = 20;
122c674474cSPatrick Venture         int attempts = 0;
123c674474cSPatrick Venture         bool exitLoop = false;
124c674474cSPatrick Venture 
125d61b0ff8SPatrick Venture         /* Reach back the current status from the verification service output.
126d61b0ff8SPatrick Venture          */
127d61b0ff8SPatrick Venture         while (attempts++ < commandAttempts)
128d61b0ff8SPatrick Venture         {
129d61b0ff8SPatrick Venture             ipmiblob::StatResponse resp = blob->getStat(session);
130d61b0ff8SPatrick Venture 
131d61b0ff8SPatrick Venture             if (resp.metadata.size() != sizeof(std::uint8_t))
132d61b0ff8SPatrick Venture             {
133d61b0ff8SPatrick Venture                 /* TODO: How do we want to handle the verification failures,
134d61b0ff8SPatrick Venture                  * because closing the session to the verify blob has a special
135d61b0ff8SPatrick Venture                  * as-of-yet not fully defined behavior.
136d61b0ff8SPatrick Venture                  */
137d61b0ff8SPatrick Venture                 std::fprintf(stderr, "Received invalid metadata response!!!\n");
138d61b0ff8SPatrick Venture             }
139d61b0ff8SPatrick Venture 
140da66fd84SPatrick Venture             result = static_cast<ipmi_flash::ActionStatus>(resp.metadata[0]);
141d61b0ff8SPatrick Venture 
142d61b0ff8SPatrick Venture             switch (result)
143d61b0ff8SPatrick Venture             {
144da66fd84SPatrick Venture                 case ipmi_flash::ActionStatus::failed:
145d61b0ff8SPatrick Venture                     std::fprintf(stderr, "failed\n");
146d61b0ff8SPatrick Venture                     exitLoop = true;
147d61b0ff8SPatrick Venture                     break;
148da66fd84SPatrick Venture                 case ipmi_flash::ActionStatus::unknown:
149d61b0ff8SPatrick Venture                     std::fprintf(stderr, "other\n");
150d61b0ff8SPatrick Venture                     break;
151da66fd84SPatrick Venture                 case ipmi_flash::ActionStatus::running:
152d61b0ff8SPatrick Venture                     std::fprintf(stderr, "running\n");
153d61b0ff8SPatrick Venture                     break;
154da66fd84SPatrick Venture                 case ipmi_flash::ActionStatus::success:
155d61b0ff8SPatrick Venture                     std::fprintf(stderr, "success\n");
156d61b0ff8SPatrick Venture                     exitLoop = true;
157d61b0ff8SPatrick Venture                     break;
158d61b0ff8SPatrick Venture                 default:
159d61b0ff8SPatrick Venture                     std::fprintf(stderr, "wat\n");
160d61b0ff8SPatrick Venture             }
161d61b0ff8SPatrick Venture 
162d61b0ff8SPatrick Venture             if (exitLoop)
163d61b0ff8SPatrick Venture             {
164d61b0ff8SPatrick Venture                 break;
165d61b0ff8SPatrick Venture             }
166d61b0ff8SPatrick Venture             std::this_thread::sleep_for(verificationSleep);
167d61b0ff8SPatrick Venture         }
168d61b0ff8SPatrick Venture     }
169d61b0ff8SPatrick Venture     catch (const ipmiblob::BlobException& b)
170d61b0ff8SPatrick Venture     {
171d61b0ff8SPatrick Venture         throw ToolException("blob exception received: " +
172d61b0ff8SPatrick Venture                             std::string(b.what()));
173d61b0ff8SPatrick Venture     }
174d61b0ff8SPatrick Venture 
175d61b0ff8SPatrick Venture     /* TODO: If this is reached and it's not success, it may be worth just
176d61b0ff8SPatrick Venture      * throwing a ToolException with a timeout message specifying the final
177d61b0ff8SPatrick Venture      * read's value.
178d61b0ff8SPatrick Venture      *
179d61b0ff8SPatrick Venture      * TODO: Given that excepting from certain points leaves the BMC update
180d61b0ff8SPatrick Venture      * state machine in an inconsistent state, we need to carefully evaluate
181d61b0ff8SPatrick Venture      * which exceptions from the lower layers allow one to try and delete the
182d61b0ff8SPatrick Venture      * blobs to rollback the state and progress.
183d61b0ff8SPatrick Venture      */
184da66fd84SPatrick Venture     return (result == ipmi_flash::ActionStatus::success);
185d61b0ff8SPatrick Venture }
186d61b0ff8SPatrick Venture 
18755646decSPatrick Venture bool UpdateHandler::verifyFile(const std::string& target)
188bf58cd64SPatrick Venture {
1890533d0b0SPatrick Venture     std::uint16_t session;
19055646decSPatrick Venture     bool success = false;
19155646decSPatrick Venture 
1920533d0b0SPatrick Venture     try
1930533d0b0SPatrick Venture     {
194664c5bc7SPatrick Venture         session = blob->openBlob(
19555646decSPatrick Venture             target, static_cast<std::uint16_t>(blobs::OpenFlags::write));
1967dcca5ddSPatrick Venture     }
1977dcca5ddSPatrick Venture     catch (const ipmiblob::BlobException& b)
1987dcca5ddSPatrick Venture     {
1997dcca5ddSPatrick Venture         throw ToolException("blob exception received: " +
2007dcca5ddSPatrick Venture                             std::string(b.what()));
2017dcca5ddSPatrick Venture     }
2027dcca5ddSPatrick Venture 
203f6ea2e83SPatrick Venture     std::fprintf(stderr, "Committing to %s to trigger service\n",
204f6ea2e83SPatrick Venture                  target.c_str());
20555646decSPatrick Venture 
2067dcca5ddSPatrick Venture     try
2077dcca5ddSPatrick Venture     {
2087dcca5ddSPatrick Venture         blob->commit(session, {});
2097dcca5ddSPatrick Venture     }
2107dcca5ddSPatrick Venture     catch (const ipmiblob::BlobException& b)
2117dcca5ddSPatrick Venture     {
2127dcca5ddSPatrick Venture         throw ToolException("blob exception received: " +
2137dcca5ddSPatrick Venture                             std::string(b.what()));
2147dcca5ddSPatrick Venture     }
2157dcca5ddSPatrick Venture 
216f6ea2e83SPatrick Venture     std::fprintf(stderr, "Calling stat on %s session to check status\n",
217f6ea2e83SPatrick Venture                  target.c_str());
2187dcca5ddSPatrick Venture 
21914713becSPatrick Venture     if (pollStatus(session, blob))
220d61b0ff8SPatrick Venture     {
221f6ea2e83SPatrick Venture         std::fprintf(stderr, "Returned success\n");
22255646decSPatrick Venture         success = true;
223d61b0ff8SPatrick Venture     }
224d61b0ff8SPatrick Venture     else
225d61b0ff8SPatrick Venture     {
226f6ea2e83SPatrick Venture         std::fprintf(stderr, "Returned non-success (could still "
227d61b0ff8SPatrick Venture                              "be running (unlikely))\n");
228d61b0ff8SPatrick Venture     }
229d61b0ff8SPatrick Venture 
2307dcca5ddSPatrick Venture     blob->closeBlob(session);
23155646decSPatrick Venture     return (success == true);
23255646decSPatrick Venture }
23355646decSPatrick Venture 
234*5f2fcc4eSPatrick Venture void UpdateHandler::cleanArtifacts()
235*5f2fcc4eSPatrick Venture {
236*5f2fcc4eSPatrick Venture     /* open(), commit(), close() */
237*5f2fcc4eSPatrick Venture     std::uint16_t session;
238*5f2fcc4eSPatrick Venture 
239*5f2fcc4eSPatrick Venture     /* Errors aren't important for this call. */
240*5f2fcc4eSPatrick Venture     try
241*5f2fcc4eSPatrick Venture     {
242*5f2fcc4eSPatrick Venture         std::fprintf(stderr, "Opening the cleanup blob\n");
243*5f2fcc4eSPatrick Venture         session =
244*5f2fcc4eSPatrick Venture             blob->openBlob(ipmi_flash::cleanupBlobId,
245*5f2fcc4eSPatrick Venture                            static_cast<std::uint16_t>(blobs::OpenFlags::write));
246*5f2fcc4eSPatrick Venture         std::fprintf(stderr, "Committing to the cleanup blob\n");
247*5f2fcc4eSPatrick Venture         blob->commit(session, {});
248*5f2fcc4eSPatrick Venture         std::fprintf(stderr, "Closing cleanup blob\n");
249*5f2fcc4eSPatrick Venture         blob->closeBlob(session);
250*5f2fcc4eSPatrick Venture     }
251*5f2fcc4eSPatrick Venture     catch (...)
252*5f2fcc4eSPatrick Venture     {
253*5f2fcc4eSPatrick Venture     }
254*5f2fcc4eSPatrick Venture }
255*5f2fcc4eSPatrick Venture 
2561f09d414SPatrick Venture void updaterMain(UpdateHandlerInterface* updater, const std::string& imagePath,
25755646decSPatrick Venture                  const std::string& signaturePath)
25855646decSPatrick Venture {
25955646decSPatrick Venture     /* TODO(venture): Add optional parameter to specify the flash type, default
26055646decSPatrick Venture      * to legacy for now.
26155646decSPatrick Venture      */
2621d5a31c9SPatrick Venture     bool goalSupported =
2631d5a31c9SPatrick Venture         updater->checkAvailable(ipmi_flash::staticLayoutBlobId);
26455646decSPatrick Venture     if (!goalSupported)
26555646decSPatrick Venture     {
26655646decSPatrick Venture         throw ToolException("Goal firmware or interface not supported");
26755646decSPatrick Venture     }
26855646decSPatrick Venture 
26955646decSPatrick Venture     /* Yay, our data handler is supported. */
270*5f2fcc4eSPatrick Venture     try
271*5f2fcc4eSPatrick Venture     {
27255646decSPatrick Venture 
27355646decSPatrick Venture         /* Send over the firmware image. */
27455646decSPatrick Venture         std::fprintf(stderr, "Sending over the firmware image.\n");
2751d5a31c9SPatrick Venture         updater->sendFile(ipmi_flash::staticLayoutBlobId, imagePath);
27655646decSPatrick Venture 
27755646decSPatrick Venture         /* Send over the hash contents. */
27855646decSPatrick Venture         std::fprintf(stderr, "Sending over the hash file.\n");
2791d5a31c9SPatrick Venture         updater->sendFile(ipmi_flash::hashBlobId, signaturePath);
28055646decSPatrick Venture 
281*5f2fcc4eSPatrick Venture         /* Trigger the verification by opening and committing the verify file.
282*5f2fcc4eSPatrick Venture          */
28355646decSPatrick Venture         std::fprintf(stderr, "Opening the verification file\n");
2841d5a31c9SPatrick Venture         if (updater->verifyFile(ipmi_flash::verifyBlobId))
28555646decSPatrick Venture         {
28655646decSPatrick Venture             std::fprintf(stderr, "succeeded\n");
28755646decSPatrick Venture         }
28855646decSPatrick Venture         else
28955646decSPatrick Venture         {
29055646decSPatrick Venture             std::fprintf(stderr, "failed\n");
29114713becSPatrick Venture             throw ToolException("Verification failed");
29214713becSPatrick Venture         }
29314713becSPatrick Venture 
29414713becSPatrick Venture         /* Trigger the update by opening and committing the update file. */
29514713becSPatrick Venture         std::fprintf(stderr, "Opening the update file\n");
29614713becSPatrick Venture         if (updater->verifyFile(ipmi_flash::updateBlobId))
29714713becSPatrick Venture         {
29814713becSPatrick Venture             std::fprintf(stderr, "succeeded\n");
29914713becSPatrick Venture         }
30014713becSPatrick Venture         else
30114713becSPatrick Venture         {
302*5f2fcc4eSPatrick Venture             /* Depending on the update mechanism used, this may be
303*5f2fcc4eSPatrick Venture              * uninteresting. For instance, for the static layout, we use the
304*5f2fcc4eSPatrick Venture              * reboot update mechanism.  Which doesn't always lead to a
305*5f2fcc4eSPatrick Venture              * successful return before the BMC starts shutting down services.
30614713becSPatrick Venture              */
30714713becSPatrick Venture             std::fprintf(stderr, "failed\n");
30814713becSPatrick Venture             throw ToolException("Update failed");
30955646decSPatrick Venture         }
310bf58cd64SPatrick Venture     }
311*5f2fcc4eSPatrick Venture     catch (...)
312*5f2fcc4eSPatrick Venture     {
313*5f2fcc4eSPatrick Venture         updater->cleanArtifacts();
314*5f2fcc4eSPatrick Venture         throw;
315*5f2fcc4eSPatrick Venture     }
316*5f2fcc4eSPatrick Venture }
3179b534f06SPatrick Venture 
3189b534f06SPatrick Venture } // namespace host_tool
319