Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0dfa752e authored by Yi Jin's avatar Yi Jin
Browse files

Implement System Properties Section

Bug: 68774852
Test: unit tested and on device tests
Change-Id: I0d4aadf8d4203fe56e35bbfb77e5c532116fd27e
parent a379f499
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -62,6 +62,7 @@ cc_library {
                "core/proto/android/os/kernelwake.proto",
                "core/proto/android/os/pagetypeinfo.proto",
                "core/proto/android/os/procrank.proto",
                "core/proto/android/os/system_properties.proto",
                "core/proto/android/service/graphicsstats.proto",
                "tools/streaming_proto/stream.proto",
            ],
@@ -86,6 +87,7 @@ gensrcs {
        "core/proto/android/os/kernelwake.proto",
        "core/proto/android/os/pagetypeinfo.proto",
        "core/proto/android/os/procrank.proto",
        "core/proto/android/os/system_properties.proto",
    ],

    // Append protoc-gen-cppstream tool's PATH otherwise aprotoc can't find the plugin tool
+81 −68
Original line number Diff line number Diff line
@@ -30,22 +30,26 @@ bool isValidChar(char c) {
        || (v == (uint8_t)'_');
}

static std::string trim(const std::string& s, const std::string& chars) {
    const auto head = s.find_first_not_of(chars);
std::string trim(const std::string& s, const std::string& charset) {
    const auto head = s.find_first_not_of(charset);
    if (head == std::string::npos) return "";

    const auto tail = s.find_last_not_of(chars);
    const auto tail = s.find_last_not_of(charset);
    return s.substr(head, tail - head + 1);
}

static std::string trimDefault(const std::string& s) {
static inline std::string toLowerStr(const std::string& s) {
    std::string res(s);
    std::transform(res.begin(), res.end(), res.begin(), ::tolower);
    return res;
}

static inline std::string trimDefault(const std::string& s) {
    return trim(s, DEFAULT_WHITESPACE);
}

static std::string trimHeader(const std::string& s) {
    std::string res = trimDefault(s);
    std::transform(res.begin(), res.end(), res.begin(), ::tolower);
    return res;
static inline std::string trimHeader(const std::string& s) {
    return toLowerStr(trimDefault(s));
}

// This is similiar to Split in android-base/file.h, but it won't add empty string
@@ -188,97 +192,106 @@ bool Reader::ok(std::string* error) {
}

// ==============================================================================
static int
lookupName(const char** names, const int size, const char* name)
Table::Table(const char* names[], const uint64_t ids[], const int count)
        :mEnums(),
         mEnumValuesByName()
{
    for (int i=0; i<size; i++) {
        if (strcmp(name, names[i]) == 0) {
            return i;
    map<std::string, uint64_t> fields;
    for (int i = 0; i < count; i++) {
        fields[names[i]] = ids[i];
    }
    }
    return -1;
}

EnumTypeMap::EnumTypeMap(const char* enumNames[], const uint32_t enumValues[], const int enumCount)
        :mEnumNames(enumNames),
         mEnumValues(enumValues),
         mEnumCount(enumCount)
{
    mFields = fields;
}

EnumTypeMap::~EnumTypeMap()
Table::~Table()
{
}

int
EnumTypeMap::parseValue(const std::string& value)
void
Table::addEnumTypeMap(const char* field, const char* enumNames[], const int enumValues[], const int enumSize)
{
    int index = lookupName(mEnumNames, mEnumCount, value.c_str());
    if (index < 0) return mEnumValues[0]; // Assume value 0 is default
    return mEnumValues[index];
}
    if (mFields.find(field) == mFields.end()) return;

Table::Table(const char* names[], const uint64_t ids[], const int count)
        :mFieldNames(names),
         mFieldIds(ids),
         mFieldCount(count),
         mEnums()
{
    map<std::string, int> enu;
    for (int i = 0; i < enumSize; i++) {
        enu[enumNames[i]] = enumValues[i];
    }

Table::~Table()
{
    mEnums[field] = enu;
}

void
Table::addEnumTypeMap(const char* field, const char* enumNames[], const uint32_t enumValues[], const int enumSize)
Table::addEnumNameToValue(const char* enumName, const int enumValue)
{
    int index = lookupName(mFieldNames, mFieldCount, field);
    if (index < 0) return;

    EnumTypeMap enu(enumNames, enumValues, enumSize);
    mEnums[index] = enu;
    mEnumValuesByName[enumName] = enumValue;
}

bool
Table::insertField(ProtoOutputStream* proto, const std::string& name, const std::string& value)
{
    int index = lookupName(mFieldNames, mFieldCount, name.c_str());
    if (index < 0) return false;
    if (mFields.find(name) == mFields.end()) return false;

    uint64_t found = mFieldIds[index];
    switch (found & FIELD_TYPE_MASK) {
        case FIELD_TYPE_DOUBLE:
        case FIELD_TYPE_FLOAT:
    uint64_t found = mFields[name];
    record_t repeats; // used for repeated fields
    switch ((found & FIELD_COUNT_MASK) | (found & FIELD_TYPE_MASK)) {
        case FIELD_COUNT_SINGLE | FIELD_TYPE_DOUBLE:
        case FIELD_COUNT_SINGLE | FIELD_TYPE_FLOAT:
            proto->write(found, toDouble(value));
            break;
        case FIELD_TYPE_STRING:
        case FIELD_TYPE_BYTES:
        case FIELD_COUNT_SINGLE | FIELD_TYPE_STRING:
        case FIELD_COUNT_SINGLE | FIELD_TYPE_BYTES:
            proto->write(found, value);
            break;
        case FIELD_TYPE_INT64:
        case FIELD_TYPE_SINT64:
        case FIELD_TYPE_UINT64:
        case FIELD_TYPE_FIXED64:
        case FIELD_TYPE_SFIXED64:
        case FIELD_COUNT_SINGLE | FIELD_TYPE_INT64:
        case FIELD_COUNT_SINGLE | FIELD_TYPE_SINT64:
        case FIELD_COUNT_SINGLE | FIELD_TYPE_UINT64:
        case FIELD_COUNT_SINGLE | FIELD_TYPE_FIXED64:
        case FIELD_COUNT_SINGLE | FIELD_TYPE_SFIXED64:
            proto->write(found, toLongLong(value));
            break;
        case FIELD_TYPE_BOOL:
        case FIELD_COUNT_SINGLE | FIELD_TYPE_BOOL:
            if (strcmp(toLowerStr(value).c_str(), "true") == 0 || strcmp(value.c_str(), "1") == 0) {
                proto->write(found, true);
                break;
            }
            if (strcmp(toLowerStr(value).c_str(), "false") == 0 || strcmp(value.c_str(), "0") == 0) {
                proto->write(found, false);
                break;
            }
            return false;
        case FIELD_TYPE_ENUM:
            if (mEnums.find(index) == mEnums.end()) {
                // forget to add enum type mapping
        case FIELD_COUNT_SINGLE | FIELD_TYPE_ENUM:
            // if the field has its own enum mapping, use this, otherwise use general name to value mapping.
            if (mEnums.find(name) != mEnums.end()) {
                if (mEnums[name].find(value) != mEnums[name].end()) {
                    proto->write(found, mEnums[name][value]);
                } else {
                    proto->write(found, 0); // TODO: should get the default enum value (Unknown)
                }
            } else if (mEnumValuesByName.find(value) != mEnumValuesByName.end()) {
                proto->write(found, mEnumValuesByName[value]);
            } else {
                return false;
            }
            proto->write(found, mEnums[index].parseValue(value));
            break;
        case FIELD_TYPE_INT32:
        case FIELD_TYPE_SINT32:
        case FIELD_TYPE_UINT32:
        case FIELD_TYPE_FIXED32:
        case FIELD_TYPE_SFIXED32:
        case FIELD_COUNT_SINGLE | FIELD_TYPE_INT32:
        case FIELD_COUNT_SINGLE | FIELD_TYPE_SINT32:
        case FIELD_COUNT_SINGLE | FIELD_TYPE_UINT32:
        case FIELD_COUNT_SINGLE | FIELD_TYPE_FIXED32:
        case FIELD_COUNT_SINGLE | FIELD_TYPE_SFIXED32:
            proto->write(found, toInt(value));
            break;
        // REPEATED TYPE below:
        case FIELD_COUNT_REPEATED | FIELD_TYPE_INT32:
            repeats = parseRecord(value, COMMA_DELIMITER);
            for (size_t i=0; i<repeats.size(); i++) {
                proto->write(found, toInt(repeats[i]));
            }
            break;
        case FIELD_COUNT_REPEATED | FIELD_TYPE_STRING:
            repeats = parseRecord(value, COMMA_DELIMITER);
            for (size_t i=0; i<repeats.size(); i++) {
                proto->write(found, repeats[i]);
            }
            break;
        default:
            return false;
    }
+10 −20
Original line number Diff line number Diff line
@@ -37,6 +37,9 @@ const std::string COMMA_DELIMITER = ",";
// returns true if c is a-zA-Z0-9 or underscore _
bool isValidChar(char c);

// trim the string with the given charset
std::string trim(const std::string& s, const std::string& charset);

/**
 * When a text has a table format like this
 * line 1: HeadA HeadB HeadC
@@ -98,21 +101,6 @@ private:
    std::string mStatus;
};

class EnumTypeMap
{
public:
    EnumTypeMap() {};
    EnumTypeMap(const char* enumNames[], const uint32_t enumValues[], const int enumCount);
    ~EnumTypeMap();

    int parseValue(const std::string& value);

private:
    const char** mEnumNames;
    const uint32_t* mEnumValues;
    int mEnumCount;
};

/**
 * The class contains a mapping between table headers to its field ids.
 * And allow users to insert the field values to proto based on its header name.
@@ -124,14 +112,16 @@ public:
    ~Table();

    // Add enum names to values for parsing purpose.
    void addEnumTypeMap(const char* field, const char* enumNames[], const uint32_t enumValues[], const int enumSize);
    void addEnumTypeMap(const char* field, const char* enumNames[], const int enumValues[], const int enumSize);

    // manually add enum names to values mapping, useful when an Enum type is used by a lot of fields, and there are no name conflicts
    void addEnumNameToValue(const char* enumName, const int enumValue);

    bool insertField(ProtoOutputStream* proto, const std::string& name, const std::string& value);
private:
    const char** mFieldNames;
    const uint64_t* mFieldIds;
    const int mFieldCount;
    map<int, EnumTypeMap> mEnums;
    map<std::string, uint64_t> mFields;
    map<std::string, map<std::string, int>> mEnums;
    map<std::string, int> mEnumValuesByName;
};

#endif  // INCIDENT_HELPER_UTIL_H
+3 −0
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@
#include "parsers/KernelWakesParser.h"
#include "parsers/PageTypeInfoParser.h"
#include "parsers/ProcrankParser.h"
#include "parsers/SystemPropertiesParser.h"

#include <android-base/file.h>
#include <getopt.h>
@@ -49,6 +50,8 @@ static TextParserBase* selectParser(int section) {
            return new ReverseParser();
/* ========================================================================= */
        // IDs larger than 1 are section ids reserved in incident.proto
        case 1000:
            return new SystemPropertiesParser();
        case 2000:
            return new ProcrankParser();
        case 2001:
+89 −0
Original line number Diff line number Diff line
/*
 * 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.
 */
#define LOG_TAG "incident_helper"

#include <android/util/ProtoOutputStream.h>

#include "frameworks/base/core/proto/android/os/system_properties.proto.h"
#include "ih_util.h"
#include "SystemPropertiesParser.h"

using namespace android::os;

const string LINE_DELIMITER = "]: [";

// system properties' names sometimes are not valid proto field names, make the names valid.
static string convertToFieldName(const string& name) {
    int len = (int)name.length();
    char cstr[len + 1];
    strcpy(cstr, name.c_str());
    for (int i = 0; i < len; i++) {
        if (!isValidChar(cstr[i])) {
            cstr[i] = '_';
        }
    }
    return string(cstr);
}

status_t
SystemPropertiesParser::Parse(const int in, const int out) const
{
    Reader reader(in);
    string line;
    string name;  // the name of the property
    string value; // the string value of the property

    ProtoOutputStream proto;
    Table table(SystemPropertiesProto::_FIELD_NAMES, SystemPropertiesProto::_FIELD_IDS, SystemPropertiesProto::_FIELD_COUNT);
    table.addEnumNameToValue("running", SystemPropertiesProto::STATUS_RUNNING);
    table.addEnumNameToValue("stopped", SystemPropertiesProto::STATUS_STOPPED);

    // parse line by line
    while (reader.readLine(&line)) {
        if (line.empty()) continue;

        line = line.substr(1, line.size() - 2); // trim []
        size_t index = line.find(LINE_DELIMITER); // split by "]: ["
        if (index == string::npos) {
            fprintf(stderr, "Bad Line %s\n", line.c_str());
            continue;
        }
        name = line.substr(0, index);
        value = trim(line.substr(index + 4), DEFAULT_WHITESPACE);
        if (value.empty()) continue;

        // if the property name couldn't be found in proto definition or the value has mistype,
        // add to extra properties with its name and value
        if (!table.insertField(&proto, convertToFieldName(name), value)) {
            long long token = proto.start(SystemPropertiesProto::EXTRA_PROPERTIES);
            proto.write(SystemPropertiesProto::Property::NAME, name);
            proto.write(SystemPropertiesProto::Property::VALUE, value);
            proto.end(token);
        }
    }

    if (!reader.ok(&line)) {
        fprintf(stderr, "Bad read from fd %d: %s\n", in, line.c_str());
        return -1;
    }

    if (!proto.flush(out)) {
        fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
        return -1;
    }
    fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.string(), proto.size());
    return NO_ERROR;
}
Loading