fs_config: modularize fs_config_generator
This internally structures fs_config_generator.py to be able to plug in generators to produce different outputs. This prepares this tool for group and pwd file outputs. Test: Checked diff and hash of before and after files. Change-Id: Ie558518ac227dd946d70ab48027698b72a9bc94a Signed-off-by: William Roberts <william.c.roberts@intel.com>
This commit is contained in:
parent
31543713fc
commit
11c29283ec
|
@ -84,7 +84,7 @@ ifneq ($(TARGET_FS_CONFIG_GEN),)
|
||||||
gen := $(local-generated-sources-dir)/$(ANDROID_FS_CONFIG_H)
|
gen := $(local-generated-sources-dir)/$(ANDROID_FS_CONFIG_H)
|
||||||
$(gen): PRIVATE_LOCAL_PATH := $(LOCAL_PATH)
|
$(gen): PRIVATE_LOCAL_PATH := $(LOCAL_PATH)
|
||||||
$(gen): PRIVATE_TARGET_FS_CONFIG_GEN := $(TARGET_FS_CONFIG_GEN)
|
$(gen): PRIVATE_TARGET_FS_CONFIG_GEN := $(TARGET_FS_CONFIG_GEN)
|
||||||
$(gen): PRIVATE_CUSTOM_TOOL = $(PRIVATE_LOCAL_PATH)/fs_config_generator.py $(PRIVATE_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): $(TARGET_FS_CONFIG_GEN) $(LOCAL_PATH)/fs_config_generator.py
|
||||||
$(transform-generated-source)
|
$(transform-generated-source)
|
||||||
|
|
||||||
|
|
|
@ -1,294 +1,662 @@
|
||||||
#!/usr/bin/env python
|
#!/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 ConfigParser
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
import textwrap
|
||||||
|
|
||||||
|
|
||||||
GENERATED = '''
|
# Lowercase generator used to be inline with @staticmethod.
|
||||||
/*
|
class generator(object): # pylint: disable=invalid-name
|
||||||
* THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY
|
"""A decorator class to add commandlet plugins.
|
||||||
*/
|
|
||||||
'''
|
|
||||||
|
|
||||||
INCLUDE = '#include <private/android_filesystem_config.h>'
|
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.
|
||||||
|
|
||||||
DEFINE_NO_DIRS = '#define NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS\n'
|
For instance, to add a new generator
|
||||||
DEFINE_NO_FILES = '#define NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_FILES\n'
|
called foo and have it added just do this:
|
||||||
|
|
||||||
DEFAULT_WARNING = '#warning No device-supplied android_filesystem_config.h, using empty default.'
|
@generator("foo")
|
||||||
|
class FooGen(object):
|
||||||
|
...
|
||||||
|
"""
|
||||||
|
_generators = {}
|
||||||
|
|
||||||
NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS_ENTRY = '{ 00000, AID_ROOT, AID_ROOT, 0, "system/etc/fs_config_dirs" },'
|
def __init__(self, gen):
|
||||||
NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_FILES_ENTRY = '{ 00000, AID_ROOT, AID_ROOT, 0, "system/etc/fs_config_files" },'
|
"""
|
||||||
|
Args:
|
||||||
|
gen (str): The name of the generator to add.
|
||||||
|
|
||||||
IFDEF_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS = '#ifdef NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS'
|
Raises:
|
||||||
ENDIF = '#endif'
|
ValueError: If there is a similarly named generator already added.
|
||||||
|
|
||||||
OPEN_FILE_STRUCT = 'static const struct fs_path_config android_device_files[] = {'
|
"""
|
||||||
OPEN_DIR_STRUCT = 'static const struct fs_path_config android_device_dirs[] = {'
|
self._gen = gen
|
||||||
CLOSE_FILE_STRUCT = '};'
|
|
||||||
|
|
||||||
GENERIC_DEFINE = "#define %s\t%s"
|
if gen in generator._generators:
|
||||||
|
raise ValueError('Duplicate generator name: ' + gen)
|
||||||
|
|
||||||
FILE_COMMENT = '// Defined in file: \"%s\"'
|
generator._generators[gen] = None
|
||||||
|
|
||||||
# from system/core/include/private/android_filesystem_config.h
|
def __call__(self, cls):
|
||||||
AID_OEM_RESERVED_RANGES = [
|
|
||||||
(2900, 2999),
|
generator._generators[self._gen] = cls()
|
||||||
(5000, 5999),
|
return cls
|
||||||
]
|
|
||||||
|
@staticmethod
|
||||||
|
def get():
|
||||||
|
"""Gets the list of generators.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The list of registered generators.
|
||||||
|
"""
|
||||||
|
return generator._generators
|
||||||
|
|
||||||
|
|
||||||
AID_MATCH = re.compile('AID_[a-zA-Z]+')
|
class AID(object):
|
||||||
|
"""This class represents an Android ID or an AID.
|
||||||
|
|
||||||
def handle_aid(file_name, section_name, config, aids, seen_aids):
|
Attributes:
|
||||||
value = config.get(section_name, 'value')
|
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.
|
||||||
|
"""
|
||||||
|
|
||||||
errmsg = '%s for: \"' + section_name + '" file: \"' + file_name + '\"'
|
def __init__(self, identifier, value, found):
|
||||||
|
"""
|
||||||
|
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.
|
||||||
|
|
||||||
if not value:
|
Raises:
|
||||||
raise Exception(errmsg % 'Found specified but unset "value"')
|
ValueError: if value is not a valid string number as processed by
|
||||||
|
int(x, 0)
|
||||||
v = convert_int(value)
|
"""
|
||||||
if not v:
|
self.identifier = identifier
|
||||||
raise Exception(errmsg % ('Invalid "value", not a number, got: \"%s\"' % value))
|
self.value = value
|
||||||
|
self.found = found
|
||||||
# Values must be within OEM range
|
self.normalized_value = str(int(value, 0))
|
||||||
if not any(lower <= v <= upper for (lower, upper) in AID_OEM_RESERVED_RANGES):
|
|
||||||
s = '"value" not in valid range %s, got: %s'
|
|
||||||
s = s % (str(AID_OEM_RESERVED_RANGES), value)
|
|
||||||
raise Exception(errmsg % s)
|
|
||||||
|
|
||||||
# use the normalized int value in the dict and detect
|
|
||||||
# duplicate definitions of the same vallue
|
|
||||||
v = str(v)
|
|
||||||
if v in seen_aids[1]:
|
|
||||||
# map of value to aid name
|
|
||||||
a = seen_aids[1][v]
|
|
||||||
|
|
||||||
# aid name to file
|
|
||||||
f = seen_aids[0][a]
|
|
||||||
|
|
||||||
s = 'Duplicate AID value "%s" found on AID: "%s".' % (value, seen_aids[1][v])
|
|
||||||
s += ' Previous found in file: "%s."' % f
|
|
||||||
raise Exception(errmsg % s)
|
|
||||||
|
|
||||||
seen_aids[1][v] = section_name
|
|
||||||
|
|
||||||
# Append a tuple of (AID_*, base10(value), str(value))
|
|
||||||
# We keep the str 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.
|
|
||||||
aids.append((file_name, section_name, v, value))
|
|
||||||
|
|
||||||
def convert_int(num):
|
|
||||||
|
|
||||||
try:
|
|
||||||
if num.startswith('0x'):
|
|
||||||
return int(num, 16)
|
|
||||||
elif num.startswith('0b'):
|
|
||||||
return int(num, 2)
|
|
||||||
elif num.startswith('0'):
|
|
||||||
return int(num, 8)
|
|
||||||
else:
|
|
||||||
return int(num, 10)
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
return None
|
|
||||||
|
|
||||||
def handle_path(file_name, section_name, config, files, dirs):
|
|
||||||
|
|
||||||
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:
|
|
||||||
raise Exception(errmsg % 'mode')
|
|
||||||
|
|
||||||
if not user:
|
|
||||||
raise Exception(errmsg % 'user')
|
|
||||||
|
|
||||||
if not group:
|
|
||||||
raise Exception(errmsg % 'group')
|
|
||||||
|
|
||||||
if not caps:
|
|
||||||
raise Exception(errmsg % 'caps')
|
|
||||||
|
|
||||||
caps = caps.split()
|
|
||||||
|
|
||||||
tmp = []
|
|
||||||
for x in caps:
|
|
||||||
if convert_int(x):
|
|
||||||
tmp.append('(' + x + ')')
|
|
||||||
else:
|
|
||||||
tmp.append('(1ULL << CAP_' + x.upper() + ')')
|
|
||||||
|
|
||||||
caps = tmp
|
|
||||||
|
|
||||||
path = '"' + section_name + '"'
|
|
||||||
|
|
||||||
if len(mode) == 3:
|
|
||||||
mode = '0' + mode
|
|
||||||
|
|
||||||
try:
|
|
||||||
int(mode, 8)
|
|
||||||
except:
|
|
||||||
raise Exception('Mode must be octal characters, got: "' + mode + '"')
|
|
||||||
|
|
||||||
if len(mode) != 4:
|
|
||||||
raise Exception('Mode must be 3 or 4 characters, got: "' + mode + '"')
|
|
||||||
|
|
||||||
|
|
||||||
caps = '|'.join(caps)
|
class FSConfig(object):
|
||||||
|
"""Represents a filesystem config array entry.
|
||||||
|
|
||||||
x = [ mode, user, group, caps, section_name ]
|
Represents a file system configuration entry for specifying
|
||||||
if section_name[-1] == '/':
|
file system capabilities.
|
||||||
dirs.append((file_name, x))
|
|
||||||
else:
|
|
||||||
files.append((file_name, x))
|
|
||||||
|
|
||||||
def handle_dup(name, file_name, section_name, seen):
|
Attributes:
|
||||||
if section_name in seen:
|
mode (str): The mode of the file or directory.
|
||||||
dups = '"' + seen[section_name] + '" and '
|
user (str): The uid or #define identifier (AID_SYSTEM)
|
||||||
dups += file_name
|
group (str): The gid or #define identifier (AID_SYSTEM)
|
||||||
raise Exception('Duplicate ' + name + ' "' + section_name + '" found in files: ' + dups)
|
caps (str): The capability set.
|
||||||
|
filename (str): The file it was found in.
|
||||||
|
"""
|
||||||
|
|
||||||
def parse(file_name, files, dirs, aids, seen_paths, seen_aids):
|
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.
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
# 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):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
config_files ([str]): The list of config.fs files to parse.
|
||||||
|
Note the filename is not important.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self._files = []
|
||||||
|
self._dirs = []
|
||||||
|
self._aids = []
|
||||||
|
|
||||||
|
self._seen_paths = {}
|
||||||
|
# (name to file, value to aid)
|
||||||
|
self._seen_aids = ({}, {})
|
||||||
|
|
||||||
|
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 = ConfigParser.ConfigParser()
|
||||||
config.read(file_name)
|
config.read(file_name)
|
||||||
|
|
||||||
for s in config.sections():
|
for section in config.sections():
|
||||||
|
|
||||||
if AID_MATCH.match(s) and config.has_option(s, 'value'):
|
if FSConfigFileParser._AID_MATCH.match(
|
||||||
handle_dup('AID', file_name, s, seen_aids[0])
|
section) and config.has_option(section, 'value'):
|
||||||
seen_aids[0][s] = file_name
|
FSConfigFileParser._handle_dup('AID', file_name, section,
|
||||||
handle_aid(file_name, s, config, aids, seen_aids)
|
self._seen_aids[0])
|
||||||
|
self._seen_aids[0][section] = file_name
|
||||||
|
self._handle_aid(file_name, section, config)
|
||||||
else:
|
else:
|
||||||
handle_dup('path', file_name, s, seen_paths)
|
FSConfigFileParser._handle_dup('path', file_name, section,
|
||||||
seen_paths[s] = file_name
|
self._seen_paths)
|
||||||
handle_path(file_name, s, config, files, dirs)
|
self._seen_paths[section] = file_name
|
||||||
|
self._handle_path(file_name, section, config)
|
||||||
|
|
||||||
def generate(files, dirs, aids):
|
# sort entries:
|
||||||
print GENERATED
|
# * specified path before prefix match
|
||||||
print INCLUDE
|
# ** ie foo before f*
|
||||||
print
|
# * 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)
|
||||||
|
|
||||||
are_dirs = len(dirs) > 0
|
# sort on value of (file_name, name, value, strvalue)
|
||||||
are_files = len(files) > 0
|
# This is only cosmetic so AIDS are arranged in ascending order
|
||||||
are_aids = len(aids) > 0
|
# within the generated file.
|
||||||
|
self._aids.sort(key=lambda item: item.normalized_value)
|
||||||
|
|
||||||
if are_aids:
|
def _handle_aid(self, file_name, section_name, config):
|
||||||
for a in aids:
|
"""Verifies an AID entry and adds it to the aid list.
|
||||||
# use the preserved str value
|
|
||||||
print FILE_COMMENT % a[0]
|
|
||||||
print GENERIC_DEFINE % (a[1], a[2])
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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)
|
||||||
|
except ValueError:
|
||||||
|
sys.exit(
|
||||||
|
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):
|
||||||
|
emsg = '"value" not in valid range %s, got: %s'
|
||||||
|
emsg = emsg % (str(FSConfigFileParser._AID_OEM_RESERVED_RANGES),
|
||||||
|
value)
|
||||||
|
sys.exit(error_message(emsg))
|
||||||
|
|
||||||
|
# use the normalized int value in the dict and detect
|
||||||
|
# duplicate definitions of the same value
|
||||||
|
if aid.normalized_value in self._seen_aids[1]:
|
||||||
|
# map of value to aid name
|
||||||
|
aid = self._seen_aids[1][aid.normalized_value]
|
||||||
|
|
||||||
|
# aid name to file
|
||||||
|
file_name = self._seen_aids[0][aid]
|
||||||
|
|
||||||
|
emsg = 'Duplicate AID value "%s" found on AID: "%s".' % (
|
||||||
|
value, self._seen_aids[1][aid.normalized_value])
|
||||||
|
emsg += ' Previous found in file: "%s."' % file_name
|
||||||
|
sys.exit(error_message(emsg))
|
||||||
|
|
||||||
|
self._seen_aids[1][aid.normalized_value] = section_name
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
"""
|
||||||
|
|
||||||
|
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('(1ULL << 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(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.
|
||||||
|
"""
|
||||||
|
|
||||||
|
_GENERATED = textwrap.dedent("""\
|
||||||
|
/*
|
||||||
|
* THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY
|
||||||
|
*/
|
||||||
|
""")
|
||||||
|
|
||||||
|
_INCLUDE = '#include <private/android_filesystem_config.h>'
|
||||||
|
|
||||||
|
_DEFINE_NO_DIRS = '#define NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS'
|
||||||
|
_DEFINE_NO_FILES = '#define NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_FILES'
|
||||||
|
|
||||||
|
_DEFAULT_WARNING = (
|
||||||
|
'#warning No device-supplied android_filesystem_config.h,'
|
||||||
|
' using empty default.')
|
||||||
|
|
||||||
|
# Long names.
|
||||||
|
# pylint: disable=invalid-name
|
||||||
|
_NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS_ENTRY = (
|
||||||
|
'{ 00000, AID_ROOT, AID_ROOT, 0,'
|
||||||
|
'"system/etc/fs_config_dirs" },')
|
||||||
|
|
||||||
|
_NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_FILES_ENTRY = (
|
||||||
|
'{ 00000, AID_ROOT, AID_ROOT, 0,'
|
||||||
|
'"system/etc/fs_config_files" },')
|
||||||
|
|
||||||
|
_IFDEF_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS = (
|
||||||
|
'#ifdef NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS')
|
||||||
|
# pylint: enable=invalid-name
|
||||||
|
|
||||||
|
_ENDIF = '#endif'
|
||||||
|
|
||||||
|
_OPEN_FILE_STRUCT = (
|
||||||
|
'static const struct fs_path_config android_device_files[] = {')
|
||||||
|
|
||||||
|
_OPEN_DIR_STRUCT = (
|
||||||
|
'static const struct fs_path_config android_device_dirs[] = {')
|
||||||
|
|
||||||
|
_CLOSE_FILE_STRUCT = '};'
|
||||||
|
|
||||||
|
_GENERIC_DEFINE = "#define %s\t%s"
|
||||||
|
|
||||||
|
_FILE_COMMENT = '// Defined in file: \"%s\"'
|
||||||
|
|
||||||
|
def add_opts(self, opt_group):
|
||||||
|
|
||||||
|
opt_group.add_argument(
|
||||||
|
'fsconfig', nargs='+', help='The list of fsconfig files to parse')
|
||||||
|
|
||||||
|
def __call__(self, args):
|
||||||
|
|
||||||
|
parser = FSConfigFileParser(args['fsconfig'])
|
||||||
|
FSConfigGen._generate(parser.files, parser.dirs, parser.aids)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _to_fs_entry(fs_config):
|
||||||
|
"""
|
||||||
|
Given an FSConfig entry, converts it to a proper
|
||||||
|
array entry for the array entry.
|
||||||
|
|
||||||
|
{ mode, user, group, caps, "path" },
|
||||||
|
|
||||||
|
Args:
|
||||||
|
fs_config (FSConfig): The entry to convert to
|
||||||
|
a valid C array entry.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Get some short names
|
||||||
|
mode = fs_config.mode
|
||||||
|
user = fs_config.user
|
||||||
|
group = fs_config.group
|
||||||
|
fname = fs_config.filename
|
||||||
|
caps = fs_config.caps
|
||||||
|
path = fs_config.path
|
||||||
|
|
||||||
|
fmt = '{ %s, %s, %s, %s, "%s" },'
|
||||||
|
|
||||||
|
expanded = fmt % (mode, user, group, caps, path)
|
||||||
|
|
||||||
|
print FSConfigGen._FILE_COMMENT % fname
|
||||||
|
print ' ' + expanded
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _generate(files, dirs, aids):
|
||||||
|
"""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.
|
||||||
|
"""
|
||||||
|
print FSConfigGen._GENERATED
|
||||||
|
print FSConfigGen._INCLUDE
|
||||||
print
|
print
|
||||||
|
|
||||||
if not are_dirs:
|
are_dirs = len(dirs) > 0
|
||||||
print DEFINE_NO_DIRS
|
are_files = len(files) > 0
|
||||||
|
are_aids = len(aids) > 0
|
||||||
|
|
||||||
if not are_files:
|
if are_aids:
|
||||||
print DEFINE_NO_FILES
|
for aid in aids:
|
||||||
|
# use the preserved _path value
|
||||||
|
print FSConfigGen._FILE_COMMENT % aid.found
|
||||||
|
print FSConfigGen._GENERIC_DEFINE % (aid.identifier, aid.value)
|
||||||
|
|
||||||
if not are_files and not are_dirs and not are_aids:
|
print
|
||||||
print DEFAULT_WARNING
|
|
||||||
return
|
|
||||||
|
|
||||||
if are_files:
|
|
||||||
print OPEN_FILE_STRUCT
|
|
||||||
for tup in files:
|
|
||||||
f = tup[0]
|
|
||||||
c = tup[1]
|
|
||||||
c[4] = '"' + c[4] + '"'
|
|
||||||
c = '{ ' + ' ,'.join(c) + ' },'
|
|
||||||
print FILE_COMMENT % f
|
|
||||||
print ' ' + c
|
|
||||||
|
|
||||||
if not are_dirs:
|
if not are_dirs:
|
||||||
print IFDEF_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS
|
print FSConfigGen._DEFINE_NO_DIRS + '\n'
|
||||||
print ' ' + NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS_ENTRY
|
|
||||||
print ENDIF
|
|
||||||
print CLOSE_FILE_STRUCT
|
|
||||||
|
|
||||||
if are_dirs:
|
if not are_files:
|
||||||
print OPEN_DIR_STRUCT
|
print FSConfigGen._DEFINE_NO_FILES + '\n'
|
||||||
for d in dirs:
|
|
||||||
f[4] = '"' + f[4] + '"'
|
|
||||||
d = '{ ' + ' ,'.join(d) + ' },'
|
|
||||||
print ' ' + d
|
|
||||||
|
|
||||||
print CLOSE_FILE_STRUCT
|
if not are_files and not are_dirs and not are_aids:
|
||||||
|
print FSConfigGen._DEFAULT_WARNING
|
||||||
|
return
|
||||||
|
|
||||||
def file_key(x):
|
if are_files:
|
||||||
|
print FSConfigGen._OPEN_FILE_STRUCT
|
||||||
|
for fs_config in files:
|
||||||
|
FSConfigGen._to_fs_entry(fs_config)
|
||||||
|
|
||||||
# Wrapper class for custom prefix matching strings
|
if not are_dirs:
|
||||||
class S(object):
|
print FSConfigGen._IFDEF_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS
|
||||||
def __init__(self, str):
|
print(
|
||||||
|
' ' +
|
||||||
|
FSConfigGen._NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS_ENTRY)
|
||||||
|
print FSConfigGen._ENDIF
|
||||||
|
print FSConfigGen._CLOSE_FILE_STRUCT
|
||||||
|
|
||||||
self.orig = str
|
if are_dirs:
|
||||||
self.is_prefix = str[-1] == '*'
|
print FSConfigGen._OPEN_DIR_STRUCT
|
||||||
if self.is_prefix:
|
for dir_entry in dirs:
|
||||||
self.str = str[:-1]
|
FSConfigGen._to_fs_entry(dir_entry)
|
||||||
else:
|
|
||||||
self.str = str
|
|
||||||
|
|
||||||
def __lt__(self, other):
|
print FSConfigGen._CLOSE_FILE_STRUCT
|
||||||
|
|
||||||
# if were both suffixed the smallest string
|
|
||||||
# is 'bigger'
|
|
||||||
if self.is_prefix and other.is_prefix:
|
|
||||||
b = len(self.str) > len(other.str)
|
|
||||||
# If I am an the suffix match, im bigger
|
|
||||||
elif self.is_prefix:
|
|
||||||
b = False
|
|
||||||
# If other is the suffix match, he's bigger
|
|
||||||
elif other.is_prefix:
|
|
||||||
b = True
|
|
||||||
# Alphabetical
|
|
||||||
else:
|
|
||||||
b = self.str < other.str
|
|
||||||
return b
|
|
||||||
|
|
||||||
return S(x[4])
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
"""Main entry point for execution."""
|
||||||
|
|
||||||
files = []
|
opt_parser = argparse.ArgumentParser(
|
||||||
dirs = []
|
description='A tool for parsing fsconfig config files and producing' +
|
||||||
aids = []
|
'digestable outputs.')
|
||||||
seen_paths = {}
|
subparser = opt_parser.add_subparsers(help='generators')
|
||||||
|
|
||||||
# (name to file, value to aid)
|
gens = generator.get()
|
||||||
seen_aids = ({}, {})
|
|
||||||
|
|
||||||
for x in sys.argv[1:]:
|
# for each gen, instantiate and add them as an option
|
||||||
parse(x, files, dirs, aids, seen_paths, seen_aids)
|
for name, gen in gens.iteritems():
|
||||||
|
|
||||||
# sort entries:
|
generator_option_parser = subparser.add_parser(name, help=gen.__doc__)
|
||||||
# * specified path before prefix match
|
generator_option_parser.set_defaults(which=name)
|
||||||
# ** 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.
|
|
||||||
files.sort(key= lambda x: file_key(x[1]))
|
|
||||||
|
|
||||||
# sort on value of (file_name, name, value, strvalue)
|
opt_group = generator_option_parser.add_argument_group(name +
|
||||||
# This is only cosmetic so AIDS are arranged in ascending order
|
' options')
|
||||||
# within the generated file.
|
gen.add_opts(opt_group)
|
||||||
aids.sort(key=lambda x: x[2])
|
|
||||||
|
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)
|
||||||
|
|
||||||
generate(files, dirs, aids)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
[MESSAGES CONTROL]
|
||||||
|
disable=fixme,design,locally-disabled,too-many-lines
|
||||||
|
|
||||||
|
[VARIABLES]
|
||||||
|
dummy-variables-rgx=_|dummy
|
Loading…
Reference in New Issue