1#!/usr/bin/env python3 2# 3# Copyright (C) 2009 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17import argparse 18import sys 19 20from uffd_gc_utils import should_enable_uffd_gc 21 22# Usage: post_process_props.py file.prop [disallowed_key, ...] 23# Disallowed keys are removed from the property file, if present 24 25# See PROP_VALUE_MAX in system_properties.h. 26# The constant in system_properties.h includes the terminating NUL, 27# so we decrease the value by 1 here. 28PROP_VALUE_MAX = 91 29 30# Put the modifications that you need to make into the */build.prop into this 31# function. 32def mangle_build_prop(prop_list, kernel_version_file_for_uffd_gc): 33 # If ro.debuggable is 1, then enable adb on USB by default 34 # (this is for userdebug builds) 35 if prop_list.get_value("ro.debuggable") == "1": 36 val = prop_list.get_value("persist.sys.usb.config") 37 if "adb" not in val: 38 if val == "": 39 val = "adb" 40 else: 41 val = val + ",adb" 42 prop_list.put("persist.sys.usb.config", val) 43 if prop_list.get_value("ro.dalvik.vm.enable_uffd_gc") == "default": 44 assert kernel_version_file_for_uffd_gc != "" 45 enable_uffd_gc = should_enable_uffd_gc(kernel_version_file_for_uffd_gc) 46 prop_list.put("ro.dalvik.vm.enable_uffd_gc", 47 "true" if enable_uffd_gc else "false") 48 49def validate_grf_props(prop_list): 50 """Validate GRF properties if exist. 51 52 If ro.board.first_api_level is defined, check if its value is valid. 53 54 Returns: 55 True if the GRF properties are valid. 56 """ 57 grf_api_level = prop_list.get_value("ro.board.first_api_level") 58 board_api_level = prop_list.get_value("ro.board.api_level") 59 60 if grf_api_level and board_api_level: 61 grf_api_level = int(grf_api_level) 62 board_api_level = int(board_api_level) 63 if board_api_level < grf_api_level: 64 sys.stderr.write("error: ro.board.api_level(%d) must not be less than " 65 "ro.board.first_api_level(%d)\n" 66 % (board_api_level, grf_api_level)) 67 return False 68 69 return True 70 71def validate(prop_list): 72 """Validate the properties. 73 74 If the value of a sysprop exceeds the max limit (91), it's an error, unless 75 the sysprop is a read-only one. 76 77 Checks if there is no optional prop assignments. 78 79 Returns: 80 True if nothing is wrong. 81 """ 82 check_pass = True 83 for p in prop_list.get_all_props(): 84 if len(p.value) > PROP_VALUE_MAX and not p.name.startswith("ro."): 85 check_pass = False 86 sys.stderr.write("error: %s cannot exceed %d bytes: " % 87 (p.name, PROP_VALUE_MAX)) 88 sys.stderr.write("%s (%d)\n" % (p.value, len(p.value))) 89 90 if p.is_optional(): 91 check_pass = False 92 sys.stderr.write("error: found unresolved optional prop assignment:\n") 93 sys.stderr.write(str(p) + "\n") 94 95 return check_pass 96 97def override_optional_props(prop_list, allow_dup=False): 98 """Override a?=b with a=c, if the latter exists 99 100 Overriding is done by deleting a?=b 101 When there are a?=b and a?=c, then only the last one survives 102 When there are a=b and a=c, then it's an error. 103 104 Returns: 105 True if the override was successful 106 """ 107 success = True 108 for name in prop_list.get_all_names(): 109 props = prop_list.get_props(name) 110 optional_props = [p for p in props if p.is_optional()] 111 overriding_props = [p for p in props if not p.is_optional()] 112 if len(overriding_props) > 1: 113 # duplicated props are allowed when the all have the same value 114 if all(overriding_props[0].value == p.value for p in overriding_props): 115 for p in optional_props: 116 p.delete("overridden by %s" % str(overriding_props[0])) 117 continue 118 # or if dup is explicitly allowed for compat reason 119 if allow_dup: 120 # this could left one or more optional props unresolved. 121 # Convert them into non-optional because init doesn't understand ?= 122 # syntax 123 for p in optional_props: 124 p.optional = False 125 continue 126 127 success = False 128 sys.stderr.write("error: found duplicate sysprop assignments:\n") 129 for p in overriding_props: 130 sys.stderr.write("%s\n" % str(p)) 131 elif len(overriding_props) == 1: 132 for p in optional_props: 133 p.delete("overridden by %s" % str(overriding_props[0])) 134 else: 135 if len(optional_props) > 1: 136 for p in optional_props[:-1]: 137 p.delete("overridden by %s" % str(optional_props[-1])) 138 # Make the last optional one as non-optional 139 optional_props[-1].optional = False 140 141 return success 142 143class Prop: 144 145 def __init__(self, name, value, optional=False, comment=None): 146 self.name = name.strip() 147 self.value = value.strip() 148 if comment != None: 149 self.comments = [comment] 150 else: 151 self.comments = [] 152 self.optional = optional 153 154 @staticmethod 155 def from_line(line): 156 line = line.rstrip('\n') 157 if line.startswith("#"): 158 return Prop("", "", comment=line) 159 elif "?=" in line: 160 name, value = line.split("?=", 1) 161 return Prop(name, value, optional=True) 162 elif "=" in line: 163 name, value = line.split("=", 1) 164 return Prop(name, value, optional=False) 165 else: 166 # don't fail on invalid line 167 # TODO(jiyong) make this a hard error 168 return Prop("", "", comment=line) 169 170 def is_comment(self): 171 return bool(self.comments and not self.name) 172 173 def is_optional(self): 174 return (not self.is_comment()) and self.optional 175 176 def make_as_comment(self): 177 # Prepend "#" to the last line which is the prop assignment 178 if not self.is_comment(): 179 assignment = str(self).rsplit("\n", 1)[-1] 180 self.comments.append("#" + assignment) 181 self.name = "" 182 self.value = "" 183 184 def delete(self, reason): 185 self.comments.append("# Removed by post_process_props.py because " + reason) 186 self.make_as_comment() 187 188 def __str__(self): 189 assignment = [] 190 if not self.is_comment(): 191 operator = "?=" if self.is_optional() else "=" 192 assignment.append(self.name + operator + self.value) 193 return "\n".join(self.comments + assignment) 194 195class PropList: 196 197 def __init__(self, filename): 198 with open(filename) as f: 199 self.props = [Prop.from_line(l) 200 for l in f.readlines() if l.strip() != ""] 201 202 def get_all_props(self): 203 return [p for p in self.props if not p.is_comment()] 204 205 def get_all_names(self): 206 return set([p.name for p in self.get_all_props()]) 207 208 def get_props(self, name): 209 return [p for p in self.get_all_props() if p.name == name] 210 211 def get_value(self, name): 212 # Caution: only the value of the first sysprop having the name is returned. 213 return next((p.value for p in self.props if p.name == name), "") 214 215 def put(self, name, value): 216 # Note: when there is an optional prop for the name, its value isn't changed. 217 # Instead a new non-optional prop is appended, which will override the 218 # optional prop. Otherwise, the new value might be overridden by an existing 219 # non-optional prop of the same name. 220 index = next((i for i,p in enumerate(self.props) 221 if p.name == name and not p.is_optional()), -1) 222 if index == -1: 223 self.props.append(Prop(name, value, 224 comment="# Auto-added by post_process_props.py")) 225 else: 226 self.props[index].comments.append( 227 "# Value overridden by post_process_props.py. Original value: %s" % 228 self.props[index].value) 229 self.props[index].value = value 230 231 def write(self, filename): 232 with open(filename, 'w+') as f: 233 for p in self.props: 234 f.write(str(p) + "\n") 235 236def main(argv): 237 parser = argparse.ArgumentParser(description="Post-process build.prop file") 238 parser.add_argument("--allow-dup", dest="allow_dup", action="store_true", 239 default=False) 240 parser.add_argument("filename") 241 parser.add_argument("disallowed_keys", metavar="KEY", type=str, nargs="*") 242 parser.add_argument("--sdk-version", type=int, required=True) 243 parser.add_argument("--kernel-version-file-for-uffd-gc", required=True) 244 args = parser.parse_args() 245 246 if not args.filename.endswith("/build.prop"): 247 sys.stderr.write("bad command line: " + str(argv) + "\n") 248 sys.exit(1) 249 250 props = PropList(args.filename) 251 mangle_build_prop(props, args.kernel_version_file_for_uffd_gc) 252 if not override_optional_props(props, args.allow_dup): 253 sys.exit(1) 254 if not validate_grf_props(props): 255 sys.exit(1) 256 if not validate(props): 257 sys.exit(1) 258 259 # Drop any disallowed keys 260 for key in args.disallowed_keys: 261 for p in props.get_props(key): 262 p.delete("%s is a disallowed key" % key) 263 264 props.write(args.filename) 265 266if __name__ == "__main__": 267 main(sys.argv) 268