blob: ed63c0e112d96fa4ee33afcbd6ab568f3e9057c9 [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:
Tom Cherryee0610e2018-02-08 14:26:53 -0800149 ValueError: if the friendly name is longer than 31 characters as
150 that is bionic's internal buffer size for name.
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000151 ValueError: if value is not a valid string number as processed by
152 int(x, 0)
153 """
154 self.identifier = identifier
155 self.value = value
156 self.found = found
Tom Cherryee0610e2018-02-08 14:26:53 -0800157 try:
158 self.normalized_value = str(int(value, 0))
159 except ValueException:
160 raise ValueError('Invalid "value", not aid number, got: \"%s\"' % value)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000161
162 # Where we calculate the friendly name
163 friendly = identifier[len(AID.PREFIX):].lower()
164 self.friendly = AID._fixup_friendly(friendly)
165
Tom Cherryee0610e2018-02-08 14:26:53 -0800166 if len(self.friendly) > 31:
167 raise ValueError('AID names must be under 32 characters "%s"' % self.friendly)
168
169
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000170 def __eq__(self, other):
171
172 return self.identifier == other.identifier \
173 and self.value == other.value and self.found == other.found \
174 and self.normalized_value == other.normalized_value
175
176 @staticmethod
177 def is_friendly(name):
178 """Determines if an AID is a freindly name or C define.
179
180 For example if name is AID_SYSTEM it returns false, if name
181 was system, it would return true.
182
183 Returns:
184 True if name is a friendly name False otherwise.
185 """
186
187 return not name.startswith(AID.PREFIX)
188
189 @staticmethod
190 def _fixup_friendly(friendly):
191 """Fixup friendly names that historically don't follow the convention.
192
193 Args:
194 friendly (str): The friendly name.
195
196 Returns:
197 The fixedup friendly name as a str.
198 """
199
200 if friendly in AID._FIXUPS:
201 return AID._FIXUPS[friendly]
202
203 return friendly
204
205
206class FSConfig(object):
207 """Represents a filesystem config array entry.
208
209 Represents a file system configuration entry for specifying
210 file system capabilities.
211
212 Attributes:
213 mode (str): The mode of the file or directory.
214 user (str): The uid or #define identifier (AID_SYSTEM)
215 group (str): The gid or #define identifier (AID_SYSTEM)
216 caps (str): The capability set.
217 filename (str): The file it was found in.
218 """
219
220 def __init__(self, mode, user, group, caps, path, filename):
221 """
222 Args:
223 mode (str): The mode of the file or directory.
224 user (str): The uid or #define identifier (AID_SYSTEM)
225 group (str): The gid or #define identifier (AID_SYSTEM)
226 caps (str): The capability set as a list.
227 filename (str): The file it was found in.
228 """
229 self.mode = mode
230 self.user = user
231 self.group = group
232 self.caps = caps
233 self.path = path
234 self.filename = filename
235
236 def __eq__(self, other):
237
238 return self.mode == other.mode and self.user == other.user \
239 and self.group == other.group and self.caps == other.caps \
240 and self.path == other.path and self.filename == other.filename
241
242
243class AIDHeaderParser(object):
244 """Parses an android_filesystem_config.h file.
245
246 Parses a C header file and extracts lines starting with #define AID_<name>
247 while capturing the OEM defined ranges and ignoring other ranges. It also
248 skips some hardcoded AIDs it doesn't need to generate a mapping for.
249 It provides some basic sanity checks. The information extracted from this
250 file can later be used to sanity check other things (like oem ranges) as
251 well as generating a mapping of names to uids. It was primarily designed to
252 parse the private/android_filesystem_config.h, but any C header should
253 work.
254 """
255
256
257 _SKIP_AIDS = [
258 re.compile(r'%sUNUSED[0-9].*' % AID.PREFIX),
259 re.compile(r'%sAPP' % AID.PREFIX), re.compile(r'%sUSER' % AID.PREFIX)
260 ]
261 _AID_DEFINE = re.compile(r'\s*#define\s+%s.*' % AID.PREFIX)
262 _OEM_START_KW = 'START'
263 _OEM_END_KW = 'END'
Johan Redestig1552a282017-01-03 09:36:47 +0100264 _OEM_RANGE = re.compile('%sOEM_RESERVED_[0-9]*_{0,1}(%s|%s)' %
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000265 (AID.PREFIX, _OEM_START_KW, _OEM_END_KW))
266 # AID lines cannot end with _START or _END, ie AID_FOO is OK
267 # but AID_FOO_START is skiped. Note that AID_FOOSTART is NOT skipped.
268 _AID_SKIP_RANGE = ['_' + _OEM_START_KW, '_' + _OEM_END_KW]
269 _COLLISION_OK = ['AID_APP', 'AID_APP_START', 'AID_USER', 'AID_USER_OFFSET']
270
271 def __init__(self, aid_header):
272 """
273 Args:
274 aid_header (str): file name for the header
275 file containing AID entries.
276 """
277 self._aid_header = aid_header
278 self._aid_name_to_value = {}
279 self._aid_value_to_name = {}
280 self._oem_ranges = {}
281
282 with open(aid_header) as open_file:
283 self._parse(open_file)
William Roberts64edf5b2016-04-11 17:12:47 -0700284
285 try:
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000286 self._process_and_check()
287 except ValueError as exception:
288 sys.exit('Error processing parsed data: "%s"' % (str(exception)))
289
290 def _parse(self, aid_file):
291 """Parses an AID header file. Internal use only.
292
293 Args:
294 aid_file (file): The open AID header file to parse.
295 """
296
297 for lineno, line in enumerate(aid_file):
William Roberts820421c2016-12-13 19:12:35 -0800298
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000299 def error_message(msg):
300 """Creates an error message with the current parsing state."""
William Roberts4165c632016-12-13 19:17:07 -0800301 # pylint: disable=cell-var-from-loop
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000302 return 'Error "{}" in file: "{}" on line: {}'.format(
303 msg, self._aid_header, str(lineno))
304
305 if AIDHeaderParser._AID_DEFINE.match(line):
306 chunks = line.split()
307 identifier = chunks[1]
308 value = chunks[2]
309
310 if any(x.match(identifier) for x in AIDHeaderParser._SKIP_AIDS):
311 continue
312
313 try:
314 if AIDHeaderParser._is_oem_range(identifier):
315 self._handle_oem_range(identifier, value)
316 elif not any(
317 identifier.endswith(x)
318 for x in AIDHeaderParser._AID_SKIP_RANGE):
319 self._handle_aid(identifier, value)
320 except ValueError as exception:
William Roberts820421c2016-12-13 19:12:35 -0800321 sys.exit(
322 error_message('{} for "{}"'.format(exception,
323 identifier)))
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000324
325 def _handle_aid(self, identifier, value):
326 """Handle an AID C #define.
327
328 Handles an AID, sanity checking, generating the friendly name and
329 adding it to the internal maps. Internal use only.
330
331 Args:
332 identifier (str): The name of the #define identifier. ie AID_FOO.
333 value (str): The value associated with the identifier.
334
335 Raises:
336 ValueError: With message set to indicate the error.
337 """
338
339 aid = AID(identifier, value, self._aid_header)
340
341 # duplicate name
342 if aid.friendly in self._aid_name_to_value:
343 raise ValueError('Duplicate aid "%s"' % identifier)
344
345 if value in self._aid_value_to_name and aid.identifier not in AIDHeaderParser._COLLISION_OK:
346 raise ValueError('Duplicate aid value "%s" for %s' % (value,
347 identifier))
348
349 self._aid_name_to_value[aid.friendly] = aid
350 self._aid_value_to_name[value] = aid.friendly
351
352 def _handle_oem_range(self, identifier, value):
353 """Handle an OEM range C #define.
354
355 When encountering special AID defines, notably for the OEM ranges
356 this method handles sanity checking and adding them to the internal
357 maps. For internal use only.
358
359 Args:
360 identifier (str): The name of the #define identifier.
361 ie AID_OEM_RESERVED_START/END.
362 value (str): The value associated with the identifier.
363
364 Raises:
365 ValueError: With message set to indicate the error.
366 """
367
368 try:
369 int_value = int(value, 0)
William Roberts64edf5b2016-04-11 17:12:47 -0700370 except ValueError:
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000371 raise ValueError(
372 'Could not convert "%s" to integer value, got: "%s"' %
373 (identifier, value))
William Roberts64edf5b2016-04-11 17:12:47 -0700374
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000375 # convert AID_OEM_RESERVED_START or AID_OEM_RESERVED_<num>_START
376 # to AID_OEM_RESERVED or AID_OEM_RESERVED_<num>
377 is_start = identifier.endswith(AIDHeaderParser._OEM_START_KW)
William Roberts64edf5b2016-04-11 17:12:47 -0700378
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000379 if is_start:
380 tostrip = len(AIDHeaderParser._OEM_START_KW)
381 else:
382 tostrip = len(AIDHeaderParser._OEM_END_KW)
William Roberts64edf5b2016-04-11 17:12:47 -0700383
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000384 # ending _
385 tostrip = tostrip + 1
William Roberts64edf5b2016-04-11 17:12:47 -0700386
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000387 strip = identifier[:-tostrip]
388 if strip not in self._oem_ranges:
389 self._oem_ranges[strip] = []
William Roberts64edf5b2016-04-11 17:12:47 -0700390
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000391 if len(self._oem_ranges[strip]) > 2:
392 raise ValueError('Too many same OEM Ranges "%s"' % identifier)
William Roberts64edf5b2016-04-11 17:12:47 -0700393
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000394 if len(self._oem_ranges[strip]) == 1:
395 tmp = self._oem_ranges[strip][0]
William Roberts64edf5b2016-04-11 17:12:47 -0700396
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000397 if tmp == int_value:
398 raise ValueError('START and END values equal %u' % int_value)
399 elif is_start and tmp < int_value:
400 raise ValueError('END value %u less than START value %u' %
401 (tmp, int_value))
402 elif not is_start and tmp > int_value:
403 raise ValueError('END value %u less than START value %u' %
404 (int_value, tmp))
William Roberts64edf5b2016-04-11 17:12:47 -0700405
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000406 # Add START values to the head of the list and END values at the end.
407 # Thus, the list is ordered with index 0 as START and index 1 as END.
408 if is_start:
409 self._oem_ranges[strip].insert(0, int_value)
410 else:
411 self._oem_ranges[strip].append(int_value)
William Roberts64edf5b2016-04-11 17:12:47 -0700412
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000413 def _process_and_check(self):
414 """Process, check and populate internal data structures.
William Roberts64edf5b2016-04-11 17:12:47 -0700415
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000416 After parsing and generating the internal data structures, this method
417 is responsible for sanity checking ALL of the acquired data.
William Roberts64edf5b2016-04-11 17:12:47 -0700418
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000419 Raises:
420 ValueError: With the message set to indicate the specific error.
421 """
William Roberts64edf5b2016-04-11 17:12:47 -0700422
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000423 # tuplefy the lists since range() does not like them mutable.
424 self._oem_ranges = [
425 AIDHeaderParser._convert_lst_to_tup(k, v)
426 for k, v in self._oem_ranges.iteritems()
427 ]
William Roberts64edf5b2016-04-11 17:12:47 -0700428
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000429 # Check for overlapping ranges
430 for i, range1 in enumerate(self._oem_ranges):
431 for range2 in self._oem_ranges[i + 1:]:
432 if AIDHeaderParser._is_overlap(range1, range2):
433 raise ValueError("Overlapping OEM Ranges found %s and %s" %
434 (str(range1), str(range2)))
William Roberts64edf5b2016-04-11 17:12:47 -0700435
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000436 # No core AIDs should be within any oem range.
437 for aid in self._aid_value_to_name:
438
439 if Utils.in_any_range(aid, self._oem_ranges):
440 name = self._aid_value_to_name[aid]
441 raise ValueError(
442 'AID "%s" value: %u within reserved OEM Range: "%s"' %
443 (name, aid, str(self._oem_ranges)))
444
445 @property
446 def oem_ranges(self):
447 """Retrieves the OEM closed ranges as a list of tuples.
448
449 Returns:
450 A list of closed range tuples: [ (0, 42), (50, 105) ... ]
451 """
452 return self._oem_ranges
453
454 @property
455 def aids(self):
456 """Retrieves the list of found AIDs.
457
458 Returns:
459 A list of AID() objects.
460 """
461 return self._aid_name_to_value.values()
462
463 @staticmethod
464 def _convert_lst_to_tup(name, lst):
465 """Converts a mutable list to a non-mutable tuple.
466
467 Used ONLY for ranges and thus enforces a length of 2.
468
469 Args:
470 lst (List): list that should be "tuplefied".
471
472 Raises:
473 ValueError if lst is not a list or len is not 2.
474
475 Returns:
476 Tuple(lst)
477 """
478 if not lst or len(lst) != 2:
479 raise ValueError('Mismatched range for "%s"' % name)
480
481 return tuple(lst)
482
483 @staticmethod
484 def _is_oem_range(aid):
485 """Detects if a given aid is within the reserved OEM range.
486
487 Args:
488 aid (int): The aid to test
489
490 Returns:
491 True if it is within the range, False otherwise.
492 """
493
494 return AIDHeaderParser._OEM_RANGE.match(aid)
495
496 @staticmethod
497 def _is_overlap(range_a, range_b):
498 """Calculates the overlap of two range tuples.
499
500 A range tuple is a closed range. A closed range includes its endpoints.
501 Note that python tuples use () notation which collides with the
502 mathematical notation for open ranges.
503
504 Args:
505 range_a: The first tuple closed range eg (0, 5).
506 range_b: The second tuple closed range eg (3, 7).
507
508 Returns:
509 True if they overlap, False otherwise.
510 """
511
512 return max(range_a[0], range_b[0]) <= min(range_a[1], range_b[1])
William Roberts64edf5b2016-04-11 17:12:47 -0700513
514
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000515class FSConfigFileParser(object):
516 """Parses a config.fs ini format file.
William Roberts11c29282016-04-09 10:32:30 -0700517
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000518 This class is responsible for parsing the config.fs ini format files.
519 It collects and checks all the data in these files and makes it available
520 for consumption post processed.
521 """
William Roberts11c29282016-04-09 10:32:30 -0700522
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000523 # These _AID vars work together to ensure that an AID section name
524 # cannot contain invalid characters for a C define or a passwd/group file.
525 # Since _AID_PREFIX is within the set of _AID_MATCH the error logic only
526 # checks end, if you change this, you may have to update the error
527 # detection code.
528 _AID_MATCH = re.compile('%s[A-Z0-9_]+' % AID.PREFIX)
529 _AID_ERR_MSG = 'Expecting upper case, a number or underscore'
William Roberts5f059a72016-04-25 10:36:45 -0700530
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000531 # list of handler to required options, used to identify the
532 # parsing section
533 _SECTIONS = [('_handle_aid', ('value',)),
534 ('_handle_path', ('mode', 'user', 'group', 'caps'))]
535
536 def __init__(self, config_files, oem_ranges):
537 """
538 Args:
539 config_files ([str]): The list of config.fs files to parse.
540 Note the filename is not important.
541 oem_ranges ([(),()]): range tuples indicating reserved OEM ranges.
542 """
543
544 self._files = []
545 self._dirs = []
546 self._aids = []
547
548 self._seen_paths = {}
549 # (name to file, value to aid)
550 self._seen_aids = ({}, {})
551
552 self._oem_ranges = oem_ranges
553
554 self._config_files = config_files
555
556 for config_file in self._config_files:
557 self._parse(config_file)
558
559 def _parse(self, file_name):
560 """Parses and verifies config.fs files. Internal use only.
561
562 Args:
563 file_name (str): The config.fs (PythonConfigParser file format)
564 file to parse.
565
566 Raises:
567 Anything raised by ConfigParser.read()
568 """
569
570 # Separate config parsers for each file found. If you use
571 # read(filenames...) later files can override earlier files which is
572 # not what we want. Track state across files and enforce with
573 # _handle_dup(). Note, strict ConfigParser is set to true in
574 # Python >= 3.2, so in previous versions same file sections can
575 # override previous
576 # sections.
William Robertsc950a352016-03-04 18:12:29 -0800577
578 config = ConfigParser.ConfigParser()
579 config.read(file_name)
580
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000581 for section in config.sections():
William Robertsc950a352016-03-04 18:12:29 -0800582
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000583 found = False
William Roberts5f059a72016-04-25 10:36:45 -0700584
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000585 for test in FSConfigFileParser._SECTIONS:
586 handler = test[0]
587 options = test[1]
William Roberts5f059a72016-04-25 10:36:45 -0700588
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000589 if all([config.has_option(section, item) for item in options]):
590 handler = getattr(self, handler)
591 handler(file_name, section, config)
592 found = True
593 break
William Roberts5f059a72016-04-25 10:36:45 -0700594
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000595 if not found:
596 sys.exit('Invalid section "%s" in file: "%s"' %
597 (section, file_name))
William Roberts11c29282016-04-09 10:32:30 -0700598
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000599 # sort entries:
600 # * specified path before prefix match
601 # ** ie foo before f*
602 # * lexicographical less than before other
603 # ** ie boo before foo
604 # Given these paths:
605 # paths=['ac', 'a', 'acd', 'an', 'a*', 'aa', 'ac*']
606 # The sort order would be:
607 # paths=['a', 'aa', 'ac', 'acd', 'an', 'ac*', 'a*']
608 # Thus the fs_config tools will match on specified paths before
609 # attempting prefix, and match on the longest matching prefix.
610 self._files.sort(key=FSConfigFileParser._file_key)
611
612 # sort on value of (file_name, name, value, strvalue)
613 # This is only cosmetic so AIDS are arranged in ascending order
614 # within the generated file.
615 self._aids.sort(key=lambda item: item.normalized_value)
616
617 def _handle_aid(self, file_name, section_name, config):
618 """Verifies an AID entry and adds it to the aid list.
619
620 Calls sys.exit() with a descriptive message of the failure.
621
622 Args:
623 file_name (str): The filename of the config file being parsed.
624 section_name (str): The section name currently being parsed.
625 config (ConfigParser): The ConfigParser section being parsed that
626 the option values will come from.
627 """
628
629 def error_message(msg):
630 """Creates an error message with current parsing state."""
631 return '{} for: "{}" file: "{}"'.format(msg, section_name,
632 file_name)
633
634 FSConfigFileParser._handle_dup_and_add('AID', file_name, section_name,
635 self._seen_aids[0])
636
637 match = FSConfigFileParser._AID_MATCH.match(section_name)
638 invalid = match.end() if match else len(AID.PREFIX)
639 if invalid != len(section_name):
640 tmp_errmsg = ('Invalid characters in AID section at "%d" for: "%s"'
641 % (invalid, FSConfigFileParser._AID_ERR_MSG))
642 sys.exit(error_message(tmp_errmsg))
643
644 value = config.get(section_name, 'value')
645
646 if not value:
647 sys.exit(error_message('Found specified but unset "value"'))
648
649 try:
650 aid = AID(section_name, value, file_name)
Tom Cherryee0610e2018-02-08 14:26:53 -0800651 except ValueError as exception:
652 sys.exit(error_message(exception))
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000653
654 # Values must be within OEM range
655 if not Utils.in_any_range(int(aid.value, 0), self._oem_ranges):
656 emsg = '"value" not in valid range %s, got: %s'
657 emsg = emsg % (str(self._oem_ranges), value)
658 sys.exit(error_message(emsg))
659
660 # use the normalized int value in the dict and detect
661 # duplicate definitions of the same value
662 FSConfigFileParser._handle_dup_and_add(
663 'AID', file_name, aid.normalized_value, self._seen_aids[1])
664
665 # Append aid tuple of (AID_*, base10(value), _path(value))
666 # We keep the _path version of value so we can print that out in the
667 # generated header so investigating parties can identify parts.
668 # We store the base10 value for sorting, so everything is ascending
669 # later.
670 self._aids.append(aid)
671
672 def _handle_path(self, file_name, section_name, config):
673 """Add a file capability entry to the internal list.
674
675 Handles a file capability entry, verifies it, and adds it to
676 to the internal dirs or files list based on path. If it ends
677 with a / its a dir. Internal use only.
678
679 Calls sys.exit() on any validation error with message set.
680
681 Args:
682 file_name (str): The current name of the file being parsed.
683 section_name (str): The name of the section to parse.
684 config (str): The config parser.
685 """
686
687 FSConfigFileParser._handle_dup_and_add('path', file_name, section_name,
688 self._seen_paths)
689
690 mode = config.get(section_name, 'mode')
691 user = config.get(section_name, 'user')
692 group = config.get(section_name, 'group')
693 caps = config.get(section_name, 'caps')
694
695 errmsg = ('Found specified but unset option: \"%s" in file: \"' +
696 file_name + '\"')
697
698 if not mode:
699 sys.exit(errmsg % 'mode')
700
701 if not user:
702 sys.exit(errmsg % 'user')
703
704 if not group:
705 sys.exit(errmsg % 'group')
706
707 if not caps:
708 sys.exit(errmsg % 'caps')
709
710 caps = caps.split()
711
712 tmp = []
713 for cap in caps:
714 try:
715 # test if string is int, if it is, use as is.
716 int(cap, 0)
717 tmp.append('(' + cap + ')')
718 except ValueError:
doheon1.lee5cd3bca2017-04-03 15:17:06 +0900719 tmp.append('CAP_MASK_LONG(CAP_' + cap.upper() + ')')
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000720
721 caps = tmp
722
723 if len(mode) == 3:
724 mode = '0' + mode
725
726 try:
727 int(mode, 8)
728 except ValueError:
729 sys.exit('Mode must be octal characters, got: "%s"' % mode)
730
731 if len(mode) != 4:
732 sys.exit('Mode must be 3 or 4 characters, got: "%s"' % mode)
733
734 caps_str = '|'.join(caps)
735
736 entry = FSConfig(mode, user, group, caps_str, section_name, file_name)
737 if section_name[-1] == '/':
738 self._dirs.append(entry)
739 else:
740 self._files.append(entry)
741
742 @property
743 def files(self):
744 """Get the list of FSConfig file entries.
745
746 Returns:
747 a list of FSConfig() objects for file paths.
748 """
749 return self._files
750
751 @property
752 def dirs(self):
753 """Get the list of FSConfig dir entries.
754
755 Returns:
756 a list of FSConfig() objects for directory paths.
757 """
758 return self._dirs
759
760 @property
761 def aids(self):
762 """Get the list of AID entries.
763
764 Returns:
765 a list of AID() objects.
766 """
767 return self._aids
768
769 @staticmethod
770 def _file_key(fs_config):
771 """Used as the key paramter to sort.
772
773 This is used as a the function to the key parameter of a sort.
774 it wraps the string supplied in a class that implements the
775 appropriate __lt__ operator for the sort on path strings. See
776 StringWrapper class for more details.
777
778 Args:
779 fs_config (FSConfig): A FSConfig entry.
780
781 Returns:
782 A StringWrapper object
783 """
784
785 # Wrapper class for custom prefix matching strings
786 class StringWrapper(object):
787 """Wrapper class used for sorting prefix strings.
788
789 The algorithm is as follows:
790 - specified path before prefix match
791 - ie foo before f*
792 - lexicographical less than before other
793 - ie boo before foo
794
795 Given these paths:
796 paths=['ac', 'a', 'acd', 'an', 'a*', 'aa', 'ac*']
797 The sort order would be:
798 paths=['a', 'aa', 'ac', 'acd', 'an', 'ac*', 'a*']
799 Thus the fs_config tools will match on specified paths before
800 attempting prefix, and match on the longest matching prefix.
801 """
802
803 def __init__(self, path):
804 """
805 Args:
806 path (str): the path string to wrap.
807 """
808 self.is_prefix = path[-1] == '*'
809 if self.is_prefix:
810 self.path = path[:-1]
811 else:
812 self.path = path
813
814 def __lt__(self, other):
815
816 # if were both suffixed the smallest string
817 # is 'bigger'
818 if self.is_prefix and other.is_prefix:
819 result = len(self.path) > len(other.path)
820 # If I am an the suffix match, im bigger
821 elif self.is_prefix:
822 result = False
823 # If other is the suffix match, he's bigger
824 elif other.is_prefix:
825 result = True
826 # Alphabetical
827 else:
828 result = self.path < other.path
829 return result
830
831 return StringWrapper(fs_config.path)
832
833 @staticmethod
834 def _handle_dup_and_add(name, file_name, section_name, seen):
835 """Tracks and detects duplicates. Internal use only.
836
837 Calls sys.exit() on a duplicate.
838
839 Args:
840 name (str): The name to use in the error reporting. The pretty
841 name for the section.
842 file_name (str): The file currently being parsed.
843 section_name (str): The name of the section. This would be path
844 or identifier depending on what's being parsed.
845 seen (dict): The dictionary of seen things to check against.
846 """
847 if section_name in seen:
848 dups = '"' + seen[section_name] + '" and '
849 dups += file_name
850 sys.exit('Duplicate %s "%s" found in files: %s' %
851 (name, section_name, dups))
852
853 seen[section_name] = file_name
854
855
856class BaseGenerator(object):
857 """Interface for Generators.
858
859 Base class for generators, generators should implement
860 these method stubs.
861 """
862
863 def add_opts(self, opt_group):
864 """Used to add per-generator options to the command line.
865
866 Args:
867 opt_group (argument group object): The argument group to append to.
868 See the ArgParse docs for more details.
869 """
870
871 raise NotImplementedError("Not Implemented")
872
873 def __call__(self, args):
874 """This is called to do whatever magic the generator does.
875
876 Args:
877 args (dict): The arguments from ArgParse as a dictionary.
878 ie if you specified an argument of foo in add_opts, access
879 it via args['foo']
880 """
881
882 raise NotImplementedError("Not Implemented")
883
884
885@generator('fsconfig')
886class FSConfigGen(BaseGenerator):
887 """Generates the android_filesystem_config.h file.
888
889 Output is used in generating fs_config_files and fs_config_dirs.
890 """
891
892 _GENERATED = textwrap.dedent("""\
893 /*
894 * THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY
895 */
896 """)
897
898 _INCLUDES = [
899 '<private/android_filesystem_config.h>', '"generated_oem_aid.h"'
900 ]
901
902 _DEFINE_NO_DIRS = '#define NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS'
903 _DEFINE_NO_FILES = '#define NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_FILES'
904
905 _DEFAULT_WARNING = (
906 '#warning No device-supplied android_filesystem_config.h,'
907 ' using empty default.')
908
909 # Long names.
910 # pylint: disable=invalid-name
911 _NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS_ENTRY = (
912 '{ 00000, AID_ROOT, AID_ROOT, 0,'
913 '"system/etc/fs_config_dirs" },')
914
915 _NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_FILES_ENTRY = (
916 '{ 00000, AID_ROOT, AID_ROOT, 0,'
917 '"system/etc/fs_config_files" },')
918
919 _IFDEF_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS = (
920 '#ifdef NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS')
921 # pylint: enable=invalid-name
922
923 _ENDIF = '#endif'
924
925 _OPEN_FILE_STRUCT = (
926 'static const struct fs_path_config android_device_files[] = {')
927
928 _OPEN_DIR_STRUCT = (
929 'static const struct fs_path_config android_device_dirs[] = {')
930
931 _CLOSE_FILE_STRUCT = '};'
932
933 _GENERIC_DEFINE = "#define %s\t%s"
934
935 _FILE_COMMENT = '// Defined in file: \"%s\"'
936
937 def __init__(self, *args, **kwargs):
938 BaseGenerator.__init__(args, kwargs)
939
940 self._oem_parser = None
941 self._base_parser = None
942 self._friendly_to_aid = None
943
944 def add_opts(self, opt_group):
945
946 opt_group.add_argument(
947 'fsconfig', nargs='+', help='The list of fsconfig files to parse')
948
949 opt_group.add_argument(
950 '--aid-header',
951 required=True,
952 help='An android_filesystem_config.h file'
953 ' to parse AIDs and OEM Ranges from')
954
955 def __call__(self, args):
956
957 self._base_parser = AIDHeaderParser(args['aid_header'])
958 self._oem_parser = FSConfigFileParser(args['fsconfig'],
959 self._base_parser.oem_ranges)
960 base_aids = self._base_parser.aids
961 oem_aids = self._oem_parser.aids
962
963 # Detect name collisions on AIDs. Since friendly works as the
964 # identifier for collision testing and we need friendly later on for
965 # name resolution, just calculate and use friendly.
966 # {aid.friendly: aid for aid in base_aids}
967 base_friendly = {aid.friendly: aid for aid in base_aids}
968 oem_friendly = {aid.friendly: aid for aid in oem_aids}
969
970 base_set = set(base_friendly.keys())
971 oem_set = set(oem_friendly.keys())
972
973 common = base_set & oem_set
974
975 if len(common) > 0:
976 emsg = 'Following AID Collisions detected for: \n'
977 for friendly in common:
978 base = base_friendly[friendly]
979 oem = oem_friendly[friendly]
980 emsg += (
981 'Identifier: "%s" Friendly Name: "%s" '
982 'found in file "%s" and "%s"' %
983 (base.identifier, base.friendly, base.found, oem.found))
984 sys.exit(emsg)
985
986 self._friendly_to_aid = oem_friendly
987 self._friendly_to_aid.update(base_friendly)
988
989 self._generate()
990
991 def _to_fs_entry(self, fs_config):
992 """Converts an FSConfig entry to an fs entry.
993
994 Prints '{ mode, user, group, caps, "path" },'.
995
996 Calls sys.exit() on error.
997
998 Args:
999 fs_config (FSConfig): The entry to convert to
1000 a valid C array entry.
1001 """
1002
1003 # Get some short names
1004 mode = fs_config.mode
1005 user = fs_config.user
1006 group = fs_config.group
1007 fname = fs_config.filename
1008 caps = fs_config.caps
1009 path = fs_config.path
1010
1011 emsg = 'Cannot convert friendly name "%s" to identifier!'
1012
1013 # remap friendly names to identifier names
1014 if AID.is_friendly(user):
1015 if user not in self._friendly_to_aid:
1016 sys.exit(emsg % user)
1017 user = self._friendly_to_aid[user].identifier
1018
1019 if AID.is_friendly(group):
1020 if group not in self._friendly_to_aid:
1021 sys.exit(emsg % group)
1022 group = self._friendly_to_aid[group].identifier
1023
1024 fmt = '{ %s, %s, %s, %s, "%s" },'
1025
1026 expanded = fmt % (mode, user, group, caps, path)
1027
1028 print FSConfigGen._FILE_COMMENT % fname
1029 print ' ' + expanded
1030
1031 @staticmethod
1032 def _gen_inc():
1033 """Generate the include header lines and print to stdout."""
1034 for include in FSConfigGen._INCLUDES:
1035 print '#include %s' % include
1036
1037 def _generate(self):
1038 """Generates an OEM android_filesystem_config.h header file to stdout.
1039
1040 Args:
1041 files ([FSConfig]): A list of FSConfig objects for file entries.
1042 dirs ([FSConfig]): A list of FSConfig objects for directory
1043 entries.
1044 aids ([AIDS]): A list of AID objects for Android Id entries.
1045 """
1046 print FSConfigGen._GENERATED
William Robertscfc51f52016-04-12 08:51:13 -07001047 print
1048
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001049 FSConfigGen._gen_inc()
1050 print
William Robertsc950a352016-03-04 18:12:29 -08001051
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001052 dirs = self._oem_parser.dirs
1053 files = self._oem_parser.files
1054 aids = self._oem_parser.aids
William Roberts8f42ce72016-04-25 12:27:43 -07001055
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001056 are_dirs = len(dirs) > 0
1057 are_files = len(files) > 0
1058 are_aids = len(aids) > 0
William Robertsc950a352016-03-04 18:12:29 -08001059
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001060 if are_aids:
1061 for aid in aids:
1062 # use the preserved _path value
1063 print FSConfigGen._FILE_COMMENT % aid.found
1064 print FSConfigGen._GENERIC_DEFINE % (aid.identifier, aid.value)
1065
1066 print
William Robertsc950a352016-03-04 18:12:29 -08001067
1068 if not are_dirs:
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001069 print FSConfigGen._DEFINE_NO_DIRS + '\n'
William Robertsc950a352016-03-04 18:12:29 -08001070
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001071 if not are_files:
1072 print FSConfigGen._DEFINE_NO_FILES + '\n'
William Robertsc950a352016-03-04 18:12:29 -08001073
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001074 if not are_files and not are_dirs and not are_aids:
1075 return
William Robertsc950a352016-03-04 18:12:29 -08001076
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001077 if are_files:
1078 print FSConfigGen._OPEN_FILE_STRUCT
1079 for fs_config in files:
1080 self._to_fs_entry(fs_config)
William Robertsc950a352016-03-04 18:12:29 -08001081
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001082 if not are_dirs:
1083 print FSConfigGen._IFDEF_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS
1084 print(
1085 ' ' +
1086 FSConfigGen._NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS_ENTRY)
1087 print FSConfigGen._ENDIF
1088 print FSConfigGen._CLOSE_FILE_STRUCT
William Robertsc950a352016-03-04 18:12:29 -08001089
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001090 if are_dirs:
1091 print FSConfigGen._OPEN_DIR_STRUCT
1092 for dir_entry in dirs:
1093 self._to_fs_entry(dir_entry)
William Robertsc950a352016-03-04 18:12:29 -08001094
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001095 print FSConfigGen._CLOSE_FILE_STRUCT
William Robertsc950a352016-03-04 18:12:29 -08001096
William Robertsc950a352016-03-04 18:12:29 -08001097
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001098@generator('aidarray')
1099class AIDArrayGen(BaseGenerator):
1100 """Generates the android_id static array."""
1101
1102 _GENERATED = ('/*\n'
1103 ' * THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY!\n'
1104 ' */')
1105
1106 _INCLUDE = '#include <private/android_filesystem_config.h>'
1107
1108 _STRUCT_FS_CONFIG = textwrap.dedent("""
1109 struct android_id_info {
1110 const char *name;
1111 unsigned aid;
1112 };""")
1113
1114 _OPEN_ID_ARRAY = 'static const struct android_id_info android_ids[] = {'
1115
1116 _ID_ENTRY = ' { "%s", %s },'
1117
1118 _CLOSE_FILE_STRUCT = '};'
1119
1120 _COUNT = ('#define android_id_count \\\n'
1121 ' (sizeof(android_ids) / sizeof(android_ids[0]))')
1122
1123 def add_opts(self, opt_group):
1124
1125 opt_group.add_argument(
1126 'hdrfile', help='The android_filesystem_config.h'
1127 'file to parse')
1128
1129 def __call__(self, args):
1130
1131 hdr = AIDHeaderParser(args['hdrfile'])
1132
1133 print AIDArrayGen._GENERATED
1134 print
1135 print AIDArrayGen._INCLUDE
1136 print
1137 print AIDArrayGen._STRUCT_FS_CONFIG
1138 print
1139 print AIDArrayGen._OPEN_ID_ARRAY
1140
1141 for aid in hdr.aids:
1142 print AIDArrayGen._ID_ENTRY % (aid.friendly, aid.identifier)
1143
1144 print AIDArrayGen._CLOSE_FILE_STRUCT
1145 print
1146 print AIDArrayGen._COUNT
1147 print
1148
1149
1150@generator('oemaid')
1151class OEMAidGen(BaseGenerator):
1152 """Generates the OEM AID_<name> value header file."""
1153
1154 _GENERATED = ('/*\n'
1155 ' * THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY!\n'
1156 ' */')
1157
1158 _GENERIC_DEFINE = "#define %s\t%s"
1159
1160 _FILE_COMMENT = '// Defined in file: \"%s\"'
1161
1162 # Intentional trailing newline for readability.
1163 _FILE_IFNDEF_DEFINE = ('#ifndef GENERATED_OEM_AIDS_H_\n'
1164 '#define GENERATED_OEM_AIDS_H_\n')
1165
1166 _FILE_ENDIF = '#endif'
1167
1168 def __init__(self):
1169
1170 self._old_file = None
1171
1172 def add_opts(self, opt_group):
1173
1174 opt_group.add_argument(
1175 'fsconfig', nargs='+', help='The list of fsconfig files to parse.')
1176
1177 opt_group.add_argument(
1178 '--aid-header',
1179 required=True,
1180 help='An android_filesystem_config.h file'
1181 'to parse AIDs and OEM Ranges from')
1182
1183 def __call__(self, args):
1184
1185 hdr_parser = AIDHeaderParser(args['aid_header'])
1186
1187 parser = FSConfigFileParser(args['fsconfig'], hdr_parser.oem_ranges)
1188
1189 print OEMAidGen._GENERATED
1190
1191 print OEMAidGen._FILE_IFNDEF_DEFINE
1192
1193 for aid in parser.aids:
1194 self._print_aid(aid)
1195 print
1196
1197 print OEMAidGen._FILE_ENDIF
1198
1199 def _print_aid(self, aid):
1200 """Prints a valid #define AID identifier to stdout.
1201
1202 Args:
1203 aid to print
1204 """
1205
1206 # print the source file location of the AID
1207 found_file = aid.found
1208 if found_file != self._old_file:
1209 print OEMAidGen._FILE_COMMENT % found_file
1210 self._old_file = found_file
1211
1212 print OEMAidGen._GENERIC_DEFINE % (aid.identifier, aid.value)
1213
1214
1215@generator('passwd')
1216class PasswdGen(BaseGenerator):
1217 """Generates the /etc/passwd file per man (5) passwd."""
1218
1219 _GENERATED = ('#\n# THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY!\n#')
1220
1221 _FILE_COMMENT = '# Defined in file: \"%s\"'
1222
1223 def __init__(self):
1224
1225 self._old_file = None
1226
1227 def add_opts(self, opt_group):
1228
1229 opt_group.add_argument(
1230 'fsconfig', nargs='+', help='The list of fsconfig files to parse.')
1231
1232 opt_group.add_argument(
1233 '--aid-header',
1234 required=True,
1235 help='An android_filesystem_config.h file'
1236 'to parse AIDs and OEM Ranges from')
1237
Tom Cherry2d197a12018-05-14 13:14:41 -07001238 opt_group.add_argument(
1239 '--required-prefix',
1240 required=False,
1241 help='A prefix that the names are required to contain.')
1242
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001243 def __call__(self, args):
1244
1245 hdr_parser = AIDHeaderParser(args['aid_header'])
1246
1247 parser = FSConfigFileParser(args['fsconfig'], hdr_parser.oem_ranges)
1248
Tom Cherry2d197a12018-05-14 13:14:41 -07001249 required_prefix = args['required_prefix']
1250
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001251 aids = parser.aids
1252
1253 # nothing to do if no aids defined
1254 if len(aids) == 0:
1255 return
1256
1257 print PasswdGen._GENERATED
1258
1259 for aid in aids:
Tom Cherry2d197a12018-05-14 13:14:41 -07001260 if required_prefix is None or aid.friendly.startswith(required_prefix):
1261 self._print_formatted_line(aid)
1262 else:
1263 sys.exit("%s: AID '%s' must start with '%s'" %
1264 (args['fsconfig'], aid.friendly, required_prefix))
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001265
1266 def _print_formatted_line(self, aid):
1267 """Prints the aid to stdout in the passwd format. Internal use only.
1268
1269 Colon delimited:
1270 login name, friendly name
1271 encrypted password (optional)
1272 uid (int)
1273 gid (int)
1274 User name or comment field
1275 home directory
1276 interpreter (optional)
1277
1278 Args:
1279 aid (AID): The aid to print.
1280 """
1281 if self._old_file != aid.found:
1282 self._old_file = aid.found
1283 print PasswdGen._FILE_COMMENT % aid.found
1284
1285 try:
1286 logon, uid = Utils.get_login_and_uid_cleansed(aid)
1287 except ValueError as exception:
1288 sys.exit(exception)
1289
1290 print "%s::%s:%s::/:/system/bin/sh" % (logon, uid, uid)
1291
1292
1293@generator('group')
1294class GroupGen(PasswdGen):
1295 """Generates the /etc/group file per man (5) group."""
1296
1297 # Overrides parent
1298 def _print_formatted_line(self, aid):
1299 """Prints the aid to stdout in the group format. Internal use only.
1300
1301 Formatted (per man 5 group) like:
1302 group_name:password:GID:user_list
1303
1304 Args:
1305 aid (AID): The aid to print.
1306 """
1307 if self._old_file != aid.found:
1308 self._old_file = aid.found
1309 print PasswdGen._FILE_COMMENT % aid.found
1310
1311 try:
1312 logon, uid = Utils.get_login_and_uid_cleansed(aid)
1313 except ValueError as exception:
1314 sys.exit(exception)
1315
1316 print "%s::%s:" % (logon, uid)
1317
William Roberts1c4721c2016-04-26 13:05:34 -07001318
William Robertsc950a352016-03-04 18:12:29 -08001319def main():
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001320 """Main entry point for execution."""
William Robertsc950a352016-03-04 18:12:29 -08001321
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001322 opt_parser = argparse.ArgumentParser(
1323 description='A tool for parsing fsconfig config files and producing' +
1324 'digestable outputs.')
1325 subparser = opt_parser.add_subparsers(help='generators')
William Robertsc950a352016-03-04 18:12:29 -08001326
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001327 gens = generator.get()
William Robertsc950a352016-03-04 18:12:29 -08001328
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001329 # for each gen, instantiate and add them as an option
1330 for name, gen in gens.iteritems():
William Robertsc950a352016-03-04 18:12:29 -08001331
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001332 generator_option_parser = subparser.add_parser(name, help=gen.__doc__)
1333 generator_option_parser.set_defaults(which=name)
William Roberts8cb6a182016-04-08 22:06:19 -07001334
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001335 opt_group = generator_option_parser.add_argument_group(name +
1336 ' options')
1337 gen.add_opts(opt_group)
William Roberts8cb6a182016-04-08 22:06:19 -07001338
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001339 args = opt_parser.parse_args()
1340
1341 args_as_dict = vars(args)
1342 which = args_as_dict['which']
1343 del args_as_dict['which']
1344
1345 gens[which](args_as_dict)
1346
William Robertsc950a352016-03-04 18:12:29 -08001347
1348if __name__ == '__main__':
1349 main()