• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3
4# Copyright (c) 2024 Hunan OpenValley Digital Industry Development 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 : pack chunks to update.bin.
18"""
19import os
20import subprocess
21import build_module_img
22import tempfile
23from utils import OPTIONS_MANAGER
24from utils import DIFF_EXE_PATH
25from transfers_manager import ActionType
26
27DIFF_BLOCK_LIMIT = 10240
28
29
30class PatchPackageChunk:
31
32    def __init__(self, *args):
33        self.src_file, self.tgt_file, self.do_pkg_diff, self.transfer_content, self.diff_offset, self.patch_dat_file_obj,\
34            self.src_img_obj, self.tgt_img_obj, each_action, self.chunk_data_list = args
35        self.chunk_src_obj = tempfile.NamedTemporaryFile(prefix="chunk_src_file", mode='wb')
36        self.chunk_tgt_obj = tempfile.NamedTemporaryFile(prefix="chunk_tft_file", mode='wb')
37        self.chunk_patch_obj = tempfile.NamedTemporaryFile(prefix="chunk_patch_file", mode='wb')
38        self.patch_obj = tempfile.NamedTemporaryFile(prefix="chunk_patch_file", mode='wb')
39        self.limit_size = OPTIONS_MANAGER.chunk_limit * build_module_img.BLOCK_SIZE
40
41        self.__apply_compute_patch(self.src_file, self.tgt_file, self.patch_obj,
42                                   4096)
43        print(os.stat(self.patch_obj.name).st_size)
44        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()})')
45        self.__apply_compute_patch(self.src_file, self.tgt_file, self.patch_obj, int(self.limit_size / DIFF_BLOCK_LIMIT)) # 45KB
46
47        self.__chunk_patch(self.patch_obj.name, int(self.limit_size / DIFF_BLOCK_LIMIT), each_action)
48
49    def split_into_closest_multiples_of_ten(self, n):
50        """
51        Split an integer into two parts, such that the sum of the parts is a multiple of 10.
52        :param n: The integer to split.
53        :return: A tuple containing the two parts.
54        """
55        # Check if the input is a multiple of 10
56        if n % 10 != 0:
57            raise ValueError("Input must be a multiple of 10.")
58        # Calculate the two parts
59        half = n // 2
60        # Ensure both parts are multiples of 10
61        part1 = (half // 10) * 10  # Nearest lower multiple of 10
62        part2 = n - part1           # Remaining part
63        # If part2 is not a multiple of 10, adjust part1 and part2
64        if part2 % 10 != 0:
65            part1 += 10
66            part2 = n - part1
67        return part1, part2
68
69    def split_old_file(self, each_action, start_blocks, blocks, src_file_blocks, src_file_obj, src_file_bytes):
70        """处理旧文件的分块逻辑"""
71        if (start_blocks + blocks) > each_action.src_block_set.size():
72            src_file_blocks = each_action.src_block_set
73            temp_blocks = each_action.src_block_set.size() - blocks
74            if temp_blocks < 0:
75                temp_blocks = 0
76            else:
77                temp_src_blocks_to_write = src_file_blocks.get_first_block_obj(temp_blocks)
78                src_file_blocks = src_file_blocks.get_subtract_with_other(temp_src_blocks_to_write)
79            src_blocks_to_write = src_file_blocks.get_first_block_obj(blocks)
80            src_start = src_file_bytes - blocks * build_module_img.BLOCK_SIZE
81            if src_start < 0:
82                src_start = 0
83        else:
84            src_start = start_blocks * build_module_img.BLOCK_SIZE
85            src_blocks_to_write = src_file_blocks.get_first_block_obj(blocks)
86            src_file_blocks = src_file_blocks.get_subtract_with_other(src_blocks_to_write)
87
88        if src_file_bytes == 0:
89            print(f'error: {src_file_bytes}')
90            raise RuntimeError
91
92        src_file_obj.seek(src_start)
93        self.chunk_src_obj.seek(0)
94        self.chunk_src_obj.truncate(0)
95        bytes_obj = src_file_obj.read(int(blocks * build_module_img.BLOCK_SIZE))
96        if len(bytes_obj) == 0:
97            src_total_size = each_action.src_block_set.size()
98            print(f'in: {src_total_size}')
99            raise RuntimeError
100
101        try:
102            self.chunk_src_obj.write(bytes_obj)
103        except Exception as e:
104            print(f'error:{e}')
105            raise RuntimeError
106
107        return src_file_blocks, src_blocks_to_write, src_start
108
109    def split_new_tgt_file(self, each_action, start_blocks, blocks, tgt_file_blocks, tgt_file_obj):
110        """处理新目标文件的分块逻辑"""
111        tgt_blocks_to_write = tgt_file_blocks.get_first_block_obj(blocks)
112        tgt_file_blocks = tgt_file_blocks.get_subtract_with_other(tgt_blocks_to_write)
113        tgt_file_obj.seek(int(start_blocks * build_module_img.BLOCK_SIZE))
114        self.chunk_tgt_obj.seek(0)
115        self.chunk_tgt_obj.truncate(0)
116        bytes_obj = tgt_file_obj.read(int(blocks * build_module_img.BLOCK_SIZE))
117        if len(bytes_obj) == 0:
118            tgt_total_size = each_action.tgt_block_set.size()
119            print(f'in: {tgt_total_size}')
120            raise RuntimeError
121        self.chunk_tgt_obj.write(bytes_obj)
122
123        return tgt_file_blocks, tgt_blocks_to_write
124
125    def process_patch_chunk(self, src_blocks_to_write, tgt_blocks_to_write, chunk_patch_size, start_blocks, blocks):
126        """
127        处理每个 chunk patch 的逻辑,包括打印信息、更新状态、保存 patch 内容等。
128        """
129        subfile_patch_totalsize = chunk_patch_size
130        start_blocks += blocks
131
132        # 打印 patch 文件的信息
133        print(f'self.chunk_patch_obj len: {os.stat(self.chunk_patch_obj.name).st_size} '
134            f'old: {os.stat(self.patch_dat_file_obj.name).st_size} total: {self.diff_offset}')
135
136        # 读取 patch 文件内容并保存
137        with open(self.chunk_patch_obj.name, 'rb') as file_read:
138            patch_value = file_read.read()
139            self.patch_dat_file_obj.write(patch_value)
140
141        # 如果 patch 有内容,保存到 transfer_content 和其他相关变量
142        if len(patch_value) > 0:
143            diff_type = "pkgdiff" if self.do_pkg_diff else "bsdiff"
144            diff_str = ("%s %d %d %s %s %s %d %s\n" % (
145                diff_type,
146                self.diff_offset, len(patch_value),
147                self.src_img_obj.range_sha256(src_blocks_to_write),
148                self.tgt_img_obj.range_sha256(tgt_blocks_to_write),
149                tgt_blocks_to_write.to_string_raw(), src_blocks_to_write.size(), src_blocks_to_write.to_string_raw()))
150            print(diff_str)
151
152            self.diff_offset += len(patch_value)
153            self.chunk_data_list.append(patch_value)
154            self.transfer_content.append(diff_str)
155
156            # 打印 transfer_content 长度信息
157            print(f'in transfer_content len: {len(self.transfer_content)} '
158                f'self.chunk_patch_obj len: {os.stat(self.patch_dat_file_obj.name).st_size}')
159
160        # 返回更新后的 totalsize 和 start_blocks
161        return subfile_patch_totalsize, start_blocks
162
163    def cut_files(self, subblocks_list, each_action):
164        start_blocks = 0
165        subfile_patch_sizelist = []
166        subfile_patch_totalsize = 0
167        src_file_blocks = each_action.src_block_set
168        tgt_file_blocks = each_action.tgt_block_set
169
170        src_file_obj = open(self.src_file, 'rb')
171        file_stat = os.stat(self.src_file)
172        src_file_bytes = file_stat.st_size
173        tgt_file_obj = open(self.tgt_file, 'rb')
174        file_stat = os.stat(self.tgt_file)
175        tgt_file_bytes = file_stat.st_size
176        src_start = 0
177
178        # 遍历 subblocks_list
179        i = 0
180        while i < len(subblocks_list):
181            blocks = subblocks_list[i]
182
183            # Store the original state for restoration
184            original_src_file_blocks = src_file_blocks
185            original_tgt_file_blocks = tgt_file_blocks
186            original_start_blocks = start_blocks
187
188            # Splite old files
189            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)
190
191            # Splite new tgt file
192            tgt_file_blocks, tgt_blocks_to_write = self.split_new_tgt_file(each_action, start_blocks, blocks, tgt_file_blocks, tgt_file_obj)
193
194            # Here's a patch
195            self.chunk_patch_obj = self.__apply_compute_patch(self.chunk_src_obj.name, self.chunk_tgt_obj.name, self.chunk_patch_obj, 4096)
196            chunk_patch_size = os.stat(self.chunk_patch_obj.name).st_size
197
198            # 如果patch大小超过限制,将blocks切分为两部分插入subblocks_list中
199            if chunk_patch_size > self.limit_size:
200                print(f'Patch size {chunk_patch_size} exceeds limit {self.limit_size}, splitting blocks...')
201                block_one, block_two = self.split_into_closest_multiples_of_ten(blocks)
202                if block_one % 10 != 0 or block_two % 10 != 0 or block_one + block_two != blocks:
203                    print(f"Error: blocks size split error.")
204                    raise RuntimeError
205
206                # Restore the previous state
207                src_file_blocks = original_src_file_blocks
208                tgt_file_blocks = original_tgt_file_blocks
209                start_blocks = original_start_blocks
210
211                # 在当前位置插入两个新的blocks
212                subblocks_list[i: i + 1] = [block_one, block_two]
213                print(f'Inserted two new blocks: {block_one, block_two}')
214                print(f'Current subblocks_list: {subblocks_list}')
215                continue  # 跳出这次循环,重新处理新分解的块
216            else:
217                print(f'Patch size {chunk_patch_size} within limit {self.limit_size}, no splitting needed.')
218                subfile_patch_sizelist.append(chunk_patch_size)
219
220            print(f'[{int(src_start)}, {int(os.stat(self.chunk_src_obj.name).st_size / build_module_img.BLOCK_SIZE)}] '
221            f'diff [{start_blocks}, {int(os.stat(self.chunk_tgt_obj.name).st_size / build_module_img.BLOCK_SIZE)}] => {chunk_patch_size}')
222
223            subfile_patch_totalsize, start_blocks = self.process_patch_chunk(
224                src_blocks_to_write, tgt_blocks_to_write, chunk_patch_size, start_blocks, blocks)
225            # 继续处理下一个块
226            i += 1
227
228        src_file_obj.close()
229        tgt_file_obj.close()
230
231        return subfile_patch_sizelist, subfile_patch_totalsize
232
233    def __apply_compute_patch(self, src_file, tgt_file, patch_obj, limit):
234        """
235        Add command content to the script.
236        :param src_file: source file name
237        :param tgt_file: target file name
238        :return:
239        """
240        patch_obj.seek(0)
241        patch_obj.truncate(0)
242        cmd = [DIFF_EXE_PATH] if self.do_pkg_diff else [DIFF_EXE_PATH, '-b', '1']
243
244        cmd.extend(['-s', src_file, '-d', tgt_file,
245                    '-p', patch_obj.name, '-l', f'{limit}'])
246        sub_p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
247                                 stderr=subprocess.STDOUT)
248        output, _ = sub_p.communicate(timeout=300)
249        sub_p.wait()
250        patch_obj.seek(0)
251
252        if sub_p.returncode != 0:
253            raise ValueError(output)
254
255        return patch_obj
256
257    def __chunk_patch(self, patch_file_obj, file_limit_size, each_action):
258        # 1.Parase patch
259        patch_list = []
260        file_limit_size = int(file_limit_size * DIFF_BLOCK_LIMIT / build_module_img.BLOCK_SIZE)
261        file_stat = os.stat(patch_file_obj)
262        file_size_bytes = file_stat.st_size
263        with open(patch_file_obj, 'rb') as file:
264            print(file.read(8))  # title
265            blocks = int.from_bytes(file.read(4), byteorder='little')
266            print({blocks})
267            lastoffset = 0
268            for i in range(blocks):
269                file.read(20)  # Ignore 20B
270                offset = int.from_bytes(file.read(8), byteorder='little')  # patchOffset
271                if lastoffset == 0:
272                    lastoffset = offset
273                else:
274                    patch_list.append(offset - lastoffset)
275                    lastoffset = offset
276            patch_list.append(file_size_bytes - lastoffset)
277            print(patch_list)
278            print(f'fileLen:{file_size_bytes}')
279
280        # 2.Split files
281        total = 0
282        blocks = 0
283        index = 0
284        subblocks_list = []
285        for dt in patch_list:
286            total += dt
287            if total < 0:
288                total = 0
289            blocks += file_limit_size
290            if total > self.limit_size:
291                subblocks_list.append(blocks - file_limit_size)  # 确保结果小于45 * 102
292                blocks = file_limit_size
293                total = dt
294        if blocks > 0:
295            subblocks_list.append(blocks)
296        print(subblocks_list)
297        # 3、Cut files
298        subfile_patch_sizelist, subfile_patch_totalsize = self.cut_files(subblocks_list, each_action)
299
300        # 4.Stats
301        print(f'\ndebug do over\n')
302        print(subfile_patch_sizelist)
303        print(subfile_patch_totalsize)
304        # 5.Comparison with Native
305        print(f'old:{file_stat.st_size} new:{subfile_patch_totalsize}')
306