• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3
4# Copyright (c) 2021 Huawei Device Co., Ltd.
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16"""
17Description : Defining constants, common interface
18"""
19import argparse
20import json
21import os
22import shutil
23import tempfile
24import zipfile
25from collections import OrderedDict
26from copy import copy
27from ctypes import cdll
28from cryptography.hazmat.primitives import hashes
29from log_exception import UPDATE_LOGGER
30import xmltodict
31from build_pkcs7 import sign_ota_package
32
33operation_path = os.path.dirname(os.path.realpath(__file__))
34PRODUCT = 'hi3516'
35BUILD_TOOLS_FILE_NAME = 'build_tools.zip'
36UPDATE_BIN_FILE_NAME = "update.bin"
37UPDATE_EXE_FILE_NAME = "updater_binary"
38
39SCRIPT_KEY_LIST = ['prelude', 'verse', 'refrain', 'ending']
40TOTAL_SCRIPT_FILE_NAME = "loadScript.us"
41REGISTER_SCRIPT_FILE_NAME = "registerCmd.us"
42SCRIPT_FILE_NAME = '-script.us'
43
44UPDATER_CONFIG = "updater_config"
45XML_FILE_PATH = "updater_specified_config.xml"
46SO_PATH = os.path.join(operation_path, 'lib/libpackage.so')
47SO_PATH_L1 = os.path.join(operation_path, 'lib/libpackageL1.so')
48DIFF_EXE_PATH = os.path.join(operation_path, 'lib/diff')
49E2FSDROID_PATH = os.path.join(operation_path, 'lib/e2fsdroid')
50MISC_INFO_PATH = "misc_info.txt"
51VERSION_MBN_PATH = "VERSION.mbn"
52BOARD_LIST_PATH = "BOARD.list"
53EXTEND_COMPONENT_LIST = ["version_list", "board_list"]
54EXTEND_OPTIONAL_COMPONENT_LIST = ["partitions_file"]
55PARTITION_FILE = "partitions_file"
56IGNORED_PARTITION_LIST = ['fastboot', 'boot', 'kernel', 'misc',
57                          'updater', 'userdata']
58
59HASH_ALGORITHM_DICT = {'sha256': hashes.SHA256, 'sha384': hashes.SHA384}
60LINUX_HASH_ALGORITHM_DICT = {'sha256': 'sha256sum', 'sha384': 'sha384sum'}
61HASH_CONTENT_LEN_DICT = {'sha256': 64, 'sha384': 96}
62
63COMPONENT_INFO_INNIT = ['', '000', '00', '0', '0o00']
64
65ON_SERVER = "ON_SERVER"
66
67EXTEND_VALUE = 512
68
69FILE_MAP_ZERO_KEY = "__ZERO"
70FILE_MAP_NONZERO_KEY = "__NONZERO"
71FILE_MAP_COPY_KEY = "__COPY"
72
73MAX_BLOCKS_PER_GROUP = BLOCK_LIMIT = 1024
74PER_BLOCK_SIZE = 4096
75
76VERSE_SCRIPT_EVENT = 0
77INC_IMAGE_EVENT = 1
78SIGN_PACKAGE_EVENT = 2
79GENERATE_SIGNED_DATA_EVENT = 6 # sign build tools files to get hash_signed_data
80
81# 1000000: max number of function recursion depth
82MAXIMUM_RECURSION_DEPTH = 1000000
83
84
85def singleton(cls):
86    _instance = {}
87
88    def _singleton(*args, **kargs):
89        if cls not in _instance:
90            _instance[cls] = cls(*args, **kargs)
91        return _instance[cls]
92
93    return _singleton
94
95
96class ExtInit:
97    """
98    Init event for ext
99    """
100
101    def __init__(self):
102        self.funs = []
103
104    def reg_event(self, evevt_id, funs):
105            self.funs.append([evevt_id, funs])
106            UPDATE_LOGGER.print_log(
107                'register event %s: %s' % (evevt_id, funs.__name__))
108
109    def invoke_event(self, evevt_id):
110        UPDATE_LOGGER.print_log(self.funs)
111        for event in self.funs:
112            funs = event[1]
113            if event[0] == evevt_id and funs is not None:
114                UPDATE_LOGGER.print_log(
115                    'invoke event %s: %s' % (evevt_id, funs.__name__))
116                return funs
117        return False
118
119
120@singleton
121class OptionsManager:
122    """
123    Options management class
124    """
125
126    def __init__(self):
127        self.init = ExtInit()
128        self.parser = argparse.ArgumentParser()
129
130        # Own parameters
131        self.product = None
132
133        # Entry parameters
134        self.source_package = None
135        self.target_package = None
136        self.update_package = None
137        self.unpack_package_path = None
138        self.no_zip = False
139        self.partition_file = None
140        self.signing_algorithm = None
141        self.hash_algorithm = None
142        self.private_key = None
143        self.not_l2 = False
144        self.signing_length = 256
145        self.xml_path = None
146        self.sd_card = False
147
148        self.make_dir_path = None
149
150        # Parsed package parameters
151        self.target_package_dir = None
152        self.target_package_config_dir = None
153        self.target_package_temp_obj = None
154        self.misc_info_dict = {}
155        self.version_mbn_file_path = None
156        self.version_mbn_content = None
157        self.board_list_file_path = None
158        self.board_list_content = None
159
160        self.source_package_dir = None
161        self.source_package_temp_obj = None
162
163        # XML parsing parameters
164        self.head_info_list = []
165        self.component_info_dict = {}
166        self.full_img_list = []
167        self.full_img_name_list = []
168        self.incremental_img_list = []
169        self.incremental_img_name_list = []
170        self.target_package_version = None
171        self.source_package_version = None
172        self.full_image_path_list = []
173
174        self.partition_file_obj = None
175
176        # Full processing parameters
177        self.full_image_content_len_list = []
178        self.full_image_file_obj_list = []
179
180        # Incremental processing parameters
181        self.incremental_content_len_list = []
182        self.incremental_image_file_obj_dict = {}
183        self.incremental_block_file_obj_dict = {}
184        self.incremental_temp_file_obj_list = []
185        self.max_stash_size = 0
186        self.src_image = None
187        self.tgt_image = None
188
189        # Script parameters
190        self.opera_script_file_name_dict = {}
191        for each in SCRIPT_KEY_LIST:
192            self.opera_script_file_name_dict[each] = []
193        self.total_script_file_obj = None
194
195        self.register_script_file_obj = None
196
197        # Update package parameters
198        self.update_bin_obj = None
199        self.build_tools_zip_obj = None
200        self.update_package_file_path = None
201        self.signed_package = None
202
203
204OPTIONS_MANAGER = OptionsManager()
205
206
207def unzip_package(package_path, origin='target'):
208    """
209    Decompress the zip package.
210    :param package_path: zip package path
211    :param origin: package origin, which indicates
212                whether the zip package is a source package or target package
213    :return: Temporary directory (tmp_dir) and zip_data package;
214            false if an exception occurs.
215    """
216    try:
217        tmp_dir_obj = tempfile.TemporaryDirectory(prefix="%sfiles-" % origin)
218        tmp_dir = tmp_dir_obj.name
219
220        zf_obj = zipfile.ZipFile(package_path)
221        for name in zf_obj.namelist():
222            if name.endswith('/'):
223                os.mkdir(os.path.join(tmp_dir, name))
224            else:
225                ext_filename = os.path.join(
226                    tmp_dir, name)
227                with open(ext_filename, 'wb') as f_w:
228                    f_w.write(zf_obj.read(name))
229    except OSError:
230        UPDATE_LOGGER.print_log(
231            "Unzip package failed! path: %s" % package_path,
232            log_type=UPDATE_LOGGER.ERROR_LOG)
233        return False, False
234    tmp_dir_list = os.listdir(tmp_dir)
235    if len(tmp_dir_list) == 1:
236        unzip_dir = os.path.join(tmp_dir, tmp_dir_list[0])
237        if UPDATER_CONFIG not in \
238                os.listdir(unzip_dir):
239            UPDATE_LOGGER.print_log(
240                'Unsupported zip package structure!', UPDATE_LOGGER.ERROR_LOG)
241            return False, False
242    elif UPDATER_CONFIG in tmp_dir_list:
243        unzip_dir = tmp_dir
244    else:
245        UPDATE_LOGGER.print_log(
246            'Unsupported zip package structure!', UPDATE_LOGGER.ERROR_LOG)
247        return False, False
248    UPDATE_LOGGER.print_log(
249        '%s package unzip complete! path: %s' % (origin.title(), unzip_dir))
250
251    return tmp_dir_obj, unzip_dir
252
253
254def split_img_name(image_path):
255    """
256    Split the image name by image path
257    :return image name
258    """
259    tmp_path = image_path
260    str_list = tmp_path.split("/")
261
262    return str_list[-1]
263
264
265def get_update_config_softversion(mbn_dir, head_info_dict):
266    soft_version_file = head_info_dict.get('softVersionFile')
267    if soft_version_file is not None:
268        mbn_path = os.path.join(mbn_dir, soft_version_file)
269        if os.path.exists(mbn_path):
270            with open(mbn_path, 'r') as mbn_file:
271                head_info_dict['info']["@softVersion"] = mbn_file.read()
272
273
274def parse_update_config(xml_path):
275    """
276    Parse the XML configuration file.
277    :param xml_path: XML configuration file path
278    :return head_info_dict: header information dict of the update package
279            component_info_dict: component information dict
280            full_img_list: full image list
281            incremental_img_list: incremental image list
282    """
283    if os.path.exists(xml_path):
284        with open(xml_path, 'r') as xml_file:
285            xml_str = xml_file.read()
286    else:
287        UPDATE_LOGGER.print_log("XML file does not exist! xml path: %s" %
288                                xml_path, UPDATE_LOGGER.ERROR_LOG)
289        ret_params = [False, False, False, False, False, False, False]
290        return ret_params
291    xml_content_dict = xmltodict.parse(xml_str, encoding='utf-8')
292    package_dict = xml_content_dict.get('package', {})
293    get_update_config_softversion(OPTIONS_MANAGER.target_package_dir, package_dict.get('head', {}))
294    head_dict = package_dict.get('head', {}).get('info')
295    package_version = head_dict.get("@softVersion")
296    # component
297    component_info = package_dict.get('group', {}).get('component')
298    head_list = list(head_dict.values())
299    head_list.pop()
300    whole_list = []
301    difference_list = []
302    component_dict = {}
303    full_image_path_list = []
304
305    if not OPTIONS_MANAGER.not_l2:
306        expand_component(component_dict)
307    if isinstance(component_info, OrderedDict) or isinstance(component_info, dict):
308        component_info = [component_info]
309    if component_info is None:
310        ret_params = [[], {}, [], [], '', [], False]
311        return ret_params
312    for component in component_info:
313        component_list = list(component.values())
314        component_list.pop()
315
316        if component['@compAddr'] in (whole_list + difference_list):
317            UPDATE_LOGGER.print_log("This component %s  repeats!" %
318                                    component['@compAddr'],
319                                    UPDATE_LOGGER.ERROR_LOG)
320            ret_params = [False, False, False, False, False, False, False]
321            return ret_params
322
323        if component['@compType'] == '0':
324            whole_list.append(component['@compAddr'])
325            OPTIONS_MANAGER.full_img_name_list.\
326                append(split_img_name(component['#text']))
327            tem_path = os.path.join(OPTIONS_MANAGER.target_package_dir,
328                                    component.get("#text", None))
329            full_image_path_list.append(tem_path)
330            component_dict[component['@compAddr']] = component_list
331        elif component['@compType'] == '1':
332            difference_list.append(component['@compAddr'])
333            OPTIONS_MANAGER.incremental_img_name_list.\
334                append(split_img_name(component['#text']))
335
336    UPDATE_LOGGER.print_log('XML file parsing completed!')
337    ret_params = [head_list, component_dict,
338                  whole_list, difference_list, package_version,
339                  full_image_path_list]
340    return ret_params
341
342
343def partitions_conversion(data):
344    """
345    Convert the start or length data in the partition table through
346    multiply 1024 * 1024 and return the data.
347    :param data: start or length data
348    :return :
349    """
350    if data == '0':
351        return 0
352    elif data.endswith('M'):
353        return int(data[0:-1]) * 1024 * 1024 // 512
354    else:
355        return False
356
357
358def parse_partition_file_xml(xml_path):
359    """
360    Parse the XML configuration file.
361    :param xml_path: XML configuration file path
362    :return part_json: partition table information in JSON format
363    """
364    if os.path.exists(xml_path):
365        with open(xml_path, 'r') as xml_file:
366            xml_str = xml_file.read()
367    else:
368        UPDATE_LOGGER.print_log("XML file does not exist! xml path: %s" %
369                                xml_path, UPDATE_LOGGER.ERROR_LOG)
370        return False, False, False
371    partitions_list = []
372    partitions_file_path_list = []
373    xml_content_dict = xmltodict.parse(xml_str, encoding='utf-8')
374    part_list = xml_content_dict['Partition_Info']['Part']
375    new_part_list = []
376    for i, part in enumerate(part_list):
377        start_value = partitions_conversion(part.get('@Start'))
378        length_value = partitions_conversion(part.get('@Length'))
379        if start_value is False or length_value is False:
380            UPDATE_LOGGER.print_log(
381                "Partition file parsing failed! part_name: %s, xml_path: %s" %
382                (part.get('@PartitionName'), xml_path),
383                UPDATE_LOGGER.ERROR_LOG)
384            return False, False, False
385
386        if part.get('@PartitionName') not in IGNORED_PARTITION_LIST:
387            partitions_list.append(part.get('@PartitionName'))
388            partitions_file_path_list.append(
389                os.path.join(OPTIONS_MANAGER.target_package_dir,
390                             "%s.img" % part.get('@PartitionName')))
391        part_dict = {'start': start_value,
392                     'length': length_value,
393                     'partName': part.get('@PartitionName'),
394                     'fsType': part.get('@FlashType')}
395        new_part_list.append(part_dict)
396    part_json = json.dumps(new_part_list)
397    part_json = '{"Partition": %s}' % part_json
398    file_obj = tempfile.NamedTemporaryFile(
399        dir=OPTIONS_MANAGER.target_package_dir, prefix="partition_file-", mode='wb')
400    file_obj.write(part_json.encode())
401    file_obj.seek(0)
402    return file_obj, partitions_list, partitions_file_path_list
403
404
405def expand_component(component_dict):
406    """
407    Append components such as VERSION.mbn and board list.
408    :param component_dict: component information dict
409    :return:
410    """
411    if OPTIONS_MANAGER.partition_file is not None:
412        extend_component_list = \
413            EXTEND_COMPONENT_LIST + EXTEND_OPTIONAL_COMPONENT_LIST
414    else:
415        extend_component_list = EXTEND_COMPONENT_LIST
416    for each in extend_component_list:
417        tmp_info_list = copy(COMPONENT_INFO_INNIT)
418        tmp_info_list[0] = each
419        component_dict[each] = tmp_info_list
420
421
422def clear_options():
423    """
424    Clear OPTIONS_MANAGER.
425    """
426    OPTIONS_MANAGER.product = None
427
428    # Entry parameters
429    OPTIONS_MANAGER.source_package = None
430    OPTIONS_MANAGER.target_package = None
431    OPTIONS_MANAGER.update_package = None
432    OPTIONS_MANAGER.no_zip = False
433    OPTIONS_MANAGER.partition_file = None
434    OPTIONS_MANAGER.signing_algorithm = None
435    OPTIONS_MANAGER.hash_algorithm = None
436    OPTIONS_MANAGER.private_key = None
437    OPTIONS_MANAGER.not_l2 = False
438    OPTIONS_MANAGER.signing_length = 256
439    OPTIONS_MANAGER.xml_path = None
440    OPTIONS_MANAGER.sd_card = False
441
442    OPTIONS_MANAGER.full_image_path_list = []
443
444    OPTIONS_MANAGER.make_dir_path = None
445
446    # Parsed package parameters
447    OPTIONS_MANAGER.target_package_dir = None
448    OPTIONS_MANAGER.target_package_config_dir = None
449    OPTIONS_MANAGER.target_package_temp_obj = None
450    OPTIONS_MANAGER.misc_info_dict = {}
451    OPTIONS_MANAGER.version_mbn_file_path = None
452    OPTIONS_MANAGER.version_mbn_content = None
453    OPTIONS_MANAGER.board_list_file_path = None
454    OPTIONS_MANAGER.board_list_content = None
455
456    OPTIONS_MANAGER.source_package_dir = None
457    OPTIONS_MANAGER.source_package_temp_obj = None
458
459    # XML parsing parameters
460    OPTIONS_MANAGER.head_info_list = []
461    OPTIONS_MANAGER.component_info_dict = {}
462    OPTIONS_MANAGER.full_img_list = []
463    OPTIONS_MANAGER.incremental_img_list = []
464    OPTIONS_MANAGER.target_package_version = None
465    OPTIONS_MANAGER.source_package_version = None
466    OPTIONS_MANAGER.partition_file_obj = None
467
468    # Global processing parameters
469    OPTIONS_MANAGER.full_image_content_len_list = []
470    OPTIONS_MANAGER.full_image_file_obj_list = []
471
472    # Incremental processing parameters
473    OPTIONS_MANAGER.incremental_content_len_list = []
474    OPTIONS_MANAGER.incremental_temp_file_obj_list = []
475
476    # Script parameters
477    OPTIONS_MANAGER.opera_script_file_name_dict = {}
478    for each in SCRIPT_KEY_LIST:
479        OPTIONS_MANAGER.opera_script_file_name_dict[each] = []
480    OPTIONS_MANAGER.total_script_file_obj = None
481
482    OPTIONS_MANAGER.register_script_file_obj = None
483
484    # Update package parameters
485    OPTIONS_MANAGER.update_bin_obj = None
486    OPTIONS_MANAGER.build_tools_zip_obj = None
487    OPTIONS_MANAGER.update_package_file_path = None
488
489
490def clear_resource(err_clear=False):
491    """
492    Clear resources, close temporary files, and clear temporary paths.
493    :param err_clear: whether to clear errors
494    :return:
495    """
496    target_package_temp_obj = OPTIONS_MANAGER.target_package_temp_obj
497    if target_package_temp_obj is not None:
498        target_package_temp_obj.cleanup()
499    source_package_temp_obj = OPTIONS_MANAGER.source_package_temp_obj
500    if source_package_temp_obj is not None:
501        source_package_temp_obj.cleanup()
502
503    partition_file_obj = OPTIONS_MANAGER.partition_file_obj
504    if partition_file_obj is not None:
505        partition_file_obj.close()
506
507    build_tools_zip_obj = OPTIONS_MANAGER.build_tools_zip_obj
508    if build_tools_zip_obj is not None:
509        build_tools_zip_obj.close()
510    update_bin_obj = OPTIONS_MANAGER.update_bin_obj
511    if update_bin_obj is not None:
512        update_bin_obj.close()
513    total_script_file_obj = OPTIONS_MANAGER.total_script_file_obj
514    if total_script_file_obj is not None:
515        total_script_file_obj.close()
516
517    register_script_file_obj = OPTIONS_MANAGER.register_script_file_obj
518    if register_script_file_obj is not None:
519        register_script_file_obj.close()
520
521    full_image_file_obj_list = OPTIONS_MANAGER.full_image_file_obj_list
522    if len(full_image_file_obj_list) != 0:
523        for each_full_obj in full_image_file_obj_list:
524            each_full_obj.close()
525
526    clear_file_obj(err_clear)
527    clear_options()
528
529
530def clear_file_obj(err_clear):
531    """
532    Clear resources and temporary file objects.
533    :param err_clear: whether to clear errors
534    :return:
535    """
536    incremental_temp_file_obj_list = \
537        OPTIONS_MANAGER.incremental_temp_file_obj_list
538    if len(incremental_temp_file_obj_list) != 0:
539        for each_incremental_temp_obj in incremental_temp_file_obj_list:
540            if each_incremental_temp_obj is not None:
541                each_incremental_temp_obj.close()
542    opera_script_file_name_dict = OPTIONS_MANAGER.opera_script_file_name_dict
543    for each_value in opera_script_file_name_dict.values():
544        for each in each_value:
545            each[1].close()
546    if err_clear:
547        make_dir_path = OPTIONS_MANAGER.make_dir_path
548        if make_dir_path is not None and os.path.exists(make_dir_path):
549            shutil.rmtree(make_dir_path)
550        update_package_file_path = OPTIONS_MANAGER.update_package_file_path
551        if update_package_file_path is not None and \
552                os.path.exists(update_package_file_path):
553            os.remove(update_package_file_path)
554        UPDATE_LOGGER.print_log(
555            'Exception occurred, Resource cleaning completed!')
556    else:
557        UPDATE_LOGGER.print_log('Resource cleaning completed!')
558
559
560def get_file_content(file_path, file_name=None):
561    """
562    Read the file content.
563    :param file_path: file path
564    :param file_name: file name
565    :return: file content
566    """
567    if not os.path.exists(file_path):
568        UPDATE_LOGGER.print_log(
569            "%s is not exist! path: %s" % (file_name, file_path),
570            log_type=UPDATE_LOGGER.ERROR_LOG)
571        return False
572    with open(file_path, 'r') as r_f:
573        file_content = r_f.read()
574    UPDATE_LOGGER.print_log(
575        "%s file parsing complete! path: %s" % (file_name, file_path))
576    return file_content
577
578
579def get_update_info():
580    """
581    Parse the configuration file to obtain the update information.
582    :return: update information if any; false otherwise.
583    """
584    if not OPTIONS_MANAGER.not_l2:
585        OPTIONS_MANAGER.version_mbn_file_path = os.path.join(
586            OPTIONS_MANAGER.target_package_config_dir, VERSION_MBN_PATH)
587        version_mbn_content = \
588            get_file_content(
589                OPTIONS_MANAGER.version_mbn_file_path, os.path.basename(
590                    os.path.join(OPTIONS_MANAGER.target_package_config_dir,
591                                 VERSION_MBN_PATH)))
592        if version_mbn_content is False:
593            UPDATE_LOGGER.print_log(
594                "Get version mbn content failed!",
595                log_type=UPDATE_LOGGER.ERROR_LOG)
596            return False
597        OPTIONS_MANAGER.version_mbn_content = version_mbn_content
598        OPTIONS_MANAGER.board_list_file_path = os.path.join(
599            OPTIONS_MANAGER.target_package_config_dir, BOARD_LIST_PATH)
600        board_list_content = \
601            get_file_content(
602                OPTIONS_MANAGER.board_list_file_path, os.path.basename(
603                    os.path.join(OPTIONS_MANAGER.target_package_config_dir,
604                                 BOARD_LIST_PATH)))
605        if board_list_content is False:
606            UPDATE_LOGGER.print_log(
607                "Get board list content failed!",
608                log_type=UPDATE_LOGGER.ERROR_LOG)
609            return False
610        OPTIONS_MANAGER.board_list_content = board_list_content
611
612    if OPTIONS_MANAGER.xml_path is None:
613        xml_file_path = os.path.join(
614            OPTIONS_MANAGER.target_package_config_dir, XML_FILE_PATH)
615    else:
616        xml_file_path = OPTIONS_MANAGER.xml_path
617
618    # Parse the XML configuration file.
619    head_info_list, component_info_dict, \
620        full_img_list, incremental_img_list, \
621        OPTIONS_MANAGER.target_package_version, \
622        OPTIONS_MANAGER.full_image_path_list = \
623        parse_update_config(xml_file_path)
624    if head_info_list is False or component_info_dict is False or \
625            full_img_list is False or incremental_img_list is False:
626        UPDATE_LOGGER.print_log(
627            "Get parse update config xml failed!",
628            log_type=UPDATE_LOGGER.ERROR_LOG)
629        return False
630    OPTIONS_MANAGER.head_info_list, OPTIONS_MANAGER.component_info_dict, \
631        OPTIONS_MANAGER.full_img_list, OPTIONS_MANAGER.incremental_img_list = \
632        head_info_list, component_info_dict, \
633        full_img_list, incremental_img_list
634    return True
635
636
637def sign_package():
638    return sign_ota_package(
639        OPTIONS_MANAGER.update_package_file_path,
640        OPTIONS_MANAGER.signed_package,
641        OPTIONS_MANAGER.private_key)