blob: e1aafc95bcb9670c81d545d5920a435ccb02acee [file] [log] [blame]
William Robertsc950a352016-03-04 18:12:29 -08001#!/usr/bin/env python
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00002"""Generates config files for Android file system properties.
William Robertsc950a352016-03-04 18:12:29 -08003
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00004This script is used for generating configuration files for configuring
5Android filesystem properties. Internally, its composed of a plug-able
6interface to support the understanding of new input and output parameters.
7
8Run the help for a list of supported plugins and their capabilities.
9
10Further documentation can be found in the README.
11"""
12
13import argparse
William Robertsc950a352016-03-04 18:12:29 -080014import ConfigParser
Tom Cherry9d924f62019-02-13 14:02:30 -080015import ctypes
William Robertsc950a352016-03-04 18:12:29 -080016import re
17import sys
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000018import textwrap
19
20# Keep the tool in one file to make it easy to run.
21# pylint: disable=too-many-lines
William Robertsd7104bc2016-04-11 21:17:12 -070022
William Robertsc950a352016-03-04 18:12:29 -080023
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000024# Lowercase generator used to be inline with @staticmethod.
25class generator(object): # pylint: disable=invalid-name
26 """A decorator class to add commandlet plugins.
William Robertsc950a352016-03-04 18:12:29 -080027
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000028 Used as a decorator to classes to add them to
29 the internal plugin interface. Plugins added
30 with @generator() are automatically added to
31 the command line.
William Robertsc950a352016-03-04 18:12:29 -080032
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000033 For instance, to add a new generator
34 called foo and have it added just do this:
William Robertsc950a352016-03-04 18:12:29 -080035
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000036 @generator("foo")
37 class FooGen(object):
38 ...
39 """
40 _generators = {}
William Robertsc950a352016-03-04 18:12:29 -080041
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000042 def __init__(self, gen):
43 """
44 Args:
45 gen (str): The name of the generator to add.
William Robertsc950a352016-03-04 18:12:29 -080046
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000047 Raises:
48 ValueError: If there is a similarly named generator already added.
William Robertsc950a352016-03-04 18:12:29 -080049
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000050 """
51 self._gen = gen
William Robertsc950a352016-03-04 18:12:29 -080052
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000053 if gen in generator._generators:
54 raise ValueError('Duplicate generator name: ' + gen)
William Robertsc950a352016-03-04 18:12:29 -080055
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000056 generator._generators[gen] = None
William Robertsc950a352016-03-04 18:12:29 -080057
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000058 def __call__(self, cls):
59
60 generator._generators[self._gen] = cls()
61 return cls
62
63 @staticmethod
64 def get():
65 """Gets the list of generators.
66
67 Returns:
68 The list of registered generators.
69 """
70 return generator._generators
William Robertsc950a352016-03-04 18:12:29 -080071
72
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000073class Utils(object):
74 """Various assorted static utilities."""
William Roberts64edf5b2016-04-11 17:12:47 -070075
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000076 @staticmethod
77 def in_any_range(value, ranges):
78 """Tests if a value is in a list of given closed range tuples.
William Roberts64edf5b2016-04-11 17:12:47 -070079
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000080 A range tuple is a closed range. That means it's inclusive of its
81 start and ending values.
William Roberts64edf5b2016-04-11 17:12:47 -070082
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000083 Args:
84 value (int): The value to test.
85 range [(int, int)]: The closed range list to test value within.
William Roberts64edf5b2016-04-11 17:12:47 -070086
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000087 Returns:
88 True if value is within the closed range, false otherwise.
89 """
William Roberts64edf5b2016-04-11 17:12:47 -070090
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000091 return any(lower <= value <= upper for (lower, upper) in ranges)
William Roberts64edf5b2016-04-11 17:12:47 -070092
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000093 @staticmethod
94 def get_login_and_uid_cleansed(aid):
95 """Returns a passwd/group file safe logon and uid.
William Roberts1c4721c2016-04-26 13:05:34 -070096
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000097 This checks that the logon and uid of the AID do not
98 contain the delimiter ":" for a passwd/group file.
William Roberts1c4721c2016-04-26 13:05:34 -070099
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000100 Args:
101 aid (AID): The aid to check
William Roberts1c4721c2016-04-26 13:05:34 -0700102
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000103 Returns:
104 logon, uid of the AID after checking its safe.
William Roberts1c4721c2016-04-26 13:05:34 -0700105
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000106 Raises:
107 ValueError: If there is a delimiter charcter found.
108 """
109 logon = aid.friendly
110 uid = aid.normalized_value
111 if ':' in uid:
112 raise ValueError(
113 'Cannot specify delimiter character ":" in uid: "%s"' % uid)
114 if ':' in logon:
115 raise ValueError(
Tom Cherry766adc92019-02-13 14:24:52 -0800116 'Cannot specify delimiter character ":" in logon: "%s"' %
117 logon)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000118 return logon, uid
William Roberts1c4721c2016-04-26 13:05:34 -0700119
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000120
121class AID(object):
122 """This class represents an Android ID or an AID.
123
124 Attributes:
125 identifier (str): The identifier name for a #define.
126 value (str) The User Id (uid) of the associate define.
127 found (str) The file it was found in, can be None.
128 normalized_value (str): Same as value, but base 10.
129 friendly (str): The friendly name of aid.
130 """
131
132 PREFIX = 'AID_'
133
134 # Some of the AIDS like AID_MEDIA_EX had names like mediaex
135 # list a map of things to fixup until we can correct these
136 # at a later date.
137 _FIXUPS = {
138 'media_drm': 'mediadrm',
139 'media_ex': 'mediaex',
140 'media_codec': 'mediacodec'
141 }
142
Wei Wang77e329a2018-06-05 16:00:07 -0700143 def __init__(self, identifier, value, found, login_shell):
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000144 """
145 Args:
146 identifier: The identifier name for a #define <identifier>.
147 value: The value of the AID, aka the uid.
148 found (str): The file found in, not required to be specified.
Wei Wang77e329a2018-06-05 16:00:07 -0700149 login_shell (str): The shell field per man (5) passwd file.
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000150 Raises:
Tom Cherryee0610e2018-02-08 14:26:53 -0800151 ValueError: if the friendly name is longer than 31 characters as
152 that is bionic's internal buffer size for name.
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000153 ValueError: if value is not a valid string number as processed by
154 int(x, 0)
155 """
156 self.identifier = identifier
157 self.value = value
158 self.found = found
Wei Wang77e329a2018-06-05 16:00:07 -0700159 self.login_shell = login_shell
160
Tom Cherryee0610e2018-02-08 14:26:53 -0800161 try:
162 self.normalized_value = str(int(value, 0))
Tom Cherry766adc92019-02-13 14:24:52 -0800163 except ValueError:
164 raise ValueError(
165 'Invalid "value", not aid number, got: \"%s\"' % value)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000166
167 # Where we calculate the friendly name
168 friendly = identifier[len(AID.PREFIX):].lower()
169 self.friendly = AID._fixup_friendly(friendly)
170
Tom Cherryee0610e2018-02-08 14:26:53 -0800171 if len(self.friendly) > 31:
Tom Cherry766adc92019-02-13 14:24:52 -0800172 raise ValueError(
173 'AID names must be under 32 characters "%s"' % self.friendly)
Tom Cherryee0610e2018-02-08 14:26:53 -0800174
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000175 def __eq__(self, other):
176
177 return self.identifier == other.identifier \
178 and self.value == other.value and self.found == other.found \
Wei Wang77e329a2018-06-05 16:00:07 -0700179 and self.normalized_value == other.normalized_value \
180 and self.login_shell == other.login_shell
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000181
182 @staticmethod
183 def is_friendly(name):
184 """Determines if an AID is a freindly name or C define.
185
186 For example if name is AID_SYSTEM it returns false, if name
187 was system, it would return true.
188
189 Returns:
190 True if name is a friendly name False otherwise.
191 """
192
193 return not name.startswith(AID.PREFIX)
194
195 @staticmethod
196 def _fixup_friendly(friendly):
197 """Fixup friendly names that historically don't follow the convention.
198
199 Args:
200 friendly (str): The friendly name.
201
202 Returns:
203 The fixedup friendly name as a str.
204 """
205
206 if friendly in AID._FIXUPS:
207 return AID._FIXUPS[friendly]
208
209 return friendly
210
211
212class FSConfig(object):
213 """Represents a filesystem config array entry.
214
215 Represents a file system configuration entry for specifying
216 file system capabilities.
217
218 Attributes:
219 mode (str): The mode of the file or directory.
220 user (str): The uid or #define identifier (AID_SYSTEM)
221 group (str): The gid or #define identifier (AID_SYSTEM)
222 caps (str): The capability set.
Tom Cherry766adc92019-02-13 14:24:52 -0800223 path (str): The path of the file or directory.
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000224 filename (str): The file it was found in.
225 """
226
227 def __init__(self, mode, user, group, caps, path, filename):
228 """
229 Args:
230 mode (str): The mode of the file or directory.
231 user (str): The uid or #define identifier (AID_SYSTEM)
232 group (str): The gid or #define identifier (AID_SYSTEM)
233 caps (str): The capability set as a list.
Tom Cherry766adc92019-02-13 14:24:52 -0800234 path (str): The path of the file or directory.
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000235 filename (str): The file it was found in.
236 """
237 self.mode = mode
238 self.user = user
239 self.group = group
240 self.caps = caps
241 self.path = path
242 self.filename = filename
243
244 def __eq__(self, other):
245
246 return self.mode == other.mode and self.user == other.user \
247 and self.group == other.group and self.caps == other.caps \
248 and self.path == other.path and self.filename == other.filename
249
Tom Cherry766adc92019-02-13 14:24:52 -0800250 def __repr__(self):
251 return 'FSConfig(%r, %r, %r, %r, %r, %r)' % (self.mode, self.user,
252 self.group, self.caps,
253 self.path, self.filename)
254
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000255
Tom Cherry9d924f62019-02-13 14:02:30 -0800256class CapabilityHeaderParser(object):
257 """Parses capability.h file
258
259 Parses a C header file and extracts lines starting with #define CAP_<name>.
260 """
261
262 _CAP_DEFINE = re.compile(r'\s*#define\s+(CAP_\S+)\s+(\S+)')
263 _SKIP_CAPS = ['CAP_LAST_CAP', 'CAP_TO_INDEX(x)', 'CAP_TO_MASK(x)']
264
265 def __init__(self, capability_header):
266 """
267 Args:
268 capability_header (str): file name for the header file containing AID entries.
269 """
270
271 self.caps = {}
272 with open(capability_header) as open_file:
273 self._parse(open_file)
274
275 def _parse(self, capability_file):
276 """Parses a capability header file. Internal use only.
277
278 Args:
279 capability_file (file): The open capability header file to parse.
280 """
281
282 for line in capability_file:
283 match = CapabilityHeaderParser._CAP_DEFINE.match(line)
284 if match:
285 cap = match.group(1)
286 value = match.group(2)
287
288 if not cap in self._SKIP_CAPS:
289 try:
290 self.caps[cap] = int(value, 0)
291 except ValueError:
292 sys.exit('Could not parse capability define "%s":"%s"'
293 % (cap, value))
294
295
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000296class AIDHeaderParser(object):
297 """Parses an android_filesystem_config.h file.
298
299 Parses a C header file and extracts lines starting with #define AID_<name>
300 while capturing the OEM defined ranges and ignoring other ranges. It also
301 skips some hardcoded AIDs it doesn't need to generate a mapping for.
302 It provides some basic sanity checks. The information extracted from this
303 file can later be used to sanity check other things (like oem ranges) as
304 well as generating a mapping of names to uids. It was primarily designed to
305 parse the private/android_filesystem_config.h, but any C header should
306 work.
307 """
308
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000309 _SKIP_AIDS = [
310 re.compile(r'%sUNUSED[0-9].*' % AID.PREFIX),
Tom Cherry766adc92019-02-13 14:24:52 -0800311 re.compile(r'%sAPP' % AID.PREFIX),
312 re.compile(r'%sUSER' % AID.PREFIX)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000313 ]
314 _AID_DEFINE = re.compile(r'\s*#define\s+%s.*' % AID.PREFIX)
Tom Cherryfb303a52019-07-11 15:31:36 -0700315 _RESERVED_RANGE = re.compile(
316 r'#define AID_(.+)_RESERVED_\d*_*(START|END)\s+(\d+)')
317
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000318 # AID lines cannot end with _START or _END, ie AID_FOO is OK
319 # but AID_FOO_START is skiped. Note that AID_FOOSTART is NOT skipped.
Tom Cherryfb303a52019-07-11 15:31:36 -0700320 _AID_SKIP_RANGE = ['_START', '_END']
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000321 _COLLISION_OK = ['AID_APP', 'AID_APP_START', 'AID_USER', 'AID_USER_OFFSET']
322
323 def __init__(self, aid_header):
324 """
325 Args:
326 aid_header (str): file name for the header
327 file containing AID entries.
328 """
329 self._aid_header = aid_header
330 self._aid_name_to_value = {}
331 self._aid_value_to_name = {}
Tom Cherryfb303a52019-07-11 15:31:36 -0700332 self._ranges = {}
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000333
334 with open(aid_header) as open_file:
335 self._parse(open_file)
William Roberts64edf5b2016-04-11 17:12:47 -0700336
337 try:
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000338 self._process_and_check()
339 except ValueError as exception:
340 sys.exit('Error processing parsed data: "%s"' % (str(exception)))
341
342 def _parse(self, aid_file):
343 """Parses an AID header file. Internal use only.
344
345 Args:
346 aid_file (file): The open AID header file to parse.
347 """
348
349 for lineno, line in enumerate(aid_file):
William Roberts820421c2016-12-13 19:12:35 -0800350
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000351 def error_message(msg):
352 """Creates an error message with the current parsing state."""
William Roberts4165c632016-12-13 19:17:07 -0800353 # pylint: disable=cell-var-from-loop
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000354 return 'Error "{}" in file: "{}" on line: {}'.format(
355 msg, self._aid_header, str(lineno))
356
Tom Cherryfb303a52019-07-11 15:31:36 -0700357 range_match = self._RESERVED_RANGE.match(line)
358 if range_match:
359 partition = range_match.group(1).lower()
360 value = int(range_match.group(3), 0)
361
362 if partition == 'oem':
363 partition = 'vendor'
364
365 if partition in self._ranges:
366 if isinstance(self._ranges[partition][-1], int):
367 self._ranges[partition][-1] = (
368 self._ranges[partition][-1], value)
369 else:
370 self._ranges[partition].append(value)
371 else:
372 self._ranges[partition] = [value]
373
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000374 if AIDHeaderParser._AID_DEFINE.match(line):
375 chunks = line.split()
376 identifier = chunks[1]
377 value = chunks[2]
378
Tom Cherry766adc92019-02-13 14:24:52 -0800379 if any(
380 x.match(identifier)
381 for x in AIDHeaderParser._SKIP_AIDS):
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000382 continue
383
384 try:
Tom Cherryfb303a52019-07-11 15:31:36 -0700385 if not any(
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000386 identifier.endswith(x)
387 for x in AIDHeaderParser._AID_SKIP_RANGE):
388 self._handle_aid(identifier, value)
389 except ValueError as exception:
William Roberts820421c2016-12-13 19:12:35 -0800390 sys.exit(
Tom Cherry766adc92019-02-13 14:24:52 -0800391 error_message('{} for "{}"'.format(
392 exception, identifier)))
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000393
394 def _handle_aid(self, identifier, value):
395 """Handle an AID C #define.
396
397 Handles an AID, sanity checking, generating the friendly name and
398 adding it to the internal maps. Internal use only.
399
400 Args:
401 identifier (str): The name of the #define identifier. ie AID_FOO.
402 value (str): The value associated with the identifier.
403
404 Raises:
405 ValueError: With message set to indicate the error.
406 """
407
Wei Wang77e329a2018-06-05 16:00:07 -0700408 aid = AID(identifier, value, self._aid_header, '/system/bin/sh')
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000409
410 # duplicate name
411 if aid.friendly in self._aid_name_to_value:
412 raise ValueError('Duplicate aid "%s"' % identifier)
413
414 if value in self._aid_value_to_name and aid.identifier not in AIDHeaderParser._COLLISION_OK:
Tom Cherry766adc92019-02-13 14:24:52 -0800415 raise ValueError(
416 'Duplicate aid value "%s" for %s' % (value, identifier))
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000417
418 self._aid_name_to_value[aid.friendly] = aid
419 self._aid_value_to_name[value] = aid.friendly
420
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000421 def _process_and_check(self):
422 """Process, check and populate internal data structures.
William Roberts64edf5b2016-04-11 17:12:47 -0700423
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000424 After parsing and generating the internal data structures, this method
425 is responsible for sanity checking ALL of the acquired data.
William Roberts64edf5b2016-04-11 17:12:47 -0700426
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000427 Raises:
428 ValueError: With the message set to indicate the specific error.
429 """
William Roberts64edf5b2016-04-11 17:12:47 -0700430
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000431 # Check for overlapping ranges
Tom Cherryfb303a52019-07-11 15:31:36 -0700432 for ranges in self._ranges.values():
433 for i, range1 in enumerate(ranges):
434 for range2 in ranges[i + 1:]:
435 if AIDHeaderParser._is_overlap(range1, range2):
436 raise ValueError(
437 "Overlapping OEM Ranges found %s and %s" %
438 (str(range1), str(range2)))
William Roberts64edf5b2016-04-11 17:12:47 -0700439
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000440 # No core AIDs should be within any oem range.
441 for aid in self._aid_value_to_name:
Tom Cherryfb303a52019-07-11 15:31:36 -0700442 for ranges in self._ranges.values():
443 if Utils.in_any_range(aid, ranges):
444 name = self._aid_value_to_name[aid]
445 raise ValueError(
446 'AID "%s" value: %u within reserved OEM Range: "%s"' %
447 (name, aid, str(ranges)))
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000448
449 @property
Tom Cherryfb303a52019-07-11 15:31:36 -0700450 def ranges(self):
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000451 """Retrieves the OEM closed ranges as a list of tuples.
452
453 Returns:
454 A list of closed range tuples: [ (0, 42), (50, 105) ... ]
455 """
Tom Cherryfb303a52019-07-11 15:31:36 -0700456 return self._ranges
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000457
458 @property
459 def aids(self):
460 """Retrieves the list of found AIDs.
461
462 Returns:
463 A list of AID() objects.
464 """
465 return self._aid_name_to_value.values()
466
467 @staticmethod
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000468 def _is_overlap(range_a, range_b):
469 """Calculates the overlap of two range tuples.
470
471 A range tuple is a closed range. A closed range includes its endpoints.
472 Note that python tuples use () notation which collides with the
473 mathematical notation for open ranges.
474
475 Args:
476 range_a: The first tuple closed range eg (0, 5).
477 range_b: The second tuple closed range eg (3, 7).
478
479 Returns:
480 True if they overlap, False otherwise.
481 """
482
483 return max(range_a[0], range_b[0]) <= min(range_a[1], range_b[1])
William Roberts64edf5b2016-04-11 17:12:47 -0700484
485
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000486class FSConfigFileParser(object):
487 """Parses a config.fs ini format file.
William Roberts11c29282016-04-09 10:32:30 -0700488
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000489 This class is responsible for parsing the config.fs ini format files.
490 It collects and checks all the data in these files and makes it available
491 for consumption post processed.
492 """
William Roberts11c29282016-04-09 10:32:30 -0700493
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000494 # These _AID vars work together to ensure that an AID section name
495 # cannot contain invalid characters for a C define or a passwd/group file.
496 # Since _AID_PREFIX is within the set of _AID_MATCH the error logic only
497 # checks end, if you change this, you may have to update the error
498 # detection code.
499 _AID_MATCH = re.compile('%s[A-Z0-9_]+' % AID.PREFIX)
500 _AID_ERR_MSG = 'Expecting upper case, a number or underscore'
William Roberts5f059a72016-04-25 10:36:45 -0700501
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000502 # list of handler to required options, used to identify the
503 # parsing section
Tom Cherry766adc92019-02-13 14:24:52 -0800504 _SECTIONS = [('_handle_aid', ('value', )),
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000505 ('_handle_path', ('mode', 'user', 'group', 'caps'))]
506
Tom Cherryfb303a52019-07-11 15:31:36 -0700507 def __init__(self, config_files, ranges):
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000508 """
509 Args:
510 config_files ([str]): The list of config.fs files to parse.
511 Note the filename is not important.
Tom Cherryfb303a52019-07-11 15:31:36 -0700512 ranges ({str,[()]): Dictionary of partitions and a list of tuples that correspond to their ranges
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000513 """
514
515 self._files = []
516 self._dirs = []
517 self._aids = []
518
519 self._seen_paths = {}
520 # (name to file, value to aid)
521 self._seen_aids = ({}, {})
522
Tom Cherryfb303a52019-07-11 15:31:36 -0700523 self._ranges = ranges
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000524
525 self._config_files = config_files
526
527 for config_file in self._config_files:
528 self._parse(config_file)
529
530 def _parse(self, file_name):
531 """Parses and verifies config.fs files. Internal use only.
532
533 Args:
534 file_name (str): The config.fs (PythonConfigParser file format)
535 file to parse.
536
537 Raises:
538 Anything raised by ConfigParser.read()
539 """
540
541 # Separate config parsers for each file found. If you use
542 # read(filenames...) later files can override earlier files which is
543 # not what we want. Track state across files and enforce with
544 # _handle_dup(). Note, strict ConfigParser is set to true in
545 # Python >= 3.2, so in previous versions same file sections can
546 # override previous
547 # sections.
William Robertsc950a352016-03-04 18:12:29 -0800548
549 config = ConfigParser.ConfigParser()
550 config.read(file_name)
551
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000552 for section in config.sections():
William Robertsc950a352016-03-04 18:12:29 -0800553
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000554 found = False
William Roberts5f059a72016-04-25 10:36:45 -0700555
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000556 for test in FSConfigFileParser._SECTIONS:
557 handler = test[0]
558 options = test[1]
William Roberts5f059a72016-04-25 10:36:45 -0700559
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000560 if all([config.has_option(section, item) for item in options]):
561 handler = getattr(self, handler)
562 handler(file_name, section, config)
563 found = True
564 break
William Roberts5f059a72016-04-25 10:36:45 -0700565
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000566 if not found:
Tom Cherry766adc92019-02-13 14:24:52 -0800567 sys.exit('Invalid section "%s" in file: "%s"' % (section,
568 file_name))
William Roberts11c29282016-04-09 10:32:30 -0700569
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000570 # sort entries:
571 # * specified path before prefix match
572 # ** ie foo before f*
573 # * lexicographical less than before other
574 # ** ie boo before foo
575 # Given these paths:
576 # paths=['ac', 'a', 'acd', 'an', 'a*', 'aa', 'ac*']
577 # The sort order would be:
578 # paths=['a', 'aa', 'ac', 'acd', 'an', 'ac*', 'a*']
579 # Thus the fs_config tools will match on specified paths before
580 # attempting prefix, and match on the longest matching prefix.
581 self._files.sort(key=FSConfigFileParser._file_key)
582
583 # sort on value of (file_name, name, value, strvalue)
584 # This is only cosmetic so AIDS are arranged in ascending order
585 # within the generated file.
586 self._aids.sort(key=lambda item: item.normalized_value)
587
Tom Cherryfb303a52019-07-11 15:31:36 -0700588 def _verify_valid_range(self, aid):
589 """Verified an AID entry is in a valid range"""
590
591 ranges = None
592
593 partitions = self._ranges.keys()
594 partitions.sort(key=len, reverse=True)
595 for partition in partitions:
596 if aid.friendly.startswith(partition):
597 ranges = self._ranges[partition]
598 break
599
600 if ranges is None:
601 sys.exit('AID "%s" must be prefixed with a partition name' %
602 aid.friendly)
603
604 if not Utils.in_any_range(int(aid.value, 0), ranges):
605 emsg = '"value" for aid "%s" not in valid range %s, got: %s'
606 emsg = emsg % (aid.friendly, str(ranges), aid.value)
607 sys.exit(emsg)
608
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000609 def _handle_aid(self, file_name, section_name, config):
610 """Verifies an AID entry and adds it to the aid list.
611
612 Calls sys.exit() with a descriptive message of the failure.
613
614 Args:
615 file_name (str): The filename of the config file being parsed.
616 section_name (str): The section name currently being parsed.
617 config (ConfigParser): The ConfigParser section being parsed that
618 the option values will come from.
619 """
620
621 def error_message(msg):
622 """Creates an error message with current parsing state."""
623 return '{} for: "{}" file: "{}"'.format(msg, section_name,
624 file_name)
625
626 FSConfigFileParser._handle_dup_and_add('AID', file_name, section_name,
627 self._seen_aids[0])
628
629 match = FSConfigFileParser._AID_MATCH.match(section_name)
630 invalid = match.end() if match else len(AID.PREFIX)
631 if invalid != len(section_name):
632 tmp_errmsg = ('Invalid characters in AID section at "%d" for: "%s"'
633 % (invalid, FSConfigFileParser._AID_ERR_MSG))
634 sys.exit(error_message(tmp_errmsg))
635
636 value = config.get(section_name, 'value')
637
638 if not value:
639 sys.exit(error_message('Found specified but unset "value"'))
640
641 try:
Tom Cherryfb303a52019-07-11 15:31:36 -0700642 aid = AID(section_name, value, file_name, '/bin/sh')
Tom Cherryee0610e2018-02-08 14:26:53 -0800643 except ValueError as exception:
644 sys.exit(error_message(exception))
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000645
Tom Cherryfb303a52019-07-11 15:31:36 -0700646 self._verify_valid_range(aid)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000647
648 # use the normalized int value in the dict and detect
649 # duplicate definitions of the same value
650 FSConfigFileParser._handle_dup_and_add(
651 'AID', file_name, aid.normalized_value, self._seen_aids[1])
652
653 # Append aid tuple of (AID_*, base10(value), _path(value))
654 # We keep the _path version of value so we can print that out in the
655 # generated header so investigating parties can identify parts.
656 # We store the base10 value for sorting, so everything is ascending
657 # later.
658 self._aids.append(aid)
659
660 def _handle_path(self, file_name, section_name, config):
661 """Add a file capability entry to the internal list.
662
663 Handles a file capability entry, verifies it, and adds it to
664 to the internal dirs or files list based on path. If it ends
665 with a / its a dir. Internal use only.
666
667 Calls sys.exit() on any validation error with message set.
668
669 Args:
670 file_name (str): The current name of the file being parsed.
671 section_name (str): The name of the section to parse.
672 config (str): The config parser.
673 """
674
675 FSConfigFileParser._handle_dup_and_add('path', file_name, section_name,
676 self._seen_paths)
677
678 mode = config.get(section_name, 'mode')
679 user = config.get(section_name, 'user')
680 group = config.get(section_name, 'group')
681 caps = config.get(section_name, 'caps')
682
683 errmsg = ('Found specified but unset option: \"%s" in file: \"' +
684 file_name + '\"')
685
686 if not mode:
687 sys.exit(errmsg % 'mode')
688
689 if not user:
690 sys.exit(errmsg % 'user')
691
692 if not group:
693 sys.exit(errmsg % 'group')
694
695 if not caps:
696 sys.exit(errmsg % 'caps')
697
698 caps = caps.split()
699
700 tmp = []
701 for cap in caps:
702 try:
703 # test if string is int, if it is, use as is.
704 int(cap, 0)
Tom Cherry9d924f62019-02-13 14:02:30 -0800705 tmp.append(cap)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000706 except ValueError:
Tom Cherry9d924f62019-02-13 14:02:30 -0800707 tmp.append('CAP_' + cap.upper())
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000708
709 caps = tmp
710
711 if len(mode) == 3:
712 mode = '0' + mode
713
714 try:
715 int(mode, 8)
716 except ValueError:
717 sys.exit('Mode must be octal characters, got: "%s"' % mode)
718
719 if len(mode) != 4:
720 sys.exit('Mode must be 3 or 4 characters, got: "%s"' % mode)
721
Tom Cherry9d924f62019-02-13 14:02:30 -0800722 caps_str = ','.join(caps)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000723
724 entry = FSConfig(mode, user, group, caps_str, section_name, file_name)
725 if section_name[-1] == '/':
726 self._dirs.append(entry)
727 else:
728 self._files.append(entry)
729
730 @property
731 def files(self):
732 """Get the list of FSConfig file entries.
733
734 Returns:
735 a list of FSConfig() objects for file paths.
736 """
737 return self._files
738
739 @property
740 def dirs(self):
741 """Get the list of FSConfig dir entries.
742
743 Returns:
744 a list of FSConfig() objects for directory paths.
745 """
746 return self._dirs
747
748 @property
749 def aids(self):
750 """Get the list of AID entries.
751
752 Returns:
753 a list of AID() objects.
754 """
755 return self._aids
756
757 @staticmethod
758 def _file_key(fs_config):
759 """Used as the key paramter to sort.
760
761 This is used as a the function to the key parameter of a sort.
762 it wraps the string supplied in a class that implements the
763 appropriate __lt__ operator for the sort on path strings. See
764 StringWrapper class for more details.
765
766 Args:
767 fs_config (FSConfig): A FSConfig entry.
768
769 Returns:
770 A StringWrapper object
771 """
772
773 # Wrapper class for custom prefix matching strings
774 class StringWrapper(object):
775 """Wrapper class used for sorting prefix strings.
776
777 The algorithm is as follows:
778 - specified path before prefix match
779 - ie foo before f*
780 - lexicographical less than before other
781 - ie boo before foo
782
783 Given these paths:
784 paths=['ac', 'a', 'acd', 'an', 'a*', 'aa', 'ac*']
785 The sort order would be:
786 paths=['a', 'aa', 'ac', 'acd', 'an', 'ac*', 'a*']
787 Thus the fs_config tools will match on specified paths before
788 attempting prefix, and match on the longest matching prefix.
789 """
790
791 def __init__(self, path):
792 """
793 Args:
794 path (str): the path string to wrap.
795 """
796 self.is_prefix = path[-1] == '*'
797 if self.is_prefix:
798 self.path = path[:-1]
799 else:
800 self.path = path
801
802 def __lt__(self, other):
803
804 # if were both suffixed the smallest string
805 # is 'bigger'
806 if self.is_prefix and other.is_prefix:
807 result = len(self.path) > len(other.path)
808 # If I am an the suffix match, im bigger
809 elif self.is_prefix:
810 result = False
811 # If other is the suffix match, he's bigger
812 elif other.is_prefix:
813 result = True
814 # Alphabetical
815 else:
816 result = self.path < other.path
817 return result
818
819 return StringWrapper(fs_config.path)
820
821 @staticmethod
822 def _handle_dup_and_add(name, file_name, section_name, seen):
823 """Tracks and detects duplicates. Internal use only.
824
825 Calls sys.exit() on a duplicate.
826
827 Args:
828 name (str): The name to use in the error reporting. The pretty
829 name for the section.
830 file_name (str): The file currently being parsed.
831 section_name (str): The name of the section. This would be path
832 or identifier depending on what's being parsed.
833 seen (dict): The dictionary of seen things to check against.
834 """
835 if section_name in seen:
836 dups = '"' + seen[section_name] + '" and '
837 dups += file_name
838 sys.exit('Duplicate %s "%s" found in files: %s' %
839 (name, section_name, dups))
840
841 seen[section_name] = file_name
842
843
844class BaseGenerator(object):
845 """Interface for Generators.
846
847 Base class for generators, generators should implement
848 these method stubs.
849 """
850
851 def add_opts(self, opt_group):
852 """Used to add per-generator options to the command line.
853
854 Args:
855 opt_group (argument group object): The argument group to append to.
856 See the ArgParse docs for more details.
857 """
858
859 raise NotImplementedError("Not Implemented")
860
861 def __call__(self, args):
862 """This is called to do whatever magic the generator does.
863
864 Args:
865 args (dict): The arguments from ArgParse as a dictionary.
866 ie if you specified an argument of foo in add_opts, access
867 it via args['foo']
868 """
869
870 raise NotImplementedError("Not Implemented")
871
872
873@generator('fsconfig')
874class FSConfigGen(BaseGenerator):
875 """Generates the android_filesystem_config.h file.
876
877 Output is used in generating fs_config_files and fs_config_dirs.
878 """
879
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000880 def __init__(self, *args, **kwargs):
881 BaseGenerator.__init__(args, kwargs)
882
883 self._oem_parser = None
884 self._base_parser = None
885 self._friendly_to_aid = None
Tom Cherry9d924f62019-02-13 14:02:30 -0800886 self._id_to_aid = None
887 self._capability_parser = None
888
889 self._partition = None
890 self._all_partitions = None
891 self._out_file = None
892 self._generate_files = False
893 self._generate_dirs = False
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000894
895 def add_opts(self, opt_group):
896
897 opt_group.add_argument(
898 'fsconfig', nargs='+', help='The list of fsconfig files to parse')
899
900 opt_group.add_argument(
901 '--aid-header',
902 required=True,
903 help='An android_filesystem_config.h file'
904 ' to parse AIDs and OEM Ranges from')
905
Tom Cherry9d924f62019-02-13 14:02:30 -0800906 opt_group.add_argument(
907 '--capability-header',
908 required=True,
909 help='A capability.h file to parse capability defines from')
910
911 opt_group.add_argument(
912 '--partition',
913 required=True,
914 help='Partition to generate contents for')
915
916 opt_group.add_argument(
917 '--all-partitions',
918 help='Comma separated list of all possible partitions, used to'
919 ' ignore these partitions when generating the output for the system partition'
920 )
921
922 opt_group.add_argument(
923 '--files', action='store_true', help='Output fs_config_files')
924
925 opt_group.add_argument(
926 '--dirs', action='store_true', help='Output fs_config_dirs')
927
928 opt_group.add_argument('--out_file', required=True, help='Output file')
929
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000930 def __call__(self, args):
931
Tom Cherry9d924f62019-02-13 14:02:30 -0800932 self._capability_parser = CapabilityHeaderParser(
933 args['capability_header'])
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000934 self._base_parser = AIDHeaderParser(args['aid_header'])
935 self._oem_parser = FSConfigFileParser(args['fsconfig'],
Tom Cherryfb303a52019-07-11 15:31:36 -0700936 self._base_parser.ranges)
Tom Cherry9d924f62019-02-13 14:02:30 -0800937
938 self._partition = args['partition']
939 self._all_partitions = args['all_partitions']
Tom Cherry9d924f62019-02-13 14:02:30 -0800940
941 self._out_file = args['out_file']
942
943 self._generate_files = args['files']
944 self._generate_dirs = args['dirs']
945
946 if self._generate_files and self._generate_dirs:
947 sys.exit('Only one of --files or --dirs can be provided')
948
949 if not self._generate_files and not self._generate_dirs:
950 sys.exit('One of --files or --dirs must be provided')
951
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000952 base_aids = self._base_parser.aids
953 oem_aids = self._oem_parser.aids
954
955 # Detect name collisions on AIDs. Since friendly works as the
956 # identifier for collision testing and we need friendly later on for
957 # name resolution, just calculate and use friendly.
958 # {aid.friendly: aid for aid in base_aids}
959 base_friendly = {aid.friendly: aid for aid in base_aids}
960 oem_friendly = {aid.friendly: aid for aid in oem_aids}
961
962 base_set = set(base_friendly.keys())
963 oem_set = set(oem_friendly.keys())
964
965 common = base_set & oem_set
966
Tom Cherry766adc92019-02-13 14:24:52 -0800967 if common:
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000968 emsg = 'Following AID Collisions detected for: \n'
969 for friendly in common:
970 base = base_friendly[friendly]
971 oem = oem_friendly[friendly]
972 emsg += (
973 'Identifier: "%s" Friendly Name: "%s" '
974 'found in file "%s" and "%s"' %
975 (base.identifier, base.friendly, base.found, oem.found))
976 sys.exit(emsg)
977
978 self._friendly_to_aid = oem_friendly
979 self._friendly_to_aid.update(base_friendly)
980
Tom Cherry9d924f62019-02-13 14:02:30 -0800981 self._id_to_aid = {aid.identifier: aid for aid in base_aids}
982 self._id_to_aid.update({aid.identifier: aid for aid in oem_aids})
983
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000984 self._generate()
985
Tom Cherry9d924f62019-02-13 14:02:30 -0800986 def _to_fs_entry(self, fs_config, out_file):
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000987 """Converts an FSConfig entry to an fs entry.
988
Tom Cherry9d924f62019-02-13 14:02:30 -0800989 Writes the fs_config contents to the output file.
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000990
991 Calls sys.exit() on error.
992
993 Args:
Tom Cherry9d924f62019-02-13 14:02:30 -0800994 fs_config (FSConfig): The entry to convert to write to file.
995 file (File): The file to write to.
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000996 """
997
998 # Get some short names
999 mode = fs_config.mode
1000 user = fs_config.user
1001 group = fs_config.group
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001002 caps = fs_config.caps
1003 path = fs_config.path
1004
Tom Cherry9d924f62019-02-13 14:02:30 -08001005 emsg = 'Cannot convert "%s" to identifier!'
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001006
Tom Cherry9d924f62019-02-13 14:02:30 -08001007 # convert mode from octal string to integer
1008 mode = int(mode, 8)
1009
1010 # remap names to values
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001011 if AID.is_friendly(user):
1012 if user not in self._friendly_to_aid:
1013 sys.exit(emsg % user)
Tom Cherry9d924f62019-02-13 14:02:30 -08001014 user = self._friendly_to_aid[user].value
1015 else:
1016 if user not in self._id_to_aid:
1017 sys.exit(emsg % user)
1018 user = self._id_to_aid[user].value
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001019
1020 if AID.is_friendly(group):
1021 if group not in self._friendly_to_aid:
1022 sys.exit(emsg % group)
Tom Cherry9d924f62019-02-13 14:02:30 -08001023 group = self._friendly_to_aid[group].value
1024 else:
1025 if group not in self._id_to_aid:
1026 sys.exit(emsg % group)
1027 group = self._id_to_aid[group].value
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001028
Tom Cherry9d924f62019-02-13 14:02:30 -08001029 caps_dict = self._capability_parser.caps
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001030
Tom Cherry9d924f62019-02-13 14:02:30 -08001031 caps_value = 0
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001032
Tom Cherry9d924f62019-02-13 14:02:30 -08001033 try:
1034 # test if caps is an int
1035 caps_value = int(caps, 0)
1036 except ValueError:
1037 caps_split = caps.split(',')
1038 for cap in caps_split:
1039 if cap not in caps_dict:
1040 sys.exit('Unkonwn cap "%s" found!' % cap)
1041 caps_value += 1 << caps_dict[cap]
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001042
Tom Cherry9d924f62019-02-13 14:02:30 -08001043 path_length_with_null = len(path) + 1
1044 path_length_aligned_64 = (path_length_with_null + 7) & ~7
1045 # 16 bytes of header plus the path length with alignment
1046 length = 16 + path_length_aligned_64
1047
1048 length_binary = bytearray(ctypes.c_uint16(length))
1049 mode_binary = bytearray(ctypes.c_uint16(mode))
1050 user_binary = bytearray(ctypes.c_uint16(int(user, 0)))
1051 group_binary = bytearray(ctypes.c_uint16(int(group, 0)))
1052 caps_binary = bytearray(ctypes.c_uint64(caps_value))
1053 path_binary = ctypes.create_string_buffer(path,
1054 path_length_aligned_64).raw
1055
1056 out_file.write(length_binary)
1057 out_file.write(mode_binary)
1058 out_file.write(user_binary)
1059 out_file.write(group_binary)
1060 out_file.write(caps_binary)
1061 out_file.write(path_binary)
1062
1063 def _emit_entry(self, fs_config):
1064 """Returns a boolean whether or not to emit the input fs_config"""
1065
1066 path = fs_config.path
1067
1068 if self._partition == 'system':
1069 for skip_partition in self._all_partitions.split(','):
1070 if path.startswith(skip_partition) or path.startswith(
1071 'system/' + skip_partition):
1072 return False
1073 return True
1074 else:
1075 if path.startswith(
1076 self._partition) or path.startswith('system/' +
1077 self._partition):
1078 return True
1079 return False
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001080
1081 def _generate(self):
1082 """Generates an OEM android_filesystem_config.h header file to stdout.
1083
1084 Args:
1085 files ([FSConfig]): A list of FSConfig objects for file entries.
1086 dirs ([FSConfig]): A list of FSConfig objects for directory
1087 entries.
1088 aids ([AIDS]): A list of AID objects for Android Id entries.
1089 """
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001090 dirs = self._oem_parser.dirs
1091 files = self._oem_parser.files
William Roberts8f42ce72016-04-25 12:27:43 -07001092
Tom Cherry9d924f62019-02-13 14:02:30 -08001093 if self._generate_files:
1094 with open(self._out_file, 'wb') as open_file:
1095 for fs_config in files:
1096 if self._emit_entry(fs_config):
1097 self._to_fs_entry(fs_config, open_file)
William Robertsc950a352016-03-04 18:12:29 -08001098
Tom Cherry9d924f62019-02-13 14:02:30 -08001099 if self._generate_dirs:
1100 with open(self._out_file, 'wb') as open_file:
1101 for dir_entry in dirs:
1102 if self._emit_entry(dir_entry):
1103 self._to_fs_entry(dir_entry, open_file)
William Robertsc950a352016-03-04 18:12:29 -08001104
William Robertsc950a352016-03-04 18:12:29 -08001105
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001106@generator('aidarray')
1107class AIDArrayGen(BaseGenerator):
1108 """Generates the android_id static array."""
1109
1110 _GENERATED = ('/*\n'
1111 ' * THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY!\n'
1112 ' */')
1113
1114 _INCLUDE = '#include <private/android_filesystem_config.h>'
1115
Vic Yang5b3a7c02018-12-03 21:40:33 -08001116 # Note that the android_id name field is of type 'const char[]' instead of
1117 # 'const char*'. While this seems less straightforward as we need to
1118 # calculate the max length of all names, this allows the entire android_ids
1119 # table to be placed in .rodata section instead of .data.rel.ro section,
1120 # resulting in less memory pressure.
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001121 _STRUCT_FS_CONFIG = textwrap.dedent("""
1122 struct android_id_info {
Vic Yang5b3a7c02018-12-03 21:40:33 -08001123 const char name[%d];
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001124 unsigned aid;
1125 };""")
1126
1127 _OPEN_ID_ARRAY = 'static const struct android_id_info android_ids[] = {'
1128
1129 _ID_ENTRY = ' { "%s", %s },'
1130
1131 _CLOSE_FILE_STRUCT = '};'
1132
1133 _COUNT = ('#define android_id_count \\\n'
1134 ' (sizeof(android_ids) / sizeof(android_ids[0]))')
1135
1136 def add_opts(self, opt_group):
1137
1138 opt_group.add_argument(
1139 'hdrfile', help='The android_filesystem_config.h'
1140 'file to parse')
1141
1142 def __call__(self, args):
1143
1144 hdr = AIDHeaderParser(args['hdrfile'])
Vic Yang5b3a7c02018-12-03 21:40:33 -08001145 max_name_length = max(len(aid.friendly) + 1 for aid in hdr.aids)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001146
1147 print AIDArrayGen._GENERATED
1148 print
1149 print AIDArrayGen._INCLUDE
1150 print
Vic Yang5b3a7c02018-12-03 21:40:33 -08001151 print AIDArrayGen._STRUCT_FS_CONFIG % max_name_length
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001152 print
1153 print AIDArrayGen._OPEN_ID_ARRAY
1154
1155 for aid in hdr.aids:
1156 print AIDArrayGen._ID_ENTRY % (aid.friendly, aid.identifier)
1157
1158 print AIDArrayGen._CLOSE_FILE_STRUCT
1159 print
1160 print AIDArrayGen._COUNT
1161 print
1162
1163
1164@generator('oemaid')
1165class OEMAidGen(BaseGenerator):
1166 """Generates the OEM AID_<name> value header file."""
1167
1168 _GENERATED = ('/*\n'
1169 ' * THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY!\n'
1170 ' */')
1171
1172 _GENERIC_DEFINE = "#define %s\t%s"
1173
1174 _FILE_COMMENT = '// Defined in file: \"%s\"'
1175
1176 # Intentional trailing newline for readability.
1177 _FILE_IFNDEF_DEFINE = ('#ifndef GENERATED_OEM_AIDS_H_\n'
1178 '#define GENERATED_OEM_AIDS_H_\n')
1179
1180 _FILE_ENDIF = '#endif'
1181
1182 def __init__(self):
1183
1184 self._old_file = None
1185
1186 def add_opts(self, opt_group):
1187
1188 opt_group.add_argument(
1189 'fsconfig', nargs='+', help='The list of fsconfig files to parse.')
1190
1191 opt_group.add_argument(
1192 '--aid-header',
1193 required=True,
1194 help='An android_filesystem_config.h file'
1195 'to parse AIDs and OEM Ranges from')
1196
1197 def __call__(self, args):
1198
1199 hdr_parser = AIDHeaderParser(args['aid_header'])
1200
Tom Cherryfb303a52019-07-11 15:31:36 -07001201 parser = FSConfigFileParser(args['fsconfig'], hdr_parser.ranges)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001202
1203 print OEMAidGen._GENERATED
1204
1205 print OEMAidGen._FILE_IFNDEF_DEFINE
1206
1207 for aid in parser.aids:
1208 self._print_aid(aid)
1209 print
1210
1211 print OEMAidGen._FILE_ENDIF
1212
1213 def _print_aid(self, aid):
1214 """Prints a valid #define AID identifier to stdout.
1215
1216 Args:
1217 aid to print
1218 """
1219
1220 # print the source file location of the AID
1221 found_file = aid.found
1222 if found_file != self._old_file:
1223 print OEMAidGen._FILE_COMMENT % found_file
1224 self._old_file = found_file
1225
1226 print OEMAidGen._GENERIC_DEFINE % (aid.identifier, aid.value)
1227
1228
1229@generator('passwd')
1230class PasswdGen(BaseGenerator):
1231 """Generates the /etc/passwd file per man (5) passwd."""
1232
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001233 def __init__(self):
1234
1235 self._old_file = None
1236
1237 def add_opts(self, opt_group):
1238
1239 opt_group.add_argument(
1240 'fsconfig', nargs='+', help='The list of fsconfig files to parse.')
1241
1242 opt_group.add_argument(
1243 '--aid-header',
1244 required=True,
1245 help='An android_filesystem_config.h file'
1246 'to parse AIDs and OEM Ranges from')
1247
Tom Cherry2d197a12018-05-14 13:14:41 -07001248 opt_group.add_argument(
Tom Cherryfb303a52019-07-11 15:31:36 -07001249 '--partition',
1250 required=True,
1251 help=
1252 'Filter the input file and only output entries for the given partition.'
1253 )
Tom Cherry2d197a12018-05-14 13:14:41 -07001254
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001255 def __call__(self, args):
1256
1257 hdr_parser = AIDHeaderParser(args['aid_header'])
1258
Tom Cherryfb303a52019-07-11 15:31:36 -07001259 parser = FSConfigFileParser(args['fsconfig'], hdr_parser.ranges)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001260
Tom Cherryfb303a52019-07-11 15:31:36 -07001261 filter_partition = args['partition']
Tom Cherry2d197a12018-05-14 13:14:41 -07001262
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001263 aids = parser.aids
1264
1265 # nothing to do if no aids defined
Tom Cherry766adc92019-02-13 14:24:52 -08001266 if not aids:
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001267 return
1268
Tom Cherryfb303a52019-07-11 15:31:36 -07001269 aids_by_partition = {}
1270 partitions = hdr_parser.ranges.keys()
1271 partitions.sort(key=len, reverse=True)
1272
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001273 for aid in aids:
Tom Cherryfb303a52019-07-11 15:31:36 -07001274 for partition in partitions:
1275 if aid.friendly.startswith(partition):
1276 if partition in aids_by_partition:
1277 aids_by_partition[partition].append(aid)
1278 else:
1279 aids_by_partition[partition] = [aid]
1280 break
1281
1282 if filter_partition in aids_by_partition:
1283 for aid in aids_by_partition[filter_partition]:
Tom Cherry2d197a12018-05-14 13:14:41 -07001284 self._print_formatted_line(aid)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001285
1286 def _print_formatted_line(self, aid):
1287 """Prints the aid to stdout in the passwd format. Internal use only.
1288
1289 Colon delimited:
1290 login name, friendly name
1291 encrypted password (optional)
1292 uid (int)
1293 gid (int)
1294 User name or comment field
1295 home directory
1296 interpreter (optional)
1297
1298 Args:
1299 aid (AID): The aid to print.
1300 """
1301 if self._old_file != aid.found:
1302 self._old_file = aid.found
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001303
1304 try:
1305 logon, uid = Utils.get_login_and_uid_cleansed(aid)
1306 except ValueError as exception:
1307 sys.exit(exception)
1308
Wei Wang77e329a2018-06-05 16:00:07 -07001309 print "%s::%s:%s::/:%s" % (logon, uid, uid, aid.login_shell)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001310
1311
1312@generator('group')
1313class GroupGen(PasswdGen):
1314 """Generates the /etc/group file per man (5) group."""
1315
1316 # Overrides parent
1317 def _print_formatted_line(self, aid):
1318 """Prints the aid to stdout in the group format. Internal use only.
1319
1320 Formatted (per man 5 group) like:
1321 group_name:password:GID:user_list
1322
1323 Args:
1324 aid (AID): The aid to print.
1325 """
1326 if self._old_file != aid.found:
1327 self._old_file = aid.found
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001328
1329 try:
1330 logon, uid = Utils.get_login_and_uid_cleansed(aid)
1331 except ValueError as exception:
1332 sys.exit(exception)
1333
1334 print "%s::%s:" % (logon, uid)
1335
Tom Cherry766adc92019-02-13 14:24:52 -08001336
Joe Onorato2afb6eb2018-12-04 14:51:58 -08001337@generator('print')
1338class PrintGen(BaseGenerator):
1339 """Prints just the constants and values, separated by spaces, in an easy to
1340 parse format for use by other scripts.
1341
1342 Each line is just the identifier and the value, separated by a space.
1343 """
1344
1345 def add_opts(self, opt_group):
1346 opt_group.add_argument(
1347 'aid-header', help='An android_filesystem_config.h file.')
1348
1349 def __call__(self, args):
1350
1351 hdr_parser = AIDHeaderParser(args['aid-header'])
1352 aids = hdr_parser.aids
1353
1354 aids.sort(key=lambda item: int(item.normalized_value))
1355
1356 for aid in aids:
1357 print '%s %s' % (aid.identifier, aid.normalized_value)
1358
William Roberts1c4721c2016-04-26 13:05:34 -07001359
William Robertsc950a352016-03-04 18:12:29 -08001360def main():
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001361 """Main entry point for execution."""
William Robertsc950a352016-03-04 18:12:29 -08001362
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001363 opt_parser = argparse.ArgumentParser(
1364 description='A tool for parsing fsconfig config files and producing' +
1365 'digestable outputs.')
1366 subparser = opt_parser.add_subparsers(help='generators')
William Robertsc950a352016-03-04 18:12:29 -08001367
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001368 gens = generator.get()
William Robertsc950a352016-03-04 18:12:29 -08001369
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001370 # for each gen, instantiate and add them as an option
1371 for name, gen in gens.iteritems():
William Robertsc950a352016-03-04 18:12:29 -08001372
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001373 generator_option_parser = subparser.add_parser(name, help=gen.__doc__)
1374 generator_option_parser.set_defaults(which=name)
William Roberts8cb6a182016-04-08 22:06:19 -07001375
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001376 opt_group = generator_option_parser.add_argument_group(name +
1377 ' options')
1378 gen.add_opts(opt_group)
William Roberts8cb6a182016-04-08 22:06:19 -07001379
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001380 args = opt_parser.parse_args()
1381
1382 args_as_dict = vars(args)
1383 which = args_as_dict['which']
1384 del args_as_dict['which']
1385
1386 gens[which](args_as_dict)
1387
William Robertsc950a352016-03-04 18:12:29 -08001388
1389if __name__ == '__main__':
1390 main()