blob: 098fde664f612626bc6342aa46808d467145281c [file] [log] [blame]
Chema Gonzalezf8b158b2020-10-28 09:46:22 -07001#!/usr/bin/env python2
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.
Joel Galenson154ac1d2020-07-30 14:23:31 -0700302 It provides some basic checks. The information extracted from this file can
303 later be used to quickly check other things (like oem ranges) as well as
304 generating a mapping of names to uids. It was primarily designed to parse
305 the private/android_filesystem_config.h, but any C header should work.
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000306 """
307
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000308 _SKIP_AIDS = [
309 re.compile(r'%sUNUSED[0-9].*' % AID.PREFIX),
Tom Cherry766adc92019-02-13 14:24:52 -0800310 re.compile(r'%sAPP' % AID.PREFIX),
311 re.compile(r'%sUSER' % AID.PREFIX)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000312 ]
313 _AID_DEFINE = re.compile(r'\s*#define\s+%s.*' % AID.PREFIX)
Tom Cherryfb303a52019-07-11 15:31:36 -0700314 _RESERVED_RANGE = re.compile(
315 r'#define AID_(.+)_RESERVED_\d*_*(START|END)\s+(\d+)')
316
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000317 # AID lines cannot end with _START or _END, ie AID_FOO is OK
318 # but AID_FOO_START is skiped. Note that AID_FOOSTART is NOT skipped.
Tom Cherryfb303a52019-07-11 15:31:36 -0700319 _AID_SKIP_RANGE = ['_START', '_END']
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000320 _COLLISION_OK = ['AID_APP', 'AID_APP_START', 'AID_USER', 'AID_USER_OFFSET']
321
322 def __init__(self, aid_header):
323 """
324 Args:
325 aid_header (str): file name for the header
326 file containing AID entries.
327 """
328 self._aid_header = aid_header
329 self._aid_name_to_value = {}
330 self._aid_value_to_name = {}
Tom Cherryfb303a52019-07-11 15:31:36 -0700331 self._ranges = {}
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000332
333 with open(aid_header) as open_file:
334 self._parse(open_file)
William Roberts64edf5b2016-04-11 17:12:47 -0700335
336 try:
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000337 self._process_and_check()
338 except ValueError as exception:
339 sys.exit('Error processing parsed data: "%s"' % (str(exception)))
340
341 def _parse(self, aid_file):
342 """Parses an AID header file. Internal use only.
343
344 Args:
345 aid_file (file): The open AID header file to parse.
346 """
347
348 for lineno, line in enumerate(aid_file):
William Roberts820421c2016-12-13 19:12:35 -0800349
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000350 def error_message(msg):
351 """Creates an error message with the current parsing state."""
William Roberts4165c632016-12-13 19:17:07 -0800352 # pylint: disable=cell-var-from-loop
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000353 return 'Error "{}" in file: "{}" on line: {}'.format(
354 msg, self._aid_header, str(lineno))
355
Tom Cherryfb303a52019-07-11 15:31:36 -0700356 range_match = self._RESERVED_RANGE.match(line)
357 if range_match:
358 partition = range_match.group(1).lower()
359 value = int(range_match.group(3), 0)
360
361 if partition == 'oem':
362 partition = 'vendor'
363
364 if partition in self._ranges:
365 if isinstance(self._ranges[partition][-1], int):
366 self._ranges[partition][-1] = (
367 self._ranges[partition][-1], value)
368 else:
369 self._ranges[partition].append(value)
370 else:
371 self._ranges[partition] = [value]
372
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000373 if AIDHeaderParser._AID_DEFINE.match(line):
374 chunks = line.split()
375 identifier = chunks[1]
376 value = chunks[2]
377
Tom Cherry766adc92019-02-13 14:24:52 -0800378 if any(
379 x.match(identifier)
380 for x in AIDHeaderParser._SKIP_AIDS):
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000381 continue
382
383 try:
Tom Cherryfb303a52019-07-11 15:31:36 -0700384 if not any(
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000385 identifier.endswith(x)
386 for x in AIDHeaderParser._AID_SKIP_RANGE):
387 self._handle_aid(identifier, value)
388 except ValueError as exception:
William Roberts820421c2016-12-13 19:12:35 -0800389 sys.exit(
Tom Cherry766adc92019-02-13 14:24:52 -0800390 error_message('{} for "{}"'.format(
391 exception, identifier)))
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000392
393 def _handle_aid(self, identifier, value):
394 """Handle an AID C #define.
395
Joel Galenson154ac1d2020-07-30 14:23:31 -0700396 Handles an AID, quick checking, generating the friendly name and
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000397 adding it to the internal maps. Internal use only.
398
399 Args:
400 identifier (str): The name of the #define identifier. ie AID_FOO.
401 value (str): The value associated with the identifier.
402
403 Raises:
404 ValueError: With message set to indicate the error.
405 """
406
Wei Wang77e329a2018-06-05 16:00:07 -0700407 aid = AID(identifier, value, self._aid_header, '/system/bin/sh')
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000408
409 # duplicate name
410 if aid.friendly in self._aid_name_to_value:
411 raise ValueError('Duplicate aid "%s"' % identifier)
412
413 if value in self._aid_value_to_name and aid.identifier not in AIDHeaderParser._COLLISION_OK:
Tom Cherry766adc92019-02-13 14:24:52 -0800414 raise ValueError(
415 'Duplicate aid value "%s" for %s' % (value, identifier))
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000416
417 self._aid_name_to_value[aid.friendly] = aid
418 self._aid_value_to_name[value] = aid.friendly
419
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000420 def _process_and_check(self):
421 """Process, check and populate internal data structures.
William Roberts64edf5b2016-04-11 17:12:47 -0700422
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000423 After parsing and generating the internal data structures, this method
Joel Galenson154ac1d2020-07-30 14:23:31 -0700424 is responsible for quickly checking ALL of the acquired data.
William Roberts64edf5b2016-04-11 17:12:47 -0700425
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000426 Raises:
427 ValueError: With the message set to indicate the specific error.
428 """
William Roberts64edf5b2016-04-11 17:12:47 -0700429
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000430 # Check for overlapping ranges
Tom Cherryfb303a52019-07-11 15:31:36 -0700431 for ranges in self._ranges.values():
432 for i, range1 in enumerate(ranges):
433 for range2 in ranges[i + 1:]:
434 if AIDHeaderParser._is_overlap(range1, range2):
435 raise ValueError(
436 "Overlapping OEM Ranges found %s and %s" %
437 (str(range1), str(range2)))
William Roberts64edf5b2016-04-11 17:12:47 -0700438
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000439 # No core AIDs should be within any oem range.
440 for aid in self._aid_value_to_name:
Tom Cherryfb303a52019-07-11 15:31:36 -0700441 for ranges in self._ranges.values():
442 if Utils.in_any_range(aid, ranges):
443 name = self._aid_value_to_name[aid]
444 raise ValueError(
445 'AID "%s" value: %u within reserved OEM Range: "%s"' %
446 (name, aid, str(ranges)))
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000447
448 @property
Tom Cherryfb303a52019-07-11 15:31:36 -0700449 def ranges(self):
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000450 """Retrieves the OEM closed ranges as a list of tuples.
451
452 Returns:
453 A list of closed range tuples: [ (0, 42), (50, 105) ... ]
454 """
Tom Cherryfb303a52019-07-11 15:31:36 -0700455 return self._ranges
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000456
457 @property
458 def aids(self):
459 """Retrieves the list of found AIDs.
460
461 Returns:
462 A list of AID() objects.
463 """
464 return self._aid_name_to_value.values()
465
466 @staticmethod
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000467 def _is_overlap(range_a, range_b):
468 """Calculates the overlap of two range tuples.
469
470 A range tuple is a closed range. A closed range includes its endpoints.
471 Note that python tuples use () notation which collides with the
472 mathematical notation for open ranges.
473
474 Args:
475 range_a: The first tuple closed range eg (0, 5).
476 range_b: The second tuple closed range eg (3, 7).
477
478 Returns:
479 True if they overlap, False otherwise.
480 """
481
482 return max(range_a[0], range_b[0]) <= min(range_a[1], range_b[1])
William Roberts64edf5b2016-04-11 17:12:47 -0700483
484
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000485class FSConfigFileParser(object):
486 """Parses a config.fs ini format file.
William Roberts11c29282016-04-09 10:32:30 -0700487
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000488 This class is responsible for parsing the config.fs ini format files.
489 It collects and checks all the data in these files and makes it available
490 for consumption post processed.
491 """
William Roberts11c29282016-04-09 10:32:30 -0700492
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000493 # These _AID vars work together to ensure that an AID section name
494 # cannot contain invalid characters for a C define or a passwd/group file.
495 # Since _AID_PREFIX is within the set of _AID_MATCH the error logic only
496 # checks end, if you change this, you may have to update the error
497 # detection code.
498 _AID_MATCH = re.compile('%s[A-Z0-9_]+' % AID.PREFIX)
499 _AID_ERR_MSG = 'Expecting upper case, a number or underscore'
William Roberts5f059a72016-04-25 10:36:45 -0700500
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000501 # list of handler to required options, used to identify the
502 # parsing section
Tom Cherry766adc92019-02-13 14:24:52 -0800503 _SECTIONS = [('_handle_aid', ('value', )),
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000504 ('_handle_path', ('mode', 'user', 'group', 'caps'))]
505
Tom Cherryfb303a52019-07-11 15:31:36 -0700506 def __init__(self, config_files, ranges):
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000507 """
508 Args:
509 config_files ([str]): The list of config.fs files to parse.
510 Note the filename is not important.
Tom Cherryfb303a52019-07-11 15:31:36 -0700511 ranges ({str,[()]): Dictionary of partitions and a list of tuples that correspond to their ranges
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000512 """
513
514 self._files = []
515 self._dirs = []
516 self._aids = []
517
518 self._seen_paths = {}
519 # (name to file, value to aid)
520 self._seen_aids = ({}, {})
521
Tom Cherryfb303a52019-07-11 15:31:36 -0700522 self._ranges = ranges
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000523
524 self._config_files = config_files
525
526 for config_file in self._config_files:
527 self._parse(config_file)
528
529 def _parse(self, file_name):
530 """Parses and verifies config.fs files. Internal use only.
531
532 Args:
533 file_name (str): The config.fs (PythonConfigParser file format)
534 file to parse.
535
536 Raises:
537 Anything raised by ConfigParser.read()
538 """
539
540 # Separate config parsers for each file found. If you use
541 # read(filenames...) later files can override earlier files which is
542 # not what we want. Track state across files and enforce with
543 # _handle_dup(). Note, strict ConfigParser is set to true in
544 # Python >= 3.2, so in previous versions same file sections can
545 # override previous
546 # sections.
William Robertsc950a352016-03-04 18:12:29 -0800547
548 config = ConfigParser.ConfigParser()
549 config.read(file_name)
550
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000551 for section in config.sections():
William Robertsc950a352016-03-04 18:12:29 -0800552
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000553 found = False
William Roberts5f059a72016-04-25 10:36:45 -0700554
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000555 for test in FSConfigFileParser._SECTIONS:
556 handler = test[0]
557 options = test[1]
William Roberts5f059a72016-04-25 10:36:45 -0700558
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000559 if all([config.has_option(section, item) for item in options]):
560 handler = getattr(self, handler)
561 handler(file_name, section, config)
562 found = True
563 break
William Roberts5f059a72016-04-25 10:36:45 -0700564
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000565 if not found:
Tom Cherry766adc92019-02-13 14:24:52 -0800566 sys.exit('Invalid section "%s" in file: "%s"' % (section,
567 file_name))
William Roberts11c29282016-04-09 10:32:30 -0700568
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000569 # sort entries:
570 # * specified path before prefix match
571 # ** ie foo before f*
572 # * lexicographical less than before other
573 # ** ie boo before foo
574 # Given these paths:
575 # paths=['ac', 'a', 'acd', 'an', 'a*', 'aa', 'ac*']
576 # The sort order would be:
577 # paths=['a', 'aa', 'ac', 'acd', 'an', 'ac*', 'a*']
578 # Thus the fs_config tools will match on specified paths before
579 # attempting prefix, and match on the longest matching prefix.
580 self._files.sort(key=FSConfigFileParser._file_key)
581
582 # sort on value of (file_name, name, value, strvalue)
583 # This is only cosmetic so AIDS are arranged in ascending order
584 # within the generated file.
585 self._aids.sort(key=lambda item: item.normalized_value)
586
Tom Cherryfb303a52019-07-11 15:31:36 -0700587 def _verify_valid_range(self, aid):
588 """Verified an AID entry is in a valid range"""
589
590 ranges = None
591
592 partitions = self._ranges.keys()
593 partitions.sort(key=len, reverse=True)
594 for partition in partitions:
595 if aid.friendly.startswith(partition):
596 ranges = self._ranges[partition]
597 break
598
599 if ranges is None:
600 sys.exit('AID "%s" must be prefixed with a partition name' %
601 aid.friendly)
602
603 if not Utils.in_any_range(int(aid.value, 0), ranges):
604 emsg = '"value" for aid "%s" not in valid range %s, got: %s'
605 emsg = emsg % (aid.friendly, str(ranges), aid.value)
606 sys.exit(emsg)
607
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000608 def _handle_aid(self, file_name, section_name, config):
609 """Verifies an AID entry and adds it to the aid list.
610
611 Calls sys.exit() with a descriptive message of the failure.
612
613 Args:
614 file_name (str): The filename of the config file being parsed.
615 section_name (str): The section name currently being parsed.
616 config (ConfigParser): The ConfigParser section being parsed that
617 the option values will come from.
618 """
619
620 def error_message(msg):
621 """Creates an error message with current parsing state."""
622 return '{} for: "{}" file: "{}"'.format(msg, section_name,
623 file_name)
624
625 FSConfigFileParser._handle_dup_and_add('AID', file_name, section_name,
626 self._seen_aids[0])
627
628 match = FSConfigFileParser._AID_MATCH.match(section_name)
629 invalid = match.end() if match else len(AID.PREFIX)
630 if invalid != len(section_name):
631 tmp_errmsg = ('Invalid characters in AID section at "%d" for: "%s"'
632 % (invalid, FSConfigFileParser._AID_ERR_MSG))
633 sys.exit(error_message(tmp_errmsg))
634
635 value = config.get(section_name, 'value')
636
637 if not value:
638 sys.exit(error_message('Found specified but unset "value"'))
639
640 try:
Tom Cherryfb303a52019-07-11 15:31:36 -0700641 aid = AID(section_name, value, file_name, '/bin/sh')
Tom Cherryee0610e2018-02-08 14:26:53 -0800642 except ValueError as exception:
643 sys.exit(error_message(exception))
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000644
Tom Cherryfb303a52019-07-11 15:31:36 -0700645 self._verify_valid_range(aid)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000646
647 # use the normalized int value in the dict and detect
648 # duplicate definitions of the same value
649 FSConfigFileParser._handle_dup_and_add(
650 'AID', file_name, aid.normalized_value, self._seen_aids[1])
651
652 # Append aid tuple of (AID_*, base10(value), _path(value))
653 # We keep the _path version of value so we can print that out in the
654 # generated header so investigating parties can identify parts.
655 # We store the base10 value for sorting, so everything is ascending
656 # later.
657 self._aids.append(aid)
658
659 def _handle_path(self, file_name, section_name, config):
660 """Add a file capability entry to the internal list.
661
662 Handles a file capability entry, verifies it, and adds it to
663 to the internal dirs or files list based on path. If it ends
664 with a / its a dir. Internal use only.
665
666 Calls sys.exit() on any validation error with message set.
667
668 Args:
669 file_name (str): The current name of the file being parsed.
670 section_name (str): The name of the section to parse.
671 config (str): The config parser.
672 """
673
674 FSConfigFileParser._handle_dup_and_add('path', file_name, section_name,
675 self._seen_paths)
676
677 mode = config.get(section_name, 'mode')
678 user = config.get(section_name, 'user')
679 group = config.get(section_name, 'group')
680 caps = config.get(section_name, 'caps')
681
682 errmsg = ('Found specified but unset option: \"%s" in file: \"' +
683 file_name + '\"')
684
685 if not mode:
686 sys.exit(errmsg % 'mode')
687
688 if not user:
689 sys.exit(errmsg % 'user')
690
691 if not group:
692 sys.exit(errmsg % 'group')
693
694 if not caps:
695 sys.exit(errmsg % 'caps')
696
697 caps = caps.split()
698
699 tmp = []
700 for cap in caps:
701 try:
702 # test if string is int, if it is, use as is.
703 int(cap, 0)
Tom Cherry9d924f62019-02-13 14:02:30 -0800704 tmp.append(cap)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000705 except ValueError:
Tom Cherry9d924f62019-02-13 14:02:30 -0800706 tmp.append('CAP_' + cap.upper())
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000707
708 caps = tmp
709
710 if len(mode) == 3:
711 mode = '0' + mode
712
713 try:
714 int(mode, 8)
715 except ValueError:
716 sys.exit('Mode must be octal characters, got: "%s"' % mode)
717
718 if len(mode) != 4:
719 sys.exit('Mode must be 3 or 4 characters, got: "%s"' % mode)
720
Tom Cherry9d924f62019-02-13 14:02:30 -0800721 caps_str = ','.join(caps)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000722
723 entry = FSConfig(mode, user, group, caps_str, section_name, file_name)
724 if section_name[-1] == '/':
725 self._dirs.append(entry)
726 else:
727 self._files.append(entry)
728
729 @property
730 def files(self):
731 """Get the list of FSConfig file entries.
732
733 Returns:
734 a list of FSConfig() objects for file paths.
735 """
736 return self._files
737
738 @property
739 def dirs(self):
740 """Get the list of FSConfig dir entries.
741
742 Returns:
743 a list of FSConfig() objects for directory paths.
744 """
745 return self._dirs
746
747 @property
748 def aids(self):
749 """Get the list of AID entries.
750
751 Returns:
752 a list of AID() objects.
753 """
754 return self._aids
755
756 @staticmethod
757 def _file_key(fs_config):
758 """Used as the key paramter to sort.
759
760 This is used as a the function to the key parameter of a sort.
761 it wraps the string supplied in a class that implements the
762 appropriate __lt__ operator for the sort on path strings. See
763 StringWrapper class for more details.
764
765 Args:
766 fs_config (FSConfig): A FSConfig entry.
767
768 Returns:
769 A StringWrapper object
770 """
771
772 # Wrapper class for custom prefix matching strings
773 class StringWrapper(object):
774 """Wrapper class used for sorting prefix strings.
775
776 The algorithm is as follows:
777 - specified path before prefix match
778 - ie foo before f*
779 - lexicographical less than before other
780 - ie boo before foo
781
782 Given these paths:
783 paths=['ac', 'a', 'acd', 'an', 'a*', 'aa', 'ac*']
784 The sort order would be:
785 paths=['a', 'aa', 'ac', 'acd', 'an', 'ac*', 'a*']
786 Thus the fs_config tools will match on specified paths before
787 attempting prefix, and match on the longest matching prefix.
788 """
789
790 def __init__(self, path):
791 """
792 Args:
793 path (str): the path string to wrap.
794 """
795 self.is_prefix = path[-1] == '*'
796 if self.is_prefix:
797 self.path = path[:-1]
798 else:
799 self.path = path
800
801 def __lt__(self, other):
802
803 # if were both suffixed the smallest string
804 # is 'bigger'
805 if self.is_prefix and other.is_prefix:
806 result = len(self.path) > len(other.path)
807 # If I am an the suffix match, im bigger
808 elif self.is_prefix:
809 result = False
810 # If other is the suffix match, he's bigger
811 elif other.is_prefix:
812 result = True
813 # Alphabetical
814 else:
815 result = self.path < other.path
816 return result
817
818 return StringWrapper(fs_config.path)
819
820 @staticmethod
821 def _handle_dup_and_add(name, file_name, section_name, seen):
822 """Tracks and detects duplicates. Internal use only.
823
824 Calls sys.exit() on a duplicate.
825
826 Args:
827 name (str): The name to use in the error reporting. The pretty
828 name for the section.
829 file_name (str): The file currently being parsed.
830 section_name (str): The name of the section. This would be path
831 or identifier depending on what's being parsed.
832 seen (dict): The dictionary of seen things to check against.
833 """
834 if section_name in seen:
835 dups = '"' + seen[section_name] + '" and '
836 dups += file_name
837 sys.exit('Duplicate %s "%s" found in files: %s' %
838 (name, section_name, dups))
839
840 seen[section_name] = file_name
841
842
843class BaseGenerator(object):
844 """Interface for Generators.
845
846 Base class for generators, generators should implement
847 these method stubs.
848 """
849
850 def add_opts(self, opt_group):
851 """Used to add per-generator options to the command line.
852
853 Args:
854 opt_group (argument group object): The argument group to append to.
855 See the ArgParse docs for more details.
856 """
857
858 raise NotImplementedError("Not Implemented")
859
860 def __call__(self, args):
861 """This is called to do whatever magic the generator does.
862
863 Args:
864 args (dict): The arguments from ArgParse as a dictionary.
865 ie if you specified an argument of foo in add_opts, access
866 it via args['foo']
867 """
868
869 raise NotImplementedError("Not Implemented")
870
871
872@generator('fsconfig')
873class FSConfigGen(BaseGenerator):
874 """Generates the android_filesystem_config.h file.
875
876 Output is used in generating fs_config_files and fs_config_dirs.
877 """
878
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000879 def __init__(self, *args, **kwargs):
880 BaseGenerator.__init__(args, kwargs)
881
882 self._oem_parser = None
883 self._base_parser = None
884 self._friendly_to_aid = None
Tom Cherry9d924f62019-02-13 14:02:30 -0800885 self._id_to_aid = None
886 self._capability_parser = None
887
888 self._partition = None
889 self._all_partitions = None
890 self._out_file = None
891 self._generate_files = False
892 self._generate_dirs = False
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000893
894 def add_opts(self, opt_group):
895
896 opt_group.add_argument(
897 'fsconfig', nargs='+', help='The list of fsconfig files to parse')
898
899 opt_group.add_argument(
900 '--aid-header',
901 required=True,
902 help='An android_filesystem_config.h file'
903 ' to parse AIDs and OEM Ranges from')
904
Tom Cherry9d924f62019-02-13 14:02:30 -0800905 opt_group.add_argument(
906 '--capability-header',
907 required=True,
908 help='A capability.h file to parse capability defines from')
909
910 opt_group.add_argument(
911 '--partition',
912 required=True,
913 help='Partition to generate contents for')
914
915 opt_group.add_argument(
916 '--all-partitions',
917 help='Comma separated list of all possible partitions, used to'
918 ' ignore these partitions when generating the output for the system partition'
919 )
920
921 opt_group.add_argument(
922 '--files', action='store_true', help='Output fs_config_files')
923
924 opt_group.add_argument(
925 '--dirs', action='store_true', help='Output fs_config_dirs')
926
927 opt_group.add_argument('--out_file', required=True, help='Output file')
928
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000929 def __call__(self, args):
930
Tom Cherry9d924f62019-02-13 14:02:30 -0800931 self._capability_parser = CapabilityHeaderParser(
932 args['capability_header'])
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000933 self._base_parser = AIDHeaderParser(args['aid_header'])
934 self._oem_parser = FSConfigFileParser(args['fsconfig'],
Tom Cherryfb303a52019-07-11 15:31:36 -0700935 self._base_parser.ranges)
Tom Cherry9d924f62019-02-13 14:02:30 -0800936
937 self._partition = args['partition']
938 self._all_partitions = args['all_partitions']
Tom Cherry9d924f62019-02-13 14:02:30 -0800939
940 self._out_file = args['out_file']
941
942 self._generate_files = args['files']
943 self._generate_dirs = args['dirs']
944
945 if self._generate_files and self._generate_dirs:
946 sys.exit('Only one of --files or --dirs can be provided')
947
948 if not self._generate_files and not self._generate_dirs:
949 sys.exit('One of --files or --dirs must be provided')
950
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000951 base_aids = self._base_parser.aids
952 oem_aids = self._oem_parser.aids
953
954 # Detect name collisions on AIDs. Since friendly works as the
955 # identifier for collision testing and we need friendly later on for
956 # name resolution, just calculate and use friendly.
957 # {aid.friendly: aid for aid in base_aids}
958 base_friendly = {aid.friendly: aid for aid in base_aids}
959 oem_friendly = {aid.friendly: aid for aid in oem_aids}
960
961 base_set = set(base_friendly.keys())
962 oem_set = set(oem_friendly.keys())
963
964 common = base_set & oem_set
965
Tom Cherry766adc92019-02-13 14:24:52 -0800966 if common:
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000967 emsg = 'Following AID Collisions detected for: \n'
968 for friendly in common:
969 base = base_friendly[friendly]
970 oem = oem_friendly[friendly]
971 emsg += (
972 'Identifier: "%s" Friendly Name: "%s" '
973 'found in file "%s" and "%s"' %
974 (base.identifier, base.friendly, base.found, oem.found))
975 sys.exit(emsg)
976
977 self._friendly_to_aid = oem_friendly
978 self._friendly_to_aid.update(base_friendly)
979
Tom Cherry9d924f62019-02-13 14:02:30 -0800980 self._id_to_aid = {aid.identifier: aid for aid in base_aids}
981 self._id_to_aid.update({aid.identifier: aid for aid in oem_aids})
982
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000983 self._generate()
984
Tom Cherry9d924f62019-02-13 14:02:30 -0800985 def _to_fs_entry(self, fs_config, out_file):
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000986 """Converts an FSConfig entry to an fs entry.
987
Tom Cherry9d924f62019-02-13 14:02:30 -0800988 Writes the fs_config contents to the output file.
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000989
990 Calls sys.exit() on error.
991
992 Args:
Tom Cherry9d924f62019-02-13 14:02:30 -0800993 fs_config (FSConfig): The entry to convert to write to file.
994 file (File): The file to write to.
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000995 """
996
997 # Get some short names
998 mode = fs_config.mode
999 user = fs_config.user
1000 group = fs_config.group
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001001 caps = fs_config.caps
1002 path = fs_config.path
1003
Tom Cherry9d924f62019-02-13 14:02:30 -08001004 emsg = 'Cannot convert "%s" to identifier!'
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001005
Tom Cherry9d924f62019-02-13 14:02:30 -08001006 # convert mode from octal string to integer
1007 mode = int(mode, 8)
1008
1009 # remap names to values
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001010 if AID.is_friendly(user):
1011 if user not in self._friendly_to_aid:
1012 sys.exit(emsg % user)
Tom Cherry9d924f62019-02-13 14:02:30 -08001013 user = self._friendly_to_aid[user].value
1014 else:
1015 if user not in self._id_to_aid:
1016 sys.exit(emsg % user)
1017 user = self._id_to_aid[user].value
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001018
1019 if AID.is_friendly(group):
1020 if group not in self._friendly_to_aid:
1021 sys.exit(emsg % group)
Tom Cherry9d924f62019-02-13 14:02:30 -08001022 group = self._friendly_to_aid[group].value
1023 else:
1024 if group not in self._id_to_aid:
1025 sys.exit(emsg % group)
1026 group = self._id_to_aid[group].value
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001027
Tom Cherry9d924f62019-02-13 14:02:30 -08001028 caps_dict = self._capability_parser.caps
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001029
Tom Cherry9d924f62019-02-13 14:02:30 -08001030 caps_value = 0
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001031
Tom Cherry9d924f62019-02-13 14:02:30 -08001032 try:
1033 # test if caps is an int
1034 caps_value = int(caps, 0)
1035 except ValueError:
1036 caps_split = caps.split(',')
1037 for cap in caps_split:
1038 if cap not in caps_dict:
Roland Levillain5ca32df2019-08-07 15:50:02 +01001039 sys.exit('Unknown cap "%s" found!' % cap)
Tom Cherry9d924f62019-02-13 14:02:30 -08001040 caps_value += 1 << caps_dict[cap]
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001041
Tom Cherry9d924f62019-02-13 14:02:30 -08001042 path_length_with_null = len(path) + 1
1043 path_length_aligned_64 = (path_length_with_null + 7) & ~7
1044 # 16 bytes of header plus the path length with alignment
1045 length = 16 + path_length_aligned_64
1046
1047 length_binary = bytearray(ctypes.c_uint16(length))
1048 mode_binary = bytearray(ctypes.c_uint16(mode))
1049 user_binary = bytearray(ctypes.c_uint16(int(user, 0)))
1050 group_binary = bytearray(ctypes.c_uint16(int(group, 0)))
1051 caps_binary = bytearray(ctypes.c_uint64(caps_value))
1052 path_binary = ctypes.create_string_buffer(path,
1053 path_length_aligned_64).raw
1054
1055 out_file.write(length_binary)
1056 out_file.write(mode_binary)
1057 out_file.write(user_binary)
1058 out_file.write(group_binary)
1059 out_file.write(caps_binary)
1060 out_file.write(path_binary)
1061
1062 def _emit_entry(self, fs_config):
1063 """Returns a boolean whether or not to emit the input fs_config"""
1064
1065 path = fs_config.path
1066
1067 if self._partition == 'system':
dianlujitao5dcbe702019-09-10 15:42:56 +08001068 if not self._all_partitions:
1069 return True
Tom Cherry9d924f62019-02-13 14:02:30 -08001070 for skip_partition in self._all_partitions.split(','):
1071 if path.startswith(skip_partition) or path.startswith(
1072 'system/' + skip_partition):
1073 return False
1074 return True
1075 else:
1076 if path.startswith(
1077 self._partition) or path.startswith('system/' +
1078 self._partition):
1079 return True
1080 return False
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001081
1082 def _generate(self):
1083 """Generates an OEM android_filesystem_config.h header file to stdout.
1084
1085 Args:
1086 files ([FSConfig]): A list of FSConfig objects for file entries.
1087 dirs ([FSConfig]): A list of FSConfig objects for directory
1088 entries.
1089 aids ([AIDS]): A list of AID objects for Android Id entries.
1090 """
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001091 dirs = self._oem_parser.dirs
1092 files = self._oem_parser.files
William Roberts8f42ce72016-04-25 12:27:43 -07001093
Tom Cherry9d924f62019-02-13 14:02:30 -08001094 if self._generate_files:
1095 with open(self._out_file, 'wb') as open_file:
1096 for fs_config in files:
1097 if self._emit_entry(fs_config):
1098 self._to_fs_entry(fs_config, open_file)
William Robertsc950a352016-03-04 18:12:29 -08001099
Tom Cherry9d924f62019-02-13 14:02:30 -08001100 if self._generate_dirs:
1101 with open(self._out_file, 'wb') as open_file:
1102 for dir_entry in dirs:
1103 if self._emit_entry(dir_entry):
1104 self._to_fs_entry(dir_entry, open_file)
William Robertsc950a352016-03-04 18:12:29 -08001105
William Robertsc950a352016-03-04 18:12:29 -08001106
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001107@generator('aidarray')
1108class AIDArrayGen(BaseGenerator):
1109 """Generates the android_id static array."""
1110
1111 _GENERATED = ('/*\n'
1112 ' * THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY!\n'
1113 ' */')
1114
1115 _INCLUDE = '#include <private/android_filesystem_config.h>'
1116
Vic Yang5b3a7c02018-12-03 21:40:33 -08001117 # Note that the android_id name field is of type 'const char[]' instead of
1118 # 'const char*'. While this seems less straightforward as we need to
1119 # calculate the max length of all names, this allows the entire android_ids
1120 # table to be placed in .rodata section instead of .data.rel.ro section,
1121 # resulting in less memory pressure.
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001122 _STRUCT_FS_CONFIG = textwrap.dedent("""
1123 struct android_id_info {
Vic Yang5b3a7c02018-12-03 21:40:33 -08001124 const char name[%d];
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001125 unsigned aid;
1126 };""")
1127
1128 _OPEN_ID_ARRAY = 'static const struct android_id_info android_ids[] = {'
1129
1130 _ID_ENTRY = ' { "%s", %s },'
1131
1132 _CLOSE_FILE_STRUCT = '};'
1133
1134 _COUNT = ('#define android_id_count \\\n'
1135 ' (sizeof(android_ids) / sizeof(android_ids[0]))')
1136
1137 def add_opts(self, opt_group):
1138
1139 opt_group.add_argument(
1140 'hdrfile', help='The android_filesystem_config.h'
1141 'file to parse')
1142
1143 def __call__(self, args):
1144
1145 hdr = AIDHeaderParser(args['hdrfile'])
Vic Yang5b3a7c02018-12-03 21:40:33 -08001146 max_name_length = max(len(aid.friendly) + 1 for aid in hdr.aids)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001147
1148 print AIDArrayGen._GENERATED
1149 print
1150 print AIDArrayGen._INCLUDE
1151 print
Vic Yang5b3a7c02018-12-03 21:40:33 -08001152 print AIDArrayGen._STRUCT_FS_CONFIG % max_name_length
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001153 print
1154 print AIDArrayGen._OPEN_ID_ARRAY
1155
1156 for aid in hdr.aids:
1157 print AIDArrayGen._ID_ENTRY % (aid.friendly, aid.identifier)
1158
1159 print AIDArrayGen._CLOSE_FILE_STRUCT
1160 print
1161 print AIDArrayGen._COUNT
1162 print
1163
1164
1165@generator('oemaid')
1166class OEMAidGen(BaseGenerator):
1167 """Generates the OEM AID_<name> value header file."""
1168
1169 _GENERATED = ('/*\n'
1170 ' * THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY!\n'
1171 ' */')
1172
1173 _GENERIC_DEFINE = "#define %s\t%s"
1174
1175 _FILE_COMMENT = '// Defined in file: \"%s\"'
1176
1177 # Intentional trailing newline for readability.
1178 _FILE_IFNDEF_DEFINE = ('#ifndef GENERATED_OEM_AIDS_H_\n'
1179 '#define GENERATED_OEM_AIDS_H_\n')
1180
1181 _FILE_ENDIF = '#endif'
1182
1183 def __init__(self):
1184
1185 self._old_file = None
1186
1187 def add_opts(self, opt_group):
1188
1189 opt_group.add_argument(
1190 'fsconfig', nargs='+', help='The list of fsconfig files to parse.')
1191
1192 opt_group.add_argument(
1193 '--aid-header',
1194 required=True,
1195 help='An android_filesystem_config.h file'
1196 'to parse AIDs and OEM Ranges from')
1197
1198 def __call__(self, args):
1199
1200 hdr_parser = AIDHeaderParser(args['aid_header'])
1201
Tom Cherryfb303a52019-07-11 15:31:36 -07001202 parser = FSConfigFileParser(args['fsconfig'], hdr_parser.ranges)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001203
1204 print OEMAidGen._GENERATED
1205
1206 print OEMAidGen._FILE_IFNDEF_DEFINE
1207
1208 for aid in parser.aids:
1209 self._print_aid(aid)
1210 print
1211
1212 print OEMAidGen._FILE_ENDIF
1213
1214 def _print_aid(self, aid):
1215 """Prints a valid #define AID identifier to stdout.
1216
1217 Args:
1218 aid to print
1219 """
1220
1221 # print the source file location of the AID
1222 found_file = aid.found
1223 if found_file != self._old_file:
1224 print OEMAidGen._FILE_COMMENT % found_file
1225 self._old_file = found_file
1226
1227 print OEMAidGen._GENERIC_DEFINE % (aid.identifier, aid.value)
1228
1229
1230@generator('passwd')
1231class PasswdGen(BaseGenerator):
1232 """Generates the /etc/passwd file per man (5) passwd."""
1233
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001234 def __init__(self):
1235
1236 self._old_file = None
1237
1238 def add_opts(self, opt_group):
1239
1240 opt_group.add_argument(
1241 'fsconfig', nargs='+', help='The list of fsconfig files to parse.')
1242
1243 opt_group.add_argument(
1244 '--aid-header',
1245 required=True,
1246 help='An android_filesystem_config.h file'
1247 'to parse AIDs and OEM Ranges from')
1248
Tom Cherry2d197a12018-05-14 13:14:41 -07001249 opt_group.add_argument(
Tom Cherryfb303a52019-07-11 15:31:36 -07001250 '--partition',
1251 required=True,
1252 help=
1253 'Filter the input file and only output entries for the given partition.'
1254 )
Tom Cherry2d197a12018-05-14 13:14:41 -07001255
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001256 def __call__(self, args):
1257
1258 hdr_parser = AIDHeaderParser(args['aid_header'])
1259
Tom Cherryfb303a52019-07-11 15:31:36 -07001260 parser = FSConfigFileParser(args['fsconfig'], hdr_parser.ranges)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001261
Tom Cherryfb303a52019-07-11 15:31:36 -07001262 filter_partition = args['partition']
Tom Cherry2d197a12018-05-14 13:14:41 -07001263
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001264 aids = parser.aids
1265
1266 # nothing to do if no aids defined
Tom Cherry766adc92019-02-13 14:24:52 -08001267 if not aids:
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001268 return
1269
Tom Cherryfb303a52019-07-11 15:31:36 -07001270 aids_by_partition = {}
1271 partitions = hdr_parser.ranges.keys()
1272 partitions.sort(key=len, reverse=True)
1273
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001274 for aid in aids:
Tom Cherryfb303a52019-07-11 15:31:36 -07001275 for partition in partitions:
1276 if aid.friendly.startswith(partition):
1277 if partition in aids_by_partition:
1278 aids_by_partition[partition].append(aid)
1279 else:
1280 aids_by_partition[partition] = [aid]
1281 break
1282
1283 if filter_partition in aids_by_partition:
1284 for aid in aids_by_partition[filter_partition]:
Tom Cherry2d197a12018-05-14 13:14:41 -07001285 self._print_formatted_line(aid)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001286
1287 def _print_formatted_line(self, aid):
1288 """Prints the aid to stdout in the passwd format. Internal use only.
1289
1290 Colon delimited:
1291 login name, friendly name
1292 encrypted password (optional)
1293 uid (int)
1294 gid (int)
1295 User name or comment field
1296 home directory
1297 interpreter (optional)
1298
1299 Args:
1300 aid (AID): The aid to print.
1301 """
1302 if self._old_file != aid.found:
1303 self._old_file = aid.found
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001304
1305 try:
1306 logon, uid = Utils.get_login_and_uid_cleansed(aid)
1307 except ValueError as exception:
1308 sys.exit(exception)
1309
Wei Wang77e329a2018-06-05 16:00:07 -07001310 print "%s::%s:%s::/:%s" % (logon, uid, uid, aid.login_shell)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001311
1312
1313@generator('group')
1314class GroupGen(PasswdGen):
1315 """Generates the /etc/group file per man (5) group."""
1316
1317 # Overrides parent
1318 def _print_formatted_line(self, aid):
1319 """Prints the aid to stdout in the group format. Internal use only.
1320
1321 Formatted (per man 5 group) like:
1322 group_name:password:GID:user_list
1323
1324 Args:
1325 aid (AID): The aid to print.
1326 """
1327 if self._old_file != aid.found:
1328 self._old_file = aid.found
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001329
1330 try:
1331 logon, uid = Utils.get_login_and_uid_cleansed(aid)
1332 except ValueError as exception:
1333 sys.exit(exception)
1334
1335 print "%s::%s:" % (logon, uid)
1336
Tom Cherry766adc92019-02-13 14:24:52 -08001337
Joe Onorato2afb6eb2018-12-04 14:51:58 -08001338@generator('print')
1339class PrintGen(BaseGenerator):
1340 """Prints just the constants and values, separated by spaces, in an easy to
1341 parse format for use by other scripts.
1342
1343 Each line is just the identifier and the value, separated by a space.
1344 """
1345
1346 def add_opts(self, opt_group):
1347 opt_group.add_argument(
1348 'aid-header', help='An android_filesystem_config.h file.')
1349
1350 def __call__(self, args):
1351
1352 hdr_parser = AIDHeaderParser(args['aid-header'])
1353 aids = hdr_parser.aids
1354
1355 aids.sort(key=lambda item: int(item.normalized_value))
1356
1357 for aid in aids:
1358 print '%s %s' % (aid.identifier, aid.normalized_value)
1359
William Roberts1c4721c2016-04-26 13:05:34 -07001360
William Robertsc950a352016-03-04 18:12:29 -08001361def main():
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001362 """Main entry point for execution."""
William Robertsc950a352016-03-04 18:12:29 -08001363
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001364 opt_parser = argparse.ArgumentParser(
1365 description='A tool for parsing fsconfig config files and producing' +
1366 'digestable outputs.')
1367 subparser = opt_parser.add_subparsers(help='generators')
William Robertsc950a352016-03-04 18:12:29 -08001368
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001369 gens = generator.get()
William Robertsc950a352016-03-04 18:12:29 -08001370
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001371 # for each gen, instantiate and add them as an option
1372 for name, gen in gens.iteritems():
William Robertsc950a352016-03-04 18:12:29 -08001373
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001374 generator_option_parser = subparser.add_parser(name, help=gen.__doc__)
1375 generator_option_parser.set_defaults(which=name)
William Roberts8cb6a182016-04-08 22:06:19 -07001376
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001377 opt_group = generator_option_parser.add_argument_group(name +
1378 ' options')
1379 gen.add_opts(opt_group)
William Roberts8cb6a182016-04-08 22:06:19 -07001380
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001381 args = opt_parser.parse_args()
1382
1383 args_as_dict = vars(args)
1384 which = args_as_dict['which']
1385 del args_as_dict['which']
1386
1387 gens[which](args_as_dict)
1388
William Robertsc950a352016-03-04 18:12:29 -08001389
1390if __name__ == '__main__':
1391 main()