blob: d46db9fad9283a5b454cc41533e4ec655d00d25c [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
William Roberts1c4721c2016-04-26 13:05:34 -070092 @staticmethod
93 def get_login_and_uid_cleansed(aid):
94 """Returns a passwd/group file safe logon and uid.
95
96 This checks that the logon and uid of the AID do not
97 contain the delimiter ":" for a passwd/group file.
98
99 Args:
100 aid (AID): The aid to check
101
102 Returns:
103 logon, uid of the AID after checking its safe.
104
105 Raises:
106 ValueError: If there is a delimiter charcter found.
107 """
108 logon = aid.friendly
109 uid = aid.normalized_value
110 if ':' in uid:
111 raise ValueError(
112 'Cannot specify delimiter character ":" in uid: "%s"' % uid)
113 if ':' in logon:
114 raise ValueError(
115 'Cannot specify delimiter character ":" in logon: "%s"' % logon)
116 return logon, uid
117
William Roberts64edf5b2016-04-11 17:12:47 -0700118
William Roberts11c29282016-04-09 10:32:30 -0700119class AID(object):
120 """This class represents an Android ID or an AID.
William Robertsc950a352016-03-04 18:12:29 -0800121
William Roberts11c29282016-04-09 10:32:30 -0700122 Attributes:
123 identifier (str): The identifier name for a #define.
124 value (str) The User Id (uid) of the associate define.
125 found (str) The file it was found in, can be None.
126 normalized_value (str): Same as value, but base 10.
William Roberts8f42ce72016-04-25 12:27:43 -0700127 friendly (str): The friendly name of aid.
William Roberts11c29282016-04-09 10:32:30 -0700128 """
William Robertsc950a352016-03-04 18:12:29 -0800129
William Roberts8f42ce72016-04-25 12:27:43 -0700130 PREFIX = 'AID_'
131
132 # Some of the AIDS like AID_MEDIA_EX had names like mediaex
133 # list a map of things to fixup until we can correct these
134 # at a later date.
135 _FIXUPS = {
136 'media_drm': 'mediadrm',
137 'media_ex': 'mediaex',
138 'media_codec': 'mediacodec'
139 }
140
William Roberts11c29282016-04-09 10:32:30 -0700141 def __init__(self, identifier, value, found):
142 """
143 Args:
144 identifier: The identifier name for a #define <identifier>.
145 value: The value of the AID, aka the uid.
146 found (str): The file found in, not required to be specified.
William Robertsc950a352016-03-04 18:12:29 -0800147
William Roberts11c29282016-04-09 10:32:30 -0700148 Raises:
149 ValueError: if value is not a valid string number as processed by
150 int(x, 0)
151 """
152 self.identifier = identifier
153 self.value = value
154 self.found = found
155 self.normalized_value = str(int(value, 0))
William Robertsc950a352016-03-04 18:12:29 -0800156
William Roberts8f42ce72016-04-25 12:27:43 -0700157 # Where we calculate the friendly name
158 friendly = identifier[len(AID.PREFIX):].lower()
159 self.friendly = AID._fixup_friendly(friendly)
160
161 @staticmethod
162 def is_friendly(name):
163 """Determines if an AID is a freindly name or C define.
164
165 For example if name is AID_SYSTEM it returns false, if name
166 was system, it would return true.
167
168 Returns:
169 True if name is a friendly name False otherwise.
170 """
171
172 return not name.startswith(AID.PREFIX)
173
174 @staticmethod
175 def _fixup_friendly(friendly):
176 """Fixup friendly names that historically don't follow the convention.
177
178 Args:
179 friendly (str): The friendly name.
180
181 Returns:
182 The fixedup friendly name as a str.
183 """
184
185 if friendly in AID._FIXUPS:
186 return AID._FIXUPS[friendly]
187
188 return friendly
189
William Robertsc950a352016-03-04 18:12:29 -0800190
William Roberts11c29282016-04-09 10:32:30 -0700191class FSConfig(object):
192 """Represents a filesystem config array entry.
William Robertsc950a352016-03-04 18:12:29 -0800193
William Roberts11c29282016-04-09 10:32:30 -0700194 Represents a file system configuration entry for specifying
195 file system capabilities.
William Robertsc950a352016-03-04 18:12:29 -0800196
William Roberts11c29282016-04-09 10:32:30 -0700197 Attributes:
198 mode (str): The mode of the file or directory.
199 user (str): The uid or #define identifier (AID_SYSTEM)
200 group (str): The gid or #define identifier (AID_SYSTEM)
201 caps (str): The capability set.
202 filename (str): The file it was found in.
203 """
William Robertsc950a352016-03-04 18:12:29 -0800204
William Roberts11c29282016-04-09 10:32:30 -0700205 def __init__(self, mode, user, group, caps, path, filename):
206 """
207 Args:
208 mode (str): The mode of the file or directory.
209 user (str): The uid or #define identifier (AID_SYSTEM)
210 group (str): The gid or #define identifier (AID_SYSTEM)
211 caps (str): The capability set as a list.
212 filename (str): The file it was found in.
213 """
214 self.mode = mode
215 self.user = user
216 self.group = group
217 self.caps = caps
218 self.path = path
219 self.filename = filename
220
221
William Roberts64edf5b2016-04-11 17:12:47 -0700222class AIDHeaderParser(object):
223 """Parses an android_filesystem_config.h file.
224
225 Parses a C header file and extracts lines starting with #define AID_<name>
226 It provides some basic sanity checks. The information extracted from this
227 file can later be used to sanity check other things (like oem ranges) as
228 well as generating a mapping of names to uids. It was primarily designed to
229 parse the private/android_filesystem_config.h, but any C header should
230 work.
231 """
232
233 _SKIPWORDS = ['UNUSED']
William Roberts8f42ce72016-04-25 12:27:43 -0700234 _AID_DEFINE = re.compile(r'\s*#define\s+%s.*' % AID.PREFIX)
William Roberts64edf5b2016-04-11 17:12:47 -0700235 _OEM_START_KW = 'START'
236 _OEM_END_KW = 'END'
237 _OEM_RANGE = re.compile('AID_OEM_RESERVED_[0-9]*_{0,1}(%s|%s)' %
238 (_OEM_START_KW, _OEM_END_KW))
239
William Roberts64edf5b2016-04-11 17:12:47 -0700240 def __init__(self, aid_header):
241 """
242 Args:
243 aid_header (str): file name for the header
244 file containing AID entries.
245 """
246 self._aid_header = aid_header
247 self._aid_name_to_value = {}
248 self._aid_value_to_name = {}
249 self._oem_ranges = {}
250
251 with open(aid_header) as open_file:
252 self._parse(open_file)
253
254 try:
255 self._process_and_check()
256 except ValueError as exception:
257 sys.exit('Error processing parsed data: "%s"' % (str(exception)))
258
259 def _parse(self, aid_file):
260 """Parses an AID header file. Internal use only.
261
262 Args:
263 aid_file (file): The open AID header file to parse.
264 """
265
266 for lineno, line in enumerate(aid_file):
267 def error_message(msg):
268 """Creates an error message with the current parsing state."""
269 return 'Error "{}" in file: "{}" on line: {}'.format(
270 msg, self._aid_header, str(lineno))
271
272 if AIDHeaderParser._AID_DEFINE.match(line):
273 chunks = line.split()
274
275 if any(x in chunks[1] for x in AIDHeaderParser._SKIPWORDS):
276 continue
277
278 identifier = chunks[1]
279 value = chunks[2]
280
281 try:
282 if AIDHeaderParser._is_oem_range(identifier):
283 self._handle_oem_range(identifier, value)
284 else:
285 self._handle_aid(identifier, value)
286 except ValueError as exception:
287 sys.exit(error_message(
288 '{} for "{}"'.format(exception, identifier)))
289
290 def _handle_aid(self, identifier, value):
291 """Handle an AID C #define.
292
293 Handles an AID, sanity checking, generating the friendly name and
294 adding it to the internal maps. Internal use only.
295
296 Args:
297 identifier (str): The name of the #define identifier. ie AID_FOO.
298 value (str): The value associated with the identifier.
299
300 Raises:
301 ValueError: With message set to indicate the error.
302 """
303
William Roberts8f42ce72016-04-25 12:27:43 -0700304 aid = AID(identifier, value, self._aid_header)
William Roberts64edf5b2016-04-11 17:12:47 -0700305
306 # duplicate name
William Roberts8f42ce72016-04-25 12:27:43 -0700307 if aid.friendly in self._aid_name_to_value:
William Roberts64edf5b2016-04-11 17:12:47 -0700308 raise ValueError('Duplicate aid "%s"' % identifier)
309
310 if value in self._aid_value_to_name:
311 raise ValueError('Duplicate aid value "%u" for %s' % value,
312 identifier)
313
William Roberts8f42ce72016-04-25 12:27:43 -0700314 self._aid_name_to_value[aid.friendly] = aid
315 self._aid_value_to_name[value] = aid.friendly
William Roberts64edf5b2016-04-11 17:12:47 -0700316
317 def _handle_oem_range(self, identifier, value):
318 """Handle an OEM range C #define.
319
320 When encountering special AID defines, notably for the OEM ranges
321 this method handles sanity checking and adding them to the internal
322 maps. For internal use only.
323
324 Args:
325 identifier (str): The name of the #define identifier.
326 ie AID_OEM_RESERVED_START/END.
327 value (str): The value associated with the identifier.
328
329 Raises:
330 ValueError: With message set to indicate the error.
331 """
332
333 try:
334 int_value = int(value, 0)
335 except ValueError:
336 raise ValueError(
337 'Could not convert "%s" to integer value, got: "%s"' %
338 (identifier, value))
339
340 # convert AID_OEM_RESERVED_START or AID_OEM_RESERVED_<num>_START
341 # to AID_OEM_RESERVED or AID_OEM_RESERVED_<num>
342 is_start = identifier.endswith(AIDHeaderParser._OEM_START_KW)
343
344 if is_start:
345 tostrip = len(AIDHeaderParser._OEM_START_KW)
346 else:
347 tostrip = len(AIDHeaderParser._OEM_END_KW)
348
349 # ending _
350 tostrip = tostrip + 1
351
352 strip = identifier[:-tostrip]
353 if strip not in self._oem_ranges:
354 self._oem_ranges[strip] = []
355
356 if len(self._oem_ranges[strip]) > 2:
357 raise ValueError('Too many same OEM Ranges "%s"' % identifier)
358
359 if len(self._oem_ranges[strip]) == 1:
360 tmp = self._oem_ranges[strip][0]
361
362 if tmp == int_value:
363 raise ValueError('START and END values equal %u' % int_value)
364 elif is_start and tmp < int_value:
365 raise ValueError('END value %u less than START value %u' %
366 (tmp, int_value))
367 elif not is_start and tmp > int_value:
368 raise ValueError('END value %u less than START value %u' %
369 (int_value, tmp))
370
371 # Add START values to the head of the list and END values at the end.
372 # Thus, the list is ordered with index 0 as START and index 1 as END.
373 if is_start:
374 self._oem_ranges[strip].insert(0, int_value)
375 else:
376 self._oem_ranges[strip].append(int_value)
377
378 def _process_and_check(self):
379 """Process, check and populate internal data structures.
380
381 After parsing and generating the internal data structures, this method
382 is responsible for sanity checking ALL of the acquired data.
383
384 Raises:
385 ValueError: With the message set to indicate the specific error.
386 """
387
388 # tuplefy the lists since range() does not like them mutable.
389 self._oem_ranges = [
390 AIDHeaderParser._convert_lst_to_tup(k, v)
391 for k, v in self._oem_ranges.iteritems()
392 ]
393
394 # Check for overlapping ranges
395 for i, range1 in enumerate(self._oem_ranges):
396 for range2 in self._oem_ranges[i + 1:]:
397 if AIDHeaderParser._is_overlap(range1, range2):
398 raise ValueError("Overlapping OEM Ranges found %s and %s" %
399 (str(range1), str(range2)))
400
401 # No core AIDs should be within any oem range.
402 for aid in self._aid_value_to_name:
403
404 if Utils.in_any_range(aid, self._oem_ranges):
405 name = self._aid_value_to_name[aid]
406 raise ValueError(
407 'AID "%s" value: %u within reserved OEM Range: "%s"' %
408 (name, aid, str(self._oem_ranges)))
409
410 @property
411 def oem_ranges(self):
412 """Retrieves the OEM closed ranges as a list of tuples.
413
414 Returns:
415 A list of closed range tuples: [ (0, 42), (50, 105) ... ]
416 """
417 return self._oem_ranges
418
419 @property
420 def aids(self):
421 """Retrieves the list of found AIDs.
422
423 Returns:
424 A list of AID() objects.
425 """
William Roberts8f42ce72016-04-25 12:27:43 -0700426 return self._aid_name_to_value.values()
William Roberts64edf5b2016-04-11 17:12:47 -0700427
428 @staticmethod
429 def _convert_lst_to_tup(name, lst):
430 """Converts a mutable list to a non-mutable tuple.
431
432 Used ONLY for ranges and thus enforces a length of 2.
433
434 Args:
435 lst (List): list that should be "tuplefied".
436
437 Raises:
438 ValueError if lst is not a list or len is not 2.
439
440 Returns:
441 Tuple(lst)
442 """
443 if not lst or len(lst) != 2:
444 raise ValueError('Mismatched range for "%s"' % name)
445
446 return tuple(lst)
447
448 @staticmethod
William Roberts64edf5b2016-04-11 17:12:47 -0700449 def _is_oem_range(aid):
450 """Detects if a given aid is within the reserved OEM range.
451
452 Args:
453 aid (int): The aid to test
454
455 Returns:
456 True if it is within the range, False otherwise.
457 """
458
459 return AIDHeaderParser._OEM_RANGE.match(aid)
460
461 @staticmethod
462 def _is_overlap(range_a, range_b):
463 """Calculates the overlap of two range tuples.
464
465 A range tuple is a closed range. A closed range includes its endpoints.
466 Note that python tuples use () notation which collides with the
467 mathematical notation for open ranges.
468
469 Args:
470 range_a: The first tuple closed range eg (0, 5).
471 range_b: The second tuple closed range eg (3, 7).
472
473 Returns:
474 True if they overlap, False otherwise.
475 """
476
477 return max(range_a[0], range_b[0]) <= min(range_a[1], range_b[1])
478
479
William Roberts11c29282016-04-09 10:32:30 -0700480class FSConfigFileParser(object):
481 """Parses a config.fs ini format file.
482
483 This class is responsible for parsing the config.fs ini format files.
484 It collects and checks all the data in these files and makes it available
485 for consumption post processed.
486 """
William Roberts11c29282016-04-09 10:32:30 -0700487
William Roberts5f059a72016-04-25 10:36:45 -0700488 # These _AID vars work together to ensure that an AID section name
489 # cannot contain invalid characters for a C define or a passwd/group file.
490 # Since _AID_PREFIX is within the set of _AID_MATCH the error logic only
491 # checks end, if you change this, you may have to update the error
492 # detection code.
William Roberts8f42ce72016-04-25 12:27:43 -0700493 _AID_MATCH = re.compile('%s[A-Z0-9_]+' % AID.PREFIX)
William Roberts5f059a72016-04-25 10:36:45 -0700494 _AID_ERR_MSG = 'Expecting upper case, a number or underscore'
495
496 # list of handler to required options, used to identify the
497 # parsing section
498 _SECTIONS = [('_handle_aid', ('value',)),
499 ('_handle_path', ('mode', 'user', 'group', 'caps'))]
William Roberts11c29282016-04-09 10:32:30 -0700500
William Roberts64edf5b2016-04-11 17:12:47 -0700501 def __init__(self, config_files, oem_ranges):
William Roberts11c29282016-04-09 10:32:30 -0700502 """
503 Args:
504 config_files ([str]): The list of config.fs files to parse.
505 Note the filename is not important.
William Roberts64edf5b2016-04-11 17:12:47 -0700506 oem_ranges ([(),()]): range tuples indicating reserved OEM ranges.
William Roberts11c29282016-04-09 10:32:30 -0700507 """
508
509 self._files = []
510 self._dirs = []
511 self._aids = []
512
513 self._seen_paths = {}
514 # (name to file, value to aid)
515 self._seen_aids = ({}, {})
516
William Roberts64edf5b2016-04-11 17:12:47 -0700517 self._oem_ranges = oem_ranges
518
William Roberts11c29282016-04-09 10:32:30 -0700519 self._config_files = config_files
520
521 for config_file in self._config_files:
522 self._parse(config_file)
523
524 def _parse(self, file_name):
525 """Parses and verifies config.fs files. Internal use only.
526
527 Args:
528 file_name (str): The config.fs (PythonConfigParser file format)
529 file to parse.
530
531 Raises:
532 Anything raised by ConfigParser.read()
533 """
534
535 # Separate config parsers for each file found. If you use
536 # read(filenames...) later files can override earlier files which is
537 # not what we want. Track state across files and enforce with
538 # _handle_dup(). Note, strict ConfigParser is set to true in
539 # Python >= 3.2, so in previous versions same file sections can
540 # override previous
541 # sections.
William Robertsc950a352016-03-04 18:12:29 -0800542
543 config = ConfigParser.ConfigParser()
544 config.read(file_name)
545
William Roberts11c29282016-04-09 10:32:30 -0700546 for section in config.sections():
William Robertsc950a352016-03-04 18:12:29 -0800547
William Roberts5f059a72016-04-25 10:36:45 -0700548 found = False
549
550 for test in FSConfigFileParser._SECTIONS:
551 handler = test[0]
552 options = test[1]
553
554 if all([config.has_option(section, item) for item in options]):
555 handler = getattr(self, handler)
556 handler(file_name, section, config)
557 found = True
558 break
559
560 if not found:
561 sys.exit('Invalid section "%s" in file: "%s"' %
562 (section, file_name))
William Robertsc950a352016-03-04 18:12:29 -0800563
William Roberts11c29282016-04-09 10:32:30 -0700564 # sort entries:
565 # * specified path before prefix match
566 # ** ie foo before f*
567 # * lexicographical less than before other
568 # ** ie boo before foo
569 # Given these paths:
570 # paths=['ac', 'a', 'acd', 'an', 'a*', 'aa', 'ac*']
571 # The sort order would be:
572 # paths=['a', 'aa', 'ac', 'acd', 'an', 'ac*', 'a*']
573 # Thus the fs_config tools will match on specified paths before
574 # attempting prefix, and match on the longest matching prefix.
575 self._files.sort(key=FSConfigFileParser._file_key)
William Robertsc950a352016-03-04 18:12:29 -0800576
William Roberts11c29282016-04-09 10:32:30 -0700577 # sort on value of (file_name, name, value, strvalue)
578 # This is only cosmetic so AIDS are arranged in ascending order
579 # within the generated file.
580 self._aids.sort(key=lambda item: item.normalized_value)
William Robertsc950a352016-03-04 18:12:29 -0800581
William Roberts11c29282016-04-09 10:32:30 -0700582 def _handle_aid(self, file_name, section_name, config):
583 """Verifies an AID entry and adds it to the aid list.
William Robertsc950a352016-03-04 18:12:29 -0800584
William Roberts11c29282016-04-09 10:32:30 -0700585 Calls sys.exit() with a descriptive message of the failure.
586
587 Args:
588 file_name (str): The filename of the config file being parsed.
589 section_name (str): The section name currently being parsed.
590 config (ConfigParser): The ConfigParser section being parsed that
591 the option values will come from.
592 """
593
594 def error_message(msg):
595 """Creates an error message with current parsing state."""
596 return '{} for: "{}" file: "{}"'.format(msg, section_name,
597 file_name)
598
William Roberts5f059a72016-04-25 10:36:45 -0700599 FSConfigFileParser._handle_dup_and_add('AID', file_name, section_name,
600 self._seen_aids[0])
601
602 match = FSConfigFileParser._AID_MATCH.match(section_name)
William Roberts8f42ce72016-04-25 12:27:43 -0700603 invalid = match.end() if match else len(AID.PREFIX)
William Roberts5f059a72016-04-25 10:36:45 -0700604 if invalid != len(section_name):
605 tmp_errmsg = ('Invalid characters in AID section at "%d" for: "%s"'
606 % (invalid, FSConfigFileParser._AID_ERR_MSG))
607 sys.exit(error_message(tmp_errmsg))
608
William Roberts11c29282016-04-09 10:32:30 -0700609 value = config.get(section_name, 'value')
610
611 if not value:
612 sys.exit(error_message('Found specified but unset "value"'))
613
614 try:
615 aid = AID(section_name, value, file_name)
616 except ValueError:
617 sys.exit(
618 error_message('Invalid "value", not aid number, got: \"%s\"' %
619 value))
620
William Roberts64edf5b2016-04-11 17:12:47 -0700621 # Values must be within OEM range
622 if not Utils.in_any_range(int(aid.value, 0), self._oem_ranges):
William Roberts11c29282016-04-09 10:32:30 -0700623 emsg = '"value" not in valid range %s, got: %s'
William Roberts64edf5b2016-04-11 17:12:47 -0700624 emsg = emsg % (str(self._oem_ranges), value)
William Roberts11c29282016-04-09 10:32:30 -0700625 sys.exit(error_message(emsg))
626
627 # use the normalized int value in the dict and detect
628 # duplicate definitions of the same value
William Roberts5f059a72016-04-25 10:36:45 -0700629 FSConfigFileParser._handle_dup_and_add(
630 'AID', file_name, aid.normalized_value, self._seen_aids[1])
William Roberts11c29282016-04-09 10:32:30 -0700631
632 # Append aid tuple of (AID_*, base10(value), _path(value))
633 # We keep the _path version of value so we can print that out in the
634 # generated header so investigating parties can identify parts.
635 # We store the base10 value for sorting, so everything is ascending
636 # later.
637 self._aids.append(aid)
638
639 def _handle_path(self, file_name, section_name, config):
640 """Add a file capability entry to the internal list.
641
642 Handles a file capability entry, verifies it, and adds it to
643 to the internal dirs or files list based on path. If it ends
644 with a / its a dir. Internal use only.
645
646 Calls sys.exit() on any validation error with message set.
647
648 Args:
649 file_name (str): The current name of the file being parsed.
650 section_name (str): The name of the section to parse.
651 config (str): The config parser.
652 """
653
William Roberts5f059a72016-04-25 10:36:45 -0700654 FSConfigFileParser._handle_dup_and_add('path', file_name, section_name,
655 self._seen_paths)
656
William Roberts11c29282016-04-09 10:32:30 -0700657 mode = config.get(section_name, 'mode')
658 user = config.get(section_name, 'user')
659 group = config.get(section_name, 'group')
660 caps = config.get(section_name, 'caps')
661
662 errmsg = ('Found specified but unset option: \"%s" in file: \"' +
663 file_name + '\"')
664
665 if not mode:
666 sys.exit(errmsg % 'mode')
667
668 if not user:
669 sys.exit(errmsg % 'user')
670
671 if not group:
672 sys.exit(errmsg % 'group')
673
674 if not caps:
675 sys.exit(errmsg % 'caps')
676
677 caps = caps.split()
678
679 tmp = []
680 for cap in caps:
681 try:
682 # test if string is int, if it is, use as is.
683 int(cap, 0)
684 tmp.append('(' + cap + ')')
685 except ValueError:
686 tmp.append('(1ULL << CAP_' + cap.upper() + ')')
687
688 caps = tmp
689
690 if len(mode) == 3:
691 mode = '0' + mode
692
693 try:
694 int(mode, 8)
695 except ValueError:
696 sys.exit('Mode must be octal characters, got: "%s"' % mode)
697
698 if len(mode) != 4:
699 sys.exit('Mode must be 3 or 4 characters, got: "%s"' % mode)
700
701 caps_str = '|'.join(caps)
702
703 entry = FSConfig(mode, user, group, caps_str, section_name, file_name)
704 if section_name[-1] == '/':
705 self._dirs.append(entry)
706 else:
707 self._files.append(entry)
708
709 @property
710 def files(self):
711 """Get the list of FSConfig file entries.
712
713 Returns:
714 a list of FSConfig() objects for file paths.
715 """
716 return self._files
717
718 @property
719 def dirs(self):
720 """Get the list of FSConfig dir entries.
721
722 Returns:
723 a list of FSConfig() objects for directory paths.
724 """
725 return self._dirs
726
727 @property
728 def aids(self):
729 """Get the list of AID entries.
730
731 Returns:
732 a list of AID() objects.
733 """
734 return self._aids
735
736 @staticmethod
737 def _file_key(fs_config):
738 """Used as the key paramter to sort.
739
740 This is used as a the function to the key parameter of a sort.
741 it wraps the string supplied in a class that implements the
742 appropriate __lt__ operator for the sort on path strings. See
743 StringWrapper class for more details.
744
745 Args:
746 fs_config (FSConfig): A FSConfig entry.
747
748 Returns:
749 A StringWrapper object
750 """
751
752 # Wrapper class for custom prefix matching strings
753 class StringWrapper(object):
754 """Wrapper class used for sorting prefix strings.
755
756 The algorithm is as follows:
757 - specified path before prefix match
758 - ie foo before f*
759 - lexicographical less than before other
760 - ie boo before foo
761
762 Given these paths:
763 paths=['ac', 'a', 'acd', 'an', 'a*', 'aa', 'ac*']
764 The sort order would be:
765 paths=['a', 'aa', 'ac', 'acd', 'an', 'ac*', 'a*']
766 Thus the fs_config tools will match on specified paths before
767 attempting prefix, and match on the longest matching prefix.
768 """
769
770 def __init__(self, path):
771 """
772 Args:
773 path (str): the path string to wrap.
774 """
775 self.is_prefix = path[-1] == '*'
776 if self.is_prefix:
777 self.path = path[:-1]
778 else:
779 self.path = path
780
781 def __lt__(self, other):
782
783 # if were both suffixed the smallest string
784 # is 'bigger'
785 if self.is_prefix and other.is_prefix:
786 result = len(self.path) > len(other.path)
787 # If I am an the suffix match, im bigger
788 elif self.is_prefix:
789 result = False
790 # If other is the suffix match, he's bigger
791 elif other.is_prefix:
792 result = True
793 # Alphabetical
794 else:
795 result = self.path < other.path
796 return result
797
798 return StringWrapper(fs_config.path)
799
800 @staticmethod
William Roberts5f059a72016-04-25 10:36:45 -0700801 def _handle_dup_and_add(name, file_name, section_name, seen):
William Roberts11c29282016-04-09 10:32:30 -0700802 """Tracks and detects duplicates. Internal use only.
803
804 Calls sys.exit() on a duplicate.
805
806 Args:
807 name (str): The name to use in the error reporting. The pretty
808 name for the section.
809 file_name (str): The file currently being parsed.
810 section_name (str): The name of the section. This would be path
811 or identifier depending on what's being parsed.
812 seen (dict): The dictionary of seen things to check against.
813 """
814 if section_name in seen:
815 dups = '"' + seen[section_name] + '" and '
816 dups += file_name
817 sys.exit('Duplicate %s "%s" found in files: %s' %
818 (name, section_name, dups))
819
820 seen[section_name] = file_name
821
822
823class BaseGenerator(object):
824 """Interface for Generators.
825
826 Base class for generators, generators should implement
827 these method stubs.
828 """
829
830 def add_opts(self, opt_group):
831 """Used to add per-generator options to the command line.
832
833 Args:
834 opt_group (argument group object): The argument group to append to.
835 See the ArgParse docs for more details.
836 """
837
838 raise NotImplementedError("Not Implemented")
839
840 def __call__(self, args):
841 """This is called to do whatever magic the generator does.
842
843 Args:
844 args (dict): The arguments from ArgParse as a dictionary.
845 ie if you specified an argument of foo in add_opts, access
846 it via args['foo']
847 """
848
849 raise NotImplementedError("Not Implemented")
850
851
852@generator('fsconfig')
853class FSConfigGen(BaseGenerator):
854 """Generates the android_filesystem_config.h file.
855
856 Output is used in generating fs_config_files and fs_config_dirs.
857 """
858
859 _GENERATED = textwrap.dedent("""\
860 /*
861 * THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY
862 */
863 """)
864
William Robertscfc51f52016-04-12 08:51:13 -0700865 _INCLUDES = [
866 '<private/android_filesystem_config.h>', '"generated_oem_aid.h"'
867 ]
William Roberts11c29282016-04-09 10:32:30 -0700868
869 _DEFINE_NO_DIRS = '#define NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS'
870 _DEFINE_NO_FILES = '#define NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_FILES'
871
872 _DEFAULT_WARNING = (
873 '#warning No device-supplied android_filesystem_config.h,'
874 ' using empty default.')
875
876 # Long names.
877 # pylint: disable=invalid-name
878 _NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS_ENTRY = (
879 '{ 00000, AID_ROOT, AID_ROOT, 0,'
880 '"system/etc/fs_config_dirs" },')
881
882 _NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_FILES_ENTRY = (
883 '{ 00000, AID_ROOT, AID_ROOT, 0,'
884 '"system/etc/fs_config_files" },')
885
886 _IFDEF_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS = (
887 '#ifdef NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS')
888 # pylint: enable=invalid-name
889
890 _ENDIF = '#endif'
891
892 _OPEN_FILE_STRUCT = (
893 'static const struct fs_path_config android_device_files[] = {')
894
895 _OPEN_DIR_STRUCT = (
896 'static const struct fs_path_config android_device_dirs[] = {')
897
898 _CLOSE_FILE_STRUCT = '};'
899
900 _GENERIC_DEFINE = "#define %s\t%s"
901
902 _FILE_COMMENT = '// Defined in file: \"%s\"'
903
William Roberts8f42ce72016-04-25 12:27:43 -0700904 def __init__(self, *args, **kwargs):
905 BaseGenerator.__init__(args, kwargs)
906
907 self._oem_parser = None
908 self._base_parser = None
909 self._friendly_to_aid = None
910
William Roberts11c29282016-04-09 10:32:30 -0700911 def add_opts(self, opt_group):
912
913 opt_group.add_argument(
914 'fsconfig', nargs='+', help='The list of fsconfig files to parse')
915
William Roberts64edf5b2016-04-11 17:12:47 -0700916 opt_group.add_argument(
917 '--aid-header',
918 required=True,
919 help='An android_filesystem_config.h file'
920 ' to parse AIDs and OEM Ranges from')
921
William Roberts11c29282016-04-09 10:32:30 -0700922 def __call__(self, args):
923
William Roberts8f42ce72016-04-25 12:27:43 -0700924 self._base_parser = AIDHeaderParser(args['aid_header'])
925 self._oem_parser = FSConfigFileParser(args['fsconfig'],
926 self._base_parser.oem_ranges)
927 base_aids = self._base_parser.aids
928 oem_aids = self._oem_parser.aids
William Roberts64edf5b2016-04-11 17:12:47 -0700929
William Roberts8f42ce72016-04-25 12:27:43 -0700930 # Detect name collisions on AIDs. Since friendly works as the
931 # identifier for collision testing and we need friendly later on for
932 # name resolution, just calculate and use friendly.
933 # {aid.friendly: aid for aid in base_aids}
934 base_friendly = {aid.friendly: aid for aid in base_aids}
935 oem_friendly = {aid.friendly: aid for aid in oem_aids}
William Roberts11c29282016-04-09 10:32:30 -0700936
William Roberts8f42ce72016-04-25 12:27:43 -0700937 base_set = set(base_friendly.keys())
938 oem_set = set(oem_friendly.keys())
William Roberts11c29282016-04-09 10:32:30 -0700939
William Roberts8f42ce72016-04-25 12:27:43 -0700940 common = base_set & oem_set
941
942 if len(common) > 0:
943 emsg = 'Following AID Collisions detected for: \n'
944 for friendly in common:
945 base = base_friendly[friendly]
946 oem = oem_friendly[friendly]
947 emsg += (
948 'Identifier: "%s" Friendly Name: "%s" '
949 'found in file "%s" and "%s"' %
950 (base.identifier, base.friendly, base.found, oem.found))
951 sys.exit(emsg)
952
953 self._friendly_to_aid = oem_friendly
954 self._friendly_to_aid.update(base_friendly)
955
956 self._generate()
957
958 def _to_fs_entry(self, fs_config):
959 """Converts an FSConfig entry to an fs entry.
960
961 Prints '{ mode, user, group, caps, "path" },'.
962
963 Calls sys.exit() on error.
William Roberts11c29282016-04-09 10:32:30 -0700964
965 Args:
966 fs_config (FSConfig): The entry to convert to
967 a valid C array entry.
968 """
969
970 # Get some short names
971 mode = fs_config.mode
972 user = fs_config.user
973 group = fs_config.group
974 fname = fs_config.filename
975 caps = fs_config.caps
976 path = fs_config.path
977
William Roberts8f42ce72016-04-25 12:27:43 -0700978 emsg = 'Cannot convert friendly name "%s" to identifier!'
979
980 # remap friendly names to identifier names
981 if AID.is_friendly(user):
982 if user not in self._friendly_to_aid:
983 sys.exit(emsg % user)
984 user = self._friendly_to_aid[user].identifier
985
986 if AID.is_friendly(group):
987 if group not in self._friendly_to_aid:
988 sys.exit(emsg % group)
989 group = self._friendly_to_aid[group].identifier
990
William Roberts11c29282016-04-09 10:32:30 -0700991 fmt = '{ %s, %s, %s, %s, "%s" },'
992
993 expanded = fmt % (mode, user, group, caps, path)
994
995 print FSConfigGen._FILE_COMMENT % fname
996 print ' ' + expanded
997
998 @staticmethod
William Robertscfc51f52016-04-12 08:51:13 -0700999 def _gen_inc():
William Roberts8f42ce72016-04-25 12:27:43 -07001000 """Generate the include header lines and print to stdout."""
William Robertscfc51f52016-04-12 08:51:13 -07001001 for include in FSConfigGen._INCLUDES:
1002 print '#include %s' % include
1003
William Roberts8f42ce72016-04-25 12:27:43 -07001004 def _generate(self):
William Roberts11c29282016-04-09 10:32:30 -07001005 """Generates an OEM android_filesystem_config.h header file to stdout.
1006
1007 Args:
1008 files ([FSConfig]): A list of FSConfig objects for file entries.
1009 dirs ([FSConfig]): A list of FSConfig objects for directory
1010 entries.
1011 aids ([AIDS]): A list of AID objects for Android Id entries.
1012 """
1013 print FSConfigGen._GENERATED
William Robertscfc51f52016-04-12 08:51:13 -07001014 print
1015
1016 FSConfigGen._gen_inc()
William Robertsc950a352016-03-04 18:12:29 -08001017 print
1018
William Roberts8f42ce72016-04-25 12:27:43 -07001019 dirs = self._oem_parser.dirs
1020 files = self._oem_parser.files
1021 aids = self._oem_parser.aids
1022
William Roberts11c29282016-04-09 10:32:30 -07001023 are_dirs = len(dirs) > 0
1024 are_files = len(files) > 0
1025 are_aids = len(aids) > 0
William Robertsc950a352016-03-04 18:12:29 -08001026
William Roberts11c29282016-04-09 10:32:30 -07001027 if are_aids:
1028 for aid in aids:
1029 # use the preserved _path value
1030 print FSConfigGen._FILE_COMMENT % aid.found
1031 print FSConfigGen._GENERIC_DEFINE % (aid.identifier, aid.value)
William Robertsc950a352016-03-04 18:12:29 -08001032
William Roberts11c29282016-04-09 10:32:30 -07001033 print
William Robertsc950a352016-03-04 18:12:29 -08001034
1035 if not are_dirs:
William Roberts11c29282016-04-09 10:32:30 -07001036 print FSConfigGen._DEFINE_NO_DIRS + '\n'
William Robertsc950a352016-03-04 18:12:29 -08001037
William Roberts11c29282016-04-09 10:32:30 -07001038 if not are_files:
1039 print FSConfigGen._DEFINE_NO_FILES + '\n'
William Robertsc950a352016-03-04 18:12:29 -08001040
William Roberts11c29282016-04-09 10:32:30 -07001041 if not are_files and not are_dirs and not are_aids:
William Roberts11c29282016-04-09 10:32:30 -07001042 return
William Robertsc950a352016-03-04 18:12:29 -08001043
William Roberts11c29282016-04-09 10:32:30 -07001044 if are_files:
1045 print FSConfigGen._OPEN_FILE_STRUCT
1046 for fs_config in files:
William Roberts8f42ce72016-04-25 12:27:43 -07001047 self._to_fs_entry(fs_config)
William Robertsc950a352016-03-04 18:12:29 -08001048
William Roberts11c29282016-04-09 10:32:30 -07001049 if not are_dirs:
1050 print FSConfigGen._IFDEF_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS
1051 print(
1052 ' ' +
1053 FSConfigGen._NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS_ENTRY)
1054 print FSConfigGen._ENDIF
1055 print FSConfigGen._CLOSE_FILE_STRUCT
William Robertsc950a352016-03-04 18:12:29 -08001056
William Roberts11c29282016-04-09 10:32:30 -07001057 if are_dirs:
1058 print FSConfigGen._OPEN_DIR_STRUCT
1059 for dir_entry in dirs:
William Roberts8f42ce72016-04-25 12:27:43 -07001060 self._to_fs_entry(dir_entry)
William Robertsc950a352016-03-04 18:12:29 -08001061
William Roberts11c29282016-04-09 10:32:30 -07001062 print FSConfigGen._CLOSE_FILE_STRUCT
William Robertsc950a352016-03-04 18:12:29 -08001063
William Robertsc950a352016-03-04 18:12:29 -08001064
William Robertsd7104bc2016-04-11 21:17:12 -07001065@generator('aidarray')
1066class AIDArrayGen(BaseGenerator):
1067 """Generates the android_id static array."""
1068
1069 _GENERATED = ('/*\n'
1070 ' * THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY!\n'
1071 ' */')
1072
1073 _INCLUDE = '#include <private/android_filesystem_config.h>'
1074
1075 _STRUCT_FS_CONFIG = textwrap.dedent("""
1076 struct android_id_info {
1077 const char *name;
1078 unsigned aid;
1079 };""")
1080
1081 _OPEN_ID_ARRAY = 'static const struct android_id_info android_ids[] = {'
1082
1083 _ID_ENTRY = ' { "%s", %s },'
1084
1085 _CLOSE_FILE_STRUCT = '};'
1086
1087 _COUNT = ('#define android_id_count \\\n'
1088 ' (sizeof(android_ids) / sizeof(android_ids[0]))')
1089
1090 def add_opts(self, opt_group):
1091
1092 opt_group.add_argument(
1093 'hdrfile', help='The android_filesystem_config.h'
1094 'file to parse')
1095
1096 def __call__(self, args):
1097
1098 hdr = AIDHeaderParser(args['hdrfile'])
1099
1100 print AIDArrayGen._GENERATED
1101 print
1102 print AIDArrayGen._INCLUDE
1103 print
1104 print AIDArrayGen._STRUCT_FS_CONFIG
1105 print
1106 print AIDArrayGen._OPEN_ID_ARRAY
1107
William Roberts8f42ce72016-04-25 12:27:43 -07001108 for aid in hdr.aids:
1109 print AIDArrayGen._ID_ENTRY % (aid.friendly, aid.identifier)
William Robertsd7104bc2016-04-11 21:17:12 -07001110
1111 print AIDArrayGen._CLOSE_FILE_STRUCT
1112 print
1113 print AIDArrayGen._COUNT
1114 print
1115
1116
William Robertscfc51f52016-04-12 08:51:13 -07001117@generator('oemaid')
1118class OEMAidGen(BaseGenerator):
1119 """Generates the OEM AID_<name> value header file."""
1120
1121 _GENERATED = ('/*\n'
1122 ' * THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY!\n'
1123 ' */')
1124
1125 _GENERIC_DEFINE = "#define %s\t%s"
1126
1127 _FILE_COMMENT = '// Defined in file: \"%s\"'
1128
1129 # Intentional trailing newline for readability.
1130 _FILE_IFNDEF_DEFINE = ('#ifndef GENERATED_OEM_AIDS_H_\n'
1131 '#define GENERATED_OEM_AIDS_H_\n')
1132
1133 _FILE_ENDIF = '#endif'
1134
1135 def __init__(self):
1136
1137 self._old_file = None
1138
1139 def add_opts(self, opt_group):
1140
1141 opt_group.add_argument(
1142 'fsconfig', nargs='+', help='The list of fsconfig files to parse.')
1143
1144 opt_group.add_argument(
1145 '--aid-header',
1146 required=True,
1147 help='An android_filesystem_config.h file'
1148 'to parse AIDs and OEM Ranges from')
1149
1150 def __call__(self, args):
1151
1152 hdr_parser = AIDHeaderParser(args['aid_header'])
1153
1154 parser = FSConfigFileParser(args['fsconfig'], hdr_parser.oem_ranges)
1155
1156 print OEMAidGen._GENERATED
1157
1158 print OEMAidGen._FILE_IFNDEF_DEFINE
1159
1160 for aid in parser.aids:
1161 self._print_aid(aid)
1162 print
1163
1164 print OEMAidGen._FILE_ENDIF
1165
1166 def _print_aid(self, aid):
1167 """Prints a valid #define AID identifier to stdout.
1168
1169 Args:
1170 aid to print
1171 """
1172
1173 # print the source file location of the AID
1174 found_file = aid.found
1175 if found_file != self._old_file:
1176 print OEMAidGen._FILE_COMMENT % found_file
1177 self._old_file = found_file
1178
1179 print OEMAidGen._GENERIC_DEFINE % (aid.identifier, aid.value)
1180
1181
William Roberts316f9462016-04-25 15:00:34 -07001182@generator('passwd')
1183class PasswdGen(BaseGenerator):
1184 """Generates the /etc/passwd file per man (5) passwd."""
1185
1186 _GENERATED = ('#\n# THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY!\n#')
1187
1188 _FILE_COMMENT = '# Defined in file: \"%s\"'
1189
1190 def __init__(self):
1191
1192 self._old_file = None
1193
1194 def add_opts(self, opt_group):
1195
1196 opt_group.add_argument(
1197 'fsconfig', nargs='+', help='The list of fsconfig files to parse.')
1198
1199 opt_group.add_argument(
1200 '--aid-header',
1201 required=True,
1202 help='An android_filesystem_config.h file'
1203 'to parse AIDs and OEM Ranges from')
1204
1205 def __call__(self, args):
1206
1207 hdr_parser = AIDHeaderParser(args['aid_header'])
1208
1209 parser = FSConfigFileParser(args['fsconfig'], hdr_parser.oem_ranges)
1210
1211 aids = parser.aids
1212
1213 # nothing to do if no aids defined
1214 if len(aids) == 0:
1215 return
1216
1217 print PasswdGen._GENERATED
1218
1219 for aid in aids:
William Roberts1c4721c2016-04-26 13:05:34 -07001220 self._print_formatted_line(aid)
William Roberts316f9462016-04-25 15:00:34 -07001221
William Roberts1c4721c2016-04-26 13:05:34 -07001222 def _print_formatted_line(self, aid):
1223 """Prints the aid to stdout in the passwd format. Internal use only.
William Roberts316f9462016-04-25 15:00:34 -07001224
1225 Colon delimited:
1226 login name, friendly name
1227 encrypted password (optional)
1228 uid (int)
1229 gid (int)
1230 User name or comment field
1231 home directory
1232 interpreter (optional)
1233
1234 Args:
1235 aid (AID): The aid to print.
1236 """
1237 if self._old_file != aid.found:
1238 self._old_file = aid.found
1239 print PasswdGen._FILE_COMMENT % aid.found
1240
William Roberts1c4721c2016-04-26 13:05:34 -07001241 try:
1242 logon, uid = Utils.get_login_and_uid_cleansed(aid)
1243 except ValueError as exception:
1244 sys.exit(exception)
William Roberts316f9462016-04-25 15:00:34 -07001245
1246 print "%s::%s:%s::/:/system/bin/sh" % (logon, uid, uid)
1247
1248
William Roberts1c4721c2016-04-26 13:05:34 -07001249@generator('group')
1250class GroupGen(PasswdGen):
1251 """Generates the /etc/group file per man (5) group."""
1252
1253 # Overrides parent
1254 def _print_formatted_line(self, aid):
1255 """Prints the aid to stdout in the group format. Internal use only.
1256
1257 Formatted (per man 5 group) like:
1258 group_name:password:GID:user_list
1259
1260 Args:
1261 aid (AID): The aid to print.
1262 """
1263 if self._old_file != aid.found:
1264 self._old_file = aid.found
1265 print PasswdGen._FILE_COMMENT % aid.found
1266
1267 try:
1268 logon, uid = Utils.get_login_and_uid_cleansed(aid)
1269 except ValueError as exception:
1270 sys.exit(exception)
1271
1272 print "%s::%s:" % (logon, uid)
1273
1274
William Robertsc950a352016-03-04 18:12:29 -08001275def main():
William Roberts11c29282016-04-09 10:32:30 -07001276 """Main entry point for execution."""
William Robertsc950a352016-03-04 18:12:29 -08001277
William Roberts11c29282016-04-09 10:32:30 -07001278 opt_parser = argparse.ArgumentParser(
1279 description='A tool for parsing fsconfig config files and producing' +
1280 'digestable outputs.')
1281 subparser = opt_parser.add_subparsers(help='generators')
William Robertsc950a352016-03-04 18:12:29 -08001282
William Roberts11c29282016-04-09 10:32:30 -07001283 gens = generator.get()
William Robertsc950a352016-03-04 18:12:29 -08001284
William Roberts11c29282016-04-09 10:32:30 -07001285 # for each gen, instantiate and add them as an option
1286 for name, gen in gens.iteritems():
William Robertsc950a352016-03-04 18:12:29 -08001287
William Roberts11c29282016-04-09 10:32:30 -07001288 generator_option_parser = subparser.add_parser(name, help=gen.__doc__)
1289 generator_option_parser.set_defaults(which=name)
William Roberts8cb6a182016-04-08 22:06:19 -07001290
William Roberts11c29282016-04-09 10:32:30 -07001291 opt_group = generator_option_parser.add_argument_group(name +
1292 ' options')
1293 gen.add_opts(opt_group)
William Roberts8cb6a182016-04-08 22:06:19 -07001294
William Roberts11c29282016-04-09 10:32:30 -07001295 args = opt_parser.parse_args()
1296
1297 args_as_dict = vars(args)
1298 which = args_as_dict['which']
1299 del args_as_dict['which']
1300
1301 gens[which](args_as_dict)
1302
William Robertsc950a352016-03-04 18:12:29 -08001303
1304if __name__ == '__main__':
1305 main()