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 
19*d61b0ff8SPatrick Venture #include "firmware_handler.hpp"
202bc23fe1SPatrick Venture #include "tool_errors.hpp"
210533d0b0SPatrick Venture 
2200887597SPatrick Venture #include <algorithm>
23664c5bc7SPatrick Venture #include <blobs-ipmid/blobs.hpp>
24339dece8SPatrick Venture #include <cstring>
25664c5bc7SPatrick Venture #include <ipmiblob/blob_errors.hpp>
26af69625fSPatrick Venture #include <memory>
272a927e87SPatrick Venture #include <string>
28*d61b0ff8SPatrick Venture #include <thread>
29af69625fSPatrick Venture 
309b534f06SPatrick Venture namespace host_tool
319b534f06SPatrick Venture {
329b534f06SPatrick Venture 
33*d61b0ff8SPatrick Venture /* Poll an open verification session.  Handling closing the session is not yet
34*d61b0ff8SPatrick Venture  * owned by this method. */
35*d61b0ff8SPatrick Venture bool pollVerificationStatus(std::uint16_t session,
36*d61b0ff8SPatrick Venture                             ipmiblob::BlobInterface* blob)
37*d61b0ff8SPatrick Venture {
38*d61b0ff8SPatrick Venture     using namespace std::chrono_literals;
39*d61b0ff8SPatrick Venture 
40*d61b0ff8SPatrick Venture     static constexpr auto verificationSleep = 5s;
41*d61b0ff8SPatrick Venture     static constexpr int commandAttempts = 20;
42*d61b0ff8SPatrick Venture     int attempts = 0;
43*d61b0ff8SPatrick Venture     bool exitLoop = false;
44*d61b0ff8SPatrick Venture     blobs::FirmwareBlobHandler::VerifyCheckResponses result =
45*d61b0ff8SPatrick Venture         blobs::FirmwareBlobHandler::VerifyCheckResponses::other;
46*d61b0ff8SPatrick Venture 
47*d61b0ff8SPatrick Venture     try
48*d61b0ff8SPatrick Venture     {
49*d61b0ff8SPatrick Venture         /* Reach back the current status from the verification service output.
50*d61b0ff8SPatrick Venture          */
51*d61b0ff8SPatrick Venture         while (attempts++ < commandAttempts)
52*d61b0ff8SPatrick Venture         {
53*d61b0ff8SPatrick Venture             ipmiblob::StatResponse resp = blob->getStat(session);
54*d61b0ff8SPatrick Venture 
55*d61b0ff8SPatrick Venture             if (resp.metadata.size() != sizeof(std::uint8_t))
56*d61b0ff8SPatrick Venture             {
57*d61b0ff8SPatrick Venture                 /* TODO: How do we want to handle the verification failures,
58*d61b0ff8SPatrick Venture                  * because closing the session to the verify blob has a special
59*d61b0ff8SPatrick Venture                  * as-of-yet not fully defined behavior.
60*d61b0ff8SPatrick Venture                  */
61*d61b0ff8SPatrick Venture                 std::fprintf(stderr, "Received invalid metadata response!!!\n");
62*d61b0ff8SPatrick Venture             }
63*d61b0ff8SPatrick Venture 
64*d61b0ff8SPatrick Venture             result =
65*d61b0ff8SPatrick Venture                 static_cast<blobs::FirmwareBlobHandler::VerifyCheckResponses>(
66*d61b0ff8SPatrick Venture                     resp.metadata[0]);
67*d61b0ff8SPatrick Venture 
68*d61b0ff8SPatrick Venture             switch (result)
69*d61b0ff8SPatrick Venture             {
70*d61b0ff8SPatrick Venture                 case blobs::FirmwareBlobHandler::VerifyCheckResponses::failed:
71*d61b0ff8SPatrick Venture                     std::fprintf(stderr, "failed\n");
72*d61b0ff8SPatrick Venture                     exitLoop = true;
73*d61b0ff8SPatrick Venture                     break;
74*d61b0ff8SPatrick Venture                 case blobs::FirmwareBlobHandler::VerifyCheckResponses::other:
75*d61b0ff8SPatrick Venture                     std::fprintf(stderr, "other\n");
76*d61b0ff8SPatrick Venture                     break;
77*d61b0ff8SPatrick Venture                 case blobs::FirmwareBlobHandler::VerifyCheckResponses::running:
78*d61b0ff8SPatrick Venture                     std::fprintf(stderr, "running\n");
79*d61b0ff8SPatrick Venture                     break;
80*d61b0ff8SPatrick Venture                 case blobs::FirmwareBlobHandler::VerifyCheckResponses::success:
81*d61b0ff8SPatrick Venture                     std::fprintf(stderr, "success\n");
82*d61b0ff8SPatrick Venture                     exitLoop = true;
83*d61b0ff8SPatrick Venture                     break;
84*d61b0ff8SPatrick Venture                 default:
85*d61b0ff8SPatrick Venture                     std::fprintf(stderr, "wat\n");
86*d61b0ff8SPatrick Venture             }
87*d61b0ff8SPatrick Venture 
88*d61b0ff8SPatrick Venture             if (exitLoop)
89*d61b0ff8SPatrick Venture             {
90*d61b0ff8SPatrick Venture                 break;
91*d61b0ff8SPatrick Venture             }
92*d61b0ff8SPatrick Venture             std::this_thread::sleep_for(verificationSleep);
93*d61b0ff8SPatrick Venture         }
94*d61b0ff8SPatrick Venture     }
95*d61b0ff8SPatrick Venture     catch (const ipmiblob::BlobException& b)
96*d61b0ff8SPatrick Venture     {
97*d61b0ff8SPatrick Venture         throw ToolException("blob exception received: " +
98*d61b0ff8SPatrick Venture                             std::string(b.what()));
99*d61b0ff8SPatrick Venture     }
100*d61b0ff8SPatrick Venture 
101*d61b0ff8SPatrick Venture     /* TODO: If this is reached and it's not success, it may be worth just
102*d61b0ff8SPatrick Venture      * throwing a ToolException with a timeout message specifying the final
103*d61b0ff8SPatrick Venture      * read's value.
104*d61b0ff8SPatrick Venture      *
105*d61b0ff8SPatrick Venture      * TODO: Given that excepting from certain points leaves the BMC update
106*d61b0ff8SPatrick Venture      * state machine in an inconsistent state, we need to carefully evaluate
107*d61b0ff8SPatrick Venture      * which exceptions from the lower layers allow one to try and delete the
108*d61b0ff8SPatrick Venture      * blobs to rollback the state and progress.
109*d61b0ff8SPatrick Venture      */
110*d61b0ff8SPatrick Venture     return (result ==
111*d61b0ff8SPatrick Venture             blobs::FirmwareBlobHandler::VerifyCheckResponses::success);
112*d61b0ff8SPatrick Venture }
113*d61b0ff8SPatrick Venture 
114664c5bc7SPatrick Venture void updaterMain(ipmiblob::BlobInterface* blob, DataInterface* handler,
11500887597SPatrick Venture                  const std::string& imagePath, const std::string& signaturePath)
116bf58cd64SPatrick Venture {
117af69625fSPatrick Venture     /* TODO(venture): Add optional parameter to specify the flash type, default
118af69625fSPatrick Venture      * to legacy for now.
1197dcca5ddSPatrick Venture      *
1207dcca5ddSPatrick Venture      * TODO(venture): Move the strings from the FirmwareHandler object to a
1217dcca5ddSPatrick Venture      * boring utils object so it will be more happly linked cleanly to both the
1227dcca5ddSPatrick Venture      * BMC and host-side.
123af69625fSPatrick Venture      */
12400887597SPatrick Venture     std::string goalFirmware = "/flash/image";
12573528388SPatrick Venture     std::string hashFilename = "/flash/hash";
1267dcca5ddSPatrick Venture     std::string verifyFilename = "/flash/verify";
12700887597SPatrick Venture 
1280bf8bf0cSPatrick Venture     /* Get list of blob_ids, check for /flash/image, or /flash/tarball.
1290bf8bf0cSPatrick Venture      * TODO(venture) the mechanism doesn't care, but the caller of burn_my_bmc
1300bf8bf0cSPatrick Venture      * will have in mind which they're sending and we need to verify it's
1310bf8bf0cSPatrick Venture      * available and use it.
1320bf8bf0cSPatrick Venture      */
13300887597SPatrick Venture     std::vector<std::string> blobs = blob->getBlobList();
134339dece8SPatrick Venture     auto blobInst = std::find_if(
1352a927e87SPatrick Venture         blobs.begin(), blobs.end(), [&goalFirmware](const std::string& iter) {
136339dece8SPatrick Venture             /* Running into weird scenarios where the string comparison doesn't
137339dece8SPatrick Venture              * work.  TODO: revisit.
138339dece8SPatrick Venture              */
139339dece8SPatrick Venture             return (0 == std::memcmp(goalFirmware.c_str(), iter.c_str(),
140339dece8SPatrick Venture                                      goalFirmware.length()));
141339dece8SPatrick Venture             // return (goalFirmware.compare(iter));
142339dece8SPatrick Venture         });
14300887597SPatrick Venture     if (blobInst == blobs.end())
14400887597SPatrick Venture     {
1452bc23fe1SPatrick Venture         throw ToolException(goalFirmware + " not found");
14600887597SPatrick Venture     }
147af69625fSPatrick Venture 
148af69625fSPatrick Venture     /* Call stat on /flash/image (or /flash/tarball) and check if data interface
14900887597SPatrick Venture      * is supported.
15000887597SPatrick Venture      */
151664c5bc7SPatrick Venture     ipmiblob::StatResponse stat;
152339dece8SPatrick Venture     try
153339dece8SPatrick Venture     {
154339dece8SPatrick Venture         stat = blob->getStat(goalFirmware);
155339dece8SPatrick Venture     }
156664c5bc7SPatrick Venture     catch (const ipmiblob::BlobException& b)
157339dece8SPatrick Venture     {
158339dece8SPatrick Venture         throw ToolException("blob exception received: " +
159339dece8SPatrick Venture                             std::string(b.what()));
160339dece8SPatrick Venture     }
161339dece8SPatrick Venture 
162aa32a36aSPatrick Venture     auto supported = handler->supportedType();
163aa32a36aSPatrick Venture     if ((stat.blob_state & supported) == 0)
1648a55dcbdSPatrick Venture     {
1652bc23fe1SPatrick Venture         throw ToolException("data interface selected not supported.");
1668a55dcbdSPatrick Venture     }
167af69625fSPatrick Venture 
1680533d0b0SPatrick Venture     /* Yay, our data handler is supported. */
16973528388SPatrick Venture 
17073528388SPatrick Venture     /* Send over the firmware image. */
17173528388SPatrick Venture     std::fprintf(stderr, "Sending over the firmware image.\n");
1720533d0b0SPatrick Venture     std::uint16_t session;
1730533d0b0SPatrick Venture     try
1740533d0b0SPatrick Venture     {
175664c5bc7SPatrick Venture         session = blob->openBlob(
176664c5bc7SPatrick Venture             goalFirmware,
177664c5bc7SPatrick Venture             static_cast<std::uint16_t>(supported) |
178664c5bc7SPatrick Venture                 static_cast<std::uint16_t>(blobs::OpenFlags::write));
1790533d0b0SPatrick Venture     }
180664c5bc7SPatrick Venture     catch (const ipmiblob::BlobException& b)
1810533d0b0SPatrick Venture     {
1822bc23fe1SPatrick Venture         throw ToolException("blob exception received: " +
1832bc23fe1SPatrick Venture                             std::string(b.what()));
1840533d0b0SPatrick Venture     }
1850533d0b0SPatrick Venture 
186fd6aaec8SPatrick Venture     if (!handler->sendContents(imagePath, session))
187fd6aaec8SPatrick Venture     {
188f9566d88SPatrick Venture         /* Need to close the session on failure, or it's stuck open (until the
189f9566d88SPatrick Venture          * blob handler timeout is implemented, and even then, why make it wait.
190f9566d88SPatrick Venture          */
191f9566d88SPatrick Venture         blob->closeBlob(session);
1922bc23fe1SPatrick Venture         throw ToolException("Failed to send contents of " + imagePath);
193fd6aaec8SPatrick Venture     }
194fd6aaec8SPatrick Venture 
1959a5ce561SPatrick Venture     blob->closeBlob(session);
1969a5ce561SPatrick Venture 
197fd6aaec8SPatrick Venture     /* Send over the hash contents. */
19873528388SPatrick Venture     std::fprintf(stderr, "Sending over the hash file.\n");
19973528388SPatrick Venture     try
20073528388SPatrick Venture     {
20173528388SPatrick Venture         session = blob->openBlob(
20273528388SPatrick Venture             hashFilename,
20373528388SPatrick Venture             static_cast<std::uint16_t>(supported) |
20473528388SPatrick Venture                 static_cast<std::uint16_t>(blobs::OpenFlags::write));
20573528388SPatrick Venture     }
20673528388SPatrick Venture     catch (const ipmiblob::BlobException& b)
20773528388SPatrick Venture     {
20873528388SPatrick Venture         throw ToolException("blob exception received: " +
20973528388SPatrick Venture                             std::string(b.what()));
21073528388SPatrick Venture     }
21173528388SPatrick Venture 
21273528388SPatrick Venture     if (!handler->sendContents(signaturePath, session))
21373528388SPatrick Venture     {
21473528388SPatrick Venture         blob->closeBlob(session);
21573528388SPatrick Venture         throw ToolException("Failed to send contents of " + signaturePath);
21673528388SPatrick Venture     }
21773528388SPatrick Venture 
21873528388SPatrick Venture     blob->closeBlob(session);
21973528388SPatrick Venture 
2207dcca5ddSPatrick Venture     /* Trigger the verification by opening the verify file. */
2217dcca5ddSPatrick Venture     std::fprintf(stderr, "Opening the verification file\n");
2227dcca5ddSPatrick Venture     try
2237dcca5ddSPatrick Venture     {
2247dcca5ddSPatrick Venture         session = blob->openBlob(
2257dcca5ddSPatrick Venture             verifyFilename,
2267dcca5ddSPatrick Venture             static_cast<std::uint16_t>(supported) |
2277dcca5ddSPatrick Venture                 static_cast<std::uint16_t>(blobs::OpenFlags::write));
2287dcca5ddSPatrick Venture     }
2297dcca5ddSPatrick Venture     catch (const ipmiblob::BlobException& b)
2307dcca5ddSPatrick Venture     {
2317dcca5ddSPatrick Venture         throw ToolException("blob exception received: " +
2327dcca5ddSPatrick Venture                             std::string(b.what()));
2337dcca5ddSPatrick Venture     }
2347dcca5ddSPatrick Venture 
2357dcca5ddSPatrick Venture     std::fprintf(
2367dcca5ddSPatrick Venture         stderr,
2377dcca5ddSPatrick Venture         "Committing to verification file to trigger verification service\n");
2387dcca5ddSPatrick Venture     try
2397dcca5ddSPatrick Venture     {
2407dcca5ddSPatrick Venture         blob->commit(session, {});
2417dcca5ddSPatrick Venture     }
2427dcca5ddSPatrick Venture     catch (const ipmiblob::BlobException& b)
2437dcca5ddSPatrick Venture     {
2447dcca5ddSPatrick Venture         throw ToolException("blob exception received: " +
2457dcca5ddSPatrick Venture                             std::string(b.what()));
2467dcca5ddSPatrick Venture     }
2477dcca5ddSPatrick Venture 
248*d61b0ff8SPatrick Venture     std::fprintf(stderr,
249*d61b0ff8SPatrick Venture                  "Calling stat on verification session to check status\n");
2507dcca5ddSPatrick Venture 
251*d61b0ff8SPatrick Venture     if (pollVerificationStatus(session, blob))
252*d61b0ff8SPatrick Venture     {
253*d61b0ff8SPatrick Venture         std::fprintf(stderr, "Verification returned success\n");
254*d61b0ff8SPatrick Venture     }
255*d61b0ff8SPatrick Venture     else
256*d61b0ff8SPatrick Venture     {
257*d61b0ff8SPatrick Venture         std::fprintf(stderr, "Verification returned non-success (could still "
258*d61b0ff8SPatrick Venture                              "be running (unlikely))\n");
259*d61b0ff8SPatrick Venture     }
260*d61b0ff8SPatrick Venture 
261*d61b0ff8SPatrick Venture     /* DO NOT CLOSE the verification session until it's done.
262*d61b0ff8SPatrick Venture      * TODO: Evaluate what closing verification should do?  If the process is
263*d61b0ff8SPatrick Venture      * complete, nothing bad, maybe reset the entire state machine?  This will
264*d61b0ff8SPatrick Venture      * benefit from a diagram.
265*d61b0ff8SPatrick Venture      */
2667dcca5ddSPatrick Venture     blob->closeBlob(session);
267fd6aaec8SPatrick Venture 
2682bc23fe1SPatrick Venture     return;
269bf58cd64SPatrick Venture }
2709b534f06SPatrick Venture 
2719b534f06SPatrick Venture } // namespace host_tool
272