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