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