xref: /aosp_15_r20/tools/treble/cuttlefish/build_chd_utils.py (revision 105f628577ac4ba0e277a494fbb614ed8c12a994)
1*105f6285SAndroid Build Coastguard Worker#!/usr/bin/python3
2*105f6285SAndroid Build Coastguard Worker#
3*105f6285SAndroid Build Coastguard Worker# Copyright (C) 2023 The Android Open Source Project
4*105f6285SAndroid Build Coastguard Worker#
5*105f6285SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); you may not
6*105f6285SAndroid Build Coastguard Worker# use this file except in compliance with the License. You may obtain a copy of
7*105f6285SAndroid Build Coastguard Worker# the License at
8*105f6285SAndroid Build Coastguard Worker#
9*105f6285SAndroid Build Coastguard Worker#      http://www.apache.org/licenses/LICENSE-2.0
10*105f6285SAndroid Build Coastguard Worker#
11*105f6285SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
12*105f6285SAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13*105f6285SAndroid Build Coastguard Worker# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14*105f6285SAndroid Build Coastguard Worker# License for the specific language governing permissions and limitations under
15*105f6285SAndroid Build Coastguard Worker# the License.
16*105f6285SAndroid Build Coastguard Worker
17*105f6285SAndroid Build Coastguard Workerimport fnmatch
18*105f6285SAndroid Build Coastguard Workerimport glob
19*105f6285SAndroid Build Coastguard Workerimport os
20*105f6285SAndroid Build Coastguard Workerimport shutil
21*105f6285SAndroid Build Coastguard Workerimport subprocess
22*105f6285SAndroid Build Coastguard Workerimport tempfile
23*105f6285SAndroid Build Coastguard Workerfrom typing import List, Tuple
24*105f6285SAndroid Build Coastguard Workerimport zipfile
25*105f6285SAndroid Build Coastguard Worker
26*105f6285SAndroid Build Coastguard Worker
27*105f6285SAndroid Build Coastguard Workerdef unzip_otatools(
28*105f6285SAndroid Build Coastguard Worker    otatools_zip_path: str, output_dir: str, patterns: List[str] = None
29*105f6285SAndroid Build Coastguard Worker) -> None:
30*105f6285SAndroid Build Coastguard Worker  """Unzip otatools to a directory and set the permissions for execution.
31*105f6285SAndroid Build Coastguard Worker
32*105f6285SAndroid Build Coastguard Worker  Args:
33*105f6285SAndroid Build Coastguard Worker    otatools_zip_path: The path to otatools zip archive.
34*105f6285SAndroid Build Coastguard Worker    output_dir: The root directory of the unzip output.
35*105f6285SAndroid Build Coastguard Worker    patterns: If provided, only extract files matching any of these patterns
36*105f6285SAndroid Build Coastguard Worker              from the otatools zip archive; otherwise, extract all files.
37*105f6285SAndroid Build Coastguard Worker  """
38*105f6285SAndroid Build Coastguard Worker  with zipfile.ZipFile(otatools_zip_path, 'r') as zf:
39*105f6285SAndroid Build Coastguard Worker    if patterns is None:
40*105f6285SAndroid Build Coastguard Worker      zf.extractall(path=output_dir)
41*105f6285SAndroid Build Coastguard Worker    else:
42*105f6285SAndroid Build Coastguard Worker      for file in zf.namelist():
43*105f6285SAndroid Build Coastguard Worker        if any(fnmatch.fnmatch(file, p) for p in patterns):
44*105f6285SAndroid Build Coastguard Worker          zf.extract(file, output_dir)
45*105f6285SAndroid Build Coastguard Worker
46*105f6285SAndroid Build Coastguard Worker  for f in glob.glob(os.path.join(output_dir, 'bin', '*')):
47*105f6285SAndroid Build Coastguard Worker    os.chmod(f, 0o777)
48*105f6285SAndroid Build Coastguard Worker
49*105f6285SAndroid Build Coastguard Worker
50*105f6285SAndroid Build Coastguard Workerdef _parse_copy_file_pair(copy_file_pair: str) -> Tuple[str, str]:
51*105f6285SAndroid Build Coastguard Worker  """Convert a string to a source path and a destination path.
52*105f6285SAndroid Build Coastguard Worker
53*105f6285SAndroid Build Coastguard Worker  Args:
54*105f6285SAndroid Build Coastguard Worker    copy_file_pair: A string in the format of <src glob pattern>:<dst path>.
55*105f6285SAndroid Build Coastguard Worker
56*105f6285SAndroid Build Coastguard Worker  Returns:
57*105f6285SAndroid Build Coastguard Worker    The source path and the destination path.
58*105f6285SAndroid Build Coastguard Worker
59*105f6285SAndroid Build Coastguard Worker  Raises:
60*105f6285SAndroid Build Coastguard Worker    ValueError if the input string is in a wrong format.
61*105f6285SAndroid Build Coastguard Worker  """
62*105f6285SAndroid Build Coastguard Worker  split_pair = copy_file_pair.split(':', 1)
63*105f6285SAndroid Build Coastguard Worker  if len(split_pair) != 2:
64*105f6285SAndroid Build Coastguard Worker    raise ValueError(f'{copy_file_pair} is not a <src>:<dst> pair.')
65*105f6285SAndroid Build Coastguard Worker  src_list = glob.glob(split_pair[0])
66*105f6285SAndroid Build Coastguard Worker  if len(src_list) != 1:
67*105f6285SAndroid Build Coastguard Worker    raise ValueError(f'{copy_file_pair} has more than one matched src files: '
68*105f6285SAndroid Build Coastguard Worker                     f'{" ".join(src_list)}.')
69*105f6285SAndroid Build Coastguard Worker  return src_list[0], split_pair[1]
70*105f6285SAndroid Build Coastguard Worker
71*105f6285SAndroid Build Coastguard Worker
72*105f6285SAndroid Build Coastguard Workerdef copy_files(copy_files_list: List[str], output_dir: str) -> None:
73*105f6285SAndroid Build Coastguard Worker  """Copy files to the output directory.
74*105f6285SAndroid Build Coastguard Worker
75*105f6285SAndroid Build Coastguard Worker  Args:
76*105f6285SAndroid Build Coastguard Worker    copy_files_list: A list of copy file pairs, where a pair defines the src
77*105f6285SAndroid Build Coastguard Worker                     glob pattern and the dst path.
78*105f6285SAndroid Build Coastguard Worker    output_dir: The root directory of the copy dst.
79*105f6285SAndroid Build Coastguard Worker
80*105f6285SAndroid Build Coastguard Worker  Raises:
81*105f6285SAndroid Build Coastguard Worker    FileExistsError if the dst file already exists.
82*105f6285SAndroid Build Coastguard Worker  """
83*105f6285SAndroid Build Coastguard Worker  for pair in copy_files_list:
84*105f6285SAndroid Build Coastguard Worker    src, dst = _parse_copy_file_pair(pair)
85*105f6285SAndroid Build Coastguard Worker    # this line does not change dst if dst is absolute.
86*105f6285SAndroid Build Coastguard Worker    dst = os.path.join(output_dir, dst)
87*105f6285SAndroid Build Coastguard Worker    os.makedirs(os.path.dirname(dst), exist_ok=True)
88*105f6285SAndroid Build Coastguard Worker    print(f'Copying {src} to {dst}')
89*105f6285SAndroid Build Coastguard Worker    if os.path.exists(dst):
90*105f6285SAndroid Build Coastguard Worker      raise FileExistsError(dst)
91*105f6285SAndroid Build Coastguard Worker    shutil.copyfile(src, dst)
92*105f6285SAndroid Build Coastguard Worker
93*105f6285SAndroid Build Coastguard Worker
94*105f6285SAndroid Build Coastguard Workerdef _extract_cil_files(target_files_zip: str, output_dir: str) -> None:
95*105f6285SAndroid Build Coastguard Worker  """Extract sepolicy cil files from a target files zip archive.
96*105f6285SAndroid Build Coastguard Worker
97*105f6285SAndroid Build Coastguard Worker  Args:
98*105f6285SAndroid Build Coastguard Worker    target_files_zip: A path to the target files zip archive.
99*105f6285SAndroid Build Coastguard Worker    output_dir: The directory of extracted cil files.
100*105f6285SAndroid Build Coastguard Worker  """
101*105f6285SAndroid Build Coastguard Worker  with zipfile.ZipFile(target_files_zip, 'r') as zf:
102*105f6285SAndroid Build Coastguard Worker    cil_files = [name for name in zf.namelist() if name.endswith('.cil')]
103*105f6285SAndroid Build Coastguard Worker    for f in cil_files:
104*105f6285SAndroid Build Coastguard Worker      zf.extract(f, output_dir)
105*105f6285SAndroid Build Coastguard Worker
106*105f6285SAndroid Build Coastguard Worker
107*105f6285SAndroid Build Coastguard Workerdef _get_sepolicy_plat_version(target_files_zip: str) -> str:
108*105f6285SAndroid Build Coastguard Worker  """Get the platform sepolicy version from a vendor target files zip archive.
109*105f6285SAndroid Build Coastguard Worker
110*105f6285SAndroid Build Coastguard Worker  Args:
111*105f6285SAndroid Build Coastguard Worker    target_files_zip: A path to the target files zip archive.
112*105f6285SAndroid Build Coastguard Worker
113*105f6285SAndroid Build Coastguard Worker  Returns:
114*105f6285SAndroid Build Coastguard Worker    A string that represents the platform sepolicy version.
115*105f6285SAndroid Build Coastguard Worker  """
116*105f6285SAndroid Build Coastguard Worker  with zipfile.ZipFile(target_files_zip, 'r') as zf:
117*105f6285SAndroid Build Coastguard Worker    try:
118*105f6285SAndroid Build Coastguard Worker      with zf.open('VENDOR/etc/selinux/plat_sepolicy_vers.txt') as ver_file:
119*105f6285SAndroid Build Coastguard Worker        return ver_file.readline().decode('utf-8').strip('\n')
120*105f6285SAndroid Build Coastguard Worker    except Exception as error:
121*105f6285SAndroid Build Coastguard Worker      print(f'cannot get platform sepolicy version from {target_files_zip}')
122*105f6285SAndroid Build Coastguard Worker      raise
123*105f6285SAndroid Build Coastguard Worker
124*105f6285SAndroid Build Coastguard Worker
125*105f6285SAndroid Build Coastguard Workerdef merge_chd_sepolicy(
126*105f6285SAndroid Build Coastguard Worker    framework_target_files_zip: str, vendor_target_files_zip: str,
127*105f6285SAndroid Build Coastguard Worker    otatools_dir: str, output_dir: str
128*105f6285SAndroid Build Coastguard Worker) -> str:
129*105f6285SAndroid Build Coastguard Worker  """Merge the sepolicy files for CHD.
130*105f6285SAndroid Build Coastguard Worker
131*105f6285SAndroid Build Coastguard Worker  This function takes both the system and vendor sepolicy files from
132*105f6285SAndroid Build Coastguard Worker  framework_target_files_zip, and merges them with the vendor sepolicy from
133*105f6285SAndroid Build Coastguard Worker  vendor_target_files_zip to generate `chd_merged_sepolicy`.
134*105f6285SAndroid Build Coastguard Worker
135*105f6285SAndroid Build Coastguard Worker  In certain instances, a device may possess components that do not put their
136*105f6285SAndroid Build Coastguard Worker  sepolicy rules within the same partition as the components themselves. This
137*105f6285SAndroid Build Coastguard Worker  results in a problem that CHD is missing necessary vendor sepolicy rules
138*105f6285SAndroid Build Coastguard Worker  after the replacement of the device's vendor image with Cuttlefish. As a
139*105f6285SAndroid Build Coastguard Worker  short term solution to resolve this issue, the vendor sepolicy files from
140*105f6285SAndroid Build Coastguard Worker  framework_target_files_zip are additionally merged.
141*105f6285SAndroid Build Coastguard Worker
142*105f6285SAndroid Build Coastguard Worker  Args:
143*105f6285SAndroid Build Coastguard Worker    framework_target_files_zip: A path to the framework target files zip
144*105f6285SAndroid Build Coastguard Worker                                archive.
145*105f6285SAndroid Build Coastguard Worker    vendor_target_files_zip: A path to the vendor target files zip archive.
146*105f6285SAndroid Build Coastguard Worker    otatools_dir: The otatools directory.
147*105f6285SAndroid Build Coastguard Worker    output_dir: The output directory for generating a merged sepolicy file.
148*105f6285SAndroid Build Coastguard Worker
149*105f6285SAndroid Build Coastguard Worker  Returns:
150*105f6285SAndroid Build Coastguard Worker    The path to the CHD merged sepolicy file.
151*105f6285SAndroid Build Coastguard Worker
152*105f6285SAndroid Build Coastguard Worker  Raises:
153*105f6285SAndroid Build Coastguard Worker    FileNotFoundError if any mandatory sepolicy file is missing.
154*105f6285SAndroid Build Coastguard Worker  """
155*105f6285SAndroid Build Coastguard Worker  with tempfile.TemporaryDirectory(prefix='framework_',
156*105f6285SAndroid Build Coastguard Worker                                   dir=output_dir) as framework_dir, \
157*105f6285SAndroid Build Coastguard Worker       tempfile.TemporaryDirectory(prefix='vendor_',
158*105f6285SAndroid Build Coastguard Worker                                   dir=output_dir) as vendor_dir:
159*105f6285SAndroid Build Coastguard Worker    merged_policy = os.path.join(output_dir, 'chd_merged_sepolicy')
160*105f6285SAndroid Build Coastguard Worker    _extract_cil_files(framework_target_files_zip, framework_dir)
161*105f6285SAndroid Build Coastguard Worker    _extract_cil_files(vendor_target_files_zip, vendor_dir)
162*105f6285SAndroid Build Coastguard Worker    plat_ver = _get_sepolicy_plat_version(vendor_target_files_zip)
163*105f6285SAndroid Build Coastguard Worker    print(f'Merging sepolicy files from {framework_target_files_zip} and '
164*105f6285SAndroid Build Coastguard Worker          f'{vendor_target_files_zip}: platform version {plat_ver}.')
165*105f6285SAndroid Build Coastguard Worker
166*105f6285SAndroid Build Coastguard Worker    # (partition, path, required)
167*105f6285SAndroid Build Coastguard Worker    system_policy_files = (
168*105f6285SAndroid Build Coastguard Worker        ('system', 'etc/selinux/plat_sepolicy.cil', True),
169*105f6285SAndroid Build Coastguard Worker        ('system', f'etc/selinux/mapping/{plat_ver}.cil', True),
170*105f6285SAndroid Build Coastguard Worker        ('system', f'etc/selinux/mapping/{plat_ver}.compat.cil', False),
171*105f6285SAndroid Build Coastguard Worker        ('system_ext', 'etc/selinux/system_ext_sepolicy.cil', False),
172*105f6285SAndroid Build Coastguard Worker        ('system_ext', f'etc/selinux/mapping/{plat_ver}.cil', False),
173*105f6285SAndroid Build Coastguard Worker        ('system_ext', f'etc/selinux/mapping/{plat_ver}.compat.cil', False),
174*105f6285SAndroid Build Coastguard Worker        ('product', 'etc/selinux/product_sepolicy.cil', False),
175*105f6285SAndroid Build Coastguard Worker        ('product', f'etc/selinux/mapping/{plat_ver}.cil', False),
176*105f6285SAndroid Build Coastguard Worker    )
177*105f6285SAndroid Build Coastguard Worker    vendor_policy_files = (
178*105f6285SAndroid Build Coastguard Worker        ('vendor', 'etc/selinux/vendor_sepolicy.cil', True),
179*105f6285SAndroid Build Coastguard Worker        ('vendor', 'etc/selinux/plat_pub_versioned.cil', True),
180*105f6285SAndroid Build Coastguard Worker        ('odm', 'etc/selinux/odm_sepolicy.cil', False),
181*105f6285SAndroid Build Coastguard Worker    )
182*105f6285SAndroid Build Coastguard Worker
183*105f6285SAndroid Build Coastguard Worker    # merge system and vendor policy files from framework_dir with vendor
184*105f6285SAndroid Build Coastguard Worker    # policy files from vendor_dir.
185*105f6285SAndroid Build Coastguard Worker    merge_cmd = [
186*105f6285SAndroid Build Coastguard Worker        os.path.join(otatools_dir, 'bin', 'secilc'),
187*105f6285SAndroid Build Coastguard Worker        '-m', '-M', 'true', '-G', '-N',
188*105f6285SAndroid Build Coastguard Worker        '-o', merged_policy,
189*105f6285SAndroid Build Coastguard Worker        '-f', '/dev/null'
190*105f6285SAndroid Build Coastguard Worker    ]
191*105f6285SAndroid Build Coastguard Worker    policy_dirs_and_files = (
192*105f6285SAndroid Build Coastguard Worker        # For the normal case, we should merge the system policies from
193*105f6285SAndroid Build Coastguard Worker        # framework_dir with the vendor policies from vendor_dir.
194*105f6285SAndroid Build Coastguard Worker        (framework_dir, system_policy_files),
195*105f6285SAndroid Build Coastguard Worker        (vendor_dir, vendor_policy_files),
196*105f6285SAndroid Build Coastguard Worker
197*105f6285SAndroid Build Coastguard Worker        # Additionally merging the vendor policies from framework_dir in order
198*105f6285SAndroid Build Coastguard Worker        # to fix the policy misplaced issue.
199*105f6285SAndroid Build Coastguard Worker        # TODO (b/315474132): remove this when all the policies from
200*105f6285SAndroid Build Coastguard Worker        #                     framework_dir are moved to the right partition.
201*105f6285SAndroid Build Coastguard Worker        (framework_dir, vendor_policy_files),
202*105f6285SAndroid Build Coastguard Worker    )
203*105f6285SAndroid Build Coastguard Worker    for policy_dir, policy_files in policy_dirs_and_files:
204*105f6285SAndroid Build Coastguard Worker      for partition, path, required in policy_files:
205*105f6285SAndroid Build Coastguard Worker        policy_file = os.path.join(policy_dir, partition.upper(), path)
206*105f6285SAndroid Build Coastguard Worker        if os.path.exists(policy_file):
207*105f6285SAndroid Build Coastguard Worker          merge_cmd.append(policy_file)
208*105f6285SAndroid Build Coastguard Worker        elif required:
209*105f6285SAndroid Build Coastguard Worker          raise FileNotFoundError(f'{policy_file} does not exist')
210*105f6285SAndroid Build Coastguard Worker
211*105f6285SAndroid Build Coastguard Worker    subprocess.run(merge_cmd, check=True)
212*105f6285SAndroid Build Coastguard Worker    return merged_policy
213