fs_config: support parsing android_filesystem_config.h

Rather than hardcode the OEM ranges, parse and extract
AID values from android_filesystem_config.h.

An AID is defined to the tool as:
  * #define AID_<name>

An OEM Range is defined to the the tool as:
  * AID_OEM_RESERVED_START
  * AID_OEM_RESERVED_END
  or
  * AID_OEM_RESERVED_N_START
  * AID_OEM_RESERVED_N_END

Where N is a number.

While parsing, perform sanity checks such as:
1. AIDs defined in the header cannot be within OEM range
2. OEM Ranges must be valid:
   * Cannot overlap one another.
   * Range START must be less than range END
3. Like the C preproccessor, multiple matching AID_<name> throws
   en error.

The parser introduced here, prepares the tool to output android_ids
consumable for bionic.

Note that some AID_* friendly names were not consistent, thus a small
fixup map had to be placed inside the tool.

Test: tested parsing and dumping the data from android_filesystem_config.h
file.
Change-Id: Ifa4d1c9565d061b60542296fe33c8eba31649e62
Signed-off-by: William Roberts <william.c.roberts@intel.com>
This commit is contained in:
William Roberts 2016-04-11 17:12:47 -07:00 committed by Dan Albert
parent 11c29283ec
commit 64edf5bb97
2 changed files with 331 additions and 15 deletions

View file

@ -81,15 +81,18 @@ LOCAL_SHARED_LIBRARIES := libcutils
LOCAL_CFLAGS := -Werror -Wno-error=\#warnings
ifneq ($(TARGET_FS_CONFIG_GEN),)
system_android_filesystem_config := system/core/include/private/android_filesystem_config.h
gen := $(local-generated-sources-dir)/$(ANDROID_FS_CONFIG_H)
$(gen): PRIVATE_LOCAL_PATH := $(LOCAL_PATH)
$(gen): PRIVATE_TARGET_FS_CONFIG_GEN := $(TARGET_FS_CONFIG_GEN)
$(gen): PRIVATE_CUSTOM_TOOL = $(PRIVATE_LOCAL_PATH)/fs_config_generator.py fsconfig $(PRIVATE_TARGET_FS_CONFIG_GEN) > $@
$(gen): $(TARGET_FS_CONFIG_GEN) $(LOCAL_PATH)/fs_config_generator.py
$(gen): PRIVATE_ANDROID_FS_HDR := $(system_android_filesystem_config)
$(gen): PRIVATE_CUSTOM_TOOL = $(PRIVATE_LOCAL_PATH)/fs_config_generator.py fsconfig --aid-header=$(PRIVATE_ANDROID_FS_HDR) $(PRIVATE_TARGET_FS_CONFIG_GEN) > $@
$(gen): $(TARGET_FS_CONFIG_GEN) $(system_android_filesystem_config) $(LOCAL_PATH)/fs_config_generator.py
$(transform-generated-source)
LOCAL_GENERATED_SOURCES := $(gen)
my_fs_config_h := $(gen)
system_android_filesystem_config :=
gen :=
endif

View file

@ -66,6 +66,27 @@ class generator(object): # pylint: disable=invalid-name
return generator._generators
class Utils(object):
"""Various assorted static utilities."""
@staticmethod
def in_any_range(value, ranges):
"""Tests if a value is in a list of given closed range tuples.
A range tuple is a closed range. That means it's inclusive of its
start and ending values.
Args:
value (int): The value to test.
range [(int, int)]: The closed range list to test value within.
Returns:
True if value is within the closed range, false otherwise.
"""
return any(lower <= value <= upper for (lower, upper) in ranges)
class AID(object):
"""This class represents an Android ID or an AID.
@ -124,6 +145,294 @@ class FSConfig(object):
self.filename = filename
class AIDHeaderParser(object):
"""Parses an android_filesystem_config.h file.
Parses a C header file and extracts lines starting with #define AID_<name>
It provides some basic sanity checks. The information extracted from this
file can later be used to sanity check other things (like oem ranges) as
well as generating a mapping of names to uids. It was primarily designed to
parse the private/android_filesystem_config.h, but any C header should
work.
"""
_SKIPWORDS = ['UNUSED']
_AID_KW = 'AID_'
_AID_DEFINE = re.compile(r'\s*#define\s+%s.*' % _AID_KW)
_OEM_START_KW = 'START'
_OEM_END_KW = 'END'
_OEM_RANGE = re.compile('AID_OEM_RESERVED_[0-9]*_{0,1}(%s|%s)' %
(_OEM_START_KW, _OEM_END_KW))
# Some of the AIDS like AID_MEDIA_EX had names like mediaex
# list a map of things to fixup until we can correct these
# at a later date.
_FIXUPS = {
'media_drm': 'mediadrm',
'media_ex': 'mediaex',
'media_codec': 'mediacodec'
}
def __init__(self, aid_header):
"""
Args:
aid_header (str): file name for the header
file containing AID entries.
"""
self._aid_header = aid_header
self._aid_name_to_value = {}
self._aid_value_to_name = {}
self._oem_ranges = {}
with open(aid_header) as open_file:
self._parse(open_file)
try:
self._process_and_check()
except ValueError as exception:
sys.exit('Error processing parsed data: "%s"' % (str(exception)))
def _parse(self, aid_file):
"""Parses an AID header file. Internal use only.
Args:
aid_file (file): The open AID header file to parse.
"""
for lineno, line in enumerate(aid_file):
def error_message(msg):
"""Creates an error message with the current parsing state."""
return 'Error "{}" in file: "{}" on line: {}'.format(
msg, self._aid_header, str(lineno))
if AIDHeaderParser._AID_DEFINE.match(line):
chunks = line.split()
if any(x in chunks[1] for x in AIDHeaderParser._SKIPWORDS):
continue
identifier = chunks[1]
value = chunks[2]
try:
if AIDHeaderParser._is_oem_range(identifier):
self._handle_oem_range(identifier, value)
else:
self._handle_aid(identifier, value)
except ValueError as exception:
sys.exit(error_message(
'{} for "{}"'.format(exception, identifier)))
def _handle_aid(self, identifier, value):
"""Handle an AID C #define.
Handles an AID, sanity checking, generating the friendly name and
adding it to the internal maps. Internal use only.
Args:
identifier (str): The name of the #define identifier. ie AID_FOO.
value (str): The value associated with the identifier.
Raises:
ValueError: With message set to indicate the error.
"""
# friendly name
name = AIDHeaderParser._convert_friendly(identifier)
# duplicate name
if name in self._aid_name_to_value:
raise ValueError('Duplicate aid "%s"' % identifier)
if value in self._aid_value_to_name:
raise ValueError('Duplicate aid value "%u" for %s' % value,
identifier)
self._aid_name_to_value[name] = AID(identifier, value, self._aid_header)
self._aid_value_to_name[value] = name
def _handle_oem_range(self, identifier, value):
"""Handle an OEM range C #define.
When encountering special AID defines, notably for the OEM ranges
this method handles sanity checking and adding them to the internal
maps. For internal use only.
Args:
identifier (str): The name of the #define identifier.
ie AID_OEM_RESERVED_START/END.
value (str): The value associated with the identifier.
Raises:
ValueError: With message set to indicate the error.
"""
try:
int_value = int(value, 0)
except ValueError:
raise ValueError(
'Could not convert "%s" to integer value, got: "%s"' %
(identifier, value))
# convert AID_OEM_RESERVED_START or AID_OEM_RESERVED_<num>_START
# to AID_OEM_RESERVED or AID_OEM_RESERVED_<num>
is_start = identifier.endswith(AIDHeaderParser._OEM_START_KW)
if is_start:
tostrip = len(AIDHeaderParser._OEM_START_KW)
else:
tostrip = len(AIDHeaderParser._OEM_END_KW)
# ending _
tostrip = tostrip + 1
strip = identifier[:-tostrip]
if strip not in self._oem_ranges:
self._oem_ranges[strip] = []
if len(self._oem_ranges[strip]) > 2:
raise ValueError('Too many same OEM Ranges "%s"' % identifier)
if len(self._oem_ranges[strip]) == 1:
tmp = self._oem_ranges[strip][0]
if tmp == int_value:
raise ValueError('START and END values equal %u' % int_value)
elif is_start and tmp < int_value:
raise ValueError('END value %u less than START value %u' %
(tmp, int_value))
elif not is_start and tmp > int_value:
raise ValueError('END value %u less than START value %u' %
(int_value, tmp))
# Add START values to the head of the list and END values at the end.
# Thus, the list is ordered with index 0 as START and index 1 as END.
if is_start:
self._oem_ranges[strip].insert(0, int_value)
else:
self._oem_ranges[strip].append(int_value)
def _process_and_check(self):
"""Process, check and populate internal data structures.
After parsing and generating the internal data structures, this method
is responsible for sanity checking ALL of the acquired data.
Raises:
ValueError: With the message set to indicate the specific error.
"""
# tuplefy the lists since range() does not like them mutable.
self._oem_ranges = [
AIDHeaderParser._convert_lst_to_tup(k, v)
for k, v in self._oem_ranges.iteritems()
]
# Check for overlapping ranges
for i, range1 in enumerate(self._oem_ranges):
for range2 in self._oem_ranges[i + 1:]:
if AIDHeaderParser._is_overlap(range1, range2):
raise ValueError("Overlapping OEM Ranges found %s and %s" %
(str(range1), str(range2)))
# No core AIDs should be within any oem range.
for aid in self._aid_value_to_name:
if Utils.in_any_range(aid, self._oem_ranges):
name = self._aid_value_to_name[aid]
raise ValueError(
'AID "%s" value: %u within reserved OEM Range: "%s"' %
(name, aid, str(self._oem_ranges)))
@property
def oem_ranges(self):
"""Retrieves the OEM closed ranges as a list of tuples.
Returns:
A list of closed range tuples: [ (0, 42), (50, 105) ... ]
"""
return self._oem_ranges
@property
def aids(self):
"""Retrieves the list of found AIDs.
Returns:
A list of AID() objects.
"""
return self._aid_name_to_value.values()
@staticmethod
def _convert_lst_to_tup(name, lst):
"""Converts a mutable list to a non-mutable tuple.
Used ONLY for ranges and thus enforces a length of 2.
Args:
lst (List): list that should be "tuplefied".
Raises:
ValueError if lst is not a list or len is not 2.
Returns:
Tuple(lst)
"""
if not lst or len(lst) != 2:
raise ValueError('Mismatched range for "%s"' % name)
return tuple(lst)
@staticmethod
def _convert_friendly(identifier):
"""
Translate AID_FOO_BAR to foo_bar (ie name)
Args:
identifier (str): The name of the #define.
Returns:
The friendly name as a str.
"""
name = identifier[len(AIDHeaderParser._AID_KW):].lower()
if name in AIDHeaderParser._FIXUPS:
return AIDHeaderParser._FIXUPS[name]
return name
@staticmethod
def _is_oem_range(aid):
"""Detects if a given aid is within the reserved OEM range.
Args:
aid (int): The aid to test
Returns:
True if it is within the range, False otherwise.
"""
return AIDHeaderParser._OEM_RANGE.match(aid)
@staticmethod
def _is_overlap(range_a, range_b):
"""Calculates the overlap of two range tuples.
A range tuple is a closed range. A closed range includes its endpoints.
Note that python tuples use () notation which collides with the
mathematical notation for open ranges.
Args:
range_a: The first tuple closed range eg (0, 5).
range_b: The second tuple closed range eg (3, 7).
Returns:
True if they overlap, False otherwise.
"""
return max(range_a[0], range_b[0]) <= min(range_a[1], range_b[1])
class FSConfigFileParser(object):
"""Parses a config.fs ini format file.
@ -131,19 +440,15 @@ class FSConfigFileParser(object):
It collects and checks all the data in these files and makes it available
for consumption post processed.
"""
# from system/core/include/private/android_filesystem_config.h
_AID_OEM_RESERVED_RANGES = [
(2900, 2999),
(5000, 5999),
]
_AID_MATCH = re.compile('AID_[a-zA-Z]+')
def __init__(self, config_files):
def __init__(self, config_files, oem_ranges):
"""
Args:
config_files ([str]): The list of config.fs files to parse.
Note the filename is not important.
oem_ranges ([(),()]): range tuples indicating reserved OEM ranges.
"""
self._files = []
@ -154,6 +459,8 @@ class FSConfigFileParser(object):
# (name to file, value to aid)
self._seen_aids = ({}, {})
self._oem_ranges = oem_ranges
self._config_files = config_files
for config_file in self._config_files:
@ -242,13 +549,10 @@ class FSConfigFileParser(object):
error_message('Invalid "value", not aid number, got: \"%s\"' %
value))
# Values must be within OEM range.
if not any(lower <= int(aid.value, 0) <= upper
for (lower, upper
) in FSConfigFileParser._AID_OEM_RESERVED_RANGES):
# Values must be within OEM range
if not Utils.in_any_range(int(aid.value, 0), self._oem_ranges):
emsg = '"value" not in valid range %s, got: %s'
emsg = emsg % (str(FSConfigFileParser._AID_OEM_RESERVED_RANGES),
value)
emsg = emsg % (str(self._oem_ranges), value)
sys.exit(error_message(emsg))
# use the normalized int value in the dict and detect
@ -539,9 +843,18 @@ class FSConfigGen(BaseGenerator):
opt_group.add_argument(
'fsconfig', nargs='+', help='The list of fsconfig files to parse')
opt_group.add_argument(
'--aid-header',
required=True,
help='An android_filesystem_config.h file'
' to parse AIDs and OEM Ranges from')
def __call__(self, args):
parser = FSConfigFileParser(args['fsconfig'])
hdr = AIDHeaderParser(args['aid_header'])
oem_ranges = hdr.oem_ranges
parser = FSConfigFileParser(args['fsconfig'], oem_ranges)
FSConfigGen._generate(parser.files, parser.dirs, parser.aids)
@staticmethod