blob: 44480b8aa4df0a85f9a82d772a1d7f4b3f763ffc [file] [log] [blame]
Cole Faust216978e2022-10-20 11:39:45 -07001#!/usr/bin/env python3
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
Cole Faust216978e2022-10-20 11:39:45 -070014import 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
Cole Faustf08ab3d2022-10-20 13:36:05 -0700182 def __repr__(self):
183 return "AID { identifier = %s, value = %s, normalized_value = %s, login_shell = %s }" % (
184 self.identifier, self.value, self.normalized_value, self.login_shell)
185
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000186 @staticmethod
187 def is_friendly(name):
188 """Determines if an AID is a freindly name or C define.
189
190 For example if name is AID_SYSTEM it returns false, if name
191 was system, it would return true.
192
193 Returns:
194 True if name is a friendly name False otherwise.
195 """
196
197 return not name.startswith(AID.PREFIX)
198
199 @staticmethod
200 def _fixup_friendly(friendly):
201 """Fixup friendly names that historically don't follow the convention.
202
203 Args:
204 friendly (str): The friendly name.
205
206 Returns:
207 The fixedup friendly name as a str.
208 """
209
210 if friendly in AID._FIXUPS:
211 return AID._FIXUPS[friendly]
212
213 return friendly
214
215
216class FSConfig(object):
217 """Represents a filesystem config array entry.
218
219 Represents a file system configuration entry for specifying
220 file system capabilities.
221
222 Attributes:
223 mode (str): The mode of the file or directory.
224 user (str): The uid or #define identifier (AID_SYSTEM)
225 group (str): The gid or #define identifier (AID_SYSTEM)
226 caps (str): The capability set.
Tom Cherry766adc92019-02-13 14:24:52 -0800227 path (str): The path of the file or directory.
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000228 filename (str): The file it was found in.
229 """
230
231 def __init__(self, mode, user, group, caps, path, filename):
232 """
233 Args:
234 mode (str): The mode of the file or directory.
235 user (str): The uid or #define identifier (AID_SYSTEM)
236 group (str): The gid or #define identifier (AID_SYSTEM)
237 caps (str): The capability set as a list.
Tom Cherry766adc92019-02-13 14:24:52 -0800238 path (str): The path of the file or directory.
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000239 filename (str): The file it was found in.
240 """
241 self.mode = mode
242 self.user = user
243 self.group = group
244 self.caps = caps
245 self.path = path
246 self.filename = filename
247
248 def __eq__(self, other):
249
250 return self.mode == other.mode and self.user == other.user \
251 and self.group == other.group and self.caps == other.caps \
252 and self.path == other.path and self.filename == other.filename
253
Tom Cherry766adc92019-02-13 14:24:52 -0800254 def __repr__(self):
255 return 'FSConfig(%r, %r, %r, %r, %r, %r)' % (self.mode, self.user,
256 self.group, self.caps,
257 self.path, self.filename)
258
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000259
Tom Cherry9d924f62019-02-13 14:02:30 -0800260class CapabilityHeaderParser(object):
261 """Parses capability.h file
262
263 Parses a C header file and extracts lines starting with #define CAP_<name>.
264 """
265
266 _CAP_DEFINE = re.compile(r'\s*#define\s+(CAP_\S+)\s+(\S+)')
267 _SKIP_CAPS = ['CAP_LAST_CAP', 'CAP_TO_INDEX(x)', 'CAP_TO_MASK(x)']
268
269 def __init__(self, capability_header):
270 """
271 Args:
272 capability_header (str): file name for the header file containing AID entries.
273 """
274
275 self.caps = {}
276 with open(capability_header) as open_file:
277 self._parse(open_file)
278
279 def _parse(self, capability_file):
280 """Parses a capability header file. Internal use only.
281
282 Args:
283 capability_file (file): The open capability header file to parse.
284 """
285
286 for line in capability_file:
287 match = CapabilityHeaderParser._CAP_DEFINE.match(line)
288 if match:
289 cap = match.group(1)
290 value = match.group(2)
291
292 if not cap in self._SKIP_CAPS:
293 try:
294 self.caps[cap] = int(value, 0)
295 except ValueError:
296 sys.exit('Could not parse capability define "%s":"%s"'
297 % (cap, value))
298
299
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000300class AIDHeaderParser(object):
301 """Parses an android_filesystem_config.h file.
302
303 Parses a C header file and extracts lines starting with #define AID_<name>
304 while capturing the OEM defined ranges and ignoring other ranges. It also
305 skips some hardcoded AIDs it doesn't need to generate a mapping for.
Joel Galenson154ac1d2020-07-30 14:23:31 -0700306 It provides some basic checks. The information extracted from this file can
307 later be used to quickly check other things (like oem ranges) as well as
308 generating a mapping of names to uids. It was primarily designed to parse
309 the private/android_filesystem_config.h, but any C header should work.
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000310 """
311
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000312 _SKIP_AIDS = [
313 re.compile(r'%sUNUSED[0-9].*' % AID.PREFIX),
Tom Cherry766adc92019-02-13 14:24:52 -0800314 re.compile(r'%sAPP' % AID.PREFIX),
315 re.compile(r'%sUSER' % AID.PREFIX)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000316 ]
317 _AID_DEFINE = re.compile(r'\s*#define\s+%s.*' % AID.PREFIX)
Tom Cherryfb303a52019-07-11 15:31:36 -0700318 _RESERVED_RANGE = re.compile(
Cole Faustf08ab3d2022-10-20 13:36:05 -0700319 r'#define AID_(.+)_RESERVED_(?:(\d+)_)?(START|END)\s+(\d+)')
Tom Cherryfb303a52019-07-11 15:31:36 -0700320
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000321 # AID lines cannot end with _START or _END, ie AID_FOO is OK
322 # but AID_FOO_START is skiped. Note that AID_FOOSTART is NOT skipped.
Tom Cherryfb303a52019-07-11 15:31:36 -0700323 _AID_SKIP_RANGE = ['_START', '_END']
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000324 _COLLISION_OK = ['AID_APP', 'AID_APP_START', 'AID_USER', 'AID_USER_OFFSET']
325
326 def __init__(self, aid_header):
327 """
328 Args:
329 aid_header (str): file name for the header
330 file containing AID entries.
331 """
332 self._aid_header = aid_header
333 self._aid_name_to_value = {}
334 self._aid_value_to_name = {}
Tom Cherryfb303a52019-07-11 15:31:36 -0700335 self._ranges = {}
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000336
337 with open(aid_header) as open_file:
338 self._parse(open_file)
William Roberts64edf5b2016-04-11 17:12:47 -0700339
340 try:
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000341 self._process_and_check()
342 except ValueError as exception:
343 sys.exit('Error processing parsed data: "%s"' % (str(exception)))
344
345 def _parse(self, aid_file):
346 """Parses an AID header file. Internal use only.
347
348 Args:
349 aid_file (file): The open AID header file to parse.
350 """
351
Cole Faustf08ab3d2022-10-20 13:36:05 -0700352 ranges_by_name = {}
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000353 for lineno, line in enumerate(aid_file):
William Roberts820421c2016-12-13 19:12:35 -0800354
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000355 def error_message(msg):
356 """Creates an error message with the current parsing state."""
William Roberts4165c632016-12-13 19:17:07 -0800357 # pylint: disable=cell-var-from-loop
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000358 return 'Error "{}" in file: "{}" on line: {}'.format(
359 msg, self._aid_header, str(lineno))
360
Tom Cherryfb303a52019-07-11 15:31:36 -0700361 range_match = self._RESERVED_RANGE.match(line)
362 if range_match:
Cole Faustf08ab3d2022-10-20 13:36:05 -0700363 partition, name, start, value = range_match.groups()
364 partition = partition.lower()
365 if name is None:
366 name = "unnamed"
367 start = start == "START"
368 value = int(value, 0)
Tom Cherryfb303a52019-07-11 15:31:36 -0700369
370 if partition == 'oem':
371 partition = 'vendor'
372
Cole Faustf08ab3d2022-10-20 13:36:05 -0700373 if partition not in ranges_by_name:
374 ranges_by_name[partition] = {}
375 if name not in ranges_by_name[partition]:
376 ranges_by_name[partition][name] = [None, None]
377 if ranges_by_name[partition][name][0 if start else 1] is not None:
378 sys.exit(error_message("{} of range {} of partition {} was already defined".format(
379 "Start" if start else "End", name, partition)))
380 ranges_by_name[partition][name][0 if start else 1] = value
Tom Cherryfb303a52019-07-11 15:31:36 -0700381
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000382 if AIDHeaderParser._AID_DEFINE.match(line):
383 chunks = line.split()
384 identifier = chunks[1]
385 value = chunks[2]
386
Tom Cherry766adc92019-02-13 14:24:52 -0800387 if any(
388 x.match(identifier)
389 for x in AIDHeaderParser._SKIP_AIDS):
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000390 continue
391
392 try:
Tom Cherryfb303a52019-07-11 15:31:36 -0700393 if not any(
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000394 identifier.endswith(x)
395 for x in AIDHeaderParser._AID_SKIP_RANGE):
396 self._handle_aid(identifier, value)
397 except ValueError as exception:
William Roberts820421c2016-12-13 19:12:35 -0800398 sys.exit(
Tom Cherry766adc92019-02-13 14:24:52 -0800399 error_message('{} for "{}"'.format(
400 exception, identifier)))
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000401
Cole Faustf08ab3d2022-10-20 13:36:05 -0700402 for partition in ranges_by_name:
403 for name in ranges_by_name[partition]:
404 start = ranges_by_name[partition][name][0]
405 end = ranges_by_name[partition][name][1]
406 if start is None:
407 sys.exit("Range '%s' for partition '%s' had undefined start" % (name, partition))
408 if end is None:
409 sys.exit("Range '%s' for partition '%s' had undefined end" % (name, partition))
410 if start > end:
411 sys.exit("Range '%s' for partition '%s' had start after end. Start: %d, end: %d" % (name, partition, start, end))
412
413 if partition not in self._ranges:
414 self._ranges[partition] = []
415 self._ranges[partition].append((start, end))
416
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000417 def _handle_aid(self, identifier, value):
418 """Handle an AID C #define.
419
Joel Galenson154ac1d2020-07-30 14:23:31 -0700420 Handles an AID, quick checking, generating the friendly name and
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000421 adding it to the internal maps. Internal use only.
422
423 Args:
424 identifier (str): The name of the #define identifier. ie AID_FOO.
425 value (str): The value associated with the identifier.
426
427 Raises:
428 ValueError: With message set to indicate the error.
429 """
430
Wei Wang77e329a2018-06-05 16:00:07 -0700431 aid = AID(identifier, value, self._aid_header, '/system/bin/sh')
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000432
433 # duplicate name
434 if aid.friendly in self._aid_name_to_value:
435 raise ValueError('Duplicate aid "%s"' % identifier)
436
437 if value in self._aid_value_to_name and aid.identifier not in AIDHeaderParser._COLLISION_OK:
Tom Cherry766adc92019-02-13 14:24:52 -0800438 raise ValueError(
439 'Duplicate aid value "%s" for %s' % (value, identifier))
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000440
441 self._aid_name_to_value[aid.friendly] = aid
442 self._aid_value_to_name[value] = aid.friendly
443
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000444 def _process_and_check(self):
445 """Process, check and populate internal data structures.
William Roberts64edf5b2016-04-11 17:12:47 -0700446
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000447 After parsing and generating the internal data structures, this method
Joel Galenson154ac1d2020-07-30 14:23:31 -0700448 is responsible for quickly checking ALL of the acquired data.
William Roberts64edf5b2016-04-11 17:12:47 -0700449
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000450 Raises:
451 ValueError: With the message set to indicate the specific error.
452 """
William Roberts64edf5b2016-04-11 17:12:47 -0700453
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000454 # Check for overlapping ranges
Tom Cherryfb303a52019-07-11 15:31:36 -0700455 for ranges in self._ranges.values():
456 for i, range1 in enumerate(ranges):
457 for range2 in ranges[i + 1:]:
458 if AIDHeaderParser._is_overlap(range1, range2):
459 raise ValueError(
460 "Overlapping OEM Ranges found %s and %s" %
461 (str(range1), str(range2)))
William Roberts64edf5b2016-04-11 17:12:47 -0700462
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000463 # No core AIDs should be within any oem range.
464 for aid in self._aid_value_to_name:
Tom Cherryfb303a52019-07-11 15:31:36 -0700465 for ranges in self._ranges.values():
Cole Faust216978e2022-10-20 11:39:45 -0700466 if Utils.in_any_range(int(aid, 0), ranges):
Tom Cherryfb303a52019-07-11 15:31:36 -0700467 name = self._aid_value_to_name[aid]
468 raise ValueError(
469 'AID "%s" value: %u within reserved OEM Range: "%s"' %
470 (name, aid, str(ranges)))
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000471
472 @property
Tom Cherryfb303a52019-07-11 15:31:36 -0700473 def ranges(self):
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000474 """Retrieves the OEM closed ranges as a list of tuples.
475
476 Returns:
477 A list of closed range tuples: [ (0, 42), (50, 105) ... ]
478 """
Tom Cherryfb303a52019-07-11 15:31:36 -0700479 return self._ranges
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000480
481 @property
482 def aids(self):
483 """Retrieves the list of found AIDs.
484
485 Returns:
486 A list of AID() objects.
487 """
488 return self._aid_name_to_value.values()
489
490 @staticmethod
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000491 def _is_overlap(range_a, range_b):
492 """Calculates the overlap of two range tuples.
493
494 A range tuple is a closed range. A closed range includes its endpoints.
495 Note that python tuples use () notation which collides with the
496 mathematical notation for open ranges.
497
498 Args:
499 range_a: The first tuple closed range eg (0, 5).
500 range_b: The second tuple closed range eg (3, 7).
501
502 Returns:
503 True if they overlap, False otherwise.
504 """
505
506 return max(range_a[0], range_b[0]) <= min(range_a[1], range_b[1])
William Roberts64edf5b2016-04-11 17:12:47 -0700507
508
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000509class FSConfigFileParser(object):
510 """Parses a config.fs ini format file.
William Roberts11c29282016-04-09 10:32:30 -0700511
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000512 This class is responsible for parsing the config.fs ini format files.
513 It collects and checks all the data in these files and makes it available
514 for consumption post processed.
515 """
William Roberts11c29282016-04-09 10:32:30 -0700516
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000517 # These _AID vars work together to ensure that an AID section name
518 # cannot contain invalid characters for a C define or a passwd/group file.
519 # Since _AID_PREFIX is within the set of _AID_MATCH the error logic only
520 # checks end, if you change this, you may have to update the error
521 # detection code.
522 _AID_MATCH = re.compile('%s[A-Z0-9_]+' % AID.PREFIX)
523 _AID_ERR_MSG = 'Expecting upper case, a number or underscore'
William Roberts5f059a72016-04-25 10:36:45 -0700524
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000525 # list of handler to required options, used to identify the
526 # parsing section
Tom Cherry766adc92019-02-13 14:24:52 -0800527 _SECTIONS = [('_handle_aid', ('value', )),
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000528 ('_handle_path', ('mode', 'user', 'group', 'caps'))]
529
Tom Cherryfb303a52019-07-11 15:31:36 -0700530 def __init__(self, config_files, ranges):
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000531 """
532 Args:
533 config_files ([str]): The list of config.fs files to parse.
534 Note the filename is not important.
Tom Cherryfb303a52019-07-11 15:31:36 -0700535 ranges ({str,[()]): Dictionary of partitions and a list of tuples that correspond to their ranges
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000536 """
537
538 self._files = []
539 self._dirs = []
540 self._aids = []
541
542 self._seen_paths = {}
543 # (name to file, value to aid)
544 self._seen_aids = ({}, {})
545
Tom Cherryfb303a52019-07-11 15:31:36 -0700546 self._ranges = ranges
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000547
548 self._config_files = config_files
549
550 for config_file in self._config_files:
551 self._parse(config_file)
552
553 def _parse(self, file_name):
554 """Parses and verifies config.fs files. Internal use only.
555
556 Args:
557 file_name (str): The config.fs (PythonConfigParser file format)
558 file to parse.
559
560 Raises:
561 Anything raised by ConfigParser.read()
562 """
563
564 # Separate config parsers for each file found. If you use
565 # read(filenames...) later files can override earlier files which is
566 # not what we want. Track state across files and enforce with
567 # _handle_dup(). Note, strict ConfigParser is set to true in
568 # Python >= 3.2, so in previous versions same file sections can
569 # override previous
570 # sections.
William Robertsc950a352016-03-04 18:12:29 -0800571
Cole Faust216978e2022-10-20 11:39:45 -0700572 config = configparser.ConfigParser()
William Robertsc950a352016-03-04 18:12:29 -0800573 config.read(file_name)
574
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000575 for section in config.sections():
William Robertsc950a352016-03-04 18:12:29 -0800576
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000577 found = False
William Roberts5f059a72016-04-25 10:36:45 -0700578
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000579 for test in FSConfigFileParser._SECTIONS:
580 handler = test[0]
581 options = test[1]
William Roberts5f059a72016-04-25 10:36:45 -0700582
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000583 if all([config.has_option(section, item) for item in options]):
584 handler = getattr(self, handler)
585 handler(file_name, section, config)
586 found = True
587 break
William Roberts5f059a72016-04-25 10:36:45 -0700588
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000589 if not found:
Tom Cherry766adc92019-02-13 14:24:52 -0800590 sys.exit('Invalid section "%s" in file: "%s"' % (section,
591 file_name))
William Roberts11c29282016-04-09 10:32:30 -0700592
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000593 # sort entries:
594 # * specified path before prefix match
595 # ** ie foo before f*
596 # * lexicographical less than before other
597 # ** ie boo before foo
598 # Given these paths:
599 # paths=['ac', 'a', 'acd', 'an', 'a*', 'aa', 'ac*']
600 # The sort order would be:
601 # paths=['a', 'aa', 'ac', 'acd', 'an', 'ac*', 'a*']
602 # Thus the fs_config tools will match on specified paths before
603 # attempting prefix, and match on the longest matching prefix.
604 self._files.sort(key=FSConfigFileParser._file_key)
605
606 # sort on value of (file_name, name, value, strvalue)
607 # This is only cosmetic so AIDS are arranged in ascending order
608 # within the generated file.
609 self._aids.sort(key=lambda item: item.normalized_value)
610
Tom Cherryfb303a52019-07-11 15:31:36 -0700611 def _verify_valid_range(self, aid):
612 """Verified an AID entry is in a valid range"""
613
614 ranges = None
615
Cole Faust216978e2022-10-20 11:39:45 -0700616 partitions = list(self._ranges.keys())
Tom Cherryfb303a52019-07-11 15:31:36 -0700617 partitions.sort(key=len, reverse=True)
618 for partition in partitions:
619 if aid.friendly.startswith(partition):
620 ranges = self._ranges[partition]
621 break
622
623 if ranges is None:
624 sys.exit('AID "%s" must be prefixed with a partition name' %
625 aid.friendly)
626
627 if not Utils.in_any_range(int(aid.value, 0), ranges):
628 emsg = '"value" for aid "%s" not in valid range %s, got: %s'
629 emsg = emsg % (aid.friendly, str(ranges), aid.value)
630 sys.exit(emsg)
631
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000632 def _handle_aid(self, file_name, section_name, config):
633 """Verifies an AID entry and adds it to the aid list.
634
635 Calls sys.exit() with a descriptive message of the failure.
636
637 Args:
638 file_name (str): The filename of the config file being parsed.
639 section_name (str): The section name currently being parsed.
640 config (ConfigParser): The ConfigParser section being parsed that
641 the option values will come from.
642 """
643
644 def error_message(msg):
645 """Creates an error message with current parsing state."""
646 return '{} for: "{}" file: "{}"'.format(msg, section_name,
647 file_name)
648
649 FSConfigFileParser._handle_dup_and_add('AID', file_name, section_name,
650 self._seen_aids[0])
651
652 match = FSConfigFileParser._AID_MATCH.match(section_name)
653 invalid = match.end() if match else len(AID.PREFIX)
654 if invalid != len(section_name):
655 tmp_errmsg = ('Invalid characters in AID section at "%d" for: "%s"'
656 % (invalid, FSConfigFileParser._AID_ERR_MSG))
657 sys.exit(error_message(tmp_errmsg))
658
659 value = config.get(section_name, 'value')
660
661 if not value:
662 sys.exit(error_message('Found specified but unset "value"'))
663
664 try:
Tom Cherryfb303a52019-07-11 15:31:36 -0700665 aid = AID(section_name, value, file_name, '/bin/sh')
Tom Cherryee0610e2018-02-08 14:26:53 -0800666 except ValueError as exception:
667 sys.exit(error_message(exception))
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000668
Tom Cherryfb303a52019-07-11 15:31:36 -0700669 self._verify_valid_range(aid)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000670
671 # use the normalized int value in the dict and detect
672 # duplicate definitions of the same value
673 FSConfigFileParser._handle_dup_and_add(
674 'AID', file_name, aid.normalized_value, self._seen_aids[1])
675
676 # Append aid tuple of (AID_*, base10(value), _path(value))
677 # We keep the _path version of value so we can print that out in the
678 # generated header so investigating parties can identify parts.
679 # We store the base10 value for sorting, so everything is ascending
680 # later.
681 self._aids.append(aid)
682
683 def _handle_path(self, file_name, section_name, config):
684 """Add a file capability entry to the internal list.
685
686 Handles a file capability entry, verifies it, and adds it to
687 to the internal dirs or files list based on path. If it ends
688 with a / its a dir. Internal use only.
689
690 Calls sys.exit() on any validation error with message set.
691
692 Args:
693 file_name (str): The current name of the file being parsed.
694 section_name (str): The name of the section to parse.
695 config (str): The config parser.
696 """
697
698 FSConfigFileParser._handle_dup_and_add('path', file_name, section_name,
699 self._seen_paths)
700
701 mode = config.get(section_name, 'mode')
702 user = config.get(section_name, 'user')
703 group = config.get(section_name, 'group')
704 caps = config.get(section_name, 'caps')
705
706 errmsg = ('Found specified but unset option: \"%s" in file: \"' +
707 file_name + '\"')
708
709 if not mode:
710 sys.exit(errmsg % 'mode')
711
712 if not user:
713 sys.exit(errmsg % 'user')
714
715 if not group:
716 sys.exit(errmsg % 'group')
717
718 if not caps:
719 sys.exit(errmsg % 'caps')
720
721 caps = caps.split()
722
723 tmp = []
724 for cap in caps:
725 try:
726 # test if string is int, if it is, use as is.
727 int(cap, 0)
Tom Cherry9d924f62019-02-13 14:02:30 -0800728 tmp.append(cap)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000729 except ValueError:
Tom Cherry9d924f62019-02-13 14:02:30 -0800730 tmp.append('CAP_' + cap.upper())
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000731
732 caps = tmp
733
734 if len(mode) == 3:
735 mode = '0' + mode
736
737 try:
738 int(mode, 8)
739 except ValueError:
740 sys.exit('Mode must be octal characters, got: "%s"' % mode)
741
742 if len(mode) != 4:
743 sys.exit('Mode must be 3 or 4 characters, got: "%s"' % mode)
744
Tom Cherry9d924f62019-02-13 14:02:30 -0800745 caps_str = ','.join(caps)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000746
747 entry = FSConfig(mode, user, group, caps_str, section_name, file_name)
748 if section_name[-1] == '/':
749 self._dirs.append(entry)
750 else:
751 self._files.append(entry)
752
753 @property
754 def files(self):
755 """Get the list of FSConfig file entries.
756
757 Returns:
758 a list of FSConfig() objects for file paths.
759 """
760 return self._files
761
762 @property
763 def dirs(self):
764 """Get the list of FSConfig dir entries.
765
766 Returns:
767 a list of FSConfig() objects for directory paths.
768 """
769 return self._dirs
770
771 @property
772 def aids(self):
773 """Get the list of AID entries.
774
775 Returns:
776 a list of AID() objects.
777 """
778 return self._aids
779
780 @staticmethod
781 def _file_key(fs_config):
782 """Used as the key paramter to sort.
783
784 This is used as a the function to the key parameter of a sort.
785 it wraps the string supplied in a class that implements the
786 appropriate __lt__ operator for the sort on path strings. See
787 StringWrapper class for more details.
788
789 Args:
790 fs_config (FSConfig): A FSConfig entry.
791
792 Returns:
793 A StringWrapper object
794 """
795
796 # Wrapper class for custom prefix matching strings
797 class StringWrapper(object):
798 """Wrapper class used for sorting prefix strings.
799
800 The algorithm is as follows:
801 - specified path before prefix match
802 - ie foo before f*
803 - lexicographical less than before other
804 - ie boo before foo
805
806 Given these paths:
807 paths=['ac', 'a', 'acd', 'an', 'a*', 'aa', 'ac*']
808 The sort order would be:
809 paths=['a', 'aa', 'ac', 'acd', 'an', 'ac*', 'a*']
810 Thus the fs_config tools will match on specified paths before
811 attempting prefix, and match on the longest matching prefix.
812 """
813
814 def __init__(self, path):
815 """
816 Args:
817 path (str): the path string to wrap.
818 """
819 self.is_prefix = path[-1] == '*'
820 if self.is_prefix:
821 self.path = path[:-1]
822 else:
823 self.path = path
824
825 def __lt__(self, other):
826
827 # if were both suffixed the smallest string
828 # is 'bigger'
829 if self.is_prefix and other.is_prefix:
830 result = len(self.path) > len(other.path)
831 # If I am an the suffix match, im bigger
832 elif self.is_prefix:
833 result = False
834 # If other is the suffix match, he's bigger
835 elif other.is_prefix:
836 result = True
837 # Alphabetical
838 else:
839 result = self.path < other.path
840 return result
841
842 return StringWrapper(fs_config.path)
843
844 @staticmethod
845 def _handle_dup_and_add(name, file_name, section_name, seen):
846 """Tracks and detects duplicates. Internal use only.
847
848 Calls sys.exit() on a duplicate.
849
850 Args:
851 name (str): The name to use in the error reporting. The pretty
852 name for the section.
853 file_name (str): The file currently being parsed.
854 section_name (str): The name of the section. This would be path
855 or identifier depending on what's being parsed.
856 seen (dict): The dictionary of seen things to check against.
857 """
858 if section_name in seen:
859 dups = '"' + seen[section_name] + '" and '
860 dups += file_name
861 sys.exit('Duplicate %s "%s" found in files: %s' %
862 (name, section_name, dups))
863
864 seen[section_name] = file_name
865
866
867class BaseGenerator(object):
868 """Interface for Generators.
869
870 Base class for generators, generators should implement
871 these method stubs.
872 """
873
874 def add_opts(self, opt_group):
875 """Used to add per-generator options to the command line.
876
877 Args:
878 opt_group (argument group object): The argument group to append to.
879 See the ArgParse docs for more details.
880 """
881
882 raise NotImplementedError("Not Implemented")
883
884 def __call__(self, args):
885 """This is called to do whatever magic the generator does.
886
887 Args:
888 args (dict): The arguments from ArgParse as a dictionary.
889 ie if you specified an argument of foo in add_opts, access
890 it via args['foo']
891 """
892
893 raise NotImplementedError("Not Implemented")
894
895
896@generator('fsconfig')
897class FSConfigGen(BaseGenerator):
898 """Generates the android_filesystem_config.h file.
899
900 Output is used in generating fs_config_files and fs_config_dirs.
901 """
902
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000903 def __init__(self, *args, **kwargs):
904 BaseGenerator.__init__(args, kwargs)
905
906 self._oem_parser = None
907 self._base_parser = None
908 self._friendly_to_aid = None
Tom Cherry9d924f62019-02-13 14:02:30 -0800909 self._id_to_aid = None
910 self._capability_parser = None
911
912 self._partition = None
913 self._all_partitions = None
914 self._out_file = None
915 self._generate_files = False
916 self._generate_dirs = False
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000917
918 def add_opts(self, opt_group):
919
920 opt_group.add_argument(
921 'fsconfig', nargs='+', help='The list of fsconfig files to parse')
922
923 opt_group.add_argument(
924 '--aid-header',
925 required=True,
926 help='An android_filesystem_config.h file'
927 ' to parse AIDs and OEM Ranges from')
928
Tom Cherry9d924f62019-02-13 14:02:30 -0800929 opt_group.add_argument(
930 '--capability-header',
931 required=True,
932 help='A capability.h file to parse capability defines from')
933
934 opt_group.add_argument(
935 '--partition',
936 required=True,
937 help='Partition to generate contents for')
938
939 opt_group.add_argument(
940 '--all-partitions',
941 help='Comma separated list of all possible partitions, used to'
942 ' ignore these partitions when generating the output for the system partition'
943 )
944
945 opt_group.add_argument(
946 '--files', action='store_true', help='Output fs_config_files')
947
948 opt_group.add_argument(
949 '--dirs', action='store_true', help='Output fs_config_dirs')
950
951 opt_group.add_argument('--out_file', required=True, help='Output file')
952
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000953 def __call__(self, args):
954
Tom Cherry9d924f62019-02-13 14:02:30 -0800955 self._capability_parser = CapabilityHeaderParser(
956 args['capability_header'])
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000957 self._base_parser = AIDHeaderParser(args['aid_header'])
958 self._oem_parser = FSConfigFileParser(args['fsconfig'],
Tom Cherryfb303a52019-07-11 15:31:36 -0700959 self._base_parser.ranges)
Tom Cherry9d924f62019-02-13 14:02:30 -0800960
961 self._partition = args['partition']
962 self._all_partitions = args['all_partitions']
Tom Cherry9d924f62019-02-13 14:02:30 -0800963
964 self._out_file = args['out_file']
965
966 self._generate_files = args['files']
967 self._generate_dirs = args['dirs']
968
969 if self._generate_files and self._generate_dirs:
970 sys.exit('Only one of --files or --dirs can be provided')
971
972 if not self._generate_files and not self._generate_dirs:
973 sys.exit('One of --files or --dirs must be provided')
974
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000975 base_aids = self._base_parser.aids
976 oem_aids = self._oem_parser.aids
977
978 # Detect name collisions on AIDs. Since friendly works as the
979 # identifier for collision testing and we need friendly later on for
980 # name resolution, just calculate and use friendly.
981 # {aid.friendly: aid for aid in base_aids}
982 base_friendly = {aid.friendly: aid for aid in base_aids}
983 oem_friendly = {aid.friendly: aid for aid in oem_aids}
984
985 base_set = set(base_friendly.keys())
986 oem_set = set(oem_friendly.keys())
987
988 common = base_set & oem_set
989
Tom Cherry766adc92019-02-13 14:24:52 -0800990 if common:
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000991 emsg = 'Following AID Collisions detected for: \n'
992 for friendly in common:
993 base = base_friendly[friendly]
994 oem = oem_friendly[friendly]
995 emsg += (
996 'Identifier: "%s" Friendly Name: "%s" '
997 'found in file "%s" and "%s"' %
998 (base.identifier, base.friendly, base.found, oem.found))
999 sys.exit(emsg)
1000
1001 self._friendly_to_aid = oem_friendly
1002 self._friendly_to_aid.update(base_friendly)
1003
Tom Cherry9d924f62019-02-13 14:02:30 -08001004 self._id_to_aid = {aid.identifier: aid for aid in base_aids}
1005 self._id_to_aid.update({aid.identifier: aid for aid in oem_aids})
1006
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001007 self._generate()
1008
Tom Cherry9d924f62019-02-13 14:02:30 -08001009 def _to_fs_entry(self, fs_config, out_file):
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001010 """Converts an FSConfig entry to an fs entry.
1011
Tom Cherry9d924f62019-02-13 14:02:30 -08001012 Writes the fs_config contents to the output file.
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001013
1014 Calls sys.exit() on error.
1015
1016 Args:
Tom Cherry9d924f62019-02-13 14:02:30 -08001017 fs_config (FSConfig): The entry to convert to write to file.
1018 file (File): The file to write to.
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001019 """
1020
1021 # Get some short names
1022 mode = fs_config.mode
1023 user = fs_config.user
1024 group = fs_config.group
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001025 caps = fs_config.caps
1026 path = fs_config.path
1027
Tom Cherry9d924f62019-02-13 14:02:30 -08001028 emsg = 'Cannot convert "%s" to identifier!'
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001029
Tom Cherry9d924f62019-02-13 14:02:30 -08001030 # convert mode from octal string to integer
1031 mode = int(mode, 8)
1032
1033 # remap names to values
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001034 if AID.is_friendly(user):
1035 if user not in self._friendly_to_aid:
1036 sys.exit(emsg % user)
Tom Cherry9d924f62019-02-13 14:02:30 -08001037 user = self._friendly_to_aid[user].value
1038 else:
1039 if user not in self._id_to_aid:
1040 sys.exit(emsg % user)
1041 user = self._id_to_aid[user].value
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001042
1043 if AID.is_friendly(group):
1044 if group not in self._friendly_to_aid:
1045 sys.exit(emsg % group)
Tom Cherry9d924f62019-02-13 14:02:30 -08001046 group = self._friendly_to_aid[group].value
1047 else:
1048 if group not in self._id_to_aid:
1049 sys.exit(emsg % group)
1050 group = self._id_to_aid[group].value
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001051
Tom Cherry9d924f62019-02-13 14:02:30 -08001052 caps_dict = self._capability_parser.caps
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001053
Tom Cherry9d924f62019-02-13 14:02:30 -08001054 caps_value = 0
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001055
Tom Cherry9d924f62019-02-13 14:02:30 -08001056 try:
1057 # test if caps is an int
1058 caps_value = int(caps, 0)
1059 except ValueError:
1060 caps_split = caps.split(',')
1061 for cap in caps_split:
1062 if cap not in caps_dict:
Roland Levillain5ca32df2019-08-07 15:50:02 +01001063 sys.exit('Unknown cap "%s" found!' % cap)
Tom Cherry9d924f62019-02-13 14:02:30 -08001064 caps_value += 1 << caps_dict[cap]
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001065
Tom Cherry9d924f62019-02-13 14:02:30 -08001066 path_length_with_null = len(path) + 1
1067 path_length_aligned_64 = (path_length_with_null + 7) & ~7
1068 # 16 bytes of header plus the path length with alignment
1069 length = 16 + path_length_aligned_64
1070
1071 length_binary = bytearray(ctypes.c_uint16(length))
1072 mode_binary = bytearray(ctypes.c_uint16(mode))
1073 user_binary = bytearray(ctypes.c_uint16(int(user, 0)))
1074 group_binary = bytearray(ctypes.c_uint16(int(group, 0)))
1075 caps_binary = bytearray(ctypes.c_uint64(caps_value))
Cole Faust216978e2022-10-20 11:39:45 -07001076 path_binary = ctypes.create_string_buffer(path.encode(),
Tom Cherry9d924f62019-02-13 14:02:30 -08001077 path_length_aligned_64).raw
1078
1079 out_file.write(length_binary)
1080 out_file.write(mode_binary)
1081 out_file.write(user_binary)
1082 out_file.write(group_binary)
1083 out_file.write(caps_binary)
1084 out_file.write(path_binary)
1085
1086 def _emit_entry(self, fs_config):
1087 """Returns a boolean whether or not to emit the input fs_config"""
1088
1089 path = fs_config.path
1090
1091 if self._partition == 'system':
dianlujitao5dcbe702019-09-10 15:42:56 +08001092 if not self._all_partitions:
1093 return True
Tom Cherry9d924f62019-02-13 14:02:30 -08001094 for skip_partition in self._all_partitions.split(','):
1095 if path.startswith(skip_partition) or path.startswith(
1096 'system/' + skip_partition):
1097 return False
1098 return True
1099 else:
1100 if path.startswith(
1101 self._partition) or path.startswith('system/' +
1102 self._partition):
1103 return True
1104 return False
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001105
1106 def _generate(self):
1107 """Generates an OEM android_filesystem_config.h header file to stdout.
1108
1109 Args:
1110 files ([FSConfig]): A list of FSConfig objects for file entries.
1111 dirs ([FSConfig]): A list of FSConfig objects for directory
1112 entries.
1113 aids ([AIDS]): A list of AID objects for Android Id entries.
1114 """
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001115 dirs = self._oem_parser.dirs
1116 files = self._oem_parser.files
William Roberts8f42ce72016-04-25 12:27:43 -07001117
Tom Cherry9d924f62019-02-13 14:02:30 -08001118 if self._generate_files:
1119 with open(self._out_file, 'wb') as open_file:
1120 for fs_config in files:
1121 if self._emit_entry(fs_config):
1122 self._to_fs_entry(fs_config, open_file)
William Robertsc950a352016-03-04 18:12:29 -08001123
Tom Cherry9d924f62019-02-13 14:02:30 -08001124 if self._generate_dirs:
1125 with open(self._out_file, 'wb') as open_file:
1126 for dir_entry in dirs:
1127 if self._emit_entry(dir_entry):
1128 self._to_fs_entry(dir_entry, open_file)
William Robertsc950a352016-03-04 18:12:29 -08001129
William Robertsc950a352016-03-04 18:12:29 -08001130
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001131@generator('aidarray')
1132class AIDArrayGen(BaseGenerator):
1133 """Generates the android_id static array."""
1134
1135 _GENERATED = ('/*\n'
1136 ' * THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY!\n'
1137 ' */')
1138
1139 _INCLUDE = '#include <private/android_filesystem_config.h>'
1140
Vic Yang5b3a7c02018-12-03 21:40:33 -08001141 # Note that the android_id name field is of type 'const char[]' instead of
1142 # 'const char*'. While this seems less straightforward as we need to
1143 # calculate the max length of all names, this allows the entire android_ids
1144 # table to be placed in .rodata section instead of .data.rel.ro section,
1145 # resulting in less memory pressure.
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001146 _STRUCT_FS_CONFIG = textwrap.dedent("""
1147 struct android_id_info {
Vic Yang5b3a7c02018-12-03 21:40:33 -08001148 const char name[%d];
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001149 unsigned aid;
1150 };""")
1151
1152 _OPEN_ID_ARRAY = 'static const struct android_id_info android_ids[] = {'
1153
1154 _ID_ENTRY = ' { "%s", %s },'
1155
1156 _CLOSE_FILE_STRUCT = '};'
1157
1158 _COUNT = ('#define android_id_count \\\n'
1159 ' (sizeof(android_ids) / sizeof(android_ids[0]))')
1160
1161 def add_opts(self, opt_group):
1162
1163 opt_group.add_argument(
1164 'hdrfile', help='The android_filesystem_config.h'
1165 'file to parse')
1166
1167 def __call__(self, args):
1168
1169 hdr = AIDHeaderParser(args['hdrfile'])
Vic Yang5b3a7c02018-12-03 21:40:33 -08001170 max_name_length = max(len(aid.friendly) + 1 for aid in hdr.aids)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001171
Cole Faust216978e2022-10-20 11:39:45 -07001172 print(AIDArrayGen._GENERATED)
1173 print()
1174 print(AIDArrayGen._INCLUDE)
1175 print()
1176 print(AIDArrayGen._STRUCT_FS_CONFIG % max_name_length)
1177 print()
1178 print(AIDArrayGen._OPEN_ID_ARRAY)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001179
1180 for aid in hdr.aids:
Cole Faust216978e2022-10-20 11:39:45 -07001181 print(AIDArrayGen._ID_ENTRY % (aid.friendly, aid.identifier))
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001182
Cole Faust216978e2022-10-20 11:39:45 -07001183 print(AIDArrayGen._CLOSE_FILE_STRUCT)
1184 print()
1185 print(AIDArrayGen._COUNT)
1186 print()
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001187
1188
1189@generator('oemaid')
1190class OEMAidGen(BaseGenerator):
1191 """Generates the OEM AID_<name> value header file."""
1192
1193 _GENERATED = ('/*\n'
1194 ' * THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY!\n'
1195 ' */')
1196
1197 _GENERIC_DEFINE = "#define %s\t%s"
1198
1199 _FILE_COMMENT = '// Defined in file: \"%s\"'
1200
1201 # Intentional trailing newline for readability.
1202 _FILE_IFNDEF_DEFINE = ('#ifndef GENERATED_OEM_AIDS_H_\n'
1203 '#define GENERATED_OEM_AIDS_H_\n')
1204
1205 _FILE_ENDIF = '#endif'
1206
1207 def __init__(self):
1208
1209 self._old_file = None
1210
1211 def add_opts(self, opt_group):
1212
1213 opt_group.add_argument(
1214 'fsconfig', nargs='+', help='The list of fsconfig files to parse.')
1215
1216 opt_group.add_argument(
1217 '--aid-header',
1218 required=True,
1219 help='An android_filesystem_config.h file'
1220 'to parse AIDs and OEM Ranges from')
1221
1222 def __call__(self, args):
1223
1224 hdr_parser = AIDHeaderParser(args['aid_header'])
1225
Tom Cherryfb303a52019-07-11 15:31:36 -07001226 parser = FSConfigFileParser(args['fsconfig'], hdr_parser.ranges)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001227
Cole Faust216978e2022-10-20 11:39:45 -07001228 print(OEMAidGen._GENERATED)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001229
Cole Faust216978e2022-10-20 11:39:45 -07001230 print(OEMAidGen._FILE_IFNDEF_DEFINE)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001231
1232 for aid in parser.aids:
1233 self._print_aid(aid)
Cole Faust216978e2022-10-20 11:39:45 -07001234 print()
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001235
Cole Faust216978e2022-10-20 11:39:45 -07001236 print(OEMAidGen._FILE_ENDIF)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001237
1238 def _print_aid(self, aid):
1239 """Prints a valid #define AID identifier to stdout.
1240
1241 Args:
1242 aid to print
1243 """
1244
1245 # print the source file location of the AID
1246 found_file = aid.found
1247 if found_file != self._old_file:
Cole Faust216978e2022-10-20 11:39:45 -07001248 print(OEMAidGen._FILE_COMMENT % found_file)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001249 self._old_file = found_file
1250
Cole Faust216978e2022-10-20 11:39:45 -07001251 print(OEMAidGen._GENERIC_DEFINE % (aid.identifier, aid.value))
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001252
1253
1254@generator('passwd')
1255class PasswdGen(BaseGenerator):
1256 """Generates the /etc/passwd file per man (5) passwd."""
1257
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001258 def __init__(self):
1259
1260 self._old_file = None
1261
1262 def add_opts(self, opt_group):
1263
1264 opt_group.add_argument(
1265 'fsconfig', nargs='+', help='The list of fsconfig files to parse.')
1266
1267 opt_group.add_argument(
1268 '--aid-header',
1269 required=True,
1270 help='An android_filesystem_config.h file'
1271 'to parse AIDs and OEM Ranges from')
1272
Tom Cherry2d197a12018-05-14 13:14:41 -07001273 opt_group.add_argument(
Tom Cherryfb303a52019-07-11 15:31:36 -07001274 '--partition',
1275 required=True,
1276 help=
1277 'Filter the input file and only output entries for the given partition.'
1278 )
Tom Cherry2d197a12018-05-14 13:14:41 -07001279
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001280 def __call__(self, args):
1281
1282 hdr_parser = AIDHeaderParser(args['aid_header'])
1283
Tom Cherryfb303a52019-07-11 15:31:36 -07001284 parser = FSConfigFileParser(args['fsconfig'], hdr_parser.ranges)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001285
Tom Cherryfb303a52019-07-11 15:31:36 -07001286 filter_partition = args['partition']
Tom Cherry2d197a12018-05-14 13:14:41 -07001287
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001288 aids = parser.aids
1289
1290 # nothing to do if no aids defined
Tom Cherry766adc92019-02-13 14:24:52 -08001291 if not aids:
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001292 return
1293
Tom Cherryfb303a52019-07-11 15:31:36 -07001294 aids_by_partition = {}
Cole Faust216978e2022-10-20 11:39:45 -07001295 partitions = list(hdr_parser.ranges.keys())
Tom Cherryfb303a52019-07-11 15:31:36 -07001296 partitions.sort(key=len, reverse=True)
1297
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001298 for aid in aids:
Tom Cherryfb303a52019-07-11 15:31:36 -07001299 for partition in partitions:
1300 if aid.friendly.startswith(partition):
1301 if partition in aids_by_partition:
1302 aids_by_partition[partition].append(aid)
1303 else:
1304 aids_by_partition[partition] = [aid]
1305 break
1306
1307 if filter_partition in aids_by_partition:
1308 for aid in aids_by_partition[filter_partition]:
Tom Cherry2d197a12018-05-14 13:14:41 -07001309 self._print_formatted_line(aid)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001310
1311 def _print_formatted_line(self, aid):
1312 """Prints the aid to stdout in the passwd format. Internal use only.
1313
1314 Colon delimited:
1315 login name, friendly name
1316 encrypted password (optional)
1317 uid (int)
1318 gid (int)
1319 User name or comment field
1320 home directory
1321 interpreter (optional)
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
Cole Faust216978e2022-10-20 11:39:45 -07001334 print("%s::%s:%s::/:%s" % (logon, uid, uid, aid.login_shell))
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001335
1336
1337@generator('group')
1338class GroupGen(PasswdGen):
1339 """Generates the /etc/group file per man (5) group."""
1340
1341 # Overrides parent
1342 def _print_formatted_line(self, aid):
1343 """Prints the aid to stdout in the group format. Internal use only.
1344
1345 Formatted (per man 5 group) like:
1346 group_name:password:GID:user_list
1347
1348 Args:
1349 aid (AID): The aid to print.
1350 """
1351 if self._old_file != aid.found:
1352 self._old_file = aid.found
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001353
1354 try:
1355 logon, uid = Utils.get_login_and_uid_cleansed(aid)
1356 except ValueError as exception:
1357 sys.exit(exception)
1358
Cole Faust216978e2022-10-20 11:39:45 -07001359 print("%s::%s:" % (logon, uid))
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001360
Tom Cherry766adc92019-02-13 14:24:52 -08001361
Joe Onorato2afb6eb2018-12-04 14:51:58 -08001362@generator('print')
1363class PrintGen(BaseGenerator):
1364 """Prints just the constants and values, separated by spaces, in an easy to
1365 parse format for use by other scripts.
1366
1367 Each line is just the identifier and the value, separated by a space.
1368 """
1369
1370 def add_opts(self, opt_group):
1371 opt_group.add_argument(
1372 'aid-header', help='An android_filesystem_config.h file.')
1373
1374 def __call__(self, args):
1375
1376 hdr_parser = AIDHeaderParser(args['aid-header'])
1377 aids = hdr_parser.aids
1378
1379 aids.sort(key=lambda item: int(item.normalized_value))
1380
1381 for aid in aids:
Cole Faust216978e2022-10-20 11:39:45 -07001382 print('%s %s' % (aid.identifier, aid.normalized_value))
Joe Onorato2afb6eb2018-12-04 14:51:58 -08001383
William Roberts1c4721c2016-04-26 13:05:34 -07001384
William Robertsc950a352016-03-04 18:12:29 -08001385def main():
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001386 """Main entry point for execution."""
William Robertsc950a352016-03-04 18:12:29 -08001387
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001388 opt_parser = argparse.ArgumentParser(
1389 description='A tool for parsing fsconfig config files and producing' +
1390 'digestable outputs.')
1391 subparser = opt_parser.add_subparsers(help='generators')
William Robertsc950a352016-03-04 18:12:29 -08001392
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001393 gens = generator.get()
William Robertsc950a352016-03-04 18:12:29 -08001394
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001395 # for each gen, instantiate and add them as an option
Cole Faust216978e2022-10-20 11:39:45 -07001396 for name, gen in gens.items():
William Robertsc950a352016-03-04 18:12:29 -08001397
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001398 generator_option_parser = subparser.add_parser(name, help=gen.__doc__)
1399 generator_option_parser.set_defaults(which=name)
William Roberts8cb6a182016-04-08 22:06:19 -07001400
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001401 opt_group = generator_option_parser.add_argument_group(name +
1402 ' options')
1403 gen.add_opts(opt_group)
William Roberts8cb6a182016-04-08 22:06:19 -07001404
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001405 args = opt_parser.parse_args()
1406
1407 args_as_dict = vars(args)
1408 which = args_as_dict['which']
1409 del args_as_dict['which']
1410
1411 gens[which](args_as_dict)
1412
William Robertsc950a352016-03-04 18:12:29 -08001413
1414if __name__ == '__main__':
1415 main()