• 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.
16import bisect
17import copy
18import os
19import struct
20import tempfile
21from hashlib import sha256
22
23from log_exception import UPDATE_LOGGER
24from blocks_manager import BlocksManager
25from utils import OPTIONS_MANAGER
26from utils import EXTEND_VALUE
27from utils import FILE_MAP_ZERO_KEY
28from utils import FILE_MAP_NONZERO_KEY
29from utils import FILE_MAP_COPY_KEY
30from utils import MAX_BLOCKS_PER_GROUP
31from utils import UPDATE_BIN_FILE_NAME
32from utils import FORBIDEN_UPDATE_IMAGE_SET
33
34
35class FullUpdateImage:
36    """
37    Full image processing class
38    """
39
40    def __init__(self, target_package_images_dir,
41                 full_img_list, full_img_name_list,
42                 verse_script, full_image_path_list,
43                 no_zip=False):
44        self.target_package_images_dir = target_package_images_dir
45        self.full_img_list = full_img_list
46        self.full_img_name_list = full_img_name_list
47        self.verse_script = verse_script
48        self.full_image_path_list = full_image_path_list
49        self.no_zip = no_zip
50
51    def update_full_image(self):
52        """
53        Processing of the full image
54        :return full_image_content_len_list: full image content length list
55        :return full_image_file_obj_list: full image temporary file list
56        """
57        full_image_file_obj_list = []
58        full_image_content_len_list = []
59        for idx, each_name in enumerate(self.full_img_list):
60            full_image_content = self.get_full_image_content(
61                self.full_image_path_list[idx])
62            img_name = self.full_img_name_list[idx][:-4]
63            if full_image_content is False:
64                UPDATE_LOGGER.print_log(
65                    "Get full image content failed!",
66                    log_type=UPDATE_LOGGER.ERROR_LOG)
67                return False, False
68            each_img = tempfile.NamedTemporaryFile(
69                dir=self.target_package_images_dir,
70                prefix="full_image%s" % img_name, mode='wb')
71            each_img.write(full_image_content)
72            each_img.seek(0)
73            full_image_content_len_list.append(len(full_image_content))
74            full_image_file_obj_list.append(each_img)
75            # 全量流式升级处理
76            OPTIONS_MANAGER.full_image_new_data[each_name] = full_image_content
77
78            UPDATE_LOGGER.print_log(
79                "Image %s full processing completed" % img_name)
80        update_image_set = set(self.full_img_list) - FORBIDEN_UPDATE_IMAGE_SET
81        if not self.no_zip and len(update_image_set) != 0:
82            # No zip mode (no script command)
83            image_write_cmd = self.verse_script.full_image_update(UPDATE_BIN_FILE_NAME)
84            cmd = '%s_WRITE_FLAG%s' % (UPDATE_BIN_FILE_NAME, image_write_cmd)
85            if each_name not in FORBIDEN_UPDATE_IMAGE_SET:
86                self.verse_script.add_command(cmd=cmd)
87
88        UPDATE_LOGGER.print_log(
89            "All full image processing completed! image count: %d" %
90            len(self.full_img_list))
91        return full_image_content_len_list, full_image_file_obj_list
92
93    @staticmethod
94    def get_full_image_content(each_name):
95        """
96        Obtain the full image content.
97        :param each_name: image name
98        :return content: full image content if available; false otherwise
99        """
100        each_image_path = each_name
101        if not os.path.exists(each_image_path):
102            UPDATE_LOGGER.print_log(
103                "The file is missing "
104                "from the target package, "
105                "the component: %s cannot be full update processed. " %
106                each_image_path)
107            return False
108        with open(each_image_path, 'rb') as f_r:
109            content = f_r.read()
110        return content
111
112
113class IncUpdateImage:
114    """
115    Increment update image class
116    """
117
118    def __init__(self, image_path, map_path):
119        """
120        Initialize the inc image.
121        :param image_path: img file path
122        :param map_path: map file path
123        """
124        self.image_path = image_path
125        self.map_path = map_path
126        self.offset_value_list = []
127        self.care_block_range = None
128        self.extended_range = None
129        self.reserved_blocks = BlocksManager("0")
130        self.file_map = []
131        self.offset_index = []
132        self.block_size = None
133        self.total_blocks = None
134        self.parse_raw_image_file(image_path, map_path)
135
136    def parse_raw_image_file(self, image_path, map_path):
137        """
138        Parse the .img file.
139        :param image_path: img file path
140        :param map_path: map file path
141        """
142        self.block_size = block_size = 4096
143        self.total_blocks = total_blocks = \
144            os.path.getsize(self.image_path) // self.block_size
145        reference = b'\0' * self.block_size
146        with open(image_path, 'rb') as f_r:
147            care_value_list, offset_value_list = [], []
148            nonzero_blocks = []
149            for i in range(self.total_blocks):
150                blocks_data = f_r.read(self.block_size)
151                if blocks_data != reference:
152                    nonzero_blocks.append(i)
153                    nonzero_blocks.append(i + 1)
154            self.care_block_range = BlocksManager(nonzero_blocks)
155            care_value_list = list(self.care_block_range.range_data)
156            for idx, value in enumerate(care_value_list):
157                if idx != 0 and (idx + 1) % 2 == 0:
158                    be_value = int(care_value_list[idx - 1])
159                    af_value = int(care_value_list[idx])
160                    file_tell = be_value * block_size
161                    offset_value_list.append(
162                        (be_value, af_value - be_value,
163                         file_tell, None))
164
165            self.offset_index = [i[0] for i in offset_value_list]
166            self.offset_value_list = offset_value_list
167            extended_range = \
168                self.care_block_range.extend_value_to_blocks(EXTEND_VALUE)
169            all_blocks = BlocksManager(range_data=(0, total_blocks))
170            self.extended_range = \
171                extended_range.get_intersect_with_other(all_blocks). \
172                get_subtract_with_other(self.care_block_range)
173            self.parse_block_map_file(map_path, f_r)
174
175    def parse_block_map_file(self, map_path, image_file_r):
176        """
177        Parses the map file for blocks where files are contained in the image.
178        :param map_path: map file path
179        :param image_file_r: file reading object
180        :return:
181        """
182        remain_range = self.care_block_range
183        temp_file_map = {}
184
185        with open(map_path, 'r') as f_r:
186            # Read the .map file and process each line.
187            for each_line in f_r.readlines():
188                each_map_path, ranges_value = each_line.split(None, 1)
189                each_range = BlocksManager(ranges_value)
190                each_range = each_range.get_subtract_with_other(BlocksManager("0"))
191                # each_range may not contained in the remain range.
192                intersect_range = each_range.get_intersect_with_other(remain_range)
193                if each_range.size() != intersect_range.size():
194                    each_range = intersect_range
195                temp_file_map[each_map_path] = each_range
196                # After the processing is complete,
197                # remove each_range from remain_range.
198                remain_range = remain_range.get_subtract_with_other(each_range)
199        reserved_blocks = self.reserved_blocks
200        # Remove reserved blocks from all blocks.
201        remain_range = remain_range.get_subtract_with_other(reserved_blocks)
202
203        # Divide all blocks into zero_blocks
204        # (if there are many) and nonzero_blocks.
205        zero_blocks_list = []
206        nonzero_blocks_list = []
207        nonzero_groups_list = []
208        default_zero_block = ('\0' * self.block_size).encode()
209
210        nonzero_blocks_list, nonzero_groups_list, zero_blocks_list = \
211            self.apply_remain_range(
212                default_zero_block, image_file_r, nonzero_blocks_list,
213                nonzero_groups_list, remain_range, zero_blocks_list)
214
215        temp_file_map = self.get_file_map(
216            nonzero_blocks_list, nonzero_groups_list,
217            reserved_blocks, temp_file_map, zero_blocks_list)
218        self.file_map = temp_file_map
219
220    def apply_remain_range(self, *args):
221        """
222        Implement traversal processing of remain_range.
223        """
224        default_zero_block, image_file_r, \
225            nonzero_blocks_list, nonzero_groups_list, \
226            remain_range, zero_blocks_list = args
227        for start_value, end_value in remain_range:
228            for each_value in range(start_value, end_value):
229                # bisect 二分查找,b在self.offset_index中的位置
230                idx = bisect.bisect_right(self.offset_index, each_value) - 1
231                chunk_start, _, file_pos, fill_data = \
232                    self.offset_value_list[idx]
233                data = self.get_file_data(self.block_size, chunk_start,
234                                          default_zero_block, each_value,
235                                          file_pos, fill_data, image_file_r)
236
237                zero_blocks_list, nonzero_blocks_list, nonzero_groups_list = \
238                    self.get_zero_nonzero_blocks_list(
239                        data, default_zero_block, each_value,
240                        nonzero_blocks_list, nonzero_groups_list,
241                        zero_blocks_list)
242        return nonzero_blocks_list, nonzero_groups_list, zero_blocks_list
243
244    @staticmethod
245    def get_file_map(*args):
246        """
247        Obtain the file map.
248        nonzero_blocks_list nonzero blocks list,
249        nonzero_groups_list nonzero groups list,
250        reserved_blocks reserved blocks ,
251        temp_file_map temporary file map,
252        zero_blocks_list zero block list
253        :return temp_file_map file map
254        """
255        nonzero_blocks_list, nonzero_groups_list, \
256            reserved_blocks, temp_file_map, zero_blocks_list = args
257        if nonzero_blocks_list:
258            nonzero_groups_list.append(nonzero_blocks_list)
259        if zero_blocks_list:
260            temp_file_map[FILE_MAP_ZERO_KEY] = \
261                BlocksManager(range_data=zero_blocks_list)
262        if nonzero_groups_list:
263            for i, blocks in enumerate(nonzero_groups_list):
264                temp_file_map["%s-%d" % (FILE_MAP_NONZERO_KEY, i)] = \
265                    BlocksManager(range_data=blocks)
266        if reserved_blocks:
267            temp_file_map[FILE_MAP_COPY_KEY] = reserved_blocks
268        return temp_file_map
269
270    @staticmethod
271    def get_zero_nonzero_blocks_list(*args):
272        """
273        Get zero_blocks_list, nonzero_blocks_list, and nonzero_groups_list.
274        data: block data,
275        default_zero_block: default to zero block,
276        each_value: each value,
277        nonzero_blocks_list: nonzero_blocks_list,
278        nonzero_groups_list: nonzero_groups_list,
279        zero_blocks_list: zero_blocks_list,
280        :return new_zero_blocks_list: new zero blocks list,
281        :return new_nonzero_blocks_list: new nonzero blocks list,
282        :return new_nonzero_groups_list: new nonzero groups list.
283        """
284        data, default_zero_block, each_value, \
285            nonzero_blocks_list, nonzero_groups_list, \
286            zero_blocks_list = args
287        # Check whether the data block is equal to the default zero_blocks.
288        if data == default_zero_block:
289            zero_blocks_list.append(each_value)
290            zero_blocks_list.append(each_value + 1)
291        else:
292            nonzero_blocks_list.append(each_value)
293            nonzero_blocks_list.append(each_value + 1)
294            # The number of nonzero_blocks is greater than
295            # or equal to the upper limit.
296            if len(nonzero_blocks_list) >= MAX_BLOCKS_PER_GROUP:
297                nonzero_groups_list.append(nonzero_blocks_list)
298                nonzero_blocks_list = []
299        new_zero_blocks_list, new_nonzero_blocks_list, \
300            new_nonzero_groups_list = \
301            copy.copy(zero_blocks_list), \
302            copy.copy(nonzero_blocks_list),\
303            copy.copy(nonzero_groups_list)
304        return new_zero_blocks_list, new_nonzero_blocks_list, \
305            new_nonzero_groups_list
306
307    @staticmethod
308    def get_file_data(*args):
309        """
310        Get the file data.
311        block_size: blocksize,
312        chunk_start: the start position of chunk,
313        default_zero_block: default to zero blocks,
314        each_value: each_value,
315        file_pos: file position,
316        fill_data: data,
317        image_file_r: read file object,
318        :return data: Get the file data.
319        """
320        block_size, chunk_start, default_zero_block, each_value, \
321            file_pos, fill_data, image_file_r = args
322        if file_pos is not None:
323            file_pos += (each_value - chunk_start) * block_size
324            image_file_r.seek(file_pos, os.SEEK_SET)
325            data = image_file_r.read(block_size)
326        else:
327            if fill_data == default_zero_block[:4]:
328                data = default_zero_block
329            else:
330                data = None
331        return data
332
333    def range_sha256(self, ranges):
334        """
335        range sha256 hash content
336        :param ranges: ranges value
337        :return:
338        """
339        hash_obj = sha256()
340        for data in self.__get_blocks_set_data(ranges):
341            hash_obj.update(data)
342        return hash_obj.hexdigest()
343
344    def write_range_data_2_fd(self, ranges, file_obj):
345        """
346        write range data to fd
347        :param ranges: ranges obj
348        :param file_obj: file obj
349        :return:
350        """
351        for data in self.__get_blocks_set_data(ranges):
352            file_obj.write(data)
353
354    def get_ranges(self, ranges):
355        """
356        get ranges value
357        :param ranges: ranges
358        :return: ranges value
359        """
360        return [each_data for each_data in self.__get_blocks_set_data(ranges)]
361
362    def __get_blocks_set_data(self, blocks_set_data):
363        """
364        Get the range data.
365        """
366        with open(self.image_path, 'rb') as f_r:
367            for start, end in blocks_set_data:
368                diff_value = end - start
369                idx = bisect.bisect_right(self.offset_index, start) - 1
370                chunk_start, chunk_len, file_pos, fill_data = \
371                    self.offset_value_list[idx]
372
373                remain = chunk_len - (start - chunk_start)
374                this_read = min(remain, diff_value)
375                if file_pos is not None:
376                    pos = file_pos + ((start - chunk_start) * self.block_size)
377                    f_r.seek(pos, os.SEEK_SET)
378                    yield f_r.read(this_read * self.block_size)
379                else:
380                    yield fill_data * (this_read * (self.block_size >> 2))
381                diff_value -= this_read
382
383                while diff_value > 0:
384                    idx += 1
385                    chunk_start, chunk_len, file_pos, fill_data = \
386                        self.offset_value_list[idx]
387                    this_read = min(chunk_len, diff_value)
388                    if file_pos is not None:
389                        f_r.seek(file_pos, os.SEEK_SET)
390                        yield f_r.read(this_read * self.block_size)
391                    else:
392                        yield fill_data * (this_read * (self.block_size >> 2))
393                    diff_value -= this_read