xref: /openbmc/phosphor-ipmi-blobs/manager.cpp (revision b61a88bacdd5fdd2507cb218afcf95c40a2565f2)
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 "manager.hpp"
18 
19 #include <algorithm>
20 #include <iostream>
21 #include <memory>
22 #include <string>
23 #include <vector>
24 
25 namespace blobs
26 {
27 
28 void BlobManager::incrementOpen(const std::string& path)
29 {
30     if (path.empty())
31     {
32         return;
33     }
34 
35     openFiles[path] += 1;
36 }
37 
38 void BlobManager::decrementOpen(const std::string& path)
39 {
40     if (path.empty())
41     {
42         return;
43     }
44 
45     /* TODO(venture): Check into the iterator from find, does it makes sense
46      * to just update it directly? */
47     auto entry = openFiles.find(path);
48     if (entry != openFiles.end())
49     {
50         /* found it, decrement it and remove it if 0. */
51         openFiles[path] -= 1;
52         if (openFiles[path] == 0)
53         {
54             openFiles.erase(path);
55         }
56     }
57 }
58 
59 int BlobManager::getOpen(const std::string& path) const
60 {
61     /* No need to input check on the read-only call. */
62     auto entry = openFiles.find(path);
63     if (entry != openFiles.end())
64     {
65         return entry->second;
66     }
67 
68     return 0;
69 }
70 
71 void BlobManager::eraseSession(GenericBlobInterface* handler, uint16_t session)
72 {
73     sessions.erase(session);
74     /* Ok for openSessions[handler] to be an empty set */
75     openSessions[handler].erase(session);
76     decrementOpen(getPath(session));
77 }
78 
79 void BlobManager::cleanUpStaleSessions(GenericBlobInterface* handler)
80 {
81     if (openSessions.count(handler) == 0)
82     {
83         return;
84     }
85 
86     auto timeNow = std::chrono::steady_clock::now();
87     std::set<uint16_t> expiredSet;
88 
89     for (auto sessionId : openSessions[handler])
90     {
91         if (timeNow - sessions[sessionId].lastActionTime >= sessionTimeout)
92         {
93             expiredSet.insert(sessionId);
94         }
95     }
96 
97     for (auto sessionId : expiredSet)
98     {
99         std::cerr << "phosphor-ipmi-blobs: expiring stale session " << sessionId
100                   << std::endl;
101 
102         /* We do a best case recovery by issuing an expire call. If it fails
103          * don't erase sessions since the handler side might be still tracking
104          * it as open. */
105         if (handler->expire(sessionId))
106         {
107             eraseSession(handler, sessionId);
108         }
109         else
110         {
111             std::cerr << "phosphor-ipmi-blobs: failed to expire session "
112                       << sessionId << std::endl;
113         }
114     }
115 }
116 
117 bool BlobManager::registerHandler(std::unique_ptr<GenericBlobInterface> handler)
118 {
119     if (!handler)
120     {
121         return false;
122     }
123 
124     handlers.push_back(std::move(handler));
125     return true;
126 }
127 
128 uint32_t BlobManager::buildBlobList()
129 {
130     /* Clear out the current list (IPMI handler is presently single-threaded).
131      */
132     ids.clear();
133 
134     /* Grab the list of blobs and extend the local list */
135     for (const auto& h : handlers)
136     {
137         std::vector<std::string> blobs = h->getBlobIds();
138         ids.insert(ids.end(), blobs.begin(), blobs.end());
139     }
140 
141     return ids.size();
142 }
143 
144 std::string BlobManager::getBlobId(uint32_t index)
145 {
146     /* Range check. */
147     if (index >= ids.size())
148     {
149         return "";
150     }
151 
152     return ids[index];
153 }
154 
155 bool BlobManager::open(uint16_t flags, const std::string& path,
156                        uint16_t* session)
157 {
158     GenericBlobInterface* handler = getHandler(path);
159 
160     /* No handler found. */
161     if (!handler)
162     {
163         return false;
164     }
165 
166     /* No sessions available... */
167     if (!getSession(session))
168     {
169         return false;
170     }
171 
172     /* Verify flags - must be at least read or write */
173     if (!(flags & (OpenFlags::read | OpenFlags::write)))
174     {
175         /* Neither read not write set, which means calls to Read/Write will
176          * reject. */
177         return false;
178     }
179 
180     /* Try to clean up anything that's falling out of cleanup timeout for this
181      * handler */
182     cleanUpStaleSessions(handler);
183 
184     if (!handler->open(*session, flags, path))
185     {
186         return false;
187     }
188 
189     /* Associate session with handler */
190     sessions[*session] = SessionInfo(path, handler, flags);
191     openSessions[handler].insert(*session);
192     incrementOpen(path);
193     return true;
194 }
195 
196 GenericBlobInterface* BlobManager::getHandler(const std::string& path)
197 {
198     /* Find a handler. */
199     auto h = std::find_if(
200         handlers.begin(), handlers.end(),
201         [&path](const auto& iter) { return (iter->canHandleBlob(path)); });
202     if (h != handlers.end())
203     {
204         return h->get();
205     }
206 
207     return nullptr;
208 }
209 
210 std::string BlobManager::getPath(uint16_t session) const
211 {
212     auto item = sessions.find(session);
213     if (item == sessions.end())
214     {
215         return "";
216     }
217 
218     return item->second.blobId;
219 }
220 
221 bool BlobManager::stat(const std::string& path, BlobMeta* meta)
222 {
223     /* meta should never be NULL. */
224     GenericBlobInterface* handler = getHandler(path);
225 
226     /* No handler found. */
227     if (!handler)
228     {
229         return false;
230     }
231 
232     return handler->stat(path, meta);
233 }
234 
235 bool BlobManager::stat(uint16_t session, BlobMeta* meta)
236 {
237     if (auto handler = getActionHandle(session))
238     {
239         return handler->stat(session, meta);
240     }
241     return false;
242 }
243 
244 bool BlobManager::commit(uint16_t session, const std::vector<uint8_t>& data)
245 {
246     if (auto handler = getActionHandle(session))
247     {
248         return handler->commit(session, data);
249     }
250     return false;
251 }
252 
253 bool BlobManager::close(uint16_t session)
254 {
255     if (auto handler = getActionHandle(session))
256     {
257         if (!handler->close(session))
258         {
259             return false;
260         }
261         eraseSession(handler, session);
262         return true;
263     }
264     return false;
265 }
266 
267 std::vector<uint8_t> BlobManager::read(uint16_t session, uint32_t offset,
268                                        uint32_t requestedSize)
269 {
270     /* TODO: Currently, configure_ac isn't finding libuserlayer, w.r.t the
271      * symbols I need.
272      */
273 
274     /** The channel to use for now.
275      * TODO: We will receive this information through the IPMI message call.
276      */
277     // const int ipmiChannel = ipmi::currentChNum;
278     /** This information is transport specific.
279      * TODO: We need a way to know this dynamically.
280      * on BT, 4 bytes of header, and 1 reply code.
281      */
282     // uint32_t maxTransportSize = ipmi::getChannelMaxTransferSize(ipmiChannel);
283 
284     if (auto handler = getActionHandle(session, OpenFlags::read))
285     {
286         return handler->read(session, offset,
287                              std::min(maximumReadSize, requestedSize));
288     }
289     return {};
290 }
291 
292 bool BlobManager::write(uint16_t session, uint32_t offset,
293                         const std::vector<uint8_t>& data)
294 {
295     if (auto handler = getActionHandle(session, OpenFlags::write))
296     {
297         return handler->write(session, offset, data);
298     }
299     return {};
300 }
301 
302 bool BlobManager::deleteBlob(const std::string& path)
303 {
304     GenericBlobInterface* handler = getHandler(path);
305 
306     /* No handler found. */
307     if (!handler)
308     {
309         return false;
310     }
311 
312     /* Check if the file has any open handles. */
313     if (getOpen(path) > 0)
314     {
315         return false;
316     }
317 
318     /* Try deleting it. */
319     return handler->deleteBlob(path);
320 }
321 
322 bool BlobManager::writeMeta(uint16_t session, uint32_t offset,
323                             const std::vector<uint8_t>& data)
324 {
325     if (auto handler = getActionHandle(session))
326     {
327         return handler->writeMeta(session, offset, data);
328     }
329     return false;
330 }
331 
332 bool BlobManager::getSession(uint16_t* sess)
333 {
334     uint16_t tries = 0;
335 
336     if (!sess)
337     {
338         return false;
339     }
340 
341     /* This is not meant to fail as you have 64KiB values available. */
342 
343     /* TODO(venture): We could just count the keys in the session map to know
344      * if it's full.
345      */
346     do
347     {
348         uint16_t lsess = next++;
349         if (!sessions.count(lsess))
350         {
351             /* value not in use, return it. */
352             (*sess) = lsess;
353             return true;
354         }
355     } while (++tries < 0xffff);
356 
357     return false;
358 }
359 
360 static std::unique_ptr<BlobManager> manager;
361 
362 ManagerInterface* getBlobManager()
363 {
364     if (manager == nullptr)
365     {
366         manager = std::make_unique<BlobManager>();
367     }
368 
369     return manager.get();
370 }
371 
372 } // namespace blobs
373