blob: b9f0d533d88596532b507164fc28720084b431d1 [file] [log] [blame]
William Robertsc950a352016-03-04 18:12:29 -08001#!/usr/bin/env python
William Roberts11c29282016-04-09 10:32:30 -07002"""Generates config files for Android file system properties.
William Robertsc950a352016-03-04 18:12:29 -08003
William Roberts11c29282016-04-09 10:32:30 -07004This 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
William Roberts11c29282016-04-09 10:32:30 -070017import textwrap
William Robertsc950a352016-03-04 18:12:29 -080018
William Robertsd7104bc2016-04-11 21:17:12 -070019# Keep the tool in one file to make it easy to run.
20# pylint: disable=too-many-lines
21
William Robertsc950a352016-03-04 18:12:29 -080022
William Roberts11c29282016-04-09 10:32:30 -070023# 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
William Roberts11c29282016-04-09 10:32:30 -070027 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
William Roberts11c29282016-04-09 10:32:30 -070032 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
William Roberts11c29282016-04-09 10:32:30 -070035 @generator("foo")
36 class FooGen(object):
37 ...
38 """
39 _generators = {}
William Robertsc950a352016-03-04 18:12:29 -080040
William Roberts11c29282016-04-09 10:32:30 -070041 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
William Roberts11c29282016-04-09 10:32:30 -070046 Raises:
47 ValueError: If there is a similarly named generator already added.
William Robertsc950a352016-03-04 18:12:29 -080048
William Roberts11c29282016-04-09 10:32:30 -070049 """
50 self._gen = gen
William Robertsc950a352016-03-04 18:12:29 -080051
William Roberts11c29282016-04-09 10:32:30 -070052 if gen in generator._generators:
53 raise ValueError('Duplicate generator name: ' + gen)
William Robertsc950a352016-03-04 18:12:29 -080054
William Roberts11c29282016-04-09 10:32:30 -070055 generator._generators[gen] = None
William Robertsc950a352016-03-04 18:12:29 -080056
William Roberts11c29282016-04-09 10:32:30 -070057 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
William Roberts64edf5b2016-04-11 17:12:47 -070072class Utils(object):
73 """Various assorted static utilities."""
74
75 @staticmethod
76 def in_any_range(value, ranges):
77 """Tests if a value is in a list of given closed range tuples.
78
79 A range tuple is a closed range. That means it's inclusive of its
80 start and ending values.
81
82 Args:
83 value (int): The value to test.
84 range [(int, int)]: The closed range list to test value within.
85
86 Returns:
87 True if value is within the closed range, false otherwise.
88 """
89
90 return any(lower <= value <= upper for (lower, upper) in ranges)
91
92
William Roberts11c29282016-04-09 10:32:30 -070093class AID(object):
94 """This class represents an Android ID or an AID.
William Robertsc950a352016-03-04 18:12:29 -080095
William Roberts11c29282016-04-09 10:32:30 -070096 Attributes:
97 identifier (str): The identifier name for a #define.
98 value (str) The User Id (uid) of the associate define.
99 found (str) The file it was found in, can be None.
100 normalized_value (str): Same as value, but base 10.
William Roberts8f42ce72016-04-25 12:27:43 -0700101 friendly (str): The friendly name of aid.
William Roberts11c29282016-04-09 10:32:30 -0700102 """
William Robertsc950a352016-03-04 18:12:29 -0800103
William Roberts8f42ce72016-04-25 12:27:43 -0700104 PREFIX = 'AID_'
105
106 # Some of the AIDS like AID_MEDIA_EX had names like mediaex
107 # list a map of things to fixup until we can correct these
108 # at a later date.
109 _FIXUPS = {
110 'media_drm': 'mediadrm',
111 'media_ex': 'mediaex',
112 'media_codec': 'mediacodec'
113 }
114
William Roberts11c29282016-04-09 10:32:30 -0700115 def __init__(self, identifier, value, found):
116 """
117 Args:
118 identifier: The identifier name for a #define <identifier>.
119 value: The value of the AID, aka the uid.
120 found (str): The file found in, not required to be specified.
William Robertsc950a352016-03-04 18:12:29 -0800121
William Roberts11c29282016-04-09 10:32:30 -0700122 Raises:
123 ValueError: if value is not a valid string number as processed by
124 int(x, 0)
125 """
126 self.identifier = identifier
127 self.value = value
128 self.found = found
129 self.normalized_value = str(int(value, 0))
William Robertsc950a352016-03-04 18:12:29 -0800130
William Roberts8f42ce72016-04-25 12:27:43 -0700131 # Where we calculate the friendly name
132 friendly = identifier[len(AID.PREFIX):].lower()
133 self.friendly = AID._fixup_friendly(friendly)
134
135 @staticmethod
136 def is_friendly(name):
137 """Determines if an AID is a freindly name or C define.
138
139 For example if name is AID_SYSTEM it returns false, if name
140 was system, it would return true.
141
142 Returns:
143 True if name is a friendly name False otherwise.
144 """
145
146 return not name.startswith(AID.PREFIX)
147
148 @staticmethod
149 def _fixup_friendly(friendly):
150 """Fixup friendly names that historically don't follow the convention.
151
152 Args:
153 friendly (str): The friendly name.
154
155 Returns:
156 The fixedup friendly name as a str.
157 """
158
159 if friendly in AID._FIXUPS:
160 return AID._FIXUPS[friendly]
161
162 return friendly
163
William Robertsc950a352016-03-04 18:12:29 -0800164
William Roberts11c29282016-04-09 10:32:30 -0700165class FSConfig(object):
166 """Represents a filesystem config array entry.
William Robertsc950a352016-03-04 18:12:29 -0800167
William Roberts11c29282016-04-09 10:32:30 -0700168 Represents a file system configuration entry for specifying
169 file system capabilities.
William Robertsc950a352016-03-04 18:12:29 -0800170
William Roberts11c29282016-04-09 10:32:30 -0700171 Attributes:
172 mode (str): The mode of the file or directory.
173 user (str): The uid or #define identifier (AID_SYSTEM)
174 group (str): The gid or #define identifier (AID_SYSTEM)
175 caps (str): The capability set.
176 filename (str): The file it was found in.
177 """
William Robertsc950a352016-03-04 18:12:29 -0800178
William Roberts11c29282016-04-09 10:32:30 -0700179 def __init__(self, mode, user, group, caps, path, filename):
180 """
181 Args:
182 mode (str): The mode of the file or directory.
183 user (str): The uid or #define identifier (AID_SYSTEM)
184 group (str): The gid or #define identifier (AID_SYSTEM)
185 caps (str): The capability set as a list.
186 filename (str): The file it was found in.
187 """
188 self.mode = mode
189 self.user = user
190 self.group = group
191 self.caps = caps
192 self.path = path
193 self.filename = filename
194
195
William Roberts64edf5b2016-04-11 17:12:47 -0700196class AIDHeaderParser(object):
197 """Parses an android_filesystem_config.h file.
198
199 Parses a C header file and extracts lines starting with #define AID_<name>
200 It provides some basic sanity checks. The information extracted from this
201 file can later be used to sanity check other things (like oem ranges) as
202 well as generating a mapping of names to uids. It was primarily designed to
203 parse the private/android_filesystem_config.h, but any C header should
204 work.
205 """
206
207 _SKIPWORDS = ['UNUSED']
William Roberts8f42ce72016-04-25 12:27:43 -0700208 _AID_DEFINE = re.compile(r'\s*#define\s+%s.*' % AID.PREFIX)
William Roberts64edf5b2016-04-11 17:12:47 -0700209 _OEM_START_KW = 'START'
210 _OEM_END_KW = 'END'
211 _OEM_RANGE = re.compile('AID_OEM_RESERVED_[0-9]*_{0,1}(%s|%s)' %
212 (_OEM_START_KW, _OEM_END_KW))
213
William Roberts64edf5b2016-04-11 17:12:47 -0700214 def __init__(self, aid_header):
215 """
216 Args:
217 aid_header (str): file name for the header
218 file containing AID entries.
219 """
220 self._aid_header = aid_header
221 self._aid_name_to_value = {}
222 self._aid_value_to_name = {}
223 self._oem_ranges = {}
224
225 with open(aid_header) as open_file:
226 self._parse(open_file)
227
228 try:
229 self._process_and_check()
230 except ValueError as exception:
231 sys.exit('Error processing parsed data: "%s"' % (str(exception)))
232
233 def _parse(self, aid_file):
234 """Parses an AID header file. Internal use only.
235
236 Args:
237 aid_file (file): The open AID header file to parse.
238 """
239
240 for lineno, line in enumerate(aid_file):
241 def error_message(msg):
242 """Creates an error message with the current parsing state."""
243 return 'Error "{}" in file: "{}" on line: {}'.format(
244 msg, self._aid_header, str(lineno))
245
246 if AIDHeaderParser._AID_DEFINE.match(line):
247 chunks = line.split()
248
249 if any(x in chunks[1] for x in AIDHeaderParser._SKIPWORDS):
250 continue
251
252 identifier = chunks[1]
253 value = chunks[2]
254
255 try:
256 if AIDHeaderParser._is_oem_range(identifier):
257 self._handle_oem_range(identifier, value)
258 else:
259 self._handle_aid(identifier, value)
260 except ValueError as exception:
261 sys.exit(error_message(
262 '{} for "{}"'.format(exception, identifier)))
263
264 def _handle_aid(self, identifier, value):
265 """Handle an AID C #define.
266
267 Handles an AID, sanity checking, generating the friendly name and
268 adding it to the internal maps. Internal use only.
269
270 Args:
271 identifier (str): The name of the #define identifier. ie AID_FOO.
272 value (str): The value associated with the identifier.
273
274 Raises:
275 ValueError: With message set to indicate the error.
276 """
277
William Roberts8f42ce72016-04-25 12:27:43 -0700278 aid = AID(identifier, value, self._aid_header)
William Roberts64edf5b2016-04-11 17:12:47 -0700279
280 # duplicate name
William Roberts8f42ce72016-04-25 12:27:43 -0700281 if aid.friendly in self._aid_name_to_value:
William Roberts64edf5b2016-04-11 17:12:47 -0700282 raise ValueError('Duplicate aid "%s"' % identifier)
283
284 if value in self._aid_value_to_name:
285 raise ValueError('Duplicate aid value "%u" for %s' % value,
286 identifier)
287
William Roberts8f42ce72016-04-25 12:27:43 -0700288 self._aid_name_to_value[aid.friendly] = aid
289 self._aid_value_to_name[value] = aid.friendly
William Roberts64edf5b2016-04-11 17:12:47 -0700290
291 def _handle_oem_range(self, identifier, value):
292 """Handle an OEM range C #define.
293
294 When encountering special AID defines, notably for the OEM ranges
295 this method handles sanity checking and adding them to the internal
296 maps. For internal use only.
297
298 Args:
299 identifier (str): The name of the #define identifier.
300 ie AID_OEM_RESERVED_START/END.
301 value (str): The value associated with the identifier.
302
303 Raises:
304 ValueError: With message set to indicate the error.
305 """
306
307 try:
308 int_value = int(value, 0)
309 except ValueError:
310 raise ValueError(
311 'Could not convert "%s" to integer value, got: "%s"' %
312 (identifier, value))
313
314 # convert AID_OEM_RESERVED_START or AID_OEM_RESERVED_<num>_START
315 # to AID_OEM_RESERVED or AID_OEM_RESERVED_<num>
316 is_start = identifier.endswith(AIDHeaderParser._OEM_START_KW)
317
318 if is_start:
319 tostrip = len(AIDHeaderParser._OEM_START_KW)
320 else:
321 tostrip = len(AIDHeaderParser._OEM_END_KW)
322
323 # ending _
324 tostrip = tostrip + 1
325
326 strip = identifier[:-tostrip]
327 if strip not in self._oem_ranges:
328 self._oem_ranges[strip] = []
329
330 if len(self._oem_ranges[strip]) > 2:
331 raise ValueError('Too many same OEM Ranges "%s"' % identifier)
332
333 if len(self._oem_ranges[strip]) == 1:
334 tmp = self._oem_ranges[strip][0]
335
336 if tmp == int_value:
337 raise ValueError('START and END values equal %u' % int_value)
338 elif is_start and tmp < int_value:
339 raise ValueError('END value %u less than START value %u' %
340 (tmp, int_value))
341 elif not is_start and tmp > int_value:
342 raise ValueError('END value %u less than START value %u' %
343 (int_value, tmp))
344
345 # Add START values to the head of the list and END values at the end.
346 # Thus, the list is ordered with index 0 as START and index 1 as END.
347 if is_start:
348 self._oem_ranges[strip].insert(0, int_value)
349 else:
350 self._oem_ranges[strip].append(int_value)
351
352 def _process_and_check(self):
353 """Process, check and populate internal data structures.
354
355 After parsing and generating the internal data structures, this method
356 is responsible for sanity checking ALL of the acquired data.
357
358 Raises:
359 ValueError: With the message set to indicate the specific error.
360 """
361
362 # tuplefy the lists since range() does not like them mutable.
363 self._oem_ranges = [
364 AIDHeaderParser._convert_lst_to_tup(k, v)
365 for k, v in self._oem_ranges.iteritems()
366 ]
367
368 # Check for overlapping ranges
369 for i, range1 in enumerate(self._oem_ranges):
370 for range2 in self._oem_ranges[i + 1:]:
371 if AIDHeaderParser._is_overlap(range1, range2):
372 raise ValueError("Overlapping OEM Ranges found %s and %s" %
373 (str(range1), str(range2)))
374
375 # No core AIDs should be within any oem range.
376 for aid in self._aid_value_to_name:
377
378 if Utils.in_any_range(aid, self._oem_ranges):
379 name = self._aid_value_to_name[aid]
380 raise ValueError(
381 'AID "%s" value: %u within reserved OEM Range: "%s"' %
382 (name, aid, str(self._oem_ranges)))
383
384 @property
385 def oem_ranges(self):
386 """Retrieves the OEM closed ranges as a list of tuples.
387
388 Returns:
389 A list of closed range tuples: [ (0, 42), (50, 105) ... ]
390 """
391 return self._oem_ranges
392
393 @property
394 def aids(self):
395 """Retrieves the list of found AIDs.
396
397 Returns:
398 A list of AID() objects.
399 """
William Roberts8f42ce72016-04-25 12:27:43 -0700400 return self._aid_name_to_value.values()
William Roberts64edf5b2016-04-11 17:12:47 -0700401
402 @staticmethod
403 def _convert_lst_to_tup(name, lst):
404 """Converts a mutable list to a non-mutable tuple.
405
406 Used ONLY for ranges and thus enforces a length of 2.
407
408 Args:
409 lst (List): list that should be "tuplefied".
410
411 Raises:
412 ValueError if lst is not a list or len is not 2.
413
414 Returns:
415 Tuple(lst)
416 """
417 if not lst or len(lst) != 2:
418 raise ValueError('Mismatched range for "%s"' % name)
419
420 return tuple(lst)
421
422 @staticmethod
William Roberts64edf5b2016-04-11 17:12:47 -0700423 def _is_oem_range(aid):
424 """Detects if a given aid is within the reserved OEM range.
425
426 Args:
427 aid (int): The aid to test
428
429 Returns:
430 True if it is within the range, False otherwise.
431 """
432
433 return AIDHeaderParser._OEM_RANGE.match(aid)
434
435 @staticmethod
436 def _is_overlap(range_a, range_b):
437 """Calculates the overlap of two range tuples.
438
439 A range tuple is a closed range. A closed range includes its endpoints.
440 Note that python tuples use () notation which collides with the
441 mathematical notation for open ranges.
442
443 Args:
444 range_a: The first tuple closed range eg (0, 5).
445 range_b: The second tuple closed range eg (3, 7).
446
447 Returns:
448 True if they overlap, False otherwise.
449 """
450
451 return max(range_a[0], range_b[0]) <= min(range_a[1], range_b[1])
452
453
William Roberts11c29282016-04-09 10:32:30 -0700454class FSConfigFileParser(object):
455 """Parses a config.fs ini format file.
456
457 This class is responsible for parsing the config.fs ini format files.
458 It collects and checks all the data in these files and makes it available
459 for consumption post processed.
460 """
William Roberts11c29282016-04-09 10:32:30 -0700461
William Roberts5f059a72016-04-25 10:36:45 -0700462 # These _AID vars work together to ensure that an AID section name
463 # cannot contain invalid characters for a C define or a passwd/group file.
464 # Since _AID_PREFIX is within the set of _AID_MATCH the error logic only
465 # checks end, if you change this, you may have to update the error
466 # detection code.
William Roberts8f42ce72016-04-25 12:27:43 -0700467 _AID_MATCH = re.compile('%s[A-Z0-9_]+' % AID.PREFIX)
William Roberts5f059a72016-04-25 10:36:45 -0700468 _AID_ERR_MSG = 'Expecting upper case, a number or underscore'
469
470 # list of handler to required options, used to identify the
471 # parsing section
472 _SECTIONS = [('_handle_aid', ('value',)),
473 ('_handle_path', ('mode', 'user', 'group', 'caps'))]
William Roberts11c29282016-04-09 10:32:30 -0700474
William Roberts64edf5b2016-04-11 17:12:47 -0700475 def __init__(self, config_files, oem_ranges):
William Roberts11c29282016-04-09 10:32:30 -0700476 """
477 Args:
478 config_files ([str]): The list of config.fs files to parse.
479 Note the filename is not important.
William Roberts64edf5b2016-04-11 17:12:47 -0700480 oem_ranges ([(),()]): range tuples indicating reserved OEM ranges.
William Roberts11c29282016-04-09 10:32:30 -0700481 """
482
483 self._files = []
484 self._dirs = []
485 self._aids = []
486
487 self._seen_paths = {}
488 # (name to file, value to aid)
489 self._seen_aids = ({}, {})
490
William Roberts64edf5b2016-04-11 17:12:47 -0700491 self._oem_ranges = oem_ranges
492
William Roberts11c29282016-04-09 10:32:30 -0700493 self._config_files = config_files
494
495 for config_file in self._config_files:
496 self._parse(config_file)
497
498 def _parse(self, file_name):
499 """Parses and verifies config.fs files. Internal use only.
500
501 Args:
502 file_name (str): The config.fs (PythonConfigParser file format)
503 file to parse.
504
505 Raises:
506 Anything raised by ConfigParser.read()
507 """
508
509 # Separate config parsers for each file found. If you use
510 # read(filenames...) later files can override earlier files which is
511 # not what we want. Track state across files and enforce with
512 # _handle_dup(). Note, strict ConfigParser is set to true in
513 # Python >= 3.2, so in previous versions same file sections can
514 # override previous
515 # sections.
William Robertsc950a352016-03-04 18:12:29 -0800516
517 config = ConfigParser.ConfigParser()
518 config.read(file_name)
519
William Roberts11c29282016-04-09 10:32:30 -0700520 for section in config.sections():
William Robertsc950a352016-03-04 18:12:29 -0800521
William Roberts5f059a72016-04-25 10:36:45 -0700522 found = False
523
524 for test in FSConfigFileParser._SECTIONS:
525 handler = test[0]
526 options = test[1]
527
528 if all([config.has_option(section, item) for item in options]):
529 handler = getattr(self, handler)
530 handler(file_name, section, config)
531 found = True
532 break
533
534 if not found:
535 sys.exit('Invalid section "%s" in file: "%s"' %
536 (section, file_name))
William Robertsc950a352016-03-04 18:12:29 -0800537
William Roberts11c29282016-04-09 10:32:30 -0700538 # sort entries:
539 # * specified path before prefix match
540 # ** ie foo before f*
541 # * lexicographical less than before other
542 # ** ie boo before foo
543 # Given these paths:
544 # paths=['ac', 'a', 'acd', 'an', 'a*', 'aa', 'ac*']
545 # The sort order would be:
546 # paths=['a', 'aa', 'ac', 'acd', 'an', 'ac*', 'a*']
547 # Thus the fs_config tools will match on specified paths before
548 # attempting prefix, and match on the longest matching prefix.
549 self._files.sort(key=FSConfigFileParser._file_key)
William Robertsc950a352016-03-04 18:12:29 -0800550
William Roberts11c29282016-04-09 10:32:30 -0700551 # sort on value of (file_name, name, value, strvalue)
552 # This is only cosmetic so AIDS are arranged in ascending order
553 # within the generated file.
554 self._aids.sort(key=lambda item: item.normalized_value)
William Robertsc950a352016-03-04 18:12:29 -0800555
William Roberts11c29282016-04-09 10:32:30 -0700556 def _handle_aid(self, file_name, section_name, config):
557 """Verifies an AID entry and adds it to the aid list.
William Robertsc950a352016-03-04 18:12:29 -0800558
William Roberts11c29282016-04-09 10:32:30 -0700559 Calls sys.exit() with a descriptive message of the failure.
560
561 Args:
562 file_name (str): The filename of the config file being parsed.
563 section_name (str): The section name currently being parsed.
564 config (ConfigParser): The ConfigParser section being parsed that
565 the option values will come from.
566 """
567
568 def error_message(msg):
569 """Creates an error message with current parsing state."""
570 return '{} for: "{}" file: "{}"'.format(msg, section_name,
571 file_name)
572
William Roberts5f059a72016-04-25 10:36:45 -0700573 FSConfigFileParser._handle_dup_and_add('AID', file_name, section_name,
574 self._seen_aids[0])
575
576 match = FSConfigFileParser._AID_MATCH.match(section_name)
William Roberts8f42ce72016-04-25 12:27:43 -0700577 invalid = match.end() if match else len(AID.PREFIX)
William Roberts5f059a72016-04-25 10:36:45 -0700578 if invalid != len(section_name):
579 tmp_errmsg = ('Invalid characters in AID section at "%d" for: "%s"'
580 % (invalid, FSConfigFileParser._AID_ERR_MSG))
581 sys.exit(error_message(tmp_errmsg))
582
William Roberts11c29282016-04-09 10:32:30 -0700583 value = config.get(section_name, 'value')
584
585 if not value:
586 sys.exit(error_message('Found specified but unset "value"'))
587
588 try:
589 aid = AID(section_name, value, file_name)
590 except ValueError:
591 sys.exit(
592 error_message('Invalid "value", not aid number, got: \"%s\"' %
593 value))
594
William Roberts64edf5b2016-04-11 17:12:47 -0700595 # Values must be within OEM range
596 if not Utils.in_any_range(int(aid.value, 0), self._oem_ranges):
William Roberts11c29282016-04-09 10:32:30 -0700597 emsg = '"value" not in valid range %s, got: %s'
William Roberts64edf5b2016-04-11 17:12:47 -0700598 emsg = emsg % (str(self._oem_ranges), value)
William Roberts11c29282016-04-09 10:32:30 -0700599 sys.exit(error_message(emsg))
600
601 # use the normalized int value in the dict and detect
602 # duplicate definitions of the same value
William Roberts5f059a72016-04-25 10:36:45 -0700603 FSConfigFileParser._handle_dup_and_add(
604 'AID', file_name, aid.normalized_value, self._seen_aids[1])
William Roberts11c29282016-04-09 10:32:30 -0700605
606 # Append aid tuple of (AID_*, base10(value), _path(value))
607 # We keep the _path version of value so we can print that out in the
608 # generated header so investigating parties can identify parts.
609 # We store the base10 value for sorting, so everything is ascending
610 # later.
611 self._aids.append(aid)
612
613 def _handle_path(self, file_name, section_name, config):
614 """Add a file capability entry to the internal list.
615
616 Handles a file capability entry, verifies it, and adds it to
617 to the internal dirs or files list based on path. If it ends
618 with a / its a dir. Internal use only.
619
620 Calls sys.exit() on any validation error with message set.
621
622 Args:
623 file_name (str): The current name of the file being parsed.
624 section_name (str): The name of the section to parse.
625 config (str): The config parser.
626 """
627
William Roberts5f059a72016-04-25 10:36:45 -0700628 FSConfigFileParser._handle_dup_and_add('path', file_name, section_name,
629 self._seen_paths)
630
William Roberts11c29282016-04-09 10:32:30 -0700631 mode = config.get(section_name, 'mode')
632 user = config.get(section_name, 'user')
633 group = config.get(section_name, 'group')
634 caps = config.get(section_name, 'caps')
635
636 errmsg = ('Found specified but unset option: \"%s" in file: \"' +
637 file_name + '\"')
638
639 if not mode:
640 sys.exit(errmsg % 'mode')
641
642 if not user:
643 sys.exit(errmsg % 'user')
644
645 if not group:
646 sys.exit(errmsg % 'group')
647
648 if not caps:
649 sys.exit(errmsg % 'caps')
650
651 caps = caps.split()
652
653 tmp = []
654 for cap in caps:
655 try:
656 # test if string is int, if it is, use as is.
657 int(cap, 0)
658 tmp.append('(' + cap + ')')
659 except ValueError:
660 tmp.append('(1ULL << CAP_' + cap.upper() + ')')
661
662 caps = tmp
663
664 if len(mode) == 3:
665 mode = '0' + mode
666
667 try:
668 int(mode, 8)
669 except ValueError:
670 sys.exit('Mode must be octal characters, got: "%s"' % mode)
671
672 if len(mode) != 4:
673 sys.exit('Mode must be 3 or 4 characters, got: "%s"' % mode)
674
675 caps_str = '|'.join(caps)
676
677 entry = FSConfig(mode, user, group, caps_str, section_name, file_name)
678 if section_name[-1] == '/':
679 self._dirs.append(entry)
680 else:
681 self._files.append(entry)
682
683 @property
684 def files(self):
685 """Get the list of FSConfig file entries.
686
687 Returns:
688 a list of FSConfig() objects for file paths.
689 """
690 return self._files
691
692 @property
693 def dirs(self):
694 """Get the list of FSConfig dir entries.
695
696 Returns:
697 a list of FSConfig() objects for directory paths.
698 """
699 return self._dirs
700
701 @property
702 def aids(self):
703 """Get the list of AID entries.
704
705 Returns:
706 a list of AID() objects.
707 """
708 return self._aids
709
710 @staticmethod
711 def _file_key(fs_config):
712 """Used as the key paramter to sort.
713
714 This is used as a the function to the key parameter of a sort.
715 it wraps the string supplied in a class that implements the
716 appropriate __lt__ operator for the sort on path strings. See
717 StringWrapper class for more details.
718
719 Args:
720 fs_config (FSConfig): A FSConfig entry.
721
722 Returns:
723 A StringWrapper object
724 """
725
726 # Wrapper class for custom prefix matching strings
727 class StringWrapper(object):
728 """Wrapper class used for sorting prefix strings.
729
730 The algorithm is as follows:
731 - specified path before prefix match
732 - ie foo before f*
733 - lexicographical less than before other
734 - ie boo before foo
735
736 Given these paths:
737 paths=['ac', 'a', 'acd', 'an', 'a*', 'aa', 'ac*']
738 The sort order would be:
739 paths=['a', 'aa', 'ac', 'acd', 'an', 'ac*', 'a*']
740 Thus the fs_config tools will match on specified paths before
741 attempting prefix, and match on the longest matching prefix.
742 """
743
744 def __init__(self, path):
745 """
746 Args:
747 path (str): the path string to wrap.
748 """
749 self.is_prefix = path[-1] == '*'
750 if self.is_prefix:
751 self.path = path[:-1]
752 else:
753 self.path = path
754
755 def __lt__(self, other):
756
757 # if were both suffixed the smallest string
758 # is 'bigger'
759 if self.is_prefix and other.is_prefix:
760 result = len(self.path) > len(other.path)
761 # If I am an the suffix match, im bigger
762 elif self.is_prefix:
763 result = False
764 # If other is the suffix match, he's bigger
765 elif other.is_prefix:
766 result = True
767 # Alphabetical
768 else:
769 result = self.path < other.path
770 return result
771
772 return StringWrapper(fs_config.path)
773
774 @staticmethod
William Roberts5f059a72016-04-25 10:36:45 -0700775 def _handle_dup_and_add(name, file_name, section_name, seen):
William Roberts11c29282016-04-09 10:32:30 -0700776 """Tracks and detects duplicates. Internal use only.
777
778 Calls sys.exit() on a duplicate.
779
780 Args:
781 name (str): The name to use in the error reporting. The pretty
782 name for the section.
783 file_name (str): The file currently being parsed.
784 section_name (str): The name of the section. This would be path
785 or identifier depending on what's being parsed.
786 seen (dict): The dictionary of seen things to check against.
787 """
788 if section_name in seen:
789 dups = '"' + seen[section_name] + '" and '
790 dups += file_name
791 sys.exit('Duplicate %s "%s" found in files: %s' %
792 (name, section_name, dups))
793
794 seen[section_name] = file_name
795
796
797class BaseGenerator(object):
798 """Interface for Generators.
799
800 Base class for generators, generators should implement
801 these method stubs.
802 """
803
804 def add_opts(self, opt_group):
805 """Used to add per-generator options to the command line.
806
807 Args:
808 opt_group (argument group object): The argument group to append to.
809 See the ArgParse docs for more details.
810 """
811
812 raise NotImplementedError("Not Implemented")
813
814 def __call__(self, args):
815 """This is called to do whatever magic the generator does.
816
817 Args:
818 args (dict): The arguments from ArgParse as a dictionary.
819 ie if you specified an argument of foo in add_opts, access
820 it via args['foo']
821 """
822
823 raise NotImplementedError("Not Implemented")
824
825
826@generator('fsconfig')
827class FSConfigGen(BaseGenerator):
828 """Generates the android_filesystem_config.h file.
829
830 Output is used in generating fs_config_files and fs_config_dirs.
831 """
832
833 _GENERATED = textwrap.dedent("""\
834 /*
835 * THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY
836 */
837 """)
838
William Robertscfc51f52016-04-12 08:51:13 -0700839 _INCLUDES = [
840 '<private/android_filesystem_config.h>', '"generated_oem_aid.h"'
841 ]
William Roberts11c29282016-04-09 10:32:30 -0700842
843 _DEFINE_NO_DIRS = '#define NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS'
844 _DEFINE_NO_FILES = '#define NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_FILES'
845
846 _DEFAULT_WARNING = (
847 '#warning No device-supplied android_filesystem_config.h,'
848 ' using empty default.')
849
850 # Long names.
851 # pylint: disable=invalid-name
852 _NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS_ENTRY = (
853 '{ 00000, AID_ROOT, AID_ROOT, 0,'
854 '"system/etc/fs_config_dirs" },')
855
856 _NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_FILES_ENTRY = (
857 '{ 00000, AID_ROOT, AID_ROOT, 0,'
858 '"system/etc/fs_config_files" },')
859
860 _IFDEF_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS = (
861 '#ifdef NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS')
862 # pylint: enable=invalid-name
863
864 _ENDIF = '#endif'
865
866 _OPEN_FILE_STRUCT = (
867 'static const struct fs_path_config android_device_files[] = {')
868
869 _OPEN_DIR_STRUCT = (
870 'static const struct fs_path_config android_device_dirs[] = {')
871
872 _CLOSE_FILE_STRUCT = '};'
873
874 _GENERIC_DEFINE = "#define %s\t%s"
875
876 _FILE_COMMENT = '// Defined in file: \"%s\"'
877
William Roberts8f42ce72016-04-25 12:27:43 -0700878 def __init__(self, *args, **kwargs):
879 BaseGenerator.__init__(args, kwargs)
880
881 self._oem_parser = None
882 self._base_parser = None
883 self._friendly_to_aid = None
884
William Roberts11c29282016-04-09 10:32:30 -0700885 def add_opts(self, opt_group):
886
887 opt_group.add_argument(
888 'fsconfig', nargs='+', help='The list of fsconfig files to parse')
889
William Roberts64edf5b2016-04-11 17:12:47 -0700890 opt_group.add_argument(
891 '--aid-header',
892 required=True,
893 help='An android_filesystem_config.h file'
894 ' to parse AIDs and OEM Ranges from')
895
William Roberts11c29282016-04-09 10:32:30 -0700896 def __call__(self, args):
897
William Roberts8f42ce72016-04-25 12:27:43 -0700898 self._base_parser = AIDHeaderParser(args['aid_header'])
899 self._oem_parser = FSConfigFileParser(args['fsconfig'],
900 self._base_parser.oem_ranges)
901 base_aids = self._base_parser.aids
902 oem_aids = self._oem_parser.aids
William Roberts64edf5b2016-04-11 17:12:47 -0700903
William Roberts8f42ce72016-04-25 12:27:43 -0700904 # Detect name collisions on AIDs. Since friendly works as the
905 # identifier for collision testing and we need friendly later on for
906 # name resolution, just calculate and use friendly.
907 # {aid.friendly: aid for aid in base_aids}
908 base_friendly = {aid.friendly: aid for aid in base_aids}
909 oem_friendly = {aid.friendly: aid for aid in oem_aids}
William Roberts11c29282016-04-09 10:32:30 -0700910
William Roberts8f42ce72016-04-25 12:27:43 -0700911 base_set = set(base_friendly.keys())
912 oem_set = set(oem_friendly.keys())
William Roberts11c29282016-04-09 10:32:30 -0700913
William Roberts8f42ce72016-04-25 12:27:43 -0700914 common = base_set & oem_set
915
916 if len(common) > 0:
917 emsg = 'Following AID Collisions detected for: \n'
918 for friendly in common:
919 base = base_friendly[friendly]
920 oem = oem_friendly[friendly]
921 emsg += (
922 'Identifier: "%s" Friendly Name: "%s" '
923 'found in file "%s" and "%s"' %
924 (base.identifier, base.friendly, base.found, oem.found))
925 sys.exit(emsg)
926
927 self._friendly_to_aid = oem_friendly
928 self._friendly_to_aid.update(base_friendly)
929
930 self._generate()
931
932 def _to_fs_entry(self, fs_config):
933 """Converts an FSConfig entry to an fs entry.
934
935 Prints '{ mode, user, group, caps, "path" },'.
936
937 Calls sys.exit() on error.
William Roberts11c29282016-04-09 10:32:30 -0700938
939 Args:
940 fs_config (FSConfig): The entry to convert to
941 a valid C array entry.
942 """
943
944 # Get some short names
945 mode = fs_config.mode
946 user = fs_config.user
947 group = fs_config.group
948 fname = fs_config.filename
949 caps = fs_config.caps
950 path = fs_config.path
951
William Roberts8f42ce72016-04-25 12:27:43 -0700952 emsg = 'Cannot convert friendly name "%s" to identifier!'
953
954 # remap friendly names to identifier names
955 if AID.is_friendly(user):
956 if user not in self._friendly_to_aid:
957 sys.exit(emsg % user)
958 user = self._friendly_to_aid[user].identifier
959
960 if AID.is_friendly(group):
961 if group not in self._friendly_to_aid:
962 sys.exit(emsg % group)
963 group = self._friendly_to_aid[group].identifier
964
William Roberts11c29282016-04-09 10:32:30 -0700965 fmt = '{ %s, %s, %s, %s, "%s" },'
966
967 expanded = fmt % (mode, user, group, caps, path)
968
969 print FSConfigGen._FILE_COMMENT % fname
970 print ' ' + expanded
971
972 @staticmethod
William Robertscfc51f52016-04-12 08:51:13 -0700973 def _gen_inc():
William Roberts8f42ce72016-04-25 12:27:43 -0700974 """Generate the include header lines and print to stdout."""
William Robertscfc51f52016-04-12 08:51:13 -0700975 for include in FSConfigGen._INCLUDES:
976 print '#include %s' % include
977
William Roberts8f42ce72016-04-25 12:27:43 -0700978 def _generate(self):
William Roberts11c29282016-04-09 10:32:30 -0700979 """Generates an OEM android_filesystem_config.h header file to stdout.
980
981 Args:
982 files ([FSConfig]): A list of FSConfig objects for file entries.
983 dirs ([FSConfig]): A list of FSConfig objects for directory
984 entries.
985 aids ([AIDS]): A list of AID objects for Android Id entries.
986 """
987 print FSConfigGen._GENERATED
William Robertscfc51f52016-04-12 08:51:13 -0700988 print
989
990 FSConfigGen._gen_inc()
William Robertsc950a352016-03-04 18:12:29 -0800991 print
992
William Roberts8f42ce72016-04-25 12:27:43 -0700993 dirs = self._oem_parser.dirs
994 files = self._oem_parser.files
995 aids = self._oem_parser.aids
996
William Roberts11c29282016-04-09 10:32:30 -0700997 are_dirs = len(dirs) > 0
998 are_files = len(files) > 0
999 are_aids = len(aids) > 0
William Robertsc950a352016-03-04 18:12:29 -08001000
William Roberts11c29282016-04-09 10:32:30 -07001001 if are_aids:
1002 for aid in aids:
1003 # use the preserved _path value
1004 print FSConfigGen._FILE_COMMENT % aid.found
1005 print FSConfigGen._GENERIC_DEFINE % (aid.identifier, aid.value)
William Robertsc950a352016-03-04 18:12:29 -08001006
William Roberts11c29282016-04-09 10:32:30 -07001007 print
William Robertsc950a352016-03-04 18:12:29 -08001008
1009 if not are_dirs:
William Roberts11c29282016-04-09 10:32:30 -07001010 print FSConfigGen._DEFINE_NO_DIRS + '\n'
William Robertsc950a352016-03-04 18:12:29 -08001011
William Roberts11c29282016-04-09 10:32:30 -07001012 if not are_files:
1013 print FSConfigGen._DEFINE_NO_FILES + '\n'
William Robertsc950a352016-03-04 18:12:29 -08001014
William Roberts11c29282016-04-09 10:32:30 -07001015 if not are_files and not are_dirs and not are_aids:
William Roberts11c29282016-04-09 10:32:30 -07001016 return
William Robertsc950a352016-03-04 18:12:29 -08001017
William Roberts11c29282016-04-09 10:32:30 -07001018 if are_files:
1019 print FSConfigGen._OPEN_FILE_STRUCT
1020 for fs_config in files:
William Roberts8f42ce72016-04-25 12:27:43 -07001021 self._to_fs_entry(fs_config)
William Robertsc950a352016-03-04 18:12:29 -08001022
William Roberts11c29282016-04-09 10:32:30 -07001023 if not are_dirs:
1024 print FSConfigGen._IFDEF_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS
1025 print(
1026 ' ' +
1027 FSConfigGen._NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS_ENTRY)
1028 print FSConfigGen._ENDIF
1029 print FSConfigGen._CLOSE_FILE_STRUCT
William Robertsc950a352016-03-04 18:12:29 -08001030
William Roberts11c29282016-04-09 10:32:30 -07001031 if are_dirs:
1032 print FSConfigGen._OPEN_DIR_STRUCT
1033 for dir_entry in dirs:
William Roberts8f42ce72016-04-25 12:27:43 -07001034 self._to_fs_entry(dir_entry)
William Robertsc950a352016-03-04 18:12:29 -08001035
William Roberts11c29282016-04-09 10:32:30 -07001036 print FSConfigGen._CLOSE_FILE_STRUCT
William Robertsc950a352016-03-04 18:12:29 -08001037
William Robertsc950a352016-03-04 18:12:29 -08001038
William Robertsd7104bc2016-04-11 21:17:12 -07001039@generator('aidarray')
1040class AIDArrayGen(BaseGenerator):
1041 """Generates the android_id static array."""
1042
1043 _GENERATED = ('/*\n'
1044 ' * THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY!\n'
1045 ' */')
1046
1047 _INCLUDE = '#include <private/android_filesystem_config.h>'
1048
1049 _STRUCT_FS_CONFIG = textwrap.dedent("""
1050 struct android_id_info {
1051 const char *name;
1052 unsigned aid;
1053 };""")
1054
1055 _OPEN_ID_ARRAY = 'static const struct android_id_info android_ids[] = {'
1056
1057 _ID_ENTRY = ' { "%s", %s },'
1058
1059 _CLOSE_FILE_STRUCT = '};'
1060
1061 _COUNT = ('#define android_id_count \\\n'
1062 ' (sizeof(android_ids) / sizeof(android_ids[0]))')
1063
1064 def add_opts(self, opt_group):
1065
1066 opt_group.add_argument(
1067 'hdrfile', help='The android_filesystem_config.h'
1068 'file to parse')
1069
1070 def __call__(self, args):
1071
1072 hdr = AIDHeaderParser(args['hdrfile'])
1073
1074 print AIDArrayGen._GENERATED
1075 print
1076 print AIDArrayGen._INCLUDE
1077 print
1078 print AIDArrayGen._STRUCT_FS_CONFIG
1079 print
1080 print AIDArrayGen._OPEN_ID_ARRAY
1081
William Roberts8f42ce72016-04-25 12:27:43 -07001082 for aid in hdr.aids:
1083 print AIDArrayGen._ID_ENTRY % (aid.friendly, aid.identifier)
William Robertsd7104bc2016-04-11 21:17:12 -07001084
1085 print AIDArrayGen._CLOSE_FILE_STRUCT
1086 print
1087 print AIDArrayGen._COUNT
1088 print
1089
1090
William Robertscfc51f52016-04-12 08:51:13 -07001091@generator('oemaid')
1092class OEMAidGen(BaseGenerator):
1093 """Generates the OEM AID_<name> value header file."""
1094
1095 _GENERATED = ('/*\n'
1096 ' * THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY!\n'
1097 ' */')
1098
1099 _GENERIC_DEFINE = "#define %s\t%s"
1100
1101 _FILE_COMMENT = '// Defined in file: \"%s\"'
1102
1103 # Intentional trailing newline for readability.
1104 _FILE_IFNDEF_DEFINE = ('#ifndef GENERATED_OEM_AIDS_H_\n'
1105 '#define GENERATED_OEM_AIDS_H_\n')
1106
1107 _FILE_ENDIF = '#endif'
1108
1109 def __init__(self):
1110
1111 self._old_file = None
1112
1113 def add_opts(self, opt_group):
1114
1115 opt_group.add_argument(
1116 'fsconfig', nargs='+', help='The list of fsconfig files to parse.')
1117
1118 opt_group.add_argument(
1119 '--aid-header',
1120 required=True,
1121 help='An android_filesystem_config.h file'
1122 'to parse AIDs and OEM Ranges from')
1123
1124 def __call__(self, args):
1125
1126 hdr_parser = AIDHeaderParser(args['aid_header'])
1127
1128 parser = FSConfigFileParser(args['fsconfig'], hdr_parser.oem_ranges)
1129
1130 print OEMAidGen._GENERATED
1131
1132 print OEMAidGen._FILE_IFNDEF_DEFINE
1133
1134 for aid in parser.aids:
1135 self._print_aid(aid)
1136 print
1137
1138 print OEMAidGen._FILE_ENDIF
1139
1140 def _print_aid(self, aid):
1141 """Prints a valid #define AID identifier to stdout.
1142
1143 Args:
1144 aid to print
1145 """
1146
1147 # print the source file location of the AID
1148 found_file = aid.found
1149 if found_file != self._old_file:
1150 print OEMAidGen._FILE_COMMENT % found_file
1151 self._old_file = found_file
1152
1153 print OEMAidGen._GENERIC_DEFINE % (aid.identifier, aid.value)
1154
1155
William Robertsc950a352016-03-04 18:12:29 -08001156def main():
William Roberts11c29282016-04-09 10:32:30 -07001157 """Main entry point for execution."""
William Robertsc950a352016-03-04 18:12:29 -08001158
William Roberts11c29282016-04-09 10:32:30 -07001159 opt_parser = argparse.ArgumentParser(
1160 description='A tool for parsing fsconfig config files and producing' +
1161 'digestable outputs.')
1162 subparser = opt_parser.add_subparsers(help='generators')
William Robertsc950a352016-03-04 18:12:29 -08001163
William Roberts11c29282016-04-09 10:32:30 -07001164 gens = generator.get()
William Robertsc950a352016-03-04 18:12:29 -08001165
William Roberts11c29282016-04-09 10:32:30 -07001166 # for each gen, instantiate and add them as an option
1167 for name, gen in gens.iteritems():
William Robertsc950a352016-03-04 18:12:29 -08001168
William Roberts11c29282016-04-09 10:32:30 -07001169 generator_option_parser = subparser.add_parser(name, help=gen.__doc__)
1170 generator_option_parser.set_defaults(which=name)
William Roberts8cb6a182016-04-08 22:06:19 -07001171
William Roberts11c29282016-04-09 10:32:30 -07001172 opt_group = generator_option_parser.add_argument_group(name +
1173 ' options')
1174 gen.add_opts(opt_group)
William Roberts8cb6a182016-04-08 22:06:19 -07001175
William Roberts11c29282016-04-09 10:32:30 -07001176 args = opt_parser.parse_args()
1177
1178 args_as_dict = vars(args)
1179 which = args_as_dict['which']
1180 del args_as_dict['which']
1181
1182 gens[which](args_as_dict)
1183
William Robertsc950a352016-03-04 18:12:29 -08001184
1185if __name__ == '__main__':
1186 main()