1 /*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "Common.h"
18
19 #include <sys/stat.h>
20 #include <sys/types.h>
21 #include <unistd.h>
22
23 #include <algorithm>
24 #include <cctype>
25 #include <cerrno>
26 #include <cmath>
27 #include <cstdlib>
28 #include <cstring>
29 #include <initializer_list>
30 #include <memory>
31 #include <regex>
32 #include <string>
33 #include <unordered_set>
34 #include <vector>
35
36 #include <android-base/file.h>
37 #include <android-base/logging.h>
38 #include <android-base/strings.h>
39 #include <google/protobuf/text_format.h>
40
41 #include "sysprop.pb.h"
42
43 using android::base::Result;
44
45 namespace {
46
47 std::string GenerateDefaultPropName(const sysprop::Properties& props,
48 const sysprop::Property& prop);
49 bool IsCorrectIdentifier(const std::string& name);
50 Result<void> ValidateProp(const sysprop::Properties& props,
51 const sysprop::Property& prop);
52 Result<void> ValidateProps(const sysprop::Properties& props);
53
GenerateDefaultPropName(const sysprop::Properties & props,const sysprop::Property & prop)54 std::string GenerateDefaultPropName(const sysprop::Properties& props,
55 const sysprop::Property& prop) {
56 std::string ret;
57
58 if (prop.access() != sysprop::ReadWrite) ret = "ro.";
59
60 switch (props.owner()) {
61 case sysprop::Vendor:
62 ret += "vendor.";
63 break;
64 case sysprop::Odm:
65 ret += "odm.";
66 break;
67 default:
68 break;
69 }
70
71 ret += prop.api_name();
72
73 return ret;
74 }
75
IsCorrectIdentifier(const std::string & name)76 bool IsCorrectIdentifier(const std::string& name) {
77 if (name.empty()) return false;
78 if (std::isalpha(name[0]) == 0 && name[0] != '_') return false;
79
80 return std::all_of(name.begin() + 1, name.end(), [](char ch) {
81 return std::isalnum(ch) != 0 || ch == '_';
82 });
83 }
84
IsCorrectName(const std::string & name,const std::unordered_set<char> & allowed_chars)85 bool IsCorrectName(const std::string& name,
86 const std::unordered_set<char>& allowed_chars) {
87 if (name.empty()) return false;
88 if (!std::isalpha(*name.begin())) return false;
89
90 return std::all_of(name.begin(), name.end(), [allowed_chars](char ch) {
91 return std::isalnum(ch) != 0 || allowed_chars.count(ch) != 0;
92 });
93 }
94
IsCorrectPropertyName(const std::string & name)95 bool IsCorrectPropertyName(const std::string& name) {
96 std::unordered_set<char> allowed{'_', '-', '.'};
97 if (android::base::StartsWith(name, "ctl.")) {
98 allowed.emplace('$');
99 }
100 return IsCorrectName(name, allowed);
101 }
102
IsCorrectApiName(const std::string & name)103 bool IsCorrectApiName(const std::string& name) {
104 static std::unordered_set<char> allowed{'_', '-'};
105 return IsCorrectName(name, allowed);
106 }
107
ValidateProp(const sysprop::Properties & props,const sysprop::Property & prop)108 Result<void> ValidateProp(const sysprop::Properties& props,
109 const sysprop::Property& prop) {
110 if (!IsCorrectApiName(prop.api_name())) {
111 return Errorf("Invalid API name \"{}\"", prop.api_name());
112 }
113
114 if (prop.type() == sysprop::Enum || prop.type() == sysprop::EnumList) {
115 std::vector<std::string> names =
116 android::base::Split(prop.enum_values(), "|");
117 if (names.empty()) {
118 return Errorf("Enum values are empty for API \"{}\"", prop.api_name());
119 }
120
121 for (const std::string& name : names) {
122 if (!IsCorrectIdentifier(name)) {
123 return Errorf("Invalid enum value \"{}\" for API \"{}\"", name,
124 prop.api_name());
125 }
126 }
127
128 std::unordered_set<std::string> name_set;
129 for (const std::string& name : names) {
130 if (!name_set.insert(ToUpper(name)).second) {
131 return Errorf("Duplicated enum value \"{}\" for API \"{}\"", name,
132 prop.api_name());
133 }
134 }
135 }
136
137 std::string prop_name = prop.prop_name();
138 if (prop_name.empty()) prop_name = GenerateDefaultPropName(props, prop);
139
140 if (!IsCorrectPropertyName(prop_name)) {
141 return Errorf("Invalid prop name \"{}\"", prop.prop_name());
142 }
143
144 std::string legacy_name = prop.legacy_prop_name();
145 if (!legacy_name.empty()) {
146 if (!IsCorrectPropertyName(legacy_name)) {
147 return Errorf("Invalid legacy prop name \"{}\"", legacy_name);
148 }
149 if (prop.access() != sysprop::Readonly) {
150 return Errorf("Prop \"{}\" which has legacy_prop_name must be Readonly",
151 prop.prop_name());
152 }
153 }
154
155 static const std::regex vendor_regex(
156 "(init\\.svc\\.|ro\\.|persist\\.)?vendor\\..+|ro\\.hardware\\..+");
157 static const std::regex odm_regex(
158 "(init\\.svc\\.|ro\\.|persist\\.)?odm\\..+|ro\\.hardware\\..+");
159
160 switch (props.owner()) {
161 case sysprop::Platform:
162 if (std::regex_match(prop_name, vendor_regex) ||
163 std::regex_match(prop_name, odm_regex)) {
164 return Errorf(
165 "Prop \"{}\" owned by platform cannot have vendor. or odm. "
166 "namespace",
167 prop_name);
168 }
169 break;
170 case sysprop::Vendor:
171 if (!std::regex_match(prop_name, vendor_regex)) {
172 return Errorf(
173 "Prop \"{}\" owned by vendor should have vendor. namespace",
174 prop_name);
175 }
176 break;
177 case sysprop::Odm:
178 if (!std::regex_match(prop_name, odm_regex)) {
179 return Errorf("Prop \"{}\" owned by odm should have odm. namespace",
180 prop_name);
181 }
182 break;
183 default:
184 break;
185 }
186
187 if (prop.access() == sysprop::ReadWrite &&
188 android::base::StartsWith(prop_name, "ro.")) {
189 return Errorf("Prop \"{}\" is ReadWrite and also have prefix \"ro.\"",
190 prop_name);
191 }
192
193 if (prop.integer_as_bool() && !(prop.type() == sysprop::Boolean ||
194 prop.type() == sysprop::BooleanList)) {
195 return Errorf("Prop \"{}\" has integer_as_bool: true, but not a boolean",
196 prop_name);
197 }
198
199 return {};
200 }
201
ValidateProps(const sysprop::Properties & props)202 Result<void> ValidateProps(const sysprop::Properties& props) {
203 std::vector<std::string> names = android::base::Split(props.module(), ".");
204 if (names.size() <= 1) {
205 return Errorf("Invalid module name \"{}\"", props.module());
206 }
207
208 for (const auto& name : names) {
209 if (!IsCorrectIdentifier(name)) {
210 return Errorf("Invalid name \"{}\" in module", name);
211 }
212 }
213
214 if (props.prop_size() == 0) {
215 return Errorf("There is no defined property");
216 }
217
218 for (int i = 0; i < props.prop_size(); ++i) {
219 const auto& prop = props.prop(i);
220 if (auto res = ValidateProp(props, prop); !res.ok()) return res;
221 }
222
223 std::unordered_set<std::string> prop_names;
224 std::unordered_map<std::string, sysprop::Type> prop_types;
225
226 for (int i = 0; i < props.prop_size(); ++i) {
227 const auto& prop = props.prop(i);
228 auto res = prop_names.insert(ApiNameToIdentifier(prop.api_name()));
229
230 if (!res.second) {
231 return Errorf("Duplicated API name \"{}\"", prop.api_name());
232 }
233
234 std::vector<std::string> prop_names{prop.prop_name()};
235 std::string legacy_name = prop.legacy_prop_name();
236 if (!legacy_name.empty()) prop_names.push_back(legacy_name);
237
238 sysprop::Type type = prop.type();
239
240 for (auto& name : prop_names) {
241 // get type if already exists. inserts mine if not.
242 sysprop::Type prev_type = prop_types.emplace(name, type).first->second;
243 if (prev_type != type) {
244 return Errorf("Type error on prop \"{}\": it's {} but was {}", name,
245 sysprop::Type_Name(type), sysprop::Type_Name(prev_type));
246 }
247 }
248 }
249
250 return {};
251 }
252
SetDefaultValues(sysprop::Properties * props)253 void SetDefaultValues(sysprop::Properties* props) {
254 for (int i = 0; i < props->prop_size(); ++i) {
255 // set each optional field to its default value
256 sysprop::Property& prop = *props->mutable_prop(i);
257 if (prop.prop_name().empty())
258 prop.set_prop_name(GenerateDefaultPropName(*props, prop));
259 if (prop.scope() == sysprop::Scope::System) {
260 LOG(WARNING) << "Sysprop API " << prop.api_name()
261 << ": System scope is deprecated."
262 << " Please use Public scope instead.";
263 prop.set_scope(sysprop::Scope::Public);
264 }
265 }
266 }
267
268 } // namespace
269
IsListProp(const sysprop::Property & prop)270 bool IsListProp(const sysprop::Property& prop) {
271 switch (prop.type()) {
272 case sysprop::BooleanList:
273 case sysprop::IntegerList:
274 case sysprop::LongList:
275 case sysprop::DoubleList:
276 case sysprop::StringList:
277 case sysprop::EnumList:
278 case sysprop::UIntList:
279 case sysprop::ULongList:
280 return true;
281 default:
282 return false;
283 }
284 }
285
GetModuleName(const sysprop::Properties & props)286 std::string GetModuleName(const sysprop::Properties& props) {
287 const std::string& module = props.module();
288 return module.substr(module.rfind('.') + 1);
289 }
290
ParseEnumValues(const std::string & enum_values)291 std::vector<std::string> ParseEnumValues(const std::string& enum_values) {
292 return android::base::Split(enum_values, "|");
293 }
294
ParseProps(const std::string & input_file_path)295 Result<sysprop::Properties> ParseProps(const std::string& input_file_path) {
296 sysprop::Properties ret;
297 std::string file_contents;
298
299 if (!android::base::ReadFileToString(input_file_path, &file_contents, true)) {
300 return ErrnoErrorf("Error reading file {}", input_file_path);
301 }
302
303 if (!google::protobuf::TextFormat::ParseFromString(file_contents, &ret)) {
304 return Errorf("Error parsing file {}", input_file_path);
305 }
306
307 SetDefaultValues(&ret);
308
309 // validate after filling default values such as prop_name
310 if (auto res = ValidateProps(ret); !res.ok()) {
311 return res.error();
312 }
313
314 return ret;
315 }
316
ParseApiFile(const std::string & input_file_path)317 Result<sysprop::SyspropLibraryApis> ParseApiFile(
318 const std::string& input_file_path) {
319 sysprop::SyspropLibraryApis ret;
320 std::string file_contents;
321
322 if (!android::base::ReadFileToString(input_file_path, &file_contents, true)) {
323 return ErrnoErrorf("Error reading file {}", input_file_path);
324 }
325
326 if (!google::protobuf::TextFormat::ParseFromString(file_contents, &ret)) {
327 return Errorf("Error parsing file {}", input_file_path);
328 }
329
330 std::unordered_set<std::string> modules;
331
332 for (int i = 0; i < ret.props_size(); ++i) {
333 sysprop::Properties* props = ret.mutable_props(i);
334
335 if (!modules.insert(props->module()).second) {
336 return Errorf("Error parsing file {}: duplicated module {}",
337 input_file_path, props->module());
338 }
339
340 SetDefaultValues(props);
341
342 // validate after filling default values such as prop_name
343 if (auto res = ValidateProps(*props); !res.ok()) {
344 return res.error();
345 }
346 }
347
348 return ret;
349 }
350
ToUpper(std::string str)351 std::string ToUpper(std::string str) {
352 for (char& ch : str) {
353 ch = toupper(ch);
354 }
355 return str;
356 }
357
ApiNameToIdentifier(const std::string & name)358 std::string ApiNameToIdentifier(const std::string& name) {
359 static const std::regex kRegexAllowed{"-|\\."};
360 return (isdigit(name[0]) ? "_" : "") +
361 std::regex_replace(name, kRegexAllowed, "_");
362 }
363
SnakeCaseToCamelCase(const std::string & s)364 std::string SnakeCaseToCamelCase(const std::string& s) {
365 std::string result;
366 for (int i = 0; i < s.size(); ++i) {
367 if (s[i] == '_') continue;
368 char current = tolower(s[i]); // Handle screaming snake case.
369 if (i == 0 || s[i - 1] == '_') {
370 current = toupper(current);
371 }
372 result += current;
373 }
374 return result;
375 }
376
CamelCaseToSnakeCase(const std::string & s)377 std::string CamelCaseToSnakeCase(const std::string& s) {
378 // Desired behaviour: "CurrentAPIVersion" -> "current_api_version".
379 std::string result;
380 for (int i = 0; i < s.size(); ++i) {
381 char current = s[i];
382 char prev = (i > 0) ? s[i - 1] : 0;
383 char next = (i < s.size() - 1) ? s[i + 1] : 0;
384 if (prev && isupper(prev) && (!next || isupper(next) || !isalpha(next))) {
385 current = tolower(current);
386 }
387 if (i > 0 && isupper(current) && result[result.size() - 1] != '_') {
388 result += '_';
389 }
390 result += tolower(current);
391 }
392 return result;
393 }