blob: c76e8f4c8ed4fc29743f5a56c13011e958865af6 [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'
Johan Redestig1552a282017-01-03 09:36:47 +0100255 _OEM_RANGE = re.compile('%sOEM_RESERVED_[0-9]*_{0,1}(%s|%s)' %
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000256 (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):
William Roberts820421c2016-12-13 19:12:35 -0800289
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000290 def error_message(msg):
291 """Creates an error message with the current parsing state."""
292 return 'Error "{}" in file: "{}" on line: {}'.format(
293 msg, self._aid_header, str(lineno))
294
295 if AIDHeaderParser._AID_DEFINE.match(line):
296 chunks = line.split()
297 identifier = chunks[1]
298 value = chunks[2]
299
300 if any(x.match(identifier) for x in AIDHeaderParser._SKIP_AIDS):
301 continue
302
303 try:
304 if AIDHeaderParser._is_oem_range(identifier):
305 self._handle_oem_range(identifier, value)
306 elif not any(
307 identifier.endswith(x)
308 for x in AIDHeaderParser._AID_SKIP_RANGE):
309 self._handle_aid(identifier, value)
310 except ValueError as exception:
William Roberts820421c2016-12-13 19:12:35 -0800311 sys.exit(
312 error_message('{} for "{}"'.format(exception,
313 identifier)))
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000314
315 def _handle_aid(self, identifier, value):
316 """Handle an AID C #define.
317
318 Handles an AID, sanity checking, generating the friendly name and
319 adding it to the internal maps. Internal use only.
320
321 Args:
322 identifier (str): The name of the #define identifier. ie AID_FOO.
323 value (str): The value associated with the identifier.
324
325 Raises:
326 ValueError: With message set to indicate the error.
327 """
328
329 aid = AID(identifier, value, self._aid_header)
330
331 # duplicate name
332 if aid.friendly in self._aid_name_to_value:
333 raise ValueError('Duplicate aid "%s"' % identifier)
334
335 if value in self._aid_value_to_name and aid.identifier not in AIDHeaderParser._COLLISION_OK:
336 raise ValueError('Duplicate aid value "%s" for %s' % (value,
337 identifier))
338
339 self._aid_name_to_value[aid.friendly] = aid
340 self._aid_value_to_name[value] = aid.friendly
341
342 def _handle_oem_range(self, identifier, value):
343 """Handle an OEM range C #define.
344
345 When encountering special AID defines, notably for the OEM ranges
346 this method handles sanity checking and adding them to the internal
347 maps. For internal use only.
348
349 Args:
350 identifier (str): The name of the #define identifier.
351 ie AID_OEM_RESERVED_START/END.
352 value (str): The value associated with the identifier.
353
354 Raises:
355 ValueError: With message set to indicate the error.
356 """
357
358 try:
359 int_value = int(value, 0)
William Roberts64edf5b2016-04-11 17:12:47 -0700360 except ValueError:
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000361 raise ValueError(
362 'Could not convert "%s" to integer value, got: "%s"' %
363 (identifier, value))
William Roberts64edf5b2016-04-11 17:12:47 -0700364
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000365 # convert AID_OEM_RESERVED_START or AID_OEM_RESERVED_<num>_START
366 # to AID_OEM_RESERVED or AID_OEM_RESERVED_<num>
367 is_start = identifier.endswith(AIDHeaderParser._OEM_START_KW)
William Roberts64edf5b2016-04-11 17:12:47 -0700368
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000369 if is_start:
370 tostrip = len(AIDHeaderParser._OEM_START_KW)
371 else:
372 tostrip = len(AIDHeaderParser._OEM_END_KW)
William Roberts64edf5b2016-04-11 17:12:47 -0700373
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000374 # ending _
375 tostrip = tostrip + 1
William Roberts64edf5b2016-04-11 17:12:47 -0700376
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000377 strip = identifier[:-tostrip]
378 if strip not in self._oem_ranges:
379 self._oem_ranges[strip] = []
William Roberts64edf5b2016-04-11 17:12:47 -0700380
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000381 if len(self._oem_ranges[strip]) > 2:
382 raise ValueError('Too many same OEM Ranges "%s"' % identifier)
William Roberts64edf5b2016-04-11 17:12:47 -0700383
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000384 if len(self._oem_ranges[strip]) == 1:
385 tmp = self._oem_ranges[strip][0]
William Roberts64edf5b2016-04-11 17:12:47 -0700386
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000387 if tmp == int_value:
388 raise ValueError('START and END values equal %u' % int_value)
389 elif is_start and tmp < int_value:
390 raise ValueError('END value %u less than START value %u' %
391 (tmp, int_value))
392 elif not is_start and tmp > int_value:
393 raise ValueError('END value %u less than START value %u' %
394 (int_value, tmp))
William Roberts64edf5b2016-04-11 17:12:47 -0700395
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000396 # Add START values to the head of the list and END values at the end.
397 # Thus, the list is ordered with index 0 as START and index 1 as END.
398 if is_start:
399 self._oem_ranges[strip].insert(0, int_value)
400 else:
401 self._oem_ranges[strip].append(int_value)
William Roberts64edf5b2016-04-11 17:12:47 -0700402
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000403 def _process_and_check(self):
404 """Process, check and populate internal data structures.
William Roberts64edf5b2016-04-11 17:12:47 -0700405
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000406 After parsing and generating the internal data structures, this method
407 is responsible for sanity checking ALL of the acquired data.
William Roberts64edf5b2016-04-11 17:12:47 -0700408
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000409 Raises:
410 ValueError: With the message set to indicate the specific error.
411 """
William Roberts64edf5b2016-04-11 17:12:47 -0700412
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000413 # tuplefy the lists since range() does not like them mutable.
414 self._oem_ranges = [
415 AIDHeaderParser._convert_lst_to_tup(k, v)
416 for k, v in self._oem_ranges.iteritems()
417 ]
William Roberts64edf5b2016-04-11 17:12:47 -0700418
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000419 # Check for overlapping ranges
420 for i, range1 in enumerate(self._oem_ranges):
421 for range2 in self._oem_ranges[i + 1:]:
422 if AIDHeaderParser._is_overlap(range1, range2):
423 raise ValueError("Overlapping OEM Ranges found %s and %s" %
424 (str(range1), str(range2)))
William Roberts64edf5b2016-04-11 17:12:47 -0700425
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000426 # No core AIDs should be within any oem range.
427 for aid in self._aid_value_to_name:
428
429 if Utils.in_any_range(aid, self._oem_ranges):
430 name = self._aid_value_to_name[aid]
431 raise ValueError(
432 'AID "%s" value: %u within reserved OEM Range: "%s"' %
433 (name, aid, str(self._oem_ranges)))
434
435 @property
436 def oem_ranges(self):
437 """Retrieves the OEM closed ranges as a list of tuples.
438
439 Returns:
440 A list of closed range tuples: [ (0, 42), (50, 105) ... ]
441 """
442 return self._oem_ranges
443
444 @property
445 def aids(self):
446 """Retrieves the list of found AIDs.
447
448 Returns:
449 A list of AID() objects.
450 """
451 return self._aid_name_to_value.values()
452
453 @staticmethod
454 def _convert_lst_to_tup(name, lst):
455 """Converts a mutable list to a non-mutable tuple.
456
457 Used ONLY for ranges and thus enforces a length of 2.
458
459 Args:
460 lst (List): list that should be "tuplefied".
461
462 Raises:
463 ValueError if lst is not a list or len is not 2.
464
465 Returns:
466 Tuple(lst)
467 """
468 if not lst or len(lst) != 2:
469 raise ValueError('Mismatched range for "%s"' % name)
470
471 return tuple(lst)
472
473 @staticmethod
474 def _is_oem_range(aid):
475 """Detects if a given aid is within the reserved OEM range.
476
477 Args:
478 aid (int): The aid to test
479
480 Returns:
481 True if it is within the range, False otherwise.
482 """
483
484 return AIDHeaderParser._OEM_RANGE.match(aid)
485
486 @staticmethod
487 def _is_overlap(range_a, range_b):
488 """Calculates the overlap of two range tuples.
489
490 A range tuple is a closed range. A closed range includes its endpoints.
491 Note that python tuples use () notation which collides with the
492 mathematical notation for open ranges.
493
494 Args:
495 range_a: The first tuple closed range eg (0, 5).
496 range_b: The second tuple closed range eg (3, 7).
497
498 Returns:
499 True if they overlap, False otherwise.
500 """
501
502 return max(range_a[0], range_b[0]) <= min(range_a[1], range_b[1])
William Roberts64edf5b2016-04-11 17:12:47 -0700503
504
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000505class FSConfigFileParser(object):
506 """Parses a config.fs ini format file.
William Roberts11c29282016-04-09 10:32:30 -0700507
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000508 This class is responsible for parsing the config.fs ini format files.
509 It collects and checks all the data in these files and makes it available
510 for consumption post processed.
511 """
William Roberts11c29282016-04-09 10:32:30 -0700512
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000513 # These _AID vars work together to ensure that an AID section name
514 # cannot contain invalid characters for a C define or a passwd/group file.
515 # Since _AID_PREFIX is within the set of _AID_MATCH the error logic only
516 # checks end, if you change this, you may have to update the error
517 # detection code.
518 _AID_MATCH = re.compile('%s[A-Z0-9_]+' % AID.PREFIX)
519 _AID_ERR_MSG = 'Expecting upper case, a number or underscore'
William Roberts5f059a72016-04-25 10:36:45 -0700520
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000521 # list of handler to required options, used to identify the
522 # parsing section
523 _SECTIONS = [('_handle_aid', ('value',)),
524 ('_handle_path', ('mode', 'user', 'group', 'caps'))]
525
526 def __init__(self, config_files, oem_ranges):
527 """
528 Args:
529 config_files ([str]): The list of config.fs files to parse.
530 Note the filename is not important.
531 oem_ranges ([(),()]): range tuples indicating reserved OEM ranges.
532 """
533
534 self._files = []
535 self._dirs = []
536 self._aids = []
537
538 self._seen_paths = {}
539 # (name to file, value to aid)
540 self._seen_aids = ({}, {})
541
542 self._oem_ranges = oem_ranges
543
544 self._config_files = config_files
545
546 for config_file in self._config_files:
547 self._parse(config_file)
548
549 def _parse(self, file_name):
550 """Parses and verifies config.fs files. Internal use only.
551
552 Args:
553 file_name (str): The config.fs (PythonConfigParser file format)
554 file to parse.
555
556 Raises:
557 Anything raised by ConfigParser.read()
558 """
559
560 # Separate config parsers for each file found. If you use
561 # read(filenames...) later files can override earlier files which is
562 # not what we want. Track state across files and enforce with
563 # _handle_dup(). Note, strict ConfigParser is set to true in
564 # Python >= 3.2, so in previous versions same file sections can
565 # override previous
566 # sections.
William Robertsc950a352016-03-04 18:12:29 -0800567
568 config = ConfigParser.ConfigParser()
569 config.read(file_name)
570
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000571 for section in config.sections():
William Robertsc950a352016-03-04 18:12:29 -0800572
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000573 found = False
William Roberts5f059a72016-04-25 10:36:45 -0700574
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000575 for test in FSConfigFileParser._SECTIONS:
576 handler = test[0]
577 options = test[1]
William Roberts5f059a72016-04-25 10:36:45 -0700578
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000579 if all([config.has_option(section, item) for item in options]):
580 handler = getattr(self, handler)
581 handler(file_name, section, config)
582 found = True
583 break
William Roberts5f059a72016-04-25 10:36:45 -0700584
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000585 if not found:
586 sys.exit('Invalid section "%s" in file: "%s"' %
587 (section, file_name))
William Roberts11c29282016-04-09 10:32:30 -0700588
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000589 # sort entries:
590 # * specified path before prefix match
591 # ** ie foo before f*
592 # * lexicographical less than before other
593 # ** ie boo before foo
594 # Given these paths:
595 # paths=['ac', 'a', 'acd', 'an', 'a*', 'aa', 'ac*']
596 # The sort order would be:
597 # paths=['a', 'aa', 'ac', 'acd', 'an', 'ac*', 'a*']
598 # Thus the fs_config tools will match on specified paths before
599 # attempting prefix, and match on the longest matching prefix.
600 self._files.sort(key=FSConfigFileParser._file_key)
601
602 # sort on value of (file_name, name, value, strvalue)
603 # This is only cosmetic so AIDS are arranged in ascending order
604 # within the generated file.
605 self._aids.sort(key=lambda item: item.normalized_value)
606
607 def _handle_aid(self, file_name, section_name, config):
608 """Verifies an AID entry and adds it to the aid list.
609
610 Calls sys.exit() with a descriptive message of the failure.
611
612 Args:
613 file_name (str): The filename of the config file being parsed.
614 section_name (str): The section name currently being parsed.
615 config (ConfigParser): The ConfigParser section being parsed that
616 the option values will come from.
617 """
618
619 def error_message(msg):
620 """Creates an error message with current parsing state."""
621 return '{} for: "{}" file: "{}"'.format(msg, section_name,
622 file_name)
623
624 FSConfigFileParser._handle_dup_and_add('AID', file_name, section_name,
625 self._seen_aids[0])
626
627 match = FSConfigFileParser._AID_MATCH.match(section_name)
628 invalid = match.end() if match else len(AID.PREFIX)
629 if invalid != len(section_name):
630 tmp_errmsg = ('Invalid characters in AID section at "%d" for: "%s"'
631 % (invalid, FSConfigFileParser._AID_ERR_MSG))
632 sys.exit(error_message(tmp_errmsg))
633
634 value = config.get(section_name, 'value')
635
636 if not value:
637 sys.exit(error_message('Found specified but unset "value"'))
638
639 try:
640 aid = AID(section_name, value, file_name)
641 except ValueError:
642 sys.exit(
643 error_message('Invalid "value", not aid number, got: \"%s\"' %
644 value))
645
646 # Values must be within OEM range
647 if not Utils.in_any_range(int(aid.value, 0), self._oem_ranges):
648 emsg = '"value" not in valid range %s, got: %s'
649 emsg = emsg % (str(self._oem_ranges), value)
650 sys.exit(error_message(emsg))
651
652 # use the normalized int value in the dict and detect
653 # duplicate definitions of the same value
654 FSConfigFileParser._handle_dup_and_add(
655 'AID', file_name, aid.normalized_value, self._seen_aids[1])
656
657 # Append aid tuple of (AID_*, base10(value), _path(value))
658 # We keep the _path version of value so we can print that out in the
659 # generated header so investigating parties can identify parts.
660 # We store the base10 value for sorting, so everything is ascending
661 # later.
662 self._aids.append(aid)
663
664 def _handle_path(self, file_name, section_name, config):
665 """Add a file capability entry to the internal list.
666
667 Handles a file capability entry, verifies it, and adds it to
668 to the internal dirs or files list based on path. If it ends
669 with a / its a dir. Internal use only.
670
671 Calls sys.exit() on any validation error with message set.
672
673 Args:
674 file_name (str): The current name of the file being parsed.
675 section_name (str): The name of the section to parse.
676 config (str): The config parser.
677 """
678
679 FSConfigFileParser._handle_dup_and_add('path', file_name, section_name,
680 self._seen_paths)
681
682 mode = config.get(section_name, 'mode')
683 user = config.get(section_name, 'user')
684 group = config.get(section_name, 'group')
685 caps = config.get(section_name, 'caps')
686
687 errmsg = ('Found specified but unset option: \"%s" in file: \"' +
688 file_name + '\"')
689
690 if not mode:
691 sys.exit(errmsg % 'mode')
692
693 if not user:
694 sys.exit(errmsg % 'user')
695
696 if not group:
697 sys.exit(errmsg % 'group')
698
699 if not caps:
700 sys.exit(errmsg % 'caps')
701
702 caps = caps.split()
703
704 tmp = []
705 for cap in caps:
706 try:
707 # test if string is int, if it is, use as is.
708 int(cap, 0)
709 tmp.append('(' + cap + ')')
710 except ValueError:
711 tmp.append('(1ULL << CAP_' + cap.upper() + ')')
712
713 caps = tmp
714
715 if len(mode) == 3:
716 mode = '0' + mode
717
718 try:
719 int(mode, 8)
720 except ValueError:
721 sys.exit('Mode must be octal characters, got: "%s"' % mode)
722
723 if len(mode) != 4:
724 sys.exit('Mode must be 3 or 4 characters, got: "%s"' % mode)
725
726 caps_str = '|'.join(caps)
727
728 entry = FSConfig(mode, user, group, caps_str, section_name, file_name)
729 if section_name[-1] == '/':
730 self._dirs.append(entry)
731 else:
732 self._files.append(entry)
733
734 @property
735 def files(self):
736 """Get the list of FSConfig file entries.
737
738 Returns:
739 a list of FSConfig() objects for file paths.
740 """
741 return self._files
742
743 @property
744 def dirs(self):
745 """Get the list of FSConfig dir entries.
746
747 Returns:
748 a list of FSConfig() objects for directory paths.
749 """
750 return self._dirs
751
752 @property
753 def aids(self):
754 """Get the list of AID entries.
755
756 Returns:
757 a list of AID() objects.
758 """
759 return self._aids
760
761 @staticmethod
762 def _file_key(fs_config):
763 """Used as the key paramter to sort.
764
765 This is used as a the function to the key parameter of a sort.
766 it wraps the string supplied in a class that implements the
767 appropriate __lt__ operator for the sort on path strings. See
768 StringWrapper class for more details.
769
770 Args:
771 fs_config (FSConfig): A FSConfig entry.
772
773 Returns:
774 A StringWrapper object
775 """
776
777 # Wrapper class for custom prefix matching strings
778 class StringWrapper(object):
779 """Wrapper class used for sorting prefix strings.
780
781 The algorithm is as follows:
782 - specified path before prefix match
783 - ie foo before f*
784 - lexicographical less than before other
785 - ie boo before foo
786
787 Given these paths:
788 paths=['ac', 'a', 'acd', 'an', 'a*', 'aa', 'ac*']
789 The sort order would be:
790 paths=['a', 'aa', 'ac', 'acd', 'an', 'ac*', 'a*']
791 Thus the fs_config tools will match on specified paths before
792 attempting prefix, and match on the longest matching prefix.
793 """
794
795 def __init__(self, path):
796 """
797 Args:
798 path (str): the path string to wrap.
799 """
800 self.is_prefix = path[-1] == '*'
801 if self.is_prefix:
802 self.path = path[:-1]
803 else:
804 self.path = path
805
806 def __lt__(self, other):
807
808 # if were both suffixed the smallest string
809 # is 'bigger'
810 if self.is_prefix and other.is_prefix:
811 result = len(self.path) > len(other.path)
812 # If I am an the suffix match, im bigger
813 elif self.is_prefix:
814 result = False
815 # If other is the suffix match, he's bigger
816 elif other.is_prefix:
817 result = True
818 # Alphabetical
819 else:
820 result = self.path < other.path
821 return result
822
823 return StringWrapper(fs_config.path)
824
825 @staticmethod
826 def _handle_dup_and_add(name, file_name, section_name, seen):
827 """Tracks and detects duplicates. Internal use only.
828
829 Calls sys.exit() on a duplicate.
830
831 Args:
832 name (str): The name to use in the error reporting. The pretty
833 name for the section.
834 file_name (str): The file currently being parsed.
835 section_name (str): The name of the section. This would be path
836 or identifier depending on what's being parsed.
837 seen (dict): The dictionary of seen things to check against.
838 """
839 if section_name in seen:
840 dups = '"' + seen[section_name] + '" and '
841 dups += file_name
842 sys.exit('Duplicate %s "%s" found in files: %s' %
843 (name, section_name, dups))
844
845 seen[section_name] = file_name
846
847
848class BaseGenerator(object):
849 """Interface for Generators.
850
851 Base class for generators, generators should implement
852 these method stubs.
853 """
854
855 def add_opts(self, opt_group):
856 """Used to add per-generator options to the command line.
857
858 Args:
859 opt_group (argument group object): The argument group to append to.
860 See the ArgParse docs for more details.
861 """
862
863 raise NotImplementedError("Not Implemented")
864
865 def __call__(self, args):
866 """This is called to do whatever magic the generator does.
867
868 Args:
869 args (dict): The arguments from ArgParse as a dictionary.
870 ie if you specified an argument of foo in add_opts, access
871 it via args['foo']
872 """
873
874 raise NotImplementedError("Not Implemented")
875
876
877@generator('fsconfig')
878class FSConfigGen(BaseGenerator):
879 """Generates the android_filesystem_config.h file.
880
881 Output is used in generating fs_config_files and fs_config_dirs.
882 """
883
884 _GENERATED = textwrap.dedent("""\
885 /*
886 * THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY
887 */
888 """)
889
890 _INCLUDES = [
891 '<private/android_filesystem_config.h>', '"generated_oem_aid.h"'
892 ]
893
894 _DEFINE_NO_DIRS = '#define NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS'
895 _DEFINE_NO_FILES = '#define NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_FILES'
896
897 _DEFAULT_WARNING = (
898 '#warning No device-supplied android_filesystem_config.h,'
899 ' using empty default.')
900
901 # Long names.
902 # pylint: disable=invalid-name
903 _NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS_ENTRY = (
904 '{ 00000, AID_ROOT, AID_ROOT, 0,'
905 '"system/etc/fs_config_dirs" },')
906
907 _NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_FILES_ENTRY = (
908 '{ 00000, AID_ROOT, AID_ROOT, 0,'
909 '"system/etc/fs_config_files" },')
910
911 _IFDEF_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS = (
912 '#ifdef NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS')
913 # pylint: enable=invalid-name
914
915 _ENDIF = '#endif'
916
917 _OPEN_FILE_STRUCT = (
918 'static const struct fs_path_config android_device_files[] = {')
919
920 _OPEN_DIR_STRUCT = (
921 'static const struct fs_path_config android_device_dirs[] = {')
922
923 _CLOSE_FILE_STRUCT = '};'
924
925 _GENERIC_DEFINE = "#define %s\t%s"
926
927 _FILE_COMMENT = '// Defined in file: \"%s\"'
928
929 def __init__(self, *args, **kwargs):
930 BaseGenerator.__init__(args, kwargs)
931
932 self._oem_parser = None
933 self._base_parser = None
934 self._friendly_to_aid = None
935
936 def add_opts(self, opt_group):
937
938 opt_group.add_argument(
939 'fsconfig', nargs='+', help='The list of fsconfig files to parse')
940
941 opt_group.add_argument(
942 '--aid-header',
943 required=True,
944 help='An android_filesystem_config.h file'
945 ' to parse AIDs and OEM Ranges from')
946
947 def __call__(self, args):
948
949 self._base_parser = AIDHeaderParser(args['aid_header'])
950 self._oem_parser = FSConfigFileParser(args['fsconfig'],
951 self._base_parser.oem_ranges)
952 base_aids = self._base_parser.aids
953 oem_aids = self._oem_parser.aids
954
955 # Detect name collisions on AIDs. Since friendly works as the
956 # identifier for collision testing and we need friendly later on for
957 # name resolution, just calculate and use friendly.
958 # {aid.friendly: aid for aid in base_aids}
959 base_friendly = {aid.friendly: aid for aid in base_aids}
960 oem_friendly = {aid.friendly: aid for aid in oem_aids}
961
962 base_set = set(base_friendly.keys())
963 oem_set = set(oem_friendly.keys())
964
965 common = base_set & oem_set
966
967 if len(common) > 0:
968 emsg = 'Following AID Collisions detected for: \n'
969 for friendly in common:
970 base = base_friendly[friendly]
971 oem = oem_friendly[friendly]
972 emsg += (
973 'Identifier: "%s" Friendly Name: "%s" '
974 'found in file "%s" and "%s"' %
975 (base.identifier, base.friendly, base.found, oem.found))
976 sys.exit(emsg)
977
978 self._friendly_to_aid = oem_friendly
979 self._friendly_to_aid.update(base_friendly)
980
981 self._generate()
982
983 def _to_fs_entry(self, fs_config):
984 """Converts an FSConfig entry to an fs entry.
985
986 Prints '{ mode, user, group, caps, "path" },'.
987
988 Calls sys.exit() on error.
989
990 Args:
991 fs_config (FSConfig): The entry to convert to
992 a valid C array entry.
993 """
994
995 # Get some short names
996 mode = fs_config.mode
997 user = fs_config.user
998 group = fs_config.group
999 fname = fs_config.filename
1000 caps = fs_config.caps
1001 path = fs_config.path
1002
1003 emsg = 'Cannot convert friendly name "%s" to identifier!'
1004
1005 # remap friendly names to identifier names
1006 if AID.is_friendly(user):
1007 if user not in self._friendly_to_aid:
1008 sys.exit(emsg % user)
1009 user = self._friendly_to_aid[user].identifier
1010
1011 if AID.is_friendly(group):
1012 if group not in self._friendly_to_aid:
1013 sys.exit(emsg % group)
1014 group = self._friendly_to_aid[group].identifier
1015
1016 fmt = '{ %s, %s, %s, %s, "%s" },'
1017
1018 expanded = fmt % (mode, user, group, caps, path)
1019
1020 print FSConfigGen._FILE_COMMENT % fname
1021 print ' ' + expanded
1022
1023 @staticmethod
1024 def _gen_inc():
1025 """Generate the include header lines and print to stdout."""
1026 for include in FSConfigGen._INCLUDES:
1027 print '#include %s' % include
1028
1029 def _generate(self):
1030 """Generates an OEM android_filesystem_config.h header file to stdout.
1031
1032 Args:
1033 files ([FSConfig]): A list of FSConfig objects for file entries.
1034 dirs ([FSConfig]): A list of FSConfig objects for directory
1035 entries.
1036 aids ([AIDS]): A list of AID objects for Android Id entries.
1037 """
1038 print FSConfigGen._GENERATED
William Robertscfc51f52016-04-12 08:51:13 -07001039 print
1040
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001041 FSConfigGen._gen_inc()
1042 print
William Robertsc950a352016-03-04 18:12:29 -08001043
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001044 dirs = self._oem_parser.dirs
1045 files = self._oem_parser.files
1046 aids = self._oem_parser.aids
William Roberts8f42ce72016-04-25 12:27:43 -07001047
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001048 are_dirs = len(dirs) > 0
1049 are_files = len(files) > 0
1050 are_aids = len(aids) > 0
William Robertsc950a352016-03-04 18:12:29 -08001051
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001052 if are_aids:
1053 for aid in aids:
1054 # use the preserved _path value
1055 print FSConfigGen._FILE_COMMENT % aid.found
1056 print FSConfigGen._GENERIC_DEFINE % (aid.identifier, aid.value)
1057
1058 print
William Robertsc950a352016-03-04 18:12:29 -08001059
1060 if not are_dirs:
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001061 print FSConfigGen._DEFINE_NO_DIRS + '\n'
William Robertsc950a352016-03-04 18:12:29 -08001062
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001063 if not are_files:
1064 print FSConfigGen._DEFINE_NO_FILES + '\n'
William Robertsc950a352016-03-04 18:12:29 -08001065
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001066 if not are_files and not are_dirs and not are_aids:
1067 return
William Robertsc950a352016-03-04 18:12:29 -08001068
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001069 if are_files:
1070 print FSConfigGen._OPEN_FILE_STRUCT
1071 for fs_config in files:
1072 self._to_fs_entry(fs_config)
William Robertsc950a352016-03-04 18:12:29 -08001073
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001074 if not are_dirs:
1075 print FSConfigGen._IFDEF_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS
1076 print(
1077 ' ' +
1078 FSConfigGen._NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS_ENTRY)
1079 print FSConfigGen._ENDIF
1080 print FSConfigGen._CLOSE_FILE_STRUCT
William Robertsc950a352016-03-04 18:12:29 -08001081
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001082 if are_dirs:
1083 print FSConfigGen._OPEN_DIR_STRUCT
1084 for dir_entry in dirs:
1085 self._to_fs_entry(dir_entry)
William Robertsc950a352016-03-04 18:12:29 -08001086
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001087 print FSConfigGen._CLOSE_FILE_STRUCT
William Robertsc950a352016-03-04 18:12:29 -08001088
William Robertsc950a352016-03-04 18:12:29 -08001089
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001090@generator('aidarray')
1091class AIDArrayGen(BaseGenerator):
1092 """Generates the android_id static array."""
1093
1094 _GENERATED = ('/*\n'
1095 ' * THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY!\n'
1096 ' */')
1097
1098 _INCLUDE = '#include <private/android_filesystem_config.h>'
1099
1100 _STRUCT_FS_CONFIG = textwrap.dedent("""
1101 struct android_id_info {
1102 const char *name;
1103 unsigned aid;
1104 };""")
1105
1106 _OPEN_ID_ARRAY = 'static const struct android_id_info android_ids[] = {'
1107
1108 _ID_ENTRY = ' { "%s", %s },'
1109
1110 _CLOSE_FILE_STRUCT = '};'
1111
1112 _COUNT = ('#define android_id_count \\\n'
1113 ' (sizeof(android_ids) / sizeof(android_ids[0]))')
1114
1115 def add_opts(self, opt_group):
1116
1117 opt_group.add_argument(
1118 'hdrfile', help='The android_filesystem_config.h'
1119 'file to parse')
1120
1121 def __call__(self, args):
1122
1123 hdr = AIDHeaderParser(args['hdrfile'])
1124
1125 print AIDArrayGen._GENERATED
1126 print
1127 print AIDArrayGen._INCLUDE
1128 print
1129 print AIDArrayGen._STRUCT_FS_CONFIG
1130 print
1131 print AIDArrayGen._OPEN_ID_ARRAY
1132
1133 for aid in hdr.aids:
1134 print AIDArrayGen._ID_ENTRY % (aid.friendly, aid.identifier)
1135
1136 print AIDArrayGen._CLOSE_FILE_STRUCT
1137 print
1138 print AIDArrayGen._COUNT
1139 print
1140
1141
1142@generator('oemaid')
1143class OEMAidGen(BaseGenerator):
1144 """Generates the OEM AID_<name> value header file."""
1145
1146 _GENERATED = ('/*\n'
1147 ' * THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY!\n'
1148 ' */')
1149
1150 _GENERIC_DEFINE = "#define %s\t%s"
1151
1152 _FILE_COMMENT = '// Defined in file: \"%s\"'
1153
1154 # Intentional trailing newline for readability.
1155 _FILE_IFNDEF_DEFINE = ('#ifndef GENERATED_OEM_AIDS_H_\n'
1156 '#define GENERATED_OEM_AIDS_H_\n')
1157
1158 _FILE_ENDIF = '#endif'
1159
1160 def __init__(self):
1161
1162 self._old_file = None
1163
1164 def add_opts(self, opt_group):
1165
1166 opt_group.add_argument(
1167 'fsconfig', nargs='+', help='The list of fsconfig files to parse.')
1168
1169 opt_group.add_argument(
1170 '--aid-header',
1171 required=True,
1172 help='An android_filesystem_config.h file'
1173 'to parse AIDs and OEM Ranges from')
1174
1175 def __call__(self, args):
1176
1177 hdr_parser = AIDHeaderParser(args['aid_header'])
1178
1179 parser = FSConfigFileParser(args['fsconfig'], hdr_parser.oem_ranges)
1180
1181 print OEMAidGen._GENERATED
1182
1183 print OEMAidGen._FILE_IFNDEF_DEFINE
1184
1185 for aid in parser.aids:
1186 self._print_aid(aid)
1187 print
1188
1189 print OEMAidGen._FILE_ENDIF
1190
1191 def _print_aid(self, aid):
1192 """Prints a valid #define AID identifier to stdout.
1193
1194 Args:
1195 aid to print
1196 """
1197
1198 # print the source file location of the AID
1199 found_file = aid.found
1200 if found_file != self._old_file:
1201 print OEMAidGen._FILE_COMMENT % found_file
1202 self._old_file = found_file
1203
1204 print OEMAidGen._GENERIC_DEFINE % (aid.identifier, aid.value)
1205
1206
1207@generator('passwd')
1208class PasswdGen(BaseGenerator):
1209 """Generates the /etc/passwd file per man (5) passwd."""
1210
1211 _GENERATED = ('#\n# THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY!\n#')
1212
1213 _FILE_COMMENT = '# Defined in file: \"%s\"'
1214
1215 def __init__(self):
1216
1217 self._old_file = None
1218
1219 def add_opts(self, opt_group):
1220
1221 opt_group.add_argument(
1222 'fsconfig', nargs='+', help='The list of fsconfig files to parse.')
1223
1224 opt_group.add_argument(
1225 '--aid-header',
1226 required=True,
1227 help='An android_filesystem_config.h file'
1228 'to parse AIDs and OEM Ranges from')
1229
1230 def __call__(self, args):
1231
1232 hdr_parser = AIDHeaderParser(args['aid_header'])
1233
1234 parser = FSConfigFileParser(args['fsconfig'], hdr_parser.oem_ranges)
1235
1236 aids = parser.aids
1237
1238 # nothing to do if no aids defined
1239 if len(aids) == 0:
1240 return
1241
1242 print PasswdGen._GENERATED
1243
1244 for aid in aids:
1245 self._print_formatted_line(aid)
1246
1247 def _print_formatted_line(self, aid):
1248 """Prints the aid to stdout in the passwd format. Internal use only.
1249
1250 Colon delimited:
1251 login name, friendly name
1252 encrypted password (optional)
1253 uid (int)
1254 gid (int)
1255 User name or comment field
1256 home directory
1257 interpreter (optional)
1258
1259 Args:
1260 aid (AID): The aid to print.
1261 """
1262 if self._old_file != aid.found:
1263 self._old_file = aid.found
1264 print PasswdGen._FILE_COMMENT % aid.found
1265
1266 try:
1267 logon, uid = Utils.get_login_and_uid_cleansed(aid)
1268 except ValueError as exception:
1269 sys.exit(exception)
1270
1271 print "%s::%s:%s::/:/system/bin/sh" % (logon, uid, uid)
1272
1273
1274@generator('group')
1275class GroupGen(PasswdGen):
1276 """Generates the /etc/group file per man (5) group."""
1277
1278 # Overrides parent
1279 def _print_formatted_line(self, aid):
1280 """Prints the aid to stdout in the group format. Internal use only.
1281
1282 Formatted (per man 5 group) like:
1283 group_name:password:GID:user_list
1284
1285 Args:
1286 aid (AID): The aid to print.
1287 """
1288 if self._old_file != aid.found:
1289 self._old_file = aid.found
1290 print PasswdGen._FILE_COMMENT % aid.found
1291
1292 try:
1293 logon, uid = Utils.get_login_and_uid_cleansed(aid)
1294 except ValueError as exception:
1295 sys.exit(exception)
1296
1297 print "%s::%s:" % (logon, uid)
1298
William Roberts1c4721c2016-04-26 13:05:34 -07001299
William Robertsc950a352016-03-04 18:12:29 -08001300def main():
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001301 """Main entry point for execution."""
William Robertsc950a352016-03-04 18:12:29 -08001302
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001303 opt_parser = argparse.ArgumentParser(
1304 description='A tool for parsing fsconfig config files and producing' +
1305 'digestable outputs.')
1306 subparser = opt_parser.add_subparsers(help='generators')
William Robertsc950a352016-03-04 18:12:29 -08001307
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001308 gens = generator.get()
William Robertsc950a352016-03-04 18:12:29 -08001309
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001310 # for each gen, instantiate and add them as an option
1311 for name, gen in gens.iteritems():
William Robertsc950a352016-03-04 18:12:29 -08001312
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001313 generator_option_parser = subparser.add_parser(name, help=gen.__doc__)
1314 generator_option_parser.set_defaults(which=name)
William Roberts8cb6a182016-04-08 22:06:19 -07001315
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001316 opt_group = generator_option_parser.add_argument_group(name +
1317 ' options')
1318 gen.add_opts(opt_group)
William Roberts8cb6a182016-04-08 22:06:19 -07001319
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001320 args = opt_parser.parse_args()
1321
1322 args_as_dict = vars(args)
1323 which = args_as_dict['which']
1324 del args_as_dict['which']
1325
1326 gens[which](args_as_dict)
1327
William Robertsc950a352016-03-04 18:12:29 -08001328
1329if __name__ == '__main__':
1330 main()