blob: 31a2b61f7e534bc8b9b85195dc7a6e52bd8ce1a1 [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 }
99 }
Kun Yic83d2fa2019-04-03 18:40:19 -0700100 catch (const std::system_error& e)
Kun Yi97be3af2019-03-05 22:43:41 -0800101 {
102 /* Read causes unexpected system-level failure */
103 log<level::ERR>("Reading from sysfile failed",
104 entry("ERROR=%s", e.what()));
105 return false;
106 }
Kun Yic83d2fa2019-04-03 18:40:19 -0700107 catch (const std::exception& e)
108 {
Kun Yie535a732019-04-03 19:03:25 -0700109 /* Non system error originates from junk value in 'size' */
110 commitState_ = CommitState::Uninitialized;
111 }
112
113 if (commitState_ == CommitState::Uninitialized)
114 {
115 log<level::WARNING>("Fail to parse. There might be no persisted blobs",
116 entry("BASE_ID=%s", baseBlobId_.c_str()));
Kun Yic83d2fa2019-04-03 18:40:19 -0700117 return true;
118 }
Kun Yi97be3af2019-03-05 22:43:41 -0800119
Willy Tu7f107802023-11-06 23:05:25 -0800120 std::string alias = aliasBlobBaseId.value_or("");
121 if (blob_.blob_base_id() == alias)
122 {
123 log<level::WARNING>("Alias blob id, rename blob id...",
124 entry("LOADED=%s", alias.c_str()),
125 entry("RENAMED=%s", baseBlobId_.c_str()));
126 std::string tmpBlobId = baseBlobId_;
127 baseBlobId_ = alias;
128 return setBaseBlobId(tmpBlobId);
129 }
Maksym Sloykoeb274112021-10-27 23:48:32 +0000130 if (blob_.blob_base_id() != baseBlobId_ && !readOnly_)
Kun Yi8ca234e2019-03-04 10:14:45 -0800131 {
132 /* Uh oh, stale data loaded. Clean it and commit. */
133 // TODO: it might be safer to add an option in config to error out
134 // instead of to overwrite.
135 log<level::ERR>("Stale blob data, resetting internals...",
136 entry("LOADED=%s", blob_.blob_base_id().c_str()),
137 entry("EXPECTED=%s", baseBlobId_.c_str()));
138 blob_.Clear();
139 blob_.set_blob_base_id(baseBlobId_);
140 return this->commit();
141 }
142
Kun Yi97be3af2019-03-05 22:43:41 -0800143 return true;
144}
145
Kun Yi64dc05c2018-12-19 13:19:03 -0800146std::string BinaryStore::getBaseBlobId() const
147{
Maksym Sloykoeb274112021-10-27 23:48:32 +0000148 if (!baseBlobId_.empty())
149 {
150 return baseBlobId_;
151 }
152
153 return blob_.blob_base_id();
Kun Yi64dc05c2018-12-19 13:19:03 -0800154}
155
Willy Tu7f107802023-11-06 23:05:25 -0800156bool BinaryStore::setBaseBlobId(const std::string& baseBlobId)
157{
158 if (baseBlobId_.empty())
159 {
160 baseBlobId_ = blob_.blob_base_id();
161 }
162
163 std::string oldBlobId = baseBlobId_;
164 size_t oldPrefixIndex = baseBlobId_.size();
165 baseBlobId_ = baseBlobId;
166 blob_.set_blob_base_id(baseBlobId_);
167 auto blobsPtr = blob_.mutable_blobs();
168 for (auto blob = blobsPtr->begin(); blob != blobsPtr->end(); blob++)
169 {
170 const std::string& blodId = blob->blob_id();
171 if (blodId.starts_with(oldBlobId))
172 {
173 blob->set_blob_id(baseBlobId_ + blodId.substr(oldPrefixIndex));
174 }
175 }
176 return this->commit();
177}
178
Kun Yi64dc05c2018-12-19 13:19:03 -0800179std::vector<std::string> BinaryStore::getBlobIds() const
180{
181 std::vector<std::string> result;
Willy Tufdebaa32022-02-08 16:34:20 -0800182 result.reserve(blob_.blobs().size() + 1);
183 result.emplace_back(getBaseBlobId());
184 std::for_each(
185 blob_.blobs().begin(), blob_.blobs().end(),
186 [&result](const auto& blob) { result.emplace_back(blob.blob_id()); });
Kun Yi64dc05c2018-12-19 13:19:03 -0800187
188 return result;
189}
190
191bool BinaryStore::openOrCreateBlob(const std::string& blobId, uint16_t flags)
192{
Kun Yi6baa7132019-01-08 21:21:02 -0800193 if (!(flags & blobs::OpenFlags::read))
194 {
Kun Yi8bcf79d2019-01-16 15:17:57 -0800195 log<level::ERR>("OpenFlags::read not specified when opening",
196 entry("BLOB_ID=%s", blobId.c_str()));
Kun Yi6baa7132019-01-08 21:21:02 -0800197 return false;
198 }
199
200 if (currentBlob_ && (currentBlob_->blob_id() != blobId))
201 {
Kun Yi8bcf79d2019-01-16 15:17:57 -0800202 log<level::ERR>("Already handling a different blob",
203 entry("EXPECTED=%s", currentBlob_->blob_id().c_str()),
204 entry("RECEIVED=%s", blobId.c_str()));
Kun Yi6baa7132019-01-08 21:21:02 -0800205 return false;
206 }
207
Maksym Sloykoeb274112021-10-27 23:48:32 +0000208 if (readOnly_ && (flags & blobs::OpenFlags::write))
209 {
210 log<level::ERR>("Can't open the blob for writing: read-only store",
211 entry("BLOB_ID=%s", blobId.c_str()));
212 return false;
213 }
214
Kun Yi6baa7132019-01-08 21:21:02 -0800215 writable_ = flags & blobs::OpenFlags::write;
216
Kun Yi97be3af2019-03-05 22:43:41 -0800217 /* If there are uncommitted data, discard them. */
218 if (!this->loadSerializedData())
Kun Yid297c9f2019-01-09 13:52:30 -0800219 {
Kun Yi97be3af2019-03-05 22:43:41 -0800220 return false;
Kun Yid297c9f2019-01-09 13:52:30 -0800221 }
222
Kun Yi6baa7132019-01-08 21:21:02 -0800223 /* Iterate and find if there is an existing blob with this id.
224 * blobsPtr points to a BinaryBlob container with STL-like semantics*/
225 auto blobsPtr = blob_.mutable_blobs();
226 auto blobIt =
227 std::find_if(blobsPtr->begin(), blobsPtr->end(),
228 [&](const auto& b) { return b.blob_id() == blobId; });
229
230 if (blobIt != blobsPtr->end())
231 {
232 currentBlob_ = &(*blobIt);
233 return true;
234 }
235
236 /* Otherwise, create the blob and append it */
Maksym Sloykoeb274112021-10-27 23:48:32 +0000237 if (readOnly_)
238 {
239 return false;
240 }
241 else
242 {
243 currentBlob_ = blob_.add_blobs();
244 currentBlob_->set_blob_id(blobId);
Kun Yi6baa7132019-01-08 21:21:02 -0800245
Maksym Sloykoeb274112021-10-27 23:48:32 +0000246 commitState_ = CommitState::Dirty;
247 log<level::NOTICE>("Created new blob",
248 entry("BLOB_ID=%s", blobId.c_str()));
249 }
250
Kun Yi6baa7132019-01-08 21:21:02 -0800251 return true;
Kun Yi64dc05c2018-12-19 13:19:03 -0800252}
253
Willy Tu351a5d02021-12-07 19:48:40 -0800254bool BinaryStore::deleteBlob(const std::string&)
Kun Yi64dc05c2018-12-19 13:19:03 -0800255{
256 return false;
257}
258
259std::vector<uint8_t> BinaryStore::read(uint32_t offset, uint32_t requestedSize)
260{
Kun Yi6967b772019-02-22 13:48:12 -0800261 std::vector<uint8_t> result;
262
263 if (!currentBlob_)
264 {
Kun Yi8bcf79d2019-01-16 15:17:57 -0800265 log<level::ERR>("No open blob to read");
Kun Yi6967b772019-02-22 13:48:12 -0800266 return result;
267 }
268
269 auto dataPtr = currentBlob_->mutable_data();
270
271 /* If it is out of bound, return empty vector */
272 if (offset >= dataPtr->size())
273 {
Kun Yi8bcf79d2019-01-16 15:17:57 -0800274 log<level::ERR>("Read offset is beyond data size",
275 entry("MAX_SIZE=0x%x", dataPtr->size()),
276 entry("RECEIVED_OFFSET=0x%x", offset));
Kun Yi6967b772019-02-22 13:48:12 -0800277 return result;
278 }
279
280 uint32_t requestedEndPos = offset + requestedSize;
281
282 result = std::vector<uint8_t>(
283 dataPtr->begin() + offset,
284 std::min(dataPtr->begin() + requestedEndPos, dataPtr->end()));
Kun Yi64dc05c2018-12-19 13:19:03 -0800285 return result;
286}
287
Maksym Sloykoeb274112021-10-27 23:48:32 +0000288std::vector<uint8_t> BinaryStore::readBlob(const std::string& blobId) const
289{
290 const auto blobs = blob_.blobs();
291 const auto blobIt =
292 std::find_if(blobs.begin(), blobs.end(),
293 [&](const auto& b) { return b.blob_id() == blobId; });
294
295 if (blobIt == blobs.end())
296 {
297 throw ipmi::HandlerCompletion(ipmi::ccUnspecifiedError);
298 }
299
300 const auto blobData = blobIt->data();
301
302 return std::vector<uint8_t>(blobData.begin(), blobData.end());
303}
304
Kun Yi64dc05c2018-12-19 13:19:03 -0800305bool BinaryStore::write(uint32_t offset, const std::vector<uint8_t>& data)
306{
Kun Yi6967b772019-02-22 13:48:12 -0800307 if (!currentBlob_)
308 {
Kun Yi8bcf79d2019-01-16 15:17:57 -0800309 log<level::ERR>("No open blob to write");
Kun Yi6967b772019-02-22 13:48:12 -0800310 return false;
311 }
312
313 if (!writable_)
314 {
Kun Yi8bcf79d2019-01-16 15:17:57 -0800315 log<level::ERR>("Open blob is not writable");
Kun Yi6967b772019-02-22 13:48:12 -0800316 return false;
317 }
318
319 auto dataPtr = currentBlob_->mutable_data();
320
321 if (offset > dataPtr->size())
322 {
Kun Yi8bcf79d2019-01-16 15:17:57 -0800323 log<level::ERR>("Write would leave a gap with undefined data. Return.");
Kun Yi6967b772019-02-22 13:48:12 -0800324 return false;
325 }
326
Willy Tu67391d52021-12-07 19:53:49 -0800327 bool needResize = offset + data.size() > dataPtr->size();
328
329 // current size is the binary blob proto size + uint64 tracking the total
330 // size of the binary blob.
331 // currentSize = blob_size + x (uint64_t), where x = blob_size.
332 size_t currentSize = blob_.SerializeAsString().size() +
333 sizeof(boost::endian::little_uint64_t);
334 size_t sizeDelta = needResize ? offset + data.size() - dataPtr->size() : 0;
335
336 if (maxSize && currentSize + sizeDelta > *maxSize)
337 {
338 log<level::ERR>("Write data would make the total size exceed the max "
339 "size allowed. Return.");
340 return false;
341 }
342
Kun Yi1a25e0d2020-05-11 12:28:53 -0700343 commitState_ = CommitState::Dirty;
Kun Yi6967b772019-02-22 13:48:12 -0800344 /* Copy (overwrite) the data */
Willy Tu67391d52021-12-07 19:53:49 -0800345 if (needResize)
Kun Yi6967b772019-02-22 13:48:12 -0800346 {
347 dataPtr->resize(offset + data.size()); // not enough space, extend
348 }
349 std::copy(data.begin(), data.end(), dataPtr->begin() + offset);
350 return true;
Kun Yi64dc05c2018-12-19 13:19:03 -0800351}
352
353bool BinaryStore::commit()
354{
Maksym Sloykoeb274112021-10-27 23:48:32 +0000355 if (readOnly_)
356 {
357 log<level::ERR>("ReadOnly blob, not committing");
358 return false;
359 }
360
Kun Yi70059172019-02-22 16:31:42 -0800361 /* Store as little endian to be platform agnostic. Consistent with read. */
Kun Yid297c9f2019-01-09 13:52:30 -0800362 auto blobData = blob_.SerializeAsString();
Kun Yi70059172019-02-22 16:31:42 -0800363 boost::endian::little_uint64_t sizeLE = blobData.size();
William A. Kennington III805ade32020-05-06 20:39:05 -0700364 std::string commitData(reinterpret_cast<const char*>(sizeLE.data()),
365 sizeof(sizeLE));
Kun Yid297c9f2019-01-09 13:52:30 -0800366 commitData += blobData;
367
Willy Tu67391d52021-12-07 19:53:49 -0800368 // This should never be true if it is blocked by the write command
369 if (maxSize && sizeof(commitData) > *maxSize)
370 {
Willy Tu91633852022-02-18 08:55:43 -0800371 log<level::ERR>("Commit Data exceeded maximum allowed size");
Willy Tu67391d52021-12-07 19:53:49 -0800372 return false;
373 }
374
Kun Yid297c9f2019-01-09 13:52:30 -0800375 try
376 {
377 file_->writeStr(commitData, 0);
378 }
379 catch (const std::exception& e)
380 {
Kun Yid297c9f2019-01-09 13:52:30 -0800381 commitState_ = CommitState::CommitError;
Kun Yi8bcf79d2019-01-16 15:17:57 -0800382 log<level::ERR>("Writing to sysfile failed",
383 entry("ERROR=%s", e.what()));
Kun Yid297c9f2019-01-09 13:52:30 -0800384 return false;
385 };
386
387 commitState_ = CommitState::Clean;
388 return true;
Kun Yi64dc05c2018-12-19 13:19:03 -0800389}
390
391bool BinaryStore::close()
392{
Kun Yi6baa7132019-01-08 21:21:02 -0800393 currentBlob_ = nullptr;
394 writable_ = false;
Kun Yid297c9f2019-01-09 13:52:30 -0800395 commitState_ = CommitState::Dirty;
Kun Yi6baa7132019-01-08 21:21:02 -0800396 return true;
Kun Yi64dc05c2018-12-19 13:19:03 -0800397}
398
Kun Yi1a25e0d2020-05-11 12:28:53 -0700399/*
400 * Sets |meta| with size and state of the blob. Returns |blobState| with
401 * standard definition from phosphor-ipmi-blobs header blob.hpp, plus OEM
402 * flag bits BinaryStore::CommitState.
403
404enum StateFlags
Kun Yi64dc05c2018-12-19 13:19:03 -0800405{
Kun Yi1a25e0d2020-05-11 12:28:53 -0700406 open_read = (1 << 0),
407 open_write = (1 << 1),
408 committing = (1 << 2),
409 committed = (1 << 3),
410 commit_error = (1 << 4),
411};
412
413enum CommitState
414{
415 Dirty = (1 << 8), // In-memory data might not match persisted data
416 Clean = (1 << 9), // In-memory data matches persisted data
417 Uninitialized = (1 << 10), // Cannot find persisted data
418 CommitError = (1 << 11) // Error happened during committing
419};
420
421*/
422bool BinaryStore::stat(blobs::BlobMeta* meta)
423{
424 uint16_t blobState = blobs::StateFlags::open_read;
425 if (writable_)
426 {
427 blobState |= blobs::StateFlags::open_write;
428 }
429
430 if (commitState_ == CommitState::Clean)
431 {
432 blobState |= blobs::StateFlags::committed;
433 }
434 else if (commitState_ == CommitState::CommitError)
435 {
436 blobState |= blobs::StateFlags::commit_error;
437 }
438 blobState |= commitState_;
439
440 if (currentBlob_)
441 {
442 meta->size = currentBlob_->data().size();
443 }
444 else
445 {
446 meta->size = 0;
447 }
448 meta->blobState = blobState;
449
450 return true;
Kun Yi64dc05c2018-12-19 13:19:03 -0800451}
452
453} // namespace binstore