blob: 06130db27c24a85961876afcef678a1d756009e2 [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
William Robertscfc51f52016-04-12 08:51:13 -0700807 _INCLUDES = [
808 '<private/android_filesystem_config.h>', '"generated_oem_aid.h"'
809 ]
William Roberts11c29282016-04-09 10:32:30 -0700810
811 _DEFINE_NO_DIRS = '#define NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS'
812 _DEFINE_NO_FILES = '#define NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_FILES'
813
814 _DEFAULT_WARNING = (
815 '#warning No device-supplied android_filesystem_config.h,'
816 ' using empty default.')
817
818 # Long names.
819 # pylint: disable=invalid-name
820 _NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS_ENTRY = (
821 '{ 00000, AID_ROOT, AID_ROOT, 0,'
822 '"system/etc/fs_config_dirs" },')
823
824 _NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_FILES_ENTRY = (
825 '{ 00000, AID_ROOT, AID_ROOT, 0,'
826 '"system/etc/fs_config_files" },')
827
828 _IFDEF_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS = (
829 '#ifdef NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS')
830 # pylint: enable=invalid-name
831
832 _ENDIF = '#endif'
833
834 _OPEN_FILE_STRUCT = (
835 'static const struct fs_path_config android_device_files[] = {')
836
837 _OPEN_DIR_STRUCT = (
838 'static const struct fs_path_config android_device_dirs[] = {')
839
840 _CLOSE_FILE_STRUCT = '};'
841
842 _GENERIC_DEFINE = "#define %s\t%s"
843
844 _FILE_COMMENT = '// Defined in file: \"%s\"'
845
846 def add_opts(self, opt_group):
847
848 opt_group.add_argument(
849 'fsconfig', nargs='+', help='The list of fsconfig files to parse')
850
William Roberts64edf5b2016-04-11 17:12:47 -0700851 opt_group.add_argument(
852 '--aid-header',
853 required=True,
854 help='An android_filesystem_config.h file'
855 ' to parse AIDs and OEM Ranges from')
856
William Roberts11c29282016-04-09 10:32:30 -0700857 def __call__(self, args):
858
William Roberts64edf5b2016-04-11 17:12:47 -0700859 hdr = AIDHeaderParser(args['aid_header'])
860 oem_ranges = hdr.oem_ranges
861
862 parser = FSConfigFileParser(args['fsconfig'], oem_ranges)
William Roberts11c29282016-04-09 10:32:30 -0700863 FSConfigGen._generate(parser.files, parser.dirs, parser.aids)
864
865 @staticmethod
866 def _to_fs_entry(fs_config):
867 """
868 Given an FSConfig entry, converts it to a proper
869 array entry for the array entry.
870
871 { mode, user, group, caps, "path" },
872
873 Args:
874 fs_config (FSConfig): The entry to convert to
875 a valid C array entry.
876 """
877
878 # Get some short names
879 mode = fs_config.mode
880 user = fs_config.user
881 group = fs_config.group
882 fname = fs_config.filename
883 caps = fs_config.caps
884 path = fs_config.path
885
886 fmt = '{ %s, %s, %s, %s, "%s" },'
887
888 expanded = fmt % (mode, user, group, caps, path)
889
890 print FSConfigGen._FILE_COMMENT % fname
891 print ' ' + expanded
892
893 @staticmethod
William Robertscfc51f52016-04-12 08:51:13 -0700894 def _gen_inc():
895 """
896 Generate the include header lines and print to stdout.
897 Internal use only.
898 """
899 for include in FSConfigGen._INCLUDES:
900 print '#include %s' % include
901
902 @staticmethod
William Roberts11c29282016-04-09 10:32:30 -0700903 def _generate(files, dirs, aids):
904 """Generates an OEM android_filesystem_config.h header file to stdout.
905
906 Args:
907 files ([FSConfig]): A list of FSConfig objects for file entries.
908 dirs ([FSConfig]): A list of FSConfig objects for directory
909 entries.
910 aids ([AIDS]): A list of AID objects for Android Id entries.
911 """
912 print FSConfigGen._GENERATED
William Robertscfc51f52016-04-12 08:51:13 -0700913 print
914
915 FSConfigGen._gen_inc()
William Robertsc950a352016-03-04 18:12:29 -0800916 print
917
William Roberts11c29282016-04-09 10:32:30 -0700918 are_dirs = len(dirs) > 0
919 are_files = len(files) > 0
920 are_aids = len(aids) > 0
William Robertsc950a352016-03-04 18:12:29 -0800921
William Roberts11c29282016-04-09 10:32:30 -0700922 if are_aids:
923 for aid in aids:
924 # use the preserved _path value
925 print FSConfigGen._FILE_COMMENT % aid.found
926 print FSConfigGen._GENERIC_DEFINE % (aid.identifier, aid.value)
William Robertsc950a352016-03-04 18:12:29 -0800927
William Roberts11c29282016-04-09 10:32:30 -0700928 print
William Robertsc950a352016-03-04 18:12:29 -0800929
930 if not are_dirs:
William Roberts11c29282016-04-09 10:32:30 -0700931 print FSConfigGen._DEFINE_NO_DIRS + '\n'
William Robertsc950a352016-03-04 18:12:29 -0800932
William Roberts11c29282016-04-09 10:32:30 -0700933 if not are_files:
934 print FSConfigGen._DEFINE_NO_FILES + '\n'
William Robertsc950a352016-03-04 18:12:29 -0800935
William Roberts11c29282016-04-09 10:32:30 -0700936 if not are_files and not are_dirs and not are_aids:
937 print FSConfigGen._DEFAULT_WARNING
938 return
William Robertsc950a352016-03-04 18:12:29 -0800939
William Roberts11c29282016-04-09 10:32:30 -0700940 if are_files:
941 print FSConfigGen._OPEN_FILE_STRUCT
942 for fs_config in files:
943 FSConfigGen._to_fs_entry(fs_config)
William Robertsc950a352016-03-04 18:12:29 -0800944
William Roberts11c29282016-04-09 10:32:30 -0700945 if not are_dirs:
946 print FSConfigGen._IFDEF_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS
947 print(
948 ' ' +
949 FSConfigGen._NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS_ENTRY)
950 print FSConfigGen._ENDIF
951 print FSConfigGen._CLOSE_FILE_STRUCT
William Robertsc950a352016-03-04 18:12:29 -0800952
William Roberts11c29282016-04-09 10:32:30 -0700953 if are_dirs:
954 print FSConfigGen._OPEN_DIR_STRUCT
955 for dir_entry in dirs:
956 FSConfigGen._to_fs_entry(dir_entry)
William Robertsc950a352016-03-04 18:12:29 -0800957
William Roberts11c29282016-04-09 10:32:30 -0700958 print FSConfigGen._CLOSE_FILE_STRUCT
William Robertsc950a352016-03-04 18:12:29 -0800959
William Robertsc950a352016-03-04 18:12:29 -0800960
William Robertsd7104bc2016-04-11 21:17:12 -0700961@generator('aidarray')
962class AIDArrayGen(BaseGenerator):
963 """Generates the android_id static array."""
964
965 _GENERATED = ('/*\n'
966 ' * THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY!\n'
967 ' */')
968
969 _INCLUDE = '#include <private/android_filesystem_config.h>'
970
971 _STRUCT_FS_CONFIG = textwrap.dedent("""
972 struct android_id_info {
973 const char *name;
974 unsigned aid;
975 };""")
976
977 _OPEN_ID_ARRAY = 'static const struct android_id_info android_ids[] = {'
978
979 _ID_ENTRY = ' { "%s", %s },'
980
981 _CLOSE_FILE_STRUCT = '};'
982
983 _COUNT = ('#define android_id_count \\\n'
984 ' (sizeof(android_ids) / sizeof(android_ids[0]))')
985
986 def add_opts(self, opt_group):
987
988 opt_group.add_argument(
989 'hdrfile', help='The android_filesystem_config.h'
990 'file to parse')
991
992 def __call__(self, args):
993
994 hdr = AIDHeaderParser(args['hdrfile'])
995
996 print AIDArrayGen._GENERATED
997 print
998 print AIDArrayGen._INCLUDE
999 print
1000 print AIDArrayGen._STRUCT_FS_CONFIG
1001 print
1002 print AIDArrayGen._OPEN_ID_ARRAY
1003
1004 for name, aid in hdr.aids.iteritems():
1005 print AIDArrayGen._ID_ENTRY % (name, aid.identifier)
1006
1007 print AIDArrayGen._CLOSE_FILE_STRUCT
1008 print
1009 print AIDArrayGen._COUNT
1010 print
1011
1012
William Robertscfc51f52016-04-12 08:51:13 -07001013@generator('oemaid')
1014class OEMAidGen(BaseGenerator):
1015 """Generates the OEM AID_<name> value header file."""
1016
1017 _GENERATED = ('/*\n'
1018 ' * THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY!\n'
1019 ' */')
1020
1021 _GENERIC_DEFINE = "#define %s\t%s"
1022
1023 _FILE_COMMENT = '// Defined in file: \"%s\"'
1024
1025 # Intentional trailing newline for readability.
1026 _FILE_IFNDEF_DEFINE = ('#ifndef GENERATED_OEM_AIDS_H_\n'
1027 '#define GENERATED_OEM_AIDS_H_\n')
1028
1029 _FILE_ENDIF = '#endif'
1030
1031 def __init__(self):
1032
1033 self._old_file = None
1034
1035 def add_opts(self, opt_group):
1036
1037 opt_group.add_argument(
1038 'fsconfig', nargs='+', help='The list of fsconfig files to parse.')
1039
1040 opt_group.add_argument(
1041 '--aid-header',
1042 required=True,
1043 help='An android_filesystem_config.h file'
1044 'to parse AIDs and OEM Ranges from')
1045
1046 def __call__(self, args):
1047
1048 hdr_parser = AIDHeaderParser(args['aid_header'])
1049
1050 parser = FSConfigFileParser(args['fsconfig'], hdr_parser.oem_ranges)
1051
1052 print OEMAidGen._GENERATED
1053
1054 print OEMAidGen._FILE_IFNDEF_DEFINE
1055
1056 for aid in parser.aids:
1057 self._print_aid(aid)
1058 print
1059
1060 print OEMAidGen._FILE_ENDIF
1061
1062 def _print_aid(self, aid):
1063 """Prints a valid #define AID identifier to stdout.
1064
1065 Args:
1066 aid to print
1067 """
1068
1069 # print the source file location of the AID
1070 found_file = aid.found
1071 if found_file != self._old_file:
1072 print OEMAidGen._FILE_COMMENT % found_file
1073 self._old_file = found_file
1074
1075 print OEMAidGen._GENERIC_DEFINE % (aid.identifier, aid.value)
1076
1077
William Robertsc950a352016-03-04 18:12:29 -08001078def main():
William Roberts11c29282016-04-09 10:32:30 -07001079 """Main entry point for execution."""
William Robertsc950a352016-03-04 18:12:29 -08001080
William Roberts11c29282016-04-09 10:32:30 -07001081 opt_parser = argparse.ArgumentParser(
1082 description='A tool for parsing fsconfig config files and producing' +
1083 'digestable outputs.')
1084 subparser = opt_parser.add_subparsers(help='generators')
William Robertsc950a352016-03-04 18:12:29 -08001085
William Roberts11c29282016-04-09 10:32:30 -07001086 gens = generator.get()
William Robertsc950a352016-03-04 18:12:29 -08001087
William Roberts11c29282016-04-09 10:32:30 -07001088 # for each gen, instantiate and add them as an option
1089 for name, gen in gens.iteritems():
William Robertsc950a352016-03-04 18:12:29 -08001090
William Roberts11c29282016-04-09 10:32:30 -07001091 generator_option_parser = subparser.add_parser(name, help=gen.__doc__)
1092 generator_option_parser.set_defaults(which=name)
William Roberts8cb6a182016-04-08 22:06:19 -07001093
William Roberts11c29282016-04-09 10:32:30 -07001094 opt_group = generator_option_parser.add_argument_group(name +
1095 ' options')
1096 gen.add_opts(opt_group)
William Roberts8cb6a182016-04-08 22:06:19 -07001097
William Roberts11c29282016-04-09 10:32:30 -07001098 args = opt_parser.parse_args()
1099
1100 args_as_dict = vars(args)
1101 which = args_as_dict['which']
1102 del args_as_dict['which']
1103
1104 gens[which](args_as_dict)
1105
William Robertsc950a352016-03-04 18:12:29 -08001106
1107if __name__ == '__main__':
1108 main()