• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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