• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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