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>
diff --git a/tools/fs_config/fs_config_generator.py b/tools/fs_config/fs_config_generator.py
index 749af89..f995546 100755
--- a/tools/fs_config/fs_config_generator.py
+++ b/tools/fs_config/fs_config_generator.py
@@ -66,6 +66,27 @@
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 @@
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 @@
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 @@
# (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 @@
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 @@
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