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 bullhead/taimen
Change-Id: Ie0ae4fc5256a550c72954cde5d5dd213a22d159a
diff --git a/build/Android.bp b/build/Android.bp
new file mode 100644
index 0000000..d3f1fc3
--- /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 0000000..ff2ff07
--- /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 0000000..1559a9b
--- /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)