build: Refactor the build structure of the project
Moved all header files to `include` and all cpp files to `src`.
Updated the meson.build accordingly.
Change-Id: I9e26197b9c73b5e284cfc9d0d78a234546c282ad
Signed-off-by: Willy Tu <wltu@google.com>
diff --git a/src/binarystore.cpp b/src/binarystore.cpp
new file mode 100644
index 0000000..0760f33
--- /dev/null
+++ b/src/binarystore.cpp
@@ -0,0 +1,432 @@
+#include "binarystore.hpp"
+
+#include "sys_file.hpp"
+
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <blobs-ipmid/blobs.hpp>
+#include <boost/endian/arithmetic.hpp>
+#include <cstdint>
+#include <ipmid/handler.hpp>
+#include <memory>
+#include <optional>
+#include <phosphor-logging/elog.hpp>
+#include <string>
+#include <vector>
+
+#include "binaryblob.pb.h"
+
+using std::size_t;
+using std::uint16_t;
+using std::uint32_t;
+using std::uint64_t;
+using std::uint8_t;
+
+namespace binstore
+{
+
+using namespace phosphor::logging;
+
+std::unique_ptr<BinaryStoreInterface>
+ BinaryStore::createFromConfig(const std::string& baseBlobId,
+ std::unique_ptr<SysFile> file,
+ std::optional<uint32_t> maxSize)
+{
+ if (baseBlobId.empty() || !file)
+ {
+ log<level::ERR>("Unable to create binarystore from invalid config",
+ entry("BASE_ID=%s", baseBlobId.c_str()));
+ return nullptr;
+ }
+
+ auto store =
+ std::make_unique<BinaryStore>(baseBlobId, std::move(file), maxSize);
+
+ if (!store->loadSerializedData())
+ {
+ return nullptr;
+ }
+
+ return store;
+}
+
+std::unique_ptr<BinaryStoreInterface>
+ BinaryStore::createFromFile(std::unique_ptr<SysFile> file, bool readOnly,
+ std::optional<uint32_t> maxSize)
+{
+ if (!file)
+ {
+ log<level::ERR>("Unable to create binarystore from invalid file");
+ return nullptr;
+ }
+
+ auto store =
+ std::make_unique<BinaryStore>(std::move(file), readOnly, maxSize);
+
+ if (!store->loadSerializedData())
+ {
+ return nullptr;
+ }
+
+ return store;
+}
+
+bool BinaryStore::loadSerializedData()
+{
+ /* Load blob from sysfile if we know it might not match what we have.
+ * Note it will overwrite existing unsaved data per design. */
+ if (commitState_ == CommitState::Clean ||
+ commitState_ == CommitState::Uninitialized)
+ {
+ return true;
+ }
+
+ log<level::NOTICE>("Try loading blob from persistent data",
+ entry("BASE_ID=%s", baseBlobId_.c_str()));
+ try
+ {
+ /* Parse length-prefixed format to protobuf */
+ boost::endian::little_uint64_t size = 0;
+ file_->readToBuf(0, sizeof(size), reinterpret_cast<char*>(&size));
+
+ if (!blob_.ParseFromString(file_->readAsStr(sizeof(uint64_t), size)))
+ {
+ /* Fail to parse the data, which might mean no preexsiting blobs
+ * and is a valid case to handle. Simply init an empty binstore. */
+ commitState_ = CommitState::Uninitialized;
+ }
+
+ // The new max size takes priority
+ if (maxSize)
+ {
+ blob_.set_max_size_bytes(*maxSize);
+ }
+ else
+ {
+ blob_.clear_max_size_bytes();
+ }
+ }
+ catch (const std::system_error& e)
+ {
+ /* Read causes unexpected system-level failure */
+ log<level::ERR>("Reading from sysfile failed",
+ entry("ERROR=%s", e.what()));
+ return false;
+ }
+ catch (const std::exception& e)
+ {
+ /* Non system error originates from junk value in 'size' */
+ commitState_ = CommitState::Uninitialized;
+ }
+
+ if (commitState_ == CommitState::Uninitialized)
+ {
+ log<level::WARNING>("Fail to parse. There might be no persisted blobs",
+ entry("BASE_ID=%s", baseBlobId_.c_str()));
+ return true;
+ }
+
+ if (blob_.blob_base_id() != baseBlobId_ && !readOnly_)
+ {
+ /* Uh oh, stale data loaded. Clean it and commit. */
+ // TODO: it might be safer to add an option in config to error out
+ // instead of to overwrite.
+ log<level::ERR>("Stale blob data, resetting internals...",
+ entry("LOADED=%s", blob_.blob_base_id().c_str()),
+ entry("EXPECTED=%s", baseBlobId_.c_str()));
+ blob_.Clear();
+ blob_.set_blob_base_id(baseBlobId_);
+ return this->commit();
+ }
+
+ return true;
+}
+
+std::string BinaryStore::getBaseBlobId() const
+{
+ if (!baseBlobId_.empty())
+ {
+ return baseBlobId_;
+ }
+
+ return blob_.blob_base_id();
+}
+
+std::vector<std::string> BinaryStore::getBlobIds() const
+{
+ std::vector<std::string> result;
+ result.push_back(getBaseBlobId());
+
+ for (const auto& blob : blob_.blobs())
+ {
+ result.push_back(blob.blob_id());
+ }
+
+ return result;
+}
+
+bool BinaryStore::openOrCreateBlob(const std::string& blobId, uint16_t flags)
+{
+ if (!(flags & blobs::OpenFlags::read))
+ {
+ log<level::ERR>("OpenFlags::read not specified when opening",
+ entry("BLOB_ID=%s", blobId.c_str()));
+ return false;
+ }
+
+ if (currentBlob_ && (currentBlob_->blob_id() != blobId))
+ {
+ log<level::ERR>("Already handling a different blob",
+ entry("EXPECTED=%s", currentBlob_->blob_id().c_str()),
+ entry("RECEIVED=%s", blobId.c_str()));
+ return false;
+ }
+
+ if (readOnly_ && (flags & blobs::OpenFlags::write))
+ {
+ log<level::ERR>("Can't open the blob for writing: read-only store",
+ entry("BLOB_ID=%s", blobId.c_str()));
+ return false;
+ }
+
+ writable_ = flags & blobs::OpenFlags::write;
+
+ /* If there are uncommitted data, discard them. */
+ if (!this->loadSerializedData())
+ {
+ return false;
+ }
+
+ /* Iterate and find if there is an existing blob with this id.
+ * blobsPtr points to a BinaryBlob container with STL-like semantics*/
+ auto blobsPtr = blob_.mutable_blobs();
+ auto blobIt =
+ std::find_if(blobsPtr->begin(), blobsPtr->end(),
+ [&](const auto& b) { return b.blob_id() == blobId; });
+
+ if (blobIt != blobsPtr->end())
+ {
+ currentBlob_ = &(*blobIt);
+ return true;
+ }
+
+ /* Otherwise, create the blob and append it */
+ if (readOnly_)
+ {
+ return false;
+ }
+ else
+ {
+ currentBlob_ = blob_.add_blobs();
+ currentBlob_->set_blob_id(blobId);
+
+ commitState_ = CommitState::Dirty;
+ log<level::NOTICE>("Created new blob",
+ entry("BLOB_ID=%s", blobId.c_str()));
+ }
+
+ return true;
+}
+
+bool BinaryStore::deleteBlob(const std::string&)
+{
+ return false;
+}
+
+std::vector<uint8_t> BinaryStore::read(uint32_t offset, uint32_t requestedSize)
+{
+ std::vector<uint8_t> result;
+
+ if (!currentBlob_)
+ {
+ log<level::ERR>("No open blob to read");
+ return result;
+ }
+
+ auto dataPtr = currentBlob_->mutable_data();
+
+ /* If it is out of bound, return empty vector */
+ if (offset >= dataPtr->size())
+ {
+ log<level::ERR>("Read offset is beyond data size",
+ entry("MAX_SIZE=0x%x", dataPtr->size()),
+ entry("RECEIVED_OFFSET=0x%x", offset));
+ return result;
+ }
+
+ uint32_t requestedEndPos = offset + requestedSize;
+
+ result = std::vector<uint8_t>(
+ dataPtr->begin() + offset,
+ std::min(dataPtr->begin() + requestedEndPos, dataPtr->end()));
+ return result;
+}
+
+std::vector<uint8_t> BinaryStore::readBlob(const std::string& blobId) const
+{
+ const auto blobs = blob_.blobs();
+ const auto blobIt =
+ std::find_if(blobs.begin(), blobs.end(),
+ [&](const auto& b) { return b.blob_id() == blobId; });
+
+ if (blobIt == blobs.end())
+ {
+ throw ipmi::HandlerCompletion(ipmi::ccUnspecifiedError);
+ }
+
+ const auto blobData = blobIt->data();
+
+ return std::vector<uint8_t>(blobData.begin(), blobData.end());
+}
+
+bool BinaryStore::write(uint32_t offset, const std::vector<uint8_t>& data)
+{
+ if (!currentBlob_)
+ {
+ log<level::ERR>("No open blob to write");
+ return false;
+ }
+
+ if (!writable_)
+ {
+ log<level::ERR>("Open blob is not writable");
+ return false;
+ }
+
+ auto dataPtr = currentBlob_->mutable_data();
+
+ if (offset > dataPtr->size())
+ {
+ log<level::ERR>("Write would leave a gap with undefined data. Return.");
+ return false;
+ }
+
+ bool needResize = offset + data.size() > dataPtr->size();
+
+ // current size is the binary blob proto size + uint64 tracking the total
+ // size of the binary blob.
+ // currentSize = blob_size + x (uint64_t), where x = blob_size.
+ size_t currentSize = blob_.SerializeAsString().size() +
+ sizeof(boost::endian::little_uint64_t);
+ size_t sizeDelta = needResize ? offset + data.size() - dataPtr->size() : 0;
+
+ if (maxSize && currentSize + sizeDelta > *maxSize)
+ {
+ log<level::ERR>("Write data would make the total size exceed the max "
+ "size allowed. Return.");
+ return false;
+ }
+
+ commitState_ = CommitState::Dirty;
+ /* Copy (overwrite) the data */
+ if (needResize)
+ {
+ dataPtr->resize(offset + data.size()); // not enough space, extend
+ }
+ std::copy(data.begin(), data.end(), dataPtr->begin() + offset);
+ return true;
+}
+
+bool BinaryStore::commit()
+{
+ if (readOnly_)
+ {
+ log<level::ERR>("ReadOnly blob, not committing");
+ return false;
+ }
+
+ /* Store as little endian to be platform agnostic. Consistent with read. */
+ auto blobData = blob_.SerializeAsString();
+ boost::endian::little_uint64_t sizeLE = blobData.size();
+ std::string commitData(reinterpret_cast<const char*>(sizeLE.data()),
+ sizeof(sizeLE));
+ commitData += blobData;
+
+ // This should never be true if it is blocked by the write command
+ if (maxSize && sizeof(commitData) > *maxSize)
+ {
+ log<level::ERR>("Commit Data excedded maximum allowed size");
+ return false;
+ }
+
+ try
+ {
+ file_->writeStr(commitData, 0);
+ }
+ catch (const std::exception& e)
+ {
+ commitState_ = CommitState::CommitError;
+ log<level::ERR>("Writing to sysfile failed",
+ entry("ERROR=%s", e.what()));
+ return false;
+ };
+
+ commitState_ = CommitState::Clean;
+ return true;
+}
+
+bool BinaryStore::close()
+{
+ currentBlob_ = nullptr;
+ writable_ = false;
+ commitState_ = CommitState::Dirty;
+ return true;
+}
+
+/*
+ * Sets |meta| with size and state of the blob. Returns |blobState| with
+ * standard definition from phosphor-ipmi-blobs header blob.hpp, plus OEM
+ * flag bits BinaryStore::CommitState.
+
+enum StateFlags
+{
+ open_read = (1 << 0),
+ open_write = (1 << 1),
+ committing = (1 << 2),
+ committed = (1 << 3),
+ commit_error = (1 << 4),
+};
+
+enum CommitState
+{
+ Dirty = (1 << 8), // In-memory data might not match persisted data
+ Clean = (1 << 9), // In-memory data matches persisted data
+ Uninitialized = (1 << 10), // Cannot find persisted data
+ CommitError = (1 << 11) // Error happened during committing
+};
+
+*/
+bool BinaryStore::stat(blobs::BlobMeta* meta)
+{
+ uint16_t blobState = blobs::StateFlags::open_read;
+ if (writable_)
+ {
+ blobState |= blobs::StateFlags::open_write;
+ }
+
+ if (commitState_ == CommitState::Clean)
+ {
+ blobState |= blobs::StateFlags::committed;
+ }
+ else if (commitState_ == CommitState::CommitError)
+ {
+ blobState |= blobs::StateFlags::commit_error;
+ }
+ blobState |= commitState_;
+
+ if (currentBlob_)
+ {
+ meta->size = currentBlob_->data().size();
+ }
+ else
+ {
+ meta->size = 0;
+ }
+ meta->blobState = blobState;
+
+ return true;
+}
+
+} // namespace binstore
diff --git a/src/blobtool.cpp b/src/blobtool.cpp
new file mode 100644
index 0000000..ac29abd
--- /dev/null
+++ b/src/blobtool.cpp
@@ -0,0 +1,238 @@
+#include "binarystore.hpp"
+#include "parse_config.hpp"
+#include "sys_file_impl.hpp"
+
+#include <getopt.h>
+
+#include <algorithm>
+#include <fstream>
+#include <iostream>
+#include <nlohmann/json.hpp>
+
+constexpr auto defaultBlobConfigPath = "/usr/share/binaryblob/config.json";
+
+struct BlobToolConfig
+{
+ std::string configPath = defaultBlobConfigPath;
+ std::string programName;
+ std::string binStore;
+ std::string blobName;
+ size_t offsetBytes = 0;
+ enum class Action
+ {
+ HELP,
+ LIST,
+ READ,
+ } action = Action::LIST;
+} toolConfig;
+
+void printUsage(const BlobToolConfig& cfg)
+{
+ std::cout
+ << "Usage: \n"
+ << cfg.programName << " [OPTIONS]\n"
+ << "\t--list\t\tList all supported blobs. This is a default.\n"
+ << "\t--read\t\tRead blob specified in --blob argument"
+ " (which becomes mandatory).\n"
+ << "\t--config\tFILENAME\tPath to the configuration file. The default "
+ "is /usr/share/binaryblob/config.json.\n"
+ << "\t--binary-store\tFILENAME\tPath to the binary storage. If "
+ "specified,"
+ "configuration file is not used.\n"
+ << "\t--blob\tSTRING\tThe name of the blob to read.\n"
+ << "\t--offset\tNUMBER\tThe offset in the binary store file, where"
+ " the binary store actually starts.\n"
+ << "\t--help\t\tPrint this help and exit\n";
+}
+
+bool parseOptions(int argc, char* argv[], BlobToolConfig& cfg)
+{
+ cfg.programName = argv[0];
+
+ struct option longOptions[] = {
+ {"help", no_argument, 0, 'h'},
+ {"list", no_argument, 0, 'l'},
+ {"read", no_argument, 0, 'r'},
+ {"config", required_argument, 0, 'c'},
+ {"binary-store", required_argument, 0, 's'},
+ {"blob", required_argument, 0, 'b'},
+ {"offset", required_argument, 0, 'g'},
+ {0, 0, 0, 0},
+ };
+
+ int optionIndex = 0;
+ std::string configPath = defaultBlobConfigPath;
+ bool res = true;
+ while (1)
+ {
+ int ret = getopt_long_only(argc, argv, "", longOptions, &optionIndex);
+
+ if (ret == -1)
+ break;
+
+ switch (ret)
+ {
+ case 'h':
+ cfg.action = BlobToolConfig::Action::HELP;
+ break;
+ case 'l':
+ cfg.action = BlobToolConfig::Action::LIST;
+ break;
+ case 'r':
+ cfg.action = BlobToolConfig::Action::READ;
+ break;
+ case 'c':
+ cfg.configPath = optarg;
+ break;
+ case 's':
+ cfg.binStore = optarg;
+ break;
+ case 'b':
+ cfg.blobName = optarg;
+ break;
+ case 'g':
+ cfg.offsetBytes = std::stoi(optarg);
+ break;
+ default:
+ res = false;
+ break;
+ }
+ }
+
+ return res;
+}
+
+int main(int argc, char* argv[])
+{
+ parseOptions(argc, argv, toolConfig);
+ if (toolConfig.action == BlobToolConfig::Action::HELP)
+ {
+ printUsage(toolConfig);
+ return 0;
+ }
+
+ std::vector<std::unique_ptr<binstore::BinaryStoreInterface>> stores;
+ if (!toolConfig.binStore.empty())
+ {
+ auto file = std::make_unique<binstore::SysFileImpl>(
+ toolConfig.binStore, toolConfig.offsetBytes);
+ if (!file)
+ {
+ std::cerr << "Can't open binary store " << toolConfig.binStore
+ << std::endl;
+ printUsage(toolConfig);
+ return 1;
+ }
+
+ auto store =
+ binstore::BinaryStore::createFromFile(std::move(file), true);
+ stores.push_back(std::move(store));
+ }
+ else
+ {
+ std::ifstream input(toolConfig.configPath);
+ json j;
+
+ if (!input.good())
+ {
+ std::cerr << "Config file not found: " << toolConfig.configPath
+ << std::endl;
+ return 1;
+ }
+
+ try
+ {
+ input >> j;
+ }
+ catch (const std::exception& e)
+ {
+ std::cerr << "Failed to parse config into json: " << std::endl
+ << e.what() << std::endl;
+ return 1;
+ }
+
+ for (const auto& element : j)
+ {
+ conf::BinaryBlobConfig config;
+ try
+ {
+ conf::parseFromConfigFile(element, config);
+ }
+ catch (const std::exception& e)
+ {
+ std::cerr << "Encountered error when parsing config file:"
+ << std::endl
+ << e.what() << std::endl;
+ return 1;
+ }
+
+ auto file = std::make_unique<binstore::SysFileImpl>(
+ config.sysFilePath, config.offsetBytes);
+
+ auto store = binstore::BinaryStore::createFromConfig(
+ config.blobBaseId, std::move(file));
+ stores.push_back(std::move(store));
+ }
+ }
+
+ if (toolConfig.action == BlobToolConfig::Action::LIST)
+ {
+ std::cout << "Supported Blobs: " << std::endl;
+ for (const auto& store : stores)
+ {
+ const auto blobIds = store->getBlobIds();
+ std::copy(
+ blobIds.begin(), blobIds.end(),
+ std::ostream_iterator<decltype(blobIds[0])>(std::cout, "\n"));
+ }
+ }
+ else if (toolConfig.action == BlobToolConfig::Action::READ)
+ {
+ if (toolConfig.blobName.empty())
+ {
+ std::cerr << "Must specify the name of the blob to read."
+ << std::endl;
+ printUsage(toolConfig);
+ return 1;
+ }
+
+ bool blobFound = false;
+
+ for (const auto& store : stores)
+ {
+ const auto blobIds = store->getBlobIds();
+ if (std::any_of(blobIds.begin(), blobIds.end(),
+ [](const std::string& bn) {
+ return bn == toolConfig.blobName;
+ }))
+ {
+ const auto blobData = store->readBlob(toolConfig.blobName);
+ if (blobData.empty())
+ {
+ std::cerr << "No data read from " << store->getBaseBlobId()
+ << std::endl;
+ continue;
+ }
+
+ blobFound = true;
+
+ std::copy(
+ blobData.begin(), blobData.end(),
+ std::ostream_iterator<decltype(blobData[0])>(std::cout));
+
+ // It's assumed that the names of the blobs are unique within
+ // the system.
+ break;
+ }
+ }
+
+ if (!blobFound)
+ {
+ std::cerr << "Blob " << toolConfig.blobName << " not found."
+ << std::endl;
+ return 1;
+ }
+ }
+
+ return 0;
+}
diff --git a/src/handler.cpp b/src/handler.cpp
new file mode 100644
index 0000000..a391b5a
--- /dev/null
+++ b/src/handler.cpp
@@ -0,0 +1,201 @@
+#include "handler.hpp"
+
+#include <algorithm>
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <vector>
+
+using std::size_t;
+using std::uint16_t;
+using std::uint32_t;
+using std::uint64_t;
+using std::uint8_t;
+
+namespace blobs
+{
+
+namespace internal
+{
+
+/**
+ * @brief: Get baseId from a blob id string
+ * @param blobId: Input blob id which is expected to only contain alphanumerical
+ * characters and '/'.
+ * @returns: the baseId containing the blobId, stripping all contents from the
+ * last '/'. If no '/' is present, an empty string is returned.
+ */
+static std::string getBaseFromId(const std::string& blobId)
+{
+ return blobId.substr(0, blobId.find_last_of('/') + 1);
+}
+
+} // namespace internal
+
+void BinaryStoreBlobHandler::addNewBinaryStore(
+ std::unique_ptr<binstore::BinaryStoreInterface> store)
+{
+ // TODO: this is a very rough measure to test the mock interface for now.
+ stores_[store->getBaseBlobId()] = std::move(store);
+}
+
+bool BinaryStoreBlobHandler::canHandleBlob(const std::string& path)
+{
+ auto base = internal::getBaseFromId(path);
+ if (base.empty() || base == path)
+ {
+ /* Operations on baseId itself or an empty base is not allowed */
+ return false;
+ }
+
+ return std::any_of(stores_.begin(), stores_.end(),
+ [&](const auto& baseStorePair) {
+ return base == baseStorePair.second->getBaseBlobId();
+ });
+}
+
+std::vector<std::string> BinaryStoreBlobHandler::getBlobIds()
+{
+ std::vector<std::string> result;
+
+ for (const auto& baseStorePair : stores_)
+ {
+ const auto& ids = baseStorePair.second->getBlobIds();
+ result.insert(result.end(), ids.begin(), ids.end());
+ }
+
+ return result;
+}
+
+bool BinaryStoreBlobHandler::deleteBlob(const std::string& path)
+{
+ auto it = stores_.find(internal::getBaseFromId(path));
+ if (it == stores_.end())
+ {
+ return false;
+ }
+
+ return it->second->deleteBlob(path);
+}
+
+bool BinaryStoreBlobHandler::stat(const std::string& path,
+ struct BlobMeta* meta)
+{
+ auto it = stores_.find(internal::getBaseFromId(path));
+ if (it == stores_.end())
+ {
+ return false;
+ }
+
+ return it->second->stat(meta);
+}
+
+bool BinaryStoreBlobHandler::open(uint16_t session, uint16_t flags,
+ const std::string& path)
+{
+ if (!canHandleBlob(path))
+ {
+ return false;
+ }
+
+ auto found = sessions_.find(session);
+ if (found != sessions_.end())
+ {
+ /* This session is already active */
+ return false;
+ }
+
+ const auto& base = internal::getBaseFromId(path);
+
+ if (stores_.find(base) == stores_.end())
+ {
+ return false;
+ }
+
+ if (!stores_[base]->openOrCreateBlob(path, flags))
+ {
+ return false;
+ }
+
+ sessions_[session] = stores_[base].get();
+ return true;
+}
+
+std::vector<uint8_t> BinaryStoreBlobHandler::read(uint16_t session,
+ uint32_t offset,
+ uint32_t requestedSize)
+{
+ auto it = sessions_.find(session);
+ if (it == sessions_.end())
+ {
+ return std::vector<uint8_t>();
+ }
+
+ return it->second->read(offset, requestedSize);
+}
+
+bool BinaryStoreBlobHandler::write(uint16_t session, uint32_t offset,
+ const std::vector<uint8_t>& data)
+{
+ auto it = sessions_.find(session);
+ if (it == sessions_.end())
+ {
+ return false;
+ }
+
+ return it->second->write(offset, data);
+}
+
+bool BinaryStoreBlobHandler::writeMeta(uint16_t, uint32_t,
+ const std::vector<uint8_t>&)
+{
+ /* Binary store handler doesn't support write meta */
+ return false;
+}
+
+bool BinaryStoreBlobHandler::commit(uint16_t session,
+ const std::vector<uint8_t>&)
+{
+ auto it = sessions_.find(session);
+ if (it == sessions_.end())
+ {
+ return false;
+ }
+
+ return it->second->commit();
+}
+
+bool BinaryStoreBlobHandler::close(uint16_t session)
+{
+ auto it = sessions_.find(session);
+ if (it == sessions_.end())
+ {
+ return false;
+ }
+
+ if (!it->second->close())
+ {
+ return false;
+ }
+
+ sessions_.erase(session);
+ return true;
+}
+
+bool BinaryStoreBlobHandler::stat(uint16_t session, struct BlobMeta* meta)
+{
+ auto it = sessions_.find(session);
+ if (it == sessions_.end())
+ {
+ return false;
+ }
+
+ return it->second->stat(meta);
+}
+
+bool BinaryStoreBlobHandler::expire(uint16_t session)
+{
+ return close(session);
+}
+
+} // namespace blobs
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
index 0000000..5cf6c10
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,78 @@
+#include "handler.hpp"
+#include "parse_config.hpp"
+#include "sys_file_impl.hpp"
+
+#include <blobs-ipmid/blobs.hpp>
+#include <exception>
+#include <fstream>
+#include <memory>
+#include <phosphor-logging/elog.hpp>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * This is required by the blob manager.
+ * TODO: move the declaration to blobs.hpp since all handlers need it
+ */
+std::unique_ptr<blobs::GenericBlobInterface> createHandler();
+
+#ifdef __cplusplus
+}
+#endif
+
+/* Configuration file path */
+constexpr auto blobConfigPath = "/usr/share/binaryblob/config.json";
+
+std::unique_ptr<blobs::GenericBlobInterface> createHandler()
+{
+ using namespace phosphor::logging;
+ using nlohmann::json;
+
+ std::ifstream input(blobConfigPath);
+ json j;
+
+ try
+ {
+ input >> j;
+ }
+ catch (const std::exception& e)
+ {
+ log<level::ERR>("Failed to parse config into json",
+ entry("ERR=%s", e.what()));
+ return nullptr;
+ }
+
+ // Construct binary blobs from config and add to handler
+ auto handler = std::make_unique<blobs::BinaryStoreBlobHandler>();
+
+ for (const auto& element : j)
+ {
+ conf::BinaryBlobConfig config;
+ try
+ {
+ conf::parseFromConfigFile(element, config);
+ }
+ catch (const std::exception& e)
+ {
+ log<level::ERR>("Encountered error when parsing config file",
+ entry("ERR=%s", e.what()));
+ return nullptr;
+ }
+
+ log<level::INFO>("Loading from config with",
+ entry("BASE_ID=%s", config.blobBaseId.c_str()),
+ entry("FILE=%s", config.sysFilePath.c_str()),
+ entry("MAX_SIZE=%llx", static_cast<unsigned long long>(
+ config.maxSizeBytes)));
+
+ auto file = std::make_unique<binstore::SysFileImpl>(config.sysFilePath,
+ config.offsetBytes);
+
+ handler->addNewBinaryStore(binstore::BinaryStore::createFromConfig(
+ config.blobBaseId, std::move(file), config.maxSizeBytes));
+ }
+
+ return handler;
+}
diff --git a/src/meson.build b/src/meson.build
new file mode 100644
index 0000000..a8917ac
--- /dev/null
+++ b/src/meson.build
@@ -0,0 +1,40 @@
+binarystoreblob_pre = declare_dependency(
+ include_directories: [
+ include_directories('.'),
+ blobstore_includes,
+ ],
+ dependencies: [
+ protobuf_dep,
+ ipmi_blob_dep,
+ phosphor_logging_dep,
+ binaryblobproto_dep,
+ ]
+)
+
+binarystoreblob_lib = library(
+ 'binarystore',
+ 'binarystore.cpp',
+ 'sys.cpp',
+ 'sys_file_impl.cpp',
+ 'handler.cpp',
+ src_pb,
+ implicit_include_directories: false,
+ dependencies: binarystoreblob_pre,
+ install: true,
+ install_dir: get_option('libdir') / 'blob-ipmid'
+)
+
+binarystoreblob_dep = declare_dependency(
+ link_with: binarystoreblob_lib,
+ dependencies: binarystoreblob_pre
+)
+
+if not get_option('blobtool').disabled()
+ executable(
+ 'blobtool',
+ 'blobtool.cpp',
+ implicit_include_directories: false,
+ dependencies: binarystoreblob_dep,
+ install: true
+ )
+endif
diff --git a/src/sys.cpp b/src/sys.cpp
new file mode 100644
index 0000000..0a16dcb
--- /dev/null
+++ b/src/sys.cpp
@@ -0,0 +1,41 @@
+#include "sys.hpp"
+
+#include <fcntl.h>
+#include <unistd.h>
+
+namespace binstore
+{
+
+namespace internal
+{
+
+int SysImpl::open(const char* pathname, int flags) const
+{
+ return ::open(pathname, flags);
+}
+
+int SysImpl::close(int fd) const
+{
+ return ::close(fd);
+}
+
+off_t SysImpl::lseek(int fd, off_t offset, int whence) const
+{
+ return ::lseek(fd, offset, whence);
+}
+
+ssize_t SysImpl::read(int fd, void* buf, size_t count) const
+{
+ return ::read(fd, buf, count);
+}
+
+ssize_t SysImpl::write(int fd, const void* buf, size_t count) const
+{
+ return ::write(fd, buf, count);
+}
+
+SysImpl sys_impl;
+
+} // namespace internal
+
+} // namespace binstore
diff --git a/src/sys_file_impl.cpp b/src/sys_file_impl.cpp
new file mode 100644
index 0000000..243d82d
--- /dev/null
+++ b/src/sys_file_impl.cpp
@@ -0,0 +1,131 @@
+#include "sys_file_impl.hpp"
+
+#include <system_error>
+
+using namespace std::string_literals;
+
+static constexpr size_t rwBlockSize = 8192;
+
+namespace binstore
+{
+
+namespace
+{
+
+std::system_error errnoException(const std::string& message)
+{
+ return std::system_error(errno, std::generic_category(), message);
+}
+
+} // namespace
+
+SysFileImpl::SysFileImpl(const std::string& path, size_t offset,
+ const internal::Sys* sys) :
+ sys(sys)
+{
+ fd_ = sys->open(path.c_str(), O_RDWR);
+ offset_ = offset;
+
+ if (fd_ < 0)
+ {
+ throw errnoException("Error opening file "s + path);
+ }
+}
+
+SysFileImpl::~SysFileImpl()
+{
+ sys->close(fd_);
+}
+
+void SysFileImpl::lseek(size_t pos) const
+{
+ if (sys->lseek(fd_, offset_ + pos, SEEK_SET) < 0)
+ {
+ throw errnoException("Cannot lseek to pos "s + std::to_string(pos));
+ }
+}
+
+size_t SysFileImpl::readToBuf(size_t pos, size_t count, char* buf) const
+{
+
+ lseek(pos);
+
+ size_t bytesRead = 0;
+
+ do
+ {
+ auto ret = sys->read(fd_, &buf[bytesRead], count - bytesRead);
+ if (ret < 0)
+ {
+ if (errno == EINTR)
+ {
+ continue;
+ }
+
+ throw errnoException("Error reading from file"s);
+ }
+ else if (ret > 0)
+ {
+ bytesRead += ret;
+ }
+ else // ret == 0
+ {
+ break;
+ }
+ } while (bytesRead < count);
+
+ return bytesRead;
+}
+
+std::string SysFileImpl::readAsStr(size_t pos, size_t count) const
+{
+ std::string result;
+
+ /* If count is invalid, return an empty string. */
+ if (count == 0 || count > result.max_size())
+ {
+ return result;
+ }
+
+ result.resize(count);
+ size_t bytesRead = readToBuf(pos, count, result.data());
+ result.resize(bytesRead);
+ return result;
+}
+
+std::string SysFileImpl::readRemainingAsStr(size_t pos) const
+{
+ std::string result;
+ size_t bytesRead, size = 0;
+
+ /* Since we don't know how much to read, read 'rwBlockSize' at a time
+ * until there is nothing to read anymore. */
+ do
+ {
+ result.resize(size + rwBlockSize);
+ bytesRead = readToBuf(pos + size, rwBlockSize, result.data() + size);
+ size += bytesRead;
+ } while (bytesRead == rwBlockSize);
+
+ result.resize(size);
+ return result;
+}
+
+void SysFileImpl::writeStr(const std::string& data, size_t pos)
+{
+ lseek(pos);
+ ssize_t ret;
+ ret = sys->write(fd_, data.data(), data.size());
+ if (ret < 0)
+ {
+ throw errnoException("Error writing to file"s);
+ }
+ if (static_cast<size_t>(ret) != data.size())
+ {
+ throw std::runtime_error(
+ "Tried to send data size "s + std::to_string(data.size()) +
+ " but could only send "s + std::to_string(ret));
+ }
+}
+
+} // namespace binstore