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 TWO_STEP 31from utils import TOTAL_SCRIPT_FILE_NAME 32from utils import SCRIPT_FILE_NAME 33from utils import SCRIPT_KEY_LIST 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 236class RefrainScript(Script): 237 def __init__(self): 238 super().__init__() 239 240 241class EndingScript(Script): 242 def __init__(self): 243 super().__init__() 244 245 246def write_script(script_content, opera_name): 247 """ 248 Generate the {opera}script. 249 :param script_content: script content 250 :param opera_name: Opera phase names corresponding to the script content 251 'prelude', 'verse', 'refrain', and 'ending'. 252 :return: 253 """ 254 script_file = tempfile.NamedTemporaryFile(mode='w+') 255 script_file.write(script_content) 256 script_file.seek(0) 257 script_file_name = ''.join([opera_name.title(), SCRIPT_FILE_NAME]) 258 OPTIONS_MANAGER.opera_script_file_name_dict[opera_name].\ 259 append((script_file_name, script_file)) 260 UPDATE_LOGGER.print_log("%s generation complete!" % script_file_name) 261 262 263def generate_total_script(): 264 """ 265 Generate the overall script. 266 """ 267 content_list = [] 268 for each_key, each_value in \ 269 OPTIONS_MANAGER.opera_script_file_name_dict.items(): 270 for each in each_value: 271 each_content = "LoadScript(\"%s\", %s);" % \ 272 (each[0], SCRIPT_KEY_LIST.index(each_key)) 273 content_list.append(each_content) 274 script_total = tempfile.NamedTemporaryFile(mode='w+') 275 script_total.write('\n'.join(content_list)) 276 script_total.seek(0) 277 OPTIONS_MANAGER.total_script_file_obj = script_total 278 UPDATE_LOGGER.print_log("%s generation complete!" % TOTAL_SCRIPT_FILE_NAME) 279 280 281def get_progress_value(distributable_value=100): 282 """ 283 Allocate a progress value to each image update. 284 :param distributable_value: distributable value 285 :return: 286 """ 287 progress_value_dict = {} 288 full_img_list = OPTIONS_MANAGER.full_img_list 289 incremental_img_list = OPTIONS_MANAGER.incremental_img_list 290 file_size_list = [] 291 if len(full_img_list) == 0 and len(incremental_img_list) == 0: 292 UPDATE_LOGGER.print_log( 293 "get progress value failed! > getting progress value failed!", 294 UPDATE_LOGGER.ERROR_LOG) 295 return False 296 for idx, _ in enumerate(incremental_img_list): 297 # Obtain the size of the incremental image file. 298 if OPTIONS_MANAGER.two_step and incremental_img_list[idx] == TWO_STEP: 299 # Updater images are not involved in progress calculation. 300 incremental_img_list.remove(TWO_STEP) 301 continue 302 file_obj = OPTIONS_MANAGER.incremental_image_file_obj_list[idx] 303 each_img_size = os.path.getsize(file_obj.name) 304 file_size_list.append(each_img_size) 305 306 for idx, _ in enumerate(full_img_list): 307 # Obtain the size of the full image file. 308 if OPTIONS_MANAGER.two_step and full_img_list[idx] == TWO_STEP: 309 # Updater images are not involved in progress calculation. 310 continue 311 file_obj = OPTIONS_MANAGER.full_image_file_obj_list[idx] 312 each_img_size = os.path.getsize(file_obj.name) 313 file_size_list.append(each_img_size) 314 if OPTIONS_MANAGER.two_step and TWO_STEP in full_img_list: 315 full_img_list.remove(TWO_STEP) 316 317 proportion_value_list = get_proportion_value_list( 318 file_size_list, distributable_value=distributable_value) 319 320 adjusted_proportion_value_list = adjust_proportion_value_list( 321 proportion_value_list, distributable_value) 322 323 all_img_list = incremental_img_list + full_img_list 324 current_progress = 40 325 for idx, each_img in enumerate(all_img_list): 326 temp_progress = current_progress + adjusted_proportion_value_list[idx] 327 progress_value_dict[each_img] = (current_progress, temp_progress) 328 current_progress = temp_progress 329 return progress_value_dict 330 331 332def get_proportion_value_list(file_size_list, distributable_value=100): 333 """ 334 Obtain the calculated progress proportion value list 335 (proportion_value_list). 336 :param file_size_list: file size list 337 :param distributable_value: distributable value 338 :return proportion_value_list: progress proportion value list 339 """ 340 sum_size = sum(file_size_list) 341 getcontext().prec = 2 342 proportion_value_list = [] 343 for each_size_value in file_size_list: 344 proportion = Decimal(str(float(each_size_value))) / Decimal( 345 str(float(sum_size))) 346 proportion_value = int( 347 Decimal(str(proportion)) * 348 Decimal(str(float(distributable_value)))) 349 if proportion_value == 0: 350 proportion_value = 1 351 proportion_value_list.append(proportion_value) 352 return proportion_value_list 353 354 355def adjust_proportion_value_list(proportion_value_list, distributable_value): 356 """ 357 Adjust the calculated progress proportion value list to ensure that 358 sum is equal to distributable_value. 359 :param proportion_value_list: calculated progress proportion value list 360 :param distributable_value: number of distributable progress values 361 :return proportion_value_list: new progress proportion value list 362 """ 363 if len(proportion_value_list) == 0: 364 return [] 365 sum_proportion_value = sum(proportion_value_list) 366 if sum_proportion_value > distributable_value: 367 max_value = max(proportion_value_list) 368 max_idx = proportion_value_list.index(max_value) 369 proportion_value_list[max_idx] = \ 370 max_value - (sum_proportion_value - distributable_value) 371 elif sum_proportion_value < distributable_value: 372 min_value = min(proportion_value_list) 373 min_idx = proportion_value_list.index(min_value) 374 proportion_value_list[min_idx] = \ 375 min_value + (distributable_value - sum_proportion_value) 376 return proportion_value_list 377 378 379def create_script(prelude_script, verse_script, 380 refrain_script, ending_script): 381 """ 382 Generate the script file. 383 :param prelude_script: prelude script 384 :param verse_script: verse script 385 :param refrain_script: refrain script 386 :param ending_script: ending script 387 :return: 388 """ 389 # Generate the prelude script. 390 prelude_script.add_command("\n# ---- prelude ----\n") 391 392 # Get the distribution progress. 393 progress_value_dict = get_progress_value() 394 if progress_value_dict is False: 395 return False 396 verse_script_content_list = verse_script.get_script() 397 updater_content = [] 398 if OPTIONS_MANAGER.two_step: 399 for idx, each_cmd in enumerate(verse_script_content_list[1:]): 400 if "/%s" % TWO_STEP in each_cmd: 401 updater_content.append(each_cmd) 402 each_cmd = \ 403 '\n'.join( 404 [' %s' % each for each in each_cmd.split('\n')]) 405 verse_script_content_list[0] = \ 406 verse_script_content_list[0].replace( 407 "UPDATER_WRITE_FLAG", 408 "%s\nUPDATER_WRITE_FLAG" % each_cmd) 409 verse_script_content_list[0] = \ 410 verse_script_content_list[0].replace("UPDATER_WRITE_FLAG", "") 411 verse_script_content_list[0] = \ 412 verse_script_content_list[0].replace("updater_WRITE_FLAG", "") 413 for each in updater_content: 414 verse_script_content_list.remove(each) 415 verse_script_content = '\n'.join(verse_script_content_list[1:]) 416 else: 417 verse_script_content = '\n'.join(verse_script_content_list) 418 419 for key, value in progress_value_dict.items(): 420 show_progress_content = \ 421 verse_script.show_progress((value[1] - value[0]) / 100, 0) 422 verse_script_content = \ 423 re.sub(r'%s_WRITE_FLAG' % key, '%s' % show_progress_content, 424 verse_script_content, count=1) 425 if OPTIONS_MANAGER.two_step: 426 verse_script_content = '\n'.join( 427 [' %s' % each for each in verse_script_content.split('\n')]) 428 verse_script_content = verse_script_content_list[0].replace( 429 "ALL_WRITE_FLAG", verse_script_content) 430 # Generate the verse script. 431 write_script(verse_script_content, 'verse') 432 # Generate the refrain script. 433 refrain_script.add_command("\n# ---- refrain ----\n") 434 # Generate the ending script. 435 ending_script.add_command("\n# ---- ending ----\n") 436 437 generate_total_script() 438