blob: f995546b129664aa7adf16c5730cbaeb9b45f06a [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
19
William Roberts11c29282016-04-09 10:32:30 -070020# Lowercase generator used to be inline with @staticmethod.
21class generator(object): # pylint: disable=invalid-name
22 """A decorator class to add commandlet plugins.
William Robertsc950a352016-03-04 18:12:29 -080023
William Roberts11c29282016-04-09 10:32:30 -070024 Used as a decorator to classes to add them to
25 the internal plugin interface. Plugins added
26 with @generator() are automatically added to
27 the command line.
William Robertsc950a352016-03-04 18:12:29 -080028
William Roberts11c29282016-04-09 10:32:30 -070029 For instance, to add a new generator
30 called foo and have it added just do this:
William Robertsc950a352016-03-04 18:12:29 -080031
William Roberts11c29282016-04-09 10:32:30 -070032 @generator("foo")
33 class FooGen(object):
34 ...
35 """
36 _generators = {}
William Robertsc950a352016-03-04 18:12:29 -080037
William Roberts11c29282016-04-09 10:32:30 -070038 def __init__(self, gen):
39 """
40 Args:
41 gen (str): The name of the generator to add.
William Robertsc950a352016-03-04 18:12:29 -080042
William Roberts11c29282016-04-09 10:32:30 -070043 Raises:
44 ValueError: If there is a similarly named generator already added.
William Robertsc950a352016-03-04 18:12:29 -080045
William Roberts11c29282016-04-09 10:32:30 -070046 """
47 self._gen = gen
William Robertsc950a352016-03-04 18:12:29 -080048
William Roberts11c29282016-04-09 10:32:30 -070049 if gen in generator._generators:
50 raise ValueError('Duplicate generator name: ' + gen)
William Robertsc950a352016-03-04 18:12:29 -080051
William Roberts11c29282016-04-09 10:32:30 -070052 generator._generators[gen] = None
William Robertsc950a352016-03-04 18:12:29 -080053
William Roberts11c29282016-04-09 10:32:30 -070054 def __call__(self, cls):
55
56 generator._generators[self._gen] = cls()
57 return cls
58
59 @staticmethod
60 def get():
61 """Gets the list of generators.
62
63 Returns:
64 The list of registered generators.
65 """
66 return generator._generators
William Robertsc950a352016-03-04 18:12:29 -080067
68
William Roberts64edf5b2016-04-11 17:12:47 -070069class Utils(object):
70 """Various assorted static utilities."""
71
72 @staticmethod
73 def in_any_range(value, ranges):
74 """Tests if a value is in a list of given closed range tuples.
75
76 A range tuple is a closed range. That means it's inclusive of its
77 start and ending values.
78
79 Args:
80 value (int): The value to test.
81 range [(int, int)]: The closed range list to test value within.
82
83 Returns:
84 True if value is within the closed range, false otherwise.
85 """
86
87 return any(lower <= value <= upper for (lower, upper) in ranges)
88
89
William Roberts11c29282016-04-09 10:32:30 -070090class AID(object):
91 """This class represents an Android ID or an AID.
William Robertsc950a352016-03-04 18:12:29 -080092
William Roberts11c29282016-04-09 10:32:30 -070093 Attributes:
94 identifier (str): The identifier name for a #define.
95 value (str) The User Id (uid) of the associate define.
96 found (str) The file it was found in, can be None.
97 normalized_value (str): Same as value, but base 10.
98 """
William Robertsc950a352016-03-04 18:12:29 -080099
William Roberts11c29282016-04-09 10:32:30 -0700100 def __init__(self, identifier, value, found):
101 """
102 Args:
103 identifier: The identifier name for a #define <identifier>.
104 value: The value of the AID, aka the uid.
105 found (str): The file found in, not required to be specified.
William Robertsc950a352016-03-04 18:12:29 -0800106
William Roberts11c29282016-04-09 10:32:30 -0700107 Raises:
108 ValueError: if value is not a valid string number as processed by
109 int(x, 0)
110 """
111 self.identifier = identifier
112 self.value = value
113 self.found = found
114 self.normalized_value = str(int(value, 0))
William Robertsc950a352016-03-04 18:12:29 -0800115
116
William Roberts11c29282016-04-09 10:32:30 -0700117class FSConfig(object):
118 """Represents a filesystem config array entry.
William Robertsc950a352016-03-04 18:12:29 -0800119
William Roberts11c29282016-04-09 10:32:30 -0700120 Represents a file system configuration entry for specifying
121 file system capabilities.
William Robertsc950a352016-03-04 18:12:29 -0800122
William Roberts11c29282016-04-09 10:32:30 -0700123 Attributes:
124 mode (str): The mode of the file or directory.
125 user (str): The uid or #define identifier (AID_SYSTEM)
126 group (str): The gid or #define identifier (AID_SYSTEM)
127 caps (str): The capability set.
128 filename (str): The file it was found in.
129 """
William Robertsc950a352016-03-04 18:12:29 -0800130
William Roberts11c29282016-04-09 10:32:30 -0700131 def __init__(self, mode, user, group, caps, path, filename):
132 """
133 Args:
134 mode (str): The mode of the file or directory.
135 user (str): The uid or #define identifier (AID_SYSTEM)
136 group (str): The gid or #define identifier (AID_SYSTEM)
137 caps (str): The capability set as a list.
138 filename (str): The file it was found in.
139 """
140 self.mode = mode
141 self.user = user
142 self.group = group
143 self.caps = caps
144 self.path = path
145 self.filename = filename
146
147
William Roberts64edf5b2016-04-11 17:12:47 -0700148class AIDHeaderParser(object):
149 """Parses an android_filesystem_config.h file.
150
151 Parses a C header file and extracts lines starting with #define AID_<name>
152 It provides some basic sanity checks. The information extracted from this
153 file can later be used to sanity check other things (like oem ranges) as
154 well as generating a mapping of names to uids. It was primarily designed to
155 parse the private/android_filesystem_config.h, but any C header should
156 work.
157 """
158
159 _SKIPWORDS = ['UNUSED']
160 _AID_KW = 'AID_'
161 _AID_DEFINE = re.compile(r'\s*#define\s+%s.*' % _AID_KW)
162 _OEM_START_KW = 'START'
163 _OEM_END_KW = 'END'
164 _OEM_RANGE = re.compile('AID_OEM_RESERVED_[0-9]*_{0,1}(%s|%s)' %
165 (_OEM_START_KW, _OEM_END_KW))
166
167 # Some of the AIDS like AID_MEDIA_EX had names like mediaex
168 # list a map of things to fixup until we can correct these
169 # at a later date.
170 _FIXUPS = {
171 'media_drm': 'mediadrm',
172 'media_ex': 'mediaex',
173 'media_codec': 'mediacodec'
174 }
175
176 def __init__(self, aid_header):
177 """
178 Args:
179 aid_header (str): file name for the header
180 file containing AID entries.
181 """
182 self._aid_header = aid_header
183 self._aid_name_to_value = {}
184 self._aid_value_to_name = {}
185 self._oem_ranges = {}
186
187 with open(aid_header) as open_file:
188 self._parse(open_file)
189
190 try:
191 self._process_and_check()
192 except ValueError as exception:
193 sys.exit('Error processing parsed data: "%s"' % (str(exception)))
194
195 def _parse(self, aid_file):
196 """Parses an AID header file. Internal use only.
197
198 Args:
199 aid_file (file): The open AID header file to parse.
200 """
201
202 for lineno, line in enumerate(aid_file):
203 def error_message(msg):
204 """Creates an error message with the current parsing state."""
205 return 'Error "{}" in file: "{}" on line: {}'.format(
206 msg, self._aid_header, str(lineno))
207
208 if AIDHeaderParser._AID_DEFINE.match(line):
209 chunks = line.split()
210
211 if any(x in chunks[1] for x in AIDHeaderParser._SKIPWORDS):
212 continue
213
214 identifier = chunks[1]
215 value = chunks[2]
216
217 try:
218 if AIDHeaderParser._is_oem_range(identifier):
219 self._handle_oem_range(identifier, value)
220 else:
221 self._handle_aid(identifier, value)
222 except ValueError as exception:
223 sys.exit(error_message(
224 '{} for "{}"'.format(exception, identifier)))
225
226 def _handle_aid(self, identifier, value):
227 """Handle an AID C #define.
228
229 Handles an AID, sanity checking, generating the friendly name and
230 adding it to the internal maps. Internal use only.
231
232 Args:
233 identifier (str): The name of the #define identifier. ie AID_FOO.
234 value (str): The value associated with the identifier.
235
236 Raises:
237 ValueError: With message set to indicate the error.
238 """
239
240 # friendly name
241 name = AIDHeaderParser._convert_friendly(identifier)
242
243 # duplicate name
244 if name in self._aid_name_to_value:
245 raise ValueError('Duplicate aid "%s"' % identifier)
246
247 if value in self._aid_value_to_name:
248 raise ValueError('Duplicate aid value "%u" for %s' % value,
249 identifier)
250
251 self._aid_name_to_value[name] = AID(identifier, value, self._aid_header)
252 self._aid_value_to_name[value] = name
253
254 def _handle_oem_range(self, identifier, value):
255 """Handle an OEM range C #define.
256
257 When encountering special AID defines, notably for the OEM ranges
258 this method handles sanity checking and adding them to the internal
259 maps. For internal use only.
260
261 Args:
262 identifier (str): The name of the #define identifier.
263 ie AID_OEM_RESERVED_START/END.
264 value (str): The value associated with the identifier.
265
266 Raises:
267 ValueError: With message set to indicate the error.
268 """
269
270 try:
271 int_value = int(value, 0)
272 except ValueError:
273 raise ValueError(
274 'Could not convert "%s" to integer value, got: "%s"' %
275 (identifier, value))
276
277 # convert AID_OEM_RESERVED_START or AID_OEM_RESERVED_<num>_START
278 # to AID_OEM_RESERVED or AID_OEM_RESERVED_<num>
279 is_start = identifier.endswith(AIDHeaderParser._OEM_START_KW)
280
281 if is_start:
282 tostrip = len(AIDHeaderParser._OEM_START_KW)
283 else:
284 tostrip = len(AIDHeaderParser._OEM_END_KW)
285
286 # ending _
287 tostrip = tostrip + 1
288
289 strip = identifier[:-tostrip]
290 if strip not in self._oem_ranges:
291 self._oem_ranges[strip] = []
292
293 if len(self._oem_ranges[strip]) > 2:
294 raise ValueError('Too many same OEM Ranges "%s"' % identifier)
295
296 if len(self._oem_ranges[strip]) == 1:
297 tmp = self._oem_ranges[strip][0]
298
299 if tmp == int_value:
300 raise ValueError('START and END values equal %u' % int_value)
301 elif is_start and tmp < int_value:
302 raise ValueError('END value %u less than START value %u' %
303 (tmp, int_value))
304 elif not is_start and tmp > int_value:
305 raise ValueError('END value %u less than START value %u' %
306 (int_value, tmp))
307
308 # Add START values to the head of the list and END values at the end.
309 # Thus, the list is ordered with index 0 as START and index 1 as END.
310 if is_start:
311 self._oem_ranges[strip].insert(0, int_value)
312 else:
313 self._oem_ranges[strip].append(int_value)
314
315 def _process_and_check(self):
316 """Process, check and populate internal data structures.
317
318 After parsing and generating the internal data structures, this method
319 is responsible for sanity checking ALL of the acquired data.
320
321 Raises:
322 ValueError: With the message set to indicate the specific error.
323 """
324
325 # tuplefy the lists since range() does not like them mutable.
326 self._oem_ranges = [
327 AIDHeaderParser._convert_lst_to_tup(k, v)
328 for k, v in self._oem_ranges.iteritems()
329 ]
330
331 # Check for overlapping ranges
332 for i, range1 in enumerate(self._oem_ranges):
333 for range2 in self._oem_ranges[i + 1:]:
334 if AIDHeaderParser._is_overlap(range1, range2):
335 raise ValueError("Overlapping OEM Ranges found %s and %s" %
336 (str(range1), str(range2)))
337
338 # No core AIDs should be within any oem range.
339 for aid in self._aid_value_to_name:
340
341 if Utils.in_any_range(aid, self._oem_ranges):
342 name = self._aid_value_to_name[aid]
343 raise ValueError(
344 'AID "%s" value: %u within reserved OEM Range: "%s"' %
345 (name, aid, str(self._oem_ranges)))
346
347 @property
348 def oem_ranges(self):
349 """Retrieves the OEM closed ranges as a list of tuples.
350
351 Returns:
352 A list of closed range tuples: [ (0, 42), (50, 105) ... ]
353 """
354 return self._oem_ranges
355
356 @property
357 def aids(self):
358 """Retrieves the list of found AIDs.
359
360 Returns:
361 A list of AID() objects.
362 """
363 return self._aid_name_to_value.values()
364
365 @staticmethod
366 def _convert_lst_to_tup(name, lst):
367 """Converts a mutable list to a non-mutable tuple.
368
369 Used ONLY for ranges and thus enforces a length of 2.
370
371 Args:
372 lst (List): list that should be "tuplefied".
373
374 Raises:
375 ValueError if lst is not a list or len is not 2.
376
377 Returns:
378 Tuple(lst)
379 """
380 if not lst or len(lst) != 2:
381 raise ValueError('Mismatched range for "%s"' % name)
382
383 return tuple(lst)
384
385 @staticmethod
386 def _convert_friendly(identifier):
387 """
388 Translate AID_FOO_BAR to foo_bar (ie name)
389
390 Args:
391 identifier (str): The name of the #define.
392
393 Returns:
394 The friendly name as a str.
395 """
396
397 name = identifier[len(AIDHeaderParser._AID_KW):].lower()
398
399 if name in AIDHeaderParser._FIXUPS:
400 return AIDHeaderParser._FIXUPS[name]
401
402 return name
403
404 @staticmethod
405 def _is_oem_range(aid):
406 """Detects if a given aid is within the reserved OEM range.
407
408 Args:
409 aid (int): The aid to test
410
411 Returns:
412 True if it is within the range, False otherwise.
413 """
414
415 return AIDHeaderParser._OEM_RANGE.match(aid)
416
417 @staticmethod
418 def _is_overlap(range_a, range_b):
419 """Calculates the overlap of two range tuples.
420
421 A range tuple is a closed range. A closed range includes its endpoints.
422 Note that python tuples use () notation which collides with the
423 mathematical notation for open ranges.
424
425 Args:
426 range_a: The first tuple closed range eg (0, 5).
427 range_b: The second tuple closed range eg (3, 7).
428
429 Returns:
430 True if they overlap, False otherwise.
431 """
432
433 return max(range_a[0], range_b[0]) <= min(range_a[1], range_b[1])
434
435
William Roberts11c29282016-04-09 10:32:30 -0700436class FSConfigFileParser(object):
437 """Parses a config.fs ini format file.
438
439 This class is responsible for parsing the config.fs ini format files.
440 It collects and checks all the data in these files and makes it available
441 for consumption post processed.
442 """
William Roberts11c29282016-04-09 10:32:30 -0700443
444 _AID_MATCH = re.compile('AID_[a-zA-Z]+')
445
William Roberts64edf5b2016-04-11 17:12:47 -0700446 def __init__(self, config_files, oem_ranges):
William Roberts11c29282016-04-09 10:32:30 -0700447 """
448 Args:
449 config_files ([str]): The list of config.fs files to parse.
450 Note the filename is not important.
William Roberts64edf5b2016-04-11 17:12:47 -0700451 oem_ranges ([(),()]): range tuples indicating reserved OEM ranges.
William Roberts11c29282016-04-09 10:32:30 -0700452 """
453
454 self._files = []
455 self._dirs = []
456 self._aids = []
457
458 self._seen_paths = {}
459 # (name to file, value to aid)
460 self._seen_aids = ({}, {})
461
William Roberts64edf5b2016-04-11 17:12:47 -0700462 self._oem_ranges = oem_ranges
463
William Roberts11c29282016-04-09 10:32:30 -0700464 self._config_files = config_files
465
466 for config_file in self._config_files:
467 self._parse(config_file)
468
469 def _parse(self, file_name):
470 """Parses and verifies config.fs files. Internal use only.
471
472 Args:
473 file_name (str): The config.fs (PythonConfigParser file format)
474 file to parse.
475
476 Raises:
477 Anything raised by ConfigParser.read()
478 """
479
480 # Separate config parsers for each file found. If you use
481 # read(filenames...) later files can override earlier files which is
482 # not what we want. Track state across files and enforce with
483 # _handle_dup(). Note, strict ConfigParser is set to true in
484 # Python >= 3.2, so in previous versions same file sections can
485 # override previous
486 # sections.
William Robertsc950a352016-03-04 18:12:29 -0800487
488 config = ConfigParser.ConfigParser()
489 config.read(file_name)
490
William Roberts11c29282016-04-09 10:32:30 -0700491 for section in config.sections():
William Robertsc950a352016-03-04 18:12:29 -0800492
William Roberts11c29282016-04-09 10:32:30 -0700493 if FSConfigFileParser._AID_MATCH.match(
494 section) and config.has_option(section, 'value'):
495 FSConfigFileParser._handle_dup('AID', file_name, section,
496 self._seen_aids[0])
497 self._seen_aids[0][section] = file_name
498 self._handle_aid(file_name, section, config)
William Robertsc950a352016-03-04 18:12:29 -0800499 else:
William Roberts11c29282016-04-09 10:32:30 -0700500 FSConfigFileParser._handle_dup('path', file_name, section,
501 self._seen_paths)
502 self._seen_paths[section] = file_name
503 self._handle_path(file_name, section, config)
William Robertsc950a352016-03-04 18:12:29 -0800504
William Roberts11c29282016-04-09 10:32:30 -0700505 # sort entries:
506 # * specified path before prefix match
507 # ** ie foo before f*
508 # * lexicographical less than before other
509 # ** ie boo before foo
510 # Given these paths:
511 # paths=['ac', 'a', 'acd', 'an', 'a*', 'aa', 'ac*']
512 # The sort order would be:
513 # paths=['a', 'aa', 'ac', 'acd', 'an', 'ac*', 'a*']
514 # Thus the fs_config tools will match on specified paths before
515 # attempting prefix, and match on the longest matching prefix.
516 self._files.sort(key=FSConfigFileParser._file_key)
William Robertsc950a352016-03-04 18:12:29 -0800517
William Roberts11c29282016-04-09 10:32:30 -0700518 # sort on value of (file_name, name, value, strvalue)
519 # This is only cosmetic so AIDS are arranged in ascending order
520 # within the generated file.
521 self._aids.sort(key=lambda item: item.normalized_value)
William Robertsc950a352016-03-04 18:12:29 -0800522
William Roberts11c29282016-04-09 10:32:30 -0700523 def _handle_aid(self, file_name, section_name, config):
524 """Verifies an AID entry and adds it to the aid list.
William Robertsc950a352016-03-04 18:12:29 -0800525
William Roberts11c29282016-04-09 10:32:30 -0700526 Calls sys.exit() with a descriptive message of the failure.
527
528 Args:
529 file_name (str): The filename of the config file being parsed.
530 section_name (str): The section name currently being parsed.
531 config (ConfigParser): The ConfigParser section being parsed that
532 the option values will come from.
533 """
534
535 def error_message(msg):
536 """Creates an error message with current parsing state."""
537 return '{} for: "{}" file: "{}"'.format(msg, section_name,
538 file_name)
539
540 value = config.get(section_name, 'value')
541
542 if not value:
543 sys.exit(error_message('Found specified but unset "value"'))
544
545 try:
546 aid = AID(section_name, value, file_name)
547 except ValueError:
548 sys.exit(
549 error_message('Invalid "value", not aid number, got: \"%s\"' %
550 value))
551
William Roberts64edf5b2016-04-11 17:12:47 -0700552 # Values must be within OEM range
553 if not Utils.in_any_range(int(aid.value, 0), self._oem_ranges):
William Roberts11c29282016-04-09 10:32:30 -0700554 emsg = '"value" not in valid range %s, got: %s'
William Roberts64edf5b2016-04-11 17:12:47 -0700555 emsg = emsg % (str(self._oem_ranges), value)
William Roberts11c29282016-04-09 10:32:30 -0700556 sys.exit(error_message(emsg))
557
558 # use the normalized int value in the dict and detect
559 # duplicate definitions of the same value
560 if aid.normalized_value in self._seen_aids[1]:
561 # map of value to aid name
562 aid = self._seen_aids[1][aid.normalized_value]
563
564 # aid name to file
565 file_name = self._seen_aids[0][aid]
566
567 emsg = 'Duplicate AID value "%s" found on AID: "%s".' % (
568 value, self._seen_aids[1][aid.normalized_value])
569 emsg += ' Previous found in file: "%s."' % file_name
570 sys.exit(error_message(emsg))
571
572 self._seen_aids[1][aid.normalized_value] = section_name
573
574 # Append aid tuple of (AID_*, base10(value), _path(value))
575 # We keep the _path version of value so we can print that out in the
576 # generated header so investigating parties can identify parts.
577 # We store the base10 value for sorting, so everything is ascending
578 # later.
579 self._aids.append(aid)
580
581 def _handle_path(self, file_name, section_name, config):
582 """Add a file capability entry to the internal list.
583
584 Handles a file capability entry, verifies it, and adds it to
585 to the internal dirs or files list based on path. If it ends
586 with a / its a dir. Internal use only.
587
588 Calls sys.exit() on any validation error with message set.
589
590 Args:
591 file_name (str): The current name of the file being parsed.
592 section_name (str): The name of the section to parse.
593 config (str): The config parser.
594 """
595
596 mode = config.get(section_name, 'mode')
597 user = config.get(section_name, 'user')
598 group = config.get(section_name, 'group')
599 caps = config.get(section_name, 'caps')
600
601 errmsg = ('Found specified but unset option: \"%s" in file: \"' +
602 file_name + '\"')
603
604 if not mode:
605 sys.exit(errmsg % 'mode')
606
607 if not user:
608 sys.exit(errmsg % 'user')
609
610 if not group:
611 sys.exit(errmsg % 'group')
612
613 if not caps:
614 sys.exit(errmsg % 'caps')
615
616 caps = caps.split()
617
618 tmp = []
619 for cap in caps:
620 try:
621 # test if string is int, if it is, use as is.
622 int(cap, 0)
623 tmp.append('(' + cap + ')')
624 except ValueError:
625 tmp.append('(1ULL << CAP_' + cap.upper() + ')')
626
627 caps = tmp
628
629 if len(mode) == 3:
630 mode = '0' + mode
631
632 try:
633 int(mode, 8)
634 except ValueError:
635 sys.exit('Mode must be octal characters, got: "%s"' % mode)
636
637 if len(mode) != 4:
638 sys.exit('Mode must be 3 or 4 characters, got: "%s"' % mode)
639
640 caps_str = '|'.join(caps)
641
642 entry = FSConfig(mode, user, group, caps_str, section_name, file_name)
643 if section_name[-1] == '/':
644 self._dirs.append(entry)
645 else:
646 self._files.append(entry)
647
648 @property
649 def files(self):
650 """Get the list of FSConfig file entries.
651
652 Returns:
653 a list of FSConfig() objects for file paths.
654 """
655 return self._files
656
657 @property
658 def dirs(self):
659 """Get the list of FSConfig dir entries.
660
661 Returns:
662 a list of FSConfig() objects for directory paths.
663 """
664 return self._dirs
665
666 @property
667 def aids(self):
668 """Get the list of AID entries.
669
670 Returns:
671 a list of AID() objects.
672 """
673 return self._aids
674
675 @staticmethod
676 def _file_key(fs_config):
677 """Used as the key paramter to sort.
678
679 This is used as a the function to the key parameter of a sort.
680 it wraps the string supplied in a class that implements the
681 appropriate __lt__ operator for the sort on path strings. See
682 StringWrapper class for more details.
683
684 Args:
685 fs_config (FSConfig): A FSConfig entry.
686
687 Returns:
688 A StringWrapper object
689 """
690
691 # Wrapper class for custom prefix matching strings
692 class StringWrapper(object):
693 """Wrapper class used for sorting prefix strings.
694
695 The algorithm is as follows:
696 - specified path before prefix match
697 - ie foo before f*
698 - lexicographical less than before other
699 - ie boo before foo
700
701 Given these paths:
702 paths=['ac', 'a', 'acd', 'an', 'a*', 'aa', 'ac*']
703 The sort order would be:
704 paths=['a', 'aa', 'ac', 'acd', 'an', 'ac*', 'a*']
705 Thus the fs_config tools will match on specified paths before
706 attempting prefix, and match on the longest matching prefix.
707 """
708
709 def __init__(self, path):
710 """
711 Args:
712 path (str): the path string to wrap.
713 """
714 self.is_prefix = path[-1] == '*'
715 if self.is_prefix:
716 self.path = path[:-1]
717 else:
718 self.path = path
719
720 def __lt__(self, other):
721
722 # if were both suffixed the smallest string
723 # is 'bigger'
724 if self.is_prefix and other.is_prefix:
725 result = len(self.path) > len(other.path)
726 # If I am an the suffix match, im bigger
727 elif self.is_prefix:
728 result = False
729 # If other is the suffix match, he's bigger
730 elif other.is_prefix:
731 result = True
732 # Alphabetical
733 else:
734 result = self.path < other.path
735 return result
736
737 return StringWrapper(fs_config.path)
738
739 @staticmethod
740 def _handle_dup(name, file_name, section_name, seen):
741 """Tracks and detects duplicates. Internal use only.
742
743 Calls sys.exit() on a duplicate.
744
745 Args:
746 name (str): The name to use in the error reporting. The pretty
747 name for the section.
748 file_name (str): The file currently being parsed.
749 section_name (str): The name of the section. This would be path
750 or identifier depending on what's being parsed.
751 seen (dict): The dictionary of seen things to check against.
752 """
753 if section_name in seen:
754 dups = '"' + seen[section_name] + '" and '
755 dups += file_name
756 sys.exit('Duplicate %s "%s" found in files: %s' %
757 (name, section_name, dups))
758
759 seen[section_name] = file_name
760
761
762class BaseGenerator(object):
763 """Interface for Generators.
764
765 Base class for generators, generators should implement
766 these method stubs.
767 """
768
769 def add_opts(self, opt_group):
770 """Used to add per-generator options to the command line.
771
772 Args:
773 opt_group (argument group object): The argument group to append to.
774 See the ArgParse docs for more details.
775 """
776
777 raise NotImplementedError("Not Implemented")
778
779 def __call__(self, args):
780 """This is called to do whatever magic the generator does.
781
782 Args:
783 args (dict): The arguments from ArgParse as a dictionary.
784 ie if you specified an argument of foo in add_opts, access
785 it via args['foo']
786 """
787
788 raise NotImplementedError("Not Implemented")
789
790
791@generator('fsconfig')
792class FSConfigGen(BaseGenerator):
793 """Generates the android_filesystem_config.h file.
794
795 Output is used in generating fs_config_files and fs_config_dirs.
796 """
797
798 _GENERATED = textwrap.dedent("""\
799 /*
800 * THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY
801 */
802 """)
803
804 _INCLUDE = '#include <private/android_filesystem_config.h>'
805
806 _DEFINE_NO_DIRS = '#define NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS'
807 _DEFINE_NO_FILES = '#define NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_FILES'
808
809 _DEFAULT_WARNING = (
810 '#warning No device-supplied android_filesystem_config.h,'
811 ' using empty default.')
812
813 # Long names.
814 # pylint: disable=invalid-name
815 _NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS_ENTRY = (
816 '{ 00000, AID_ROOT, AID_ROOT, 0,'
817 '"system/etc/fs_config_dirs" },')
818
819 _NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_FILES_ENTRY = (
820 '{ 00000, AID_ROOT, AID_ROOT, 0,'
821 '"system/etc/fs_config_files" },')
822
823 _IFDEF_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS = (
824 '#ifdef NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS')
825 # pylint: enable=invalid-name
826
827 _ENDIF = '#endif'
828
829 _OPEN_FILE_STRUCT = (
830 'static const struct fs_path_config android_device_files[] = {')
831
832 _OPEN_DIR_STRUCT = (
833 'static const struct fs_path_config android_device_dirs[] = {')
834
835 _CLOSE_FILE_STRUCT = '};'
836
837 _GENERIC_DEFINE = "#define %s\t%s"
838
839 _FILE_COMMENT = '// Defined in file: \"%s\"'
840
841 def add_opts(self, opt_group):
842
843 opt_group.add_argument(
844 'fsconfig', nargs='+', help='The list of fsconfig files to parse')
845
William Roberts64edf5b2016-04-11 17:12:47 -0700846 opt_group.add_argument(
847 '--aid-header',
848 required=True,
849 help='An android_filesystem_config.h file'
850 ' to parse AIDs and OEM Ranges from')
851
William Roberts11c29282016-04-09 10:32:30 -0700852 def __call__(self, args):
853
William Roberts64edf5b2016-04-11 17:12:47 -0700854 hdr = AIDHeaderParser(args['aid_header'])
855 oem_ranges = hdr.oem_ranges
856
857 parser = FSConfigFileParser(args['fsconfig'], oem_ranges)
William Roberts11c29282016-04-09 10:32:30 -0700858 FSConfigGen._generate(parser.files, parser.dirs, parser.aids)
859
860 @staticmethod
861 def _to_fs_entry(fs_config):
862 """
863 Given an FSConfig entry, converts it to a proper
864 array entry for the array entry.
865
866 { mode, user, group, caps, "path" },
867
868 Args:
869 fs_config (FSConfig): The entry to convert to
870 a valid C array entry.
871 """
872
873 # Get some short names
874 mode = fs_config.mode
875 user = fs_config.user
876 group = fs_config.group
877 fname = fs_config.filename
878 caps = fs_config.caps
879 path = fs_config.path
880
881 fmt = '{ %s, %s, %s, %s, "%s" },'
882
883 expanded = fmt % (mode, user, group, caps, path)
884
885 print FSConfigGen._FILE_COMMENT % fname
886 print ' ' + expanded
887
888 @staticmethod
889 def _generate(files, dirs, aids):
890 """Generates an OEM android_filesystem_config.h header file to stdout.
891
892 Args:
893 files ([FSConfig]): A list of FSConfig objects for file entries.
894 dirs ([FSConfig]): A list of FSConfig objects for directory
895 entries.
896 aids ([AIDS]): A list of AID objects for Android Id entries.
897 """
898 print FSConfigGen._GENERATED
899 print FSConfigGen._INCLUDE
William Robertsc950a352016-03-04 18:12:29 -0800900 print
901
William Roberts11c29282016-04-09 10:32:30 -0700902 are_dirs = len(dirs) > 0
903 are_files = len(files) > 0
904 are_aids = len(aids) > 0
William Robertsc950a352016-03-04 18:12:29 -0800905
William Roberts11c29282016-04-09 10:32:30 -0700906 if are_aids:
907 for aid in aids:
908 # use the preserved _path value
909 print FSConfigGen._FILE_COMMENT % aid.found
910 print FSConfigGen._GENERIC_DEFINE % (aid.identifier, aid.value)
William Robertsc950a352016-03-04 18:12:29 -0800911
William Roberts11c29282016-04-09 10:32:30 -0700912 print
William Robertsc950a352016-03-04 18:12:29 -0800913
914 if not are_dirs:
William Roberts11c29282016-04-09 10:32:30 -0700915 print FSConfigGen._DEFINE_NO_DIRS + '\n'
William Robertsc950a352016-03-04 18:12:29 -0800916
William Roberts11c29282016-04-09 10:32:30 -0700917 if not are_files:
918 print FSConfigGen._DEFINE_NO_FILES + '\n'
William Robertsc950a352016-03-04 18:12:29 -0800919
William Roberts11c29282016-04-09 10:32:30 -0700920 if not are_files and not are_dirs and not are_aids:
921 print FSConfigGen._DEFAULT_WARNING
922 return
William Robertsc950a352016-03-04 18:12:29 -0800923
William Roberts11c29282016-04-09 10:32:30 -0700924 if are_files:
925 print FSConfigGen._OPEN_FILE_STRUCT
926 for fs_config in files:
927 FSConfigGen._to_fs_entry(fs_config)
William Robertsc950a352016-03-04 18:12:29 -0800928
William Roberts11c29282016-04-09 10:32:30 -0700929 if not are_dirs:
930 print FSConfigGen._IFDEF_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS
931 print(
932 ' ' +
933 FSConfigGen._NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS_ENTRY)
934 print FSConfigGen._ENDIF
935 print FSConfigGen._CLOSE_FILE_STRUCT
William Robertsc950a352016-03-04 18:12:29 -0800936
William Roberts11c29282016-04-09 10:32:30 -0700937 if are_dirs:
938 print FSConfigGen._OPEN_DIR_STRUCT
939 for dir_entry in dirs:
940 FSConfigGen._to_fs_entry(dir_entry)
William Robertsc950a352016-03-04 18:12:29 -0800941
William Roberts11c29282016-04-09 10:32:30 -0700942 print FSConfigGen._CLOSE_FILE_STRUCT
William Robertsc950a352016-03-04 18:12:29 -0800943
William Robertsc950a352016-03-04 18:12:29 -0800944
945def main():
William Roberts11c29282016-04-09 10:32:30 -0700946 """Main entry point for execution."""
William Robertsc950a352016-03-04 18:12:29 -0800947
William Roberts11c29282016-04-09 10:32:30 -0700948 opt_parser = argparse.ArgumentParser(
949 description='A tool for parsing fsconfig config files and producing' +
950 'digestable outputs.')
951 subparser = opt_parser.add_subparsers(help='generators')
William Robertsc950a352016-03-04 18:12:29 -0800952
William Roberts11c29282016-04-09 10:32:30 -0700953 gens = generator.get()
William Robertsc950a352016-03-04 18:12:29 -0800954
William Roberts11c29282016-04-09 10:32:30 -0700955 # for each gen, instantiate and add them as an option
956 for name, gen in gens.iteritems():
William Robertsc950a352016-03-04 18:12:29 -0800957
William Roberts11c29282016-04-09 10:32:30 -0700958 generator_option_parser = subparser.add_parser(name, help=gen.__doc__)
959 generator_option_parser.set_defaults(which=name)
William Roberts8cb6a182016-04-08 22:06:19 -0700960
William Roberts11c29282016-04-09 10:32:30 -0700961 opt_group = generator_option_parser.add_argument_group(name +
962 ' options')
963 gen.add_opts(opt_group)
William Roberts8cb6a182016-04-08 22:06:19 -0700964
William Roberts11c29282016-04-09 10:32:30 -0700965 args = opt_parser.parse_args()
966
967 args_as_dict = vars(args)
968 which = args_as_dict['which']
969 del args_as_dict['which']
970
971 gens[which](args_as_dict)
972
William Robertsc950a352016-03-04 18:12:29 -0800973
974if __name__ == '__main__':
975 main()