blob: c69a6537b29ac5df38dd7a5c76887bf3636920ed [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
Willy Tu7f107802023-11-06 23:05:25 -080032std::unique_ptr<BinaryStoreInterface> BinaryStore::createFromConfig(
33 const std::string& baseBlobId, std::unique_ptr<SysFile> file,
34 std::optional<uint32_t> maxSize, std::optional<std::string> aliasBlobBaseId)
Kun Yi64dc05c2018-12-19 13:19:03 -080035{
Kun Yi2765b642019-01-16 11:11:24 -080036 if (baseBlobId.empty() || !file)
37 {
Kun Yi8bcf79d2019-01-16 15:17:57 -080038 log<level::ERR>("Unable to create binarystore from invalid config",
39 entry("BASE_ID=%s", baseBlobId.c_str()));
Kun Yi2765b642019-01-16 11:11:24 -080040 return nullptr;
41 }
42
Willy Tu67391d52021-12-07 19:53:49 -080043 auto store =
44 std::make_unique<BinaryStore>(baseBlobId, std::move(file), maxSize);
Kun Yi2765b642019-01-16 11:11:24 -080045
Willy Tu7f107802023-11-06 23:05:25 -080046 if (!store->loadSerializedData(aliasBlobBaseId))
Kun Yi8ca234e2019-03-04 10:14:45 -080047 {
48 return nullptr;
49 }
50
Willy Tu351a5d02021-12-07 19:48:40 -080051 return store;
Kun Yi64dc05c2018-12-19 13:19:03 -080052}
53
Maksym Sloykoeb274112021-10-27 23:48:32 +000054std::unique_ptr<BinaryStoreInterface>
Willy Tu67391d52021-12-07 19:53:49 -080055 BinaryStore::createFromFile(std::unique_ptr<SysFile> file, bool readOnly,
56 std::optional<uint32_t> maxSize)
Maksym Sloykoeb274112021-10-27 23:48:32 +000057{
58 if (!file)
59 {
60 log<level::ERR>("Unable to create binarystore from invalid file");
61 return nullptr;
62 }
63
Willy Tu67391d52021-12-07 19:53:49 -080064 auto store =
65 std::make_unique<BinaryStore>(std::move(file), readOnly, maxSize);
Maksym Sloykoeb274112021-10-27 23:48:32 +000066
67 if (!store->loadSerializedData())
68 {
69 return nullptr;
70 }
71
Willy Tu351a5d02021-12-07 19:48:40 -080072 return store;
Maksym Sloykoeb274112021-10-27 23:48:32 +000073}
74
Willy Tu7f107802023-11-06 23:05:25 -080075bool BinaryStore::loadSerializedData(std::optional<std::string> aliasBlobBaseId)
Kun Yi97be3af2019-03-05 22:43:41 -080076{
77 /* Load blob from sysfile if we know it might not match what we have.
78 * Note it will overwrite existing unsaved data per design. */
Kun Yie535a732019-04-03 19:03:25 -070079 if (commitState_ == CommitState::Clean ||
80 commitState_ == CommitState::Uninitialized)
Kun Yi97be3af2019-03-05 22:43:41 -080081 {
82 return true;
83 }
84
85 log<level::NOTICE>("Try loading blob from persistent data",
86 entry("BASE_ID=%s", baseBlobId_.c_str()));
87 try
88 {
89 /* Parse length-prefixed format to protobuf */
90 boost::endian::little_uint64_t size = 0;
91 file_->readToBuf(0, sizeof(size), reinterpret_cast<char*>(&size));
92
93 if (!blob_.ParseFromString(file_->readAsStr(sizeof(uint64_t), size)))
94 {
95 /* Fail to parse the data, which might mean no preexsiting blobs
96 * and is a valid case to handle. Simply init an empty binstore. */
Kun Yie535a732019-04-03 19:03:25 -070097 commitState_ = CommitState::Uninitialized;
Kun Yi97be3af2019-03-05 22:43:41 -080098 }
Willy Tu67391d52021-12-07 19:53:49 -080099
100 // The new max size takes priority
101 if (maxSize)
102 {
103 blob_.set_max_size_bytes(*maxSize);
104 }
105 else
106 {
107 blob_.clear_max_size_bytes();
108 }
Kun Yi97be3af2019-03-05 22:43:41 -0800109 }
Kun Yic83d2fa2019-04-03 18:40:19 -0700110 catch (const std::system_error& e)
Kun Yi97be3af2019-03-05 22:43:41 -0800111 {
112 /* Read causes unexpected system-level failure */
113 log<level::ERR>("Reading from sysfile failed",
114 entry("ERROR=%s", e.what()));
115 return false;
116 }
Kun Yic83d2fa2019-04-03 18:40:19 -0700117 catch (const std::exception& e)
118 {
Kun Yie535a732019-04-03 19:03:25 -0700119 /* Non system error originates from junk value in 'size' */
120 commitState_ = CommitState::Uninitialized;
121 }
122
123 if (commitState_ == CommitState::Uninitialized)
124 {
125 log<level::WARNING>("Fail to parse. There might be no persisted blobs",
126 entry("BASE_ID=%s", baseBlobId_.c_str()));
Kun Yic83d2fa2019-04-03 18:40:19 -0700127 return true;
128 }
Kun Yi97be3af2019-03-05 22:43:41 -0800129
Willy Tu7f107802023-11-06 23:05:25 -0800130 std::string alias = aliasBlobBaseId.value_or("");
131 if (blob_.blob_base_id() == alias)
132 {
133 log<level::WARNING>("Alias blob id, rename blob id...",
134 entry("LOADED=%s", alias.c_str()),
135 entry("RENAMED=%s", baseBlobId_.c_str()));
136 std::string tmpBlobId = baseBlobId_;
137 baseBlobId_ = alias;
138 return setBaseBlobId(tmpBlobId);
139 }
Maksym Sloykoeb274112021-10-27 23:48:32 +0000140 if (blob_.blob_base_id() != baseBlobId_ && !readOnly_)
Kun Yi8ca234e2019-03-04 10:14:45 -0800141 {
142 /* Uh oh, stale data loaded. Clean it and commit. */
143 // TODO: it might be safer to add an option in config to error out
144 // instead of to overwrite.
145 log<level::ERR>("Stale blob data, resetting internals...",
146 entry("LOADED=%s", blob_.blob_base_id().c_str()),
147 entry("EXPECTED=%s", baseBlobId_.c_str()));
148 blob_.Clear();
149 blob_.set_blob_base_id(baseBlobId_);
150 return this->commit();
151 }
152
Kun Yi97be3af2019-03-05 22:43:41 -0800153 return true;
154}
155
Kun Yi64dc05c2018-12-19 13:19:03 -0800156std::string BinaryStore::getBaseBlobId() const
157{
Maksym Sloykoeb274112021-10-27 23:48:32 +0000158 if (!baseBlobId_.empty())
159 {
160 return baseBlobId_;
161 }
162
163 return blob_.blob_base_id();
Kun Yi64dc05c2018-12-19 13:19:03 -0800164}
165
Willy Tu7f107802023-11-06 23:05:25 -0800166bool BinaryStore::setBaseBlobId(const std::string& baseBlobId)
167{
168 if (baseBlobId_.empty())
169 {
170 baseBlobId_ = blob_.blob_base_id();
171 }
172
173 std::string oldBlobId = baseBlobId_;
174 size_t oldPrefixIndex = baseBlobId_.size();
175 baseBlobId_ = baseBlobId;
176 blob_.set_blob_base_id(baseBlobId_);
177 auto blobsPtr = blob_.mutable_blobs();
178 for (auto blob = blobsPtr->begin(); blob != blobsPtr->end(); blob++)
179 {
180 const std::string& blodId = blob->blob_id();
181 if (blodId.starts_with(oldBlobId))
182 {
183 blob->set_blob_id(baseBlobId_ + blodId.substr(oldPrefixIndex));
184 }
185 }
186 return this->commit();
187}
188
Kun Yi64dc05c2018-12-19 13:19:03 -0800189std::vector<std::string> BinaryStore::getBlobIds() const
190{
191 std::vector<std::string> result;
Willy Tufdebaa32022-02-08 16:34:20 -0800192 result.reserve(blob_.blobs().size() + 1);
193 result.emplace_back(getBaseBlobId());
194 std::for_each(
195 blob_.blobs().begin(), blob_.blobs().end(),
196 [&result](const auto& blob) { result.emplace_back(blob.blob_id()); });
Kun Yi64dc05c2018-12-19 13:19:03 -0800197
198 return result;
199}
200
201bool BinaryStore::openOrCreateBlob(const std::string& blobId, uint16_t flags)
202{
Kun Yi6baa7132019-01-08 21:21:02 -0800203 if (!(flags & blobs::OpenFlags::read))
204 {
Kun Yi8bcf79d2019-01-16 15:17:57 -0800205 log<level::ERR>("OpenFlags::read not specified when opening",
206 entry("BLOB_ID=%s", blobId.c_str()));
Kun Yi6baa7132019-01-08 21:21:02 -0800207 return false;
208 }
209
210 if (currentBlob_ && (currentBlob_->blob_id() != blobId))
211 {
Kun Yi8bcf79d2019-01-16 15:17:57 -0800212 log<level::ERR>("Already handling a different blob",
213 entry("EXPECTED=%s", currentBlob_->blob_id().c_str()),
214 entry("RECEIVED=%s", blobId.c_str()));
Kun Yi6baa7132019-01-08 21:21:02 -0800215 return false;
216 }
217
Maksym Sloykoeb274112021-10-27 23:48:32 +0000218 if (readOnly_ && (flags & blobs::OpenFlags::write))
219 {
220 log<level::ERR>("Can't open the blob for writing: read-only store",
221 entry("BLOB_ID=%s", blobId.c_str()));
222 return false;
223 }
224
Kun Yi6baa7132019-01-08 21:21:02 -0800225 writable_ = flags & blobs::OpenFlags::write;
226
Kun Yi97be3af2019-03-05 22:43:41 -0800227 /* If there are uncommitted data, discard them. */
228 if (!this->loadSerializedData())
Kun Yid297c9f2019-01-09 13:52:30 -0800229 {
Kun Yi97be3af2019-03-05 22:43:41 -0800230 return false;
Kun Yid297c9f2019-01-09 13:52:30 -0800231 }
232
Kun Yi6baa7132019-01-08 21:21:02 -0800233 /* Iterate and find if there is an existing blob with this id.
234 * blobsPtr points to a BinaryBlob container with STL-like semantics*/
235 auto blobsPtr = blob_.mutable_blobs();
236 auto blobIt =
237 std::find_if(blobsPtr->begin(), blobsPtr->end(),
238 [&](const auto& b) { return b.blob_id() == blobId; });
239
240 if (blobIt != blobsPtr->end())
241 {
242 currentBlob_ = &(*blobIt);
243 return true;
244 }
245
246 /* Otherwise, create the blob and append it */
Maksym Sloykoeb274112021-10-27 23:48:32 +0000247 if (readOnly_)
248 {
249 return false;
250 }
251 else
252 {
253 currentBlob_ = blob_.add_blobs();
254 currentBlob_->set_blob_id(blobId);
Kun Yi6baa7132019-01-08 21:21:02 -0800255
Maksym Sloykoeb274112021-10-27 23:48:32 +0000256 commitState_ = CommitState::Dirty;
257 log<level::NOTICE>("Created new blob",
258 entry("BLOB_ID=%s", blobId.c_str()));
259 }
260
Kun Yi6baa7132019-01-08 21:21:02 -0800261 return true;
Kun Yi64dc05c2018-12-19 13:19:03 -0800262}
263
Willy Tu351a5d02021-12-07 19:48:40 -0800264bool BinaryStore::deleteBlob(const std::string&)
Kun Yi64dc05c2018-12-19 13:19:03 -0800265{
266 return false;
267}
268
269std::vector<uint8_t> BinaryStore::read(uint32_t offset, uint32_t requestedSize)
270{
Kun Yi6967b772019-02-22 13:48:12 -0800271 std::vector<uint8_t> result;
272
273 if (!currentBlob_)
274 {
Kun Yi8bcf79d2019-01-16 15:17:57 -0800275 log<level::ERR>("No open blob to read");
Kun Yi6967b772019-02-22 13:48:12 -0800276 return result;
277 }
278
279 auto dataPtr = currentBlob_->mutable_data();
280
281 /* If it is out of bound, return empty vector */
282 if (offset >= dataPtr->size())
283 {
Kun Yi8bcf79d2019-01-16 15:17:57 -0800284 log<level::ERR>("Read offset is beyond data size",
285 entry("MAX_SIZE=0x%x", dataPtr->size()),
286 entry("RECEIVED_OFFSET=0x%x", offset));
Kun Yi6967b772019-02-22 13:48:12 -0800287 return result;
288 }
289
290 uint32_t requestedEndPos = offset + requestedSize;
291
292 result = std::vector<uint8_t>(
293 dataPtr->begin() + offset,
294 std::min(dataPtr->begin() + requestedEndPos, dataPtr->end()));
Kun Yi64dc05c2018-12-19 13:19:03 -0800295 return result;
296}
297
Maksym Sloykoeb274112021-10-27 23:48:32 +0000298std::vector<uint8_t> BinaryStore::readBlob(const std::string& blobId) const
299{
300 const auto blobs = blob_.blobs();
301 const auto blobIt =
302 std::find_if(blobs.begin(), blobs.end(),
303 [&](const auto& b) { return b.blob_id() == blobId; });
304
305 if (blobIt == blobs.end())
306 {
307 throw ipmi::HandlerCompletion(ipmi::ccUnspecifiedError);
308 }
309
310 const auto blobData = blobIt->data();
311
312 return std::vector<uint8_t>(blobData.begin(), blobData.end());
313}
314
Kun Yi64dc05c2018-12-19 13:19:03 -0800315bool BinaryStore::write(uint32_t offset, const std::vector<uint8_t>& data)
316{
Kun Yi6967b772019-02-22 13:48:12 -0800317 if (!currentBlob_)
318 {
Kun Yi8bcf79d2019-01-16 15:17:57 -0800319 log<level::ERR>("No open blob to write");
Kun Yi6967b772019-02-22 13:48:12 -0800320 return false;
321 }
322
323 if (!writable_)
324 {
Kun Yi8bcf79d2019-01-16 15:17:57 -0800325 log<level::ERR>("Open blob is not writable");
Kun Yi6967b772019-02-22 13:48:12 -0800326 return false;
327 }
328
329 auto dataPtr = currentBlob_->mutable_data();
330
331 if (offset > dataPtr->size())
332 {
Kun Yi8bcf79d2019-01-16 15:17:57 -0800333 log<level::ERR>("Write would leave a gap with undefined data. Return.");
Kun Yi6967b772019-02-22 13:48:12 -0800334 return false;
335 }
336
Willy Tu67391d52021-12-07 19:53:49 -0800337 bool needResize = offset + data.size() > dataPtr->size();
338
339 // current size is the binary blob proto size + uint64 tracking the total
340 // size of the binary blob.
341 // currentSize = blob_size + x (uint64_t), where x = blob_size.
342 size_t currentSize = blob_.SerializeAsString().size() +
343 sizeof(boost::endian::little_uint64_t);
344 size_t sizeDelta = needResize ? offset + data.size() - dataPtr->size() : 0;
345
346 if (maxSize && currentSize + sizeDelta > *maxSize)
347 {
348 log<level::ERR>("Write data would make the total size exceed the max "
349 "size allowed. Return.");
350 return false;
351 }
352
Kun Yi1a25e0d2020-05-11 12:28:53 -0700353 commitState_ = CommitState::Dirty;
Kun Yi6967b772019-02-22 13:48:12 -0800354 /* Copy (overwrite) the data */
Willy Tu67391d52021-12-07 19:53:49 -0800355 if (needResize)
Kun Yi6967b772019-02-22 13:48:12 -0800356 {
357 dataPtr->resize(offset + data.size()); // not enough space, extend
358 }
359 std::copy(data.begin(), data.end(), dataPtr->begin() + offset);
360 return true;
Kun Yi64dc05c2018-12-19 13:19:03 -0800361}
362
363bool BinaryStore::commit()
364{
Maksym Sloykoeb274112021-10-27 23:48:32 +0000365 if (readOnly_)
366 {
367 log<level::ERR>("ReadOnly blob, not committing");
368 return false;
369 }
370
Kun Yi70059172019-02-22 16:31:42 -0800371 /* Store as little endian to be platform agnostic. Consistent with read. */
Kun Yid297c9f2019-01-09 13:52:30 -0800372 auto blobData = blob_.SerializeAsString();
Kun Yi70059172019-02-22 16:31:42 -0800373 boost::endian::little_uint64_t sizeLE = blobData.size();
William A. Kennington III805ade32020-05-06 20:39:05 -0700374 std::string commitData(reinterpret_cast<const char*>(sizeLE.data()),
375 sizeof(sizeLE));
Kun Yid297c9f2019-01-09 13:52:30 -0800376 commitData += blobData;
377
Willy Tu67391d52021-12-07 19:53:49 -0800378 // This should never be true if it is blocked by the write command
379 if (maxSize && sizeof(commitData) > *maxSize)
380 {
Willy Tu91633852022-02-18 08:55:43 -0800381 log<level::ERR>("Commit Data exceeded maximum allowed size");
Willy Tu67391d52021-12-07 19:53:49 -0800382 return false;
383 }
384
Kun Yid297c9f2019-01-09 13:52:30 -0800385 try
386 {
387 file_->writeStr(commitData, 0);
388 }
389 catch (const std::exception& e)
390 {
Kun Yid297c9f2019-01-09 13:52:30 -0800391 commitState_ = CommitState::CommitError;
Kun Yi8bcf79d2019-01-16 15:17:57 -0800392 log<level::ERR>("Writing to sysfile failed",
393 entry("ERROR=%s", e.what()));
Kun Yid297c9f2019-01-09 13:52:30 -0800394 return false;
395 };
396
397 commitState_ = CommitState::Clean;
398 return true;
Kun Yi64dc05c2018-12-19 13:19:03 -0800399}
400
401bool BinaryStore::close()
402{
Kun Yi6baa7132019-01-08 21:21:02 -0800403 currentBlob_ = nullptr;
404 writable_ = false;
Kun Yid297c9f2019-01-09 13:52:30 -0800405 commitState_ = CommitState::Dirty;
Kun Yi6baa7132019-01-08 21:21:02 -0800406 return true;
Kun Yi64dc05c2018-12-19 13:19:03 -0800407}
408
Kun Yi1a25e0d2020-05-11 12:28:53 -0700409/*
410 * Sets |meta| with size and state of the blob. Returns |blobState| with
411 * standard definition from phosphor-ipmi-blobs header blob.hpp, plus OEM
412 * flag bits BinaryStore::CommitState.
413
414enum StateFlags
Kun Yi64dc05c2018-12-19 13:19:03 -0800415{
Kun Yi1a25e0d2020-05-11 12:28:53 -0700416 open_read = (1 << 0),
417 open_write = (1 << 1),
418 committing = (1 << 2),
419 committed = (1 << 3),
420 commit_error = (1 << 4),
421};
422
423enum CommitState
424{
425 Dirty = (1 << 8), // In-memory data might not match persisted data
426 Clean = (1 << 9), // In-memory data matches persisted data
427 Uninitialized = (1 << 10), // Cannot find persisted data
428 CommitError = (1 << 11) // Error happened during committing
429};
430
431*/
432bool BinaryStore::stat(blobs::BlobMeta* meta)
433{
434 uint16_t blobState = blobs::StateFlags::open_read;
435 if (writable_)
436 {
437 blobState |= blobs::StateFlags::open_write;
438 }
439
440 if (commitState_ == CommitState::Clean)
441 {
442 blobState |= blobs::StateFlags::committed;
443 }
444 else if (commitState_ == CommitState::CommitError)
445 {
446 blobState |= blobs::StateFlags::commit_error;
447 }
448 blobState |= commitState_;
449
450 if (currentBlob_)
451 {
452 meta->size = currentBlob_->data().size();
453 }
454 else
455 {
456 meta->size = 0;
457 }
458 meta->blobState = blobState;
459
460 return true;
Kun Yi64dc05c2018-12-19 13:19:03 -0800461}
462
463} // namespace binstore