blob: ad544871685417bd6b0e5a3453d99cb110db66a8 [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.
101 """
William Robertsc950a352016-03-04 18:12:29 -0800102
William Roberts11c29282016-04-09 10:32:30 -0700103 def __init__(self, identifier, value, found):
104 """
105 Args:
106 identifier: The identifier name for a #define <identifier>.
107 value: The value of the AID, aka the uid.
108 found (str): The file found in, not required to be specified.
William Robertsc950a352016-03-04 18:12:29 -0800109
William Roberts11c29282016-04-09 10:32:30 -0700110 Raises:
111 ValueError: if value is not a valid string number as processed by
112 int(x, 0)
113 """
114 self.identifier = identifier
115 self.value = value
116 self.found = found
117 self.normalized_value = str(int(value, 0))
William Robertsc950a352016-03-04 18:12:29 -0800118
119
William Roberts11c29282016-04-09 10:32:30 -0700120class FSConfig(object):
121 """Represents a filesystem config array entry.
William Robertsc950a352016-03-04 18:12:29 -0800122
William Roberts11c29282016-04-09 10:32:30 -0700123 Represents a file system configuration entry for specifying
124 file system capabilities.
William Robertsc950a352016-03-04 18:12:29 -0800125
William Roberts11c29282016-04-09 10:32:30 -0700126 Attributes:
127 mode (str): The mode of the file or directory.
128 user (str): The uid or #define identifier (AID_SYSTEM)
129 group (str): The gid or #define identifier (AID_SYSTEM)
130 caps (str): The capability set.
131 filename (str): The file it was found in.
132 """
William Robertsc950a352016-03-04 18:12:29 -0800133
William Roberts11c29282016-04-09 10:32:30 -0700134 def __init__(self, mode, user, group, caps, path, filename):
135 """
136 Args:
137 mode (str): The mode of the file or directory.
138 user (str): The uid or #define identifier (AID_SYSTEM)
139 group (str): The gid or #define identifier (AID_SYSTEM)
140 caps (str): The capability set as a list.
141 filename (str): The file it was found in.
142 """
143 self.mode = mode
144 self.user = user
145 self.group = group
146 self.caps = caps
147 self.path = path
148 self.filename = filename
149
150
William Roberts64edf5b2016-04-11 17:12:47 -0700151class AIDHeaderParser(object):
152 """Parses an android_filesystem_config.h file.
153
154 Parses a C header file and extracts lines starting with #define AID_<name>
155 It provides some basic sanity checks. The information extracted from this
156 file can later be used to sanity check other things (like oem ranges) as
157 well as generating a mapping of names to uids. It was primarily designed to
158 parse the private/android_filesystem_config.h, but any C header should
159 work.
160 """
161
162 _SKIPWORDS = ['UNUSED']
163 _AID_KW = 'AID_'
164 _AID_DEFINE = re.compile(r'\s*#define\s+%s.*' % _AID_KW)
165 _OEM_START_KW = 'START'
166 _OEM_END_KW = 'END'
167 _OEM_RANGE = re.compile('AID_OEM_RESERVED_[0-9]*_{0,1}(%s|%s)' %
168 (_OEM_START_KW, _OEM_END_KW))
169
170 # Some of the AIDS like AID_MEDIA_EX had names like mediaex
171 # list a map of things to fixup until we can correct these
172 # at a later date.
173 _FIXUPS = {
174 'media_drm': 'mediadrm',
175 'media_ex': 'mediaex',
176 'media_codec': 'mediacodec'
177 }
178
179 def __init__(self, aid_header):
180 """
181 Args:
182 aid_header (str): file name for the header
183 file containing AID entries.
184 """
185 self._aid_header = aid_header
186 self._aid_name_to_value = {}
187 self._aid_value_to_name = {}
188 self._oem_ranges = {}
189
190 with open(aid_header) as open_file:
191 self._parse(open_file)
192
193 try:
194 self._process_and_check()
195 except ValueError as exception:
196 sys.exit('Error processing parsed data: "%s"' % (str(exception)))
197
198 def _parse(self, aid_file):
199 """Parses an AID header file. Internal use only.
200
201 Args:
202 aid_file (file): The open AID header file to parse.
203 """
204
205 for lineno, line in enumerate(aid_file):
206 def error_message(msg):
207 """Creates an error message with the current parsing state."""
208 return 'Error "{}" in file: "{}" on line: {}'.format(
209 msg, self._aid_header, str(lineno))
210
211 if AIDHeaderParser._AID_DEFINE.match(line):
212 chunks = line.split()
213
214 if any(x in chunks[1] for x in AIDHeaderParser._SKIPWORDS):
215 continue
216
217 identifier = chunks[1]
218 value = chunks[2]
219
220 try:
221 if AIDHeaderParser._is_oem_range(identifier):
222 self._handle_oem_range(identifier, value)
223 else:
224 self._handle_aid(identifier, value)
225 except ValueError as exception:
226 sys.exit(error_message(
227 '{} for "{}"'.format(exception, identifier)))
228
229 def _handle_aid(self, identifier, value):
230 """Handle an AID C #define.
231
232 Handles an AID, sanity checking, generating the friendly name and
233 adding it to the internal maps. Internal use only.
234
235 Args:
236 identifier (str): The name of the #define identifier. ie AID_FOO.
237 value (str): The value associated with the identifier.
238
239 Raises:
240 ValueError: With message set to indicate the error.
241 """
242
243 # friendly name
244 name = AIDHeaderParser._convert_friendly(identifier)
245
246 # duplicate name
247 if name in self._aid_name_to_value:
248 raise ValueError('Duplicate aid "%s"' % identifier)
249
250 if value in self._aid_value_to_name:
251 raise ValueError('Duplicate aid value "%u" for %s' % value,
252 identifier)
253
254 self._aid_name_to_value[name] = AID(identifier, value, self._aid_header)
255 self._aid_value_to_name[value] = name
256
257 def _handle_oem_range(self, identifier, value):
258 """Handle an OEM range C #define.
259
260 When encountering special AID defines, notably for the OEM ranges
261 this method handles sanity checking and adding them to the internal
262 maps. For internal use only.
263
264 Args:
265 identifier (str): The name of the #define identifier.
266 ie AID_OEM_RESERVED_START/END.
267 value (str): The value associated with the identifier.
268
269 Raises:
270 ValueError: With message set to indicate the error.
271 """
272
273 try:
274 int_value = int(value, 0)
275 except ValueError:
276 raise ValueError(
277 'Could not convert "%s" to integer value, got: "%s"' %
278 (identifier, value))
279
280 # convert AID_OEM_RESERVED_START or AID_OEM_RESERVED_<num>_START
281 # to AID_OEM_RESERVED or AID_OEM_RESERVED_<num>
282 is_start = identifier.endswith(AIDHeaderParser._OEM_START_KW)
283
284 if is_start:
285 tostrip = len(AIDHeaderParser._OEM_START_KW)
286 else:
287 tostrip = len(AIDHeaderParser._OEM_END_KW)
288
289 # ending _
290 tostrip = tostrip + 1
291
292 strip = identifier[:-tostrip]
293 if strip not in self._oem_ranges:
294 self._oem_ranges[strip] = []
295
296 if len(self._oem_ranges[strip]) > 2:
297 raise ValueError('Too many same OEM Ranges "%s"' % identifier)
298
299 if len(self._oem_ranges[strip]) == 1:
300 tmp = self._oem_ranges[strip][0]
301
302 if tmp == int_value:
303 raise ValueError('START and END values equal %u' % int_value)
304 elif is_start and tmp < int_value:
305 raise ValueError('END value %u less than START value %u' %
306 (tmp, int_value))
307 elif not is_start and tmp > int_value:
308 raise ValueError('END value %u less than START value %u' %
309 (int_value, tmp))
310
311 # Add START values to the head of the list and END values at the end.
312 # Thus, the list is ordered with index 0 as START and index 1 as END.
313 if is_start:
314 self._oem_ranges[strip].insert(0, int_value)
315 else:
316 self._oem_ranges[strip].append(int_value)
317
318 def _process_and_check(self):
319 """Process, check and populate internal data structures.
320
321 After parsing and generating the internal data structures, this method
322 is responsible for sanity checking ALL of the acquired data.
323
324 Raises:
325 ValueError: With the message set to indicate the specific error.
326 """
327
328 # tuplefy the lists since range() does not like them mutable.
329 self._oem_ranges = [
330 AIDHeaderParser._convert_lst_to_tup(k, v)
331 for k, v in self._oem_ranges.iteritems()
332 ]
333
334 # Check for overlapping ranges
335 for i, range1 in enumerate(self._oem_ranges):
336 for range2 in self._oem_ranges[i + 1:]:
337 if AIDHeaderParser._is_overlap(range1, range2):
338 raise ValueError("Overlapping OEM Ranges found %s and %s" %
339 (str(range1), str(range2)))
340
341 # No core AIDs should be within any oem range.
342 for aid in self._aid_value_to_name:
343
344 if Utils.in_any_range(aid, self._oem_ranges):
345 name = self._aid_value_to_name[aid]
346 raise ValueError(
347 'AID "%s" value: %u within reserved OEM Range: "%s"' %
348 (name, aid, str(self._oem_ranges)))
349
350 @property
351 def oem_ranges(self):
352 """Retrieves the OEM closed ranges as a list of tuples.
353
354 Returns:
355 A list of closed range tuples: [ (0, 42), (50, 105) ... ]
356 """
357 return self._oem_ranges
358
359 @property
360 def aids(self):
361 """Retrieves the list of found AIDs.
362
363 Returns:
364 A list of AID() objects.
365 """
William Robertsd7104bc2016-04-11 21:17:12 -0700366 return self._aid_name_to_value
William Roberts64edf5b2016-04-11 17:12:47 -0700367
368 @staticmethod
369 def _convert_lst_to_tup(name, lst):
370 """Converts a mutable list to a non-mutable tuple.
371
372 Used ONLY for ranges and thus enforces a length of 2.
373
374 Args:
375 lst (List): list that should be "tuplefied".
376
377 Raises:
378 ValueError if lst is not a list or len is not 2.
379
380 Returns:
381 Tuple(lst)
382 """
383 if not lst or len(lst) != 2:
384 raise ValueError('Mismatched range for "%s"' % name)
385
386 return tuple(lst)
387
388 @staticmethod
389 def _convert_friendly(identifier):
390 """
391 Translate AID_FOO_BAR to foo_bar (ie name)
392
393 Args:
394 identifier (str): The name of the #define.
395
396 Returns:
397 The friendly name as a str.
398 """
399
400 name = identifier[len(AIDHeaderParser._AID_KW):].lower()
401
402 if name in AIDHeaderParser._FIXUPS:
403 return AIDHeaderParser._FIXUPS[name]
404
405 return name
406
407 @staticmethod
408 def _is_oem_range(aid):
409 """Detects if a given aid is within the reserved OEM range.
410
411 Args:
412 aid (int): The aid to test
413
414 Returns:
415 True if it is within the range, False otherwise.
416 """
417
418 return AIDHeaderParser._OEM_RANGE.match(aid)
419
420 @staticmethod
421 def _is_overlap(range_a, range_b):
422 """Calculates the overlap of two range tuples.
423
424 A range tuple is a closed range. A closed range includes its endpoints.
425 Note that python tuples use () notation which collides with the
426 mathematical notation for open ranges.
427
428 Args:
429 range_a: The first tuple closed range eg (0, 5).
430 range_b: The second tuple closed range eg (3, 7).
431
432 Returns:
433 True if they overlap, False otherwise.
434 """
435
436 return max(range_a[0], range_b[0]) <= min(range_a[1], range_b[1])
437
438
William Roberts11c29282016-04-09 10:32:30 -0700439class FSConfigFileParser(object):
440 """Parses a config.fs ini format file.
441
442 This class is responsible for parsing the config.fs ini format files.
443 It collects and checks all the data in these files and makes it available
444 for consumption post processed.
445 """
William Roberts11c29282016-04-09 10:32:30 -0700446
William Roberts5f059a72016-04-25 10:36:45 -0700447 # These _AID vars work together to ensure that an AID section name
448 # cannot contain invalid characters for a C define or a passwd/group file.
449 # Since _AID_PREFIX is within the set of _AID_MATCH the error logic only
450 # checks end, if you change this, you may have to update the error
451 # detection code.
452 _AID_PREFIX = 'AID_'
453 _AID_MATCH = re.compile('%s[A-Z0-9_]+' % _AID_PREFIX)
454 _AID_ERR_MSG = 'Expecting upper case, a number or underscore'
455
456 # list of handler to required options, used to identify the
457 # parsing section
458 _SECTIONS = [('_handle_aid', ('value',)),
459 ('_handle_path', ('mode', 'user', 'group', 'caps'))]
William Roberts11c29282016-04-09 10:32:30 -0700460
William Roberts64edf5b2016-04-11 17:12:47 -0700461 def __init__(self, config_files, oem_ranges):
William Roberts11c29282016-04-09 10:32:30 -0700462 """
463 Args:
464 config_files ([str]): The list of config.fs files to parse.
465 Note the filename is not important.
William Roberts64edf5b2016-04-11 17:12:47 -0700466 oem_ranges ([(),()]): range tuples indicating reserved OEM ranges.
William Roberts11c29282016-04-09 10:32:30 -0700467 """
468
469 self._files = []
470 self._dirs = []
471 self._aids = []
472
473 self._seen_paths = {}
474 # (name to file, value to aid)
475 self._seen_aids = ({}, {})
476
William Roberts64edf5b2016-04-11 17:12:47 -0700477 self._oem_ranges = oem_ranges
478
William Roberts11c29282016-04-09 10:32:30 -0700479 self._config_files = config_files
480
481 for config_file in self._config_files:
482 self._parse(config_file)
483
484 def _parse(self, file_name):
485 """Parses and verifies config.fs files. Internal use only.
486
487 Args:
488 file_name (str): The config.fs (PythonConfigParser file format)
489 file to parse.
490
491 Raises:
492 Anything raised by ConfigParser.read()
493 """
494
495 # Separate config parsers for each file found. If you use
496 # read(filenames...) later files can override earlier files which is
497 # not what we want. Track state across files and enforce with
498 # _handle_dup(). Note, strict ConfigParser is set to true in
499 # Python >= 3.2, so in previous versions same file sections can
500 # override previous
501 # sections.
William Robertsc950a352016-03-04 18:12:29 -0800502
503 config = ConfigParser.ConfigParser()
504 config.read(file_name)
505
William Roberts11c29282016-04-09 10:32:30 -0700506 for section in config.sections():
William Robertsc950a352016-03-04 18:12:29 -0800507
William Roberts5f059a72016-04-25 10:36:45 -0700508 found = False
509
510 for test in FSConfigFileParser._SECTIONS:
511 handler = test[0]
512 options = test[1]
513
514 if all([config.has_option(section, item) for item in options]):
515 handler = getattr(self, handler)
516 handler(file_name, section, config)
517 found = True
518 break
519
520 if not found:
521 sys.exit('Invalid section "%s" in file: "%s"' %
522 (section, file_name))
William Robertsc950a352016-03-04 18:12:29 -0800523
William Roberts11c29282016-04-09 10:32:30 -0700524 # sort entries:
525 # * specified path before prefix match
526 # ** ie foo before f*
527 # * lexicographical less than before other
528 # ** ie boo before foo
529 # Given these paths:
530 # paths=['ac', 'a', 'acd', 'an', 'a*', 'aa', 'ac*']
531 # The sort order would be:
532 # paths=['a', 'aa', 'ac', 'acd', 'an', 'ac*', 'a*']
533 # Thus the fs_config tools will match on specified paths before
534 # attempting prefix, and match on the longest matching prefix.
535 self._files.sort(key=FSConfigFileParser._file_key)
William Robertsc950a352016-03-04 18:12:29 -0800536
William Roberts11c29282016-04-09 10:32:30 -0700537 # sort on value of (file_name, name, value, strvalue)
538 # This is only cosmetic so AIDS are arranged in ascending order
539 # within the generated file.
540 self._aids.sort(key=lambda item: item.normalized_value)
William Robertsc950a352016-03-04 18:12:29 -0800541
William Roberts11c29282016-04-09 10:32:30 -0700542 def _handle_aid(self, file_name, section_name, config):
543 """Verifies an AID entry and adds it to the aid list.
William Robertsc950a352016-03-04 18:12:29 -0800544
William Roberts11c29282016-04-09 10:32:30 -0700545 Calls sys.exit() with a descriptive message of the failure.
546
547 Args:
548 file_name (str): The filename of the config file being parsed.
549 section_name (str): The section name currently being parsed.
550 config (ConfigParser): The ConfigParser section being parsed that
551 the option values will come from.
552 """
553
554 def error_message(msg):
555 """Creates an error message with current parsing state."""
556 return '{} for: "{}" file: "{}"'.format(msg, section_name,
557 file_name)
558
William Roberts5f059a72016-04-25 10:36:45 -0700559 FSConfigFileParser._handle_dup_and_add('AID', file_name, section_name,
560 self._seen_aids[0])
561
562 match = FSConfigFileParser._AID_MATCH.match(section_name)
563 invalid = match.end() if match else len(FSConfigFileParser._AID_PREFIX)
564 if invalid != len(section_name):
565 tmp_errmsg = ('Invalid characters in AID section at "%d" for: "%s"'
566 % (invalid, FSConfigFileParser._AID_ERR_MSG))
567 sys.exit(error_message(tmp_errmsg))
568
William Roberts11c29282016-04-09 10:32:30 -0700569 value = config.get(section_name, 'value')
570
571 if not value:
572 sys.exit(error_message('Found specified but unset "value"'))
573
574 try:
575 aid = AID(section_name, value, file_name)
576 except ValueError:
577 sys.exit(
578 error_message('Invalid "value", not aid number, got: \"%s\"' %
579 value))
580
William Roberts64edf5b2016-04-11 17:12:47 -0700581 # Values must be within OEM range
582 if not Utils.in_any_range(int(aid.value, 0), self._oem_ranges):
William Roberts11c29282016-04-09 10:32:30 -0700583 emsg = '"value" not in valid range %s, got: %s'
William Roberts64edf5b2016-04-11 17:12:47 -0700584 emsg = emsg % (str(self._oem_ranges), value)
William Roberts11c29282016-04-09 10:32:30 -0700585 sys.exit(error_message(emsg))
586
587 # use the normalized int value in the dict and detect
588 # duplicate definitions of the same value
William Roberts5f059a72016-04-25 10:36:45 -0700589 FSConfigFileParser._handle_dup_and_add(
590 'AID', file_name, aid.normalized_value, self._seen_aids[1])
William Roberts11c29282016-04-09 10:32:30 -0700591
592 # Append aid tuple of (AID_*, base10(value), _path(value))
593 # We keep the _path version of value so we can print that out in the
594 # generated header so investigating parties can identify parts.
595 # We store the base10 value for sorting, so everything is ascending
596 # later.
597 self._aids.append(aid)
598
599 def _handle_path(self, file_name, section_name, config):
600 """Add a file capability entry to the internal list.
601
602 Handles a file capability entry, verifies it, and adds it to
603 to the internal dirs or files list based on path. If it ends
604 with a / its a dir. Internal use only.
605
606 Calls sys.exit() on any validation error with message set.
607
608 Args:
609 file_name (str): The current name of the file being parsed.
610 section_name (str): The name of the section to parse.
611 config (str): The config parser.
612 """
613
William Roberts5f059a72016-04-25 10:36:45 -0700614 FSConfigFileParser._handle_dup_and_add('path', file_name, section_name,
615 self._seen_paths)
616
William Roberts11c29282016-04-09 10:32:30 -0700617 mode = config.get(section_name, 'mode')
618 user = config.get(section_name, 'user')
619 group = config.get(section_name, 'group')
620 caps = config.get(section_name, 'caps')
621
622 errmsg = ('Found specified but unset option: \"%s" in file: \"' +
623 file_name + '\"')
624
625 if not mode:
626 sys.exit(errmsg % 'mode')
627
628 if not user:
629 sys.exit(errmsg % 'user')
630
631 if not group:
632 sys.exit(errmsg % 'group')
633
634 if not caps:
635 sys.exit(errmsg % 'caps')
636
637 caps = caps.split()
638
639 tmp = []
640 for cap in caps:
641 try:
642 # test if string is int, if it is, use as is.
643 int(cap, 0)
644 tmp.append('(' + cap + ')')
645 except ValueError:
646 tmp.append('(1ULL << CAP_' + cap.upper() + ')')
647
648 caps = tmp
649
650 if len(mode) == 3:
651 mode = '0' + mode
652
653 try:
654 int(mode, 8)
655 except ValueError:
656 sys.exit('Mode must be octal characters, got: "%s"' % mode)
657
658 if len(mode) != 4:
659 sys.exit('Mode must be 3 or 4 characters, got: "%s"' % mode)
660
661 caps_str = '|'.join(caps)
662
663 entry = FSConfig(mode, user, group, caps_str, section_name, file_name)
664 if section_name[-1] == '/':
665 self._dirs.append(entry)
666 else:
667 self._files.append(entry)
668
669 @property
670 def files(self):
671 """Get the list of FSConfig file entries.
672
673 Returns:
674 a list of FSConfig() objects for file paths.
675 """
676 return self._files
677
678 @property
679 def dirs(self):
680 """Get the list of FSConfig dir entries.
681
682 Returns:
683 a list of FSConfig() objects for directory paths.
684 """
685 return self._dirs
686
687 @property
688 def aids(self):
689 """Get the list of AID entries.
690
691 Returns:
692 a list of AID() objects.
693 """
694 return self._aids
695
696 @staticmethod
697 def _file_key(fs_config):
698 """Used as the key paramter to sort.
699
700 This is used as a the function to the key parameter of a sort.
701 it wraps the string supplied in a class that implements the
702 appropriate __lt__ operator for the sort on path strings. See
703 StringWrapper class for more details.
704
705 Args:
706 fs_config (FSConfig): A FSConfig entry.
707
708 Returns:
709 A StringWrapper object
710 """
711
712 # Wrapper class for custom prefix matching strings
713 class StringWrapper(object):
714 """Wrapper class used for sorting prefix strings.
715
716 The algorithm is as follows:
717 - specified path before prefix match
718 - ie foo before f*
719 - lexicographical less than before other
720 - ie boo before foo
721
722 Given these paths:
723 paths=['ac', 'a', 'acd', 'an', 'a*', 'aa', 'ac*']
724 The sort order would be:
725 paths=['a', 'aa', 'ac', 'acd', 'an', 'ac*', 'a*']
726 Thus the fs_config tools will match on specified paths before
727 attempting prefix, and match on the longest matching prefix.
728 """
729
730 def __init__(self, path):
731 """
732 Args:
733 path (str): the path string to wrap.
734 """
735 self.is_prefix = path[-1] == '*'
736 if self.is_prefix:
737 self.path = path[:-1]
738 else:
739 self.path = path
740
741 def __lt__(self, other):
742
743 # if were both suffixed the smallest string
744 # is 'bigger'
745 if self.is_prefix and other.is_prefix:
746 result = len(self.path) > len(other.path)
747 # If I am an the suffix match, im bigger
748 elif self.is_prefix:
749 result = False
750 # If other is the suffix match, he's bigger
751 elif other.is_prefix:
752 result = True
753 # Alphabetical
754 else:
755 result = self.path < other.path
756 return result
757
758 return StringWrapper(fs_config.path)
759
760 @staticmethod
William Roberts5f059a72016-04-25 10:36:45 -0700761 def _handle_dup_and_add(name, file_name, section_name, seen):
William Roberts11c29282016-04-09 10:32:30 -0700762 """Tracks and detects duplicates. Internal use only.
763
764 Calls sys.exit() on a duplicate.
765
766 Args:
767 name (str): The name to use in the error reporting. The pretty
768 name for the section.
769 file_name (str): The file currently being parsed.
770 section_name (str): The name of the section. This would be path
771 or identifier depending on what's being parsed.
772 seen (dict): The dictionary of seen things to check against.
773 """
774 if section_name in seen:
775 dups = '"' + seen[section_name] + '" and '
776 dups += file_name
777 sys.exit('Duplicate %s "%s" found in files: %s' %
778 (name, section_name, dups))
779
780 seen[section_name] = file_name
781
782
783class BaseGenerator(object):
784 """Interface for Generators.
785
786 Base class for generators, generators should implement
787 these method stubs.
788 """
789
790 def add_opts(self, opt_group):
791 """Used to add per-generator options to the command line.
792
793 Args:
794 opt_group (argument group object): The argument group to append to.
795 See the ArgParse docs for more details.
796 """
797
798 raise NotImplementedError("Not Implemented")
799
800 def __call__(self, args):
801 """This is called to do whatever magic the generator does.
802
803 Args:
804 args (dict): The arguments from ArgParse as a dictionary.
805 ie if you specified an argument of foo in add_opts, access
806 it via args['foo']
807 """
808
809 raise NotImplementedError("Not Implemented")
810
811
812@generator('fsconfig')
813class FSConfigGen(BaseGenerator):
814 """Generates the android_filesystem_config.h file.
815
816 Output is used in generating fs_config_files and fs_config_dirs.
817 """
818
819 _GENERATED = textwrap.dedent("""\
820 /*
821 * THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY
822 */
823 """)
824
William Robertscfc51f52016-04-12 08:51:13 -0700825 _INCLUDES = [
826 '<private/android_filesystem_config.h>', '"generated_oem_aid.h"'
827 ]
William Roberts11c29282016-04-09 10:32:30 -0700828
829 _DEFINE_NO_DIRS = '#define NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS'
830 _DEFINE_NO_FILES = '#define NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_FILES'
831
832 _DEFAULT_WARNING = (
833 '#warning No device-supplied android_filesystem_config.h,'
834 ' using empty default.')
835
836 # Long names.
837 # pylint: disable=invalid-name
838 _NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS_ENTRY = (
839 '{ 00000, AID_ROOT, AID_ROOT, 0,'
840 '"system/etc/fs_config_dirs" },')
841
842 _NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_FILES_ENTRY = (
843 '{ 00000, AID_ROOT, AID_ROOT, 0,'
844 '"system/etc/fs_config_files" },')
845
846 _IFDEF_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS = (
847 '#ifdef NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS')
848 # pylint: enable=invalid-name
849
850 _ENDIF = '#endif'
851
852 _OPEN_FILE_STRUCT = (
853 'static const struct fs_path_config android_device_files[] = {')
854
855 _OPEN_DIR_STRUCT = (
856 'static const struct fs_path_config android_device_dirs[] = {')
857
858 _CLOSE_FILE_STRUCT = '};'
859
860 _GENERIC_DEFINE = "#define %s\t%s"
861
862 _FILE_COMMENT = '// Defined in file: \"%s\"'
863
864 def add_opts(self, opt_group):
865
866 opt_group.add_argument(
867 'fsconfig', nargs='+', help='The list of fsconfig files to parse')
868
William Roberts64edf5b2016-04-11 17:12:47 -0700869 opt_group.add_argument(
870 '--aid-header',
871 required=True,
872 help='An android_filesystem_config.h file'
873 ' to parse AIDs and OEM Ranges from')
874
William Roberts11c29282016-04-09 10:32:30 -0700875 def __call__(self, args):
876
William Roberts64edf5b2016-04-11 17:12:47 -0700877 hdr = AIDHeaderParser(args['aid_header'])
878 oem_ranges = hdr.oem_ranges
879
880 parser = FSConfigFileParser(args['fsconfig'], oem_ranges)
William Roberts11c29282016-04-09 10:32:30 -0700881 FSConfigGen._generate(parser.files, parser.dirs, parser.aids)
882
883 @staticmethod
884 def _to_fs_entry(fs_config):
885 """
886 Given an FSConfig entry, converts it to a proper
887 array entry for the array entry.
888
889 { mode, user, group, caps, "path" },
890
891 Args:
892 fs_config (FSConfig): The entry to convert to
893 a valid C array entry.
894 """
895
896 # Get some short names
897 mode = fs_config.mode
898 user = fs_config.user
899 group = fs_config.group
900 fname = fs_config.filename
901 caps = fs_config.caps
902 path = fs_config.path
903
904 fmt = '{ %s, %s, %s, %s, "%s" },'
905
906 expanded = fmt % (mode, user, group, caps, path)
907
908 print FSConfigGen._FILE_COMMENT % fname
909 print ' ' + expanded
910
911 @staticmethod
William Robertscfc51f52016-04-12 08:51:13 -0700912 def _gen_inc():
913 """
914 Generate the include header lines and print to stdout.
915 Internal use only.
916 """
917 for include in FSConfigGen._INCLUDES:
918 print '#include %s' % include
919
920 @staticmethod
William Roberts11c29282016-04-09 10:32:30 -0700921 def _generate(files, dirs, aids):
922 """Generates an OEM android_filesystem_config.h header file to stdout.
923
924 Args:
925 files ([FSConfig]): A list of FSConfig objects for file entries.
926 dirs ([FSConfig]): A list of FSConfig objects for directory
927 entries.
928 aids ([AIDS]): A list of AID objects for Android Id entries.
929 """
930 print FSConfigGen._GENERATED
William Robertscfc51f52016-04-12 08:51:13 -0700931 print
932
933 FSConfigGen._gen_inc()
William Robertsc950a352016-03-04 18:12:29 -0800934 print
935
William Roberts11c29282016-04-09 10:32:30 -0700936 are_dirs = len(dirs) > 0
937 are_files = len(files) > 0
938 are_aids = len(aids) > 0
William Robertsc950a352016-03-04 18:12:29 -0800939
William Roberts11c29282016-04-09 10:32:30 -0700940 if are_aids:
941 for aid in aids:
942 # use the preserved _path value
943 print FSConfigGen._FILE_COMMENT % aid.found
944 print FSConfigGen._GENERIC_DEFINE % (aid.identifier, aid.value)
William Robertsc950a352016-03-04 18:12:29 -0800945
William Roberts11c29282016-04-09 10:32:30 -0700946 print
William Robertsc950a352016-03-04 18:12:29 -0800947
948 if not are_dirs:
William Roberts11c29282016-04-09 10:32:30 -0700949 print FSConfigGen._DEFINE_NO_DIRS + '\n'
William Robertsc950a352016-03-04 18:12:29 -0800950
William Roberts11c29282016-04-09 10:32:30 -0700951 if not are_files:
952 print FSConfigGen._DEFINE_NO_FILES + '\n'
William Robertsc950a352016-03-04 18:12:29 -0800953
William Roberts11c29282016-04-09 10:32:30 -0700954 if not are_files and not are_dirs and not are_aids:
955 print FSConfigGen._DEFAULT_WARNING
956 return
William Robertsc950a352016-03-04 18:12:29 -0800957
William Roberts11c29282016-04-09 10:32:30 -0700958 if are_files:
959 print FSConfigGen._OPEN_FILE_STRUCT
960 for fs_config in files:
961 FSConfigGen._to_fs_entry(fs_config)
William Robertsc950a352016-03-04 18:12:29 -0800962
William Roberts11c29282016-04-09 10:32:30 -0700963 if not are_dirs:
964 print FSConfigGen._IFDEF_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS
965 print(
966 ' ' +
967 FSConfigGen._NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS_ENTRY)
968 print FSConfigGen._ENDIF
969 print FSConfigGen._CLOSE_FILE_STRUCT
William Robertsc950a352016-03-04 18:12:29 -0800970
William Roberts11c29282016-04-09 10:32:30 -0700971 if are_dirs:
972 print FSConfigGen._OPEN_DIR_STRUCT
973 for dir_entry in dirs:
974 FSConfigGen._to_fs_entry(dir_entry)
William Robertsc950a352016-03-04 18:12:29 -0800975
William Roberts11c29282016-04-09 10:32:30 -0700976 print FSConfigGen._CLOSE_FILE_STRUCT
William Robertsc950a352016-03-04 18:12:29 -0800977
William Robertsc950a352016-03-04 18:12:29 -0800978
William Robertsd7104bc2016-04-11 21:17:12 -0700979@generator('aidarray')
980class AIDArrayGen(BaseGenerator):
981 """Generates the android_id static array."""
982
983 _GENERATED = ('/*\n'
984 ' * THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY!\n'
985 ' */')
986
987 _INCLUDE = '#include <private/android_filesystem_config.h>'
988
989 _STRUCT_FS_CONFIG = textwrap.dedent("""
990 struct android_id_info {
991 const char *name;
992 unsigned aid;
993 };""")
994
995 _OPEN_ID_ARRAY = 'static const struct android_id_info android_ids[] = {'
996
997 _ID_ENTRY = ' { "%s", %s },'
998
999 _CLOSE_FILE_STRUCT = '};'
1000
1001 _COUNT = ('#define android_id_count \\\n'
1002 ' (sizeof(android_ids) / sizeof(android_ids[0]))')
1003
1004 def add_opts(self, opt_group):
1005
1006 opt_group.add_argument(
1007 'hdrfile', help='The android_filesystem_config.h'
1008 'file to parse')
1009
1010 def __call__(self, args):
1011
1012 hdr = AIDHeaderParser(args['hdrfile'])
1013
1014 print AIDArrayGen._GENERATED
1015 print
1016 print AIDArrayGen._INCLUDE
1017 print
1018 print AIDArrayGen._STRUCT_FS_CONFIG
1019 print
1020 print AIDArrayGen._OPEN_ID_ARRAY
1021
1022 for name, aid in hdr.aids.iteritems():
1023 print AIDArrayGen._ID_ENTRY % (name, aid.identifier)
1024
1025 print AIDArrayGen._CLOSE_FILE_STRUCT
1026 print
1027 print AIDArrayGen._COUNT
1028 print
1029
1030
William Robertscfc51f52016-04-12 08:51:13 -07001031@generator('oemaid')
1032class OEMAidGen(BaseGenerator):
1033 """Generates the OEM AID_<name> value header file."""
1034
1035 _GENERATED = ('/*\n'
1036 ' * THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY!\n'
1037 ' */')
1038
1039 _GENERIC_DEFINE = "#define %s\t%s"
1040
1041 _FILE_COMMENT = '// Defined in file: \"%s\"'
1042
1043 # Intentional trailing newline for readability.
1044 _FILE_IFNDEF_DEFINE = ('#ifndef GENERATED_OEM_AIDS_H_\n'
1045 '#define GENERATED_OEM_AIDS_H_\n')
1046
1047 _FILE_ENDIF = '#endif'
1048
1049 def __init__(self):
1050
1051 self._old_file = None
1052
1053 def add_opts(self, opt_group):
1054
1055 opt_group.add_argument(
1056 'fsconfig', nargs='+', help='The list of fsconfig files to parse.')
1057
1058 opt_group.add_argument(
1059 '--aid-header',
1060 required=True,
1061 help='An android_filesystem_config.h file'
1062 'to parse AIDs and OEM Ranges from')
1063
1064 def __call__(self, args):
1065
1066 hdr_parser = AIDHeaderParser(args['aid_header'])
1067
1068 parser = FSConfigFileParser(args['fsconfig'], hdr_parser.oem_ranges)
1069
1070 print OEMAidGen._GENERATED
1071
1072 print OEMAidGen._FILE_IFNDEF_DEFINE
1073
1074 for aid in parser.aids:
1075 self._print_aid(aid)
1076 print
1077
1078 print OEMAidGen._FILE_ENDIF
1079
1080 def _print_aid(self, aid):
1081 """Prints a valid #define AID identifier to stdout.
1082
1083 Args:
1084 aid to print
1085 """
1086
1087 # print the source file location of the AID
1088 found_file = aid.found
1089 if found_file != self._old_file:
1090 print OEMAidGen._FILE_COMMENT % found_file
1091 self._old_file = found_file
1092
1093 print OEMAidGen._GENERIC_DEFINE % (aid.identifier, aid.value)
1094
1095
William Robertsc950a352016-03-04 18:12:29 -08001096def main():
William Roberts11c29282016-04-09 10:32:30 -07001097 """Main entry point for execution."""
William Robertsc950a352016-03-04 18:12:29 -08001098
William Roberts11c29282016-04-09 10:32:30 -07001099 opt_parser = argparse.ArgumentParser(
1100 description='A tool for parsing fsconfig config files and producing' +
1101 'digestable outputs.')
1102 subparser = opt_parser.add_subparsers(help='generators')
William Robertsc950a352016-03-04 18:12:29 -08001103
William Roberts11c29282016-04-09 10:32:30 -07001104 gens = generator.get()
William Robertsc950a352016-03-04 18:12:29 -08001105
William Roberts11c29282016-04-09 10:32:30 -07001106 # for each gen, instantiate and add them as an option
1107 for name, gen in gens.iteritems():
William Robertsc950a352016-03-04 18:12:29 -08001108
William Roberts11c29282016-04-09 10:32:30 -07001109 generator_option_parser = subparser.add_parser(name, help=gen.__doc__)
1110 generator_option_parser.set_defaults(which=name)
William Roberts8cb6a182016-04-08 22:06:19 -07001111
William Roberts11c29282016-04-09 10:32:30 -07001112 opt_group = generator_option_parser.add_argument_group(name +
1113 ' options')
1114 gen.add_opts(opt_group)
William Roberts8cb6a182016-04-08 22:06:19 -07001115
William Roberts11c29282016-04-09 10:32:30 -07001116 args = opt_parser.parse_args()
1117
1118 args_as_dict = vars(args)
1119 which = args_as_dict['which']
1120 del args_as_dict['which']
1121
1122 gens[which](args_as_dict)
1123
William Robertsc950a352016-03-04 18:12:29 -08001124
1125if __name__ == '__main__':
1126 main()