blob: 6b5e03c71896592c597afdfbd381103e944728b6 [file] [log] [blame]
William Robertsc950a352016-03-04 18:12:29 -08001#!/usr/bin/env python
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00002"""Generates config files for Android file system properties.
William Robertsc950a352016-03-04 18:12:29 -08003
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00004This 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
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000017import textwrap
18
19# Keep the tool in one file to make it easy to run.
20# pylint: disable=too-many-lines
William Robertsd7104bc2016-04-11 21:17:12 -070021
William Robertsc950a352016-03-04 18:12:29 -080022
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000023# 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
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000027 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
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000032 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
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000035 @generator("foo")
36 class FooGen(object):
37 ...
38 """
39 _generators = {}
William Robertsc950a352016-03-04 18:12:29 -080040
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000041 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
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000046 Raises:
47 ValueError: If there is a similarly named generator already added.
William Robertsc950a352016-03-04 18:12:29 -080048
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000049 """
50 self._gen = gen
William Robertsc950a352016-03-04 18:12:29 -080051
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000052 if gen in generator._generators:
53 raise ValueError('Duplicate generator name: ' + gen)
William Robertsc950a352016-03-04 18:12:29 -080054
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000055 generator._generators[gen] = None
William Robertsc950a352016-03-04 18:12:29 -080056
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000057 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
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000072class Utils(object):
73 """Various assorted static utilities."""
William Roberts64edf5b2016-04-11 17:12:47 -070074
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000075 @staticmethod
76 def in_any_range(value, ranges):
77 """Tests if a value is in a list of given closed range tuples.
William Roberts64edf5b2016-04-11 17:12:47 -070078
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000079 A range tuple is a closed range. That means it's inclusive of its
80 start and ending values.
William Roberts64edf5b2016-04-11 17:12:47 -070081
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000082 Args:
83 value (int): The value to test.
84 range [(int, int)]: The closed range list to test value within.
William Roberts64edf5b2016-04-11 17:12:47 -070085
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000086 Returns:
87 True if value is within the closed range, false otherwise.
88 """
William Roberts64edf5b2016-04-11 17:12:47 -070089
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000090 return any(lower <= value <= upper for (lower, upper) in ranges)
William Roberts64edf5b2016-04-11 17:12:47 -070091
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000092 @staticmethod
93 def get_login_and_uid_cleansed(aid):
94 """Returns a passwd/group file safe logon and uid.
William Roberts1c4721c2016-04-26 13:05:34 -070095
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000096 This checks that the logon and uid of the AID do not
97 contain the delimiter ":" for a passwd/group file.
William Roberts1c4721c2016-04-26 13:05:34 -070098
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000099 Args:
100 aid (AID): The aid to check
William Roberts1c4721c2016-04-26 13:05:34 -0700101
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000102 Returns:
103 logon, uid of the AID after checking its safe.
William Roberts1c4721c2016-04-26 13:05:34 -0700104
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000105 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
William Roberts1c4721c2016-04-26 13:05:34 -0700117
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000118
119class AID(object):
120 """This class represents an Android ID or an AID.
121
122 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.
127 friendly (str): The friendly name of aid.
128 """
129
130 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
141 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.
147
148 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))
156
157 # Where we calculate the friendly name
158 friendly = identifier[len(AID.PREFIX):].lower()
159 self.friendly = AID._fixup_friendly(friendly)
160
161 def __eq__(self, other):
162
163 return self.identifier == other.identifier \
164 and self.value == other.value and self.found == other.found \
165 and self.normalized_value == other.normalized_value
166
167 @staticmethod
168 def is_friendly(name):
169 """Determines if an AID is a freindly name or C define.
170
171 For example if name is AID_SYSTEM it returns false, if name
172 was system, it would return true.
173
174 Returns:
175 True if name is a friendly name False otherwise.
176 """
177
178 return not name.startswith(AID.PREFIX)
179
180 @staticmethod
181 def _fixup_friendly(friendly):
182 """Fixup friendly names that historically don't follow the convention.
183
184 Args:
185 friendly (str): The friendly name.
186
187 Returns:
188 The fixedup friendly name as a str.
189 """
190
191 if friendly in AID._FIXUPS:
192 return AID._FIXUPS[friendly]
193
194 return friendly
195
196
197class FSConfig(object):
198 """Represents a filesystem config array entry.
199
200 Represents a file system configuration entry for specifying
201 file system capabilities.
202
203 Attributes:
204 mode (str): The mode of the file or directory.
205 user (str): The uid or #define identifier (AID_SYSTEM)
206 group (str): The gid or #define identifier (AID_SYSTEM)
207 caps (str): The capability set.
208 filename (str): The file it was found in.
209 """
210
211 def __init__(self, mode, user, group, caps, path, filename):
212 """
213 Args:
214 mode (str): The mode of the file or directory.
215 user (str): The uid or #define identifier (AID_SYSTEM)
216 group (str): The gid or #define identifier (AID_SYSTEM)
217 caps (str): The capability set as a list.
218 filename (str): The file it was found in.
219 """
220 self.mode = mode
221 self.user = user
222 self.group = group
223 self.caps = caps
224 self.path = path
225 self.filename = filename
226
227 def __eq__(self, other):
228
229 return self.mode == other.mode and self.user == other.user \
230 and self.group == other.group and self.caps == other.caps \
231 and self.path == other.path and self.filename == other.filename
232
233
234class AIDHeaderParser(object):
235 """Parses an android_filesystem_config.h file.
236
237 Parses a C header file and extracts lines starting with #define AID_<name>
238 while capturing the OEM defined ranges and ignoring other ranges. It also
239 skips some hardcoded AIDs it doesn't need to generate a mapping for.
240 It provides some basic sanity checks. The information extracted from this
241 file can later be used to sanity check other things (like oem ranges) as
242 well as generating a mapping of names to uids. It was primarily designed to
243 parse the private/android_filesystem_config.h, but any C header should
244 work.
245 """
246
247
248 _SKIP_AIDS = [
249 re.compile(r'%sUNUSED[0-9].*' % AID.PREFIX),
250 re.compile(r'%sAPP' % AID.PREFIX), re.compile(r'%sUSER' % AID.PREFIX)
251 ]
252 _AID_DEFINE = re.compile(r'\s*#define\s+%s.*' % AID.PREFIX)
253 _OEM_START_KW = 'START'
254 _OEM_END_KW = 'END'
255 _OEM_RANGE = re.compile('%s_OEM_RESERVED_[0-9]*_{0,1}(%s|%s)' %
256 (AID.PREFIX, _OEM_START_KW, _OEM_END_KW))
257 # AID lines cannot end with _START or _END, ie AID_FOO is OK
258 # but AID_FOO_START is skiped. Note that AID_FOOSTART is NOT skipped.
259 _AID_SKIP_RANGE = ['_' + _OEM_START_KW, '_' + _OEM_END_KW]
260 _COLLISION_OK = ['AID_APP', 'AID_APP_START', 'AID_USER', 'AID_USER_OFFSET']
261
262 def __init__(self, aid_header):
263 """
264 Args:
265 aid_header (str): file name for the header
266 file containing AID entries.
267 """
268 self._aid_header = aid_header
269 self._aid_name_to_value = {}
270 self._aid_value_to_name = {}
271 self._oem_ranges = {}
272
273 with open(aid_header) as open_file:
274 self._parse(open_file)
William Roberts64edf5b2016-04-11 17:12:47 -0700275
276 try:
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000277 self._process_and_check()
278 except ValueError as exception:
279 sys.exit('Error processing parsed data: "%s"' % (str(exception)))
280
281 def _parse(self, aid_file):
282 """Parses an AID header file. Internal use only.
283
284 Args:
285 aid_file (file): The open AID header file to parse.
286 """
287
288 for lineno, line in enumerate(aid_file):
289 def error_message(msg):
290 """Creates an error message with the current parsing state."""
291 return 'Error "{}" in file: "{}" on line: {}'.format(
292 msg, self._aid_header, str(lineno))
293
294 if AIDHeaderParser._AID_DEFINE.match(line):
295 chunks = line.split()
296 identifier = chunks[1]
297 value = chunks[2]
298
299 if any(x.match(identifier) for x in AIDHeaderParser._SKIP_AIDS):
300 continue
301
302 try:
303 if AIDHeaderParser._is_oem_range(identifier):
304 self._handle_oem_range(identifier, value)
305 elif not any(
306 identifier.endswith(x)
307 for x in AIDHeaderParser._AID_SKIP_RANGE):
308 self._handle_aid(identifier, value)
309 except ValueError as exception:
310 sys.exit(error_message(
311 '{} for "{}"'.format(exception, identifier)))
312
313 def _handle_aid(self, identifier, value):
314 """Handle an AID C #define.
315
316 Handles an AID, sanity checking, generating the friendly name and
317 adding it to the internal maps. Internal use only.
318
319 Args:
320 identifier (str): The name of the #define identifier. ie AID_FOO.
321 value (str): The value associated with the identifier.
322
323 Raises:
324 ValueError: With message set to indicate the error.
325 """
326
327 aid = AID(identifier, value, self._aid_header)
328
329 # duplicate name
330 if aid.friendly in self._aid_name_to_value:
331 raise ValueError('Duplicate aid "%s"' % identifier)
332
333 if value in self._aid_value_to_name and aid.identifier not in AIDHeaderParser._COLLISION_OK:
334 raise ValueError('Duplicate aid value "%s" for %s' % (value,
335 identifier))
336
337 self._aid_name_to_value[aid.friendly] = aid
338 self._aid_value_to_name[value] = aid.friendly
339
340 def _handle_oem_range(self, identifier, value):
341 """Handle an OEM range C #define.
342
343 When encountering special AID defines, notably for the OEM ranges
344 this method handles sanity checking and adding them to the internal
345 maps. For internal use only.
346
347 Args:
348 identifier (str): The name of the #define identifier.
349 ie AID_OEM_RESERVED_START/END.
350 value (str): The value associated with the identifier.
351
352 Raises:
353 ValueError: With message set to indicate the error.
354 """
355
356 try:
357 int_value = int(value, 0)
William Roberts64edf5b2016-04-11 17:12:47 -0700358 except ValueError:
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000359 raise ValueError(
360 'Could not convert "%s" to integer value, got: "%s"' %
361 (identifier, value))
William Roberts64edf5b2016-04-11 17:12:47 -0700362
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000363 # convert AID_OEM_RESERVED_START or AID_OEM_RESERVED_<num>_START
364 # to AID_OEM_RESERVED or AID_OEM_RESERVED_<num>
365 is_start = identifier.endswith(AIDHeaderParser._OEM_START_KW)
William Roberts64edf5b2016-04-11 17:12:47 -0700366
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000367 if is_start:
368 tostrip = len(AIDHeaderParser._OEM_START_KW)
369 else:
370 tostrip = len(AIDHeaderParser._OEM_END_KW)
William Roberts64edf5b2016-04-11 17:12:47 -0700371
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000372 # ending _
373 tostrip = tostrip + 1
William Roberts64edf5b2016-04-11 17:12:47 -0700374
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000375 strip = identifier[:-tostrip]
376 if strip not in self._oem_ranges:
377 self._oem_ranges[strip] = []
William Roberts64edf5b2016-04-11 17:12:47 -0700378
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000379 if len(self._oem_ranges[strip]) > 2:
380 raise ValueError('Too many same OEM Ranges "%s"' % identifier)
William Roberts64edf5b2016-04-11 17:12:47 -0700381
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000382 if len(self._oem_ranges[strip]) == 1:
383 tmp = self._oem_ranges[strip][0]
William Roberts64edf5b2016-04-11 17:12:47 -0700384
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000385 if tmp == int_value:
386 raise ValueError('START and END values equal %u' % int_value)
387 elif is_start and tmp < int_value:
388 raise ValueError('END value %u less than START value %u' %
389 (tmp, int_value))
390 elif not is_start and tmp > int_value:
391 raise ValueError('END value %u less than START value %u' %
392 (int_value, tmp))
William Roberts64edf5b2016-04-11 17:12:47 -0700393
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000394 # Add START values to the head of the list and END values at the end.
395 # Thus, the list is ordered with index 0 as START and index 1 as END.
396 if is_start:
397 self._oem_ranges[strip].insert(0, int_value)
398 else:
399 self._oem_ranges[strip].append(int_value)
William Roberts64edf5b2016-04-11 17:12:47 -0700400
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000401 def _process_and_check(self):
402 """Process, check and populate internal data structures.
William Roberts64edf5b2016-04-11 17:12:47 -0700403
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000404 After parsing and generating the internal data structures, this method
405 is responsible for sanity checking ALL of the acquired data.
William Roberts64edf5b2016-04-11 17:12:47 -0700406
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000407 Raises:
408 ValueError: With the message set to indicate the specific error.
409 """
William Roberts64edf5b2016-04-11 17:12:47 -0700410
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000411 # tuplefy the lists since range() does not like them mutable.
412 self._oem_ranges = [
413 AIDHeaderParser._convert_lst_to_tup(k, v)
414 for k, v in self._oem_ranges.iteritems()
415 ]
William Roberts64edf5b2016-04-11 17:12:47 -0700416
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000417 # Check for overlapping ranges
418 for i, range1 in enumerate(self._oem_ranges):
419 for range2 in self._oem_ranges[i + 1:]:
420 if AIDHeaderParser._is_overlap(range1, range2):
421 raise ValueError("Overlapping OEM Ranges found %s and %s" %
422 (str(range1), str(range2)))
William Roberts64edf5b2016-04-11 17:12:47 -0700423
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000424 # No core AIDs should be within any oem range.
425 for aid in self._aid_value_to_name:
426
427 if Utils.in_any_range(aid, self._oem_ranges):
428 name = self._aid_value_to_name[aid]
429 raise ValueError(
430 'AID "%s" value: %u within reserved OEM Range: "%s"' %
431 (name, aid, str(self._oem_ranges)))
432
433 @property
434 def oem_ranges(self):
435 """Retrieves the OEM closed ranges as a list of tuples.
436
437 Returns:
438 A list of closed range tuples: [ (0, 42), (50, 105) ... ]
439 """
440 return self._oem_ranges
441
442 @property
443 def aids(self):
444 """Retrieves the list of found AIDs.
445
446 Returns:
447 A list of AID() objects.
448 """
449 return self._aid_name_to_value.values()
450
451 @staticmethod
452 def _convert_lst_to_tup(name, lst):
453 """Converts a mutable list to a non-mutable tuple.
454
455 Used ONLY for ranges and thus enforces a length of 2.
456
457 Args:
458 lst (List): list that should be "tuplefied".
459
460 Raises:
461 ValueError if lst is not a list or len is not 2.
462
463 Returns:
464 Tuple(lst)
465 """
466 if not lst or len(lst) != 2:
467 raise ValueError('Mismatched range for "%s"' % name)
468
469 return tuple(lst)
470
471 @staticmethod
472 def _is_oem_range(aid):
473 """Detects if a given aid is within the reserved OEM range.
474
475 Args:
476 aid (int): The aid to test
477
478 Returns:
479 True if it is within the range, False otherwise.
480 """
481
482 return AIDHeaderParser._OEM_RANGE.match(aid)
483
484 @staticmethod
485 def _is_overlap(range_a, range_b):
486 """Calculates the overlap of two range tuples.
487
488 A range tuple is a closed range. A closed range includes its endpoints.
489 Note that python tuples use () notation which collides with the
490 mathematical notation for open ranges.
491
492 Args:
493 range_a: The first tuple closed range eg (0, 5).
494 range_b: The second tuple closed range eg (3, 7).
495
496 Returns:
497 True if they overlap, False otherwise.
498 """
499
500 return max(range_a[0], range_b[0]) <= min(range_a[1], range_b[1])
William Roberts64edf5b2016-04-11 17:12:47 -0700501
502
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000503class FSConfigFileParser(object):
504 """Parses a config.fs ini format file.
William Roberts11c29282016-04-09 10:32:30 -0700505
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000506 This class is responsible for parsing the config.fs ini format files.
507 It collects and checks all the data in these files and makes it available
508 for consumption post processed.
509 """
William Roberts11c29282016-04-09 10:32:30 -0700510
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000511 # These _AID vars work together to ensure that an AID section name
512 # cannot contain invalid characters for a C define or a passwd/group file.
513 # Since _AID_PREFIX is within the set of _AID_MATCH the error logic only
514 # checks end, if you change this, you may have to update the error
515 # detection code.
516 _AID_MATCH = re.compile('%s[A-Z0-9_]+' % AID.PREFIX)
517 _AID_ERR_MSG = 'Expecting upper case, a number or underscore'
William Roberts5f059a72016-04-25 10:36:45 -0700518
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000519 # list of handler to required options, used to identify the
520 # parsing section
521 _SECTIONS = [('_handle_aid', ('value',)),
522 ('_handle_path', ('mode', 'user', 'group', 'caps'))]
523
524 def __init__(self, config_files, oem_ranges):
525 """
526 Args:
527 config_files ([str]): The list of config.fs files to parse.
528 Note the filename is not important.
529 oem_ranges ([(),()]): range tuples indicating reserved OEM ranges.
530 """
531
532 self._files = []
533 self._dirs = []
534 self._aids = []
535
536 self._seen_paths = {}
537 # (name to file, value to aid)
538 self._seen_aids = ({}, {})
539
540 self._oem_ranges = oem_ranges
541
542 self._config_files = config_files
543
544 for config_file in self._config_files:
545 self._parse(config_file)
546
547 def _parse(self, file_name):
548 """Parses and verifies config.fs files. Internal use only.
549
550 Args:
551 file_name (str): The config.fs (PythonConfigParser file format)
552 file to parse.
553
554 Raises:
555 Anything raised by ConfigParser.read()
556 """
557
558 # Separate config parsers for each file found. If you use
559 # read(filenames...) later files can override earlier files which is
560 # not what we want. Track state across files and enforce with
561 # _handle_dup(). Note, strict ConfigParser is set to true in
562 # Python >= 3.2, so in previous versions same file sections can
563 # override previous
564 # sections.
William Robertsc950a352016-03-04 18:12:29 -0800565
566 config = ConfigParser.ConfigParser()
567 config.read(file_name)
568
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000569 for section in config.sections():
William Robertsc950a352016-03-04 18:12:29 -0800570
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000571 found = False
William Roberts5f059a72016-04-25 10:36:45 -0700572
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000573 for test in FSConfigFileParser._SECTIONS:
574 handler = test[0]
575 options = test[1]
William Roberts5f059a72016-04-25 10:36:45 -0700576
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000577 if all([config.has_option(section, item) for item in options]):
578 handler = getattr(self, handler)
579 handler(file_name, section, config)
580 found = True
581 break
William Roberts5f059a72016-04-25 10:36:45 -0700582
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000583 if not found:
584 sys.exit('Invalid section "%s" in file: "%s"' %
585 (section, file_name))
William Roberts11c29282016-04-09 10:32:30 -0700586
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000587 # sort entries:
588 # * specified path before prefix match
589 # ** ie foo before f*
590 # * lexicographical less than before other
591 # ** ie boo before foo
592 # Given these paths:
593 # paths=['ac', 'a', 'acd', 'an', 'a*', 'aa', 'ac*']
594 # The sort order would be:
595 # paths=['a', 'aa', 'ac', 'acd', 'an', 'ac*', 'a*']
596 # Thus the fs_config tools will match on specified paths before
597 # attempting prefix, and match on the longest matching prefix.
598 self._files.sort(key=FSConfigFileParser._file_key)
599
600 # sort on value of (file_name, name, value, strvalue)
601 # This is only cosmetic so AIDS are arranged in ascending order
602 # within the generated file.
603 self._aids.sort(key=lambda item: item.normalized_value)
604
605 def _handle_aid(self, file_name, section_name, config):
606 """Verifies an AID entry and adds it to the aid list.
607
608 Calls sys.exit() with a descriptive message of the failure.
609
610 Args:
611 file_name (str): The filename of the config file being parsed.
612 section_name (str): The section name currently being parsed.
613 config (ConfigParser): The ConfigParser section being parsed that
614 the option values will come from.
615 """
616
617 def error_message(msg):
618 """Creates an error message with current parsing state."""
619 return '{} for: "{}" file: "{}"'.format(msg, section_name,
620 file_name)
621
622 FSConfigFileParser._handle_dup_and_add('AID', file_name, section_name,
623 self._seen_aids[0])
624
625 match = FSConfigFileParser._AID_MATCH.match(section_name)
626 invalid = match.end() if match else len(AID.PREFIX)
627 if invalid != len(section_name):
628 tmp_errmsg = ('Invalid characters in AID section at "%d" for: "%s"'
629 % (invalid, FSConfigFileParser._AID_ERR_MSG))
630 sys.exit(error_message(tmp_errmsg))
631
632 value = config.get(section_name, 'value')
633
634 if not value:
635 sys.exit(error_message('Found specified but unset "value"'))
636
637 try:
638 aid = AID(section_name, value, file_name)
639 except ValueError:
640 sys.exit(
641 error_message('Invalid "value", not aid number, got: \"%s\"' %
642 value))
643
644 # Values must be within OEM range
645 if not Utils.in_any_range(int(aid.value, 0), self._oem_ranges):
646 emsg = '"value" not in valid range %s, got: %s'
647 emsg = emsg % (str(self._oem_ranges), value)
648 sys.exit(error_message(emsg))
649
650 # use the normalized int value in the dict and detect
651 # duplicate definitions of the same value
652 FSConfigFileParser._handle_dup_and_add(
653 'AID', file_name, aid.normalized_value, self._seen_aids[1])
654
655 # Append aid tuple of (AID_*, base10(value), _path(value))
656 # We keep the _path version of value so we can print that out in the
657 # generated header so investigating parties can identify parts.
658 # We store the base10 value for sorting, so everything is ascending
659 # later.
660 self._aids.append(aid)
661
662 def _handle_path(self, file_name, section_name, config):
663 """Add a file capability entry to the internal list.
664
665 Handles a file capability entry, verifies it, and adds it to
666 to the internal dirs or files list based on path. If it ends
667 with a / its a dir. Internal use only.
668
669 Calls sys.exit() on any validation error with message set.
670
671 Args:
672 file_name (str): The current name of the file being parsed.
673 section_name (str): The name of the section to parse.
674 config (str): The config parser.
675 """
676
677 FSConfigFileParser._handle_dup_and_add('path', file_name, section_name,
678 self._seen_paths)
679
680 mode = config.get(section_name, 'mode')
681 user = config.get(section_name, 'user')
682 group = config.get(section_name, 'group')
683 caps = config.get(section_name, 'caps')
684
685 errmsg = ('Found specified but unset option: \"%s" in file: \"' +
686 file_name + '\"')
687
688 if not mode:
689 sys.exit(errmsg % 'mode')
690
691 if not user:
692 sys.exit(errmsg % 'user')
693
694 if not group:
695 sys.exit(errmsg % 'group')
696
697 if not caps:
698 sys.exit(errmsg % 'caps')
699
700 caps = caps.split()
701
702 tmp = []
703 for cap in caps:
704 try:
705 # test if string is int, if it is, use as is.
706 int(cap, 0)
707 tmp.append('(' + cap + ')')
708 except ValueError:
709 tmp.append('(1ULL << CAP_' + cap.upper() + ')')
710
711 caps = tmp
712
713 if len(mode) == 3:
714 mode = '0' + mode
715
716 try:
717 int(mode, 8)
718 except ValueError:
719 sys.exit('Mode must be octal characters, got: "%s"' % mode)
720
721 if len(mode) != 4:
722 sys.exit('Mode must be 3 or 4 characters, got: "%s"' % mode)
723
724 caps_str = '|'.join(caps)
725
726 entry = FSConfig(mode, user, group, caps_str, section_name, file_name)
727 if section_name[-1] == '/':
728 self._dirs.append(entry)
729 else:
730 self._files.append(entry)
731
732 @property
733 def files(self):
734 """Get the list of FSConfig file entries.
735
736 Returns:
737 a list of FSConfig() objects for file paths.
738 """
739 return self._files
740
741 @property
742 def dirs(self):
743 """Get the list of FSConfig dir entries.
744
745 Returns:
746 a list of FSConfig() objects for directory paths.
747 """
748 return self._dirs
749
750 @property
751 def aids(self):
752 """Get the list of AID entries.
753
754 Returns:
755 a list of AID() objects.
756 """
757 return self._aids
758
759 @staticmethod
760 def _file_key(fs_config):
761 """Used as the key paramter to sort.
762
763 This is used as a the function to the key parameter of a sort.
764 it wraps the string supplied in a class that implements the
765 appropriate __lt__ operator for the sort on path strings. See
766 StringWrapper class for more details.
767
768 Args:
769 fs_config (FSConfig): A FSConfig entry.
770
771 Returns:
772 A StringWrapper object
773 """
774
775 # Wrapper class for custom prefix matching strings
776 class StringWrapper(object):
777 """Wrapper class used for sorting prefix strings.
778
779 The algorithm is as follows:
780 - specified path before prefix match
781 - ie foo before f*
782 - lexicographical less than before other
783 - ie boo before foo
784
785 Given these paths:
786 paths=['ac', 'a', 'acd', 'an', 'a*', 'aa', 'ac*']
787 The sort order would be:
788 paths=['a', 'aa', 'ac', 'acd', 'an', 'ac*', 'a*']
789 Thus the fs_config tools will match on specified paths before
790 attempting prefix, and match on the longest matching prefix.
791 """
792
793 def __init__(self, path):
794 """
795 Args:
796 path (str): the path string to wrap.
797 """
798 self.is_prefix = path[-1] == '*'
799 if self.is_prefix:
800 self.path = path[:-1]
801 else:
802 self.path = path
803
804 def __lt__(self, other):
805
806 # if were both suffixed the smallest string
807 # is 'bigger'
808 if self.is_prefix and other.is_prefix:
809 result = len(self.path) > len(other.path)
810 # If I am an the suffix match, im bigger
811 elif self.is_prefix:
812 result = False
813 # If other is the suffix match, he's bigger
814 elif other.is_prefix:
815 result = True
816 # Alphabetical
817 else:
818 result = self.path < other.path
819 return result
820
821 return StringWrapper(fs_config.path)
822
823 @staticmethod
824 def _handle_dup_and_add(name, file_name, section_name, seen):
825 """Tracks and detects duplicates. Internal use only.
826
827 Calls sys.exit() on a duplicate.
828
829 Args:
830 name (str): The name to use in the error reporting. The pretty
831 name for the section.
832 file_name (str): The file currently being parsed.
833 section_name (str): The name of the section. This would be path
834 or identifier depending on what's being parsed.
835 seen (dict): The dictionary of seen things to check against.
836 """
837 if section_name in seen:
838 dups = '"' + seen[section_name] + '" and '
839 dups += file_name
840 sys.exit('Duplicate %s "%s" found in files: %s' %
841 (name, section_name, dups))
842
843 seen[section_name] = file_name
844
845
846class BaseGenerator(object):
847 """Interface for Generators.
848
849 Base class for generators, generators should implement
850 these method stubs.
851 """
852
853 def add_opts(self, opt_group):
854 """Used to add per-generator options to the command line.
855
856 Args:
857 opt_group (argument group object): The argument group to append to.
858 See the ArgParse docs for more details.
859 """
860
861 raise NotImplementedError("Not Implemented")
862
863 def __call__(self, args):
864 """This is called to do whatever magic the generator does.
865
866 Args:
867 args (dict): The arguments from ArgParse as a dictionary.
868 ie if you specified an argument of foo in add_opts, access
869 it via args['foo']
870 """
871
872 raise NotImplementedError("Not Implemented")
873
874
875@generator('fsconfig')
876class FSConfigGen(BaseGenerator):
877 """Generates the android_filesystem_config.h file.
878
879 Output is used in generating fs_config_files and fs_config_dirs.
880 """
881
882 _GENERATED = textwrap.dedent("""\
883 /*
884 * THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY
885 */
886 """)
887
888 _INCLUDES = [
889 '<private/android_filesystem_config.h>', '"generated_oem_aid.h"'
890 ]
891
892 _DEFINE_NO_DIRS = '#define NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS'
893 _DEFINE_NO_FILES = '#define NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_FILES'
894
895 _DEFAULT_WARNING = (
896 '#warning No device-supplied android_filesystem_config.h,'
897 ' using empty default.')
898
899 # Long names.
900 # pylint: disable=invalid-name
901 _NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS_ENTRY = (
902 '{ 00000, AID_ROOT, AID_ROOT, 0,'
903 '"system/etc/fs_config_dirs" },')
904
905 _NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_FILES_ENTRY = (
906 '{ 00000, AID_ROOT, AID_ROOT, 0,'
907 '"system/etc/fs_config_files" },')
908
909 _IFDEF_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS = (
910 '#ifdef NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS')
911 # pylint: enable=invalid-name
912
913 _ENDIF = '#endif'
914
915 _OPEN_FILE_STRUCT = (
916 'static const struct fs_path_config android_device_files[] = {')
917
918 _OPEN_DIR_STRUCT = (
919 'static const struct fs_path_config android_device_dirs[] = {')
920
921 _CLOSE_FILE_STRUCT = '};'
922
923 _GENERIC_DEFINE = "#define %s\t%s"
924
925 _FILE_COMMENT = '// Defined in file: \"%s\"'
926
927 def __init__(self, *args, **kwargs):
928 BaseGenerator.__init__(args, kwargs)
929
930 self._oem_parser = None
931 self._base_parser = None
932 self._friendly_to_aid = None
933
934 def add_opts(self, opt_group):
935
936 opt_group.add_argument(
937 'fsconfig', nargs='+', help='The list of fsconfig files to parse')
938
939 opt_group.add_argument(
940 '--aid-header',
941 required=True,
942 help='An android_filesystem_config.h file'
943 ' to parse AIDs and OEM Ranges from')
944
945 def __call__(self, args):
946
947 self._base_parser = AIDHeaderParser(args['aid_header'])
948 self._oem_parser = FSConfigFileParser(args['fsconfig'],
949 self._base_parser.oem_ranges)
950 base_aids = self._base_parser.aids
951 oem_aids = self._oem_parser.aids
952
953 # Detect name collisions on AIDs. Since friendly works as the
954 # identifier for collision testing and we need friendly later on for
955 # name resolution, just calculate and use friendly.
956 # {aid.friendly: aid for aid in base_aids}
957 base_friendly = {aid.friendly: aid for aid in base_aids}
958 oem_friendly = {aid.friendly: aid for aid in oem_aids}
959
960 base_set = set(base_friendly.keys())
961 oem_set = set(oem_friendly.keys())
962
963 common = base_set & oem_set
964
965 if len(common) > 0:
966 emsg = 'Following AID Collisions detected for: \n'
967 for friendly in common:
968 base = base_friendly[friendly]
969 oem = oem_friendly[friendly]
970 emsg += (
971 'Identifier: "%s" Friendly Name: "%s" '
972 'found in file "%s" and "%s"' %
973 (base.identifier, base.friendly, base.found, oem.found))
974 sys.exit(emsg)
975
976 self._friendly_to_aid = oem_friendly
977 self._friendly_to_aid.update(base_friendly)
978
979 self._generate()
980
981 def _to_fs_entry(self, fs_config):
982 """Converts an FSConfig entry to an fs entry.
983
984 Prints '{ mode, user, group, caps, "path" },'.
985
986 Calls sys.exit() on error.
987
988 Args:
989 fs_config (FSConfig): The entry to convert to
990 a valid C array entry.
991 """
992
993 # Get some short names
994 mode = fs_config.mode
995 user = fs_config.user
996 group = fs_config.group
997 fname = fs_config.filename
998 caps = fs_config.caps
999 path = fs_config.path
1000
1001 emsg = 'Cannot convert friendly name "%s" to identifier!'
1002
1003 # remap friendly names to identifier names
1004 if AID.is_friendly(user):
1005 if user not in self._friendly_to_aid:
1006 sys.exit(emsg % user)
1007 user = self._friendly_to_aid[user].identifier
1008
1009 if AID.is_friendly(group):
1010 if group not in self._friendly_to_aid:
1011 sys.exit(emsg % group)
1012 group = self._friendly_to_aid[group].identifier
1013
1014 fmt = '{ %s, %s, %s, %s, "%s" },'
1015
1016 expanded = fmt % (mode, user, group, caps, path)
1017
1018 print FSConfigGen._FILE_COMMENT % fname
1019 print ' ' + expanded
1020
1021 @staticmethod
1022 def _gen_inc():
1023 """Generate the include header lines and print to stdout."""
1024 for include in FSConfigGen._INCLUDES:
1025 print '#include %s' % include
1026
1027 def _generate(self):
1028 """Generates an OEM android_filesystem_config.h header file to stdout.
1029
1030 Args:
1031 files ([FSConfig]): A list of FSConfig objects for file entries.
1032 dirs ([FSConfig]): A list of FSConfig objects for directory
1033 entries.
1034 aids ([AIDS]): A list of AID objects for Android Id entries.
1035 """
1036 print FSConfigGen._GENERATED
William Robertscfc51f52016-04-12 08:51:13 -07001037 print
1038
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001039 FSConfigGen._gen_inc()
1040 print
William Robertsc950a352016-03-04 18:12:29 -08001041
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001042 dirs = self._oem_parser.dirs
1043 files = self._oem_parser.files
1044 aids = self._oem_parser.aids
William Roberts8f42ce72016-04-25 12:27:43 -07001045
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001046 are_dirs = len(dirs) > 0
1047 are_files = len(files) > 0
1048 are_aids = len(aids) > 0
William Robertsc950a352016-03-04 18:12:29 -08001049
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001050 if are_aids:
1051 for aid in aids:
1052 # use the preserved _path value
1053 print FSConfigGen._FILE_COMMENT % aid.found
1054 print FSConfigGen._GENERIC_DEFINE % (aid.identifier, aid.value)
1055
1056 print
William Robertsc950a352016-03-04 18:12:29 -08001057
1058 if not are_dirs:
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001059 print FSConfigGen._DEFINE_NO_DIRS + '\n'
William Robertsc950a352016-03-04 18:12:29 -08001060
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001061 if not are_files:
1062 print FSConfigGen._DEFINE_NO_FILES + '\n'
William Robertsc950a352016-03-04 18:12:29 -08001063
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001064 if not are_files and not are_dirs and not are_aids:
1065 return
William Robertsc950a352016-03-04 18:12:29 -08001066
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001067 if are_files:
1068 print FSConfigGen._OPEN_FILE_STRUCT
1069 for fs_config in files:
1070 self._to_fs_entry(fs_config)
William Robertsc950a352016-03-04 18:12:29 -08001071
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001072 if not are_dirs:
1073 print FSConfigGen._IFDEF_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS
1074 print(
1075 ' ' +
1076 FSConfigGen._NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS_ENTRY)
1077 print FSConfigGen._ENDIF
1078 print FSConfigGen._CLOSE_FILE_STRUCT
William Robertsc950a352016-03-04 18:12:29 -08001079
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001080 if are_dirs:
1081 print FSConfigGen._OPEN_DIR_STRUCT
1082 for dir_entry in dirs:
1083 self._to_fs_entry(dir_entry)
William Robertsc950a352016-03-04 18:12:29 -08001084
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001085 print FSConfigGen._CLOSE_FILE_STRUCT
William Robertsc950a352016-03-04 18:12:29 -08001086
William Robertsc950a352016-03-04 18:12:29 -08001087
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001088@generator('aidarray')
1089class AIDArrayGen(BaseGenerator):
1090 """Generates the android_id static array."""
1091
1092 _GENERATED = ('/*\n'
1093 ' * THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY!\n'
1094 ' */')
1095
1096 _INCLUDE = '#include <private/android_filesystem_config.h>'
1097
1098 _STRUCT_FS_CONFIG = textwrap.dedent("""
1099 struct android_id_info {
1100 const char *name;
1101 unsigned aid;
1102 };""")
1103
1104 _OPEN_ID_ARRAY = 'static const struct android_id_info android_ids[] = {'
1105
1106 _ID_ENTRY = ' { "%s", %s },'
1107
1108 _CLOSE_FILE_STRUCT = '};'
1109
1110 _COUNT = ('#define android_id_count \\\n'
1111 ' (sizeof(android_ids) / sizeof(android_ids[0]))')
1112
1113 def add_opts(self, opt_group):
1114
1115 opt_group.add_argument(
1116 'hdrfile', help='The android_filesystem_config.h'
1117 'file to parse')
1118
1119 def __call__(self, args):
1120
1121 hdr = AIDHeaderParser(args['hdrfile'])
1122
1123 print AIDArrayGen._GENERATED
1124 print
1125 print AIDArrayGen._INCLUDE
1126 print
1127 print AIDArrayGen._STRUCT_FS_CONFIG
1128 print
1129 print AIDArrayGen._OPEN_ID_ARRAY
1130
1131 for aid in hdr.aids:
1132 print AIDArrayGen._ID_ENTRY % (aid.friendly, aid.identifier)
1133
1134 print AIDArrayGen._CLOSE_FILE_STRUCT
1135 print
1136 print AIDArrayGen._COUNT
1137 print
1138
1139
1140@generator('oemaid')
1141class OEMAidGen(BaseGenerator):
1142 """Generates the OEM AID_<name> value header file."""
1143
1144 _GENERATED = ('/*\n'
1145 ' * THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY!\n'
1146 ' */')
1147
1148 _GENERIC_DEFINE = "#define %s\t%s"
1149
1150 _FILE_COMMENT = '// Defined in file: \"%s\"'
1151
1152 # Intentional trailing newline for readability.
1153 _FILE_IFNDEF_DEFINE = ('#ifndef GENERATED_OEM_AIDS_H_\n'
1154 '#define GENERATED_OEM_AIDS_H_\n')
1155
1156 _FILE_ENDIF = '#endif'
1157
1158 def __init__(self):
1159
1160 self._old_file = None
1161
1162 def add_opts(self, opt_group):
1163
1164 opt_group.add_argument(
1165 'fsconfig', nargs='+', help='The list of fsconfig files to parse.')
1166
1167 opt_group.add_argument(
1168 '--aid-header',
1169 required=True,
1170 help='An android_filesystem_config.h file'
1171 'to parse AIDs and OEM Ranges from')
1172
1173 def __call__(self, args):
1174
1175 hdr_parser = AIDHeaderParser(args['aid_header'])
1176
1177 parser = FSConfigFileParser(args['fsconfig'], hdr_parser.oem_ranges)
1178
1179 print OEMAidGen._GENERATED
1180
1181 print OEMAidGen._FILE_IFNDEF_DEFINE
1182
1183 for aid in parser.aids:
1184 self._print_aid(aid)
1185 print
1186
1187 print OEMAidGen._FILE_ENDIF
1188
1189 def _print_aid(self, aid):
1190 """Prints a valid #define AID identifier to stdout.
1191
1192 Args:
1193 aid to print
1194 """
1195
1196 # print the source file location of the AID
1197 found_file = aid.found
1198 if found_file != self._old_file:
1199 print OEMAidGen._FILE_COMMENT % found_file
1200 self._old_file = found_file
1201
1202 print OEMAidGen._GENERIC_DEFINE % (aid.identifier, aid.value)
1203
1204
1205@generator('passwd')
1206class PasswdGen(BaseGenerator):
1207 """Generates the /etc/passwd file per man (5) passwd."""
1208
1209 _GENERATED = ('#\n# THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY!\n#')
1210
1211 _FILE_COMMENT = '# Defined in file: \"%s\"'
1212
1213 def __init__(self):
1214
1215 self._old_file = None
1216
1217 def add_opts(self, opt_group):
1218
1219 opt_group.add_argument(
1220 'fsconfig', nargs='+', help='The list of fsconfig files to parse.')
1221
1222 opt_group.add_argument(
1223 '--aid-header',
1224 required=True,
1225 help='An android_filesystem_config.h file'
1226 'to parse AIDs and OEM Ranges from')
1227
1228 def __call__(self, args):
1229
1230 hdr_parser = AIDHeaderParser(args['aid_header'])
1231
1232 parser = FSConfigFileParser(args['fsconfig'], hdr_parser.oem_ranges)
1233
1234 aids = parser.aids
1235
1236 # nothing to do if no aids defined
1237 if len(aids) == 0:
1238 return
1239
1240 print PasswdGen._GENERATED
1241
1242 for aid in aids:
1243 self._print_formatted_line(aid)
1244
1245 def _print_formatted_line(self, aid):
1246 """Prints the aid to stdout in the passwd format. Internal use only.
1247
1248 Colon delimited:
1249 login name, friendly name
1250 encrypted password (optional)
1251 uid (int)
1252 gid (int)
1253 User name or comment field
1254 home directory
1255 interpreter (optional)
1256
1257 Args:
1258 aid (AID): The aid to print.
1259 """
1260 if self._old_file != aid.found:
1261 self._old_file = aid.found
1262 print PasswdGen._FILE_COMMENT % aid.found
1263
1264 try:
1265 logon, uid = Utils.get_login_and_uid_cleansed(aid)
1266 except ValueError as exception:
1267 sys.exit(exception)
1268
1269 print "%s::%s:%s::/:/system/bin/sh" % (logon, uid, uid)
1270
1271
1272@generator('group')
1273class GroupGen(PasswdGen):
1274 """Generates the /etc/group file per man (5) group."""
1275
1276 # Overrides parent
1277 def _print_formatted_line(self, aid):
1278 """Prints the aid to stdout in the group format. Internal use only.
1279
1280 Formatted (per man 5 group) like:
1281 group_name:password:GID:user_list
1282
1283 Args:
1284 aid (AID): The aid to print.
1285 """
1286 if self._old_file != aid.found:
1287 self._old_file = aid.found
1288 print PasswdGen._FILE_COMMENT % aid.found
1289
1290 try:
1291 logon, uid = Utils.get_login_and_uid_cleansed(aid)
1292 except ValueError as exception:
1293 sys.exit(exception)
1294
1295 print "%s::%s:" % (logon, uid)
1296
William Roberts1c4721c2016-04-26 13:05:34 -07001297
William Robertsc950a352016-03-04 18:12:29 -08001298def main():
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001299 """Main entry point for execution."""
William Robertsc950a352016-03-04 18:12:29 -08001300
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001301 opt_parser = argparse.ArgumentParser(
1302 description='A tool for parsing fsconfig config files and producing' +
1303 'digestable outputs.')
1304 subparser = opt_parser.add_subparsers(help='generators')
William Robertsc950a352016-03-04 18:12:29 -08001305
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001306 gens = generator.get()
William Robertsc950a352016-03-04 18:12:29 -08001307
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001308 # for each gen, instantiate and add them as an option
1309 for name, gen in gens.iteritems():
William Robertsc950a352016-03-04 18:12:29 -08001310
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001311 generator_option_parser = subparser.add_parser(name, help=gen.__doc__)
1312 generator_option_parser.set_defaults(which=name)
William Roberts8cb6a182016-04-08 22:06:19 -07001313
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001314 opt_group = generator_option_parser.add_argument_group(name +
1315 ' options')
1316 gen.add_opts(opt_group)
William Roberts8cb6a182016-04-08 22:06:19 -07001317
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001318 args = opt_parser.parse_args()
1319
1320 args_as_dict = vars(args)
1321 which = args_as_dict['which']
1322 del args_as_dict['which']
1323
1324 gens[which](args_as_dict)
1325
William Robertsc950a352016-03-04 18:12:29 -08001326
1327if __name__ == '__main__':
1328 main()