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. 16""" 17Description : Create script file for updater 18""" 19 20import os 21import re 22import tempfile 23from decimal import getcontext 24from decimal import Decimal 25 26from log_exception import VendorExpandError 27from log_exception import UPDATE_LOGGER 28from utils import OPTIONS_MANAGER 29from utils import PARTITION_FILE 30from utils import TOTAL_SCRIPT_FILE_NAME 31from utils import SCRIPT_FILE_NAME 32from utils import SCRIPT_KEY_LIST 33from utils import UPDATE_BIN_FILE_NAME 34 35 36class Script: 37 38 def __init__(self): 39 self.script = [] 40 self.version = 0 41 self.info = {} 42 43 def add_command(self, cmd=None): 44 """ 45 Add command content to the script. 46 :param cmd: command content 47 :return: 48 """ 49 self.script.append(cmd) 50 51 def get_script(self): 52 """ 53 Get the script list. 54 :return: script list 55 """ 56 return self.script 57 58 def sha_check(self, *args, **kwargs): 59 raise VendorExpandError(type(self), 'sha_check') 60 61 def image_sha_check(self, *args, **kwargs): 62 raise VendorExpandError(type(self), 'image_sha_check') 63 64 def first_block_check(self, *args, **kwargs): 65 raise VendorExpandError(type(self), 'first_block_check') 66 67 def image_patch(self, *args, **kwargs): 68 raise VendorExpandError(type(self), 'image_patch') 69 70 def abort(self, *args, **kwargs): 71 raise VendorExpandError(type(self), 'abort') 72 73 def show_progress(self, *args, **kwargs): 74 raise VendorExpandError(type(self), 'show_progress') 75 76 def block_update(self, *args, **kwargs): 77 raise VendorExpandError(type(self), 'block_update') 78 79 def raw_image_write(self, *args, **kwargs): 80 raise VendorExpandError(type(self), 'raw_image_write') 81 82 def get_status(self, *args, **kwargs): 83 raise VendorExpandError(type(self), 'get_status') 84 85 def set_status(self, *args, **kwargs): 86 raise VendorExpandError(type(self), 'set_status') 87 88 def reboot_now(self, *args, **kwargs): 89 raise VendorExpandError(type(self), 'reboot_now') 90 91 def updater_partitions(self, *args, **kwargs): 92 raise VendorExpandError(type(self), 'updater_partitions') 93 94 95class PreludeScript(Script): 96 def __init__(self): 97 super().__init__() 98 99 100class VerseScript(Script): 101 def __init__(self): 102 super().__init__() 103 104 def sha_check(self, ranges_str, expected_sha, partition): 105 """ 106 Get the sha_check command. 107 :param ranges_str: ranges string 108 :param expected_sha: hash value 109 :param partition: image name 110 :return: 111 """ 112 cmd = ('sha_check("/{partition}", "{ranges_str}", ' 113 '"{expected_sha}")').format( 114 ranges_str=ranges_str, 115 expected_sha=expected_sha, partition=partition) 116 return cmd 117 118 def image_sha_check(self, partition, src_size, src_hash, 119 dst_size, dst_hash): 120 """ 121 Get the image_sha_check command. 122 :param ranges_str: ranges string 123 :param expected_sha: hash value 124 :param partition: image name 125 :return: 126 """ 127 cmd = ('image_sha_check("/{partition}", ' 128 '"{src_size}", "{src_hash}", ' 129 '"{dst_size}", "{dst_hash}")').format( 130 partition=partition, src_size=src_size, src_hash=src_hash, 131 dst_size=dst_size, dst_hash=dst_hash) 132 return cmd 133 134 def first_block_check(self, partition): 135 """ 136 Get the first_block_check command. 137 :param partition: image name 138 :return: 139 """ 140 cmd = 'first_block_check("/{partition}")'.format( 141 partition=partition) 142 return cmd 143 144 def abort(self, partition): 145 """ 146 Get the abort command. 147 :param partition: image name 148 :return: 149 """ 150 cmd = 'abort("ERROR: {partition} partition ' \ 151 'fails to incremental check!");\n'.format( 152 partition=partition) 153 return cmd 154 155 def show_progress(self, start_progress, dur): 156 """ 157 Get the show_progress command. 158 'dur' may be zero to advance the progress via SetProgress 159 :param start_progress: start progress 160 :param dur: seconds 161 :return: 162 """ 163 cmd = 'show_progress({start_progress}, {dur});\n'.format( 164 start_progress=float(start_progress), dur=float(dur)) 165 return cmd 166 167 def image_patch(self, partition, src_size, src_hash, target_size, target_hash): 168 """ 169 Get the image_patch command. 170 :param partition: image name 171 :return: 172 """ 173 cmd = 'image_patch("/{partition}", "{src_size}", ' \ 174 '"{src_hash}", "{target_size}", "{target_hash}", ' \ 175 '"{partition}.patch.dat");\n'.format(partition=partition, src_size=src_size, 176 src_hash=src_hash, target_size=target_size, target_hash=target_hash) 177 return cmd 178 179 def block_update(self, partition): 180 """ 181 Get the block_update command. 182 :param partition: image name 183 :return: 184 """ 185 cmd = 'block_update("/{partition}", ' \ 186 '"{partition}.transfer.list", "{partition}.new.dat", ' \ 187 '"{partition}.patch.dat");\n'.format(partition=partition) 188 return cmd 189 190 def image_write(self, partition, image_name, image_path): 191 return self.raw_image_write(partition, image_name) 192 193 def raw_image_write(self, partition, image_name): 194 """ 195 Get the raw_image_write command. 196 :param partition: image name 197 :return: 198 """ 199 cmd = 'raw_image_write("/%s", "/%s");\n' % (partition, image_name) 200 return cmd 201 202 def get_status(self): 203 """ 204 Get the get_status command. 205 :return: 206 """ 207 cmd = 'get_status("/misc")' 208 return cmd 209 210 def set_status(self, status_value): 211 """ 212 Get the set_status command. 213 :param status_value: status value to be set 214 :return: 215 """ 216 cmd = 'set_status("/misc", %s);\n' % status_value 217 return cmd 218 219 def reboot_now(self): 220 """ 221 Get the reboot_now command. 222 :return: 223 """ 224 cmd = 'reboot_now();\n' 225 return cmd 226 227 def updater_partitions(self): 228 """ 229 Get the updater_partitions command. 230 :return: 231 """ 232 cmd = 'update_partitions("/%s");\n' % PARTITION_FILE 233 return cmd 234 235 def full_image_update(self, update_file_name): 236 cmd = 'update_from_bin("%s");\n' % update_file_name 237 return cmd 238 239 240class RefrainScript(Script): 241 def __init__(self): 242 super().__init__() 243 244 245class EndingScript(Script): 246 def __init__(self): 247 super().__init__() 248 249 250def write_script(script_content, opera_name): 251 """ 252 Generate the {opera}script. 253 :param script_content: script content 254 :param opera_name: Opera phase names corresponding to the script content 255 'prelude', 'verse', 'refrain', and 'ending'. 256 :return: 257 """ 258 script_file = tempfile.NamedTemporaryFile(mode='w+') 259 script_file.write(script_content) 260 script_file.seek(0) 261 script_file_name = ''.join([opera_name.title(), SCRIPT_FILE_NAME]) 262 OPTIONS_MANAGER.opera_script_file_name_dict[opera_name].\ 263 append((script_file_name, script_file)) 264 UPDATE_LOGGER.print_log("%s generation complete!" % script_file_name) 265 266 267def generate_total_script(): 268 """ 269 Generate the overall script. 270 """ 271 content_list = [] 272 for each_key, each_value in \ 273 OPTIONS_MANAGER.opera_script_file_name_dict.items(): 274 for each in each_value: 275 each_content = "LoadScript(\"%s\", %s);" % \ 276 (each[0], SCRIPT_KEY_LIST.index(each_key)) 277 content_list.append(each_content) 278 script_total = tempfile.NamedTemporaryFile(mode='w+') 279 script_total.write('\n'.join(content_list)) 280 script_total.seek(0) 281 OPTIONS_MANAGER.total_script_file_obj = script_total 282 UPDATE_LOGGER.print_log("%s generation complete!" % TOTAL_SCRIPT_FILE_NAME) 283 284 285def get_progress_value(distributable_value=100): 286 """ 287 Allocate a progress value to each image update. 288 :param distributable_value: distributable value 289 :return: 290 """ 291 progress_value_dict = {} 292 full_img_list = OPTIONS_MANAGER.full_img_list 293 incremental_img_list = OPTIONS_MANAGER.incremental_img_list 294 file_size_list = [] 295 each_img_size = 0 296 if len(full_img_list) == 0 and len(incremental_img_list) == 0: 297 UPDATE_LOGGER.print_log( 298 "get progress value failed! > getting progress value failed!", 299 UPDATE_LOGGER.ERROR_LOG) 300 return False 301 for partition in incremental_img_list: 302 # Obtain the size of the incremental image file. 303 if partition in OPTIONS_MANAGER.incremental_image_file_obj_dict: 304 file_obj = OPTIONS_MANAGER.incremental_image_file_obj_dict[partition] 305 each_img_size = os.path.getsize(file_obj.name) 306 elif partition in OPTIONS_MANAGER.incremental_block_file_obj_dict: 307 new_dat_file_obj, patch_dat_file_obj, transfer_list_file_obj =\ 308 OPTIONS_MANAGER.incremental_block_file_obj_dict[partition].get_file_obj() 309 each_img_size = os.path.getsize(new_dat_file_obj.name) + os.path.getsize(patch_dat_file_obj.name) 310 file_size_list.append(each_img_size) 311 312 total_full_img_size = 0 313 for idx, _ in enumerate(full_img_list): 314 # Obtain the size of the full image file. 315 file_obj = OPTIONS_MANAGER.full_image_file_obj_list[idx] 316 total_full_img_size += os.path.getsize(file_obj.name) 317 file_size_list.append(total_full_img_size) 318 319 proportion_value_list = get_proportion_value_list( 320 file_size_list, distributable_value=distributable_value) 321 322 adjusted_proportion_value_list = adjust_proportion_value_list( 323 proportion_value_list, distributable_value) 324 325 all_img_list = incremental_img_list + [UPDATE_BIN_FILE_NAME] 326 current_progress = 40 327 for idx, each_img in enumerate(all_img_list): 328 temp_progress = current_progress + adjusted_proportion_value_list[idx] 329 progress_value_dict[each_img] = (current_progress, temp_progress) 330 current_progress = temp_progress 331 return progress_value_dict 332 333 334def get_proportion_value_list(file_size_list, distributable_value=100): 335 """ 336 Obtain the calculated progress proportion value list 337 (proportion_value_list). 338 :param file_size_list: file size list 339 :param distributable_value: distributable value 340 :return proportion_value_list: progress proportion value list 341 """ 342 sum_size = sum(file_size_list) 343 getcontext().prec = 2 344 proportion_value_list = [] 345 for each_size_value in file_size_list: 346 proportion = Decimal(str(float(each_size_value))) / Decimal( 347 str(float(sum_size))) 348 proportion_value = int( 349 Decimal(str(proportion)) * 350 Decimal(str(float(distributable_value)))) 351 if proportion_value == 0: 352 proportion_value = 1 353 proportion_value_list.append(proportion_value) 354 return proportion_value_list 355 356 357def adjust_proportion_value_list(proportion_value_list, distributable_value): 358 """ 359 Adjust the calculated progress proportion value list to ensure that 360 sum is equal to distributable_value. 361 :param proportion_value_list: calculated progress proportion value list 362 :param distributable_value: number of distributable progress values 363 :return proportion_value_list: new progress proportion value list 364 """ 365 if len(proportion_value_list) == 0: 366 return [] 367 sum_proportion_value = sum(proportion_value_list) 368 if sum_proportion_value > distributable_value: 369 max_value = max(proportion_value_list) 370 max_idx = proportion_value_list.index(max_value) 371 proportion_value_list[max_idx] = \ 372 max_value - (sum_proportion_value - distributable_value) 373 elif sum_proportion_value < distributable_value: 374 min_value = min(proportion_value_list) 375 min_idx = proportion_value_list.index(min_value) 376 proportion_value_list[min_idx] = \ 377 min_value + (distributable_value - sum_proportion_value) 378 return proportion_value_list 379 380 381def create_script(prelude_script, verse_script, 382 refrain_script, ending_script): 383 """ 384 Generate the script file. 385 :param prelude_script: prelude script 386 :param verse_script: verse script 387 :param refrain_script: refrain script 388 :param ending_script: ending script 389 :return: 390 """ 391 # Generate the prelude script. 392 prelude_script.add_command("\n# ---- prelude ----\n") 393 394 # Get the distribution progress. 395 progress_value_dict = get_progress_value() 396 if progress_value_dict is False: 397 return False 398 verse_script_content_list = verse_script.get_script() 399 updater_content = [] 400 verse_script_content = '\n'.join(verse_script_content_list) 401 402 for key, value in progress_value_dict.items(): 403 show_progress_content = \ 404 verse_script.show_progress((value[1] - value[0]) / 100, 0) 405 verse_script_content = \ 406 re.sub(r'%s_WRITE_FLAG' % key, '%s' % show_progress_content, 407 verse_script_content, count=1) 408 # Generate the verse script. 409 write_script(verse_script_content, 'verse') 410 # Generate the refrain script. 411 refrain_script.add_command("\n# ---- refrain ----\n") 412 # Generate the ending script. 413 ending_script.add_command("\n# ---- ending ----\n") 414 415 generate_total_script() 416