blob: 44004660845f06f456c1e66a7d4a1ab46618a3bf [file] [log] [blame]
William Robertsc950a352016-03-04 18:12:29 -08001#!/usr/bin/env python
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00002"""Generates config files for Android file system properties.
William Robertsc950a352016-03-04 18:12:29 -08003
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00004This script is used for generating configuration files for configuring
5Android filesystem properties. Internally, its composed of a plug-able
6interface to support the understanding of new input and output parameters.
7
8Run the help for a list of supported plugins and their capabilities.
9
10Further documentation can be found in the README.
11"""
12
13import argparse
William Robertsc950a352016-03-04 18:12:29 -080014import ConfigParser
Tom Cherry9d924f62019-02-13 14:02:30 -080015import ctypes
William Robertsc950a352016-03-04 18:12:29 -080016import re
17import sys
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000018import textwrap
19
20# Keep the tool in one file to make it easy to run.
21# pylint: disable=too-many-lines
William Robertsd7104bc2016-04-11 21:17:12 -070022
William Robertsc950a352016-03-04 18:12:29 -080023
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000024# Lowercase generator used to be inline with @staticmethod.
25class generator(object): # pylint: disable=invalid-name
26 """A decorator class to add commandlet plugins.
William Robertsc950a352016-03-04 18:12:29 -080027
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000028 Used as a decorator to classes to add them to
29 the internal plugin interface. Plugins added
30 with @generator() are automatically added to
31 the command line.
William Robertsc950a352016-03-04 18:12:29 -080032
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000033 For instance, to add a new generator
34 called foo and have it added just do this:
William Robertsc950a352016-03-04 18:12:29 -080035
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000036 @generator("foo")
37 class FooGen(object):
38 ...
39 """
40 _generators = {}
William Robertsc950a352016-03-04 18:12:29 -080041
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000042 def __init__(self, gen):
43 """
44 Args:
45 gen (str): The name of the generator to add.
William Robertsc950a352016-03-04 18:12:29 -080046
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000047 Raises:
48 ValueError: If there is a similarly named generator already added.
William Robertsc950a352016-03-04 18:12:29 -080049
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000050 """
51 self._gen = gen
William Robertsc950a352016-03-04 18:12:29 -080052
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000053 if gen in generator._generators:
54 raise ValueError('Duplicate generator name: ' + gen)
William Robertsc950a352016-03-04 18:12:29 -080055
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000056 generator._generators[gen] = None
William Robertsc950a352016-03-04 18:12:29 -080057
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000058 def __call__(self, cls):
59
60 generator._generators[self._gen] = cls()
61 return cls
62
63 @staticmethod
64 def get():
65 """Gets the list of generators.
66
67 Returns:
68 The list of registered generators.
69 """
70 return generator._generators
William Robertsc950a352016-03-04 18:12:29 -080071
72
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000073class Utils(object):
74 """Various assorted static utilities."""
William Roberts64edf5b2016-04-11 17:12:47 -070075
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000076 @staticmethod
77 def in_any_range(value, ranges):
78 """Tests if a value is in a list of given closed range tuples.
William Roberts64edf5b2016-04-11 17:12:47 -070079
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000080 A range tuple is a closed range. That means it's inclusive of its
81 start and ending values.
William Roberts64edf5b2016-04-11 17:12:47 -070082
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000083 Args:
84 value (int): The value to test.
85 range [(int, int)]: The closed range list to test value within.
William Roberts64edf5b2016-04-11 17:12:47 -070086
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000087 Returns:
88 True if value is within the closed range, false otherwise.
89 """
William Roberts64edf5b2016-04-11 17:12:47 -070090
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000091 return any(lower <= value <= upper for (lower, upper) in ranges)
William Roberts64edf5b2016-04-11 17:12:47 -070092
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000093 @staticmethod
94 def get_login_and_uid_cleansed(aid):
95 """Returns a passwd/group file safe logon and uid.
William Roberts1c4721c2016-04-26 13:05:34 -070096
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000097 This checks that the logon and uid of the AID do not
98 contain the delimiter ":" for a passwd/group file.
William Roberts1c4721c2016-04-26 13:05:34 -070099
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000100 Args:
101 aid (AID): The aid to check
William Roberts1c4721c2016-04-26 13:05:34 -0700102
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000103 Returns:
104 logon, uid of the AID after checking its safe.
William Roberts1c4721c2016-04-26 13:05:34 -0700105
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000106 Raises:
107 ValueError: If there is a delimiter charcter found.
108 """
109 logon = aid.friendly
110 uid = aid.normalized_value
111 if ':' in uid:
112 raise ValueError(
113 'Cannot specify delimiter character ":" in uid: "%s"' % uid)
114 if ':' in logon:
115 raise ValueError(
Tom Cherry766adc92019-02-13 14:24:52 -0800116 'Cannot specify delimiter character ":" in logon: "%s"' %
117 logon)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000118 return logon, uid
William Roberts1c4721c2016-04-26 13:05:34 -0700119
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000120
121class AID(object):
122 """This class represents an Android ID or an AID.
123
124 Attributes:
125 identifier (str): The identifier name for a #define.
126 value (str) The User Id (uid) of the associate define.
127 found (str) The file it was found in, can be None.
128 normalized_value (str): Same as value, but base 10.
129 friendly (str): The friendly name of aid.
130 """
131
132 PREFIX = 'AID_'
133
134 # Some of the AIDS like AID_MEDIA_EX had names like mediaex
135 # list a map of things to fixup until we can correct these
136 # at a later date.
137 _FIXUPS = {
138 'media_drm': 'mediadrm',
139 'media_ex': 'mediaex',
140 'media_codec': 'mediacodec'
141 }
142
Wei Wang77e329a2018-06-05 16:00:07 -0700143 def __init__(self, identifier, value, found, login_shell):
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000144 """
145 Args:
146 identifier: The identifier name for a #define <identifier>.
147 value: The value of the AID, aka the uid.
148 found (str): The file found in, not required to be specified.
Wei Wang77e329a2018-06-05 16:00:07 -0700149 login_shell (str): The shell field per man (5) passwd file.
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000150 Raises:
Tom Cherryee0610e2018-02-08 14:26:53 -0800151 ValueError: if the friendly name is longer than 31 characters as
152 that is bionic's internal buffer size for name.
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000153 ValueError: if value is not a valid string number as processed by
154 int(x, 0)
155 """
156 self.identifier = identifier
157 self.value = value
158 self.found = found
Wei Wang77e329a2018-06-05 16:00:07 -0700159 self.login_shell = login_shell
160
Tom Cherryee0610e2018-02-08 14:26:53 -0800161 try:
162 self.normalized_value = str(int(value, 0))
Tom Cherry766adc92019-02-13 14:24:52 -0800163 except ValueError:
164 raise ValueError(
165 'Invalid "value", not aid number, got: \"%s\"' % value)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000166
167 # Where we calculate the friendly name
168 friendly = identifier[len(AID.PREFIX):].lower()
169 self.friendly = AID._fixup_friendly(friendly)
170
Tom Cherryee0610e2018-02-08 14:26:53 -0800171 if len(self.friendly) > 31:
Tom Cherry766adc92019-02-13 14:24:52 -0800172 raise ValueError(
173 'AID names must be under 32 characters "%s"' % self.friendly)
Tom Cherryee0610e2018-02-08 14:26:53 -0800174
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000175 def __eq__(self, other):
176
177 return self.identifier == other.identifier \
178 and self.value == other.value and self.found == other.found \
Wei Wang77e329a2018-06-05 16:00:07 -0700179 and self.normalized_value == other.normalized_value \
180 and self.login_shell == other.login_shell
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000181
182 @staticmethod
183 def is_friendly(name):
184 """Determines if an AID is a freindly name or C define.
185
186 For example if name is AID_SYSTEM it returns false, if name
187 was system, it would return true.
188
189 Returns:
190 True if name is a friendly name False otherwise.
191 """
192
193 return not name.startswith(AID.PREFIX)
194
195 @staticmethod
196 def _fixup_friendly(friendly):
197 """Fixup friendly names that historically don't follow the convention.
198
199 Args:
200 friendly (str): The friendly name.
201
202 Returns:
203 The fixedup friendly name as a str.
204 """
205
206 if friendly in AID._FIXUPS:
207 return AID._FIXUPS[friendly]
208
209 return friendly
210
211
212class FSConfig(object):
213 """Represents a filesystem config array entry.
214
215 Represents a file system configuration entry for specifying
216 file system capabilities.
217
218 Attributes:
219 mode (str): The mode of the file or directory.
220 user (str): The uid or #define identifier (AID_SYSTEM)
221 group (str): The gid or #define identifier (AID_SYSTEM)
222 caps (str): The capability set.
Tom Cherry766adc92019-02-13 14:24:52 -0800223 path (str): The path of the file or directory.
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000224 filename (str): The file it was found in.
225 """
226
227 def __init__(self, mode, user, group, caps, path, filename):
228 """
229 Args:
230 mode (str): The mode of the file or directory.
231 user (str): The uid or #define identifier (AID_SYSTEM)
232 group (str): The gid or #define identifier (AID_SYSTEM)
233 caps (str): The capability set as a list.
Tom Cherry766adc92019-02-13 14:24:52 -0800234 path (str): The path of the file or directory.
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000235 filename (str): The file it was found in.
236 """
237 self.mode = mode
238 self.user = user
239 self.group = group
240 self.caps = caps
241 self.path = path
242 self.filename = filename
243
244 def __eq__(self, other):
245
246 return self.mode == other.mode and self.user == other.user \
247 and self.group == other.group and self.caps == other.caps \
248 and self.path == other.path and self.filename == other.filename
249
Tom Cherry766adc92019-02-13 14:24:52 -0800250 def __repr__(self):
251 return 'FSConfig(%r, %r, %r, %r, %r, %r)' % (self.mode, self.user,
252 self.group, self.caps,
253 self.path, self.filename)
254
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000255
Tom Cherry9d924f62019-02-13 14:02:30 -0800256class CapabilityHeaderParser(object):
257 """Parses capability.h file
258
259 Parses a C header file and extracts lines starting with #define CAP_<name>.
260 """
261
262 _CAP_DEFINE = re.compile(r'\s*#define\s+(CAP_\S+)\s+(\S+)')
263 _SKIP_CAPS = ['CAP_LAST_CAP', 'CAP_TO_INDEX(x)', 'CAP_TO_MASK(x)']
264
265 def __init__(self, capability_header):
266 """
267 Args:
268 capability_header (str): file name for the header file containing AID entries.
269 """
270
271 self.caps = {}
272 with open(capability_header) as open_file:
273 self._parse(open_file)
274
275 def _parse(self, capability_file):
276 """Parses a capability header file. Internal use only.
277
278 Args:
279 capability_file (file): The open capability header file to parse.
280 """
281
282 for line in capability_file:
283 match = CapabilityHeaderParser._CAP_DEFINE.match(line)
284 if match:
285 cap = match.group(1)
286 value = match.group(2)
287
288 if not cap in self._SKIP_CAPS:
289 try:
290 self.caps[cap] = int(value, 0)
291 except ValueError:
292 sys.exit('Could not parse capability define "%s":"%s"'
293 % (cap, value))
294
295
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000296class AIDHeaderParser(object):
297 """Parses an android_filesystem_config.h file.
298
299 Parses a C header file and extracts lines starting with #define AID_<name>
300 while capturing the OEM defined ranges and ignoring other ranges. It also
301 skips some hardcoded AIDs it doesn't need to generate a mapping for.
302 It provides some basic sanity checks. The information extracted from this
303 file can later be used to sanity check other things (like oem ranges) as
304 well as generating a mapping of names to uids. It was primarily designed to
305 parse the private/android_filesystem_config.h, but any C header should
306 work.
307 """
308
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000309 _SKIP_AIDS = [
310 re.compile(r'%sUNUSED[0-9].*' % AID.PREFIX),
Tom Cherry766adc92019-02-13 14:24:52 -0800311 re.compile(r'%sAPP' % AID.PREFIX),
312 re.compile(r'%sUSER' % AID.PREFIX)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000313 ]
314 _AID_DEFINE = re.compile(r'\s*#define\s+%s.*' % AID.PREFIX)
315 _OEM_START_KW = 'START'
316 _OEM_END_KW = 'END'
Johan Redestig1552a282017-01-03 09:36:47 +0100317 _OEM_RANGE = re.compile('%sOEM_RESERVED_[0-9]*_{0,1}(%s|%s)' %
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000318 (AID.PREFIX, _OEM_START_KW, _OEM_END_KW))
319 # AID lines cannot end with _START or _END, ie AID_FOO is OK
320 # but AID_FOO_START is skiped. Note that AID_FOOSTART is NOT skipped.
321 _AID_SKIP_RANGE = ['_' + _OEM_START_KW, '_' + _OEM_END_KW]
322 _COLLISION_OK = ['AID_APP', 'AID_APP_START', 'AID_USER', 'AID_USER_OFFSET']
323
324 def __init__(self, aid_header):
325 """
326 Args:
327 aid_header (str): file name for the header
328 file containing AID entries.
329 """
330 self._aid_header = aid_header
331 self._aid_name_to_value = {}
332 self._aid_value_to_name = {}
333 self._oem_ranges = {}
334
335 with open(aid_header) as open_file:
336 self._parse(open_file)
William Roberts64edf5b2016-04-11 17:12:47 -0700337
338 try:
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000339 self._process_and_check()
340 except ValueError as exception:
341 sys.exit('Error processing parsed data: "%s"' % (str(exception)))
342
343 def _parse(self, aid_file):
344 """Parses an AID header file. Internal use only.
345
346 Args:
347 aid_file (file): The open AID header file to parse.
348 """
349
350 for lineno, line in enumerate(aid_file):
William Roberts820421c2016-12-13 19:12:35 -0800351
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000352 def error_message(msg):
353 """Creates an error message with the current parsing state."""
William Roberts4165c632016-12-13 19:17:07 -0800354 # pylint: disable=cell-var-from-loop
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000355 return 'Error "{}" in file: "{}" on line: {}'.format(
356 msg, self._aid_header, str(lineno))
357
358 if AIDHeaderParser._AID_DEFINE.match(line):
359 chunks = line.split()
360 identifier = chunks[1]
361 value = chunks[2]
362
Tom Cherry766adc92019-02-13 14:24:52 -0800363 if any(
364 x.match(identifier)
365 for x in AIDHeaderParser._SKIP_AIDS):
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000366 continue
367
368 try:
369 if AIDHeaderParser._is_oem_range(identifier):
370 self._handle_oem_range(identifier, value)
371 elif not any(
372 identifier.endswith(x)
373 for x in AIDHeaderParser._AID_SKIP_RANGE):
374 self._handle_aid(identifier, value)
375 except ValueError as exception:
William Roberts820421c2016-12-13 19:12:35 -0800376 sys.exit(
Tom Cherry766adc92019-02-13 14:24:52 -0800377 error_message('{} for "{}"'.format(
378 exception, identifier)))
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000379
380 def _handle_aid(self, identifier, value):
381 """Handle an AID C #define.
382
383 Handles an AID, sanity checking, generating the friendly name and
384 adding it to the internal maps. Internal use only.
385
386 Args:
387 identifier (str): The name of the #define identifier. ie AID_FOO.
388 value (str): The value associated with the identifier.
389
390 Raises:
391 ValueError: With message set to indicate the error.
392 """
393
Wei Wang77e329a2018-06-05 16:00:07 -0700394 aid = AID(identifier, value, self._aid_header, '/system/bin/sh')
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000395
396 # duplicate name
397 if aid.friendly in self._aid_name_to_value:
398 raise ValueError('Duplicate aid "%s"' % identifier)
399
400 if value in self._aid_value_to_name and aid.identifier not in AIDHeaderParser._COLLISION_OK:
Tom Cherry766adc92019-02-13 14:24:52 -0800401 raise ValueError(
402 'Duplicate aid value "%s" for %s' % (value, identifier))
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000403
404 self._aid_name_to_value[aid.friendly] = aid
405 self._aid_value_to_name[value] = aid.friendly
406
407 def _handle_oem_range(self, identifier, value):
408 """Handle an OEM range C #define.
409
410 When encountering special AID defines, notably for the OEM ranges
411 this method handles sanity checking and adding them to the internal
412 maps. For internal use only.
413
414 Args:
415 identifier (str): The name of the #define identifier.
416 ie AID_OEM_RESERVED_START/END.
417 value (str): The value associated with the identifier.
418
419 Raises:
420 ValueError: With message set to indicate the error.
421 """
422
423 try:
424 int_value = int(value, 0)
William Roberts64edf5b2016-04-11 17:12:47 -0700425 except ValueError:
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000426 raise ValueError(
427 'Could not convert "%s" to integer value, got: "%s"' %
428 (identifier, value))
William Roberts64edf5b2016-04-11 17:12:47 -0700429
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000430 # convert AID_OEM_RESERVED_START or AID_OEM_RESERVED_<num>_START
431 # to AID_OEM_RESERVED or AID_OEM_RESERVED_<num>
432 is_start = identifier.endswith(AIDHeaderParser._OEM_START_KW)
William Roberts64edf5b2016-04-11 17:12:47 -0700433
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000434 if is_start:
435 tostrip = len(AIDHeaderParser._OEM_START_KW)
436 else:
437 tostrip = len(AIDHeaderParser._OEM_END_KW)
William Roberts64edf5b2016-04-11 17:12:47 -0700438
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000439 # ending _
440 tostrip = tostrip + 1
William Roberts64edf5b2016-04-11 17:12:47 -0700441
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000442 strip = identifier[:-tostrip]
443 if strip not in self._oem_ranges:
444 self._oem_ranges[strip] = []
William Roberts64edf5b2016-04-11 17:12:47 -0700445
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000446 if len(self._oem_ranges[strip]) > 2:
447 raise ValueError('Too many same OEM Ranges "%s"' % identifier)
William Roberts64edf5b2016-04-11 17:12:47 -0700448
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000449 if len(self._oem_ranges[strip]) == 1:
450 tmp = self._oem_ranges[strip][0]
William Roberts64edf5b2016-04-11 17:12:47 -0700451
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000452 if tmp == int_value:
453 raise ValueError('START and END values equal %u' % int_value)
454 elif is_start and tmp < int_value:
Tom Cherry766adc92019-02-13 14:24:52 -0800455 raise ValueError(
456 'END value %u less than START value %u' % (tmp, int_value))
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000457 elif not is_start and tmp > int_value:
Tom Cherry766adc92019-02-13 14:24:52 -0800458 raise ValueError(
459 'END value %u less than START value %u' % (int_value, tmp))
William Roberts64edf5b2016-04-11 17:12:47 -0700460
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000461 # Add START values to the head of the list and END values at the end.
462 # Thus, the list is ordered with index 0 as START and index 1 as END.
463 if is_start:
464 self._oem_ranges[strip].insert(0, int_value)
465 else:
466 self._oem_ranges[strip].append(int_value)
William Roberts64edf5b2016-04-11 17:12:47 -0700467
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000468 def _process_and_check(self):
469 """Process, check and populate internal data structures.
William Roberts64edf5b2016-04-11 17:12:47 -0700470
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000471 After parsing and generating the internal data structures, this method
472 is responsible for sanity checking ALL of the acquired data.
William Roberts64edf5b2016-04-11 17:12:47 -0700473
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000474 Raises:
475 ValueError: With the message set to indicate the specific error.
476 """
William Roberts64edf5b2016-04-11 17:12:47 -0700477
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000478 # tuplefy the lists since range() does not like them mutable.
479 self._oem_ranges = [
480 AIDHeaderParser._convert_lst_to_tup(k, v)
481 for k, v in self._oem_ranges.iteritems()
482 ]
William Roberts64edf5b2016-04-11 17:12:47 -0700483
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000484 # Check for overlapping ranges
485 for i, range1 in enumerate(self._oem_ranges):
486 for range2 in self._oem_ranges[i + 1:]:
487 if AIDHeaderParser._is_overlap(range1, range2):
488 raise ValueError("Overlapping OEM Ranges found %s and %s" %
489 (str(range1), str(range2)))
William Roberts64edf5b2016-04-11 17:12:47 -0700490
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000491 # No core AIDs should be within any oem range.
492 for aid in self._aid_value_to_name:
493
494 if Utils.in_any_range(aid, self._oem_ranges):
495 name = self._aid_value_to_name[aid]
496 raise ValueError(
497 'AID "%s" value: %u within reserved OEM Range: "%s"' %
498 (name, aid, str(self._oem_ranges)))
499
500 @property
501 def oem_ranges(self):
502 """Retrieves the OEM closed ranges as a list of tuples.
503
504 Returns:
505 A list of closed range tuples: [ (0, 42), (50, 105) ... ]
506 """
507 return self._oem_ranges
508
509 @property
510 def aids(self):
511 """Retrieves the list of found AIDs.
512
513 Returns:
514 A list of AID() objects.
515 """
516 return self._aid_name_to_value.values()
517
518 @staticmethod
519 def _convert_lst_to_tup(name, lst):
520 """Converts a mutable list to a non-mutable tuple.
521
522 Used ONLY for ranges and thus enforces a length of 2.
523
524 Args:
525 lst (List): list that should be "tuplefied".
526
527 Raises:
528 ValueError if lst is not a list or len is not 2.
529
530 Returns:
531 Tuple(lst)
532 """
533 if not lst or len(lst) != 2:
534 raise ValueError('Mismatched range for "%s"' % name)
535
536 return tuple(lst)
537
538 @staticmethod
539 def _is_oem_range(aid):
540 """Detects if a given aid is within the reserved OEM range.
541
542 Args:
543 aid (int): The aid to test
544
545 Returns:
546 True if it is within the range, False otherwise.
547 """
548
549 return AIDHeaderParser._OEM_RANGE.match(aid)
550
551 @staticmethod
552 def _is_overlap(range_a, range_b):
553 """Calculates the overlap of two range tuples.
554
555 A range tuple is a closed range. A closed range includes its endpoints.
556 Note that python tuples use () notation which collides with the
557 mathematical notation for open ranges.
558
559 Args:
560 range_a: The first tuple closed range eg (0, 5).
561 range_b: The second tuple closed range eg (3, 7).
562
563 Returns:
564 True if they overlap, False otherwise.
565 """
566
567 return max(range_a[0], range_b[0]) <= min(range_a[1], range_b[1])
William Roberts64edf5b2016-04-11 17:12:47 -0700568
569
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000570class FSConfigFileParser(object):
571 """Parses a config.fs ini format file.
William Roberts11c29282016-04-09 10:32:30 -0700572
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000573 This class is responsible for parsing the config.fs ini format files.
574 It collects and checks all the data in these files and makes it available
575 for consumption post processed.
576 """
William Roberts11c29282016-04-09 10:32:30 -0700577
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000578 # These _AID vars work together to ensure that an AID section name
579 # cannot contain invalid characters for a C define or a passwd/group file.
580 # Since _AID_PREFIX is within the set of _AID_MATCH the error logic only
581 # checks end, if you change this, you may have to update the error
582 # detection code.
583 _AID_MATCH = re.compile('%s[A-Z0-9_]+' % AID.PREFIX)
584 _AID_ERR_MSG = 'Expecting upper case, a number or underscore'
William Roberts5f059a72016-04-25 10:36:45 -0700585
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000586 # list of handler to required options, used to identify the
587 # parsing section
Tom Cherry766adc92019-02-13 14:24:52 -0800588 _SECTIONS = [('_handle_aid', ('value', )),
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000589 ('_handle_path', ('mode', 'user', 'group', 'caps'))]
590
591 def __init__(self, config_files, oem_ranges):
592 """
593 Args:
594 config_files ([str]): The list of config.fs files to parse.
595 Note the filename is not important.
596 oem_ranges ([(),()]): range tuples indicating reserved OEM ranges.
597 """
598
599 self._files = []
600 self._dirs = []
601 self._aids = []
602
603 self._seen_paths = {}
604 # (name to file, value to aid)
605 self._seen_aids = ({}, {})
606
607 self._oem_ranges = oem_ranges
608
609 self._config_files = config_files
610
611 for config_file in self._config_files:
612 self._parse(config_file)
613
614 def _parse(self, file_name):
615 """Parses and verifies config.fs files. Internal use only.
616
617 Args:
618 file_name (str): The config.fs (PythonConfigParser file format)
619 file to parse.
620
621 Raises:
622 Anything raised by ConfigParser.read()
623 """
624
625 # Separate config parsers for each file found. If you use
626 # read(filenames...) later files can override earlier files which is
627 # not what we want. Track state across files and enforce with
628 # _handle_dup(). Note, strict ConfigParser is set to true in
629 # Python >= 3.2, so in previous versions same file sections can
630 # override previous
631 # sections.
William Robertsc950a352016-03-04 18:12:29 -0800632
633 config = ConfigParser.ConfigParser()
634 config.read(file_name)
635
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000636 for section in config.sections():
William Robertsc950a352016-03-04 18:12:29 -0800637
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000638 found = False
William Roberts5f059a72016-04-25 10:36:45 -0700639
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000640 for test in FSConfigFileParser._SECTIONS:
641 handler = test[0]
642 options = test[1]
William Roberts5f059a72016-04-25 10:36:45 -0700643
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000644 if all([config.has_option(section, item) for item in options]):
645 handler = getattr(self, handler)
646 handler(file_name, section, config)
647 found = True
648 break
William Roberts5f059a72016-04-25 10:36:45 -0700649
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000650 if not found:
Tom Cherry766adc92019-02-13 14:24:52 -0800651 sys.exit('Invalid section "%s" in file: "%s"' % (section,
652 file_name))
William Roberts11c29282016-04-09 10:32:30 -0700653
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000654 # sort entries:
655 # * specified path before prefix match
656 # ** ie foo before f*
657 # * lexicographical less than before other
658 # ** ie boo before foo
659 # Given these paths:
660 # paths=['ac', 'a', 'acd', 'an', 'a*', 'aa', 'ac*']
661 # The sort order would be:
662 # paths=['a', 'aa', 'ac', 'acd', 'an', 'ac*', 'a*']
663 # Thus the fs_config tools will match on specified paths before
664 # attempting prefix, and match on the longest matching prefix.
665 self._files.sort(key=FSConfigFileParser._file_key)
666
667 # sort on value of (file_name, name, value, strvalue)
668 # This is only cosmetic so AIDS are arranged in ascending order
669 # within the generated file.
670 self._aids.sort(key=lambda item: item.normalized_value)
671
672 def _handle_aid(self, file_name, section_name, config):
673 """Verifies an AID entry and adds it to the aid list.
674
675 Calls sys.exit() with a descriptive message of the failure.
676
677 Args:
678 file_name (str): The filename of the config file being parsed.
679 section_name (str): The section name currently being parsed.
680 config (ConfigParser): The ConfigParser section being parsed that
681 the option values will come from.
682 """
683
684 def error_message(msg):
685 """Creates an error message with current parsing state."""
686 return '{} for: "{}" file: "{}"'.format(msg, section_name,
687 file_name)
688
689 FSConfigFileParser._handle_dup_and_add('AID', file_name, section_name,
690 self._seen_aids[0])
691
692 match = FSConfigFileParser._AID_MATCH.match(section_name)
693 invalid = match.end() if match else len(AID.PREFIX)
694 if invalid != len(section_name):
695 tmp_errmsg = ('Invalid characters in AID section at "%d" for: "%s"'
696 % (invalid, FSConfigFileParser._AID_ERR_MSG))
697 sys.exit(error_message(tmp_errmsg))
698
699 value = config.get(section_name, 'value')
700
701 if not value:
702 sys.exit(error_message('Found specified but unset "value"'))
703
704 try:
Wei Wang77e329a2018-06-05 16:00:07 -0700705 aid = AID(section_name, value, file_name, '/vendor/bin/sh')
Tom Cherryee0610e2018-02-08 14:26:53 -0800706 except ValueError as exception:
707 sys.exit(error_message(exception))
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000708
709 # Values must be within OEM range
710 if not Utils.in_any_range(int(aid.value, 0), self._oem_ranges):
711 emsg = '"value" not in valid range %s, got: %s'
712 emsg = emsg % (str(self._oem_ranges), value)
713 sys.exit(error_message(emsg))
714
715 # use the normalized int value in the dict and detect
716 # duplicate definitions of the same value
717 FSConfigFileParser._handle_dup_and_add(
718 'AID', file_name, aid.normalized_value, self._seen_aids[1])
719
720 # Append aid tuple of (AID_*, base10(value), _path(value))
721 # We keep the _path version of value so we can print that out in the
722 # generated header so investigating parties can identify parts.
723 # We store the base10 value for sorting, so everything is ascending
724 # later.
725 self._aids.append(aid)
726
727 def _handle_path(self, file_name, section_name, config):
728 """Add a file capability entry to the internal list.
729
730 Handles a file capability entry, verifies it, and adds it to
731 to the internal dirs or files list based on path. If it ends
732 with a / its a dir. Internal use only.
733
734 Calls sys.exit() on any validation error with message set.
735
736 Args:
737 file_name (str): The current name of the file being parsed.
738 section_name (str): The name of the section to parse.
739 config (str): The config parser.
740 """
741
742 FSConfigFileParser._handle_dup_and_add('path', file_name, section_name,
743 self._seen_paths)
744
745 mode = config.get(section_name, 'mode')
746 user = config.get(section_name, 'user')
747 group = config.get(section_name, 'group')
748 caps = config.get(section_name, 'caps')
749
750 errmsg = ('Found specified but unset option: \"%s" in file: \"' +
751 file_name + '\"')
752
753 if not mode:
754 sys.exit(errmsg % 'mode')
755
756 if not user:
757 sys.exit(errmsg % 'user')
758
759 if not group:
760 sys.exit(errmsg % 'group')
761
762 if not caps:
763 sys.exit(errmsg % 'caps')
764
765 caps = caps.split()
766
767 tmp = []
768 for cap in caps:
769 try:
770 # test if string is int, if it is, use as is.
771 int(cap, 0)
Tom Cherry9d924f62019-02-13 14:02:30 -0800772 tmp.append(cap)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000773 except ValueError:
Tom Cherry9d924f62019-02-13 14:02:30 -0800774 tmp.append('CAP_' + cap.upper())
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000775
776 caps = tmp
777
778 if len(mode) == 3:
779 mode = '0' + mode
780
781 try:
782 int(mode, 8)
783 except ValueError:
784 sys.exit('Mode must be octal characters, got: "%s"' % mode)
785
786 if len(mode) != 4:
787 sys.exit('Mode must be 3 or 4 characters, got: "%s"' % mode)
788
Tom Cherry9d924f62019-02-13 14:02:30 -0800789 caps_str = ','.join(caps)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000790
791 entry = FSConfig(mode, user, group, caps_str, section_name, file_name)
792 if section_name[-1] == '/':
793 self._dirs.append(entry)
794 else:
795 self._files.append(entry)
796
797 @property
798 def files(self):
799 """Get the list of FSConfig file entries.
800
801 Returns:
802 a list of FSConfig() objects for file paths.
803 """
804 return self._files
805
806 @property
807 def dirs(self):
808 """Get the list of FSConfig dir entries.
809
810 Returns:
811 a list of FSConfig() objects for directory paths.
812 """
813 return self._dirs
814
815 @property
816 def aids(self):
817 """Get the list of AID entries.
818
819 Returns:
820 a list of AID() objects.
821 """
822 return self._aids
823
824 @staticmethod
825 def _file_key(fs_config):
826 """Used as the key paramter to sort.
827
828 This is used as a the function to the key parameter of a sort.
829 it wraps the string supplied in a class that implements the
830 appropriate __lt__ operator for the sort on path strings. See
831 StringWrapper class for more details.
832
833 Args:
834 fs_config (FSConfig): A FSConfig entry.
835
836 Returns:
837 A StringWrapper object
838 """
839
840 # Wrapper class for custom prefix matching strings
841 class StringWrapper(object):
842 """Wrapper class used for sorting prefix strings.
843
844 The algorithm is as follows:
845 - specified path before prefix match
846 - ie foo before f*
847 - lexicographical less than before other
848 - ie boo before foo
849
850 Given these paths:
851 paths=['ac', 'a', 'acd', 'an', 'a*', 'aa', 'ac*']
852 The sort order would be:
853 paths=['a', 'aa', 'ac', 'acd', 'an', 'ac*', 'a*']
854 Thus the fs_config tools will match on specified paths before
855 attempting prefix, and match on the longest matching prefix.
856 """
857
858 def __init__(self, path):
859 """
860 Args:
861 path (str): the path string to wrap.
862 """
863 self.is_prefix = path[-1] == '*'
864 if self.is_prefix:
865 self.path = path[:-1]
866 else:
867 self.path = path
868
869 def __lt__(self, other):
870
871 # if were both suffixed the smallest string
872 # is 'bigger'
873 if self.is_prefix and other.is_prefix:
874 result = len(self.path) > len(other.path)
875 # If I am an the suffix match, im bigger
876 elif self.is_prefix:
877 result = False
878 # If other is the suffix match, he's bigger
879 elif other.is_prefix:
880 result = True
881 # Alphabetical
882 else:
883 result = self.path < other.path
884 return result
885
886 return StringWrapper(fs_config.path)
887
888 @staticmethod
889 def _handle_dup_and_add(name, file_name, section_name, seen):
890 """Tracks and detects duplicates. Internal use only.
891
892 Calls sys.exit() on a duplicate.
893
894 Args:
895 name (str): The name to use in the error reporting. The pretty
896 name for the section.
897 file_name (str): The file currently being parsed.
898 section_name (str): The name of the section. This would be path
899 or identifier depending on what's being parsed.
900 seen (dict): The dictionary of seen things to check against.
901 """
902 if section_name in seen:
903 dups = '"' + seen[section_name] + '" and '
904 dups += file_name
905 sys.exit('Duplicate %s "%s" found in files: %s' %
906 (name, section_name, dups))
907
908 seen[section_name] = file_name
909
910
911class BaseGenerator(object):
912 """Interface for Generators.
913
914 Base class for generators, generators should implement
915 these method stubs.
916 """
917
918 def add_opts(self, opt_group):
919 """Used to add per-generator options to the command line.
920
921 Args:
922 opt_group (argument group object): The argument group to append to.
923 See the ArgParse docs for more details.
924 """
925
926 raise NotImplementedError("Not Implemented")
927
928 def __call__(self, args):
929 """This is called to do whatever magic the generator does.
930
931 Args:
932 args (dict): The arguments from ArgParse as a dictionary.
933 ie if you specified an argument of foo in add_opts, access
934 it via args['foo']
935 """
936
937 raise NotImplementedError("Not Implemented")
938
939
940@generator('fsconfig')
941class FSConfigGen(BaseGenerator):
942 """Generates the android_filesystem_config.h file.
943
944 Output is used in generating fs_config_files and fs_config_dirs.
945 """
946
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000947 def __init__(self, *args, **kwargs):
948 BaseGenerator.__init__(args, kwargs)
949
950 self._oem_parser = None
951 self._base_parser = None
952 self._friendly_to_aid = None
Tom Cherry9d924f62019-02-13 14:02:30 -0800953 self._id_to_aid = None
954 self._capability_parser = None
955
956 self._partition = None
957 self._all_partitions = None
958 self._out_file = None
959 self._generate_files = False
960 self._generate_dirs = False
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000961
962 def add_opts(self, opt_group):
963
964 opt_group.add_argument(
965 'fsconfig', nargs='+', help='The list of fsconfig files to parse')
966
967 opt_group.add_argument(
968 '--aid-header',
969 required=True,
970 help='An android_filesystem_config.h file'
971 ' to parse AIDs and OEM Ranges from')
972
Tom Cherry9d924f62019-02-13 14:02:30 -0800973 opt_group.add_argument(
974 '--capability-header',
975 required=True,
976 help='A capability.h file to parse capability defines from')
977
978 opt_group.add_argument(
979 '--partition',
980 required=True,
981 help='Partition to generate contents for')
982
983 opt_group.add_argument(
984 '--all-partitions',
985 help='Comma separated list of all possible partitions, used to'
986 ' ignore these partitions when generating the output for the system partition'
987 )
988
989 opt_group.add_argument(
990 '--files', action='store_true', help='Output fs_config_files')
991
992 opt_group.add_argument(
993 '--dirs', action='store_true', help='Output fs_config_dirs')
994
995 opt_group.add_argument('--out_file', required=True, help='Output file')
996
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000997 def __call__(self, args):
998
Tom Cherry9d924f62019-02-13 14:02:30 -0800999 self._capability_parser = CapabilityHeaderParser(
1000 args['capability_header'])
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001001 self._base_parser = AIDHeaderParser(args['aid_header'])
1002 self._oem_parser = FSConfigFileParser(args['fsconfig'],
1003 self._base_parser.oem_ranges)
Tom Cherry9d924f62019-02-13 14:02:30 -08001004
1005 self._partition = args['partition']
1006 self._all_partitions = args['all_partitions']
Tom Cherry9d924f62019-02-13 14:02:30 -08001007
1008 self._out_file = args['out_file']
1009
1010 self._generate_files = args['files']
1011 self._generate_dirs = args['dirs']
1012
1013 if self._generate_files and self._generate_dirs:
1014 sys.exit('Only one of --files or --dirs can be provided')
1015
1016 if not self._generate_files and not self._generate_dirs:
1017 sys.exit('One of --files or --dirs must be provided')
1018
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001019 base_aids = self._base_parser.aids
1020 oem_aids = self._oem_parser.aids
1021
1022 # Detect name collisions on AIDs. Since friendly works as the
1023 # identifier for collision testing and we need friendly later on for
1024 # name resolution, just calculate and use friendly.
1025 # {aid.friendly: aid for aid in base_aids}
1026 base_friendly = {aid.friendly: aid for aid in base_aids}
1027 oem_friendly = {aid.friendly: aid for aid in oem_aids}
1028
1029 base_set = set(base_friendly.keys())
1030 oem_set = set(oem_friendly.keys())
1031
1032 common = base_set & oem_set
1033
Tom Cherry766adc92019-02-13 14:24:52 -08001034 if common:
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001035 emsg = 'Following AID Collisions detected for: \n'
1036 for friendly in common:
1037 base = base_friendly[friendly]
1038 oem = oem_friendly[friendly]
1039 emsg += (
1040 'Identifier: "%s" Friendly Name: "%s" '
1041 'found in file "%s" and "%s"' %
1042 (base.identifier, base.friendly, base.found, oem.found))
1043 sys.exit(emsg)
1044
1045 self._friendly_to_aid = oem_friendly
1046 self._friendly_to_aid.update(base_friendly)
1047
Tom Cherry9d924f62019-02-13 14:02:30 -08001048 self._id_to_aid = {aid.identifier: aid for aid in base_aids}
1049 self._id_to_aid.update({aid.identifier: aid for aid in oem_aids})
1050
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001051 self._generate()
1052
Tom Cherry9d924f62019-02-13 14:02:30 -08001053 def _to_fs_entry(self, fs_config, out_file):
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001054 """Converts an FSConfig entry to an fs entry.
1055
Tom Cherry9d924f62019-02-13 14:02:30 -08001056 Writes the fs_config contents to the output file.
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001057
1058 Calls sys.exit() on error.
1059
1060 Args:
Tom Cherry9d924f62019-02-13 14:02:30 -08001061 fs_config (FSConfig): The entry to convert to write to file.
1062 file (File): The file to write to.
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001063 """
1064
1065 # Get some short names
1066 mode = fs_config.mode
1067 user = fs_config.user
1068 group = fs_config.group
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001069 caps = fs_config.caps
1070 path = fs_config.path
1071
Tom Cherry9d924f62019-02-13 14:02:30 -08001072 emsg = 'Cannot convert "%s" to identifier!'
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001073
Tom Cherry9d924f62019-02-13 14:02:30 -08001074 # convert mode from octal string to integer
1075 mode = int(mode, 8)
1076
1077 # remap names to values
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001078 if AID.is_friendly(user):
1079 if user not in self._friendly_to_aid:
1080 sys.exit(emsg % user)
Tom Cherry9d924f62019-02-13 14:02:30 -08001081 user = self._friendly_to_aid[user].value
1082 else:
1083 if user not in self._id_to_aid:
1084 sys.exit(emsg % user)
1085 user = self._id_to_aid[user].value
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001086
1087 if AID.is_friendly(group):
1088 if group not in self._friendly_to_aid:
1089 sys.exit(emsg % group)
Tom Cherry9d924f62019-02-13 14:02:30 -08001090 group = self._friendly_to_aid[group].value
1091 else:
1092 if group not in self._id_to_aid:
1093 sys.exit(emsg % group)
1094 group = self._id_to_aid[group].value
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001095
Tom Cherry9d924f62019-02-13 14:02:30 -08001096 caps_dict = self._capability_parser.caps
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001097
Tom Cherry9d924f62019-02-13 14:02:30 -08001098 caps_value = 0
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001099
Tom Cherry9d924f62019-02-13 14:02:30 -08001100 try:
1101 # test if caps is an int
1102 caps_value = int(caps, 0)
1103 except ValueError:
1104 caps_split = caps.split(',')
1105 for cap in caps_split:
1106 if cap not in caps_dict:
1107 sys.exit('Unkonwn cap "%s" found!' % cap)
1108 caps_value += 1 << caps_dict[cap]
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001109
Tom Cherry9d924f62019-02-13 14:02:30 -08001110 path_length_with_null = len(path) + 1
1111 path_length_aligned_64 = (path_length_with_null + 7) & ~7
1112 # 16 bytes of header plus the path length with alignment
1113 length = 16 + path_length_aligned_64
1114
1115 length_binary = bytearray(ctypes.c_uint16(length))
1116 mode_binary = bytearray(ctypes.c_uint16(mode))
1117 user_binary = bytearray(ctypes.c_uint16(int(user, 0)))
1118 group_binary = bytearray(ctypes.c_uint16(int(group, 0)))
1119 caps_binary = bytearray(ctypes.c_uint64(caps_value))
1120 path_binary = ctypes.create_string_buffer(path,
1121 path_length_aligned_64).raw
1122
1123 out_file.write(length_binary)
1124 out_file.write(mode_binary)
1125 out_file.write(user_binary)
1126 out_file.write(group_binary)
1127 out_file.write(caps_binary)
1128 out_file.write(path_binary)
1129
1130 def _emit_entry(self, fs_config):
1131 """Returns a boolean whether or not to emit the input fs_config"""
1132
1133 path = fs_config.path
1134
1135 if self._partition == 'system':
1136 for skip_partition in self._all_partitions.split(','):
1137 if path.startswith(skip_partition) or path.startswith(
1138 'system/' + skip_partition):
1139 return False
1140 return True
1141 else:
1142 if path.startswith(
1143 self._partition) or path.startswith('system/' +
1144 self._partition):
1145 return True
1146 return False
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001147
1148 def _generate(self):
1149 """Generates an OEM android_filesystem_config.h header file to stdout.
1150
1151 Args:
1152 files ([FSConfig]): A list of FSConfig objects for file entries.
1153 dirs ([FSConfig]): A list of FSConfig objects for directory
1154 entries.
1155 aids ([AIDS]): A list of AID objects for Android Id entries.
1156 """
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001157 dirs = self._oem_parser.dirs
1158 files = self._oem_parser.files
William Roberts8f42ce72016-04-25 12:27:43 -07001159
Tom Cherry9d924f62019-02-13 14:02:30 -08001160 if self._generate_files:
1161 with open(self._out_file, 'wb') as open_file:
1162 for fs_config in files:
1163 if self._emit_entry(fs_config):
1164 self._to_fs_entry(fs_config, open_file)
William Robertsc950a352016-03-04 18:12:29 -08001165
Tom Cherry9d924f62019-02-13 14:02:30 -08001166 if self._generate_dirs:
1167 with open(self._out_file, 'wb') as open_file:
1168 for dir_entry in dirs:
1169 if self._emit_entry(dir_entry):
1170 self._to_fs_entry(dir_entry, open_file)
William Robertsc950a352016-03-04 18:12:29 -08001171
William Robertsc950a352016-03-04 18:12:29 -08001172
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001173@generator('aidarray')
1174class AIDArrayGen(BaseGenerator):
1175 """Generates the android_id static array."""
1176
1177 _GENERATED = ('/*\n'
1178 ' * THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY!\n'
1179 ' */')
1180
1181 _INCLUDE = '#include <private/android_filesystem_config.h>'
1182
Vic Yang5b3a7c02018-12-03 21:40:33 -08001183 # Note that the android_id name field is of type 'const char[]' instead of
1184 # 'const char*'. While this seems less straightforward as we need to
1185 # calculate the max length of all names, this allows the entire android_ids
1186 # table to be placed in .rodata section instead of .data.rel.ro section,
1187 # resulting in less memory pressure.
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001188 _STRUCT_FS_CONFIG = textwrap.dedent("""
1189 struct android_id_info {
Vic Yang5b3a7c02018-12-03 21:40:33 -08001190 const char name[%d];
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001191 unsigned aid;
1192 };""")
1193
1194 _OPEN_ID_ARRAY = 'static const struct android_id_info android_ids[] = {'
1195
1196 _ID_ENTRY = ' { "%s", %s },'
1197
1198 _CLOSE_FILE_STRUCT = '};'
1199
1200 _COUNT = ('#define android_id_count \\\n'
1201 ' (sizeof(android_ids) / sizeof(android_ids[0]))')
1202
1203 def add_opts(self, opt_group):
1204
1205 opt_group.add_argument(
1206 'hdrfile', help='The android_filesystem_config.h'
1207 'file to parse')
1208
1209 def __call__(self, args):
1210
1211 hdr = AIDHeaderParser(args['hdrfile'])
Vic Yang5b3a7c02018-12-03 21:40:33 -08001212 max_name_length = max(len(aid.friendly) + 1 for aid in hdr.aids)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001213
1214 print AIDArrayGen._GENERATED
1215 print
1216 print AIDArrayGen._INCLUDE
1217 print
Vic Yang5b3a7c02018-12-03 21:40:33 -08001218 print AIDArrayGen._STRUCT_FS_CONFIG % max_name_length
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001219 print
1220 print AIDArrayGen._OPEN_ID_ARRAY
1221
1222 for aid in hdr.aids:
1223 print AIDArrayGen._ID_ENTRY % (aid.friendly, aid.identifier)
1224
1225 print AIDArrayGen._CLOSE_FILE_STRUCT
1226 print
1227 print AIDArrayGen._COUNT
1228 print
1229
1230
1231@generator('oemaid')
1232class OEMAidGen(BaseGenerator):
1233 """Generates the OEM AID_<name> value header file."""
1234
1235 _GENERATED = ('/*\n'
1236 ' * THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY!\n'
1237 ' */')
1238
1239 _GENERIC_DEFINE = "#define %s\t%s"
1240
1241 _FILE_COMMENT = '// Defined in file: \"%s\"'
1242
1243 # Intentional trailing newline for readability.
1244 _FILE_IFNDEF_DEFINE = ('#ifndef GENERATED_OEM_AIDS_H_\n'
1245 '#define GENERATED_OEM_AIDS_H_\n')
1246
1247 _FILE_ENDIF = '#endif'
1248
1249 def __init__(self):
1250
1251 self._old_file = None
1252
1253 def add_opts(self, opt_group):
1254
1255 opt_group.add_argument(
1256 'fsconfig', nargs='+', help='The list of fsconfig files to parse.')
1257
1258 opt_group.add_argument(
1259 '--aid-header',
1260 required=True,
1261 help='An android_filesystem_config.h file'
1262 'to parse AIDs and OEM Ranges from')
1263
1264 def __call__(self, args):
1265
1266 hdr_parser = AIDHeaderParser(args['aid_header'])
1267
1268 parser = FSConfigFileParser(args['fsconfig'], hdr_parser.oem_ranges)
1269
1270 print OEMAidGen._GENERATED
1271
1272 print OEMAidGen._FILE_IFNDEF_DEFINE
1273
1274 for aid in parser.aids:
1275 self._print_aid(aid)
1276 print
1277
1278 print OEMAidGen._FILE_ENDIF
1279
1280 def _print_aid(self, aid):
1281 """Prints a valid #define AID identifier to stdout.
1282
1283 Args:
1284 aid to print
1285 """
1286
1287 # print the source file location of the AID
1288 found_file = aid.found
1289 if found_file != self._old_file:
1290 print OEMAidGen._FILE_COMMENT % found_file
1291 self._old_file = found_file
1292
1293 print OEMAidGen._GENERIC_DEFINE % (aid.identifier, aid.value)
1294
1295
1296@generator('passwd')
1297class PasswdGen(BaseGenerator):
1298 """Generates the /etc/passwd file per man (5) passwd."""
1299
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001300 def __init__(self):
1301
1302 self._old_file = None
1303
1304 def add_opts(self, opt_group):
1305
1306 opt_group.add_argument(
1307 'fsconfig', nargs='+', help='The list of fsconfig files to parse.')
1308
1309 opt_group.add_argument(
1310 '--aid-header',
1311 required=True,
1312 help='An android_filesystem_config.h file'
1313 'to parse AIDs and OEM Ranges from')
1314
Tom Cherry2d197a12018-05-14 13:14:41 -07001315 opt_group.add_argument(
1316 '--required-prefix',
1317 required=False,
1318 help='A prefix that the names are required to contain.')
1319
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001320 def __call__(self, args):
1321
1322 hdr_parser = AIDHeaderParser(args['aid_header'])
1323
1324 parser = FSConfigFileParser(args['fsconfig'], hdr_parser.oem_ranges)
1325
Tom Cherry2d197a12018-05-14 13:14:41 -07001326 required_prefix = args['required_prefix']
1327
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001328 aids = parser.aids
1329
1330 # nothing to do if no aids defined
Tom Cherry766adc92019-02-13 14:24:52 -08001331 if not aids:
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001332 return
1333
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001334 for aid in aids:
Tom Cherry766adc92019-02-13 14:24:52 -08001335 if required_prefix is None or aid.friendly.startswith(
1336 required_prefix):
Tom Cherry2d197a12018-05-14 13:14:41 -07001337 self._print_formatted_line(aid)
1338 else:
1339 sys.exit("%s: AID '%s' must start with '%s'" %
1340 (args['fsconfig'], aid.friendly, required_prefix))
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001341
1342 def _print_formatted_line(self, aid):
1343 """Prints the aid to stdout in the passwd format. Internal use only.
1344
1345 Colon delimited:
1346 login name, friendly name
1347 encrypted password (optional)
1348 uid (int)
1349 gid (int)
1350 User name or comment field
1351 home directory
1352 interpreter (optional)
1353
1354 Args:
1355 aid (AID): The aid to print.
1356 """
1357 if self._old_file != aid.found:
1358 self._old_file = aid.found
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001359
1360 try:
1361 logon, uid = Utils.get_login_and_uid_cleansed(aid)
1362 except ValueError as exception:
1363 sys.exit(exception)
1364
Wei Wang77e329a2018-06-05 16:00:07 -07001365 print "%s::%s:%s::/:%s" % (logon, uid, uid, aid.login_shell)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001366
1367
1368@generator('group')
1369class GroupGen(PasswdGen):
1370 """Generates the /etc/group file per man (5) group."""
1371
1372 # Overrides parent
1373 def _print_formatted_line(self, aid):
1374 """Prints the aid to stdout in the group format. Internal use only.
1375
1376 Formatted (per man 5 group) like:
1377 group_name:password:GID:user_list
1378
1379 Args:
1380 aid (AID): The aid to print.
1381 """
1382 if self._old_file != aid.found:
1383 self._old_file = aid.found
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001384
1385 try:
1386 logon, uid = Utils.get_login_and_uid_cleansed(aid)
1387 except ValueError as exception:
1388 sys.exit(exception)
1389
1390 print "%s::%s:" % (logon, uid)
1391
Tom Cherry766adc92019-02-13 14:24:52 -08001392
Joe Onorato2afb6eb2018-12-04 14:51:58 -08001393@generator('print')
1394class PrintGen(BaseGenerator):
1395 """Prints just the constants and values, separated by spaces, in an easy to
1396 parse format for use by other scripts.
1397
1398 Each line is just the identifier and the value, separated by a space.
1399 """
1400
1401 def add_opts(self, opt_group):
1402 opt_group.add_argument(
1403 'aid-header', help='An android_filesystem_config.h file.')
1404
1405 def __call__(self, args):
1406
1407 hdr_parser = AIDHeaderParser(args['aid-header'])
1408 aids = hdr_parser.aids
1409
1410 aids.sort(key=lambda item: int(item.normalized_value))
1411
1412 for aid in aids:
1413 print '%s %s' % (aid.identifier, aid.normalized_value)
1414
William Roberts1c4721c2016-04-26 13:05:34 -07001415
William Robertsc950a352016-03-04 18:12:29 -08001416def main():
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001417 """Main entry point for execution."""
William Robertsc950a352016-03-04 18:12:29 -08001418
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001419 opt_parser = argparse.ArgumentParser(
1420 description='A tool for parsing fsconfig config files and producing' +
1421 'digestable outputs.')
1422 subparser = opt_parser.add_subparsers(help='generators')
William Robertsc950a352016-03-04 18:12:29 -08001423
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001424 gens = generator.get()
William Robertsc950a352016-03-04 18:12:29 -08001425
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001426 # for each gen, instantiate and add them as an option
1427 for name, gen in gens.iteritems():
William Robertsc950a352016-03-04 18:12:29 -08001428
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001429 generator_option_parser = subparser.add_parser(name, help=gen.__doc__)
1430 generator_option_parser.set_defaults(which=name)
William Roberts8cb6a182016-04-08 22:06:19 -07001431
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001432 opt_group = generator_option_parser.add_argument_group(name +
1433 ' options')
1434 gen.add_opts(opt_group)
William Roberts8cb6a182016-04-08 22:06:19 -07001435
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001436 args = opt_parser.parse_args()
1437
1438 args_as_dict = vars(args)
1439 which = args_as_dict['which']
1440 del args_as_dict['which']
1441
1442 gens[which](args_as_dict)
1443
William Robertsc950a352016-03-04 18:12:29 -08001444
1445if __name__ == '__main__':
1446 main()