1#!/usr/bin/env python3
2# coding=utf-8
3
4#
5# Copyright (c) 2020-2023 Huawei Device Co., Ltd.
6# Licensed under the Apache License, Version 2.0 (the "License");
7# you may not use this file except in compliance with the License.
8# You may obtain a copy of the License at
9#
10#     http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS,
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17#
18
19import os
20import shutil
21import subprocess
22import json
23import sys
24import time
25import xml.etree.ElementTree as ET
26from public_method import get_server_dict, get_config_ip, get_sn_list
27import stat
28
29FLAGS = os.O_WRONLY | os.O_CREAT | os.O_EXCL
30MODES = stat.S_IWUSR | stat.S_IRUSR
31
32
33def _init_sys_config():
34    sys.localcoverage_path = os.path.join(current_path, "..")
35    sys.path.insert(0, sys.localcoverage_path)
36
37
38def modify_init_file(developer_path, hdc_str):
39    """
40    /etc/init.cfg文件添加cmds
41    """
42    recv_path = os.path.join(developer_path, "local_coverage/resident_service/resources")
43    print("%s file recv /etc/init.cfg %s" % (hdc_str, recv_path))
44    coverage_command("%s file recv /etc/init.cfg %s" % (hdc_str, recv_path))
45    recv_restores_path = os.path.join(recv_path, "restores_environment")
46    if not os.path.exists(recv_restores_path):
47        os.mkdir(recv_restores_path)
48    recv_restores_name = os.path.join(recv_restores_path, "init.cfg")
49    if not os.path.exists(recv_restores_name):
50        coverage_command("%s file recv /etc/init.cfg %s" % (hdc_str, recv_restores_path))
51    else:
52        print("INFO: file exit", recv_restores_name)
53
54    cfg_file_path = os.path.join(recv_path, "init.cfg")
55    if os.path.exists(cfg_file_path):
56        with open(cfg_file_path, "r") as fp:
57            json_data = json.load(fp)
58
59        for jobs_list in json_data["jobs"]:
60            if jobs_list["name"] == "init":
61                if jobs_list["cmds"][-1] != "export GCOV_FETCH_METHOD FM_SIGNA":
62                    jobs_list["cmds"].append("mkdir /data/gcov 0777 system system")
63                    jobs_list["cmds"].append("export GCOV_PREFIX /data/gcov")
64                    jobs_list["cmds"].append("export GCOV_FETCH_METHOD FM_SIGNA")
65                else:
66                    return
67        json_str = json.dumps(json_data, indent=2)
68        if os.path.exists(cfg_file_path):
69            os.remove(cfg_file_path)
70        with os.fdopen(os.open(cfg_file_path, FLAGS, MODES), 'w') as json_file:
71            json_file.write(json_str)
72    else:
73        print("init.cfg file not exists")
74        return
75    print("%s shell mount -o rw,remount / > /dev/null 2>&1" % hdc_str)
76    coverage_command("%s shell mount -o rw,remount / > /dev/null 2>&1" % hdc_str)
77    print("%s target mount" % hdc_str)
78    coverage_command("%s target mount" % hdc_str)
79    print("%s file send %s %s" % (hdc_str, cfg_file_path, "/etc/"))
80    coverage_command("%s file send %s %s" % (hdc_str, cfg_file_path, "/etc/"))
81    coverage_command("%s shell param set persist.appspawn.client.timeout 120 > /dev/null 2>&1" % hdc_str)
82    return
83
84
85def modify_faultloggerd_file(developer_path, hdc_str):
86    _, enforce = subprocess.getstatusoutput("%s shell getenforce" % hdc_str)
87    coverage_command("%s shell mount -o rw,remount /" % hdc_str)
88    print("%s shell mount -o rw,remount /" % hdc_str)
89    coverage_command("%s target mount" % hdc_str)
90    if enforce != "Permissive":
91        coverage_command("%s shell sed -i 's/enforcing/permissive/g' /system/etc/selinux/config" % hdc_str)
92
93    recv_path = os.path.join(developer_path, "local_coverage/resident_service/resources")
94    print("%s file recv /system/etc/init/faultloggerd.cfg %s" % (hdc_str, recv_path))
95    coverage_command("%s file recv /system/etc/init/faultloggerd.cfg %s" % (hdc_str, recv_path))
96
97    cfg_file_path = os.path.join(recv_path, "faultloggerd.cfg")
98    if os.path.exists(cfg_file_path):
99        with open(cfg_file_path, "r") as fp:
100            json_data = json.load(fp)
101        if json_data.get("jobs") and json_data["jobs"][0]["name"] != "pre-init":
102            json_data["jobs"].insert(0, {
103                "name": "pre-init",
104                "cmds": [
105                    "export LD_PRELOAD libcoverage_signal_handler.z.so"
106                ]
107            })
108            json_str = json.dumps(json_data, indent=4)
109            if os.path.exists(cfg_file_path):
110                os.remove(cfg_file_path)
111            with os.fdopen(os.open(cfg_file_path, FLAGS, MODES), 'w') as json_file:
112                json_file.write(json_str)
113            print("%s file send %s %s" % (hdc_str, cfg_file_path, "/system/etc/init/"))
114            coverage_command("%s file send %s %s" % (hdc_str, cfg_file_path, "/system/etc/init/"))
115    else:
116        print("faultloggerd.cfg file not exists.")
117
118    return
119
120
121def modify_foundation_xml(serv, config_path, origin_xml) -> str:
122    """
123    修改foundation.xml文件,删去拆分的进程相关
124    :param serv: 拆分进程
125    :param config_path: 配置文件路径
126    :param origin_xml: 原foundation.xml
127    :return: 修改后foundation.xml路径
128    """
129    lib_list = FoundationServer.lib_dict.get(serv)
130
131    tree = ET.parse(origin_xml)
132    root = tree.getroot()
133    loadlibs = root.find("loadlibs")
134
135    for lib in lib_list:
136        for sa in root.findall('systemability'):
137            if lib in sa.find('libpath').text:
138                root.remove(sa)
139        for ll in loadlibs.findall('libpath'):
140            if lib in ll.text:
141                loadlibs.remove(ll)
142
143    tree.write(os.path.join(config_path, 'foundation.xml'), encoding='utf-8', xml_declaration=True)
144    return os.path.join(config_path, 'foundation.xml')
145
146
147def modify_foundation_json(serv, config_path, origin_json) -> str:
148    """
149    修改foundation.json文件,删去拆分的进程相关
150    :param serv: 拆分进程
151    :param config_path: 配置文件路径
152    :param origin_json: 原foundation.json
153    :return: 修改后foundation.json路径
154    """
155    lib_list = FoundationServer.lib_dict.get(serv)
156
157    with open(origin_json, "r", encoding="UTF-8") as f:
158        f_dict = json.load(f)
159
160    tmp_list = list()
161    for i in range(len(f_dict["systemability"])):
162        if f_dict["systemability"][i]["libpath"] not in lib_list:
163            tmp_list.append(f_dict["systemability"][i])
164    f_dict["systemability"] = tmp_list
165
166    new_json = os.path.join(config_path, 'foundation.json')
167    if os.path.exists(new_json):
168        os.remove(new_json)
169    with os.fdopen(os.open(new_json, FLAGS, MODES), 'w') as f:
170        json.dump(f_dict, f, indent=4)
171
172    return new_json
173
174
175def create_service_json(serv, config_path, origin_json) -> str:
176    """
177    创建进程json
178    :param serv: 进程名
179    :param config_path:配置文件所在目录
180    :param origin_json: 原foundation.json
181    :return: json文件路径
182    """
183    lib_list = FoundationServer.lib_dict.get(serv)
184    with open(origin_json, "r", encoding="UTF-8") as f:
185        f_dict = json.load(f)
186
187    tmp_list = list()
188    for lib in lib_list:
189        for i in range(len(f_dict["systemability"])):
190            if f_dict["systemability"][i]["libpath"] == lib:
191                tmp_list.append(f_dict["systemability"][i])
192    f_dict["systemability"] = tmp_list
193    f_dict["process"] = "{}".format(serv)
194
195    new_json = os.path.join(config_path, '{}.json'.format(serv))
196    if os.path.exists(new_json):
197        os.remove(new_json)
198    with os.fdopen(os.open(new_json, FLAGS, MODES), 'w') as f:
199        json.dump(f_dict, f, indent=4)
200
201    return new_json
202
203
204def create_service_xml(serv, config_path, origin_xml) -> str:
205    """
206    创建进程xml
207    :param serv: 进程名
208    :param config_path:配置文件所在目录
209    :param origin_xml: 原foundation.xml
210    :return: xml文件路径
211    """
212    lib_list = FoundationServer.lib_dict.get(serv)
213
214    tree = ET.parse(origin_xml)
215    root = tree.getroot()
216    loadlibs = root.find("loadlibs")
217
218    for lib in lib_list:
219        for sa in root.findall('systemability'):
220            if lib not in sa.find('libpath').text:
221                root.remove(sa)
222        for lp in loadlibs.findall('libpath'):
223            if lib not in lp.text:
224                loadlibs.remove(lp)
225
226    tree.write(os.path.join(config_path, '{}.xml'.format(serv)), encoding='utf-8', xml_declaration=True)
227    return os.path.join(config_path, '{}.xml'.format(serv))
228
229
230def create_service_cfg(serv, config_path, origin_cfg) -> str:
231    """
232    创建进程cfg文件
233    :param serv: 进程名
234    :param config_path:配置文件所在目录
235    :param origin_cfg: 原foundation.cfg
236    :return: cfg文件路径
237    """
238    with open(origin_cfg, "r") as jf:
239        json_obj = json.load(jf)
240        json_obj["jobs"][0]["name"] = "services:{}".format(serv)
241
242        json_obj["services"][0]["name"] = "{}".format(serv)
243
244        path_list = json_obj["services"][0]["path"]
245        path_list.remove("/system/profile/foundation.json")
246        path_list.append("/system/profile/{}.json".format(serv))
247        json_obj["services"][0]["path"] = path_list
248
249        json_obj["services"][0]["jobs"]["on-start"] = "services:{}".format(serv)
250
251    cfg_path = os.path.join(config_path, "{}.cfg".format(serv))
252    if os.path.exists(cfg_path):
253        os.remove(cfg_path)
254    with os.fdopen(os.open(cfg_path, FLAGS, MODES), 'w') as r:
255        json.dump(json_obj, r, indent=4)
256    return cfg_path
257
258
259def remove_configs(config_path):
260    """
261    清理配置文件目录下的xml和cfg文件
262    :param config_path: 配置文件目录
263    :return:
264    """
265    logger("Clear config path...", "INFO")
266    shutil.rmtree(config_path)
267    os.mkdir(config_path)
268
269
270def split_foundation_services(developer_path, system_info_dict, home_path, hdc_dict):
271    """
272    foundation.xmlXXX.xml文件推送到 /system/profile
273    XXX.cfg文件推送到/etc/init/
274    reboot设备,可以将服务从foundation中拆分出来,成为一个独立服务进程
275    """
276    config_path = os.path.join(developer_path, "local_coverage", "resident_service", "config")
277    remove_configs(config_path)
278
279    device_ip = hdc_dict["device_ip"]
280    hdc_port = hdc_dict["device_port"]
281    device_sn = hdc_dict["device_sn_str"]
282
283    hdc_command(device_ip, hdc_port, device_sn, "file recv /system/profile/foundation.json {}".format(config_path))
284    hdc_command(device_ip, hdc_port, device_sn, "file recv /etc/init/foundation.cfg {}".format(config_path))
285
286    if os.path.exists(os.path.join(config_path, "foundation.json")):
287        origin_json = os.path.join(config_path, "foundation_origin.json")
288        os.rename(os.path.join(config_path, "foundation.json"), origin_json)
289    else:
290        logger("{} not exist, Cannot modify.".format(os.path.join(config_path, "foundation.json")), "ERROR")
291        return
292
293    if os.path.exists(os.path.join(config_path, "foundation.cfg")):
294        origin_cfg = os.path.join(config_path, "foundation_origin.cfg")
295        os.rename(os.path.join(config_path, "foundation.cfg"), origin_cfg)
296    else:
297        logger("{} not exist, Cannot modify.".format(os.path.join(config_path, "foundation.cfg")), "ERROR")
298        return
299
300    foundation_process_list = FoundationServer.lib_dict.keys()
301
302    # 推送配置文件
303    for _, value_list in system_info_dict.items():
304        for process_str in value_list:
305            if process_str in foundation_process_list:
306                foundation_json = modify_foundation_json(process_str, config_path, origin_json)
307                service_json = create_service_json(process_str, config_path, origin_json)
308                service_cfg = create_service_cfg(process_str, config_path, origin_cfg)
309
310                hdc_command(device_ip, hdc_port, device_sn, "shell rm -rf {}".format(home_path))
311                hdc_command(device_ip, hdc_port, device_sn, "file send {} /system/profile/".format(foundation_json))
312                hdc_command(device_ip, hdc_port, device_sn, "file send {} /system/profile/".format(service_json))
313                hdc_command(device_ip, hdc_port, device_sn, "file send {} /etc/init/".format(service_cfg))
314
315    return
316
317
318def modify_cfg_xml_file(developer_path, device_ip, device_sn_list,
319                        system_info_dict, home_path, device_port):
320    if device_ip and len(device_sn_list) >= 1:
321        for device_sn_str in device_sn_list:
322            hdc_str = "hdc -s %s:%s -t %s" % (device_ip, device_port, device_sn_str)
323            hdc_dict = {"device_ip": device_ip, "device_port": device_port, "device_sn_str": device_sn_str}
324            modify_init_file(developer_path, hdc_str)
325            modify_faultloggerd_file(
326                developer_path, hdc_str)
327            # 推送服务对应的配置文件
328            split_foundation_services(developer_path, system_info_dict, home_path, hdc_dict)
329            logger("{} shell reboot".format(hdc_str), "INFO")
330            coverage_command("%s shell reboot > /dev/null 2>&1" % hdc_str)
331            while True:
332                after_sn_list = get_sn_list("hdc -s %s:%s list targets" % (device_ip, device_port))
333                time.sleep(10)
334                if device_sn_str in after_sn_list:
335                    break
336            coverage_command("%s shell getenforce" % hdc_str)
337    else:
338        logger("user_config.xml device ip not config", "ERROR")
339
340
341if __name__ == '__main__':
342    command_args = sys.argv[1]
343    command_str = command_args.split("command_str=")[1].replace(",", " ")
344    current_path = os.getcwd()
345    _init_sys_config()
346    from local_coverage.utils import coverage_command, \
347        logger, hdc_command, FoundationServer
348
349    root_path = current_path.split("/test/testfwk/developer_test")[0]
350    developer_test_path = os.path.join(root_path, "test/testfwk/developer_test")
351    home_paths = '/'.join(root_path.split("/")[:3])
352
353    # 获取user_config中的device ip
354    ip, port, sn = get_config_ip(os.path.join(developer_test_path, "config/user_config.xml"))
355    if not port:
356        port = "8710"
357    sn_list = []
358    if sn:
359        sn_list.extend(sn.replace(" ", "").split(";"))
360    else:
361        sn_list = get_sn_list("hdc -s %s:%s list targets" % (ip, port))
362
363    # 获取子系统部件与服务的关系
364    system_dict, _, _ = get_server_dict(command_str)
365
366    # 修改设备init.cfg, faultloggerd.cfg等文件
367    modify_cfg_xml_file(developer_test_path, ip, sn_list,
368                        system_dict, home_paths, port)
369