blob: dccff928f9595fcb6c22e3da2559e4ce720cbdfc [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
Tom Cherry9d924f62019-02-13 14:02:30 -080015import ctypes
William Robertsc950a352016-03-04 18:12:29 -080016import re
17import sys
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000018import textwrap
19
20# Keep the tool in one file to make it easy to run.
21# pylint: disable=too-many-lines
William Robertsd7104bc2016-04-11 21:17:12 -070022
William Robertsc950a352016-03-04 18:12:29 -080023
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000024# Lowercase generator used to be inline with @staticmethod.
25class generator(object): # pylint: disable=invalid-name
26 """A decorator class to add commandlet plugins.
William Robertsc950a352016-03-04 18:12:29 -080027
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000028 Used as a decorator to classes to add them to
29 the internal plugin interface. Plugins added
30 with @generator() are automatically added to
31 the command line.
William Robertsc950a352016-03-04 18:12:29 -080032
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000033 For instance, to add a new generator
34 called foo and have it added just do this:
William Robertsc950a352016-03-04 18:12:29 -080035
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000036 @generator("foo")
37 class FooGen(object):
38 ...
39 """
40 _generators = {}
William Robertsc950a352016-03-04 18:12:29 -080041
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000042 def __init__(self, gen):
43 """
44 Args:
45 gen (str): The name of the generator to add.
William Robertsc950a352016-03-04 18:12:29 -080046
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000047 Raises:
48 ValueError: If there is a similarly named generator already added.
William Robertsc950a352016-03-04 18:12:29 -080049
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000050 """
51 self._gen = gen
William Robertsc950a352016-03-04 18:12:29 -080052
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000053 if gen in generator._generators:
54 raise ValueError('Duplicate generator name: ' + gen)
William Robertsc950a352016-03-04 18:12:29 -080055
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000056 generator._generators[gen] = None
William Robertsc950a352016-03-04 18:12:29 -080057
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000058 def __call__(self, cls):
59
60 generator._generators[self._gen] = cls()
61 return cls
62
63 @staticmethod
64 def get():
65 """Gets the list of generators.
66
67 Returns:
68 The list of registered generators.
69 """
70 return generator._generators
William Robertsc950a352016-03-04 18:12:29 -080071
72
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000073class Utils(object):
74 """Various assorted static utilities."""
William Roberts64edf5b2016-04-11 17:12:47 -070075
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000076 @staticmethod
77 def in_any_range(value, ranges):
78 """Tests if a value is in a list of given closed range tuples.
William Roberts64edf5b2016-04-11 17:12:47 -070079
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000080 A range tuple is a closed range. That means it's inclusive of its
81 start and ending values.
William Roberts64edf5b2016-04-11 17:12:47 -070082
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000083 Args:
84 value (int): The value to test.
85 range [(int, int)]: The closed range list to test value within.
William Roberts64edf5b2016-04-11 17:12:47 -070086
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000087 Returns:
88 True if value is within the closed range, false otherwise.
89 """
William Roberts64edf5b2016-04-11 17:12:47 -070090
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000091 return any(lower <= value <= upper for (lower, upper) in ranges)
William Roberts64edf5b2016-04-11 17:12:47 -070092
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000093 @staticmethod
94 def get_login_and_uid_cleansed(aid):
95 """Returns a passwd/group file safe logon and uid.
William Roberts1c4721c2016-04-26 13:05:34 -070096
Elliott Hughes2d7c86d2016-12-13 23:37:07 +000097 This checks that the logon and uid of the AID do not
98 contain the delimiter ":" for a passwd/group file.
William Roberts1c4721c2016-04-26 13:05:34 -070099
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000100 Args:
101 aid (AID): The aid to check
William Roberts1c4721c2016-04-26 13:05:34 -0700102
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000103 Returns:
104 logon, uid of the AID after checking its safe.
William Roberts1c4721c2016-04-26 13:05:34 -0700105
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000106 Raises:
107 ValueError: If there is a delimiter charcter found.
108 """
109 logon = aid.friendly
110 uid = aid.normalized_value
111 if ':' in uid:
112 raise ValueError(
113 'Cannot specify delimiter character ":" in uid: "%s"' % uid)
114 if ':' in logon:
115 raise ValueError(
Tom Cherry766adc92019-02-13 14:24:52 -0800116 'Cannot specify delimiter character ":" in logon: "%s"' %
117 logon)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000118 return logon, uid
William Roberts1c4721c2016-04-26 13:05:34 -0700119
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000120
121class AID(object):
122 """This class represents an Android ID or an AID.
123
124 Attributes:
125 identifier (str): The identifier name for a #define.
126 value (str) The User Id (uid) of the associate define.
127 found (str) The file it was found in, can be None.
128 normalized_value (str): Same as value, but base 10.
129 friendly (str): The friendly name of aid.
130 """
131
132 PREFIX = 'AID_'
133
134 # Some of the AIDS like AID_MEDIA_EX had names like mediaex
135 # list a map of things to fixup until we can correct these
136 # at a later date.
137 _FIXUPS = {
138 'media_drm': 'mediadrm',
139 'media_ex': 'mediaex',
140 'media_codec': 'mediacodec'
141 }
142
Wei Wang77e329a2018-06-05 16:00:07 -0700143 def __init__(self, identifier, value, found, login_shell):
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000144 """
145 Args:
146 identifier: The identifier name for a #define <identifier>.
147 value: The value of the AID, aka the uid.
148 found (str): The file found in, not required to be specified.
Wei Wang77e329a2018-06-05 16:00:07 -0700149 login_shell (str): The shell field per man (5) passwd file.
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000150 Raises:
Tom Cherryee0610e2018-02-08 14:26:53 -0800151 ValueError: if the friendly name is longer than 31 characters as
152 that is bionic's internal buffer size for name.
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000153 ValueError: if value is not a valid string number as processed by
154 int(x, 0)
155 """
156 self.identifier = identifier
157 self.value = value
158 self.found = found
Wei Wang77e329a2018-06-05 16:00:07 -0700159 self.login_shell = login_shell
160
Tom Cherryee0610e2018-02-08 14:26:53 -0800161 try:
162 self.normalized_value = str(int(value, 0))
Tom Cherry766adc92019-02-13 14:24:52 -0800163 except ValueError:
164 raise ValueError(
165 'Invalid "value", not aid number, got: \"%s\"' % value)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000166
167 # Where we calculate the friendly name
168 friendly = identifier[len(AID.PREFIX):].lower()
169 self.friendly = AID._fixup_friendly(friendly)
170
Tom Cherryee0610e2018-02-08 14:26:53 -0800171 if len(self.friendly) > 31:
Tom Cherry766adc92019-02-13 14:24:52 -0800172 raise ValueError(
173 'AID names must be under 32 characters "%s"' % self.friendly)
Tom Cherryee0610e2018-02-08 14:26:53 -0800174
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000175 def __eq__(self, other):
176
177 return self.identifier == other.identifier \
178 and self.value == other.value and self.found == other.found \
Wei Wang77e329a2018-06-05 16:00:07 -0700179 and self.normalized_value == other.normalized_value \
180 and self.login_shell == other.login_shell
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000181
182 @staticmethod
183 def is_friendly(name):
184 """Determines if an AID is a freindly name or C define.
185
186 For example if name is AID_SYSTEM it returns false, if name
187 was system, it would return true.
188
189 Returns:
190 True if name is a friendly name False otherwise.
191 """
192
193 return not name.startswith(AID.PREFIX)
194
195 @staticmethod
196 def _fixup_friendly(friendly):
197 """Fixup friendly names that historically don't follow the convention.
198
199 Args:
200 friendly (str): The friendly name.
201
202 Returns:
203 The fixedup friendly name as a str.
204 """
205
206 if friendly in AID._FIXUPS:
207 return AID._FIXUPS[friendly]
208
209 return friendly
210
211
212class FSConfig(object):
213 """Represents a filesystem config array entry.
214
215 Represents a file system configuration entry for specifying
216 file system capabilities.
217
218 Attributes:
219 mode (str): The mode of the file or directory.
220 user (str): The uid or #define identifier (AID_SYSTEM)
221 group (str): The gid or #define identifier (AID_SYSTEM)
222 caps (str): The capability set.
Tom Cherry766adc92019-02-13 14:24:52 -0800223 path (str): The path of the file or directory.
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000224 filename (str): The file it was found in.
225 """
226
227 def __init__(self, mode, user, group, caps, path, filename):
228 """
229 Args:
230 mode (str): The mode of the file or directory.
231 user (str): The uid or #define identifier (AID_SYSTEM)
232 group (str): The gid or #define identifier (AID_SYSTEM)
233 caps (str): The capability set as a list.
Tom Cherry766adc92019-02-13 14:24:52 -0800234 path (str): The path of the file or directory.
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000235 filename (str): The file it was found in.
236 """
237 self.mode = mode
238 self.user = user
239 self.group = group
240 self.caps = caps
241 self.path = path
242 self.filename = filename
243
244 def __eq__(self, other):
245
246 return self.mode == other.mode and self.user == other.user \
247 and self.group == other.group and self.caps == other.caps \
248 and self.path == other.path and self.filename == other.filename
249
Tom Cherry766adc92019-02-13 14:24:52 -0800250 def __repr__(self):
251 return 'FSConfig(%r, %r, %r, %r, %r, %r)' % (self.mode, self.user,
252 self.group, self.caps,
253 self.path, self.filename)
254
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000255
Tom Cherry9d924f62019-02-13 14:02:30 -0800256class CapabilityHeaderParser(object):
257 """Parses capability.h file
258
259 Parses a C header file and extracts lines starting with #define CAP_<name>.
260 """
261
262 _CAP_DEFINE = re.compile(r'\s*#define\s+(CAP_\S+)\s+(\S+)')
263 _SKIP_CAPS = ['CAP_LAST_CAP', 'CAP_TO_INDEX(x)', 'CAP_TO_MASK(x)']
264
265 def __init__(self, capability_header):
266 """
267 Args:
268 capability_header (str): file name for the header file containing AID entries.
269 """
270
271 self.caps = {}
272 with open(capability_header) as open_file:
273 self._parse(open_file)
274
275 def _parse(self, capability_file):
276 """Parses a capability header file. Internal use only.
277
278 Args:
279 capability_file (file): The open capability header file to parse.
280 """
281
282 for line in capability_file:
283 match = CapabilityHeaderParser._CAP_DEFINE.match(line)
284 if match:
285 cap = match.group(1)
286 value = match.group(2)
287
288 if not cap in self._SKIP_CAPS:
289 try:
290 self.caps[cap] = int(value, 0)
291 except ValueError:
292 sys.exit('Could not parse capability define "%s":"%s"'
293 % (cap, value))
294
295
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000296class AIDHeaderParser(object):
297 """Parses an android_filesystem_config.h file.
298
299 Parses a C header file and extracts lines starting with #define AID_<name>
300 while capturing the OEM defined ranges and ignoring other ranges. It also
301 skips some hardcoded AIDs it doesn't need to generate a mapping for.
302 It provides some basic sanity checks. The information extracted from this
303 file can later be used to sanity check other things (like oem ranges) as
304 well as generating a mapping of names to uids. It was primarily designed to
305 parse the private/android_filesystem_config.h, but any C header should
306 work.
307 """
308
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000309 _SKIP_AIDS = [
310 re.compile(r'%sUNUSED[0-9].*' % AID.PREFIX),
Tom Cherry766adc92019-02-13 14:24:52 -0800311 re.compile(r'%sAPP' % AID.PREFIX),
312 re.compile(r'%sUSER' % AID.PREFIX)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000313 ]
314 _AID_DEFINE = re.compile(r'\s*#define\s+%s.*' % AID.PREFIX)
315 _OEM_START_KW = 'START'
316 _OEM_END_KW = 'END'
Johan Redestig1552a282017-01-03 09:36:47 +0100317 _OEM_RANGE = re.compile('%sOEM_RESERVED_[0-9]*_{0,1}(%s|%s)' %
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000318 (AID.PREFIX, _OEM_START_KW, _OEM_END_KW))
319 # AID lines cannot end with _START or _END, ie AID_FOO is OK
320 # but AID_FOO_START is skiped. Note that AID_FOOSTART is NOT skipped.
321 _AID_SKIP_RANGE = ['_' + _OEM_START_KW, '_' + _OEM_END_KW]
322 _COLLISION_OK = ['AID_APP', 'AID_APP_START', 'AID_USER', 'AID_USER_OFFSET']
323
324 def __init__(self, aid_header):
325 """
326 Args:
327 aid_header (str): file name for the header
328 file containing AID entries.
329 """
330 self._aid_header = aid_header
331 self._aid_name_to_value = {}
332 self._aid_value_to_name = {}
333 self._oem_ranges = {}
334
335 with open(aid_header) as open_file:
336 self._parse(open_file)
William Roberts64edf5b2016-04-11 17:12:47 -0700337
338 try:
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000339 self._process_and_check()
340 except ValueError as exception:
341 sys.exit('Error processing parsed data: "%s"' % (str(exception)))
342
343 def _parse(self, aid_file):
344 """Parses an AID header file. Internal use only.
345
346 Args:
347 aid_file (file): The open AID header file to parse.
348 """
349
350 for lineno, line in enumerate(aid_file):
William Roberts820421c2016-12-13 19:12:35 -0800351
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000352 def error_message(msg):
353 """Creates an error message with the current parsing state."""
William Roberts4165c632016-12-13 19:17:07 -0800354 # pylint: disable=cell-var-from-loop
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000355 return 'Error "{}" in file: "{}" on line: {}'.format(
356 msg, self._aid_header, str(lineno))
357
358 if AIDHeaderParser._AID_DEFINE.match(line):
359 chunks = line.split()
360 identifier = chunks[1]
361 value = chunks[2]
362
Tom Cherry766adc92019-02-13 14:24:52 -0800363 if any(
364 x.match(identifier)
365 for x in AIDHeaderParser._SKIP_AIDS):
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000366 continue
367
368 try:
369 if AIDHeaderParser._is_oem_range(identifier):
370 self._handle_oem_range(identifier, value)
371 elif not any(
372 identifier.endswith(x)
373 for x in AIDHeaderParser._AID_SKIP_RANGE):
374 self._handle_aid(identifier, value)
375 except ValueError as exception:
William Roberts820421c2016-12-13 19:12:35 -0800376 sys.exit(
Tom Cherry766adc92019-02-13 14:24:52 -0800377 error_message('{} for "{}"'.format(
378 exception, identifier)))
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000379
380 def _handle_aid(self, identifier, value):
381 """Handle an AID C #define.
382
383 Handles an AID, sanity checking, generating the friendly name and
384 adding it to the internal maps. Internal use only.
385
386 Args:
387 identifier (str): The name of the #define identifier. ie AID_FOO.
388 value (str): The value associated with the identifier.
389
390 Raises:
391 ValueError: With message set to indicate the error.
392 """
393
Wei Wang77e329a2018-06-05 16:00:07 -0700394 aid = AID(identifier, value, self._aid_header, '/system/bin/sh')
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000395
396 # duplicate name
397 if aid.friendly in self._aid_name_to_value:
398 raise ValueError('Duplicate aid "%s"' % identifier)
399
400 if value in self._aid_value_to_name and aid.identifier not in AIDHeaderParser._COLLISION_OK:
Tom Cherry766adc92019-02-13 14:24:52 -0800401 raise ValueError(
402 'Duplicate aid value "%s" for %s' % (value, identifier))
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000403
404 self._aid_name_to_value[aid.friendly] = aid
405 self._aid_value_to_name[value] = aid.friendly
406
407 def _handle_oem_range(self, identifier, value):
408 """Handle an OEM range C #define.
409
410 When encountering special AID defines, notably for the OEM ranges
411 this method handles sanity checking and adding them to the internal
412 maps. For internal use only.
413
414 Args:
415 identifier (str): The name of the #define identifier.
416 ie AID_OEM_RESERVED_START/END.
417 value (str): The value associated with the identifier.
418
419 Raises:
420 ValueError: With message set to indicate the error.
421 """
422
423 try:
424 int_value = int(value, 0)
William Roberts64edf5b2016-04-11 17:12:47 -0700425 except ValueError:
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000426 raise ValueError(
427 'Could not convert "%s" to integer value, got: "%s"' %
428 (identifier, value))
William Roberts64edf5b2016-04-11 17:12:47 -0700429
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000430 # convert AID_OEM_RESERVED_START or AID_OEM_RESERVED_<num>_START
431 # to AID_OEM_RESERVED or AID_OEM_RESERVED_<num>
432 is_start = identifier.endswith(AIDHeaderParser._OEM_START_KW)
William Roberts64edf5b2016-04-11 17:12:47 -0700433
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000434 if is_start:
435 tostrip = len(AIDHeaderParser._OEM_START_KW)
436 else:
437 tostrip = len(AIDHeaderParser._OEM_END_KW)
William Roberts64edf5b2016-04-11 17:12:47 -0700438
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000439 # ending _
440 tostrip = tostrip + 1
William Roberts64edf5b2016-04-11 17:12:47 -0700441
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000442 strip = identifier[:-tostrip]
443 if strip not in self._oem_ranges:
444 self._oem_ranges[strip] = []
William Roberts64edf5b2016-04-11 17:12:47 -0700445
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000446 if len(self._oem_ranges[strip]) > 2:
447 raise ValueError('Too many same OEM Ranges "%s"' % identifier)
William Roberts64edf5b2016-04-11 17:12:47 -0700448
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000449 if len(self._oem_ranges[strip]) == 1:
450 tmp = self._oem_ranges[strip][0]
William Roberts64edf5b2016-04-11 17:12:47 -0700451
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000452 if tmp == int_value:
453 raise ValueError('START and END values equal %u' % int_value)
454 elif is_start and tmp < int_value:
Tom Cherry766adc92019-02-13 14:24:52 -0800455 raise ValueError(
456 'END value %u less than START value %u' % (tmp, int_value))
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000457 elif not is_start and tmp > int_value:
Tom Cherry766adc92019-02-13 14:24:52 -0800458 raise ValueError(
459 'END value %u less than START value %u' % (int_value, tmp))
William Roberts64edf5b2016-04-11 17:12:47 -0700460
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000461 # Add START values to the head of the list and END values at the end.
462 # Thus, the list is ordered with index 0 as START and index 1 as END.
463 if is_start:
464 self._oem_ranges[strip].insert(0, int_value)
465 else:
466 self._oem_ranges[strip].append(int_value)
William Roberts64edf5b2016-04-11 17:12:47 -0700467
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000468 def _process_and_check(self):
469 """Process, check and populate internal data structures.
William Roberts64edf5b2016-04-11 17:12:47 -0700470
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000471 After parsing and generating the internal data structures, this method
472 is responsible for sanity checking ALL of the acquired data.
William Roberts64edf5b2016-04-11 17:12:47 -0700473
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000474 Raises:
475 ValueError: With the message set to indicate the specific error.
476 """
William Roberts64edf5b2016-04-11 17:12:47 -0700477
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000478 # tuplefy the lists since range() does not like them mutable.
479 self._oem_ranges = [
480 AIDHeaderParser._convert_lst_to_tup(k, v)
481 for k, v in self._oem_ranges.iteritems()
482 ]
William Roberts64edf5b2016-04-11 17:12:47 -0700483
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000484 # Check for overlapping ranges
485 for i, range1 in enumerate(self._oem_ranges):
486 for range2 in self._oem_ranges[i + 1:]:
487 if AIDHeaderParser._is_overlap(range1, range2):
488 raise ValueError("Overlapping OEM Ranges found %s and %s" %
489 (str(range1), str(range2)))
William Roberts64edf5b2016-04-11 17:12:47 -0700490
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000491 # No core AIDs should be within any oem range.
492 for aid in self._aid_value_to_name:
493
494 if Utils.in_any_range(aid, self._oem_ranges):
495 name = self._aid_value_to_name[aid]
496 raise ValueError(
497 'AID "%s" value: %u within reserved OEM Range: "%s"' %
498 (name, aid, str(self._oem_ranges)))
499
500 @property
501 def oem_ranges(self):
502 """Retrieves the OEM closed ranges as a list of tuples.
503
504 Returns:
505 A list of closed range tuples: [ (0, 42), (50, 105) ... ]
506 """
507 return self._oem_ranges
508
509 @property
510 def aids(self):
511 """Retrieves the list of found AIDs.
512
513 Returns:
514 A list of AID() objects.
515 """
516 return self._aid_name_to_value.values()
517
518 @staticmethod
519 def _convert_lst_to_tup(name, lst):
520 """Converts a mutable list to a non-mutable tuple.
521
522 Used ONLY for ranges and thus enforces a length of 2.
523
524 Args:
525 lst (List): list that should be "tuplefied".
526
527 Raises:
528 ValueError if lst is not a list or len is not 2.
529
530 Returns:
531 Tuple(lst)
532 """
533 if not lst or len(lst) != 2:
534 raise ValueError('Mismatched range for "%s"' % name)
535
536 return tuple(lst)
537
538 @staticmethod
539 def _is_oem_range(aid):
540 """Detects if a given aid is within the reserved OEM range.
541
542 Args:
543 aid (int): The aid to test
544
545 Returns:
546 True if it is within the range, False otherwise.
547 """
548
549 return AIDHeaderParser._OEM_RANGE.match(aid)
550
551 @staticmethod
552 def _is_overlap(range_a, range_b):
553 """Calculates the overlap of two range tuples.
554
555 A range tuple is a closed range. A closed range includes its endpoints.
556 Note that python tuples use () notation which collides with the
557 mathematical notation for open ranges.
558
559 Args:
560 range_a: The first tuple closed range eg (0, 5).
561 range_b: The second tuple closed range eg (3, 7).
562
563 Returns:
564 True if they overlap, False otherwise.
565 """
566
567 return max(range_a[0], range_b[0]) <= min(range_a[1], range_b[1])
William Roberts64edf5b2016-04-11 17:12:47 -0700568
569
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000570class FSConfigFileParser(object):
571 """Parses a config.fs ini format file.
William Roberts11c29282016-04-09 10:32:30 -0700572
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000573 This class is responsible for parsing the config.fs ini format files.
574 It collects and checks all the data in these files and makes it available
575 for consumption post processed.
576 """
William Roberts11c29282016-04-09 10:32:30 -0700577
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000578 # These _AID vars work together to ensure that an AID section name
579 # cannot contain invalid characters for a C define or a passwd/group file.
580 # Since _AID_PREFIX is within the set of _AID_MATCH the error logic only
581 # checks end, if you change this, you may have to update the error
582 # detection code.
583 _AID_MATCH = re.compile('%s[A-Z0-9_]+' % AID.PREFIX)
584 _AID_ERR_MSG = 'Expecting upper case, a number or underscore'
William Roberts5f059a72016-04-25 10:36:45 -0700585
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000586 # list of handler to required options, used to identify the
587 # parsing section
Tom Cherry766adc92019-02-13 14:24:52 -0800588 _SECTIONS = [('_handle_aid', ('value', )),
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000589 ('_handle_path', ('mode', 'user', 'group', 'caps'))]
590
591 def __init__(self, config_files, oem_ranges):
592 """
593 Args:
594 config_files ([str]): The list of config.fs files to parse.
595 Note the filename is not important.
596 oem_ranges ([(),()]): range tuples indicating reserved OEM ranges.
597 """
598
599 self._files = []
600 self._dirs = []
601 self._aids = []
602
603 self._seen_paths = {}
604 # (name to file, value to aid)
605 self._seen_aids = ({}, {})
606
607 self._oem_ranges = oem_ranges
608
609 self._config_files = config_files
610
611 for config_file in self._config_files:
612 self._parse(config_file)
613
614 def _parse(self, file_name):
615 """Parses and verifies config.fs files. Internal use only.
616
617 Args:
618 file_name (str): The config.fs (PythonConfigParser file format)
619 file to parse.
620
621 Raises:
622 Anything raised by ConfigParser.read()
623 """
624
625 # Separate config parsers for each file found. If you use
626 # read(filenames...) later files can override earlier files which is
627 # not what we want. Track state across files and enforce with
628 # _handle_dup(). Note, strict ConfigParser is set to true in
629 # Python >= 3.2, so in previous versions same file sections can
630 # override previous
631 # sections.
William Robertsc950a352016-03-04 18:12:29 -0800632
633 config = ConfigParser.ConfigParser()
634 config.read(file_name)
635
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000636 for section in config.sections():
William Robertsc950a352016-03-04 18:12:29 -0800637
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000638 found = False
William Roberts5f059a72016-04-25 10:36:45 -0700639
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000640 for test in FSConfigFileParser._SECTIONS:
641 handler = test[0]
642 options = test[1]
William Roberts5f059a72016-04-25 10:36:45 -0700643
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000644 if all([config.has_option(section, item) for item in options]):
645 handler = getattr(self, handler)
646 handler(file_name, section, config)
647 found = True
648 break
William Roberts5f059a72016-04-25 10:36:45 -0700649
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000650 if not found:
Tom Cherry766adc92019-02-13 14:24:52 -0800651 sys.exit('Invalid section "%s" in file: "%s"' % (section,
652 file_name))
William Roberts11c29282016-04-09 10:32:30 -0700653
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000654 # sort entries:
655 # * specified path before prefix match
656 # ** ie foo before f*
657 # * lexicographical less than before other
658 # ** ie boo before foo
659 # Given these paths:
660 # paths=['ac', 'a', 'acd', 'an', 'a*', 'aa', 'ac*']
661 # The sort order would be:
662 # paths=['a', 'aa', 'ac', 'acd', 'an', 'ac*', 'a*']
663 # Thus the fs_config tools will match on specified paths before
664 # attempting prefix, and match on the longest matching prefix.
665 self._files.sort(key=FSConfigFileParser._file_key)
666
667 # sort on value of (file_name, name, value, strvalue)
668 # This is only cosmetic so AIDS are arranged in ascending order
669 # within the generated file.
670 self._aids.sort(key=lambda item: item.normalized_value)
671
672 def _handle_aid(self, file_name, section_name, config):
673 """Verifies an AID entry and adds it to the aid list.
674
675 Calls sys.exit() with a descriptive message of the failure.
676
677 Args:
678 file_name (str): The filename of the config file being parsed.
679 section_name (str): The section name currently being parsed.
680 config (ConfigParser): The ConfigParser section being parsed that
681 the option values will come from.
682 """
683
684 def error_message(msg):
685 """Creates an error message with current parsing state."""
686 return '{} for: "{}" file: "{}"'.format(msg, section_name,
687 file_name)
688
689 FSConfigFileParser._handle_dup_and_add('AID', file_name, section_name,
690 self._seen_aids[0])
691
692 match = FSConfigFileParser._AID_MATCH.match(section_name)
693 invalid = match.end() if match else len(AID.PREFIX)
694 if invalid != len(section_name):
695 tmp_errmsg = ('Invalid characters in AID section at "%d" for: "%s"'
696 % (invalid, FSConfigFileParser._AID_ERR_MSG))
697 sys.exit(error_message(tmp_errmsg))
698
699 value = config.get(section_name, 'value')
700
701 if not value:
702 sys.exit(error_message('Found specified but unset "value"'))
703
704 try:
Wei Wang77e329a2018-06-05 16:00:07 -0700705 aid = AID(section_name, value, file_name, '/vendor/bin/sh')
Tom Cherryee0610e2018-02-08 14:26:53 -0800706 except ValueError as exception:
707 sys.exit(error_message(exception))
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000708
709 # Values must be within OEM range
710 if not Utils.in_any_range(int(aid.value, 0), self._oem_ranges):
711 emsg = '"value" not in valid range %s, got: %s'
712 emsg = emsg % (str(self._oem_ranges), value)
713 sys.exit(error_message(emsg))
714
715 # use the normalized int value in the dict and detect
716 # duplicate definitions of the same value
717 FSConfigFileParser._handle_dup_and_add(
718 'AID', file_name, aid.normalized_value, self._seen_aids[1])
719
720 # Append aid tuple of (AID_*, base10(value), _path(value))
721 # We keep the _path version of value so we can print that out in the
722 # generated header so investigating parties can identify parts.
723 # We store the base10 value for sorting, so everything is ascending
724 # later.
725 self._aids.append(aid)
726
727 def _handle_path(self, file_name, section_name, config):
728 """Add a file capability entry to the internal list.
729
730 Handles a file capability entry, verifies it, and adds it to
731 to the internal dirs or files list based on path. If it ends
732 with a / its a dir. Internal use only.
733
734 Calls sys.exit() on any validation error with message set.
735
736 Args:
737 file_name (str): The current name of the file being parsed.
738 section_name (str): The name of the section to parse.
739 config (str): The config parser.
740 """
741
742 FSConfigFileParser._handle_dup_and_add('path', file_name, section_name,
743 self._seen_paths)
744
745 mode = config.get(section_name, 'mode')
746 user = config.get(section_name, 'user')
747 group = config.get(section_name, 'group')
748 caps = config.get(section_name, 'caps')
749
750 errmsg = ('Found specified but unset option: \"%s" in file: \"' +
751 file_name + '\"')
752
753 if not mode:
754 sys.exit(errmsg % 'mode')
755
756 if not user:
757 sys.exit(errmsg % 'user')
758
759 if not group:
760 sys.exit(errmsg % 'group')
761
762 if not caps:
763 sys.exit(errmsg % 'caps')
764
765 caps = caps.split()
766
767 tmp = []
768 for cap in caps:
769 try:
770 # test if string is int, if it is, use as is.
771 int(cap, 0)
Tom Cherry9d924f62019-02-13 14:02:30 -0800772 tmp.append(cap)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000773 except ValueError:
Tom Cherry9d924f62019-02-13 14:02:30 -0800774 tmp.append('CAP_' + cap.upper())
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000775
776 caps = tmp
777
778 if len(mode) == 3:
779 mode = '0' + mode
780
781 try:
782 int(mode, 8)
783 except ValueError:
784 sys.exit('Mode must be octal characters, got: "%s"' % mode)
785
786 if len(mode) != 4:
787 sys.exit('Mode must be 3 or 4 characters, got: "%s"' % mode)
788
Tom Cherry9d924f62019-02-13 14:02:30 -0800789 caps_str = ','.join(caps)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000790
791 entry = FSConfig(mode, user, group, caps_str, section_name, file_name)
792 if section_name[-1] == '/':
793 self._dirs.append(entry)
794 else:
795 self._files.append(entry)
796
797 @property
798 def files(self):
799 """Get the list of FSConfig file entries.
800
801 Returns:
802 a list of FSConfig() objects for file paths.
803 """
804 return self._files
805
806 @property
807 def dirs(self):
808 """Get the list of FSConfig dir entries.
809
810 Returns:
811 a list of FSConfig() objects for directory paths.
812 """
813 return self._dirs
814
815 @property
816 def aids(self):
817 """Get the list of AID entries.
818
819 Returns:
820 a list of AID() objects.
821 """
822 return self._aids
823
824 @staticmethod
825 def _file_key(fs_config):
826 """Used as the key paramter to sort.
827
828 This is used as a the function to the key parameter of a sort.
829 it wraps the string supplied in a class that implements the
830 appropriate __lt__ operator for the sort on path strings. See
831 StringWrapper class for more details.
832
833 Args:
834 fs_config (FSConfig): A FSConfig entry.
835
836 Returns:
837 A StringWrapper object
838 """
839
840 # Wrapper class for custom prefix matching strings
841 class StringWrapper(object):
842 """Wrapper class used for sorting prefix strings.
843
844 The algorithm is as follows:
845 - specified path before prefix match
846 - ie foo before f*
847 - lexicographical less than before other
848 - ie boo before foo
849
850 Given these paths:
851 paths=['ac', 'a', 'acd', 'an', 'a*', 'aa', 'ac*']
852 The sort order would be:
853 paths=['a', 'aa', 'ac', 'acd', 'an', 'ac*', 'a*']
854 Thus the fs_config tools will match on specified paths before
855 attempting prefix, and match on the longest matching prefix.
856 """
857
858 def __init__(self, path):
859 """
860 Args:
861 path (str): the path string to wrap.
862 """
863 self.is_prefix = path[-1] == '*'
864 if self.is_prefix:
865 self.path = path[:-1]
866 else:
867 self.path = path
868
869 def __lt__(self, other):
870
871 # if were both suffixed the smallest string
872 # is 'bigger'
873 if self.is_prefix and other.is_prefix:
874 result = len(self.path) > len(other.path)
875 # If I am an the suffix match, im bigger
876 elif self.is_prefix:
877 result = False
878 # If other is the suffix match, he's bigger
879 elif other.is_prefix:
880 result = True
881 # Alphabetical
882 else:
883 result = self.path < other.path
884 return result
885
886 return StringWrapper(fs_config.path)
887
888 @staticmethod
889 def _handle_dup_and_add(name, file_name, section_name, seen):
890 """Tracks and detects duplicates. Internal use only.
891
892 Calls sys.exit() on a duplicate.
893
894 Args:
895 name (str): The name to use in the error reporting. The pretty
896 name for the section.
897 file_name (str): The file currently being parsed.
898 section_name (str): The name of the section. This would be path
899 or identifier depending on what's being parsed.
900 seen (dict): The dictionary of seen things to check against.
901 """
902 if section_name in seen:
903 dups = '"' + seen[section_name] + '" and '
904 dups += file_name
905 sys.exit('Duplicate %s "%s" found in files: %s' %
906 (name, section_name, dups))
907
908 seen[section_name] = file_name
909
910
911class BaseGenerator(object):
912 """Interface for Generators.
913
914 Base class for generators, generators should implement
915 these method stubs.
916 """
917
918 def add_opts(self, opt_group):
919 """Used to add per-generator options to the command line.
920
921 Args:
922 opt_group (argument group object): The argument group to append to.
923 See the ArgParse docs for more details.
924 """
925
926 raise NotImplementedError("Not Implemented")
927
928 def __call__(self, args):
929 """This is called to do whatever magic the generator does.
930
931 Args:
932 args (dict): The arguments from ArgParse as a dictionary.
933 ie if you specified an argument of foo in add_opts, access
934 it via args['foo']
935 """
936
937 raise NotImplementedError("Not Implemented")
938
939
940@generator('fsconfig')
941class FSConfigGen(BaseGenerator):
942 """Generates the android_filesystem_config.h file.
943
944 Output is used in generating fs_config_files and fs_config_dirs.
945 """
946
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000947 def __init__(self, *args, **kwargs):
948 BaseGenerator.__init__(args, kwargs)
949
950 self._oem_parser = None
951 self._base_parser = None
952 self._friendly_to_aid = None
Tom Cherry9d924f62019-02-13 14:02:30 -0800953 self._id_to_aid = None
954 self._capability_parser = None
955
956 self._partition = None
957 self._all_partitions = None
958 self._out_file = None
959 self._generate_files = False
960 self._generate_dirs = False
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000961
962 def add_opts(self, opt_group):
963
964 opt_group.add_argument(
965 'fsconfig', nargs='+', help='The list of fsconfig files to parse')
966
967 opt_group.add_argument(
968 '--aid-header',
969 required=True,
970 help='An android_filesystem_config.h file'
971 ' to parse AIDs and OEM Ranges from')
972
Tom Cherry9d924f62019-02-13 14:02:30 -0800973 opt_group.add_argument(
974 '--capability-header',
975 required=True,
976 help='A capability.h file to parse capability defines from')
977
978 opt_group.add_argument(
979 '--partition',
980 required=True,
981 help='Partition to generate contents for')
982
983 opt_group.add_argument(
984 '--all-partitions',
985 help='Comma separated list of all possible partitions, used to'
986 ' ignore these partitions when generating the output for the system partition'
987 )
988
989 opt_group.add_argument(
990 '--files', action='store_true', help='Output fs_config_files')
991
992 opt_group.add_argument(
993 '--dirs', action='store_true', help='Output fs_config_dirs')
994
995 opt_group.add_argument('--out_file', required=True, help='Output file')
996
Elliott Hughes2d7c86d2016-12-13 23:37:07 +0000997 def __call__(self, args):
998
Tom Cherry9d924f62019-02-13 14:02:30 -0800999 self._capability_parser = CapabilityHeaderParser(
1000 args['capability_header'])
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001001 self._base_parser = AIDHeaderParser(args['aid_header'])
1002 self._oem_parser = FSConfigFileParser(args['fsconfig'],
1003 self._base_parser.oem_ranges)
Tom Cherry9d924f62019-02-13 14:02:30 -08001004
1005 self._partition = args['partition']
1006 self._all_partitions = args['all_partitions']
1007 if self._partition == 'system' and self._all_partitions is None:
1008 sys.exit(
1009 'All other partitions must be provided if generating output'
1010 ' for the system partition')
1011
1012 self._out_file = args['out_file']
1013
1014 self._generate_files = args['files']
1015 self._generate_dirs = args['dirs']
1016
1017 if self._generate_files and self._generate_dirs:
1018 sys.exit('Only one of --files or --dirs can be provided')
1019
1020 if not self._generate_files and not self._generate_dirs:
1021 sys.exit('One of --files or --dirs must be provided')
1022
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001023 base_aids = self._base_parser.aids
1024 oem_aids = self._oem_parser.aids
1025
1026 # Detect name collisions on AIDs. Since friendly works as the
1027 # identifier for collision testing and we need friendly later on for
1028 # name resolution, just calculate and use friendly.
1029 # {aid.friendly: aid for aid in base_aids}
1030 base_friendly = {aid.friendly: aid for aid in base_aids}
1031 oem_friendly = {aid.friendly: aid for aid in oem_aids}
1032
1033 base_set = set(base_friendly.keys())
1034 oem_set = set(oem_friendly.keys())
1035
1036 common = base_set & oem_set
1037
Tom Cherry766adc92019-02-13 14:24:52 -08001038 if common:
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001039 emsg = 'Following AID Collisions detected for: \n'
1040 for friendly in common:
1041 base = base_friendly[friendly]
1042 oem = oem_friendly[friendly]
1043 emsg += (
1044 'Identifier: "%s" Friendly Name: "%s" '
1045 'found in file "%s" and "%s"' %
1046 (base.identifier, base.friendly, base.found, oem.found))
1047 sys.exit(emsg)
1048
1049 self._friendly_to_aid = oem_friendly
1050 self._friendly_to_aid.update(base_friendly)
1051
Tom Cherry9d924f62019-02-13 14:02:30 -08001052 self._id_to_aid = {aid.identifier: aid for aid in base_aids}
1053 self._id_to_aid.update({aid.identifier: aid for aid in oem_aids})
1054
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001055 self._generate()
1056
Tom Cherry9d924f62019-02-13 14:02:30 -08001057 def _to_fs_entry(self, fs_config, out_file):
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001058 """Converts an FSConfig entry to an fs entry.
1059
Tom Cherry9d924f62019-02-13 14:02:30 -08001060 Writes the fs_config contents to the output file.
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001061
1062 Calls sys.exit() on error.
1063
1064 Args:
Tom Cherry9d924f62019-02-13 14:02:30 -08001065 fs_config (FSConfig): The entry to convert to write to file.
1066 file (File): The file to write to.
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001067 """
1068
1069 # Get some short names
1070 mode = fs_config.mode
1071 user = fs_config.user
1072 group = fs_config.group
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001073 caps = fs_config.caps
1074 path = fs_config.path
1075
Tom Cherry9d924f62019-02-13 14:02:30 -08001076 emsg = 'Cannot convert "%s" to identifier!'
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001077
Tom Cherry9d924f62019-02-13 14:02:30 -08001078 # convert mode from octal string to integer
1079 mode = int(mode, 8)
1080
1081 # remap names to values
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001082 if AID.is_friendly(user):
1083 if user not in self._friendly_to_aid:
1084 sys.exit(emsg % user)
Tom Cherry9d924f62019-02-13 14:02:30 -08001085 user = self._friendly_to_aid[user].value
1086 else:
1087 if user not in self._id_to_aid:
1088 sys.exit(emsg % user)
1089 user = self._id_to_aid[user].value
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001090
1091 if AID.is_friendly(group):
1092 if group not in self._friendly_to_aid:
1093 sys.exit(emsg % group)
Tom Cherry9d924f62019-02-13 14:02:30 -08001094 group = self._friendly_to_aid[group].value
1095 else:
1096 if group not in self._id_to_aid:
1097 sys.exit(emsg % group)
1098 group = self._id_to_aid[group].value
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001099
Tom Cherry9d924f62019-02-13 14:02:30 -08001100 caps_dict = self._capability_parser.caps
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001101
Tom Cherry9d924f62019-02-13 14:02:30 -08001102 caps_value = 0
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001103
Tom Cherry9d924f62019-02-13 14:02:30 -08001104 try:
1105 # test if caps is an int
1106 caps_value = int(caps, 0)
1107 except ValueError:
1108 caps_split = caps.split(',')
1109 for cap in caps_split:
1110 if cap not in caps_dict:
1111 sys.exit('Unkonwn cap "%s" found!' % cap)
1112 caps_value += 1 << caps_dict[cap]
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001113
Tom Cherry9d924f62019-02-13 14:02:30 -08001114 path_length_with_null = len(path) + 1
1115 path_length_aligned_64 = (path_length_with_null + 7) & ~7
1116 # 16 bytes of header plus the path length with alignment
1117 length = 16 + path_length_aligned_64
1118
1119 length_binary = bytearray(ctypes.c_uint16(length))
1120 mode_binary = bytearray(ctypes.c_uint16(mode))
1121 user_binary = bytearray(ctypes.c_uint16(int(user, 0)))
1122 group_binary = bytearray(ctypes.c_uint16(int(group, 0)))
1123 caps_binary = bytearray(ctypes.c_uint64(caps_value))
1124 path_binary = ctypes.create_string_buffer(path,
1125 path_length_aligned_64).raw
1126
1127 out_file.write(length_binary)
1128 out_file.write(mode_binary)
1129 out_file.write(user_binary)
1130 out_file.write(group_binary)
1131 out_file.write(caps_binary)
1132 out_file.write(path_binary)
1133
1134 def _emit_entry(self, fs_config):
1135 """Returns a boolean whether or not to emit the input fs_config"""
1136
1137 path = fs_config.path
1138
1139 if self._partition == 'system':
1140 for skip_partition in self._all_partitions.split(','):
1141 if path.startswith(skip_partition) or path.startswith(
1142 'system/' + skip_partition):
1143 return False
1144 return True
1145 else:
1146 if path.startswith(
1147 self._partition) or path.startswith('system/' +
1148 self._partition):
1149 return True
1150 return False
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001151
1152 def _generate(self):
1153 """Generates an OEM android_filesystem_config.h header file to stdout.
1154
1155 Args:
1156 files ([FSConfig]): A list of FSConfig objects for file entries.
1157 dirs ([FSConfig]): A list of FSConfig objects for directory
1158 entries.
1159 aids ([AIDS]): A list of AID objects for Android Id entries.
1160 """
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001161 dirs = self._oem_parser.dirs
1162 files = self._oem_parser.files
William Roberts8f42ce72016-04-25 12:27:43 -07001163
Tom Cherry9d924f62019-02-13 14:02:30 -08001164 if self._generate_files:
1165 with open(self._out_file, 'wb') as open_file:
1166 for fs_config in files:
1167 if self._emit_entry(fs_config):
1168 self._to_fs_entry(fs_config, open_file)
William Robertsc950a352016-03-04 18:12:29 -08001169
Tom Cherry9d924f62019-02-13 14:02:30 -08001170 if self._generate_dirs:
1171 with open(self._out_file, 'wb') as open_file:
1172 for dir_entry in dirs:
1173 if self._emit_entry(dir_entry):
1174 self._to_fs_entry(dir_entry, open_file)
William Robertsc950a352016-03-04 18:12:29 -08001175
William Robertsc950a352016-03-04 18:12:29 -08001176
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001177@generator('aidarray')
1178class AIDArrayGen(BaseGenerator):
1179 """Generates the android_id static array."""
1180
1181 _GENERATED = ('/*\n'
1182 ' * THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY!\n'
1183 ' */')
1184
1185 _INCLUDE = '#include <private/android_filesystem_config.h>'
1186
Vic Yang5b3a7c02018-12-03 21:40:33 -08001187 # Note that the android_id name field is of type 'const char[]' instead of
1188 # 'const char*'. While this seems less straightforward as we need to
1189 # calculate the max length of all names, this allows the entire android_ids
1190 # table to be placed in .rodata section instead of .data.rel.ro section,
1191 # resulting in less memory pressure.
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001192 _STRUCT_FS_CONFIG = textwrap.dedent("""
1193 struct android_id_info {
Vic Yang5b3a7c02018-12-03 21:40:33 -08001194 const char name[%d];
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001195 unsigned aid;
1196 };""")
1197
1198 _OPEN_ID_ARRAY = 'static const struct android_id_info android_ids[] = {'
1199
1200 _ID_ENTRY = ' { "%s", %s },'
1201
1202 _CLOSE_FILE_STRUCT = '};'
1203
1204 _COUNT = ('#define android_id_count \\\n'
1205 ' (sizeof(android_ids) / sizeof(android_ids[0]))')
1206
1207 def add_opts(self, opt_group):
1208
1209 opt_group.add_argument(
1210 'hdrfile', help='The android_filesystem_config.h'
1211 'file to parse')
1212
1213 def __call__(self, args):
1214
1215 hdr = AIDHeaderParser(args['hdrfile'])
Vic Yang5b3a7c02018-12-03 21:40:33 -08001216 max_name_length = max(len(aid.friendly) + 1 for aid in hdr.aids)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001217
1218 print AIDArrayGen._GENERATED
1219 print
1220 print AIDArrayGen._INCLUDE
1221 print
Vic Yang5b3a7c02018-12-03 21:40:33 -08001222 print AIDArrayGen._STRUCT_FS_CONFIG % max_name_length
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001223 print
1224 print AIDArrayGen._OPEN_ID_ARRAY
1225
1226 for aid in hdr.aids:
1227 print AIDArrayGen._ID_ENTRY % (aid.friendly, aid.identifier)
1228
1229 print AIDArrayGen._CLOSE_FILE_STRUCT
1230 print
1231 print AIDArrayGen._COUNT
1232 print
1233
1234
1235@generator('oemaid')
1236class OEMAidGen(BaseGenerator):
1237 """Generates the OEM AID_<name> value header file."""
1238
1239 _GENERATED = ('/*\n'
1240 ' * THIS IS AN AUTOGENERATED FILE! DO NOT MODIFY!\n'
1241 ' */')
1242
1243 _GENERIC_DEFINE = "#define %s\t%s"
1244
1245 _FILE_COMMENT = '// Defined in file: \"%s\"'
1246
1247 # Intentional trailing newline for readability.
1248 _FILE_IFNDEF_DEFINE = ('#ifndef GENERATED_OEM_AIDS_H_\n'
1249 '#define GENERATED_OEM_AIDS_H_\n')
1250
1251 _FILE_ENDIF = '#endif'
1252
1253 def __init__(self):
1254
1255 self._old_file = None
1256
1257 def add_opts(self, opt_group):
1258
1259 opt_group.add_argument(
1260 'fsconfig', nargs='+', help='The list of fsconfig files to parse.')
1261
1262 opt_group.add_argument(
1263 '--aid-header',
1264 required=True,
1265 help='An android_filesystem_config.h file'
1266 'to parse AIDs and OEM Ranges from')
1267
1268 def __call__(self, args):
1269
1270 hdr_parser = AIDHeaderParser(args['aid_header'])
1271
1272 parser = FSConfigFileParser(args['fsconfig'], hdr_parser.oem_ranges)
1273
1274 print OEMAidGen._GENERATED
1275
1276 print OEMAidGen._FILE_IFNDEF_DEFINE
1277
1278 for aid in parser.aids:
1279 self._print_aid(aid)
1280 print
1281
1282 print OEMAidGen._FILE_ENDIF
1283
1284 def _print_aid(self, aid):
1285 """Prints a valid #define AID identifier to stdout.
1286
1287 Args:
1288 aid to print
1289 """
1290
1291 # print the source file location of the AID
1292 found_file = aid.found
1293 if found_file != self._old_file:
1294 print OEMAidGen._FILE_COMMENT % found_file
1295 self._old_file = found_file
1296
1297 print OEMAidGen._GENERIC_DEFINE % (aid.identifier, aid.value)
1298
1299
1300@generator('passwd')
1301class PasswdGen(BaseGenerator):
1302 """Generates the /etc/passwd file per man (5) passwd."""
1303
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001304 def __init__(self):
1305
1306 self._old_file = None
1307
1308 def add_opts(self, opt_group):
1309
1310 opt_group.add_argument(
1311 'fsconfig', nargs='+', help='The list of fsconfig files to parse.')
1312
1313 opt_group.add_argument(
1314 '--aid-header',
1315 required=True,
1316 help='An android_filesystem_config.h file'
1317 'to parse AIDs and OEM Ranges from')
1318
Tom Cherry2d197a12018-05-14 13:14:41 -07001319 opt_group.add_argument(
1320 '--required-prefix',
1321 required=False,
1322 help='A prefix that the names are required to contain.')
1323
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001324 def __call__(self, args):
1325
1326 hdr_parser = AIDHeaderParser(args['aid_header'])
1327
1328 parser = FSConfigFileParser(args['fsconfig'], hdr_parser.oem_ranges)
1329
Tom Cherry2d197a12018-05-14 13:14:41 -07001330 required_prefix = args['required_prefix']
1331
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001332 aids = parser.aids
1333
1334 # nothing to do if no aids defined
Tom Cherry766adc92019-02-13 14:24:52 -08001335 if not aids:
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001336 return
1337
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001338 for aid in aids:
Tom Cherry766adc92019-02-13 14:24:52 -08001339 if required_prefix is None or aid.friendly.startswith(
1340 required_prefix):
Tom Cherry2d197a12018-05-14 13:14:41 -07001341 self._print_formatted_line(aid)
1342 else:
1343 sys.exit("%s: AID '%s' must start with '%s'" %
1344 (args['fsconfig'], aid.friendly, required_prefix))
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001345
1346 def _print_formatted_line(self, aid):
1347 """Prints the aid to stdout in the passwd format. Internal use only.
1348
1349 Colon delimited:
1350 login name, friendly name
1351 encrypted password (optional)
1352 uid (int)
1353 gid (int)
1354 User name or comment field
1355 home directory
1356 interpreter (optional)
1357
1358 Args:
1359 aid (AID): The aid to print.
1360 """
1361 if self._old_file != aid.found:
1362 self._old_file = aid.found
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001363
1364 try:
1365 logon, uid = Utils.get_login_and_uid_cleansed(aid)
1366 except ValueError as exception:
1367 sys.exit(exception)
1368
Wei Wang77e329a2018-06-05 16:00:07 -07001369 print "%s::%s:%s::/:%s" % (logon, uid, uid, aid.login_shell)
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001370
1371
1372@generator('group')
1373class GroupGen(PasswdGen):
1374 """Generates the /etc/group file per man (5) group."""
1375
1376 # Overrides parent
1377 def _print_formatted_line(self, aid):
1378 """Prints the aid to stdout in the group format. Internal use only.
1379
1380 Formatted (per man 5 group) like:
1381 group_name:password:GID:user_list
1382
1383 Args:
1384 aid (AID): The aid to print.
1385 """
1386 if self._old_file != aid.found:
1387 self._old_file = aid.found
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001388
1389 try:
1390 logon, uid = Utils.get_login_and_uid_cleansed(aid)
1391 except ValueError as exception:
1392 sys.exit(exception)
1393
1394 print "%s::%s:" % (logon, uid)
1395
Tom Cherry766adc92019-02-13 14:24:52 -08001396
Joe Onorato2afb6eb2018-12-04 14:51:58 -08001397@generator('print')
1398class PrintGen(BaseGenerator):
1399 """Prints just the constants and values, separated by spaces, in an easy to
1400 parse format for use by other scripts.
1401
1402 Each line is just the identifier and the value, separated by a space.
1403 """
1404
1405 def add_opts(self, opt_group):
1406 opt_group.add_argument(
1407 'aid-header', help='An android_filesystem_config.h file.')
1408
1409 def __call__(self, args):
1410
1411 hdr_parser = AIDHeaderParser(args['aid-header'])
1412 aids = hdr_parser.aids
1413
1414 aids.sort(key=lambda item: int(item.normalized_value))
1415
1416 for aid in aids:
1417 print '%s %s' % (aid.identifier, aid.normalized_value)
1418
William Roberts1c4721c2016-04-26 13:05:34 -07001419
William Robertsc950a352016-03-04 18:12:29 -08001420def main():
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001421 """Main entry point for execution."""
William Robertsc950a352016-03-04 18:12:29 -08001422
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001423 opt_parser = argparse.ArgumentParser(
1424 description='A tool for parsing fsconfig config files and producing' +
1425 'digestable outputs.')
1426 subparser = opt_parser.add_subparsers(help='generators')
William Robertsc950a352016-03-04 18:12:29 -08001427
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001428 gens = generator.get()
William Robertsc950a352016-03-04 18:12:29 -08001429
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001430 # for each gen, instantiate and add them as an option
1431 for name, gen in gens.iteritems():
William Robertsc950a352016-03-04 18:12:29 -08001432
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001433 generator_option_parser = subparser.add_parser(name, help=gen.__doc__)
1434 generator_option_parser.set_defaults(which=name)
William Roberts8cb6a182016-04-08 22:06:19 -07001435
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001436 opt_group = generator_option_parser.add_argument_group(name +
1437 ' options')
1438 gen.add_opts(opt_group)
William Roberts8cb6a182016-04-08 22:06:19 -07001439
Elliott Hughes2d7c86d2016-12-13 23:37:07 +00001440 args = opt_parser.parse_args()
1441
1442 args_as_dict = vars(args)
1443 which = args_as_dict['which']
1444 del args_as_dict['which']
1445
1446 gens[which](args_as_dict)
1447
William Robertsc950a352016-03-04 18:12:29 -08001448
1449if __name__ == '__main__':
1450 main()