blob: 18e6534c464b7fc674b337a740833a4dfb67277f [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(
Tom Cherry766adc92019-02-13 14:24:52 -0800115 'Cannot specify delimiter character ":" in logon: "%s"' %
116 logon)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000117 return logon, uid
William Roberts1c4721c2016-04-26 13:05:34 -0700118
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000119
120class AID(object):
121 """This class represents an Android ID or an AID.
122
123 Attributes:
124 identifier (str): The identifier name for a #define.
125 value (str) The User Id (uid) of the associate define.
126 found (str) The file it was found in, can be None.
127 normalized_value (str): Same as value, but base 10.
128 friendly (str): The friendly name of aid.
129 """
130
131 PREFIX = 'AID_'
132
133 # Some of the AIDS like AID_MEDIA_EX had names like mediaex
134 # list a map of things to fixup until we can correct these
135 # at a later date.
136 _FIXUPS = {
137 'media_drm': 'mediadrm',
138 'media_ex': 'mediaex',
139 'media_codec': 'mediacodec'
140 }
141
Wei Wang77e329a2018-06-05 16:00:07 -0700142 def __init__(self, identifier, value, found, login_shell):
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000143 """
144 Args:
145 identifier: The identifier name for a #define <identifier>.
146 value: The value of the AID, aka the uid.
147 found (str): The file found in, not required to be specified.
Wei Wang77e329a2018-06-05 16:00:07 -0700148 login_shell (str): The shell field per man (5) passwd file.
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000149 Raises:
Tom Cherryee0610e2018-02-08 14:26:53 -0800150 ValueError: if the friendly name is longer than 31 characters as
151 that is bionic's internal buffer size for name.
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000152 ValueError: if value is not a valid string number as processed by
153 int(x, 0)
154 """
155 self.identifier = identifier
156 self.value = value
157 self.found = found
Wei Wang77e329a2018-06-05 16:00:07 -0700158 self.login_shell = login_shell
159
Tom Cherryee0610e2018-02-08 14:26:53 -0800160 try:
161 self.normalized_value = str(int(value, 0))
Tom Cherry766adc92019-02-13 14:24:52 -0800162 except ValueError:
163 raise ValueError(
164 'Invalid "value", not aid number, got: \"%s\"' % value)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000165
166 # Where we calculate the friendly name
167 friendly = identifier[len(AID.PREFIX):].lower()
168 self.friendly = AID._fixup_friendly(friendly)
169
Tom Cherryee0610e2018-02-08 14:26:53 -0800170 if len(self.friendly) > 31:
Tom Cherry766adc92019-02-13 14:24:52 -0800171 raise ValueError(
172 'AID names must be under 32 characters "%s"' % self.friendly)
Tom Cherryee0610e2018-02-08 14:26:53 -0800173
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000174 def __eq__(self, other):
175
176 return self.identifier == other.identifier \
177 and self.value == other.value and self.found == other.found \
Wei Wang77e329a2018-06-05 16:00:07 -0700178 and self.normalized_value == other.normalized_value \
179 and self.login_shell == other.login_shell
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000180
181 @staticmethod
182 def is_friendly(name):
183 """Determines if an AID is a freindly name or C define.
184
185 For example if name is AID_SYSTEM it returns false, if name
186 was system, it would return true.
187
188 Returns:
189 True if name is a friendly name False otherwise.
190 """
191
192 return not name.startswith(AID.PREFIX)
193
194 @staticmethod
195 def _fixup_friendly(friendly):
196 """Fixup friendly names that historically don't follow the convention.
197
198 Args:
199 friendly (str): The friendly name.
200
201 Returns:
202 The fixedup friendly name as a str.
203 """
204
205 if friendly in AID._FIXUPS:
206 return AID._FIXUPS[friendly]
207
208 return friendly
209
210
211class FSConfig(object):
212 """Represents a filesystem config array entry.
213
214 Represents a file system configuration entry for specifying
215 file system capabilities.
216
217 Attributes:
218 mode (str): The mode of the file or directory.
219 user (str): The uid or #define identifier (AID_SYSTEM)
220 group (str): The gid or #define identifier (AID_SYSTEM)
221 caps (str): The capability set.
Tom Cherry766adc92019-02-13 14:24:52 -0800222 path (str): The path of the file or directory.
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000223 filename (str): The file it was found in.
224 """
225
226 def __init__(self, mode, user, group, caps, path, filename):
227 """
228 Args:
229 mode (str): The mode of the file or directory.
230 user (str): The uid or #define identifier (AID_SYSTEM)
231 group (str): The gid or #define identifier (AID_SYSTEM)
232 caps (str): The capability set as a list.
Tom Cherry766adc92019-02-13 14:24:52 -0800233 path (str): The path of the file or directory.
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000234 filename (str): The file it was found in.
235 """
236 self.mode = mode
237 self.user = user
238 self.group = group
239 self.caps = caps
240 self.path = path
241 self.filename = filename
242
243 def __eq__(self, other):
244
245 return self.mode == other.mode and self.user == other.user \
246 and self.group == other.group and self.caps == other.caps \
247 and self.path == other.path and self.filename == other.filename
248
Tom Cherry766adc92019-02-13 14:24:52 -0800249 def __repr__(self):
250 return 'FSConfig(%r, %r, %r, %r, %r, %r)' % (self.mode, self.user,
251 self.group, self.caps,
252 self.path, self.filename)
253
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000254
255class AIDHeaderParser(object):
256 """Parses an android_filesystem_config.h file.
257
258 Parses a C header file and extracts lines starting with #define AID_<name>
259 while capturing the OEM defined ranges and ignoring other ranges. It also
260 skips some hardcoded AIDs it doesn't need to generate a mapping for.
261 It provides some basic sanity checks. The information extracted from this
262 file can later be used to sanity check other things (like oem ranges) as
263 well as generating a mapping of names to uids. It was primarily designed to
264 parse the private/android_filesystem_config.h, but any C header should
265 work.
266 """
267
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000268 _SKIP_AIDS = [
269 re.compile(r'%sUNUSED[0-9].*' % AID.PREFIX),
Tom Cherry766adc92019-02-13 14:24:52 -0800270 re.compile(r'%sAPP' % AID.PREFIX),
271 re.compile(r'%sUSER' % AID.PREFIX)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000272 ]
273 _AID_DEFINE = re.compile(r'\s*#define\s+%s.*' % AID.PREFIX)
274 _OEM_START_KW = 'START'
275 _OEM_END_KW = 'END'
Johan Redestig1552a282017-01-03 09:36:47 +0100276 _OEM_RANGE = re.compile('%sOEM_RESERVED_[0-9]*_{0,1}(%s|%s)' %
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000277 (AID.PREFIX, _OEM_START_KW, _OEM_END_KW))
278 # AID lines cannot end with _START or _END, ie AID_FOO is OK
279 # but AID_FOO_START is skiped. Note that AID_FOOSTART is NOT skipped.
280 _AID_SKIP_RANGE = ['_' + _OEM_START_KW, '_' + _OEM_END_KW]
281 _COLLISION_OK = ['AID_APP', 'AID_APP_START', 'AID_USER', 'AID_USER_OFFSET']
282
283 def __init__(self, aid_header):
284 """
285 Args:
286 aid_header (str): file name for the header
287 file containing AID entries.
288 """
289 self._aid_header = aid_header
290 self._aid_name_to_value = {}
291 self._aid_value_to_name = {}
292 self._oem_ranges = {}
293
294 with open(aid_header) as open_file:
295 self._parse(open_file)
William Roberts64edf5b2016-04-11 17:12:47 -0700296
297 try:
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000298 self._process_and_check()
299 except ValueError as exception:
300 sys.exit('Error processing parsed data: "%s"' % (str(exception)))
301
302 def _parse(self, aid_file):
303 """Parses an AID header file. Internal use only.
304
305 Args:
306 aid_file (file): The open AID header file to parse.
307 """
308
309 for lineno, line in enumerate(aid_file):
William Roberts820421c2016-12-13 19:12:35 -0800310
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000311 def error_message(msg):
312 """Creates an error message with the current parsing state."""
William Roberts4165c632016-12-13 19:17:07 -0800313 # pylint: disable=cell-var-from-loop
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000314 return 'Error "{}" in file: "{}" on line: {}'.format(
315 msg, self._aid_header, str(lineno))
316
317 if AIDHeaderParser._AID_DEFINE.match(line):
318 chunks = line.split()
319 identifier = chunks[1]
320 value = chunks[2]
321
Tom Cherry766adc92019-02-13 14:24:52 -0800322 if any(
323 x.match(identifier)
324 for x in AIDHeaderParser._SKIP_AIDS):
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000325 continue
326
327 try:
328 if AIDHeaderParser._is_oem_range(identifier):
329 self._handle_oem_range(identifier, value)
330 elif not any(
331 identifier.endswith(x)
332 for x in AIDHeaderParser._AID_SKIP_RANGE):
333 self._handle_aid(identifier, value)
334 except ValueError as exception:
William Roberts820421c2016-12-13 19:12:35 -0800335 sys.exit(
Tom Cherry766adc92019-02-13 14:24:52 -0800336 error_message('{} for "{}"'.format(
337 exception, identifier)))
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000338
339 def _handle_aid(self, identifier, value):
340 """Handle an AID C #define.
341
342 Handles an AID, sanity checking, generating the friendly name and
343 adding it to the internal maps. Internal use only.
344
345 Args:
346 identifier (str): The name of the #define identifier. ie AID_FOO.
347 value (str): The value associated with the identifier.
348
349 Raises:
350 ValueError: With message set to indicate the error.
351 """
352
Wei Wang77e329a2018-06-05 16:00:07 -0700353 aid = AID(identifier, value, self._aid_header, '/system/bin/sh')
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000354
355 # duplicate name
356 if aid.friendly in self._aid_name_to_value:
357 raise ValueError('Duplicate aid "%s"' % identifier)
358
359 if value in self._aid_value_to_name and aid.identifier not in AIDHeaderParser._COLLISION_OK:
Tom Cherry766adc92019-02-13 14:24:52 -0800360 raise ValueError(
361 'Duplicate aid value "%s" for %s' % (value, identifier))
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000362
363 self._aid_name_to_value[aid.friendly] = aid
364 self._aid_value_to_name[value] = aid.friendly
365
366 def _handle_oem_range(self, identifier, value):
367 """Handle an OEM range C #define.
368
369 When encountering special AID defines, notably for the OEM ranges
370 this method handles sanity checking and adding them to the internal
371 maps. For internal use only.
372
373 Args:
374 identifier (str): The name of the #define identifier.
375 ie AID_OEM_RESERVED_START/END.
376 value (str): The value associated with the identifier.
377
378 Raises:
379 ValueError: With message set to indicate the error.
380 """
381
382 try:
383 int_value = int(value, 0)
William Roberts64edf5b2016-04-11 17:12:47 -0700384 except ValueError:
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000385 raise ValueError(
386 'Could not convert "%s" to integer value, got: "%s"' %
387 (identifier, value))
William Roberts64edf5b2016-04-11 17:12:47 -0700388
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000389 # convert AID_OEM_RESERVED_START or AID_OEM_RESERVED_<num>_START
390 # to AID_OEM_RESERVED or AID_OEM_RESERVED_<num>
391 is_start = identifier.endswith(AIDHeaderParser._OEM_START_KW)
William Roberts64edf5b2016-04-11 17:12:47 -0700392
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000393 if is_start:
394 tostrip = len(AIDHeaderParser._OEM_START_KW)
395 else:
396 tostrip = len(AIDHeaderParser._OEM_END_KW)
William Roberts64edf5b2016-04-11 17:12:47 -0700397
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000398 # ending _
399 tostrip = tostrip + 1
William Roberts64edf5b2016-04-11 17:12:47 -0700400
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000401 strip = identifier[:-tostrip]
402 if strip not in self._oem_ranges:
403 self._oem_ranges[strip] = []
William Roberts64edf5b2016-04-11 17:12:47 -0700404
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000405 if len(self._oem_ranges[strip]) > 2:
406 raise ValueError('Too many same OEM Ranges "%s"' % identifier)
William Roberts64edf5b2016-04-11 17:12:47 -0700407
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000408 if len(self._oem_ranges[strip]) == 1:
409 tmp = self._oem_ranges[strip][0]
William Roberts64edf5b2016-04-11 17:12:47 -0700410
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000411 if tmp == int_value:
412 raise ValueError('START and END values equal %u' % int_value)
413 elif is_start and tmp < int_value:
Tom Cherry766adc92019-02-13 14:24:52 -0800414 raise ValueError(
415 'END value %u less than START value %u' % (tmp, int_value))
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000416 elif not is_start and tmp > int_value:
Tom Cherry766adc92019-02-13 14:24:52 -0800417 raise ValueError(
418 'END value %u less than START value %u' % (int_value, tmp))
William Roberts64edf5b2016-04-11 17:12:47 -0700419
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000420 # Add START values to the head of the list and END values at the end.
421 # Thus, the list is ordered with index 0 as START and index 1 as END.
422 if is_start:
423 self._oem_ranges[strip].insert(0, int_value)
424 else:
425 self._oem_ranges[strip].append(int_value)
William Roberts64edf5b2016-04-11 17:12:47 -0700426
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000427 def _process_and_check(self):
428 """Process, check and populate internal data structures.
William Roberts64edf5b2016-04-11 17:12:47 -0700429
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000430 After parsing and generating the internal data structures, this method
431 is responsible for sanity checking ALL of the acquired data.
William Roberts64edf5b2016-04-11 17:12:47 -0700432
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000433 Raises:
434 ValueError: With the message set to indicate the specific error.
435 """
William Roberts64edf5b2016-04-11 17:12:47 -0700436
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000437 # tuplefy the lists since range() does not like them mutable.
438 self._oem_ranges = [
439 AIDHeaderParser._convert_lst_to_tup(k, v)
440 for k, v in self._oem_ranges.iteritems()
441 ]
William Roberts64edf5b2016-04-11 17:12:47 -0700442
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000443 # Check for overlapping ranges
444 for i, range1 in enumerate(self._oem_ranges):
445 for range2 in self._oem_ranges[i + 1:]:
446 if AIDHeaderParser._is_overlap(range1, range2):
447 raise ValueError("Overlapping OEM Ranges found %s and %s" %
448 (str(range1), str(range2)))
William Roberts64edf5b2016-04-11 17:12:47 -0700449
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000450 # No core AIDs should be within any oem range.
451 for aid in self._aid_value_to_name:
452
453 if Utils.in_any_range(aid, self._oem_ranges):
454 name = self._aid_value_to_name[aid]
455 raise ValueError(
456 'AID "%s" value: %u within reserved OEM Range: "%s"' %
457 (name, aid, str(self._oem_ranges)))
458
459 @property
460 def oem_ranges(self):
461 """Retrieves the OEM closed ranges as a list of tuples.
462
463 Returns:
464 A list of closed range tuples: [ (0, 42), (50, 105) ... ]
465 """
466 return self._oem_ranges
467
468 @property
469 def aids(self):
470 """Retrieves the list of found AIDs.
471
472 Returns:
473 A list of AID() objects.
474 """
475 return self._aid_name_to_value.values()
476
477 @staticmethod
478 def _convert_lst_to_tup(name, lst):
479 """Converts a mutable list to a non-mutable tuple.
480
481 Used ONLY for ranges and thus enforces a length of 2.
482
483 Args:
484 lst (List): list that should be "tuplefied".
485
486 Raises:
487 ValueError if lst is not a list or len is not 2.
488
489 Returns:
490 Tuple(lst)
491 """
492 if not lst or len(lst) != 2:
493 raise ValueError('Mismatched range for "%s"' % name)
494
495 return tuple(lst)
496
497 @staticmethod
498 def _is_oem_range(aid):
499 """Detects if a given aid is within the reserved OEM range.
500
501 Args:
502 aid (int): The aid to test
503
504 Returns:
505 True if it is within the range, False otherwise.
506 """
507
508 return AIDHeaderParser._OEM_RANGE.match(aid)
509
510 @staticmethod
511 def _is_overlap(range_a, range_b):
512 """Calculates the overlap of two range tuples.
513
514 A range tuple is a closed range. A closed range includes its endpoints.
515 Note that python tuples use () notation which collides with the
516 mathematical notation for open ranges.
517
518 Args:
519 range_a: The first tuple closed range eg (0, 5).
520 range_b: The second tuple closed range eg (3, 7).
521
522 Returns:
523 True if they overlap, False otherwise.
524 """
525
526 return max(range_a[0], range_b[0]) <= min(range_a[1], range_b[1])
William Roberts64edf5b2016-04-11 17:12:47 -0700527
528
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000529class FSConfigFileParser(object):
530 """Parses a config.fs ini format file.
William Roberts11c29282016-04-09 10:32:30 -0700531
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000532 This class is responsible for parsing the config.fs ini format files.
533 It collects and checks all the data in these files and makes it available
534 for consumption post processed.
535 """
William Roberts11c29282016-04-09 10:32:30 -0700536
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000537 # These _AID vars work together to ensure that an AID section name
538 # cannot contain invalid characters for a C define or a passwd/group file.
539 # Since _AID_PREFIX is within the set of _AID_MATCH the error logic only
540 # checks end, if you change this, you may have to update the error
541 # detection code.
542 _AID_MATCH = re.compile('%s[A-Z0-9_]+' % AID.PREFIX)
543 _AID_ERR_MSG = 'Expecting upper case, a number or underscore'
William Roberts5f059a72016-04-25 10:36:45 -0700544
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000545 # list of handler to required options, used to identify the
546 # parsing section
Tom Cherry766adc92019-02-13 14:24:52 -0800547 _SECTIONS = [('_handle_aid', ('value', )),
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000548 ('_handle_path', ('mode', 'user', 'group', 'caps'))]
549
550 def __init__(self, config_files, oem_ranges):
551 """
552 Args:
553 config_files ([str]): The list of config.fs files to parse.
554 Note the filename is not important.
555 oem_ranges ([(),()]): range tuples indicating reserved OEM ranges.
556 """
557
558 self._files = []
559 self._dirs = []
560 self._aids = []
561
562 self._seen_paths = {}
563 # (name to file, value to aid)
564 self._seen_aids = ({}, {})
565
566 self._oem_ranges = oem_ranges
567
568 self._config_files = config_files
569
570 for config_file in self._config_files:
571 self._parse(config_file)
572
573 def _parse(self, file_name):
574 """Parses and verifies config.fs files. Internal use only.
575
576 Args:
577 file_name (str): The config.fs (PythonConfigParser file format)
578 file to parse.
579
580 Raises:
581 Anything raised by ConfigParser.read()
582 """
583
584 # Separate config parsers for each file found. If you use
585 # read(filenames...) later files can override earlier files which is
586 # not what we want. Track state across files and enforce with
587 # _handle_dup(). Note, strict ConfigParser is set to true in
588 # Python >= 3.2, so in previous versions same file sections can
589 # override previous
590 # sections.
William Robertsc950a352016-03-04 18:12:29 -0800591
592 config = ConfigParser.ConfigParser()
593 config.read(file_name)
594
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000595 for section in config.sections():
William Robertsc950a352016-03-04 18:12:29 -0800596
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000597 found = False
William Roberts5f059a72016-04-25 10:36:45 -0700598
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000599 for test in FSConfigFileParser._SECTIONS:
600 handler = test[0]
601 options = test[1]
William Roberts5f059a72016-04-25 10:36:45 -0700602
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000603 if all([config.has_option(section, item) for item in options]):
604 handler = getattr(self, handler)
605 handler(file_name, section, config)
606 found = True
607 break
William Roberts5f059a72016-04-25 10:36:45 -0700608
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000609 if not found:
Tom Cherry766adc92019-02-13 14:24:52 -0800610 sys.exit('Invalid section "%s" in file: "%s"' % (section,
611 file_name))
William Roberts11c29282016-04-09 10:32:30 -0700612
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000613 # sort entries:
614 # * specified path before prefix match
615 # ** ie foo before f*
616 # * lexicographical less than before other
617 # ** ie boo before foo
618 # Given these paths:
619 # paths=['ac', 'a', 'acd', 'an', 'a*', 'aa', 'ac*']
620 # The sort order would be:
621 # paths=['a', 'aa', 'ac', 'acd', 'an', 'ac*', 'a*']
622 # Thus the fs_config tools will match on specified paths before
623 # attempting prefix, and match on the longest matching prefix.
624 self._files.sort(key=FSConfigFileParser._file_key)
625
626 # sort on value of (file_name, name, value, strvalue)
627 # This is only cosmetic so AIDS are arranged in ascending order
628 # within the generated file.
629 self._aids.sort(key=lambda item: item.normalized_value)
630
631 def _handle_aid(self, file_name, section_name, config):
632 """Verifies an AID entry and adds it to the aid list.
633
634 Calls sys.exit() with a descriptive message of the failure.
635
636 Args:
637 file_name (str): The filename of the config file being parsed.
638 section_name (str): The section name currently being parsed.
639 config (ConfigParser): The ConfigParser section being parsed that
640 the option values will come from.
641 """
642
643 def error_message(msg):
644 """Creates an error message with current parsing state."""
645 return '{} for: "{}" file: "{}"'.format(msg, section_name,
646 file_name)
647
648 FSConfigFileParser._handle_dup_and_add('AID', file_name, section_name,
649 self._seen_aids[0])
650
651 match = FSConfigFileParser._AID_MATCH.match(section_name)
652 invalid = match.end() if match else len(AID.PREFIX)
653 if invalid != len(section_name):
654 tmp_errmsg = ('Invalid characters in AID section at "%d" for: "%s"'
655 % (invalid, FSConfigFileParser._AID_ERR_MSG))
656 sys.exit(error_message(tmp_errmsg))
657
658 value = config.get(section_name, 'value')
659
660 if not value:
661 sys.exit(error_message('Found specified but unset "value"'))
662
663 try:
Wei Wang77e329a2018-06-05 16:00:07 -0700664 aid = AID(section_name, value, file_name, '/vendor/bin/sh')
Tom Cherryee0610e2018-02-08 14:26:53 -0800665 except ValueError as exception:
666 sys.exit(error_message(exception))
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000667
668 # Values must be within OEM range
669 if not Utils.in_any_range(int(aid.value, 0), self._oem_ranges):
670 emsg = '"value" not in valid range %s, got: %s'
671 emsg = emsg % (str(self._oem_ranges), value)
672 sys.exit(error_message(emsg))
673
674 # use the normalized int value in the dict and detect
675 # duplicate definitions of the same value
676 FSConfigFileParser._handle_dup_and_add(
677 'AID', file_name, aid.normalized_value, self._seen_aids[1])
678
679 # Append aid tuple of (AID_*, base10(value), _path(value))
680 # We keep the _path version of value so we can print that out in the
681 # generated header so investigating parties can identify parts.
682 # We store the base10 value for sorting, so everything is ascending
683 # later.
684 self._aids.append(aid)
685
686 def _handle_path(self, file_name, section_name, config):
687 """Add a file capability entry to the internal list.
688
689 Handles a file capability entry, verifies it, and adds it to
690 to the internal dirs or files list based on path. If it ends
691 with a / its a dir. Internal use only.
692
693 Calls sys.exit() on any validation error with message set.
694
695 Args:
696 file_name (str): The current name of the file being parsed.
697 section_name (str): The name of the section to parse.
698 config (str): The config parser.
699 """
700
701 FSConfigFileParser._handle_dup_and_add('path', file_name, section_name,
702 self._seen_paths)
703
704 mode = config.get(section_name, 'mode')
705 user = config.get(section_name, 'user')
706 group = config.get(section_name, 'group')
707 caps = config.get(section_name, 'caps')
708
709 errmsg = ('Found specified but unset option: \"%s" in file: \"' +
710 file_name + '\"')
711
712 if not mode:
713 sys.exit(errmsg % 'mode')
714
715 if not user:
716 sys.exit(errmsg % 'user')
717
718 if not group:
719 sys.exit(errmsg % 'group')
720
721 if not caps:
722 sys.exit(errmsg % 'caps')
723
724 caps = caps.split()
725
726 tmp = []
727 for cap in caps:
728 try:
729 # test if string is int, if it is, use as is.
730 int(cap, 0)
731 tmp.append('(' + cap + ')')
732 except ValueError:
doheon1.lee5cd3bca2017-04-03 15:17:06 +0900733 tmp.append('CAP_MASK_LONG(CAP_' + cap.upper() + ')')
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000734
735 caps = tmp
736
737 if len(mode) == 3:
738 mode = '0' + mode
739
740 try:
741 int(mode, 8)
742 except ValueError:
743 sys.exit('Mode must be octal characters, got: "%s"' % mode)
744
745 if len(mode) != 4:
746 sys.exit('Mode must be 3 or 4 characters, got: "%s"' % mode)
747
748 caps_str = '|'.join(caps)
749
750 entry = FSConfig(mode, user, group, caps_str, section_name, file_name)
751 if section_name[-1] == '/':
752 self._dirs.append(entry)
753 else:
754 self._files.append(entry)
755
756 @property
757 def files(self):
758 """Get the list of FSConfig file entries.
759
760 Returns:
761 a list of FSConfig() objects for file paths.
762 """
763 return self._files
764
765 @property
766 def dirs(self):
767 """Get the list of FSConfig dir entries.
768
769 Returns:
770 a list of FSConfig() objects for directory paths.
771 """
772 return self._dirs
773
774 @property
775 def aids(self):
776 """Get the list of AID entries.
777
778 Returns:
779 a list of AID() objects.
780 """
781 return self._aids
782
783 @staticmethod
784 def _file_key(fs_config):
785 """Used as the key paramter to sort.
786
787 This is used as a the function to the key parameter of a sort.
788 it wraps the string supplied in a class that implements the
789 appropriate __lt__ operator for the sort on path strings. See
790 StringWrapper class for more details.
791
792 Args:
793 fs_config (FSConfig): A FSConfig entry.
794
795 Returns:
796 A StringWrapper object
797 """
798
799 # Wrapper class for custom prefix matching strings
800 class StringWrapper(object):
801 """Wrapper class used for sorting prefix strings.
802
803 The algorithm is as follows:
804 - specified path before prefix match
805 - ie foo before f*
806 - lexicographical less than before other
807 - ie boo before foo
808
809 Given these paths:
810 paths=['ac', 'a', 'acd', 'an', 'a*', 'aa', 'ac*']
811 The sort order would be:
812 paths=['a', 'aa', 'ac', 'acd', 'an', 'ac*', 'a*']
813 Thus the fs_config tools will match on specified paths before
814 attempting prefix, and match on the longest matching prefix.
815 """
816
817 def __init__(self, path):
818 """
819 Args:
820 path (str): the path string to wrap.
821 """
822 self.is_prefix = path[-1] == '*'
823 if self.is_prefix:
824 self.path = path[:-1]
825 else:
826 self.path = path
827
828 def __lt__(self, other):
829
830 # if were both suffixed the smallest string
831 # is 'bigger'
832 if self.is_prefix and other.is_prefix:
833 result = len(self.path) > len(other.path)
834 # If I am an the suffix match, im bigger
835 elif self.is_prefix:
836 result = False
837 # If other is the suffix match, he's bigger
838 elif other.is_prefix:
839 result = True
840 # Alphabetical
841 else:
842 result = self.path < other.path
843 return result
844
845 return StringWrapper(fs_config.path)
846
847 @staticmethod
848 def _handle_dup_and_add(name, file_name, section_name, seen):
849 """Tracks and detects duplicates. Internal use only.
850
851 Calls sys.exit() on a duplicate.
852
853 Args:
854 name (str): The name to use in the error reporting. The pretty
855 name for the section.
856 file_name (str): The file currently being parsed.
857 section_name (str): The name of the section. This would be path
858 or identifier depending on what's being parsed.
859 seen (dict): The dictionary of seen things to check against.
860 """
861 if section_name in seen:
862 dups = '"' + seen[section_name] + '" and '
863 dups += file_name
864 sys.exit('Duplicate %s "%s" found in files: %s' %
865 (name, section_name, dups))
866
867 seen[section_name] = file_name
868
869
870class BaseGenerator(object):
871 """Interface for Generators.
872
873 Base class for generators, generators should implement
874 these method stubs.
875 """
876
877 def add_opts(self, opt_group):
878 """Used to add per-generator options to the command line.
879
880 Args:
881 opt_group (argument group object): The argument group to append to.
882 See the ArgParse docs for more details.
883 """
884
885 raise NotImplementedError("Not Implemented")
886
887 def __call__(self, args):
888 """This is called to do whatever magic the generator does.
889
890 Args:
891 args (dict): The arguments from ArgParse as a dictionary.
892 ie if you specified an argument of foo in add_opts, access
893 it via args['foo']
894 """
895
896 raise NotImplementedError("Not Implemented")
897
898
899@generator('fsconfig')
900class FSConfigGen(BaseGenerator):
901 """Generates the android_filesystem_config.h file.
902
903 Output is used in generating fs_config_files and fs_config_dirs.
904 """
905
906 _GENERATED = textwrap.dedent("""\
907 /*
908 * THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY
909 */
910 """)
911
912 _INCLUDES = [
913 '<private/android_filesystem_config.h>', '"generated_oem_aid.h"'
914 ]
915
916 _DEFINE_NO_DIRS = '#define NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS'
917 _DEFINE_NO_FILES = '#define NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_FILES'
918
919 _DEFAULT_WARNING = (
920 '#warning No device-supplied android_filesystem_config.h,'
921 ' using empty default.')
922
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000923 _OPEN_FILE_STRUCT = (
924 'static const struct fs_path_config android_device_files[] = {')
925
926 _OPEN_DIR_STRUCT = (
927 'static const struct fs_path_config android_device_dirs[] = {')
928
929 _CLOSE_FILE_STRUCT = '};'
930
931 _GENERIC_DEFINE = "#define %s\t%s"
932
933 _FILE_COMMENT = '// Defined in file: \"%s\"'
934
935 def __init__(self, *args, **kwargs):
936 BaseGenerator.__init__(args, kwargs)
937
938 self._oem_parser = None
939 self._base_parser = None
940 self._friendly_to_aid = None
941
942 def add_opts(self, opt_group):
943
944 opt_group.add_argument(
945 'fsconfig', nargs='+', help='The list of fsconfig files to parse')
946
947 opt_group.add_argument(
948 '--aid-header',
949 required=True,
950 help='An android_filesystem_config.h file'
951 ' to parse AIDs and OEM Ranges from')
952
953 def __call__(self, args):
954
955 self._base_parser = AIDHeaderParser(args['aid_header'])
956 self._oem_parser = FSConfigFileParser(args['fsconfig'],
957 self._base_parser.oem_ranges)
958 base_aids = self._base_parser.aids
959 oem_aids = self._oem_parser.aids
960
961 # Detect name collisions on AIDs. Since friendly works as the
962 # identifier for collision testing and we need friendly later on for
963 # name resolution, just calculate and use friendly.
964 # {aid.friendly: aid for aid in base_aids}
965 base_friendly = {aid.friendly: aid for aid in base_aids}
966 oem_friendly = {aid.friendly: aid for aid in oem_aids}
967
968 base_set = set(base_friendly.keys())
969 oem_set = set(oem_friendly.keys())
970
971 common = base_set & oem_set
972
Tom Cherry766adc92019-02-13 14:24:52 -0800973 if common:
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000974 emsg = 'Following AID Collisions detected for: \n'
975 for friendly in common:
976 base = base_friendly[friendly]
977 oem = oem_friendly[friendly]
978 emsg += (
979 'Identifier: "%s" Friendly Name: "%s" '
980 'found in file "%s" and "%s"' %
981 (base.identifier, base.friendly, base.found, oem.found))
982 sys.exit(emsg)
983
984 self._friendly_to_aid = oem_friendly
985 self._friendly_to_aid.update(base_friendly)
986
987 self._generate()
988
989 def _to_fs_entry(self, fs_config):
990 """Converts an FSConfig entry to an fs entry.
991
992 Prints '{ mode, user, group, caps, "path" },'.
993
994 Calls sys.exit() on error.
995
996 Args:
997 fs_config (FSConfig): The entry to convert to
998 a valid C array entry.
999 """
1000
1001 # Get some short names
1002 mode = fs_config.mode
1003 user = fs_config.user
1004 group = fs_config.group
1005 fname = fs_config.filename
1006 caps = fs_config.caps
1007 path = fs_config.path
1008
1009 emsg = 'Cannot convert friendly name "%s" to identifier!'
1010
1011 # remap friendly names to identifier names
1012 if AID.is_friendly(user):
1013 if user not in self._friendly_to_aid:
1014 sys.exit(emsg % user)
1015 user = self._friendly_to_aid[user].identifier
1016
1017 if AID.is_friendly(group):
1018 if group not in self._friendly_to_aid:
1019 sys.exit(emsg % group)
1020 group = self._friendly_to_aid[group].identifier
1021
1022 fmt = '{ %s, %s, %s, %s, "%s" },'
1023
1024 expanded = fmt % (mode, user, group, caps, path)
1025
1026 print FSConfigGen._FILE_COMMENT % fname
1027 print ' ' + expanded
1028
1029 @staticmethod
1030 def _gen_inc():
1031 """Generate the include header lines and print to stdout."""
1032 for include in FSConfigGen._INCLUDES:
1033 print '#include %s' % include
1034
1035 def _generate(self):
1036 """Generates an OEM android_filesystem_config.h header file to stdout.
1037
1038 Args:
1039 files ([FSConfig]): A list of FSConfig objects for file entries.
1040 dirs ([FSConfig]): A list of FSConfig objects for directory
1041 entries.
1042 aids ([AIDS]): A list of AID objects for Android Id entries.
1043 """
1044 print FSConfigGen._GENERATED
William Robertscfc51f52016-04-12 08:51:13 -07001045 print
1046
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001047 FSConfigGen._gen_inc()
1048 print
William Robertsc950a352016-03-04 18:12:29 -08001049
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001050 dirs = self._oem_parser.dirs
1051 files = self._oem_parser.files
1052 aids = self._oem_parser.aids
William Roberts8f42ce72016-04-25 12:27:43 -07001053
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001054 are_dirs = len(dirs) > 0
1055 are_files = len(files) > 0
1056 are_aids = len(aids) > 0
William Robertsc950a352016-03-04 18:12:29 -08001057
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001058 if are_aids:
1059 for aid in aids:
1060 # use the preserved _path value
1061 print FSConfigGen._FILE_COMMENT % aid.found
1062 print FSConfigGen._GENERIC_DEFINE % (aid.identifier, aid.value)
1063
1064 print
William Robertsc950a352016-03-04 18:12:29 -08001065
1066 if not are_dirs:
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001067 print FSConfigGen._DEFINE_NO_DIRS + '\n'
William Robertsc950a352016-03-04 18:12:29 -08001068
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001069 if not are_files:
1070 print FSConfigGen._DEFINE_NO_FILES + '\n'
William Robertsc950a352016-03-04 18:12:29 -08001071
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001072 if not are_files and not are_dirs and not are_aids:
1073 return
William Robertsc950a352016-03-04 18:12:29 -08001074
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001075 if are_files:
1076 print FSConfigGen._OPEN_FILE_STRUCT
1077 for fs_config in files:
1078 self._to_fs_entry(fs_config)
William Robertsc950a352016-03-04 18:12:29 -08001079
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001080 print FSConfigGen._CLOSE_FILE_STRUCT
William Robertsc950a352016-03-04 18:12:29 -08001081
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001082 if are_dirs:
1083 print FSConfigGen._OPEN_DIR_STRUCT
1084 for dir_entry in dirs:
1085 self._to_fs_entry(dir_entry)
William Robertsc950a352016-03-04 18:12:29 -08001086
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001087 print FSConfigGen._CLOSE_FILE_STRUCT
William Robertsc950a352016-03-04 18:12:29 -08001088
William Robertsc950a352016-03-04 18:12:29 -08001089
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001090@generator('aidarray')
1091class AIDArrayGen(BaseGenerator):
1092 """Generates the android_id static array."""
1093
1094 _GENERATED = ('/*\n'
1095 ' * THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY!\n'
1096 ' */')
1097
1098 _INCLUDE = '#include <private/android_filesystem_config.h>'
1099
Vic Yang5b3a7c02018-12-03 21:40:33 -08001100 # Note that the android_id name field is of type 'const char[]' instead of
1101 # 'const char*'. While this seems less straightforward as we need to
1102 # calculate the max length of all names, this allows the entire android_ids
1103 # table to be placed in .rodata section instead of .data.rel.ro section,
1104 # resulting in less memory pressure.
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001105 _STRUCT_FS_CONFIG = textwrap.dedent("""
1106 struct android_id_info {
Vic Yang5b3a7c02018-12-03 21:40:33 -08001107 const char name[%d];
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001108 unsigned aid;
1109 };""")
1110
1111 _OPEN_ID_ARRAY = 'static const struct android_id_info android_ids[] = {'
1112
1113 _ID_ENTRY = ' { "%s", %s },'
1114
1115 _CLOSE_FILE_STRUCT = '};'
1116
1117 _COUNT = ('#define android_id_count \\\n'
1118 ' (sizeof(android_ids) / sizeof(android_ids[0]))')
1119
1120 def add_opts(self, opt_group):
1121
1122 opt_group.add_argument(
1123 'hdrfile', help='The android_filesystem_config.h'
1124 'file to parse')
1125
1126 def __call__(self, args):
1127
1128 hdr = AIDHeaderParser(args['hdrfile'])
Vic Yang5b3a7c02018-12-03 21:40:33 -08001129 max_name_length = max(len(aid.friendly) + 1 for aid in hdr.aids)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001130
1131 print AIDArrayGen._GENERATED
1132 print
1133 print AIDArrayGen._INCLUDE
1134 print
Vic Yang5b3a7c02018-12-03 21:40:33 -08001135 print AIDArrayGen._STRUCT_FS_CONFIG % max_name_length
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001136 print
1137 print AIDArrayGen._OPEN_ID_ARRAY
1138
1139 for aid in hdr.aids:
1140 print AIDArrayGen._ID_ENTRY % (aid.friendly, aid.identifier)
1141
1142 print AIDArrayGen._CLOSE_FILE_STRUCT
1143 print
1144 print AIDArrayGen._COUNT
1145 print
1146
1147
1148@generator('oemaid')
1149class OEMAidGen(BaseGenerator):
1150 """Generates the OEM AID_<name> value header file."""
1151
1152 _GENERATED = ('/*\n'
1153 ' * THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY!\n'
1154 ' */')
1155
1156 _GENERIC_DEFINE = "#define %s\t%s"
1157
1158 _FILE_COMMENT = '// Defined in file: \"%s\"'
1159
1160 # Intentional trailing newline for readability.
1161 _FILE_IFNDEF_DEFINE = ('#ifndef GENERATED_OEM_AIDS_H_\n'
1162 '#define GENERATED_OEM_AIDS_H_\n')
1163
1164 _FILE_ENDIF = '#endif'
1165
1166 def __init__(self):
1167
1168 self._old_file = None
1169
1170 def add_opts(self, opt_group):
1171
1172 opt_group.add_argument(
1173 'fsconfig', nargs='+', help='The list of fsconfig files to parse.')
1174
1175 opt_group.add_argument(
1176 '--aid-header',
1177 required=True,
1178 help='An android_filesystem_config.h file'
1179 'to parse AIDs and OEM Ranges from')
1180
1181 def __call__(self, args):
1182
1183 hdr_parser = AIDHeaderParser(args['aid_header'])
1184
1185 parser = FSConfigFileParser(args['fsconfig'], hdr_parser.oem_ranges)
1186
1187 print OEMAidGen._GENERATED
1188
1189 print OEMAidGen._FILE_IFNDEF_DEFINE
1190
1191 for aid in parser.aids:
1192 self._print_aid(aid)
1193 print
1194
1195 print OEMAidGen._FILE_ENDIF
1196
1197 def _print_aid(self, aid):
1198 """Prints a valid #define AID identifier to stdout.
1199
1200 Args:
1201 aid to print
1202 """
1203
1204 # print the source file location of the AID
1205 found_file = aid.found
1206 if found_file != self._old_file:
1207 print OEMAidGen._FILE_COMMENT % found_file
1208 self._old_file = found_file
1209
1210 print OEMAidGen._GENERIC_DEFINE % (aid.identifier, aid.value)
1211
1212
1213@generator('passwd')
1214class PasswdGen(BaseGenerator):
1215 """Generates the /etc/passwd file per man (5) passwd."""
1216
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001217 def __init__(self):
1218
1219 self._old_file = None
1220
1221 def add_opts(self, opt_group):
1222
1223 opt_group.add_argument(
1224 'fsconfig', nargs='+', help='The list of fsconfig files to parse.')
1225
1226 opt_group.add_argument(
1227 '--aid-header',
1228 required=True,
1229 help='An android_filesystem_config.h file'
1230 'to parse AIDs and OEM Ranges from')
1231
Tom Cherry2d197a12018-05-14 13:14:41 -07001232 opt_group.add_argument(
1233 '--required-prefix',
1234 required=False,
1235 help='A prefix that the names are required to contain.')
1236
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001237 def __call__(self, args):
1238
1239 hdr_parser = AIDHeaderParser(args['aid_header'])
1240
1241 parser = FSConfigFileParser(args['fsconfig'], hdr_parser.oem_ranges)
1242
Tom Cherry2d197a12018-05-14 13:14:41 -07001243 required_prefix = args['required_prefix']
1244
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001245 aids = parser.aids
1246
1247 # nothing to do if no aids defined
Tom Cherry766adc92019-02-13 14:24:52 -08001248 if not aids:
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001249 return
1250
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001251 for aid in aids:
Tom Cherry766adc92019-02-13 14:24:52 -08001252 if required_prefix is None or aid.friendly.startswith(
1253 required_prefix):
Tom Cherry2d197a12018-05-14 13:14:41 -07001254 self._print_formatted_line(aid)
1255 else:
1256 sys.exit("%s: AID '%s' must start with '%s'" %
1257 (args['fsconfig'], aid.friendly, required_prefix))
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001258
1259 def _print_formatted_line(self, aid):
1260 """Prints the aid to stdout in the passwd format. Internal use only.
1261
1262 Colon delimited:
1263 login name, friendly name
1264 encrypted password (optional)
1265 uid (int)
1266 gid (int)
1267 User name or comment field
1268 home directory
1269 interpreter (optional)
1270
1271 Args:
1272 aid (AID): The aid to print.
1273 """
1274 if self._old_file != aid.found:
1275 self._old_file = aid.found
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001276
1277 try:
1278 logon, uid = Utils.get_login_and_uid_cleansed(aid)
1279 except ValueError as exception:
1280 sys.exit(exception)
1281
Wei Wang77e329a2018-06-05 16:00:07 -07001282 print "%s::%s:%s::/:%s" % (logon, uid, uid, aid.login_shell)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001283
1284
1285@generator('group')
1286class GroupGen(PasswdGen):
1287 """Generates the /etc/group file per man (5) group."""
1288
1289 # Overrides parent
1290 def _print_formatted_line(self, aid):
1291 """Prints the aid to stdout in the group format. Internal use only.
1292
1293 Formatted (per man 5 group) like:
1294 group_name:password:GID:user_list
1295
1296 Args:
1297 aid (AID): The aid to print.
1298 """
1299 if self._old_file != aid.found:
1300 self._old_file = aid.found
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001301
1302 try:
1303 logon, uid = Utils.get_login_and_uid_cleansed(aid)
1304 except ValueError as exception:
1305 sys.exit(exception)
1306
1307 print "%s::%s:" % (logon, uid)
1308
Tom Cherry766adc92019-02-13 14:24:52 -08001309
Joe Onorato2afb6eb2018-12-04 14:51:58 -08001310@generator('print')
1311class PrintGen(BaseGenerator):
1312 """Prints just the constants and values, separated by spaces, in an easy to
1313 parse format for use by other scripts.
1314
1315 Each line is just the identifier and the value, separated by a space.
1316 """
1317
1318 def add_opts(self, opt_group):
1319 opt_group.add_argument(
1320 'aid-header', help='An android_filesystem_config.h file.')
1321
1322 def __call__(self, args):
1323
1324 hdr_parser = AIDHeaderParser(args['aid-header'])
1325 aids = hdr_parser.aids
1326
1327 aids.sort(key=lambda item: int(item.normalized_value))
1328
1329 for aid in aids:
1330 print '%s %s' % (aid.identifier, aid.normalized_value)
1331
William Roberts1c4721c2016-04-26 13:05:34 -07001332
William Robertsc950a352016-03-04 18:12:29 -08001333def main():
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001334 """Main entry point for execution."""
William Robertsc950a352016-03-04 18:12:29 -08001335
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001336 opt_parser = argparse.ArgumentParser(
1337 description='A tool for parsing fsconfig config files and producing' +
1338 'digestable outputs.')
1339 subparser = opt_parser.add_subparsers(help='generators')
William Robertsc950a352016-03-04 18:12:29 -08001340
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001341 gens = generator.get()
William Robertsc950a352016-03-04 18:12:29 -08001342
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001343 # for each gen, instantiate and add them as an option
1344 for name, gen in gens.iteritems():
William Robertsc950a352016-03-04 18:12:29 -08001345
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001346 generator_option_parser = subparser.add_parser(name, help=gen.__doc__)
1347 generator_option_parser.set_defaults(which=name)
William Roberts8cb6a182016-04-08 22:06:19 -07001348
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001349 opt_group = generator_option_parser.add_argument_group(name +
1350 ' options')
1351 gen.add_opts(opt_group)
William Roberts8cb6a182016-04-08 22:06:19 -07001352
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001353 args = opt_parser.parse_args()
1354
1355 args_as_dict = vars(args)
1356 which = args_as_dict['which']
1357 del args_as_dict['which']
1358
1359 gens[which](args_as_dict)
1360
William Robertsc950a352016-03-04 18:12:29 -08001361
1362if __name__ == '__main__':
1363 main()