blob: 0a8def8e03ed91e9d4976bd8dbf8acdf4be3b135 [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
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000912 _OPEN_FILE_STRUCT = (
913 'static const struct fs_path_config android_device_files[] = {')
914
915 _OPEN_DIR_STRUCT = (
916 'static const struct fs_path_config android_device_dirs[] = {')
917
918 _CLOSE_FILE_STRUCT = '};'
919
920 _GENERIC_DEFINE = "#define %s\t%s"
921
922 _FILE_COMMENT = '// Defined in file: \"%s\"'
923
924 def __init__(self, *args, **kwargs):
925 BaseGenerator.__init__(args, kwargs)
926
927 self._oem_parser = None
928 self._base_parser = None
929 self._friendly_to_aid = None
930
931 def add_opts(self, opt_group):
932
933 opt_group.add_argument(
934 'fsconfig', nargs='+', help='The list of fsconfig files to parse')
935
936 opt_group.add_argument(
937 '--aid-header',
938 required=True,
939 help='An android_filesystem_config.h file'
940 ' to parse AIDs and OEM Ranges from')
941
942 def __call__(self, args):
943
944 self._base_parser = AIDHeaderParser(args['aid_header'])
945 self._oem_parser = FSConfigFileParser(args['fsconfig'],
946 self._base_parser.oem_ranges)
947 base_aids = self._base_parser.aids
948 oem_aids = self._oem_parser.aids
949
950 # Detect name collisions on AIDs. Since friendly works as the
951 # identifier for collision testing and we need friendly later on for
952 # name resolution, just calculate and use friendly.
953 # {aid.friendly: aid for aid in base_aids}
954 base_friendly = {aid.friendly: aid for aid in base_aids}
955 oem_friendly = {aid.friendly: aid for aid in oem_aids}
956
957 base_set = set(base_friendly.keys())
958 oem_set = set(oem_friendly.keys())
959
960 common = base_set & oem_set
961
962 if len(common) > 0:
963 emsg = 'Following AID Collisions detected for: \n'
964 for friendly in common:
965 base = base_friendly[friendly]
966 oem = oem_friendly[friendly]
967 emsg += (
968 'Identifier: "%s" Friendly Name: "%s" '
969 'found in file "%s" and "%s"' %
970 (base.identifier, base.friendly, base.found, oem.found))
971 sys.exit(emsg)
972
973 self._friendly_to_aid = oem_friendly
974 self._friendly_to_aid.update(base_friendly)
975
976 self._generate()
977
978 def _to_fs_entry(self, fs_config):
979 """Converts an FSConfig entry to an fs entry.
980
981 Prints '{ mode, user, group, caps, "path" },'.
982
983 Calls sys.exit() on error.
984
985 Args:
986 fs_config (FSConfig): The entry to convert to
987 a valid C array entry.
988 """
989
990 # Get some short names
991 mode = fs_config.mode
992 user = fs_config.user
993 group = fs_config.group
994 fname = fs_config.filename
995 caps = fs_config.caps
996 path = fs_config.path
997
998 emsg = 'Cannot convert friendly name "%s" to identifier!'
999
1000 # remap friendly names to identifier names
1001 if AID.is_friendly(user):
1002 if user not in self._friendly_to_aid:
1003 sys.exit(emsg % user)
1004 user = self._friendly_to_aid[user].identifier
1005
1006 if AID.is_friendly(group):
1007 if group not in self._friendly_to_aid:
1008 sys.exit(emsg % group)
1009 group = self._friendly_to_aid[group].identifier
1010
1011 fmt = '{ %s, %s, %s, %s, "%s" },'
1012
1013 expanded = fmt % (mode, user, group, caps, path)
1014
1015 print FSConfigGen._FILE_COMMENT % fname
1016 print ' ' + expanded
1017
1018 @staticmethod
1019 def _gen_inc():
1020 """Generate the include header lines and print to stdout."""
1021 for include in FSConfigGen._INCLUDES:
1022 print '#include %s' % include
1023
1024 def _generate(self):
1025 """Generates an OEM android_filesystem_config.h header file to stdout.
1026
1027 Args:
1028 files ([FSConfig]): A list of FSConfig objects for file entries.
1029 dirs ([FSConfig]): A list of FSConfig objects for directory
1030 entries.
1031 aids ([AIDS]): A list of AID objects for Android Id entries.
1032 """
1033 print FSConfigGen._GENERATED
William Robertscfc51f52016-04-12 08:51:13 -07001034 print
1035
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001036 FSConfigGen._gen_inc()
1037 print
William Robertsc950a352016-03-04 18:12:29 -08001038
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001039 dirs = self._oem_parser.dirs
1040 files = self._oem_parser.files
1041 aids = self._oem_parser.aids
William Roberts8f42ce72016-04-25 12:27:43 -07001042
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001043 are_dirs = len(dirs) > 0
1044 are_files = len(files) > 0
1045 are_aids = len(aids) > 0
William Robertsc950a352016-03-04 18:12:29 -08001046
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001047 if are_aids:
1048 for aid in aids:
1049 # use the preserved _path value
1050 print FSConfigGen._FILE_COMMENT % aid.found
1051 print FSConfigGen._GENERIC_DEFINE % (aid.identifier, aid.value)
1052
1053 print
William Robertsc950a352016-03-04 18:12:29 -08001054
1055 if not are_dirs:
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001056 print FSConfigGen._DEFINE_NO_DIRS + '\n'
William Robertsc950a352016-03-04 18:12:29 -08001057
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001058 if not are_files:
1059 print FSConfigGen._DEFINE_NO_FILES + '\n'
William Robertsc950a352016-03-04 18:12:29 -08001060
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001061 if not are_files and not are_dirs and not are_aids:
1062 return
William Robertsc950a352016-03-04 18:12:29 -08001063
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001064 if are_files:
1065 print FSConfigGen._OPEN_FILE_STRUCT
1066 for fs_config in files:
1067 self._to_fs_entry(fs_config)
William Robertsc950a352016-03-04 18:12:29 -08001068
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001069 print FSConfigGen._CLOSE_FILE_STRUCT
William Robertsc950a352016-03-04 18:12:29 -08001070
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001071 if are_dirs:
1072 print FSConfigGen._OPEN_DIR_STRUCT
1073 for dir_entry in dirs:
1074 self._to_fs_entry(dir_entry)
William Robertsc950a352016-03-04 18:12:29 -08001075
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001076 print FSConfigGen._CLOSE_FILE_STRUCT
William Robertsc950a352016-03-04 18:12:29 -08001077
William Robertsc950a352016-03-04 18:12:29 -08001078
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001079@generator('aidarray')
1080class AIDArrayGen(BaseGenerator):
1081 """Generates the android_id static array."""
1082
1083 _GENERATED = ('/*\n'
1084 ' * THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY!\n'
1085 ' */')
1086
1087 _INCLUDE = '#include <private/android_filesystem_config.h>'
1088
Vic Yang5b3a7c02018-12-03 21:40:33 -08001089 # Note that the android_id name field is of type 'const char[]' instead of
1090 # 'const char*'. While this seems less straightforward as we need to
1091 # calculate the max length of all names, this allows the entire android_ids
1092 # table to be placed in .rodata section instead of .data.rel.ro section,
1093 # resulting in less memory pressure.
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001094 _STRUCT_FS_CONFIG = textwrap.dedent("""
1095 struct android_id_info {
Vic Yang5b3a7c02018-12-03 21:40:33 -08001096 const char name[%d];
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001097 unsigned aid;
1098 };""")
1099
1100 _OPEN_ID_ARRAY = 'static const struct android_id_info android_ids[] = {'
1101
1102 _ID_ENTRY = ' { "%s", %s },'
1103
1104 _CLOSE_FILE_STRUCT = '};'
1105
1106 _COUNT = ('#define android_id_count \\\n'
1107 ' (sizeof(android_ids) / sizeof(android_ids[0]))')
1108
1109 def add_opts(self, opt_group):
1110
1111 opt_group.add_argument(
1112 'hdrfile', help='The android_filesystem_config.h'
1113 'file to parse')
1114
1115 def __call__(self, args):
1116
1117 hdr = AIDHeaderParser(args['hdrfile'])
Vic Yang5b3a7c02018-12-03 21:40:33 -08001118 max_name_length = max(len(aid.friendly) + 1 for aid in hdr.aids)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001119
1120 print AIDArrayGen._GENERATED
1121 print
1122 print AIDArrayGen._INCLUDE
1123 print
Vic Yang5b3a7c02018-12-03 21:40:33 -08001124 print AIDArrayGen._STRUCT_FS_CONFIG % max_name_length
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001125 print
1126 print AIDArrayGen._OPEN_ID_ARRAY
1127
1128 for aid in hdr.aids:
1129 print AIDArrayGen._ID_ENTRY % (aid.friendly, aid.identifier)
1130
1131 print AIDArrayGen._CLOSE_FILE_STRUCT
1132 print
1133 print AIDArrayGen._COUNT
1134 print
1135
1136
1137@generator('oemaid')
1138class OEMAidGen(BaseGenerator):
1139 """Generates the OEM AID_<name> value header file."""
1140
1141 _GENERATED = ('/*\n'
1142 ' * THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY!\n'
1143 ' */')
1144
1145 _GENERIC_DEFINE = "#define %s\t%s"
1146
1147 _FILE_COMMENT = '// Defined in file: \"%s\"'
1148
1149 # Intentional trailing newline for readability.
1150 _FILE_IFNDEF_DEFINE = ('#ifndef GENERATED_OEM_AIDS_H_\n'
1151 '#define GENERATED_OEM_AIDS_H_\n')
1152
1153 _FILE_ENDIF = '#endif'
1154
1155 def __init__(self):
1156
1157 self._old_file = None
1158
1159 def add_opts(self, opt_group):
1160
1161 opt_group.add_argument(
1162 'fsconfig', nargs='+', help='The list of fsconfig files to parse.')
1163
1164 opt_group.add_argument(
1165 '--aid-header',
1166 required=True,
1167 help='An android_filesystem_config.h file'
1168 'to parse AIDs and OEM Ranges from')
1169
1170 def __call__(self, args):
1171
1172 hdr_parser = AIDHeaderParser(args['aid_header'])
1173
1174 parser = FSConfigFileParser(args['fsconfig'], hdr_parser.oem_ranges)
1175
1176 print OEMAidGen._GENERATED
1177
1178 print OEMAidGen._FILE_IFNDEF_DEFINE
1179
1180 for aid in parser.aids:
1181 self._print_aid(aid)
1182 print
1183
1184 print OEMAidGen._FILE_ENDIF
1185
1186 def _print_aid(self, aid):
1187 """Prints a valid #define AID identifier to stdout.
1188
1189 Args:
1190 aid to print
1191 """
1192
1193 # print the source file location of the AID
1194 found_file = aid.found
1195 if found_file != self._old_file:
1196 print OEMAidGen._FILE_COMMENT % found_file
1197 self._old_file = found_file
1198
1199 print OEMAidGen._GENERIC_DEFINE % (aid.identifier, aid.value)
1200
1201
1202@generator('passwd')
1203class PasswdGen(BaseGenerator):
1204 """Generates the /etc/passwd file per man (5) passwd."""
1205
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001206 def __init__(self):
1207
1208 self._old_file = None
1209
1210 def add_opts(self, opt_group):
1211
1212 opt_group.add_argument(
1213 'fsconfig', nargs='+', help='The list of fsconfig files to parse.')
1214
1215 opt_group.add_argument(
1216 '--aid-header',
1217 required=True,
1218 help='An android_filesystem_config.h file'
1219 'to parse AIDs and OEM Ranges from')
1220
Tom Cherry2d197a12018-05-14 13:14:41 -07001221 opt_group.add_argument(
1222 '--required-prefix',
1223 required=False,
1224 help='A prefix that the names are required to contain.')
1225
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001226 def __call__(self, args):
1227
1228 hdr_parser = AIDHeaderParser(args['aid_header'])
1229
1230 parser = FSConfigFileParser(args['fsconfig'], hdr_parser.oem_ranges)
1231
Tom Cherry2d197a12018-05-14 13:14:41 -07001232 required_prefix = args['required_prefix']
1233
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001234 aids = parser.aids
1235
1236 # nothing to do if no aids defined
1237 if len(aids) == 0:
1238 return
1239
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001240 for aid in aids:
Tom Cherry2d197a12018-05-14 13:14:41 -07001241 if required_prefix is None or aid.friendly.startswith(required_prefix):
1242 self._print_formatted_line(aid)
1243 else:
1244 sys.exit("%s: AID '%s' must start with '%s'" %
1245 (args['fsconfig'], aid.friendly, required_prefix))
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001246
1247 def _print_formatted_line(self, aid):
1248 """Prints the aid to stdout in the passwd format. Internal use only.
1249
1250 Colon delimited:
1251 login name, friendly name
1252 encrypted password (optional)
1253 uid (int)
1254 gid (int)
1255 User name or comment field
1256 home directory
1257 interpreter (optional)
1258
1259 Args:
1260 aid (AID): The aid to print.
1261 """
1262 if self._old_file != aid.found:
1263 self._old_file = aid.found
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001264
1265 try:
1266 logon, uid = Utils.get_login_and_uid_cleansed(aid)
1267 except ValueError as exception:
1268 sys.exit(exception)
1269
Wei Wang77e329a2018-06-05 16:00:07 -07001270 print "%s::%s:%s::/:%s" % (logon, uid, uid, aid.login_shell)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001271
1272
1273@generator('group')
1274class GroupGen(PasswdGen):
1275 """Generates the /etc/group file per man (5) group."""
1276
1277 # Overrides parent
1278 def _print_formatted_line(self, aid):
1279 """Prints the aid to stdout in the group format. Internal use only.
1280
1281 Formatted (per man 5 group) like:
1282 group_name:password:GID:user_list
1283
1284 Args:
1285 aid (AID): The aid to print.
1286 """
1287 if self._old_file != aid.found:
1288 self._old_file = aid.found
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001289
1290 try:
1291 logon, uid = Utils.get_login_and_uid_cleansed(aid)
1292 except ValueError as exception:
1293 sys.exit(exception)
1294
1295 print "%s::%s:" % (logon, uid)
1296
Joe Onorato2afb6eb2018-12-04 14:51:58 -08001297@generator('print')
1298class PrintGen(BaseGenerator):
1299 """Prints just the constants and values, separated by spaces, in an easy to
1300 parse format for use by other scripts.
1301
1302 Each line is just the identifier and the value, separated by a space.
1303 """
1304
1305 def add_opts(self, opt_group):
1306 opt_group.add_argument(
1307 'aid-header', help='An android_filesystem_config.h file.')
1308
1309 def __call__(self, args):
1310
1311 hdr_parser = AIDHeaderParser(args['aid-header'])
1312 aids = hdr_parser.aids
1313
1314 aids.sort(key=lambda item: int(item.normalized_value))
1315
1316 for aid in aids:
1317 print '%s %s' % (aid.identifier, aid.normalized_value)
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()