blob: f7e3eb2c14b9d7dffa5293a6e5cda865d63ddeb9 [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
15import re
16import sys
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000017import textwrap
18
19# Keep the tool in one file to make it easy to run.
20# pylint: disable=too-many-lines
William Robertsd7104bc2016-04-11 21:17:12 -070021
William Robertsc950a352016-03-04 18:12:29 -080022
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000023# Lowercase generator used to be inline with @staticmethod.
24class generator(object): # pylint: disable=invalid-name
25 """A decorator class to add commandlet plugins.
William Robertsc950a352016-03-04 18:12:29 -080026
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000027 Used as a decorator to classes to add them to
28 the internal plugin interface. Plugins added
29 with @generator() are automatically added to
30 the command line.
William Robertsc950a352016-03-04 18:12:29 -080031
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000032 For instance, to add a new generator
33 called foo and have it added just do this:
William Robertsc950a352016-03-04 18:12:29 -080034
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000035 @generator("foo")
36 class FooGen(object):
37 ...
38 """
39 _generators = {}
William Robertsc950a352016-03-04 18:12:29 -080040
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000041 def __init__(self, gen):
42 """
43 Args:
44 gen (str): The name of the generator to add.
William Robertsc950a352016-03-04 18:12:29 -080045
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000046 Raises:
47 ValueError: If there is a similarly named generator already added.
William Robertsc950a352016-03-04 18:12:29 -080048
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000049 """
50 self._gen = gen
William Robertsc950a352016-03-04 18:12:29 -080051
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000052 if gen in generator._generators:
53 raise ValueError('Duplicate generator name: ' + gen)
William Robertsc950a352016-03-04 18:12:29 -080054
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000055 generator._generators[gen] = None
William Robertsc950a352016-03-04 18:12:29 -080056
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000057 def __call__(self, cls):
58
59 generator._generators[self._gen] = cls()
60 return cls
61
62 @staticmethod
63 def get():
64 """Gets the list of generators.
65
66 Returns:
67 The list of registered generators.
68 """
69 return generator._generators
William Robertsc950a352016-03-04 18:12:29 -080070
71
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000072class Utils(object):
73 """Various assorted static utilities."""
William Roberts64edf5b2016-04-11 17:12:47 -070074
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000075 @staticmethod
76 def in_any_range(value, ranges):
77 """Tests if a value is in a list of given closed range tuples.
William Roberts64edf5b2016-04-11 17:12:47 -070078
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000079 A range tuple is a closed range. That means it's inclusive of its
80 start and ending values.
William Roberts64edf5b2016-04-11 17:12:47 -070081
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000082 Args:
83 value (int): The value to test.
84 range [(int, int)]: The closed range list to test value within.
William Roberts64edf5b2016-04-11 17:12:47 -070085
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000086 Returns:
87 True if value is within the closed range, false otherwise.
88 """
William Roberts64edf5b2016-04-11 17:12:47 -070089
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000090 return any(lower <= value <= upper for (lower, upper) in ranges)
William Roberts64edf5b2016-04-11 17:12:47 -070091
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000092 @staticmethod
93 def get_login_and_uid_cleansed(aid):
94 """Returns a passwd/group file safe logon and uid.
William Roberts1c4721c2016-04-26 13:05:34 -070095
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000096 This checks that the logon and uid of the AID do not
97 contain the delimiter ":" for a passwd/group file.
William Roberts1c4721c2016-04-26 13:05:34 -070098
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000099 Args:
100 aid (AID): The aid to check
William Roberts1c4721c2016-04-26 13:05:34 -0700101
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000102 Returns:
103 logon, uid of the AID after checking its safe.
William Roberts1c4721c2016-04-26 13:05:34 -0700104
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000105 Raises:
106 ValueError: If there is a delimiter charcter found.
107 """
108 logon = aid.friendly
109 uid = aid.normalized_value
110 if ':' in uid:
111 raise ValueError(
112 'Cannot specify delimiter character ":" in uid: "%s"' % uid)
113 if ':' in logon:
114 raise ValueError(
115 'Cannot specify delimiter character ":" in logon: "%s"' % logon)
116 return logon, uid
William Roberts1c4721c2016-04-26 13:05:34 -0700117
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000118
119class AID(object):
120 """This class represents an Android ID or an AID.
121
122 Attributes:
123 identifier (str): The identifier name for a #define.
124 value (str) The User Id (uid) of the associate define.
125 found (str) The file it was found in, can be None.
126 normalized_value (str): Same as value, but base 10.
127 friendly (str): The friendly name of aid.
128 """
129
130 PREFIX = 'AID_'
131
132 # Some of the AIDS like AID_MEDIA_EX had names like mediaex
133 # list a map of things to fixup until we can correct these
134 # at a later date.
135 _FIXUPS = {
136 'media_drm': 'mediadrm',
137 'media_ex': 'mediaex',
138 'media_codec': 'mediacodec'
139 }
140
Wei Wang77e329a2018-06-05 16:00:07 -0700141 def __init__(self, identifier, value, found, login_shell):
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000142 """
143 Args:
144 identifier: The identifier name for a #define <identifier>.
145 value: The value of the AID, aka the uid.
146 found (str): The file found in, not required to be specified.
Wei Wang77e329a2018-06-05 16:00:07 -0700147 login_shell (str): The shell field per man (5) passwd file.
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000148 Raises:
Tom Cherryee0610e2018-02-08 14:26:53 -0800149 ValueError: if the friendly name is longer than 31 characters as
150 that is bionic's internal buffer size for name.
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000151 ValueError: if value is not a valid string number as processed by
152 int(x, 0)
153 """
154 self.identifier = identifier
155 self.value = value
156 self.found = found
Wei Wang77e329a2018-06-05 16:00:07 -0700157 self.login_shell = login_shell
158
Tom Cherryee0610e2018-02-08 14:26:53 -0800159 try:
160 self.normalized_value = str(int(value, 0))
161 except ValueException:
162 raise ValueError('Invalid "value", not aid number, got: \"%s\"' % value)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000163
164 # Where we calculate the friendly name
165 friendly = identifier[len(AID.PREFIX):].lower()
166 self.friendly = AID._fixup_friendly(friendly)
167
Tom Cherryee0610e2018-02-08 14:26:53 -0800168 if len(self.friendly) > 31:
169 raise ValueError('AID names must be under 32 characters "%s"' % self.friendly)
170
171
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000172 def __eq__(self, other):
173
174 return self.identifier == other.identifier \
175 and self.value == other.value and self.found == other.found \
Wei Wang77e329a2018-06-05 16:00:07 -0700176 and self.normalized_value == other.normalized_value \
177 and self.login_shell == other.login_shell
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000178
179 @staticmethod
180 def is_friendly(name):
181 """Determines if an AID is a freindly name or C define.
182
183 For example if name is AID_SYSTEM it returns false, if name
184 was system, it would return true.
185
186 Returns:
187 True if name is a friendly name False otherwise.
188 """
189
190 return not name.startswith(AID.PREFIX)
191
192 @staticmethod
193 def _fixup_friendly(friendly):
194 """Fixup friendly names that historically don't follow the convention.
195
196 Args:
197 friendly (str): The friendly name.
198
199 Returns:
200 The fixedup friendly name as a str.
201 """
202
203 if friendly in AID._FIXUPS:
204 return AID._FIXUPS[friendly]
205
206 return friendly
207
208
209class FSConfig(object):
210 """Represents a filesystem config array entry.
211
212 Represents a file system configuration entry for specifying
213 file system capabilities.
214
215 Attributes:
216 mode (str): The mode of the file or directory.
217 user (str): The uid or #define identifier (AID_SYSTEM)
218 group (str): The gid or #define identifier (AID_SYSTEM)
219 caps (str): The capability set.
220 filename (str): The file it was found in.
221 """
222
223 def __init__(self, mode, user, group, caps, path, filename):
224 """
225 Args:
226 mode (str): The mode of the file or directory.
227 user (str): The uid or #define identifier (AID_SYSTEM)
228 group (str): The gid or #define identifier (AID_SYSTEM)
229 caps (str): The capability set as a list.
230 filename (str): The file it was found in.
231 """
232 self.mode = mode
233 self.user = user
234 self.group = group
235 self.caps = caps
236 self.path = path
237 self.filename = filename
238
239 def __eq__(self, other):
240
241 return self.mode == other.mode and self.user == other.user \
242 and self.group == other.group and self.caps == other.caps \
243 and self.path == other.path and self.filename == other.filename
244
245
246class AIDHeaderParser(object):
247 """Parses an android_filesystem_config.h file.
248
249 Parses a C header file and extracts lines starting with #define AID_<name>
250 while capturing the OEM defined ranges and ignoring other ranges. It also
251 skips some hardcoded AIDs it doesn't need to generate a mapping for.
252 It provides some basic sanity checks. The information extracted from this
253 file can later be used to sanity check other things (like oem ranges) as
254 well as generating a mapping of names to uids. It was primarily designed to
255 parse the private/android_filesystem_config.h, but any C header should
256 work.
257 """
258
259
260 _SKIP_AIDS = [
261 re.compile(r'%sUNUSED[0-9].*' % AID.PREFIX),
262 re.compile(r'%sAPP' % AID.PREFIX), re.compile(r'%sUSER' % AID.PREFIX)
263 ]
264 _AID_DEFINE = re.compile(r'\s*#define\s+%s.*' % AID.PREFIX)
265 _OEM_START_KW = 'START'
266 _OEM_END_KW = 'END'
Johan Redestig1552a282017-01-03 09:36:47 +0100267 _OEM_RANGE = re.compile('%sOEM_RESERVED_[0-9]*_{0,1}(%s|%s)' %
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000268 (AID.PREFIX, _OEM_START_KW, _OEM_END_KW))
269 # AID lines cannot end with _START or _END, ie AID_FOO is OK
270 # but AID_FOO_START is skiped. Note that AID_FOOSTART is NOT skipped.
271 _AID_SKIP_RANGE = ['_' + _OEM_START_KW, '_' + _OEM_END_KW]
272 _COLLISION_OK = ['AID_APP', 'AID_APP_START', 'AID_USER', 'AID_USER_OFFSET']
273
274 def __init__(self, aid_header):
275 """
276 Args:
277 aid_header (str): file name for the header
278 file containing AID entries.
279 """
280 self._aid_header = aid_header
281 self._aid_name_to_value = {}
282 self._aid_value_to_name = {}
283 self._oem_ranges = {}
284
285 with open(aid_header) as open_file:
286 self._parse(open_file)
William Roberts64edf5b2016-04-11 17:12:47 -0700287
288 try:
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000289 self._process_and_check()
290 except ValueError as exception:
291 sys.exit('Error processing parsed data: "%s"' % (str(exception)))
292
293 def _parse(self, aid_file):
294 """Parses an AID header file. Internal use only.
295
296 Args:
297 aid_file (file): The open AID header file to parse.
298 """
299
300 for lineno, line in enumerate(aid_file):
William Roberts820421c2016-12-13 19:12:35 -0800301
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000302 def error_message(msg):
303 """Creates an error message with the current parsing state."""
William Roberts4165c632016-12-13 19:17:07 -0800304 # pylint: disable=cell-var-from-loop
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000305 return 'Error "{}" in file: "{}" on line: {}'.format(
306 msg, self._aid_header, str(lineno))
307
308 if AIDHeaderParser._AID_DEFINE.match(line):
309 chunks = line.split()
310 identifier = chunks[1]
311 value = chunks[2]
312
313 if any(x.match(identifier) for x in AIDHeaderParser._SKIP_AIDS):
314 continue
315
316 try:
317 if AIDHeaderParser._is_oem_range(identifier):
318 self._handle_oem_range(identifier, value)
319 elif not any(
320 identifier.endswith(x)
321 for x in AIDHeaderParser._AID_SKIP_RANGE):
322 self._handle_aid(identifier, value)
323 except ValueError as exception:
William Roberts820421c2016-12-13 19:12:35 -0800324 sys.exit(
325 error_message('{} for "{}"'.format(exception,
326 identifier)))
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000327
328 def _handle_aid(self, identifier, value):
329 """Handle an AID C #define.
330
331 Handles an AID, sanity checking, generating the friendly name and
332 adding it to the internal maps. Internal use only.
333
334 Args:
335 identifier (str): The name of the #define identifier. ie AID_FOO.
336 value (str): The value associated with the identifier.
337
338 Raises:
339 ValueError: With message set to indicate the error.
340 """
341
Wei Wang77e329a2018-06-05 16:00:07 -0700342 aid = AID(identifier, value, self._aid_header, '/system/bin/sh')
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000343
344 # duplicate name
345 if aid.friendly in self._aid_name_to_value:
346 raise ValueError('Duplicate aid "%s"' % identifier)
347
348 if value in self._aid_value_to_name and aid.identifier not in AIDHeaderParser._COLLISION_OK:
349 raise ValueError('Duplicate aid value "%s" for %s' % (value,
350 identifier))
351
352 self._aid_name_to_value[aid.friendly] = aid
353 self._aid_value_to_name[value] = aid.friendly
354
355 def _handle_oem_range(self, identifier, value):
356 """Handle an OEM range C #define.
357
358 When encountering special AID defines, notably for the OEM ranges
359 this method handles sanity checking and adding them to the internal
360 maps. For internal use only.
361
362 Args:
363 identifier (str): The name of the #define identifier.
364 ie AID_OEM_RESERVED_START/END.
365 value (str): The value associated with the identifier.
366
367 Raises:
368 ValueError: With message set to indicate the error.
369 """
370
371 try:
372 int_value = int(value, 0)
William Roberts64edf5b2016-04-11 17:12:47 -0700373 except ValueError:
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000374 raise ValueError(
375 'Could not convert "%s" to integer value, got: "%s"' %
376 (identifier, value))
William Roberts64edf5b2016-04-11 17:12:47 -0700377
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000378 # convert AID_OEM_RESERVED_START or AID_OEM_RESERVED_<num>_START
379 # to AID_OEM_RESERVED or AID_OEM_RESERVED_<num>
380 is_start = identifier.endswith(AIDHeaderParser._OEM_START_KW)
William Roberts64edf5b2016-04-11 17:12:47 -0700381
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000382 if is_start:
383 tostrip = len(AIDHeaderParser._OEM_START_KW)
384 else:
385 tostrip = len(AIDHeaderParser._OEM_END_KW)
William Roberts64edf5b2016-04-11 17:12:47 -0700386
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000387 # ending _
388 tostrip = tostrip + 1
William Roberts64edf5b2016-04-11 17:12:47 -0700389
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000390 strip = identifier[:-tostrip]
391 if strip not in self._oem_ranges:
392 self._oem_ranges[strip] = []
William Roberts64edf5b2016-04-11 17:12:47 -0700393
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000394 if len(self._oem_ranges[strip]) > 2:
395 raise ValueError('Too many same OEM Ranges "%s"' % identifier)
William Roberts64edf5b2016-04-11 17:12:47 -0700396
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000397 if len(self._oem_ranges[strip]) == 1:
398 tmp = self._oem_ranges[strip][0]
William Roberts64edf5b2016-04-11 17:12:47 -0700399
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000400 if tmp == int_value:
401 raise ValueError('START and END values equal %u' % int_value)
402 elif is_start and tmp < int_value:
403 raise ValueError('END value %u less than START value %u' %
404 (tmp, int_value))
405 elif not is_start and tmp > int_value:
406 raise ValueError('END value %u less than START value %u' %
407 (int_value, tmp))
William Roberts64edf5b2016-04-11 17:12:47 -0700408
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000409 # Add START values to the head of the list and END values at the end.
410 # Thus, the list is ordered with index 0 as START and index 1 as END.
411 if is_start:
412 self._oem_ranges[strip].insert(0, int_value)
413 else:
414 self._oem_ranges[strip].append(int_value)
William Roberts64edf5b2016-04-11 17:12:47 -0700415
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000416 def _process_and_check(self):
417 """Process, check and populate internal data structures.
William Roberts64edf5b2016-04-11 17:12:47 -0700418
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000419 After parsing and generating the internal data structures, this method
420 is responsible for sanity checking ALL of the acquired data.
William Roberts64edf5b2016-04-11 17:12:47 -0700421
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000422 Raises:
423 ValueError: With the message set to indicate the specific error.
424 """
William Roberts64edf5b2016-04-11 17:12:47 -0700425
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000426 # tuplefy the lists since range() does not like them mutable.
427 self._oem_ranges = [
428 AIDHeaderParser._convert_lst_to_tup(k, v)
429 for k, v in self._oem_ranges.iteritems()
430 ]
William Roberts64edf5b2016-04-11 17:12:47 -0700431
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000432 # Check for overlapping ranges
433 for i, range1 in enumerate(self._oem_ranges):
434 for range2 in self._oem_ranges[i + 1:]:
435 if AIDHeaderParser._is_overlap(range1, range2):
436 raise ValueError("Overlapping OEM Ranges found %s and %s" %
437 (str(range1), str(range2)))
William Roberts64edf5b2016-04-11 17:12:47 -0700438
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000439 # No core AIDs should be within any oem range.
440 for aid in self._aid_value_to_name:
441
442 if Utils.in_any_range(aid, self._oem_ranges):
443 name = self._aid_value_to_name[aid]
444 raise ValueError(
445 'AID "%s" value: %u within reserved OEM Range: "%s"' %
446 (name, aid, str(self._oem_ranges)))
447
448 @property
449 def oem_ranges(self):
450 """Retrieves the OEM closed ranges as a list of tuples.
451
452 Returns:
453 A list of closed range tuples: [ (0, 42), (50, 105) ... ]
454 """
455 return self._oem_ranges
456
457 @property
458 def aids(self):
459 """Retrieves the list of found AIDs.
460
461 Returns:
462 A list of AID() objects.
463 """
464 return self._aid_name_to_value.values()
465
466 @staticmethod
467 def _convert_lst_to_tup(name, lst):
468 """Converts a mutable list to a non-mutable tuple.
469
470 Used ONLY for ranges and thus enforces a length of 2.
471
472 Args:
473 lst (List): list that should be "tuplefied".
474
475 Raises:
476 ValueError if lst is not a list or len is not 2.
477
478 Returns:
479 Tuple(lst)
480 """
481 if not lst or len(lst) != 2:
482 raise ValueError('Mismatched range for "%s"' % name)
483
484 return tuple(lst)
485
486 @staticmethod
487 def _is_oem_range(aid):
488 """Detects if a given aid is within the reserved OEM range.
489
490 Args:
491 aid (int): The aid to test
492
493 Returns:
494 True if it is within the range, False otherwise.
495 """
496
497 return AIDHeaderParser._OEM_RANGE.match(aid)
498
499 @staticmethod
500 def _is_overlap(range_a, range_b):
501 """Calculates the overlap of two range tuples.
502
503 A range tuple is a closed range. A closed range includes its endpoints.
504 Note that python tuples use () notation which collides with the
505 mathematical notation for open ranges.
506
507 Args:
508 range_a: The first tuple closed range eg (0, 5).
509 range_b: The second tuple closed range eg (3, 7).
510
511 Returns:
512 True if they overlap, False otherwise.
513 """
514
515 return max(range_a[0], range_b[0]) <= min(range_a[1], range_b[1])
William Roberts64edf5b2016-04-11 17:12:47 -0700516
517
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000518class FSConfigFileParser(object):
519 """Parses a config.fs ini format file.
William Roberts11c29282016-04-09 10:32:30 -0700520
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000521 This class is responsible for parsing the config.fs ini format files.
522 It collects and checks all the data in these files and makes it available
523 for consumption post processed.
524 """
William Roberts11c29282016-04-09 10:32:30 -0700525
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000526 # These _AID vars work together to ensure that an AID section name
527 # cannot contain invalid characters for a C define or a passwd/group file.
528 # Since _AID_PREFIX is within the set of _AID_MATCH the error logic only
529 # checks end, if you change this, you may have to update the error
530 # detection code.
531 _AID_MATCH = re.compile('%s[A-Z0-9_]+' % AID.PREFIX)
532 _AID_ERR_MSG = 'Expecting upper case, a number or underscore'
William Roberts5f059a72016-04-25 10:36:45 -0700533
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000534 # list of handler to required options, used to identify the
535 # parsing section
536 _SECTIONS = [('_handle_aid', ('value',)),
537 ('_handle_path', ('mode', 'user', 'group', 'caps'))]
538
539 def __init__(self, config_files, oem_ranges):
540 """
541 Args:
542 config_files ([str]): The list of config.fs files to parse.
543 Note the filename is not important.
544 oem_ranges ([(),()]): range tuples indicating reserved OEM ranges.
545 """
546
547 self._files = []
548 self._dirs = []
549 self._aids = []
550
551 self._seen_paths = {}
552 # (name to file, value to aid)
553 self._seen_aids = ({}, {})
554
555 self._oem_ranges = oem_ranges
556
557 self._config_files = config_files
558
559 for config_file in self._config_files:
560 self._parse(config_file)
561
562 def _parse(self, file_name):
563 """Parses and verifies config.fs files. Internal use only.
564
565 Args:
566 file_name (str): The config.fs (PythonConfigParser file format)
567 file to parse.
568
569 Raises:
570 Anything raised by ConfigParser.read()
571 """
572
573 # Separate config parsers for each file found. If you use
574 # read(filenames...) later files can override earlier files which is
575 # not what we want. Track state across files and enforce with
576 # _handle_dup(). Note, strict ConfigParser is set to true in
577 # Python >= 3.2, so in previous versions same file sections can
578 # override previous
579 # sections.
William Robertsc950a352016-03-04 18:12:29 -0800580
581 config = ConfigParser.ConfigParser()
582 config.read(file_name)
583
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000584 for section in config.sections():
William Robertsc950a352016-03-04 18:12:29 -0800585
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000586 found = False
William Roberts5f059a72016-04-25 10:36:45 -0700587
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000588 for test in FSConfigFileParser._SECTIONS:
589 handler = test[0]
590 options = test[1]
William Roberts5f059a72016-04-25 10:36:45 -0700591
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000592 if all([config.has_option(section, item) for item in options]):
593 handler = getattr(self, handler)
594 handler(file_name, section, config)
595 found = True
596 break
William Roberts5f059a72016-04-25 10:36:45 -0700597
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000598 if not found:
599 sys.exit('Invalid section "%s" in file: "%s"' %
600 (section, file_name))
William Roberts11c29282016-04-09 10:32:30 -0700601
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000602 # sort entries:
603 # * specified path before prefix match
604 # ** ie foo before f*
605 # * lexicographical less than before other
606 # ** ie boo before foo
607 # Given these paths:
608 # paths=['ac', 'a', 'acd', 'an', 'a*', 'aa', 'ac*']
609 # The sort order would be:
610 # paths=['a', 'aa', 'ac', 'acd', 'an', 'ac*', 'a*']
611 # Thus the fs_config tools will match on specified paths before
612 # attempting prefix, and match on the longest matching prefix.
613 self._files.sort(key=FSConfigFileParser._file_key)
614
615 # sort on value of (file_name, name, value, strvalue)
616 # This is only cosmetic so AIDS are arranged in ascending order
617 # within the generated file.
618 self._aids.sort(key=lambda item: item.normalized_value)
619
620 def _handle_aid(self, file_name, section_name, config):
621 """Verifies an AID entry and adds it to the aid list.
622
623 Calls sys.exit() with a descriptive message of the failure.
624
625 Args:
626 file_name (str): The filename of the config file being parsed.
627 section_name (str): The section name currently being parsed.
628 config (ConfigParser): The ConfigParser section being parsed that
629 the option values will come from.
630 """
631
632 def error_message(msg):
633 """Creates an error message with current parsing state."""
634 return '{} for: "{}" file: "{}"'.format(msg, section_name,
635 file_name)
636
637 FSConfigFileParser._handle_dup_and_add('AID', file_name, section_name,
638 self._seen_aids[0])
639
640 match = FSConfigFileParser._AID_MATCH.match(section_name)
641 invalid = match.end() if match else len(AID.PREFIX)
642 if invalid != len(section_name):
643 tmp_errmsg = ('Invalid characters in AID section at "%d" for: "%s"'
644 % (invalid, FSConfigFileParser._AID_ERR_MSG))
645 sys.exit(error_message(tmp_errmsg))
646
647 value = config.get(section_name, 'value')
648
649 if not value:
650 sys.exit(error_message('Found specified but unset "value"'))
651
652 try:
Wei Wang77e329a2018-06-05 16:00:07 -0700653 aid = AID(section_name, value, file_name, '/vendor/bin/sh')
Tom Cherryee0610e2018-02-08 14:26:53 -0800654 except ValueError as exception:
655 sys.exit(error_message(exception))
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000656
657 # Values must be within OEM range
658 if not Utils.in_any_range(int(aid.value, 0), self._oem_ranges):
659 emsg = '"value" not in valid range %s, got: %s'
660 emsg = emsg % (str(self._oem_ranges), value)
661 sys.exit(error_message(emsg))
662
663 # use the normalized int value in the dict and detect
664 # duplicate definitions of the same value
665 FSConfigFileParser._handle_dup_and_add(
666 'AID', file_name, aid.normalized_value, self._seen_aids[1])
667
668 # Append aid tuple of (AID_*, base10(value), _path(value))
669 # We keep the _path version of value so we can print that out in the
670 # generated header so investigating parties can identify parts.
671 # We store the base10 value for sorting, so everything is ascending
672 # later.
673 self._aids.append(aid)
674
675 def _handle_path(self, file_name, section_name, config):
676 """Add a file capability entry to the internal list.
677
678 Handles a file capability entry, verifies it, and adds it to
679 to the internal dirs or files list based on path. If it ends
680 with a / its a dir. Internal use only.
681
682 Calls sys.exit() on any validation error with message set.
683
684 Args:
685 file_name (str): The current name of the file being parsed.
686 section_name (str): The name of the section to parse.
687 config (str): The config parser.
688 """
689
690 FSConfigFileParser._handle_dup_and_add('path', file_name, section_name,
691 self._seen_paths)
692
693 mode = config.get(section_name, 'mode')
694 user = config.get(section_name, 'user')
695 group = config.get(section_name, 'group')
696 caps = config.get(section_name, 'caps')
697
698 errmsg = ('Found specified but unset option: \"%s" in file: \"' +
699 file_name + '\"')
700
701 if not mode:
702 sys.exit(errmsg % 'mode')
703
704 if not user:
705 sys.exit(errmsg % 'user')
706
707 if not group:
708 sys.exit(errmsg % 'group')
709
710 if not caps:
711 sys.exit(errmsg % 'caps')
712
713 caps = caps.split()
714
715 tmp = []
716 for cap in caps:
717 try:
718 # test if string is int, if it is, use as is.
719 int(cap, 0)
720 tmp.append('(' + cap + ')')
721 except ValueError:
doheon1.lee5cd3bca2017-04-03 15:17:06 +0900722 tmp.append('CAP_MASK_LONG(CAP_' + cap.upper() + ')')
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000723
724 caps = tmp
725
726 if len(mode) == 3:
727 mode = '0' + mode
728
729 try:
730 int(mode, 8)
731 except ValueError:
732 sys.exit('Mode must be octal characters, got: "%s"' % mode)
733
734 if len(mode) != 4:
735 sys.exit('Mode must be 3 or 4 characters, got: "%s"' % mode)
736
737 caps_str = '|'.join(caps)
738
739 entry = FSConfig(mode, user, group, caps_str, section_name, file_name)
740 if section_name[-1] == '/':
741 self._dirs.append(entry)
742 else:
743 self._files.append(entry)
744
745 @property
746 def files(self):
747 """Get the list of FSConfig file entries.
748
749 Returns:
750 a list of FSConfig() objects for file paths.
751 """
752 return self._files
753
754 @property
755 def dirs(self):
756 """Get the list of FSConfig dir entries.
757
758 Returns:
759 a list of FSConfig() objects for directory paths.
760 """
761 return self._dirs
762
763 @property
764 def aids(self):
765 """Get the list of AID entries.
766
767 Returns:
768 a list of AID() objects.
769 """
770 return self._aids
771
772 @staticmethod
773 def _file_key(fs_config):
774 """Used as the key paramter to sort.
775
776 This is used as a the function to the key parameter of a sort.
777 it wraps the string supplied in a class that implements the
778 appropriate __lt__ operator for the sort on path strings. See
779 StringWrapper class for more details.
780
781 Args:
782 fs_config (FSConfig): A FSConfig entry.
783
784 Returns:
785 A StringWrapper object
786 """
787
788 # Wrapper class for custom prefix matching strings
789 class StringWrapper(object):
790 """Wrapper class used for sorting prefix strings.
791
792 The algorithm is as follows:
793 - specified path before prefix match
794 - ie foo before f*
795 - lexicographical less than before other
796 - ie boo before foo
797
798 Given these paths:
799 paths=['ac', 'a', 'acd', 'an', 'a*', 'aa', 'ac*']
800 The sort order would be:
801 paths=['a', 'aa', 'ac', 'acd', 'an', 'ac*', 'a*']
802 Thus the fs_config tools will match on specified paths before
803 attempting prefix, and match on the longest matching prefix.
804 """
805
806 def __init__(self, path):
807 """
808 Args:
809 path (str): the path string to wrap.
810 """
811 self.is_prefix = path[-1] == '*'
812 if self.is_prefix:
813 self.path = path[:-1]
814 else:
815 self.path = path
816
817 def __lt__(self, other):
818
819 # if were both suffixed the smallest string
820 # is 'bigger'
821 if self.is_prefix and other.is_prefix:
822 result = len(self.path) > len(other.path)
823 # If I am an the suffix match, im bigger
824 elif self.is_prefix:
825 result = False
826 # If other is the suffix match, he's bigger
827 elif other.is_prefix:
828 result = True
829 # Alphabetical
830 else:
831 result = self.path < other.path
832 return result
833
834 return StringWrapper(fs_config.path)
835
836 @staticmethod
837 def _handle_dup_and_add(name, file_name, section_name, seen):
838 """Tracks and detects duplicates. Internal use only.
839
840 Calls sys.exit() on a duplicate.
841
842 Args:
843 name (str): The name to use in the error reporting. The pretty
844 name for the section.
845 file_name (str): The file currently being parsed.
846 section_name (str): The name of the section. This would be path
847 or identifier depending on what's being parsed.
848 seen (dict): The dictionary of seen things to check against.
849 """
850 if section_name in seen:
851 dups = '"' + seen[section_name] + '" and '
852 dups += file_name
853 sys.exit('Duplicate %s "%s" found in files: %s' %
854 (name, section_name, dups))
855
856 seen[section_name] = file_name
857
858
859class BaseGenerator(object):
860 """Interface for Generators.
861
862 Base class for generators, generators should implement
863 these method stubs.
864 """
865
866 def add_opts(self, opt_group):
867 """Used to add per-generator options to the command line.
868
869 Args:
870 opt_group (argument group object): The argument group to append to.
871 See the ArgParse docs for more details.
872 """
873
874 raise NotImplementedError("Not Implemented")
875
876 def __call__(self, args):
877 """This is called to do whatever magic the generator does.
878
879 Args:
880 args (dict): The arguments from ArgParse as a dictionary.
881 ie if you specified an argument of foo in add_opts, access
882 it via args['foo']
883 """
884
885 raise NotImplementedError("Not Implemented")
886
887
888@generator('fsconfig')
889class FSConfigGen(BaseGenerator):
890 """Generates the android_filesystem_config.h file.
891
892 Output is used in generating fs_config_files and fs_config_dirs.
893 """
894
895 _GENERATED = textwrap.dedent("""\
896 /*
897 * THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY
898 */
899 """)
900
901 _INCLUDES = [
902 '<private/android_filesystem_config.h>', '"generated_oem_aid.h"'
903 ]
904
905 _DEFINE_NO_DIRS = '#define NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS'
906 _DEFINE_NO_FILES = '#define NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_FILES'
907
908 _DEFAULT_WARNING = (
909 '#warning No device-supplied android_filesystem_config.h,'
910 ' using empty default.')
911
912 # Long names.
913 # pylint: disable=invalid-name
914 _NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS_ENTRY = (
915 '{ 00000, AID_ROOT, AID_ROOT, 0,'
916 '"system/etc/fs_config_dirs" },')
917
918 _NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_FILES_ENTRY = (
919 '{ 00000, AID_ROOT, AID_ROOT, 0,'
920 '"system/etc/fs_config_files" },')
921
922 _IFDEF_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS = (
923 '#ifdef NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS')
924 # pylint: enable=invalid-name
925
926 _ENDIF = '#endif'
927
928 _OPEN_FILE_STRUCT = (
929 'static const struct fs_path_config android_device_files[] = {')
930
931 _OPEN_DIR_STRUCT = (
932 'static const struct fs_path_config android_device_dirs[] = {')
933
934 _CLOSE_FILE_STRUCT = '};'
935
936 _GENERIC_DEFINE = "#define %s\t%s"
937
938 _FILE_COMMENT = '// Defined in file: \"%s\"'
939
940 def __init__(self, *args, **kwargs):
941 BaseGenerator.__init__(args, kwargs)
942
943 self._oem_parser = None
944 self._base_parser = None
945 self._friendly_to_aid = None
946
947 def add_opts(self, opt_group):
948
949 opt_group.add_argument(
950 'fsconfig', nargs='+', help='The list of fsconfig files to parse')
951
952 opt_group.add_argument(
953 '--aid-header',
954 required=True,
955 help='An android_filesystem_config.h file'
956 ' to parse AIDs and OEM Ranges from')
957
958 def __call__(self, args):
959
960 self._base_parser = AIDHeaderParser(args['aid_header'])
961 self._oem_parser = FSConfigFileParser(args['fsconfig'],
962 self._base_parser.oem_ranges)
963 base_aids = self._base_parser.aids
964 oem_aids = self._oem_parser.aids
965
966 # Detect name collisions on AIDs. Since friendly works as the
967 # identifier for collision testing and we need friendly later on for
968 # name resolution, just calculate and use friendly.
969 # {aid.friendly: aid for aid in base_aids}
970 base_friendly = {aid.friendly: aid for aid in base_aids}
971 oem_friendly = {aid.friendly: aid for aid in oem_aids}
972
973 base_set = set(base_friendly.keys())
974 oem_set = set(oem_friendly.keys())
975
976 common = base_set & oem_set
977
978 if len(common) > 0:
979 emsg = 'Following AID Collisions detected for: \n'
980 for friendly in common:
981 base = base_friendly[friendly]
982 oem = oem_friendly[friendly]
983 emsg += (
984 'Identifier: "%s" Friendly Name: "%s" '
985 'found in file "%s" and "%s"' %
986 (base.identifier, base.friendly, base.found, oem.found))
987 sys.exit(emsg)
988
989 self._friendly_to_aid = oem_friendly
990 self._friendly_to_aid.update(base_friendly)
991
992 self._generate()
993
994 def _to_fs_entry(self, fs_config):
995 """Converts an FSConfig entry to an fs entry.
996
997 Prints '{ mode, user, group, caps, "path" },'.
998
999 Calls sys.exit() on error.
1000
1001 Args:
1002 fs_config (FSConfig): The entry to convert to
1003 a valid C array entry.
1004 """
1005
1006 # Get some short names
1007 mode = fs_config.mode
1008 user = fs_config.user
1009 group = fs_config.group
1010 fname = fs_config.filename
1011 caps = fs_config.caps
1012 path = fs_config.path
1013
1014 emsg = 'Cannot convert friendly name "%s" to identifier!'
1015
1016 # remap friendly names to identifier names
1017 if AID.is_friendly(user):
1018 if user not in self._friendly_to_aid:
1019 sys.exit(emsg % user)
1020 user = self._friendly_to_aid[user].identifier
1021
1022 if AID.is_friendly(group):
1023 if group not in self._friendly_to_aid:
1024 sys.exit(emsg % group)
1025 group = self._friendly_to_aid[group].identifier
1026
1027 fmt = '{ %s, %s, %s, %s, "%s" },'
1028
1029 expanded = fmt % (mode, user, group, caps, path)
1030
1031 print FSConfigGen._FILE_COMMENT % fname
1032 print ' ' + expanded
1033
1034 @staticmethod
1035 def _gen_inc():
1036 """Generate the include header lines and print to stdout."""
1037 for include in FSConfigGen._INCLUDES:
1038 print '#include %s' % include
1039
1040 def _generate(self):
1041 """Generates an OEM android_filesystem_config.h header file to stdout.
1042
1043 Args:
1044 files ([FSConfig]): A list of FSConfig objects for file entries.
1045 dirs ([FSConfig]): A list of FSConfig objects for directory
1046 entries.
1047 aids ([AIDS]): A list of AID objects for Android Id entries.
1048 """
1049 print FSConfigGen._GENERATED
William Robertscfc51f52016-04-12 08:51:13 -07001050 print
1051
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001052 FSConfigGen._gen_inc()
1053 print
William Robertsc950a352016-03-04 18:12:29 -08001054
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001055 dirs = self._oem_parser.dirs
1056 files = self._oem_parser.files
1057 aids = self._oem_parser.aids
William Roberts8f42ce72016-04-25 12:27:43 -07001058
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001059 are_dirs = len(dirs) > 0
1060 are_files = len(files) > 0
1061 are_aids = len(aids) > 0
William Robertsc950a352016-03-04 18:12:29 -08001062
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001063 if are_aids:
1064 for aid in aids:
1065 # use the preserved _path value
1066 print FSConfigGen._FILE_COMMENT % aid.found
1067 print FSConfigGen._GENERIC_DEFINE % (aid.identifier, aid.value)
1068
1069 print
William Robertsc950a352016-03-04 18:12:29 -08001070
1071 if not are_dirs:
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001072 print FSConfigGen._DEFINE_NO_DIRS + '\n'
William Robertsc950a352016-03-04 18:12:29 -08001073
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001074 if not are_files:
1075 print FSConfigGen._DEFINE_NO_FILES + '\n'
William Robertsc950a352016-03-04 18:12:29 -08001076
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001077 if not are_files and not are_dirs and not are_aids:
1078 return
William Robertsc950a352016-03-04 18:12:29 -08001079
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001080 if are_files:
1081 print FSConfigGen._OPEN_FILE_STRUCT
1082 for fs_config in files:
1083 self._to_fs_entry(fs_config)
William Robertsc950a352016-03-04 18:12:29 -08001084
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001085 if not are_dirs:
1086 print FSConfigGen._IFDEF_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS
1087 print(
1088 ' ' +
1089 FSConfigGen._NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS_ENTRY)
1090 print FSConfigGen._ENDIF
1091 print FSConfigGen._CLOSE_FILE_STRUCT
William Robertsc950a352016-03-04 18:12:29 -08001092
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001093 if are_dirs:
1094 print FSConfigGen._OPEN_DIR_STRUCT
1095 for dir_entry in dirs:
1096 self._to_fs_entry(dir_entry)
William Robertsc950a352016-03-04 18:12:29 -08001097
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001098 print FSConfigGen._CLOSE_FILE_STRUCT
William Robertsc950a352016-03-04 18:12:29 -08001099
William Robertsc950a352016-03-04 18:12:29 -08001100
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001101@generator('aidarray')
1102class AIDArrayGen(BaseGenerator):
1103 """Generates the android_id static array."""
1104
1105 _GENERATED = ('/*\n'
1106 ' * THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY!\n'
1107 ' */')
1108
1109 _INCLUDE = '#include <private/android_filesystem_config.h>'
1110
Vic Yang5b3a7c02018-12-03 21:40:33 -08001111 # Note that the android_id name field is of type 'const char[]' instead of
1112 # 'const char*'. While this seems less straightforward as we need to
1113 # calculate the max length of all names, this allows the entire android_ids
1114 # table to be placed in .rodata section instead of .data.rel.ro section,
1115 # resulting in less memory pressure.
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001116 _STRUCT_FS_CONFIG = textwrap.dedent("""
1117 struct android_id_info {
Vic Yang5b3a7c02018-12-03 21:40:33 -08001118 const char name[%d];
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001119 unsigned aid;
1120 };""")
1121
1122 _OPEN_ID_ARRAY = 'static const struct android_id_info android_ids[] = {'
1123
1124 _ID_ENTRY = ' { "%s", %s },'
1125
1126 _CLOSE_FILE_STRUCT = '};'
1127
1128 _COUNT = ('#define android_id_count \\\n'
1129 ' (sizeof(android_ids) / sizeof(android_ids[0]))')
1130
1131 def add_opts(self, opt_group):
1132
1133 opt_group.add_argument(
1134 'hdrfile', help='The android_filesystem_config.h'
1135 'file to parse')
1136
1137 def __call__(self, args):
1138
1139 hdr = AIDHeaderParser(args['hdrfile'])
Vic Yang5b3a7c02018-12-03 21:40:33 -08001140 max_name_length = max(len(aid.friendly) + 1 for aid in hdr.aids)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001141
1142 print AIDArrayGen._GENERATED
1143 print
1144 print AIDArrayGen._INCLUDE
1145 print
Vic Yang5b3a7c02018-12-03 21:40:33 -08001146 print AIDArrayGen._STRUCT_FS_CONFIG % max_name_length
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001147 print
1148 print AIDArrayGen._OPEN_ID_ARRAY
1149
1150 for aid in hdr.aids:
1151 print AIDArrayGen._ID_ENTRY % (aid.friendly, aid.identifier)
1152
1153 print AIDArrayGen._CLOSE_FILE_STRUCT
1154 print
1155 print AIDArrayGen._COUNT
1156 print
1157
1158
1159@generator('oemaid')
1160class OEMAidGen(BaseGenerator):
1161 """Generates the OEM AID_<name> value header file."""
1162
1163 _GENERATED = ('/*\n'
1164 ' * THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY!\n'
1165 ' */')
1166
1167 _GENERIC_DEFINE = "#define %s\t%s"
1168
1169 _FILE_COMMENT = '// Defined in file: \"%s\"'
1170
1171 # Intentional trailing newline for readability.
1172 _FILE_IFNDEF_DEFINE = ('#ifndef GENERATED_OEM_AIDS_H_\n'
1173 '#define GENERATED_OEM_AIDS_H_\n')
1174
1175 _FILE_ENDIF = '#endif'
1176
1177 def __init__(self):
1178
1179 self._old_file = None
1180
1181 def add_opts(self, opt_group):
1182
1183 opt_group.add_argument(
1184 'fsconfig', nargs='+', help='The list of fsconfig files to parse.')
1185
1186 opt_group.add_argument(
1187 '--aid-header',
1188 required=True,
1189 help='An android_filesystem_config.h file'
1190 'to parse AIDs and OEM Ranges from')
1191
1192 def __call__(self, args):
1193
1194 hdr_parser = AIDHeaderParser(args['aid_header'])
1195
1196 parser = FSConfigFileParser(args['fsconfig'], hdr_parser.oem_ranges)
1197
1198 print OEMAidGen._GENERATED
1199
1200 print OEMAidGen._FILE_IFNDEF_DEFINE
1201
1202 for aid in parser.aids:
1203 self._print_aid(aid)
1204 print
1205
1206 print OEMAidGen._FILE_ENDIF
1207
1208 def _print_aid(self, aid):
1209 """Prints a valid #define AID identifier to stdout.
1210
1211 Args:
1212 aid to print
1213 """
1214
1215 # print the source file location of the AID
1216 found_file = aid.found
1217 if found_file != self._old_file:
1218 print OEMAidGen._FILE_COMMENT % found_file
1219 self._old_file = found_file
1220
1221 print OEMAidGen._GENERIC_DEFINE % (aid.identifier, aid.value)
1222
1223
1224@generator('passwd')
1225class PasswdGen(BaseGenerator):
1226 """Generates the /etc/passwd file per man (5) passwd."""
1227
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001228 def __init__(self):
1229
1230 self._old_file = None
1231
1232 def add_opts(self, opt_group):
1233
1234 opt_group.add_argument(
1235 'fsconfig', nargs='+', help='The list of fsconfig files to parse.')
1236
1237 opt_group.add_argument(
1238 '--aid-header',
1239 required=True,
1240 help='An android_filesystem_config.h file'
1241 'to parse AIDs and OEM Ranges from')
1242
Tom Cherry2d197a12018-05-14 13:14:41 -07001243 opt_group.add_argument(
1244 '--required-prefix',
1245 required=False,
1246 help='A prefix that the names are required to contain.')
1247
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001248 def __call__(self, args):
1249
1250 hdr_parser = AIDHeaderParser(args['aid_header'])
1251
1252 parser = FSConfigFileParser(args['fsconfig'], hdr_parser.oem_ranges)
1253
Tom Cherry2d197a12018-05-14 13:14:41 -07001254 required_prefix = args['required_prefix']
1255
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001256 aids = parser.aids
1257
1258 # nothing to do if no aids defined
1259 if len(aids) == 0:
1260 return
1261
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001262 for aid in aids:
Tom Cherry2d197a12018-05-14 13:14:41 -07001263 if required_prefix is None or aid.friendly.startswith(required_prefix):
1264 self._print_formatted_line(aid)
1265 else:
1266 sys.exit("%s: AID '%s' must start with '%s'" %
1267 (args['fsconfig'], aid.friendly, required_prefix))
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001268
1269 def _print_formatted_line(self, aid):
1270 """Prints the aid to stdout in the passwd format. Internal use only.
1271
1272 Colon delimited:
1273 login name, friendly name
1274 encrypted password (optional)
1275 uid (int)
1276 gid (int)
1277 User name or comment field
1278 home directory
1279 interpreter (optional)
1280
1281 Args:
1282 aid (AID): The aid to print.
1283 """
1284 if self._old_file != aid.found:
1285 self._old_file = aid.found
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001286
1287 try:
1288 logon, uid = Utils.get_login_and_uid_cleansed(aid)
1289 except ValueError as exception:
1290 sys.exit(exception)
1291
Wei Wang77e329a2018-06-05 16:00:07 -07001292 print "%s::%s:%s::/:%s" % (logon, uid, uid, aid.login_shell)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001293
1294
1295@generator('group')
1296class GroupGen(PasswdGen):
1297 """Generates the /etc/group file per man (5) group."""
1298
1299 # Overrides parent
1300 def _print_formatted_line(self, aid):
1301 """Prints the aid to stdout in the group format. Internal use only.
1302
1303 Formatted (per man 5 group) like:
1304 group_name:password:GID:user_list
1305
1306 Args:
1307 aid (AID): The aid to print.
1308 """
1309 if self._old_file != aid.found:
1310 self._old_file = aid.found
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001311
1312 try:
1313 logon, uid = Utils.get_login_and_uid_cleansed(aid)
1314 except ValueError as exception:
1315 sys.exit(exception)
1316
1317 print "%s::%s:" % (logon, uid)
1318
William Roberts1c4721c2016-04-26 13:05:34 -07001319
William Robertsc950a352016-03-04 18:12:29 -08001320def main():
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001321 """Main entry point for execution."""
William Robertsc950a352016-03-04 18:12:29 -08001322
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001323 opt_parser = argparse.ArgumentParser(
1324 description='A tool for parsing fsconfig config files and producing' +
1325 'digestable outputs.')
1326 subparser = opt_parser.add_subparsers(help='generators')
William Robertsc950a352016-03-04 18:12:29 -08001327
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001328 gens = generator.get()
William Robertsc950a352016-03-04 18:12:29 -08001329
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001330 # for each gen, instantiate and add them as an option
1331 for name, gen in gens.iteritems():
William Robertsc950a352016-03-04 18:12:29 -08001332
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001333 generator_option_parser = subparser.add_parser(name, help=gen.__doc__)
1334 generator_option_parser.set_defaults(which=name)
William Roberts8cb6a182016-04-08 22:06:19 -07001335
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001336 opt_group = generator_option_parser.add_argument_group(name +
1337 ' options')
1338 gen.add_opts(opt_group)
William Roberts8cb6a182016-04-08 22:06:19 -07001339
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001340 args = opt_parser.parse_args()
1341
1342 args_as_dict = vars(args)
1343 which = args_as_dict['which']
1344 del args_as_dict['which']
1345
1346 gens[which](args_as_dict)
1347
William Robertsc950a352016-03-04 18:12:29 -08001348
1349if __name__ == '__main__':
1350 main()