Add KernelConfigParser

KernelConfigParser is a helper class to parse text like follows:

    # CONFIG_FOO is not set
    CONFIG_BAR=y
    CONFIG_BAZ=1

It is used in parsing android-base.cfg and /proc/config.gz.

Test: libvintf_test
Test: device boots

Bug: 38324908
Change-Id: Ic2044a9e8580538fb5bdac0df278b2a65c5b6341
diff --git a/KernelConfigParser.cpp b/KernelConfigParser.cpp
new file mode 100644
index 0000000..e657a33
--- /dev/null
+++ b/KernelConfigParser.cpp
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "KernelConfigParser.h"
+
+#include <regex>
+
+namespace android {
+namespace vintf {
+
+KernelConfigParser::KernelConfigParser(bool processComments) : mProcessComments(processComments) {}
+
+status_t KernelConfigParser::finish() {
+    return process("\n", 1 /* sizeof "\n" */);
+}
+
+const std::stringstream& KernelConfigParser::error() const {
+    return mError;
+}
+
+std::map<std::string, std::string>& KernelConfigParser::configs() {
+    return mConfigs;
+}
+
+const std::map<std::string, std::string>& KernelConfigParser::configs() const {
+    return mConfigs;
+}
+
+status_t KernelConfigParser::processRemaining() {
+    static std::regex sCommentPattern("^# (CONFIG[\\w_]+) is not set$");
+
+    if (mRemaining.empty()) {
+        return OK;
+    }
+
+    if (mRemaining[0] == '#') {
+        if (!mProcessComments) {
+            return OK;
+        }
+        std::smatch sm;
+        if (!std::regex_match(mRemaining, sm, sCommentPattern)) {
+            return OK;  // ignore this comment;
+        }
+        if (!mConfigs.emplace(sm[1], "n").second) {
+            mError << "Key " << sm[1] << " is set but commented as not set"
+                   << "\n";
+            return UNKNOWN_ERROR;
+        }
+
+        return OK;
+    }
+
+    size_t equalPos = mRemaining.find('=');
+    if (equalPos == std::string::npos) {
+        mError << "Unrecognized line in configs: " << mRemaining << "\n";
+        return UNKNOWN_ERROR;
+    }
+    std::string key = mRemaining.substr(0, equalPos);
+    std::string value = mRemaining.substr(equalPos + 1);
+    if (!mConfigs.emplace(std::move(key), std::move(value)).second) {
+        mError << "Duplicated key in configs: " << mRemaining.substr(0, equalPos) << "\n";
+        return UNKNOWN_ERROR;
+    }
+
+    return OK;
+}
+
+status_t KernelConfigParser::process(const char* buf, size_t len) {
+    const char* begin = buf;
+    const char* end = buf;
+    const char* stop = buf + len;
+    status_t err = OK;
+    while (end < stop) {
+        if (*end == '\n') {
+            mRemaining.insert(mRemaining.size(), begin, end - begin);
+            status_t newErr = processRemaining();
+            if (newErr != OK && err == OK) {
+                err = newErr;
+                // but continue to get more
+            }
+            mRemaining.clear();
+            begin = end + 1;
+        }
+        end++;
+    }
+    mRemaining.insert(mRemaining.size(), begin, end - begin);
+    return err;
+}
+
+}  // namespace vintf
+}  // namespace android