1#!/usr/bin/env python
2# coding: utf-8
3
4"""
5Copyright (c) 2024 Huawei Device Co., Ltd.
6Licensed under the Apache License, Version 2.0 (the "License");
7you may not use this file except in compliance with the License.
8You may obtain a copy of the License at
9
10    http://www.apache.org/licenses/LICENSE-2.0
11
12Unless required by applicable law or agreed to in writing, software
13distributed under the License is distributed on an "AS IS" BASIS,
14WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15See the License for the specific language governing permissions and
16limitations under the License.
17
18"""
19
20import os
21import argparse
22
23
24def parse_args():
25    parser = argparse.ArgumentParser()
26    parser.add_argument("--dst-dir", help="the output dest path", required=True)
27    parser.add_argument("--source-root-dir", help="project root path", required=True)
28    parser.add_argument(
29        "--policy-dir-list", help="policy dirs need to be included", required=True
30    )
31    parser.add_argument(
32        "--components", help="system or vendor or default", required=True
33    )
34    return parser.parse_args()
35
36
37def prepare_build_path(dir_list, root_dir, build_dir_list):
38    build_ignore_cfg_list = [
39        "base/security/selinux_adapter/sepolicy/base",
40        "base/security/selinux_adapter/sepolicy/ohos_policy",
41    ]
42    build_ignore_cfg_list += dir_list.split(":")
43
44    for i in build_ignore_cfg_list:
45        if not i or i == "default":
46            continue
47        path = os.path.join(root_dir, i)
48        if os.path.exists(path):
49            build_dir_list.append(path)
50        else:
51            raise Exception(f"following path not exists!! {path}")
52
53
54def check_ignore_file(ignore_file, err_msg):
55    """
56    Check the format of ignore_cfg file.
57    :param ignore_file: ignore_file
58    :return:
59    """
60    err = ""
61    lines = []
62    with open(ignore_file, "rb") as fp:
63        lines = fp.readlines()
64    if len(lines) == 0:
65        return
66    last_line = lines[-1]
67    if b"\n" not in last_line:
68        err = "".join((ignore_file, " : need an empty line at end "))
69    for line in lines:
70        strip_line = line.strip()
71        if not strip_line:
72            continue
73        if line.endswith(b"\r\n") or line.endswith(b"\r"):
74            err = "".join((ignore_file, " : must be unix format"))
75            break
76        if strip_line in [b"/", b"/*"]:
77            err = "".join((ignore_file, " : line must not be only / or /*"))
78            break
79        if not (strip_line.endswith(b"/") or strip_line.endswith(b"/*")):
80            err = "".join((ignore_file, " : line must end with / or /*"))
81            break
82    if err:
83        err_msg.append(err)
84
85
86def traverse_folder_in_type(search_dir_list, file_suffix):
87    """
88    for special folder search_dir, find all files endwith file_suffix.
89    :param search_dir: path to search
90    :param file_suffix: postfix of file name
91    :return: file list
92    """
93    err_msg = []
94    ignore_cfg_file_list = []
95    for item in search_dir_list:
96        for root, _, files in sorted(os.walk(item)):
97            filtered_files = [f for f in files if f == file_suffix]
98            for each_file in filtered_files:
99                file_list_path = os.path.join(root, each_file)
100                check_ignore_file(file_list_path, err_msg)
101                ignore_cfg_file_list.append(file_list_path)
102    if err_msg:
103        err_str = "\n{}".format("\n".join(err_msg))
104        raise Exception(err_str)
105    ignore_cfg_file_list.sort()
106    return ignore_cfg_file_list
107
108
109def check_and_add_line(lines, line):
110    line = line.strip()
111    if not line:
112        return
113    for existing_line in lines[:]:
114        if len(line) >= len(existing_line) and line.startswith(existing_line):
115            return
116        elif len(line) < len(existing_line) and existing_line.startswith(line):
117            lines.remove(existing_line)
118            lines.append(line)
119            return
120    lines.append(line)
121
122
123def get_path_lines(ignore_cfg_list):
124    lines = []
125    for ignore_cfg in ignore_cfg_list:
126        with open(ignore_cfg, "r") as src_file:
127            for line in src_file:
128                check_and_add_line(lines, line)
129    return lines
130
131
132def filter_and_write_to_dst(lines, dst_file):
133    fd = os.open(dst_file, os.O_WRONLY | os.O_CREAT, 0o664)
134    with os.fdopen(fd, "w") as dst_f:
135        dst_f.truncate(0)
136        for line in lines:
137            if line and not line.startswith("#"):
138                line = "".join([line, "\n"])
139                dst_f.write(line)
140
141
142def combine_ignore_cfg(ignore_cfg_list, combined_ignore_cfg):
143    lines = get_path_lines(ignore_cfg_list)
144    filter_and_write_to_dst(lines, combined_ignore_cfg)
145
146
147def traverse_folder_in_dir_name(search_dir, folder_suffix):
148    folder_list = []
149    for root, dirs, _ in sorted(os.walk(search_dir)):
150        for dir_i in dirs:
151            if dir_i == folder_suffix:
152                folder_list.append(os.path.join(root, dir_i))
153    return folder_list
154
155
156def build_ignore_cfg(output_path, folder_list):
157    combined_ignore_cfg = os.path.join(output_path, "ignore_cfg")
158    ignore_cfg_list = traverse_folder_in_type(folder_list, "ignore_cfg")
159    combine_ignore_cfg(ignore_cfg_list, combined_ignore_cfg)
160
161
162def main(args):
163    output_path = args.dst_dir
164    print("output_path: ", output_path)
165    policy_path = []
166    prepare_build_path(args.policy_dir_list, args.source_root_dir, policy_path)
167    print("policy_path: ", policy_path)
168
169    folder_list = []
170    for item in policy_path:
171        public_ = traverse_folder_in_dir_name(item, "public")
172        if args.components == "system":
173            system_policy = traverse_folder_in_dir_name(item, "system")
174            folder_list += public_ + system_policy
175        elif args.components == "vendor":
176            vendor_policy = traverse_folder_in_dir_name(item, "vendor")
177            folder_list += public_ + vendor_policy
178        else:
179            system_policy = traverse_folder_in_dir_name(item, "system")
180            vendor_policy = traverse_folder_in_dir_name(item, "vendor")
181            folder_list += public_ + system_policy + vendor_policy
182
183    build_ignore_cfg(output_path, folder_list)
184    print("build_ignore_cfg done")
185
186
187if __name__ == "__main__":
188    input_args = parse_args()
189    main(input_args)
190