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 17""" 18The tool for making updater package. 19 20positional arguments: 21 target_package Target package file path. 22 update_package Update package file path. 23 24optional arguments: 25 -h, --help show this help message and exit 26 -s SOURCE_PACKAGE, --source_package SOURCE_PACKAGE 27 Source package file path. 28 -nz, --no_zip No zip mode, 29 which means to output the update package without zip. 30 -pf PARTITION_FILE, --partition_file PARTITION_FILE 31 Variable partition mode, Partition list file path. 32 -sa {ECC,RSA}, --signing_algorithm {ECC,RSA} 33 The signing algorithms 34 supported by the tool include ['ECC', 'RSA']. 35 -ha {sha256,sha384}, --hash_algorithm {sha256,sha384} 36 The hash algorithms 37 supported by the tool include ['sha256', 'sha384']. 38 -pk PRIVATE_KEY, --private_key PRIVATE_KEY 39 Private key file path. 40 -nl2, --not_l2 Not L2 mode, Distinguish between L1 and L2. 41 -sl {256,384}, --signing_length {256,384} 42 The signing content length 43 supported by the tool include ['256', '384']. 44 -xp, --xml_path XML file path. 45 -sc, --sd_card SD Card mode, Create update package for SD Card. 46 -su, --stream_update Stream update mode, Create update package for stream update. 47 -ab, --ab_partition_update Ab partition update mode, Create update package for ab partition update. 48""" 49import filecmp 50import os 51import sys 52import argparse 53import subprocess 54import tempfile 55import hashlib 56import xmltodict 57import patch_package_process 58import math 59 60 61from gigraph_process import GigraphProcess 62from image_class import FullUpdateImage 63from image_class import IncUpdateImage 64from transfers_manager import TransfersManager 65from log_exception import UPDATE_LOGGER 66from script_generator import PreludeScript 67from script_generator import VerseScript 68from script_generator import RefrainScript 69from script_generator import EndingScript 70from update_package import build_update_package 71from unpack_updater_package import UnpackPackage 72from utils import OPTIONS_MANAGER 73from utils import UPDATER_CONFIG 74from utils import parse_partition_file_xml 75from utils import unzip_package 76from utils import clear_resource 77from utils import PRODUCT 78from utils import XML_FILE_PATH 79from utils import get_update_info 80from utils import SCRIPT_KEY_LIST 81from utils import PER_BLOCK_SIZE 82from utils import E2FSDROID_PATH 83from utils import MAXIMUM_RECURSION_DEPTH 84from utils import VERSE_SCRIPT_EVENT 85from utils import INC_IMAGE_EVENT 86from utils import DIFF_EXE_PATH 87from utils import PARTITION_CHANGE_EVENT 88from utils import DECOUPLED_EVENT 89from utils import get_update_config_softversion 90from vendor_script import create_vendor_script_class 91from create_chunk import CreateChunk 92from concurrent.futures import ThreadPoolExecutor 93 94sys.setrecursionlimit(MAXIMUM_RECURSION_DEPTH) 95 96 97def type_check(arg): 98 """ 99 Argument check, which is used to check whether the specified arg is a file. 100 :param arg: the arg to check 101 :return: Check result, which is False if the arg is invalid. 102 """ 103 if arg is not None and not os.path.exists(arg): 104 UPDATE_LOGGER.print_log( 105 "FileNotFoundError, path: %s" % arg, UPDATE_LOGGER.ERROR_LOG) 106 return False 107 return arg 108 109 110def private_key_check(arg): 111 """ 112 Argument check, which is used to check whether 113 the specified arg is a private_key. 114 :param arg: The arg to check. 115 :return: Check result, which is False if the arg is invalid. 116 """ 117 if arg != "ON_SERVER" and not os.path.isfile(arg): 118 UPDATE_LOGGER.print_log( 119 "FileNotFoundError, path: %s" % arg, UPDATE_LOGGER.ERROR_LOG) 120 return False 121 return arg 122 123 124def check_update_package(arg): 125 """ 126 Argument check, which is used to check whether 127 the update package path exists. 128 :param arg: The arg to check. 129 :return: Check result 130 """ 131 make_dir_path = None 132 if os.path.exists(arg): 133 if os.path.isfile(arg): 134 UPDATE_LOGGER.print_log( 135 "Update package must be a dir path, not a file path. " 136 "path: %s" % arg, UPDATE_LOGGER.ERROR_LOG) 137 return False 138 else: 139 try: 140 UPDATE_LOGGER.print_log( 141 "Update package path does not exist. The dir will be created!" 142 "path: %s" % arg, UPDATE_LOGGER.WARNING_LOG) 143 os.makedirs(arg) 144 make_dir_path = arg 145 except OSError: 146 UPDATE_LOGGER.print_log( 147 "Make update package path dir failed! " 148 "path: %s" % arg, UPDATE_LOGGER.ERROR_LOG) 149 return False 150 if make_dir_path is not None: 151 OPTIONS_MANAGER.make_dir_path = make_dir_path 152 OPTIONS_MANAGER.update_package = arg 153 return arg 154 155 156def unpack_check(arg): 157 """ 158 Argument check, which is used to check whether 159 the update package path exists. 160 :param arg: The arg to check. 161 :return: Check result 162 """ 163 unpack_package = os.path.join(OPTIONS_MANAGER.update_package, arg) 164 if not os.path.isfile(unpack_package): 165 UPDATE_LOGGER.print_log( 166 "FileNotFoundError, path: %s" % unpack_package, UPDATE_LOGGER.ERROR_LOG) 167 OPTIONS_MANAGER.unpack_package_path = None 168 return False 169 OPTIONS_MANAGER.unpack_package_path = unpack_package 170 return arg 171 172 173def create_entrance_args(): 174 """ 175 Arguments for the tool to create an update package 176 :return source_package : source version package 177 target_package : target version package 178 update_package : update package output path 179 no_zip : whether to enable the update package zip function. 180 partition_file : partition table XML file 181 signing_algorithm : signature algorithm (ECC and RSA (default)) 182 private_key : path of the private key file 183 """ 184 parser = OPTIONS_MANAGER.parser 185 parser.description = "Tool for creating update package." 186 parser.add_argument("-unpack", "--unpack_package", type=unpack_check, 187 default=None, help="Unpack updater package.") 188 parser.add_argument("-s", "--source_package", type=type_check, 189 default=None, help="Source package file path.") 190 parser.add_argument("target_package", type=type_check, 191 help="Target package file path.") 192 parser.add_argument("update_package", type=check_update_package, 193 help="Update package file path.") 194 parser.add_argument("-nz", "--no_zip", action='store_true', 195 help="No zip mode, Output update package without zip.") 196 parser.add_argument("-pf", "--partition_file", default=None, 197 help="Variable partition mode, " 198 "Partition list file path.") 199 parser.add_argument("-sa", "--signing_algorithm", default='RSA', 200 choices=['ECC', 'RSA'], 201 help="The signing algorithm " 202 "supported by the tool include ['ECC', 'RSA'].") 203 parser.add_argument("-ha", "--hash_algorithm", default='sha256', 204 choices=['sha256', 'sha384'], 205 help="The hash algorithm " 206 "supported by the tool include " 207 "['sha256', 'sha384'].") 208 parser.add_argument("-pk", "--private_key", type=private_key_check, 209 default=None, help="Private key file path.") 210 parser.add_argument("-nl2", "--not_l2", action='store_true', 211 help="Not L2 mode, Distinguish between L1 and L2.") 212 parser.add_argument("-sl", "--signing_length", default='256', 213 choices=['256', '384'], 214 help="The signing content length " 215 "supported by the tool include " 216 "['256', '384'].") 217 parser.add_argument("-xp", "--xml_path", type=private_key_check, 218 default=None, help="XML file path.") 219 parser.add_argument("-sc", "--sd_card", action='store_true', 220 help="SD Card mode, " 221 "Create update package for SD Card.") 222 parser.add_argument("-su", "--stream_update", action='store_true', 223 help="Stream update mode, " 224 "Create update package for stream update.") 225 parser.add_argument("-ab", "--ab_partition_update", action='store_true', 226 help="Ab partition update mode, " 227 "Create update package for ab partition update.") 228 229 230def parse_args(): 231 args = OPTIONS_MANAGER.parser.parse_args() 232 OPTIONS_MANAGER.source_package = args.source_package 233 OPTIONS_MANAGER.target_package = args.target_package 234 OPTIONS_MANAGER.update_package = args.update_package 235 OPTIONS_MANAGER.no_zip = args.no_zip 236 OPTIONS_MANAGER.partition_file = args.partition_file 237 OPTIONS_MANAGER.signing_algorithm = args.signing_algorithm 238 OPTIONS_MANAGER.hash_algorithm = args.hash_algorithm 239 OPTIONS_MANAGER.private_key = args.private_key 240 OPTIONS_MANAGER.not_l2 = args.not_l2 241 OPTIONS_MANAGER.signing_length = int(args.signing_length) 242 OPTIONS_MANAGER.xml_path = args.xml_path 243 OPTIONS_MANAGER.sd_card = args.sd_card 244 OPTIONS_MANAGER.stream_update = args.stream_update 245 OPTIONS_MANAGER.ab_partition_update = args.ab_partition_update 246 247 248def get_args(): 249 ret_args = \ 250 [OPTIONS_MANAGER.source_package, 251 OPTIONS_MANAGER.target_package, 252 OPTIONS_MANAGER.update_package, 253 OPTIONS_MANAGER.no_zip, 254 OPTIONS_MANAGER.not_l2, 255 OPTIONS_MANAGER.partition_file, 256 OPTIONS_MANAGER.signing_algorithm, 257 OPTIONS_MANAGER.hash_algorithm, 258 OPTIONS_MANAGER.private_key] 259 return ret_args 260 261 262def get_script_obj(): 263 """ 264 Obtain Opera script object 265 :return: 266 """ 267 script_obj_list = create_vendor_script_class() 268 if script_obj_list == [None] * len(SCRIPT_KEY_LIST): 269 prelude_script = PreludeScript() 270 verse_script = VerseScript() 271 refrain_script = RefrainScript() 272 ending_script = EndingScript() 273 274 generate_verse_script = \ 275 OPTIONS_MANAGER.init.invoke_event(VERSE_SCRIPT_EVENT) 276 if generate_verse_script: 277 verse_script = generate_verse_script() 278 else: 279 UPDATE_LOGGER.print_log( 280 "Get vendor extension object completed!" 281 "The vendor extension script will be generated.") 282 prelude_script = script_obj_list[0] 283 verse_script = script_obj_list[1] 284 refrain_script = script_obj_list[2] 285 ending_script = script_obj_list[3] 286 return prelude_script, verse_script, refrain_script, ending_script 287 288 289def get_source_package_path(source_package): 290 """ 291 get_source_package_path. 292 :param source_package: source package path 293 :return: 294 """ 295 if os.path.isdir(source_package): 296 OPTIONS_MANAGER.source_package_dir = source_package 297 elif source_package.endswith('.zip'): 298 # Decompress the source package. 299 tmp_dir_obj, unzip_dir = unzip_package(source_package) 300 if tmp_dir_obj is False or unzip_dir is False: 301 clear_resource(err_clear=True) 302 return False 303 OPTIONS_MANAGER.source_package_dir = unzip_dir 304 OPTIONS_MANAGER.source_package_temp_obj = tmp_dir_obj 305 else: 306 UPDATE_LOGGER.print_log("Input Update Package type exception!" 307 "path: %s" % source_package, UPDATE_LOGGER.ERROR_LOG) 308 clear_resource(err_clear=True) 309 return False 310 return True 311 312 313def check_incremental_args(no_zip, partition_file, source_package, 314 incremental_img_list): 315 """ 316 When the incremental list is not empty, incremental processing is required. 317 In this case, check related arguments. 318 :param no_zip: no zip mode 319 :param partition_file: 320 :param source_package: 321 :param incremental_img_list: 322 :return: 323 """ 324 if "boot" in incremental_img_list: 325 UPDATE_LOGGER.print_log("boot cannot be incrementally processed!", UPDATE_LOGGER.ERROR_LOG) 326 clear_resource(err_clear=True) 327 return False 328 if source_package is None: 329 UPDATE_LOGGER.print_log("The source package is missing, " 330 "cannot be incrementally processed!", UPDATE_LOGGER.ERROR_LOG) 331 clear_resource(err_clear=True) 332 return False 333 if no_zip: 334 UPDATE_LOGGER.print_log("No ZIP mode, cannot be incrementally processed!", UPDATE_LOGGER.ERROR_LOG) 335 clear_resource(err_clear=True) 336 return False 337 if partition_file is not None: 338 UPDATE_LOGGER.print_log("Partition file is not None, " 339 "cannot be incrementally processed!", UPDATE_LOGGER.ERROR_LOG) 340 clear_resource(err_clear=True) 341 return False 342 343 if not get_source_package_path(source_package): 344 return False 345 partition_change = OPTIONS_MANAGER.init.invoke_event(PARTITION_CHANGE_EVENT) 346 if callable(partition_change) and partition_change() is False: 347 return False 348 UPDATE_LOGGER.print_log("Partition interception check finish.", UPDATE_LOGGER.INFO_LOG) 349 xml_path = '' 350 if OPTIONS_MANAGER.source_package_dir is not False: 351 xml_path = os.path.join(OPTIONS_MANAGER.source_package_dir, UPDATER_CONFIG, XML_FILE_PATH) 352 if OPTIONS_MANAGER.source_package_dir is False: 353 OPTIONS_MANAGER.source_package_temp_obj = None 354 OPTIONS_MANAGER.source_package_dir = None 355 if os.path.exists(xml_path): 356 with open(xml_path, 'r') as xml_file: 357 xml_str = xml_file.read() 358 else: 359 UPDATE_LOGGER.print_log("XML file does not exist! xml path: %s" % 360 xml_path, UPDATE_LOGGER.ERROR_LOG) 361 return False 362 363 xml_content_dict = xmltodict.parse(xml_str, encoding='utf-8') 364 package_dict = xml_content_dict.get('package', {}) 365 get_update_config_softversion(OPTIONS_MANAGER.source_package_dir, package_dict.get('head', {})) 366 head_dict = package_dict.get('head', {}).get('info') 367 OPTIONS_MANAGER.source_package_version = head_dict.get("@softVersion") 368 no_need_version_check_res = OPTIONS_MANAGER.init.invoke_event(DECOUPLED_EVENT) 369 if no_need_version_check_res is False: 370 if check_package_version(OPTIONS_MANAGER.target_package_version, 371 OPTIONS_MANAGER.source_package_version) is False: 372 clear_resource(err_clear=True) 373 return False 374 return True 375 376 377def check_userdata_image(): 378 """ 379 Check the userdata image. Updating this image is prohibited. 380 :return: 381 """ 382 if 'userdata' in OPTIONS_MANAGER.full_img_list or \ 383 'userdata' in OPTIONS_MANAGER.incremental_img_list: 384 UPDATE_LOGGER.print_log( 385 "userdata image does not participate in update!" 386 "Please check xml config, path: %s!" % 387 os.path.join(OPTIONS_MANAGER.target_package_config_dir, 388 XML_FILE_PATH), 389 UPDATE_LOGGER.ERROR_LOG) 390 clear_resource(err_clear=True) 391 return False 392 return True 393 394 395def check_images_list(): 396 """ 397 Check full_img_list and incremental_img_list. 398 If their lengths are 0, an error will be logged. 399 :return: 400 """ 401 if len(OPTIONS_MANAGER.full_img_list) == 0 and \ 402 len(OPTIONS_MANAGER.incremental_img_list) == 0: 403 UPDATE_LOGGER.print_log( 404 "The image list is empty!" 405 "Please check xml config, path: %s!" % 406 os.path.join(OPTIONS_MANAGER.target_package_config_dir, 407 XML_FILE_PATH), 408 UPDATE_LOGGER.ERROR_LOG) 409 clear_resource(err_clear=True) 410 return False 411 return True 412 413 414def check_target_package_path(target_package): 415 """ 416 Check the target_package path. 417 :param target_package: target package path 418 :return: 419 """ 420 if os.path.isdir(target_package): 421 OPTIONS_MANAGER.target_package_dir = target_package 422 temp_dir_list = os.listdir(target_package) 423 if UPDATER_CONFIG in temp_dir_list: 424 OPTIONS_MANAGER.target_package_config_dir = \ 425 os.path.join(target_package, UPDATER_CONFIG) 426 else: 427 UPDATE_LOGGER.print_log( 428 "Exception's target package path! path: %s" % 429 target_package, UPDATE_LOGGER.ERROR_LOG) 430 return False 431 elif target_package.endswith('.zip'): 432 # Decompress the target package. 433 tmp_dir_obj, unzip_dir = unzip_package(target_package) 434 if tmp_dir_obj is False or unzip_dir is False: 435 clear_resource(err_clear=True) 436 return False 437 OPTIONS_MANAGER.target_package_dir = unzip_dir 438 OPTIONS_MANAGER.target_package_temp_obj = tmp_dir_obj 439 OPTIONS_MANAGER.target_package_config_dir = \ 440 os.path.join(unzip_dir, UPDATER_CONFIG) 441 else: 442 UPDATE_LOGGER.print_log( 443 "Input Update Package type exception! path: %s" % 444 target_package, UPDATE_LOGGER.ERROR_LOG) 445 clear_resource(err_clear=True) 446 return False 447 return True 448 449 450def check_miss_private_key(private_key): 451 """ 452 Check private key. 453 :param private_key: 454 :return: 455 """ 456 if private_key is None: 457 UPDATE_LOGGER.print_log( 458 "Private key is None, update package cannot be signed! " 459 "Please specify the signature private key by -pk.", 460 UPDATE_LOGGER.ERROR_LOG) 461 clear_resource(err_clear=True) 462 return False 463 return True 464 465 466def check_package_version(target_ver, source_ver): 467 """ 468 target_ver: target version 469 source_ver: source version 470 return: 471 """ 472 try: 473 target_num = ''.join(target_ver.split(' ')[-1].replace('.', '')) 474 source_num = ''.join(source_ver.split(' ')[-1].replace('.', '')) 475 if int(target_num) <= int(source_num): 476 UPDATE_LOGGER.print_log( 477 'Target package version %s <= Source package version!' 478 'Unable to make updater package!', 479 UPDATE_LOGGER.ERROR_LOG) 480 return False 481 except ValueError: 482 UPDATE_LOGGER.print_log('your package version number is not compliant.' 483 'Please check your package version number!', 484 UPDATE_LOGGER.ERROR_LOG) 485 return False 486 return True 487 488 489def generate_image_map_file(image_path, map_path, image_name): 490 """ 491 :param image_path: image path 492 :param map_path: image map file path 493 :param image_name: image name 494 :return: 495 """ 496 if not os.path.exists(image_path): 497 UPDATE_LOGGER.print_log("The source %s.img file is missing from the" 498 "source package, cannot be incrementally processed. " 499 % image_name, UPDATE_LOGGER.ERROR_LOG) 500 return False 501 502 cmd = [E2FSDROID_PATH, "-B", map_path, "-a", "/%s" % image_name, image_path, "-e"] 503 res = subprocess.Popen(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 504 _, err = res.communicate(timeout=300) 505 if res.returncode != 0: 506 UPDATE_LOGGER.print_log("%s.map generate failed, reason:%s" % 507 (image_name, err.decode()), UPDATE_LOGGER.ERROR_LOG) 508 return False 509 UPDATE_LOGGER.print_log("%s.map generate success" % image_name, UPDATE_LOGGER.INFO_LOG) 510 return True 511 512 513def get_file_sha256(update_package): 514 sha256obj = hashlib.sha256() 515 maxbuf = 8192 516 with open(update_package, 'rb') as package_file: 517 while True: 518 buf = package_file.read(maxbuf) 519 if not buf: 520 break 521 sha256obj.update(buf) 522 hash_value = sha256obj.hexdigest() 523 return str(hash_value).upper() 524 525 526def write_image_patch_script(partition, src_image_path, tgt_image_path, 527 script_check_cmd_list, script_write_cmd_list, verse_script): 528 """ 529 Add command content to the script. 530 :param partition: image name 531 :param script_check_cmd_list: incremental check command list 532 :param script_write_cmd_list: incremental write command list 533 :param verse_script: verse script object 534 :return: 535 """ 536 src_sha = get_file_sha256(src_image_path) 537 src_size = os.path.getsize(src_image_path) 538 tgt_sha = get_file_sha256(tgt_image_path) 539 tgt_size = os.path.getsize(tgt_image_path) 540 541 sha_check_cmd = verse_script.image_sha_check(partition, 542 src_size, src_sha, tgt_size, tgt_sha) 543 544 first_block_check_cmd = verse_script.first_block_check(partition) 545 546 abort_cmd = verse_script.abort(partition) 547 548 cmd = 'if ({sha_check_cmd} != 0)' \ 549 '{{\n {abort_cmd}}}\n'.format( 550 sha_check_cmd=sha_check_cmd, 551 abort_cmd=abort_cmd) 552 553 script_check_cmd_list.append(cmd) 554 555 image_patch_cmd = verse_script.image_patch(partition, os.path.getsize(src_image_path), 556 get_file_sha256(src_image_path), os.path.getsize(tgt_image_path), 557 get_file_sha256(tgt_image_path)) 558 559 cmd = '%s_WRITE_FLAG%s' % (partition, image_patch_cmd) 560 script_write_cmd_list.append(cmd) 561 return True 562 563 564def increment_image_diff_processing( 565 partition, src_image_path, tgt_image_path, 566 script_check_cmd_list, script_write_cmd_list, verse_script): 567 """ 568 Incremental image processing 569 :param verse_script: verse script 570 :param incremental_img_list: incremental image list 571 :param source_package_dir: source package path 572 :param target_package_dir: target package path 573 :return: 574 """ 575 patch_file_obj = tempfile.NamedTemporaryFile( 576 prefix="%s_patch.dat-" % partition, mode='wb') 577 OPTIONS_MANAGER.incremental_image_file_obj_dict[partition] = patch_file_obj 578 cmd = [DIFF_EXE_PATH] 579 580 cmd.extend(['-s', src_image_path, '-d', tgt_image_path, 581 '-p', patch_file_obj.name, '-l', '4096']) 582 sub_p = subprocess.Popen(cmd, stdout=subprocess.PIPE, 583 stderr=subprocess.STDOUT) 584 try: 585 output, _ = sub_p.communicate(timeout=1800) 586 except subprocess.TimeoutExpired: 587 sub_p.kill() 588 589 sub_p.wait() 590 if sub_p.returncode != 0: 591 raise ValueError(output) 592 593 return write_image_patch_script(partition, src_image_path, tgt_image_path, 594 script_check_cmd_list, script_write_cmd_list, verse_script) 595 596 597def copy_in_ab_process(patch_process, src_image_class, need_copy_blocks, 598 non_continuous_blocks, each_img): 599 """ 600 Handle the process of copying blocks in an AB partition during an update. 601 :param patch_process: The patch process object responsible for managing the update. 602 :param src_image_class: The source image class containing metadata about the image. 603 :param need_copy_blocks: A list to store blocks that need to be copied. 604 :param non_continuous_blocks: A list to store blocks that are non-continuous. 605 :param each_img: The current image being processed. 606 :return: 607 """ 608 if OPTIONS_MANAGER.ab_partition_update: 609 chunk_pkgdiff_list = patch_process.get_chunk_pkgdiff_list() 610 transfer_content = patch_process.get_transfer_content_in_chunk() 611 chunk_new_list = patch_process.get_chunk_new_list() 612 613 # Parase transfer_content, record all blocks involved in the content 614 no_copy_blocks_in_ab, transfer_content_no_startoffert = parse_transfer_content(transfer_content) 615 616 # Find the blocks to copy, total blocks includeing zero, excluding total_blocks 617 no_copy_blocks_set = set(no_copy_blocks_in_ab) 618 619 def process_block(src_block): 620 if src_block in no_copy_blocks_set: 621 return None, src_block # Not to copy 622 return src_block, None # To copy 623 with ThreadPoolExecutor() as executor: 624 results = executor.map(process_block, range(src_image_class.total_blocks)) 625 626 for to_copy, not_to_copy in results: 627 if to_copy is not None: 628 need_copy_blocks.append(to_copy) 629 if not_to_copy is not None: 630 non_continuous_blocks.append(not_to_copy) 631 # Sort blocks consecutively 632 if len(need_copy_blocks): 633 group_numbers_list = group_numbers(need_copy_blocks) 634 else: 635 group_numbers_list = [] 636 print(f'there is no copy blocks in image {each_img} !') 637 # Add the copy command for ab partition synchronization 638 transfer_content = patch_process.add_ab_copy_content(len(need_copy_blocks), 639 group_numbers_list, transfer_content_no_startoffert) 640 OPTIONS_MANAGER.image_transfer_dict_contents[each_img] = transfer_content 641 OPTIONS_MANAGER.image_patch_dic[each_img] = chunk_pkgdiff_list 642 OPTIONS_MANAGER.image_new_dic[each_img] = chunk_new_list 643 644 645def add_incremental_command(verse_script, script_check_cmd_list, script_write_cmd_list): 646 """ 647 add command for increment_image_progressing to verse_script 648 :param verse_script: verse script 649 :param script_check_cmd_list: verse_script check command list 650 :param script_write_cmd_list: verse_script write command list 651 :return: 652 """ 653 verse_script.add_command("\n# ---- start incremental check here ----\n") 654 for each_check_cmd in script_check_cmd_list: 655 verse_script.add_command(each_check_cmd) 656 verse_script.add_command("\n# ---- start incremental write here ----\n") 657 for each_write_cmd in script_write_cmd_list: 658 verse_script.add_command(each_write_cmd) 659 660 661def increment_image_processing( 662 verse_script, incremental_img_list, source_package_dir, 663 target_package_dir): 664 """ 665 Incremental image processing 666 :param verse_script: verse script 667 :param incremental_img_list: incremental image list 668 :param source_package_dir: source package path 669 :param target_package_dir: target package path 670 :return: 671 """ 672 script_check_cmd_list = [] 673 script_write_cmd_list = [] 674 patch_process = None 675 676 for each_img_name in OPTIONS_MANAGER.incremental_img_name_list: 677 each_img = each_img_name[:-4] 678 each_src_image_path = os.path.join(source_package_dir, '%s.img' % each_img) 679 each_src_map_path = os.path.join(source_package_dir, '%s.map' % each_img) 680 each_tgt_image_path = os.path.join(target_package_dir, '%s.img' % each_img) 681 each_tgt_map_path = os.path.join(target_package_dir, '%s.map' % each_img) 682 683 # This will store tuples of (start, end, length) for continuous ranges 684 non_continuous_blocks = [] 685 need_copy_blocks = [] 686 check_make_map_path(each_img) 687 688 # Call the new function to process image maps 689 continue_processing, src_generate_map, tgt_generate_map = process_image_maps( 690 each_img, each_src_image_path, each_src_map_path, 691 each_tgt_image_path, each_tgt_map_path) 692 if not continue_processing: 693 continue 694 695 # get the large of target image 696 if not get_large_of_target_image(each_tgt_image_path, each_img): 697 return False 698 699 if not src_generate_map or not tgt_generate_map: 700 if not handle_no_map_generation(each_img, each_src_image_path, each_tgt_image_path, \ 701 script_check_cmd_list, script_write_cmd_list, verse_script): 702 return False 703 continue 704 705 inc_image = OPTIONS_MANAGER.init.invoke_event(INC_IMAGE_EVENT) 706 if inc_image: 707 src_image_class, tgt_image_class = inc_image(each_src_image_path, each_src_map_path, 708 each_tgt_image_path, each_tgt_map_path) 709 else: 710 src_image_class = IncUpdateImage(each_src_image_path, each_src_map_path) 711 tgt_image_class = IncUpdateImage(each_tgt_image_path, each_tgt_map_path) 712 713 transfers_manager = TransfersManager(each_img, tgt_image_class, src_image_class) 714 transfers_manager.find_process_needs() 715 actions_list = transfers_manager.get_action_list() 716 717 graph_process = GigraphProcess(actions_list, src_image_class, tgt_image_class) 718 # Streaming update does not need to handle stash and free commands 719 if not OPTIONS_MANAGER.ab_partition_update: 720 graph_process.stash_process() 721 722 actions_list = graph_process.actions_list 723 patch_process = patch_package_process.PatchProcess(each_img, tgt_image_class, src_image_class, actions_list) 724 725 patch_process.patch_process(each_tgt_image_path) 726 727 # Add copy command for ab partition 728 copy_in_ab_process(patch_process, src_image_class, need_copy_blocks, 729 non_continuous_blocks, each_img) 730 731 patch_process.write_script(each_img, script_check_cmd_list, script_write_cmd_list, verse_script) 732 OPTIONS_MANAGER.incremental_block_file_obj_dict[each_img] = patch_process.package_patch_zip 733 if not OPTIONS_MANAGER.stream_update: 734 if not check_patch_file(patch_process): 735 UPDATE_LOGGER.print_log('Verify the incremental result failed!', UPDATE_LOGGER.ERROR_LOG) 736 raise RuntimeError 737 738 add_incremental_command(verse_script, script_check_cmd_list, script_write_cmd_list) 739 return True 740 741 742def get_large_of_target_image(each_tgt_image_path, each_img): 743 """ 744 Reads the target image content and stores it in OPTIONS_MANAGER.diff_image_new_data. 745 746 :param each_tgt_image_path: The path to the target image. 747 :param each_img: The name of the image (without extension). 748 :return: True if successful, False otherwise. 749 """ 750 try: 751 with open(each_tgt_image_path, 'rb') as f: 752 target_content = f.read() 753 OPTIONS_MANAGER.diff_image_new_data[each_img] = target_content 754 return True 755 except Exception as e: 756 print(f"Error reading target image {each_img}: {e}") 757 return False 758 759 760def process_image_maps(each_img, each_src_image_path, each_src_map_path, 761 each_tgt_image_path, each_tgt_map_path): 762 """ 763 Process source and target image maps for incremental updates. 764 765 :param each_img: The image name without extension 766 :param each_src_image_path: Path to the source image 767 :param each_src_map_path: Path to the source map 768 :param each_tgt_image_path: Path to the target image 769 :param each_tgt_map_path: Path to the target map 770 :return: A tuple (continue_processing, src_generate_map, tgt_generate_map) 771 - continue_processing: Whether to continue processing the current image 772 - src_generate_map: Whether the source map was successfully generated 773 - tgt_generate_map: Whether the target map was successfully generated 774 """ 775 # Check if source and target images are identical 776 if filecmp.cmp(each_src_image_path, each_tgt_image_path): 777 UPDATE_LOGGER.print_log( 778 "Source Image is the same as Target Image! src image path: %s, tgt image path: %s" 779 % (each_src_image_path, each_tgt_image_path), 780 UPDATE_LOGGER.INFO_LOG) 781 OPTIONS_MANAGER.incremental_img_list.remove(each_img) 782 return False, False, False # Stop further processing for this image 783 784 # Initialize map generation flags 785 src_generate_map = True 786 tgt_generate_map = True 787 788 # Generate source map if it does not exist 789 if not os.path.exists(each_src_map_path): 790 src_generate_map = generate_image_map_file(each_src_image_path, each_src_map_path, each_img) 791 792 # Generate target map if it does not exist 793 if not os.path.exists(each_tgt_map_path): 794 tgt_generate_map = generate_image_map_file(each_tgt_image_path, each_tgt_map_path, each_img) 795 796 return True, src_generate_map, tgt_generate_map 797 798 799def handle_no_map_generation(each_img, each_src_image_path, each_tgt_image_path, script_check_cmd_list, script_write_cmd_list, verse_script): 800 if OPTIONS_MANAGER.stream_update: 801 print(f'do no map process:{each_img}') 802 OPTIONS_MANAGER.no_map_image_exist = True 803 OPTIONS_MANAGER.no_map_file_list.append(each_img) 804 # Directly cut the target image into new commands 805 if not os.path.exists(each_tgt_image_path): 806 UPDATE_LOGGER.print_log( 807 "The file is missing " 808 "from the target package, " 809 "the component: %s cannot be full update processed. " % 810 each_tgt_image_path) 811 return False 812 with open(each_tgt_image_path, 'rb') as f: 813 tartget_no_map_content = f.read() 814 chunk, block_sets = split_image_file(each_img, tartget_no_map_content) 815 OPTIONS_MANAGER.image_chunk[each_img] = chunk 816 OPTIONS_MANAGER.image_block_sets[each_img] = block_sets 817 return True 818 # If it is not a streaming update and cannot generate map file,directly diff the image 819 elif increment_image_diff_processing(each_img, each_src_image_path, each_tgt_image_path, 820 script_check_cmd_list, script_write_cmd_list, verse_script) is True: 821 return True 822 UPDATE_LOGGER.print_log("increment_image_diff_processing %s failed" % each_img) 823 clear_resource(err_clear=True) 824 return False 825 826 827def check_patch_file(patch_process): 828 new_dat_file_obj, patch_dat_file_obj, transfer_list_file_obj = \ 829 patch_process.package_patch_zip.get_file_obj() 830 with open(transfer_list_file_obj.name) as f_t: 831 num = 0 832 diff_str = None 833 diff_num = 0 834 for line in f_t: 835 if line.startswith('new '): 836 each_line_list = \ 837 line.strip().replace("new ", "").split(",")[1:] 838 for idx in range(0, len(each_line_list), 2): 839 num += \ 840 int(each_line_list[idx + 1]) - int(each_line_list[idx]) 841 continue 842 if line.startswith('bsdiff ') or line.startswith('pkgdiff '): 843 diff_str = line 844 if diff_str: 845 diff_list = diff_str.split('\n')[0].split(' ') 846 diff_num = int(diff_list[1]) + int(diff_list[2]) 847 check_flag = \ 848 (os.path.getsize(new_dat_file_obj.name) == num * PER_BLOCK_SIZE) and \ 849 (os.path.getsize(patch_dat_file_obj.name) == diff_num) 850 return check_flag 851 852 853def check_make_map_path(each_img): 854 """ 855 If env does not exist, the command for map generation does not exist 856 in the environment variable, and False will be returned. 857 """ 858 try: 859 cmd = [E2FSDROID_PATH, " -h"] 860 subprocess.Popen(cmd, shell=False, stdout=subprocess.PIPE, 861 stderr=subprocess.STDOUT) 862 except FileNotFoundError: 863 UPDATE_LOGGER.print_log( 864 "Command not found, need check the env! " 865 "Make %s.map failed!" % each_img, 866 UPDATE_LOGGER.ERROR_LOG) 867 clear_resource(err_clear=True) 868 raise RuntimeError 869 return True 870 871 872def incremental_processing(no_zip, partition_file, source_package, 873 verse_script): 874 """ 875 Incremental processing. 876 :param no_zip: no zip mode 877 :param partition_file: partition xml file path 878 :param source_package: source package path 879 :param verse_script: verse script obj 880 :return : processing result 881 """ 882 if len(OPTIONS_MANAGER.incremental_img_list) != 0: 883 if check_incremental_args(no_zip, partition_file, source_package, 884 OPTIONS_MANAGER.incremental_img_list) \ 885 is False: 886 return False 887 if increment_image_processing( 888 verse_script, OPTIONS_MANAGER.incremental_img_list, 889 OPTIONS_MANAGER.source_package_dir, 890 OPTIONS_MANAGER.target_package_dir) is False: 891 return False 892 else: 893 if source_package is not None: 894 UPDATE_LOGGER.print_log( 895 "There is no incremental image, " 896 "the - S parameter is not required!", 897 UPDATE_LOGGER.ERROR_LOG) 898 raise RuntimeError 899 900 901def check_args(private_key, source_package, target_package, update_package): 902 """ 903 Input args check. 904 :param private_key: private key path 905 :param source_package: source package path 906 :param target_package: target package path 907 :param update_package: output package path 908 :return : Judgment result 909 """ 910 if source_package is False or private_key is False or \ 911 target_package is False or update_package is False: 912 return False 913 if check_miss_private_key(private_key) is False: 914 return False 915 if check_target_package_path(target_package) is False: 916 return False 917 if get_update_info() is False: 918 return False 919 if check_images_list() is False: 920 return False 921 return True 922 923 924def unpack_package_processing(): 925 if OPTIONS_MANAGER.unpack_package_path: 926 package = UnpackPackage() 927 if not package.unpack_package(): 928 UPDATE_LOGGER.print_log("Unpack update package.bin failed!", UPDATE_LOGGER.ERROR_LOG) 929 clear_resource(err_clear=True) 930 sys.exit(1) 931 UPDATE_LOGGER.print_log("Unpack update package.bin success!") 932 clear_resource() 933 sys.exit(0) 934 935 936def group_numbers(arr): 937 """ 938 Groups and sorts a list of consecutive numbers, returning a list of block ranges. 939 :param arr: A list of integers representing block numbers. 940 :return: A list of integers representing the start and end of each block range. 941 """ 942 result = [] 943 current_group = [arr[0]] 944 transfer_content_blocks = [] 945 946 for i in range(1, len(arr)): 947 if arr[i] == arr[i - 1] + 1: 948 current_group.append(arr[i]) 949 else: 950 result.append({ 951 'min': min(current_group), 952 'max': max(current_group), 953 'length': len(current_group) 954 }) 955 transfer_content_blocks.append(min(current_group)) 956 transfer_content_blocks.append(max(current_group) + 1) 957 958 current_group = [arr[i]] 959 960 961 result.append({ 962 'min': min(current_group), 963 'max': max(current_group), 964 'length': len(current_group) 965 }) 966 transfer_content_blocks.append(min(current_group)) 967 transfer_content_blocks.append(max(current_group) + 1) 968 969 return transfer_content_blocks 970 971 972def parse_transfer_content(content): 973 """ 974 Parses the transfer content to extract block information and modified lines. 975 :param content: The string content to be parsed, typically containing block data. 976 :return: A tuple containing: 977 - all_blocks: A list of all extracted block numbers. 978 - modified_content: A modified version of the original content with adjustments made. 979 """ 980 all_blocks = [] 981 modified_lines = [] 982 lines = content.splitlines() 983 for line in lines[4:]: 984 if 'pkgdiff' in line: 985 modified_line, blocks = handle_pkgdiff(line) 986 modified_lines.append(modified_line) 987 all_blocks.extend(blocks) 988 elif 'zero' in line or 'erase' in line: 989 blocks = handle_zero_erase(line) 990 all_blocks.extend(blocks) 991 modified_lines.append(line) 992 elif 'move' in line or 'new' in line: 993 blocks = handle_move_new(line) 994 all_blocks.extend(blocks) 995 modified_lines.append(line) 996 elif 'free' in line: 997 print('no add free cmd') 998 modified_lines.append(line) 999 else: 1000 raise ValueError(f"no parse transfer_list info, got {lines}.") 1001 modified_content = '\n'.join(lines[:4] + modified_lines) 1002 return all_blocks, modified_content 1003 1004 1005def handle_pkgdiff(line): 1006 """ 1007 Handles the 'pkgdiff' line type. 1008 :param line: The line to process. 1009 :return: A tuple containing the modified line and the list of blocks. 1010 """ 1011 parts = line.replace(',', ' ').split() 1012 # Change the start offset address of pkgdiff to all zeros 1013 modified_line = line.replace(line.split()[1], '0', 1) 1014 length = int(parts[5]) 1015 if length % 2 != 0: 1016 raise ValueError(f"Length must be even, got {length}.") 1017 # Extract the range values of a pair of blocks 1018 pairs = list(map(int, parts[6:length + 6])) 1019 blocks = [] 1020 # List all blocks in a single pair of block range values 1021 for i in range(0, len(pairs), 2): 1022 start = pairs[i] 1023 print(f'start:{start}') 1024 end = pairs[i + 1] 1025 print(f'end:{end}') 1026 blocks.extend(range(start, end)) 1027 print(f'len(blocks):{len(blocks)}') 1028 return modified_line, blocks 1029 1030 1031def handle_zero_erase(line): 1032 """ 1033 Handles the 'zero' or 'erase' line type. 1034 :param line: The line to process. 1035 :return: The list of blocks extracted from the line. 1036 """ 1037 parts = line.replace(',', ' ').split() 1038 length = int(parts[1]) 1039 print(f'lenght:{length}') 1040 if length % 2 != 0: 1041 raise ValueError(f"Length must be even, got {length}.") 1042 pairs = list(map(int, parts[2:length + 2])) 1043 blocks = [] 1044 for i in range(0, len(pairs), 2): 1045 start = pairs[i] 1046 print(f'start:{start}') 1047 end = pairs[i + 1] 1048 print(f'end:{end}') 1049 blocks.extend(range(start, end)) 1050 print(f'len(blocks):{len(blocks)}') 1051 return blocks 1052 1053 1054def handle_move_new(line): 1055 """ 1056 Handles the 'move' or 'new' line type. 1057 :param line: The line to process. 1058 :return: The list of blocks extracted from the line. 1059 """ 1060 parts = line.replace(',', ' ').split() 1061 length = int(parts[2]) 1062 print(f'lenght:{length}') 1063 if length % 2 != 0: 1064 raise ValueError(f"Length must be even, got {length}.") 1065 pairs = list(map(int, parts[3:length + 3])) 1066 blocks = [] 1067 for i in range(0, len(pairs), 2): 1068 start = pairs[i] 1069 print(f'start:{start}') 1070 end = pairs[i + 1] 1071 print(f'end:{end}') 1072 blocks.extend(range(start, end)) 1073 print(f'len(blocks):{len(blocks)}') 1074 return blocks 1075 1076 1077def split_image_file(each_img, full_image_data): 1078 """ 1079 Splits the full image data into smaller chunks. 1080 :param each_img: The image to be split (not used in the current implementation). 1081 :param full_image_data: The complete image data to be split. 1082 :return: A tuple containing two lists: 1083 - chunks: A list of data chunks. 1084 - block_sets: A list of corresponding block sets for each chunk. 1085 """ 1086 # get the total block number of the image 1087 total_blocks = math.ceil(len(full_image_data) / 4096) 1088 print(f'total tgt blocks:{total_blocks}') 1089 # step 1:get the total size of the image data 1090 max_chunk_size = OPTIONS_MANAGER.chunk_limit * 4096 1091 total_size = len(full_image_data) 1092 print(f'total size:{total_size}') 1093 chunks = [] 1094 block_sets = [] 1095 # step 2:cut the image data into fixed block size 1096 num = math.ceil(total_size / max_chunk_size) 1097 print(f'num:{num}') 1098 for i in range(num): 1099 start_index = i * max_chunk_size 1100 end_index = min(start_index + max_chunk_size, total_size) 1101 chunk = full_image_data[start_index:end_index] 1102 # step 3:record the corresponding block set 1103 block_set = list(range(math.ceil(start_index / 4096), 1104 math.ceil(end_index / 4096))) 1105 chunks.append(chunk) 1106 block_sets.append(block_set) 1107 print(f"block {i + 1}: size = {len(chunk)} bytes, block sets = {block_set}") 1108 print(f'total size:{total_size}') 1109 print(f'total tgt blocks:{total_blocks}') 1110 return chunks, block_sets 1111 1112 1113create_entrance_args() 1114 1115 1116def main(): 1117 """ 1118 Entry function. 1119 """ 1120 parse_args() 1121 1122 OPTIONS_MANAGER.product = PRODUCT 1123 1124 source_package, target_package, update_package, no_zip, not_l2, \ 1125 partition_file, signing_algorithm, hash_algorithm, private_key = get_args() 1126 if not_l2: 1127 no_zip = True 1128 1129 # Unpack updater package 1130 unpack_package_processing() 1131 1132 if OPTIONS_MANAGER.sd_card: 1133 if source_package is not None or OPTIONS_MANAGER.xml_path is not None or partition_file is not None: 1134 UPDATE_LOGGER.print_log("SDCard updater:-S/-xp/-pf parameter is not required!", UPDATE_LOGGER.ERROR_LOG) 1135 raise RuntimeError 1136 if check_args(private_key, source_package, target_package, update_package) is False: 1137 clear_resource(err_clear=True) 1138 return 1139 1140 if not OPTIONS_MANAGER.sd_card: 1141 if check_userdata_image() is False: 1142 clear_resource(err_clear=True) 1143 return 1144 1145 # Create a Script object. 1146 prelude_script, verse_script, refrain_script, ending_script = get_script_obj() 1147 1148 # Create partition. 1149 if partition_file is not None: 1150 verse_script.add_command("\n# ---- do updater partitions ----\n") 1151 updater_partitions_cmd = verse_script.updater_partitions() 1152 verse_script.add_command(updater_partitions_cmd) 1153 1154 partition_file_obj, partitions_list, partitions_file_path_list = parse_partition_file_xml(partition_file) 1155 if partition_file_obj is False: 1156 clear_resource(err_clear=True) 1157 return False 1158 OPTIONS_MANAGER.partition_file_obj = partition_file_obj 1159 OPTIONS_MANAGER.full_img_list = partitions_list 1160 OPTIONS_MANAGER.full_image_path_list = partitions_file_path_list 1161 1162 # Incremental processing 1163 if incremental_processing(no_zip, partition_file, source_package, verse_script) is False: 1164 clear_resource(err_clear=True) 1165 return 1166 1167 # Full processing 1168 if len(OPTIONS_MANAGER.full_img_list) != 0: 1169 verse_script.add_command("\n# ---- full image ----\n") 1170 full_update_image = FullUpdateImage(OPTIONS_MANAGER.target_package_dir, 1171 OPTIONS_MANAGER.full_img_list, OPTIONS_MANAGER.full_img_name_list, verse_script, 1172 OPTIONS_MANAGER.full_image_path_list, no_zip=OPTIONS_MANAGER.no_zip) 1173 full_image_content_len_list, full_image_file_obj_list = full_update_image.update_full_image() 1174 if full_image_content_len_list is False or full_image_file_obj_list is False: 1175 clear_resource(err_clear=True) 1176 return 1177 # Full streaming process 1178 if OPTIONS_MANAGER.stream_update: 1179 for each_img_name in OPTIONS_MANAGER.full_img_name_list: 1180 print(f'full streaming process! current image name is:{each_img_name}') 1181 each_img = each_img_name[:-4] 1182 chunks, block_sets = split_image_file(each_img, 1183 OPTIONS_MANAGER.full_image_new_data[each_img]) 1184 OPTIONS_MANAGER.full_chunk[each_img] = chunks 1185 OPTIONS_MANAGER.full_block_sets[each_img] = block_sets 1186 1187 OPTIONS_MANAGER.full_image_content_len_list, OPTIONS_MANAGER.full_image_file_obj_list = \ 1188 full_image_content_len_list, full_image_file_obj_list 1189 1190 # Generate the update package. 1191 if build_update_package( 1192 no_zip, update_package, prelude_script, verse_script, refrain_script, ending_script) is False: 1193 clear_resource(err_clear=True) 1194 return 1195 # Clear resources. 1196 clear_resource() 1197 1198 1199if __name__ == '__main__': 1200 main() 1201