#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Copyright (c) 2024 Hunan OpenValley Digital Industry Development Co., Ltd. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """ Description : pack chunks to update.bin. """ import os import subprocess import build_module_img import tempfile from utils import OPTIONS_MANAGER from utils import DIFF_EXE_PATH from transfers_manager import ActionType DIFF_BLOCK_LIMIT = 10240 class PatchPackageChunk: def __init__(self, *args): self.src_file, self.tgt_file, self.do_pkg_diff, self.transfer_content, self.diff_offset, self.patch_dat_file_obj,\ self.src_img_obj, self.tgt_img_obj, each_action, self.chunk_data_list = args self.chunk_src_obj = tempfile.NamedTemporaryFile(prefix="chunk_src_file", mode='wb') self.chunk_tgt_obj = tempfile.NamedTemporaryFile(prefix="chunk_tft_file", mode='wb') self.chunk_patch_obj = tempfile.NamedTemporaryFile(prefix="chunk_patch_file", mode='wb') self.patch_obj = tempfile.NamedTemporaryFile(prefix="chunk_patch_file", mode='wb') self.limit_size = OPTIONS_MANAGER.chunk_limit * build_module_img.BLOCK_SIZE self.__apply_compute_patch(self.src_file, self.tgt_file, self.patch_obj, 4096) print(os.stat(self.patch_obj.name).st_size) print(f'src:{each_action.src_block_set.to_string_raw()}({each_action.src_block_set.size()}) tgt:{each_action.tgt_block_set.to_string_raw()}({each_action.tgt_block_set.size()})') self.__apply_compute_patch(self.src_file, self.tgt_file, self.patch_obj, int(self.limit_size / DIFF_BLOCK_LIMIT)) # 45KB self.__chunk_patch(self.patch_obj.name, int(self.limit_size / DIFF_BLOCK_LIMIT), each_action) def split_into_closest_multiples_of_ten(self, n): """ Split an integer into two parts, such that the sum of the parts is a multiple of 10. :param n: The integer to split. :return: A tuple containing the two parts. """ # Check if the input is a multiple of 10 if n % 10 != 0: raise ValueError("Input must be a multiple of 10.") # Calculate the two parts half = n // 2 # Ensure both parts are multiples of 10 part1 = (half // 10) * 10 # Nearest lower multiple of 10 part2 = n - part1 # Remaining part # If part2 is not a multiple of 10, adjust part1 and part2 if part2 % 10 != 0: part1 += 10 part2 = n - part1 return part1, part2 def split_old_file(self, each_action, start_blocks, blocks, src_file_blocks, src_file_obj, src_file_bytes): """处理旧文件的分块逻辑""" if (start_blocks + blocks) > each_action.src_block_set.size(): src_file_blocks = each_action.src_block_set temp_blocks = each_action.src_block_set.size() - blocks if temp_blocks < 0: temp_blocks = 0 else: temp_src_blocks_to_write = src_file_blocks.get_first_block_obj(temp_blocks) src_file_blocks = src_file_blocks.get_subtract_with_other(temp_src_blocks_to_write) src_blocks_to_write = src_file_blocks.get_first_block_obj(blocks) src_start = src_file_bytes - blocks * build_module_img.BLOCK_SIZE if src_start < 0: src_start = 0 else: src_start = start_blocks * build_module_img.BLOCK_SIZE src_blocks_to_write = src_file_blocks.get_first_block_obj(blocks) src_file_blocks = src_file_blocks.get_subtract_with_other(src_blocks_to_write) if src_file_bytes == 0: print(f'error: {src_file_bytes}') raise RuntimeError src_file_obj.seek(src_start) self.chunk_src_obj.seek(0) self.chunk_src_obj.truncate(0) bytes_obj = src_file_obj.read(int(blocks * build_module_img.BLOCK_SIZE)) if len(bytes_obj) == 0: src_total_size = each_action.src_block_set.size() print(f'in: {src_total_size}') raise RuntimeError try: self.chunk_src_obj.write(bytes_obj) except Exception as e: print(f'error:{e}') raise RuntimeError return src_file_blocks, src_blocks_to_write, src_start def split_new_tgt_file(self, each_action, start_blocks, blocks, tgt_file_blocks, tgt_file_obj): """处理新目标文件的分块逻辑""" tgt_blocks_to_write = tgt_file_blocks.get_first_block_obj(blocks) tgt_file_blocks = tgt_file_blocks.get_subtract_with_other(tgt_blocks_to_write) tgt_file_obj.seek(int(start_blocks * build_module_img.BLOCK_SIZE)) self.chunk_tgt_obj.seek(0) self.chunk_tgt_obj.truncate(0) bytes_obj = tgt_file_obj.read(int(blocks * build_module_img.BLOCK_SIZE)) if len(bytes_obj) == 0: tgt_total_size = each_action.tgt_block_set.size() print(f'in: {tgt_total_size}') raise RuntimeError self.chunk_tgt_obj.write(bytes_obj) return tgt_file_blocks, tgt_blocks_to_write def process_patch_chunk(self, src_blocks_to_write, tgt_blocks_to_write, chunk_patch_size, start_blocks, blocks): """ 处理每个 chunk patch 的逻辑,包括打印信息、更新状态、保存 patch 内容等。 """ subfile_patch_totalsize = chunk_patch_size start_blocks += blocks # 打印 patch 文件的信息 print(f'self.chunk_patch_obj len: {os.stat(self.chunk_patch_obj.name).st_size} ' f'old: {os.stat(self.patch_dat_file_obj.name).st_size} total: {self.diff_offset}') # 读取 patch 文件内容并保存 with open(self.chunk_patch_obj.name, 'rb') as file_read: patch_value = file_read.read() self.patch_dat_file_obj.write(patch_value) # 如果 patch 有内容,保存到 transfer_content 和其他相关变量 if len(patch_value) > 0: diff_type = "pkgdiff" if self.do_pkg_diff else "bsdiff" diff_str = ("%s %d %d %s %s %s %d %s\n" % ( diff_type, self.diff_offset, len(patch_value), self.src_img_obj.range_sha256(src_blocks_to_write), self.tgt_img_obj.range_sha256(tgt_blocks_to_write), tgt_blocks_to_write.to_string_raw(), src_blocks_to_write.size(), src_blocks_to_write.to_string_raw())) print(diff_str) self.diff_offset += len(patch_value) self.chunk_data_list.append(patch_value) self.transfer_content.append(diff_str) # 打印 transfer_content 长度信息 print(f'in transfer_content len: {len(self.transfer_content)} ' f'self.chunk_patch_obj len: {os.stat(self.patch_dat_file_obj.name).st_size}') # 返回更新后的 totalsize 和 start_blocks return subfile_patch_totalsize, start_blocks def cut_files(self, subblocks_list, each_action): start_blocks = 0 subfile_patch_sizelist = [] subfile_patch_totalsize = 0 src_file_blocks = each_action.src_block_set tgt_file_blocks = each_action.tgt_block_set src_file_obj = open(self.src_file, 'rb') file_stat = os.stat(self.src_file) src_file_bytes = file_stat.st_size tgt_file_obj = open(self.tgt_file, 'rb') file_stat = os.stat(self.tgt_file) tgt_file_bytes = file_stat.st_size src_start = 0 # 遍历 subblocks_list i = 0 while i < len(subblocks_list): blocks = subblocks_list[i] # Store the original state for restoration original_src_file_blocks = src_file_blocks original_tgt_file_blocks = tgt_file_blocks original_start_blocks = start_blocks # Splite old files src_file_blocks, src_blocks_to_write, src_start = self.split_old_file(each_action, start_blocks, blocks, src_file_blocks, src_file_obj, src_file_bytes) # Splite new tgt file tgt_file_blocks, tgt_blocks_to_write = self.split_new_tgt_file(each_action, start_blocks, blocks, tgt_file_blocks, tgt_file_obj) # Here's a patch self.chunk_patch_obj = self.__apply_compute_patch(self.chunk_src_obj.name, self.chunk_tgt_obj.name, self.chunk_patch_obj, 4096) chunk_patch_size = os.stat(self.chunk_patch_obj.name).st_size # 如果patch大小超过限制,将blocks切分为两部分插入subblocks_list中 if chunk_patch_size > self.limit_size: print(f'Patch size {chunk_patch_size} exceeds limit {self.limit_size}, splitting blocks...') block_one, block_two = self.split_into_closest_multiples_of_ten(blocks) if block_one % 10 != 0 or block_two % 10 != 0 or block_one + block_two != blocks: print(f"Error: blocks size split error.") raise RuntimeError # Restore the previous state src_file_blocks = original_src_file_blocks tgt_file_blocks = original_tgt_file_blocks start_blocks = original_start_blocks # 在当前位置插入两个新的blocks subblocks_list[i: i + 1] = [block_one, block_two] print(f'Inserted two new blocks: {block_one, block_two}') print(f'Current subblocks_list: {subblocks_list}') continue # 跳出这次循环,重新处理新分解的块 else: print(f'Patch size {chunk_patch_size} within limit {self.limit_size}, no splitting needed.') subfile_patch_sizelist.append(chunk_patch_size) print(f'[{int(src_start)}, {int(os.stat(self.chunk_src_obj.name).st_size / build_module_img.BLOCK_SIZE)}] ' f'diff [{start_blocks}, {int(os.stat(self.chunk_tgt_obj.name).st_size / build_module_img.BLOCK_SIZE)}] => {chunk_patch_size}') subfile_patch_totalsize, start_blocks = self.process_patch_chunk( src_blocks_to_write, tgt_blocks_to_write, chunk_patch_size, start_blocks, blocks) # 继续处理下一个块 i += 1 src_file_obj.close() tgt_file_obj.close() return subfile_patch_sizelist, subfile_patch_totalsize def __apply_compute_patch(self, src_file, tgt_file, patch_obj, limit): """ Add command content to the script. :param src_file: source file name :param tgt_file: target file name :return: """ patch_obj.seek(0) patch_obj.truncate(0) cmd = [DIFF_EXE_PATH] if self.do_pkg_diff else [DIFF_EXE_PATH, '-b', '1'] cmd.extend(['-s', src_file, '-d', tgt_file, '-p', patch_obj.name, '-l', f'{limit}']) sub_p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) output, _ = sub_p.communicate(timeout=300) sub_p.wait() patch_obj.seek(0) if sub_p.returncode != 0: raise ValueError(output) return patch_obj def __chunk_patch(self, patch_file_obj, file_limit_size, each_action): # 1.Parase patch patch_list = [] file_limit_size = int(file_limit_size * DIFF_BLOCK_LIMIT / build_module_img.BLOCK_SIZE) file_stat = os.stat(patch_file_obj) file_size_bytes = file_stat.st_size with open(patch_file_obj, 'rb') as file: print(file.read(8)) # title blocks = int.from_bytes(file.read(4), byteorder='little') print({blocks}) lastoffset = 0 for i in range(blocks): file.read(20) # Ignore 20B offset = int.from_bytes(file.read(8), byteorder='little') # patchOffset if lastoffset == 0: lastoffset = offset else: patch_list.append(offset - lastoffset) lastoffset = offset patch_list.append(file_size_bytes - lastoffset) print(patch_list) print(f'fileLen:{file_size_bytes}') # 2.Split files total = 0 blocks = 0 index = 0 subblocks_list = [] for dt in patch_list: total += dt if total < 0: total = 0 blocks += file_limit_size if total > self.limit_size: subblocks_list.append(blocks - file_limit_size) # 确保结果小于45 * 102 blocks = file_limit_size total = dt if blocks > 0: subblocks_list.append(blocks) print(subblocks_list) # 3、Cut files subfile_patch_sizelist, subfile_patch_totalsize = self.cut_files(subblocks_list, each_action) # 4.Stats print(f'\ndebug do over\n') print(subfile_patch_sizelist) print(subfile_patch_totalsize) # 5.Comparison with Native print(f'old:{file_stat.st_size} new:{subfile_patch_totalsize}')