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