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