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