Add blobtool

Add command line tool "blobtool" for examining the binary store:
listing and reading blobs. The modification are not supported.

The tool is enabled in the build by default and can be disabled
by running configure script with "--disable-blobtool" option.

Signed-off-by: Maksym Sloyko <maxims@google.com>
Change-Id: Ibed6df639e541fd5100dbbc032846cbc15340654
diff --git a/Makefile.am b/Makefile.am
index b1746be..a7d0801 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -33,6 +33,15 @@
 
 BUILT_SOURCES = binaryblob.pb.h binaryblob.pb.cc
 
+bin_PROGRAMS =
+
+if BUILD_BLOBTOOL
+bin_PROGRAMS += blobtool
+blobtool_SOURCES = blobtool.cpp
+blobtool_LDADD = libbinarystore.la
+blobtool_LDFLAGS = -lprotobuf
+endif
+
 SUBDIRS = . test
 
 ACLOCAL_AMFLAGS = -I m4
diff --git a/blobtool.cpp b/blobtool.cpp
new file mode 100644
index 0000000..ac29abd
--- /dev/null
+++ b/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/configure.ac b/configure.ac
index 5ea68ed..cc0fc6d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -36,6 +36,17 @@
     AS_HELP_STRING([--enable-oe-sdk], [Link testcases absolutely against OE SDK so they can be ran within it.])
 )
 
+# Add --disable-blobtool flag to configure script
+AC_ARG_ENABLE([blobtool],
+    AS_HELP_STRING([--disable-blobtool], [Disable blobtool, command line tool to inspect the blobs]),
+    [case "${enableval}" in
+        no) build_blobtool=false;;
+        *) build_blobtool=true;;
+    esac],
+    [build_blobtool=true]
+)
+AM_CONDITIONAL([BUILD_BLOBTOOL], [ test x${build_blobtool} = xtrue ])
+
 # Check for OECORE_TARGET_SYSROOT in the environment.
 AC_ARG_VAR(OECORE_TARGET_SYSROOT,
     [Path to the OE SDK SYSROOT])