/* * Copyright (C) 2018 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 "Common.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sysprop.pb.h" using android::base::Result; namespace { std::string GenerateDefaultPropName(const sysprop::Properties& props, const sysprop::Property& prop); bool IsCorrectIdentifier(const std::string& name); Result ValidateProp(const sysprop::Properties& props, const sysprop::Property& prop); Result ValidateProps(const sysprop::Properties& props); std::string GenerateDefaultPropName(const sysprop::Properties& props, const sysprop::Property& prop) { std::string ret; if (prop.access() != sysprop::ReadWrite) ret = "ro."; switch (props.owner()) { case sysprop::Vendor: ret += "vendor."; break; case sysprop::Odm: ret += "odm."; break; default: break; } ret += prop.api_name(); return ret; } bool IsCorrectIdentifier(const std::string& name) { if (name.empty()) return false; if (std::isalpha(name[0]) == 0 && name[0] != '_') return false; return std::all_of(name.begin() + 1, name.end(), [](char ch) { return std::isalnum(ch) != 0 || ch == '_'; }); } bool IsCorrectName(const std::string& name, const std::unordered_set& allowed_chars) { if (name.empty()) return false; if (!std::isalpha(*name.begin())) return false; return std::all_of(name.begin(), name.end(), [allowed_chars](char ch) { return std::isalnum(ch) != 0 || allowed_chars.count(ch) != 0; }); } bool IsCorrectPropertyName(const std::string& name) { std::unordered_set allowed{'_', '-', '.'}; if (android::base::StartsWith(name, "ctl.")) { allowed.emplace('$'); } return IsCorrectName(name, allowed); } bool IsCorrectApiName(const std::string& name) { static std::unordered_set allowed{'_', '-'}; return IsCorrectName(name, allowed); } Result ValidateProp(const sysprop::Properties& props, const sysprop::Property& prop) { if (!IsCorrectApiName(prop.api_name())) { return Errorf("Invalid API name \"{}\"", prop.api_name()); } if (prop.type() == sysprop::Enum || prop.type() == sysprop::EnumList) { std::vector names = android::base::Split(prop.enum_values(), "|"); if (names.empty()) { return Errorf("Enum values are empty for API \"{}\"", prop.api_name()); } for (const std::string& name : names) { if (!IsCorrectIdentifier(name)) { return Errorf("Invalid enum value \"{}\" for API \"{}\"", name, prop.api_name()); } } std::unordered_set name_set; for (const std::string& name : names) { if (!name_set.insert(ToUpper(name)).second) { return Errorf("Duplicated enum value \"{}\" for API \"{}\"", name, prop.api_name()); } } } std::string prop_name = prop.prop_name(); if (prop_name.empty()) prop_name = GenerateDefaultPropName(props, prop); if (!IsCorrectPropertyName(prop_name)) { return Errorf("Invalid prop name \"{}\"", prop.prop_name()); } std::string legacy_name = prop.legacy_prop_name(); if (!legacy_name.empty()) { if (!IsCorrectPropertyName(legacy_name)) { return Errorf("Invalid legacy prop name \"{}\"", legacy_name); } if (prop.access() != sysprop::Readonly) { return Errorf("Prop \"{}\" which has legacy_prop_name must be Readonly", prop.prop_name()); } } static const std::regex vendor_regex( "(init\\.svc\\.|ro\\.|persist\\.)?vendor\\..+|ro\\.hardware\\..+"); static const std::regex odm_regex( "(init\\.svc\\.|ro\\.|persist\\.)?odm\\..+|ro\\.hardware\\..+"); switch (props.owner()) { case sysprop::Platform: if (std::regex_match(prop_name, vendor_regex) || std::regex_match(prop_name, odm_regex)) { return Errorf( "Prop \"{}\" owned by platform cannot have vendor. or odm. " "namespace", prop_name); } break; case sysprop::Vendor: if (!std::regex_match(prop_name, vendor_regex)) { return Errorf( "Prop \"{}\" owned by vendor should have vendor. namespace", prop_name); } break; case sysprop::Odm: if (!std::regex_match(prop_name, odm_regex)) { return Errorf("Prop \"{}\" owned by odm should have odm. namespace", prop_name); } break; default: break; } if (prop.access() == sysprop::ReadWrite && android::base::StartsWith(prop_name, "ro.")) { return Errorf("Prop \"{}\" is ReadWrite and also have prefix \"ro.\"", prop_name); } if (prop.integer_as_bool() && !(prop.type() == sysprop::Boolean || prop.type() == sysprop::BooleanList)) { return Errorf("Prop \"{}\" has integer_as_bool: true, but not a boolean", prop_name); } return {}; } Result ValidateProps(const sysprop::Properties& props) { std::vector names = android::base::Split(props.module(), "."); if (names.size() <= 1) { return Errorf("Invalid module name \"{}\"", props.module()); } for (const auto& name : names) { if (!IsCorrectIdentifier(name)) { return Errorf("Invalid name \"{}\" in module", name); } } if (props.prop_size() == 0) { return Errorf("There is no defined property"); } for (int i = 0; i < props.prop_size(); ++i) { const auto& prop = props.prop(i); if (auto res = ValidateProp(props, prop); !res.ok()) return res; } std::unordered_set prop_names; std::unordered_map prop_types; for (int i = 0; i < props.prop_size(); ++i) { const auto& prop = props.prop(i); auto res = prop_names.insert(ApiNameToIdentifier(prop.api_name())); if (!res.second) { return Errorf("Duplicated API name \"{}\"", prop.api_name()); } std::vector prop_names{prop.prop_name()}; std::string legacy_name = prop.legacy_prop_name(); if (!legacy_name.empty()) prop_names.push_back(legacy_name); sysprop::Type type = prop.type(); for (auto& name : prop_names) { // get type if already exists. inserts mine if not. sysprop::Type prev_type = prop_types.emplace(name, type).first->second; if (prev_type != type) { return Errorf("Type error on prop \"{}\": it's {} but was {}", name, sysprop::Type_Name(type), sysprop::Type_Name(prev_type)); } } } return {}; } void SetDefaultValues(sysprop::Properties* props) { for (int i = 0; i < props->prop_size(); ++i) { // set each optional field to its default value sysprop::Property& prop = *props->mutable_prop(i); if (prop.prop_name().empty()) prop.set_prop_name(GenerateDefaultPropName(*props, prop)); if (prop.scope() == sysprop::Scope::System) { LOG(WARNING) << "Sysprop API " << prop.api_name() << ": System scope is deprecated." << " Please use Public scope instead."; prop.set_scope(sysprop::Scope::Public); } } } } // namespace bool IsListProp(const sysprop::Property& prop) { switch (prop.type()) { case sysprop::BooleanList: case sysprop::IntegerList: case sysprop::LongList: case sysprop::DoubleList: case sysprop::StringList: case sysprop::EnumList: case sysprop::UIntList: case sysprop::ULongList: return true; default: return false; } } std::string GetModuleName(const sysprop::Properties& props) { const std::string& module = props.module(); return module.substr(module.rfind('.') + 1); } std::vector ParseEnumValues(const std::string& enum_values) { return android::base::Split(enum_values, "|"); } Result ParseProps(const std::string& input_file_path) { sysprop::Properties ret; std::string file_contents; if (!android::base::ReadFileToString(input_file_path, &file_contents, true)) { return ErrnoErrorf("Error reading file {}", input_file_path); } if (!google::protobuf::TextFormat::ParseFromString(file_contents, &ret)) { return Errorf("Error parsing file {}", input_file_path); } SetDefaultValues(&ret); // validate after filling default values such as prop_name if (auto res = ValidateProps(ret); !res.ok()) { return res.error(); } return ret; } Result ParseApiFile( const std::string& input_file_path) { sysprop::SyspropLibraryApis ret; std::string file_contents; if (!android::base::ReadFileToString(input_file_path, &file_contents, true)) { return ErrnoErrorf("Error reading file {}", input_file_path); } if (!google::protobuf::TextFormat::ParseFromString(file_contents, &ret)) { return Errorf("Error parsing file {}", input_file_path); } std::unordered_set modules; for (int i = 0; i < ret.props_size(); ++i) { sysprop::Properties* props = ret.mutable_props(i); if (!modules.insert(props->module()).second) { return Errorf("Error parsing file {}: duplicated module {}", input_file_path, props->module()); } SetDefaultValues(props); // validate after filling default values such as prop_name if (auto res = ValidateProps(*props); !res.ok()) { return res.error(); } } return ret; } std::string ToUpper(std::string str) { for (char& ch : str) { ch = toupper(ch); } return str; } std::string ApiNameToIdentifier(const std::string& name) { static const std::regex kRegexAllowed{"-|\\."}; return (isdigit(name[0]) ? "_" : "") + std::regex_replace(name, kRegexAllowed, "_"); }