1# Copyright (c) 2013 The Chromium OS Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4"""Code to provide functions for FAFT tests. 5 6These will be exposed via an xmlrpc server running on the DUT. 7 8@note: When adding categories, please also update server/cros/faft/rpc_proxy.pyi 9""" 10 11from __future__ import print_function 12 13import binascii 14from six.moves import http_client as httplib 15import logging 16import os 17import signal 18import tempfile 19from six.moves import xmlrpc_client as xmlrpclib 20 21from autotest_lib.client.common_lib import lsbrelease_utils 22from autotest_lib.client.common_lib.cros import cros_config 23from autotest_lib.client.cros import xmlrpc_server 24from autotest_lib.client.cros.faft.utils import ( 25 cgpt_handler, 26 os_interface, 27 firmware_check_keys, 28 firmware_updater, 29 flashrom_handler, 30 kernel_handler, 31 rootfs_handler, 32 tpm_handler, 33) 34 35 36class FaftXmlRpcDelegate(xmlrpc_server.XmlRpcDelegate): 37 """ 38 A class which routes RPC methods to the proper servicers. 39 40 Firmware tests are able to call an RPC method via: 41 <FAFTClient>.[category].[method_name](params) 42 When XML-RPC is being used, the RPC server routes the called method to: 43 <XmlRpcDelegate>._dispatch('[category].[method_name]', params) 44 The method is then dispatched to a Servicer class. 45 """ 46 47 def __init__(self, os_if): 48 """Initialize the servicer for each category. 49 50 @type os_if: os_interface.OSInterface 51 """ 52 self._ready = False 53 self.bios = BiosServicer(os_if) 54 self.cgpt = CgptServicer(os_if) 55 self.ec = EcServicer(os_if) 56 self.kernel = KernelServicer(os_if) 57 self.minios_kernel = KernelServicer(os_if, is_minios=True) 58 self.rootfs = RootfsServicer(os_if) 59 self.rpc_settings = RpcSettingsServicer(os_if) 60 self.system = SystemServicer(os_if) 61 self.tpm = TpmServicer(os_if) 62 self.updater = UpdaterServicer(os_if) 63 64 self._rpc_servicers = { 65 'bios': self.bios, 66 'cgpt': self.cgpt, 67 'ec': self.ec, 68 'kernel': self.kernel, 69 'minios': self.minios_kernel, 70 'rpc_settings': self.rpc_settings, 71 'rootfs': self.rootfs, 72 'system': self.system, 73 'tpm': self.tpm, 74 'updater': self.updater 75 } 76 77 self._os_if = os_if 78 79 def __enter__(self): 80 """Enter the the delegate context (when XmlRpcServer.run() starts). 81 82 The server is marked ready here, rather than immediately when created. 83 """ 84 logging.debug("%s: Serving FAFT functions", self.__class__.__name__) 85 self._ready = True 86 87 def __exit__(self, exception, value, traceback): 88 """Exit the delegate context (when XmlRpcServer.run() finishes). 89 90 The server is marked not ready, to prevent the client from using 91 the wrong server when quitting one instance and starting another. 92 """ 93 self._ready = False 94 logging.debug("%s: Done.", self.__class__.__name__) 95 96 def quit(self): 97 """Exit the xmlrpc server.""" 98 self._ready = False 99 os.kill(os.getpid(), signal.SIGINT) 100 101 def ready(self): 102 """Is the RPC server ready to serve calls in a useful manner? 103 104 The server is only marked ready during the XmlRpcServer.run() loop. 105 This method suppresses the extra logging of ready() from the superclass. 106 """ 107 return self._ready 108 109 def _report_error(self, fault_code, message): 110 """Raise the given RPC error text. 111 112 @param fault_code: the status code to use 113 @param message: the string message to include before exception text 114 @return the exception to raise 115 116 @type fault_code: int 117 @type message: str 118 @rtype: Exception 119 """ 120 logging.error(message) 121 return xmlrpclib.Fault(fault_code, message) 122 123 def _dispatch(self, called_method, params): 124 """ 125 Send any RPC call to the appropriate servicer method. 126 127 @param called_method: The method of FAFTClient that was called. 128 Should take the form 'category.method'. 129 @param params: The arguments passed into the method. 130 131 @type called_method: str 132 @type params: tuple 133 134 @raise: xmlrpclib.Fault (using http error codes for fault codes) 135 """ 136 logging.info('Called: %s%s', called_method, params) 137 138 name_pieces = called_method.split('.') 139 140 if not name_pieces: 141 raise self._report_error( 142 httplib.BAD_REQUEST, 143 'RPC request is invalid (completely empty): "%s"' % 144 called_method) 145 146 method_name = name_pieces.pop() 147 category = '.'.join(name_pieces) 148 149 if (method_name.startswith('_') 150 and method_name not in ('__str__', '__repr__', '__call__')): 151 # *._private() or *.__special__() 152 # Forbid early, to prevent seeing which methods exist. 153 raise self._report_error( 154 httplib.FORBIDDEN, 155 'RPC method name is private: %s%s[%s]' % 156 (category, '.' if category else '', method_name)) 157 158 elif not method_name: 159 # anything.() 160 raise self._report_error( 161 httplib.BAD_REQUEST, 162 'RPC method name is empty: %s%s[%s]' % 163 (category, '.' if category else '', method_name)) 164 165 if category in self._rpc_servicers: 166 # system.func() 167 holder = self._rpc_servicers[category] 168 if not hasattr(holder, method_name): 169 raise self._report_error( 170 httplib.NOT_FOUND, 171 'RPC method not found: %s.[%s]' % 172 (category, method_name)) 173 174 elif category: 175 # invalid.func() 176 raise self._report_error( 177 httplib.NOT_FOUND, 178 'RPC category not found: [%s].%s' % 179 (category, method_name)) 180 181 else: 182 # .func() or .invalid() 183 holder = self 184 if not hasattr(holder, method_name): 185 raise self._report_error( 186 httplib.NOT_FOUND, 187 'RPC method not found: [%s]' % method_name) 188 189 try: 190 method = getattr(holder, method_name) 191 192 except AttributeError as e: 193 logging.exception(e) 194 raise 195 try: 196 return method(*params) 197 198 except Exception as e: 199 logging.exception(e) 200 raise 201 202 203class BiosServicer(object): 204 """Class to service all BIOS RPCs""" 205 206 def __init__(self, os_if): 207 """ 208 @type os_if: os_interface.OSInterface 209 """ 210 self._os_if = os_if 211 212 # This attribute is accessed via a property, so it can load lazily 213 # when actually used by the test. 214 self._real_bios_handler = flashrom_handler.FlashromHandler( 215 self._os_if, None, '/usr/share/vboot/devkeys', 'bios') 216 217 @property 218 def _bios_handler(self): 219 """Return the BIOS flashrom handler, after initializing it if necessary 220 221 @rtype: flashrom_handler.FlashromHandler 222 """ 223 if not self._real_bios_handler.initialized: 224 self._real_bios_handler.init() 225 return self._real_bios_handler 226 227 def reload(self): 228 """Reload the firmware image that may be changed.""" 229 self._bios_handler.new_image() 230 231 def get_gbb_flags(self): 232 """Get the GBB flags. 233 234 @return: An integer of the GBB flags. 235 """ 236 return self._bios_handler.get_gbb_flags() 237 238 def set_gbb_flags(self, flags): 239 """Set the GBB flags. 240 241 @param flags: An integer of the GBB flags. 242 """ 243 self._bios_handler.set_gbb_flags(flags, write_through=True) 244 245 def get_preamble_flags(self, section): 246 """Get the preamble flags of a firmware section. 247 248 @param section: A firmware section, either 'a' or 'b'. 249 @return: An integer of the preamble flags. 250 """ 251 return self._bios_handler.get_section_flags(section) 252 253 def set_preamble_flags(self, section, flags): 254 """Set the preamble flags of a firmware section. 255 256 @param section: A firmware section, either 'a' or 'b'. 257 @param flags: An integer of preamble flags. 258 """ 259 version = self.get_version(section) 260 self._bios_handler.set_section_version( 261 section, version, flags, write_through=True) 262 263 def get_body_sha(self, section): 264 """Get SHA1 hash of BIOS RW firmware section. 265 266 @param section: A firmware section, either 'a' or 'b'. 267 @return: A string of the body SHA1 hash. 268 """ 269 return self._bios_handler.get_section_sha(section) 270 271 def get_sig_sha(self, section): 272 """Get SHA1 hash of firmware vblock in section. 273 274 @param section: A firmware section, either 'a' or 'b'. 275 @return: A string of the sig SHA1 hash. 276 """ 277 return self._bios_handler.get_section_sig_sha(section) 278 279 def get_section_fwid(self, section=None): 280 """Retrieve the RO or RW fwid. 281 282 @param section: A firmware section, either 'a' or 'b'. 283 @return: A string of the fwid 284 """ 285 return self._bios_handler.get_section_fwid(section) 286 287 def get_sig_one_byte(self, section): 288 """Get a specific byte of firmware signature of the section. 289 290 @param section: A firmware section, either 'a' or 'b'. 291 @return: Tuple of (offset, byte). 292 """ 293 return self._bios_handler.get_firmware_sig_one_byte(section) 294 295 def modify_sig(self, section, offset, value): 296 """Modify a byte of firmware signature of the section. 297 298 @param section: A firmware section, either 'a' or 'b'. 299 @offset: Offset of section to be modified. 300 @value: The byte value. 301 """ 302 return self._bios_handler.modify_firmware_sig(section, offset, value) 303 304 def get_body_one_byte(self, section): 305 """Get a specific byte of firmware body of the section. 306 307 @param section: A firmware section, either 'a' or 'b'. 308 @return: Tuple of (offset, byte). 309 """ 310 return self._bios_handler.get_firmware_body_one_byte(section) 311 312 def modify_body(self, section, offset, value): 313 """Modify a byte of firmware body of the section. 314 315 @param section: A firmware section, either 'a' or 'b'. 316 @offset: Offset of section to be modified. 317 @value: The byte value. 318 """ 319 return self._bios_handler.modify_firmware_body(section, offset, value) 320 321 def corrupt_mrc_cache(self): 322 """Corrupt MRC cache. 323 324 NOTE: This method is not idempotent. A second call will still change the 325 flashrom content of the client. 326 """ 327 self._bios_handler.corrupt_mrc_cache() 328 329 def get_version(self, section): 330 """Retrieve firmware version of a section.""" 331 return self._bios_handler.get_section_version(section) 332 333 def set_version(self, section, version): 334 """Set firmware version of a section.""" 335 flags = self._bios_handler.get_section_flags(section) 336 logging.info('Setting firmware section %s version to %d', section, 337 version) 338 self._bios_handler.set_section_version(section, 339 version, 340 flags, 341 write_through=True) 342 343 def get_datakey_version(self, section): 344 """Return firmware data key version.""" 345 return self._bios_handler.get_section_datakey_version(section) 346 347 def get_kernel_subkey_version(self, section): 348 """Return kernel subkey version.""" 349 return self._bios_handler.get_section_kernel_subkey_version(section) 350 351 def dump_whole(self, bios_path): 352 """Dump the current BIOS firmware to a file, specified by bios_path. 353 354 @param bios_path: The path of the BIOS image to be written. 355 """ 356 self._bios_handler.dump_whole(bios_path) 357 358 def write_whole(self, bios_path): 359 """Write the firmware from bios_path to the current system. 360 361 @param bios_path: The path of the source BIOS image 362 """ 363 self._bios_handler.new_image(bios_path) 364 self._bios_handler.write_whole() 365 366 def strip_modified_fwids(self): 367 """Strip trailing suffixes out of the FWIDs (see modify_image_fwids). 368 369 @return: a dict of any fwids that were adjusted, by section (ro, a, b) 370 @rtype: dict 371 """ 372 return self._bios_handler.strip_modified_fwids() 373 374 def set_write_protect_region(self, region, enabled=None): 375 """Modify software write protect region and flag in one operation. 376 377 @param region: Region to set (usually WP_RO) 378 @param enabled: If True, run --wp-enable; if False, run --wp-disable. 379 If None (default), don't specify either one. 380 """ 381 self._bios_handler.set_write_protect_region(region, enabled) 382 383 def set_write_protect_range(self, start, length, enabled=None): 384 """Modify software write protect range and flag in one operation. 385 386 @param start: offset (bytes) from start of flash to start of range 387 @param length: offset (bytes) from start of range to end of range 388 @param enabled: If True, run --wp-enable; if False, run --wp-disable. 389 If None (default), don't specify either one. 390 """ 391 self._bios_handler.set_write_protect_range(start, length, enabled) 392 393 def get_write_protect_status(self): 394 """Get a dict describing the status of the write protection 395 396 @return: {'enabled': True/False, 'start': '0x0', 'length': '0x0', ...} 397 @rtype: dict 398 """ 399 return self._bios_handler.get_write_protect_status() 400 401 def is_available(self): 402 """Return True if available, False if not.""" 403 # Use the real handler, to avoid .init() raising an exception 404 return self._real_bios_handler.is_available() 405 406 def get_write_cmd(self, image=None): 407 """Get the command needed to write the whole image to the device. 408 409 @param image: the filename (empty to use current handler data) 410 """ 411 if image: 412 # Don't bother loading the usual image, since it's overridden. 413 return self._real_bios_handler.get_write_cmd(image) 414 else: 415 return self._bios_handler.get_write_cmd() 416 417class CgptServicer(object): 418 """Class to service all CGPT RPCs""" 419 420 def __init__(self, os_if): 421 """ 422 @type os_if: os_interface.OSInterface 423 """ 424 self._os_if = os_if 425 self._cgpt_handler = cgpt_handler.CgptHandler(self._os_if) 426 427 def get_attributes(self): 428 """Get kernel attributes.""" 429 rootdev = self._os_if.get_root_dev() 430 self._cgpt_handler.read_device_info(rootdev) 431 return { 432 'A': self._cgpt_handler.get_partition(rootdev, 'KERN-A'), 433 'B': self._cgpt_handler.get_partition(rootdev, 'KERN-B') 434 } 435 436 def set_attributes(self, a=None, b=None): 437 """Set kernel attributes for either partition (or both).""" 438 partitions = {'A': a, 'B': b} 439 rootdev = self._os_if.get_root_dev() 440 modifiable_attributes = list(self._cgpt_handler.ATTR_TO_COMMAND.keys()) 441 for partition_name in partitions.keys(): 442 partition = partitions[partition_name] 443 if partition is None: 444 continue 445 attributes_to_set = { 446 key: partition[key] 447 for key in modifiable_attributes 448 } 449 if attributes_to_set: 450 self._cgpt_handler.set_partition( 451 rootdev, 'KERN-%s' % partition_name, attributes_to_set) 452 453 454class EcServicer(object): 455 """Class to service all EC RPCs""" 456 457 def __init__(self, os_if): 458 """ 459 @type os_if: os_interface.OSInterface 460 """ 461 self._os_if = os_if 462 463 # This attribute is accessed via a property, so it can load lazily 464 # when actually used by the test. 465 self._real_ec_handler = None 466 ec_status = self._os_if.run_shell_command_get_status('mosys ec info') 467 if ec_status == 0: 468 self._real_ec_handler = flashrom_handler.FlashromHandler( 469 self._os_if, 'ec_root_key.vpubk', 470 '/usr/share/vboot/devkeys', 'ec') 471 472 else: 473 logging.info('No EC is reported by mosys (rc=%s).', ec_status) 474 475 @property 476 def _ec_handler(self): 477 """Return the EC flashrom handler, after initializing it if necessary 478 479 @rtype: flashrom_handler.FlashromHandler 480 """ 481 if not self._real_ec_handler: 482 # No EC handler if board has no EC 483 return None 484 485 if not self._real_ec_handler.initialized: 486 self._real_ec_handler.init() 487 return self._real_ec_handler 488 489 def reload(self): 490 """Reload the firmware image that may be changed.""" 491 self._ec_handler.new_image() 492 493 def get_version(self, target=None): 494 """Get the requested EC version. 495 496 @param target: 'ro'/'rw', or None to signify the active fw. 497 On a Wilco EC, this would be ignored, since Wilco 498 doesn't use ro/rw/active versions. 499 @return: A string of the requested EC version, or '' if DUT has no EC. 500 """ 501 CROS_EC_FILE = '/dev/cros_ec' 502 WILCO_VERSION_FILE = '/sys/bus/platform/devices/GOOG000C:00/version' 503 504 # If DUT has a Chrome EC, parse `ectool version` for the target. 505 if self._os_if.path_exists(CROS_EC_FILE): 506 out = self._os_if.run_shell_command_get_output('ectool version') 507 keyvals = dict([line.split(':', 1) for line in out]) 508 ro = keyvals['RO version'].strip() 509 rw = keyvals['RW version'].strip() 510 active = keyvals['Firmware copy'].strip() 511 if target == None: 512 if active == 'RO': 513 return ro 514 elif active == 'RW': 515 return rw 516 raise ValueError( 517 'Unexpected active FW type: want RO/RW; got ' + active) 518 elif target.lower() == 'ro': 519 return ro 520 elif target.lower() == 'rw': 521 return rw 522 raise ValueError( 523 'Invalid EC version target: want ro/rw/None; got ' + 524 target) 525 # If DUT has a Wilco EC read sysfs for the EC version. 526 # Wilco doesn't use RO/RW/active, so ignore target. 527 elif self._os_if.path_exists(WILCO_VERSION_FILE): 528 with open(WILCO_VERSION_FILE, "r") as f: 529 return f.read().strip() 530 # If DUT doesn't have an EC, return the empty string. 531 else: 532 return '' 533 534 def get_active_hash(self): 535 """Get hash of active EC RW firmware.""" 536 return self._os_if.run_shell_command_get_output( 537 'ectool echash | grep hash: | sed "s/hash:\s\+//"')[0] 538 539 def dump_whole(self, ec_path): 540 """Dump the current EC firmware to a file, specified by ec_path. 541 542 @param ec_path: The path of the EC image to be written. 543 """ 544 self._ec_handler.dump_whole(ec_path) 545 546 def write_whole(self, ec_path): 547 """Write the firmware from ec_path to the current system. 548 549 @param ec_path: The path of the source EC image. 550 """ 551 self._ec_handler.new_image(ec_path) 552 self._ec_handler.write_whole() 553 554 def corrupt_body(self, section): 555 """Corrupt the requested EC section body. 556 557 NOTE: This method is not idempotent. A second call will still change the 558 flashrom content of the client. 559 560 @param section: An EC section, either 'a' or 'b'. 561 """ 562 self._ec_handler.corrupt_firmware_body(section) 563 564 def dump_firmware(self, ec_path): 565 """Dump the current EC firmware to a file, specified by ec_path. 566 567 @param ec_path: The path of the EC image to be written. 568 """ 569 self._ec_handler.dump_whole(ec_path) 570 571 def set_write_protect(self, enable): 572 """Enable write protect of the EC flash chip. 573 574 @param enable: True if activating EC write protect. Otherwise, False. 575 """ 576 if enable: 577 self._ec_handler.enable_write_protect() 578 else: 579 self._ec_handler.disable_write_protect() 580 581 def get_write_protect_status(self): 582 """Get a dict describing the status of the write protection 583 584 @return: {'enabled': True/False, 'start': '0x0', 'length': '0x0', ...} 585 @rtype: dict 586 """ 587 logging.debug("Calling self._ec_handler.get_write_protect_status") 588 rec = self._ec_handler.get_write_protect_status() 589 logging.debug("Returning %s", rec) 590 return rec 591 592 def is_efs(self): 593 """Return True if the EC supports EFS.""" 594 return self._ec_handler.has_section_body('rw_b') 595 596 def copy_rw(self, from_section, to_section): 597 """Copy EC RW from from_section to to_section.""" 598 self._ec_handler.copy_from_to(from_section, to_section) 599 600 def reboot_to_switch_slot(self): 601 """Reboot EC to switch the active RW slot.""" 602 self._os_if.run_shell_command( 603 'ectool reboot_ec cold switch-slot', modifies_device=True) 604 605 def strip_modified_fwids(self): 606 """Strip trailing suffixes out of the FWIDs (see modify_image_fwids).""" 607 return self._ec_handler.strip_modified_fwids() 608 609 def get_write_cmd(self, image=None): 610 """Get the command needed to write the whole image to the device. 611 612 @param image: the filename (empty to use current handler data) 613 """ 614 if image: 615 # Don't bother loading the usual image, since it's overridden. 616 return self._real_ec_handler.get_write_cmd(image) 617 else: 618 return self._ec_handler.get_write_cmd() 619 620 621class KernelServicer(object): 622 """Class to service all Kernel RPCs""" 623 624 def __init__(self, os_if, is_minios=False): 625 """ 626 @type os_if: os_interface.OSInterface 627 @type is_minios: True if it is a MiniOS kernel; otherwise, False. 628 """ 629 self._os_if = os_if 630 self._real_kernel_handler = kernel_handler.KernelHandler( 631 self._os_if, is_minios) 632 633 @property 634 def _kernel_handler(self): 635 """Return the kernel handler, after initializing it if necessary 636 637 @rtype: kernel_handler.KernelHandler 638 """ 639 if not self._real_kernel_handler.initialized: 640 self._real_kernel_handler.init( 641 dev_key_path='/usr/share/vboot/devkeys', 642 internal_disk=True) 643 return self._real_kernel_handler 644 645 def corrupt_sig(self, section): 646 """Corrupt the requested kernel section. 647 648 @param section: A kernel section, either 'a' or 'b'. 649 """ 650 self._kernel_handler.corrupt_kernel(section) 651 652 def restore_sig(self, section): 653 """Restore the requested kernel section (previously corrupted). 654 655 @param section: A kernel section, either 'a' or 'b'. 656 """ 657 self._kernel_handler.restore_kernel(section) 658 659 def _modify_version(self, section, delta): 660 """Modify kernel version for the requested section, by adding delta. 661 662 The passed in delta, a positive or a negative number, is added to the 663 original kernel version. 664 """ 665 original_version = self._kernel_handler.get_version(section) 666 new_version = original_version + delta 667 logging.info('Setting kernel section %s version from %d to %d', 668 section, original_version, new_version) 669 self._kernel_handler.set_version(section, new_version) 670 671 def move_version_backward(self, section): 672 """Decrement kernel version for the requested section.""" 673 self._modify_version(section, -1) 674 675 def move_version_forward(self, section): 676 """Increase kernel version for the requested section.""" 677 self._modify_version(section, 1) 678 679 def get_version(self, section): 680 """Return kernel version.""" 681 return self._kernel_handler.get_version(section) 682 683 def get_datakey_version(self, section): 684 """Return kernel datakey version.""" 685 return self._kernel_handler.get_datakey_version(section) 686 687 def diff_a_b(self): 688 """Compare kernel A with B. 689 690 @return: True: if kernel A is different with B. 691 False: if kernel A is the same as B. 692 """ 693 rootdev = self._os_if.get_root_dev() 694 kernel_a = self._os_if.join_part(rootdev, '2') 695 kernel_b = self._os_if.join_part(rootdev, '4') 696 697 # The signature (some kind of hash) for the kernel body is stored in 698 # the beginning. So compare the first 64KB (including header, preamble, 699 # and signature) should be enough to check them identical. 700 header_a = self._os_if.read_partition(kernel_a, 0x10000) 701 header_b = self._os_if.read_partition(kernel_b, 0x10000) 702 703 return header_a != header_b 704 705 def resign_with_keys(self, section, key_path=None): 706 """Resign kernel with temporary key.""" 707 self._kernel_handler.resign_kernel(section, key_path) 708 709 def dump(self, section, kernel_path): 710 """Dump the specified kernel to a file. 711 712 @param section: The kernel to dump. May be A or B. 713 @param kernel_path: The path to the kernel image to be written. 714 """ 715 self._kernel_handler.dump_kernel(section, kernel_path) 716 717 def write(self, section, kernel_path): 718 """Write a kernel image to the specified section. 719 720 @param section: The kernel to dump. May be A or B. 721 @param kernel_path: The path to the kernel image. 722 """ 723 self._kernel_handler.write_kernel(section, kernel_path) 724 725 def get_sha(self, section): 726 """Return the SHA1 hash of the specified kernel section.""" 727 return self._kernel_handler.get_sha(section) 728 729 730class RootfsServicer(object): 731 """Class to service all RootFS RPCs""" 732 733 def __init__(self, os_if): 734 """ 735 @type os_if: os_interface.OSInterface 736 """ 737 self._os_if = os_if 738 self._real_rootfs_handler = rootfs_handler.RootfsHandler(self._os_if) 739 740 @property 741 def _rootfs_handler(self): 742 """Return the rootfs handler, after initializing it if necessary 743 744 @rtype: rootfs_handler.RootfsHandler 745 """ 746 if not self._real_rootfs_handler.initialized: 747 self._real_rootfs_handler.init() 748 return self._real_rootfs_handler 749 750 def verify_rootfs(self, section): 751 """Verifies the integrity of the root FS. 752 753 @param section: The rootfs to verify. May be A or B. 754 """ 755 return self._rootfs_handler.verify_rootfs(section) 756 757 758class RpcSettingsServicer(object): 759 """Class to service RPCs for settings of the RPC server itself""" 760 761 def __init__(self, os_if): 762 """ 763 @type os_if: os_interface.OSInterface 764 """ 765 self._os_if = os_if 766 767 def enable_test_mode(self): 768 """Enable test mode (avoids writing to flash or gpt)""" 769 self._os_if.test_mode = True 770 771 def disable_test_mode(self): 772 """Disable test mode and return to normal operation""" 773 self._os_if.test_mode = False 774 775 776class SystemServicer(object): 777 """Class to service all System RPCs""" 778 779 def __init__(self, os_if): 780 """ 781 @type os_if: os_interface.OSInterface 782 """ 783 self._os_if = os_if 784 self._key_checker = firmware_check_keys.firmwareCheckKeys() 785 786 def is_available(self): 787 """Function for polling the RPC server availability. 788 789 @return: Always True. 790 """ 791 return True 792 793 def run_shell_command(self, command, block=True): 794 """Run shell command. 795 796 @param command: A shell command to be run. 797 @param block: if True (default), wait for command to finish 798 """ 799 self._os_if.run_shell_command(command, block=block) 800 801 def run_shell_command_check_output(self, command, success_token): 802 """Run shell command and check its stdout for a string. 803 804 @param command: A shell command to be run. 805 @param success_token: A string to search the output for. 806 @return: A Boolean indicating whether the success_token was found in 807 the command output. 808 """ 809 return self._os_if.run_shell_command_check_output( 810 command, success_token) 811 812 def run_shell_command_get_output(self, command, include_stderr=False): 813 """Run shell command and get its console output. 814 815 @param command: A shell command to be run. 816 @return: A list of strings stripped of the newline characters. 817 """ 818 return self._os_if.run_shell_command_get_output(command, include_stderr) 819 820 def run_shell_command_get_status(self, command): 821 """Run shell command and get its console status. 822 823 @param command: A shell command to be run. 824 @return: The returncode of the process 825 @rtype: int 826 """ 827 return self._os_if.run_shell_command_get_status(command) 828 829 def get_platform_name(self): 830 """Get the platform name of the current system. 831 832 @return: A string of the platform name. 833 """ 834 return lsbrelease_utils.get_current_board() 835 836 def get_model_name(self): 837 """Get the model name of the current system. 838 839 @return: A string of the model name. 840 """ 841 model = cros_config.call_cros_config_get_output( 842 '/ name', self._os_if.run_shell_command_get_result) 843 if not model: 844 raise Exception('Failed getting model name from cros_config') 845 return model 846 847 def dev_tpm_present(self): 848 """Check if /dev/tpm0 is present. 849 850 @return: Boolean true or false. 851 """ 852 return os.path.exists('/dev/tpm0') 853 854 def get_crossystem_value(self, key): 855 """Get crossystem value of the requested key. 856 857 @param key: A crossystem key. 858 @return: A string of the requested crossystem value. 859 """ 860 return self._os_if.run_shell_command_get_output( 861 'crossystem %s' % key)[0] 862 863 def get_boot_mode(self): 864 """Get the current firmware boot mode. 865 866 @return: Either 'normal', 'dev', or 'rec'. 867 @raise: ValueError if mainfw_type and devsw_boot do not correspond to 868 an expected boot mode combination. 869 """ 870 mainfw_type = self._os_if.cs.mainfw_type 871 devsw_boot = self._os_if.cs.devsw_boot 872 if mainfw_type == 'normal' and devsw_boot == '0': 873 return 'normal' 874 elif mainfw_type == 'developer' and devsw_boot == '1': 875 return 'dev' 876 elif mainfw_type == 'recovery': 877 return 'rec' 878 else: 879 raise ValueError('Unexpected mainfw_type/devsw_boot combination: ' 880 'mainfw_type=%s, devsw_boot=%s' % 881 (mainfw_type, devsw_boot)) 882 883 def get_root_dev(self): 884 """Get the name of root device without partition number. 885 886 @return: A string of the root device without partition number. 887 """ 888 return self._os_if.get_root_dev() 889 890 def get_root_part(self): 891 """Get the name of root device with partition number. 892 893 @return: A string of the root device with partition number. 894 """ 895 return self._os_if.get_root_part() 896 897 def set_try_fw_b(self, count=1): 898 """Set 'Try Firmware B' flag in crossystem. 899 900 @param count: # times to try booting into FW B 901 """ 902 self._os_if.cs.fwb_tries = count 903 904 def set_fw_try_next(self, next, count=0): 905 """Set fw_try_next to A or B. 906 907 @param next: Next FW to reboot to (A or B) 908 @param count: # of times to try booting into FW <next> 909 """ 910 self._os_if.cs.fw_try_next = next 911 if count: 912 self._os_if.cs.fw_try_count = count 913 914 def get_minios_priority(self): 915 """Get minios_priority value, which denotes the minios image to try 916 first. (A or B) 917 918 @return: 'A' or 'B' 919 """ 920 return self._os_if.cs.minios_priority 921 922 def set_minios_priority(self, priority): 923 """Set minios_priority to A or B. 924 925 @param priority: MiniOS partition to try first (A or B) 926 """ 927 self._os_if.cs.minios_priority = priority 928 929 def get_fw_vboot2(self): 930 """Get fw_vboot2.""" 931 try: 932 return self._os_if.cs.fw_vboot2 == '1' 933 except os_interface.OSInterfaceError: 934 return False 935 936 def request_recovery_boot(self): 937 """Request running in recovery mode on the restart.""" 938 self._os_if.cs.request_recovery() 939 940 def get_dev_boot_usb(self): 941 """Get dev_boot_usb value which controls developer mode boot from USB. 942 943 @return: True if enable, False if disable. 944 """ 945 return self._os_if.cs.dev_boot_usb == '1' 946 947 def set_dev_boot_usb(self, value): 948 """Set dev_boot_usb value which controls developer mode boot from USB. 949 950 @param value: True to enable, False to disable. 951 """ 952 self._os_if.cs.dev_boot_usb = 1 if value else 0 953 954 def get_dev_default_boot(self): 955 """Get dev_default_boot value, which selects the default boot device. 956 957 @return: 'disk' or 'usb' or 'legacy' 958 """ 959 return self._os_if.cs.dev_default_boot 960 961 def set_dev_default_boot(self, device='disk'): 962 """Set dev_default_boot value, which selects the default boot device. 963 964 @param device: 'disk' or 'usb' or 'legacy' (default: 'disk') 965 """ 966 self._os_if.cs.dev_default_boot = device 967 968 def is_removable_device_boot(self): 969 """Check the current boot device is removable. 970 971 @return: True: if a removable device boots. 972 False: if a non-removable device boots. 973 """ 974 root_part = self._os_if.get_root_part() 975 return self._os_if.is_removable_device(root_part) 976 977 def get_internal_device(self): 978 """Get the internal disk by given the current disk.""" 979 root_part = self._os_if.get_root_part() 980 return self._os_if.get_internal_disk(root_part) 981 982 def create_temp_dir(self, prefix='backup_', dir=None): 983 """Create a temporary directory and return the path.""" 984 return tempfile.mkdtemp(prefix=prefix, dir=dir) 985 986 def remove_file(self, file_path): 987 """Remove the file.""" 988 return self._os_if.remove_file(file_path) 989 990 def remove_dir(self, dir_path): 991 """Remove the directory.""" 992 return self._os_if.remove_dir(dir_path) 993 994 def check_keys(self, expected_sequence): 995 """Check the keys sequence was as expected. 996 997 @param expected_sequence: A list of expected key sequences. 998 """ 999 return self._key_checker.check_keys(expected_sequence) 1000 1001 1002class TpmServicer(object): 1003 """Class to service all TPM RPCs""" 1004 1005 def __init__(self, os_if): 1006 """ 1007 @type os_if: os_interface.OSInterface 1008 """ 1009 self._os_if = os_if 1010 1011 # This attribute is accessed via a property, so it can load lazily 1012 # when actually used by the test. 1013 self._real_tpm_handler = tpm_handler.TpmHandler(self._os_if) 1014 1015 @property 1016 def _tpm_handler(self): 1017 """Handler for the TPM 1018 1019 @rtype: tpm_handler.TpmHandler 1020 """ 1021 if not self._real_tpm_handler.initialized: 1022 self._real_tpm_handler.init() 1023 return self._real_tpm_handler 1024 1025 def get_firmware_version(self): 1026 """Retrieve tpm firmware body version.""" 1027 return self._tpm_handler.get_fw_version() 1028 1029 def get_firmware_datakey_version(self): 1030 """Retrieve tpm firmware data key version.""" 1031 return self._tpm_handler.get_fw_key_version() 1032 1033 def get_kernel_version(self): 1034 """Retrieve tpm kernel body version.""" 1035 return self._tpm_handler.get_kernel_version() 1036 1037 def get_kernel_datakey_version(self): 1038 """Retrieve tpm kernel data key version.""" 1039 return self._tpm_handler.get_kernel_key_version() 1040 1041 def get_tpm_version(self): 1042 """Returns '1.2' or '2.0' as a string.""" 1043 # tpmc can return this without stopping daemons, so access real handler. 1044 return self._real_tpm_handler.get_tpm_version() 1045 1046 def stop_daemon(self): 1047 """Stop tpm related daemon.""" 1048 return self._tpm_handler.stop_daemon() 1049 1050 def restart_daemon(self): 1051 """Restart tpm related daemon which was stopped by stop_daemon().""" 1052 return self._tpm_handler.restart_daemon() 1053 1054 1055class UpdaterServicer(object): 1056 """Class to service all Updater RPCs""" 1057 1058 def __init__(self, os_if): 1059 """ 1060 @type os_if: os_interface.OSInterface 1061 """ 1062 self._os_if = os_if 1063 self._real_updater = firmware_updater.FirmwareUpdater(self._os_if) 1064 1065 @property 1066 def _updater(self): 1067 """Handler for the updater 1068 1069 @rtype: firmware_updater.FirmwareUpdater 1070 """ 1071 if not self._real_updater.initialized: 1072 self._real_updater.init() 1073 return self._real_updater 1074 1075 def cleanup(self): 1076 """Clean up the temporary directory""" 1077 # Use the updater directly, to avoid initializing it just to clean it up 1078 self._real_updater.cleanup_temp_dir() 1079 1080 def stop_daemon(self): 1081 """Stop update-engine daemon.""" 1082 return self._real_updater.stop_daemon() 1083 1084 def start_daemon(self): 1085 """Start update-engine daemon.""" 1086 return self._real_updater.start_daemon() 1087 1088 def get_section_fwid(self, target='bios', section=None): 1089 """Retrieve shellball's RW or RO fwid.""" 1090 return self._updater.get_section_fwid(target, section) 1091 1092 def get_device_fwids(self, target='bios'): 1093 """Retrieve flash device's fwids for the target.""" 1094 return self._updater.get_device_fwids(target) 1095 1096 def get_image_fwids(self, target='bios', filename=None): 1097 """Retrieve image file's fwids for the target.""" 1098 return self._updater.get_image_fwids(target, filename) 1099 1100 def modify_image_fwids(self, target='bios', sections=None): 1101 """Modify the fwid in the image, but don't flash it.""" 1102 return self._updater.modify_image_fwids(target, sections) 1103 1104 def modify_ecid_and_flash_to_bios(self): 1105 """Modify ecid, put it to AP firmware, and flash it to the system.""" 1106 self._updater.modify_ecid_and_flash_to_bios() 1107 1108 def corrupt_diagnostics_image(self, local_filename): 1109 """Corrupts a diagnostics image in the CBFS working directory. 1110 1111 @param local_filename: Filename for storing the diagnostics image in the 1112 CBFS working directory 1113 """ 1114 self._updater.corrupt_diagnostics_image(local_filename) 1115 1116 def get_ec_hash(self): 1117 """Return the hex string of the EC hash.""" 1118 blob = self._updater.get_ec_hash() 1119 # Format it to a hex string 1120 return binascii.hexlify(blob) 1121 1122 def resign_firmware(self, version): 1123 """Resign firmware with version. 1124 1125 @param version: new version number. 1126 """ 1127 self._updater.resign_firmware(version) 1128 1129 def extract_shellball(self, append=None): 1130 """Extract shellball with the given append suffix. 1131 1132 @param append: use for the shellball name. 1133 """ 1134 return self._updater.extract_shellball(append) 1135 1136 def repack_shellball(self, append=None): 1137 """Repack shellball with new fwid. 1138 1139 @param append: use for the shellball name. 1140 """ 1141 return self._updater.repack_shellball(append) 1142 1143 def reset_shellball(self): 1144 """Revert to the stock shellball""" 1145 self._updater.reset_shellball() 1146 1147 def reload_images(self): 1148 """Reload handlers from the on-disk images, in case they've changed.""" 1149 self._updater.reload_images() 1150 1151 def get_firmwareupdate_command(self, mode, append=None, options=()): 1152 """Get the command needed to run updater with the given options. 1153 1154 The client should run it via ssh, in case the update resets USB network. 1155 1156 @param mode: mode for the updater 1157 @param append: extra string appended to shellball filename to run 1158 @param options: options for chromeos-firmwareupdate 1159 @return: returncode of the updater 1160 @rtype: str 1161 """ 1162 return self._updater.get_firmwareupdate_command(mode, append, options) 1163 1164 def run_firmwareupdate(self, mode, append=None, options=()): 1165 """Run updater with the given options 1166 1167 @param mode: mode for the updater 1168 @param append: extra string appended to shellball filename to run 1169 @param options: options for chromeos-firmwareupdate 1170 @return: returncode of the updater 1171 @rtype: int 1172 """ 1173 return self._updater.run_firmwareupdate(mode, append, options) 1174 1175 def run_autoupdate(self, append): 1176 """Run chromeos-firmwareupdate with autoupdate mode.""" 1177 options = ['--noupdate_ec', '--wp=1'] 1178 self._updater.run_firmwareupdate( 1179 mode='autoupdate', append=append, options=options) 1180 1181 def run_factory_install(self): 1182 """Run chromeos-firmwareupdate with factory_install mode.""" 1183 options = ['--noupdate_ec', '--wp=0'] 1184 self._updater.run_firmwareupdate( 1185 mode='factory_install', options=options) 1186 1187 def run_bootok(self, append): 1188 """Run chromeos-firmwareupdate with bootok mode.""" 1189 self._updater.run_firmwareupdate(mode='bootok', append=append) 1190 1191 def run_recovery(self): 1192 """Run chromeos-firmwareupdate with recovery mode.""" 1193 options = ['--noupdate_ec', '--nocheck_keys', '--force', '--wp=1'] 1194 self._updater.run_firmwareupdate(mode='recovery', options=options) 1195 1196 def cbfs_setup_work_dir(self): 1197 """Sets up cbfstool work directory.""" 1198 return self._updater.cbfs_setup_work_dir() 1199 1200 def cbfs_extract_chip(self, 1201 fw_name, 1202 extension='.bin', 1203 hash_extension='.hash'): 1204 """Runs cbfstool to extract chip firmware. 1205 1206 @param fw_name: Name of chip firmware to extract. 1207 @return: Boolean success status. 1208 """ 1209 return self._updater.cbfs_extract_chip(fw_name, extension, 1210 hash_extension) 1211 1212 def cbfs_extract_diagnostics(self, diag_name, local_filename): 1213 """Runs cbfstool to extract a diagnostics image. 1214 1215 @param diag_name: Name of the diagnostics image in CBFS 1216 @param local_filename: Filename for storing the diagnostics image in the 1217 CBFS working directory 1218 """ 1219 self._updater.cbfs_extract_diagnostics(diag_name, local_filename) 1220 1221 def cbfs_replace_diagnostics(self, diag_name, local_filename): 1222 """Runs cbfstool to replace a diagnostics image in the firmware image. 1223 1224 @param diag_name: Name of the diagnostics image in CBFS 1225 @param local_filename: Filename for storing the diagnostics image in the 1226 CBFS working directory 1227 """ 1228 self._updater.cbfs_replace_diagnostics(diag_name, local_filename) 1229 1230 def cbfs_get_chip_hash(self, fw_name, hash_extension='.hash'): 1231 """Gets the chip firmware hash blob. 1232 1233 The hash data is returned as a list of stringified two-byte pieces: 1234 \x12\x34...\xab\xcd\xef -> ['0x12', '0x34', ..., '0xab', '0xcd', '0xef'] 1235 1236 @param fw_name: Name of chip firmware whose hash blob to return. 1237 @return: Hex string of hash blob. 1238 """ 1239 return self._updater.cbfs_get_chip_hash(fw_name, hash_extension) 1240 1241 def cbfs_replace_chip(self, 1242 fw_name, 1243 extension='.bin', 1244 hash_extension='.hash', 1245 regions=('a', 'b')): 1246 """Runs cbfstool to replace chip firmware. 1247 1248 @param fw_name: Name of chip firmware to extract. 1249 @return: Boolean success status. 1250 """ 1251 return self._updater.cbfs_replace_chip(fw_name, extension, 1252 hash_extension, regions) 1253 1254 def cbfs_sign_and_flash(self): 1255 """Runs cbfs signer and flash it. 1256 1257 @param fw_name: Name of chip firmware to extract. 1258 @return: Boolean success status. 1259 """ 1260 return self._updater.cbfs_sign_and_flash() 1261 1262 def cbfs_extract(self, 1263 filename, 1264 extension, 1265 regions, 1266 local_filename=None, 1267 arch=None, 1268 bios=None): 1269 """Extracts an arbitrary file from cbfs. 1270 1271 Note that extracting from 1272 @param filename: Filename in cbfs, including extension 1273 @param extension: Extension of the file, including '.' 1274 @param regions: Tuple of regions (the default is just 'a') 1275 @param arch: Specific machine architecture to extract (default unset) 1276 @param local_filename: Path to use on the DUT, overriding the default in 1277 the cbfs work dir. 1278 @param bios: Image from which the cbfs file to be extracted 1279 @return: The full path of the extracted file, or None 1280 """ 1281 return self._updater.cbfs_extract(filename, 1282 extension, regions, 1283 local_filename, 1284 arch, 1285 bios) 1286 1287 def get_temp_path(self): 1288 """Get updater's temp directory path.""" 1289 return self._updater.get_temp_path() 1290 1291 def get_keys_path(self): 1292 """Get updater's keys directory path.""" 1293 return self._updater.get_keys_path() 1294 1295 def get_work_path(self): 1296 """Get updater's work directory path.""" 1297 return self._updater.get_work_path() 1298 1299 def get_bios_relative_path(self): 1300 """Gets the relative path of the bios image in the shellball.""" 1301 return self._updater.get_bios_relative_path() 1302 1303 def get_ec_relative_path(self): 1304 """Gets the relative path of the ec image in the shellball.""" 1305 return self._updater.get_ec_relative_path() 1306 1307 def copy_bios(self, filename): 1308 """Make a copy of the shellball bios.bin""" 1309 return self._updater.copy_bios(filename) 1310 1311 def get_image_gbb_flags(self, filename=None): 1312 """Get the GBB flags in the given image (shellball image if unspecified) 1313 1314 @param filename: the image path to act on (None to use shellball image) 1315 @return: An integer of the GBB flags. 1316 """ 1317 return self._updater.get_image_gbb_flags(filename) 1318 1319 def set_image_gbb_flags(self, flags, filename=None): 1320 """Set the GBB flags in the given image (shellball image if unspecified) 1321 1322 @param flags: the flags to set 1323 @param filename: the image path to act on (None to use shellball image) 1324 1325 @type flags: int 1326 @type filename: str | None 1327 """ 1328 return self._updater.set_image_gbb_flags(flags, filename) 1329