blob: b258d0773e64f786f288cb6b495d8f3203d6124b [file] [log] [blame]
Kun Yi64dc05c2018-12-19 13:19:03 -08001#include "binarystore.hpp"
2
Kun Yi97be3af2019-03-05 22:43:41 -08003#include "sys_file.hpp"
4
5#include <sys/types.h>
Kun Yid2d8cb62019-01-10 15:06:12 -08006#include <unistd.h>
7
Kun Yi6baa7132019-01-08 21:21:02 -08008#include <algorithm>
9#include <blobs-ipmid/blobs.hpp>
Kun Yi70059172019-02-22 16:31:42 -080010#include <boost/endian/arithmetic.hpp>
Kun Yid2d8cb62019-01-10 15:06:12 -080011#include <cstdint>
Maksym Sloykoeb274112021-10-27 23:48:32 +000012#include <ipmid/handler.hpp>
Kun Yid2d8cb62019-01-10 15:06:12 -080013#include <memory>
Willy Tu67391d52021-12-07 19:53:49 -080014#include <optional>
Kun Yi8bcf79d2019-01-16 15:17:57 -080015#include <phosphor-logging/elog.hpp>
Kun Yid2d8cb62019-01-10 15:06:12 -080016#include <string>
17#include <vector>
18
19#include "binaryblob.pb.h"
20
21using std::size_t;
22using std::uint16_t;
23using std::uint32_t;
24using std::uint64_t;
25using std::uint8_t;
Kun Yi6baa7132019-01-08 21:21:02 -080026
Kun Yi64dc05c2018-12-19 13:19:03 -080027namespace binstore
28{
29
Kun Yi8bcf79d2019-01-16 15:17:57 -080030using namespace phosphor::logging;
31
Kun Yi64dc05c2018-12-19 13:19:03 -080032std::unique_ptr<BinaryStoreInterface>
33 BinaryStore::createFromConfig(const std::string& baseBlobId,
Willy Tu67391d52021-12-07 19:53:49 -080034 std::unique_ptr<SysFile> file,
35 std::optional<uint32_t> maxSize)
Kun Yi64dc05c2018-12-19 13:19:03 -080036{
Kun Yi2765b642019-01-16 11:11:24 -080037 if (baseBlobId.empty() || !file)
38 {
Kun Yi8bcf79d2019-01-16 15:17:57 -080039 log<level::ERR>("Unable to create binarystore from invalid config",
40 entry("BASE_ID=%s", baseBlobId.c_str()));
Kun Yi2765b642019-01-16 11:11:24 -080041 return nullptr;
42 }
43
Willy Tu67391d52021-12-07 19:53:49 -080044 auto store =
45 std::make_unique<BinaryStore>(baseBlobId, std::move(file), maxSize);
Kun Yi2765b642019-01-16 11:11:24 -080046
Kun Yi8ca234e2019-03-04 10:14:45 -080047 if (!store->loadSerializedData())
48 {
49 return nullptr;
50 }
51
Willy Tu351a5d02021-12-07 19:48:40 -080052 return store;
Kun Yi64dc05c2018-12-19 13:19:03 -080053}
54
Maksym Sloykoeb274112021-10-27 23:48:32 +000055std::unique_ptr<BinaryStoreInterface>
Willy Tu67391d52021-12-07 19:53:49 -080056 BinaryStore::createFromFile(std::unique_ptr<SysFile> file, bool readOnly,
57 std::optional<uint32_t> maxSize)
Maksym Sloykoeb274112021-10-27 23:48:32 +000058{
59 if (!file)
60 {
61 log<level::ERR>("Unable to create binarystore from invalid file");
62 return nullptr;
63 }
64
Willy Tu67391d52021-12-07 19:53:49 -080065 auto store =
66 std::make_unique<BinaryStore>(std::move(file), readOnly, maxSize);
Maksym Sloykoeb274112021-10-27 23:48:32 +000067
68 if (!store->loadSerializedData())
69 {
70 return nullptr;
71 }
72
Willy Tu351a5d02021-12-07 19:48:40 -080073 return store;
Maksym Sloykoeb274112021-10-27 23:48:32 +000074}
75
Kun Yi97be3af2019-03-05 22:43:41 -080076bool BinaryStore::loadSerializedData()
77{
78 /* Load blob from sysfile if we know it might not match what we have.
79 * Note it will overwrite existing unsaved data per design. */
Kun Yie535a732019-04-03 19:03:25 -070080 if (commitState_ == CommitState::Clean ||
81 commitState_ == CommitState::Uninitialized)
Kun Yi97be3af2019-03-05 22:43:41 -080082 {
83 return true;
84 }
85
86 log<level::NOTICE>("Try loading blob from persistent data",
87 entry("BASE_ID=%s", baseBlobId_.c_str()));
88 try
89 {
90 /* Parse length-prefixed format to protobuf */
91 boost::endian::little_uint64_t size = 0;
92 file_->readToBuf(0, sizeof(size), reinterpret_cast<char*>(&size));
93
94 if (!blob_.ParseFromString(file_->readAsStr(sizeof(uint64_t), size)))
95 {
96 /* Fail to parse the data, which might mean no preexsiting blobs
97 * and is a valid case to handle. Simply init an empty binstore. */
Kun Yie535a732019-04-03 19:03:25 -070098 commitState_ = CommitState::Uninitialized;
Kun Yi97be3af2019-03-05 22:43:41 -080099 }
Willy Tu67391d52021-12-07 19:53:49 -0800100
101 // The new max size takes priority
102 if (maxSize)
103 {
104 blob_.set_max_size_bytes(*maxSize);
105 }
106 else
107 {
108 blob_.clear_max_size_bytes();
109 }
Kun Yi97be3af2019-03-05 22:43:41 -0800110 }
Kun Yic83d2fa2019-04-03 18:40:19 -0700111 catch (const std::system_error& e)
Kun Yi97be3af2019-03-05 22:43:41 -0800112 {
113 /* Read causes unexpected system-level failure */
114 log<level::ERR>("Reading from sysfile failed",
115 entry("ERROR=%s", e.what()));
116 return false;
117 }
Kun Yic83d2fa2019-04-03 18:40:19 -0700118 catch (const std::exception& e)
119 {
Kun Yie535a732019-04-03 19:03:25 -0700120 /* Non system error originates from junk value in 'size' */
121 commitState_ = CommitState::Uninitialized;
122 }
123
124 if (commitState_ == CommitState::Uninitialized)
125 {
126 log<level::WARNING>("Fail to parse. There might be no persisted blobs",
127 entry("BASE_ID=%s", baseBlobId_.c_str()));
Kun Yic83d2fa2019-04-03 18:40:19 -0700128 return true;
129 }
Kun Yi97be3af2019-03-05 22:43:41 -0800130
Maksym Sloykoeb274112021-10-27 23:48:32 +0000131 if (blob_.blob_base_id() != baseBlobId_ && !readOnly_)
Kun Yi8ca234e2019-03-04 10:14:45 -0800132 {
133 /* Uh oh, stale data loaded. Clean it and commit. */
134 // TODO: it might be safer to add an option in config to error out
135 // instead of to overwrite.
136 log<level::ERR>("Stale blob data, resetting internals...",
137 entry("LOADED=%s", blob_.blob_base_id().c_str()),
138 entry("EXPECTED=%s", baseBlobId_.c_str()));
139 blob_.Clear();
140 blob_.set_blob_base_id(baseBlobId_);
141 return this->commit();
142 }
143
Kun Yi97be3af2019-03-05 22:43:41 -0800144 return true;
145}
146
Kun Yi64dc05c2018-12-19 13:19:03 -0800147std::string BinaryStore::getBaseBlobId() const
148{
Maksym Sloykoeb274112021-10-27 23:48:32 +0000149 if (!baseBlobId_.empty())
150 {
151 return baseBlobId_;
152 }
153
154 return blob_.blob_base_id();
Kun Yi64dc05c2018-12-19 13:19:03 -0800155}
156
157std::vector<std::string> BinaryStore::getBlobIds() const
158{
159 std::vector<std::string> result;
Maksym Sloykoeb274112021-10-27 23:48:32 +0000160 result.push_back(getBaseBlobId());
Kun Yi64dc05c2018-12-19 13:19:03 -0800161
Kun Yi0a940b92019-01-07 16:33:11 -0800162 for (const auto& blob : blob_.blobs())
Kun Yi64dc05c2018-12-19 13:19:03 -0800163 {
Kun Yi0a940b92019-01-07 16:33:11 -0800164 result.push_back(blob.blob_id());
Kun Yi64dc05c2018-12-19 13:19:03 -0800165 }
166
167 return result;
168}
169
170bool BinaryStore::openOrCreateBlob(const std::string& blobId, uint16_t flags)
171{
Kun Yi6baa7132019-01-08 21:21:02 -0800172 if (!(flags & blobs::OpenFlags::read))
173 {
Kun Yi8bcf79d2019-01-16 15:17:57 -0800174 log<level::ERR>("OpenFlags::read not specified when opening",
175 entry("BLOB_ID=%s", blobId.c_str()));
Kun Yi6baa7132019-01-08 21:21:02 -0800176 return false;
177 }
178
179 if (currentBlob_ && (currentBlob_->blob_id() != blobId))
180 {
Kun Yi8bcf79d2019-01-16 15:17:57 -0800181 log<level::ERR>("Already handling a different blob",
182 entry("EXPECTED=%s", currentBlob_->blob_id().c_str()),
183 entry("RECEIVED=%s", blobId.c_str()));
Kun Yi6baa7132019-01-08 21:21:02 -0800184 return false;
185 }
186
Maksym Sloykoeb274112021-10-27 23:48:32 +0000187 if (readOnly_ && (flags & blobs::OpenFlags::write))
188 {
189 log<level::ERR>("Can't open the blob for writing: read-only store",
190 entry("BLOB_ID=%s", blobId.c_str()));
191 return false;
192 }
193
Kun Yi6baa7132019-01-08 21:21:02 -0800194 writable_ = flags & blobs::OpenFlags::write;
195
Kun Yi97be3af2019-03-05 22:43:41 -0800196 /* If there are uncommitted data, discard them. */
197 if (!this->loadSerializedData())
Kun Yid297c9f2019-01-09 13:52:30 -0800198 {
Kun Yi97be3af2019-03-05 22:43:41 -0800199 return false;
Kun Yid297c9f2019-01-09 13:52:30 -0800200 }
201
Kun Yi6baa7132019-01-08 21:21:02 -0800202 /* Iterate and find if there is an existing blob with this id.
203 * blobsPtr points to a BinaryBlob container with STL-like semantics*/
204 auto blobsPtr = blob_.mutable_blobs();
205 auto blobIt =
206 std::find_if(blobsPtr->begin(), blobsPtr->end(),
207 [&](const auto& b) { return b.blob_id() == blobId; });
208
209 if (blobIt != blobsPtr->end())
210 {
211 currentBlob_ = &(*blobIt);
212 return true;
213 }
214
215 /* Otherwise, create the blob and append it */
Maksym Sloykoeb274112021-10-27 23:48:32 +0000216 if (readOnly_)
217 {
218 return false;
219 }
220 else
221 {
222 currentBlob_ = blob_.add_blobs();
223 currentBlob_->set_blob_id(blobId);
Kun Yi6baa7132019-01-08 21:21:02 -0800224
Maksym Sloykoeb274112021-10-27 23:48:32 +0000225 commitState_ = CommitState::Dirty;
226 log<level::NOTICE>("Created new blob",
227 entry("BLOB_ID=%s", blobId.c_str()));
228 }
229
Kun Yi6baa7132019-01-08 21:21:02 -0800230 return true;
Kun Yi64dc05c2018-12-19 13:19:03 -0800231}
232
Willy Tu351a5d02021-12-07 19:48:40 -0800233bool BinaryStore::deleteBlob(const std::string&)
Kun Yi64dc05c2018-12-19 13:19:03 -0800234{
235 return false;
236}
237
238std::vector<uint8_t> BinaryStore::read(uint32_t offset, uint32_t requestedSize)
239{
Kun Yi6967b772019-02-22 13:48:12 -0800240 std::vector<uint8_t> result;
241
242 if (!currentBlob_)
243 {
Kun Yi8bcf79d2019-01-16 15:17:57 -0800244 log<level::ERR>("No open blob to read");
Kun Yi6967b772019-02-22 13:48:12 -0800245 return result;
246 }
247
248 auto dataPtr = currentBlob_->mutable_data();
249
250 /* If it is out of bound, return empty vector */
251 if (offset >= dataPtr->size())
252 {
Kun Yi8bcf79d2019-01-16 15:17:57 -0800253 log<level::ERR>("Read offset is beyond data size",
254 entry("MAX_SIZE=0x%x", dataPtr->size()),
255 entry("RECEIVED_OFFSET=0x%x", offset));
Kun Yi6967b772019-02-22 13:48:12 -0800256 return result;
257 }
258
259 uint32_t requestedEndPos = offset + requestedSize;
260
261 result = std::vector<uint8_t>(
262 dataPtr->begin() + offset,
263 std::min(dataPtr->begin() + requestedEndPos, dataPtr->end()));
Kun Yi64dc05c2018-12-19 13:19:03 -0800264 return result;
265}
266
Maksym Sloykoeb274112021-10-27 23:48:32 +0000267std::vector<uint8_t> BinaryStore::readBlob(const std::string& blobId) const
268{
269 const auto blobs = blob_.blobs();
270 const auto blobIt =
271 std::find_if(blobs.begin(), blobs.end(),
272 [&](const auto& b) { return b.blob_id() == blobId; });
273
274 if (blobIt == blobs.end())
275 {
276 throw ipmi::HandlerCompletion(ipmi::ccUnspecifiedError);
277 }
278
279 const auto blobData = blobIt->data();
280
281 return std::vector<uint8_t>(blobData.begin(), blobData.end());
282}
283
Kun Yi64dc05c2018-12-19 13:19:03 -0800284bool BinaryStore::write(uint32_t offset, const std::vector<uint8_t>& data)
285{
Kun Yi6967b772019-02-22 13:48:12 -0800286 if (!currentBlob_)
287 {
Kun Yi8bcf79d2019-01-16 15:17:57 -0800288 log<level::ERR>("No open blob to write");
Kun Yi6967b772019-02-22 13:48:12 -0800289 return false;
290 }
291
292 if (!writable_)
293 {
Kun Yi8bcf79d2019-01-16 15:17:57 -0800294 log<level::ERR>("Open blob is not writable");
Kun Yi6967b772019-02-22 13:48:12 -0800295 return false;
296 }
297
298 auto dataPtr = currentBlob_->mutable_data();
299
300 if (offset > dataPtr->size())
301 {
Kun Yi8bcf79d2019-01-16 15:17:57 -0800302 log<level::ERR>("Write would leave a gap with undefined data. Return.");
Kun Yi6967b772019-02-22 13:48:12 -0800303 return false;
304 }
305
Willy Tu67391d52021-12-07 19:53:49 -0800306 bool needResize = offset + data.size() > dataPtr->size();
307
308 // current size is the binary blob proto size + uint64 tracking the total
309 // size of the binary blob.
310 // currentSize = blob_size + x (uint64_t), where x = blob_size.
311 size_t currentSize = blob_.SerializeAsString().size() +
312 sizeof(boost::endian::little_uint64_t);
313 size_t sizeDelta = needResize ? offset + data.size() - dataPtr->size() : 0;
314
315 if (maxSize && currentSize + sizeDelta > *maxSize)
316 {
317 log<level::ERR>("Write data would make the total size exceed the max "
318 "size allowed. Return.");
319 return false;
320 }
321
Kun Yi1a25e0d2020-05-11 12:28:53 -0700322 commitState_ = CommitState::Dirty;
Kun Yi6967b772019-02-22 13:48:12 -0800323 /* Copy (overwrite) the data */
Willy Tu67391d52021-12-07 19:53:49 -0800324 if (needResize)
Kun Yi6967b772019-02-22 13:48:12 -0800325 {
326 dataPtr->resize(offset + data.size()); // not enough space, extend
327 }
328 std::copy(data.begin(), data.end(), dataPtr->begin() + offset);
329 return true;
Kun Yi64dc05c2018-12-19 13:19:03 -0800330}
331
332bool BinaryStore::commit()
333{
Maksym Sloykoeb274112021-10-27 23:48:32 +0000334 if (readOnly_)
335 {
336 log<level::ERR>("ReadOnly blob, not committing");
337 return false;
338 }
339
Kun Yi70059172019-02-22 16:31:42 -0800340 /* Store as little endian to be platform agnostic. Consistent with read. */
Kun Yid297c9f2019-01-09 13:52:30 -0800341 auto blobData = blob_.SerializeAsString();
Kun Yi70059172019-02-22 16:31:42 -0800342 boost::endian::little_uint64_t sizeLE = blobData.size();
William A. Kennington III805ade32020-05-06 20:39:05 -0700343 std::string commitData(reinterpret_cast<const char*>(sizeLE.data()),
344 sizeof(sizeLE));
Kun Yid297c9f2019-01-09 13:52:30 -0800345 commitData += blobData;
346
Willy Tu67391d52021-12-07 19:53:49 -0800347 // This should never be true if it is blocked by the write command
348 if (maxSize && sizeof(commitData) > *maxSize)
349 {
Willy Tu91633852022-02-18 08:55:43 -0800350 log<level::ERR>("Commit Data exceeded maximum allowed size");
Willy Tu67391d52021-12-07 19:53:49 -0800351 return false;
352 }
353
Kun Yid297c9f2019-01-09 13:52:30 -0800354 try
355 {
356 file_->writeStr(commitData, 0);
357 }
358 catch (const std::exception& e)
359 {
Kun Yid297c9f2019-01-09 13:52:30 -0800360 commitState_ = CommitState::CommitError;
Kun Yi8bcf79d2019-01-16 15:17:57 -0800361 log<level::ERR>("Writing to sysfile failed",
362 entry("ERROR=%s", e.what()));
Kun Yid297c9f2019-01-09 13:52:30 -0800363 return false;
364 };
365
366 commitState_ = CommitState::Clean;
367 return true;
Kun Yi64dc05c2018-12-19 13:19:03 -0800368}
369
370bool BinaryStore::close()
371{
Kun Yi6baa7132019-01-08 21:21:02 -0800372 currentBlob_ = nullptr;
373 writable_ = false;
Kun Yid297c9f2019-01-09 13:52:30 -0800374 commitState_ = CommitState::Dirty;
Kun Yi6baa7132019-01-08 21:21:02 -0800375 return true;
Kun Yi64dc05c2018-12-19 13:19:03 -0800376}
377
Kun Yi1a25e0d2020-05-11 12:28:53 -0700378/*
379 * Sets |meta| with size and state of the blob. Returns |blobState| with
380 * standard definition from phosphor-ipmi-blobs header blob.hpp, plus OEM
381 * flag bits BinaryStore::CommitState.
382
383enum StateFlags
Kun Yi64dc05c2018-12-19 13:19:03 -0800384{
Kun Yi1a25e0d2020-05-11 12:28:53 -0700385 open_read = (1 << 0),
386 open_write = (1 << 1),
387 committing = (1 << 2),
388 committed = (1 << 3),
389 commit_error = (1 << 4),
390};
391
392enum CommitState
393{
394 Dirty = (1 << 8), // In-memory data might not match persisted data
395 Clean = (1 << 9), // In-memory data matches persisted data
396 Uninitialized = (1 << 10), // Cannot find persisted data
397 CommitError = (1 << 11) // Error happened during committing
398};
399
400*/
401bool BinaryStore::stat(blobs::BlobMeta* meta)
402{
403 uint16_t blobState = blobs::StateFlags::open_read;
404 if (writable_)
405 {
406 blobState |= blobs::StateFlags::open_write;
407 }
408
409 if (commitState_ == CommitState::Clean)
410 {
411 blobState |= blobs::StateFlags::committed;
412 }
413 else if (commitState_ == CommitState::CommitError)
414 {
415 blobState |= blobs::StateFlags::commit_error;
416 }
417 blobState |= commitState_;
418
419 if (currentBlob_)
420 {
421 meta->size = currentBlob_->data().size();
422 }
423 else
424 {
425 meta->size = 0;
426 }
427 meta->blobState = blobState;
428
429 return true;
Kun Yi64dc05c2018-12-19 13:19:03 -0800430}
431
432} // namespace binstore