|  | #!/usr/bin/env python | 
|  | """Generates config files for Android file system properties. | 
|  |  | 
|  | This script is used for generating configuration files for configuring | 
|  | Android filesystem properties. Internally, its composed of a plug-able | 
|  | interface to support the understanding of new input and output parameters. | 
|  |  | 
|  | Run the help for a list of supported plugins and their capabilities. | 
|  |  | 
|  | Further documentation can be found in the README. | 
|  | """ | 
|  |  | 
|  | import argparse | 
|  | import ConfigParser | 
|  | import ctypes | 
|  | import re | 
|  | import sys | 
|  | import textwrap | 
|  |  | 
|  | # Keep the tool in one file to make it easy to run. | 
|  | # pylint: disable=too-many-lines | 
|  |  | 
|  |  | 
|  | # Lowercase generator used to be inline with @staticmethod. | 
|  | class generator(object):  # pylint: disable=invalid-name | 
|  | """A decorator class to add commandlet plugins. | 
|  |  | 
|  | Used as a decorator to classes to add them to | 
|  | the internal plugin interface. Plugins added | 
|  | with @generator() are automatically added to | 
|  | the command line. | 
|  |  | 
|  | For instance, to add a new generator | 
|  | called foo and have it added just do this: | 
|  |  | 
|  | @generator("foo") | 
|  | class FooGen(object): | 
|  | ... | 
|  | """ | 
|  | _generators = {} | 
|  |  | 
|  | def __init__(self, gen): | 
|  | """ | 
|  | Args: | 
|  | gen (str): The name of the generator to add. | 
|  |  | 
|  | Raises: | 
|  | ValueError: If there is a similarly named generator already added. | 
|  |  | 
|  | """ | 
|  | self._gen = gen | 
|  |  | 
|  | if gen in generator._generators: | 
|  | raise ValueError('Duplicate generator name: ' + gen) | 
|  |  | 
|  | generator._generators[gen] = None | 
|  |  | 
|  | def __call__(self, cls): | 
|  |  | 
|  | generator._generators[self._gen] = cls() | 
|  | return cls | 
|  |  | 
|  | @staticmethod | 
|  | def get(): | 
|  | """Gets the list of generators. | 
|  |  | 
|  | Returns: | 
|  | The list of registered generators. | 
|  | """ | 
|  | 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) | 
|  |  | 
|  | @staticmethod | 
|  | def get_login_and_uid_cleansed(aid): | 
|  | """Returns a passwd/group file safe logon and uid. | 
|  |  | 
|  | This checks that the logon and uid of the AID do not | 
|  | contain the delimiter ":" for a passwd/group file. | 
|  |  | 
|  | Args: | 
|  | aid (AID): The aid to check | 
|  |  | 
|  | Returns: | 
|  | logon, uid of the AID after checking its safe. | 
|  |  | 
|  | Raises: | 
|  | ValueError: If there is a delimiter charcter found. | 
|  | """ | 
|  | logon = aid.friendly | 
|  | uid = aid.normalized_value | 
|  | if ':' in uid: | 
|  | raise ValueError( | 
|  | 'Cannot specify delimiter character ":" in uid: "%s"' % uid) | 
|  | if ':' in logon: | 
|  | raise ValueError( | 
|  | 'Cannot specify delimiter character ":" in logon: "%s"' % | 
|  | logon) | 
|  | return logon, uid | 
|  |  | 
|  |  | 
|  | class AID(object): | 
|  | """This class represents an Android ID or an AID. | 
|  |  | 
|  | Attributes: | 
|  | identifier (str): The identifier name for a #define. | 
|  | value (str) The User Id (uid) of the associate define. | 
|  | found (str) The file it was found in, can be None. | 
|  | normalized_value (str): Same as value, but base 10. | 
|  | friendly (str): The friendly name of aid. | 
|  | """ | 
|  |  | 
|  | PREFIX = 'AID_' | 
|  |  | 
|  | # 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, identifier, value, found, login_shell): | 
|  | """ | 
|  | Args: | 
|  | identifier: The identifier name for a #define <identifier>. | 
|  | value: The value of the AID, aka the uid. | 
|  | found (str): The file found in, not required to be specified. | 
|  | login_shell (str): The shell field per man (5) passwd file. | 
|  | Raises: | 
|  | ValueError: if the friendly name is longer than 31 characters as | 
|  | that is bionic's internal buffer size for name. | 
|  | ValueError: if value is not a valid string number as processed by | 
|  | int(x, 0) | 
|  | """ | 
|  | self.identifier = identifier | 
|  | self.value = value | 
|  | self.found = found | 
|  | self.login_shell = login_shell | 
|  |  | 
|  | try: | 
|  | self.normalized_value = str(int(value, 0)) | 
|  | except ValueError: | 
|  | raise ValueError( | 
|  | 'Invalid "value", not aid number, got: \"%s\"' % value) | 
|  |  | 
|  | # Where we calculate the friendly name | 
|  | friendly = identifier[len(AID.PREFIX):].lower() | 
|  | self.friendly = AID._fixup_friendly(friendly) | 
|  |  | 
|  | if len(self.friendly) > 31: | 
|  | raise ValueError( | 
|  | 'AID names must be under 32 characters "%s"' % self.friendly) | 
|  |  | 
|  | def __eq__(self, other): | 
|  |  | 
|  | return self.identifier == other.identifier \ | 
|  | and self.value == other.value and self.found == other.found \ | 
|  | and self.normalized_value == other.normalized_value \ | 
|  | and self.login_shell == other.login_shell | 
|  |  | 
|  | @staticmethod | 
|  | def is_friendly(name): | 
|  | """Determines if an AID is a freindly name or C define. | 
|  |  | 
|  | For example if name is AID_SYSTEM it returns false, if name | 
|  | was system, it would return true. | 
|  |  | 
|  | Returns: | 
|  | True if name is a friendly name False otherwise. | 
|  | """ | 
|  |  | 
|  | return not name.startswith(AID.PREFIX) | 
|  |  | 
|  | @staticmethod | 
|  | def _fixup_friendly(friendly): | 
|  | """Fixup friendly names that historically don't follow the convention. | 
|  |  | 
|  | Args: | 
|  | friendly (str): The friendly name. | 
|  |  | 
|  | Returns: | 
|  | The fixedup friendly name as a str. | 
|  | """ | 
|  |  | 
|  | if friendly in AID._FIXUPS: | 
|  | return AID._FIXUPS[friendly] | 
|  |  | 
|  | return friendly | 
|  |  | 
|  |  | 
|  | class FSConfig(object): | 
|  | """Represents a filesystem config array entry. | 
|  |  | 
|  | Represents a file system configuration entry for specifying | 
|  | file system capabilities. | 
|  |  | 
|  | Attributes: | 
|  | mode (str): The mode of the file or directory. | 
|  | user (str): The uid or #define identifier (AID_SYSTEM) | 
|  | group (str): The gid or #define identifier (AID_SYSTEM) | 
|  | caps (str): The capability set. | 
|  | path (str): The path of the file or directory. | 
|  | filename (str): The file it was found in. | 
|  | """ | 
|  |  | 
|  | def __init__(self, mode, user, group, caps, path, filename): | 
|  | """ | 
|  | Args: | 
|  | mode (str): The mode of the file or directory. | 
|  | user (str): The uid or #define identifier (AID_SYSTEM) | 
|  | group (str): The gid or #define identifier (AID_SYSTEM) | 
|  | caps (str): The capability set as a list. | 
|  | path (str): The path of the file or directory. | 
|  | filename (str): The file it was found in. | 
|  | """ | 
|  | self.mode = mode | 
|  | self.user = user | 
|  | self.group = group | 
|  | self.caps = caps | 
|  | self.path = path | 
|  | self.filename = filename | 
|  |  | 
|  | def __eq__(self, other): | 
|  |  | 
|  | return self.mode == other.mode and self.user == other.user \ | 
|  | and self.group == other.group and self.caps == other.caps \ | 
|  | and self.path == other.path and self.filename == other.filename | 
|  |  | 
|  | def __repr__(self): | 
|  | return 'FSConfig(%r, %r, %r, %r, %r, %r)' % (self.mode, self.user, | 
|  | self.group, self.caps, | 
|  | self.path, self.filename) | 
|  |  | 
|  |  | 
|  | class CapabilityHeaderParser(object): | 
|  | """Parses capability.h file | 
|  |  | 
|  | Parses a C header file and extracts lines starting with #define CAP_<name>. | 
|  | """ | 
|  |  | 
|  | _CAP_DEFINE = re.compile(r'\s*#define\s+(CAP_\S+)\s+(\S+)') | 
|  | _SKIP_CAPS = ['CAP_LAST_CAP', 'CAP_TO_INDEX(x)', 'CAP_TO_MASK(x)'] | 
|  |  | 
|  | def __init__(self, capability_header): | 
|  | """ | 
|  | Args: | 
|  | capability_header (str): file name for the header file containing AID entries. | 
|  | """ | 
|  |  | 
|  | self.caps = {} | 
|  | with open(capability_header) as open_file: | 
|  | self._parse(open_file) | 
|  |  | 
|  | def _parse(self, capability_file): | 
|  | """Parses a capability header file. Internal use only. | 
|  |  | 
|  | Args: | 
|  | capability_file (file): The open capability header file to parse. | 
|  | """ | 
|  |  | 
|  | for line in capability_file: | 
|  | match = CapabilityHeaderParser._CAP_DEFINE.match(line) | 
|  | if match: | 
|  | cap = match.group(1) | 
|  | value = match.group(2) | 
|  |  | 
|  | if not cap in self._SKIP_CAPS: | 
|  | try: | 
|  | self.caps[cap] = int(value, 0) | 
|  | except ValueError: | 
|  | sys.exit('Could not parse capability define "%s":"%s"' | 
|  | % (cap, value)) | 
|  |  | 
|  |  | 
|  | class AIDHeaderParser(object): | 
|  | """Parses an android_filesystem_config.h file. | 
|  |  | 
|  | Parses a C header file and extracts lines starting with #define AID_<name> | 
|  | while capturing the OEM defined ranges and ignoring other ranges. It also | 
|  | skips some hardcoded AIDs it doesn't need to generate a mapping for. | 
|  | 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. | 
|  | """ | 
|  |  | 
|  | _SKIP_AIDS = [ | 
|  | re.compile(r'%sUNUSED[0-9].*' % AID.PREFIX), | 
|  | re.compile(r'%sAPP' % AID.PREFIX), | 
|  | re.compile(r'%sUSER' % AID.PREFIX) | 
|  | ] | 
|  | _AID_DEFINE = re.compile(r'\s*#define\s+%s.*' % AID.PREFIX) | 
|  | _RESERVED_RANGE = re.compile( | 
|  | r'#define AID_(.+)_RESERVED_\d*_*(START|END)\s+(\d+)') | 
|  |  | 
|  | # AID lines cannot end with _START or _END, ie AID_FOO is OK | 
|  | # but AID_FOO_START is skiped. Note that AID_FOOSTART is NOT skipped. | 
|  | _AID_SKIP_RANGE = ['_START', '_END'] | 
|  | _COLLISION_OK = ['AID_APP', 'AID_APP_START', 'AID_USER', 'AID_USER_OFFSET'] | 
|  |  | 
|  | 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._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.""" | 
|  | # pylint: disable=cell-var-from-loop | 
|  | return 'Error "{}" in file: "{}" on line: {}'.format( | 
|  | msg, self._aid_header, str(lineno)) | 
|  |  | 
|  | range_match = self._RESERVED_RANGE.match(line) | 
|  | if range_match: | 
|  | partition = range_match.group(1).lower() | 
|  | value = int(range_match.group(3), 0) | 
|  |  | 
|  | if partition == 'oem': | 
|  | partition = 'vendor' | 
|  |  | 
|  | if partition in self._ranges: | 
|  | if isinstance(self._ranges[partition][-1], int): | 
|  | self._ranges[partition][-1] = ( | 
|  | self._ranges[partition][-1], value) | 
|  | else: | 
|  | self._ranges[partition].append(value) | 
|  | else: | 
|  | self._ranges[partition] = [value] | 
|  |  | 
|  | if AIDHeaderParser._AID_DEFINE.match(line): | 
|  | chunks = line.split() | 
|  | identifier = chunks[1] | 
|  | value = chunks[2] | 
|  |  | 
|  | if any( | 
|  | x.match(identifier) | 
|  | for x in AIDHeaderParser._SKIP_AIDS): | 
|  | continue | 
|  |  | 
|  | try: | 
|  | if not any( | 
|  | identifier.endswith(x) | 
|  | for x in AIDHeaderParser._AID_SKIP_RANGE): | 
|  | 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. | 
|  | """ | 
|  |  | 
|  | aid = AID(identifier, value, self._aid_header, '/system/bin/sh') | 
|  |  | 
|  | # duplicate name | 
|  | if aid.friendly in self._aid_name_to_value: | 
|  | raise ValueError('Duplicate aid "%s"' % identifier) | 
|  |  | 
|  | if value in self._aid_value_to_name and aid.identifier not in AIDHeaderParser._COLLISION_OK: | 
|  | raise ValueError( | 
|  | 'Duplicate aid value "%s" for %s' % (value, identifier)) | 
|  |  | 
|  | self._aid_name_to_value[aid.friendly] = aid | 
|  | self._aid_value_to_name[value] = aid.friendly | 
|  |  | 
|  | 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. | 
|  | """ | 
|  |  | 
|  | # Check for overlapping ranges | 
|  | for ranges in self._ranges.values(): | 
|  | for i, range1 in enumerate(ranges): | 
|  | for range2 in 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: | 
|  | for ranges in self._ranges.values(): | 
|  | if Utils.in_any_range(aid, ranges): | 
|  | name = self._aid_value_to_name[aid] | 
|  | raise ValueError( | 
|  | 'AID "%s" value: %u within reserved OEM Range: "%s"' % | 
|  | (name, aid, str(ranges))) | 
|  |  | 
|  | @property | 
|  | def 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._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 _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. | 
|  |  | 
|  | This class is responsible for parsing the config.fs ini format files. | 
|  | It collects and checks all the data in these files and makes it available | 
|  | for consumption post processed. | 
|  | """ | 
|  |  | 
|  | # These _AID vars work together to ensure that an AID section name | 
|  | # cannot contain invalid characters for a C define or a passwd/group file. | 
|  | # Since _AID_PREFIX is within the set of _AID_MATCH the error logic only | 
|  | # checks end, if you change this, you may have to update the error | 
|  | # detection code. | 
|  | _AID_MATCH = re.compile('%s[A-Z0-9_]+' % AID.PREFIX) | 
|  | _AID_ERR_MSG = 'Expecting upper case, a number or underscore' | 
|  |  | 
|  | # list of handler to required options, used to identify the | 
|  | # parsing section | 
|  | _SECTIONS = [('_handle_aid', ('value', )), | 
|  | ('_handle_path', ('mode', 'user', 'group', 'caps'))] | 
|  |  | 
|  | def __init__(self, config_files, ranges): | 
|  | """ | 
|  | Args: | 
|  | config_files ([str]): The list of config.fs files to parse. | 
|  | Note the filename is not important. | 
|  | ranges ({str,[()]): Dictionary of partitions and a list of tuples that correspond to their ranges | 
|  | """ | 
|  |  | 
|  | self._files = [] | 
|  | self._dirs = [] | 
|  | self._aids = [] | 
|  |  | 
|  | self._seen_paths = {} | 
|  | # (name to file, value to aid) | 
|  | self._seen_aids = ({}, {}) | 
|  |  | 
|  | self._ranges = ranges | 
|  |  | 
|  | self._config_files = config_files | 
|  |  | 
|  | for config_file in self._config_files: | 
|  | self._parse(config_file) | 
|  |  | 
|  | def _parse(self, file_name): | 
|  | """Parses and verifies config.fs files. Internal use only. | 
|  |  | 
|  | Args: | 
|  | file_name (str): The config.fs (PythonConfigParser file format) | 
|  | file to parse. | 
|  |  | 
|  | Raises: | 
|  | Anything raised by ConfigParser.read() | 
|  | """ | 
|  |  | 
|  | # Separate config parsers for each file found. If you use | 
|  | # read(filenames...) later files can override earlier files which is | 
|  | # not what we want. Track state across files and enforce with | 
|  | # _handle_dup(). Note, strict ConfigParser is set to true in | 
|  | # Python >= 3.2, so in previous versions same file sections can | 
|  | # override previous | 
|  | # sections. | 
|  |  | 
|  | config = ConfigParser.ConfigParser() | 
|  | config.read(file_name) | 
|  |  | 
|  | for section in config.sections(): | 
|  |  | 
|  | found = False | 
|  |  | 
|  | for test in FSConfigFileParser._SECTIONS: | 
|  | handler = test[0] | 
|  | options = test[1] | 
|  |  | 
|  | if all([config.has_option(section, item) for item in options]): | 
|  | handler = getattr(self, handler) | 
|  | handler(file_name, section, config) | 
|  | found = True | 
|  | break | 
|  |  | 
|  | if not found: | 
|  | sys.exit('Invalid section "%s" in file: "%s"' % (section, | 
|  | file_name)) | 
|  |  | 
|  | # sort entries: | 
|  | # * specified path before prefix match | 
|  | # ** ie foo before f* | 
|  | # * lexicographical less than before other | 
|  | # ** ie boo before foo | 
|  | # Given these paths: | 
|  | # paths=['ac', 'a', 'acd', 'an', 'a*', 'aa', 'ac*'] | 
|  | # The sort order would be: | 
|  | # paths=['a', 'aa', 'ac', 'acd', 'an', 'ac*', 'a*'] | 
|  | # Thus the fs_config tools will match on specified paths before | 
|  | # attempting prefix, and match on the longest matching prefix. | 
|  | self._files.sort(key=FSConfigFileParser._file_key) | 
|  |  | 
|  | # sort on value of (file_name, name, value, strvalue) | 
|  | # This is only cosmetic so AIDS are arranged in ascending order | 
|  | # within the generated file. | 
|  | self._aids.sort(key=lambda item: item.normalized_value) | 
|  |  | 
|  | def _verify_valid_range(self, aid): | 
|  | """Verified an AID entry is in a valid range""" | 
|  |  | 
|  | ranges = None | 
|  |  | 
|  | partitions = self._ranges.keys() | 
|  | partitions.sort(key=len, reverse=True) | 
|  | for partition in partitions: | 
|  | if aid.friendly.startswith(partition): | 
|  | ranges = self._ranges[partition] | 
|  | break | 
|  |  | 
|  | if ranges is None: | 
|  | sys.exit('AID "%s" must be prefixed with a partition name' % | 
|  | aid.friendly) | 
|  |  | 
|  | if not Utils.in_any_range(int(aid.value, 0), ranges): | 
|  | emsg = '"value" for aid "%s" not in valid range %s, got: %s' | 
|  | emsg = emsg % (aid.friendly, str(ranges), aid.value) | 
|  | sys.exit(emsg) | 
|  |  | 
|  | def _handle_aid(self, file_name, section_name, config): | 
|  | """Verifies an AID entry and adds it to the aid list. | 
|  |  | 
|  | Calls sys.exit() with a descriptive message of the failure. | 
|  |  | 
|  | Args: | 
|  | file_name (str): The filename of the config file being parsed. | 
|  | section_name (str): The section name currently being parsed. | 
|  | config (ConfigParser): The ConfigParser section being parsed that | 
|  | the option values will come from. | 
|  | """ | 
|  |  | 
|  | def error_message(msg): | 
|  | """Creates an error message with current parsing state.""" | 
|  | return '{} for: "{}" file: "{}"'.format(msg, section_name, | 
|  | file_name) | 
|  |  | 
|  | FSConfigFileParser._handle_dup_and_add('AID', file_name, section_name, | 
|  | self._seen_aids[0]) | 
|  |  | 
|  | match = FSConfigFileParser._AID_MATCH.match(section_name) | 
|  | invalid = match.end() if match else len(AID.PREFIX) | 
|  | if invalid != len(section_name): | 
|  | tmp_errmsg = ('Invalid characters in AID section at "%d" for: "%s"' | 
|  | % (invalid, FSConfigFileParser._AID_ERR_MSG)) | 
|  | sys.exit(error_message(tmp_errmsg)) | 
|  |  | 
|  | value = config.get(section_name, 'value') | 
|  |  | 
|  | if not value: | 
|  | sys.exit(error_message('Found specified but unset "value"')) | 
|  |  | 
|  | try: | 
|  | aid = AID(section_name, value, file_name, '/bin/sh') | 
|  | except ValueError as exception: | 
|  | sys.exit(error_message(exception)) | 
|  |  | 
|  | self._verify_valid_range(aid) | 
|  |  | 
|  | # use the normalized int value in the dict and detect | 
|  | # duplicate definitions of the same value | 
|  | FSConfigFileParser._handle_dup_and_add( | 
|  | 'AID', file_name, aid.normalized_value, self._seen_aids[1]) | 
|  |  | 
|  | # Append aid tuple of (AID_*, base10(value), _path(value)) | 
|  | # We keep the _path version of value so we can print that out in the | 
|  | # generated header so investigating parties can identify parts. | 
|  | # We store the base10 value for sorting, so everything is ascending | 
|  | # later. | 
|  | self._aids.append(aid) | 
|  |  | 
|  | def _handle_path(self, file_name, section_name, config): | 
|  | """Add a file capability entry to the internal list. | 
|  |  | 
|  | Handles a file capability entry, verifies it, and adds it to | 
|  | to the internal dirs or files list based on path. If it ends | 
|  | with a / its a dir. Internal use only. | 
|  |  | 
|  | Calls sys.exit() on any validation error with message set. | 
|  |  | 
|  | Args: | 
|  | file_name (str): The current name of the file being parsed. | 
|  | section_name (str): The name of the section to parse. | 
|  | config (str): The config parser. | 
|  | """ | 
|  |  | 
|  | FSConfigFileParser._handle_dup_and_add('path', file_name, section_name, | 
|  | self._seen_paths) | 
|  |  | 
|  | mode = config.get(section_name, 'mode') | 
|  | user = config.get(section_name, 'user') | 
|  | group = config.get(section_name, 'group') | 
|  | caps = config.get(section_name, 'caps') | 
|  |  | 
|  | errmsg = ('Found specified but unset option: \"%s" in file: \"' + | 
|  | file_name + '\"') | 
|  |  | 
|  | if not mode: | 
|  | sys.exit(errmsg % 'mode') | 
|  |  | 
|  | if not user: | 
|  | sys.exit(errmsg % 'user') | 
|  |  | 
|  | if not group: | 
|  | sys.exit(errmsg % 'group') | 
|  |  | 
|  | if not caps: | 
|  | sys.exit(errmsg % 'caps') | 
|  |  | 
|  | caps = caps.split() | 
|  |  | 
|  | tmp = [] | 
|  | for cap in caps: | 
|  | try: | 
|  | # test if string is int, if it is, use as is. | 
|  | int(cap, 0) | 
|  | tmp.append(cap) | 
|  | except ValueError: | 
|  | tmp.append('CAP_' + cap.upper()) | 
|  |  | 
|  | caps = tmp | 
|  |  | 
|  | if len(mode) == 3: | 
|  | mode = '0' + mode | 
|  |  | 
|  | try: | 
|  | int(mode, 8) | 
|  | except ValueError: | 
|  | sys.exit('Mode must be octal characters, got: "%s"' % mode) | 
|  |  | 
|  | if len(mode) != 4: | 
|  | sys.exit('Mode must be 3 or 4 characters, got: "%s"' % mode) | 
|  |  | 
|  | caps_str = ','.join(caps) | 
|  |  | 
|  | entry = FSConfig(mode, user, group, caps_str, section_name, file_name) | 
|  | if section_name[-1] == '/': | 
|  | self._dirs.append(entry) | 
|  | else: | 
|  | self._files.append(entry) | 
|  |  | 
|  | @property | 
|  | def files(self): | 
|  | """Get the list of FSConfig file entries. | 
|  |  | 
|  | Returns: | 
|  | a list of FSConfig() objects for file paths. | 
|  | """ | 
|  | return self._files | 
|  |  | 
|  | @property | 
|  | def dirs(self): | 
|  | """Get the list of FSConfig dir entries. | 
|  |  | 
|  | Returns: | 
|  | a list of FSConfig() objects for directory paths. | 
|  | """ | 
|  | return self._dirs | 
|  |  | 
|  | @property | 
|  | def aids(self): | 
|  | """Get the list of AID entries. | 
|  |  | 
|  | Returns: | 
|  | a list of AID() objects. | 
|  | """ | 
|  | return self._aids | 
|  |  | 
|  | @staticmethod | 
|  | def _file_key(fs_config): | 
|  | """Used as the key paramter to sort. | 
|  |  | 
|  | This is used as a the function to the key parameter of a sort. | 
|  | it wraps the string supplied in a class that implements the | 
|  | appropriate __lt__ operator for the sort on path strings. See | 
|  | StringWrapper class for more details. | 
|  |  | 
|  | Args: | 
|  | fs_config (FSConfig): A FSConfig entry. | 
|  |  | 
|  | Returns: | 
|  | A StringWrapper object | 
|  | """ | 
|  |  | 
|  | # Wrapper class for custom prefix matching strings | 
|  | class StringWrapper(object): | 
|  | """Wrapper class used for sorting prefix strings. | 
|  |  | 
|  | The algorithm is as follows: | 
|  | - specified path before prefix match | 
|  | - ie foo before f* | 
|  | - lexicographical less than before other | 
|  | - ie boo before foo | 
|  |  | 
|  | Given these paths: | 
|  | paths=['ac', 'a', 'acd', 'an', 'a*', 'aa', 'ac*'] | 
|  | The sort order would be: | 
|  | paths=['a', 'aa', 'ac', 'acd', 'an', 'ac*', 'a*'] | 
|  | Thus the fs_config tools will match on specified paths before | 
|  | attempting prefix, and match on the longest matching prefix. | 
|  | """ | 
|  |  | 
|  | def __init__(self, path): | 
|  | """ | 
|  | Args: | 
|  | path (str): the path string to wrap. | 
|  | """ | 
|  | self.is_prefix = path[-1] == '*' | 
|  | if self.is_prefix: | 
|  | self.path = path[:-1] | 
|  | else: | 
|  | self.path = path | 
|  |  | 
|  | def __lt__(self, other): | 
|  |  | 
|  | # if were both suffixed the smallest string | 
|  | # is 'bigger' | 
|  | if self.is_prefix and other.is_prefix: | 
|  | result = len(self.path) > len(other.path) | 
|  | # If I am an the suffix match, im bigger | 
|  | elif self.is_prefix: | 
|  | result = False | 
|  | # If other is the suffix match, he's bigger | 
|  | elif other.is_prefix: | 
|  | result = True | 
|  | # Alphabetical | 
|  | else: | 
|  | result = self.path < other.path | 
|  | return result | 
|  |  | 
|  | return StringWrapper(fs_config.path) | 
|  |  | 
|  | @staticmethod | 
|  | def _handle_dup_and_add(name, file_name, section_name, seen): | 
|  | """Tracks and detects duplicates. Internal use only. | 
|  |  | 
|  | Calls sys.exit() on a duplicate. | 
|  |  | 
|  | Args: | 
|  | name (str): The name to use in the error reporting. The pretty | 
|  | name for the section. | 
|  | file_name (str): The file currently being parsed. | 
|  | section_name (str): The name of the section. This would be path | 
|  | or identifier depending on what's being parsed. | 
|  | seen (dict): The dictionary of seen things to check against. | 
|  | """ | 
|  | if section_name in seen: | 
|  | dups = '"' + seen[section_name] + '" and ' | 
|  | dups += file_name | 
|  | sys.exit('Duplicate %s "%s" found in files: %s' % | 
|  | (name, section_name, dups)) | 
|  |  | 
|  | seen[section_name] = file_name | 
|  |  | 
|  |  | 
|  | class BaseGenerator(object): | 
|  | """Interface for Generators. | 
|  |  | 
|  | Base class for generators, generators should implement | 
|  | these method stubs. | 
|  | """ | 
|  |  | 
|  | def add_opts(self, opt_group): | 
|  | """Used to add per-generator options to the command line. | 
|  |  | 
|  | Args: | 
|  | opt_group (argument group object): The argument group to append to. | 
|  | See the ArgParse docs for more details. | 
|  | """ | 
|  |  | 
|  | raise NotImplementedError("Not Implemented") | 
|  |  | 
|  | def __call__(self, args): | 
|  | """This is called to do whatever magic the generator does. | 
|  |  | 
|  | Args: | 
|  | args (dict): The arguments from ArgParse as a dictionary. | 
|  | ie if you specified an argument of foo in add_opts, access | 
|  | it via args['foo'] | 
|  | """ | 
|  |  | 
|  | raise NotImplementedError("Not Implemented") | 
|  |  | 
|  |  | 
|  | @generator('fsconfig') | 
|  | class FSConfigGen(BaseGenerator): | 
|  | """Generates the android_filesystem_config.h file. | 
|  |  | 
|  | Output is  used in generating fs_config_files and fs_config_dirs. | 
|  | """ | 
|  |  | 
|  | def __init__(self, *args, **kwargs): | 
|  | BaseGenerator.__init__(args, kwargs) | 
|  |  | 
|  | self._oem_parser = None | 
|  | self._base_parser = None | 
|  | self._friendly_to_aid = None | 
|  | self._id_to_aid = None | 
|  | self._capability_parser = None | 
|  |  | 
|  | self._partition = None | 
|  | self._all_partitions = None | 
|  | self._out_file = None | 
|  | self._generate_files = False | 
|  | self._generate_dirs = False | 
|  |  | 
|  | def add_opts(self, opt_group): | 
|  |  | 
|  | 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') | 
|  |  | 
|  | opt_group.add_argument( | 
|  | '--capability-header', | 
|  | required=True, | 
|  | help='A capability.h file to parse capability defines from') | 
|  |  | 
|  | opt_group.add_argument( | 
|  | '--partition', | 
|  | required=True, | 
|  | help='Partition to generate contents for') | 
|  |  | 
|  | opt_group.add_argument( | 
|  | '--all-partitions', | 
|  | help='Comma separated list of all possible partitions, used to' | 
|  | ' ignore these partitions when generating the output for the system partition' | 
|  | ) | 
|  |  | 
|  | opt_group.add_argument( | 
|  | '--files', action='store_true', help='Output fs_config_files') | 
|  |  | 
|  | opt_group.add_argument( | 
|  | '--dirs', action='store_true', help='Output fs_config_dirs') | 
|  |  | 
|  | opt_group.add_argument('--out_file', required=True, help='Output file') | 
|  |  | 
|  | def __call__(self, args): | 
|  |  | 
|  | self._capability_parser = CapabilityHeaderParser( | 
|  | args['capability_header']) | 
|  | self._base_parser = AIDHeaderParser(args['aid_header']) | 
|  | self._oem_parser = FSConfigFileParser(args['fsconfig'], | 
|  | self._base_parser.ranges) | 
|  |  | 
|  | self._partition = args['partition'] | 
|  | self._all_partitions = args['all_partitions'] | 
|  |  | 
|  | self._out_file = args['out_file'] | 
|  |  | 
|  | self._generate_files = args['files'] | 
|  | self._generate_dirs = args['dirs'] | 
|  |  | 
|  | if self._generate_files and self._generate_dirs: | 
|  | sys.exit('Only one of --files or --dirs can be provided') | 
|  |  | 
|  | if not self._generate_files and not self._generate_dirs: | 
|  | sys.exit('One of --files or --dirs must be provided') | 
|  |  | 
|  | base_aids = self._base_parser.aids | 
|  | oem_aids = self._oem_parser.aids | 
|  |  | 
|  | # Detect name collisions on AIDs. Since friendly works as the | 
|  | # identifier for collision testing and we need friendly later on for | 
|  | # name resolution, just calculate and use friendly. | 
|  | # {aid.friendly: aid for aid in base_aids} | 
|  | base_friendly = {aid.friendly: aid for aid in base_aids} | 
|  | oem_friendly = {aid.friendly: aid for aid in oem_aids} | 
|  |  | 
|  | base_set = set(base_friendly.keys()) | 
|  | oem_set = set(oem_friendly.keys()) | 
|  |  | 
|  | common = base_set & oem_set | 
|  |  | 
|  | if common: | 
|  | emsg = 'Following AID Collisions detected for: \n' | 
|  | for friendly in common: | 
|  | base = base_friendly[friendly] | 
|  | oem = oem_friendly[friendly] | 
|  | emsg += ( | 
|  | 'Identifier: "%s" Friendly Name: "%s" ' | 
|  | 'found in file "%s" and "%s"' % | 
|  | (base.identifier, base.friendly, base.found, oem.found)) | 
|  | sys.exit(emsg) | 
|  |  | 
|  | self._friendly_to_aid = oem_friendly | 
|  | self._friendly_to_aid.update(base_friendly) | 
|  |  | 
|  | self._id_to_aid = {aid.identifier: aid for aid in base_aids} | 
|  | self._id_to_aid.update({aid.identifier: aid for aid in oem_aids}) | 
|  |  | 
|  | self._generate() | 
|  |  | 
|  | def _to_fs_entry(self, fs_config, out_file): | 
|  | """Converts an FSConfig entry to an fs entry. | 
|  |  | 
|  | Writes the fs_config contents to the output file. | 
|  |  | 
|  | Calls sys.exit() on error. | 
|  |  | 
|  | Args: | 
|  | fs_config (FSConfig): The entry to convert to write to file. | 
|  | file (File): The file to write to. | 
|  | """ | 
|  |  | 
|  | # Get some short names | 
|  | mode = fs_config.mode | 
|  | user = fs_config.user | 
|  | group = fs_config.group | 
|  | caps = fs_config.caps | 
|  | path = fs_config.path | 
|  |  | 
|  | emsg = 'Cannot convert "%s" to identifier!' | 
|  |  | 
|  | # convert mode from octal string to integer | 
|  | mode = int(mode, 8) | 
|  |  | 
|  | # remap names to values | 
|  | if AID.is_friendly(user): | 
|  | if user not in self._friendly_to_aid: | 
|  | sys.exit(emsg % user) | 
|  | user = self._friendly_to_aid[user].value | 
|  | else: | 
|  | if user not in self._id_to_aid: | 
|  | sys.exit(emsg % user) | 
|  | user = self._id_to_aid[user].value | 
|  |  | 
|  | if AID.is_friendly(group): | 
|  | if group not in self._friendly_to_aid: | 
|  | sys.exit(emsg % group) | 
|  | group = self._friendly_to_aid[group].value | 
|  | else: | 
|  | if group not in self._id_to_aid: | 
|  | sys.exit(emsg % group) | 
|  | group = self._id_to_aid[group].value | 
|  |  | 
|  | caps_dict = self._capability_parser.caps | 
|  |  | 
|  | caps_value = 0 | 
|  |  | 
|  | try: | 
|  | # test if caps is an int | 
|  | caps_value = int(caps, 0) | 
|  | except ValueError: | 
|  | caps_split = caps.split(',') | 
|  | for cap in caps_split: | 
|  | if cap not in caps_dict: | 
|  | sys.exit('Unknown cap "%s" found!' % cap) | 
|  | caps_value += 1 << caps_dict[cap] | 
|  |  | 
|  | path_length_with_null = len(path) + 1 | 
|  | path_length_aligned_64 = (path_length_with_null + 7) & ~7 | 
|  | # 16 bytes of header plus the path length with alignment | 
|  | length = 16 + path_length_aligned_64 | 
|  |  | 
|  | length_binary = bytearray(ctypes.c_uint16(length)) | 
|  | mode_binary = bytearray(ctypes.c_uint16(mode)) | 
|  | user_binary = bytearray(ctypes.c_uint16(int(user, 0))) | 
|  | group_binary = bytearray(ctypes.c_uint16(int(group, 0))) | 
|  | caps_binary = bytearray(ctypes.c_uint64(caps_value)) | 
|  | path_binary = ctypes.create_string_buffer(path, | 
|  | path_length_aligned_64).raw | 
|  |  | 
|  | out_file.write(length_binary) | 
|  | out_file.write(mode_binary) | 
|  | out_file.write(user_binary) | 
|  | out_file.write(group_binary) | 
|  | out_file.write(caps_binary) | 
|  | out_file.write(path_binary) | 
|  |  | 
|  | def _emit_entry(self, fs_config): | 
|  | """Returns a boolean whether or not to emit the input fs_config""" | 
|  |  | 
|  | path = fs_config.path | 
|  |  | 
|  | if self._partition == 'system': | 
|  | for skip_partition in self._all_partitions.split(','): | 
|  | if path.startswith(skip_partition) or path.startswith( | 
|  | 'system/' + skip_partition): | 
|  | return False | 
|  | return True | 
|  | else: | 
|  | if path.startswith( | 
|  | self._partition) or path.startswith('system/' + | 
|  | self._partition): | 
|  | return True | 
|  | return False | 
|  |  | 
|  | def _generate(self): | 
|  | """Generates an OEM android_filesystem_config.h header file to stdout. | 
|  |  | 
|  | Args: | 
|  | files ([FSConfig]): A list of FSConfig objects for file entries. | 
|  | dirs ([FSConfig]): A list of FSConfig objects for directory | 
|  | entries. | 
|  | aids ([AIDS]): A list of AID objects for Android Id entries. | 
|  | """ | 
|  | dirs = self._oem_parser.dirs | 
|  | files = self._oem_parser.files | 
|  |  | 
|  | if self._generate_files: | 
|  | with open(self._out_file, 'wb') as open_file: | 
|  | for fs_config in files: | 
|  | if self._emit_entry(fs_config): | 
|  | self._to_fs_entry(fs_config, open_file) | 
|  |  | 
|  | if self._generate_dirs: | 
|  | with open(self._out_file, 'wb') as open_file: | 
|  | for dir_entry in dirs: | 
|  | if self._emit_entry(dir_entry): | 
|  | self._to_fs_entry(dir_entry, open_file) | 
|  |  | 
|  |  | 
|  | @generator('aidarray') | 
|  | class AIDArrayGen(BaseGenerator): | 
|  | """Generates the android_id static array.""" | 
|  |  | 
|  | _GENERATED = ('/*\n' | 
|  | ' * THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY!\n' | 
|  | ' */') | 
|  |  | 
|  | _INCLUDE = '#include <private/android_filesystem_config.h>' | 
|  |  | 
|  | # Note that the android_id name field is of type 'const char[]' instead of | 
|  | # 'const char*'.  While this seems less straightforward as we need to | 
|  | # calculate the max length of all names, this allows the entire android_ids | 
|  | # table to be placed in .rodata section instead of .data.rel.ro section, | 
|  | # resulting in less memory pressure. | 
|  | _STRUCT_FS_CONFIG = textwrap.dedent(""" | 
|  | struct android_id_info { | 
|  | const char name[%d]; | 
|  | unsigned aid; | 
|  | };""") | 
|  |  | 
|  | _OPEN_ID_ARRAY = 'static const struct android_id_info android_ids[] = {' | 
|  |  | 
|  | _ID_ENTRY = '    { "%s", %s },' | 
|  |  | 
|  | _CLOSE_FILE_STRUCT = '};' | 
|  |  | 
|  | _COUNT = ('#define android_id_count \\\n' | 
|  | '    (sizeof(android_ids) / sizeof(android_ids[0]))') | 
|  |  | 
|  | def add_opts(self, opt_group): | 
|  |  | 
|  | opt_group.add_argument( | 
|  | 'hdrfile', help='The android_filesystem_config.h' | 
|  | 'file to parse') | 
|  |  | 
|  | def __call__(self, args): | 
|  |  | 
|  | hdr = AIDHeaderParser(args['hdrfile']) | 
|  | max_name_length = max(len(aid.friendly) + 1 for aid in hdr.aids) | 
|  |  | 
|  | print AIDArrayGen._GENERATED | 
|  | print | 
|  | print AIDArrayGen._INCLUDE | 
|  | print | 
|  | print AIDArrayGen._STRUCT_FS_CONFIG % max_name_length | 
|  | print | 
|  | print AIDArrayGen._OPEN_ID_ARRAY | 
|  |  | 
|  | for aid in hdr.aids: | 
|  | print AIDArrayGen._ID_ENTRY % (aid.friendly, aid.identifier) | 
|  |  | 
|  | print AIDArrayGen._CLOSE_FILE_STRUCT | 
|  | print | 
|  | print AIDArrayGen._COUNT | 
|  | print | 
|  |  | 
|  |  | 
|  | @generator('oemaid') | 
|  | class OEMAidGen(BaseGenerator): | 
|  | """Generates the OEM AID_<name> value header file.""" | 
|  |  | 
|  | _GENERATED = ('/*\n' | 
|  | ' * THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY!\n' | 
|  | ' */') | 
|  |  | 
|  | _GENERIC_DEFINE = "#define %s\t%s" | 
|  |  | 
|  | _FILE_COMMENT = '// Defined in file: \"%s\"' | 
|  |  | 
|  | # Intentional trailing newline for readability. | 
|  | _FILE_IFNDEF_DEFINE = ('#ifndef GENERATED_OEM_AIDS_H_\n' | 
|  | '#define GENERATED_OEM_AIDS_H_\n') | 
|  |  | 
|  | _FILE_ENDIF = '#endif' | 
|  |  | 
|  | def __init__(self): | 
|  |  | 
|  | self._old_file = None | 
|  |  | 
|  | def add_opts(self, opt_group): | 
|  |  | 
|  | 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): | 
|  |  | 
|  | hdr_parser = AIDHeaderParser(args['aid_header']) | 
|  |  | 
|  | parser = FSConfigFileParser(args['fsconfig'], hdr_parser.ranges) | 
|  |  | 
|  | print OEMAidGen._GENERATED | 
|  |  | 
|  | print OEMAidGen._FILE_IFNDEF_DEFINE | 
|  |  | 
|  | for aid in parser.aids: | 
|  | self._print_aid(aid) | 
|  | print | 
|  |  | 
|  | print OEMAidGen._FILE_ENDIF | 
|  |  | 
|  | def _print_aid(self, aid): | 
|  | """Prints a valid #define AID identifier to stdout. | 
|  |  | 
|  | Args: | 
|  | aid to print | 
|  | """ | 
|  |  | 
|  | # print the source file location of the AID | 
|  | found_file = aid.found | 
|  | if found_file != self._old_file: | 
|  | print OEMAidGen._FILE_COMMENT % found_file | 
|  | self._old_file = found_file | 
|  |  | 
|  | print OEMAidGen._GENERIC_DEFINE % (aid.identifier, aid.value) | 
|  |  | 
|  |  | 
|  | @generator('passwd') | 
|  | class PasswdGen(BaseGenerator): | 
|  | """Generates the /etc/passwd file per man (5) passwd.""" | 
|  |  | 
|  | def __init__(self): | 
|  |  | 
|  | self._old_file = None | 
|  |  | 
|  | def add_opts(self, opt_group): | 
|  |  | 
|  | 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') | 
|  |  | 
|  | opt_group.add_argument( | 
|  | '--partition', | 
|  | required=True, | 
|  | help= | 
|  | 'Filter the input file and only output entries for the given partition.' | 
|  | ) | 
|  |  | 
|  | def __call__(self, args): | 
|  |  | 
|  | hdr_parser = AIDHeaderParser(args['aid_header']) | 
|  |  | 
|  | parser = FSConfigFileParser(args['fsconfig'], hdr_parser.ranges) | 
|  |  | 
|  | filter_partition = args['partition'] | 
|  |  | 
|  | aids = parser.aids | 
|  |  | 
|  | # nothing to do if no aids defined | 
|  | if not aids: | 
|  | return | 
|  |  | 
|  | aids_by_partition = {} | 
|  | partitions = hdr_parser.ranges.keys() | 
|  | partitions.sort(key=len, reverse=True) | 
|  |  | 
|  | for aid in aids: | 
|  | for partition in partitions: | 
|  | if aid.friendly.startswith(partition): | 
|  | if partition in aids_by_partition: | 
|  | aids_by_partition[partition].append(aid) | 
|  | else: | 
|  | aids_by_partition[partition] = [aid] | 
|  | break | 
|  |  | 
|  | if filter_partition in aids_by_partition: | 
|  | for aid in aids_by_partition[filter_partition]: | 
|  | self._print_formatted_line(aid) | 
|  |  | 
|  | def _print_formatted_line(self, aid): | 
|  | """Prints the aid to stdout in the passwd format. Internal use only. | 
|  |  | 
|  | Colon delimited: | 
|  | login name, friendly name | 
|  | encrypted password (optional) | 
|  | uid (int) | 
|  | gid (int) | 
|  | User name or comment field | 
|  | home directory | 
|  | interpreter (optional) | 
|  |  | 
|  | Args: | 
|  | aid (AID): The aid to print. | 
|  | """ | 
|  | if self._old_file != aid.found: | 
|  | self._old_file = aid.found | 
|  |  | 
|  | try: | 
|  | logon, uid = Utils.get_login_and_uid_cleansed(aid) | 
|  | except ValueError as exception: | 
|  | sys.exit(exception) | 
|  |  | 
|  | print "%s::%s:%s::/:%s" % (logon, uid, uid, aid.login_shell) | 
|  |  | 
|  |  | 
|  | @generator('group') | 
|  | class GroupGen(PasswdGen): | 
|  | """Generates the /etc/group file per man (5) group.""" | 
|  |  | 
|  | # Overrides parent | 
|  | def _print_formatted_line(self, aid): | 
|  | """Prints the aid to stdout in the group format. Internal use only. | 
|  |  | 
|  | Formatted (per man 5 group) like: | 
|  | group_name:password:GID:user_list | 
|  |  | 
|  | Args: | 
|  | aid (AID): The aid to print. | 
|  | """ | 
|  | if self._old_file != aid.found: | 
|  | self._old_file = aid.found | 
|  |  | 
|  | try: | 
|  | logon, uid = Utils.get_login_and_uid_cleansed(aid) | 
|  | except ValueError as exception: | 
|  | sys.exit(exception) | 
|  |  | 
|  | print "%s::%s:" % (logon, uid) | 
|  |  | 
|  |  | 
|  | @generator('print') | 
|  | class PrintGen(BaseGenerator): | 
|  | """Prints just the constants and values, separated by spaces, in an easy to | 
|  | parse format for use by other scripts. | 
|  |  | 
|  | Each line is just the identifier and the value, separated by a space. | 
|  | """ | 
|  |  | 
|  | def add_opts(self, opt_group): | 
|  | opt_group.add_argument( | 
|  | 'aid-header', help='An android_filesystem_config.h file.') | 
|  |  | 
|  | def __call__(self, args): | 
|  |  | 
|  | hdr_parser = AIDHeaderParser(args['aid-header']) | 
|  | aids = hdr_parser.aids | 
|  |  | 
|  | aids.sort(key=lambda item: int(item.normalized_value)) | 
|  |  | 
|  | for aid in aids: | 
|  | print '%s %s' % (aid.identifier, aid.normalized_value) | 
|  |  | 
|  |  | 
|  | def main(): | 
|  | """Main entry point for execution.""" | 
|  |  | 
|  | opt_parser = argparse.ArgumentParser( | 
|  | description='A tool for parsing fsconfig config files and producing' + | 
|  | 'digestable outputs.') | 
|  | subparser = opt_parser.add_subparsers(help='generators') | 
|  |  | 
|  | gens = generator.get() | 
|  |  | 
|  | # for each gen, instantiate and add them as an option | 
|  | for name, gen in gens.iteritems(): | 
|  |  | 
|  | generator_option_parser = subparser.add_parser(name, help=gen.__doc__) | 
|  | generator_option_parser.set_defaults(which=name) | 
|  |  | 
|  | opt_group = generator_option_parser.add_argument_group(name + | 
|  | ' options') | 
|  | gen.add_opts(opt_group) | 
|  |  | 
|  | args = opt_parser.parse_args() | 
|  |  | 
|  | args_as_dict = vars(args) | 
|  | which = args_as_dict['which'] | 
|  | del args_as_dict['which'] | 
|  |  | 
|  | gens[which](args_as_dict) | 
|  |  | 
|  |  | 
|  | if __name__ == '__main__': | 
|  | main() |