Skip to content

Commit c205462

Browse files
committed
Update
1 parent 121037c commit c205462

4 files changed

Lines changed: 337 additions & 361 deletions

File tree

.github/module/termux/py/contextpatch.py

Lines changed: 132 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -4,176 +4,162 @@
44
import os
55
import re
66
import sys
7+
from typing import List, Tuple
78

8-
# Khai báo quy tắc gán context cứng (hỗ trợ cả đường dẫn tuyệt đối lẫn tên file/thư mục đặc biệt)
9-
FIX_PERMISSIONS = {
10-
"lost+found": ["u:object_r:rootfs:s0"],
11-
"system/bin/init": ["u:object_r:init_exec:s0"],
12-
"system/bin/idmap": ["u:object_r:idmap_exec:s0"],
13-
"system/bin/fsck": ["u:object_r:fsck_exec:s0"],
14-
"system/bin/e2fsck": ["u:object_r:fsck_exec:s0"],
15-
"system/bin/logcat": ["u:object_r:logcat_exec:s0"],
16-
"vendor/bin/hw/android.hardware.wifi@1.0": ["u:object_r:hal_wifi_default_exec:s0"]
9+
ContextEntry = Tuple[str, List[str]]
10+
11+
fix_permission = {
12+
"/vendor/bin/hw/android.hardware.wifi@1.0": ["u:object_r:hal_wifi_default_exec:s0"]
1713
}
1814

19-
def escape_regex_path(path: str) -> str:
20-
"""
21-
Escape các ký tự đặc biệt Regex (+, .) cho file_contexts của Android,
22-
giữ nguyên dấu / và @ để make_ext4fs đọc chuẩn.
23-
"""
24-
# Escape dấu '+' chưa được escape trước đó
25-
path_escaped = re.sub(r'(?<!\\)\+', r'\+', path)
26-
return path_escaped
27-
28-
def scan_context(file_path: str) -> tuple:
29-
"""Đọc file context, lưu trữ dưới dạng dict và bảo toàn thứ tự dòng gốc."""
30-
context = {}
31-
original_order = []
32-
33-
with open(file_path, "r", encoding='utf-8') as f:
34-
for line in f:
15+
16+
def str_to_selinux(s: str) -> str:
17+
# Escape dấu . cho đúng định dạng regex file_contexts, giữ nguyên / và -
18+
escaped = re.escape(s).replace(r"\-", "-").replace(r"\/", "/")
19+
return escaped.replace(r"\@", "@")
20+
21+
22+
def scan_context(file_path: str) -> List[ContextEntry]:
23+
"""Đọc file gốc và GIỮ NGUYÊN thứ tự xuất hiện của từng dòng."""
24+
entries: List[ContextEntry] = []
25+
with open(file_path, "r", encoding="utf-8", errors="ignore") as fp:
26+
for line in fp:
3527
line_str = line.strip()
36-
if not line_str or line_str.startswith('#'):
28+
if not line_str or line_str.startswith("#"):
3729
continue
38-
30+
3931
parts = line_str.split()
4032
if not parts:
4133
continue
42-
43-
filepath = parts[0]
44-
# Chuẩn hóa đường dẫn bỏ escape @ nếu có
45-
filepath_clean = filepath.replace(r'\@', '@')
46-
47-
context[filepath_clean] = parts[1:]
48-
original_order.append(filepath_clean)
49-
50-
return context, original_order
34+
35+
filepath, *other = parts
36+
entries.append((filepath, other))
37+
38+
return entries
39+
5140

5241
def scan_dir(folder: str) -> list:
53-
"""
54-
Quét thư mục thực tế và chuyển đổi sang định dạng path chuẩn của Android SELinux.
55-
Hỗ trợ Dynamic Partitions (system, vendor, product, system_ext, cust...).
56-
"""
57-
folder_abs = os.path.abspath(folder).replace('\\', '/')
58-
part_name = os.path.basename(os.path.normpath(folder))
59-
60-
allfiles = [
61-
'/',
62-
f'/{part_name}',
63-
f'/{part_name}/',
64-
f'/{part_name}/lost+found'
65-
]
66-
42+
folder = os.path.abspath(folder)
43+
part_name = os.path.basename(folder.rstrip(os.sep)) or os.path.basename(folder)
44+
allfiles = ["/", f"/{part_name}/lost+found", f"/{part_name}", f"/{part_name}/"]
45+
6746
for root, dirs, files in os.walk(folder, topdown=True):
68-
root_clean = os.path.abspath(root).replace('\\', '/')
69-
70-
for dir_name in dirs:
71-
full_path = f"{root_clean}/{dir_name}"
72-
target_path = full_path.replace(folder_abs, '/' + part_name)
73-
allfiles.append(target_path)
74-
75-
for file_name in files:
76-
full_path = f"{root_clean}/{file_name}"
77-
target_path = full_path.replace(folder_abs, '/' + part_name)
78-
allfiles.append(target_path)
79-
47+
rel_root = os.path.relpath(root, folder)
48+
rel_root = "" if rel_root == "." else rel_root
49+
50+
for dir_ in dirs:
51+
rel_path = os.path.join(rel_root, dir_).replace("\\", "/")
52+
allfiles.append(f"/{part_name}/{rel_path}".replace("//", "/"))
53+
54+
for file_ in files:
55+
rel_path = os.path.join(rel_root, file_).replace("\\", "/")
56+
allfiles.append(f"/{part_name}/{rel_path}".replace("//", "/"))
57+
8058
return sorted(set(allfiles), key=allfiles.index)
8159

82-
def context_patch(fs_file: dict, filename_list: list) -> tuple:
83-
"""Vá context thông minh dựa trên luật cứng, quy tắc tương thích hoặc thừa kế thư mục cha."""
84-
new_fs = {}
85-
added_keys = []
60+
61+
def _default_permission(entries: List[ContextEntry]) -> list:
62+
if entries:
63+
return entries[0][1]
64+
return ["u:object_r:system_file:s0"]
65+
66+
67+
def find_insert_index(entries: List[ContextEntry], raw_path: str) -> Tuple[int, list]:
68+
"""
69+
Tìm vị trí xuất hiện cuối cùng của thư mục cha trong file gốc
70+
để chèn item mới vào ngay bên cạnh/dưới nó. Đồng thời trả về permission kế thừa.
71+
"""
72+
tmp_path = os.path.dirname(raw_path)
73+
74+
while tmp_path and tmp_path != "/":
75+
tmp_selinux = str_to_selinux(tmp_path)
76+
last_idx = None
77+
found_perm = None
78+
79+
for idx, (path, perm) in enumerate(entries):
80+
# Khớp với đường dẫn gốc hoặc dạng đã escape
81+
if path == tmp_path or path == tmp_selinux or path.startswith(tmp_selinux + "/"):
82+
last_idx = idx
83+
found_perm = perm
84+
85+
if last_idx is not None:
86+
return last_idx + 1, found_perm
87+
88+
tmp_path = os.path.dirname(tmp_path)
89+
90+
# Nếu không tìm thấy cha, chèn vào cuối file
91+
return len(entries), None
92+
93+
94+
def context_patch(fs_entries: List[ContextEntry], filename: list) -> Tuple[List[ContextEntry], int]:
95+
entries = list(fs_entries)
8696

87-
# Context mặc định cho file/folder không xác định
88-
default_permission = ['u:object_r:system_file:s0']
89-
if fs_file:
90-
default_permission = fs_file.get(next(iter(fs_file)), default_permission)
91-
92-
for path in filename_list:
93-
actual_path = path if path.isprintable() else ''.join(c if c.isprintable() else '*' for c in path)
94-
95-
# 1. Khớp chính xác với file_contexts gốc
96-
if actual_path in fs_file:
97-
new_fs[actual_path] = fs_file[actual_path]
98-
continue
99-
100-
if actual_path in new_fs:
97+
# Tập hợp các đường dẫn đã tồn tại để tránh chèn trùng
98+
existing_paths = {path for path, _ in entries}
99+
permission_d = _default_permission(entries)
100+
101+
seen_new = set()
102+
new_add = 0
103+
104+
for i in filename:
105+
if not i:
101106
continue
102107

103-
permission = None
104-
clean_path = actual_path.lstrip('/')
105-
base_name = os.path.basename(actual_path)
108+
raw_path = i
109+
if not raw_path.isprintable():
110+
raw_path = "".join(c if c.isprintable() else "*" for c in raw_path)
111+
112+
selinux_path = str_to_selinux(raw_path)
113+
114+
# Bỏ qua nếu dòng đã tồn tại trong file gốc hoặc đã được thêm trước đó
115+
if raw_path in existing_paths or selinux_path in existing_paths or selinux_path in seen_new:
116+
continue
106117

107-
# 2. Khớp theo FIX_PERMISSIONS (Tên file/thư mục hoặc đuôi path)
108-
if clean_path in FIX_PERMISSIONS:
109-
permission = FIX_PERMISSIONS[clean_path]
110-
elif base_name in FIX_PERMISSIONS:
111-
permission = FIX_PERMISSIONS[base_name]
118+
# Xử lý quy tắc đè thủ công trong fix_permission
119+
if raw_path in fix_permission:
120+
permission = fix_permission[raw_path]
121+
insert_at, _ = find_insert_index(entries, raw_path)
122+
elif selinux_path in fix_permission:
123+
permission = fix_permission[selinux_path]
124+
insert_at, _ = find_insert_index(entries, raw_path)
112125
else:
113-
# 3. Thừa kế context từ thư mục cha gần nhất
114-
tmp_path = os.path.dirname(actual_path)
115-
while tmp_path:
116-
if tmp_path in fs_file:
117-
permission = fs_file[tmp_path]
118-
break
119-
if tmp_path in new_fs:
120-
permission = new_fs[tmp_path]
121-
break
122-
if tmp_path in ('/', ''):
123-
break
124-
125-
prev_path = tmp_path
126-
tmp_path = os.path.dirname(tmp_path)
127-
if tmp_path == prev_path:
128-
break
129-
130-
# Nếu vẫn chưa tìm thấy context -> Dùng default
131-
if not permission:
132-
permission = default_permission
133-
134-
print(f"ADD [{actual_path} : {' '.join(permission)}]")
135-
new_fs[actual_path] = permission
136-
added_keys.append(actual_path)
137-
138-
return new_fs, added_keys
126+
# Tìm vị trí thích hợp (sau thư mục cha) và lấy permission kế thừa
127+
insert_at, parent_perm = find_insert_index(entries, raw_path)
128+
permission = parent_perm if parent_perm else permission_d
129+
130+
# Chèn trực tiếp vào vị trí tìm được mà KHÔNG làm xáo trộn các dòng khác
131+
entries.insert(insert_at, (selinux_path, permission))
132+
seen_new.add(selinux_path)
133+
existing_paths.add(selinux_path)
134+
new_add += 1
135+
136+
print(f"ADD [{selinux_path} {' '.join(permission)}] at index {insert_at}")
137+
138+
return entries, new_add
139+
139140

140141
def main(dir_path: str, fs_config: str) -> None:
141-
origin, orig_order = scan_context(os.path.abspath(fs_config))
142+
origin_entries = scan_context(os.path.abspath(fs_config))
142143
allfiles = scan_dir(os.path.abspath(dir_path))
143144

144-
new_fs, added_keys = context_patch(origin, allfiles)
145-
146-
with open(fs_config, "w", encoding='utf-8', newline='\n') as f:
147-
written_keys = set()
148-
149-
# Ghi các key cũ theo đúng thứ tự gốc
150-
for key in orig_order:
151-
if key in new_fs:
152-
formatted_key = escape_regex_path(key)
153-
f.write(f"{formatted_key} {' '.join(new_fs[key])}\n")
154-
written_keys.add(key)
155-
156-
# Ghi các key mới được bổ sung
157-
for key in sorted(added_keys):
158-
if key in new_fs and key not in written_keys:
159-
formatted_key = escape_regex_path(key)
160-
f.write(f"{formatted_key} {' '.join(new_fs[key])}\n")
161-
written_keys.add(key)
162-
163-
print("---")
164-
print(f"Load origin: {len(origin)} entries")
165-
print(f"Detect total: {len(allfiles)} entries")
166-
print(f"Added new: {len(added_keys)} entries successfully")
145+
new_entries, new_add = context_patch(origin_entries, allfiles)
146+
147+
with open(fs_config, "w", encoding="utf-8", newline="\n") as f:
148+
for path, perm in new_entries:
149+
f.write(f"{path} {' '.join(perm)}\n")
150+
151+
print(f"Load origin {len(origin_entries)} entries")
152+
print(f"Detect total {len(allfiles)} entries")
153+
print(f"Add {new_add} entries")
154+
167155

168156
if __name__ == "__main__":
169157
if len(sys.argv) < 3:
170-
print("Usage: python script.py <dir_path> <file_contexts_path>")
158+
print("Insufficient parameters")
171159
sys.exit(1)
172-
173-
dir_arg, context_arg = sys.argv[1], sys.argv[2]
174-
175-
if not os.path.exists(dir_arg) or not os.path.exists(context_arg):
176-
print("Error: Target directory or file_contexts path does not exist.")
160+
161+
if not os.path.exists(sys.argv[1]) or not os.path.exists(sys.argv[2]):
162+
print("File or directory does not exist")
177163
sys.exit(1)
178-
179-
main(dir_arg, context_arg)
164+
165+
main(sys.argv[1], sys.argv[2])

0 commit comments

Comments
 (0)