44import os
55import re
66import 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
5241def 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
140141def 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
168156if __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