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