Generate /etc/{passwd,group} for all partitions

Bug: 73062966
Test: can load AIDs from partitions other than vendor
Test: bionic-unit-tests
Merged-In: Ia85abbeefe5a945369970f2aef42692e07ab8c09
Change-Id: Ia85abbeefe5a945369970f2aef42692e07ab8c09
(cherry picked from commit 8a54ec8482cc7bfa892fe1749f53b4c1a17f026e)
diff --git a/core/definitions.mk b/core/definitions.mk
index f6472fe..fcabde8 100644
--- a/core/definitions.mk
+++ b/core/definitions.mk
@@ -2444,8 +2444,16 @@
 	$(1) \
 	$(HOST_INIT_VERIFIER) \
 	$(HIDL_INHERITANCE_HIERARCHY) \
-	$(call intermediates-dir-for,ETC,passwd)/passwd
-	$(hide) $(HOST_INIT_VERIFIER) -p $(call intermediates-dir-for,ETC,passwd)/passwd -i $(HIDL_INHERITANCE_HIERARCHY) $$<
+	$(call intermediates-dir-for,ETC,passwd_system)/passwd_system \
+	$(call intermediates-dir-for,ETC,passwd_vendor)/passwd_vendor \
+	$(call intermediates-dir-for,ETC,passwd_odm)/passwd_odm \
+	$(call intermediates-dir-for,ETC,passwd_product)/passwd_product
+	$(hide) $(HOST_INIT_VERIFIER) \
+	  -p $(call intermediates-dir-for,ETC,passwd_system)/passwd_system \
+	  -p $(call intermediates-dir-for,ETC,passwd_vendor)/passwd_vendor \
+	  -p $(call intermediates-dir-for,ETC,passwd_odm)/passwd_odm \
+	  -p $(call intermediates-dir-for,ETC,passwd_product)/passwd_product \
+	  -i $(HIDL_INHERITANCE_HIERARCHY) $$<
 else
 $(2): $(1)
 endif
diff --git a/target/product/base_product.mk b/target/product/base_product.mk
index 82557bf..749d2c2 100644
--- a/target/product/base_product.mk
+++ b/target/product/base_product.mk
@@ -16,7 +16,9 @@
 
 # Base modules and settings for the product partition.
 PRODUCT_PACKAGES += \
+    group_product \
     healthd \
     ModuleMetadata \
+    passwd_product \
     product_compatibility_matrix.xml \
     product_manifest.xml \
diff --git a/target/product/base_system.mk b/target/product/base_system.mk
index 2e8f43c..fbc9add 100644
--- a/target/product/base_system.mk
+++ b/target/product/base_system.mk
@@ -80,6 +80,7 @@
     fsck_msdos \
     fs_config_files_system \
     fs_config_dirs_system \
+    group_system \
     gsid \
     gsi_tool \
     heapprofd \
@@ -214,6 +215,7 @@
     org.apache.http.legacy \
     otacerts \
     PackageInstaller \
+    passwd_system \
     perfetto \
     PermissionController \
     ping \
diff --git a/target/product/base_vendor.mk b/target/product/base_vendor.mk
index 2d11610..64e18d2 100644
--- a/target/product/base_vendor.mk
+++ b/target/product/base_vendor.mk
@@ -47,7 +47,8 @@
     fs_config_files_nonsystem \
     fs_config_dirs_nonsystem \
     gralloc.default \
-    group \
+    group_odm \
+    group_vendor \
     init_vendor \
     libashmemd_hidl_client \
     libbundlewrapper \
@@ -62,7 +63,8 @@
     libreverbwrapper \
     libril \
     libvisualizer \
-    passwd \
+    passwd_odm \
+    passwd_vendor \
     selinux_policy_nonsystem \
     shell_and_utilities_vendor \
     vndservice \
diff --git a/tools/fs_config/Android.bp b/tools/fs_config/Android.bp
index d9a48d7..8c69417 100644
--- a/tools/fs_config/Android.bp
+++ b/tools/fs_config/Android.bp
@@ -20,7 +20,7 @@
         "soong-genrule",
     ],
     srcs: [
-        "fs_config.go"
+        "fs_config.go",
     ],
     pluginFor: ["soong_build"],
 }
@@ -56,13 +56,13 @@
     export_generated_headers: ["oemaids_header_gen"],
 }
 
-// Generate the vendor/etc/passwd text file for the target
-// This file may be empty if no AIDs are defined in
+// Generate the */etc/passwd text files for the target
+// These files may be empty if no AIDs are defined in
 // TARGET_FS_CONFIG_GEN files.
 genrule {
-    name: "passwd_gen",
+    name: "passwd_gen_system",
     tool_files: ["fs_config_generator.py"],
-    cmd: "$(location fs_config_generator.py) passwd --required-prefix=vendor_ --aid-header=$(location :android_filesystem_config_header) $(locations :target_fs_config_gen) >$(out)",
+    cmd: "$(location fs_config_generator.py) passwd --partition=system --aid-header=$(location :android_filesystem_config_header) $(locations :target_fs_config_gen) >$(out)",
     srcs: [
         ":target_fs_config_gen",
         ":android_filesystem_config_header",
@@ -71,18 +71,90 @@
 }
 
 prebuilt_etc {
-    name: "passwd",
-    vendor: true,
-    src: ":passwd_gen",
+    name: "passwd_system",
+    filename: "passwd",
+    src: ":passwd_gen_system",
 }
 
-// Generate the vendor/etc/group text file for the target
-// This file may be empty if no AIDs are defined in
+genrule {
+    name: "passwd_gen_vendor",
+    tool_files: ["fs_config_generator.py"],
+    cmd: "$(location fs_config_generator.py) passwd --partition=vendor --aid-header=$(location :android_filesystem_config_header) $(locations :target_fs_config_gen) >$(out)",
+    srcs: [
+        ":target_fs_config_gen",
+        ":android_filesystem_config_header",
+    ],
+    out: ["passwd"],
+}
+
+prebuilt_etc {
+    name: "passwd_vendor",
+    filename: "passwd",
+    vendor: true,
+    src: ":passwd_gen_vendor",
+}
+
+genrule {
+    name: "passwd_gen_odm",
+    tool_files: ["fs_config_generator.py"],
+    cmd: "$(location fs_config_generator.py) passwd --partition=odm --aid-header=$(location :android_filesystem_config_header) $(locations :target_fs_config_gen) >$(out)",
+    srcs: [
+        ":target_fs_config_gen",
+        ":android_filesystem_config_header",
+    ],
+    out: ["passwd"],
+}
+
+prebuilt_etc {
+    name: "passwd_odm",
+    filename: "passwd",
+    device_specific: true,
+    src: ":passwd_gen_odm",
+}
+
+genrule {
+    name: "passwd_gen_product",
+    tool_files: ["fs_config_generator.py"],
+    cmd: "$(location fs_config_generator.py) passwd --partition=product --aid-header=$(location :android_filesystem_config_header) $(locations :target_fs_config_gen) >$(out)",
+    srcs: [
+        ":target_fs_config_gen",
+        ":android_filesystem_config_header",
+    ],
+    out: ["passwd"],
+}
+
+prebuilt_etc {
+    name: "passwd_product",
+    filename: "passwd",
+    product_specific: true,
+    src: ":passwd_gen_product",
+}
+
+genrule {
+    name: "passwd_gen_system_ext",
+    tool_files: ["fs_config_generator.py"],
+    cmd: "$(location fs_config_generator.py) passwd --partition=system_ext --aid-header=$(location :android_filesystem_config_header) $(locations :target_fs_config_gen) >$(out)",
+    srcs: [
+        ":target_fs_config_gen",
+        ":android_filesystem_config_header",
+    ],
+    out: ["passwd"],
+}
+
+prebuilt_etc {
+    name: "passwd_system_ext",
+    filename: "passwd",
+    system_ext_specific: true,
+    src: ":passwd_gen_system_ext",
+}
+
+// Generate the */etc/group text files for the target
+// These files may be empty if no AIDs are defined in
 // TARGET_FS_CONFIG_GEN files.
 genrule {
-    name: "group_gen",
+    name: "group_gen_system",
     tool_files: ["fs_config_generator.py"],
-    cmd: "$(location fs_config_generator.py) group --required-prefix=vendor_ --aid-header=$(location :android_filesystem_config_header) $(locations :target_fs_config_gen) >$(out)",
+    cmd: "$(location fs_config_generator.py) group --partition=system --aid-header=$(location :android_filesystem_config_header) $(locations :target_fs_config_gen) >$(out)",
     srcs: [
         ":target_fs_config_gen",
         ":android_filesystem_config_header",
@@ -91,7 +163,79 @@
 }
 
 prebuilt_etc {
-    name: "group",
+    name: "group_system",
+    filename: "group",
+    src: ":group_gen_system",
+}
+
+genrule {
+    name: "group_gen_vendor",
+    tool_files: ["fs_config_generator.py"],
+    cmd: "$(location fs_config_generator.py) group --partition=vendor --aid-header=$(location :android_filesystem_config_header) $(locations :target_fs_config_gen) >$(out)",
+    srcs: [
+        ":target_fs_config_gen",
+        ":android_filesystem_config_header",
+    ],
+    out: ["group"],
+}
+
+prebuilt_etc {
+    name: "group_vendor",
+    filename: "group",
     vendor: true,
-    src: ":group_gen",
+    src: ":group_gen_vendor",
+}
+
+genrule {
+    name: "group_gen_odm",
+    tool_files: ["fs_config_generator.py"],
+    cmd: "$(location fs_config_generator.py) group --partition=odm --aid-header=$(location :android_filesystem_config_header) $(locations :target_fs_config_gen) >$(out)",
+    srcs: [
+        ":target_fs_config_gen",
+        ":android_filesystem_config_header",
+    ],
+    out: ["group"],
+}
+
+prebuilt_etc {
+    name: "group_odm",
+    filename: "group",
+    device_specific: true,
+    src: ":group_gen_odm",
+}
+
+genrule {
+    name: "group_gen_product",
+    tool_files: ["fs_config_generator.py"],
+    cmd: "$(location fs_config_generator.py) group --partition=product --aid-header=$(location :android_filesystem_config_header) $(locations :target_fs_config_gen) >$(out)",
+    srcs: [
+        ":target_fs_config_gen",
+        ":android_filesystem_config_header",
+    ],
+    out: ["group"],
+}
+
+prebuilt_etc {
+    name: "group_product",
+    filename: "group",
+    product_specific: true,
+    src: ":group_gen_product",
+}
+
+genrule {
+    name: "group_gen_system_ext",
+    tool_files: ["fs_config_generator.py"],
+    cmd: "$(location fs_config_generator.py) group --partition=system_ext --aid-header=$(location :android_filesystem_config_header) $(locations :target_fs_config_gen) >$(out)",
+    srcs: [
+        ":target_fs_config_gen",
+        ":android_filesystem_config_header",
+    ],
+    out: ["group"],
+}
+
+prebuilt_etc {
+    name: "group_system_ext",
+    filename: "group",
+    system_ext_specific: true,
+    src: ":group_gen_system_ext",
 }
diff --git a/tools/fs_config/fs_config_generator.py b/tools/fs_config/fs_config_generator.py
index 4400466..e1aafc9 100755
--- a/tools/fs_config/fs_config_generator.py
+++ b/tools/fs_config/fs_config_generator.py
@@ -312,13 +312,12 @@
         re.compile(r'%sUSER' % AID.PREFIX)
     ]
     _AID_DEFINE = re.compile(r'\s*#define\s+%s.*' % AID.PREFIX)
-    _OEM_START_KW = 'START'
-    _OEM_END_KW = 'END'
-    _OEM_RANGE = re.compile('%sOEM_RESERVED_[0-9]*_{0,1}(%s|%s)' %
-                            (AID.PREFIX, _OEM_START_KW, _OEM_END_KW))
+    _RESERVED_RANGE = re.compile(
+        r'#define AID_(.+)_RESERVED_\d*_*(START|END)\s+(\d+)')
+
     # AID lines cannot end with _START or _END, ie AID_FOO is OK
     # but AID_FOO_START is skiped. Note that AID_FOOSTART is NOT skipped.
-    _AID_SKIP_RANGE = ['_' + _OEM_START_KW, '_' + _OEM_END_KW]
+    _AID_SKIP_RANGE = ['_START', '_END']
     _COLLISION_OK = ['AID_APP', 'AID_APP_START', 'AID_USER', 'AID_USER_OFFSET']
 
     def __init__(self, aid_header):
@@ -330,7 +329,7 @@
         self._aid_header = aid_header
         self._aid_name_to_value = {}
         self._aid_value_to_name = {}
-        self._oem_ranges = {}
+        self._ranges = {}
 
         with open(aid_header) as open_file:
             self._parse(open_file)
@@ -355,6 +354,23 @@
                 return 'Error "{}" in file: "{}" on line: {}'.format(
                     msg, self._aid_header, str(lineno))
 
+            range_match = self._RESERVED_RANGE.match(line)
+            if range_match:
+                partition = range_match.group(1).lower()
+                value = int(range_match.group(3), 0)
+
+                if partition == 'oem':
+                    partition = 'vendor'
+
+                if partition in self._ranges:
+                    if isinstance(self._ranges[partition][-1], int):
+                        self._ranges[partition][-1] = (
+                            self._ranges[partition][-1], value)
+                    else:
+                        self._ranges[partition].append(value)
+                else:
+                    self._ranges[partition] = [value]
+
             if AIDHeaderParser._AID_DEFINE.match(line):
                 chunks = line.split()
                 identifier = chunks[1]
@@ -366,9 +382,7 @@
                     continue
 
                 try:
-                    if AIDHeaderParser._is_oem_range(identifier):
-                        self._handle_oem_range(identifier, value)
-                    elif not any(
+                    if not any(
                             identifier.endswith(x)
                             for x in AIDHeaderParser._AID_SKIP_RANGE):
                         self._handle_aid(identifier, value)
@@ -404,67 +418,6 @@
         self._aid_name_to_value[aid.friendly] = aid
         self._aid_value_to_name[value] = aid.friendly
 
-    def _handle_oem_range(self, identifier, value):
-        """Handle an OEM range C #define.
-
-        When encountering special AID defines, notably for the OEM ranges
-        this method handles sanity checking and adding them to the internal
-        maps. For internal use only.
-
-        Args:
-            identifier (str): The name of the #define identifier.
-                ie AID_OEM_RESERVED_START/END.
-            value (str): The value associated with the identifier.
-
-        Raises:
-            ValueError: With message set to indicate the error.
-        """
-
-        try:
-            int_value = int(value, 0)
-        except ValueError:
-            raise ValueError(
-                'Could not convert "%s" to integer value, got: "%s"' %
-                (identifier, value))
-
-        # convert AID_OEM_RESERVED_START or AID_OEM_RESERVED_<num>_START
-        # to AID_OEM_RESERVED or AID_OEM_RESERVED_<num>
-        is_start = identifier.endswith(AIDHeaderParser._OEM_START_KW)
-
-        if is_start:
-            tostrip = len(AIDHeaderParser._OEM_START_KW)
-        else:
-            tostrip = len(AIDHeaderParser._OEM_END_KW)
-
-        # ending _
-        tostrip = tostrip + 1
-
-        strip = identifier[:-tostrip]
-        if strip not in self._oem_ranges:
-            self._oem_ranges[strip] = []
-
-        if len(self._oem_ranges[strip]) > 2:
-            raise ValueError('Too many same OEM Ranges "%s"' % identifier)
-
-        if len(self._oem_ranges[strip]) == 1:
-            tmp = self._oem_ranges[strip][0]
-
-            if tmp == int_value:
-                raise ValueError('START and END values equal %u' % int_value)
-            elif is_start and tmp < int_value:
-                raise ValueError(
-                    'END value %u less than START value %u' % (tmp, int_value))
-            elif not is_start and tmp > int_value:
-                raise ValueError(
-                    'END value %u less than START value %u' % (int_value, tmp))
-
-        # Add START values to the head of the list and END values at the end.
-        # Thus, the list is ordered with index 0 as START and index 1 as END.
-        if is_start:
-            self._oem_ranges[strip].insert(0, int_value)
-        else:
-            self._oem_ranges[strip].append(int_value)
-
     def _process_and_check(self):
         """Process, check and populate internal data structures.
 
@@ -475,36 +428,32 @@
             ValueError: With the message set to indicate the specific error.
         """
 
-        # tuplefy the lists since range() does not like them mutable.
-        self._oem_ranges = [
-            AIDHeaderParser._convert_lst_to_tup(k, v)
-            for k, v in self._oem_ranges.iteritems()
-        ]
-
         # Check for overlapping ranges
-        for i, range1 in enumerate(self._oem_ranges):
-            for range2 in self._oem_ranges[i + 1:]:
-                if AIDHeaderParser._is_overlap(range1, range2):
-                    raise ValueError("Overlapping OEM Ranges found %s and %s" %
-                                     (str(range1), str(range2)))
+        for ranges in self._ranges.values():
+            for i, range1 in enumerate(ranges):
+                for range2 in ranges[i + 1:]:
+                    if AIDHeaderParser._is_overlap(range1, range2):
+                        raise ValueError(
+                            "Overlapping OEM Ranges found %s and %s" %
+                            (str(range1), str(range2)))
 
         # No core AIDs should be within any oem range.
         for aid in self._aid_value_to_name:
-
-            if Utils.in_any_range(aid, self._oem_ranges):
-                name = self._aid_value_to_name[aid]
-                raise ValueError(
-                    'AID "%s" value: %u within reserved OEM Range: "%s"' %
-                    (name, aid, str(self._oem_ranges)))
+            for ranges in self._ranges.values():
+                if Utils.in_any_range(aid, ranges):
+                    name = self._aid_value_to_name[aid]
+                    raise ValueError(
+                        'AID "%s" value: %u within reserved OEM Range: "%s"' %
+                        (name, aid, str(ranges)))
 
     @property
-    def oem_ranges(self):
+    def ranges(self):
         """Retrieves the OEM closed ranges as a list of tuples.
 
         Returns:
             A list of closed range tuples: [ (0, 42), (50, 105) ... ]
         """
-        return self._oem_ranges
+        return self._ranges
 
     @property
     def aids(self):
@@ -516,39 +465,6 @@
         return self._aid_name_to_value.values()
 
     @staticmethod
-    def _convert_lst_to_tup(name, lst):
-        """Converts a mutable list to a non-mutable tuple.
-
-        Used ONLY for ranges and thus enforces a length of 2.
-
-        Args:
-            lst (List): list that should be "tuplefied".
-
-        Raises:
-            ValueError if lst is not a list or len is not 2.
-
-        Returns:
-            Tuple(lst)
-        """
-        if not lst or len(lst) != 2:
-            raise ValueError('Mismatched range for "%s"' % name)
-
-        return tuple(lst)
-
-    @staticmethod
-    def _is_oem_range(aid):
-        """Detects if a given aid is within the reserved OEM range.
-
-        Args:
-            aid (int): The aid to test
-
-        Returns:
-            True if it is within the range, False otherwise.
-        """
-
-        return AIDHeaderParser._OEM_RANGE.match(aid)
-
-    @staticmethod
     def _is_overlap(range_a, range_b):
         """Calculates the overlap of two range tuples.
 
@@ -588,12 +504,12 @@
     _SECTIONS = [('_handle_aid', ('value', )),
                  ('_handle_path', ('mode', 'user', 'group', 'caps'))]
 
-    def __init__(self, config_files, oem_ranges):
+    def __init__(self, config_files, ranges):
         """
         Args:
             config_files ([str]): The list of config.fs files to parse.
                 Note the filename is not important.
-            oem_ranges ([(),()]): range tuples indicating reserved OEM ranges.
+            ranges ({str,[()]): Dictionary of partitions and a list of tuples that correspond to their ranges
         """
 
         self._files = []
@@ -604,7 +520,7 @@
         # (name to file, value to aid)
         self._seen_aids = ({}, {})
 
-        self._oem_ranges = oem_ranges
+        self._ranges = ranges
 
         self._config_files = config_files
 
@@ -669,6 +585,27 @@
             # within the generated file.
             self._aids.sort(key=lambda item: item.normalized_value)
 
+    def _verify_valid_range(self, aid):
+        """Verified an AID entry is in a valid range"""
+
+        ranges = None
+
+        partitions = self._ranges.keys()
+        partitions.sort(key=len, reverse=True)
+        for partition in partitions:
+            if aid.friendly.startswith(partition):
+                ranges = self._ranges[partition]
+                break
+
+        if ranges is None:
+            sys.exit('AID "%s" must be prefixed with a partition name' %
+                     aid.friendly)
+
+        if not Utils.in_any_range(int(aid.value, 0), ranges):
+            emsg = '"value" for aid "%s" not in valid range %s, got: %s'
+            emsg = emsg % (aid.friendly, str(ranges), aid.value)
+            sys.exit(emsg)
+
     def _handle_aid(self, file_name, section_name, config):
         """Verifies an AID entry and adds it to the aid list.
 
@@ -702,15 +639,11 @@
             sys.exit(error_message('Found specified but unset "value"'))
 
         try:
-            aid = AID(section_name, value, file_name, '/vendor/bin/sh')
+            aid = AID(section_name, value, file_name, '/bin/sh')
         except ValueError as exception:
             sys.exit(error_message(exception))
 
-        # Values must be within OEM range
-        if not Utils.in_any_range(int(aid.value, 0), self._oem_ranges):
-            emsg = '"value" not in valid range %s, got: %s'
-            emsg = emsg % (str(self._oem_ranges), value)
-            sys.exit(error_message(emsg))
+        self._verify_valid_range(aid)
 
         # use the normalized int value in the dict and detect
         # duplicate definitions of the same value
@@ -1000,7 +933,7 @@
             args['capability_header'])
         self._base_parser = AIDHeaderParser(args['aid_header'])
         self._oem_parser = FSConfigFileParser(args['fsconfig'],
-                                              self._base_parser.oem_ranges)
+                                              self._base_parser.ranges)
 
         self._partition = args['partition']
         self._all_partitions = args['all_partitions']
@@ -1265,7 +1198,7 @@
 
         hdr_parser = AIDHeaderParser(args['aid_header'])
 
-        parser = FSConfigFileParser(args['fsconfig'], hdr_parser.oem_ranges)
+        parser = FSConfigFileParser(args['fsconfig'], hdr_parser.ranges)
 
         print OEMAidGen._GENERATED
 
@@ -1313,17 +1246,19 @@
             'to parse AIDs and OEM Ranges from')
 
         opt_group.add_argument(
-            '--required-prefix',
-            required=False,
-            help='A prefix that the names are required to contain.')
+            '--partition',
+            required=True,
+            help=
+            'Filter the input file and only output entries for the given partition.'
+        )
 
     def __call__(self, args):
 
         hdr_parser = AIDHeaderParser(args['aid_header'])
 
-        parser = FSConfigFileParser(args['fsconfig'], hdr_parser.oem_ranges)
+        parser = FSConfigFileParser(args['fsconfig'], hdr_parser.ranges)
 
-        required_prefix = args['required_prefix']
+        filter_partition = args['partition']
 
         aids = parser.aids
 
@@ -1331,13 +1266,22 @@
         if not aids:
             return
 
+        aids_by_partition = {}
+        partitions = hdr_parser.ranges.keys()
+        partitions.sort(key=len, reverse=True)
+
         for aid in aids:
-            if required_prefix is None or aid.friendly.startswith(
-                    required_prefix):
+            for partition in partitions:
+                if aid.friendly.startswith(partition):
+                    if partition in aids_by_partition:
+                        aids_by_partition[partition].append(aid)
+                    else:
+                        aids_by_partition[partition] = [aid]
+                    break
+
+        if filter_partition in aids_by_partition:
+            for aid in aids_by_partition[filter_partition]:
                 self._print_formatted_line(aid)
-            else:
-                sys.exit("%s: AID '%s' must start with '%s'" %
-                         (args['fsconfig'], aid.friendly, required_prefix))
 
     def _print_formatted_line(self, aid):
         """Prints the aid to stdout in the passwd format. Internal use only.