/* * 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> #define KEY "(CONFIG[\\w_]+)" #define COMMENT "(?:#.*)" static const std::regex sKeyValuePattern("^\\s*" KEY "\\s*=\\s*([^#]+)" COMMENT "?$"); static const std::regex sNotSetPattern("^\\s*#\\s*" KEY " is not set\\s*$"); static const std::regex sCommentPattern("^\\s*" COMMENT "$"); namespace android { namespace vintf { KernelConfigParser::KernelConfigParser(bool processComments, bool relaxedFormat) : mProcessComments(processComments), mRelaxedFormat(relaxedFormat) {} status_t KernelConfigParser::finish() { return process("\n", 1 /* sizeof "\n" */); } std::stringbuf* KernelConfigParser::error() const { return mError.rdbuf(); } std::map<std::string, std::string>& KernelConfigParser::configs() { return mConfigs; } const std::map<std::string, std::string>& KernelConfigParser::configs() const { return mConfigs; } // trim spaces between value and #, value and end of line std::string trimTrailingSpaces(const std::string& s) { auto r = s.rbegin(); for (; r != s.rend() && std::isspace(*r); ++r) ; return std::string{s.begin(), r.base()}; } status_t KernelConfigParser::processRemaining() { if (mRemaining.empty()) { return OK; } std::smatch match; if (mRelaxedFormat) { // Allow free format like " CONFIG_FOO = bar #trailing comments" if (std::regex_match(mRemaining, match, sKeyValuePattern)) { if (mConfigs.emplace(match[1], trimTrailingSpaces(match[2])).second) { return OK; } mError << "Duplicated key in configs: " << match[1] << "\n"; return UNKNOWN_ERROR; } } else { // No spaces. Strictly like "CONFIG_FOO=bar" size_t equalPos = mRemaining.find('='); if (equalPos != std::string::npos) { std::string key = mRemaining.substr(0, equalPos); std::string value = mRemaining.substr(equalPos + 1); if (mConfigs.emplace(std::move(key), std::move(value)).second) { return OK; } mError << "Duplicated key in configs: " << mRemaining.substr(0, equalPos) << "\n"; return UNKNOWN_ERROR; } } if (mProcessComments && std::regex_match(mRemaining, match, sNotSetPattern)) { if (mConfigs.emplace(match[1], "n").second) { return OK; } mError << "Key " << match[1] << " is set but commented as not set" << "\n"; return UNKNOWN_ERROR; } if (mRelaxedFormat) { // Allow free format like " #comments here" if (std::regex_match(mRemaining, match, sCommentPattern)) { return OK; } } else { // No leading spaces before the comment if (mRemaining.at(0) == '#') { return OK; } } mError << "Unrecognized line in configs: " << mRemaining << "\n"; return UNKNOWN_ERROR; } 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