blob: 0b5e8d534d0d47e18132933f75a726b19cbf2559 [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
447 _AID_MATCH = re.compile('AID_[a-zA-Z]+')
448
William Roberts64edf5b2016-04-11 17:12:47 -0700449 def __init__(self, config_files, oem_ranges):
William Roberts11c29282016-04-09 10:32:30 -0700450 """
451 Args:
452 config_files ([str]): The list of config.fs files to parse.
453 Note the filename is not important.
William Roberts64edf5b2016-04-11 17:12:47 -0700454 oem_ranges ([(),()]): range tuples indicating reserved OEM ranges.
William Roberts11c29282016-04-09 10:32:30 -0700455 """
456
457 self._files = []
458 self._dirs = []
459 self._aids = []
460
461 self._seen_paths = {}
462 # (name to file, value to aid)
463 self._seen_aids = ({}, {})
464
William Roberts64edf5b2016-04-11 17:12:47 -0700465 self._oem_ranges = oem_ranges
466
William Roberts11c29282016-04-09 10:32:30 -0700467 self._config_files = config_files
468
469 for config_file in self._config_files:
470 self._parse(config_file)
471
472 def _parse(self, file_name):
473 """Parses and verifies config.fs files. Internal use only.
474
475 Args:
476 file_name (str): The config.fs (PythonConfigParser file format)
477 file to parse.
478
479 Raises:
480 Anything raised by ConfigParser.read()
481 """
482
483 # Separate config parsers for each file found. If you use
484 # read(filenames...) later files can override earlier files which is
485 # not what we want. Track state across files and enforce with
486 # _handle_dup(). Note, strict ConfigParser is set to true in
487 # Python >= 3.2, so in previous versions same file sections can
488 # override previous
489 # sections.
William Robertsc950a352016-03-04 18:12:29 -0800490
491 config = ConfigParser.ConfigParser()
492 config.read(file_name)
493
William Roberts11c29282016-04-09 10:32:30 -0700494 for section in config.sections():
William Robertsc950a352016-03-04 18:12:29 -0800495
William Roberts11c29282016-04-09 10:32:30 -0700496 if FSConfigFileParser._AID_MATCH.match(
497 section) and config.has_option(section, 'value'):
498 FSConfigFileParser._handle_dup('AID', file_name, section,
499 self._seen_aids[0])
500 self._seen_aids[0][section] = file_name
501 self._handle_aid(file_name, section, config)
William Robertsc950a352016-03-04 18:12:29 -0800502 else:
William Roberts11c29282016-04-09 10:32:30 -0700503 FSConfigFileParser._handle_dup('path', file_name, section,
504 self._seen_paths)
505 self._seen_paths[section] = file_name
506 self._handle_path(file_name, section, config)
William Robertsc950a352016-03-04 18:12:29 -0800507
William Roberts11c29282016-04-09 10:32:30 -0700508 # sort entries:
509 # * specified path before prefix match
510 # ** ie foo before f*
511 # * lexicographical less than before other
512 # ** ie boo before foo
513 # Given these paths:
514 # paths=['ac', 'a', 'acd', 'an', 'a*', 'aa', 'ac*']
515 # The sort order would be:
516 # paths=['a', 'aa', 'ac', 'acd', 'an', 'ac*', 'a*']
517 # Thus the fs_config tools will match on specified paths before
518 # attempting prefix, and match on the longest matching prefix.
519 self._files.sort(key=FSConfigFileParser._file_key)
William Robertsc950a352016-03-04 18:12:29 -0800520
William Roberts11c29282016-04-09 10:32:30 -0700521 # sort on value of (file_name, name, value, strvalue)
522 # This is only cosmetic so AIDS are arranged in ascending order
523 # within the generated file.
524 self._aids.sort(key=lambda item: item.normalized_value)
William Robertsc950a352016-03-04 18:12:29 -0800525
William Roberts11c29282016-04-09 10:32:30 -0700526 def _handle_aid(self, file_name, section_name, config):
527 """Verifies an AID entry and adds it to the aid list.
William Robertsc950a352016-03-04 18:12:29 -0800528
William Roberts11c29282016-04-09 10:32:30 -0700529 Calls sys.exit() with a descriptive message of the failure.
530
531 Args:
532 file_name (str): The filename of the config file being parsed.
533 section_name (str): The section name currently being parsed.
534 config (ConfigParser): The ConfigParser section being parsed that
535 the option values will come from.
536 """
537
538 def error_message(msg):
539 """Creates an error message with current parsing state."""
540 return '{} for: "{}" file: "{}"'.format(msg, section_name,
541 file_name)
542
543 value = config.get(section_name, 'value')
544
545 if not value:
546 sys.exit(error_message('Found specified but unset "value"'))
547
548 try:
549 aid = AID(section_name, value, file_name)
550 except ValueError:
551 sys.exit(
552 error_message('Invalid "value", not aid number, got: \"%s\"' %
553 value))
554
William Roberts64edf5b2016-04-11 17:12:47 -0700555 # Values must be within OEM range
556 if not Utils.in_any_range(int(aid.value, 0), self._oem_ranges):
William Roberts11c29282016-04-09 10:32:30 -0700557 emsg = '"value" not in valid range %s, got: %s'
William Roberts64edf5b2016-04-11 17:12:47 -0700558 emsg = emsg % (str(self._oem_ranges), value)
William Roberts11c29282016-04-09 10:32:30 -0700559 sys.exit(error_message(emsg))
560
561 # use the normalized int value in the dict and detect
562 # duplicate definitions of the same value
563 if aid.normalized_value in self._seen_aids[1]:
564 # map of value to aid name
565 aid = self._seen_aids[1][aid.normalized_value]
566
567 # aid name to file
568 file_name = self._seen_aids[0][aid]
569
570 emsg = 'Duplicate AID value "%s" found on AID: "%s".' % (
571 value, self._seen_aids[1][aid.normalized_value])
572 emsg += ' Previous found in file: "%s."' % file_name
573 sys.exit(error_message(emsg))
574
575 self._seen_aids[1][aid.normalized_value] = section_name
576
577 # Append aid tuple of (AID_*, base10(value), _path(value))
578 # We keep the _path version of value so we can print that out in the
579 # generated header so investigating parties can identify parts.
580 # We store the base10 value for sorting, so everything is ascending
581 # later.
582 self._aids.append(aid)
583
584 def _handle_path(self, file_name, section_name, config):
585 """Add a file capability entry to the internal list.
586
587 Handles a file capability entry, verifies it, and adds it to
588 to the internal dirs or files list based on path. If it ends
589 with a / its a dir. Internal use only.
590
591 Calls sys.exit() on any validation error with message set.
592
593 Args:
594 file_name (str): The current name of the file being parsed.
595 section_name (str): The name of the section to parse.
596 config (str): The config parser.
597 """
598
599 mode = config.get(section_name, 'mode')
600 user = config.get(section_name, 'user')
601 group = config.get(section_name, 'group')
602 caps = config.get(section_name, 'caps')
603
604 errmsg = ('Found specified but unset option: \"%s" in file: \"' +
605 file_name + '\"')
606
607 if not mode:
608 sys.exit(errmsg % 'mode')
609
610 if not user:
611 sys.exit(errmsg % 'user')
612
613 if not group:
614 sys.exit(errmsg % 'group')
615
616 if not caps:
617 sys.exit(errmsg % 'caps')
618
619 caps = caps.split()
620
621 tmp = []
622 for cap in caps:
623 try:
624 # test if string is int, if it is, use as is.
625 int(cap, 0)
626 tmp.append('(' + cap + ')')
627 except ValueError:
628 tmp.append('(1ULL << CAP_' + cap.upper() + ')')
629
630 caps = tmp
631
632 if len(mode) == 3:
633 mode = '0' + mode
634
635 try:
636 int(mode, 8)
637 except ValueError:
638 sys.exit('Mode must be octal characters, got: "%s"' % mode)
639
640 if len(mode) != 4:
641 sys.exit('Mode must be 3 or 4 characters, got: "%s"' % mode)
642
643 caps_str = '|'.join(caps)
644
645 entry = FSConfig(mode, user, group, caps_str, section_name, file_name)
646 if section_name[-1] == '/':
647 self._dirs.append(entry)
648 else:
649 self._files.append(entry)
650
651 @property
652 def files(self):
653 """Get the list of FSConfig file entries.
654
655 Returns:
656 a list of FSConfig() objects for file paths.
657 """
658 return self._files
659
660 @property
661 def dirs(self):
662 """Get the list of FSConfig dir entries.
663
664 Returns:
665 a list of FSConfig() objects for directory paths.
666 """
667 return self._dirs
668
669 @property
670 def aids(self):
671 """Get the list of AID entries.
672
673 Returns:
674 a list of AID() objects.
675 """
676 return self._aids
677
678 @staticmethod
679 def _file_key(fs_config):
680 """Used as the key paramter to sort.
681
682 This is used as a the function to the key parameter of a sort.
683 it wraps the string supplied in a class that implements the
684 appropriate __lt__ operator for the sort on path strings. See
685 StringWrapper class for more details.
686
687 Args:
688 fs_config (FSConfig): A FSConfig entry.
689
690 Returns:
691 A StringWrapper object
692 """
693
694 # Wrapper class for custom prefix matching strings
695 class StringWrapper(object):
696 """Wrapper class used for sorting prefix strings.
697
698 The algorithm is as follows:
699 - specified path before prefix match
700 - ie foo before f*
701 - lexicographical less than before other
702 - ie boo before foo
703
704 Given these paths:
705 paths=['ac', 'a', 'acd', 'an', 'a*', 'aa', 'ac*']
706 The sort order would be:
707 paths=['a', 'aa', 'ac', 'acd', 'an', 'ac*', 'a*']
708 Thus the fs_config tools will match on specified paths before
709 attempting prefix, and match on the longest matching prefix.
710 """
711
712 def __init__(self, path):
713 """
714 Args:
715 path (str): the path string to wrap.
716 """
717 self.is_prefix = path[-1] == '*'
718 if self.is_prefix:
719 self.path = path[:-1]
720 else:
721 self.path = path
722
723 def __lt__(self, other):
724
725 # if were both suffixed the smallest string
726 # is 'bigger'
727 if self.is_prefix and other.is_prefix:
728 result = len(self.path) > len(other.path)
729 # If I am an the suffix match, im bigger
730 elif self.is_prefix:
731 result = False
732 # If other is the suffix match, he's bigger
733 elif other.is_prefix:
734 result = True
735 # Alphabetical
736 else:
737 result = self.path < other.path
738 return result
739
740 return StringWrapper(fs_config.path)
741
742 @staticmethod
743 def _handle_dup(name, file_name, section_name, seen):
744 """Tracks and detects duplicates. Internal use only.
745
746 Calls sys.exit() on a duplicate.
747
748 Args:
749 name (str): The name to use in the error reporting. The pretty
750 name for the section.
751 file_name (str): The file currently being parsed.
752 section_name (str): The name of the section. This would be path
753 or identifier depending on what's being parsed.
754 seen (dict): The dictionary of seen things to check against.
755 """
756 if section_name in seen:
757 dups = '"' + seen[section_name] + '" and '
758 dups += file_name
759 sys.exit('Duplicate %s "%s" found in files: %s' %
760 (name, section_name, dups))
761
762 seen[section_name] = file_name
763
764
765class BaseGenerator(object):
766 """Interface for Generators.
767
768 Base class for generators, generators should implement
769 these method stubs.
770 """
771
772 def add_opts(self, opt_group):
773 """Used to add per-generator options to the command line.
774
775 Args:
776 opt_group (argument group object): The argument group to append to.
777 See the ArgParse docs for more details.
778 """
779
780 raise NotImplementedError("Not Implemented")
781
782 def __call__(self, args):
783 """This is called to do whatever magic the generator does.
784
785 Args:
786 args (dict): The arguments from ArgParse as a dictionary.
787 ie if you specified an argument of foo in add_opts, access
788 it via args['foo']
789 """
790
791 raise NotImplementedError("Not Implemented")
792
793
794@generator('fsconfig')
795class FSConfigGen(BaseGenerator):
796 """Generates the android_filesystem_config.h file.
797
798 Output is used in generating fs_config_files and fs_config_dirs.
799 """
800
801 _GENERATED = textwrap.dedent("""\
802 /*
803 * THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY
804 */
805 """)
806
807 _INCLUDE = '#include <private/android_filesystem_config.h>'
808
809 _DEFINE_NO_DIRS = '#define NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS'
810 _DEFINE_NO_FILES = '#define NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_FILES'
811
812 _DEFAULT_WARNING = (
813 '#warning No device-supplied android_filesystem_config.h,'
814 ' using empty default.')
815
816 # Long names.
817 # pylint: disable=invalid-name
818 _NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS_ENTRY = (
819 '{ 00000, AID_ROOT, AID_ROOT, 0,'
820 '"system/etc/fs_config_dirs" },')
821
822 _NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_FILES_ENTRY = (
823 '{ 00000, AID_ROOT, AID_ROOT, 0,'
824 '"system/etc/fs_config_files" },')
825
826 _IFDEF_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS = (
827 '#ifdef NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS')
828 # pylint: enable=invalid-name
829
830 _ENDIF = '#endif'
831
832 _OPEN_FILE_STRUCT = (
833 'static const struct fs_path_config android_device_files[] = {')
834
835 _OPEN_DIR_STRUCT = (
836 'static const struct fs_path_config android_device_dirs[] = {')
837
838 _CLOSE_FILE_STRUCT = '};'
839
840 _GENERIC_DEFINE = "#define %s\t%s"
841
842 _FILE_COMMENT = '// Defined in file: \"%s\"'
843
844 def add_opts(self, opt_group):
845
846 opt_group.add_argument(
847 'fsconfig', nargs='+', help='The list of fsconfig files to parse')
848
William Roberts64edf5b2016-04-11 17:12:47 -0700849 opt_group.add_argument(
850 '--aid-header',
851 required=True,
852 help='An android_filesystem_config.h file'
853 ' to parse AIDs and OEM Ranges from')
854
William Roberts11c29282016-04-09 10:32:30 -0700855 def __call__(self, args):
856
William Roberts64edf5b2016-04-11 17:12:47 -0700857 hdr = AIDHeaderParser(args['aid_header'])
858 oem_ranges = hdr.oem_ranges
859
860 parser = FSConfigFileParser(args['fsconfig'], oem_ranges)
William Roberts11c29282016-04-09 10:32:30 -0700861 FSConfigGen._generate(parser.files, parser.dirs, parser.aids)
862
863 @staticmethod
864 def _to_fs_entry(fs_config):
865 """
866 Given an FSConfig entry, converts it to a proper
867 array entry for the array entry.
868
869 { mode, user, group, caps, "path" },
870
871 Args:
872 fs_config (FSConfig): The entry to convert to
873 a valid C array entry.
874 """
875
876 # Get some short names
877 mode = fs_config.mode
878 user = fs_config.user
879 group = fs_config.group
880 fname = fs_config.filename
881 caps = fs_config.caps
882 path = fs_config.path
883
884 fmt = '{ %s, %s, %s, %s, "%s" },'
885
886 expanded = fmt % (mode, user, group, caps, path)
887
888 print FSConfigGen._FILE_COMMENT % fname
889 print ' ' + expanded
890
891 @staticmethod
892 def _generate(files, dirs, aids):
893 """Generates an OEM android_filesystem_config.h header file to stdout.
894
895 Args:
896 files ([FSConfig]): A list of FSConfig objects for file entries.
897 dirs ([FSConfig]): A list of FSConfig objects for directory
898 entries.
899 aids ([AIDS]): A list of AID objects for Android Id entries.
900 """
901 print FSConfigGen._GENERATED
902 print FSConfigGen._INCLUDE
William Robertsc950a352016-03-04 18:12:29 -0800903 print
904
William Roberts11c29282016-04-09 10:32:30 -0700905 are_dirs = len(dirs) > 0
906 are_files = len(files) > 0
907 are_aids = len(aids) > 0
William Robertsc950a352016-03-04 18:12:29 -0800908
William Roberts11c29282016-04-09 10:32:30 -0700909 if are_aids:
910 for aid in aids:
911 # use the preserved _path value
912 print FSConfigGen._FILE_COMMENT % aid.found
913 print FSConfigGen._GENERIC_DEFINE % (aid.identifier, aid.value)
William Robertsc950a352016-03-04 18:12:29 -0800914
William Roberts11c29282016-04-09 10:32:30 -0700915 print
William Robertsc950a352016-03-04 18:12:29 -0800916
917 if not are_dirs:
William Roberts11c29282016-04-09 10:32:30 -0700918 print FSConfigGen._DEFINE_NO_DIRS + '\n'
William Robertsc950a352016-03-04 18:12:29 -0800919
William Roberts11c29282016-04-09 10:32:30 -0700920 if not are_files:
921 print FSConfigGen._DEFINE_NO_FILES + '\n'
William Robertsc950a352016-03-04 18:12:29 -0800922
William Roberts11c29282016-04-09 10:32:30 -0700923 if not are_files and not are_dirs and not are_aids:
924 print FSConfigGen._DEFAULT_WARNING
925 return
William Robertsc950a352016-03-04 18:12:29 -0800926
William Roberts11c29282016-04-09 10:32:30 -0700927 if are_files:
928 print FSConfigGen._OPEN_FILE_STRUCT
929 for fs_config in files:
930 FSConfigGen._to_fs_entry(fs_config)
William Robertsc950a352016-03-04 18:12:29 -0800931
William Roberts11c29282016-04-09 10:32:30 -0700932 if not are_dirs:
933 print FSConfigGen._IFDEF_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS
934 print(
935 ' ' +
936 FSConfigGen._NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS_ENTRY)
937 print FSConfigGen._ENDIF
938 print FSConfigGen._CLOSE_FILE_STRUCT
William Robertsc950a352016-03-04 18:12:29 -0800939
William Roberts11c29282016-04-09 10:32:30 -0700940 if are_dirs:
941 print FSConfigGen._OPEN_DIR_STRUCT
942 for dir_entry in dirs:
943 FSConfigGen._to_fs_entry(dir_entry)
William Robertsc950a352016-03-04 18:12:29 -0800944
William Roberts11c29282016-04-09 10:32:30 -0700945 print FSConfigGen._CLOSE_FILE_STRUCT
William Robertsc950a352016-03-04 18:12:29 -0800946
William Robertsc950a352016-03-04 18:12:29 -0800947
William Robertsd7104bc2016-04-11 21:17:12 -0700948@generator('aidarray')
949class AIDArrayGen(BaseGenerator):
950 """Generates the android_id static array."""
951
952 _GENERATED = ('/*\n'
953 ' * THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY!\n'
954 ' */')
955
956 _INCLUDE = '#include <private/android_filesystem_config.h>'
957
958 _STRUCT_FS_CONFIG = textwrap.dedent("""
959 struct android_id_info {
960 const char *name;
961 unsigned aid;
962 };""")
963
964 _OPEN_ID_ARRAY = 'static const struct android_id_info android_ids[] = {'
965
966 _ID_ENTRY = ' { "%s", %s },'
967
968 _CLOSE_FILE_STRUCT = '};'
969
970 _COUNT = ('#define android_id_count \\\n'
971 ' (sizeof(android_ids) / sizeof(android_ids[0]))')
972
973 def add_opts(self, opt_group):
974
975 opt_group.add_argument(
976 'hdrfile', help='The android_filesystem_config.h'
977 'file to parse')
978
979 def __call__(self, args):
980
981 hdr = AIDHeaderParser(args['hdrfile'])
982
983 print AIDArrayGen._GENERATED
984 print
985 print AIDArrayGen._INCLUDE
986 print
987 print AIDArrayGen._STRUCT_FS_CONFIG
988 print
989 print AIDArrayGen._OPEN_ID_ARRAY
990
991 for name, aid in hdr.aids.iteritems():
992 print AIDArrayGen._ID_ENTRY % (name, aid.identifier)
993
994 print AIDArrayGen._CLOSE_FILE_STRUCT
995 print
996 print AIDArrayGen._COUNT
997 print
998
999
William Robertsc950a352016-03-04 18:12:29 -08001000def main():
William Roberts11c29282016-04-09 10:32:30 -07001001 """Main entry point for execution."""
William Robertsc950a352016-03-04 18:12:29 -08001002
William Roberts11c29282016-04-09 10:32:30 -07001003 opt_parser = argparse.ArgumentParser(
1004 description='A tool for parsing fsconfig config files and producing' +
1005 'digestable outputs.')
1006 subparser = opt_parser.add_subparsers(help='generators')
William Robertsc950a352016-03-04 18:12:29 -08001007
William Roberts11c29282016-04-09 10:32:30 -07001008 gens = generator.get()
William Robertsc950a352016-03-04 18:12:29 -08001009
William Roberts11c29282016-04-09 10:32:30 -07001010 # for each gen, instantiate and add them as an option
1011 for name, gen in gens.iteritems():
William Robertsc950a352016-03-04 18:12:29 -08001012
William Roberts11c29282016-04-09 10:32:30 -07001013 generator_option_parser = subparser.add_parser(name, help=gen.__doc__)
1014 generator_option_parser.set_defaults(which=name)
William Roberts8cb6a182016-04-08 22:06:19 -07001015
William Roberts11c29282016-04-09 10:32:30 -07001016 opt_group = generator_option_parser.add_argument_group(name +
1017 ' options')
1018 gen.add_opts(opt_group)
William Roberts8cb6a182016-04-08 22:06:19 -07001019
William Roberts11c29282016-04-09 10:32:30 -07001020 args = opt_parser.parse_args()
1021
1022 args_as_dict = vars(args)
1023 which = args_as_dict['which']
1024 del args_as_dict['which']
1025
1026 gens[which](args_as_dict)
1027
William Robertsc950a352016-03-04 18:12:29 -08001028
1029if __name__ == '__main__':
1030 main()