blob: 7c1bc42b101c65554c35a1ed0b400bebe63c7fd0 [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;
Willy Tufdebaa32022-02-08 16:34:20 -0800160 result.reserve(blob_.blobs().size() + 1);
161 result.emplace_back(getBaseBlobId());
162 std::for_each(
163 blob_.blobs().begin(), blob_.blobs().end(),
164 [&result](const auto& blob) { result.emplace_back(blob.blob_id()); });
Kun Yi64dc05c2018-12-19 13:19:03 -0800165
166 return result;
167}
168
169bool BinaryStore::openOrCreateBlob(const std::string& blobId, uint16_t flags)
170{
Kun Yi6baa7132019-01-08 21:21:02 -0800171 if (!(flags & blobs::OpenFlags::read))
172 {
Kun Yi8bcf79d2019-01-16 15:17:57 -0800173 log<level::ERR>("OpenFlags::read not specified when opening",
174 entry("BLOB_ID=%s", blobId.c_str()));
Kun Yi6baa7132019-01-08 21:21:02 -0800175 return false;
176 }
177
178 if (currentBlob_ && (currentBlob_->blob_id() != blobId))
179 {
Kun Yi8bcf79d2019-01-16 15:17:57 -0800180 log<level::ERR>("Already handling a different blob",
181 entry("EXPECTED=%s", currentBlob_->blob_id().c_str()),
182 entry("RECEIVED=%s", blobId.c_str()));
Kun Yi6baa7132019-01-08 21:21:02 -0800183 return false;
184 }
185
Maksym Sloykoeb274112021-10-27 23:48:32 +0000186 if (readOnly_ && (flags & blobs::OpenFlags::write))
187 {
188 log<level::ERR>("Can't open the blob for writing: read-only store",
189 entry("BLOB_ID=%s", blobId.c_str()));
190 return false;
191 }
192
Kun Yi6baa7132019-01-08 21:21:02 -0800193 writable_ = flags & blobs::OpenFlags::write;
194
Kun Yi97be3af2019-03-05 22:43:41 -0800195 /* If there are uncommitted data, discard them. */
196 if (!this->loadSerializedData())
Kun Yid297c9f2019-01-09 13:52:30 -0800197 {
Kun Yi97be3af2019-03-05 22:43:41 -0800198 return false;
Kun Yid297c9f2019-01-09 13:52:30 -0800199 }
200
Kun Yi6baa7132019-01-08 21:21:02 -0800201 /* Iterate and find if there is an existing blob with this id.
202 * blobsPtr points to a BinaryBlob container with STL-like semantics*/
203 auto blobsPtr = blob_.mutable_blobs();
204 auto blobIt =
205 std::find_if(blobsPtr->begin(), blobsPtr->end(),
206 [&](const auto& b) { return b.blob_id() == blobId; });
207
208 if (blobIt != blobsPtr->end())
209 {
210 currentBlob_ = &(*blobIt);
211 return true;
212 }
213
214 /* Otherwise, create the blob and append it */
Maksym Sloykoeb274112021-10-27 23:48:32 +0000215 if (readOnly_)
216 {
217 return false;
218 }
219 else
220 {
221 currentBlob_ = blob_.add_blobs();
222 currentBlob_->set_blob_id(blobId);
Kun Yi6baa7132019-01-08 21:21:02 -0800223
Maksym Sloykoeb274112021-10-27 23:48:32 +0000224 commitState_ = CommitState::Dirty;
225 log<level::NOTICE>("Created new blob",
226 entry("BLOB_ID=%s", blobId.c_str()));
227 }
228
Kun Yi6baa7132019-01-08 21:21:02 -0800229 return true;
Kun Yi64dc05c2018-12-19 13:19:03 -0800230}
231
Willy Tu351a5d02021-12-07 19:48:40 -0800232bool BinaryStore::deleteBlob(const std::string&)
Kun Yi64dc05c2018-12-19 13:19:03 -0800233{
234 return false;
235}
236
237std::vector<uint8_t> BinaryStore::read(uint32_t offset, uint32_t requestedSize)
238{
Kun Yi6967b772019-02-22 13:48:12 -0800239 std::vector<uint8_t> result;
240
241 if (!currentBlob_)
242 {
Kun Yi8bcf79d2019-01-16 15:17:57 -0800243 log<level::ERR>("No open blob to read");
Kun Yi6967b772019-02-22 13:48:12 -0800244 return result;
245 }
246
247 auto dataPtr = currentBlob_->mutable_data();
248
249 /* If it is out of bound, return empty vector */
250 if (offset >= dataPtr->size())
251 {
Kun Yi8bcf79d2019-01-16 15:17:57 -0800252 log<level::ERR>("Read offset is beyond data size",
253 entry("MAX_SIZE=0x%x", dataPtr->size()),
254 entry("RECEIVED_OFFSET=0x%x", offset));
Kun Yi6967b772019-02-22 13:48:12 -0800255 return result;
256 }
257
258 uint32_t requestedEndPos = offset + requestedSize;
259
260 result = std::vector<uint8_t>(
261 dataPtr->begin() + offset,
262 std::min(dataPtr->begin() + requestedEndPos, dataPtr->end()));
Kun Yi64dc05c2018-12-19 13:19:03 -0800263 return result;
264}
265
Maksym Sloykoeb274112021-10-27 23:48:32 +0000266std::vector<uint8_t> BinaryStore::readBlob(const std::string& blobId) const
267{
268 const auto blobs = blob_.blobs();
269 const auto blobIt =
270 std::find_if(blobs.begin(), blobs.end(),
271 [&](const auto& b) { return b.blob_id() == blobId; });
272
273 if (blobIt == blobs.end())
274 {
275 throw ipmi::HandlerCompletion(ipmi::ccUnspecifiedError);
276 }
277
278 const auto blobData = blobIt->data();
279
280 return std::vector<uint8_t>(blobData.begin(), blobData.end());
281}
282
Kun Yi64dc05c2018-12-19 13:19:03 -0800283bool BinaryStore::write(uint32_t offset, const std::vector<uint8_t>& data)
284{
Kun Yi6967b772019-02-22 13:48:12 -0800285 if (!currentBlob_)
286 {
Kun Yi8bcf79d2019-01-16 15:17:57 -0800287 log<level::ERR>("No open blob to write");
Kun Yi6967b772019-02-22 13:48:12 -0800288 return false;
289 }
290
291 if (!writable_)
292 {
Kun Yi8bcf79d2019-01-16 15:17:57 -0800293 log<level::ERR>("Open blob is not writable");
Kun Yi6967b772019-02-22 13:48:12 -0800294 return false;
295 }
296
297 auto dataPtr = currentBlob_->mutable_data();
298
299 if (offset > dataPtr->size())
300 {
Kun Yi8bcf79d2019-01-16 15:17:57 -0800301 log<level::ERR>("Write would leave a gap with undefined data. Return.");
Kun Yi6967b772019-02-22 13:48:12 -0800302 return false;
303 }
304
Willy Tu67391d52021-12-07 19:53:49 -0800305 bool needResize = offset + data.size() > dataPtr->size();
306
307 // current size is the binary blob proto size + uint64 tracking the total
308 // size of the binary blob.
309 // currentSize = blob_size + x (uint64_t), where x = blob_size.
310 size_t currentSize = blob_.SerializeAsString().size() +
311 sizeof(boost::endian::little_uint64_t);
312 size_t sizeDelta = needResize ? offset + data.size() - dataPtr->size() : 0;
313
314 if (maxSize && currentSize + sizeDelta > *maxSize)
315 {
316 log<level::ERR>("Write data would make the total size exceed the max "
317 "size allowed. Return.");
318 return false;
319 }
320
Kun Yi1a25e0d2020-05-11 12:28:53 -0700321 commitState_ = CommitState::Dirty;
Kun Yi6967b772019-02-22 13:48:12 -0800322 /* Copy (overwrite) the data */
Willy Tu67391d52021-12-07 19:53:49 -0800323 if (needResize)
Kun Yi6967b772019-02-22 13:48:12 -0800324 {
325 dataPtr->resize(offset + data.size()); // not enough space, extend
326 }
327 std::copy(data.begin(), data.end(), dataPtr->begin() + offset);
328 return true;
Kun Yi64dc05c2018-12-19 13:19:03 -0800329}
330
331bool BinaryStore::commit()
332{
Maksym Sloykoeb274112021-10-27 23:48:32 +0000333 if (readOnly_)
334 {
335 log<level::ERR>("ReadOnly blob, not committing");
336 return false;
337 }
338
Kun Yi70059172019-02-22 16:31:42 -0800339 /* Store as little endian to be platform agnostic. Consistent with read. */
Kun Yid297c9f2019-01-09 13:52:30 -0800340 auto blobData = blob_.SerializeAsString();
Kun Yi70059172019-02-22 16:31:42 -0800341 boost::endian::little_uint64_t sizeLE = blobData.size();
William A. Kennington III805ade32020-05-06 20:39:05 -0700342 std::string commitData(reinterpret_cast<const char*>(sizeLE.data()),
343 sizeof(sizeLE));
Kun Yid297c9f2019-01-09 13:52:30 -0800344 commitData += blobData;
345
Willy Tu67391d52021-12-07 19:53:49 -0800346 // This should never be true if it is blocked by the write command
347 if (maxSize && sizeof(commitData) > *maxSize)
348 {
Willy Tu91633852022-02-18 08:55:43 -0800349 log<level::ERR>("Commit Data exceeded maximum allowed size");
Willy Tu67391d52021-12-07 19:53:49 -0800350 return false;
351 }
352
Kun Yid297c9f2019-01-09 13:52:30 -0800353 try
354 {
355 file_->writeStr(commitData, 0);
356 }
357 catch (const std::exception& e)
358 {
Kun Yid297c9f2019-01-09 13:52:30 -0800359 commitState_ = CommitState::CommitError;
Kun Yi8bcf79d2019-01-16 15:17:57 -0800360 log<level::ERR>("Writing to sysfile failed",
361 entry("ERROR=%s", e.what()));
Kun Yid297c9f2019-01-09 13:52:30 -0800362 return false;
363 };
364
365 commitState_ = CommitState::Clean;
366 return true;
Kun Yi64dc05c2018-12-19 13:19:03 -0800367}
368
369bool BinaryStore::close()
370{
Kun Yi6baa7132019-01-08 21:21:02 -0800371 currentBlob_ = nullptr;
372 writable_ = false;
Kun Yid297c9f2019-01-09 13:52:30 -0800373 commitState_ = CommitState::Dirty;
Kun Yi6baa7132019-01-08 21:21:02 -0800374 return true;
Kun Yi64dc05c2018-12-19 13:19:03 -0800375}
376
Kun Yi1a25e0d2020-05-11 12:28:53 -0700377/*
378 * Sets |meta| with size and state of the blob. Returns |blobState| with
379 * standard definition from phosphor-ipmi-blobs header blob.hpp, plus OEM
380 * flag bits BinaryStore::CommitState.
381
382enum StateFlags
Kun Yi64dc05c2018-12-19 13:19:03 -0800383{
Kun Yi1a25e0d2020-05-11 12:28:53 -0700384 open_read = (1 << 0),
385 open_write = (1 << 1),
386 committing = (1 << 2),
387 committed = (1 << 3),
388 commit_error = (1 << 4),
389};
390
391enum CommitState
392{
393 Dirty = (1 << 8), // In-memory data might not match persisted data
394 Clean = (1 << 9), // In-memory data matches persisted data
395 Uninitialized = (1 << 10), // Cannot find persisted data
396 CommitError = (1 << 11) // Error happened during committing
397};
398
399*/
400bool BinaryStore::stat(blobs::BlobMeta* meta)
401{
402 uint16_t blobState = blobs::StateFlags::open_read;
403 if (writable_)
404 {
405 blobState |= blobs::StateFlags::open_write;
406 }
407
408 if (commitState_ == CommitState::Clean)
409 {
410 blobState |= blobs::StateFlags::committed;
411 }
412 else if (commitState_ == CommitState::CommitError)
413 {
414 blobState |= blobs::StateFlags::commit_error;
415 }
416 blobState |= commitState_;
417
418 if (currentBlob_)
419 {
420 meta->size = currentBlob_->data().size();
421 }
422 else
423 {
424 meta->size = 0;
425 }
426 meta->blobState = blobState;
427
428 return true;
Kun Yi64dc05c2018-12-19 13:19:03 -0800429}
430
431} // namespace binstore