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 "handler.hpp"
18 
19 #include "flags.hpp"
20 #include "helper.hpp"
21 #include "status.hpp"
22 #include "tool_errors.hpp"
23 #include "util.hpp"
24 
25 #include <ipmiblob/blob_errors.hpp>
26 #include <stdplus/function_view.hpp>
27 #include <stdplus/handle/managed.hpp>
28 
29 #include <algorithm>
30 #include <cstdint>
31 #include <cstring>
32 #include <string>
33 #include <vector>
34 
35 namespace host_tool
36 {
37 
closeBlob(uint16_t && session,ipmiblob::BlobInterface * & blob)38 static void closeBlob(uint16_t&& session, ipmiblob::BlobInterface*& blob)
39 {
40     blob->closeBlob(session);
41 }
42 
43 using BlobHandle =
44     stdplus::Managed<uint16_t, ipmiblob::BlobInterface*>::Handle<closeBlob>;
45 
46 template <typename... Args>
openBlob(ipmiblob::BlobInterface * blob,Args &&...args)47 inline BlobHandle openBlob(ipmiblob::BlobInterface* blob, Args&&... args)
48 {
49     return BlobHandle(blob->openBlob(std::forward<Args>(args)...), blob);
50 }
51 
checkAvailable(const std::string & goalFirmware)52 bool UpdateHandler::checkAvailable(const std::string& goalFirmware)
53 {
54     std::vector<std::string> blobs = blob->getBlobList();
55 
56     auto blobInst = std::find_if(blobs.begin(), blobs.end(),
57                                  [&goalFirmware](const std::string& iter) {
58         /* Running into weird scenarios where the string comparison doesn't
59          * work.  TODO: revisit.
60          */
61         return (0 == std::memcmp(goalFirmware.c_str(), iter.c_str(),
62                                  goalFirmware.length()));
63         // return (goalFirmware.compare(iter));
64     });
65     if (blobInst == blobs.end())
66     {
67         std::fprintf(stderr, "%s not found\n", goalFirmware.c_str());
68         return false;
69     }
70 
71     return true;
72 }
73 
retryIfFailed(stdplus::function_view<std::vector<uint8_t> ()> callback)74 std::vector<uint8_t> UpdateHandler::retryIfFailed(
75     stdplus::function_view<std::vector<uint8_t>()> callback)
76 {
77     constexpr uint8_t retryCount = 3;
78     uint8_t i = 1;
79     while (true)
80     {
81         try
82         {
83             return callback();
84         }
85         catch (const ipmiblob::BlobException& b)
86         {
87             throw ToolException("blob exception received: " +
88                                 std::string(b.what()));
89         }
90         catch (const ToolException& t)
91         {
92             uint8_t remains = retryCount - i;
93             std::fprintf(
94                 stderr,
95                 "tool exception received: %s: Retrying it %u more times\n",
96                 t.what(), remains);
97             if (remains == 0)
98                 throw;
99         }
100         ++i;
101         handler->waitForRetry();
102     }
103     return {};
104 }
105 
retrySendFile(const std::string & target,const std::string & path)106 void UpdateHandler::retrySendFile(const std::string& target,
107                                   const std::string& path)
108 {
109     auto supported = handler->supportedType();
110     auto session =
111         openBlob(blob, target,
112                  static_cast<std::uint16_t>(supported) |
113                      static_cast<std::uint16_t>(
114                          ipmi_flash::FirmwareFlags::UpdateFlags::openWrite));
115 
116     if (!handler->sendContents(path, *session))
117     {
118         throw ToolException("Failed to send contents of " + path);
119     }
120 }
121 
sendFile(const std::string & target,const std::string & path)122 void UpdateHandler::sendFile(const std::string& target, const std::string& path)
123 {
124     retryIfFailed([this, target, path]() {
125         this->retrySendFile(target, path);
126         return std::vector<uint8_t>{};
127     });
128 }
129 
retryVerifyFile(const std::string & target,bool ignoreStatus)130 void UpdateHandler::retryVerifyFile(const std::string& target,
131                                     bool ignoreStatus)
132 {
133     auto session =
134         openBlob(blob, target,
135                  static_cast<std::uint16_t>(
136                      ipmi_flash::FirmwareFlags::UpdateFlags::openWrite));
137 
138     std::fprintf(stderr, "Committing to %s to trigger service\n",
139                  target.c_str());
140     blob->commit(*session, {});
141 
142     if (ignoreStatus)
143     {
144         // Skip checking the blob for status if ignoreStatus is enabled
145         return;
146     }
147 
148     std::fprintf(stderr, "Calling stat on %s session to check status\n",
149                  target.c_str());
150     pollStatus(*session, blob);
151     return;
152 }
153 
verifyFile(const std::string & target,bool ignoreStatus)154 bool UpdateHandler::verifyFile(const std::string& target, bool ignoreStatus)
155 {
156     retryIfFailed([this, target, ignoreStatus]() {
157         this->retryVerifyFile(target, ignoreStatus);
158         return std::vector<uint8_t>{};
159     });
160 
161     return true;
162 }
163 
164 std::vector<uint8_t>
retryReadVersion(const std::string & versionBlob)165     UpdateHandler::retryReadVersion(const std::string& versionBlob)
166 {
167     auto session =
168         openBlob(blob, versionBlob,
169                  static_cast<std::uint16_t>(
170                      ipmi_flash::FirmwareFlags::UpdateFlags::openRead));
171 
172     std::fprintf(stderr, "Calling stat on %s session to check status\n",
173                  versionBlob.c_str());
174 
175     /* TODO: call readBytes multiple times in case IPMI message length
176      * exceeds IPMI_MAX_MSG_LENGTH.
177      */
178     auto size = pollReadReady(*session, blob);
179     if (size > 0)
180     {
181         return blob->readBytes(*session, 0, size);
182     }
183     return {};
184 }
185 
readVersion(const std::string & versionBlob)186 std::vector<uint8_t> UpdateHandler::readVersion(const std::string& versionBlob)
187 {
188     return retryIfFailed(
189         [this, versionBlob]() { return retryReadVersion(versionBlob); });
190 }
191 
cleanArtifacts()192 void UpdateHandler::cleanArtifacts()
193 {
194     /* Errors aren't important for this call. */
195     try
196     {
197         std::fprintf(stderr, "Executing cleanup blob\n");
198         auto session =
199             openBlob(blob, ipmi_flash::cleanupBlobId,
200                      static_cast<std::uint16_t>(
201                          ipmi_flash::FirmwareFlags::UpdateFlags::openWrite));
202         blob->commit(*session, {});
203     }
204     catch (const std::exception& e)
205     {
206         std::fprintf(stderr, "Cleanup failed: %s\n", e.what());
207     }
208 }
209 
210 } // namespace host_tool
211