1#!/usr/bin/env python 2# -*- coding: utf-8 -*- 3# Copyright (c) 2021 Huawei Device Co., Ltd. 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15 16import xml.etree.ElementTree as ET 17import ntpath 18import shutil 19import tempfile 20import os 21import logging 22import sys 23 24sys.path.append(os.path.dirname(os.path.abspath(__file__))) 25from sort_sa_by_bootphase import SARearrangement # noqa E402 26import sa_info_config_errors as sa_err # noqa E402 27 28 29class SAInfoMerger(object): 30 INDENT_SPACES = ' ' * 4 31 32 class SAInfoCollector(object): 33 """ 34 Class for collecting sa info pieces shared with same process name 35 """ 36 def __init__(self, process_name, wdir): 37 self.process_name = process_name 38 self.loadlibs = [] 39 self.systemabilities = [] 40 self.wdir = wdir 41 42 @property 43 def output_filename(self): 44 basename = self.process_name + '.xml' 45 return os.path.join(self.wdir, basename) 46 47 def add_libpath_info(self, libpath): 48 """ 49 A libpath shared by multiple SAs in a process should be added once 50 """ 51 if libpath not in self.loadlibs: 52 self.loadlibs.append(libpath) 53 54 def add_systemability_info(self, systemability): 55 self.systemabilities += systemability 56 57 def merge_sa_info(self): 58 """ 59 Write all pieces of sa info shared with same process to a new file 60 """ 61 DECLARATION = '<?xml version="1.0" encoding="utf-8"?>\n' 62 ROOT_OPEN_TAG = '<info>\n' 63 ROOT_ClOSE_TAG = '</info>' 64 # add declaration and root open tag 65 xml_lines = [DECLARATION, ROOT_OPEN_TAG] 66 # add process 67 process_line = '{}<process>{}</process>\n'.format( 68 SAInfoMerger.INDENT_SPACES, self.process_name) 69 xml_lines.append(process_line) 70 # add libpath 71 xml_lines.append(SAInfoMerger.INDENT_SPACES + '<loadlibs>\n') 72 xml_lines += list(self.loadlibs) 73 xml_lines.append(SAInfoMerger.INDENT_SPACES + '</loadlibs>\n') 74 # add systemability 75 xml_lines += self.systemabilities 76 # add root close tag 77 xml_lines.append(ROOT_ClOSE_TAG) 78 79 # write file to temporary directory 80 with open(self.output_filename, 'w', 81 encoding='utf-8') as xml_files: 82 for line in xml_lines: 83 xml_files.write(line) 84 85 def __init__(self, is_64bit_arch): 86 self.process_sas_dict = {} 87 self.output_filelist = [] 88 self.is_64bit_arch = is_64bit_arch 89 self.output_dir = None 90 self.temp_dir = None 91 self.sa_nodes_count = None 92 93 def __add_to_output_filelist(self, infile): 94 self.output_filelist.append(os.path.join(self.output_dir, infile)) 95 96 def __parse_xml_file(self, source_file): 97 parser = ET.XMLParser() 98 tree = ET.parse(source_file, parser) 99 root = tree.getroot() 100 101 # check root tag 102 if root.tag == 'profile': 103 _format = 'bad root <{}> tag, new format <info> is expected' 104 # the <profile> is old format, and should not be used anymore 105 raise sa_err.BadFormatXMLError(_format.format(root.tag), 106 source_file) 107 elif root.tag != 'info': 108 # other profile files whose tag name don't equal to 'info' should 109 # just left intact, e.g. <schedStrategies></schedStrategies> 110 basename = ntpath.basename(source_file) 111 dest_file = os.path.join(self.output_dir, basename) 112 shutil.copyfile(source_file, dest_file) 113 self.__add_to_output_filelist(basename) 114 115 # emit a warning to let user know it if there exists a typo 116 _logstr = '"{}" is not merged, for it\'s root tag is "{}"'.format( 117 source_file, root.tag) 118 logging.warning(_logstr) 119 return 120 121 _format = 'one and only one {} tag is expected, actually {} is found' 122 # check process tag 123 process_nodes = root.findall('process') 124 process_nodes_count = len(process_nodes) 125 if process_nodes_count != 1: 126 raise sa_err.BadFormatXMLError( 127 _format.format('<process>', process_nodes_count), source_file) 128 else: 129 # ensure that the value of <process> is valid 130 process_name = process_nodes[0].text 131 if process_name is None or process_name.strip() == '': 132 raise sa_err.BadFormatXMLError( 133 'provide a valid value for <process>', source_file) 134 process_name = process_name.strip() 135 if self.process_sas_dict.get(process_name) is None: 136 # create a new collector if a new process tag is found 137 sa_info_collector = self.SAInfoCollector( 138 process_name, self.temp_dir) 139 self.process_sas_dict[process_name] = sa_info_collector 140 self.__add_to_output_filelist(process_name + '.xml') 141 else: 142 sa_info_collector = self.process_sas_dict[process_name] 143 144 # check libpath tag 145 libpath_nodes = root.findall('systemability/libpath') 146 libpath_nodes_count = len(libpath_nodes) 147 if libpath_nodes_count != 1: 148 raise sa_err.BadFormatXMLError( 149 _format.format('<libpath>', libpath_nodes_count), source_file) 150 else: 151 libpath = libpath_nodes[0].text.strip() 152 if libpath.startswith("/system/lib") or libpath.startswith("system/lib"): 153 libname = ntpath.basename(libpath) 154 else: 155 libname = libpath 156 # [Temporary scheme] no additional process for 64-bit arch and 157 # a libpath without prefixed directory 158 if "/" in libpath: 159 if self.is_64bit_arch: 160 libpath = os.path.join("/system/lib64", libname) 161 else: 162 libpath = os.path.join("/system/lib", libname) 163 libpath_nodes[0].text = libpath 164 reconstructed_str = '<libpath>{}</libpath>\n'.format(libpath) 165 # fix weird indent problem after converting the node to string 166 string_repr = self.INDENT_SPACES * 2 + reconstructed_str 167 sa_info_collector.add_libpath_info(string_repr) 168 169 # check systemability tag 170 systemability_nodes = root.findall('systemability') 171 sa_nodes_count = len(systemability_nodes) 172 self.sa_nodes_count = sa_nodes_count 173 if sa_nodes_count != 1: 174 raise sa_err.BadFormatXMLError( 175 _format.format('<systemability>', sa_nodes_count), source_file) 176 else: 177 byte_repr = ET.tostring(systemability_nodes[0], encoding='utf-8') 178 # fix weird indent problem after converting the node to string 179 string_repr = self.INDENT_SPACES + byte_repr.decode('utf-8') 180 # fix newline problem if the </systemability> has tailling comment 181 fixed_string_repr = string_repr.rstrip() + '\n' 182 sa_info_collector.add_systemability_info([fixed_string_repr]) 183 184 def __merge(self, sa_info_filelist, output_dir): 185 """ 186 Iterate process_sas_dict and call it's merge_sa_info method to 187 merge systemability info by process name 188 """ 189 self.output_dir = output_dir 190 191 # collect systemability info by process 192 for source_file in sa_info_filelist: 193 self.__parse_xml_file(source_file) 194 195 global_ordered_systemability_names = [] 196 global_systemability_deps_dict = {} 197 # merge systemability info for each process 198 for process, collector in self.process_sas_dict.items(): 199 rearragement = SARearrangement() 200 # do the merge 201 collector.merge_sa_info() 202 # sort sa by bootphase and denpendency 203 merged_file = collector.output_filename 204 dest_file = os.path.join(output_dir, ntpath.basename(merged_file)) 205 rearragement.sort(merged_file, dest_file) 206 # get deps info for later detecting globally circular 207 # dependency use 208 deps_info = rearragement.get_deps_info() 209 global_ordered_systemability_names += deps_info[0] 210 global_systemability_deps_dict.update(deps_info[1]) 211 212 # detect possible cross-process circular dependency 213 try: 214 SARearrangement.detect_invalid_dependency_globally( 215 global_ordered_systemability_names, 216 global_systemability_deps_dict) 217 except sa_err.CircularDependencyError as e: 218 for _file in self.output_filelist: 219 try: 220 os.remove(_file) 221 except OSError: 222 pass 223 raise sa_err.CrossProcessCircularDependencyError(e) 224 225 # finally return an output filelist 226 return self.output_filelist 227 228 def merge(self, sa_info_filelist, output_dir): 229 with tempfile.TemporaryDirectory(dir='./') as temp_dir: 230 self.temp_dir = temp_dir 231 return self.__merge(sa_info_filelist, output_dir) 232