blob: 86096574d9584ca7e97a3f71259db007436568ba [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
William Roberts92ec0ab2016-11-18 16:12:41 -0800161 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
William Roberts8f42ce72016-04-25 12:27:43 -0700167 @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
William Robertsc950a352016-03-04 18:12:29 -0800196
William Roberts11c29282016-04-09 10:32:30 -0700197class FSConfig(object):
198 """Represents a filesystem config array entry.
William Robertsc950a352016-03-04 18:12:29 -0800199
William Roberts11c29282016-04-09 10:32:30 -0700200 Represents a file system configuration entry for specifying
201 file system capabilities.
William Robertsc950a352016-03-04 18:12:29 -0800202
William Roberts11c29282016-04-09 10:32:30 -0700203 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 """
William Robertsc950a352016-03-04 18:12:29 -0800210
William Roberts11c29282016-04-09 10:32:30 -0700211 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
William Roberts92ec0ab2016-11-18 16:12:41 -0800227 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
William Roberts11c29282016-04-09 10:32:30 -0700233
William Roberts64edf5b2016-04-11 17:12:47 -0700234class 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 It provides some basic sanity checks. The information extracted from this
239 file can later be used to sanity check other things (like oem ranges) as
240 well as generating a mapping of names to uids. It was primarily designed to
241 parse the private/android_filesystem_config.h, but any C header should
242 work.
243 """
244
245 _SKIPWORDS = ['UNUSED']
William Roberts8f42ce72016-04-25 12:27:43 -0700246 _AID_DEFINE = re.compile(r'\s*#define\s+%s.*' % AID.PREFIX)
William Roberts64edf5b2016-04-11 17:12:47 -0700247 _OEM_START_KW = 'START'
248 _OEM_END_KW = 'END'
249 _OEM_RANGE = re.compile('AID_OEM_RESERVED_[0-9]*_{0,1}(%s|%s)' %
250 (_OEM_START_KW, _OEM_END_KW))
251
William Roberts64edf5b2016-04-11 17:12:47 -0700252 def __init__(self, aid_header):
253 """
254 Args:
255 aid_header (str): file name for the header
256 file containing AID entries.
257 """
258 self._aid_header = aid_header
259 self._aid_name_to_value = {}
260 self._aid_value_to_name = {}
261 self._oem_ranges = {}
262
263 with open(aid_header) as open_file:
264 self._parse(open_file)
265
266 try:
267 self._process_and_check()
268 except ValueError as exception:
269 sys.exit('Error processing parsed data: "%s"' % (str(exception)))
270
271 def _parse(self, aid_file):
272 """Parses an AID header file. Internal use only.
273
274 Args:
275 aid_file (file): The open AID header file to parse.
276 """
277
278 for lineno, line in enumerate(aid_file):
279 def error_message(msg):
280 """Creates an error message with the current parsing state."""
281 return 'Error "{}" in file: "{}" on line: {}'.format(
282 msg, self._aid_header, str(lineno))
283
284 if AIDHeaderParser._AID_DEFINE.match(line):
285 chunks = line.split()
286
287 if any(x in chunks[1] for x in AIDHeaderParser._SKIPWORDS):
288 continue
289
290 identifier = chunks[1]
291 value = chunks[2]
292
293 try:
294 if AIDHeaderParser._is_oem_range(identifier):
295 self._handle_oem_range(identifier, value)
296 else:
297 self._handle_aid(identifier, value)
298 except ValueError as exception:
299 sys.exit(error_message(
300 '{} for "{}"'.format(exception, identifier)))
301
302 def _handle_aid(self, identifier, value):
303 """Handle an AID C #define.
304
305 Handles an AID, sanity checking, generating the friendly name and
306 adding it to the internal maps. Internal use only.
307
308 Args:
309 identifier (str): The name of the #define identifier. ie AID_FOO.
310 value (str): The value associated with the identifier.
311
312 Raises:
313 ValueError: With message set to indicate the error.
314 """
315
William Roberts8f42ce72016-04-25 12:27:43 -0700316 aid = AID(identifier, value, self._aid_header)
William Roberts64edf5b2016-04-11 17:12:47 -0700317
318 # duplicate name
William Roberts8f42ce72016-04-25 12:27:43 -0700319 if aid.friendly in self._aid_name_to_value:
William Roberts64edf5b2016-04-11 17:12:47 -0700320 raise ValueError('Duplicate aid "%s"' % identifier)
321
322 if value in self._aid_value_to_name:
323 raise ValueError('Duplicate aid value "%u" for %s' % value,
324 identifier)
325
William Roberts8f42ce72016-04-25 12:27:43 -0700326 self._aid_name_to_value[aid.friendly] = aid
327 self._aid_value_to_name[value] = aid.friendly
William Roberts64edf5b2016-04-11 17:12:47 -0700328
329 def _handle_oem_range(self, identifier, value):
330 """Handle an OEM range C #define.
331
332 When encountering special AID defines, notably for the OEM ranges
333 this method handles sanity checking and adding them to the internal
334 maps. For internal use only.
335
336 Args:
337 identifier (str): The name of the #define identifier.
338 ie AID_OEM_RESERVED_START/END.
339 value (str): The value associated with the identifier.
340
341 Raises:
342 ValueError: With message set to indicate the error.
343 """
344
345 try:
346 int_value = int(value, 0)
347 except ValueError:
348 raise ValueError(
349 'Could not convert "%s" to integer value, got: "%s"' %
350 (identifier, value))
351
352 # convert AID_OEM_RESERVED_START or AID_OEM_RESERVED_<num>_START
353 # to AID_OEM_RESERVED or AID_OEM_RESERVED_<num>
354 is_start = identifier.endswith(AIDHeaderParser._OEM_START_KW)
355
356 if is_start:
357 tostrip = len(AIDHeaderParser._OEM_START_KW)
358 else:
359 tostrip = len(AIDHeaderParser._OEM_END_KW)
360
361 # ending _
362 tostrip = tostrip + 1
363
364 strip = identifier[:-tostrip]
365 if strip not in self._oem_ranges:
366 self._oem_ranges[strip] = []
367
368 if len(self._oem_ranges[strip]) > 2:
369 raise ValueError('Too many same OEM Ranges "%s"' % identifier)
370
371 if len(self._oem_ranges[strip]) == 1:
372 tmp = self._oem_ranges[strip][0]
373
374 if tmp == int_value:
375 raise ValueError('START and END values equal %u' % int_value)
376 elif is_start and tmp < int_value:
377 raise ValueError('END value %u less than START value %u' %
378 (tmp, int_value))
379 elif not is_start and tmp > int_value:
380 raise ValueError('END value %u less than START value %u' %
381 (int_value, tmp))
382
383 # Add START values to the head of the list and END values at the end.
384 # Thus, the list is ordered with index 0 as START and index 1 as END.
385 if is_start:
386 self._oem_ranges[strip].insert(0, int_value)
387 else:
388 self._oem_ranges[strip].append(int_value)
389
390 def _process_and_check(self):
391 """Process, check and populate internal data structures.
392
393 After parsing and generating the internal data structures, this method
394 is responsible for sanity checking ALL of the acquired data.
395
396 Raises:
397 ValueError: With the message set to indicate the specific error.
398 """
399
400 # tuplefy the lists since range() does not like them mutable.
401 self._oem_ranges = [
402 AIDHeaderParser._convert_lst_to_tup(k, v)
403 for k, v in self._oem_ranges.iteritems()
404 ]
405
406 # Check for overlapping ranges
407 for i, range1 in enumerate(self._oem_ranges):
408 for range2 in self._oem_ranges[i + 1:]:
409 if AIDHeaderParser._is_overlap(range1, range2):
410 raise ValueError("Overlapping OEM Ranges found %s and %s" %
411 (str(range1), str(range2)))
412
413 # No core AIDs should be within any oem range.
414 for aid in self._aid_value_to_name:
415
416 if Utils.in_any_range(aid, self._oem_ranges):
417 name = self._aid_value_to_name[aid]
418 raise ValueError(
419 'AID "%s" value: %u within reserved OEM Range: "%s"' %
420 (name, aid, str(self._oem_ranges)))
421
422 @property
423 def oem_ranges(self):
424 """Retrieves the OEM closed ranges as a list of tuples.
425
426 Returns:
427 A list of closed range tuples: [ (0, 42), (50, 105) ... ]
428 """
429 return self._oem_ranges
430
431 @property
432 def aids(self):
433 """Retrieves the list of found AIDs.
434
435 Returns:
436 A list of AID() objects.
437 """
William Roberts8f42ce72016-04-25 12:27:43 -0700438 return self._aid_name_to_value.values()
William Roberts64edf5b2016-04-11 17:12:47 -0700439
440 @staticmethod
441 def _convert_lst_to_tup(name, lst):
442 """Converts a mutable list to a non-mutable tuple.
443
444 Used ONLY for ranges and thus enforces a length of 2.
445
446 Args:
447 lst (List): list that should be "tuplefied".
448
449 Raises:
450 ValueError if lst is not a list or len is not 2.
451
452 Returns:
453 Tuple(lst)
454 """
455 if not lst or len(lst) != 2:
456 raise ValueError('Mismatched range for "%s"' % name)
457
458 return tuple(lst)
459
460 @staticmethod
William Roberts64edf5b2016-04-11 17:12:47 -0700461 def _is_oem_range(aid):
462 """Detects if a given aid is within the reserved OEM range.
463
464 Args:
465 aid (int): The aid to test
466
467 Returns:
468 True if it is within the range, False otherwise.
469 """
470
471 return AIDHeaderParser._OEM_RANGE.match(aid)
472
473 @staticmethod
474 def _is_overlap(range_a, range_b):
475 """Calculates the overlap of two range tuples.
476
477 A range tuple is a closed range. A closed range includes its endpoints.
478 Note that python tuples use () notation which collides with the
479 mathematical notation for open ranges.
480
481 Args:
482 range_a: The first tuple closed range eg (0, 5).
483 range_b: The second tuple closed range eg (3, 7).
484
485 Returns:
486 True if they overlap, False otherwise.
487 """
488
489 return max(range_a[0], range_b[0]) <= min(range_a[1], range_b[1])
490
491
William Roberts11c29282016-04-09 10:32:30 -0700492class FSConfigFileParser(object):
493 """Parses a config.fs ini format file.
494
495 This class is responsible for parsing the config.fs ini format files.
496 It collects and checks all the data in these files and makes it available
497 for consumption post processed.
498 """
William Roberts11c29282016-04-09 10:32:30 -0700499
William Roberts5f059a72016-04-25 10:36:45 -0700500 # These _AID vars work together to ensure that an AID section name
501 # cannot contain invalid characters for a C define or a passwd/group file.
502 # Since _AID_PREFIX is within the set of _AID_MATCH the error logic only
503 # checks end, if you change this, you may have to update the error
504 # detection code.
William Roberts8f42ce72016-04-25 12:27:43 -0700505 _AID_MATCH = re.compile('%s[A-Z0-9_]+' % AID.PREFIX)
William Roberts5f059a72016-04-25 10:36:45 -0700506 _AID_ERR_MSG = 'Expecting upper case, a number or underscore'
507
508 # list of handler to required options, used to identify the
509 # parsing section
510 _SECTIONS = [('_handle_aid', ('value',)),
511 ('_handle_path', ('mode', 'user', 'group', 'caps'))]
William Roberts11c29282016-04-09 10:32:30 -0700512
William Roberts64edf5b2016-04-11 17:12:47 -0700513 def __init__(self, config_files, oem_ranges):
William Roberts11c29282016-04-09 10:32:30 -0700514 """
515 Args:
516 config_files ([str]): The list of config.fs files to parse.
517 Note the filename is not important.
William Roberts64edf5b2016-04-11 17:12:47 -0700518 oem_ranges ([(),()]): range tuples indicating reserved OEM ranges.
William Roberts11c29282016-04-09 10:32:30 -0700519 """
520
521 self._files = []
522 self._dirs = []
523 self._aids = []
524
525 self._seen_paths = {}
526 # (name to file, value to aid)
527 self._seen_aids = ({}, {})
528
William Roberts64edf5b2016-04-11 17:12:47 -0700529 self._oem_ranges = oem_ranges
530
William Roberts11c29282016-04-09 10:32:30 -0700531 self._config_files = config_files
532
533 for config_file in self._config_files:
534 self._parse(config_file)
535
536 def _parse(self, file_name):
537 """Parses and verifies config.fs files. Internal use only.
538
539 Args:
540 file_name (str): The config.fs (PythonConfigParser file format)
541 file to parse.
542
543 Raises:
544 Anything raised by ConfigParser.read()
545 """
546
547 # Separate config parsers for each file found. If you use
548 # read(filenames...) later files can override earlier files which is
549 # not what we want. Track state across files and enforce with
550 # _handle_dup(). Note, strict ConfigParser is set to true in
551 # Python >= 3.2, so in previous versions same file sections can
552 # override previous
553 # sections.
William Robertsc950a352016-03-04 18:12:29 -0800554
555 config = ConfigParser.ConfigParser()
556 config.read(file_name)
557
William Roberts11c29282016-04-09 10:32:30 -0700558 for section in config.sections():
William Robertsc950a352016-03-04 18:12:29 -0800559
William Roberts5f059a72016-04-25 10:36:45 -0700560 found = False
561
562 for test in FSConfigFileParser._SECTIONS:
563 handler = test[0]
564 options = test[1]
565
566 if all([config.has_option(section, item) for item in options]):
567 handler = getattr(self, handler)
568 handler(file_name, section, config)
569 found = True
570 break
571
572 if not found:
573 sys.exit('Invalid section "%s" in file: "%s"' %
574 (section, file_name))
William Robertsc950a352016-03-04 18:12:29 -0800575
William Roberts11c29282016-04-09 10:32:30 -0700576 # sort entries:
577 # * specified path before prefix match
578 # ** ie foo before f*
579 # * lexicographical less than before other
580 # ** ie boo before foo
581 # Given these paths:
582 # paths=['ac', 'a', 'acd', 'an', 'a*', 'aa', 'ac*']
583 # The sort order would be:
584 # paths=['a', 'aa', 'ac', 'acd', 'an', 'ac*', 'a*']
585 # Thus the fs_config tools will match on specified paths before
586 # attempting prefix, and match on the longest matching prefix.
587 self._files.sort(key=FSConfigFileParser._file_key)
William Robertsc950a352016-03-04 18:12:29 -0800588
William Roberts11c29282016-04-09 10:32:30 -0700589 # sort on value of (file_name, name, value, strvalue)
590 # This is only cosmetic so AIDS are arranged in ascending order
591 # within the generated file.
592 self._aids.sort(key=lambda item: item.normalized_value)
William Robertsc950a352016-03-04 18:12:29 -0800593
William Roberts11c29282016-04-09 10:32:30 -0700594 def _handle_aid(self, file_name, section_name, config):
595 """Verifies an AID entry and adds it to the aid list.
William Robertsc950a352016-03-04 18:12:29 -0800596
William Roberts11c29282016-04-09 10:32:30 -0700597 Calls sys.exit() with a descriptive message of the failure.
598
599 Args:
600 file_name (str): The filename of the config file being parsed.
601 section_name (str): The section name currently being parsed.
602 config (ConfigParser): The ConfigParser section being parsed that
603 the option values will come from.
604 """
605
606 def error_message(msg):
607 """Creates an error message with current parsing state."""
608 return '{} for: "{}" file: "{}"'.format(msg, section_name,
609 file_name)
610
William Roberts5f059a72016-04-25 10:36:45 -0700611 FSConfigFileParser._handle_dup_and_add('AID', file_name, section_name,
612 self._seen_aids[0])
613
614 match = FSConfigFileParser._AID_MATCH.match(section_name)
William Roberts8f42ce72016-04-25 12:27:43 -0700615 invalid = match.end() if match else len(AID.PREFIX)
William Roberts5f059a72016-04-25 10:36:45 -0700616 if invalid != len(section_name):
617 tmp_errmsg = ('Invalid characters in AID section at "%d" for: "%s"'
618 % (invalid, FSConfigFileParser._AID_ERR_MSG))
619 sys.exit(error_message(tmp_errmsg))
620
William Roberts11c29282016-04-09 10:32:30 -0700621 value = config.get(section_name, 'value')
622
623 if not value:
624 sys.exit(error_message('Found specified but unset "value"'))
625
626 try:
627 aid = AID(section_name, value, file_name)
628 except ValueError:
629 sys.exit(
630 error_message('Invalid "value", not aid number, got: \"%s\"' %
631 value))
632
William Roberts64edf5b2016-04-11 17:12:47 -0700633 # Values must be within OEM range
634 if not Utils.in_any_range(int(aid.value, 0), self._oem_ranges):
William Roberts11c29282016-04-09 10:32:30 -0700635 emsg = '"value" not in valid range %s, got: %s'
William Roberts64edf5b2016-04-11 17:12:47 -0700636 emsg = emsg % (str(self._oem_ranges), value)
William Roberts11c29282016-04-09 10:32:30 -0700637 sys.exit(error_message(emsg))
638
639 # use the normalized int value in the dict and detect
640 # duplicate definitions of the same value
William Roberts5f059a72016-04-25 10:36:45 -0700641 FSConfigFileParser._handle_dup_and_add(
642 'AID', file_name, aid.normalized_value, self._seen_aids[1])
William Roberts11c29282016-04-09 10:32:30 -0700643
644 # Append aid tuple of (AID_*, base10(value), _path(value))
645 # We keep the _path version of value so we can print that out in the
646 # generated header so investigating parties can identify parts.
647 # We store the base10 value for sorting, so everything is ascending
648 # later.
649 self._aids.append(aid)
650
651 def _handle_path(self, file_name, section_name, config):
652 """Add a file capability entry to the internal list.
653
654 Handles a file capability entry, verifies it, and adds it to
655 to the internal dirs or files list based on path. If it ends
656 with a / its a dir. Internal use only.
657
658 Calls sys.exit() on any validation error with message set.
659
660 Args:
661 file_name (str): The current name of the file being parsed.
662 section_name (str): The name of the section to parse.
663 config (str): The config parser.
664 """
665
William Roberts5f059a72016-04-25 10:36:45 -0700666 FSConfigFileParser._handle_dup_and_add('path', file_name, section_name,
667 self._seen_paths)
668
William Roberts11c29282016-04-09 10:32:30 -0700669 mode = config.get(section_name, 'mode')
670 user = config.get(section_name, 'user')
671 group = config.get(section_name, 'group')
672 caps = config.get(section_name, 'caps')
673
674 errmsg = ('Found specified but unset option: \"%s" in file: \"' +
675 file_name + '\"')
676
677 if not mode:
678 sys.exit(errmsg % 'mode')
679
680 if not user:
681 sys.exit(errmsg % 'user')
682
683 if not group:
684 sys.exit(errmsg % 'group')
685
686 if not caps:
687 sys.exit(errmsg % 'caps')
688
689 caps = caps.split()
690
691 tmp = []
692 for cap in caps:
693 try:
694 # test if string is int, if it is, use as is.
695 int(cap, 0)
696 tmp.append('(' + cap + ')')
697 except ValueError:
698 tmp.append('(1ULL << CAP_' + cap.upper() + ')')
699
700 caps = tmp
701
702 if len(mode) == 3:
703 mode = '0' + mode
704
705 try:
706 int(mode, 8)
707 except ValueError:
708 sys.exit('Mode must be octal characters, got: "%s"' % mode)
709
710 if len(mode) != 4:
711 sys.exit('Mode must be 3 or 4 characters, got: "%s"' % mode)
712
713 caps_str = '|'.join(caps)
714
715 entry = FSConfig(mode, user, group, caps_str, section_name, file_name)
716 if section_name[-1] == '/':
717 self._dirs.append(entry)
718 else:
719 self._files.append(entry)
720
721 @property
722 def files(self):
723 """Get the list of FSConfig file entries.
724
725 Returns:
726 a list of FSConfig() objects for file paths.
727 """
728 return self._files
729
730 @property
731 def dirs(self):
732 """Get the list of FSConfig dir entries.
733
734 Returns:
735 a list of FSConfig() objects for directory paths.
736 """
737 return self._dirs
738
739 @property
740 def aids(self):
741 """Get the list of AID entries.
742
743 Returns:
744 a list of AID() objects.
745 """
746 return self._aids
747
748 @staticmethod
749 def _file_key(fs_config):
750 """Used as the key paramter to sort.
751
752 This is used as a the function to the key parameter of a sort.
753 it wraps the string supplied in a class that implements the
754 appropriate __lt__ operator for the sort on path strings. See
755 StringWrapper class for more details.
756
757 Args:
758 fs_config (FSConfig): A FSConfig entry.
759
760 Returns:
761 A StringWrapper object
762 """
763
764 # Wrapper class for custom prefix matching strings
765 class StringWrapper(object):
766 """Wrapper class used for sorting prefix strings.
767
768 The algorithm is as follows:
769 - specified path before prefix match
770 - ie foo before f*
771 - lexicographical less than before other
772 - ie boo before foo
773
774 Given these paths:
775 paths=['ac', 'a', 'acd', 'an', 'a*', 'aa', 'ac*']
776 The sort order would be:
777 paths=['a', 'aa', 'ac', 'acd', 'an', 'ac*', 'a*']
778 Thus the fs_config tools will match on specified paths before
779 attempting prefix, and match on the longest matching prefix.
780 """
781
782 def __init__(self, path):
783 """
784 Args:
785 path (str): the path string to wrap.
786 """
787 self.is_prefix = path[-1] == '*'
788 if self.is_prefix:
789 self.path = path[:-1]
790 else:
791 self.path = path
792
793 def __lt__(self, other):
794
795 # if were both suffixed the smallest string
796 # is 'bigger'
797 if self.is_prefix and other.is_prefix:
798 result = len(self.path) > len(other.path)
799 # If I am an the suffix match, im bigger
800 elif self.is_prefix:
801 result = False
802 # If other is the suffix match, he's bigger
803 elif other.is_prefix:
804 result = True
805 # Alphabetical
806 else:
807 result = self.path < other.path
808 return result
809
810 return StringWrapper(fs_config.path)
811
812 @staticmethod
William Roberts5f059a72016-04-25 10:36:45 -0700813 def _handle_dup_and_add(name, file_name, section_name, seen):
William Roberts11c29282016-04-09 10:32:30 -0700814 """Tracks and detects duplicates. Internal use only.
815
816 Calls sys.exit() on a duplicate.
817
818 Args:
819 name (str): The name to use in the error reporting. The pretty
820 name for the section.
821 file_name (str): The file currently being parsed.
822 section_name (str): The name of the section. This would be path
823 or identifier depending on what's being parsed.
824 seen (dict): The dictionary of seen things to check against.
825 """
826 if section_name in seen:
827 dups = '"' + seen[section_name] + '" and '
828 dups += file_name
829 sys.exit('Duplicate %s "%s" found in files: %s' %
830 (name, section_name, dups))
831
832 seen[section_name] = file_name
833
834
835class BaseGenerator(object):
836 """Interface for Generators.
837
838 Base class for generators, generators should implement
839 these method stubs.
840 """
841
842 def add_opts(self, opt_group):
843 """Used to add per-generator options to the command line.
844
845 Args:
846 opt_group (argument group object): The argument group to append to.
847 See the ArgParse docs for more details.
848 """
849
850 raise NotImplementedError("Not Implemented")
851
852 def __call__(self, args):
853 """This is called to do whatever magic the generator does.
854
855 Args:
856 args (dict): The arguments from ArgParse as a dictionary.
857 ie if you specified an argument of foo in add_opts, access
858 it via args['foo']
859 """
860
861 raise NotImplementedError("Not Implemented")
862
863
864@generator('fsconfig')
865class FSConfigGen(BaseGenerator):
866 """Generates the android_filesystem_config.h file.
867
868 Output is used in generating fs_config_files and fs_config_dirs.
869 """
870
871 _GENERATED = textwrap.dedent("""\
872 /*
873 * THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY
874 */
875 """)
876
William Robertscfc51f52016-04-12 08:51:13 -0700877 _INCLUDES = [
878 '<private/android_filesystem_config.h>', '"generated_oem_aid.h"'
879 ]
William Roberts11c29282016-04-09 10:32:30 -0700880
881 _DEFINE_NO_DIRS = '#define NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS'
882 _DEFINE_NO_FILES = '#define NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_FILES'
883
884 _DEFAULT_WARNING = (
885 '#warning No device-supplied android_filesystem_config.h,'
886 ' using empty default.')
887
888 # Long names.
889 # pylint: disable=invalid-name
890 _NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS_ENTRY = (
891 '{ 00000, AID_ROOT, AID_ROOT, 0,'
892 '"system/etc/fs_config_dirs" },')
893
894 _NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_FILES_ENTRY = (
895 '{ 00000, AID_ROOT, AID_ROOT, 0,'
896 '"system/etc/fs_config_files" },')
897
898 _IFDEF_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS = (
899 '#ifdef NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS')
900 # pylint: enable=invalid-name
901
902 _ENDIF = '#endif'
903
904 _OPEN_FILE_STRUCT = (
905 'static const struct fs_path_config android_device_files[] = {')
906
907 _OPEN_DIR_STRUCT = (
908 'static const struct fs_path_config android_device_dirs[] = {')
909
910 _CLOSE_FILE_STRUCT = '};'
911
912 _GENERIC_DEFINE = "#define %s\t%s"
913
914 _FILE_COMMENT = '// Defined in file: \"%s\"'
915
William Roberts8f42ce72016-04-25 12:27:43 -0700916 def __init__(self, *args, **kwargs):
917 BaseGenerator.__init__(args, kwargs)
918
919 self._oem_parser = None
920 self._base_parser = None
921 self._friendly_to_aid = None
922
William Roberts11c29282016-04-09 10:32:30 -0700923 def add_opts(self, opt_group):
924
925 opt_group.add_argument(
926 'fsconfig', nargs='+', help='The list of fsconfig files to parse')
927
William Roberts64edf5b2016-04-11 17:12:47 -0700928 opt_group.add_argument(
929 '--aid-header',
930 required=True,
931 help='An android_filesystem_config.h file'
932 ' to parse AIDs and OEM Ranges from')
933
William Roberts11c29282016-04-09 10:32:30 -0700934 def __call__(self, args):
935
William Roberts8f42ce72016-04-25 12:27:43 -0700936 self._base_parser = AIDHeaderParser(args['aid_header'])
937 self._oem_parser = FSConfigFileParser(args['fsconfig'],
938 self._base_parser.oem_ranges)
939 base_aids = self._base_parser.aids
940 oem_aids = self._oem_parser.aids
William Roberts64edf5b2016-04-11 17:12:47 -0700941
William Roberts8f42ce72016-04-25 12:27:43 -0700942 # Detect name collisions on AIDs. Since friendly works as the
943 # identifier for collision testing and we need friendly later on for
944 # name resolution, just calculate and use friendly.
945 # {aid.friendly: aid for aid in base_aids}
946 base_friendly = {aid.friendly: aid for aid in base_aids}
947 oem_friendly = {aid.friendly: aid for aid in oem_aids}
William Roberts11c29282016-04-09 10:32:30 -0700948
William Roberts8f42ce72016-04-25 12:27:43 -0700949 base_set = set(base_friendly.keys())
950 oem_set = set(oem_friendly.keys())
William Roberts11c29282016-04-09 10:32:30 -0700951
William Roberts8f42ce72016-04-25 12:27:43 -0700952 common = base_set & oem_set
953
954 if len(common) > 0:
955 emsg = 'Following AID Collisions detected for: \n'
956 for friendly in common:
957 base = base_friendly[friendly]
958 oem = oem_friendly[friendly]
959 emsg += (
960 'Identifier: "%s" Friendly Name: "%s" '
961 'found in file "%s" and "%s"' %
962 (base.identifier, base.friendly, base.found, oem.found))
963 sys.exit(emsg)
964
965 self._friendly_to_aid = oem_friendly
966 self._friendly_to_aid.update(base_friendly)
967
968 self._generate()
969
970 def _to_fs_entry(self, fs_config):
971 """Converts an FSConfig entry to an fs entry.
972
973 Prints '{ mode, user, group, caps, "path" },'.
974
975 Calls sys.exit() on error.
William Roberts11c29282016-04-09 10:32:30 -0700976
977 Args:
978 fs_config (FSConfig): The entry to convert to
979 a valid C array entry.
980 """
981
982 # Get some short names
983 mode = fs_config.mode
984 user = fs_config.user
985 group = fs_config.group
986 fname = fs_config.filename
987 caps = fs_config.caps
988 path = fs_config.path
989
William Roberts8f42ce72016-04-25 12:27:43 -0700990 emsg = 'Cannot convert friendly name "%s" to identifier!'
991
992 # remap friendly names to identifier names
993 if AID.is_friendly(user):
994 if user not in self._friendly_to_aid:
995 sys.exit(emsg % user)
996 user = self._friendly_to_aid[user].identifier
997
998 if AID.is_friendly(group):
999 if group not in self._friendly_to_aid:
1000 sys.exit(emsg % group)
1001 group = self._friendly_to_aid[group].identifier
1002
William Roberts11c29282016-04-09 10:32:30 -07001003 fmt = '{ %s, %s, %s, %s, "%s" },'
1004
1005 expanded = fmt % (mode, user, group, caps, path)
1006
1007 print FSConfigGen._FILE_COMMENT % fname
1008 print ' ' + expanded
1009
1010 @staticmethod
William Robertscfc51f52016-04-12 08:51:13 -07001011 def _gen_inc():
William Roberts8f42ce72016-04-25 12:27:43 -07001012 """Generate the include header lines and print to stdout."""
William Robertscfc51f52016-04-12 08:51:13 -07001013 for include in FSConfigGen._INCLUDES:
1014 print '#include %s' % include
1015
William Roberts8f42ce72016-04-25 12:27:43 -07001016 def _generate(self):
William Roberts11c29282016-04-09 10:32:30 -07001017 """Generates an OEM android_filesystem_config.h header file to stdout.
1018
1019 Args:
1020 files ([FSConfig]): A list of FSConfig objects for file entries.
1021 dirs ([FSConfig]): A list of FSConfig objects for directory
1022 entries.
1023 aids ([AIDS]): A list of AID objects for Android Id entries.
1024 """
1025 print FSConfigGen._GENERATED
William Robertscfc51f52016-04-12 08:51:13 -07001026 print
1027
1028 FSConfigGen._gen_inc()
William Robertsc950a352016-03-04 18:12:29 -08001029 print
1030
William Roberts8f42ce72016-04-25 12:27:43 -07001031 dirs = self._oem_parser.dirs
1032 files = self._oem_parser.files
1033 aids = self._oem_parser.aids
1034
William Roberts11c29282016-04-09 10:32:30 -07001035 are_dirs = len(dirs) > 0
1036 are_files = len(files) > 0
1037 are_aids = len(aids) > 0
William Robertsc950a352016-03-04 18:12:29 -08001038
William Roberts11c29282016-04-09 10:32:30 -07001039 if are_aids:
1040 for aid in aids:
1041 # use the preserved _path value
1042 print FSConfigGen._FILE_COMMENT % aid.found
1043 print FSConfigGen._GENERIC_DEFINE % (aid.identifier, aid.value)
William Robertsc950a352016-03-04 18:12:29 -08001044
William Roberts11c29282016-04-09 10:32:30 -07001045 print
William Robertsc950a352016-03-04 18:12:29 -08001046
1047 if not are_dirs:
William Roberts11c29282016-04-09 10:32:30 -07001048 print FSConfigGen._DEFINE_NO_DIRS + '\n'
William Robertsc950a352016-03-04 18:12:29 -08001049
William Roberts11c29282016-04-09 10:32:30 -07001050 if not are_files:
1051 print FSConfigGen._DEFINE_NO_FILES + '\n'
William Robertsc950a352016-03-04 18:12:29 -08001052
William Roberts11c29282016-04-09 10:32:30 -07001053 if not are_files and not are_dirs and not are_aids:
William Roberts11c29282016-04-09 10:32:30 -07001054 return
William Robertsc950a352016-03-04 18:12:29 -08001055
William Roberts11c29282016-04-09 10:32:30 -07001056 if are_files:
1057 print FSConfigGen._OPEN_FILE_STRUCT
1058 for fs_config in files:
William Roberts8f42ce72016-04-25 12:27:43 -07001059 self._to_fs_entry(fs_config)
William Robertsc950a352016-03-04 18:12:29 -08001060
William Roberts11c29282016-04-09 10:32:30 -07001061 if not are_dirs:
1062 print FSConfigGen._IFDEF_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS
1063 print(
1064 ' ' +
1065 FSConfigGen._NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS_ENTRY)
1066 print FSConfigGen._ENDIF
1067 print FSConfigGen._CLOSE_FILE_STRUCT
William Robertsc950a352016-03-04 18:12:29 -08001068
William Roberts11c29282016-04-09 10:32:30 -07001069 if are_dirs:
1070 print FSConfigGen._OPEN_DIR_STRUCT
1071 for dir_entry in dirs:
William Roberts8f42ce72016-04-25 12:27:43 -07001072 self._to_fs_entry(dir_entry)
William Robertsc950a352016-03-04 18:12:29 -08001073
William Roberts11c29282016-04-09 10:32:30 -07001074 print FSConfigGen._CLOSE_FILE_STRUCT
William Robertsc950a352016-03-04 18:12:29 -08001075
William Robertsc950a352016-03-04 18:12:29 -08001076
William Robertsd7104bc2016-04-11 21:17:12 -07001077@generator('aidarray')
1078class AIDArrayGen(BaseGenerator):
1079 """Generates the android_id static array."""
1080
1081 _GENERATED = ('/*\n'
1082 ' * THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY!\n'
1083 ' */')
1084
1085 _INCLUDE = '#include <private/android_filesystem_config.h>'
1086
1087 _STRUCT_FS_CONFIG = textwrap.dedent("""
1088 struct android_id_info {
1089 const char *name;
1090 unsigned aid;
1091 };""")
1092
1093 _OPEN_ID_ARRAY = 'static const struct android_id_info android_ids[] = {'
1094
1095 _ID_ENTRY = ' { "%s", %s },'
1096
1097 _CLOSE_FILE_STRUCT = '};'
1098
1099 _COUNT = ('#define android_id_count \\\n'
1100 ' (sizeof(android_ids) / sizeof(android_ids[0]))')
1101
1102 def add_opts(self, opt_group):
1103
1104 opt_group.add_argument(
1105 'hdrfile', help='The android_filesystem_config.h'
1106 'file to parse')
1107
1108 def __call__(self, args):
1109
1110 hdr = AIDHeaderParser(args['hdrfile'])
1111
1112 print AIDArrayGen._GENERATED
1113 print
1114 print AIDArrayGen._INCLUDE
1115 print
1116 print AIDArrayGen._STRUCT_FS_CONFIG
1117 print
1118 print AIDArrayGen._OPEN_ID_ARRAY
1119
William Roberts8f42ce72016-04-25 12:27:43 -07001120 for aid in hdr.aids:
1121 print AIDArrayGen._ID_ENTRY % (aid.friendly, aid.identifier)
William Robertsd7104bc2016-04-11 21:17:12 -07001122
1123 print AIDArrayGen._CLOSE_FILE_STRUCT
1124 print
1125 print AIDArrayGen._COUNT
1126 print
1127
1128
William Robertscfc51f52016-04-12 08:51:13 -07001129@generator('oemaid')
1130class OEMAidGen(BaseGenerator):
1131 """Generates the OEM AID_<name> value header file."""
1132
1133 _GENERATED = ('/*\n'
1134 ' * THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY!\n'
1135 ' */')
1136
1137 _GENERIC_DEFINE = "#define %s\t%s"
1138
1139 _FILE_COMMENT = '// Defined in file: \"%s\"'
1140
1141 # Intentional trailing newline for readability.
1142 _FILE_IFNDEF_DEFINE = ('#ifndef GENERATED_OEM_AIDS_H_\n'
1143 '#define GENERATED_OEM_AIDS_H_\n')
1144
1145 _FILE_ENDIF = '#endif'
1146
1147 def __init__(self):
1148
1149 self._old_file = None
1150
1151 def add_opts(self, opt_group):
1152
1153 opt_group.add_argument(
1154 'fsconfig', nargs='+', help='The list of fsconfig files to parse.')
1155
1156 opt_group.add_argument(
1157 '--aid-header',
1158 required=True,
1159 help='An android_filesystem_config.h file'
1160 'to parse AIDs and OEM Ranges from')
1161
1162 def __call__(self, args):
1163
1164 hdr_parser = AIDHeaderParser(args['aid_header'])
1165
1166 parser = FSConfigFileParser(args['fsconfig'], hdr_parser.oem_ranges)
1167
1168 print OEMAidGen._GENERATED
1169
1170 print OEMAidGen._FILE_IFNDEF_DEFINE
1171
1172 for aid in parser.aids:
1173 self._print_aid(aid)
1174 print
1175
1176 print OEMAidGen._FILE_ENDIF
1177
1178 def _print_aid(self, aid):
1179 """Prints a valid #define AID identifier to stdout.
1180
1181 Args:
1182 aid to print
1183 """
1184
1185 # print the source file location of the AID
1186 found_file = aid.found
1187 if found_file != self._old_file:
1188 print OEMAidGen._FILE_COMMENT % found_file
1189 self._old_file = found_file
1190
1191 print OEMAidGen._GENERIC_DEFINE % (aid.identifier, aid.value)
1192
1193
William Roberts316f9462016-04-25 15:00:34 -07001194@generator('passwd')
1195class PasswdGen(BaseGenerator):
1196 """Generates the /etc/passwd file per man (5) passwd."""
1197
1198 _GENERATED = ('#\n# THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY!\n#')
1199
1200 _FILE_COMMENT = '# Defined in file: \"%s\"'
1201
1202 def __init__(self):
1203
1204 self._old_file = None
1205
1206 def add_opts(self, opt_group):
1207
1208 opt_group.add_argument(
1209 'fsconfig', nargs='+', help='The list of fsconfig files to parse.')
1210
1211 opt_group.add_argument(
1212 '--aid-header',
1213 required=True,
1214 help='An android_filesystem_config.h file'
1215 'to parse AIDs and OEM Ranges from')
1216
1217 def __call__(self, args):
1218
1219 hdr_parser = AIDHeaderParser(args['aid_header'])
1220
1221 parser = FSConfigFileParser(args['fsconfig'], hdr_parser.oem_ranges)
1222
1223 aids = parser.aids
1224
1225 # nothing to do if no aids defined
1226 if len(aids) == 0:
1227 return
1228
1229 print PasswdGen._GENERATED
1230
1231 for aid in aids:
William Roberts1c4721c2016-04-26 13:05:34 -07001232 self._print_formatted_line(aid)
William Roberts316f9462016-04-25 15:00:34 -07001233
William Roberts1c4721c2016-04-26 13:05:34 -07001234 def _print_formatted_line(self, aid):
1235 """Prints the aid to stdout in the passwd format. Internal use only.
William Roberts316f9462016-04-25 15:00:34 -07001236
1237 Colon delimited:
1238 login name, friendly name
1239 encrypted password (optional)
1240 uid (int)
1241 gid (int)
1242 User name or comment field
1243 home directory
1244 interpreter (optional)
1245
1246 Args:
1247 aid (AID): The aid to print.
1248 """
1249 if self._old_file != aid.found:
1250 self._old_file = aid.found
1251 print PasswdGen._FILE_COMMENT % aid.found
1252
William Roberts1c4721c2016-04-26 13:05:34 -07001253 try:
1254 logon, uid = Utils.get_login_and_uid_cleansed(aid)
1255 except ValueError as exception:
1256 sys.exit(exception)
William Roberts316f9462016-04-25 15:00:34 -07001257
1258 print "%s::%s:%s::/:/system/bin/sh" % (logon, uid, uid)
1259
1260
William Roberts1c4721c2016-04-26 13:05:34 -07001261@generator('group')
1262class GroupGen(PasswdGen):
1263 """Generates the /etc/group file per man (5) group."""
1264
1265 # Overrides parent
1266 def _print_formatted_line(self, aid):
1267 """Prints the aid to stdout in the group format. Internal use only.
1268
1269 Formatted (per man 5 group) like:
1270 group_name:password:GID:user_list
1271
1272 Args:
1273 aid (AID): The aid to print.
1274 """
1275 if self._old_file != aid.found:
1276 self._old_file = aid.found
1277 print PasswdGen._FILE_COMMENT % aid.found
1278
1279 try:
1280 logon, uid = Utils.get_login_and_uid_cleansed(aid)
1281 except ValueError as exception:
1282 sys.exit(exception)
1283
1284 print "%s::%s:" % (logon, uid)
1285
1286
William Robertsc950a352016-03-04 18:12:29 -08001287def main():
William Roberts11c29282016-04-09 10:32:30 -07001288 """Main entry point for execution."""
William Robertsc950a352016-03-04 18:12:29 -08001289
William Roberts11c29282016-04-09 10:32:30 -07001290 opt_parser = argparse.ArgumentParser(
1291 description='A tool for parsing fsconfig config files and producing' +
1292 'digestable outputs.')
1293 subparser = opt_parser.add_subparsers(help='generators')
William Robertsc950a352016-03-04 18:12:29 -08001294
William Roberts11c29282016-04-09 10:32:30 -07001295 gens = generator.get()
William Robertsc950a352016-03-04 18:12:29 -08001296
William Roberts11c29282016-04-09 10:32:30 -07001297 # for each gen, instantiate and add them as an option
1298 for name, gen in gens.iteritems():
William Robertsc950a352016-03-04 18:12:29 -08001299
William Roberts11c29282016-04-09 10:32:30 -07001300 generator_option_parser = subparser.add_parser(name, help=gen.__doc__)
1301 generator_option_parser.set_defaults(which=name)
William Roberts8cb6a182016-04-08 22:06:19 -07001302
William Roberts11c29282016-04-09 10:32:30 -07001303 opt_group = generator_option_parser.add_argument_group(name +
1304 ' options')
1305 gen.add_opts(opt_group)
William Roberts8cb6a182016-04-08 22:06:19 -07001306
William Roberts11c29282016-04-09 10:32:30 -07001307 args = opt_parser.parse_args()
1308
1309 args_as_dict = vars(args)
1310 which = args_as_dict['which']
1311 del args_as_dict['which']
1312
1313 gens[which](args_as_dict)
1314
William Robertsc950a352016-03-04 18:12:29 -08001315
1316if __name__ == '__main__':
1317 main()