From 3506ad3f310d46ace11ecb9f1a0b6d8abc4951c4 Mon Sep 17 00:00:00 2001
From: Bowgo Tsai <bowgotsai@google.com>
Date: Fri, 5 Jan 2018 14:52:04 +0800
Subject: [PATCH] Using a python script to build sepolicy

Current sepolicy CIL files are built by several command-line tools
in Android.mk. This change extracts some of the build logic into a
python script to relief the effort in Android.mk.

The first command is `build_sepolicy build_cil`. It's possible to add
more sub-commands under the build_sepolicy script in the future.

Bug: 64240127
Test: build and boot a device
Test: checks the content of $OUT/vendor/etc/selinux/vendor_sepolicy.cil
      is the same as before
Change-Id: I0b64f1088f413172e97b579b4f7799fa392762df
---
 Android.bp              |   5 +-
 Android.mk              |  38 ++++-------
 build/Android.bp        |  34 ++++++++++
 build/build_sepolicy.py | 140 ++++++++++++++++++++++++++++++++++++++++
 build/file_utils.py     |  49 ++++++++++++++
 5 files changed, 241 insertions(+), 25 deletions(-)
 create mode 100644 build/Android.bp
 create mode 100644 build/build_sepolicy.py
 create mode 100644 build/file_utils.py

diff --git a/Android.bp b/Android.bp
index 9952a8fd5..17853420b 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1 +1,4 @@
-subdirs = ["tests"]
+subdirs = [
+    "tests",
+    "build",
+]
diff --git a/Android.mk b/Android.mk
index 40c457061..0d4d67fb5 100644
--- a/Android.mk
+++ b/Android.mk
@@ -222,6 +222,7 @@ LOCAL_REQUIRED_MODULES += \
     plat_and_mapping_sepolicy.cil.sha256 \
     secilc \
     plat_sepolicy_vers.txt \
+    vendor_service_contexts \
 
 # Include precompiled policy, unless told otherwise
 ifneq ($(PRODUCT_PRECOMPILED_SEPOLICY),false)
@@ -233,6 +234,7 @@ LOCAL_REQUIRED_MODULES += sepolicy
 endif
 
 LOCAL_REQUIRED_MODULES += \
+    build_sepolicy \
     vendor_file_contexts \
     vendor_mac_permissions.xml \
     vendor_property_contexts \
@@ -247,10 +249,6 @@ LOCAL_REQUIRED_MODULES += \
     searchpolicy \
     vndservice_contexts \
 
-ifneq ($(PRODUCT_SEPOLICY_SPLIT),true)
-LOCAL_REQUIRED_MODULES += vendor_service_contexts
-endif
-
 ifneq ($(TARGET_BUILD_VARIANT), user)
 LOCAL_REQUIRED_MODULES += \
     selinux_denial_metadata \
@@ -540,31 +538,23 @@ $(PLAT_PUBLIC_POLICY) $(REQD_MASK_POLICY) $(PLAT_VENDOR_POLICY) $(BOARD_VENDOR_S
 	$(transform-policy-to-conf)
 	$(hide) sed '/dontaudit/d' $@ > $@.dontaudit
 
-vendor_policy_raw := $(intermediates)/vendor_policy_raw.cil
-$(vendor_policy_raw): PRIVATE_POL_CONF := $(vendor_policy.conf)
-$(vendor_policy_raw): PRIVATE_REQD_MASK := $(reqd_policy_mask.cil)
-$(vendor_policy_raw): $(HOST_OUT_EXECUTABLES)/checkpolicy $(vendor_policy.conf) \
-$(reqd_policy_mask.cil)
-	@mkdir -p $(dir $@)
-	$(hide) $(CHECKPOLICY_ASAN_OPTIONS) $< -C -M -c $(POLICYVERS) -o $@.tmp $(PRIVATE_POL_CONF)
-	$(hide) grep -Fxv -f $(PRIVATE_REQD_MASK) $@.tmp > $@
-
-$(LOCAL_BUILT_MODULE) : PRIVATE_VERS := $(BOARD_SEPOLICY_VERS)
-$(LOCAL_BUILT_MODULE) : PRIVATE_TGT_POL := $(vendor_policy_raw)
-$(LOCAL_BUILT_MODULE) : PRIVATE_DEP_CIL_FILES := $(built_plat_cil) $(built_plat_pub_vers_cil) $(built_mapping_cil)
-$(LOCAL_BUILT_MODULE) : PRIVATE_FILTER_CIL := $(built_plat_pub_vers_cil)
-$(LOCAL_BUILT_MODULE) : $(plat_pub_policy.cil) $(vendor_policy_raw) \
-  $(HOST_OUT_EXECUTABLES)/version_policy $(HOST_OUT_EXECUTABLES)/secilc \
+$(LOCAL_BUILT_MODULE): PRIVATE_POL_CONF := $(vendor_policy.conf)
+$(LOCAL_BUILT_MODULE): PRIVATE_REQD_MASK := $(reqd_policy_mask.cil)
+$(LOCAL_BUILT_MODULE): PRIVATE_BASE_CIL := $(plat_pub_policy.cil)
+$(LOCAL_BUILT_MODULE): PRIVATE_VERS := $(BOARD_SEPOLICY_VERS)
+$(LOCAL_BUILT_MODULE): PRIVATE_DEP_CIL_FILES := $(built_plat_cil) $(built_plat_pub_vers_cil) $(built_mapping_cil)
+$(LOCAL_BUILT_MODULE): PRIVATE_FILTER_CIL := $(built_plat_pub_vers_cil)
+$(LOCAL_BUILT_MODULE): $(HOST_OUT_EXECUTABLES)/build_sepolicy \
+  $(vendor_policy.conf) $(reqd_policy_mask.cil) $(plat_pub_policy.cil) \
   $(built_plat_cil) $(built_plat_pub_vers_cil) $(built_mapping_cil)
 	@mkdir -p $(dir $@)
-	$(HOST_OUT_EXECUTABLES)/version_policy -b $< -t $(PRIVATE_TGT_POL) -n $(PRIVATE_VERS) -o $@.tmp
-	$(hide) grep -Fxv -f $(PRIVATE_FILTER_CIL) $@.tmp > $@
-	$(hide) $(HOST_OUT_EXECUTABLES)/secilc -m -M true -G -N -c $(POLICYVERS) \
-		$(PRIVATE_DEP_CIL_FILES) $@ -o /dev/null -f /dev/null
+	$(hide) $(HOST_OUT_EXECUTABLES)/build_sepolicy -a $(HOST_OUT_EXECUTABLES) build_cil \
+		-i $(PRIVATE_POL_CONF) -m $(PRIVATE_REQD_MASK) -c $(CHECKPOLICY_ASAN_OPTIONS) \
+		-b $(PRIVATE_BASE_CIL) -d $(PRIVATE_DEP_CIL_FILES) -f $(PRIVATE_FILTER_CIL) \
+		-t $(PRIVATE_VERS) -p $(POLICYVERS) -o $@
 
 built_vendor_cil := $(LOCAL_BUILT_MODULE)
 vendor_policy.conf :=
-vendor_policy_raw :=
 
 #################################
 include $(CLEAR_VARS)
diff --git a/build/Android.bp b/build/Android.bp
new file mode 100644
index 000000000..d3f1fc36b
--- /dev/null
+++ b/build/Android.bp
@@ -0,0 +1,34 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+python_binary_host {
+  name: "build_sepolicy",
+  srcs: [
+    "build_sepolicy.py",
+    "file_utils.py",
+  ],
+  required: [
+    "checkpolicy",
+    "secilc",
+    "version_policy",
+  ],
+  version: {
+    py2: {
+      enabled: true,
+    },
+    py3: {
+      enabled: false,
+    },
+  },
+}
diff --git a/build/build_sepolicy.py b/build/build_sepolicy.py
new file mode 100644
index 000000000..ff2ff075d
--- /dev/null
+++ b/build/build_sepolicy.py
@@ -0,0 +1,140 @@
+# Copyright 2018 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Command-line tool to build SEPolicy files."""
+
+import argparse
+import os
+import subprocess
+import sys
+
+import file_utils
+
+
+# All supported commands in this module.
+# For each command, need to add two functions. Take 'build_cil' for example:
+#   - setup_build_cil()
+#     - Sets up command parsers and sets default function to do_build_cil().
+#   - do_build_cil()
+_SUPPORTED_COMMANDS = ('build_cil',)
+
+
+def run_host_command(args, **kwargs):
+    """Runs a host command and prints output."""
+    if kwargs.get('shell'):
+        command_log = args
+    else:
+        command_log = ' '.join(args)  # For args as a sequence.
+
+    try:
+        subprocess.check_call(args, **kwargs)
+    except subprocess.CalledProcessError as err:
+        sys.stderr.write(
+            'build_sepolicy - failed to run command: {!r} (ret:{})\n'.format(
+                command_log, err.returncode))
+        sys.exit(err.returncode)
+
+
+def do_build_cil(args):
+    """Builds a sepolicy CIL (Common Intermediate Language) file.
+
+    This functions invokes some host utils (e.g., secilc, checkpolicy,
+    version_sepolicy) to generate a .cil file.
+
+    Args:
+        args: the parsed command arguments.
+    """
+    # Determines the raw CIL file name.
+    input_file_name = os.path.splitext(args.input_policy_conf)[0]
+    raw_cil_file = input_file_name + '_raw.cil'
+    # Builds the raw CIL.
+    file_utils.make_parent_dirs(raw_cil_file)
+    checkpolicy_cmd = [args.checkpolicy_env]
+    checkpolicy_cmd += [os.path.join(args.android_host_path, 'checkpolicy'),
+                        '-C', '-M', '-c', args.policy_vers,
+                        '-o', raw_cil_file, args.input_policy_conf]
+    # Using shell=True to setup args.checkpolicy_env variables.
+    run_host_command(' '.join(checkpolicy_cmd), shell=True)
+    file_utils.filter_out([args.reqd_mask], raw_cil_file)
+
+    # Builds the output CIL by versioning the above raw CIL.
+    output_file = args.output_cil
+    if output_file is None:
+        output_file = input_file_name + '.cil'
+    file_utils.make_parent_dirs(output_file)
+
+    run_host_command([os.path.join(args.android_host_path, 'version_policy'),
+                      '-b', args.base_policy, '-t', raw_cil_file,
+                      '-n', args.treble_sepolicy_vers, '-o', output_file])
+    if args.filter_out_files:
+        file_utils.filter_out(args.filter_out_files, output_file)
+
+    # Tests that the output file can be merged with the given CILs.
+    if args.dependent_cils:
+        merge_cmd = [os.path.join(args.android_host_path, 'secilc'),
+                     '-m', '-M', 'true', '-G', '-N', '-c', args.policy_vers]
+        merge_cmd += args.dependent_cils      # the give CILs to merge
+        merge_cmd += [output_file, '-o', '/dev/null', '-f', '/dev/null']
+        run_host_command(merge_cmd)
+
+
+def setup_build_cil(subparsers):
+    """Sets up command args for 'build_cil' command."""
+
+    # Required arguments.
+    parser = subparsers.add_parser('build_cil', help='build CIL files')
+    parser.add_argument('-i', '--input_policy_conf', required=True,
+                        help='source policy.conf')
+    parser.add_argument('-m', '--reqd_mask', required=True,
+                        help='the bare minimum policy.conf to use checkpolicy')
+    parser.add_argument('-b', '--base_policy', required=True,
+                        help='base policy for versioning')
+    parser.add_argument('-t', '--treble_sepolicy_vers', required=True,
+                        help='the version number to use for Treble-OTA')
+    parser.add_argument('-p', '--policy_vers', required=True,
+                        help='SELinux policy version')
+
+    # Optional arguments.
+    parser.add_argument('-c', '--checkpolicy_env',
+                        help='environment variables passed to checkpolicy')
+    parser.add_argument('-f', '--filter_out_files', nargs='+',
+                        help='the pattern files to filter out the output cil')
+    parser.add_argument('-d', '--dependent_cils', nargs='+',
+                        help=('check the output file can be merged with '
+                              'the dependent cil files'))
+    parser.add_argument('-o', '--output_cil', help='the output cil file')
+
+    # The function that performs the actual works.
+    parser.set_defaults(func=do_build_cil)
+
+
+def run(argv):
+    """Sets up command parser and execuates sub-command."""
+    parser = argparse.ArgumentParser()
+
+    # Adds top-level arguments.
+    parser.add_argument('-a', '--android_host_path', default='',
+                        help='a path to host out executables')
+
+    # Adds subparsers for each COMMAND.
+    subparsers = parser.add_subparsers(title='COMMAND')
+    for command in _SUPPORTED_COMMANDS:
+        globals()['setup_' + command](subparsers)
+
+    args = parser.parse_args(argv[1:])
+    args.func(args)
+
+
+if __name__ == '__main__':
+    run(sys.argv)
diff --git a/build/file_utils.py b/build/file_utils.py
new file mode 100644
index 000000000..1559a9b9d
--- /dev/null
+++ b/build/file_utils.py
@@ -0,0 +1,49 @@
+# Copyright 2018 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""File-related utilities."""
+
+
+import os
+import shutil
+import tempfile
+
+
+def make_parent_dirs(file_path):
+    """Creates parent directories for the file_path."""
+    if os.path.exists(file_path):
+        return
+
+    parent_dir = os.path.dirname(file_path)
+    if parent_dir and not os.path.exists(parent_dir):
+        os.makedirs(parent_dir)
+
+
+def filter_out(pattern_files, input_file):
+    """"Removes lines in input_file that match any line in pattern_files."""
+
+    # Prepares patterns.
+    patterns = []
+    for f in pattern_files:
+        patterns.extend(open(f).readlines())
+
+    # Copy lines that are not in the pattern.
+    tmp_output = tempfile.NamedTemporaryFile()
+    with open(input_file, 'r') as in_file:
+        tmp_output.writelines(line for line in in_file.readlines()
+                              if line not in patterns)
+        tmp_output.flush()
+
+    # Replaces the input_file.
+    shutil.copyfile(tmp_output.name, input_file)
-- 
GitLab